def test_test_content_templating(self): context = Context() test = TestCase('', None, None, context) handler = ContentHandler() handler.is_template_content = True handler.content = '{"first_name": "Gaius","id": "$id","last_name": "Baltar","login": "******"}' context.bind_variables({'id': 9, 'login': '******'}) test.body = handler test.pre_update(context=context) self.assertEqual( string.Template(handler.content).safe_substitute( context.get_values()), test.body)
def test_variables(self): """ Test bind/return of variables """ context = Context() context.variables = {} self.assertTrue(context.get_value('foo') is None) self.assertEqual(0, context.mod_count) context.bind_variable('foo', 'bar') self.assertEqual('bar', context.get_value('foo')) self.assertEqual('bar', context.get_values()['foo']) self.assertEqual(1, context.mod_count) context.bind_variable('foo', 'bar2') self.assertEqual('bar2', context.get_value('foo')) self.assertEqual(2, context.mod_count)
class TestCase: DEFAULT_NAME = "NO NAME" KEYWORD_DICT = {k: v for k, v in TestCaseKeywords.__dict__.items() if not k.startswith('__')} def __init__(self, base_url, extract_binds, variable_binds, context=None, config=None): self.__base_url = base_url self.__url = None self.__body = None self.__config = config if config else TestCaseConfig() self.__auth_username = None self.__auth_password = None self.__delay = 0 self.__verbose = False self.__ssl_insecure = False self.__response_headers = None self.__response_code = None self.__passed = False self.__failure_list = [] self.__abs_url = False self.__header_dict = {} self.__http_method = EnumHttpMethod.GET.name self.__group = TestCaseGroup.DEFAULT_GROUP self.__name = TestCase.DEFAULT_NAME self._should_stop_on_failure = False self._test_run_delay = 0 self._auth_type = AuthType.BASIC self._curl_options = None self.__variable_binds_dict = variable_binds if variable_binds else {} self.__generator_binds_dict = {} self.__extract_binds_dict = extract_binds if extract_binds else {} self.__validator_list = [] self.__expected_http_status_code_list = [200] self.__context = Context() if context is None else context self.templates = {} self.result = None self.config = config def __str__(self): return json.dumps(self, default=Parser.safe_to_json) @property def config(self) -> Optional[TestCaseConfig]: return self.__config @config.setter def config(self, config_object: TestCaseConfig): if config_object: self.variable_binds.update(config_object.variable_binds) self.generator_binds.update(config_object.generators) @property def auth_username(self): return self.__auth_username @auth_username.setter def auth_username(self, username): self.__auth_username = Parser.coerce_string_to_ascii(username) @property def ssl_insecure(self): return self.__ssl_insecure @ssl_insecure.setter def ssl_insecure(self, val): self.__ssl_insecure = bool(val) @property def auth_password(self): return self.__auth_password @auth_password.setter def auth_password(self, password): self.__auth_password = Parser.coerce_string_to_ascii(password) @property def http_method(self): return self.__http_method @http_method.setter def http_method(self, method: str): __method = ["GET", "PUT", "POST", "DELETE", "PATCH"] if method.upper() not in __method: raise HttpMethodError("Method %s is not supported." % method) self.__http_method = method.upper() @property def name(self): return self.__name @property def group(self): return self.__group @property def is_passed(self): return bool(self.__passed) @property def url(self): val = self.realize_template("url", self.__context) if val is None: val = self.__url if not self.__abs_url: val = urljoin(self.__base_url, val) if isinstance(val, dict): logger.warning("URL is not applied template values.") return val @url.setter def url(self, value): if isinstance(value, dict): # this is the templated url , we need to convert it into actual URL template_str = Parser.lowercase_keys(value)['template'] self.set_template("url", Parser.coerce_to_string(template_str)) self.__url = value else: self.__url = value @property def generator_binds(self): return self.__generator_binds_dict @property def delay(self): return self.__delay @generator_binds.setter def generator_binds(self, value: Dict): binds_dict = Parser.flatten_dictionaries(value) __binds_dict = {str(k): str(v) for k, v in binds_dict.items()} self.__generator_binds_dict.update(__binds_dict) @property def variable_binds(self): return self.__variable_binds_dict @property def extract_binds(self): return self.__extract_binds_dict @extract_binds.setter def extract_binds(self, bind_dict): bind_dict = Parser.flatten_dictionaries(bind_dict) for variable_name, extractor in bind_dict.items(): if not isinstance(extractor, dict) or len(extractor) == 0: raise BindError("Extractors must be defined as maps of extractorType:{configs} with 1 entry") if len(extractor) > 1: raise BindError("Cannot define multiple extractors for given variable name") for extractor_type, extractor_config in extractor.items(): self.__extract_binds_dict[variable_name] = parse_extractor(extractor_type, extractor_config) @property def expected_http_status_code_list(self): return [int(x) for x in self.__expected_http_status_code_list] @expected_http_status_code_list.setter def expected_http_status_code_list(self, value): self.__expected_http_status_code_list = value @property def validators(self): return self.__validator_list @validators.setter def validators(self, validator_list): if not isinstance(validator_list, list): raise ValidatorError('Misconfigured validator section, must be a list of validators') for validator in validator_list: if not isinstance(validator, dict): raise ValidatorError("Validators must be defined as validatorType:{configs} ") for validator_type, validator_config in validator.items(): validator = parse_validator(validator_type, validator_config) self.__validator_list.append(validator) @property def headers(self) -> Dict: # if not self.templates.get('headers'): # return self.__header_dict context_values = self.__context.get_values() header_dict = {} for key, header in self.__header_dict.items(): if isinstance(header, dict): if key == 'template': for k, v in header.items(): templated_string = string.Template(v).safe_substitute(context_values) header_dict[k] = templated_string continue templated_value = header.get('template') if templated_value: templated_string = string.Template(templated_value).safe_substitute(context_values) header_dict[key] = templated_string else: logger.warning("Skipping the header: %s. We don't support mapping as header" % header) else: header_dict[key] = header return header_dict @headers.setter def headers(self, headers): config_value = Parser.flatten_dictionaries(headers) if isinstance(config_value, dict): for key, value in config_value.items(): if isinstance(value, dict): if value.get('template'): self.set_template("headers", value.get('template')) self.__header_dict.update(config_value) else: raise ValidatorError("Illegal header type: headers must be a dictionary or list of dictionary keys") def set_template(self, variable_name, template_string): self.templates[variable_name] = string.Template(str(template_string)) @property def body(self): if isinstance(self.__body, str) or self.__body is None: return self.__body return self.__body.get_content(context=self.__context) @body.setter def body(self, value): if value: if isinstance(value, bytes): self.__body = ContentHandler.parse_content(value.decode()) elif isinstance(value, str): self.__body = ContentHandler.parse_content(value) else: self.__body = value else: self.__body = value @property def failures(self): return self.__failure_list def realize_template(self, variable_name, context): if (context or self.templates) is None or (variable_name not in self.templates): return None if not context.get_values(): return None val = self.templates[variable_name].safe_substitute(context.get_values()) return val def parse(self, testcase_dict): testcase_dict = Parser.flatten_lowercase_keys_dict(testcase_dict) for keyword in TestCase.KEYWORD_DICT.keys(): value = testcase_dict.get(keyword) if value is None: continue if keyword == TestCaseKeywords.auth_username: self.auth_username = value elif keyword == TestCaseKeywords.auth_password: self.auth_password = value elif keyword == TestCaseKeywords.method: self.http_method = value elif keyword == TestCaseKeywords.delay: self.__delay = int(value) elif keyword == TestCaseKeywords.group: self.__group = value elif keyword == TestCaseKeywords.name: self.__name = value elif keyword == TestCaseKeywords.url: self.url = value elif keyword == TestCaseKeywords.extract_binds: self.extract_binds = value elif keyword == TestCaseKeywords.validators: self.validators = value elif keyword == TestCaseKeywords.headers: self.headers = value elif keyword == TestCaseKeywords.variable_binds: self.__variable_binds_dict = Parser.flatten_dictionaries(value) elif keyword == TestCaseKeywords.generator_binds: self.__generator_binds_dict = {str(k): str(v) for k, v in Parser.flatten_dictionaries(value)} elif keyword == TestCaseKeywords.options: raise NotImplementedError("Yet to Support") elif keyword == TestCaseKeywords.body: self.body = value elif keyword == TestCaseKeywords.absolute_urls: self.__abs_url = Parser.safe_to_bool(value) expected_status = testcase_dict.get(TestCaseKeywords.expected_status, []) if expected_status: self.expected_http_status_code_list = expected_status else: if self.http_method in ["POST", "PUT", "DELETE"]: self.expected_http_status_code_list = [200, 201, 204] def pre_update(self, context): if self.variable_binds: context.bind_variables(self.variable_binds) if self.generator_binds: for key, value in self.generator_binds.items(): context.bind_generator_next(key, value) def post_update(self, context): if self.extract_binds: for key, value in self.extract_binds.items(): result = value.extract( body=self.body, headers=self.headers, context=context) if result: context.bind_variable(key, result) def is_dynamic(self): if self.templates or (isinstance(self.__body, ContentHandler) and self.__body.is_dynamic()): return True return False def render(self): if self.is_dynamic() or self.__context is not None: if isinstance(self.__body, ContentHandler): self.__body = self.__body.get_content(self.__context) def __perform_validation(self) -> List: failure_list = [] for validator in self.validators: logger.debug("Running validator: %s" % validator.name) validate_result = validator.validate(body=self.body, headers=self.headers, context=self.__context) if not validate_result: self.__passed = False if hasattr(validate_result, 'details'): failure_list.append(validate_result) return failure_list def run(self, context=None, timeout=None, curl_handler=None): if context is None: context = self.__context self.pre_update(context) self.render() if timeout is None: timeout = DEFAULT_TIMEOUT if curl_handler: try: # Check the curl handle isn't closed, and reuse it if possible curl_handler.getinfo(curl_handler.HTTP_CODE) # Below clears the cookies & curl options for clean run # But retains the DNS cache and connection pool curl_handler.reset() curl_handler.setopt(curl_handler.COOKIELIST, "ALL") except pycurl.error: curl_handler = pycurl.Curl() curl_handler.setopt(pycurl.CAINFO, certifi.where()) # Fix for #29 curl_handler.setopt(pycurl.FOLLOWLOCATION, 1) # Support for HTTP 301 else: curl_handler = pycurl.Curl() body_byte, header_byte = self.__default_curl_config(curl_handler, timeout) if self.config.timeout: curl_handler.setopt(pycurl.CONNECTTIMEOUT, self.config.timeout) if self.__ssl_insecure: curl_handler.setopt(pycurl.SSL_VERIFYPEER, 0) curl_handler.setopt(pycurl.SSL_VERIFYHOST, 0) if self.body: logger.debug("Request body %s" % self.body) curl_handler.setopt(curl_handler.READFUNCTION, BytesIO(bytes(self.body, 'utf-8')).read) if self.auth_username and self.auth_password: curl_handler.setopt(pycurl.USERPWD, self.auth_username + ':' + self.auth_password) self.__configure_curl_method(curl_handler) head = self.headers self.__configure_curl_headers(curl_handler, head) if self.__delay: time.sleep(self.__delay) try: logger.info("Hitting %s" % self.url) curl_handler.perform() except pycurl.error as e: logger.error("Unknown Exception", exc_info=True) self.__passed = False curl_handler.close() trace = traceback.format_exc() self.__failure_list.append( Failure(message="Curl Exception: {0}".format(e), details=trace, failure_type=FAILURE_CURL_EXCEPTION)) return self.body = body_byte.getvalue() body_byte.close() response_code = curl_handler.getinfo(pycurl.RESPONSE_CODE) self.__response_code = int(response_code) if self.config.print_bodies: print(self.body) try: response_headers = Parser.parse_headers(header_byte.getvalue()) self.__response_headers = response_headers logger.debug("RESPONSE HEADERS: %s" % self.__response_headers) header_byte.close() except Exception as e: # Need to catch the expected exception trace = traceback.format_exc() self.__failure_list.append(Failure( message="Header parsing exception: {0}".format(e), details=trace, failure_type=FAILURE_TEST_EXCEPTION) ) self.__passed = False curl_handler.close() return if self.__response_code in self.expected_http_status_code_list: self.__passed = True self.__failure_list.extend(self.__perform_validation()) self.post_update(context) else: self.__passed = False failure_message = "Invalid HTTP response code: response code {0} not in expected codes {1}".format( self.__response_code, self.expected_http_status_code_list ) self.__failure_list.append( Failure(message=failure_message, details=None, failure_type=FAILURE_INVALID_RESPONSE) ) curl_handler.close() @staticmethod def __configure_curl_headers(curl_handler, head): if head.get('content-type'): content_type = head['content-type'] head[u'content-type'] = '%s ; charset=UTF-8' % content_type headers = ["%s:%s" % (header_name, header_value) for header_name, header_value in head.items()] headers.append("Expect:") headers.append("Connection: close") logger.debug("Request headers %s " % head) curl_handler.setopt(curl_handler.HTTPHEADER, headers) def __configure_curl_method(self, curl_handler): body_length = len(self.body) if self.body else 0 if self.http_method == EnumHttpMethod.POST.name: curl_handler.setopt(EnumHttpMethod.POST.value, 1) curl_handler.setopt(pycurl.POSTFIELDSIZE, body_length) elif self.http_method == EnumHttpMethod.PUT.name: curl_handler.setopt(EnumHttpMethod.PUT.value, 1) curl_handler.setopt(pycurl.INFILESIZE, body_length) elif self.http_method == EnumHttpMethod.PATCH.name: curl_handler.setopt(EnumHttpMethod.PATCH.value, EnumHttpMethod.PATCH.name) curl_handler.setopt(pycurl.POSTFIELDS, self.body) elif self.http_method == EnumHttpMethod.DELETE.name: curl_handler.setopt(EnumHttpMethod.DELETE.value, EnumHttpMethod.DELETE.name) if self.body: curl_handler.setopt(pycurl.POSTFIELDS, self.body) curl_handler.setopt(pycurl.POSTFIELDSIZE, body_length) elif self.http_method == EnumHttpMethod.HEAD.name: curl_handler.setopt(pycurl.NOBODY, 1) curl_handler.setopt(EnumHttpMethod.HEAD.value, EnumHttpMethod.HEAD.name) else: curl_handler.setopt(pycurl.CUSTOMREQUEST, self.http_method.upper()) if self.body: curl_handler.setopt(pycurl.POSTFIELDS, self.body) curl_handler.setopt(pycurl.POSTFIELDSIZE, body_length) def __default_curl_config(self, curl_handler, timeout): body_byte = BytesIO() header_byte = BytesIO() curl_handler.setopt(curl_handler.URL, str(self.url)) curl_handler.setopt(curl_handler.TIMEOUT, timeout) curl_handler.setopt(pycurl.WRITEFUNCTION, body_byte.write) curl_handler.setopt(pycurl.HEADERFUNCTION, header_byte.write) curl_handler.setopt(pycurl.VERBOSE, self.__verbose) return body_byte, header_byte