import decimal
import json
import io
import pytest

from werkzeug.exceptions import BadRequest
from werkzeug.wrappers import Request
from werkzeug.datastructures import FileStorage, MultiDict

from flask_restx import Api, Model, fields, inputs
from flask_restx.errors import SpecsError
from flask_restx.reqparse import Argument, RequestParser, ParseResult


class ReqParseTest(object):
    def test_api_shortcut(self, app):
        api = Api(app)
        parser = api.parser()
        assert isinstance(parser, RequestParser)

    def test_parse_model(self, app):
        model = Model("Todo", {"task": fields.String(required=True)})

        parser = RequestParser()
        parser.add_argument("todo", type=model, required=True)

        data = {"todo": {"task": "aaa"}}

        with app.test_request_context(
            "/", method="post", data=json.dumps(data), content_type="application/json"
        ):
            args = parser.parse_args()
            assert args["todo"] == {"task": "aaa"}

    def test_help(self, app, mocker):
        abort = mocker.patch(
            "flask_restx.reqparse.abort", side_effect=BadRequest("Bad Request")
        )
        parser = RequestParser()
        parser.add_argument("foo", choices=("one", "two"), help="Bad choice.")
        req = mocker.Mock(["values", "get_json"])
        req.values = MultiDict([("foo", "three")])
        req.get_json.return_value = None
        with pytest.raises(BadRequest):
            parser.parse_args(req)
        expected = {
            "foo": "Bad choice. The value 'three' is not a valid choice for 'foo'."
        }
        abort.assert_called_with(
            400, "Input payload validation failed", errors=expected
        )

    def test_no_help(self, app, mocker):
        abort = mocker.patch(
            "flask_restx.reqparse.abort", side_effect=BadRequest("Bad Request")
        )
        parser = RequestParser()
        parser.add_argument("foo", choices=["one", "two"])
        req = mocker.Mock(["values", "get_json"])
        req.get_json.return_value = None
        req.values = MultiDict([("foo", "three")])
        with pytest.raises(BadRequest):
            parser.parse_args(req)
        expected = {"foo": "The value 'three' is not a valid choice for 'foo'."}
        abort.assert_called_with(
            400, "Input payload validation failed", errors=expected
        )

    @pytest.mark.request_context()
    def test_viewargs(self, mocker):
        req = Request.from_values()
        req.view_args = {"foo": "bar"}
        parser = RequestParser()
        parser.add_argument("foo", location=["view_args"])
        args = parser.parse_args(req)
        assert args["foo"] == "bar"

        req = mocker.Mock(["get_json"])
        req.values = ()
        req.get_json.return_value = None
        req.view_args = {"foo": "bar"}
        parser = RequestParser()
        parser.add_argument("foo", store_missing=True)
        args = parser.parse_args(req)
        assert args["foo"] is None

    def test_parse_unicode(self, app):
        req = Request.from_values("/bubble?foo=barß")
        parser = RequestParser()
        parser.add_argument("foo")

        args = parser.parse_args(req)
        assert args["foo"] == "barß"

    def test_parse_unicode_app(self, app):
        parser = RequestParser()
        parser.add_argument("foo")

        with app.test_request_context("/bubble?foo=barß"):
            args = parser.parse_args()
            assert args["foo"] == "barß"

    @pytest.mark.request_context(
        "/bubble", method="post", content_type="application/json"
    )
    def test_json_location(self):
        parser = RequestParser()
        parser.add_argument("foo", location="json", store_missing=True)
        args = parser.parse_args()
        assert args["foo"] is None

    @pytest.mark.request_context(
        "/bubble",
        method="post",
        data=json.dumps({"foo": "bar"}),
        content_type="application/json",
    )
    def test_get_json_location(self):
        parser = RequestParser()
        parser.add_argument("foo", location="json")
        args = parser.parse_args()
        assert args["foo"] == "bar"

    @pytest.mark.request_context("/bubble?foo=bar")
    def test_parse_append_ignore(self, app):
        parser = RequestParser()
        parser.add_argument(
            "foo", ignore=True, type=int, action="append", store_missing=True
        ),

        args = parser.parse_args()
        assert args["foo"] is None

    @pytest.mark.request_context("/bubble?")
    def test_parse_append_default(self):
        parser = RequestParser()
        parser.add_argument("foo", action="append", store_missing=True),

        args = parser.parse_args()
        assert args["foo"] is None

    @pytest.mark.request_context("/bubble?foo=bar&foo=bat")
    def test_parse_append(self):
        parser = RequestParser()
        parser.add_argument("foo", action="append"),

        args = parser.parse_args()
        assert args["foo"] == ["bar", "bat"]

    @pytest.mark.request_context("/bubble?foo=bar")
    def test_parse_append_single(self):
        parser = RequestParser()
        parser.add_argument("foo", action="append"),

        args = parser.parse_args()
        assert args["foo"] == ["bar"]

    @pytest.mark.request_context("/bubble?foo=bar")
    def test_split_single(self):
        parser = RequestParser()
        parser.add_argument("foo", action="split"),

        args = parser.parse_args()
        assert args["foo"] == ["bar"]

    @pytest.mark.request_context("/bubble?foo=bar,bat")
    def test_split_multiple(self):
        parser = RequestParser()
        parser.add_argument("foo", action="split"),

        args = parser.parse_args()
        assert args["foo"] == ["bar", "bat"]

    @pytest.mark.request_context("/bubble?foo=1,2,3")
    def test_split_multiple_cast(self):
        parser = RequestParser()
        parser.add_argument("foo", type=int, action="split")

        args = parser.parse_args()
        assert args["foo"] == [1, 2, 3]

    @pytest.mark.request_context("/bubble?foo=bar")
    def test_parse_dest(self):
        parser = RequestParser()
        parser.add_argument("foo", dest="bat")

        args = parser.parse_args()
        assert args["bat"] == "bar"

    @pytest.mark.request_context("/bubble?foo>=bar&foo<=bat&foo=foo")
    def test_parse_gte_lte_eq(self):
        parser = RequestParser()
        parser.add_argument("foo", operators=[">=", "<=", "="], action="append"),

        args = parser.parse_args()
        assert args["foo"] == ["bar", "bat", "foo"]

    @pytest.mark.request_context("/bubble?foo>=bar")
    def test_parse_gte(self):
        parser = RequestParser()
        parser.add_argument("foo", operators=[">="])

        args = parser.parse_args()
        assert args["foo"] == "bar"

    @pytest.mark.request_context("/bubble?foo=bar")
    def test_parse_foo_operators_four_hunderd(self):
        parser = RequestParser()
        parser.add_argument("foo", type=int),
        with pytest.raises(BadRequest):
            parser.parse_args()

    @pytest.mark.request_context("/bubble")
    def test_parse_foo_operators_ignore(self):
        parser = RequestParser()
        parser.add_argument("foo", ignore=True, store_missing=True)

        args = parser.parse_args()
        assert args["foo"] is None

    @pytest.mark.request_context("/bubble?foo<=bar")
    def test_parse_lte_gte_mock(self, mocker):
        mock_type = mocker.Mock()

        parser = RequestParser()
        parser.add_argument("foo", type=mock_type, operators=["<="])

        parser.parse_args()
        mock_type.assert_called_with("bar", "foo", "<=")

    @pytest.mark.request_context("/bubble?foo<=bar")
    def test_parse_lte_gte_append(self):
        parser = RequestParser()
        parser.add_argument("foo", operators=["<=", "="], action="append")

        args = parser.parse_args()
        assert args["foo"] == ["bar"]

    @pytest.mark.request_context("/bubble?foo<=bar")
    def test_parse_lte_gte_missing(self):
        parser = RequestParser()
        parser.add_argument("foo", operators=["<=", "="])
        args = parser.parse_args()
        assert args["foo"] == "bar"

    @pytest.mark.request_context("/bubble?foo=bar&foo=bat")
    def test_parse_eq_other(self):
        parser = RequestParser()
        parser.add_argument("foo"),
        args = parser.parse_args()
        assert args["foo"] == "bar"

    @pytest.mark.request_context("/bubble?foo=bar")
    def test_parse_eq(self):
        parser = RequestParser()
        parser.add_argument("foo"),
        args = parser.parse_args()
        assert args["foo"] == "bar"

    @pytest.mark.request_context("/bubble?foo<=bar")
    def test_parse_lte(self):
        parser = RequestParser()
        parser.add_argument("foo", operators=["<="])

        args = parser.parse_args()
        assert args["foo"] == "bar"

    @pytest.mark.request_context("/bubble")
    def test_parse_required(self, app):
        parser = RequestParser()
        parser.add_argument("foo", required=True, location="values")

        expected = {
            "foo": "Missing required parameter in the post body or the query string"
        }
        with pytest.raises(BadRequest) as cm:
            parser.parse_args()

        assert cm.value.data["message"] == "Input payload validation failed"
        assert cm.value.data["errors"] == expected

        parser = RequestParser()
        parser.add_argument("bar", required=True, location=["values", "cookies"])

        expected = {
            "bar": (
                "Missing required parameter in the post body or the query "
                "string or the request's cookies"
            )
        }

        with pytest.raises(BadRequest) as cm:
            parser.parse_args()
        assert cm.value.data["message"] == "Input payload validation failed"
        assert cm.value.data["errors"] == expected

    @pytest.mark.request_context("/bubble")
    @pytest.mark.options(bundle_errors=True)
    def test_parse_error_bundling(self, app):
        parser = RequestParser()
        parser.add_argument("foo", required=True, location="values")
        parser.add_argument("bar", required=True, location=["values", "cookies"])

        with pytest.raises(BadRequest) as cm:
            parser.parse_args()

        assert cm.value.data["message"] == "Input payload validation failed"
        assert cm.value.data["errors"] == {
            "foo": "Missing required parameter in the post body or the query string",
            "bar": (
                "Missing required parameter in the post body or the query string "
                "or the request's cookies"
            ),
        }

    @pytest.mark.request_context("/bubble")
    @pytest.mark.options(bundle_errors=False)
    def test_parse_error_bundling_w_parser_arg(self, app):
        parser = RequestParser(bundle_errors=True)
        parser.add_argument("foo", required=True, location="values")
        parser.add_argument("bar", required=True, location=["values", "cookies"])

        with pytest.raises(BadRequest) as cm:
            parser.parse_args()

        assert cm.value.data["message"] == "Input payload validation failed"
        assert cm.value.data["errors"] == {
            "foo": "Missing required parameter in the post body or the query string",
            "bar": (
                "Missing required parameter in the post body or the query string "
                "or the request's cookies"
            ),
        }

    @pytest.mark.request_context("/bubble")
    def test_parse_default_append(self):
        parser = RequestParser()
        parser.add_argument("foo", default="bar", action="append", store_missing=True)

        args = parser.parse_args()

        assert args["foo"] == "bar"

    @pytest.mark.request_context("/bubble")
    def test_parse_default(self):
        parser = RequestParser()
        parser.add_argument("foo", default="bar", store_missing=True)

        args = parser.parse_args()
        assert args["foo"] == "bar"

    @pytest.mark.request_context("/bubble")
    def test_parse_callable_default(self):
        parser = RequestParser()
        parser.add_argument("foo", default=lambda: "bar", store_missing=True)

        args = parser.parse_args()
        assert args["foo"] == "bar"

    @pytest.mark.request_context("/bubble?foo=bar")
    def test_parse(self):
        parser = RequestParser()
        parser.add_argument("foo"),

        args = parser.parse_args()
        assert args["foo"] == "bar"

    @pytest.mark.request_context("/bubble")
    def test_parse_none(self):
        parser = RequestParser()
        parser.add_argument("foo")

        args = parser.parse_args()
        assert args["foo"] is None

    def test_parse_store_missing(self, app):
        req = Request.from_values("/bubble")

        parser = RequestParser()
        parser.add_argument("foo", store_missing=False)

        args = parser.parse_args(req)
        assert "foo" not in args

    def test_parse_choices_correct(self, app):
        req = Request.from_values("/bubble?foo=bat")

        parser = RequestParser()
        parser.add_argument("foo", choices=["bat"]),

        args = parser.parse_args(req)
        assert args["foo"] == "bat"

    def test_parse_choices(self, app):
        req = Request.from_values("/bubble?foo=bar")

        parser = RequestParser()
        parser.add_argument("foo", choices=["bat"]),

        with pytest.raises(BadRequest):
            parser.parse_args(req)

    def test_parse_choices_sensitive(self, app):
        req = Request.from_values("/bubble?foo=BAT")

        parser = RequestParser()
        parser.add_argument("foo", choices=["bat"], case_sensitive=True),

        with pytest.raises(BadRequest):
            parser.parse_args(req)

    def test_parse_choices_insensitive(self, app):
        req = Request.from_values("/bubble?foo=BAT")

        parser = RequestParser()
        parser.add_argument("foo", choices=["bat"], case_sensitive=False),

        args = parser.parse_args(req)
        assert "bat" == args.get("foo")

        # both choices and args are case_insensitive
        req = Request.from_values("/bubble?foo=bat")

        parser = RequestParser()
        parser.add_argument("foo", choices=["BAT"], case_sensitive=False),

        args = parser.parse_args(req)
        assert "bat" == args.get("foo")

    def test_parse_ignore(self, app):
        req = Request.from_values("/bubble?foo=bar")

        parser = RequestParser()
        parser.add_argument("foo", type=int, ignore=True, store_missing=True),

        args = parser.parse_args(req)
        assert args["foo"] is None

    def test_chaining(self):
        parser = RequestParser()
        assert parser is parser.add_argument("foo")

    def test_result_existence(self):
        result = ParseResult()
        result.foo = "bar"
        result["bar"] = "baz"
        assert result["foo"] == "bar"
        assert result.bar == "baz"

    def test_result_missing(self):
        result = ParseResult()
        pytest.raises(AttributeError, lambda: result.spam)
        pytest.raises(KeyError, lambda: result["eggs"])

    def test_result_configurability(self):
        req = Request.from_values()
        assert isinstance(RequestParser().parse_args(req), ParseResult)
        assert type(RequestParser(result_class=dict).parse_args(req)) is dict

    def test_none_argument(self, app):
        parser = RequestParser()
        parser.add_argument("foo", location="json")
        with app.test_request_context(
            "/bubble",
            method="post",
            data=json.dumps({"foo": None}),
            content_type="application/json",
        ):
            args = parser.parse_args()
        assert args["foo"] is None

    def test_type_callable(self, app):
        req = Request.from_values("/bubble?foo=1")

        parser = RequestParser()
        parser.add_argument("foo", type=lambda x: x, required=False),

        args = parser.parse_args(req)
        assert args["foo"] == "1"

    def test_type_callable_none(self, app):
        parser = RequestParser()
        parser.add_argument("foo", type=lambda x: x, location="json", required=False),

        with app.test_request_context(
            "/bubble",
            method="post",
            data=json.dumps({"foo": None}),
            content_type="application/json",
        ):
            args = parser.parse_args()
            assert args["foo"] is None

    def test_type_decimal(self, app):
        parser = RequestParser()
        parser.add_argument("foo", type=decimal.Decimal, location="json")

        with app.test_request_context(
            "/bubble",
            method="post",
            data=json.dumps({"foo": "1.0025"}),
            content_type="application/json",
        ):
            args = parser.parse_args()
            assert args["foo"] == decimal.Decimal("1.0025")

    def test_type_filestorage(self, app):
        parser = RequestParser()
        parser.add_argument("foo", type=FileStorage, location="files")

        fdata = "foo bar baz qux".encode("utf-8")
        with app.test_request_context(
            "/bubble", method="POST", data={"foo": (io.BytesIO(fdata), "baz.txt")}
        ):
            args = parser.parse_args()

            assert args["foo"].name == "foo"
            assert args["foo"].filename == "baz.txt"
            assert args["foo"].read() == fdata

    def test_filestorage_custom_type(self, app):
        def _custom_type(f):
            return FileStorage(
                stream=f.stream,
                filename="{0}aaaa".format(f.filename),
                name="{0}aaaa".format(f.name),
            )

        parser = RequestParser()
        parser.add_argument("foo", type=_custom_type, location="files")

        fdata = "foo bar baz qux".encode("utf-8")
        with app.test_request_context(
            "/bubble", method="POST", data={"foo": (io.BytesIO(fdata), "baz.txt")}
        ):
            args = parser.parse_args()

            assert args["foo"].name == "fooaaaa"
            assert args["foo"].filename == "baz.txtaaaa"
            assert args["foo"].read() == fdata

    def test_passing_arguments_object(self, app):
        req = Request.from_values("/bubble?foo=bar")
        parser = RequestParser()
        parser.add_argument(Argument("foo"))

        args = parser.parse_args(req)
        assert args["foo"] == "bar"

    def test_int_choice_types(self, app):
        parser = RequestParser()
        parser.add_argument("foo", type=int, choices=[1, 2, 3], location="json")

        with app.test_request_context(
            "/bubble",
            method="post",
            data=json.dumps({"foo": 5}),
            content_type="application/json",
        ):
            with pytest.raises(BadRequest):
                parser.parse_args()

    def test_int_range_choice_types(self, app):
        parser = RequestParser()
        parser.add_argument("foo", type=int, choices=range(100), location="json")

        with app.test_request_context(
            "/bubble",
            method="post",
            data=json.dumps({"foo": 101}),
            content_type="application/json",
        ):
            with pytest.raises(BadRequest):
                parser.parse_args()

    def test_request_parser_copy(self, app):
        req = Request.from_values("/bubble?foo=101&bar=baz")
        parser = RequestParser()
        foo_arg = Argument("foo", type=int)
        parser.args.append(foo_arg)
        parser_copy = parser.copy()

        # Deepcopy should create a clone of the argument object instead of
        # copying a reference to the new args list
        assert foo_arg not in parser_copy.args

        # Args added to new parser should not be added to the original
        bar_arg = Argument("bar")
        parser_copy.args.append(bar_arg)
        assert bar_arg not in parser.args

        args = parser_copy.parse_args(req)
        assert args["foo"] == 101
        assert args["bar"] == "baz"

    def test_request_parse_copy_including_settings(self):
        parser = RequestParser(trim=True, bundle_errors=True)
        parser_copy = parser.copy()

        assert parser.trim == parser_copy.trim
        assert parser.bundle_errors == parser_copy.bundle_errors

    def test_request_parser_replace_argument(self, app):
        req = Request.from_values("/bubble?foo=baz")
        parser = RequestParser()
        parser.add_argument("foo", type=int)
        parser_copy = parser.copy()
        parser_copy.replace_argument("foo")

        args = parser_copy.parse_args(req)
        assert args["foo"] == "baz"

    def test_both_json_and_values_location(self, app):
        parser = RequestParser()
        parser.add_argument("foo", type=int)
        parser.add_argument("baz", type=int)
        with app.test_request_context(
            "/bubble?foo=1",
            method="post",
            data=json.dumps({"baz": 2}),
            content_type="application/json",
        ):
            args = parser.parse_args()
            assert args["foo"] == 1
            assert args["baz"] == 2

    def test_not_json_location_and_content_type_json(self, app):
        parser = RequestParser()
        parser.add_argument("foo", location="args")

        with app.test_request_context(
            "/bubble", method="get", content_type="application/json"
        ):
            parser.parse_args()  # Should not raise a 400: BadRequest

    def test_request_parser_remove_argument(self):
        req = Request.from_values("/bubble?foo=baz")
        parser = RequestParser()
        parser.add_argument("foo", type=int)
        parser_copy = parser.copy()
        parser_copy.remove_argument("foo")

        args = parser_copy.parse_args(req)
        assert args == {}

    def test_strict_parsing_off(self):
        req = Request.from_values("/bubble?foo=baz")
        parser = RequestParser()
        args = parser.parse_args(req)
        assert args == {}

    def test_strict_parsing_on(self):
        req = Request.from_values("/bubble?foo=baz")
        parser = RequestParser()
        with pytest.raises(BadRequest):
            parser.parse_args(req, strict=True)

    def test_strict_parsing_off_partial_hit(self, app):
        req = Request.from_values("/bubble?foo=1&bar=bees&n=22")
        parser = RequestParser()
        parser.add_argument("foo", type=int)
        args = parser.parse_args(req)
        assert args["foo"] == 1

    def test_strict_parsing_on_partial_hit(self, app):
        req = Request.from_values("/bubble?foo=1&bar=bees&n=22")
        parser = RequestParser()
        parser.add_argument("foo", type=int)
        with pytest.raises(BadRequest):
            parser.parse_args(req, strict=True)

    def test_trim_argument(self, app):
        req = Request.from_values("/bubble?foo= 1 &bar=bees&n=22")
        parser = RequestParser()
        parser.add_argument("foo")
        args = parser.parse_args(req)
        assert args["foo"] == " 1 "

        parser = RequestParser()
        parser.add_argument("foo", trim=True)
        args = parser.parse_args(req)
        assert args["foo"] == "1"

        parser = RequestParser()
        parser.add_argument("foo", trim=True, type=int)
        args = parser.parse_args(req)
        assert args["foo"] == 1

    def test_trim_request_parser(self, app):
        req = Request.from_values("/bubble?foo= 1 &bar=bees&n=22")
        parser = RequestParser(trim=False)
        parser.add_argument("foo")
        args = parser.parse_args(req)
        assert args["foo"] == " 1 "

        parser = RequestParser(trim=True)
        parser.add_argument("foo")
        args = parser.parse_args(req)
        assert args["foo"] == "1"

        parser = RequestParser(trim=True)
        parser.add_argument("foo", type=int)
        args = parser.parse_args(req)
        assert args["foo"] == 1

    def test_trim_request_parser_override_by_argument(self):
        parser = RequestParser(trim=True)
        parser.add_argument("foo", trim=False)

        assert parser.args[0].trim is False

    def test_trim_request_parser_json(self, app):
        parser = RequestParser(trim=True)
        parser.add_argument("foo", location="json")
        parser.add_argument("int1", location="json", type=int)
        parser.add_argument("int2", location="json", type=int)

        with app.test_request_context(
            "/bubble",
            method="post",
            data=json.dumps({"foo": " bar ", "int1": 1, "int2": " 2 "}),
            content_type="application/json",
        ):
            args = parser.parse_args()
        assert args["foo"] == "bar"
        assert args["int1"] == 1
        assert args["int2"] == 2


