def parse(self, base_url: str, testcase_list: List, test_file=None, working_directory=None, variable_dict=None): if working_directory is None: working_directory = Path(os.path.abspath(os.getcwd())) else: working_directory = Path(working_directory) if variable_dict is None: self.config.variable_binds = variable_dict if test_file: self.__testcase_file.add(test_file) testcase_config_object = TestCaseConfig() for testcase_node in testcase_list: if not isinstance(testcase_node, dict): logger.warning("Skipping the configuration %s" % testcase_node) continue testcase_node = Parser.lowercase_keys(testcase_node) for key in testcase_node: sub_testcase_node = testcase_node[key] if key == YamlKeyWords.INCLUDE: if not isinstance(sub_testcase_node, list): raise ValueError("include should be list not %s" % type(sub_testcase_node)) for testcase_file_path in sub_testcase_node: testcase_file_path = testcase_file_path.replace('.', '/') testcase_file = str(working_directory.joinpath("%s.yaml" % testcase_file_path).resolve()) if testcase_file not in self.__testcase_file: self.__testcase_file.add(testcase_file) import_testcase_list = read_testcase_file(testcase_file) with ChangeDir(working_directory): self.parse(base_url, import_testcase_list, variable_dict=variable_dict) elif key == YamlKeyWords.IMPORT: if sub_testcase_node not in self.__testcase_file: testcase_file_path = sub_testcase_node logger.debug("Importing testcase from %s", testcase_file_path) testcase_file_path = str(working_directory.joinpath("%s" % testcase_file_path).resolve()) self.__testcase_file.add(sub_testcase_node) import_testcase_list = read_testcase_file(testcase_file_path) with ChangeDir(working_directory): self.parse(base_url, import_testcase_list, variable_dict=variable_dict) elif key == YamlKeyWords.URL: __group_name = TestCaseGroup.DEFAULT_GROUP group_object = TestSet.__create_test(__group_name, testcase_config_object) testcase_object = TestCase( base_url=base_url, extract_binds=group_object.extract_binds, variable_binds=group_object.variable_binds, context=group_object.context, config=group_object.config ) testcase_object.url = testcase_node[key] group_object.testcase_list = testcase_object elif key == YamlKeyWords.TEST: with ChangeDir(working_directory): self.parse_test(base_url, sub_testcase_node, testcase_config_object) elif key == YamlKeyWords.CONFIG: testcase_config_object.parse(sub_testcase_node) self.config = testcase_config_object
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 test_parse_headers(self): request_text = ( b'GET /who/ken/trust.html HTTP/1.1\r\n' b'Host: cm.bell-labs.com\r\n' b'Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3\r\n' b'Accept: text/html;q=0.9,text/plain\r\n' b'\r\n' ) result_list = Parser.parse_headers(request_text) self.assertEqual(3, len(result_list)) self.assertEqual(('host', 'cm.bell-labs.com'), result_list[0]) request_text = "" result_list = Parser.parse_headers(request_text) self.assertEqual(0, len(result_list)) request_text = '\r\n' result_list = Parser.parse_headers(request_text) self.assertEqual(0, len(result_list))
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 test_flatten(self): """ Test flattening of lists of dictionaries to single dictionaries """ # Test happy path: list of single-item dictionaries in array = [{"url": "/cheese"}, {"method": "POST"}] expected = {"url": "/cheese", "method": "POST"} output = Parser.flatten_dictionaries(array) self.assertTrue(isinstance(output, dict)) # Test that expected output matches actual self.assertFalse(len(set(output.items()) ^ set(expected.items()))) # Test dictionary input array = {"url": "/cheese", "method": "POST"} expected = {"url": "/cheese", "method": "POST"} output = Parser.flatten_dictionaries(array) self.assertTrue(isinstance(output, dict)) # Test that expected output matches actual self.assertTrue(len(set(output.items()) ^ set(expected.items())) == 0) # Test empty list input array = [] expected = {} output = Parser.flatten_dictionaries(array) self.assertTrue(isinstance(output, dict)) # Test that expected output matches actual self.assertFalse(len(set(output.items()) ^ set(expected.items()))) # Test empty dictionary input array = {} expected = {} output = Parser.flatten_dictionaries(array) self.assertTrue(isinstance(output, dict)) # Test that expected output matches actual self.assertFalse(len(set(output.items()) ^ set(expected.items()))) # Test mixed-size input dictionaries array = [{"url": "/cheese"}, {"method": "POST", "foo": "bar"}] expected = {"url": "/cheese", "method": "POST", "foo": "bar"} output = Parser.flatten_dictionaries(array) self.assertTrue(isinstance(output, dict)) # Test that expected output matches actual self.assertFalse(len(set(output.items()) ^ set(expected.items())))
def parse(cls, config): validator = JsonSchemaValidator() config = Parser.lowercase_keys(config) if 'schema' not in config: raise ValueError( "Cannot create schema validator without a 'schema' configuration element!" ) validator.schema_context = ContentHandler.parse_content( config['schema']) return validator
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)
def parse(self, config_node): node = Parser.flatten_lowercase_keys_dict(config_node) for key, value in node.items(): if key == 'timeout': self.timeout = int(value) elif key == u'print_bodies': self.print_bodies = Parser.safe_to_bool(value) elif key == 'retries': self.retries = int(value) elif key == 'variable_binds': self.variable_binds = value elif key == u'generators': if not isinstance(value, list): raise TypeError("generators in config should defined as list(array).") flat = Parser.flatten_dictionaries(value) gen_dict = {} for generator_name, generator_config in flat.items(): gen = parse_generator(generator_config) gen_dict[str(generator_name)] = gen self.generators = gen_dict
def test_coerce_list_of_ints(self): self.assertEqual([1], Parser.coerce_list_of_ints(1)) self.assertEqual([2], Parser.coerce_list_of_ints('2')) self.assertEqual([18], Parser.coerce_list_of_ints(u'18')) self.assertEqual([1, 2], Parser.coerce_list_of_ints([1, 2])) self.assertEqual([1, 2], Parser.coerce_list_of_ints([1, '2'])) try: val = Parser.coerce_list_of_ints('goober') fail("Shouldn't allow coercing a random string to a list of ints") except: pass
def test_flatten_dictionaries(self): input_dict = {"x": 1, "y": 2} result_dict = Parser.flatten_dictionaries(input_dict) self.assertEqual(input_dict, result_dict) input_dict.update({"y": {"a": 1}}) result_dict = Parser.flatten_dictionaries(input_dict) self.assertEqual(input_dict, result_dict) result_dict = Parser.flatten_dictionaries([input_dict, input_dict, input_dict]) self.assertEqual(input_dict, result_dict) result_dict = Parser.flatten_dictionaries([input_dict]) self.assertEqual(input_dict, result_dict) result_dict = Parser.flatten_dictionaries([{'x': 1}, input_dict]) self.assertEqual(input_dict, result_dict) result_dict = Parser.flatten_dictionaries([input_dict, {'x': 2}]) self.assertNotEqual(input_dict, result_dict) result_dict = Parser.flatten_dictionaries([{'x': 2}, input_dict]) self.assertEqual(input_dict, result_dict)
def get_content(self, context=None): """ Does all context binding and pathing to get content, templated out """ if self.is_file: path = self.content if self.is_template_path and context: path = string.Template(path).safe_substitute( context.get_values()) with open(path, 'r') as f: data = f.read() if self.is_template_content and context: return string.Template(data).safe_substitute(context.get_values()) else: return data else: if self.is_template_content and context: return Parser.safe_substitute_unicode_template(self.content, context.get_values()) else: return self.content
def auth_username(self, username): self.__auth_username = Parser.coerce_string_to_ascii(username)
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()
def test_coerce_to_string(self): result = Parser.coerce_to_string(bytes("Hello", 'utf-8')) self.assertEqual(result, "Hello")
def test_lowercase_keys(self): input_val = 23 result_dict = Parser.lowercase_keys(input_val) self.assertEqual(23, result_dict)
def test_safe_substitute_unicode_template(self): result = Parser.safe_substitute_unicode_template("This is $x test", {'x': 'unit'}) self.assertEqual("This is unit test", result)
def variable_binds(self, variable_dict): """Variable binding """ if isinstance(variable_dict, dict): self.__variable_binds_dict.update(Parser.flatten_dictionaries(variable_dict))
def auth_password(self, password): self.__auth_password = Parser.coerce_string_to_ascii(password)
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)
def test_flatten_lowercase_keys(self): input_dict = "22" # unexpected result_dict = Parser.flatten_lowercase_keys_dict(input_dict) self.assertEqual("22", result_dict)
def parse_content(node): """ Parse content from input node and returns ContentHandler object it'll look like: - template: - file: - temple: path or something """ # Tread carefully, this one is a bit narly because of nesting output = ContentHandler() is_template_path = False is_template_content = False is_file = False is_done = False if not isinstance(node, (str, dict, list)): raise TypeError( "Content must be a string, dictionary, or list of dictionaries") while node and not is_done: # Dive through the configuration tree # Finally we've found the value! if isinstance(node, str): output.content = node output.setup(node, is_file=is_file, is_template_path=is_template_path, is_template_content=is_template_content) return output is_done = True # Dictionary or list of dictionaries flat_dict = Parser.flatten_lowercase_keys_dict(node) for key, value in flat_dict.items(): if key == 'template': if isinstance(value, str): if is_file: value = os.path.abspath(value) output.content = value is_template_content = is_template_content or not is_file output.is_template_content = is_template_content output.is_template_path = is_file output.is_file = is_file return output else: is_template_content = True node = value is_done = False break elif key == 'file': if isinstance(value, str): output.content = os.path.abspath(value) output.is_file = True output.is_template_content = is_template_content return output else: is_file = True node = value is_done = False break raise Exception("Invalid configuration for content.")
def test_coerce_string_to_ascii(self): result = Parser.coerce_string_to_ascii(bytes("Hello", 'utf-8')) self.assertEqual(result, "Hello".encode('ascii'))
def test_coerce_string_to_ascii(self): self.assertEqual(b'stuff', Parser.coerce_string_to_ascii(u'stuff')) self.assertRaises(UnicodeEncodeError, Parser.coerce_string_to_ascii, u'st😽uff') self.assertRaises(TypeError, Parser.coerce_string_to_ascii, 1) self.assertRaises(TypeError, Parser.coerce_string_to_ascii, None)
def test_coerce_http_method(self): self.assertEqual(u'HEAD', Parser.coerce_http_method(u'hEaD')) self.assertEqual(u'HEAD', Parser.coerce_http_method(b'hEaD')) self.assertRaises(TypeError, Parser.coerce_http_method, 5) self.assertRaises(TypeError, Parser.coerce_http_method, None) self.assertRaises(TypeError, Parser.coerce_http_method, u'')