import pytest

from textwrap import dedent

from flask import url_for, Blueprint
from werkzeug.datastructures import FileStorage

import flask_restx as restx

from flask_restx import inputs


class SwaggerTest(object):
    def test_specs_endpoint(self, api, client):
        data = client.get_specs("")
        assert data["swagger"] == "2.0"
        assert data["basePath"] == "/"
        assert data["produces"] == ["application/json"]
        assert data["consumes"] == ["application/json"]
        assert data["paths"] == {}
        assert "info" in data

    @pytest.mark.api(prefix="/api")
    def test_specs_endpoint_with_prefix(self, api, client):
        data = client.get_specs("/api")
        assert data["swagger"] == "2.0"
        assert data["basePath"] == "/api"
        assert data["produces"] == ["application/json"]
        assert data["consumes"] == ["application/json"]
        assert data["paths"] == {}
        assert "info" in data

    def test_specs_endpoint_produces(self, api, client):
        def output_xml(data, code, headers=None):
            pass

        api.representations["application/xml"] = output_xml

        data = client.get_specs()
        assert len(data["produces"]) == 2
        assert "application/json" in data["produces"]
        assert "application/xml" in data["produces"]

    def test_specs_endpoint_info(self, app, client):
        api = restx.Api(
            version="1.0",
            title="My API",
            description="This is a testing API",
            terms_url="http://somewhere.com/terms/",
            contact="Support",
            contact_url="http://support.somewhere.com",
            contact_email="contact@somewhere.com",
            license="Apache 2.0",
            license_url="http://www.apache.org/licenses/LICENSE-2.0.html",
        )
        api.init_app(app)

        data = client.get_specs()
        assert data["swagger"] == "2.0"
        assert data["basePath"] == "/"
        assert data["produces"] == ["application/json"]
        assert data["paths"] == {}

        assert "info" in data
        assert data["info"]["title"] == "My API"
        assert data["info"]["version"] == "1.0"
        assert data["info"]["description"] == "This is a testing API"
        assert data["info"]["termsOfService"] == "http://somewhere.com/terms/"
        assert data["info"]["contact"] == {
            "name": "Support",
            "url": "http://support.somewhere.com",
            "email": "contact@somewhere.com",
        }
        assert data["info"]["license"] == {
            "name": "Apache 2.0",
            "url": "http://www.apache.org/licenses/LICENSE-2.0.html",
        }

    def test_specs_endpoint_info_delayed(self, app, client):
        api = restx.Api(version="1.0")
        api.init_app(
            app,
            title="My API",
            description="This is a testing API",
            terms_url="http://somewhere.com/terms/",
            contact="Support",
            contact_url="http://support.somewhere.com",
            contact_email="contact@somewhere.com",
            license="Apache 2.0",
            license_url="http://www.apache.org/licenses/LICENSE-2.0.html",
        )

        data = client.get_specs()

        assert data["swagger"] == "2.0"
        assert data["basePath"] == "/"
        assert data["produces"] == ["application/json"]
        assert data["paths"] == {}

        assert "info" in data
        assert data["info"]["title"] == "My API"
        assert data["info"]["version"] == "1.0"
        assert data["info"]["description"] == "This is a testing API"
        assert data["info"]["termsOfService"] == "http://somewhere.com/terms/"
        assert data["info"]["contact"] == {
            "name": "Support",
            "url": "http://support.somewhere.com",
            "email": "contact@somewhere.com",
        }
        assert data["info"]["license"] == {
            "name": "Apache 2.0",
            "url": "http://www.apache.org/licenses/LICENSE-2.0.html",
        }

    def test_specs_endpoint_info_callable(self, app, client):
        api = restx.Api(
            version=lambda: "1.0",
            title=lambda: "My API",
            description=lambda: "This is a testing API",
            terms_url=lambda: "http://somewhere.com/terms/",
            contact=lambda: "Support",
            contact_url=lambda: "http://support.somewhere.com",
            contact_email=lambda: "contact@somewhere.com",
            license=lambda: "Apache 2.0",
            license_url=lambda: "http://www.apache.org/licenses/LICENSE-2.0.html",
        )
        api.init_app(app)

        data = client.get_specs()
        assert data["swagger"] == "2.0"
        assert data["basePath"] == "/"
        assert data["produces"] == ["application/json"]
        assert data["paths"] == {}

        assert "info" in data
        assert data["info"]["title"] == "My API"
        assert data["info"]["version"] == "1.0"
        assert data["info"]["description"] == "This is a testing API"
        assert data["info"]["termsOfService"] == "http://somewhere.com/terms/"
        assert data["info"]["contact"] == {
            "name": "Support",
            "url": "http://support.somewhere.com",
            "email": "contact@somewhere.com",
        }
        assert data["info"]["license"] == {
            "name": "Apache 2.0",
            "url": "http://www.apache.org/licenses/LICENSE-2.0.html",
        }

    def test_specs_endpoint_no_host(self, app, client):
        restx.Api(app)

        data = client.get_specs("")
        assert "host" not in data
        assert data["basePath"] == "/"

    @pytest.mark.options(server_name="api.restx.org")
    def test_specs_endpoint_host(self, app, client):
        # app.config['SERVER_NAME'] = 'api.restx.org'
        restx.Api(app)

        data = client.get_specs("")
        assert data["host"] == "api.restx.org"
        assert data["basePath"] == "/"

    @pytest.mark.options(server_name="api.restx.org")
    def test_specs_endpoint_host_with_url_prefix(self, app, client):
        blueprint = Blueprint("api", __name__, url_prefix="/api/1")
        restx.Api(blueprint)
        app.register_blueprint(blueprint)

        data = client.get_specs("/api/1")
        assert data["host"] == "api.restx.org"
        assert data["basePath"] == "/api/1"

    @pytest.mark.options(server_name="restx.org")
    def test_specs_endpoint_host_and_subdomain(self, app, client):
        blueprint = Blueprint("api", __name__, subdomain="api")
        restx.Api(blueprint)
        app.register_blueprint(blueprint)

        data = client.get_specs(base_url="http://api.restx.org")
        assert data["host"] == "api.restx.org"
        assert data["basePath"] == "/"

    def test_specs_endpoint_tags_short(self, app, client):
        restx.Api(app, tags=["tag-1", "tag-2", "tag-3"])

        data = client.get_specs("")
        assert data["tags"] == [{"name": "tag-1"}, {"name": "tag-2"}, {"name": "tag-3"}]

    def test_specs_endpoint_tags_tuple(self, app, client):
        restx.Api(
            app,
            tags=[
                ("tag-1", "Tag 1"),
                ("tag-2", "Tag 2"),
                ("tag-3", "Tag 3"),
            ],
        )

        data = client.get_specs("")
        assert data["tags"] == [
            {"name": "tag-1", "description": "Tag 1"},
            {"name": "tag-2", "description": "Tag 2"},
            {"name": "tag-3", "description": "Tag 3"},
        ]

    def test_specs_endpoint_tags_dict(self, app, client):
        restx.Api(
            app,
            tags=[
                {"name": "tag-1", "description": "Tag 1"},
                {"name": "tag-2", "description": "Tag 2"},
                {"name": "tag-3", "description": "Tag 3"},
            ],
        )

        data = client.get_specs("")
        assert data["tags"] == [
            {"name": "tag-1", "description": "Tag 1"},
            {"name": "tag-2", "description": "Tag 2"},
            {"name": "tag-3", "description": "Tag 3"},
        ]

    @pytest.mark.api(tags=["ns", "tag"])
    def test_specs_endpoint_tags_namespaces(self, api, client):
        api.namespace("ns", "Description")

        data = client.get_specs("")
        assert data["tags"] == [{"name": "ns"}, {"name": "tag"}]

    def test_specs_endpoint_invalid_tags(self, app, client):
        api = restx.Api(app, tags=[{"description": "Tag 1"}])

        client.get_specs("", status=500)

        assert list(api.__schema__.keys()) == ["error"]

    def test_specs_endpoint_default_ns_with_resources(self, app, client):
        restx.Api(app)
        data = client.get_specs("")
        assert data["tags"] == []

    def test_specs_endpoint_default_ns_without_resources(self, app, client):
        api = restx.Api(app)

        @api.route("/test", endpoint="test")
        class TestResource(restx.Resource):
            def get(self):
                return {}

        data = client.get_specs("")
        assert data["tags"] == [{"name": "default", "description": "Default namespace"}]

    def test_specs_endpoint_default_ns_with_specified_ns(self, app, client):
        api = restx.Api(app)
        ns = api.namespace("ns", "Test namespace")

        @ns.route("/test2", endpoint="test2")
        @api.route("/test", endpoint="test")
        class TestResource(restx.Resource):
            def get(self):
                return {}

        data = client.get_specs("")
        assert data["tags"] == [
            {"name": "default", "description": "Default namespace"},
            {"name": "ns", "description": "Test namespace"},
        ]

    def test_specs_endpoint_specified_ns_without_default_ns(self, app, client):
        api = restx.Api(app)
        ns = api.namespace("ns", "Test namespace")

        @ns.route("/", endpoint="test2")
        class TestResource(restx.Resource):
            def get(self):
                return {}

        data = client.get_specs("")
        assert data["tags"] == [{"name": "ns", "description": "Test namespace"}]

    def test_specs_endpoint_namespace_without_description(self, app, client):
        api = restx.Api(app)
        ns = api.namespace("ns")

        @ns.route("/test", endpoint="test")
        class TestResource(restx.Resource):
            def get(self):
                return {}

        data = client.get_specs("")
        assert data["tags"] == [{"name": "ns"}]

    def test_specs_endpoint_namespace_all_resources_hidden(self, app, client):
        api = restx.Api(app)
        ns = api.namespace("ns")

        @ns.route("/test", endpoint="test", doc=False)
        class TestResource(restx.Resource):
            def get(self):
                return {}

        @ns.route("/test2", endpoint="test2")
        @ns.hide
        class TestResource2(restx.Resource):
            def get(self):
                return {}

        @ns.route("/test3", endpoint="test3")
        @ns.doc(False)
        class TestResource3(restx.Resource):
            def get(self):
                return {}

        data = client.get_specs("")
        assert data["tags"] == []

    def test_specs_authorizations(self, app, client):
        authorizations = {"apikey": {"type": "apiKey", "in": "header", "name": "X-API"}}
        restx.Api(app, authorizations=authorizations)

        data = client.get_specs()

        assert "securityDefinitions" in data
        assert data["securityDefinitions"] == authorizations

    @pytest.mark.api(prefix="/api")
    def test_minimal_documentation(self, api, client):
        ns = api.namespace("ns", "Test namespace")

        @ns.route("/", endpoint="test")
        class TestResource(restx.Resource):
            def get(self):
                return {}

        data = client.get_specs("/api")
        paths = data["paths"]
        assert len(paths.keys()) == 1

        assert "/ns/" in paths
        assert "get" in paths["/ns/"]
        op = paths["/ns/"]["get"]
        assert op["tags"] == ["ns"]
        assert op["operationId"] == "get_test_resource"
        assert "parameters" not in op
        assert "summary" not in op
        assert "description" not in op
        assert op["responses"] == {
            "200": {
                "description": "Success",
            }
        }

        assert url_for("api.test") == "/api/ns/"

    @pytest.mark.api(prefix="/api", version="1.0")
    def test_default_ns_resource_documentation(self, api, client):
        @api.route("/test/", endpoint="test")
        class TestResource(restx.Resource):
            def get(self):
                return {}

        data = client.get_specs("/api")
        paths = data["paths"]
        assert len(paths.keys()) == 1

        assert "/test/" in paths
        assert "get" in paths["/test/"]
        op = paths["/test/"]["get"]
        assert op["tags"] == ["default"]
        assert op["responses"] == {
            "200": {
                "description": "Success",
            }
        }

        assert len(data["tags"]) == 1
        tag = data["tags"][0]
        assert tag["name"] == "default"
        assert tag["description"] == "Default namespace"

        assert url_for("api.test") == "/api/test/"

    @pytest.mark.api(default="site", default_label="Site namespace")
    def test_default_ns_resource_documentation_with_override(self, api, client):
        @api.route("/test/", endpoint="test")
        class TestResource(restx.Resource):
            def get(self):
                return {}

        data = client.get_specs()
        paths = data["paths"]
        assert len(paths.keys()) == 1

        assert "/test/" in paths
        assert "get" in paths["/test/"]
        op = paths["/test/"]["get"]
        assert op["tags"] == ["site"]
        assert op["responses"] == {
            "200": {
                "description": "Success",
            }
        }

        assert len(data["tags"]) == 1
        tag = data["tags"][0]
        assert tag["name"] == "site"
        assert tag["description"] == "Site namespace"

        assert url_for("api.test") == "/test/"

    @pytest.mark.api(prefix="/api")
    def test_ns_resource_documentation(self, api, client):
        ns = api.namespace("ns", "Test namespace")

        @ns.route("/", endpoint="test")
        class TestResource(restx.Resource):
            def get(self):
                return {}

        data = client.get_specs("/api")
        paths = data["paths"]
        assert len(paths.keys()) == 1

        assert "/ns/" in paths
        assert "get" in paths["/ns/"]
        op = paths["/ns/"]["get"]
        assert op["tags"] == ["ns"]
        assert op["responses"] == {
            "200": {
                "description": "Success",
            }
        }
        assert "parameters" not in op

        assert len(data["tags"]) == 1
        tag = data["tags"][-1]
        assert tag["name"] == "ns"
        assert tag["description"] == "Test namespace"

        assert url_for("api.test") == "/api/ns/"

    def test_ns_resource_documentation_lazy(self, app, client):
        api = restx.Api()
        ns = api.namespace("ns", "Test namespace")

        @ns.route("/", endpoint="test")
        class TestResource(restx.Resource):
            def get(self):
                return {}

        api.init_app(app)

        data = client.get_specs()
        paths = data["paths"]
        assert len(paths.keys()) == 1

        assert "/ns/" in paths
        assert "get" in paths["/ns/"]
        op = paths["/ns/"]["get"]
        assert op["tags"] == ["ns"]
        assert op["responses"] == {
            "200": {
                "description": "Success",
            }
        }

        assert len(data["tags"]) == 1
        tag = data["tags"][-1]
        assert tag["name"] == "ns"
        assert tag["description"] == "Test namespace"

        assert url_for("test") == "/ns/"

    def test_methods_docstring_to_summary(self, api, client):
        @api.route("/test/", endpoint="test")
        class TestResource(restx.Resource):
            def get(self):
                """
                GET operation
                """
                return {}

            def post(self):
                """POST operation.

                Should be ignored
                """
                return {}

            def put(self):
                """PUT operation. Should be ignored"""
                return {}

            def delete(self):
                """
                DELETE operation.
                Should be ignored.
                """
                return {}

        data = client.get_specs()
        path = data["paths"]["/test/"]

        assert len(path.keys()) == 4

        for method in path.keys():
            operation = path[method]
            assert method in ("get", "post", "put", "delete")
            assert operation["summary"] == "{0} operation".format(method.upper())
            assert operation["operationId"] == "{0}_test_resource".format(
                method.lower()
            )
            # assert operation['parameters'] == []

    def test_path_parameter_no_type(self, api, client):
        @api.route("/id/<id>/", endpoint="by-id")
        class ByIdResource(restx.Resource):
            def get(self, id):
                return {}

        data = client.get_specs()
        assert "/id/{id}/" in data["paths"]

        path = data["paths"]["/id/{id}/"]
        assert len(path["parameters"]) == 1

        parameter = path["parameters"][0]
        assert parameter["name"] == "id"
        assert parameter["type"] == "string"
        assert parameter["in"] == "path"
        assert parameter["required"] is True

    def test_path_parameter_with_type(self, api, client):
        @api.route("/name/<int:age>/", endpoint="by-name")
        class ByNameResource(restx.Resource):
            def get(self, age):
                return {}

        data = client.get_specs()
        assert "/name/{age}/" in data["paths"]

        path = data["paths"]["/name/{age}/"]
        assert len(path["parameters"]) == 1

        parameter = path["parameters"][0]
        assert parameter["name"] == "age"
        assert parameter["type"] == "integer"
        assert parameter["in"] == "path"
        assert parameter["required"] is True

    def test_path_parameter_with_type_with_argument(self, api, client):
        @api.route("/name/<string(length=2):id>/", endpoint="by-name")
        class ByNameResource(restx.Resource):
            def get(self, id):
                return {}

        data = client.get_specs()
        assert "/name/{id}/" in data["paths"]

        path = data["paths"]["/name/{id}/"]
        assert len(path["parameters"]) == 1

        parameter = path["parameters"][0]
        assert parameter["name"] == "id"
        assert parameter["type"] == "string"
        assert parameter["in"] == "path"
        assert parameter["required"] is True

    def test_path_parameter_with_explicit_details(self, api, client):
        @api.route(
            "/name/<int:age>/",
            endpoint="by-name",
            doc={"params": {"age": {"description": "An age"}}},
        )
        class ByNameResource(restx.Resource):
            def get(self, age):
                return {}

        data = client.get_specs()
        assert "/name/{age}/" in data["paths"]

        path = data["paths"]["/name/{age}/"]
        assert len(path["parameters"]) == 1

        parameter = path["parameters"][0]
        assert parameter["name"] == "age"
        assert parameter["type"] == "integer"
        assert parameter["in"] == "path"
        assert parameter["required"] is True
        assert parameter["description"] == "An age"

    def test_path_parameter_with_decorator_details(self, api, client):
        @api.route("/name/<int:age>/")
        @api.param("age", "An age")
        class ByNameResource(restx.Resource):
            def get(self, age):
                return {}

        data = client.get_specs()
        assert "/name/{age}/" in data["paths"]

        path = data["paths"]["/name/{age}/"]
        assert len(path["parameters"]) == 1

        parameter = path["parameters"][0]
        assert parameter["name"] == "age"
        assert parameter["type"] == "integer"
        assert parameter["in"] == "path"
        assert parameter["required"] is True
        assert parameter["description"] == "An age"

    def test_expect_parser(self, api, client):
        parser = api.parser()
        parser.add_argument("param", type=int, help="Some param")
        parser.add_argument("jsonparam", type=str, location="json", help="Some param")

        @api.route("/with-parser/", endpoint="with-parser")
        class WithParserResource(restx.Resource):
            @api.expect(parser)
            def get(self):
                return {}

        data = client.get_specs()
        assert "/with-parser/" in data["paths"]

        op = data["paths"]["/with-parser/"]["get"]
        assert len(op["parameters"]) == 2

        parameter = [o for o in op["parameters"] if o["in"] == "query"][0]
        assert parameter["name"] == "param"
        assert parameter["type"] == "integer"
        assert parameter["in"] == "query"
        assert parameter["description"] == "Some param"

        parameter = [o for o in op["parameters"] if o["in"] == "body"][0]
        assert parameter["name"] == "payload"
        assert parameter["required"]
        assert parameter["in"] == "body"
        assert parameter["schema"]["properties"]["jsonparam"]["type"] == "string"

    def test_expect_parser_on_class(self, api, client):
        parser = api.parser()
        parser.add_argument("param", type=int, help="Some param")

        @api.route("/with-parser/", endpoint="with-parser")
        @api.expect(parser)
        class WithParserResource(restx.Resource):
            def get(self):
                return {}

        data = client.get_specs()
        assert "/with-parser/" in data["paths"]

        path = data["paths"]["/with-parser/"]
        assert len(path["parameters"]) == 1

        parameter = path["parameters"][0]
        assert parameter["name"] == "param"
        assert parameter["type"] == "integer"
        assert parameter["in"] == "query"
        assert parameter["description"] == "Some param"

    def test_method_parser_on_class(self, api, client):
        parser = api.parser()
        parser.add_argument("param", type=int, help="Some param")

        @api.route("/with-parser/", endpoint="with-parser")
        @api.doc(get={"expect": parser})
        class WithParserResource(restx.Resource):
            def get(self):
                return {}

            def post(self):
                return {}

        data = client.get_specs()
        assert "/with-parser/" in data["paths"]

        op = data["paths"]["/with-parser/"]["get"]
        assert len(op["parameters"]) == 1

        parameter = op["parameters"][0]
        assert parameter["name"] == "param"
        assert parameter["type"] == "integer"
        assert parameter["in"] == "query"
        assert parameter["description"] == "Some param"

        op = data["paths"]["/with-parser/"]["post"]
        assert "parameters" not in op

    def test_parser_parameters_override(self, api, client):
        parser = api.parser()
        parser.add_argument("param", type=int, help="Some param")

        @api.route("/with-parser/", endpoint="with-parser")
        class WithParserResource(restx.Resource):
            @api.expect(parser)
            @api.doc(params={"param": {"description": "New description"}})
            def get(self):
                return {}

        data = client.get_specs()
        assert "/with-parser/" in data["paths"]

        op = data["paths"]["/with-parser/"]["get"]
        assert len(op["parameters"]) == 1

        parameter = op["parameters"][0]
        assert parameter["name"] == "param"
        assert parameter["type"] == "integer"
        assert parameter["in"] == "query"
        assert parameter["description"] == "New description"

    def test_parser_parameter_in_form(self, api, client):
        parser = api.parser()
        parser.add_argument("param", type=int, help="Some param", location="form")

        @api.route("/with-parser/", endpoint="with-parser")
        class WithParserResource(restx.Resource):
            @api.expect(parser)
            def get(self):
                return {}

        data = client.get_specs()
        assert "/with-parser/" in data["paths"]

        op = data["paths"]["/with-parser/"]["get"]
        assert len(op["parameters"]) == 1

        parameter = op["parameters"][0]
        assert parameter["name"] == "param"
        assert parameter["type"] == "integer"
        assert parameter["in"] == "formData"
        assert parameter["description"] == "Some param"

        assert op["consumes"] == [
            "application/x-www-form-urlencoded",
            "multipart/form-data",
        ]

    def test_parser_parameter_in_files(self, api, client):
        parser = api.parser()
        parser.add_argument("in_files", type=FileStorage, location="files")

        @api.route("/with-parser/", endpoint="with-parser")
        class WithParserResource(restx.Resource):
            @api.expect(parser)
            def get(self):
                return {}

        data = client.get_specs()
        assert "/with-parser/" in data["paths"]

        op = data["paths"]["/with-parser/"]["get"]
        assert len(op["parameters"]) == 1

        parameter = op["parameters"][0]
        assert parameter["name"] == "in_files"
        assert parameter["type"] == "file"
        assert parameter["in"] == "formData"

        assert op["consumes"] == ["multipart/form-data"]

    def test_parser_parameter_in_files_on_class(self, api, client):
        parser = api.parser()
        parser.add_argument("in_files", type=FileStorage, location="files")

        @api.route("/with-parser/", endpoint="with-parser")
        @api.expect(parser)
        class WithParserResource(restx.Resource):
            def get(self):
                return {}

        data = client.get_specs()
        assert "/with-parser/" in data["paths"]

        path = data["paths"]["/with-parser/"]
        assert len(path["parameters"]) == 1

        parameter = path["parameters"][0]
        assert parameter["name"] == "in_files"
        assert parameter["type"] == "file"
        assert parameter["in"] == "formData"

        assert "consumes" not in path

        op = path["get"]
        assert "consumes" in op
        assert op["consumes"] == ["multipart/form-data"]

    def test_explicit_parameters(self, api, client):
        @api.route("/name/<int:age>/", endpoint="by-name")
        class ByNameResource(restx.Resource):
            @api.doc(
                params={
                    "q": {
                        "type": "string",
                        "in": "query",
                        "description": "A query string",
                    }
                }
            )
            def get(self, age):
                return {}

        data = client.get_specs()
        assert "/name/{age}/" in data["paths"]

        path = data["paths"]["/name/{age}/"]
        assert len(path["parameters"]) == 1

        parameter = path["parameters"][0]
        assert parameter["name"] == "age"
        assert parameter["type"] == "integer"
        assert parameter["in"] == "path"
        assert parameter["required"] is True

        op = path["get"]
        assert len(op["parameters"]) == 1

        parameter = op["parameters"][0]
        assert parameter["name"] == "q"
        assert parameter["type"] == "string"
        assert parameter["in"] == "query"
        assert parameter["description"] == "A query string"

    def test_explicit_parameters_with_decorator(self, api, client):
        @api.route("/name/")
        class ByNameResource(restx.Resource):
            @api.param("q", "A query string", type="string", _in="formData")
            def get(self, age):
                return {}

        data = client.get_specs()
        assert "/name/" in data["paths"]

        op = data["paths"]["/name/"]["get"]
        assert len(op["parameters"]) == 1

        parameter = op["parameters"][0]
        assert parameter["name"] == "q"
        assert parameter["type"] == "string"
        assert parameter["in"] == "formData"
        assert parameter["description"] == "A query string"

    def test_class_explicit_parameters(self, api, client):
        @api.route(
            "/name/<int:age>/",
            endpoint="by-name",
            doc={
                "params": {
                    "q": {
                        "type": "string",
                        "in": "query",
                        "description": "A query string",
                    }
                }
            },
        )
        class ByNameResource(restx.Resource):
            def get(self, age):
                return {}

        data = client.get_specs()
        assert "/name/{age}/" in data["paths"]

        path = data["paths"]["/name/{age}/"]
        assert len(path["parameters"]) == 2

        by_name = dict((p["name"], p) for p in path["parameters"])

        parameter = by_name["age"]
        assert parameter["name"] == "age"
        assert parameter["type"] == "integer"
        assert parameter["in"] == "path"
        assert parameter["required"] is True

        parameter = by_name["q"]
        assert parameter["name"] == "q"
        assert parameter["type"] == "string"
        assert parameter["in"] == "query"
        assert parameter["description"] == "A query string"

    def test_explicit_parameters_override(self, api, client):
        @api.route(
            "/name/<int:age>/",
            endpoint="by-name",
            doc={
                "params": {
                    "q": {
                        "type": "string",
                        "in": "query",
                        "description": "Overriden description",
                    },
                    "age": {"description": "An age"},
                }
            },
        )
        class ByNameResource(restx.Resource):
            @api.doc(params={"q": {"description": "A query string"}})
            def get(self, age):
                return {}

            def post(self, age):
                pass

        data = client.get_specs()
        assert "/name/{age}/" in data["paths"]

        path = data["paths"]["/name/{age}/"]
        assert len(path["parameters"]) == 1

        by_name = dict((p["name"], p) for p in path["parameters"])

        parameter = by_name["age"]
        assert parameter["name"] == "age"
        assert parameter["type"] == "integer"
        assert parameter["in"] == "path"
        assert parameter["required"] is True
        assert parameter["description"] == "An age"

        # Don't duplicate parameters
        assert "q" not in by_name

        get = data["paths"]["/name/{age}/"]["get"]
        assert len(get["parameters"]) == 1

        parameter = get["parameters"][0]
        assert parameter["name"] == "q"
        assert parameter["type"] == "string"
        assert parameter["in"] == "query"
        assert parameter["description"] == "A query string"

        post = data["paths"]["/name/{age}/"]["post"]
        assert len(post["parameters"]) == 1

        parameter = post["parameters"][0]
        assert parameter["name"] == "q"
        assert parameter["type"] == "string"
        assert parameter["in"] == "query"
        assert parameter["description"] == "Overriden description"

    def test_explicit_parameters_override_by_method(self, api, client):
        @api.route(
            "/name/<int:age>/",
            endpoint="by-name",
            doc={
                "get": {
                    "params": {
                        "q": {
                            "type": "string",
                            "in": "query",
                            "description": "A query string",
                        }
                    }
                },
                "params": {"age": {"description": "An age"}},
            },
        )
        class ByNameResource(restx.Resource):
            @api.doc(params={"age": {"description": "Overriden"}})
            def get(self, age):
                return {}

            def post(self, age):
                return {}

        data = client.get_specs()
        assert "/name/{age}/" in data["paths"]

        path = data["paths"]["/name/{age}/"]
        assert "parameters" not in path

        get = path["get"]
        assert len(get["parameters"]) == 2

        by_name = dict((p["name"], p) for p in get["parameters"])

        parameter = by_name["age"]
        assert parameter["name"] == "age"
        assert parameter["type"] == "integer"
        assert parameter["in"] == "path"
        assert parameter["required"] is True
        assert parameter["description"] == "Overriden"

        parameter = by_name["q"]
        assert parameter["name"] == "q"
        assert parameter["type"] == "string"
        assert parameter["in"] == "query"
        assert parameter["description"] == "A query string"

        post = path["post"]
        assert len(post["parameters"]) == 1

        by_name = dict((p["name"], p) for p in post["parameters"])

        parameter = by_name["age"]
        assert parameter["name"] == "age"
        assert parameter["type"] == "integer"
        assert parameter["in"] == "path"
        assert parameter["required"] is True
        assert parameter["description"] == "An age"

    def test_parameters_cascading_with_apidoc_false(self, api, client):
        @api.route(
            "/name/<int:age>/",
            endpoint="by-name",
            doc={
                "get": {
                    "params": {
                        "q": {
                            "type": "string",
                            "in": "query",
                            "description": "A query string",
                        }
                    }
                },
                "params": {"age": {"description": "An age"}},
            },
        )
        class ByNameResource(restx.Resource):
            @api.doc(params={"age": {"description": "Overriden"}})
            def get(self, age):
                return {}

            @api.doc(False)
            def post(self, age):
                return {}

        data = client.get_specs()
        assert "/name/{age}/" in data["paths"]

        path = data["paths"]["/name/{age}/"]
        assert "parameters" not in path

        get = path["get"]
        assert len(get["parameters"]) == 2

        by_name = dict((p["name"], p) for p in get["parameters"])
        assert "age" in by_name
        assert "q" in by_name

        assert "post" not in path

    def test_explicit_parameters_desription_shortcut(self, api, client):
        @api.route(
            "/name/<int:age>/",
            endpoint="by-name",
            doc={
                "get": {
                    "params": {
                        "q": "A query string",
                    }
                },
                "params": {"age": "An age"},
            },
        )
        class ByNameResource(restx.Resource):
            @api.doc(params={"age": "Overriden"})
            def get(self, age):
                return {}

            def post(self, age):
                return {}

        data = client.get_specs()
        assert "/name/{age}/" in data["paths"]

        path = data["paths"]["/name/{age}/"]
        assert "parameters" not in path

        get = path["get"]
        assert len(get["parameters"]) == 2

        by_name = dict((p["name"], p) for p in get["parameters"])

        parameter = by_name["age"]
        assert parameter["name"] == "age"
        assert parameter["type"] == "integer"
        assert parameter["in"] == "path"
        assert parameter["required"] is True
        assert parameter["description"] == "Overriden"

        parameter = by_name["q"]
        assert parameter["name"] == "q"
        assert parameter["type"] == "string"
        assert parameter["in"] == "query"
        assert parameter["description"] == "A query string"

        post = path["post"]
        assert len(post["parameters"]) == 1

        by_name = dict((p["name"], p) for p in post["parameters"])

        parameter = by_name["age"]
        assert parameter["name"] == "age"
        assert parameter["type"] == "integer"
        assert parameter["in"] == "path"
        assert parameter["required"] is True
        assert parameter["description"] == "An age"

        assert "q" not in by_name

    def test_explicit_parameters_native_types(self, api, client):
        @api.route("/types/", endpoint="native")
        class NativeTypesResource(restx.Resource):
            @api.doc(
                params={
                    "int": {
                        "type": int,
                        "in": "query",
                    },
                    "float": {
                        "type": float,
                        "in": "query",
                    },
                    "bool": {
                        "type": bool,
                        "in": "query",
                    },
                    "str": {
                        "type": str,
                        "in": "query",
                    },
                    "int-array": {
                        "type": [int],
                        "in": "query",
                    },
                    "float-array": {
                        "type": [float],
                        "in": "query",
                    },
                    "bool-array": {
                        "type": [bool],
                        "in": "query",
                    },
                    "str-array": {
                        "type": [str],
                        "in": "query",
                    },
                }
            )
            def get(self, age):
                return {}

        data = client.get_specs()

        op = data["paths"]["/types/"]["get"]

        parameters = dict((p["name"], p) for p in op["parameters"])

        assert parameters["int"]["type"] == "integer"
        assert parameters["float"]["type"] == "number"
        assert parameters["str"]["type"] == "string"
        assert parameters["bool"]["type"] == "boolean"

        assert parameters["int-array"]["type"] == "array"
        assert parameters["int-array"]["items"]["type"] == "integer"
        assert parameters["float-array"]["type"] == "array"
        assert parameters["float-array"]["items"]["type"] == "number"
        assert parameters["str-array"]["type"] == "array"
        assert parameters["str-array"]["items"]["type"] == "string"
        assert parameters["bool-array"]["type"] == "array"
        assert parameters["bool-array"]["items"]["type"] == "boolean"

    def test_response_on_method(self, api, client):
        api.model(
            "ErrorModel",
            {
                "message": restx.fields.String,
            },
        )

        @api.route("/test/")
        class ByNameResource(restx.Resource):
            @api.doc(
                responses={
                    404: "Not found",
                    405: ("Some message", "ErrorModel"),
                }
            )
            def get(self):
                return {}

        data = client.get_specs("")
        paths = data["paths"]
        assert len(paths.keys()) == 1

        op = paths["/test/"]["get"]
        assert op["tags"] == ["default"]
        assert op["responses"] == {
            "404": {
                "description": "Not found",
            },
            "405": {
                "description": "Some message",
                "schema": {
                    "$ref": "#/definitions/ErrorModel",
                },
            },
        }

        assert "definitions" in data
        assert "ErrorModel" in data["definitions"]

    def test_api_response(self, api, client):
        @api.route("/test/")
        class TestResource(restx.Resource):
            @api.response(200, "Success")
            def get(self):
                pass

        data = client.get_specs("")
        paths = data["paths"]

        op = paths["/test/"]["get"]
        assert op["responses"] == {
            "200": {
                "description": "Success",
            }
        }

    def test_api_response_multiple(self, api, client):
        @api.route("/test/")
        class TestResource(restx.Resource):
            @api.response(200, "Success")
            @api.response(400, "Validation error")
            def get(self):
                pass

        data = client.get_specs("")
        paths = data["paths"]

        op = paths["/test/"]["get"]
        assert op["responses"] == {
            "200": {
                "description": "Success",
            },
            "400": {
                "description": "Validation error",
            },
        }

    def test_api_response_with_model(self, api, client):
        model = api.model(
            "SomeModel",
            {
                "message": restx.fields.String,
            },
        )

        @api.route("/test/")
        class TestResource(restx.Resource):
            @api.response(200, "Success", model)
            def get(self):
                pass

        data = client.get_specs("")
        paths = data["paths"]

        op = paths["/test/"]["get"]
        assert op["responses"] == {
            "200": {
                "description": "Success",
                "schema": {
                    "$ref": "#/definitions/SomeModel",
                },
            }
        }

        assert "SomeModel" in data["definitions"]

    def test_api_response_default(self, api, client):
        @api.route("/test/")
        class TestResource(restx.Resource):
            @api.response("default", "Error")
            def get(self):
                pass

        data = client.get_specs("")
        paths = data["paths"]

        op = paths["/test/"]["get"]
        assert op["responses"] == {
            "default": {
                "description": "Error",
            }
        }

    def test_api_header(self, api, client):
        @api.route("/test/")
        @api.header("X-HEADER", "A class header")
        class TestResource(restx.Resource):
            @api.header(
                "X-HEADER-2", "Another header", type=[int], collectionFormat="csv"
            )
            @api.header("X-HEADER-3", type=int)
            @api.header("X-HEADER-4", type="boolean")
            def get(self):
                pass

        data = client.get_specs("")
        headers = data["paths"]["/test/"]["get"]["responses"]["200"]["headers"]

        assert "X-HEADER" in headers
        assert headers["X-HEADER"] == {
            "type": "string",
            "description": "A class header",
        }

        assert "X-HEADER-2" in headers
        assert headers["X-HEADER-2"] == {
            "type": "array",
            "items": {"type": "integer"},
            "description": "Another header",
            "collectionFormat": "csv",
        }

        assert "X-HEADER-3" in headers
        assert headers["X-HEADER-3"] == {"type": "integer"}

        assert "X-HEADER-4" in headers
        assert headers["X-HEADER-4"] == {"type": "boolean"}

    def test_response_header(self, api, client):
        @api.route("/test/")
        class TestResource(restx.Resource):
            @api.response(200, "Success")
            @api.response(400, "Validation", headers={"X-HEADER": "An header"})
            def get(self):
                pass

        data = client.get_specs("")
        headers = data["paths"]["/test/"]["get"]["responses"]["400"]["headers"]

        assert "X-HEADER" in headers
        assert headers["X-HEADER"] == {
            "type": "string",
            "description": "An header",
        }

    def test_api_and_response_header(self, api, client):
        @api.route("/test/")
        @api.header("X-HEADER", "A class header")
        class TestResource(restx.Resource):
            @api.header("X-HEADER-2", type=int)
            @api.response(200, "Success")
            @api.response(400, "Validation", headers={"X-ERROR": "An error header"})
            def get(self):
                pass

        data = client.get_specs("")
        headers200 = data["paths"]["/test/"]["get"]["responses"]["200"]["headers"]
        headers400 = data["paths"]["/test/"]["get"]["responses"]["400"]["headers"]

        for headers in (headers200, headers400):
            assert "X-HEADER" in headers
            assert "X-HEADER-2" in headers

        assert "X-ERROR" in headers400
        assert "X-ERROR" not in headers200

    def test_expect_header(self, api, client):
        parser = api.parser()
        parser.add_argument(
            "X-Header", location="headers", required=True, help="A required header"
        )
        parser.add_argument(
            "X-Header-2",
            location="headers",
            type=int,
            action="split",
            help="Another header",
        )
        parser.add_argument("X-Header-3", location="headers", type=int)
        parser.add_argument("X-Header-4", location="headers", type=inputs.boolean)

        @api.route("/test/")
        class TestResource(restx.Resource):
            @api.expect(parser)
            def get(self):
                pass

        data = client.get_specs("")
        parameters = data["paths"]["/test/"]["get"]["parameters"]

        def get_param(name):
            candidates = [p for p in parameters if p["name"] == name]
            assert len(candidates) == 1, "parameter {0} not found".format(name)
            return candidates[0]

        parameter = get_param("X-Header")
        assert parameter["type"] == "string"
        assert parameter["in"] == "header"
        assert parameter["required"] is True
        assert parameter["description"] == "A required header"

        parameter = get_param("X-Header-2")
        assert parameter["type"] == "array"
        assert parameter["in"] == "header"
        assert parameter["items"]["type"] == "integer"
        assert parameter["description"] == "Another header"
        assert parameter["collectionFormat"] == "csv"

        parameter = get_param("X-Header-3")
        assert parameter["type"] == "integer"
        assert parameter["in"] == "header"

        parameter = get_param("X-Header-4")
        assert parameter["type"] == "boolean"
        assert parameter["in"] == "header"

    def test_description(self, api, client):
        @api.route(
            "/description/",
            endpoint="description",
            doc={
                "description": "Parent description.",
                "delete": {"description": "A delete operation"},
            },
        )
        class ResourceWithDescription(restx.Resource):
            @api.doc(description="Some details")
            def get(self):
                return {}

            def post(self):
                """
                Do something.

                Extra description
                """
                return {}

            def put(self):
                """No description (only summary)"""

            def delete(self):
                """No description (only summary)"""

        @api.route("/descriptionless/", endpoint="descriptionless")
        class ResourceWithoutDescription(restx.Resource):
            def get(self):
                """No description (only summary)"""
                return {}

        data = client.get_specs()

        description = lambda m: data["paths"]["/description/"][m]["description"]  # noqa

        assert description("get") == dedent(
            """\
            Parent description.
            Some details"""
        )

        assert description("post") == dedent(
            """\
            Parent description.
            Extra description"""
        )

        assert description("delete") == dedent(
            """\
            Parent description.
            A delete operation"""
        )

        assert description("put") == "Parent description."
        assert "description" not in data["paths"]["/descriptionless/"]["get"]

    def test_operation_id(self, api, client):
        @api.route("/test/", endpoint="test")
        class TestResource(restx.Resource):
            @api.doc(id="get_objects")
            def get(self):
                return {}

            def post(self):
                return {}

        data = client.get_specs()
        path = data["paths"]["/test/"]

        assert path["get"]["operationId"] == "get_objects"
        assert path["post"]["operationId"] == "post_test_resource"

    def test_operation_id_shortcut(self, api, client):
        @api.route("/test/", endpoint="test")
        class TestResource(restx.Resource):
            @api.doc("get_objects")
            def get(self):
                return {}

        data = client.get_specs()
        path = data["paths"]["/test/"]

        assert path["get"]["operationId"] == "get_objects"

    def test_custom_default_operation_id(self, app, client):
        def default_id(resource, method):
            return "{0}{1}".format(method, resource)

        api = restx.Api(app, default_id=default_id)

        @api.route("/test/", endpoint="test")
        class TestResource(restx.Resource):
            @api.doc(id="get_objects")
            def get(self):
                return {}

            def post(self):
                return {}

        data = client.get_specs()
        path = data["paths"]["/test/"]

        assert path["get"]["operationId"] == "get_objects"
        assert path["post"]["operationId"] == "postTestResource"

    @pytest.mark.api(default_id=lambda r, m: "{0}{1}".format(m, r))
    def test_custom_default_operation_id_blueprint(self, api, client):
        @api.route("/test/", endpoint="test")
        class TestResource(restx.Resource):
            @api.doc(id="get_objects")
            def get(self):
                return {}

            def post(self):
                return {}

        data = client.get_specs()
        path = data["paths"]["/test/"]

        assert path["get"]["operationId"] == "get_objects"
        assert path["post"]["operationId"] == "postTestResource"

    def test_model_primitive_types(self, api, client):
        @api.route("/model-int/")
        class ModelInt(restx.Resource):
            @api.doc(model=int)
            def get(self):
                return {}

        data = client.get_specs()

        assert "definitions" not in data
        assert data["paths"]["/model-int/"]["get"]["responses"] == {
            "200": {"description": "Success", "schema": {"type": "integer"}}
        }

    def test_model_as_flat_dict(self, api, client):
        fields = api.model(
            "Person",
            {
                "name": restx.fields.String,
                "age": restx.fields.Integer,
                "birthdate": restx.fields.DateTime,
            },
        )

        @api.route("/model-as-dict/")
        class ModelAsDict(restx.Resource):
            @api.doc(model=fields)
            def get(self):
                return {}

            @api.doc(model="Person")
            def post(self):
                return {}

        data = client.get_specs()

        assert "definitions" in data
        assert "Person" in data["definitions"]

        path = data["paths"]["/model-as-dict/"]
        assert (
            path["get"]["responses"]["200"]["schema"]["$ref"] == "#/definitions/Person"
        )
        assert (
            path["post"]["responses"]["200"]["schema"]["$ref"] == "#/definitions/Person"
        )

    def test_model_as_nested_dict(self, api, client):
        address_fields = api.model(
            "Address",
            {
                "road": restx.fields.String,
            },
        )

        fields = api.model("Person", {"address": restx.fields.Nested(address_fields)})

        @api.route("/model-as-dict/")
        class ModelAsDict(restx.Resource):
            @api.doc(model=fields)
            def get(self):
                return {}

            @api.doc(model="Person")
            def post(self):
                return {}

        data = client.get_specs()

        assert "definitions" in data
        assert "Person" in data["definitions"]
        assert data["definitions"]["Person"] == {
            "properties": {
                "address": {"$ref": "#/definitions/Address"},
            },
            "type": "object",
        }

        assert "Address" in data["definitions"]
        assert data["definitions"]["Address"] == {
            "properties": {
                "road": {"type": "string"},
            },
            "type": "object",
        }

        path = data["paths"]["/model-as-dict/"]
        assert (
            path["get"]["responses"]["200"]["schema"]["$ref"] == "#/definitions/Person"
        )
        assert (
            path["post"]["responses"]["200"]["schema"]["$ref"] == "#/definitions/Person"
        )

    def test_model_as_nested_dict_with_details(self, api, client):
        address_fields = api.model(
            "Address",
            {
                "road": restx.fields.String,
            },
        )

        fields = api.model(
            "Person",
            {
                "address": restx.fields.Nested(
                    address_fields, description="description", readonly=True
                )
            },
        )

        @api.route("/model-as-dict/")
        class ModelAsDict(restx.Resource):
            @api.doc(model=fields)
            def get(self):
                return {}

            @api.doc(model="Person")
            def post(self):
                return {}

        data = client.get_specs()

        assert "definitions" in data
        assert "Person" in data["definitions"]
        assert data["definitions"]["Person"] == {
            "properties": {
                "address": {
                    "description": "description",
                    "readOnly": True,
                    "allOf": [{"$ref": "#/definitions/Address"}],
                },
            },
            "type": "object",
        }

        assert "Address" in data["definitions"]
        assert data["definitions"]["Address"] == {
            "properties": {
                "road": {"type": "string"},
            },
            "type": "object",
        }

    def test_model_as_flat_dict_with_marchal_decorator(self, api, client):
        fields = api.model(
            "Person",
            {
                "name": restx.fields.String,
                "age": restx.fields.Integer,
                "birthdate": restx.fields.DateTime,
            },
        )

        @api.route("/model-as-dict/")
        class ModelAsDict(restx.Resource):
            @api.marshal_with(fields)
            def get(self):
                return {}

        data = client.get_specs()

        assert "definitions" in data
        assert "Person" in data["definitions"]

        responses = data["paths"]["/model-as-dict/"]["get"]["responses"]
        assert responses == {
            "200": {
                "description": "Success",
                "schema": {"$ref": "#/definitions/Person"},
            }
        }

    def test_model_with_non_uri_chars_in_name(self, api, client):
        # name will be encoded as 'Person%2F%2F%3Flots%7B%7D%20of%20%26illegals%40%60'
        name = "Person//?lots{} of &illegals@`"
        fields = api.model(name, {})

        @api.route("/model-bad-uri/")
        class ModelBadUri(restx.Resource):
            @api.doc(model=fields)
            def get(self):
                return {}

            @api.response(201, "", model=name)
            def post(self):
                return {}

        data = client.get_specs()

        assert "definitions" in data
        assert name in data["definitions"]

        path = data["paths"]["/model-bad-uri/"]
        assert (
            path["get"]["responses"]["200"]["schema"]["$ref"]
            == "#/definitions/Person%2F%2F%3Flots%7B%7D%20of%20%26illegals%40%60"
        )
        assert (
            path["post"]["responses"]["201"]["schema"]["$ref"]
            == "#/definitions/Person%2F%2F%3Flots%7B%7D%20of%20%26illegals%40%60"
        )

    def test_marchal_decorator_with_code(self, api, client):
        fields = api.model(
            "Person",
            {
                "name": restx.fields.String,
                "age": restx.fields.Integer,
                "birthdate": restx.fields.DateTime,
            },
        )

        @api.route("/model-as-dict/")
        class ModelAsDict(restx.Resource):
            @api.marshal_with(fields, code=204)
            def delete(self):
                return {}

        data = client.get_specs()

        assert "definitions" in data
        assert "Person" in data["definitions"]

        responses = data["paths"]["/model-as-dict/"]["delete"]["responses"]
        assert responses == {
            "204": {
                "description": "Success",
                "schema": {"$ref": "#/definitions/Person"},
            }
        }

    def test_marchal_decorator_with_description(self, api, client):
        person = api.model(
            "Person",
            {
                "name": restx.fields.String,
                "age": restx.fields.Integer,
                "birthdate": restx.fields.DateTime,
            },
        )

        @api.route("/model-as-dict/")
        class ModelAsDict(restx.Resource):
            @api.marshal_with(person, description="Some details")
            def get(self):
                return {}

        data = client.get_specs()

        assert "definitions" in data
        assert "Person" in data["definitions"]

        responses = data["paths"]["/model-as-dict/"]["get"]["responses"]
        assert responses == {
            "200": {
                "description": "Some details",
                "schema": {"$ref": "#/definitions/Person"},
            }
        }

    def test_marhsal_decorator_with_envelope(self, api, client):
        person = api.model(
            "Person",
            {
                "name": restx.fields.String,
                "age": restx.fields.Integer,
                "birthdate": restx.fields.DateTime,
            },
        )

        @api.route("/model-as-dict/")
        class ModelAsDict(restx.Resource):
            @api.marshal_with(person, envelope="person")
            def get(self):
                return {}

        data = client.get_specs()

        assert "definitions" in data
        assert "Person" in data["definitions"]

        responses = data["paths"]["/model-as-dict/"]["get"]["responses"]
        assert responses == {
            "200": {
                "description": "Success",
                "schema": {"properties": {"person": {"$ref": "#/definitions/Person"}}},
            }
        }

    def test_model_as_flat_dict_with_marchal_decorator_list(self, api, client):
        fields = api.model(
            "Person",
            {
                "name": restx.fields.String,
                "age": restx.fields.Integer,
                "birthdate": restx.fields.DateTime,
            },
        )

        @api.route("/model-as-dict/")
        class ModelAsDict(restx.Resource):
            @api.marshal_with(fields, as_list=True)
            def get(self):
                return {}

        data = client.get_specs()

        assert "definitions" in data
        assert "Person" in data["definitions"]
        assert data["definitions"]["Person"] == {
            "properties": {
                "name": {"type": "string"},
                "age": {"type": "integer"},
                "birthdate": {"type": "string", "format": "date-time"},
            },
            "type": "object",
        }

        path = data["paths"]["/model-as-dict/"]
        assert path["get"]["responses"]["200"]["schema"] == {
            "type": "array",
            "items": {"$ref": "#/definitions/Person"},
        }

    def test_model_as_flat_dict_with_marchal_decorator_list_alt(self, api, client):
        fields = api.model(
            "Person",
            {
                "name": restx.fields.String,
                "age": restx.fields.Integer,
                "birthdate": restx.fields.DateTime,
            },
        )

        @api.route("/model-as-dict/")
        class ModelAsDict(restx.Resource):
            @api.marshal_list_with(fields)
            def get(self):
                return {}

        data = client.get_specs()

        assert "definitions" in data
        assert "Person" in data["definitions"]

        path = data["paths"]["/model-as-dict/"]
        assert path["get"]["responses"]["200"]["schema"] == {
            "type": "array",
            "items": {"$ref": "#/definitions/Person"},
        }

    def test_model_as_flat_dict_with_marchal_decorator_list_kwargs(self, api, client):
        fields = api.model(
            "Person",
            {
                "name": restx.fields.String,
                "age": restx.fields.Integer,
                "birthdate": restx.fields.DateTime,
            },
        )

        @api.route("/model-as-dict/")
        class ModelAsDict(restx.Resource):
            @api.marshal_list_with(fields, code=201, description="Some details")
            def get(self):
                return {}

        data = client.get_specs()

        assert "definitions" in data
        assert "Person" in data["definitions"]

        path = data["paths"]["/model-as-dict/"]
        assert path["get"]["responses"] == {
            "201": {
                "description": "Some details",
                "schema": {
                    "type": "array",
                    "items": {"$ref": "#/definitions/Person"},
                },
            }
        }

    def test_model_as_dict_with_list(self, api, client):
        fields = api.model(
            "Person",
            {
                "name": restx.fields.String,
                "age": restx.fields.Integer,
                "tags": restx.fields.List(restx.fields.String),
            },
        )

        @api.route("/model-with-list/")
        class ModelAsDict(restx.Resource):
            @api.doc(model=fields)
            def get(self):
                return {}

        data = client.get_specs()

        assert "definitions" in data
        assert "Person" in data["definitions"]
        assert data["definitions"]["Person"] == {
            "properties": {
                "name": {"type": "string"},
                "age": {"type": "integer"},
                "tags": {"type": "array", "items": {"type": "string"}},
            },
            "type": "object",
        }

        path = data["paths"]["/model-with-list/"]
        assert path["get"]["responses"]["200"]["schema"] == {
            "$ref": "#/definitions/Person"
        }

    def test_model_as_nested_dict_with_list(self, api, client):
        address = api.model(
            "Address",
            {
                "road": restx.fields.String,
            },
        )

        person = api.model(
            "Person",
            {
                "name": restx.fields.String,
                "age": restx.fields.Integer,
                "birthdate": restx.fields.DateTime,
                "addresses": restx.fields.List(restx.fields.Nested(address)),
            },
        )

        @api.route("/model-with-list/")
        class ModelAsDict(restx.Resource):
            @api.doc(model=person)
            def get(self):
                return {}

        data = client.get_specs()

        assert "definitions" in data
        assert "Person" in data["definitions"]
        assert "Address" in data["definitions"]

    def test_model_list_of_primitive_types(self, api, client):
        @api.route("/model-list/")
        class ModelAsDict(restx.Resource):
            @api.doc(model=[int])
            def get(self):
                return {}

            @api.doc(model=[str])
            def post(self):
                return {}

        data = client.get_specs()

        assert "definitions" not in data

        path = data["paths"]["/model-list/"]
        assert path["get"]["responses"]["200"]["schema"] == {
            "type": "array",
            "items": {"type": "integer"},
        }
        assert path["post"]["responses"]["200"]["schema"] == {
            "type": "array",
            "items": {"type": "string"},
        }

    def test_model_list_as_flat_dict(self, api, client):
        fields = api.model(
            "Person",
            {
                "name": restx.fields.String,
                "age": restx.fields.Integer,
                "birthdate": restx.fields.DateTime,
            },
        )

        @api.route("/model-as-dict/")
        class ModelAsDict(restx.Resource):
            @api.doc(model=[fields])
            def get(self):
                return {}

            @api.doc(model=["Person"])
            def post(self):
                return {}

        data = client.get_specs()

        assert "definitions" in data
        assert "Person" in data["definitions"]

        path = data["paths"]["/model-as-dict/"]
        for method in "get", "post":
            assert path[method]["responses"]["200"]["schema"] == {
                "type": "array",
                "items": {"$ref": "#/definitions/Person"},
            }

    def test_model_doc_on_class(self, api, client):
        fields = api.model(
            "Person",
            {
                "name": restx.fields.String,
                "age": restx.fields.Integer,
                "birthdate": restx.fields.DateTime,
            },
        )

        @api.route("/model-as-dict/")
        @api.doc(model=fields)
        class ModelAsDict(restx.Resource):
            def get(self):
                return {}

            def post(self):
                return {}

        data = client.get_specs()
        assert "definitions" in data
        assert "Person" in data["definitions"]

        path = data["paths"]["/model-as-dict/"]
        for method in "get", "post":
            assert path[method]["responses"]["200"]["schema"] == {
                "$ref": "#/definitions/Person"
            }

    def test_model_doc_for_method_on_class(self, api, client):
        fields = api.model(
            "Person",
            {
                "name": restx.fields.String,
                "age": restx.fields.Integer,
                "birthdate": restx.fields.DateTime,
            },
        )

        @api.route("/model-as-dict/")
        @api.doc(get={"model": fields})
        class ModelAsDict(restx.Resource):
            def get(self):
                return {}

            def post(self):
                return {}

        data = client.get_specs()
        assert "definitions" in data
        assert "Person" in data["definitions"]

        path = data["paths"]["/model-as-dict/"]
        assert path["get"]["responses"]["200"]["schema"] == {
            "$ref": "#/definitions/Person"
        }
        assert "schema" not in path["post"]["responses"]["200"]

    def test_model_with_discriminator(self, api, client):
        fields = api.model(
            "Person",
            {
                "name": restx.fields.String(discriminator=True),
                "age": restx.fields.Integer,
            },
        )

        @api.route("/model-with-discriminator/")
        class ModelAsDict(restx.Resource):
            @api.marshal_with(fields)
            def get(self):
                return {}

        data = client.get_specs()

        assert "definitions" in data
        assert "Person" in data["definitions"]
        assert data["definitions"]["Person"] == {
            "properties": {
                "name": {"type": "string"},
                "age": {"type": "integer"},
            },
            "discriminator": "name",
            "required": ["name"],
            "type": "object",
        }

    def test_model_with_discriminator_override_require(self, api, client):
        fields = api.model(
            "Person",
            {
                "name": restx.fields.String(discriminator=True, required=False),
                "age": restx.fields.Integer,
            },
        )

        @api.route("/model-with-discriminator/")
        class ModelAsDict(restx.Resource):
            @api.marshal_with(fields)
            def get(self):
                return {}

        data = client.get_specs()

        assert "definitions" in data
        assert "Person" in data["definitions"]
        assert data["definitions"]["Person"] == {
            "properties": {
                "name": {"type": "string"},
                "age": {"type": "integer"},
            },
            "discriminator": "name",
            "required": ["name"],
            "type": "object",
        }

    def test_model_not_found(self, api, client):
        @api.route("/model-not-found/")
        class ModelAsDict(restx.Resource):
            @api.doc(model="NotFound")
            def get(self):
                return {}

        client.get_specs(status=500)

    def test_recursive_model(self, api, client):
        fields = api.model(
            "Person",
            {
                "name": restx.fields.String,
                "age": restx.fields.Integer,
                "birthdate": restx.fields.DateTime,
            },
        )

        fields["children"] = restx.fields.List(
            restx.fields.Nested(fields),
            default=[],
        )

        @api.route("/recursive-model/")
        @api.doc(get={"model": fields})
        class ModelAsDict(restx.Resource):
            @api.marshal_with(fields)
            def get(self):
                return {}

        client.get_specs(status=200)

    def test_specs_no_duplicate_response_keys(self, api, client):
        """
        This tests that the swagger.json document will not be written with duplicate object keys
        due to the coercion of dict keys to string. The last @api.response should win.
        """

        # Note the use of a strings '404' and '200' in class decorators as opposed to ints in method decorators.
        @api.response("404", "Not Found")
        class BaseResource(restx.Resource):
            def get(self):
                pass

        model = api.model(
            "SomeModel",
            {
                "message": restx.fields.String,
            },
        )

        @api.route("/test/")
        @api.response("200", "Success")
        class TestResource(BaseResource):
            # @api.marshal_with also yields a response
            @api.marshal_with(model, code=200, description="Success on method")
            @api.response(404, "Not Found on method")
            def get(self):
                {}

        data = client.get_specs("")
        paths = data["paths"]

        op = paths["/test/"]["get"]
        print(op["responses"])
        assert op["responses"] == {
            "200": {
                "description": "Success on method",
                "schema": {"$ref": "#/definitions/SomeModel"},
            },
            "404": {
                "description": "Not Found on method",
            },
        }

    def test_clone(self, api, client):
        parent = api.model(
            "Person",
            {
                "name": restx.fields.String,
                "age": restx.fields.Integer,
                "birthdate": restx.fields.DateTime,
            },
        )

        child = api.clone(
            "Child",
            parent,
            {
                "extra": restx.fields.String,
            },
        )

        @api.route("/extend/")
        class ModelAsDict(restx.Resource):
            @api.doc(model=child)
            def get(self):
                return {}

            @api.doc(model="Child")
            def post(self):
                return {}

        data = client.get_specs()

        assert "definitions" in data
        assert "Person" not in data["definitions"]
        assert "Child" in data["definitions"]

        path = data["paths"]["/extend/"]
        assert (
            path["get"]["responses"]["200"]["schema"]["$ref"] == "#/definitions/Child"
        )
        assert (
            path["post"]["responses"]["200"]["schema"]["$ref"] == "#/definitions/Child"
        )

    def test_inherit(self, api, client):
        parent = api.model(
            "Person",
            {
                "name": restx.fields.String,
                "age": restx.fields.Integer,
            },
        )

        child = api.inherit(
            "Child",
            parent,
            {
                "extra": restx.fields.String,
            },
        )

        @api.route("/inherit/")
        class ModelAsDict(restx.Resource):
            @api.marshal_with(child)
            def get(self):
                return {
                    "name": "John",
                    "age": 42,
                    "extra": "test",
                }

            @api.doc(model="Child")
            def post(self):
                return {}

        data = client.get_specs()

        assert "definitions" in data
        assert "Person" in data["definitions"]
        assert "Child" in data["definitions"]
        assert data["definitions"]["Person"] == {
            "properties": {
                "name": {"type": "string"},
                "age": {"type": "integer"},
            },
            "type": "object",
        }
        assert data["definitions"]["Child"] == {
            "allOf": [
                {"$ref": "#/definitions/Person"},
                {"properties": {"extra": {"type": "string"}}, "type": "object"},
            ]
        }

        path = data["paths"]["/inherit/"]
        assert (
            path["get"]["responses"]["200"]["schema"]["$ref"] == "#/definitions/Child"
        )
        assert (
            path["post"]["responses"]["200"]["schema"]["$ref"] == "#/definitions/Child"
        )

        data = client.get_json("/inherit/")
        assert data == {
            "name": "John",
            "age": 42,
            "extra": "test",
        }

    def test_inherit_inline(self, api, client):
        parent = api.model(
            "Person",
            {
                "name": restx.fields.String,
                "age": restx.fields.Integer,
            },
        )

        child = api.inherit(
            "Child",
            parent,
            {
                "extra": restx.fields.String,
            },
        )

        output = api.model(
            "Output",
            {
                "child": restx.fields.Nested(child),
                "children": restx.fields.List(restx.fields.Nested(child)),
            },
        )

        @api.route("/inherit/")
        class ModelAsDict(restx.Resource):
            @api.marshal_with(output)
            def get(self):
                return {
                    "child": {
                        "name": "John",
                        "age": 42,
                        "extra": "test",
                    },
                    "children": [
                        {
                            "name": "John",
                            "age": 42,
                            "extra": "test",
                        },
                        {
                            "name": "Doe",
                            "age": 33,
                            "extra": "test2",
                        },
                    ],
                }

        data = client.get_specs()

        assert "definitions" in data
        assert "Person" in data["definitions"]
        assert "Child" in data["definitions"]

        data = client.get_json("/inherit/")
        assert data == {
            "child": {
                "name": "John",
                "age": 42,
                "extra": "test",
            },
            "children": [
                {
                    "name": "John",
                    "age": 42,
                    "extra": "test",
                },
                {
                    "name": "Doe",
                    "age": 33,
                    "extra": "test2",
                },
            ],
        }

    def test_polymorph_inherit(self, api, client):
        class Child1:
            pass

        class Child2:
            pass

        parent = api.model(
            "Person",
            {
                "name": restx.fields.String,
                "age": restx.fields.Integer,
            },
        )

        child1 = api.inherit(
            "Child1",
            parent,
            {
                "extra1": restx.fields.String,
            },
        )

        child2 = api.inherit(
            "Child2",
            parent,
            {
                "extra2": restx.fields.String,
            },
        )

        mapping = {
            Child1: child1,
            Child2: child2,
        }

        output = api.model("Output", {"child": restx.fields.Polymorph(mapping)})

        @api.route("/polymorph/")
        class ModelAsDict(restx.Resource):
            @api.marshal_with(output)
            def get(self):
                return {}

        data = client.get_specs()

        assert "definitions" in data
        assert "Person" in data["definitions"]
        assert "Child1" in data["definitions"]
        assert "Child2" in data["definitions"]
        assert "Output" in data["definitions"]

        path = data["paths"]["/polymorph/"]
        assert (
            path["get"]["responses"]["200"]["schema"]["$ref"] == "#/definitions/Output"
        )

    def test_polymorph_inherit_list(self, api, client):
        class Child1(object):
            name = "Child1"
            extra1 = "extra1"

        class Child2(object):
            name = "Child2"
            extra2 = "extra2"

        parent = api.model(
            "Person",
            {
                "name": restx.fields.String,
            },
        )

        child1 = api.inherit(
            "Child1",
            parent,
            {
                "extra1": restx.fields.String,
            },
        )

        child2 = api.inherit(
            "Child2",
            parent,
            {
                "extra2": restx.fields.String,
            },
        )

        mapping = {
            Child1: child1,
            Child2: child2,
        }

        output = api.model(
            "Output", {"children": restx.fields.List(restx.fields.Polymorph(mapping))}
        )

        @api.route("/polymorph/")
        class ModelAsDict(restx.Resource):
            @api.marshal_with(output)
            def get(self):
                return {"children": [Child1(), Child2()]}

        data = client.get_specs()

        assert "definitions" in data
        assert "Person" in data["definitions"]
        assert "Child1" in data["definitions"]
        assert "Child2" in data["definitions"]
        assert "Output" in data["definitions"]

        path = data["paths"]["/polymorph/"]
        assert (
            path["get"]["responses"]["200"]["schema"]["$ref"] == "#/definitions/Output"
        )

        data = client.get_json("/polymorph/")
        assert data == {
            "children": [
                {
                    "name": "Child1",
                    "extra1": "extra1",
                },
                {
                    "name": "Child2",
                    "extra2": "extra2",
                },
            ]
        }

    def test_expect_model(self, api, client):
        person = api.model(
            "Person",
            {
                "name": restx.fields.String,
                "age": restx.fields.Integer,
                "birthdate": restx.fields.DateTime,
            },
        )

        @api.route("/model-as-dict/")
        class ModelAsDict(restx.Resource):
            @api.expect(person)
            def post(self):
                return {}

        data = client.get_specs()

        assert "definitions" in data
        assert "Person" in data["definitions"]
        assert data["definitions"]["Person"] == {
            "properties": {
                "name": {"type": "string"},
                "age": {"type": "integer"},
                "birthdate": {"type": "string", "format": "date-time"},
            },
            "type": "object",
        }

        op = data["paths"]["/model-as-dict/"]["post"]
        assert len(op["parameters"]) == 1

        parameter = op["parameters"][0]
        assert parameter == {
            "name": "payload",
            "in": "body",
            "required": True,
            "schema": {"$ref": "#/definitions/Person"},
        }
        assert "description" not in parameter

    def test_body_model_shortcut(self, api, client):
        fields = api.model(
            "Person",
            {
                "name": restx.fields.String,
                "age": restx.fields.Integer,
                "birthdate": restx.fields.DateTime,
            },
        )

        @api.route("/model-as-dict/")
        class ModelAsDict(restx.Resource):
            @api.doc(model="Person")
            @api.expect(fields)
            def post(self):
                return {}

        data = client.get_specs()

        assert "definitions" in data
        assert "Person" in data["definitions"]
        assert data["definitions"]["Person"] == {
            "properties": {
                "name": {"type": "string"},
                "age": {"type": "integer"},
                "birthdate": {"type": "string", "format": "date-time"},
            },
            "type": "object",
        }

        op = data["paths"]["/model-as-dict/"]["post"]
        assert op["responses"]["200"]["schema"]["$ref"] == "#/definitions/Person"

        assert len(op["parameters"]) == 1

        parameter = op["parameters"][0]
        assert parameter == {
            "name": "payload",
            "in": "body",
            "required": True,
            "schema": {"$ref": "#/definitions/Person"},
        }
        assert "description" not in parameter

    def test_expect_model_list(self, api, client):
        model = api.model(
            "Person",
            {
                "name": restx.fields.String,
                "age": restx.fields.Integer,
                "birthdate": restx.fields.DateTime,
            },
        )

        @api.route("/model-list/")
        class ModelAsDict(restx.Resource):
            @api.expect([model])
            def post(self):
                return {}

        data = client.get_specs()

        assert "definitions" in data
        assert "Person" in data["definitions"]
        assert data["definitions"]["Person"] == {
            "properties": {
                "name": {"type": "string"},
                "age": {"type": "integer"},
                "birthdate": {"type": "string", "format": "date-time"},
            },
            "type": "object",
        }

        op = data["paths"]["/model-list/"]["post"]
        parameter = op["parameters"][0]

        assert parameter == {
            "name": "payload",
            "in": "body",
            "required": True,
            "schema": {
                "type": "array",
                "items": {"$ref": "#/definitions/Person"},
            },
        }

    def test_both_model_and_parser_from_expect(self, api, client):
        parser = api.parser()
        parser.add_argument("param", type=int, help="Some param")

        person = api.model(
            "Person",
            {
                "name": restx.fields.String,
                "age": restx.fields.Integer,
                "birthdate": restx.fields.DateTime,
            },
        )

        @api.route("/with-parser/", endpoint="with-parser")
        class WithParserResource(restx.Resource):
            @api.expect(parser, person)
            def get(self):
                return {}

        data = client.get_specs()

        assert "definitions" in data
        assert "Person" in data["definitions"]
        assert data["definitions"]["Person"] == {
            "properties": {
                "name": {"type": "string"},
                "age": {"type": "integer"},
                "birthdate": {"type": "string", "format": "date-time"},
            },
            "type": "object",
        }

        assert "/with-parser/" in data["paths"]

        op = data["paths"]["/with-parser/"]["get"]
        assert len(op["parameters"]) == 2

        parameters = dict((p["in"], p) for p in op["parameters"])

        parameter = parameters["query"]
        assert parameter["name"] == "param"
        assert parameter["type"] == "integer"
        assert parameter["in"] == "query"
        assert parameter["description"] == "Some param"

        parameter = parameters["body"]
        assert parameter == {
            "name": "payload",
            "in": "body",
            "required": True,
            "schema": {"$ref": "#/definitions/Person"},
        }

    def test_expect_primitive_list(self, api, client):
        @api.route("/model-list/")
        class ModelAsDict(restx.Resource):
            @api.expect([restx.fields.String])
            def post(self):
                return {}

        data = client.get_specs()

        op = data["paths"]["/model-list/"]["post"]
        parameter = op["parameters"][0]
        assert parameter == {
            "name": "payload",
            "in": "body",
            "required": True,
            "schema": {
                "type": "array",
                "items": {"type": "string"},
            },
        }

    def test_body_model_list(self, api, client):
        fields = api.model(
            "Person",
            {
                "name": restx.fields.String,
                "age": restx.fields.Integer,
                "birthdate": restx.fields.DateTime,
            },
        )

        @api.route("/model-list/")
        class ModelAsDict(restx.Resource):
            @api.expect([fields])
            def post(self):
                return {}

        data = client.get_specs()

        assert "definitions" in data
        assert "Person" in data["definitions"]
        assert data["definitions"]["Person"] == {
            "properties": {
                "name": {"type": "string"},
                "age": {"type": "integer"},
                "birthdate": {"type": "string", "format": "date-time"},
            },
            "type": "object",
        }

        op = data["paths"]["/model-list/"]["post"]
        parameter = op["parameters"][0]

        assert parameter == {
            "name": "payload",
            "in": "body",
            "required": True,
            "schema": {
                "type": "array",
                "items": {"$ref": "#/definitions/Person"},
            },
        }

    def test_expect_model_with_description(self, api, client):
        person = api.model(
            "Person",
            {
                "name": restx.fields.String,
                "age": restx.fields.Integer,
                "birthdate": restx.fields.DateTime,
            },
        )

        @api.route("/model-as-dict/")
        class ModelAsDict(restx.Resource):
            @api.expect((person, "Body description"))
            def post(self):
                return {}

        data = client.get_specs()

        assert "definitions" in data
        assert "Person" in data["definitions"]
        assert data["definitions"]["Person"] == {
            "properties": {
                "name": {"type": "string"},
                "age": {"type": "integer"},
                "birthdate": {"type": "string", "format": "date-time"},
            },
            "type": "object",
        }

        op = data["paths"]["/model-as-dict/"]["post"]
        assert len(op["parameters"]) == 1

        parameter = op["parameters"][0]

        assert parameter == {
            "name": "payload",
            "in": "body",
            "required": True,
            "description": "Body description",
            "schema": {"$ref": "#/definitions/Person"},
        }

    def test_authorizations(self, app, client):
        restx.Api(
            app,
            authorizations={
                "apikey": {"type": "apiKey", "in": "header", "name": "X-API"}
            },
        )

        # @api.route('/authorizations/')
        # class ModelAsDict(restx.Resource):
        #     def get(self):
        #         return {}

        #     def post(self):
        #         return {}

        data = client.get_specs()
        assert "securityDefinitions" in data
        assert "security" not in data

        # path = data['paths']['/authorizations/']
        # assert 'security' not in path['get']
        # assert path['post']['security'] == {'apikey': []}

    def test_single_root_security_string(self, app, client):
        api = restx.Api(
            app,
            security="apikey",
            authorizations={
                "apikey": {"type": "apiKey", "in": "header", "name": "X-API"}
            },
        )

        @api.route("/authorizations/")
        class ModelAsDict(restx.Resource):
            def post(self):
                return {}

        data = client.get_specs()
        assert data["securityDefinitions"] == {
            "apikey": {"type": "apiKey", "in": "header", "name": "X-API"}
        }
        assert data["security"] == [{"apikey": []}]

        op = data["paths"]["/authorizations/"]["post"]
        assert "security" not in op

    def test_single_root_security_object(self, app, client):
        security_definitions = {
            "oauth2": {
                "type": "oauth2",
                "flow": "accessCode",
                "tokenUrl": "https://somewhere.com/token",
                "scopes": {
                    "read": "Grant read-only access",
                    "write": "Grant read-write access",
                },
            },
            "implicit": {
                "type": "oauth2",
                "flow": "implicit",
                "tokenUrl": "https://somewhere.com/token",
                "scopes": {
                    "read": "Grant read-only access",
                    "write": "Grant read-write access",
                },
            },
        }

        api = restx.Api(
            app,
            security={"oauth2": "read", "implicit": ["read", "write"]},
            authorizations=security_definitions,
        )

        @api.route("/authorizations/")
        class ModelAsDict(restx.Resource):
            def post(self):
                return {}

        data = client.get_specs()
        assert data["securityDefinitions"] == security_definitions
        assert data["security"] == [{"oauth2": ["read"], "implicit": ["read", "write"]}]

        op = data["paths"]["/authorizations/"]["post"]
        assert "security" not in op

    def test_root_security_as_list(self, app, client):
        security_definitions = {
            "apikey": {"type": "apiKey", "in": "header", "name": "X-API"},
            "oauth2": {
                "type": "oauth2",
                "flow": "accessCode",
                "tokenUrl": "https://somewhere.com/token",
                "scopes": {
                    "read": "Grant read-only access",
                    "write": "Grant read-write access",
                },
            },
        }
        api = restx.Api(
            app,
            security=["apikey", {"oauth2": "read"}],
            authorizations=security_definitions,
        )

        @api.route("/authorizations/")
        class ModelAsDict(restx.Resource):
            def post(self):
                return {}

        data = client.get_specs()
        assert data["securityDefinitions"] == security_definitions
        assert data["security"] == [{"apikey": []}, {"oauth2": ["read"]}]

        op = data["paths"]["/authorizations/"]["post"]
        assert "security" not in op

    def test_method_security(self, app, client):
        api = restx.Api(
            app,
            authorizations={
                "apikey": {"type": "apiKey", "in": "header", "name": "X-API"}
            },
        )

        @api.route("/authorizations/")
        class ModelAsDict(restx.Resource):
            @api.doc(security=["apikey"])
            def get(self):
                return {}

            @api.doc(security="apikey")
            def post(self):
                return {}

        data = client.get_specs()
        assert data["securityDefinitions"] == {
            "apikey": {"type": "apiKey", "in": "header", "name": "X-API"}
        }
        assert "security" not in data

        path = data["paths"]["/authorizations/"]
        for method in "get", "post":
            assert path[method]["security"] == [{"apikey": []}]

    def test_security_override(self, app, client):
        security_definitions = {
            "apikey": {"type": "apiKey", "in": "header", "name": "X-API"},
            "oauth2": {
                "type": "oauth2",
                "flow": "accessCode",
                "tokenUrl": "https://somewhere.com/token",
                "scopes": {
                    "read": "Grant read-only access",
                    "write": "Grant read-write access",
                },
            },
        }
        api = restx.Api(
            app,
            security=["apikey", {"oauth2": "read"}],
            authorizations=security_definitions,
        )

        @api.route("/authorizations/")
        class ModelAsDict(restx.Resource):
            @api.doc(security=[{"oauth2": ["read", "write"]}])
            def get(self):
                return {}

        data = client.get_specs()
        assert data["securityDefinitions"] == security_definitions

        op = data["paths"]["/authorizations/"]["get"]
        assert op["security"] == [{"oauth2": ["read", "write"]}]

    def test_security_nullify(self, app, client):
        security_definitions = {
            "apikey": {"type": "apiKey", "in": "header", "name": "X-API"},
            "oauth2": {
                "type": "oauth2",
                "flow": "accessCode",
                "tokenUrl": "https://somewhere.com/token",
                "scopes": {
                    "read": "Grant read-only access",
                    "write": "Grant read-write access",
                },
            },
        }
        api = restx.Api(
            app,
            security=["apikey", {"oauth2": "read"}],
            authorizations=security_definitions,
        )

        @api.route("/authorizations/")
        class ModelAsDict(restx.Resource):
            @api.doc(security=[])
            def get(self):
                return {}

            @api.doc(security=None)
            def post(self):
                return {}

        data = client.get_specs()
        assert data["securityDefinitions"] == security_definitions

        path = data["paths"]["/authorizations/"]
        for method in "get", "post":
            assert path[method]["security"] == []

    def test_hidden_resource(self, api, client):
        @api.route("/test/", endpoint="test", doc=False)
        class TestResource(restx.Resource):
            def get(self):
                """
                GET operation
                """
                return {}

        @api.hide
        @api.route("/test2/", endpoint="test2")
        class TestResource2(restx.Resource):
            def get(self):
                """
                GET operation
                """
                return {}

        @api.doc(False)
        @api.route("/test3/", endpoint="test3")
        class TestResource3(restx.Resource):
            def get(self):
                """
                GET operation
                """
                return {}

        data = client.get_specs()
        for path in "/test/", "/test2/", "/test3/":
            assert path not in data["paths"]

            resp = client.get(path)
            assert resp.status_code == 200

    def test_hidden_resource_from_namespace(self, api, client):
        ns = api.namespace("ns")

        @ns.route("/test/", endpoint="test", doc=False)
        class TestResource(restx.Resource):
            def get(self):
                """
                GET operation
                """
                return {}

        data = client.get_specs()
        assert "/ns/test/" not in data["paths"]

        resp = client.get("/ns/test/")
        assert resp.status_code == 200

    def test_hidden_methods(self, api, client):
        @api.route("/test/", endpoint="test")
        @api.doc(delete=False)
        class TestResource(restx.Resource):
            def get(self):
                """
                GET operation
                """
                return {}

            @api.doc(False)
            def post(self):
                """POST operation.

                Should be ignored
                """
                return {}

            @api.hide
            def put(self):
                """PUT operation. Should be ignored"""
                return {}

            def delete(self):
                return {}

        data = client.get_specs()
        path = data["paths"]["/test/"]

        assert "get" in path
        assert "post" not in path
        assert "put" not in path

        for method in "GET", "POST", "PUT":
            resp = client.open("/test/", method=method)
            assert resp.status_code == 200

    def test_produces_method(self, api, client):
        @api.route("/test/", endpoint="test")
        class TestResource(restx.Resource):
            def get(self):
                pass

            @api.produces(["application/octet-stream"])
            def post(self):
                pass

        data = client.get_specs()

        get_operation = data["paths"]["/test/"]["get"]
        assert "produces" not in get_operation

        post_operation = data["paths"]["/test/"]["post"]
        assert "produces" in post_operation
        assert post_operation["produces"] == ["application/octet-stream"]

    def test_deprecated_resource(self, api, client):
        @api.deprecated
        @api.route("/test/", endpoint="test")
        class TestResource(restx.Resource):
            def get(self):
                pass

            def post(self):
                pass

        data = client.get_specs()
        resource = data["paths"]["/test/"]
        for operation in resource.values():
            assert "deprecated" in operation
            assert operation["deprecated"] is True

    def test_deprecated_method(self, api, client):
        @api.route("/test/", endpoint="test")
        class TestResource(restx.Resource):
            def get(self):
                pass

            @api.deprecated
            def post(self):
                pass

        data = client.get_specs()

        get_operation = data["paths"]["/test/"]["get"]
        assert "deprecated" not in get_operation

        post_operation = data["paths"]["/test/"]["post"]
        assert "deprecated" in post_operation
        assert post_operation["deprecated"] is True

    def test_vendor_as_kwargs(self, api, client):
        @api.route("/vendor_fields", endpoint="vendor_fields")
        class TestResource(restx.Resource):
            @api.vendor(integration={"integration1": "1"})
            def get(self):
                return {}

        data = client.get_specs()

        assert "/vendor_fields" in data["paths"]

        path = data["paths"]["/vendor_fields"]["get"]

        assert "x-integration" in path

        assert path["x-integration"] == {"integration1": "1"}

    def test_vendor_as_dict(self, api, client):
        @api.route("/vendor_fields", endpoint="vendor_fields")
        class TestResource(restx.Resource):
            @api.vendor(
                {
                    "x-some-integration": {"integration1": "1"},
                    "another-integration": True,
                },
                {"third-integration": True},
            )
            def get(self, age):
                return {}

        data = client.get_specs()

        assert "/vendor_fields" in data["paths"]

        path = data["paths"]["/vendor_fields"]["get"]
        assert "x-some-integration" in path
        assert path["x-some-integration"] == {"integration1": "1"}

        assert "x-another-integration" in path
        assert path["x-another-integration"] is True

        assert "x-third-integration" in path
        assert path["x-third-integration"] is True

    def test_method_restrictions(self, api, client):
        @api.route("/foo/bar", endpoint="foo")
        @api.route("/bar", methods=["GET"], endpoint="bar")
        class TestResource(restx.Resource):
            def get(self):
                pass

            def post(self):
                pass

        data = client.get_specs()

        path = data["paths"]["/foo/bar"]
        assert "get" in path
        assert "post" in path

        path = data["paths"]["/bar"]
        assert "get" in path
        assert "post" not in path

    def test_multiple_routes_inherit_doc(self, api, client):
        @api.route("/foo/bar")
        @api.route("/bar")
        @api.doc(description="an endpoint")
        class TestResource(restx.Resource):
            def get(self):
                pass

        data = client.get_specs()

        path = data["paths"]["/foo/bar"]
        assert path["get"]["description"] == "an endpoint"

        path = data["paths"]["/bar"]
        assert path["get"]["description"] == "an endpoint"

    def test_multiple_routes_individual_doc(self, api, client):
        @api.route("/foo/bar", doc={"description": "the same endpoint"})
        @api.route("/bar", doc={"description": "an endpoint"})
        class TestResource(restx.Resource):
            def get(self):
                pass

        data = client.get_specs()

        path = data["paths"]["/foo/bar"]
        assert path["get"]["description"] == "the same endpoint"

        path = data["paths"]["/bar"]
        assert path["get"]["description"] == "an endpoint"

    def test_multiple_routes_override_doc(self, api, client):
        @api.route("/foo/bar", doc={"description": "the same endpoint"})
        @api.route("/bar")
        @api.doc(description="an endpoint")
        class TestResource(restx.Resource):
            def get(self):
                pass

        data = client.get_specs()

        path = data["paths"]["/foo/bar"]
        assert path["get"]["description"] == "the same endpoint"

        path = data["paths"]["/bar"]
        assert path["get"]["description"] == "an endpoint"

    def test_multiple_routes_no_doc_same_operationIds(self, api, client):
        @api.route("/foo/bar")
        @api.route("/bar")
        class TestResource(restx.Resource):
            def get(self):
                pass

        data = client.get_specs()

        expected_operation_id = "get_test_resource"

        path = data["paths"]["/foo/bar"]
        assert path["get"]["operationId"] == expected_operation_id

        path = data["paths"]["/bar"]
        assert path["get"]["operationId"] == expected_operation_id

    def test_multiple_routes_with_doc_unique_operationIds(self, api, client):
        @api.route(
            "/foo/bar",
            doc={"description": "I should be treated separately"},
        )
        @api.route("/bar")
        class TestResource(restx.Resource):
            def get(self):
                pass

        data = client.get_specs()

        path = data["paths"]["/foo/bar"]
        assert path["get"]["operationId"] == "get_test_resource_/foo/bar"

        path = data["paths"]["/bar"]
        assert path["get"]["operationId"] == "get_test_resource"

    def test_mutltiple_routes_merge_doc(self, api, client):
        @api.route("/foo/bar", doc={"description": "the same endpoint"})
        @api.route("/bar", doc={"description": False})
        @api.doc(security=[{"oauth2": ["read", "write"]}])
        class TestResource(restx.Resource):
            def get(self):
                pass

        data = client.get_specs()

        path = data["paths"]["/foo/bar"]
        assert path["get"]["description"] == "the same endpoint"
        assert path["get"]["security"] == [{"oauth2": ["read", "write"]}]

        path = data["paths"]["/bar"]
        assert "description" not in path["get"]
        assert path["get"]["security"] == [{"oauth2": ["read", "write"]}]

    def test_multiple_routes_deprecation(self, api, client):
        @api.route("/foo/bar", doc={"deprecated": True})
        @api.route("/bar")
        class TestResource(restx.Resource):
            def get(self):
                pass

        data = client.get_specs()

        path = data["paths"]["/foo/bar"]
        assert path["get"]["deprecated"] is True

        path = data["paths"]["/bar"]
        assert "deprecated" not in path["get"]

    @pytest.mark.parametrize("path_name", ["/name/{age}/", "/first-name/{age}/"])
    def test_multiple_routes_explicit_parameters_override(self, path_name, api, client):
        @api.route("/name/<int:age>/", endpoint="by-name")
        @api.route("/first-name/<int:age>/")
        @api.doc(
            params={
                "q": {
                    "type": "string",
                    "in": "query",
                    "description": "Overriden description",
                },
                "age": {"description": "An age"},
            }
        )
        class ByNameResource(restx.Resource):
            @api.doc(params={"q": {"description": "A query string"}})
            def get(self, age):
                return {}

            def post(self, age):
                pass

        data = client.get_specs()
        assert path_name in data["paths"]

        path = data["paths"][path_name]
        assert len(path["parameters"]) == 1

        by_name = dict((p["name"], p) for p in path["parameters"])

        parameter = by_name["age"]
        assert parameter["name"] == "age"
        assert parameter["type"] == "integer"
        assert parameter["in"] == "path"
        assert parameter["required"] is True
        assert parameter["description"] == "An age"

        # Don't duplicate parameters
        assert "q" not in by_name

        get = path["get"]
        assert len(get["parameters"]) == 1

        parameter = get["parameters"][0]
        assert parameter["name"] == "q"
        assert parameter["type"] == "string"
        assert parameter["in"] == "query"
        assert parameter["description"] == "A query string"

        post = path["post"]
        assert len(post["parameters"]) == 1

        parameter = post["parameters"][0]
        assert parameter["name"] == "q"
        assert parameter["type"] == "string"
        assert parameter["in"] == "query"
        assert parameter["description"] == "Overriden description"


