def test_sateless_faker_1(mock_data_store): faker = StatefulFaker(mock_data_store) request = RequestBuilder.from_dict( dict(method="get", protocol="http", path="/", host="api.com") ) schema = { "type": "array", "items": { "type": "object", "required": ["foo", "baz"], "properties": { "foo": {"type": "number"}, "bar": {"type": "string"}, "baz": {"type": "string"}, }, }, } res = faker.process( OpenAPISpecification(source="default", api=spec(response_schema=schema)), request, ) assert valid_schema(res.bodyAsJson, schema)
def test_sateless_faker_2(mock_data_store): faker = StatefulFaker(mock_data_store) request = RequestBuilder.from_dict( dict(method="get", protocol="http", path="/", host="api.com") ) schema = { "$id": "https://example.com/person.schema.json", "$schema": "http://json-schema.org/draft-07/schema#", "title": "Person", "type": "object", "properties": { "firstName": {"type": "string", "description": "The person's first name."}, "lastName": {"type": "string", "description": "The person's last name."}, "age": { "description": "Age in years which must be equal to or greater than zero.", "type": "integer", "minimum": 0.0, }, }, } res = faker.process( OpenAPISpecification(source="default", api=spec(response_schema=schema)), request, ) assert valid_schema(res.bodyAsJson, schema)
def test_sateless_faker_3(mock_data_store): faker = StatefulFaker(mock_data_store) request = RequestBuilder.from_dict( dict(method="get", protocol="http", path="/", host="api.com") ) schema = { "$id": "https://example.com/geographical-location.schema.json", "$schema": "http://json-schema.org/draft-07/schema#", "title": "Longitude and Latitude Values", "description": "A geographical coordinate.", "required": ["latitude", "longitude"], "type": "object", "properties": { "latitude": {"type": "number", "minimum": -90.0, "maximum": 90.0}, "longitude": {"type": "number", "minimum": -180.0, "maximum": 180.0}, }, } res = faker.process( OpenAPISpecification(source="default", api=spec(response_schema=schema)), request, ) assert valid_schema(res.bodyAsJson, schema)
def test_request_logging_none(tmp_dir): request = RequestBuilder.from_dict( dict( method="get", host="api.com", pathname="/echo", query={"message": "Hello"}, body="", protocol="http", headers={}, )) response = ResponseBuilder.from_dict( dict( statusCode=200, body='{"message": "hello"}', bodyAsJson={"message": "hello"}, headers={}, )) log_dir = os.path.join(tmp_dir, "logs") specs_dir = os.path.join(tmp_dir, "specs") with RequestLoggingCallback(log_dir=log_dir, specs_dir=specs_dir, update_mode=None) as data_callback: data_callback.log(request, response) expected_recordings_path = os.path.join(log_dir, "api.com-recordings.jsonl") assert os.path.exists(expected_recordings_path) assert 0 == len(os.listdir(specs_dir))
def test_sateless_faker_4(mock_data_store): faker = StatefulFaker(mock_data_store) request = RequestBuilder.from_dict( dict(method="get", protocol="http", path="/", host="api.com")) schema = { "$id": "https://example.com/arrays.schema.json", "$schema": "http://json-schema.org/draft-07/schema#", "description": "A representation of a person, company, organization, or place", "type": "object", "properties": { "fruits": { "type": "array", "items": { "type": "string" } }, "vegetables": { "type": "array", "items": { "$ref": "#/components/schemas/veggie" }, }, }, } components = { "schemas": { "veggie": { "type": "object", "required": ["veggieName", "veggieLike"], "properties": { "veggieName": { "type": "string", "description": "The name of the vegetable.", }, "veggieLike": { "type": "boolean", "description": "Do I like this vegetable?", }, }, } } } oai = spec(response_schema=schema, components=components) res = faker.process( "/", OpenAPISpecification(source="default", api=oai, definitions=make_definitions_from_spec(oai)), request, ) schema["components"] = components assert valid_schema(res.bodyAsJson, schema)
def test_fake_array(mock_data_store): faker = StatefulFaker(mock_data_store) request = RequestBuilder.from_dict( dict(method="get", protocol="http", path="/items", host="api.com") ) schema = {"type": "array", "items": {"$ref": "#/components/schemas/item"}} components = { "schemas": { "item": { "type": "object", "required": ["foo", "bar"], "x-hmt-id-path": "itemId", "properties": { "foo": {"type": "number"}, "bar": {"type": "string"}, "itemId": {"type": "string"}, }, } } } spec = spec_dict( path="/items", response_schema=schema, components=components, method="get" ) spec["paths"]["/items"]["x-hmt-entity"] = "item" spec["paths"]["/items"]["get"]["x-hmt-operation"] = "read" spec = convert_to_OpenAPIObject(spec) mock_data_store.add_mock(OpenAPISpecification(spec, "default")) schema["components"] = components spec = OpenAPISpecification(source="default", api=spec) res = faker.process(spec, request) assert valid_schema(res.bodyAsJson, schema) assert 0 == len(res.bodyAsJson) mock_data_store["default"].item.insert({"foo": 10, "bar": "val", "itemId": "id123"}) res = faker.process(spec, request) assert valid_schema(res.bodyAsJson, schema) assert 1 == len(res.bodyAsJson) mock_data_store["default"].item.insert( {"foo": 10, "bar": "val", "itemId": "id1234"} ) res = faker.process(spec, request) assert valid_schema(res.bodyAsJson, schema) assert 2 == len(res.bodyAsJson)
def log(self, request: Request, response: Response): RequestBuilder.validate(request) ResponseBuilder.validate(response) host = request.host reqres = HttpExchange(request=request, response=response) if host not in self._logs: log_file = os.path.join(self._log_dir, "{}-recordings.jsonl".format(host)) if self._append and os.path.exists(log_file): self._logs[host] = open(log_file, "a") else: self._logs[host] = open(log_file, "w") HttpExchangeWriter(self._logs[host]).write(reqres) self._logs[host].flush() logger.debug("Logs for %s were updated", host) if self._update_mode: spec_file = os.path.join( self._specs_dir, "{}_{}.json".format(host, self._update_mode.name.lower()), ) if host not in self._specs: if os.path.exists(spec_file) and self._append: with open(spec_file, "r") as f: self._specs[host] = convert_to_openapi(json.load(f)) else: self._specs[host] = BASE_SCHEMA self._specs[host] = update_openapi(self._specs[host], reqres, self._update_mode) with open(spec_file, "w") as f: spec = convert_from_openapi(self._specs[host]) json.dump(spec, f) f.flush() logger.debug("Schema for %s was updated", host)
def test_matcher_5(): assert match_request_to_openapi( RequestBuilder.from_dict({ "headers": {}, "host": "api.foo.com", "path": "/user/fdsfsfwef", # no validation on path params "pathname": "/user/fdsfsfwef", # no validation on path params "protocol": "https", "method": "get", "query": {}, }), store, ) == ("/user/{id}", store[0])
def test_matcher_4(): assert match_request_to_openapi( RequestBuilder.from_dict({ "headers": {}, "host": "api.foo.com", "path": "/user/55", # correctly parses number "pathname": "/user/55", # correcly parses number "protocol": "https", "method": "get", "query": {}, }), store, ) == ("/user/{id}", store[0])
def test_matcher_3(): assert match_request_to_openapi( RequestBuilder.from_dict({ "headers": {}, "host": "api.foo.com", "path": "/users", # incorrect, should be user "pathname": "/users", # incorrect, should be user "protocol": "https", "method": "get", "query": {}, }), store, ) == (None, None)
def test_matcher_2(): assert match_request_to_openapi( RequestBuilder.from_dict({ "headers": {}, "host": "api.bar.com", "path": "/v1/guest/{id}", "pathname": "/v1/guest/{id}", "protocol": "https", "method": "post", "query": {}, }), store, ) == ("/guest/{id}", store[1])
def test_matcher_7(): assert match_request_to_openapi( RequestBuilder.from_dict({ "headers": {}, "host": "api.foo.commmm", # does not exist "path": "/user", "pathname": "/user", "protocol": "https", "method": "get", "query": {}, }), store, ) == (None, None)
def test_matcher_10(): assert (match_request_to_openapi( RequestBuilder.from_dict({ "headers": {}, "host": "api.baz.com", "path": "/guest", "pathname": "/guest", "protocol": "https", "method": "get", "query": {}, # query is not validated here }), store, )[0] == "/guest")
def test_sateless_faker_5(mock_data_store): faker = StatefulFaker(mock_data_store) request = RequestBuilder.from_dict( dict(method="get", protocol="http", path="/", host="api.com") ) schema = {"type": "array"} res = faker.process( OpenAPISpecification(source="default", api=spec(response_schema=schema)), request, ) assert valid_schema(res.bodyAsJson, schema)
def _serve(self): headers = {k: v for k, v in self.request.headers.get_all()} route_info = self._router.route(self.request.path, headers) headers["Host"] = route_info.host query = parse.parse_qs(self.request.query) fullpath = ("{}?{}".format(route_info.path, self.request.query) if query else route_info.path) # ignoring type due to this error """ 46:34 - error: Argument of type 'str' cannot be assigned to parameter 'method' of type 'Literal['connect', 'head', 'trace', 'options', 'delete', 'patch', 'post', 'put', 'get']' 'str' cannot be assigned to 'Literal['connect']' 'str' cannot be assigned to 'Literal['head']' 'str' cannot be assigned to 'Literal['trace']' 'str' cannot be assigned to 'Literal['options']' 'str' cannot be assigned to 'Literal['delete']' """ request = RequestBuilder.from_dict({ "method": self.request.method.lower(), "host": route_info.host, "path": fullpath, "pathname": route_info.path, "protocol": route_info.scheme, "query": query, "body": self.request.body.decode("utf-8"), "headers": headers, }) logger.debug("Processing request: %s", asdict(request)) response = self._request_processor.process(request) logger.debug("Resolved response: %s", asdict(response)) for header, value in response.headers.items(): self.set_header(header, value) self._http_log.put(request, response) self.set_status(response.statusCode) self.write(response.body) logger.debug("Handled writing response")
def test_matcher_14(): assert (match_request_to_openapi( RequestBuilder.from_dict({ "headers": {}, # no header will lead to undefined "host": "api.baz.com", "path": "/guest/4", "pathname": "/guest/4", "protocol": "https", "method": "post", "query": { "zzz": "aaa", "a": "foo", "b": "baz" }, }), store, )[0] == "/guest/{id}")
def test_matcher_12(): assert (match_request_to_openapi( RequestBuilder.from_dict({ "headers": {}, "host": "api.baz.com", "path": "/guest/3/name", "pathname": "/guest/3/name", "protocol": "https", "method": "post", "query": {}, "body": json.dumps({"age": "42"}), "bodyAsJson": { "age": "42" }, # wrong type, as 42 is string }), store, )[0] == "/guest/{id}/name")
def test_request_logging_mixed_append(tmp_dir): specs_dir = os.path.join(tmp_dir, "specs") spec_path = os.path.join(specs_dir, "another.api.com_mixed.json") os.makedirs(specs_dir) with open(spec_path, "w") as f: json.dump(convert_from_openapi(BASE_SCHEMA), f) with open(spec_path) as f: spec = convert_to_openapi(json.load(f)) assert spec.servers is None request = RequestBuilder.from_dict( dict( method="get", host="another.api.com", pathname="/echo", query={"message": "Hello"}, body="", bodyAsJson={}, path="/echo", protocol="http", headers={}, )) response = ResponseBuilder.from_dict( dict( statusCode=200, body='{"message": "hello"}', bodyAsJson={"message": "hello"}, headers={}, )) log_dir = os.path.join(tmp_dir, "logs") with RequestLoggingCallback(log_dir=log_dir, specs_dir=specs_dir, update_mode=UpdateMode.MIXED) as data_callback: data_callback.log(request, response) assert os.path.exists( os.path.join(log_dir, "another.api.com-recordings.jsonl")) assert os.path.exists(spec_path) with open(spec_path) as f: spec = convert_to_openapi(json.load(f)) assert "http://another.api.com" == spec.servers[0].url
def test_faker_5(): faker = StatelessFaker() request = RequestBuilder.from_dict( dict(method="get", protocol="http", path="/", host="api.com") ) schema = {"type": "array"} res = faker.process( "/", OpenAPISpecification( source="default", api=spec(response_schema=schema), definitions={"definitions": {}}, ), request, ) assert valid_schema(res.bodyAsJson, schema)
def test_request_logging_gen(tmp_dir): request = RequestBuilder.from_dict( dict( method="get", host="api.com", pathname="/echo", query={"message": "Hello"}, body="", protocol="http", headers={}, )) response = ResponseBuilder.from_dict( dict( statusCode=200, body='{"message": "hello"}', bodyAsJson={"message": "hello"}, headers={}, )) log_dir = os.path.join(tmp_dir, "logs") specs_dir = os.path.join(tmp_dir, "specs") with RequestLoggingCallback(log_dir=log_dir, specs_dir=specs_dir, update_mode=UpdateMode.GEN) as data_callback: data_callback.log(request, response) expected_recordings_path = os.path.join(log_dir, "api.com-recordings.jsonl") assert os.path.exists(expected_recordings_path) expected_specs_path = os.path.join(specs_dir, "api.com_gen.json") assert os.path.exists(expected_specs_path) with open(expected_recordings_path, "r") as f: data = [x for x in f.read().split("\n") if x != ""] assert 1 == len(data) http_exchange = HttpExchangeReader.from_json(data[0]) assert request == http_exchange.request assert response == http_exchange.response
def test_query(): schema_single = {"$ref": "#/components/schemas/item"} schema_array = { "accounts": { "items": { "$ref": "#/components/schemas/items" }, "type": "array" } } components = { "schemas": { "item": { "type": "object", "required": ["foo", "baz"], "x-hmt-id-path": "itemId", "properties": { "foo": { "type": "number" }, "bar": { "type": "string" }, "itemId": { "type": "string" }, }, } } } spec = spec_dict( path="/items/{id}", response_schema=schema_single, components=components, method="get", ) spec["paths"]["/items/{id}"]["x-hmt-entity"] = "item" spec["paths"]["/items/{id}"]["get"]["x-hmt-operation"] = "read" add_item( spec, path="/items", response_schema=schema_array, components=components, method="get", ) spec["paths"]["/items"]["x-hmt-entity"] = "item" spec["paths"]["/items"]["get"]["x-hmt-operation"] = "read" spec = convert_to_OpenAPIObject(spec) entity = Entity("item", spec) entity.insert({"foo": 10, "bar": "val", "itemId": "id123"}) entity.insert({"foo": 20, "bar": "val1", "itemId": "id1234"}) entity.insert({"foo": 30, "bar": "val2", "itemId": "id12345"}) res = entity.query_one( "/items/{id}", RequestBuilder.from_dict( dict(method="get", protocol="http", path="/items/id1234", host="api.com")), ) assert 20 == res["foo"] res = entity.query( "/items", RequestBuilder.from_dict( dict(method="get", protocol="http", path="/items", host="api.com")), ) assert 3 == len(res)
def test_insert_from_request(): schema = {"$ref": "#/components/schemas/item"} components = { "schemas": { "item": { "type": "object", "required": ["foo", "baz"], "x-hmt-id-path": "itemId", "properties": { "foo": { "type": "number" }, "bar": { "type": "string" }, "itemId": { "type": "string" }, }, } } } spec = spec_dict( path="/items/create", response_schema=schema, request_schema=schema, components=components, method="post", ) spec["paths"]["/items/create"]["x-hmt-entity"] = "item" spec["paths"]["/items/create"]["post"]["x-hmt-operation"] = "insert" spec = convert_to_OpenAPIObject(spec) entity = Entity("item", spec) request = RequestBuilder.from_dict( dict( method="post", protocol="http", path="/items", host="api.com", bodyAsJson={ "foo": 15, "bar": "val2" }, )) entity.insert_from_request("/items/create", request) res = next(iter(entity.values())) assert 15 == res["foo"] assert res["itemId"] is not None request = RequestBuilder.from_dict( dict( method="post", protocol="http", path="/items", host="api.com", bodyAsJson={ "foo": 10, "bar": "val", "itemId": "id123" }, )) entity.insert_from_request("/items/create", request) assert len(entity) == 2 res = entity["id123"] assert 10 == res["foo"]
from typing import cast from hamcrest import * from http_types import RequestBuilder from openapi_typed_2 import Parameter, Schema from hmt.build.param import ParamBuilder, SchemaParameters from hmt.build.update_mode import UpdateMode req = RequestBuilder.from_url( "https://petstore.swagger.io/v1/pets?id=32&car=ferrari") def test_build_new_query(): query = req.query schema_query = ParamBuilder("query").build(query, UpdateMode.GEN) assert_that(schema_query, has_length(2)) query_parameter = get_query_parameter("id", schema_query) assert query_parameter.name == "id" assert query_parameter._in == "query" assert query_parameter.schema._type == "string" required_query_parameter = Parameter( description=None, deprecated=None, allowEmptyValue=None, style=None, explode=None,
from hmt.serve.mock.matcher import truncate_path from hmt.serve.mock.security import ( match_request_to_security_scheme, match_to_security_schemes, ) from hmt.serve.mock.specs import load_specs spec = load_specs("tests/serve/mock/schemas/nordea")[0].api spec_petstore = load_specs("tests/serve/mock/schemas/petstore")[0].api redirect_uri = "https://example.com/callback" state = "my-state" req = RequestBuilder.from_url( f"https://api.nordeaopenbanking.com/personal/v4/authorize?redirect_uri={redirect_uri}&state={state}" ) def test_truncate_path(): truncated = truncate_path(req.pathname, spec, req) assert truncated == "/v4/authorize" def test_match_to_oauth(): match = match_request_to_security_scheme(req, spec) assert_that(match, instance_of(Response)) def test_match_to_security_schemes(): match = match_to_security_schemes(req, (spec, spec_petstore))
def test_no_match(): req_no_match = RequestBuilder.from_url( "https://petstore.swagger.io/v2/pets") norm_pathname = normalize_path_if_matches(req_no_match, [petstore_server]) assert_that(norm_pathname, is_(None))
def test_insert(mock_data_store): faker = StatefulFaker(mock_data_store) request_schema = { "type": "object", "properties": {"item": {"$ref": "#/components/schemas/item"}}, } response_schema = {"$ref": "#/components/schemas/item"} components = { "schemas": { "item": { "type": "object", "required": ["foo"], "x-hmt-id-path": "itemId", "properties": { "foo": {"type": "number"}, "bar": {"type": "string"}, "baz": {"type": "string"}, "itemId": {"type": "string"}, }, } } } spec = spec_dict( path="/items", request_schema=request_schema, response_schema=response_schema, components=components, method="post", ) spec["paths"]["/items"]["x-hmt-entity"] = "item" spec["paths"]["/items"]["post"]["x-hmt-operation"] = "insert" spec = convert_to_OpenAPIObject(spec) mock_data_store.add_mock(OpenAPISpecification(spec, "default")) schema = response_schema schema["components"] = components spec = OpenAPISpecification(source="default", api=spec) request = RequestBuilder.from_dict( dict( method="post", protocol="http", path="/items", host="api.com", bodyAsJson={"item": {"foo": 10, "bar": "val"}}, ) ) res = faker.process(spec, request) assert valid_schema(res.bodyAsJson, schema) assert res.bodyAsJson["itemId"] is not None assert 10 == res.bodyAsJson["foo"] assert 1 == len(mock_data_store["default"].item) assert "val" == mock_data_store["default"].item[res.bodyAsJson["itemId"]]["bar"] request = RequestBuilder.from_dict( dict( method="post", protocol="http", path="/items", host="api.com", bodyAsJson={"item": {"foo": 20, "bar": "val1", "itemId": "id123"}}, ) ) res = faker.process(spec, request) assert valid_schema(res.bodyAsJson, schema) assert "id123" == res.bodyAsJson["itemId"] assert 20 == res.bodyAsJson["foo"] assert 2 == len(mock_data_store["default"].item) assert "val1" == mock_data_store["default"].item[res.bodyAsJson["itemId"]]["bar"] request = RequestBuilder.from_dict( dict( method="post", protocol="http", path="/items", host="api.com", bodyAsJson={"item": {"foo": 30, "itemId": "id123"}}, ) ) res = faker.process(spec, request) assert 2 == len(mock_data_store["default"].item) assert "bar" not in mock_data_store["default"].item[res.bodyAsJson["itemId"]] assert 30 == mock_data_store["default"].item[res.bodyAsJson["itemId"]]["foo"]
from typing import List from hamcrest import * from http_types import RequestBuilder from openapi_typed_2 import Server from hmt.build.servers import normalize_path_if_matches petstore_req = RequestBuilder.from_url("https://petstore.swagger.io/v1/pets") petstore_server = Server(description=None, variables=None, _x=None, url="https://petstore.swagger.io/v1") def test_normalize_path_for_match(): norm_pathname = normalize_path_if_matches(petstore_req, [petstore_server]) assert_that(norm_pathname, is_("/pets")) def test_no_match(): req_no_match = RequestBuilder.from_url( "https://petstore.swagger.io/v2/pets") norm_pathname = normalize_path_if_matches(req_no_match, [petstore_server]) assert_that(norm_pathname, is_(None))
assert_that(paths, has_item(matches_regexp(r"\/v2\/pokemon\/\{[\w]+\}"))) assert_that(paths, has_item(matches_regexp(r"\/v2\/type\/\{[\w]+\}"))) assert_that(paths, has_item(matches_regexp(r"\/v2\/ability\/\{[\w]+\}"))) def test_pokeapi_schema_valid_replay(schema): pokeapi_schema = build_schema_batch(pokeapi_requests, UpdateMode.REPLAY) paths = list(pokeapi_schema.paths.keys()) assert_that(paths, has_length(14)) assert_that(paths, has_item("/v2/pokemon/")) assert_that(paths, has_item(matches_regexp(r"\/v2\/pokemon\/[\w]+\/"))) assert_that(paths, has_item(matches_regexp(r"\/v2\/type\/[\w]+"))) assert_that(paths, has_item(matches_regexp(r"\/v2\/ability\/[\w]+"))) get_pets_req = RequestBuilder.from_url("http://petstore.swagger.io/v1/pets") get_one_pet_req = RequestBuilder.from_url( "http://petstore.swagger.io/v1/pets/32") pet_res = Response(bodyAsJson=None, timestamp=None, body="", statusCode=200, headers={}) @pytest.fixture def get_one_pet_exchange(): return HttpExchange(request=get_one_pet_req, response=pet_res)