async def test_remuneration_endpoint(client): resp = await client.get("/schema") spec = create_spec(json.loads(resp.body)) resp = await client.post( "/remuneration", body={ "beneficiaire.age": 20, "formation.region": 27, "formation.codes_financeur": [2], }, ) assert resp.status == HTTPStatus.OK assert "remunerations" in json.loads(resp.body) remunerations = json.loads(resp.body)["remunerations"] assert remunerations print(remunerations[0]) assert "remuneration" in remunerations[0] assert "Version" in resp.headers validator = ResponseValidator(spec) request = MockRequest("http://trefle.pole-emploi.fr", "post", "/remuneration") response = MockResponse(resp.body, resp.status.value) result = validator.validate(request, response) result.raise_for_errors()
async def test_simulate_endpoint(client): resp = await client.get("/schema") spec = create_spec(json.loads(resp.body)) resp = await client.post( "/financement", body={ "beneficiaire.solde_cpf": 10, "beneficiaire.remuneration": 1400, "beneficiaire.droit_prive": True, "beneficiaire.contrat": "cdi", "formation.eligible_cpf": True, "formation.heures": 100, "beneficiaire.entreprise.commune": "2A004", "beneficiaire.entreprise.idcc": 2706, }, ) assert resp.status == HTTPStatus.OK assert "financements" in json.loads(resp.body) financements = json.loads(resp.body)["financements"] print(financements[0]) assert financements assert financements[0].get("eligible") assert "Version" in resp.headers validator = ResponseValidator(spec) request = MockRequest("http://trefle.pole-emploi.fr", "post", "/financement") response = MockResponse(resp.body, resp.status.value) result = validator.validate(request, response) result.raise_for_errors()
def load_spec(self, spec_file): log.debug('%s.load_spec: %s' % (self.__class__.__name__, spec_file)) # TODO: supporting loading from url instead of just file # TODO: How to handle/interpret/respect spec.servers[].url's? # TODO: Or should this be generated/injected into the spec_dict on startup? #spec_file = '/home/sar/vcs/nameko-openapi/petstore.yaml' spec_dict = yaml.safe_load(open(spec_file)) self.spec = openapi_core.create_spec(spec_dict) self.request_validator = RequestValidator(self.spec) self.response_validator = ResponseValidator(self.spec) self._loaded.send(self.spec)
def validate(req, resp, path, query): """Validate a query and its response.""" settings = utils.setup.Settings() warnings = [] if settings.openapi: # check that the query complies to the api spec validator = RequestValidator(settings.openapi) result = validator.validate(req) settings.query_warnings += map(str, result.errors) warnings.extend(['OpenApi: ' + str(x) for x in result.errors]) # check that the response complies to the api spec validator = ResponseValidator(settings.openapi) result = validator.validate(req, resp) settings.warnings += map(str, result.errors) warnings.extend(['OpenAPI: ' + str(x) for x in result.errors]) if settings.use_json_schemas: if path != '/' and query is not None: # validate query against jsons schemas q_warns = utils.jsonschemas.validate(query, 'query', settings) warnings.extend(q_warns) settings.query_warnings += q_warns # validate response against jsons schemas warns = utils.jsonschemas.validate(resp.data, 'response', settings, path) warnings.extend(warns) settings.warnings += warns for warning in warnings: logging.warning(warning) return json.loads(resp.data)
def test_healthz(client): """Test the /healthz endpoint.""" path = '/v1/healthz' rv = client.get(path) # Validate request and response against OpenAPI spec with app.test_request_context(path): with open(app.config['OPENAPI_SPEC']) as stream: spec = create_spec(safe_load(stream)) openapi_response = FlaskOpenAPIResponse(rv) openapi_request = FlaskOpenAPIRequest(request) validator = ResponseValidator(spec) result = validator.validate(openapi_request, openapi_response) result.raise_for_errors() assert rv.content_type == "text/plain" assert rv.status_code == 200 assert b'OK' in rv.data
async def test_simulate_endpoint_with_invalid_data(client): resp = await client.get("/schema") spec = create_spec(json.loads(resp.body)) resp = await client.post( "/financement", body={ "beneficiaire.remuneration": "1400", "beneficiaire.droit_prive": "invalide", "beneficiaire.entreprise.idcc": 2706, }, ) assert resp.status == HTTPStatus.UNPROCESSABLE_ENTITY assert "application/json" in resp.headers["Content-Type"] validator = ResponseValidator(spec) request = MockRequest("http://trefle.pole-emploi.fr", "post", "/financement") response = MockResponse(resp.body, resp.status.value) result = validator.validate(request, response) result.raise_for_errors()
def __init__( self, spec: Union[Spec, dict], *, server_url: Optional[str] = None, client: Union[ModuleType, Requestable] = httpx, request_class: Type[ClientOpenAPIRequest] = ClientOpenAPIRequest, response_factory: Callable[[Any], OpenAPIResponse] = ClientOpenAPIResponse, headers: Optional[dict] = None, ): if not isinstance(spec, Spec): spec = create_spec(spec) self.spec = spec self.client = client self.request_class = request_class self.response_factory = response_factory self.common_headers = headers or {} if server_url is None: server_url = self.spec.servers[0].url else: server_url = server_url.rstrip("/") for server in self.spec.servers: if server_url == server.url: break else: self.spec.servers.append(Server(server_url)) self.server_url = server_url self.validator = ResponseValidator(self.spec) for path_spec in spec.paths.values(): for op_spec in path_spec.operations.values(): setattr( self, snakecase(op_spec.operation_id), self._get_operation(op_spec).__get__(self), )
class OpenApiSpecManager(SharedExtension): _loaded = Event() def setup(self): log.info(' ### OpenApiSpecManager.setup') super().setup() def load_spec(self, spec_file): log.debug('%s.load_spec: %s' % (self.__class__.__name__, spec_file)) # TODO: supporting loading from url instead of just file # TODO: How to handle/interpret/respect spec.servers[].url's? # TODO: Or should this be generated/injected into the spec_dict on startup? #spec_file = '/home/sar/vcs/nameko-openapi/petstore.yaml' spec_dict = yaml.safe_load(open(spec_file)) self.spec = openapi_core.create_spec(spec_dict) self.request_validator = RequestValidator(self.spec) self.response_validator = ResponseValidator(self.spec) self._loaded.send(self.spec) def wait_for_spec(self): """Allow other extensions to wait until the spec is loaded.""" return self._loaded.wait() def get_operation_by_id(self, operation_id): self.wait_for_spec() for path_name, path in six.iteritems(self.spec.paths): for http_method, operation in six.iteritems(path.operations): if operation.operation_id == operation_id: return operation def validate_request(self, request, raise_for_errors=True): result = self.request_validator.validate(request) if raise_for_errors: result.raise_for_errors() return result def validate_response(self, response, openapi_request, raise_for_errors=True): result = self.response_validator.validate(openapi_request, response) if raise_for_errors: result.raise_for_errors() return result
def validate(req, resp, path, query): """Validate a query and its response.""" settings = utils.setup.Settings() warnings = [] if settings.openapi: # check that the query complies to the api spec validator = RequestValidator(settings.openapi) result = validator.validate(req) settings.query_warnings += map(str, result.errors) warnings.extend(['OpenApi: ' + str(x) for x in result.errors]) # check that the response complies to the api spec validator = ResponseValidator(settings.openapi) result = validator.validate(req, resp) settings.warnings += map(str, result.errors) for error in result.errors: if isinstance(error, InvalidSchemaValue): warning = f'OpenAPI:\n\tAt object {error.value}\n\t' warning += "\n\t".join(prettify_schemaerror(error)) warnings.append(warning) else: warnings.append(f'OpenAPI: {str(error)}') if settings.use_json_schemas: if path != '/' and query is not None: # validate query against jsons schemas q_warns = utils.jsonschemas.validate(query, 'query', settings) warnings.extend(q_warns) settings.query_warnings += q_warns # validate response against jsons schemas warns = utils.jsonschemas.validate(resp.data, 'response', settings, path) warnings.extend(warns) settings.warnings += warns for warning in warnings: logging.warning(warning) return json.loads(resp.data)
async def wrapper(request: Request, **kwargs) -> Response: openapi_request = await StarletteOpenAPIRequest(request) validated_request = RequestValidator( self.spec, custom_formatters=self.custom_formatters, custom_media_type_deserializers=self. custom_media_type_deserializers, ).validate(openapi_request) try: validated_request.raise_for_errors() except InvalidSecurity as ex: raise HTTPException(HTTPStatus.FORBIDDEN, "Invalid security.") from ex except OpenAPIError as ex: raise HTTPException(HTTPStatus.BAD_REQUEST, "Bad request") from ex response = endpoint_fn(request, **kwargs) if iscoroutine(response): response = await response if isinstance(response, dict): response = JSONResponse(response) elif not isinstance(response, Response): raise ValueError( f"The endpoint function `{endpoint_fn.__name__}` must return" " either a dict or a Starlette Response instance.") # TODO: pass a list of operation IDs to specify which responses not to validate if self.validate_responses: ResponseValidator( self.spec, custom_formatters=self.custom_formatters, custom_media_type_deserializers=self. custom_media_type_deserializers, ).validate( openapi_request, StarletteOpenAPIResponse(response)).raise_for_errors() return response
def register(): spec_dict = read_yaml_file(filepath) validate_spec(spec_dict) spec = create_spec(spec_dict) def spec_view(request): return FileResponse( filepath, request=request, content_type='text/yaml' ) config.add_view(route_name=route_name, view=spec_view) config.add_route(route_name, route) 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_request(req, path_pattern, path_params, spec, url): print('Validating URL: {}'.format(url)) counters['requests'] += 1 openapi_request = RequestsOpenAPIRequest(req, path_pattern, path_params) validator = RequestValidator(spec) result = validator.validate(openapi_request) request_errors = result.errors r = req.prepare() s = requests.Session() res = s.send(r) openapi_response = RequestsOpenAPIResponse(res) validator = ResponseValidator(spec) result = validator.validate(openapi_request, openapi_response) response_errors = result.errors print('Request errors: {}'.format(request_errors)) print('Response errors: {}'.format(response_errors)) if request_errors or response_errors: errors_count = len(request_errors) + len(response_errors) print( color(' [FAIL] {:d} errors found '.format(errors_count), fg='white', bg='red', style='bold')) print("Response body: {}".format(res.text)) else: errors_count = 0 print( color(' [PASS] No errors found ', fg='white', bg='green', style='bold')) print() return errors_count
def response_validator_yaml(filename): """Initialization response_validator for openapi.yaml.""" with open(filename, "r") as openapi: spec = create_spec(yaml.load(openapi, ExtendedSafeLoader)) print("spec:", spec) return ResponseValidator(spec)
def response_validator_json(filename): """Initialization response_validator for openapi.json.""" with open(filename, "r") as openapi: spec = create_spec(json.load(openapi)) return ResponseValidator(spec)
class Client: def __init__( self, spec: Union[Spec, dict], *, server_url: Optional[str] = None, client: Union[ModuleType, Requestable] = httpx, request_class: Type[ClientOpenAPIRequest] = ClientOpenAPIRequest, response_factory: Callable[[Any], OpenAPIResponse] = ClientOpenAPIResponse, headers: Optional[dict] = None, ): if not isinstance(spec, Spec): spec = create_spec(spec) self.spec = spec self.client = client self.request_class = request_class self.response_factory = response_factory self.common_headers = headers or {} if server_url is None: server_url = self.spec.servers[0].url else: server_url = server_url.rstrip("/") for server in self.spec.servers: if server_url == server.url: break else: self.spec.servers.append(Server(server_url)) self.server_url = server_url self.validator = ResponseValidator(self.spec) for path_spec in spec.paths.values(): for op_spec in path_spec.operations.values(): setattr( self, snakecase(op_spec.operation_id), self._get_operation(op_spec).__get__(self), ) @staticmethod def _get_operation(op_spec): # TODO: extract args and kwargs from operation parameters def operation( self, *args, body_: Optional[Union[dict, list]] = None, headers_: Optional[dict] = None, **kwargs, ): request_headers = self.common_headers.copy() request_headers.update(headers_ or {}) request = self.request_class(self.server_url, op_spec) request.prepare(*args, body_=body_, headers_=request_headers, **kwargs) request_params = { "method": request.method, "url": request.url, "headers": request.headers, } if request.body: request_params["json" if "json" in request.mimetype else "data"] = request.body api_response = self.client.request(**request_params) api_response.raise_for_status() response = self.response_factory(api_response) self.validator.validate(request, response).raise_for_errors() return response operation.__doc__ = op_spec.summary or op_spec.operation_id if op_spec.description: operation.__doc__ += f"\n\n{op_spec.description}" return operation @classmethod def from_file(cls, path: Union[Path, str], **kwargs): """Creates an instance of the class by loading the spec from a local file.""" spec = get_spec_from_file(path) return cls(spec, **kwargs)