Esempio n. 1
0
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))
Esempio n. 2
0
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")))
Esempio n. 3
0
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")))
Esempio n. 5
0
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"),
        )
Esempio n. 7
0
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",
            ),
        ))
Esempio n. 10
0
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",
                }
            }
        })))
Esempio n. 12
0
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",
                        },
                    },
                })))
Esempio n. 13
0
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",
                },
            },
        })))