From 3a622e0306eefd8fb7a90ff6452baa78774ae7a1 Mon Sep 17 00:00:00 2001 From: Correl Roush Date: Fri, 26 Feb 2021 23:03:33 -0500 Subject: [PATCH] Use spec objects rather than dictionaries --- tests/test_handler.py | 130 +++++++++++++++++++++++++----------- tests/test_testing.py | 60 +++++++++++------ tornado_openapi3/handler.py | 10 +-- tornado_openapi3/testing.py | 10 +-- 4 files changed, 140 insertions(+), 70 deletions(-) diff --git a/tests/test_handler.py b/tests/test_handler.py index 9bdf7f5..b7e6229 100644 --- a/tests/test_handler.py +++ b/tests/test_handler.py @@ -1,6 +1,7 @@ import json import unittest.mock +from openapi_core import create_spec # type: ignore from openapi_core.exceptions import OpenAPIError # type: ignore import tornado.httpclient # type: ignore import tornado.web # type: ignore @@ -10,56 +11,60 @@ from tornado_openapi3.handler import OpenAPIRequestHandler class ResourceHandler(OpenAPIRequestHandler): - spec = { - "openapi": "3.0.0", - "info": { - "title": "Test API", - "version": "1.0.0", - }, - "components": { - "schemas": { - "resource": { - "type": "object", - "properties": {"name": {"type": "string"}}, - "required": ["name"], + spec = create_spec( + { + "openapi": "3.0.0", + "info": { + "title": "Test API", + "version": "1.0.0", + }, + "components": { + "schemas": { + "resource": { + "type": "object", + "properties": {"name": {"type": "string"}}, + "required": ["name"], + }, + }, + "securitySchemes": { + "basicAuth": { + "type": "http", + "scheme": "bearer", + } }, }, - "securitySchemes": { - "basicAuth": { - "type": "http", - "scheme": "bearer", - } - }, - }, - "security": [{"basicAuth": []}], - "paths": { - "/resource": { - "post": { - "requestBody": { - "required": True, - "content": { - "application/vnd.example.resource+json": { - "schema": {"$ref": "#/components/schemas/resource"}, - } - }, - }, - "responses": { - "200": { - "description": "Success", + "security": [{"basicAuth": []}], + "paths": { + "/resource": { + "post": { + "requestBody": { + "required": True, "content": { "application/vnd.example.resource+json": { "schema": {"$ref": "#/components/schemas/resource"}, } }, }, - "401": { - "description": "Missing or invalid credentials", + "responses": { + "200": { + "description": "Success", + "content": { + "application/vnd.example.resource+json": { + "schema": { + "$ref": "#/components/schemas/resource" + }, + } + }, + }, + "401": { + "description": "Missing or invalid credentials", + }, }, - }, + } } - } - }, - } + }, + } + ) custom_media_type_deserializers = { "application/vnd.example.resource+json": json.loads, } @@ -75,6 +80,51 @@ class ResourceHandler(OpenAPIRequestHandler): ) +class DefaultSchemaTest(tornado.testing.AsyncHTTPTestCase): + def get_app(self) -> tornado.web.Application: + test = self + + class RequestHandler(OpenAPIRequestHandler): + async def prepare(self) -> None: + with test.assertRaises(NotImplementedError): + self.spec + + async def get(self) -> None: + ... + + return tornado.web.Application( + [ + (r"/", RequestHandler), + ] + ) + + def test_schema_must_be_implemented(self) -> None: + response = self.fetch("/") + self.assertEqual(200, response.code) + + +class DefaultDeserializers(tornado.testing.AsyncHTTPTestCase): + def get_app(self) -> tornado.web.Application: + test = self + + class RequestHandler(OpenAPIRequestHandler): + async def prepare(self) -> None: + test.assertEqual(dict(), self.custom_media_type_deserializers) + + async def get(self) -> None: + ... + + return tornado.web.Application( + [ + (r"/", RequestHandler), + ] + ) + + def test_schema_must_be_implemented(self) -> None: + response = self.fetch("/") + self.assertEqual(200, response.code) + + class RequestHandlerTests(tornado.testing.AsyncHTTPTestCase): def get_app(self) -> tornado.web.Application: return tornado.web.Application( diff --git a/tests/test_testing.py b/tests/test_testing.py index 5abc8c8..b0fa172 100644 --- a/tests/test_testing.py +++ b/tests/test_testing.py @@ -1,5 +1,6 @@ import json +from openapi_core import create_spec # type: ignore from openapi_core.schema.responses.exceptions import InvalidResponse # type: ignore import tornado.web # type: ignore @@ -8,29 +9,48 @@ from tornado_openapi3.testing import AsyncOpenAPITestCase def spec(responses: dict = dict()) -> dict: - return { - "openapi": "3.0.0", - "info": { - "title": "Test API", - "version": "1.0.0", - }, - "components": { - "schemas": { - "resource": { - "type": "object", - "properties": {"name": {"type": "string"}}, - "required": ["name"], + if not responses: + responses = {"200": {"description": "Success"}} + return create_spec( + { + "openapi": "3.0.0", + "info": { + "title": "Test API", + "version": "1.0.0", + }, + "components": { + "schemas": { + "resource": { + "type": "object", + "properties": {"name": {"type": "string"}}, + "required": ["name"], + }, }, }, - }, - "paths": { - "/resource": { - "get": { - "responses": responses, + "paths": { + "/resource": { + "get": { + "responses": responses, + } } - } - }, - } + }, + } + ) + + +class TestTestCase(AsyncOpenAPITestCase): + def setUp(self) -> None: + ... + + def tearDown(self) -> None: + ... + + def test_schema_must_be_implemented(self) -> None: + with self.assertRaises(NotImplementedError): + self.spec + + def test_no_custom_media_type_deserializers(self) -> None: + self.assertEqual(dict(), self.custom_media_type_deserializers) class BaseTestCase(AsyncOpenAPITestCase): diff --git a/tornado_openapi3/handler.py b/tornado_openapi3/handler.py index 163ac33..2da1ef9 100644 --- a/tornado_openapi3/handler.py +++ b/tornado_openapi3/handler.py @@ -1,9 +1,9 @@ import asyncio import logging -from openapi_core import create_spec # type: ignore from openapi_core.exceptions import OpenAPIError # type: ignore from openapi_core.deserializing.exceptions import DeserializeError # type: ignore +from openapi_core.schema.specs.models import Spec # type: ignore from openapi_core.schema.media_types.exceptions import ( # type: ignore InvalidContentType, ) @@ -22,14 +22,14 @@ logger = logging.getLogger(__name__) class OpenAPIRequestHandler(tornado.web.RequestHandler): @property - def spec(self) -> dict: - """The OpenAPI 3 specification as a Python dictionary. + def spec(self) -> Spec: + """The OpenAPI 3 specification. Override this in your request handlers to load or define your OpenAPI 3 spec. """ - return dict() + raise NotImplementedError() @property def custom_media_type_deserializers(self) -> dict: @@ -82,7 +82,7 @@ class OpenAPIRequestHandler(tornado.web.RequestHandler): await maybe_coro validator = RequestValidator( - create_spec(self.spec), + self.spec, custom_media_type_deserializers=self.custom_media_type_deserializers, ) result = validator.validate(self.request) diff --git a/tornado_openapi3/testing.py b/tornado_openapi3/testing.py index 0261cef..2c904e4 100644 --- a/tornado_openapi3/testing.py +++ b/tornado_openapi3/testing.py @@ -3,20 +3,20 @@ from typing import Any import tornado.httpclient # type: ignore import tornado.testing # type: ignore -from openapi_core import create_spec # type: ignore +from openapi_core.schema.specs.models import Spec # type: ignore from tornado_openapi3.responses import ResponseValidator class AsyncOpenAPITestCase(tornado.testing.AsyncHTTPTestCase): @property - def spec(self) -> dict: - """The OpenAPI 3 specification as a Python dictionary. + def spec(self) -> Spec: + """The OpenAPI 3 specification. Override this in your request handlers to load or define your OpenAPI 3 spec. """ - return dict() + raise NotImplementedError() @property def custom_media_type_deserializers(self) -> dict: @@ -38,7 +38,7 @@ class AsyncOpenAPITestCase(tornado.testing.AsyncHTTPTestCase): """ super().setUp() self.validator = ResponseValidator( - create_spec(self.spec), + self.spec, custom_media_type_deserializers=self.custom_media_type_deserializers, )