From 3c790423dd02ac52de73ed89eb02199e5b400d78 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Thu, 13 Oct 2022 11:51:12 +0100 Subject: [PATCH 1/2] Add tox file This is the defacto way to run unit tests for most Python projects. The tox-poetry extension is used to provide integration with poetry. Signed-off-by: Stephen Finucane --- .github/workflows/python-test.yml | 2 +- .pre-commit-config.yaml | 2 +- tox.ini | 10 ++++++++++ 3 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 tox.ini diff --git a/.github/workflows/python-test.yml b/.github/workflows/python-test.yml index 2f7743da..f6cea286 100644 --- a/.github/workflows/python-test.yml +++ b/.github/workflows/python-test.yml @@ -23,7 +23,7 @@ jobs: uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} - + - name: Get full Python version id: full-python-version run: echo ::set-output name=version::$(python -c "import sys; print('-'.join(str(v) for v in sys.version_info))") diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 35275c38..9a47e619 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -22,7 +22,7 @@ repos: entry: flynt language: python additional_dependencies: ['flynt==0.64'] - + - id: black name: black entry: black diff --git a/tox.ini b/tox.ini new file mode 100644 index 00000000..77480043 --- /dev/null +++ b/tox.ini @@ -0,0 +1,10 @@ +[tox] +minversion = 3.18.0 +requires = tox-poetry + +[testenv] +commands = pytest {posargs:tests/} + +[testenv:lint] +allowlist_externals = poetry +commands = poetry run pre-commit run -a From 60b2c120235939f0d7f85bb432b72943194c01f3 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Thu, 13 Oct 2022 12:15:52 +0100 Subject: [PATCH 2/2] tests: Add test case for nullable references This is somewhat undefined behavior [1][2], however, it worked until openapi-core 0.15.x. Demonstrate this, pending a fix. [1] https://stackoverflow.com/a/48114924/613428 [2] https://github.com/OAI/OpenAPI-Specification/issues/1368 Signed-off-by: Stephen Finucane --- tests/integration/data/v3.0/nullable_ref.yaml | 58 +++++++++++++++++ .../validation/test_nullable_ref.py | 62 +++++++++++++++++++ 2 files changed, 120 insertions(+) create mode 100644 tests/integration/data/v3.0/nullable_ref.yaml create mode 100644 tests/integration/validation/test_nullable_ref.py diff --git a/tests/integration/data/v3.0/nullable_ref.yaml b/tests/integration/data/v3.0/nullable_ref.yaml new file mode 100644 index 00000000..2d2aabc2 --- /dev/null +++ b/tests/integration/data/v3.0/nullable_ref.yaml @@ -0,0 +1,58 @@ +openapi: "3.0.0" +info: + version: "0.1" + title: OpenAPI specification with nullable refs +paths: + /people/{personID}: + get: + summary: Show a person + parameters: + - name: personID + in: path + required: true + description: The ID of the person to retrieve + schema: + type: string + format: uuid + responses: + default: + description: Expected response to a valid request + content: + application/json: + schema: + $ref: '#/components/schemas/Person' +components: + schemas: + Person: + x-model: Person + type: object + required: + - id + - name + properties: + id: + description: The ID of the person. + type: string + format: uuid + name: + description: The full name of the person. + type: string + user: + description: The associated user account, if any. + nullable: true + allOf: + - $ref: '#/components/schemas/User' + User: + x-model: User + type: object + required: + - id + - username + properties: + id: + description: The ID of the user. + type: string + format: uuid + username: + description: The username of the user. + type: string diff --git a/tests/integration/validation/test_nullable_ref.py b/tests/integration/validation/test_nullable_ref.py new file mode 100644 index 00000000..8051edc8 --- /dev/null +++ b/tests/integration/validation/test_nullable_ref.py @@ -0,0 +1,62 @@ +import json +import uuid +from dataclasses import is_dataclass + +import pytest + +from openapi_core.testing import MockRequest +from openapi_core.testing import MockResponse +from openapi_core.validation.response import openapi_v30_response_validator + + +@pytest.fixture(scope="class") +def spec(factory): + return factory.spec_from_file("data/v3.0/nullable_ref.yaml") + + +class TestNullableRefs: + @pytest.mark.xfail(message="The nullable attribute should be respected") + def test_with_null_value(self, spec): + person = { + "id": str(uuid.uuid4()), + "name": "Joe Bloggs", + "user": None, + } + request = MockRequest("", "get", f"/people/{person['id']}") + response = MockResponse(json.dumps(person)) + + result = openapi_v30_response_validator.validate( + spec, request, response + ) + + assert not result.errors + assert is_dataclass(result.data) + assert result.data.__class__.__name__ == "Person" + assert result.data.id == uuid.UUID(person["id"]) + assert result.data.name == person["name"] + assert result.data.user is None + + def test_with_non_null_value(self, spec): + person = { + "id": str(uuid.uuid4()), + "name": "Joe Bloggs", + "user": { + "id": str(uuid.uuid4()), + "username": "joebloggs", + }, + } + request = MockRequest("", "get", f"/people/{person['id']}") + response = MockResponse(json.dumps(person)) + + result = openapi_v30_response_validator.validate( + spec, request, response + ) + + assert not result.errors + assert is_dataclass(result.data) + assert result.data.__class__.__name__ == "Person" + assert result.data.id == uuid.UUID(person["id"]) + assert result.data.name == person["name"] + assert result.data.user is not None + assert result.data.user.id == uuid.UUID(person["user"]["id"]) + assert result.data.user.username == person["user"]["username"]