Skip to content

Commit e881ee6

Browse files
authored
Merge pull request #15 from abhijo89-to/v2
Updated more testcase and coverage
2 parents 2149359 + dfd4566 commit e881ee6

10 files changed

+257
-148
lines changed

py3resttest/constants.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import operator
22
import re
3-
from enum import Enum, unique
3+
from enum import Enum
44

55
import pycurl
66

@@ -62,6 +62,7 @@ class TestCaseKeywords:
6262
generator_binds = 'generator_binds'
6363
validators = 'validators'
6464
options = 'options'
65+
global_env = 'global_env'
6566

6667

6768
class EnumHttpMethod(Enum):

py3resttest/ext/validator_jsonschema.py

+1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ def validate(self, body=None, headers=None, context=None):
3333
trace = traceback.format_exc()
3434
return Failure(message="Invalid response json body", details=trace, validator=self,
3535
failure_type=FAILURE_VALIDATOR_EXCEPTION)
36+
3637
def get_readable_config(self, context=None):
3738
return "JSON schema validation"
3839

py3resttest/generators.py

+1
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ def seq_generator():
9090
i = 0
9191
while True:
9292
yield my_list[i]
93+
i += 1
9394
if i == len(my_list):
9495
i = 0
9596

py3resttest/testcase.py

+104-24
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import traceback
77
from io import BytesIO
88
from pathlib import Path
9-
from typing import List, Dict
9+
from typing import List, Dict, Optional
1010
from urllib.parse import urljoin
1111

1212
import pycurl
@@ -18,6 +18,7 @@
1818
)
1919
from py3resttest.contenthandling import ContentHandler
2020
from py3resttest.exception import HttpMethodError, BindError, ValidatorError
21+
from py3resttest.generators import parse_generator
2122
from py3resttest.utils import read_testcase_file, ChangeDir, Parser
2223
from py3resttest.validators import parse_extractor, parse_validator, Failure
2324

@@ -28,18 +29,41 @@ class TestCaseConfig:
2829

2930
def __init__(self):
3031
self.__variable_binds_dict = {}
32+
self.timeout = 60
33+
self.print_bodies = False
34+
self.retries = 0
35+
self.generators = {}
3136

3237
@property
33-
def variable_binds_dict(self):
38+
def variable_binds(self):
3439
return self.__variable_binds_dict
3540

36-
@variable_binds_dict.setter
37-
def variable_binds_dict(self, variable_dict):
41+
@variable_binds.setter
42+
def variable_binds(self, variable_dict):
3843
if isinstance(variable_dict, dict):
39-
self.__variable_binds_dict.update(variable_dict)
44+
self.__variable_binds_dict.update(Parser.flatten_dictionaries(variable_dict))
4045

4146
def parse(self, config_node):
42-
return
47+
node = Parser.flatten_lowercase_keys_dict(config_node)
48+
49+
for key, value in node.items():
50+
if key == 'timeout':
51+
self.timeout = int(value)
52+
elif key == u'print_bodies':
53+
self.print_bodies = Parser.safe_to_bool(value)
54+
elif key == 'retries':
55+
self.retries = int(value)
56+
elif key == 'variable_binds':
57+
self.variable_binds = value
58+
elif key == u'generators':
59+
if not isinstance(value, list):
60+
raise TypeError("generators in config should defined as list(array).")
61+
flat = Parser.flatten_dictionaries(value)
62+
gen_dict = {}
63+
for generator_name, generator_config in flat.items():
64+
gen = parse_generator(generator_config)
65+
gen_dict[str(generator_name)] = gen
66+
self.generators = gen_dict
4367

4468
def __str__(self):
4569
return json.dumps(self, default=Parser.safe_to_json)
@@ -53,20 +77,20 @@ def __init__(self):
5377
self.__context = Context()
5478
self.__extract_binds = {}
5579
self.__variable_binds = {}
80+
self.config = TestCaseConfig()
5681

