From 2b0d80dc0fe63249bac08413f066ab736c8a5535 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cristi=20V=C3=AEjdea?= Date: Sat, 2 Dec 2017 22:46:07 +0100 Subject: [PATCH] Restructure project to add test support * separated drf_swagger and testproj modules, moved both out of project root * added testing support via pytest and tox * enabled Travis CI * integrated coverage & Coveralls --- .travis.yml | 17 ++++ MANIFEST.in | 5 +- pytest.ini | 3 + requirements_dev.txt | 6 +- requirements_test.txt | 6 ++ requirements_validation.txt | 1 + setup.py | 9 +- {drf_swagger => src/drf_swagger}/__init__.py | 0 .../drf_swagger}/app_settings.py | 0 .../codec.py => src/drf_swagger/codecs.py | 0 .../drf_swagger}/generators.py | 4 +- .../drf_swagger}/inspectors.py | 0 {drf_swagger => src/drf_swagger}/openapi.py | 4 +- {drf_swagger => src/drf_swagger}/renderers.py | 2 +- .../static/drf-swagger/insQ.min.js | 0 .../static/drf-swagger/redoc/redoc.min.js | 0 .../drf-swagger/swagger-ui-dist/.npmignore | 0 .../drf-swagger/swagger-ui-dist/README.md | 0 .../swagger-ui-dist/absolute-path.js | 0 .../swagger-ui-dist/favicon-16x16.png | Bin .../swagger-ui-dist/favicon-32x32.png | Bin .../drf-swagger/swagger-ui-dist/index.js | 0 .../swagger-ui-dist/oauth2-redirect.html | 0 .../drf-swagger/swagger-ui-dist/package.json | 0 .../swagger-ui-dist/swagger-ui-bundle.js | 0 .../swagger-ui-dist/swagger-ui-bundle.js.map | 0 .../swagger-ui-standalone-preset.js | 0 .../swagger-ui-standalone-preset.js.map | 0 .../swagger-ui-dist/swagger-ui.css | 0 .../swagger-ui-dist/swagger-ui.css.map | 0 .../drf-swagger/swagger-ui-dist/swagger-ui.js | 0 .../swagger-ui-dist/swagger-ui.js.map | 0 .../templates/drf-swagger/redoc.html | 0 .../templates/drf-swagger/swagger-ui.html | 0 {drf_swagger => src/drf_swagger}/views.py | 0 testproj/snippets/serializers.py | 8 +- testproj/snippets/views.py | 22 +++++ testproj/testproj/runner.py | 32 ++++++ testproj/testproj/settings.py | 4 + testproj/testproj/tests.py | 91 ++++++++++++++++++ tests/conftest.py | 30 ++++++ tests/test_schema_generator.py | 51 ++++++++++ tests/test_schema_structure.py | 24 +++++ tox.ini | 9 ++ 44 files changed, 314 insertions(+), 14 deletions(-) create mode 100644 .travis.yml create mode 100644 pytest.ini rename {drf_swagger => src/drf_swagger}/__init__.py (100%) rename {drf_swagger => src/drf_swagger}/app_settings.py (100%) rename drf_swagger/codec.py => src/drf_swagger/codecs.py (100%) rename {drf_swagger => src/drf_swagger}/generators.py (81%) rename {drf_swagger => src/drf_swagger}/inspectors.py (100%) rename {drf_swagger => src/drf_swagger}/openapi.py (96%) rename {drf_swagger => src/drf_swagger}/renderers.py (98%) rename {drf_swagger => src/drf_swagger}/static/drf-swagger/insQ.min.js (100%) rename {drf_swagger => src/drf_swagger}/static/drf-swagger/redoc/redoc.min.js (100%) rename {drf_swagger => src/drf_swagger}/static/drf-swagger/swagger-ui-dist/.npmignore (100%) rename {drf_swagger => src/drf_swagger}/static/drf-swagger/swagger-ui-dist/README.md (100%) rename {drf_swagger => src/drf_swagger}/static/drf-swagger/swagger-ui-dist/absolute-path.js (100%) rename {drf_swagger => src/drf_swagger}/static/drf-swagger/swagger-ui-dist/favicon-16x16.png (100%) rename {drf_swagger => src/drf_swagger}/static/drf-swagger/swagger-ui-dist/favicon-32x32.png (100%) rename {drf_swagger => src/drf_swagger}/static/drf-swagger/swagger-ui-dist/index.js (100%) rename {drf_swagger => src/drf_swagger}/static/drf-swagger/swagger-ui-dist/oauth2-redirect.html (100%) rename {drf_swagger => src/drf_swagger}/static/drf-swagger/swagger-ui-dist/package.json (100%) rename {drf_swagger => src/drf_swagger}/static/drf-swagger/swagger-ui-dist/swagger-ui-bundle.js (100%) rename {drf_swagger => src/drf_swagger}/static/drf-swagger/swagger-ui-dist/swagger-ui-bundle.js.map (100%) rename {drf_swagger => src/drf_swagger}/static/drf-swagger/swagger-ui-dist/swagger-ui-standalone-preset.js (100%) rename {drf_swagger => src/drf_swagger}/static/drf-swagger/swagger-ui-dist/swagger-ui-standalone-preset.js.map (100%) rename {drf_swagger => src/drf_swagger}/static/drf-swagger/swagger-ui-dist/swagger-ui.css (100%) rename {drf_swagger => src/drf_swagger}/static/drf-swagger/swagger-ui-dist/swagger-ui.css.map (100%) rename {drf_swagger => src/drf_swagger}/static/drf-swagger/swagger-ui-dist/swagger-ui.js (100%) rename {drf_swagger => src/drf_swagger}/static/drf-swagger/swagger-ui-dist/swagger-ui.js.map (100%) rename {drf_swagger => src/drf_swagger}/templates/drf-swagger/redoc.html (100%) rename {drf_swagger => src/drf_swagger}/templates/drf-swagger/swagger-ui.html (100%) rename {drf_swagger => src/drf_swagger}/views.py (100%) create mode 100644 testproj/testproj/runner.py create mode 100644 testproj/testproj/tests.py create mode 100644 tests/conftest.py create mode 100644 tests/test_schema_generator.py create mode 100644 tests/test_schema_structure.py create mode 100644 tox.ini diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..ac6e1a0 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,17 @@ +language: python +python: + - '3.5' + - '3.6' + - '3.7' +env: + - TOX_ENV=py35 + - TOX_ENV=py36 + - TOX_ENV=py37 +install: + - pip install requirements_dev.txt +before_script: + - coverage erase +script: + - tox -e $TOX_ENV +after_success: + - coveralls \ No newline at end of file diff --git a/MANIFEST.in b/MANIFEST.in index 3772d97..93f2962 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,5 @@ include README.md include LICENSE -recursive-include drf_swagger/static * -recursive-include drf_swagger/templates * +include requirements* +recursive-include src/drf_swagger/static * +recursive-include src/drf_swagger/templates * diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..d804cf1 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,3 @@ +[pytest] +DJANGO_SETTINGS_MODULE = testproj.settings +python_paths = testproj \ No newline at end of file diff --git a/requirements_dev.txt b/requirements_dev.txt index 9fbf11b..c1302f5 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -1,2 +1,4 @@ -pygments>=2.2.0 -django-cors-headers>=2.1.0 +# Packages required for development and CI +tox>=2.9.1 +tox-battery>=0.5 +python-coveralls>=2.9.1 diff --git a/requirements_test.txt b/requirements_test.txt index e69de29..d090a94 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -0,0 +1,6 @@ +# Packages required for running the tests +pygments>=2.2.0 +django-cors-headers>=2.1.0 +pytest-django>=3.1.2 +pytest-pythonpath>=0.7.1 +pytest-cov>=2.5.1 diff --git a/requirements_validation.txt b/requirements_validation.txt index 6ff69f1..cc26ca0 100644 --- a/requirements_validation.txt +++ b/requirements_validation.txt @@ -1,2 +1,3 @@ +# Packages required for the validation feature flex>=6.11.1 swagger-spec-validator>=2.1.0 diff --git a/setup.py b/setup.py index 98e81f9..6d196f9 100644 --- a/setup.py +++ b/setup.py @@ -12,16 +12,19 @@ requirements = read_req('requirements.txt') requirements_validation = read_req('requirements_validation.txt') requirements_dev = read_req('requirements_dev.txt') requirements_test = read_req('requirements_test.txt') -0 + setup( name='drf-swagger', version='1.0.0rc1', - packages=find_packages(include=['drf_swagger']), + packages=find_packages('src', include=['drf_swagger']), + package_dir={'': 'src'}, include_package_data=True, install_requires=requirements, tests_require=requirements_test, extras_require={ - 'validation': requirements_validation + 'validation': requirements_validation, + 'test': requirements_test, + 'dev': requirements_dev, }, license='BSD License', description='Automated generation of real Swagger/OpenAPI 2.0 schemas from Django Rest Framework code.', diff --git a/drf_swagger/__init__.py b/src/drf_swagger/__init__.py similarity index 100% rename from drf_swagger/__init__.py rename to src/drf_swagger/__init__.py diff --git a/drf_swagger/app_settings.py b/src/drf_swagger/app_settings.py similarity index 100% rename from drf_swagger/app_settings.py rename to src/drf_swagger/app_settings.py diff --git a/drf_swagger/codec.py b/src/drf_swagger/codecs.py similarity index 100% rename from drf_swagger/codec.py rename to src/drf_swagger/codecs.py diff --git a/drf_swagger/generators.py b/src/drf_swagger/generators.py similarity index 81% rename from drf_swagger/generators.py rename to src/drf_swagger/generators.py index 021acce..23e2ec0 100644 --- a/drf_swagger/generators.py +++ b/src/drf_swagger/generators.py @@ -1,9 +1,9 @@ -from rest_framework.schemas import SchemaGenerator +from rest_framework.schemas import SchemaGenerator as _SchemaGenerator from . import openapi -class OpenAPISchemaGenerator(SchemaGenerator): +class OpenAPISchemaGenerator(_SchemaGenerator): def __init__(self, info, version, url=None, patterns=None, urlconf=None): super(OpenAPISchemaGenerator, self).__init__(info.title, url, info.description, patterns, urlconf) self.info = info diff --git a/drf_swagger/inspectors.py b/src/drf_swagger/inspectors.py similarity index 100% rename from drf_swagger/inspectors.py rename to src/drf_swagger/inspectors.py diff --git a/drf_swagger/openapi.py b/src/drf_swagger/openapi.py similarity index 96% rename from drf_swagger/openapi.py rename to src/drf_swagger/openapi.py index cb88608..9434a15 100644 --- a/drf_swagger/openapi.py +++ b/src/drf_swagger/openapi.py @@ -102,9 +102,9 @@ class Swagger(coreapi.Document): :param string version: API version string :return: an openapi.Swagger """ - if document.title != info.title: + if document.title and document.title != info.title: warnings.warn("document title is overriden by Swagger Info") - if document.description != info.description: + if document.description and document.description != info.description: warnings.warn("document description is overriden by Swagger Info") return Swagger( info=info, diff --git a/drf_swagger/renderers.py b/src/drf_swagger/renderers.py similarity index 98% rename from drf_swagger/renderers.py rename to src/drf_swagger/renderers.py index cbcbaca..90e6d43 100644 --- a/drf_swagger/renderers.py +++ b/src/drf_swagger/renderers.py @@ -3,7 +3,7 @@ from rest_framework.renderers import BaseRenderer from rest_framework.utils import json from .app_settings import swagger_settings, redoc_settings -from .codec import OpenAPICodecJson, VALIDATORS, OpenAPICodecYaml +from .codecs import OpenAPICodecJson, VALIDATORS, OpenAPICodecYaml class _SpecRenderer(BaseRenderer): diff --git a/drf_swagger/static/drf-swagger/insQ.min.js b/src/drf_swagger/static/drf-swagger/insQ.min.js similarity index 100% rename from drf_swagger/static/drf-swagger/insQ.min.js rename to src/drf_swagger/static/drf-swagger/insQ.min.js diff --git a/drf_swagger/static/drf-swagger/redoc/redoc.min.js b/src/drf_swagger/static/drf-swagger/redoc/redoc.min.js similarity index 100% rename from drf_swagger/static/drf-swagger/redoc/redoc.min.js rename to src/drf_swagger/static/drf-swagger/redoc/redoc.min.js diff --git a/drf_swagger/static/drf-swagger/swagger-ui-dist/.npmignore b/src/drf_swagger/static/drf-swagger/swagger-ui-dist/.npmignore similarity index 100% rename from drf_swagger/static/drf-swagger/swagger-ui-dist/.npmignore rename to src/drf_swagger/static/drf-swagger/swagger-ui-dist/.npmignore diff --git a/drf_swagger/static/drf-swagger/swagger-ui-dist/README.md b/src/drf_swagger/static/drf-swagger/swagger-ui-dist/README.md similarity index 100% rename from drf_swagger/static/drf-swagger/swagger-ui-dist/README.md rename to src/drf_swagger/static/drf-swagger/swagger-ui-dist/README.md diff --git a/drf_swagger/static/drf-swagger/swagger-ui-dist/absolute-path.js b/src/drf_swagger/static/drf-swagger/swagger-ui-dist/absolute-path.js similarity index 100% rename from drf_swagger/static/drf-swagger/swagger-ui-dist/absolute-path.js rename to src/drf_swagger/static/drf-swagger/swagger-ui-dist/absolute-path.js diff --git a/drf_swagger/static/drf-swagger/swagger-ui-dist/favicon-16x16.png b/src/drf_swagger/static/drf-swagger/swagger-ui-dist/favicon-16x16.png similarity index 100% rename from drf_swagger/static/drf-swagger/swagger-ui-dist/favicon-16x16.png rename to src/drf_swagger/static/drf-swagger/swagger-ui-dist/favicon-16x16.png diff --git a/drf_swagger/static/drf-swagger/swagger-ui-dist/favicon-32x32.png b/src/drf_swagger/static/drf-swagger/swagger-ui-dist/favicon-32x32.png similarity index 100% rename from drf_swagger/static/drf-swagger/swagger-ui-dist/favicon-32x32.png rename to src/drf_swagger/static/drf-swagger/swagger-ui-dist/favicon-32x32.png diff --git a/drf_swagger/static/drf-swagger/swagger-ui-dist/index.js b/src/drf_swagger/static/drf-swagger/swagger-ui-dist/index.js similarity index 100% rename from drf_swagger/static/drf-swagger/swagger-ui-dist/index.js rename to src/drf_swagger/static/drf-swagger/swagger-ui-dist/index.js diff --git a/drf_swagger/static/drf-swagger/swagger-ui-dist/oauth2-redirect.html b/src/drf_swagger/static/drf-swagger/swagger-ui-dist/oauth2-redirect.html similarity index 100% rename from drf_swagger/static/drf-swagger/swagger-ui-dist/oauth2-redirect.html rename to src/drf_swagger/static/drf-swagger/swagger-ui-dist/oauth2-redirect.html diff --git a/drf_swagger/static/drf-swagger/swagger-ui-dist/package.json b/src/drf_swagger/static/drf-swagger/swagger-ui-dist/package.json similarity index 100% rename from drf_swagger/static/drf-swagger/swagger-ui-dist/package.json rename to src/drf_swagger/static/drf-swagger/swagger-ui-dist/package.json diff --git a/drf_swagger/static/drf-swagger/swagger-ui-dist/swagger-ui-bundle.js b/src/drf_swagger/static/drf-swagger/swagger-ui-dist/swagger-ui-bundle.js similarity index 100% rename from drf_swagger/static/drf-swagger/swagger-ui-dist/swagger-ui-bundle.js rename to src/drf_swagger/static/drf-swagger/swagger-ui-dist/swagger-ui-bundle.js diff --git a/drf_swagger/static/drf-swagger/swagger-ui-dist/swagger-ui-bundle.js.map b/src/drf_swagger/static/drf-swagger/swagger-ui-dist/swagger-ui-bundle.js.map similarity index 100% rename from drf_swagger/static/drf-swagger/swagger-ui-dist/swagger-ui-bundle.js.map rename to src/drf_swagger/static/drf-swagger/swagger-ui-dist/swagger-ui-bundle.js.map diff --git a/drf_swagger/static/drf-swagger/swagger-ui-dist/swagger-ui-standalone-preset.js b/src/drf_swagger/static/drf-swagger/swagger-ui-dist/swagger-ui-standalone-preset.js similarity index 100% rename from drf_swagger/static/drf-swagger/swagger-ui-dist/swagger-ui-standalone-preset.js rename to src/drf_swagger/static/drf-swagger/swagger-ui-dist/swagger-ui-standalone-preset.js diff --git a/drf_swagger/static/drf-swagger/swagger-ui-dist/swagger-ui-standalone-preset.js.map b/src/drf_swagger/static/drf-swagger/swagger-ui-dist/swagger-ui-standalone-preset.js.map similarity index 100% rename from drf_swagger/static/drf-swagger/swagger-ui-dist/swagger-ui-standalone-preset.js.map rename to src/drf_swagger/static/drf-swagger/swagger-ui-dist/swagger-ui-standalone-preset.js.map diff --git a/drf_swagger/static/drf-swagger/swagger-ui-dist/swagger-ui.css b/src/drf_swagger/static/drf-swagger/swagger-ui-dist/swagger-ui.css similarity index 100% rename from drf_swagger/static/drf-swagger/swagger-ui-dist/swagger-ui.css rename to src/drf_swagger/static/drf-swagger/swagger-ui-dist/swagger-ui.css diff --git a/drf_swagger/static/drf-swagger/swagger-ui-dist/swagger-ui.css.map b/src/drf_swagger/static/drf-swagger/swagger-ui-dist/swagger-ui.css.map similarity index 100% rename from drf_swagger/static/drf-swagger/swagger-ui-dist/swagger-ui.css.map rename to src/drf_swagger/static/drf-swagger/swagger-ui-dist/swagger-ui.css.map diff --git a/drf_swagger/static/drf-swagger/swagger-ui-dist/swagger-ui.js b/src/drf_swagger/static/drf-swagger/swagger-ui-dist/swagger-ui.js similarity index 100% rename from drf_swagger/static/drf-swagger/swagger-ui-dist/swagger-ui.js rename to src/drf_swagger/static/drf-swagger/swagger-ui-dist/swagger-ui.js diff --git a/drf_swagger/static/drf-swagger/swagger-ui-dist/swagger-ui.js.map b/src/drf_swagger/static/drf-swagger/swagger-ui-dist/swagger-ui.js.map similarity index 100% rename from drf_swagger/static/drf-swagger/swagger-ui-dist/swagger-ui.js.map rename to src/drf_swagger/static/drf-swagger/swagger-ui-dist/swagger-ui.js.map diff --git a/drf_swagger/templates/drf-swagger/redoc.html b/src/drf_swagger/templates/drf-swagger/redoc.html similarity index 100% rename from drf_swagger/templates/drf-swagger/redoc.html rename to src/drf_swagger/templates/drf-swagger/redoc.html diff --git a/drf_swagger/templates/drf-swagger/swagger-ui.html b/src/drf_swagger/templates/drf-swagger/swagger-ui.html similarity index 100% rename from drf_swagger/templates/drf-swagger/swagger-ui.html rename to src/drf_swagger/templates/drf-swagger/swagger-ui.html diff --git a/drf_swagger/views.py b/src/drf_swagger/views.py similarity index 100% rename from drf_swagger/views.py rename to src/drf_swagger/views.py diff --git a/testproj/snippets/serializers.py b/testproj/snippets/serializers.py index ebe0407..49c85d4 100644 --- a/testproj/snippets/serializers.py +++ b/testproj/snippets/serializers.py @@ -15,11 +15,15 @@ class ExampleProjectsSerializer(serializers.Serializer): class SnippetSerializer(serializers.Serializer): - id = serializers.IntegerField(read_only=True) + """SnippetSerializer classdoc + + create: docstring for create from serializer classdoc + """ + id = serializers.IntegerField(read_only=True, help_text="id help text") title = serializers.CharField(required=False, allow_blank=True, max_length=100) code = serializers.CharField(style={'base_template': 'textarea.html'}) linenos = serializers.BooleanField(required=False) - language = LanguageSerializer() + language = LanguageSerializer(help_text="Sample help text for language") style = serializers.ChoiceField(choices=STYLE_CHOICES, default='friendly') lines = serializers.ListField(child=serializers.IntegerField(), allow_empty=True, allow_null=True, required=False) example_projects = serializers.ListSerializer(child=ExampleProjectsSerializer()) diff --git a/testproj/snippets/views.py b/testproj/snippets/views.py index 7bd5bc1..2754327 100644 --- a/testproj/snippets/views.py +++ b/testproj/snippets/views.py @@ -4,10 +4,32 @@ from snippets.serializers import SnippetSerializer class SnippetList(generics.ListCreateAPIView): + """SnippetList classdoc""" queryset = Snippet.objects.all() serializer_class = SnippetSerializer + def post(self, request, *args, **kwargs): + """post method docstring""" + return super().post(request, *args, **kwargs) + class SnippetDetail(generics.RetrieveUpdateDestroyAPIView): + """ + SnippetDetail classdoc + + put: + put class docstring + + patch: + patch class docstring + """ queryset = Snippet.objects.all() serializer_class = SnippetSerializer + + def patch(self, request, *args, **kwargs): + """patch method docstring""" + return super().patch(request, *args, **kwargs) + + def delete(self, request, *args, **kwargs): + """delete method docstring""" + return super().patch(request, *args, **kwargs) diff --git a/testproj/testproj/runner.py b/testproj/testproj/runner.py new file mode 100644 index 0000000..9ff39cd --- /dev/null +++ b/testproj/testproj/runner.py @@ -0,0 +1,32 @@ +import os + +class PytestTestRunner(object): + """Runs pytest to discover and run tests.""" + + def __init__(self, verbosity=1, failfast=False, keepdb=False, **kwargs): + self.verbosity = verbosity + self.failfast = failfast + self.keepdb = keepdb + + def run_tests(self, test_labels): + """Run pytest and return the exitcode. + + It translates some of Django's test command option to pytest's. + """ + import pytest + + argv = [] + if self.verbosity == 0: + argv.append('--quiet') + if self.verbosity == 2: + argv.append('--verbose') + if self.verbosity == 3: + argv.append('-vv') + if self.failfast: + argv.append('--exitfirst') + if self.keepdb: + argv.append('--reuse-db') + + argv.extend(test_labels) + os.chdir('..') + return pytest.main(argv) diff --git a/testproj/testproj/settings.py b/testproj/testproj/settings.py index f2d77d6..b9749ca 100644 --- a/testproj/testproj/settings.py +++ b/testproj/testproj/settings.py @@ -1,5 +1,7 @@ import os +import sys + # Build paths inside the project like this: os.path.join(BASE_DIR, ...) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) @@ -115,3 +117,5 @@ USE_TZ = True # https://docs.djangoproject.com/en/1.11/howto/static-files/ STATIC_URL = '/static/' + +TEST_RUNNER = 'testproj.runner.PytestTestRunner' diff --git a/testproj/testproj/tests.py b/testproj/testproj/tests.py new file mode 100644 index 0000000..b0be104 --- /dev/null +++ b/testproj/testproj/tests.py @@ -0,0 +1,91 @@ +import json + +from django.test import TestCase +from ruamel import yaml + +from drf_swagger import openapi, codecs +from drf_swagger.generators import OpenAPISchemaGenerator + + +class SchemaGeneratorTest(TestCase): + def setUp(self): + self.generator = OpenAPISchemaGenerator( + info=openapi.Info("Test generator", "v1"), + version="v2", + ) + self.codec_json = codecs.OpenAPICodecJson(['flex', 'ssv']) + self.codec_yaml = codecs.OpenAPICodecYaml(['ssv', 'flex']) + + def _validate_schema(self, swagger): + from flex.core import parse as validate_flex + from swagger_spec_validator.validator20 import validate_spec as validate_ssv + + validate_flex(swagger) + validate_ssv(swagger) + + def test_schema_generates_without_errors(self): + self.generator.get_schema(None, True) + + def test_schema_is_valid(self): + swagger = self.generator.get_schema(None, True) + self.codec_yaml.encode(swagger) + + def test_invalid_schema_fails(self): + bad_generator = OpenAPISchemaGenerator( + info=openapi.Info( + "Test generator", "v1", + contact=openapi.Contact(name=69, email=[]) + ), + version="v2", + ) + + swagger = bad_generator.get_schema(None, True) + with self.assertRaises(codecs.SwaggerValidationError): + self.codec_json.encode(swagger) + + def test_json_codec_roundtrip(self): + swagger = self.generator.get_schema(None, True) + json_bytes = self.codec_json.encode(swagger) + self._validate_schema(json.loads(json_bytes.decode('utf-8'))) + + def test_yaml_codec_roundtrip(self): + swagger = self.generator.get_schema(None, True) + json_bytes = self.codec_yaml.encode(swagger) + self._validate_schema(yaml.safe_load(json_bytes.decode('utf-8'))) + + +class SchemaTest(TestCase): + def setUp(self): + self.generator = OpenAPISchemaGenerator( + info=openapi.Info("Test generator", "v1"), + version="v2", + ) + self.codec_json = codecs.OpenAPICodecJson(['flex', 'ssv']) + self.codec_yaml = codecs.OpenAPICodecYaml(['ssv', 'flex']) + + self.swagger = self.generator.get_schema(None, True) + json_bytes = self.codec_yaml.encode(self.swagger) + self.swagger_dict = yaml.safe_load(json_bytes.decode('utf-8')) + + def test_paths_not_empty(self): + self.assertTrue(bool(self.swagger_dict['paths'])) + + def test_appropriate_status_codes(self): + snippets_list = self.swagger_dict['paths']['/snippets/'] + self.assertTrue('200' in snippets_list['get']['responses']) + self.assertTrue('201' in snippets_list['post']['responses']) + snippets_detail = self.swagger_dict['paths']['/snippets/{id}/'] + self.assertTrue('200' in snippets_detail['get']['responses']) + self.assertTrue('200' in snippets_detail['put']['responses']) + self.assertTrue('200' in snippets_detail['patch']['responses']) + self.assertTrue('204' in snippets_detail['delete']['responses']) + + def test_operation_docstrings(self): + snippets_list = self.swagger_dict['paths']['/snippets/'] + self.assertEqual(snippets_list['get']['description'], "SnippetList classdoc") + self.assertEqual(snippets_list['post']['description'], "post method docstring") + snippets_detail = self.swagger_dict['paths']['/snippets/{id}/'] + self.assertEqual(snippets_detail['get']['description'], "SnippetDetail classdoc") + self.assertEqual(snippets_detail['put']['description'], "put class docstring") + self.assertEqual(snippets_detail['patch']['description'], "patch method docstring") + self.assertEqual(snippets_detail['delete']['description'], "delete method docstring") diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..87fe968 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,30 @@ +import pytest +from ruamel import yaml + +from drf_swagger import openapi, codecs +from drf_swagger.generators import OpenAPISchemaGenerator + + +@pytest.fixture +def generator(): + return OpenAPISchemaGenerator( + info=openapi.Info("Test generator", "v1"), + version="v2", + ) + + +@pytest.fixture +def codec_json(): + return codecs.OpenAPICodecJson(['flex', 'ssv']) + + +@pytest.fixture +def codec_yaml(): + return codecs.OpenAPICodecYaml(['ssv', 'flex']) + + +@pytest.fixture +def swagger_dict(): + swagger = generator().get_schema(None, True) + json_bytes = codec_yaml().encode(swagger) + return yaml.safe_load(json_bytes.decode('utf-8')) diff --git a/tests/test_schema_generator.py b/tests/test_schema_generator.py new file mode 100644 index 0000000..6898631 --- /dev/null +++ b/tests/test_schema_generator.py @@ -0,0 +1,51 @@ +import json + +from ruamel import yaml + +from drf_swagger import openapi, codecs +from drf_swagger.generators import OpenAPISchemaGenerator + +import pytest + + +def validate_schema(swagger): + from flex.core import parse as validate_flex + from swagger_spec_validator.validator20 import validate_spec as validate_ssv + + validate_flex(swagger) + validate_ssv(swagger) + + +def test_schema_generates_without_errors(generator): + generator.get_schema(None, True) + + +def test_schema_is_valid(generator, codec_yaml): + swagger = generator.get_schema(None, True) + codec_yaml.encode(swagger) + + +def test_invalid_schema_fails(codec_json): + bad_generator = OpenAPISchemaGenerator( + info=openapi.Info( + "Test generator", "v1", + contact=openapi.Contact(name=69, email=[]) + ), + version="v2", + ) + + swagger = bad_generator.get_schema(None, True) + with pytest.raises(codecs.SwaggerValidationError): + codec_json.encode(swagger) + + +def test_json_codec_roundtrip(codec_json, generator): + swagger = generator.get_schema(None, True) + json_bytes = codec_json.encode(swagger) + validate_schema(json.loads(json_bytes.decode('utf-8'))) + + +def test_yaml_codec_roundtrip(codec_yaml, generator): + swagger = generator.get_schema(None, True) + json_bytes = codec_yaml.encode(swagger) + validate_schema(yaml.safe_load(json_bytes.decode('utf-8'))) diff --git a/tests/test_schema_structure.py b/tests/test_schema_structure.py new file mode 100644 index 0000000..7243489 --- /dev/null +++ b/tests/test_schema_structure.py @@ -0,0 +1,24 @@ +def test_paths_not_empty(swagger_dict): + assert bool(swagger_dict['paths']) + + +def test_appropriate_status_codes(swagger_dict): + snippets_list = swagger_dict['paths']['/snippets/'] + assert '200' in snippets_list['get']['responses'] + assert '201' in snippets_list['post']['responses'] + snippets_detail = swagger_dict['paths']['/snippets/{id}/'] + assert '200' in snippets_detail['get']['responses'] + assert '200' in snippets_detail['put']['responses'] + assert '200' in snippets_detail['patch']['responses'] + assert '204' in snippets_detail['delete']['responses'] + + +def test_operation_docstrings(swagger_dict): + snippets_list = swagger_dict['paths']['/snippets/'] + assert snippets_list['get']['description'] == "SnippetList classdoc" + assert snippets_list['post']['description'] == "post method docstring" + snippets_detail = swagger_dict['paths']['/snippets/{id}/'] + assert snippets_detail['get']['description'] == "SnippetDetail classdoc" + assert snippets_detail['put']['description'] == "put class docstring" + assert snippets_detail['patch']['description'] == "patch method docstring" + assert snippets_detail['delete']['description'] == "delete method docstring" diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..9676730 --- /dev/null +++ b/tox.ini @@ -0,0 +1,9 @@ +[tox] +envlist = py35,py36,py37 +[testenv] +deps= + -rrequirements.txt + -rrequirements_validation.txt + -rrequirements_test.txt +commands= + pytest --cov-append --cov=drf_swagger