class SwaggerDeprecatedTest(object):
    def test_doc_parser_parameters(self, api):
        parser = api.parser()
        parser.add_argument("param", type=int, help="Some param")

        with pytest.warns(DeprecationWarning):

            @api.route("/with-parser/")
            class WithParserResource(restx.Resource):
                @api.doc(parser=parser)
                def get(self):
                    return {}

        assert "parser" not in WithParserResource.get.__apidoc__
        assert "expect" in WithParserResource.get.__apidoc__
        doc_parser = WithParserResource.get.__apidoc__["expect"][0]
        assert doc_parser.__schema__ == parser.__schema__

    def test_doc_method_parser_on_class(self, api):
        parser = api.parser()
        parser.add_argument("param", type=int, help="Some param")

        with pytest.warns(DeprecationWarning):

            @api.route("/with-parser/")
            @api.doc(get={"parser": parser})
            class WithParserResource(restx.Resource):
                def get(self):
                    return {}

                def post(self):
                    return {}

        assert "parser" not in WithParserResource.__apidoc__["get"]
        assert "expect" in WithParserResource.__apidoc__["get"]
        doc_parser = WithParserResource.__apidoc__["get"]["expect"][0]
        assert doc_parser.__schema__ == parser.__schema__

    def test_doc_body_as_tuple(self, api):
        fields = api.model(
            "Person",
            {
                "name": restx.fields.String,
                "age": restx.fields.Integer,
                "birthdate": restx.fields.DateTime,
            },
        )

        with pytest.warns(DeprecationWarning):

            @api.route("/model-as-dict/")
            class ModelAsDict(restx.Resource):
                @api.doc(body=(fields, "Body description"))
                def post(self):
                    return {}

        assert "body" not in ModelAsDict.post.__apidoc__
        assert ModelAsDict.post.__apidoc__["expect"] == [(fields, "Body description")]

    def test_build_request_body_parameters_schema(self):
        parser = restx.reqparse.RequestParser()
        parser.add_argument("test", type=int, location="headers")
        parser.add_argument("test1", type=int, location="json")
        parser.add_argument("test2", location="json")

        body_params = [p for p in parser.__schema__ if p["in"] == "body"]
        result = restx.swagger.build_request_body_parameters_schema(body_params)

        assert result["name"] == "payload"
        assert result["required"]
        assert result["in"] == "body"
        assert result["schema"]["type"] == "object"
        assert result["schema"]["properties"]["test1"]["type"] == "integer"
        assert result["schema"]["properties"]["test2"]["type"] == "string"

    def test_expect_unused_model(self, app, api, client):
        from flask_restx import fields

        api.model(
            "SomeModel",
            {
                "param": fields.String,
                "count": fields.Integer,
            },
        )

        @api.route("/with-parser/", endpoint="with-parser")
        class WithParserResource(restx.Resource):
            def get(self):
                return {}

        app.config["RESTX_INCLUDE_ALL_MODELS"] = True
        data = client.get_specs()
        assert "/with-parser/" in data["paths"]

        path = data["paths"]["/with-parser/"]
        assert "parameters" not in path

        model = data["definitions"]["SomeModel"]
        assert model == {
            "properties": {"count": {"type": "integer"}, "param": {"type": "string"}},
            "type": "object",
        }

    def test_not_expect_unused_model(self, app, api, client):
        # This is the default configuration, RESTX_INCLUDE_ALL_MODELS=False

        from flask_restx import fields

        api.model(
            "SomeModel",
            {
                "param": fields.String,
                "count": fields.Integer,
            },
        )

        @api.route("/with-parser/", endpoint="with-parser")
        class WithParserResource(restx.Resource):
            def get(self):
                return {}

        data = client.get_specs()
        assert "/with-parser/" in data["paths"]
        assert "definitions" not in data

        path = data["paths"]["/with-parser/"]
        assert "parameters" not in path

    def test_nondefault_swagger_filename(self, app, client):
        api = restx.Api(doc="/doc/test", default_swagger_filename="test.json")
        ns = restx.Namespace("ns1")

        @ns.route("/test1")
        class Ns(restx.Resource):
            @ns.doc("Docs")
            def get(self):
                pass

        api.add_namespace(ns)
        api.init_app(app)

        resp = client.get("/test.json")
        assert resp.status_code == 200
        assert resp.content_type == "application/json"
        resp = client.get("/doc/test")
        assert resp.status_code == 200
        assert resp.content_type == "text/html; charset=utf-8"
        resp = client.get("/ns1/test1")
        assert resp.status_code == 200
