From 14ebd8514693c1a1eefa05de40c1ee9e0fbdad71 Mon Sep 17 00:00:00 2001 From: p1c2u Date: Mon, 5 Sep 2022 11:27:03 +0100 Subject: [PATCH] Responses schema validation --- .../validation/validators.py | 33 ++++++ .../data/v2.0/missing-reference.yaml | 101 ++++++++++++++++++ .../integration/validation/test_validators.py | 52 ++++++++- 3 files changed, 185 insertions(+), 1 deletion(-) create mode 100644 tests/integration/data/v2.0/missing-reference.yaml diff --git a/openapi_spec_validator/validation/validators.py b/openapi_spec_validator/validation/validators.py index c41dde6..b1bd835 100644 --- a/openapi_spec_validator/validation/validators.py +++ b/openapi_spec_validator/validation/validators.py @@ -140,6 +140,10 @@ def _iter_operation_errors( ) self.operation_ids_registry.append(operation_id) + if "responses" in operation: + responses = operation / "responses" + yield from self._iter_responses_errors(responses) + names = [] parameters = None @@ -161,6 +165,35 @@ def _iter_operation_errors( ) return + def _iter_responses_errors( + self, responses: Spec + ) -> Iterator[ValidationError]: + for response_code, response in responses.items(): + yield from self._iter_response_errors(response_code, response) + + def _iter_response_errors( + self, response_code: str, response: Spec + ) -> Iterator[ValidationError]: + # openapi 2 + if "schema" in response: + schema = response / "schema" + yield from self._iter_schema_errors(schema) + # openapi 3 + if "content" in response: + content = response / "content" + yield from self._iter_content_errors(content) + + def _iter_content_errors(self, content: Spec) -> Iterator[ValidationError]: + for mimetype, media_type in content.items(): + yield from self._iter_media_type_errors(mimetype, media_type) + + def _iter_media_type_errors( + self, mimetype: str, media_type: Spec + ) -> Iterator[ValidationError]: + if "schema" in media_type: + schema = media_type / "schema" + yield from self._iter_schema_errors(schema) + def _get_path_param_names(self, params: Spec) -> Iterator[str]: for param in params: if param["in"] == "path": diff --git a/tests/integration/data/v2.0/missing-reference.yaml b/tests/integration/data/v2.0/missing-reference.yaml new file mode 100644 index 0000000..e136aaf --- /dev/null +++ b/tests/integration/data/v2.0/missing-reference.yaml @@ -0,0 +1,101 @@ +swagger: "2.0" +info: + version: 1.0.0 + title: Swagger Petstore + license: + name: MIT +host: petstore.swagger.io +basePath: /v1 +schemes: + - http +consumes: + - application/json +produces: + - application/json +paths: + /pets: + get: + summary: List all pets + operationId: listPets + tags: + - pets + parameters: + - name: limit + in: query + description: How many items to return at one time (max 100) + required: false + type: integer + format: int32 + responses: + 200: + description: A paged array of pets + headers: + x-next: + type: string + description: A link to the next page of responses + schema: + $ref: 'definitions/Pets' + default: + description: unexpected error + schema: + $ref: '#/definitions/' + post: + summary: Create a pet + operationId: createPets + tags: + - pets + responses: + '201': + description: Null response + default: + description: unexpected error + schema: + $ref: '#/definitions/Error' + /pets/{petId}: + get: + summary: Info for a specific pet + operationId: showPetById + tags: + - pets + parameters: + - name: petId + in: path + required: true + description: The id of the pet to retrieve + type: string + responses: + '200': + description: Expected response to a valid request + schema: + $ref: '#/definitions/Pets' + default: + description: unexpected error + schema: + $ref: '#/definitions/Error' +definitions: + Pet: + required: + - id + - name + properties: + id: + type: integer + format: int64 + name: + type: string + tag: + type: string + Pets: + type: array + items: + $ref: '#/definitions/Pet' + Error: + required: + - code + - message + properties: + code: + type: integer + format: int32 + message: + type: string \ No newline at end of file diff --git a/tests/integration/validation/test_validators.py b/tests/integration/validation/test_validators.py index 21932db..83c8381 100644 --- a/tests/integration/validation/test_validators.py +++ b/tests/integration/validation/test_validators.py @@ -1,8 +1,58 @@ import pytest +from jsonschema.exceptions import RefResolutionError from openapi_spec_validator.validation.exceptions import OpenAPIValidationError +class TestLocalOpenAPIv2Validator: + + LOCAL_SOURCE_DIRECTORY = "data/v2.0/" + + def local_test_suite_file_path(self, test_file): + return f"{self.LOCAL_SOURCE_DIRECTORY}{test_file}" + + @pytest.mark.parametrize( + "spec_file", + [ + "petstore.yaml", + ], + ) + def test_valid(self, factory, validator_v2, spec_file): + spec_path = self.local_test_suite_file_path(spec_file) + spec = factory.spec_from_file(spec_path) + spec_url = factory.spec_file_url(spec_path) + + return validator_v2.validate(spec, spec_url=spec_url) + + @pytest.mark.parametrize( + "spec_file", + [ + "empty.yaml", + ], + ) + def test_validation_failed(self, factory, validator_v2, spec_file): + spec_path = self.local_test_suite_file_path(spec_file) + spec = factory.spec_from_file(spec_path) + spec_url = factory.spec_file_url(spec_path) + + with pytest.raises(OpenAPIValidationError): + validator_v2.validate(spec, spec_url=spec_url) + + @pytest.mark.parametrize( + "spec_file", + [ + "missing-reference.yaml", + ], + ) + def test_ref_failed(self, factory, validator_v2, spec_file): + spec_path = self.local_test_suite_file_path(spec_file) + spec = factory.spec_from_file(spec_path) + spec_url = factory.spec_file_url(spec_path) + + with pytest.raises(RefResolutionError): + validator_v2.validate(spec, spec_url=spec_url) + + class TestLocalOpenAPIv30Validator: LOCAL_SOURCE_DIRECTORY = "data/v3.0/" @@ -31,7 +81,7 @@ def test_valid(self, factory, validator_v30, spec_file): "empty.yaml", ], ) - def test_falied(self, factory, validator_v30, spec_file): + def test_failed(self, factory, validator_v30, spec_file): spec_path = self.local_test_suite_file_path(spec_file) spec = factory.spec_from_file(spec_path) spec_url = factory.spec_file_url(spec_path)