def build_path(operation: Operation, ns: Namespace, schema_request_arguments: Optional[Dict[Tuple, List[str]]] = None): expected_arguments: List[str] = ( schema_request_arguments.get((ns.endpoint_for(operation), operation.value.method), []) if schema_request_arguments else [] ) expected_uri_templates = create_uri_templates(expected_arguments) try: # flask will sometimes try to quote '{' and '}' characters return unquote(ns.url_for(operation, _external=False, **expected_uri_templates)) except (BuildError, ValueError) as error: # NB: The arguments were misidentified in the previous step, use the ones supplied by the error actual_arguments = ( error.suggested.arguments # type: ignore if isinstance(error, BuildError) else expected_arguments ) if ns.identifier_type == "int": return build_path_for_integer_param(ns, operation, actual_arguments) # type: ignore else: # we are missing some URI path parameters uri_templates = create_uri_templates(actual_arguments) return unquote(ns.url_for(operation, _external=False, **uri_templates))
class TestAlias(object): def setup(self): self.graph = create_object_graph(name="example", testing=True) self.ns = Namespace(subject=Person) configure_crud(self.graph, self.ns, PERSON_MAPPINGS) configure_alias(self.graph, self.ns, PERSON_MAPPINGS) self.client = self.graph.flask.test_client() def test_url_for(self): with self.graph.app.test_request_context(): url = self.ns.url_for(Operation.Alias, person_name="foo") assert_that(url, is_(equal_to("http://localhost/api/person/foo"))) def test_swagger_path(self): with self.graph.app.test_request_context(): path = build_path(Operation.Alias, self.ns) assert_that(path, is_(equal_to("/api/person/{person_name}"))) def test_alias(self): response = self.client.get("/api/person/foo") assert_that(response.status_code, is_(equal_to(302))) assert_that(response.headers["Location"], is_(equal_to("http://localhost/api/person/1")))
def test_operation_url_for_internal(): """ Operations can resolve themselves via Flask's `url_for` and get internal URIs. """ graph = create_object_graph(name="example", testing=True) ns = Namespace(subject="foo") @graph.route(ns.collection_path, Operation.Search, ns) def search_foo(): pass with graph.app.test_request_context(): url = ns.url_for(Operation.Search, _external=False) assert_that(url, is_(equal_to("/api/foo")))
def test_operation_url_for_internal(): """ Operations can resolve themselves via Flask's `url_for` and get internal URIs. """ graph = create_object_graph(name="example", testing=True) ns = Namespace(subject="foo") @graph.route(ns.collection_path, Operation.Search, ns) def search_foo(): pass with graph.app.test_request_context(): url = ns.url_for(Operation.Search, _external=False) assert_that(url, is_(equal_to("/api/foo")))
class TestAlias: def setup(self): self.graph = create_object_graph(name="example", testing=True) self.ns = Namespace( subject=Person, ) configure_crud(self.graph, self.ns, PERSON_MAPPINGS) configure_alias(self.graph, self.ns, PERSON_MAPPINGS) self.graph.config.swagger_convention.operations.append("alias") configure_swagger(self.graph) self.client = self.graph.flask.test_client() def test_url_for(self): with self.graph.app.test_request_context(): url = self.ns.url_for(Operation.Alias, person_name="foo") assert_that(url, is_(equal_to("http://localhost/api/person/foo"))) def test_swagger_path(self): with self.graph.app.test_request_context(): path = build_path(Operation.Alias, self.ns) assert_that(path, is_(equal_to("/api/person/{person_name}"))) def test_alias(self): response = self.client.get("/api/person/foo") assert_that(response.status_code, is_(equal_to(302))) assert_that(response.headers["Location"], is_(equal_to("http://localhost/api/person/1"))) def test_swagger(self): response = self.client.get("/api/swagger") assert_that(response.status_code, is_(equal_to(200))) data = loads(response.data) alias = data["paths"]["/person/{person_name}"]["get"] assert_that( alias["responses"], has_key("302"), ) retrieve = data["paths"]["/person/{person_id}"]["get"] assert_that( retrieve["responses"], has_key("200"), )
class TestAlias: def setup(self): self.graph = create_object_graph(name="example", testing=True) self.ns = Namespace( subject=Person, ) configure_crud(self.graph, self.ns, PERSON_MAPPINGS) configure_alias(self.graph, self.ns, PERSON_MAPPINGS) self.graph.config.swagger_convention.operations.append("alias") configure_swagger(self.graph) self.client = self.graph.flask.test_client() def test_url_for(self): with self.graph.app.test_request_context(): url = self.ns.url_for(Operation.Alias, person_name="foo") assert_that(url, is_(equal_to("http://localhost/api/person/foo"))) def test_swagger_path(self): with self.graph.app.test_request_context(): path = build_path(Operation.Alias, self.ns) assert_that(path, is_(equal_to("/api/person/{person_name}"))) def test_alias(self): response = self.client.get("/api/person/foo") assert_that(response.status_code, is_(equal_to(302))) assert_that(response.headers["Location"], is_(equal_to("http://localhost/api/person/1"))) def test_swagger(self): response = self.client.get("/api/swagger") assert_that(response.status_code, is_(equal_to(200))) data = loads(response.data) alias = data["paths"]["/person/{person_name}"]["get"] assert_that( alias["responses"], has_key("302"), ) retrieve = data["paths"]["/person/{person_id}"]["get"] assert_that( retrieve["responses"], has_key("200"), )
def test_namespace_accepts_controller(): """ Namespaces may optionally contain a controller. """ graph = create_object_graph(name="example", testing=True) controller = Mock() ns = Namespace(subject="foo", controller=controller) @graph.route(ns.collection_path, Operation.Search, ns) def search_foo(): pass with graph.app.test_request_context(): url = ns.href_for(Operation.Search) assert_that(url, is_(equal_to("http://localhost/api/foo"))) url = ns.url_for(Operation.Search) assert_that(url, is_(equal_to("http://localhost/api/foo"))) assert_that(ns.controller, is_(equal_to(controller)))
def test_namespace_accepts_controller(): """ Namespaces may optionally contain a controller. """ graph = create_object_graph(name="example", testing=True) controller = Mock() ns = Namespace(subject="foo", controller=controller) @graph.route(ns.collection_path, Operation.Search, ns) def search_foo(): pass with graph.app.test_request_context(): url = ns.href_for(Operation.Search) assert_that(url, is_(equal_to("http://localhost/api/foo"))) url = ns.url_for(Operation.Search) assert_that(url, is_(equal_to("http://localhost/api/foo"))) assert_that(ns.controller, is_(equal_to(controller)))
class TestUpload: def setup(self): self.graph = create_object_graph(name="example", testing=True) self.ns = Namespace(subject="file") self.relation_ns = Namespace(subject=Person, object_="file") self.controller = FileController() UPLOAD_MAPPINGS = { Operation.Upload: EndpointDefinition( func=self.controller.upload, request_schema=FileExtraSchema(), ), } UPLOAD_FOR_MAPPINGS = { Operation.UploadFor: EndpointDefinition( func=self.controller.upload_for_person, request_schema=FileExtraSchema(), response_schema=FileResponseSchema(), ), } configure_upload(self.graph, self.ns, UPLOAD_MAPPINGS) configure_upload(self.graph, self.relation_ns, UPLOAD_FOR_MAPPINGS) configure_swagger(self.graph) self.client = self.graph.flask.test_client() def test_upload_url_for(self): with self.graph.app.test_request_context(): url = self.ns.url_for(Operation.Upload) assert_that(url, is_(equal_to("http://localhost/api/file"))) def test_upload_for_url_for(self): with self.graph.app.test_request_context(): url = self.relation_ns.url_for(Operation.UploadFor, person_id=1) assert_that(url, is_(equal_to("http://localhost/api/person/1/file"))) def test_upload_swagger_path(self): with self.graph.app.test_request_context(): path = build_path(Operation.Upload, self.ns) assert_that(path, is_(equal_to("/api/file"))) def test_upload_for_swagger_path(self): with self.graph.app.test_request_context(): path = build_path(Operation.UploadFor, self.relation_ns) assert_that(path, is_(equal_to("/api/person/{person_id}/file"))) def test_swagger(self): response = self.client.get("/api/swagger") assert_that(response.status_code, is_(equal_to(200))) data = loads(response.data) upload = data["paths"]["/file"]["post"] upload_for = data["paths"]["/person/{person_id}/file"]["post"] # both endpoints return form data assert_that( upload["consumes"], contains("multipart/form-data"), ) assert_that( upload_for["consumes"], contains("multipart/form-data"), ) # one endpoint gets an extra query string parameter (and the other doesn't) assert_that( upload["parameters"], has_item( has_entries(name="extra"), ), ) assert_that( upload_for["parameters"], has_item( is_not(has_entries(name="extra")), ), ) # one endpoint gets a custom response type (and the other doesn't) assert_that( upload["responses"], all_of( has_key("204"), is_not(has_key("200")), has_entry("204", is_not(has_key("schema"))), ), ) assert_that( upload_for["responses"], all_of( has_key("200"), is_not(has_key("204")), has_entry("200", has_entry("schema", has_entry("$ref", "#/definitions/FileResponse"))), ), ) def test_upload(self): response = self.client.post( "/api/file", data=dict( file=(BytesIO(b"Hello World\n"), "hello.txt"), ), ) assert_that(response.status_code, is_(equal_to(204))) assert_that(self.controller.calls, contains( has_entries( files=contains(contains("file", anything(), "hello.txt")), extra="something", ), )) def test_upload_for(self): person_id = uuid4() response = self.client.post( "/api/person/{}/file".format(person_id), data=dict( file=(BytesIO(b"Hello World\n"), "hello.txt"), ), ) assert_that(response.status_code, is_(equal_to(200))) response_data = loads(response.get_data().decode("utf-8")) assert_that(response_data, is_(equal_to(dict( id=str(person_id), )))) assert_that(self.controller.calls, contains( has_entries( files=contains(contains("file", anything(), "hello.txt")), extra="something", person_id=person_id, ), )) def test_upload_multipart(self): response = self.client.post( "/api/file", data=dict( file=(BytesIO(b"Hello World\n"), "hello.txt"), extra="special", ), ) assert_that(response.status_code, is_(equal_to(204))) assert_that(self.controller.calls, contains( has_entries( files=contains(contains("file", anything(), "hello.txt")), extra="special", ), ))
class TestQuery(object): def setup(self): # override configuration to use "query" operations for swagger def loader(metadata): return dict( swagger_convention=dict( # default behavior appends this list to defaults; use a tuple to override operations=["query"], version="v1", ), ) self.graph = create_object_graph(name="example", testing=True, loader=loader) self.graph.use("swagger_convention") self.ns = Namespace(subject="foo") make_query(self.graph, self.ns, QueryStringSchema(), QueryResultSchema()) self.client = self.graph.flask.test_client() def test_url_for(self): """ The operation knowns how to resolve a URI for this query. """ with self.graph.flask.test_request_context(): assert_that(self.ns.url_for(Operation.Query), is_(equal_to("http://localhost/api/v1/foo/get"))) def test_query(self): """ The query can take advantage of boilerplate encoding/decoding. """ uri = "/api/v1/foo/get" query_string = { "value": "bar", } response = self.client.get(uri, query_string=query_string) assert_that(response.status_code, is_(equal_to(200))) assert_that(loads(response.get_data().decode("utf-8")), is_(equal_to({ "result": True, "value": "bar", }))) def test_swagger(self): """ Swagger definitions including this operation. """ response = self.client.get("/api/v1/swagger") assert_that(response.status_code, is_(equal_to(200))) swagger = loads(response.get_data().decode("utf-8")) assert_that(swagger["paths"], is_(equal_to({ "/foo/get": { "get": { "tags": ["foo"], "responses": { "default": { "description": "An error occcurred", "schema": { "$ref": "#/definitions/Error", } }, "200": { "description": "My doc string", "schema": { "$ref": "#/definitions/QueryResult", } } }, "parameters": [ { "in": "header", "name": "X-Response-Skip-Null", "required": False, "type": "string", }, { "required": False, "type": "string", "name": "value", "in": "query", }, ], "operationId": "query", } } })))
class TestCommand: def setup(self): # override configuration to use "query" operations for swagger def loader(metadata): return dict( swagger_convention=dict( # default behavior appends this list to defaults; use a tuple to override operations=["command"], version="v1", ), ) self.graph = create_object_graph(name="example", testing=True, loader=loader) self.graph.use("swagger_convention") self.ns = Namespace(subject="foo") make_command(self.graph, self.ns, CommandArgumentSchema(), CommandResultSchema()) self.client = self.graph.flask.test_client() def test_url_for(self): """ The operation knowns how to resolve a URI for this command. """ with self.graph.flask.test_request_context(): assert_that(self.ns.url_for(Operation.Command), is_(equal_to("http://localhost/api/v1/foo/do"))) def test_command(self): """ The command can take advantage of boilerplate encoding/decoding. """ uri = "/api/v1/foo/do" request_data = { "value": "bar", } response = self.client.post(uri, data=dumps(request_data)) assert_that(response.status_code, is_(equal_to(200))) assert_that(loads(response.get_data().decode("utf-8")), is_(equal_to({ "result": True, "value": "bar", }))) def test_swagger(self): """ Swagger definitions including this operation. """ response = self.client.get("/api/v1/swagger") assert_that(response.status_code, is_(equal_to(200))) swagger = loads(response.get_data().decode("utf-8")) assert_that(swagger["paths"], is_(equal_to({ "/foo/do": { "post": { "tags": ["foo"], "responses": { "default": { "description": "An error occurred", "schema": { "$ref": "#/definitions/Error", } }, "200": { "description": "My doc string", "schema": { "$ref": "#/definitions/CommandResult", } } }, "parameters": [ { "in": "header", "name": "X-Response-Skip-Null", "required": False, "type": "string", }, { "schema": { "$ref": "#/definitions/CommandArgument", }, "name": "body", "in": "body", }, ], "operationId": "command", } } })))
class TestCreateCollection: def setup(self): self.graph = create_object_graph(name="example", testing=True) self.ns = Namespace(subject="foo") configure_crud(self.graph, self.ns, FOO_MAPPINGS) configure_swagger(self.graph) self.client = self.graph.flask.test_client() def test_create_collection_url(self): with self.graph.app.test_request_context(): url = self.ns.url_for(Operation.CreateCollection) assert_that(url, is_(equal_to("http://localhost/api/foo"))) def test_swagger_path(self): with self.graph.app.test_request_context(): path = build_path(Operation.CreateCollection, self.ns) assert_that(path, is_(equal_to("/api/foo"))) def test_swagger(self): response = self.client.get("/api/swagger") assert_that(response.status_code, is_(equal_to(200))) data = loads(response.data)["paths"]["/foo"]["post"] assert_that( data["parameters"], has_items( has_entry( "in", "body", ), has_entry( "schema", has_entry( "$ref", "#/definitions/FooRequest", ), ), ), ) assert_that( data["responses"], all_of( has_key("200"), is_not(has_key("204")), has_entry( "200", has_entry( "schema", has_entry( "$ref", "#/definitions/FooList", ), ), ), ), ) def test_create_collection(self): text = "Some text..." response = self.client.post( "/api/foo", data=dumps({"text": text}), ) assert_that(response.status_code, is_(equal_to(200))) assert_that( loads(response.data), is_( equal_to({ "count": 1, "offset": 0, "limit": 20, "items": [{ "text": text }], "_links": { "self": { "href": "http://localhost/api/foo?offset=0&limit=20", }, }, })))
class TestUpload: def setup(self): self.graph = create_object_graph(name="example", testing=True) self.ns = Namespace(subject="file") self.relation_ns = Namespace(subject=Person, object_="file") self.controller = FileController() UPLOAD_MAPPINGS = { Operation.Upload: EndpointDefinition( func=self.controller.upload, request_schema=FileExtraSchema(), ), } UPLOAD_FOR_MAPPINGS = { Operation.UploadFor: EndpointDefinition( func=self.controller.upload_for_person, request_schema=FileExtraSchema(), response_schema=FileResponseSchema(), ), } configure_upload(self.graph, self.ns, UPLOAD_MAPPINGS) configure_upload(self.graph, self.relation_ns, UPLOAD_FOR_MAPPINGS) configure_swagger(self.graph) self.client = self.graph.flask.test_client() def test_upload_url_for(self): with self.graph.app.test_request_context(): url = self.ns.url_for(Operation.Upload) assert_that(url, is_(equal_to("http://localhost/api/file"))) def test_upload_for_url_for(self): with self.graph.app.test_request_context(): url = self.relation_ns.url_for(Operation.UploadFor, person_id=1) assert_that(url, is_(equal_to("http://localhost/api/person/1/file"))) def test_upload_swagger_path(self): with self.graph.app.test_request_context(): path = build_path(Operation.Upload, self.ns) assert_that(path, is_(equal_to("/api/file"))) def test_upload_for_swagger_path(self): with self.graph.app.test_request_context(): path = build_path(Operation.UploadFor, self.relation_ns) assert_that(path, is_(equal_to("/api/person/{person_id}/file"))) def test_swagger(self): response = self.client.get("/api/swagger") assert_that(response.status_code, is_(equal_to(200))) data = loads(response.data) upload = data["paths"]["/file"]["post"] upload_for = data["paths"]["/person/{person_id}/file"]["post"] # both endpoints return form data assert_that( upload["consumes"], contains("multipart/form-data"), ) assert_that( upload_for["consumes"], contains("multipart/form-data"), ) # one endpoint gets an extra query string parameter (and the other doesn't) assert_that( upload["parameters"], has_item( has_entries(name="extra"), ), ) assert_that( upload_for["parameters"], has_item( is_not(has_entries(name="extra")), ), ) # one endpoint gets a custom response type (and the other doesn't) assert_that( upload["responses"], all_of( has_key("204"), is_not(has_key("200")), has_entry("204", is_not(has_key("schema"))), ), ) assert_that( upload_for["responses"], all_of( has_key("200"), is_not(has_key("204")), has_entry("200", has_entry("schema", has_entry("$ref", "#/definitions/FileResponse"))), ), ) def test_upload(self): response = self.client.post( "/api/file", data=dict( file=(BytesIO(b"Hello World\n"), "hello.txt"), ), ) assert_that(response.status_code, is_(equal_to(204))) assert_that(self.controller.calls, contains( has_entries( files=contains(contains("file", anything(), "hello.txt")), extra="something", ), )) def test_upload_for(self): person_id = uuid4() response = self.client.post( "/api/person/{}/file".format(person_id), data=dict( file=(BytesIO(b"Hello World\n"), "hello.txt"), ), ) assert_that(response.status_code, is_(equal_to(200))) response_data = loads(response.get_data().decode("utf-8")) assert_that(response_data, is_(equal_to(dict( id=str(person_id), )))) assert_that(self.controller.calls, contains( has_entries( files=contains(contains("file", anything(), "hello.txt")), extra="something", person_id=person_id, ), )) def test_upload_multipart(self): response = self.client.post( "/api/file", data=dict( file=(BytesIO(b"Hello World\n"), "hello.txt"), extra="special", ), ) assert_that(response.status_code, is_(equal_to(204))) assert_that(self.controller.calls, contains( has_entries( files=contains(contains("file", anything(), "hello.txt")), extra="special", ), ))
class TestCreateCollection: def setup(self): self.graph = create_object_graph(name="example", testing=True) self.ns = Namespace(subject="foo") configure_crud(self.graph, self.ns, FOO_MAPPINGS) configure_swagger(self.graph) self.client = self.graph.flask.test_client() def test_create_collection_url(self): with self.graph.app.test_request_context(): url = self.ns.url_for(Operation.CreateCollection) assert_that(url, is_(equal_to("http://localhost/api/foo"))) def test_swagger_path(self): with self.graph.app.test_request_context(): path = build_path(Operation.CreateCollection, self.ns) assert_that(path, is_(equal_to("/api/foo"))) def test_swagger(self): response = self.client.get("/api/swagger") assert_that(response.status_code, is_(equal_to(200))) data = loads(response.data)["paths"]["/foo"]["post"] assert_that( data["parameters"], has_items( has_entry( "in", "body", ), has_entry( "schema", has_entry( "$ref", "#/definitions/FooRequest", ), ), ), ) assert_that( data["responses"], all_of( has_key("200"), is_not(has_key("204")), has_entry( "200", has_entry( "schema", has_entry( "$ref", "#/definitions/FooList", ), ), ), ), ) def test_create_collection(self): text = "Some text..." response = self.client.post( "/api/foo", data=dumps({"text": text}), ) assert_that(response.status_code, is_(equal_to(200))) assert_that(loads(response.data), is_(equal_to({ "count": 1, "offset": 0, "limit": 20, "items": [{"text": text}], "_links": { "self": { "href": "http://localhost/api/foo?offset=0&limit=20", }, }, })))