From ef4cbdd9bc2a3ade851b78f651314b92709f3ced Mon Sep 17 00:00:00 2001 From: p1c2u Date: Wed, 24 Feb 2021 22:31:19 +0000 Subject: [PATCH] Spec 3.1 schema --- README.rst | 10 +- openapi_spec_validator/__init__.py | 58 +- openapi_spec_validator/__main__.py | 12 +- .../resources/schemas/v3.1/schema.json | 1478 +++++++++++++++++ tests/integration/conftest.py | 15 +- tests/integration/data/v3.1/empty.yaml | 1 + tests/integration/data/v3.1/petstore.yaml | 109 ++ tests/integration/test_main.py | 25 +- ...test_shortcuts.py => test_v2_shortcuts.py} | 85 +- tests/integration/test_v2_validator.py | 42 + tests/integration/test_v3_0_shortcuts.py | 102 ++ ...test_validate.py => test_v3_0_validate.py} | 8 +- ...t_validators.py => test_v3_0_validator.py} | 45 +- tests/integration/test_v3_1_shortcuts.py | 80 + tests/integration/test_v3_1_validate.py | 50 + 15 files changed, 1954 insertions(+), 166 deletions(-) create mode 100644 openapi_spec_validator/resources/schemas/v3.1/schema.json create mode 100644 tests/integration/data/v3.1/empty.yaml create mode 100644 tests/integration/data/v3.1/petstore.yaml rename tests/integration/{test_shortcuts.py => test_v2_shortcuts.py} (53%) create mode 100644 tests/integration/test_v2_validator.py create mode 100644 tests/integration/test_v3_0_shortcuts.py rename tests/integration/{test_validate.py => test_v3_0_validate.py} (91%) rename tests/integration/{test_validators.py => test_v3_0_validator.py} (89%) create mode 100644 tests/integration/test_v3_1_shortcuts.py create mode 100644 tests/integration/test_v3_1_validate.py diff --git a/README.rst b/README.rst index 7764f6a..8dd3d87 100644 --- a/README.rst +++ b/README.rst @@ -18,13 +18,9 @@ OpenAPI Spec validator About ##### -OpenAPI Spec Validator is a Python library that validates OpenAPI Specs -against the `OpenAPI 2.0 (aka -Swagger) `__ -and `OpenAPI -3.0 `__ -specification. The validator aims to check for full compliance with the -Specification. +OpenAPI Spec Validator is a Python library that validates OpenAPI Specs against the `OpenAPI 2.0 (aka Swagger) `__, `OpenAPI 3.0 `__ and `OpenAPI 3.1 `__ specification. + +The validator aims to check for full compliance with the Specification. Installation ############ diff --git a/openapi_spec_validator/__init__.py b/openapi_spec_validator/__init__.py index 6ffd254..ad675dc 100644 --- a/openapi_spec_validator/__init__.py +++ b/openapi_spec_validator/__init__.py @@ -14,9 +14,16 @@ __license__ = 'Apache License, Version 2.0' __all__ = [ - 'openapi_v2_spec_validator', 'openapi_v3_spec_validator', - 'validate_v2_spec', 'validate_v3_spec', 'validate_spec', - 'validate_v2_spec_url', 'validate_v3_spec_url', 'validate_spec_url', + 'validate_spec', 'validate_spec_url', + 'openapi_spec_validator', + 'openapi_v2_spec_validator', + 'validate_v2_spec', 'validate_v2_spec_url', + 'openapi_v3_spec_validator', + 'validate_v3_spec', 'validate_v3_spec_url', + 'openapi_v3_0_spec_validator', + 'validate_v3_0_spec', 'validate_v3_0_spec_url', + 'openapi_v3_1_spec_validator', + 'validate_v3_1_spec', 'validate_v3_1_spec_url', ] file_object_handler = FileObjectHandler() @@ -40,25 +47,48 @@ ) # v3.0 spec -schema_v3, schema_v3_url = get_openapi_schema('3.0') -openapi_v3_validator_factory = JSONSpecValidatorFactory( - schema_v3, schema_v3_url, +schema_v3_0, schema_v3_0_url = get_openapi_schema('3.0') +openapi_v3_0_validator_factory = JSONSpecValidatorFactory( + schema_v3_0, schema_v3_0_url, resolver_handlers=default_handlers, ) -openapi_v3_spec_validator = SpecValidator( - openapi_v3_validator_factory, +openapi_v3_0_spec_validator = SpecValidator( + openapi_v3_0_validator_factory, + resolver_handlers=default_handlers, +) + +# v3.1 spec +schema_v3_1, schema_v3_1_url = get_openapi_schema('3.1') +openapi_v3_1_validator_factory = JSONSpecValidatorFactory( + schema_v3_1, schema_v3_1_url, + resolver_handlers=default_handlers, +) +openapi_v3_1_spec_validator = SpecValidator( + openapi_v3_1_validator_factory, resolver_handlers=default_handlers, ) # shortcuts -validate_v2_spec = validate_spec_factory(openapi_v2_spec_validator.validate) +validate_v2_spec = validate_spec_factory( + openapi_v2_spec_validator.validate) validate_v2_spec_url = validate_spec_url_factory( openapi_v2_spec_validator.validate, default_handlers) -validate_v3_spec = validate_spec_factory(openapi_v3_spec_validator.validate) -validate_v3_spec_url = validate_spec_url_factory( - openapi_v3_spec_validator.validate, default_handlers) +validate_v3_0_spec = validate_spec_factory( + openapi_v3_0_spec_validator.validate) +validate_v3_0_spec_url = validate_spec_url_factory( + openapi_v3_0_spec_validator.validate, default_handlers) + +validate_v3_1_spec = validate_spec_factory( + openapi_v3_1_spec_validator.validate) +validate_v3_1_spec_url = validate_spec_url_factory( + openapi_v3_1_spec_validator.validate, default_handlers) # aliases to the latest version -validate_spec = validate_v3_spec -validate_spec_url = validate_v3_spec_url +schema_v3, schema_v3_url = schema_v3_0, schema_v3_0_url +openapi_v3_validator_factory = openapi_v3_0_validator_factory +openapi_v3_spec_validator = openapi_v3_0_spec_validator +validate_v3_spec = validate_v3_0_spec +validate_v3_spec_url = validate_v3_0_spec_url +validate_spec = validate_v3_0_spec +validate_spec_url = validate_v3_0_spec_url diff --git a/openapi_spec_validator/__main__.py b/openapi_spec_validator/__main__.py index eb2884a..7d31c3a 100644 --- a/openapi_spec_validator/__main__.py +++ b/openapi_spec_validator/__main__.py @@ -3,7 +3,8 @@ import sys from openapi_spec_validator import ( - openapi_v2_spec_validator, openapi_v3_spec_validator, + openapi_v2_spec_validator, openapi_v3_0_spec_validator, + openapi_v3_1_spec_validator ) from openapi_spec_validator.exceptions import ValidationError from openapi_spec_validator.readers import read_from_stdin, read_from_filename @@ -20,10 +21,10 @@ def main(args=None): parser.add_argument('filename', help="Absolute or relative path to file") parser.add_argument( '--schema', - help="OpenAPI schema (default: 3.0.0)", + help="OpenAPI schema (default: 3.1)", type=str, - choices=['2.0', '3.0.0'], - default='3.0.0' + choices=['2.0', '3.0', '3.1'], + default='3.0' ) args = parser.parse_args(args) @@ -42,7 +43,8 @@ def main(args=None): # choose the validator validators = { '2.0': openapi_v2_spec_validator, - '3.0.0': openapi_v3_spec_validator, + '3.0': openapi_v3_0_spec_validator, + '3.1': openapi_v3_1_spec_validator, } validator = validators[args.schema] diff --git a/openapi_spec_validator/resources/schemas/v3.1/schema.json b/openapi_spec_validator/resources/schemas/v3.1/schema.json new file mode 100644 index 0000000..f78d1f8 --- /dev/null +++ b/openapi_spec_validator/resources/schemas/v3.1/schema.json @@ -0,0 +1,1478 @@ +{ + "title": "A JSON Schema for OpenAPI 3.1.X.", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "description": "Validation schema for OpenAPI Specification 3.1.X.", + "type": "object", + "required": [ + "openapi", + "info" + ], + "anyOf": [ + { + "required": [ + "components" + ] + }, + { + "required": [ + "paths" + ] + }, + { + "required": [ + "webhooks" + ] + } + ], + "allOf": [ + { + "$ref": "#/$defs/Mixins/$defs/Extensible" + } + ], + "properties": { + "openapi": { + "type": "string", + "pattern": "^3\\.1\\.\\d(-.+)?$" + }, + "jsonSchemaDialect": { + "type": "string", + "format": "uri" + }, + "info": { + "$ref": "#/$defs/Objects/$defs/Info" + }, + "externalDocs": { + "$ref": "#/$defs/Objects/$defs/ExternalDocumentation" + }, + "servers": { + "type": "array", + "items": { + "$ref": "#/$defs/Objects/$defs/Server" + } + }, + "security": { + "type": "array", + "items": { + "$ref": "#SecurityRequirement" + } + }, + "tags": { + "type": "array", + "items": { + "$ref": "#Tag" + } + }, + "paths": { + "$ref": "#/$defs/Objects/$defs/Paths" + }, + "webhooks": { + "$comment": "TODO: Implement meta-schema for webhooks" + }, + "components": { + "$ref": "#/$defs/Objects/$defs/Components" + } + }, + "unevaluatedProperties": false, + "$defs": { + "Mixins": { + "$defs": { + "Describable": { + "properties": { + "description": { + "type": "string" + } + } + }, + "Requireable": { + "properties": { + "required": { + "type": "boolean", + "default": false + } + } + }, + "Deprecatable": { + "properties": { + "deprecated": { + "type": "boolean", + "default": false + } + } + }, + "DescReqDep": { + "allOf": [ + { + "$ref": "#/$defs/Mixins/$defs/Describable" + }, + { + "$ref": "#/$defs/Mixins/$defs/Requireable" + }, + { + "$ref": "#/$defs/Mixins/$defs/Deprecatable" + } + ] + }, + "Extensible": { + "patternProperties": { + "^x-": true + } + }, + "WithSingleExample": { + "properties": { + "example": true, + "examples": false + } + }, + "WithExampleObjects": { + "properties": { + "example": false, + "examples": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/Objects/$defs/Example" + } + } + } + }, + "WithExamples": { + "oneOf": [ + { + "$ref": "#/$defs/Mixins/$defs/WithSingleExample" + }, + { + "$ref": "#/$defs/Mixins/$defs/WithExampleObjects" + } + ] + }, + "WithSchema": { + "properties": { + "schema": { + "$ref": "#Schema" + }, + "content": false + } + }, + "WithSchemaAndExamples": { + "allOf": [ + { + "$ref": "#/$defs/Mixins/$defs/WithSchema" + }, + { + "$ref": "#/$defs/Mixins/$defs/WithExamples" + } + ] + }, + "WithContent": { + "properties": { + "schema": false, + "content": { + "type": "object", + "additionalProperties": { + "$ref": "#MediaType" + }, + "minProperties": 1, + "maxProperties": 1 + } + } + }, + "WithNameAndLocation": { + "required": [ + "name", + "in" + ], + "properties": { + "name": { + "type": "string" + }, + "in": { + "type": "string", + "enum": [ + "path", + "query", + "header", + "cookie" + ] + } + } + }, + "WithExplodeAndReserved": { + "properties": { + "explode": { + "type": "boolean" + }, + "allowReserved": { + "type": "boolean", + "default": false + } + } + }, + "WithEmpty": { + "properties": { + "allowEmptyValue": { + "type": "boolean", + "default": false + } + } + }, + "WithExplodeReservedAndEmpty": { + "allOf": [ + { + "$ref": "#/$defs/Mixins/$defs/WithExplodeAndReserved" + }, + { + "$ref": "#/$defs/Mixins/$defs/WithEmpty" + } + ] + }, + "StyledSimple": { + "allOf": [ + { + "$ref": "#/$defs/Mixins/$defs/WithExplodeReservedAndEmpty" + } + ], + "properties": { + "style": { + "type": "string", + "const": "simple", + "default": "simple" + } + } + }, + "StyledMatrix": { + "properties": { + "style": { + "type": "string", + "enum": [ + "matrix", + "label", + "simple" + ], + "default": "simple" + } + } + }, + "StyledFormOnly": { + "allOf": [ + { + "$ref": "#/$defs/Mixins/$defs/WithExplodeReservedAndEmpty" + } + ], + "properties": { + "style": { + "type": "string", + "const": "form", + "default": "form" + } + } + }, + "StyledFormComplexNoDefaultOrEmpty": { + "allOf": [ + { + "$ref": "#/$defs/Mixins/$defs/WithExplodeAndReserved" + } + ], + "properties": { + "style": { + "type": "string", + "enum": [ + "form", + "spaceDelimited", + "pipeDelimited", + "deepObject" + ] + } + } + }, + "StyledFormComplex": { + "allOf": [ + { + "$ref": "#/$defs/Mixins/$defs/StyledFormComplexNoDefaultOrEmpty" + }, + { + "$ref": "#/$defs/Mixins/$defs/WithExplodeReservedAndEmpty" + } + ], + "properties": { + "style": { + "default": "form" + } + } + } + } + }, + "Objects": { + "$defs": { + "Reference": { + "$anchor": "Reference", + "type": "object", + "required": [ + "$ref" + ], + "properties": { + "$ref": { + "type": "string", + "format": "uri-reference" + } + } + }, + "Info": { + "$anchor": "Info", + "type": "object", + "required": [ + "title", + "version" + ], + "allOf": [ + { + "$ref": "#/$defs/Mixins/$defs/Describable" + }, + { + "$ref": "#/$defs/Mixins/$defs/Extensible" + } + ], + "properties": { + "title": { + "type": "string" + }, + "summary": { + "type": "string" + }, + "termsOfService": { + "type": "string", + "format": "uri-reference" + }, + "contact": { + "$ref": "#/$defs/Objects/$defs/Info/$defs/Contact" + }, + "license": { + "$ref": "#/$defs/Objects/$defs/Info/$defs/License" + } + }, + "$defs": { + "Contact": { + "$anchor": "Contact", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/Mixins/$defs/Extensible" + } + ], + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string", + "format": "uri-reference" + }, + "email": { + "type": "string", + "format": "email" + } + }, + "unevaluatedProperties": false + }, + "License": { + "$anchor": "License", + "type": "object", + "required": [ + "name" + ], + "allOf": [ + { + "$ref": "#/$defs/Mixins/$defs/Extensible" + } + ], + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string", + "format": "uri-reference" + }, + "identifier": { + "type": "string" + } + }, + "unevaluatedProperties": false + } + } + }, + "Server": { + "$anchor": "Server", + "type": "object", + "required": [ + "url" + ], + "allOf": [ + { + "$ref": "#/$defs/Mixins/$defs/Describable" + }, + { + "$ref": "#/$defs/Mixins/$defs/Extensible" + } + ], + "properties": { + "url": { + "type": "string" + }, + "variables": { + "type": "object", + "additionalProperties": { + "type": "object", + "required": [ + "default" + ], + "allOf": [ + { + "$ref": "#/$defs/Mixins/$defs/Describable" + }, + { + "$ref": "#/$defs/Mixins/$defs/Extensible" + } + ], + "properties": { + "enum": { + "type": "array", + "items": { + "type": "string" + }, + "minItems": 1 + }, + "default": { + "type": "string" + } + }, + "unevaluatedProperties": false + } + } + }, + "unevaluatedProperties": false + }, + "Response": { + "$anchor": "Response", + "oneOf": [ + { + "$ref": "#/$defs/Objects/$defs/Reference" + }, + { + "type": "object", + "required": [ + "description" + ], + "allOf": [ + { + "$ref": "#/$defs/Mixins/$defs/Describable" + }, + { + "$ref": "#/$defs/Mixins/$defs/Extensible" + } + ], + "properties": { + "headers": { + "type": "object", + "additionalProperties": { + "$ref": "#Header" + } + }, + "content": { + "type": "object", + "additionalProperties": { + "$ref": "#MediaType" + } + }, + "links": { + "type": "object", + "additionalProperteis": { + "$ref": "#Link" + } + } + }, + "unevaluatedProperties": false + } + ] + }, + "MediaType": { + "$anchor": "MediaType", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/Mixins/$defs/WithSchemaAndExamples" + } + ], + "properties": { + "encoding": { + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/Mixins/$defs/StyledFormComplexNoDefaultOrEmpty" + } + ], + "properties": { + "contentType": { + "type": "string" + }, + "headers": { + "type": "object", + "additionalProperties": { + "$ref": "#Header" + } + } + } + } + }, + "unevaluatedProperties": false + }, + "Example": { + "$anchor": "Example", + "oneOf": [ + { + "$ref": "#/$defs/Objects/$defs/Reference" + }, + { + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/Mixins/$defs/Describable" + }, + { + "$ref": "#/$defs/Mixins/$defs/Extensible" + } + ], + "properties": { + "summary": { + "type": "string" + }, + "value": true, + "externalValue": { + "type": "string", + "format": "uri-reference" + } + }, + "unevaluatedProperties": false + } + ] + }, + "Header": { + "$anchor": "Header", + "oneOf": [ + { + "$ref": "#/$defs/Objects/$defs/Reference" + }, + { + "type": "object", + "required": [ + "schema" + ], + "allOf": [ + { + "$ref": "#/$defs/Mixins/$defs/DescReqDep" + }, + { + "$ref": "#/$defs/Mixins/$defs/Extensible" + } + ], + "oneOf": [ + { + "allOf": [ + { + "$ref": "#/$defs/Mixins/$defs/WithSchemaAndExamples" + }, + { + "$ref": "#/$defs/Mixins/$defs/StyledSimple" + } + ] + }, + { + "allOf": [ + { + "$ref": "#/$defs/Mixins/$defs/WithContent" + }, + { + "$ref": "#/$defs/Mixins/$defs/WithEmpty" + } + ] + } + ], + "unevaluatedProperties": false + } + ] + }, + "Paths": { + "$anchor": "Paths", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/Mixins/$defs/Extensible" + } + ], + "patternProperties": { + "^/": { + "type": "object", + "oneOf": [ + { + "$ref": "#/$defs/Objects/$defs/Reference" + }, + { + "$ref": "#/$defs/Objects/$defs/PathItem" + } + ] + } + }, + "unevaluatedProperties": false + }, + "PathItem": { + "$anchor": "PathItem", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/Mixins/$defs/Describable" + }, + { + "$ref": "#/$defs/Mixins/$defs/Extensible" + } + ], + "properties": { + "summary": { + "type": "string" + }, + "servers": { + "type": "array", + "items": { + "$ref": "#/$defs/Objects/$defs/Server" + } + }, + "parameters": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/$defs/Objects/$defs/Parameter" + }, + { + "$ref": "#/$defs/Objects/$defs/Reference" + } + ] + }, + "uniqueItems": true + } + } + }, + "Operation": { + "$anchor": "Operation", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/Mixins/$defs/Describable" + }, + { + "$ref": "#/$defs/Mixins/$defs/Deprecatable" + }, + { + "$ref": "#/$defs/Mixins/$defs/Extensible" + } + ], + "properties": { + "tags": { + "type": "array", + "items": { + "type": "string" + } + }, + "summary": { + "type": "string" + }, + "externalDocs": { + "$ref": "#/$defs/Objects/$defs/ExternalDocumentation" + }, + "operationId": { + "type": "string" + }, + "parameters": { + "type": "array", + "items": { + "$ref": "#/$defs/Objects/$defs/Parameter" + } + }, + "requestBody": { + "$ref": "#/$defs/Objects/$defs/RequestBody" + }, + "responses": { + "$ref": "#/$defs/Objects/$defs/Responses" + }, + "callback": { + "$ref": "#/$defs/Objects/$defs/Callback" + }, + "security": { + "type": "array", + "items": { + "$ref": "#/$defs/Objects/$defs/SecurityRequirement" + } + }, + "servers": { + "type": "array", + "items": { + "$ref": "#/$defs/Objects/$defs/Server" + } + } + }, + "unevaluatedProperties": false + }, + "Responses": { + "$anchor": "Responses", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/Mixins/$defs/Extensible" + } + ], + "properties": { + "default": { + "$ref": "#Response" + } + }, + "patternProperties": { + "[1-5](?:\\d{2}|XX)": { + "$ref": "#Response" + } + }, + "minProperties": 1, + "unevaluatedProperties": false + }, + "SecurityRequirement": { + "$anchor": "SecurityRequirement", + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "Tag": { + "$anchor": "Tag", + "type": "object", + "required": [ + "name" + ], + "allOf": [ + { + "$ref": "#/$defs/Mixins/$defs/Describable" + }, + { + "$ref": "#/$defs/Mixins/$defs/Extensible" + } + ], + "properties": { + "name": { + "type": "string" + }, + "externalDocs": { + "$ref": "#/$defs/Objects/$defs/ExternalDocumentation" + } + }, + "unevaluatedProperties": false + }, + "ExternalDocumentation": { + "$anchor": "ExternalDocumentation", + "allOf": [ + { + "$ref": "#/$defs/Mixins/$defs/Extensible" + } + ], + "type": "object", + "required": [ + "url" + ], + "properties": { + "description": { + "type": "string" + }, + "url": { + "type": "string", + "format": "uri-reference" + } + } + }, + "Parameter": { + "$anchor": "Parameter", + "oneOf": [ + { + "$ref": "#/$defs/Objects/$defs/Reference" + }, + { + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/Mixins/$defs/WithNameAndLocation" + }, + { + "$ref": "#/$defs/Mixins/$defs/Extensible" + }, + { + "oneOf": [ + { + "allOf": [ + { + "$ref": "#PathParam" + }, + { + "$ref": "#/$defs/Mixins/$defs/Describable" + }, + { + "$ref": "#/$defs/Mixins/$defs/Deprecatable" + }, + { + "required": [ + "required" + ], + "properties": { + "required": { + "const": true, + "default": true + } + } + } + ] + }, + { + "allOf": [ + { + "anyOf": [ + { + "$ref": "#QueryParam" + }, + { + "$ref": "#HeaderParam" + }, + { + "$ref": "#CookieParam" + } + ] + }, + { + "$ref": "#/$defs/Mixins/$defs/DescReqDep" + } + ] + } + ] + } + ], + "oneOf": [ + { + "allOf": [ + { + "required": [ + "schema" + ] + }, + { + "$ref": "#/$defs/Mixins/$defs/WithSchemaAndExamples" + }, + { + "oneOf": [ + { + "allOf": [ + { + "$ref": "#PathParam" + }, + { + "$ref": "#/$defs/Mixins/$defs/StyledMatrix" + } + ] + }, + { + "allOf": [ + { + "$ref": "#QueryParam" + }, + { + "$ref": "#/$defs/Mixins/$defs/StyledFormComplex" + } + ] + }, + { + "allOf": [ + { + "$ref": "#HeaderParam" + }, + { + "$ref": "#/$defs/Mixins/$defs/StyledSimple" + } + ] + }, + { + "allOf": [ + { + "$ref": "#CookieParam" + }, + { + "$ref": "#/$defs/Mixins/$defs/StyledFormOnly" + } + ] + } + ] + } + ] + }, + { + "allOf": [ + { + "required": [ + "content" + ] + }, + { + "$ref": "#/$defs/Mixins/$defs/WithContent" + }, + { + "$ref": "#/$defs/Mixins/$defs/WithEmpty" + } + ] + } + ], + "unevaluatedProperties": false + } + ], + "$defs": { + "PathParam": { + "$anchor": "PathParam", + "properties": { + "in": { + "const": "path" + } + } + }, + "QueryParam": { + "$anchor": "QueryParam", + "properties": { + "in": { + "const": "query" + } + } + }, + "HeaderParam": { + "$anchor": "HeaderParam", + "properties": { + "in": { + "const": "header" + } + } + }, + "CookieParam": { + "$anchor": "CookieParam", + "properties": { + "in": { + "const": "cookie" + } + } + } + } + }, + "RequestBody": { + "$anchor": "RequestBody", + "required": [ + "content" + ], + "allOf": [ + { + "$ref": "#/$defs/Mixins/$defs/Describable" + }, + { + "$ref": "#/$defs/Mixins/$defs/Requireable" + }, + { + "$ref": "#/$defs/Mixins/$defs/Extensible" + } + ], + "properties": { + "content": { + "type": "object", + "additionalProperties": { + "$ref": "#MediaType" + } + } + }, + "unevaluatedProperties": false + }, + "SecurityScheme": { + "$anchor": "SecurityScheme", + "oneOf": [ + { + "$ref": "#/$defs/Objects/$defs/Reference" + }, + { + "type": "object", + "required": [ + "type" + ], + "allOf": [ + { + "$ref": "#/$defs/Mixins/$defs/Describable" + }, + { + "$ref": "#/$defs/Mixins/$defs/Extensible" + } + ], + "properties": { + "type": { + "string": null, + "enum": [ + "apiKey", + "http", + "bearer", + "mutualTLS", + "oauth2", + "openIdConnect" + ], + "oneOf": [ + { + "$ref": "#APIKey" + }, + { + "$ref": "#HTTP" + }, + { + "$ref": "#MutualTLS" + }, + { + "$ref": "#OAuth2" + }, + { + "$ref": "#OpenIdConnect" + } + ] + } + }, + "unevaluatedProperties": false + } + ], + "$defs": { + "APIKey": { + "$anchor": "APIKey", + "properties": { + "type": { + "const": "apiKey" + } + }, + "allOf": [ + { + "$ref": "#/$defs/Mixins/$defs/WithNameAndLocation" + } + ], + "not": { + "$ref": "#PathParam" + } + }, + "HTTP": { + "$anchor": "HTTP", + "required": [ + "scheme" + ], + "properties": { + "scheme": { + "type": "string" + } + }, + "oneOf": [ + { + "properties": { + "type": { + "const": "http" + } + } + }, + { + "properties": { + "type": { + "const": "bearer" + }, + "bearerFormat": { + "type": "string" + } + } + } + ] + }, + "MutalTLS": { + "$anchor": "MutualTLS", + "properties": { + "type": { + "const": "mutualTLS" + } + } + }, + "OAuth2": { + "$anchor": "OAuth2", + "required": [ + "flows" + ], + "properties": { + "type": { + "const": "oauth2" + }, + "flows": { + "$ref": "#OAuthFlows" + } + } + }, + "OpenIdConnect": { + "$anchor": "OpenIdConnect", + "required": [ + "openIdConnectUrl" + ], + "properties": { + "type": { + "const": "openIdConnect" + }, + "openIdConnectUrl": { + "type": "string", + "format": "uri" + } + } + } + } + }, + "OAuthFlows": { + "$anchor": "OAuthFlows", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/Mixins/$defs/Extensible" + } + ], + "properties": { + "implicit": { + "allOf": [ + { + "required": [ + "scopes" + ] + }, + { + "$ref": "#CommonFlow" + }, + { + "$ref": "#AuthorizationFlow" + }, + { + "$ref": "#/$defs/Mixins/$defs/Extensible" + } + ], + "unevaluatedProperties": false + }, + "password": { + "allOf": [ + { + "$ref": "#CommonFlow" + }, + { + "$ref": "#TokenFlow" + }, + { + "$ref": "#/$defs/Mixins/$defs/Extensible" + } + ], + "unevaluatedProperties": false + }, + "clientCredentials": { + "allOf": [ + { + "$ref": "#CommonFlow" + }, + { + "$ref": "#TokenFlow" + }, + { + "$ref": "#/$defs/Mixins/$defs/Extensible" + } + ], + "unevaluatedProperties": false + }, + "authorizationCode": { + "allOf": [ + { + "$ref": "#CommonFlow" + }, + { + "$ref": "#TokenFlow" + }, + { + "$ref": "#AuthorizationFlow" + }, + { + "$ref": "#/$defs/Mixins/$defs/Extensible" + } + ], + "unevaluatedProperties": false + } + }, + "unevaluatedProperties": false, + "$defs": { + "Common": { + "$anchor": "CommonFlow", + "properties": { + "refreshUrl": { + "type": "string", + "format": "uri-reference" + }, + "scopes": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + }, + "Token": { + "$anchor": "TokenFlow", + "required": [ + "tokenUrl" + ], + "properties": { + "tokenUrl": { + "type": "string", + "format": "uri-reference" + } + } + }, + "Authorization": { + "$anchor": "AuthorizationFlow", + "required": [ + "authorizationUrl" + ], + "properties": { + "authorizationUrl": { + "type": "string", + "format": "uri-reference" + } + } + } + } + }, + "Link": { + "$anchor": "Link", + "oneOf": [ + { + "$ref": "#/$defs/Objects/$defs/Reference" + }, + { + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/Mixins/$defs/Describable" + }, + { + "$ref": "#/$defs/Mixins/$defs/Extensible" + } + ], + "oneOf": [ + { + "properties": { + "operationRef": { + "type": "string", + "format": "uri-reference" + }, + "operationId": false + } + }, + { + "properties": { + "operationRef": false, + "operationId": { + "type": "string" + } + } + } + ], + "properties": { + "parameters": { + "type": "object", + "additionalProperties": true + }, + "requestBody": true, + "server": { + "$ref": "#/$defs/Objects/$defs/Server" + } + }, + "unevaluatedProperties": false + } + ] + }, + "Callback": { + "$anchor": "Callback", + "oneOf": [ + { + "$ref": "#/$defs/Objects/$defs/Reference" + }, + { + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/Mixins/$defs/Extensible" + } + ], + "unevaluatedProperties": false + } + ] + }, + "Schema": { + "$anchor": "Schema", + "oneOf": [ + { + "$ref": "#/$defs/Objects/$defs/Reference" + }, + { + "type": "object", + "allOf": [ + { + "$ref": "https://json-schema.org/draft/2020-12/schema" + }, + { + "$ref": "#/$defs/Objects/$defs/Schema/$defs/Extensions" + }, + { + "$ref": "#/$defs/Mixins/$defs/Extensible" + } + ], + "unevaluatedProperties": false + } + ], + "$defs": { + "Extensions": { + "$anchor": "SchemaExtensions", + "properties": { + "discriminator": { + "$ref": "#/$defs/Objects/$defs/Discriminator" + }, + "externalDocs": { + "$ref": "#/$defs/Objects/$defs/ExternalDocumentation" + }, + "xml": { + "$ref": "#XML" + } + } + }, + "Discriminator": { + "$anchor": "Discriminator", + "type": "object", + "required": [ + "propertyName" + ], + "properties": { + "propertyName": { + "type": "string" + }, + "mapping": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + }, + "XML": { + "$anchor": "XML", + "allOf": [ + { + "$ref": "#/$defs/Mixins/$defs/Extensible" + }, + { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "namespace": { + "type": "string", + "format": "url" + }, + "prefix": { + "type": "string" + }, + "attribute": { + "type": "boolean", + "default": false + }, + "wrapped": { + "type": "boolean", + "default": false + } + } + } + ], + "additionalProperties": false + } + } + }, + "Components": { + "$anchor": "Components", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/Mixins/$defs/Extensible" + }, + { + "additionalProperties": { + "$comment": "All sub-objects have the same property name constraints", + "type": "object", + "propertyNames": { + "pattern": "^[a-zA-Z0-9\\.\\-_]+$" + } + } + } + ], + "properties": { + "schemas": { + "additionalProperties": { + "$ref": "#/$defs/Objects/$defs/Schema" + } + }, + "responses": { + "additionalProperties": { + "$ref": "#/$defs/Objects/$defs/Responses" + } + }, + "parameters": { + "additionalProperties": { + "$ref": "#/$defs/Objects/$defs/Parameter" + } + }, + "pathItems": { + "additionalProperties": { + "$ref": "#/$defs/Objects/$defs/PathItem" + } + }, + "examples": { + "additionalProperties": { + "$ref": "#/$defs/Objects/$defs/Example" + } + }, + "requestBodies": { + "additionalProperties": { + "$ref": "#/$defs/Objects/$defs/RequestBody" + } + }, + "headers": { + "additionalProperties": { + "$ref": "#/$defs/Objects/$defs/Header" + } + }, + "links": { + "additionalProperties": { + "$ref": "#/$defs/Objects/$defs/Link" + } + }, + "callbacks": { + "additionalProperties": { + "$ref": "#/$defs/Objects/$defs/Callback" + } + } + }, + "unevaluatedProperties": false + } + } + } + } +} diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index a83ced1..74c7f23 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -5,8 +5,10 @@ from six.moves.urllib.parse import urlunparse from yaml import safe_load -from openapi_spec_validator import (openapi_v3_spec_validator, - openapi_v2_spec_validator) +from openapi_spec_validator import ( + openapi_v2_spec_validator, openapi_v3_0_spec_validator, + openapi_v3_1_spec_validator, +) from openapi_spec_validator.schemas import read_yaml_file @@ -42,8 +44,13 @@ def factory(): @pytest.fixture -def validator(): - return openapi_v3_spec_validator +def v3_0_validator(): + return openapi_v3_0_spec_validator + + +@pytest.fixture +def v3_1_validator(): + return openapi_v3_1_spec_validator @pytest.fixture diff --git a/tests/integration/data/v3.1/empty.yaml b/tests/integration/data/v3.1/empty.yaml new file mode 100644 index 0000000..9151fcb --- /dev/null +++ b/tests/integration/data/v3.1/empty.yaml @@ -0,0 +1 @@ +openapi: "3.1.0" \ No newline at end of file diff --git a/tests/integration/data/v3.1/petstore.yaml b/tests/integration/data/v3.1/petstore.yaml new file mode 100644 index 0000000..ea4218d --- /dev/null +++ b/tests/integration/data/v3.1/petstore.yaml @@ -0,0 +1,109 @@ +openapi: "3.1.0" +info: + version: 1.0.0 + title: Swagger Petstore + license: + name: MIT +servers: + - url: http://petstore.swagger.io/v1 +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 + schema: + type: integer + format: int32 + responses: + 200: + description: An paged array of pets + headers: + x-next: + description: A link to the next page of responses + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/Pets" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + post: + summary: Create a pet + operationId: createPets + tags: + - pets + responses: + '201': + description: Null response + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/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 + schema: + type: string + responses: + '200': + description: Expected response to a valid request + content: + application/json: + schema: + $ref: "#/components/schemas/Pets" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" +components: + schemas: + Pet: + required: + - id + - name + properties: + id: + type: integer + format: int64 + name: + type: string + tag: + type: string + Pets: + type: array + items: + $ref: "#/components/schemas/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/test_main.py b/tests/integration/test_main.py index bbc1e1f..3c196f0 100644 --- a/tests/integration/test_main.py +++ b/tests/integration/test_main.py @@ -7,18 +7,25 @@ def test_schema_default(): - """Test default schema is 3.0.0""" + """Test default schema is 3.0""" testargs = ['./tests/integration/data/v3.0/petstore.yaml'] main(testargs) -def test_schema_v3(): - """No errors when calling proper v3 file.""" - testargs = ['--schema', '3.0.0', +def test_schema_v3_0(): + """No errors when calling proper v3.0 file.""" + testargs = ['--schema', '3.0', './tests/integration/data/v3.0/petstore.yaml'] main(testargs) +def test_schema_v3_1(): + """No errors when calling proper v3.1 file.""" + testargs = ['--schema', '3.1', + './tests/integration/data/v3.1/petstore.yaml'] + main(testargs) + + def test_schema_v2(): """No errors when calling with proper v2 file.""" testargs = ['--schema', '2.0', @@ -36,19 +43,19 @@ def test_schema_unknown(): def test_validation_error(): """SystemExit on running with ValidationError.""" - testargs = ['--schema', '3.0.0', + testargs = ['--schema', '3.0', './tests/integration/data/v2.0/petstore.yaml'] with pytest.raises(SystemExit): main(testargs) @mock.patch( - 'openapi_spec_validator.__main__.openapi_v3_spec_validator.validate', + 'openapi_spec_validator.__main__.openapi_v3_0_spec_validator.validate', side_effect=Exception, ) def test_unknown_error(m_validate): """SystemExit on running with unknown error.""" - testargs = ['--schema', '3.0.0', + testargs = ['--schema', '3.0', './tests/integration/data/v2.0/petstore.yaml'] with pytest.raises(SystemExit): main(testargs) @@ -62,12 +69,12 @@ def test_nonexisting_file(): def test_schema_stdin(): - """Test schema from STDIN""" + """Test schema is 3.0 from STDIN""" spes_path = './tests/integration/data/v3.0/petstore.yaml' with open(spes_path, 'r') as spec_file: spec_lines = spec_file.readlines() spec_io = StringIO("".join(spec_lines)) - testargs = ['-'] + testargs = ['--schema', '3.0', '-'] with mock.patch('openapi_spec_validator.__main__.sys.stdin', spec_io): main(testargs) diff --git a/tests/integration/test_shortcuts.py b/tests/integration/test_v2_shortcuts.py similarity index 53% rename from tests/integration/test_shortcuts.py rename to tests/integration/test_v2_shortcuts.py index f27aa18..e54f129 100644 --- a/tests/integration/test_shortcuts.py +++ b/tests/integration/test_v2_shortcuts.py @@ -1,10 +1,8 @@ import pytest from openapi_spec_validator import ( - validate_spec, validate_spec_url, validate_v2_spec, validate_v2_spec_url, - validate_spec_url_factory, - openapi_v2_spec_validator, openapi_v3_spec_validator, + validate_spec_url_factory, openapi_v2_spec_validator, ) from openapi_spec_validator.exceptions import OpenAPIValidationError from openapi_spec_validator.handlers.urllib import UrllibHandler @@ -23,19 +21,6 @@ def test_failed(self, spec): validate_v2_spec(spec) -class BaseTestValidValidteSpec: - - def test_valid(self, spec): - validate_spec(spec) - - -class BaseTestFaliedValidateSpec: - - def test_failed(self, spec): - with pytest.raises(OpenAPIValidationError): - validate_spec(spec) - - class BaseTestValidValidateSpecUrl: @pytest.fixture @@ -70,34 +55,6 @@ def test_failed(self, spec_url): validate_v2_spec_url(spec_url) -class BaseTestValidValidateV3SpecUrl(BaseTestValidValidateSpecUrl): - - @pytest.fixture - def validate_spec_url_callable(self, urllib_handlers): - return validate_spec_url_factory( - openapi_v3_spec_validator.validate, urllib_handlers) - - def test_default_valid(self, spec_url): - validate_spec_url(spec_url) - - def test_urllib_valid(self, validate_spec_url_callable, spec_url): - validate_spec_url_callable(spec_url) - - -class BaseTestFaliedValidateSpecUrl: - - def test_failed(self, spec_url): - with pytest.raises(OpenAPIValidationError): - validate_spec_url(spec_url) - - -class TestLocalEmptyExample(BaseTestFaliedValidateSpec): - - @pytest.fixture - def spec(self, factory): - return factory.spec_from_file("data/v3.0/empty.yaml") - - class TestLocalPetstoreV2Example(BaseTestValidValidteV2Spec): @pytest.fixture @@ -105,13 +62,6 @@ def spec(self, factory): return factory.spec_from_file("data/v2.0/petstore.yaml") -class TestLocalPetstoreExample(BaseTestValidValidteSpec): - - @pytest.fixture - def spec(self, factory): - return factory.spec_from_file("data/v3.0/petstore.yaml") - - class TestPetstoreV2Example(BaseTestValidValidateV2SpecUrl): @pytest.fixture @@ -143,36 +93,3 @@ def spec_url(self): 'f25a1d44cff9669703257173e562376cc5bd0ec6/examples/v2.0/' 'yaml/petstore-expanded.yaml' ) - - -class TestPetstoreExample(BaseTestValidValidateV3SpecUrl): - - @pytest.fixture - def spec_url(self): - return ( - 'https://raw.githubusercontent.com/OAI/OpenAPI-Specification/' - 'f75f8486a1aae1a7ceef92fbc63692cb2556c0cd/examples/v3.0/' - 'petstore.yaml' - ) - - -class TestApiWithExampe(BaseTestValidValidateV3SpecUrl): - - @pytest.fixture - def spec_url(self): - return ( - 'https://raw.githubusercontent.com/OAI/OpenAPI-Specification/' - 'f75f8486a1aae1a7ceef92fbc63692cb2556c0cd/examples/v3.0/' - 'api-with-examples.yaml' - ) - - -class TestPetstoreExpandedExample(BaseTestValidValidateV3SpecUrl): - - @pytest.fixture - def spec_url(self): - return ( - 'https://raw.githubusercontent.com/OAI/OpenAPI-Specification/' - '970566d5ca236a5ce1a02fb7d617fdbd07df88db/examples/v3.0/' - 'api-with-examples.yaml' - ) diff --git a/tests/integration/test_v2_validator.py b/tests/integration/test_v2_validator.py new file mode 100644 index 0000000..be187a3 --- /dev/null +++ b/tests/integration/test_v2_validator.py @@ -0,0 +1,42 @@ +from openapi_spec_validator.exceptions import OpenAPIValidationError + + +class TestSpecValidatorIterErrors(object): + + def test_parameter_default_value_wrong_type(self, swagger_validator): + spec = { + 'swagger': '2.0', + 'info': { + 'title': 'Test Api', + 'version': '0.0.1', + }, + 'paths': { + '/test/': { + 'get': { + 'responses': { + '200': { + 'description': 'OK', + 'schema': {'type': 'object'}, + }, + }, + 'parameters': [ + { + 'name': 'param1', + 'in': 'query', + 'type': 'integer', + 'default': 'invaldtype', + }, + ], + }, + }, + }, + } + + errors = swagger_validator.iter_errors(spec) + + errors_list = list(errors) + assert len(errors_list) == 1 + assert errors_list[0].__class__ == OpenAPIValidationError + assert errors_list[0].message == ( + "'invaldtype' is not of type integer" + ) diff --git a/tests/integration/test_v3_0_shortcuts.py b/tests/integration/test_v3_0_shortcuts.py new file mode 100644 index 0000000..4262f7b --- /dev/null +++ b/tests/integration/test_v3_0_shortcuts.py @@ -0,0 +1,102 @@ +import pytest + +from openapi_spec_validator import ( + validate_v3_0_spec, validate_v3_0_spec_url, + validate_spec_url_factory, openapi_v3_0_spec_validator, +) +from openapi_spec_validator.exceptions import OpenAPIValidationError +from openapi_spec_validator.handlers.urllib import UrllibHandler + + +class BaseTestValidValidteSpec: + + def test_valid(self, spec): + validate_v3_0_spec(spec) + + +class BaseTestFaliedValidateSpec: + + def test_failed(self, spec): + with pytest.raises(OpenAPIValidationError): + validate_v3_0_spec(spec) + + +class BaseTestValidValidateSpecUrl: + + @pytest.fixture + def urllib_handlers(self): + all_urls_handler = UrllibHandler('http', 'https', 'file') + return { + '': all_urls_handler, + 'http': UrllibHandler('http'), + 'https': UrllibHandler('https'), + 'file': UrllibHandler('file'), + } + + +class BaseTestValidValidateV3SpecUrl(BaseTestValidValidateSpecUrl): + + @pytest.fixture + def validate_spec_url_callable(self, urllib_handlers): + return validate_spec_url_factory( + openapi_v3_0_spec_validator.validate, urllib_handlers) + + def test_default_valid(self, spec_url): + validate_v3_0_spec_url(spec_url) + + def test_urllib_valid(self, validate_spec_url_callable, spec_url): + validate_spec_url_callable(spec_url) + + +class BaseTestFaliedValidateSpecUrl: + + def test_failed(self, spec_url): + with pytest.raises(OpenAPIValidationError): + validate_v3_0_spec_url(spec_url) + + +class TestLocalEmptyv3Example(BaseTestFaliedValidateSpec): + + @pytest.fixture + def spec(self, factory): + return factory.spec_from_file("data/v3.0/empty.yaml") + + +class TestLocalPetstoreV3Example(BaseTestValidValidteSpec): + + @pytest.fixture + def spec(self, factory): + return factory.spec_from_file("data/v3.0/petstore.yaml") + + +class TestPetstoreV3Example(BaseTestValidValidateV3SpecUrl): + + @pytest.fixture + def spec_url(self): + return ( + 'https://raw.githubusercontent.com/OAI/OpenAPI-Specification/' + 'f75f8486a1aae1a7ceef92fbc63692cb2556c0cd/examples/v3.0/' + 'petstore.yaml' + ) + + +class TestApiWithV3Exampe(BaseTestValidValidateV3SpecUrl): + + @pytest.fixture + def spec_url(self): + return ( + 'https://raw.githubusercontent.com/OAI/OpenAPI-Specification/' + 'f75f8486a1aae1a7ceef92fbc63692cb2556c0cd/examples/v3.0/' + 'api-with-examples.yaml' + ) + + +class TestPetstoreV3ExpandedExample(BaseTestValidValidateV3SpecUrl): + + @pytest.fixture + def spec_url(self): + return ( + 'https://raw.githubusercontent.com/OAI/OpenAPI-Specification/' + '970566d5ca236a5ce1a02fb7d617fdbd07df88db/examples/v3.0/' + 'api-with-examples.yaml' + ) diff --git a/tests/integration/test_validate.py b/tests/integration/test_v3_0_validate.py similarity index 91% rename from tests/integration/test_validate.py rename to tests/integration/test_v3_0_validate.py index 9bb3e08..e96f6c8 100644 --- a/tests/integration/test_validate.py +++ b/tests/integration/test_v3_0_validate.py @@ -9,8 +9,8 @@ class BaseTestValidOpeAPIv3Validator(object): def spec_url(self): return '' - def test_valid(self, validator, spec, spec_url): - return validator.validate(spec, spec_url=spec_url) + def test_valid(self, v3_0_validator, spec, spec_url): + return v3_0_validator.validate(spec, spec_url=spec_url) class BaseTestFailedOpeAPIv3Validator(object): @@ -19,9 +19,9 @@ class BaseTestFailedOpeAPIv3Validator(object): def spec_url(self): return '' - def test_failed(self, validator, spec, spec_url): + def test_failed(self, v3_0_validator, spec, spec_url): with pytest.raises(OpenAPIValidationError): - validator.validate(spec, spec_url=spec_url) + v3_0_validator.validate(spec, spec_url=spec_url) class TestLocalEmptyExample(BaseTestFailedOpeAPIv3Validator): diff --git a/tests/integration/test_validators.py b/tests/integration/test_v3_0_validator.py similarity index 89% rename from tests/integration/test_validators.py rename to tests/integration/test_v3_0_validator.py index 5172b88..a395427 100644 --- a/tests/integration/test_validators.py +++ b/tests/integration/test_v3_0_validator.py @@ -1,3 +1,5 @@ +import pytest + from openapi_spec_validator.exceptions import ( ExtraParametersError, UnresolvableParameterError, OpenAPIValidationError, DuplicateOperationIDError, @@ -6,6 +8,10 @@ class TestSpecValidatorIterErrors(object): + @pytest.fixture + def validator(self, v3_0_validator): + return v3_0_validator + def test_empty(self, validator): spec = {} @@ -296,45 +302,6 @@ def test_parameter_default_value_wrong_type(self, validator): "'invaldtype' is not of type integer" ) - def test_parameter_default_value_wrong_type_swagger(self, - swagger_validator): - spec = { - 'swagger': '2.0', - 'info': { - 'title': 'Test Api', - 'version': '0.0.1', - }, - 'paths': { - '/test/': { - 'get': { - 'responses': { - '200': { - 'description': 'OK', - 'schema': {'type': 'object'}, - }, - }, - 'parameters': [ - { - 'name': 'param1', - 'in': 'query', - 'type': 'integer', - 'default': 'invaldtype', - }, - ], - }, - }, - }, - } - - errors = swagger_validator.iter_errors(spec) - - errors_list = list(errors) - assert len(errors_list) == 1 - assert errors_list[0].__class__ == OpenAPIValidationError - assert errors_list[0].message == ( - "'invaldtype' is not of type integer" - ) - def test_parameter_default_value_with_reference(self, validator): spec = { 'openapi': '3.0.0', diff --git a/tests/integration/test_v3_1_shortcuts.py b/tests/integration/test_v3_1_shortcuts.py new file mode 100644 index 0000000..496e02d --- /dev/null +++ b/tests/integration/test_v3_1_shortcuts.py @@ -0,0 +1,80 @@ +import pytest + +from openapi_spec_validator import ( + validate_v3_1_spec, validate_v3_1_spec_url, + validate_spec_url_factory, openapi_v3_1_spec_validator, +) +from openapi_spec_validator.exceptions import OpenAPIValidationError +from openapi_spec_validator.handlers.urllib import UrllibHandler + + +class BaseTestValidValidteSpec: + + def test_valid(self, spec): + validate_v3_1_spec(spec) + + +class BaseTestFaliedValidateSpec: + + def test_failed(self, spec): + with pytest.raises(OpenAPIValidationError): + validate_v3_1_spec(spec) + + +class BaseTestValidValidateSpecUrl: + + @pytest.fixture + def urllib_handlers(self): + all_urls_handler = UrllibHandler('http', 'https', 'file') + return { + '': all_urls_handler, + 'http': UrllibHandler('http'), + 'https': UrllibHandler('https'), + 'file': UrllibHandler('file'), + } + + +class BaseTestValidValidateV3SpecUrl(BaseTestValidValidateSpecUrl): + + @pytest.fixture + def validate_spec_url_callable(self, urllib_handlers): + return validate_spec_url_factory( + openapi_v3_1_spec_validator.validate, urllib_handlers) + + def test_default_valid(self, spec_url): + validate_v3_1_spec_url(spec_url) + + def test_urllib_valid(self, validate_spec_url_callable, spec_url): + validate_spec_url_callable(spec_url) + + +class BaseTestFaliedValidateSpecUrl: + + def test_failed(self, spec_url): + with pytest.raises(OpenAPIValidationError): + validate_v3_1_spec_url(spec_url) + + +class TestLocalEmptyv3Example(BaseTestFaliedValidateSpec): + + @pytest.fixture + def spec(self, factory): + return factory.spec_from_file("data/v3.1/empty.yaml") + + +class TestLocalPetstoreV3Example(BaseTestValidValidteSpec): + + @pytest.fixture + def spec(self, factory): + return factory.spec_from_file("data/v3.1/petstore.yaml") + + +class TestRemoteV3WebhookExampe(BaseTestValidValidateV3SpecUrl): + + @pytest.fixture + def spec_url(self): + return ( + 'https://raw.githubusercontent.com/OAI/OpenAPI-Specification/' + 'f1adc846131b33be72df6a0c87e5e5da59dde0ff/examples/v3.1/' + 'webhook-example.yaml' + ) diff --git a/tests/integration/test_v3_1_validate.py b/tests/integration/test_v3_1_validate.py new file mode 100644 index 0000000..f5fee6d --- /dev/null +++ b/tests/integration/test_v3_1_validate.py @@ -0,0 +1,50 @@ +import pytest + +from openapi_spec_validator.exceptions import OpenAPIValidationError + + +class BaseTestValidOpeAPIv3Validator(object): + + @pytest.fixture + def spec_url(self): + return '' + + def test_valid(self, v3_1_validator, spec, spec_url): + return v3_1_validator.validate(spec, spec_url=spec_url) + + +class BaseTestFailedOpeAPIv3Validator(object): + + @pytest.fixture + def spec_url(self): + return '' + + def test_failed(self, v3_1_validator, spec, spec_url): + with pytest.raises(OpenAPIValidationError): + v3_1_validator.validate(spec, spec_url=spec_url) + + +class TestLocalEmptyExample(BaseTestFailedOpeAPIv3Validator): + + @pytest.fixture + def spec(self, factory): + return factory.spec_from_file("data/v3.1/empty.yaml") + + +class TestLocalPetstoreExample(BaseTestValidOpeAPIv3Validator): + + @pytest.fixture + def spec(self, factory): + return factory.spec_from_file("data/v3.1/petstore.yaml") + + +class TestRemoteV3WebhookExampe(BaseTestValidOpeAPIv3Validator): + + @pytest.fixture + def spec(self, factory): + url = ( + 'https://raw.githubusercontent.com/OAI/OpenAPI-Specification/' + 'f1adc846131b33be72df6a0c87e5e5da59dde0ff/examples/v3.1/' + 'webhook-example.yaml' + ) + return factory.spec_from_url(url)