5782
def parse(self, base_url: str, testcase_list: List, test_file=None, working_directory=None, variable_dict=None):
5883

59-
testcase_config = TestCaseConfig()
60-
6184
if working_directory is None:
6285
working_directory = os.path.abspath(os.getcwd())
6386
else:
6487
working_directory = Path(working_directory)
6588
if variable_dict is None:
66-
testcase_config.variable_binds = variable_dict
89+
self.config.variable_binds = variable_dict
6790
if test_file:
6891
self.__testcase_file.add(test_file)
6992

93+
testcase_config_object = TestCaseConfig()
7094
for testcase_node in testcase_list:
7195
if not isinstance(testcase_node, dict):
7296
logger.warning("Skipping the configuration %s" % testcase_node)
@@ -99,11 +123,12 @@ def parse(self, base_url: str, testcase_list: List, test_file=None, working_dire
99123
try:
100124
group_object = TestSet.test_group_list_dict[__group_name]
101125
except KeyError:
102-
group_object = TestCaseGroup(TestCaseGroup.DEFAULT_GROUP)
126+
group_object = TestCaseGroup(TestCaseGroup.DEFAULT_GROUP, config=testcase_config_object)
103127
TestSet.test_group_list_dict[__group_name] = group_object
104128
testcase_object = TestCase(
105129
base_url=base_url, extract_binds=group_object.extract_binds,
106-
variable_binds=group_object.variable_binds, context=group_object.context
130+
variable_binds=group_object.variable_binds, context=group_object.context,
131+
config=group_object.config
107132
)
108133
testcase_object.url = testcase_node[key]
109134
group_object.testcase_list = testcase_object
@@ -112,45 +137,49 @@ def parse(self, base_url: str, testcase_list: List, test_file=None, working_dire
112137
with ChangeDir(working_directory):
113138
__group_name = None
114139
for node_dict in sub_testcase_node:
115-
__group_name = node_dict.get(TestCaseKeywords.group)
116-
if __group_name:
140+
if __group_name is None:
141+
__group_name = node_dict.get(TestCaseKeywords.group)
117142
break
143+
118144
__group_name = __group_name if __group_name else TestCaseGroup.DEFAULT_GROUP
119145
try:
120146
group_object = TestSet.test_group_list_dict[__group_name]
121147
except KeyError:
122-
group_object = TestCaseGroup(TestCaseGroup.DEFAULT_GROUP)
148+
group_object = TestCaseGroup(TestCaseGroup.DEFAULT_GROUP, config=testcase_config_object)
123149
TestSet.test_group_list_dict[__group_name] = group_object
124150

125151
testcase_object = TestCase(
126152
base_url=base_url, extract_binds=group_object.extract_binds,
127-
variable_binds=group_object.variable_binds, context=group_object.context
153+
variable_binds=group_object.variable_binds, context=group_object.context,
154+
config=group_object.config
128155
)
129156
testcase_object.parse(sub_testcase_node)
130157
group_object.testcase_list = testcase_object
131158

132-
elif key == YamlKeyWords.BENCHMARK:
133-
...
134-
135159
elif key == YamlKeyWords.CONFIG:
136-
testcase_config_object = TestCaseConfig()
137160
testcase_config_object.parse(sub_testcase_node)
138161

162+
self.config = testcase_config_object
163+
139164
return
140165

141166

142167
class TestCaseGroup:
143168
DEFAULT_GROUP = "NO GROUP"
144169

145-
def __init__(self, name, context=None, extract_binds=None, variable_binds=None):
170+
def __init__(self, name, context=None, extract_binds=None, variable_binds=None, config=None):
146171
self.__testcase_list = []
147172
self.__benchmark_list = []
148173
self.__config = None
174+
149175
self.__name = name
150176
self.__testcase_file = set()
151177
self.__context = context if context else Context()
152178
self.__extract_binds = extract_binds if extract_binds else {}
153179
self.__variable_binds = variable_binds if variable_binds else {}
180+
self.__is_global = None
181+
182+
self.config = config
154183

155184
@property
156185
def testcase_list(self):
@@ -160,6 +189,15 @@ def testcase_list(self):
160189
def testcase_list(self, testcase_object):
161190
self.__testcase_list.append(testcase_object)
162191

192+
@property
193+
def is_global(self):
194+
return self.__is_global
195+
196+
@is_global.setter
197+
def is_global(self, val):
198+
if self.__is_global is None:
199+
self.__is_global = val
200+
163201
@property
164202
def benchmark_list(self):
165203
return self.__benchmark_list
@@ -172,15 +210,34 @@ def benchmark_list(self, benchmark_objet):
172210
def extract_binds(self):
173211
return self.__extract_binds
174212

213+
@extract_binds.setter
214+
def extract_binds(self, extract_dict):
215+
self.__extract_binds.update(extract_dict)
216+
175217
@property
176218
def variable_binds(self):
177219
return self.__variable_binds
178220

221+
@variable_binds.setter
222+
def variable_binds(self, var_dict):
223+
if isinstance(var_dict, dict):
224+
self.__variable_binds.update(var_dict)
225+
226+
@property
227+
def config(self):
228+
return self.__config
229+
230+
@config.setter
231+
def config(self, config_obj: TestCaseConfig):
232+
self.__config = config_obj
233+
self.variable_binds = config_obj.variable_binds
234+
179235
@property
180236
def context(self):
181237
return self.__context
182238

183239

240+
184241
class TestResult:
185242

186243
def __init__(self, body, status_code):
@@ -213,10 +270,11 @@ class TestCase:
213270

214271
KEYWORD_DICT = {k: v for k, v in TestCaseKeywords.__dict__.items() if not k.startswith('__')}
215272

216-
def __init__(self, base_url, extract_binds, variable_binds, context=None):
273+
def __init__(self, base_url, extract_binds, variable_binds, context=None, config=None):
217274
self.__base_url = base_url
218275
self.__url = None
219276
self.__body = None
277+
self.__config = config if config else TestCaseConfig()
220278
self.__auth_username = None
221279
self.__auth_password = None
222280
self.__delay = 0
@@ -245,10 +303,21 @@ def __init__(self, base_url, extract_binds, variable_binds, context=None):
245303

246304
self.templates = {}
247305
self.result = None
306+
self.config = config
248307

249308
def __str__(self):
250309
return json.dumps(self, default=Parser.safe_to_json)
251310

311+
@property
312+
def config(self) -> Optional[TestCaseConfig]:
313+
return self.__config
314+
315+
@config.setter
316+
def config(self, config_object: TestCaseConfig):
317+
if config_object:
318+
self.variable_binds.update(config_object.variable_binds)
319+
self.generator_binds.update(config_object.generators)
320+
252321
@property
253322
def auth_username(self):
254323
return self.__auth_username
@@ -311,6 +380,10 @@ def url(self, value):
311380
def generator_binds(self):
312381
return self.__generator_binds_dict
313382

383+
@property
384+
def delay(self):
385+
return self.__delay
386+
314387
@generator_binds.setter
315388
def generator_binds(self, value: Dict):
316389
binds_dict = Parser.flatten_dictionaries(value)
@@ -367,12 +440,17 @@ def headers(self) -> Dict:
367440
header_dict = {}
368441
for key, header in self.__header_dict.items():
369442
if isinstance(header, dict):
443+
if key == 'template':
444+
for k, v in header.items():
445+
templated_string = string.Template(v).safe_substitute(context_values)
446+
header_dict[k] = templated_string
447+
continue
370448
templated_value = header.get('template')
371449
if templated_value:
372450
templated_string = string.Template(templated_value).safe_substitute(context_values)
373451
header_dict[key] = templated_string
374452
else:
375-
header_dict[key] = header
453+
logger.warning("Skipping the header: %s. We don't support mapping as header" % header)
376454
else:
377455
header_dict[key] = header
378456

@@ -419,8 +497,7 @@ def realize_template(self, variable_name, context):
419497
return None
420498
if not context.get_values():
421499
return None
422-
423-
val = self.templates[variable_name].substitute(context.get_values())
500+
val = self.templates[variable_name].safe_substitute(context.get_values())
424501
return val
425502

426503
def parse(self, testcase_dict):
@@ -543,6 +620,8 @@ def run(self, context=None, timeout=None, curl_handler=None):
543620
curl_handler.setopt(pycurl.WRITEFUNCTION, body_byte.write)
544621
curl_handler.setopt(pycurl.HEADERFUNCTION, header_byte.write)
545622
curl_handler.setopt(pycurl.VERBOSE, self.__verbose)
623+
if self.config.timeout:
624+
curl_handler.setopt(pycurl.CONNECTTIMEOUT, self.config.timeout)
546625

547626
if self.__ssl_insecure:
548627
curl_handler.setopt(pycurl.SSL_VERIFYPEER, 0)
@@ -641,3 +720,4 @@ def run(self, context=None, timeout=None, curl_handler=None):
641720
self.__failure_list.append(
642721
Failure(message=failure_message, details=None, failure_type=FAILURE_INVALID_RESPONSE)
643722
)
723+
curl_handler.close()

py3resttest/validators.py

+7-2
Original file line numberDiff line numberDiff line change
@@ -267,8 +267,13 @@ def validate(self, body=None, headers=None, context=None):
267267

268268
if not comparison:
269269
failure = Failure(validator=self)
270-
failure.message = "Comparison failed, evaluating {0}({1}, {2}) returned False".format(
271-
self.comparator_name, extracted_val, expected_val)
270+
if self.comparator_name in ("count_eq", "length_eq"): # Thanks @KellyBennett
271+
272+
failure.message = "Comparison failed, evaluating {0}({1}, {2}) returned False".format(
273+
self.comparator_name, extracted_val, len(expected_val))
274+
else:
275+
failure.message = "Comparison failed, evaluating {0}({1}, {2}) returned False".format(
276+
self.comparator_name, extracted_val, expected_val)
272277
failure.details = self.get_readable_config(context=context)
273278
failure.failure_type = FAILURE_VALIDATOR_FAILED
274279
return failure

requirements.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@ pyyaml==5.3.1
33
pycurl==7.43.0.6
44
jsonpath==0.82
55
jmespath==0.10.0
6-
jsonschema==3.2.0
6+
jsonschema==3.2.0

setup.py

+4-5
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,17 @@
11
from setuptools import setup, find_packages
2-
import py3resttest
3-
with open(u"requirements.txt") as fp:
2+
with open("requirements.txt") as fp:
43
install_requires = [lib_str.strip() for lib_str in fp.read().split("\n") if not lib_str.startswith("#")]
54

6-
with open(u"requirements.txt") as fp:
5+
with open("requirements.txt") as fp:
76
test_dependencies = [lib_str.strip() for lib_str in fp.read().split("\n") if not lib_str.startswith("#")]
87

98
setup(
109
name='resttest3',
11-
version=py3resttest.__version__,
10+
version="1.0.1",
1211
description='Python RESTful API Testing & Micro benchmarking Tool',
1312
long_description='Python RESTful API Testing & Microbenchmarking Tool '
1413
'\n Documentation at https://abhijo89-to.github.io/py3resttest/',
15-
author=py3resttest.__author__,
14+
author="Abhilash Joseph C",
1615
author_email='[email protected]',
1716
url='https://github.com/abhijo89-to/py3resttest',
1817
keywords=['rest', 'web', 'http', 'testing', 'api'],

0 commit comments

Comments
 (0)