Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion rester/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
from . import apirunner
from . import apirunner
1 change: 1 addition & 0 deletions rester/examples/prevoty/test_suite.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
{
"py/object":"rester.TestSuite",
"test_cases":["tests/weather.json"]
}
106 changes: 58 additions & 48 deletions rester/examples/prevoty/tests/xss.json
Original file line number Diff line number Diff line change
@@ -1,52 +1,62 @@
{
"name":"Prevoty XSS",
"globals":{
"variables":{
"baseApiUrl":"https://api.prevoty.com",
"api_key":"82ddb7e2-883c-4290-83f7-d15a8ba4aa7e",
"rule_key":"108ee29a-a2cb-49ec-b053-2e82f198ce08",
"expected_value":2
}
},
"testSteps":[
{
"name":"Prevoty Key Verification",
"dumpResponse":true,
"skip":false,
"apiUrl":"{baseApiUrl}/1/key/verify?api_key={api_key}",
"assertMap": {
"payLoad":{
"verified":true,
"message":"api_key is valid"
}
"py/object":"rester.loader.TestCase",
"py/state": {
"name": "Prevoty XSS",
"variables": {
"py/object": "rester.manifest.Variables",
"variables": {
"baseApiUrl": "https://api.prevoty.com",
"api_key": "82ddb7e2-883c-4290-83f7-d15a8ba4aa7e",
"rule_key": "108ee29a-a2cb-49ec-b053-2e82f198ce08",
"expected_value": 2
}
},
"testSteps": [
{
"py/object": "rester.loader.TestStep",
"py/state": {
"name": "Prevoty Key Verification",
"dumpResponse": true,
"skip": false,
"apiUrl": "{baseApiUrl}/1/key/verify?api_key={api_key}",
"assertMap": {
"payLoad": {
"verified": true,
"message": "api_key is valid"
}
}
}
},
{
"name":"XSS Basic test",
"apiUrl":"{baseApiUrl}/1/xss/filter",
"headers":{
"content-type":"application/json;"
},
"method":"post",
"params":{
"api_key":"{api_key}",
"input":"<script>Hello World!</script>",
"rule_key":"{rule_key}"
},
"assertMap":{
"headers":{
"content-type":"application/json; charset=utf-8"
},
"payLoad":{
"message":"",
"output":"Hello World!",
"output":"String",
"statistics.javascript_tags":"-ge {expected_value}",
"statistics.transformations":"int",
"statistics.transformations":"-gt 3",
"statistics.prevoty_link_metadata":"Object"
}
},
{
"py/object": "rester.loader.TestStep",
"py/state": {
"name": "XSS Basic test",
"apiUrl": "{baseApiUrl}/1/xss/filter",
"headers": {
"content-type": "application/json;"
},
"method": "post",
"params": {
"api_key": "{api_key}",
"input": "<script>Hello World!</script>",
"rule_key": "{rule_key}"
},
"assertMap": {
"headers": {
"content-type": "application/json; charset=utf-8"
},
"payLoad": {
"message": "",
"output": "Hello World!",
"output": "String",
"statistics.javascript_tags": "-ge {expected_value}",
"statistics.transformations": "int",
"statistics.transformations": "-gt 3",
"statistics.prevoty_link_metadata": "Object"
}
}
}
}
]
}
]
}
}
8 changes: 4 additions & 4 deletions rester/exc.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def __call__(self):

for step in self.case.steps:
self.logger.debug('Test Step Name : %s', step.name)
if step.get('skip', False):
if step.skip:
self.logger.info('\n=======> Skipping test case : ' + step.name)
self.skipped.append(step)
continue
Expand Down Expand Up @@ -61,7 +61,7 @@ def _format_logs(self, lc):
def _build_param_dict(self, test_step):
params = {}
if hasattr(test_step, 'params') and test_step.params is not None:
for key, value in test_step.params.items().items():
for key, value in test_step.params.iteritems():
params[key] = self.case.variables.expand(value)
return params

Expand All @@ -77,7 +77,7 @@ def _execute_test_step(self, test_step):
headers = {}
if hasattr(test_step, 'headers') and test_step.headers is not None:
self.logger.debug('Found Headers')
for key, value in test_step.headers.items().items():
for key, value in test_step.headers.iteritems():
headers[key] = self.case.variables.expand(value)

# process and set up params
Expand Down Expand Up @@ -139,7 +139,7 @@ def _assert_element_list(self, section, failures, test_step, response, assert_li
if value in json_types:
self.logger.info('Found json type : %s ', value)

if type(json_eval_expr) == DictWrapper:
if type(json_eval_expr) == dict:
value = 'Object'
json_eval_expr = {}

Expand Down
87 changes: 69 additions & 18 deletions rester/loader.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import jsonpickle

from rester.struct import DictWrapper
import json
import os
Expand All @@ -23,38 +25,87 @@ def _load(self, data):
filename = os.path.join(os.path.dirname(self.filename), case)
self.test_cases.append(TestCase(self, filename))

def __getstate__(self):
state = self.__dict__.copy()
# del elements of state you don't want to pickle here
return state

def __setstate__(self, state):
"""
behavior for unpickling. setting default values here
:param state:
:return:
"""
# Manipulate state here before assigning it to our instance

self.__dict__.update(state)


class TestCase(object):
def __init__(self, suite, filename):
self.filename = filename
self.data = None
def __init__(self, suite):
"""
generate a TestCase
:param suite: none if not part of a suite.
"""
self.variables = Variables()
if suite:
self.variables.update(suite.variables)
self.load()

def __getattr__(self, key):
return getattr(self.data, key)
self.testSteps = []

@property
def steps(self):
return self.data.testSteps
return self.testSteps

@property
def request_opts(self):
return self.variables.get('request_opts', {})

def load(self):
with open(self.filename) as fh:
data = load(self.filename, fh)
self._load(data)
def __getstate__(self):
state = self.__dict__.copy()
# del elements of state you don't want to pickle here
return state

def _load(self, data):
self.data = DictWrapper(data)
self.variables.update(data.get('globals', {}).get('variables', {}).items())
def __setstate__(self, state):
"""
behavior for unpickling. setting default values here
:param state:
:return:
"""
# Manipulate state here before assigning it to our instance

self.__dict__.update(state)


class TestStep(object):
def __init__(self):
"""
Initializing a default TestStep. Not used usually but here in case we need a default object to serialize.
:return:
"""
self.name = ""
self.skip = False
self.apiUrl = ""
self.dumpResponse = True
self.assertMap = {}
self.headers = {}
self.params = {}
self.method = "get"

def __getstate__(self):
state = self.__dict__.copy()
# del elements of state you don't want to pickle here
return state

def __setstate__(self, state):
"""
behavior for unpickling. setting default values here
:param state:
:return:
"""
# Manipulate state here before assigning it to our instance
if not hasattr(state, 'skip'):
state['skip'] = False

self.__dict__.update(state)

def load(filename, fh):
if filename.endswith(".yaml"):
return yaml.load(fh.read())
return json.load(fh)
12 changes: 6 additions & 6 deletions rester/manifest.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,31 +13,31 @@ class Variables(object):
_pattern = re.compile(r'\{(\w+)\}')

def __init__(self, variables=None):
self._variables = variables or {}
self.variables = variables or {}

def __iter__(self):
for k, v in self._variables.iteritems():
for k, v in self.variables.iteritems():
yield k, v

def get(self, k, default):
return self._variables.get(k, default)
return self.variables.get(k, default)

def update(self, values):
for k, v in values:
self.add_variable(k, v)

def add_variable(self, key, value):
if self._variables.get(key, ''):
if self.variables.get(key, ''):
self.logger.warn('WARN!!! Variable : %s Already defined!!!', key)
self._variables[key] = self.expand(value)
self.variables[key] = self.expand(value)

def expand(self, expression):
"""Expands logical constructions."""
self.logger.debug("expand : expression %s", str(expression))
if not is_string(expression):
return expression

result = self._pattern.sub(lambda var: str(self._variables[var.group(1)]), expression)
result = self._pattern.sub(lambda var: str(self.variables[var.group(1)]), expression)

result = result.strip()
self.logger.debug('expand : %s - result : %s', expression, result)
Expand Down
10 changes: 9 additions & 1 deletion rester/testcase.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from rester.http import HttpClient
from rester.loader import TestSuite, TestCase
import yaml
import jsonpickle

class bcolors:
HEADER = '\033[95m'
Expand All @@ -26,7 +27,14 @@ def run_test_suite(self, test_suite_file_name):
self._run_case(test_case)

def run_test_case(self, test_case_file):
case = TestCase(None, test_case_file)
tc = TestCase(None)
tcenc = jsonpickle.encode(tc)
with open(test_case_file) as fh:
if test_case_file.endswith(".yaml"):
# TODO : probably need to replace that with a custom backend for json pickle since YAML is JSON
return yaml.load(fh.read())
case = jsonpickle.decode(fh.read())
case.filename = test_case_file
self._run_case(case)

def _run_case(self, case):
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,5 @@
test_suite="test",
description='Rest API Testing',
long_description=open('README.md').read(),
install_requires=["requests", "testfixtures", "PyYAML>=3.9"],
install_requires=["requests", "testfixtures", "PyYAML>=3.9", "jsonpickle"],
)