Ejemplo n.º 1
0
    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)
Ejemplo n.º 2
0
    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',
        }
Ejemplo n.º 3
0
    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"])
Ejemplo n.º 4
0
    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)
Ejemplo n.º 5
0
    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
Ejemplo n.º 6
0
    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",
        }
Ejemplo n.º 7
0
    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
Ejemplo n.º 8
0
    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)
Ejemplo n.º 9
0
    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",
            }
        }
Ejemplo n.º 10
0
    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
Ejemplo n.º 11
0
    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'
            }
        }
Ejemplo n.º 12
0
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)
Ejemplo n.º 13
0
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