# -*- coding: utf-8 -*-
from __future__ import unicode_literals

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')

        @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'] == 'query'
        assert parameter['description'] == 'Some param'

    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_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')]
