def test_auto_200_response_for_bare_views(app, client, config_value): app.config['AUTO_200_RESPONSE'] = config_value @app.get('/foo') def foo(): pass @app.route('/bar') class Bar(MethodView): def get(self): pass def post(self): pass @app.route('/baz') class Baz(MethodView): def get(self): pass @input(FooSchema) def post(self): pass rv = client.get('/openapi.json') assert rv.status_code == 200 validate_spec(rv.json) assert bool('/foo' in rv.json['paths']) is config_value assert bool('/bar' in rv.json['paths']) is config_value assert '/baz' in rv.json['paths'] assert bool('get' in rv.json['paths']['/baz']) is config_value assert 'post' in rv.json['paths']['/baz']
def test_response_description_config(app, client): app.config['SUCCESS_DESCRIPTION'] = 'Success' @app.get('/foo') @input(FooSchema) # 200 def only_body_schema(foo): pass @app.get('/bar') @output(FooSchema, 201) def create(): pass @app.get('/baz') @output(EmptySchema) # 204 def no_schema(): pass @app.get('/spam') @output(FooSchema, 206) def spam(): pass rv = client.get('/openapi.json') assert rv.status_code == 200 validate_spec(rv.json) assert rv.json['paths']['/foo']['get']['responses'][ '200']['description'] == 'Success' assert rv.json['paths']['/bar']['get']['responses'][ '201']['description'] == 'Success' assert rv.json['paths']['/baz']['get']['responses'][ '204']['description'] == 'Success' assert rv.json['paths']['/spam']['get']['responses'][ '206']['description'] == 'Success'
def test_project_mock_openapi() -> None: validate_spec( yaml.full_load( check_output([ "./src.python.arcor2_mocks.scripts/mock_project.pex", "--swagger" ])))
def test_multiple_auth_names(app, client): auth1 = HTTPBasicAuth() auth2 = HTTPBasicAuth() auth3 = HTTPBasicAuth() @app.get('/foo') @auth_required(auth1) def foo(): pass @app.get('/bar') @auth_required(auth2) def bar(): pass @app.get('/baz') @auth_required(auth3) def baz(): pass rv = client.get('/openapi.json') assert rv.status_code == 200 validate_spec(rv.json) assert 'BasicAuth' in rv.json['components']['securitySchemes'] assert 'BasicAuth_2' in rv.json['components']['securitySchemes'] assert 'BasicAuth_3' in rv.json['components']['securitySchemes']
def validate_individual_schemas(list_of_paths): fake_openapi_headers = { "openapi": "3.0.0", "info":{ "title": "An include file to define sortable attributes", "version": "1.0.0" }, "paths": {}, "components": { "parameters":{}, "schemas":{} } } for spec_file_path in list_of_paths: assert spec_file_path.exists() # only consider schemas if not "openapi.yaml" in str(spec_file_path.name) and "schemas" in str(spec_file_path): with spec_file_path.open() as file_ptr: schema_specs = yaml.safe_load(file_ptr) # correct local references correct_schema_local_references(schema_specs) if str(spec_file_path).endswith("-converted.yaml"): schema_specs = add_namespace_for_converted_schemas(schema_specs) fake_openapi_headers["components"]["schemas"] = schema_specs try: validate_spec(fake_openapi_headers, spec_url=spec_file_path.as_uri()) except OpenAPIValidationError as err: pytest.fail(err.message)
def test_class_attribute_decorators(app, client): auth = HTTPTokenAuth() @app.route('/') class Foo(MethodView): decorators = [auth_required(auth), doc(responses=[404])] def get(self): pass def post(self): pass rv = client.get('/') assert rv.status_code == 401 rv = client.post('/') assert rv.status_code == 401 rv = client.get('/openapi.json') assert rv.status_code == 200 validate_spec(rv.json) assert '404' in rv.json['paths']['/']['get']['responses'] assert '404' in rv.json['paths']['/']['post']['responses'] assert 'BearerAuth' in rv.json['paths']['/']['get']['security'][0] assert 'BearerAuth' in rv.json['paths']['/']['post']['security'][0]
def test_security_schemes_description(app, client): basic_auth = HTTPBasicAuth(description='some description for basic auth') token_auth = HTTPTokenAuth(description='some description for bearer auth') @app.get('/foo') @auth_required(basic_auth) def foo(): pass @app.get('/bar') @auth_required(token_auth) def bar(): pass rv = client.get('/openapi.json') assert rv.status_code == 200 validate_spec(rv.json) assert 'BasicAuth' in rv.json['components']['securitySchemes'] assert 'BearerAuth' in rv.json['components']['securitySchemes'] assert rv.json['components']['securitySchemes']['BasicAuth'] == { 'type': 'http', 'scheme': 'Basic', 'description': 'some description for basic auth' } assert rv.json['components']['securitySchemes']['BearerAuth'] == { 'type': 'http', 'scheme': 'Bearer', 'description': 'some description for bearer auth' }
def generate(args=None): if args is None: args = [None] endpoint: Endpoint for endpoint in ENDPOINT_REGISTRY: try: SPEC.path( path=endpoint.path, operations=endpoint.to_operation_dict(), ) except TypeError: print(endpoint, file=sys.stderr) raise # NOTE: deepcopy the dict because validate_spec modifies the SPEC in-place, leaving some # internal properties lying around, which leads to an invalid spec-file. check_dict = copy.deepcopy(SPEC.to_dict()) validate_spec(check_dict) if args[-1] == '--json': output = json.dumps(SPEC.to_dict(), indent=2).rstrip() else: output = SPEC.to_yaml().rstrip() return output
def test_tags(app, client): assert app.tags is None app.tags = [ { 'name': 'foo', 'description': 'some description for foo', 'externalDocs': { 'description': 'Find more info about foo here', 'url': 'https://docs.example.com/' } }, { 'name': 'bar', 'description': 'some description for bar' }, ] rv = client.get('/openapi.json') assert rv.status_code == 200 validate_spec(rv.json) assert rv.json['tags'] assert { 'name': 'bar', 'description': 'some description for bar' } in rv.json['tags'] assert rv.json['tags'][0]['name'] == 'foo' assert rv.json['tags'][0]['description'] == 'some description for foo' assert rv.json['tags'][0]['externalDocs'][ 'description'] == 'Find more info about foo here' assert rv.json['tags'][0]['externalDocs'][ 'url'] == 'https://docs.example.com/'
def test_parameters_registration(app, client): @app.route('/foo') @input(QuerySchema, 'query') @output(FooSchema) def foo(query): pass @app.route('/bar') @input(QuerySchema, 'query') @input(PaginationSchema, 'query') @input(HeaderSchema, 'headers') def bar(query, pagination, header): return { 'query': query['id'], 'pagination': pagination, 'foo': header['foo'] } rv = client.get('/openapi.json') assert rv.status_code == 200 validate_spec(rv.json) assert '/foo' in rv.json['paths'] assert '/bar' in rv.json['paths'] assert rv.json['paths']['/foo']['get']['parameters'][0]['name'] == 'id' assert len(rv.json['paths']['/foo']['get']['parameters']) == 1 assert len(rv.json['paths']['/bar']['get']['parameters']) == 4 rv = client.get('/bar') assert rv.status_code == 200 assert rv.json['query'] == 1 assert rv.json['pagination']['page'] == 1 assert rv.json['pagination']['per_page'] == 10 assert rv.json['foo'] == 'bar'
def test_spec_path_summary_auto_generation(app, client): @app.route('/users') @output(FooSchema) def get_users(): pass @app.route('/users/<id>', methods=['PUT']) @output(FooSchema) def update_user(id): pass @app.route('/users/<id>', methods=['DELETE']) @output(FooSchema) def delete_user(id): """ Summary from Docs Delete a user with specified ID. """ pass rv = client.get('/openapi.json') assert rv.status_code == 200 validate_spec(rv.json) assert rv.json['paths']['/users']['get']['summary'] == 'Get Users' assert rv.json['paths']['/users/{id}']['put']['summary'] == \ 'Update User' assert rv.json['paths']['/users/{id}']['delete']['summary'] == \ 'Summary from Docs' assert rv.json['paths']['/users/{id}']['delete']['description'] == \ 'Delete a user with specified ID.'
def test_path_arguments_order(app, client): @app.route('/<foo>/bar') @input(QuerySchema, 'query') @output(FooSchema) def path_and_query(foo, query): pass @app.route('/<foo>/<bar>') @output(FooSchema) def two_path_variables(foo, bar): pass rv = client.get('/openapi.json') assert rv.status_code == 200 validate_spec(rv.json) assert '/{foo}/bar' in rv.json['paths'] assert '/{foo}/{bar}' in rv.json['paths'] assert rv.json['paths']['/{foo}/bar']['get']['parameters'][0][ 'name'] == 'foo' assert rv.json['paths']['/{foo}/bar']['get']['parameters'][1][ 'name'] == 'id' assert rv.json['paths']['/{foo}/{bar}']['get']['parameters'][0][ 'name'] == 'foo' assert rv.json['paths']['/{foo}/{bar}']['get']['parameters'][1][ 'name'] == 'bar'
def test_spec_path_summary_description_from_docs(app, client): @app.route('/users') @output(FooSchema) def get_users(): """Get Users""" pass @app.route('/users/<id>', methods=['PUT']) @output(FooSchema) def update_user(id): """ Update User Update a user with specified ID. """ pass rv = client.get('/openapi.json') assert rv.status_code == 200 validate_spec(rv.json) assert rv.json['paths']['/users']['get']['summary'] == 'Get Users' assert rv.json['paths']['/users/{id}']['put']['summary'] == \ 'Update User' assert rv.json['paths']['/users/{id}']['put']['description'] == \ 'Update a user with specified ID.'
def test_open_api_tag(self): spec = get_spec( 'test api', camel_case=True, default_response_descriptions={ HTTPStatus.INTERNAL_SERVER_ERROR: 'Unexpected internal server error.', HTTPStatus.NO_CONTENT: 'Operation succeeded. Nothing to return.', HTTPStatus.NOT_FOUND: 'Could not find data.', HTTPStatus.CREATED: 'Resource was created successfully.', HTTPStatus.BAD_REQUEST: 'Invalid input data.', HTTPStatus.OK: 'Success.' }, default_parameter_descriptions={ 'skip': 'The number of data records to skip before fetching a page.', 'take': 'Number of records to return. Use value -1 to get all data.', 'sortColumn': 'The database field to sort by. Use python_syntax for the field name.', 'sortDirection': 'The direction to sort by (asc or desc).', 'createdAt': 'Filter on the creation date. Use an exact date, or a date range e.g. \'[2018-01-01,2019-01-01[\', \']2018-01-01,\' or \'2015-01-01\'', 'modifiedAt': 'Filter on the last modification date. Use an exact date, or a date range e.g. \'[2018-01-01,2019-01-01[\', \']2018-01-01,\' or \'2015-01-01\'', 'ids': 'Ids to match.' }) self.assertEqual(spec['openapi'], '3.0.0') os.makedirs('./temp', exist_ok=True) spec_file = './temp/openapi.json' with open(spec_file, 'w') as file: file.write(json.dumps(spec, indent=4)) spec_dict, spec_url = read_from_filename(spec_file) validate_spec(spec_dict)
def test_servers_and_externaldocs(app): assert app.external_docs is None assert app.servers is None app.external_docs = { 'description': 'Find more info here', 'url': 'https://docs.example.com/' } app.servers = [{ 'url': 'http://localhost:5000/', 'description': 'Development server' }, { 'url': 'https://api.example.com/', 'description': 'Production server' }] rv = app.test_client().get('/openapi.json') assert rv.status_code == 200 validate_spec(rv.json) assert rv.json['externalDocs'] == { 'description': 'Find more info here', 'url': 'https://docs.example.com/' } assert rv.json['servers'] == [{ 'url': 'http://localhost:5000/', 'description': 'Development server' }, { 'url': 'https://api.example.com/', 'description': 'Production server' }]
def test_blueprint_enable_openapi_with_methodview(app, client): auth = HTTPBasicAuth() @app.get('/hello') @auth_required(auth) def hello(): pass bp = APIBlueprint('foo', __name__, tag='test', enable_openapi=False) auth = HTTPTokenAuth() @bp.before_request @auth_required(auth) def before(): pass @bp.route('/foo') class Foo(MethodView): def get(self): pass def post(self): pass app.register_blueprint(bp) rv = client.get('/foo') assert rv.status_code == 401 rv = client.get('/openapi.json') assert rv.status_code == 200 validate_spec(rv.json) assert rv.json['tags'] == [] assert '/hello' in rv.json['paths'] assert '/foo' not in rv.json['paths'] assert 'BearerAuth' not in rv.json['components']['securitySchemes']
def test_auto_200_response(app, client): @app.get('/foo') def bare(): pass @app.get('/bar') @input(FooSchema) def only_input(): pass @app.get('/baz') @doc(summary='some summary') def only_doc(): pass @app.get('/eggs') @output(FooSchema, 204) def output_204(): pass @app.get('/spam') @doc(responses={204: 'empty'}) def doc_responses(): pass rv = client.get('/openapi.json') assert rv.status_code == 200 validate_spec(rv.json) assert '200' in rv.json['paths']['/foo']['get']['responses'] assert '200' in rv.json['paths']['/bar']['get']['responses'] assert '200' in rv.json['paths']['/baz']['get']['responses'] assert '200' not in rv.json['paths']['/eggs']['get']['responses'] assert '200' not in rv.json['paths']['/spam']['get']['responses'] assert rv.json['paths']['/spam']['get']['responses']['204'][ 'description'] == 'empty'
def test_http_auth_error_response(app, client, config_value): app.config['AUTO_HTTP_ERROR_RESPONSE'] = config_value @app.get('/foo') @output(FooSchema) @doc(responses={ 204: 'empty', 400: 'bad', 404: 'not found', 500: 'server error' }) def foo(): pass rv = client.get('/openapi.json') assert rv.status_code == 200 validate_spec(rv.json) if config_value: assert 'HTTPError' in rv.json['components']['schemas'] assert '#/components/schemas/HTTPError' in \ rv.json['paths']['/foo']['get']['responses']['404'][ 'content']['application/json']['schema']['$ref'] assert '#/components/schemas/HTTPError' in \ rv.json['paths']['/foo']['get']['responses']['500'][ 'content']['application/json']['schema']['$ref'] assert rv.json['paths']['/foo']['get']['responses']['204']['content'][ 'application/json']['schema'] == {} else: assert 'HTTPError' not in rv.json['components']['schemas'] assert rv.json['paths']['/foo']['get']['responses']['404']['content'][ 'application/json']['schema'] == {} assert rv.json['paths']['/foo']['get']['responses']['500']['content'][ 'application/json']['schema'] == {} assert rv.json['paths']['/foo']['get']['responses']['204']['content'][ 'application/json']['schema'] == {}
def register() -> None: if hupper.is_active(): # pragma: no cover hupper.get_reloader().watch_files([filepath]) spec_dict = read_yaml_file(filepath) validate_spec(spec_dict) spec = create_spec(spec_dict) def spec_view(request: Request) -> FileResponse: return FileResponse(filepath, request=request, content_type="text/yaml") config.add_route(route_name, route) config.add_view(route_name=route_name, view=spec_view) custom_formatters = config.registry.settings.get( "pyramid_openapi3_formatters") config.registry.settings["pyramid_openapi3"] = { "filepath": filepath, "spec_route_name": route_name, "spec": spec, "request_validator": RequestValidator(spec, custom_formatters), "response_validator": ResponseValidator(spec, custom_formatters), }
def test_openapi() -> None: validate_spec( yaml.full_load( check_output([ "./src.python.arcor2_calibration.scripts/calibration.pex", "--swagger" ])))
def generate_data(target: EndpointTarget, validate: bool = True) -> Dict[str, Any]: endpoint: Endpoint methods = ["get", "put", "post", "delete"] for endpoint in sorted(ENDPOINT_REGISTRY, key=lambda e: (e.func.__module__, methods.index(e.method))): if target in endpoint.blacklist_in: continue SPEC.path( path=endpoint.path, operations=endpoint.to_operation_dict(), ) generated_spec = SPEC.to_dict() # return generated_spec _add_cookie_auth(generated_spec) if not validate: return generated_spec # NOTE: deepcopy the dict because validate_spec modifies the SPEC in-place, leaving some # internal properties lying around, which leads to an invalid spec-file. check_dict = copy.deepcopy(generated_spec) validate_spec(check_dict) # NOTE: We want to modify the thing afterwards. The SPEC object would be a global reference # which would make modifying the spec very awkward, so we deepcopy again. return generated_spec
def register() -> None: settings = config.registry.settings.get(apiname) if settings and settings.get("spec") is not None: raise ConfigurationError( "Spec has already been configured. You may only call " "pyramid_openapi3_spec or pyramid_openapi3_spec_directory once" ) if hupper.is_active(): # pragma: no cover hupper.get_reloader().watch_files([filepath]) spec_dict = read_yaml_file(filepath) validate_spec(spec_dict) spec = create_spec(spec_dict) def spec_view(request: Request) -> FileResponse: return FileResponse(filepath, request=request, content_type="text/yaml") config.add_route(route_name, route) config.add_view(route_name=route_name, permission=permission, view=spec_view) custom_formatters = config.registry.settings.get("pyramid_openapi3_formatters") config.registry.settings[apiname] = { "filepath": filepath, "spec_route_name": route_name, "spec": spec, "request_validator": RequestValidator( spec, custom_formatters=custom_formatters ), "response_validator": ResponseValidator( spec, custom_formatters=custom_formatters ), } APIS.append(apiname)
def generate_data(target: EndpointTarget, validate: bool = True) -> Dict[str, Any]: endpoint: Endpoint methods = ["get", "put", "post", "delete"] # NOTE # This needs to be called on the very first request to create some important configuration # files with default values for them to be considered in the OpenAPI schema. If this wouldn't # be called, the schema would for example lack certain default tag groups. watolib.init_wato_datastructures(with_wato_lock=True) for endpoint in sorted( ENDPOINT_REGISTRY, key=lambda e: (e.func.__module__, methods.index(e.method)) ): if target in endpoint.blacklist_in: continue SPEC.path( path=endpoint.path, operations=endpoint.to_operation_dict(), ) generated_spec = SPEC.to_dict() # return generated_spec _add_cookie_auth(generated_spec) if not validate: return generated_spec # NOTE: deepcopy the dict because validate_spec modifies the SPEC in-place, leaving some # internal properties lying around, which leads to an invalid spec-file. check_dict = copy.deepcopy(generated_spec) validate_spec(check_dict) # NOTE: We want to modify the thing afterwards. The SPEC object would be a global reference # which would make modifying the spec very awkward, so we deepcopy again. return generated_spec
def test_other_info_fields(app, client): assert app.description is None assert app.terms_of_service is None assert app.contact is None assert app.license is None app.description = 'My API' app.terms_of_service = 'http://example.com/terms/' app.contact = { 'name': 'API Support', 'url': 'http://www.example.com/support', 'email': '*****@*****.**' } app.license = { 'name': 'Apache 2.0', 'url': 'http://www.apache.org/licenses/LICENSE-2.0.html' } rv = client.get('/openapi.json') assert rv.status_code == 200 validate_spec(rv.json) assert rv.json['info']['description'] == 'My API' assert rv.json['info']['termsOfService'] == 'http://example.com/terms/' assert rv.json['info']['contact'] == { 'name': 'API Support', 'url': 'http://www.example.com/support', 'email': '*****@*****.**' } assert rv.json['info']['license'] == { 'name': 'Apache 2.0', 'url': 'http://www.apache.org/licenses/LICENSE-2.0.html' }
def test_validity(self, sandbox: Sandbox, rpc_path: str, tmp_path: Path): """ Mimicks the script src/openapi/generate.sh. Generates the API and check it generates a valid OpenAPI specification. """ node = sandbox.node(0) addr = f"http://localhost:{node.rpc_port}/{rpc_path}?recurse=yes" json_path = tmp_path / "result.json" with open(json_path, "w") as o_file: json_res = requests.get(addr).json() json.dump(json_res, o_file) # If you need to debug, insert time.sleep(15) in there, # to give you time to inspect generated files before the # enclosing 'with' block finishes or to execute the dune # command manually while the temporary files are still there. version = _get_tezos_node_version() cmd = [ "dune", "exec", "../src/bin_openapi/rpc_openapi.exe", "--", version, str(json_path.absolute()), ] process_ret = subprocess.run(cmd, check=True, capture_output=True, text=True) res = json.loads(process_ret.stdout) openapi_spec_validator.validate_spec(res)
def test_skip_raw_blueprint(app, client): raw_bp = Blueprint('raw', __name__) api_bp = APIBlueprint('api', __name__, tag='test') @raw_bp.route('/foo') def foo(): pass @raw_bp.route('/bar') class Bar(MethodView): def get(self): pass @api_bp.get('/baz') def baz(): pass @api_bp.route('/spam') class Spam(MethodView): def get(self): pass app.register_blueprint(raw_bp) app.register_blueprint(api_bp) rv = client.get('/openapi.json') assert rv.status_code == 200 validate_spec(rv.json) assert rv.json['tags'] == [{'name': 'test'}] assert '/foo' not in rv.json['paths'] assert '/bar' not in rv.json['paths'] assert '/baz' in rv.json['paths'] assert '/spam' in rv.json['paths']
def test_openapi_specs(): openapi_path = resources.get_path(resources.RESOURCE_OPEN_API) with resources.stream(resources.RESOURCE_OPEN_API) as fh: specs = yaml.safe_load(fh) try: validate_spec(specs, spec_url=openapi_path.as_uri()) except OpenAPIValidationError as err: pytest.fail(err.message)
async def test_oas(cli, settings): response = await cli.get(settings.base_path + '/oas/spec') assert response.status == 200 assert response.content_type == 'application/json' oas = await response.json() validate_spec(oas)
def test_validate_resolved(): simple_yaml = "tests/simple.yaml" oas = yaml_load_file(simple_yaml, cb=replace_branch_name) resolver = OpenapiResolver(oas) oas_resolved = resolver.dump_yaml() validate_spec(oas_resolved) Path("tests/out.simple.yaml").write_text(resolver.dump())
def clean(self): parsed_doc = self._parse_doc(self.doc) if not parsed_doc: raise ValidationError(_("Only Json and Yaml are allowed")) try: validate_spec(parsed_doc) except: # noqa: E722 raise ValidationError({"doc": _("Not a valid openapi schema")})
def register() -> None: spec_dict = read_yaml_file(filepath) validate_spec(spec_dict) spec = create_spec(spec_dict) def spec_view(request: Request) -> FileResponse: return FileResponse(filepath, request=request, content_type="text/yaml") config.add_route(route_name, route) config.add_view(route_name=route_name, view=spec_view) custom_formatters = config.registry.settings.get("pyramid_openapi3_formatters") config.registry.settings["pyramid_openapi3"] = { "filepath": filepath, "spec_route_name": route_name, "spec": spec, "request_validator": RequestValidator(spec, custom_formatters), "response_validator": ResponseValidator(spec, custom_formatters), }
def _validate_spec(cls, spec): from openapi_spec_validator import validate_v3_spec as validate_spec try: validate_spec(spec) except OpenAPIValidationError as e: raise InvalidSpecification.create_from(e)