class ArgumentTest(object):
    def test_name(self):
        arg = Argument("foo")
        assert arg.name == "foo"

    def test_dest(self):
        arg = Argument("foo", dest="foobar")
        assert arg.dest == "foobar"

    def test_location_url(self):
        arg = Argument("foo", location="url")
        assert arg.location == "url"

    def test_location_url_list(self):
        arg = Argument("foo", location=["url"])
        assert arg.location == ["url"]

    def test_location_header(self):
        arg = Argument("foo", location="headers")
        assert arg.location == "headers"

    def test_location_json(self):
        arg = Argument("foo", location="json")
        assert arg.location == "json"

    def test_location_get_json(self):
        arg = Argument("foo", location="get_json")
        assert arg.location == "get_json"

    def test_location_header_list(self):
        arg = Argument("foo", location=["headers"])
        assert arg.location == ["headers"]

    def test_type(self):
        arg = Argument("foo", type=int)
        assert arg.type == int

    def test_default(self):
        arg = Argument("foo", default=True)
        assert arg.default is True

    def test_default_help(self):
        arg = Argument("foo")
        assert arg.help is None

    def test_required(self):
        arg = Argument("foo", required=True)
        assert arg.required is True

    def test_ignore(self):
        arg = Argument("foo", ignore=True)
        assert arg.ignore is True

    def test_operator(self):
        arg = Argument("foo", operators=[">=", "<=", "="])
        assert arg.operators == [">=", "<=", "="]

    def test_action_filter(self):
        arg = Argument("foo", action="filter")
        assert arg.action == "filter"

    def test_action(self):
        arg = Argument("foo", action="append")
        assert arg.action == "append"

    def test_choices(self):
        arg = Argument("foo", choices=[1, 2])
        assert arg.choices == [1, 2]

    def test_default_dest(self):
        arg = Argument("foo")
        assert arg.dest is None

    def test_default_operators(self):
        arg = Argument("foo")
        assert arg.operators[0] == "="
        assert len(arg.operators) == 1

    def test_default_type(self):
        arg = Argument("foo")
        sentinel = 666
        assert arg.type(sentinel) == "666"

    def test_default_default(self):
        arg = Argument("foo")
        assert arg.default is None

    def test_required_default(self):
        arg = Argument("foo")
        assert arg.required is False

    def test_ignore_default(self):
        arg = Argument("foo")
        assert arg.ignore is False

    def test_action_default(self):
        arg = Argument("foo")
        assert arg.action == "store"

    def test_choices_default(self):
        arg = Argument("foo")
        assert len(arg.choices) == 0

    def test_source(self, mocker):
        req = mocker.Mock(["args", "headers", "values"])
        req.args = {"foo": "bar"}
        req.headers = {"baz": "bat"}
        arg = Argument("foo", location=["args"])
        assert arg.source(req) == MultiDict(req.args)

        arg = Argument("foo", location=["headers"])
        assert arg.source(req) == MultiDict(req.headers)

    def test_convert_default_type_with_null_input(self):
        arg = Argument("foo")
        assert arg.convert(None, None) is None

    def test_convert_with_null_input_when_not_nullable(self):
        arg = Argument("foo", nullable=False)
        pytest.raises(ValueError, lambda: arg.convert(None, None))

    def test_source_bad_location(self, mocker):
        req = mocker.Mock(["values"])
        arg = Argument("foo", location=["foo"])
        assert len(arg.source(req)) == 0  # yes, basically you don't find it

    def test_source_default_location(self, mocker):
        req = mocker.Mock(["values", "get_json"])
        req.get_json.return_value = None
        req._get_child_mock = lambda **kwargs: MultiDict()
        arg = Argument("foo")
        assert arg.source(req) == req.values

    def test_option_case_sensitive(self):
        arg = Argument("foo", choices=["bar", "baz"], case_sensitive=True)
        assert arg.case_sensitive is True

        # Insensitive
        arg = Argument("foo", choices=["bar", "baz"], case_sensitive=False)
        assert arg.case_sensitive is False

        # Default
        arg = Argument("foo", choices=["bar", "baz"])
        assert arg.case_sensitive is True


