Add more tests and cleanup coverage reports (#13)

Closes #11
openapi3
Cristi Vîjdea 2017-12-12 17:30:58 +01:00 committed by GitHub
parent 53ac55a24b
commit 8883894775
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 385 additions and 134 deletions

View File

@ -35,8 +35,8 @@ You want to contribute some code? Great! Here are a few steps to get you started
$ virtualenv venv
$ source venv/bin/activate
(venv) $ pip install -e .[validation,test]
(venv) $ pip install -r requirements/dev.txt
(venv) $ pip install -e .[validation]
(venv) $ pip install -rrequirements/dev.txt -rrequirements/test.txt
#. Make your changes and check them against the test project

View File

@ -280,6 +280,18 @@ This method is currently the only way to get both syntactic and semantic validat
The other validators only provide JSON schema-level validation, but miss things like duplicate operation names,
improper content types, etc
5. Code generation
==================
You can use the specification outputted by this library together with
`swagger-codegen <https://github.com/swagger-api/swagger-codegen>`_ to generate client code in your language of choice:
.. code:: console
$ docker run --rm -v ${PWD}:/local swaggerapi/swagger-codegen-cli generate -i /local/tests/reference.yaml -l javascript -o /local/.codegen/js
See the github page linked above for more details.
**********
Background
**********

6
package-lock.json generated
View File

@ -4,9 +4,9 @@
"lockfileVersion": 1,
"dependencies": {
"swagger-ui-dist": {
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-3.5.0.tgz",
"integrity": "sha1-JuvzMRqaYP6dFwYS7tUoKmW8kiY="
"version": "3.6.1",
"resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-3.6.1.tgz",
"integrity": "sha1-uzQgV/h2COTs2DlGMDSJxjYicgY="
}
}
}

View File

@ -1,6 +1,6 @@
{
"name": "drf-swagger",
"dependencies": {
"swagger-ui-dist": "^3.5.0"
"swagger-ui-dist": "^3.6.1"
}
}

View File

@ -1,7 +1,9 @@
# pytest runner + plugins
pytest-django>=3.1.2
pytest>=2.9
pytest-pythonpath>=0.7.1
pytest-cov>=2.5.1
# latest pip version of pytest-django is more than a year old and does not support Django 2.0
git+https://github.com/pytest-dev/pytest-django.git@94cccb956435dd7a719606744ee7608397e1eafb
datadiff==2.0.0
# test project requirements

View File

