def __init__(self, session, name, expected, test_block_config): # pylint: disable=unused-argument super(RestResponse, self).__init__() defaults = {"status_code": 200} self.name = name self._check_for_validate_functions(expected.get("body", {})) self.expected = deep_dict_merge(defaults, expected) self.response = None self.test_block_config = test_block_config self.status_code = None def check_code(code): if code not in _codes: logger.warning("Unexpected status code '%s'", code) if isinstance(self.expected["status_code"], int): check_code(self.expected["status_code"]) else: for code in self.expected["status_code"]: check_code(code)
def test_single_level(self): """ Merge two depth-one dicts with no conflicts """ dict_1 = { 'key_1': 'original_value_1', 'key_2': 'original_value_2' } dict_2 = { 'key_2': 'new_value_2', 'key_3': 'new_value_3' } result = deep_dict_merge(dict_1, dict_2) assert dict_1 == { 'key_1': 'original_value_1', 'key_2': 'original_value_2' } assert dict_2 == { 'key_2': 'new_value_2', 'key_3': 'new_value_3' } assert result == { 'key_1': 'original_value_1', 'key_2': 'new_value_2', 'key_3': 'new_value_3', }
def __init__(self, session: object, name: Any, expected: Any, test_block_config: Any): # pylint: disable=unused-argument super(RestResponse, self).__init__() defaults = {'status_code': 200} self.name = name body = expected.get("body") or {} if "$ext" in body: self.validate_function = get_wrapped_response_function( body["$ext"]) else: self.validate_function = None self.expected = deep_dict_merge(defaults, expected) self.response = None self.test_block_config = test_block_config self.status_code = None expected_status_code = list() if isinstance(_codes, Iterable): expected_status_code.extend(_codes) else: expected_status_code.append(_codes) if self.expected["status_code"] not in expected_status_code: logger.warning("Unexpected status code '%s'", self.expected["status_code"])
def __init__(self, session, name, expected, test_block_config): # pylint: disable=unused-argument super(RestResponse, self).__init__() defaults = { 'status_code': 200 } self.name = name body = expected.get("body") or {} if "$ext" in body: self.validate_function = get_wrapped_response_function(body["$ext"]) else: self.validate_function = None self.expected = deep_dict_merge(defaults, expected) self.response = None self.test_block_config = test_block_config self.status_code = None def check_code(code): if code not in _codes: logger.warning("Unexpected status code '%s'", code) if isinstance(self.expected["status_code"], int): check_code(self.expected["status_code"]) else: for code in self.expected["status_code"]: check_code(code)
def maybe_load_ext(v): key, value = v if is_ext_function(value): # If it is an ext function, load the new (or supplemental) value[s] ext = value.pop("$ext") f = get_wrapped_create_function(ext) new_value = f() if len(value) == 0: # Use only this new value return key, new_value elif isinstance(new_value, dict): # Merge with some existing data. At this point 'value' is known to be a dict. return key, deep_dict_merge(value, f()) else: # For example, if it's defined like # # - testkey: testval # $ext: # function: mod:func # # and 'mod:func' returns a string, it's impossible to 'merge' with the existing data. logger.error("Values still in 'val': %s", value) raise exceptions.BadSchemaError( "There were extra key/value pairs in the 'val' for this parametrize mark, but the ext function {} returned '{}' (of type {}) that was not a dictionary. It is impossible to merge these values." .format(ext, new_value, type(new_value))) return key, value
def test_single_level(self): """Merge two depth-one dicts with no conflicts""" dict_1 = {"key_1": "original_value_1", "key_2": "original_value_2"} dict_2 = {"key_2": "new_value_2", "key_3": "new_value_3"} result = deep_dict_merge(dict_1, dict_2) assert dict_1 == {"key_1": "original_value_1", "key_2": "original_value_2"} assert dict_2 == {"key_2": "new_value_2", "key_3": "new_value_3"} assert result == { "key_1": "original_value_1", "key_2": "new_value_2", "key_3": "new_value_3", }
def __init__(self, session, name, expected, test_block_config): # pylint: disable=unused-argument super(RestResponse, self).__init__() defaults = {'status_code': 200} # validate_function 只在 verify 时才使用,故而不需要先初始化 # body = expected.get("body") or {} # if "$ext" in body: # self.validate_function = get_wrapped_response_function( # body["$ext"]) # else: # self.validate_function = None self.name = name self.expected = deep_dict_merge(defaults, expected) self.response = None self.test_block_config = test_block_config self.status_code = None
def __init__(self, session, name, expected, test_block_config): # pylint: disable=unused-argument defaults = {"status_code": 200} super(RestResponse, self).__init__( name, deep_dict_merge(defaults, expected), test_block_config ) self.status_code = None def check_code(code): if code not in _codes: logger.warning("Unexpected status code '%s'", code) if isinstance(self.expected["status_code"], int): check_code(self.expected["status_code"]) else: for code in self.expected["status_code"]: check_code(code)
def test_recursive_merge(self): """Merge two depth-one dicts with no conflicts""" dict_1 = { "key": {"deep_key_1": "original_value_1", "deep_key_2": "original_value_2"} } dict_2 = {"key": {"deep_key_2": "new_value_2", "deep_key_3": "new_value_3"}} result = deep_dict_merge(dict_1, dict_2) assert dict_1 == { "key": {"deep_key_1": "original_value_1", "deep_key_2": "original_value_2"} } assert dict_2 == { "key": {"deep_key_2": "new_value_2", "deep_key_3": "new_value_3"} } assert result == { "key": { "deep_key_1": "original_value_1", "deep_key_2": "new_value_2", "deep_key_3": "new_value_3", } }
def __init__(self, session, name, expected, test_block_config): # pylint: disable=unused-argument defaults = {"status_code": 200} super().__init__(name, deep_dict_merge(defaults, expected), test_block_config) self.status_code = None def check_code(code): if int(code) not in _codes: logger.warning("Unexpected status code '%s'", code) in_file = self.expected["status_code"] try: if isinstance(in_file, list): for code_ in in_file: check_code(code_) else: check_code(in_file) except TypeError as e: raise exceptions.BadSchemaError("Invalid code") from e
def test_recursive_merge(self): """ Merge two depth-one dicts with no conflicts """ dict_1 = { 'key': { 'deep_key_1': 'original_value_1', 'deep_key_2': 'original_value_2' } } dict_2 = { 'key': { 'deep_key_2': 'new_value_2', 'deep_key_3': 'new_value_3' } } result = deep_dict_merge(dict_1, dict_2) assert dict_1 == { 'key': { 'deep_key_1': 'original_value_1', 'deep_key_2': 'original_value_2' } } assert dict_2 == { 'key': { 'deep_key_2': 'new_value_2', 'deep_key_3': 'new_value_3' } } assert result == { 'key': { 'deep_key_1': 'original_value_1', 'deep_key_2': 'new_value_2', 'deep_key_3': 'new_value_3' } }
def _read_expected_cookies(session, rspec, test_block_config): """ Read cookies to inject into request, ignoring others which are present Args: session (Session): session object rspec (dict): test spec test_block_config (dict): config available for test Returns: dict: cookies to use in request, if any """ # Need to do this down here - it is separate from getting request args as # it depends on the state of the session existing_cookies = session.cookies.get_dict() cookies_to_use = format_keys( rspec.get("cookies", None), test_block_config["variables"] ) if cookies_to_use is None: logger.debug("No cookies specified in request, sending all") return None elif cookies_to_use in ([], {}): logger.debug("Not sending any cookies with request") return {} def partition(pred, iterable): """From itertools documentation""" t1, t2 = tee(iterable) return list(filterfalse(pred, t1)), list(filter(pred, t2)) # Cookies are either a single list item, specitying which cookie to send, or # a mapping, specifying cookies to override expected, extra = partition(lambda x: isinstance(x, dict), cookies_to_use) missing = set(expected) - set(existing_cookies.keys()) if missing: logger.error("Missing cookies") raise exceptions.MissingCookieError( "Tried to use cookies '{}' in request but only had '{}' available".format( expected, existing_cookies ) ) # 'extra' should be a list of dictionaries - merge them into one here from_extra = {k: v for mapping in extra for (k, v) in mapping.items()} if len(extra) != len(from_extra): logger.error("Duplicate cookie override values specified") raise exceptions.DuplicateCookieError( "Tried to override the value of a cookie multiple times in one request" ) overwritten = [i for i in expected if i in from_extra] if overwritten: logger.error("Duplicate cookies found in request") raise exceptions.DuplicateCookieError( "Asked to use cookie {} from previous request but also redefined it as {}".format( overwritten, from_extra ) ) from_cookiejar = {c: existing_cookies.get(c) for c in expected} return deep_dict_merge(from_cookiejar, from_extra)
def get_request_args(rspec, test_block_config): """Format the test spec given values inthe global config Todo: Add similar functionality to validate/save $ext functions so input can be generated from a function Args: rspec (dict): Test spec test_block_config (dict): Test block config Returns: dict: Formatted test spec Raises: BadSchemaError: Tried to pass a body in a GET request """ # pylint: disable=too-many-locals,too-many-statements request_args = {} # Ones that are required and are enforced to be present by the schema required_in_file = ["method", "url"] optional_in_file = [ "json", "data", "params", "headers", "files", "timeout", "cert", # Ideally this would just be passed through but requests seems to error # if we pass a list instead of a tuple, so we have to manually convert # it further down # "auth" ] optional_with_default = {"verify": True, "stream": False} if "method" not in rspec: logger.debug("Using default GET method") rspec["method"] = "GET" content_keys = ["data", "json", "files", "file_body"] in_request = [c for c in content_keys if c in rspec] if len(in_request) > 1: # Explicitly raise an error here # From requests docs: # Note, the json parameter is ignored if either data or files is passed. # However, we allow the data + files case, as requests handles it correctly if set(in_request) != {"data", "files"}: raise exceptions.BadSchemaError( "Can only specify one type of request data in HTTP request (tried to " "send {})".format(" and ".join(in_request)) ) headers = rspec.get("headers", {}) has_content_header = "content-type" in [h.lower() for h in headers.keys()] if "files" in rspec: if has_content_header: logger.warning( "Tried to specify a content-type header while sending a file - this will be ignored" ) rspec["headers"] = { i: j for i, j in headers.items() if i.lower() != "content-type" } fspec = format_keys(rspec, test_block_config["variables"]) send_in_body = fspec.get("file_body") if send_in_body: request_args["file_body"] = send_in_body def add_request_args(keys, optional): for key in keys: try: request_args[key] = fspec[key] except KeyError: if optional or (key in request_args): continue # This should never happen raise add_request_args(required_in_file, False) add_request_args(optional_in_file, True) if "auth" in fspec: request_args["auth"] = tuple(fspec["auth"]) if "cert" in fspec: if isinstance(fspec["cert"], list): request_args["cert"] = tuple(fspec["cert"]) if "timeout" in fspec: # Needs to be a tuple, it being a list doesn't work if isinstance(fspec["timeout"], list): request_args["timeout"] = tuple(fspec["timeout"]) external_function_keys = optional_in_file[:] # Support to use external function to create a url. # Github issues: https://github.com/taverntesting/tavern/issues/580 external_function_keys.append("url") for key in external_function_keys: try: func = get_wrapped_create_function(request_args[key].pop("$ext")) except (KeyError, TypeError, AttributeError): pass else: merge_ext_values = test_block_config.get("merge_ext_values") logger.debug("Will merge ext values? %s", merge_ext_values) func_result = func() if merge_ext_values and not isinstance(func_result, str): request_args[key] = deep_dict_merge(request_args[key], func_result) else: request_args[key] = func_result # If there's any nested json in parameters, urlencode it # if you pass nested json to 'params' then requests silently fails and just # passes the 'top level' key, ignoring all the nested json. I don't think # there's a standard way to do this, but urlencoding it seems sensible # eg https://openid.net/specs/openid-connect-core-1_0.html#ClaimsParameter # > ...represented in an OAuth 2.0 request as UTF-8 encoded JSON (which ends # > up being form-urlencoded when passed as an OAuth parameter) for key, value in request_args.get("params", {}).items(): if isinstance(value, dict): request_args["params"][key] = quote_plus(json.dumps(value)) for key, val in optional_with_default.items(): request_args[key] = fspec.get(key, val) # TODO # requests takes all of these - we need to parse the input to get them # "cookies", # These verbs _can_ send a body but the body _should_ be ignored according # to the specs - some info here: # https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods if request_args["method"] in ["GET", "HEAD", "OPTIONS"]: if any(i in request_args for i in ["json", "data"]): warnings.warn( "You are trying to send a body with a HTTP verb that has no semantic use for it", RuntimeWarning, ) return request_args