diff --git a/.github/workflows/python-test.yml b/.github/workflows/python-test.yml index 3f9dda9f..da753a3b 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/.gitignore b/.gitignore index 8d5d484e..f5c0084b 100644 --- a/.gitignore +++ b/.gitignore @@ -46,6 +46,7 @@ nosetests.xml coverage.xml *.cover .hypothesis/ +tests/**/reports # Translations *.mo @@ -104,4 +105,4 @@ ENV/ # Jetbrains project files .idea/ -/reports/ \ No newline at end of file +/reports/ diff --git a/openapi_core/spec/shortcuts.py b/openapi_core/spec/shortcuts.py index 093c5ab3..8f0e1e81 100644 --- a/openapi_core/spec/shortcuts.py +++ b/openapi_core/spec/shortcuts.py @@ -11,10 +11,11 @@ def create_spec( spec_dict, spec_url="", handlers=default_handlers, + spec_validator=openapi_v3_spec_validator, validate_spec=True, ): if validate_spec: - openapi_v3_spec_validator.validate(spec_dict, spec_url=spec_url) + spec_validator.validate(spec_dict, spec_url=spec_url) spec_resolver = RefResolver(spec_url, spec_dict, handlers=handlers) dereferencer = Dereferencer(spec_resolver) diff --git a/poetry.lock b/poetry.lock index df812ea8..31facacf 100644 --- a/poetry.lock +++ b/poetry.lock @@ -413,14 +413,15 @@ python-versions = "*" [[package]] name = "openapi-schema-validator" -version = "0.2.3" +version = "0.3.0a1" description = "OpenAPI schema validation for Python" category = "main" optional = false python-versions = ">=3.7.0,<4.0.0" [package.dependencies] -jsonschema = ">=3.0.0,<5.0.0" +attrs = ">=19.2.0" +jsonschema = ">=4.0.0,<5.0.0" [package.extras] rfc3339-validator = ["rfc3339-validator"] @@ -429,15 +430,15 @@ isodate = ["isodate"] [[package]] name = "openapi-spec-validator" -version = "0.4.0" -description = "OpenAPI 2.0 (aka Swagger) and OpenAPI 3.0 spec validator" +version = "0.5.0a1" +description = "OpenAPI 2.0 (aka Swagger) and OpenAPI 3 spec validator" category = "main" optional = false python-versions = ">=3.7.0,<4.0.0" [package.dependencies] -jsonschema = ">=3.2.0,<5.0.0" -openapi-schema-validator = ">=0.2.0,<0.3.0" +jsonschema = ">=4.0.0,<5.0.0" +openapi-schema-validator = ">=0.3.0a1,<0.4.0" PyYAML = ">=5.1" [package.extras] @@ -1125,12 +1126,22 @@ jsonschema = [ {file = "jsonschema-4.4.0.tar.gz", hash = "sha256:636694eb41b3535ed608fe04129f26542b59ed99808b4f688aa32dcf55317a83"}, ] markupsafe = [ + {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:36bc903cbb393720fad60fc28c10de6acf10dc6cc883f3e24ee4012371399a38"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d7d807855b419fc2ed3e631034685db6079889a1f01d5d9dac950f764da3dad"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:add36cb2dbb8b736611303cd3bfcee00afd96471b09cda130da3581cbdc56a6d"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:168cd0a3642de83558a5153c8bd34f175a9a6e7f6dc6384b9655d2697312a646"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-win32.whl", hash = "sha256:99df47edb6bda1249d3e80fdabb1dab8c08ef3975f69aed437cb69d0a5de1e28"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:e0f138900af21926a02425cf736db95be9f4af72ba1bb21453432a07f6082134"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf5d821ffabf0ef3533c39c518f3357b171a1651c1ff6827325e4489b0e46c3c"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0d4b31cc67ab36e3392bbf3862cfbadac3db12bdd8b02a2731f509ed5b829724"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:baa1a4e8f868845af802979fcdbf0bb11f94f1cb7ced4c4b8a351bb60d108145"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-win32.whl", hash = "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567"}, @@ -1139,14 +1150,21 @@ markupsafe = [ {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9936f0b261d4df76ad22f8fee3ae83b60d7c3e871292cd42f40b81b70afae85"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2a7d351cbd8cfeb19ca00de495e224dea7e7d919659c2841bbb7f420ad03e2d6"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:60bf42e36abfaf9aff1f50f52644b336d4f0a3fd6d8a60ca0d054ac9f713a864"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-win32.whl", hash = "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5bb28c636d87e840583ee3adeb78172efc47c8b26127267f54a9c0ec251d41a9"}, {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066"}, {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35"}, {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b"}, {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298"}, {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75"}, {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fcf051089389abe060c9cd7caa212c707e58153afa2c649f00346ce6d260f1b"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5855f8438a7d1d458206a2466bf82b0f104a3724bf96a1c781ab731e4201731a"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3dd007d54ee88b46be476e293f48c85048603f5f516008bee124ddd891398ed6"}, {file = "MarkupSafe-2.0.1-cp38-cp38-win32.whl", hash = "sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64"}, {file = "MarkupSafe-2.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833"}, {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26"}, @@ -1156,6 +1174,9 @@ markupsafe = [ {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135"}, {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902"}, {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c47adbc92fc1bb2b3274c4b3a43ae0e4573d9fbff4f54cd484555edbf030baf1"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:37205cac2a79194e3750b0af2a5720d95f786a55ce7df90c3af697bfa100eaac"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1f2ade76b9903f39aa442b4aadd2177decb66525062db244b35d71d0ee8599b6"}, {file = "MarkupSafe-2.0.1-cp39-cp39-win32.whl", hash = "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74"}, {file = "MarkupSafe-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8"}, {file = "MarkupSafe-2.0.1.tar.gz", hash = "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a"}, @@ -1177,12 +1198,12 @@ nodeenv = [ {file = "nodeenv-1.6.0.tar.gz", hash = "sha256:3ef13ff90291ba2a4a7a4ff9a979b63ffdd00a464dbe04acf0ea6471517a4c2b"}, ] openapi-schema-validator = [ - {file = "openapi-schema-validator-0.2.3.tar.gz", hash = "sha256:2c64907728c3ef78e23711c8840a423f0b241588c9ed929855e4b2d1bb0cf5f2"}, - {file = "openapi_schema_validator-0.2.3-py3-none-any.whl", hash = "sha256:9bae709212a19222892cabcc60cafd903cbf4b220223f48583afa3c0e3cc6fc4"}, + {file = "openapi-schema-validator-0.3.0a1.tar.gz", hash = "sha256:5e27fcc49663678b90516466c0f4532ed613221cf918899cf718e3e1f4af2f24"}, + {file = "openapi_schema_validator-0.3.0a1-py3-none-any.whl", hash = "sha256:9b287390faf08f5712701ae61fa900414d2df32174bc401e6e15bbbbd8f33841"}, ] openapi-spec-validator = [ - {file = "openapi-spec-validator-0.4.0.tar.gz", hash = "sha256:97f258850afc97b048f7c2653855e0f88fa66ac103c2be5077c7960aca2ad49a"}, - {file = "openapi_spec_validator-0.4.0-py3-none-any.whl", hash = "sha256:06900ac4d546a1df3642a779da0055be58869c598e3042a2fef067cfd99d04d0"}, + {file = "openapi-spec-validator-0.5.0a1.tar.gz", hash = "sha256:780714eb30452e9c5e663d4ed4bbe89f7119c3811f74de9993993b79e07963c6"}, + {file = "openapi_spec_validator-0.5.0a1-py3-none-any.whl", hash = "sha256:16325b019dc79edf30cd25ecc2ec4b9176bea31fa2ce8da11b3cf41cc98198e9"}, ] packaging = [ {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, diff --git a/pyproject.toml b/pyproject.toml index b8c1dc9d..309e188b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,8 +41,8 @@ flask = {version = "*", optional = true} isodate = "*" more-itertools = "*" parse = "*" -openapi-schema-validator = "^0.2.0" -openapi-spec-validator = "^0.4.0" +openapi-schema-validator = {version = "0.3.0a1", allow-prereleases = true} +openapi-spec-validator = {version = "0.5.0a1", allow-prereleases = true} requests = {version = "*", optional = true} werkzeug = "*" diff --git a/tests/integration/contrib/django/data/v3.0/djangoproject/settings.py b/tests/integration/contrib/django/data/v3.0/djangoproject/settings.py index e02e990b..efaf3445 100644 --- a/tests/integration/contrib/django/data/v3.0/djangoproject/settings.py +++ b/tests/integration/contrib/django/data/v3.0/djangoproject/settings.py @@ -14,6 +14,7 @@ from pathlib import Path import yaml +from openapi_spec_validator import openapi_v30_spec_validator from openapi_core import create_spec @@ -123,4 +124,6 @@ OPENAPI_SPEC_DICT = yaml.load(OPENAPI_SPEC_PATH.read_text(), yaml.Loader) -OPENAPI_SPEC = create_spec(OPENAPI_SPEC_DICT) +OPENAPI_SPEC = create_spec( + OPENAPI_SPEC_DICT, spec_validator=openapi_v30_spec_validator +) diff --git a/tests/integration/contrib/falcon/data/v3.0/falconproject/openapi.py b/tests/integration/contrib/falcon/data/v3.0/falconproject/openapi.py index b199ece4..20e09c64 100644 --- a/tests/integration/contrib/falcon/data/v3.0/falconproject/openapi.py +++ b/tests/integration/contrib/falcon/data/v3.0/falconproject/openapi.py @@ -1,11 +1,12 @@ from pathlib import Path import yaml +from openapi_spec_validator import openapi_v30_spec_validator from openapi_core import create_spec from openapi_core.contrib.falcon.middlewares import FalconOpenAPIMiddleware openapi_spec_path = Path("tests/integration/data/v3.0/petstore.yaml") spec_dict = yaml.load(openapi_spec_path.read_text(), yaml.Loader) -spec = create_spec(spec_dict) +spec = create_spec(spec_dict, spec_validator=openapi_v30_spec_validator) openapi_middleware = FalconOpenAPIMiddleware.from_spec(spec) diff --git a/tests/integration/contrib/flask/test_flask_decorator.py b/tests/integration/contrib/flask/test_flask_decorator.py index 518a39e5..74f7ed2f 100644 --- a/tests/integration/contrib/flask/test_flask_decorator.py +++ b/tests/integration/contrib/flask/test_flask_decorator.py @@ -2,6 +2,7 @@ from flask import Flask from flask import jsonify from flask import make_response +from openapi_spec_validator import openapi_v30_spec_validator from openapi_core.contrib.flask.decorators import FlaskOpenAPIViewDecorator from openapi_core.shortcuts import create_spec @@ -15,7 +16,10 @@ class TestFlaskOpenAPIDecorator: @pytest.fixture def spec(self, factory): specfile = "contrib/flask/data/v3.0/flask_factory.yaml" - return create_spec(factory.spec_from_file(specfile)) + return create_spec( + factory.spec_from_file(specfile), + spec_validator=openapi_v30_spec_validator, + ) @pytest.fixture def decorator(self, spec): diff --git a/tests/integration/contrib/flask/test_flask_validation.py b/tests/integration/contrib/flask/test_flask_validation.py index e4d0ccb2..4db69cf8 100644 --- a/tests/integration/contrib/flask/test_flask_validation.py +++ b/tests/integration/contrib/flask/test_flask_validation.py @@ -1,4 +1,5 @@ import pytest +from openapi_spec_validator import openapi_v30_spec_validator from openapi_core.contrib.flask import FlaskOpenAPIRequest from openapi_core.contrib.flask import FlaskOpenAPIResponse @@ -11,7 +12,10 @@ class TestFlaskOpenAPIValidation: @pytest.fixture def flask_spec(self, factory): specfile = "contrib/flask/data/v3.0/flask_factory.yaml" - return create_spec(factory.spec_from_file(specfile)) + return create_spec( + factory.spec_from_file(specfile), + spec_validator=openapi_v30_spec_validator, + ) def test_response_validator_path_pattern( self, flask_spec, request_factory, response_factory diff --git a/tests/integration/contrib/flask/test_flask_views.py b/tests/integration/contrib/flask/test_flask_views.py index d61dd7dc..06f70a5a 100644 --- a/tests/integration/contrib/flask/test_flask_views.py +++ b/tests/integration/contrib/flask/test_flask_views.py @@ -2,6 +2,7 @@ from flask import Flask from flask import jsonify from flask import make_response +from openapi_spec_validator import openapi_v30_spec_validator from openapi_core.contrib.flask.views import FlaskOpenAPIView from openapi_core.shortcuts import create_spec @@ -14,7 +15,10 @@ class TestFlaskOpenAPIView: @pytest.fixture def spec(self, factory): specfile = "contrib/flask/data/v3.0/flask_factory.yaml" - return create_spec(factory.spec_from_file(specfile)) + return create_spec( + factory.spec_from_file(specfile), + spec_validator=openapi_v30_spec_validator, + ) @pytest.fixture def app(self): diff --git a/tests/integration/contrib/requests/test_requests_validation.py b/tests/integration/contrib/requests/test_requests_validation.py index 42734b36..523aa31e 100644 --- a/tests/integration/contrib/requests/test_requests_validation.py +++ b/tests/integration/contrib/requests/test_requests_validation.py @@ -1,6 +1,7 @@ import pytest import requests import responses +from openapi_spec_validator import openapi_v30_spec_validator from openapi_core.contrib.requests import RequestsOpenAPIRequest from openapi_core.contrib.requests import RequestsOpenAPIResponse @@ -13,7 +14,10 @@ class TestRequestsOpenAPIValidation: @pytest.fixture def spec(self, factory): specfile = "contrib/requests/data/v3.0/requests_factory.yaml" - return create_spec(factory.spec_from_file(specfile)) + return create_spec( + factory.spec_from_file(specfile), + spec_validator=openapi_v30_spec_validator, + ) @responses.activate def test_response_validator_path_pattern(self, spec): diff --git a/tests/integration/data/v3.1/django_factory.yaml b/tests/integration/data/v3.1/django_factory.yaml new file mode 100644 index 00000000..4f7a9c5a --- /dev/null +++ b/tests/integration/data/v3.1/django_factory.yaml @@ -0,0 +1,19 @@ +openapi: "3.1.0" +info: + title: Basic OpenAPI specification used with test_flask.TestFlaskOpenAPIIValidation + version: "0.1" +servers: + - url: 'http://testserver' +paths: + '/admin/auth/group/{object_id}/': + parameters: + - name: object_id + in: path + required: true + description: the ID of the resource to retrieve + schema: + type: integer + get: + responses: + default: + description: Return the resource. diff --git a/tests/integration/data/v3.1/empty.yaml b/tests/integration/data/v3.1/empty.yaml new file mode 100644 index 00000000..7bd07431 --- /dev/null +++ b/tests/integration/data/v3.1/empty.yaml @@ -0,0 +1 @@ +openapi: "3.1.0" diff --git a/tests/integration/data/v3.1/links.yaml b/tests/integration/data/v3.1/links.yaml new file mode 100644 index 00000000..2d136499 --- /dev/null +++ b/tests/integration/data/v3.1/links.yaml @@ -0,0 +1,48 @@ +openapi: "3.1.0" +info: + title: Minimal valid OpenAPI specification + version: "0.1" +paths: + /linked/noParam: + get: + operationId: noParOp + responses: + default: + description: the linked result + /linked/withParam: + get: + operationId: paramOp + parameters: + - name: opParam + in: query + description: test + schema: + type: string + responses: + default: + description: the linked result + /status: + get: + responses: + default: + description: Return something + links: + noParamLink: + operationId: noParOp + /status/{resourceId}: + get: + parameters: + - name: resourceId + in: path + required: true + schema: + type: string + responses: + default: + description: Return something else + links: + paramLink: + operationId: paramOp + parameters: + opParam: $request.path.resourceId + requestBody: test diff --git a/tests/integration/data/v3.1/minimal.yaml b/tests/integration/data/v3.1/minimal.yaml new file mode 100644 index 00000000..59d0c559 --- /dev/null +++ b/tests/integration/data/v3.1/minimal.yaml @@ -0,0 +1,10 @@ +openapi: "3.1.0" +info: + title: Minimal valid OpenAPI specification + version: "0.1" +paths: + /status: + get: + responses: + default: + description: Return the API status. diff --git a/tests/integration/data/v3.1/minimal_with_servers.yaml b/tests/integration/data/v3.1/minimal_with_servers.yaml new file mode 100644 index 00000000..2bd04982 --- /dev/null +++ b/tests/integration/data/v3.1/minimal_with_servers.yaml @@ -0,0 +1,12 @@ +openapi: "3.1.0" +info: + title: Minimal valid OpenAPI specification with explicit 'servers' array + version: "0.1" +servers: + - url: / +paths: + /status: + get: + responses: + default: + description: Return the API status. diff --git a/tests/integration/data/v3.1/path_param.yaml b/tests/integration/data/v3.1/path_param.yaml new file mode 100644 index 00000000..021bc754 --- /dev/null +++ b/tests/integration/data/v3.1/path_param.yaml @@ -0,0 +1,17 @@ +openapi: "3.1.0" +info: + title: Minimal OpenAPI specification with path parameters + version: "0.1" +paths: + /resource/{resId}: + parameters: + - name: resId + in: path + required: true + description: the ID of the resource to retrieve + schema: + type: string + get: + responses: + default: + description: Return the resource. diff --git a/tests/integration/data/v3.1/security_override.yaml b/tests/integration/data/v3.1/security_override.yaml new file mode 100644 index 00000000..e161eefa --- /dev/null +++ b/tests/integration/data/v3.1/security_override.yaml @@ -0,0 +1,41 @@ +openapi: "3.1.0" +info: + title: Minimal OpenAPI specification with security override + version: "0.1" +security: + - api_key: [] +paths: + /resource/{resId}: + parameters: + - name: resId + in: path + required: true + description: the ID of the resource to retrieve + schema: + type: string + get: + responses: + default: + description: Default security. + post: + security: + - petstore_auth: + - write:pets + - read:pets + responses: + default: + description: Override security. + put: + security: [] + responses: + default: + description: Remove security. +components: + securitySchemes: + api_key: + type: apiKey + name: api_key + in: query + petstore_auth: + type: http + scheme: basic diff --git a/tests/integration/data/v3.1/webhook-example.yaml b/tests/integration/data/v3.1/webhook-example.yaml new file mode 100644 index 00000000..2ac1cda9 --- /dev/null +++ b/tests/integration/data/v3.1/webhook-example.yaml @@ -0,0 +1,35 @@ +openapi: 3.1.0 +info: + title: Webhook Example + version: 1.0.0 +# Since OAS 3.1.0 the paths element isn't necessary. Now a valid OpenAPI Document can describe only paths, webhooks, or even only reusable components +webhooks: + # Each webhook needs a name + newPet: + # This is a Path Item Object, the only difference is that the request is initiated by the API provider + post: + requestBody: + description: Information about a new pet in the system + content: + application/json: + schema: + $ref: "#/components/schemas/Pet" + responses: + "200": + description: Return a 200 status to indicate that the data was received successfully + +components: + schemas: + Pet: + required: + - id + - name + properties: + id: + type: integer + format: int64 + name: + type: string + tag: + type: string + diff --git a/tests/integration/schema/test_empty.py b/tests/integration/schema/test_empty.py index 98b12c72..8ba1bb5c 100644 --- a/tests/integration/schema/test_empty.py +++ b/tests/integration/schema/test_empty.py @@ -1,18 +1,36 @@ import pytest from jsonschema.exceptions import ValidationError +from openapi_spec_validator import openapi_v30_spec_validator +from openapi_spec_validator import openapi_v31_spec_validator from openapi_core.shortcuts import create_spec -class TestEmpty: +class BaseTestEmpty: + @pytest.fixture + def spec(self, spec_dict): + return create_spec(spec_dict) + + def test_raises_on_invalid(self, spec_dict, spec_validator): + with pytest.raises(ValidationError): + create_spec(spec_dict, spec_validator=spec_validator) + + +class TestEmpty30(BaseTestEmpty): @pytest.fixture def spec_dict(self, factory): return factory.spec_from_file("data/v3.0/empty.yaml") @pytest.fixture - def spec(self, spec_dict): - return create_spec(spec_dict) + def spec_validator(self, factory): + return openapi_v30_spec_validator - def test_raises_on_invalid(self, spec_dict): - with pytest.raises(ValidationError): - create_spec(spec_dict) + +class TestEmpty31(BaseTestEmpty): + @pytest.fixture + def spec_dict(self, factory): + return factory.spec_from_file("data/v3.1/empty.yaml") + + @pytest.fixture + def spec_validator(self, factory): + return openapi_v31_spec_validator diff --git a/tests/integration/schema/test_link_spec.py b/tests/integration/schema/test_link_spec.py index 2ed33fa3..299e24fd 100644 --- a/tests/integration/schema/test_link_spec.py +++ b/tests/integration/schema/test_link_spec.py @@ -1,10 +1,13 @@ +import pytest +from openapi_spec_validator import openapi_v30_spec_validator +from openapi_spec_validator import openapi_v31_spec_validator + from openapi_core.shortcuts import create_spec -class TestLinkSpec: - def test_no_param(self, factory): - spec_dict = factory.spec_from_file("data/v3.0/links.yaml") - spec = create_spec(spec_dict) +class BaseTestLinkSpec: + def test_no_param(self, spec_dict, spec_validator): + spec = create_spec(spec_dict, spec_validator=spec_validator) resp = spec / "paths#/status#get#responses#default" links = resp / "links" @@ -16,9 +19,8 @@ def test_no_param(self, factory): assert "requestBody" not in link assert "parameters" not in link - def test_param(self, factory): - spec_dict = factory.spec_from_file("data/v3.0/links.yaml") - spec = create_spec(spec_dict) + def test_param(self, spec_dict, spec_validator): + spec = create_spec(spec_dict, spec_validator=spec_validator) resp = spec / "paths#/status/{resourceId}#get#responses#default" links = resp / "links" @@ -34,3 +36,23 @@ def test_param(self, factory): param = parameters["opParam"] assert param == "$request.path.resourceId" + + +class TestLinkSpec30(BaseTestLinkSpec): + @pytest.fixture + def spec_dict(self, factory): + return factory.spec_from_file("data/v3.0/links.yaml") + + @pytest.fixture + def spec_validator(self, factory): + return openapi_v30_spec_validator + + +class TestLinkSpec31(BaseTestLinkSpec): + @pytest.fixture + def spec_dict(self, factory): + return factory.spec_from_file("data/v3.1/links.yaml") + + @pytest.fixture + def spec_validator(self, factory): + return openapi_v31_spec_validator diff --git a/tests/integration/schema/test_path_params.py b/tests/integration/schema/test_path_params.py index 028bc674..b1c7e2ea 100644 --- a/tests/integration/schema/test_path_params.py +++ b/tests/integration/schema/test_path_params.py @@ -1,16 +1,14 @@ import pytest +from openapi_spec_validator import openapi_v30_spec_validator +from openapi_spec_validator import openapi_v31_spec_validator from openapi_core.shortcuts import create_spec -class TestMinimal: - - spec_paths = ["data/v3.0/path_param.yaml"] - - @pytest.mark.parametrize("spec_path", spec_paths) - def test_param_present(self, factory, spec_path): +class BaseTestMinimal: + def test_param_present(self, factory, spec_path, spec_validator): spec_dict = factory.spec_from_file(spec_path) - spec = create_spec(spec_dict) + spec = create_spec(spec_dict, spec_validator=spec_validator) path = spec / "paths#/resource/{resId}" @@ -21,3 +19,23 @@ def test_param_present(self, factory, spec_path): assert param["name"] == "resId" assert param["required"] assert param["in"] == "path" + + +class Test30Minimal(BaseTestMinimal): + @pytest.fixture + def spec_path(self, factory): + return "data/v3.0/path_param.yaml" + + @pytest.fixture + def spec_validator(self, factory): + return openapi_v30_spec_validator + + +class Test31Minimal(BaseTestMinimal): + @pytest.fixture + def spec_path(self, factory): + return "data/v3.1/path_param.yaml" + + @pytest.fixture + def spec_validator(self, factory): + return openapi_v31_spec_validator diff --git a/tests/integration/schema/test_spec.py b/tests/integration/schema/test_spec.py index c0b2092c..174279c4 100644 --- a/tests/integration/schema/test_spec.py +++ b/tests/integration/schema/test_spec.py @@ -1,6 +1,8 @@ from base64 import b64encode import pytest +from openapi_spec_validator import openapi_v30_spec_validator +from openapi_spec_validator import openapi_v31_spec_validator from openapi_core.schema.servers import get_server_url from openapi_core.schema.specs import get_spec_url @@ -29,7 +31,9 @@ def spec_dict(self, factory): @pytest.fixture def spec(self, spec_dict, spec_uri): - return create_spec(spec_dict, spec_uri) + return create_spec( + spec_dict, spec_uri, spec_validator=openapi_v30_spec_validator + ) @pytest.fixture def request_validator(self, spec): @@ -296,3 +300,54 @@ def test_spec(self, spec, spec_dict): schema_spec = spec_dict["components"]["schemas"][schema_name] assert schema.getkey("readOnly") == schema_spec.get("readOnly") assert schema.getkey("writeOnly") == schema_spec.get("writeOnly") + + +class TestWebhook: + api_key = "12345" + + @property + def api_key_encoded(self): + api_key_bytes = self.api_key.encode("utf8") + api_key_bytes_enc = b64encode(api_key_bytes) + return str(api_key_bytes_enc, "utf8") + + @pytest.fixture + def spec_uri(self): + return "file://tests/integration/data/v3.1/webhook-example.yaml" + + @pytest.fixture + def spec_dict(self, factory): + return factory.spec_from_file("data/v3.1/webhook-example.yaml") + + @pytest.fixture + def spec(self, spec_dict, spec_uri): + return create_spec( + spec_dict, spec_uri, spec_validator=openapi_v31_spec_validator + ) + + @pytest.fixture + def request_validator(self, spec): + return RequestValidator(spec) + + @pytest.fixture + def response_validator(self, spec): + return ResponseValidator(spec) + + def test_spec(self, spec, spec_dict): + + info = spec / "info" + info_spec = spec_dict["info"] + assert info["title"] == info_spec["title"] + assert info["version"] == info_spec["version"] + + webhooks = spec / "webhooks" + webhooks_spec = spec_dict["webhooks"] + assert webhooks["newPet"] == webhooks_spec["newPet"] + + components = spec.get("components") + if not components: + return + + schemas = components.get("schemas", {}) + for schema_name, schema in schemas.items(): + assert spec_dict["components"]["schemas"][schema_name] is not None diff --git a/tests/integration/validation/test_minimal.py b/tests/integration/validation/test_minimal.py index 076a7de7..513cd017 100644 --- a/tests/integration/validation/test_minimal.py +++ b/tests/integration/validation/test_minimal.py @@ -1,4 +1,6 @@ import pytest +from openapi_spec_validator import openapi_v30_spec_validator +from openapi_spec_validator import openapi_v31_spec_validator from openapi_core.shortcuts import create_spec from openapi_core.templating.paths.exceptions import OperationNotFound @@ -8,7 +10,7 @@ from openapi_core.validation.request.validators import RequestValidator -class TestMinimal: +class BaseTestMinimal: servers = [ "http://minimal.test/", @@ -18,16 +20,10 @@ class TestMinimal: "https://u:p@a.b:1337", ] - spec_paths = [ - "data/v3.0/minimal_with_servers.yaml", - "data/v3.0/minimal.yaml", - ] - @pytest.mark.parametrize("server", servers) - @pytest.mark.parametrize("spec_path", spec_paths) - def test_hosts(self, factory, server, spec_path): + def test_hosts(self, factory, server, spec_path, spec_validator): spec_dict = factory.spec_from_file(spec_path) - spec = create_spec(spec_dict) + spec = create_spec(spec_dict, spec_validator=spec_validator) validator = RequestValidator(spec) request = MockRequest(server, "get", "/status") @@ -36,10 +32,11 @@ def test_hosts(self, factory, server, spec_path): assert not result.errors @pytest.mark.parametrize("server", servers) - @pytest.mark.parametrize("spec_path", spec_paths) - def test_invalid_operation(self, factory, server, spec_path): + def test_invalid_operation( + self, factory, server, spec_path, spec_validator + ): spec_dict = factory.spec_from_file(spec_path) - spec = create_spec(spec_dict) + spec = create_spec(spec_dict, spec_validator=spec_validator) validator = RequestValidator(spec) request = MockRequest(server, "post", "/status") @@ -51,10 +48,9 @@ def test_invalid_operation(self, factory, server, spec_path): assert result.parameters == Parameters() @pytest.mark.parametrize("server", servers) - @pytest.mark.parametrize("spec_path", spec_paths) - def test_invalid_path(self, factory, server, spec_path): + def test_invalid_path(self, factory, server, spec_path, spec_validator): spec_dict = factory.spec_from_file(spec_path) - spec = create_spec(spec_dict) + spec = create_spec(spec_dict, spec_validator=spec_validator) validator = RequestValidator(spec) request = MockRequest(server, "get", "/nonexistent") @@ -64,3 +60,43 @@ def test_invalid_path(self, factory, server, spec_path): assert isinstance(result.errors[0], PathNotFound) assert result.body is None assert result.parameters == Parameters() + + +class Test30Minimal(BaseTestMinimal): + @pytest.fixture + def spec_validator(self, factory): + return openapi_v30_spec_validator + + @pytest.fixture + def spec_path(self, factory): + return "data/v3.0/minimal.yaml" + + +class Test30MinimalWithServers(BaseTestMinimal): + @pytest.fixture + def spec_validator(self, factory): + return openapi_v30_spec_validator + + @pytest.fixture + def spec_path(self, factory): + return "data/v3.0/minimal_with_servers.yaml" + + +class Test31Minimal(BaseTestMinimal): + @pytest.fixture + def spec_validator(self, factory): + return openapi_v31_spec_validator + + @pytest.fixture + def spec_path(self, factory): + return "data/v3.1/minimal.yaml" + + +class Test31MinimalWithServers(BaseTestMinimal): + @pytest.fixture + def spec_validator(self, factory): + return openapi_v31_spec_validator + + @pytest.fixture + def spec_path(self, factory): + return "data/v3.1/minimal_with_servers.yaml" diff --git a/tests/integration/validation/test_petstore.py b/tests/integration/validation/test_petstore.py index 8a92aa6f..1a905d67 100644 --- a/tests/integration/validation/test_petstore.py +++ b/tests/integration/validation/test_petstore.py @@ -5,6 +5,7 @@ import pytest from isodate.tzinfo import UTC +from openapi_spec_validator import openapi_v30_spec_validator from openapi_core.casting.schemas.exceptions import CastError from openapi_core.deserializing.exceptions import DeserializeError @@ -50,7 +51,9 @@ def spec_dict(self, factory): @pytest.fixture(scope="module") def spec(self, spec_dict, spec_uri): - return create_spec(spec_dict, spec_uri) + return create_spec( + spec_dict, spec_uri, spec_validator=openapi_v30_spec_validator + ) @pytest.fixture(scope="module") def request_validator(self, spec): diff --git a/tests/integration/validation/test_read_only_write_only.py b/tests/integration/validation/test_read_only_write_only.py index b6dca0bf..fb211f0a 100644 --- a/tests/integration/validation/test_read_only_write_only.py +++ b/tests/integration/validation/test_read_only_write_only.py @@ -1,6 +1,7 @@ import json import pytest +from openapi_spec_validator import openapi_v30_spec_validator from openapi_core.shortcuts import create_spec from openapi_core.testing import MockRequest @@ -23,7 +24,7 @@ def request_validator(spec): @pytest.fixture(scope="class") def spec(factory): spec_dict = factory.spec_from_file("data/v3.0/read_only_write_only.yaml") - return create_spec(spec_dict) + return create_spec(spec_dict, spec_validator=openapi_v30_spec_validator) class TestReadOnly: diff --git a/tests/integration/validation/test_security_override.py b/tests/integration/validation/test_security_override.py index d6fabee7..dd121b3b 100644 --- a/tests/integration/validation/test_security_override.py +++ b/tests/integration/validation/test_security_override.py @@ -1,6 +1,7 @@ from base64 import b64encode import pytest +from openapi_spec_validator import openapi_v30_spec_validator from openapi_core.shortcuts import create_spec from openapi_core.testing import MockRequest @@ -16,7 +17,7 @@ def request_validator(spec): @pytest.fixture(scope="class") def spec(factory): spec_dict = factory.spec_from_file("data/v3.0/security_override.yaml") - return create_spec(spec_dict) + return create_spec(spec_dict, spec_validator=openapi_v30_spec_validator) class TestSecurityOverride: diff --git a/tests/integration/validation/test_validators.py b/tests/integration/validation/test_validators.py index f974b9f5..b640d60b 100644 --- a/tests/integration/validation/test_validators.py +++ b/tests/integration/validation/test_validators.py @@ -2,6 +2,7 @@ from base64 import b64encode import pytest +from openapi_spec_validator import openapi_v30_spec_validator from openapi_core.casting.schemas.exceptions import CastError from openapi_core.deserializing.media_types.exceptions import ( @@ -43,7 +44,9 @@ def spec_dict(self, factory): @pytest.fixture(scope="session") def spec(self, spec_dict): - return create_spec(spec_dict) + return create_spec( + spec_dict, spec_validator=openapi_v30_spec_validator + ) @pytest.fixture(scope="session") def validator(self, spec): @@ -445,7 +448,9 @@ def spec_dict(self): @pytest.fixture(scope="session") def spec(self, spec_dict): - return create_spec(spec_dict) + return create_spec( + spec_dict, spec_validator=openapi_v30_spec_validator + ) @pytest.fixture(scope="session") def validator(self, spec): @@ -501,7 +506,8 @@ def test_request_override_param(self, spec_dict): } ] validator = RequestValidator( - create_spec(spec_dict), base_url="http://example.com" + create_spec(spec_dict, spec_validator=openapi_v30_spec_validator), + base_url="http://example.com", ) request = MockRequest("http://example.com", "get", "/resource") result = validator.validate(request) @@ -525,7 +531,8 @@ def test_request_override_param_uniqueness(self, spec_dict): } ] validator = RequestValidator( - create_spec(spec_dict), base_url="http://example.com" + create_spec(spec_dict, spec_validator=openapi_v30_spec_validator), + base_url="http://example.com", ) request = MockRequest("http://example.com", "get", "/resource") result = validator.validate(request) @@ -546,7 +553,9 @@ def spec_dict(self, factory): @pytest.fixture def spec(self, spec_dict): - return create_spec(spec_dict) + return create_spec( + spec_dict, spec_validator=openapi_v30_spec_validator + ) @pytest.fixture def validator(self, spec):