@ -16,7 +16,6 @@ with io.open('README.rst', encoding='utf-8') as readme:
requirements = ['djangorestframework>=3.7.0'] + read_req('base.txt')
requirements_validation = read_req('validation.txt')
requirements_test = read_req('test.txt')
setup(
name='drf-swagger',
@ -25,10 +24,8 @@ setup(
package_dir={'': 'src'},
include_package_data=True,
install_requires=requirements,
tests_require=requirements_test,
extras_require={
'validation': requirements_validation,
'test': requirements_test,
},
license='BSD License',
description='Automated generation of real Swagger/OpenAPI 2.0 schemas from Django Rest Framework code.',

View File

@ -1,5 +1,5 @@
from django.conf import settings
from rest_framework.settings import APISettings
from rest_framework.settings import perform_import
SWAGGER_DEFAULTS = {
'USE_SESSION_AUTH': True,
@ -30,16 +30,49 @@ REDOC_DEFAULTS = {
IMPORT_STRINGS = []
class AppSettings(object):
"""
Stolen from Django Rest Framework, removed caching for easier testing
"""
def __init__(self, user_settings, defaults, import_strings=None):
self._user_settings = user_settings
self.defaults = defaults
self.import_strings = import_strings or []
@property
def user_settings(self):
return getattr(settings, self._user_settings, {})
def __getattr__(self, attr):
if attr not in self.defaults:
raise AttributeError("Invalid setting: '%s'" % attr) # pragma: no cover
try:
# Check if present in user settings
val = self.user_settings[attr]
except KeyError:
# Fall back to defaults
val = self.defaults[attr]
# Coerce import strings into classes
if attr in self.import_strings:
val = perform_import(val, attr)
return val
#:
swagger_settings = APISettings(
user_settings=getattr(settings, 'SWAGGER_SETTINGS', {}),
swagger_settings = AppSettings(
user_settings='SWAGGER_SETTINGS',
defaults=SWAGGER_DEFAULTS,
import_strings=IMPORT_STRINGS,
)
#:
redoc_settings = APISettings(
user_settings=getattr(settings, 'REDOC_SETTINGS', {}),
redoc_settings = AppSettings(
user_settings='REDOC_SETTINGS',
defaults=REDOC_DEFAULTS,
import_strings=IMPORT_STRINGS,
)

View File

@ -39,9 +39,6 @@ VALIDATORS = {
class _OpenAPICodec(object):
media_type = None
#: Allows easier mocking of settings
settings = swagger_settings
def __init__(self, validators):
self._validators = validators
@ -89,7 +86,7 @@ class _OpenAPICodec(object):
:return: swagger spec as dict
:rtype: OrderedDict
"""
swagger.security_definitions = self.settings.SECURITY_DEFINITIONS
swagger.security_definitions = swagger_settings.SECURITY_DEFINITIONS
return swagger

View File

@ -65,10 +65,10 @@ class OpenAPISchemaGenerator(object):
view = self._gen.create_view(callback, method, request)
overrides = getattr(callback, 'swagger_auto_schema', None)
if overrides is not None:
# decorated function based view must have its decorator information passed on to th re-instantiated view
# decorated function based view must have its decorator information passed on to the re-instantiated view
for method, _ in overrides.items():
view_method = getattr(view, method, None)
if view_method is not None:
if view_method is not None: # pragma: no cover
setattr(view_method.__func__, 'swagger_auto_schema', overrides)
return view
@ -137,7 +137,8 @@ class OpenAPISchemaGenerator(object):
schema = auto_schema_cls(view, path, method, overrides, components)
operations[method.lower()] = schema.get_operation(operation_keys)
paths[path] = openapi.PathItem(parameters=path_parameters, **operations)
if operations:
paths[path] = openapi.PathItem(parameters=path_parameters, **operations)
return openapi.Paths(paths=paths)
@ -178,7 +179,7 @@ class OpenAPISchemaGenerator(object):
try:
model_field = model._meta.get_field(variable)
except Exception:
model_field = None
model_field = None # pragma: no cover
if model_field is not None and model_field.help_text:
description = force_text(model_field.help_text)

View File

@ -166,9 +166,9 @@ class SwaggerAutoSchema(object):
parameters = OrderedDict(((param.name, param.in_), param) for param in parameters)
manual_parameters = self.overrides.get('manual_parameters', None) or []
if any(param.in_ == openapi.IN_BODY for param in manual_parameters):
if any(param.in_ == openapi.IN_BODY for param in manual_parameters): # pragma: no cover
raise SwaggerGenerationError("specify the body parameter as a Schema or Serializer in request_body")
if any(param.in_ == openapi.IN_FORM for param in manual_parameters):
if any(param.in_ == openapi.IN_FORM for param in manual_parameters): # pragma: no cover
if any(param.in_ == openapi.IN_BODY for param in parameters.values()):
raise SwaggerGenerationError("cannot add form parameters when the request has a request schema; "
"did you forget to set an appropriate parser class on the view?")

View File

@ -5,7 +5,6 @@ from coreapi.compat import urlparse
from future.utils import raise_from
from inflection import camelize
TYPE_OBJECT = "object" #:
TYPE_STRING = "string" #:
TYPE_NUMBER = "number" #:
@ -212,7 +211,7 @@ class Paths(SwaggerDict):
super(Paths, self).__init__(**extra)
for path, path_obj in paths.items():
assert path.startswith("/")
if path_obj is not None:
if path_obj is not None: # pragma: no cover
self[path] = path_obj
self._insert_extras__()
@ -403,7 +402,7 @@ class Responses(SwaggerDict):
"""
super(Responses, self).__init__(**extra)
for status, response in responses.items():
if response is not None:
if response is not None: # pragma: no cover
self[str(status)] = response
self.default = default
self._insert_extras__()

View File

@ -1,28 +1,28 @@
{
"_from": "swagger-ui-dist",
"_id": "swagger-ui-dist@3.5.0",
"_from": "swagger-ui-dist@3.6.1",
"_id": "swagger-ui-dist@3.6.1",
"_inBundle": false,
"_integrity": "sha1-JuvzMRqaYP6dFwYS7tUoKmW8kiY=",
"_integrity": "sha1-uzQgV/h2COTs2DlGMDSJxjYicgY=",
"_location": "/swagger-ui-dist",
"_phantomChildren": {},
"_requested": {
"type": "tag",
"type": "version",
"registry": true,
"raw": "swagger-ui-dist",
"raw": "swagger-ui-dist@3.6.1",
"name": "swagger-ui-dist",
"escapedName": "swagger-ui-dist",
"rawSpec": "",
"rawSpec": "3.6.1",
"saveSpec": null,
"fetchSpec": "latest"
"fetchSpec": "3.6.1"
},
"_requiredBy": [
"#USER",
"/"
],
"_resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-3.5.0.tgz",
"_shasum": "26ebf3311a9a60fe9d170612eed5282a65bc9226",
"_spec": "swagger-ui-dist",
"_where": "C:\\Projects\\drf_openapi",
"_resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-3.6.1.tgz",
"_shasum": "bb342057f87608e4ecd83946303489c636227206",
"_spec": "swagger-ui-dist@3.6.1",
"_where": "C:\\Projects\\drf-swagger",
"bugs": {
"url": "https://github.com/swagger-api/swagger-ui/issues"
},
@ -68,5 +68,5 @@
"type": "git",
"url": "git+ssh://git@github.com/swagger-api/swagger-ui.git"
},
"version": "3.5.0"
"version": "3.6.1"
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -89,6 +89,7 @@ def swagger_auto_schema(method=None, methods=None, auto_schema=None, request_bod
it will automatically be converted into a :class:`.Schema`
"""
def decorator(view_method):
data = {
'auto_schema': auto_schema,
@ -165,6 +166,10 @@ def serializer_field_to_swagger(field, swagger_object_type, definitions=None, **
def SwaggerType(**instance_kwargs):
if swagger_object_type == openapi.Parameter:
instance_kwargs['required'] = field.required
if swagger_object_type != openapi.Items:
default = getattr(field, 'default', serializers.empty)
if default is not serializers.empty:
instance_kwargs['default'] = default
instance_kwargs.update(kwargs)
return swagger_object_type(title=title, description=description, **instance_kwargs)
@ -249,7 +254,7 @@ def serializer_field_to_swagger(field, swagger_object_type, definitions=None, **
elif isinstance(field, serializers.RegexField):
return SwaggerType(type=openapi.TYPE_STRING, pattern=find_regex(field))
elif isinstance(field, serializers.SlugField):
return SwaggerType(type=openapi.TYPE_STRING, format=openapi.FORMAT_SLUG)
return SwaggerType(type=openapi.TYPE_STRING, format=openapi.FORMAT_SLUG, pattern=find_regex(field))
elif isinstance(field, serializers.URLField):
return SwaggerType(type=openapi.TYPE_STRING, format=openapi.FORMAT_URI)
elif isinstance(field, serializers.IPAddressField):
@ -272,11 +277,6 @@ def serializer_field_to_swagger(field, swagger_object_type, definitions=None, **
if swagger_object_type != openapi.Parameter:
raise SwaggerGenerationError("parameter of type file is supported only in formData Parameter")
return SwaggerType(type=openapi.TYPE_FILE)
elif isinstance(field, serializers.JSONField):
return SwaggerType(
type=openapi.TYPE_STRING,
format=openapi.FORMAT_BINARY if field.binary else None
)
elif isinstance(field, serializers.DictField) and swagger_object_type == openapi.Schema:
child_schema = serializer_field_to_swagger(field.child, ChildSwaggerType, definitions)
return SwaggerType(
@ -284,7 +284,7 @@ def serializer_field_to_swagger(field, swagger_object_type, definitions=None, **
additional_properties=child_schema
)
# TODO unhandled fields: TimeField DurationField HiddenField ModelField NullBooleanField?
# TODO unhandled fields: TimeField DurationField HiddenField ModelField NullBooleanField? JSONField
# everything else gets string by default
return SwaggerType(type=openapi.TYPE_STRING)
@ -302,7 +302,7 @@ def find_regex(regex_field):
if isinstance(validator, RegexValidator):
if regex_validator is not None:
# bail if multiple validators are found - no obvious way to choose
return None
return None # pragma: no cover
regex_validator = validator
# regex_validator.regex should be a compiled re object...

View File

@ -4,13 +4,24 @@ from articles.models import Article
class ArticleSerializer(serializers.ModelSerializer):
references = serializers.DictField(
help_text="this is a really bad example",
child=serializers.URLField(help_text="but i needed to test these 2 fields somehow"),
)
uuid = serializers.UUIDField(help_text="should articles have UUIDs?")
class Meta:
model = Article
fields = ('title', 'body', 'slug', 'date_created', 'date_modified')
fields = ('title', 'body', 'slug', 'date_created', 'date_modified', 'references', 'uuid')
read_only_fields = ('date_created', 'date_modified')
lookup_field = 'slug'
extra_kwargs = {'body': {'help_text': 'body serializer help_text'}}
class ImageUploadSerializer(serializers.Serializer):
what_am_i_doing = serializers.RegexField(regex=r"^69$", help_text="test")
image_styles = serializers.ListSerializer(
child=serializers.ChoiceField(choices=['wide', 'tall', 'thumb', 'social']),
help_text="Parameter with Items"
)
upload = serializers.ImageField(help_text="image serializer help_text")

View File

@ -28,9 +28,10 @@ class SnippetSerializer(serializers.Serializer):
code = serializers.CharField(style={'base_template': 'textarea.html'})
linenos = serializers.BooleanField(required=False)
language = LanguageSerializer(help_text="Sample help text for language")
style = serializers.ChoiceField(choices=STYLE_CHOICES, default='friendly')
styles = serializers.MultipleChoiceField(choices=STYLE_CHOICES, default=['friendly'])
lines = serializers.ListField(child=serializers.IntegerField(), allow_empty=True, allow_null=True, required=False)
example_projects = serializers.ListSerializer(child=ExampleProjectSerializer())
difficulty_factor = serializers.FloatField(help_text="this is here just to test FloatField")
def create(self, validated_data):
"""

View File

@ -97,6 +97,12 @@ AUTH_PASSWORD_VALIDATORS = [
},
]
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
)
}
SWAGGER_SETTINGS = {
'LOGIN_URL': '/admin/login',
'LOGOUT_URL': '/admin/logout',

View File

@ -1,6 +1,7 @@
from django.conf.urls import url, include
from django.contrib import admin
from rest_framework import permissions
from rest_framework.decorators import api_view
from drf_swagger import openapi
from drf_swagger.views import get_schema_view
@ -19,6 +20,12 @@ schema_view = get_schema_view(
permission_classes=(permissions.AllowAny,),
)
@api_view(['GET'])
def plain_view(request):
pass
urlpatterns = [
url(r'^swagger(?P<format>.json|.yaml)$', schema_view.without_ui(cache_timeout=0), name='schema-json'),
url(r'^swagger/$', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'),
@ -28,4 +35,5 @@ urlpatterns = [
url(r'^snippets/', include('snippets.urls')),
url(r'^articles/', include('articles.urls')),
url(r'^users/', include('users.urls')),
url(r'^plain/', plain_view),
]

View File

@ -4,9 +4,11 @@ from rest_framework import serializers
from snippets.models import Snippet
class UserSerializer(serializers.ModelSerializer):
class UserSerializerrr(serializers.ModelSerializer):
snippets = serializers.PrimaryKeyRelatedField(many=True, queryset=Snippet.objects.all())
last_connected_ip = serializers.IPAddressField(help_text="i'm out of ideas", protocol='ipv4', read_only=True)
last_connected_at = serializers.DateField(help_text="really?", read_only=True)
class Meta:
model = User
fields = ('id', 'username', 'snippets')
fields = ('id', 'username', 'email', 'snippets', 'last_connected_ip', 'last_connected_at')

View File

@ -7,16 +7,16 @@ from rest_framework.views import APIView
from drf_swagger import openapi
from drf_swagger.utils import swagger_auto_schema, no_body
from users.serializers import UserSerializer
from users.serializers import UserSerializerrr
class UserList(APIView):
"""UserList cbv classdoc"""
@swagger_auto_schema(responses={200: UserSerializer(many=True)})
@swagger_auto_schema(responses={200: UserSerializerrr(many=True)})
def get(self, request):
queryset = User.objects.all()
serializer = UserSerializer(queryset, many=True)
serializer = UserSerializerrr(queryset, many=True)
return Response(serializer.data)
@swagger_auto_schema(operation_description="apiview post description override", request_body=openapi.Schema(
@ -27,7 +27,7 @@ class UserList(APIView):
},
))
def post(self, request):
serializer = UserSerializer(request.data)
serializer = UserSerializerrr(request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
@ -37,15 +37,15 @@ class UserList(APIView):
pass
@swagger_auto_schema(method='put', request_body=UserSerializer)
@swagger_auto_schema(method='put', request_body=UserSerializerrr)
@swagger_auto_schema(methods=['get'], manual_parameters=[
openapi.Parameter('test', openapi.IN_QUERY, "test manual param", type=openapi.TYPE_BOOLEAN),
], responses={
200: openapi.Response('response description', UserSerializer),
200: openapi.Response('response description', UserSerializerrr),
})
@api_view(['GET', 'PUT'])
def user_detail(request, pk):
"""user_detail fbv docstring"""
user = get_object_or_404(User.objects, pk=pk)
serializer = UserSerializer(user)
serializer = UserSerializerrr(user)
return Response(serializer.data)

View File

@ -1,3 +1,4 @@
import copy
import json
import os
@ -27,8 +28,13 @@ def codec_yaml():
@pytest.fixture
def swagger_dict():
swagger = generator().get_schema(None, True)
def swagger(generator):
return generator.get_schema(None, True)
@pytest.fixture
def swagger_dict(generator):
swagger = generator.get_schema(None, True)
json_bytes = codec_json().encode(swagger)
return json.loads(json_bytes.decode('utf-8'))
@ -46,16 +52,17 @@ def validate_schema():
@pytest.fixture
def bad_settings():
from drf_swagger.app_settings import swagger_settings, SWAGGER_DEFAULTS
bad_security = {
'bad': {
'bad_attribute': 'should not be accepted'
}
}
SWAGGER_DEFAULTS['SECURITY_DEFINITIONS'].update(bad_security)
yield swagger_settings
del SWAGGER_DEFAULTS['SECURITY_DEFINITIONS']['bad']
def swagger_settings(settings):
swagger_settings = copy.deepcopy(settings.SWAGGER_SETTINGS)
settings.SWAGGER_SETTINGS = swagger_settings
return swagger_settings
@pytest.fixture
def redoc_settings(settings):
redoc_settings = copy.deepcopy(settings.REDOC_SETTINGS)
settings.REDOC_SETTINGS = redoc_settings
return redoc_settings
@pytest.fixture

View File

@ -141,17 +141,6 @@ paths:
- application/json
tags:
- articles
delete:
operationId: articles_delete
description: destroy method docstring
parameters: []
responses:
'204':
description: ''
consumes:
- application/json
tags:
- articles
patch:
operationId: articles_partial_update
description: partial_update description override
@ -172,6 +161,17 @@ paths:
- application/json
tags:
- articles
delete:
operationId: articles_delete
description: destroy method docstring
parameters: []
responses:
'204':
description: ''
consumes:
- application/json
tags:
- articles
parameters:
- name: slug
in: path
@ -197,6 +197,24 @@ paths:
operationId: articles_image_create
description: image method docstring
parameters:
- name: what_am_i_doing
in: formData
description: test
required: true
type: string
pattern: ^69$
- name: image_styles
in: formData
description: Parameter with Items
required: true
type: array
items:
type: string
enum:
- wide
- tall
- thumb
- social
- name: upload
in: formData
description: image serializer help_text
@ -216,6 +234,19 @@ paths:
required: true
type: string
pattern: '[a-z0-9]+(?:-[a-z0-9]+)'
/plain/:
get:
operationId: plain_list
description: ''
parameters: []
responses:
'200':
description: ''
consumes:
- application/json
tags:
- plain
parameters: []
/snippets/:
get:
operationId: snippets_list
@ -283,17 +314,6 @@ paths:
- application/json
tags:
- snippets
delete:
operationId: snippets_delete
description: delete method docstring
parameters: []
responses:
'204':
description: ''
consumes:
- application/json
tags:
- snippets
patch:
operationId: snippets_partial_update
description: patch method docstring
@ -312,6 +332,17 @@ paths:
- application/json
tags:
- snippets
delete:
operationId: snippets_delete
description: delete method docstring
parameters: []
responses:
'204':
description: ''
consumes:
- application/json
tags:
- snippets
parameters:
- name: id
in: path
@ -329,7 +360,7 @@ paths:
schema:
type: array
items:
$ref: '#/definitions/User'
$ref: '#/definitions/UserSerializerrr'
consumes:
- application/json
tags:
@ -381,7 +412,7 @@ paths:
'200':
description: response description
schema:
$ref: '#/definitions/User'
$ref: '#/definitions/UserSerializerrr'
consumes:
- application/json
tags:
@ -394,12 +425,12 @@ paths:
in: body
required: true
schema:
$ref: '#/definitions/User'
$ref: '#/definitions/UserSerializerrr'
responses:
'200':
description: ''
schema:
$ref: '#/definitions/User'
$ref: '#/definitions/UserSerializerrr'
consumes:
- application/json
tags:
@ -414,6 +445,8 @@ definitions:
required:
- title
- body
- references
- uuid
type: object
properties:
title:
@ -426,6 +459,7 @@ definitions:
description: slug model help_text
type: string
format: slug
pattern: ^[-a-zA-Z0-9_]+$
date_created:
type: string
format: date-time
@ -434,6 +468,17 @@ definitions:
type: string
format: date-time
readOnly: true
references:
description: this is a really bad example
type: object
additionalProperties:
description: but i needed to test these 2 fields somehow
type: string
format: uri
uuid:
description: should articles have UUIDs?
type: string
format: uuid
Project:
required:
- project_name
@ -451,6 +496,7 @@ definitions:
- code
- language
- example_projects
- difficulty_factor
type: object
properties:
id:
@ -908,38 +954,43 @@ definitions:
- yaml
- yaml+jinja
- zephir
style:
type: string
enum:
- abap
- algol
- algol_nu
- arduino
- autumn
- borland
- bw
- colorful
- default
- emacs
default: python
styles:
type: array
items:
type: string
enum:
- abap
- algol
- algol_nu
- arduino
- autumn
- borland
- bw
- colorful
- default
- emacs
- friendly
- fruity
- igor
- lovelace
- manni
- monokai
- murphy
- native
- paraiso-dark
- paraiso-light
- pastie
- perldoc
- rainbow_dash
- rrt
- tango
- trac
- vim
- vs
- xcode
default:
- friendly
- fruity
- igor
- lovelace
- manni
- monokai
- murphy
- native
- paraiso-dark
- paraiso-light
- pastie
- perldoc
- rainbow_dash
- rrt
- tango
- trac
- vim
- vs
- xcode
lines:
type: array
items:
@ -948,7 +999,10 @@ definitions:
type: array
items:
$ref: '#/definitions/Project'
User:
difficulty_factor:
description: this is here just to test FloatField
type: number
UserSerializerrr:
required:
- username
- snippets
@ -961,11 +1015,24 @@ definitions:
description: Required. 150 characters or fewer. Letters, digits and @/./+/-/_
only.
type: string
email:
type: string
format: email
snippets:
type: array
items:
type: string
uniqueItems: true
last_connected_ip:
description: i'm out of ideas
type: string
format: ipv4
readOnly: true
last_connected_at:
description: really?
type: string
format: date
readOnly: true
securityDefinitions:
basic:
type: basic

View File

@ -0,0 +1,50 @@
import json
import pytest
from drf_swagger import renderers
def _check_swagger_setting(swagger, setting, expected):
context = {}
renderer = renderers.SwaggerUIRenderer()
renderer.set_context(context, swagger)
swagger_settings = json.loads(context['swagger_settings'])
assert swagger_settings[setting] == expected
def _check_setting(swagger, setting, expected):
context = {}
renderer = renderers.SwaggerUIRenderer()
renderer.set_context(context, swagger)
assert context[setting] == expected
def test_validator_url(swagger_settings, swagger):
swagger_settings['VALIDATOR_URL'] = None
_check_swagger_setting(swagger, 'validatorUrl', None)
swagger_settings['VALIDATOR_URL'] = 'not none'
_check_swagger_setting(swagger, 'validatorUrl', 'not none')
with pytest.raises(KeyError):
swagger_settings['VALIDATOR_URL'] = ''
_check_swagger_setting(swagger, 'validatorUrl', None)
@pytest.mark.urls('urlconfs.login_test_urls')
def test_login_logout(swagger_settings, swagger):
swagger_settings['LOGIN_URL'] = 'login'
_check_setting(swagger, 'LOGIN_URL', '/test/login')
swagger_settings['LOGOUT_URL'] = 'logout'
_check_setting(swagger, 'LOGOUT_URL', '/test/logout')
with pytest.raises(KeyError):
swagger_settings['LOGIN_URL'] = None
_check_setting(swagger, 'LOGIN_URL', None)
with pytest.raises(KeyError):
swagger_settings['LOGOUT_URL'] = None
_check_setting(swagger, 'LOGOUT_URL', None)

View File

@ -12,7 +12,7 @@ def test_schema_generates_without_errors(generator):
def test_schema_is_valid(generator, codec_yaml):
swagger = generator.get_schema(None, True)
swagger = generator.get_schema(request=None, public=False)
codec_yaml.encode(swagger)
@ -42,3 +42,17 @@ def test_yaml_codec_roundtrip(codec_yaml, generator, validate_schema):
yaml_bytes = codec_yaml.encode(swagger)
assert b'omap' not in yaml_bytes
validate_schema(yaml.safe_load(yaml_bytes.decode('utf-8')))
def test_basepath_only():
generator = OpenAPISchemaGenerator(
info=openapi.Info(title="Test generator", default_version="v1"),
version="v2",
url='/basepath/',
)
swagger = generator.get_schema(None, public=True)
assert 'host' not in swagger
assert 'schemes' not in swagger
assert swagger['basePath'] == '/' # base path is not implemented for now
assert swagger['info']['version'] == 'v2'

View File

@ -1,5 +1,6 @@
import json
import pytest
from ruamel import yaml
@ -23,7 +24,13 @@ def test_swagger_yaml(client, validate_schema):
_validate_text_schema_view(client, validate_schema, "/swagger.yaml", yaml.safe_load)
def test_exception_middleware(client, bad_settings):
def test_exception_middleware(client, swagger_settings):
swagger_settings['SECURITY_DEFINITIONS'] = {
'bad': {
'bad_attribute': 'should not be accepted'
}
}
response = client.get('/swagger.json')
assert response.status_code == 500
assert 'errors' in json.loads(response.content.decode('utf-8'))
@ -37,3 +44,10 @@ def test_swagger_ui(client, validate_schema):
def test_redoc(client, validate_schema):
_validate_ui_schema_view(client, '/redoc/', 'redoc/redoc.min.js')
_validate_text_schema_view(client, validate_schema, '/redoc/?format=openapi', json.loads)
@pytest.mark.urls('urlconfs.non_public_urls')
def test_non_public(client):
response = client.get('/private/swagger.yaml')
swagger = yaml.safe_load(response.content.decode('utf-8'))
assert len(swagger['paths']) == 0

View File

View File

@ -0,0 +1,11 @@
from django.conf.urls import url
def dummy(request):
pass
urlpatterns = [
url(r'^test/login$', dummy, name='login'),
url(r'^test/logout$', dummy, name='logout'),
]

View File

@ -0,0 +1,19 @@
from django.conf.urls import url
from django.conf.urls import include
from rest_framework import permissions
import testproj.urls
from drf_swagger import openapi
from drf_swagger.views import get_schema_view
view = get_schema_view(
openapi.Info('bla', 'ble'),
public=False,
permission_classes=(permissions.AllowAny,)
)
view = view.without_ui(cache_timeout=None)
urlpatterns = [
url(r'^', include(testproj.urls)),
url(r'^private/swagger.yaml', view, name='schema-private'),
]