class RequestParserSchemaTest(object):
    def test_empty_parser(self):
        parser = RequestParser()
        assert parser.__schema__ == []

    def test_primitive_types(self):
        parser = RequestParser()
        parser.add_argument("int", type=int, help="Some integer")
        parser.add_argument("str", type=str, help="Some string")
        parser.add_argument("float", type=float, help="Some float")

        assert parser.__schema__ == [
            {
                "description": "Some integer",
                "type": "integer",
                "name": "int",
                "in": "query",
            },
            {
                "description": "Some string",
                "type": "string",
                "name": "str",
                "in": "query",
            },
            {
                "description": "Some float",
                "type": "number",
                "name": "float",
                "in": "query",
            },
        ]

    def test_unknown_type(self):
        parser = RequestParser()
        parser.add_argument("unknown", type=lambda v: v)
        assert parser.__schema__ == [
            {
                "name": "unknown",
                "type": "string",
                "in": "query",
            }
        ]

    def test_required(self):
        parser = RequestParser()
        parser.add_argument("int", type=int, required=True)
        assert parser.__schema__ == [
            {
                "name": "int",
                "type": "integer",
                "in": "query",
                "required": True,
            }
        ]

    def test_default(self):
        parser = RequestParser()
        parser.add_argument("int", type=int, default=5)
        assert parser.__schema__ == [
            {
                "name": "int",
                "type": "integer",
                "in": "query",
                "default": 5,
            }
        ]

    def test_default_as_false(self):
        parser = RequestParser()
        parser.add_argument("bool", type=inputs.boolean, default=False)
        assert parser.__schema__ == [
            {
                "name": "bool",
                "type": "boolean",
                "in": "query",
                "default": False,
            }
        ]

    def test_choices(self):
        parser = RequestParser()
        parser.add_argument("string", type=str, choices=["a", "b"])
        assert parser.__schema__ == [
            {
                "name": "string",
                "type": "string",
                "in": "query",
                "enum": ["a", "b"],
            }
        ]

    def test_location(self):
        parser = RequestParser()
        parser.add_argument("default", type=int)
        parser.add_argument("in_values", type=int, location="values")
        parser.add_argument("in_query", type=int, location="args")
        parser.add_argument("in_headers", type=int, location="headers")
        parser.add_argument("in_cookie", type=int, location="cookie")
        assert parser.__schema__ == [
            {
                "name": "default",
                "type": "integer",
                "in": "query",
            },
            {
                "name": "in_values",
                "type": "integer",
                "in": "query",
            },
            {
                "name": "in_query",
                "type": "integer",
                "in": "query",
            },
            {
                "name": "in_headers",
                "type": "integer",
                "in": "header",
            },
        ]

    def test_location_json(self):
        parser = RequestParser()
        parser.add_argument("in_json", type=str, location="json")
        assert parser.__schema__ == [
            {
                "name": "in_json",
                "type": "string",
                "in": "body",
            }
        ]

    def test_location_form(self):
        parser = RequestParser()
        parser.add_argument("in_form", type=int, location="form")
        assert parser.__schema__ == [
            {
                "name": "in_form",
                "type": "integer",
                "in": "formData",
            }
        ]

    def test_location_files(self):
        parser = RequestParser()
        parser.add_argument("in_files", type=FileStorage, location="files")
        assert parser.__schema__ == [
            {
                "name": "in_files",
                "type": "file",
                "in": "formData",
            }
        ]

    def test_form_and_body_location(self):
        parser = RequestParser()
        parser.add_argument("default", type=int)
        parser.add_argument("in_form", type=int, location="form")
        parser.add_argument("in_json", type=str, location="json")
        with pytest.raises(SpecsError) as cm:
            parser.__schema__

        assert cm.value.msg == "Can't use formData and body at the same time"

    def test_files_and_body_location(self):
        parser = RequestParser()
        parser.add_argument("default", type=int)
        parser.add_argument("in_files", type=FileStorage, location="files")
        parser.add_argument("in_json", type=str, location="json")
        with pytest.raises(SpecsError) as cm:
            parser.__schema__

        assert cm.value.msg == "Can't use formData and body at the same time"

    def test_models(self):
        todo_fields = Model(
            "Todo",
            {"task": fields.String(required=True, description="The task details")},
        )
        parser = RequestParser()
        parser.add_argument("todo", type=todo_fields)
        assert parser.__schema__ == [
            {
                "name": "todo",
                "type": "Todo",
                "in": "body",
            }
        ]

    def test_lists(self):
        parser = RequestParser()
        parser.add_argument("int", type=int, action="append")
        assert parser.__schema__ == [
            {
                "name": "int",
                "in": "query",
                "type": "array",
                "collectionFormat": "multi",
                "items": {"type": "integer"},
            }
        ]

    def test_split_lists(self):
        parser = RequestParser()
        parser.add_argument("int", type=int, action="split")
        assert parser.__schema__ == [
            {
                "name": "int",
                "in": "query",
                "type": "array",
                "collectionFormat": "csv",
                "items": {"type": "integer"},
            }
        ]

    def test_schema_interface(self):
        def custom(value):
            pass

        custom.__schema__ = {
            "type": "string",
            "format": "custom-format",
        }

        parser = RequestParser()
        parser.add_argument("custom", type=custom)

        assert parser.__schema__ == [
            {
                "name": "custom",
                "in": "query",
                "type": "string",
                "format": "custom-format",
            }
        ]

    def test_callable_default(self):
        parser = RequestParser()
        parser.add_argument("int", type=int, default=lambda: 5)
        assert parser.__schema__ == [
            {
                "name": "int",
                "type": "integer",
                "in": "query",
                "default": 5,
            }
        ]
