예제 #1
0
def test_backlinks_via_components_ref():
    """
    We should be able to specify a backlink by using a $ref to refer to
    a shared backlink component.
    """
    doc_uri = fixture_uri("backlinks-components-ref.yaml")

    apigraph = APIGraph(doc_uri)
    assert apigraph.docs.keys() == {doc_uri}

    expected_nodes = [
        NodeKey(doc_uri, "/users", HttpMethod.POST),
        NodeKey(doc_uri, "/1.0/users/{username}", HttpMethod.GET),
        NodeKey(doc_uri, "/2.0/users/{username}", HttpMethod.GET),
    ]
    expected_edges = [
        (
            expected_nodes[0],
            expected_nodes[1],
            (None, "201"),
            {
                "response_id": "201",
                "chain_id": None,
                "detail": LinkDetail(
                    link_type=LinkType.BACKLINK,
                    name="CreateUser",
                    description="Create a new user that matches the username of current request url segment",
                    parameters={},
                    requestBody=None,
                    requestBodyParameters={"/username": "******"},
                ),
            },
        ),
        (
            expected_nodes[0],
            expected_nodes[2],
            (None, "201"),
            {
                "response_id": "201",
                "chain_id": None,
                "detail": LinkDetail(
                    link_type=LinkType.BACKLINK,
                    name="CreateUser",
                    description="Create a new user that matches the username of current request url segment",
                    parameters={},
                    requestBody=None,
                    requestBodyParameters={"/username": "******"},
                ),
            },
        ),
    ]
    assert [node for node in apigraph.graph.nodes] == expected_nodes
    assert [
        edge for edge in apigraph.graph.edges(data=True, keys=True)
    ] == expected_edges
예제 #2
0
def test_links_multiple_chains(httpx_mock):
    doc_uri = fixture_uri("links-with-multiple-chain-id.yaml")

    apigraph = APIGraph(doc_uri)
    assert apigraph.docs.keys() == {doc_uri}

    # sorted
    expected_nodes = [
        NodeKey(doc_uri, "/1.0/users/{username}", HttpMethod.GET),
        NodeKey(doc_uri, "/2.0/repositories/{username}", HttpMethod.GET),
        NodeKey(doc_uri, "/2.0/users/{username}", HttpMethod.GET),
    ]
    expected_edges = [
        (
            expected_nodes[0],
            expected_nodes[1],
            ("v1", "200"),
            {
                "response_id": "200",
                "chain_id": "v1",
                "detail": LinkDetail(
                    link_type=LinkType.LINK,
                    name="userRepositories",
                    description="Get list of repositories",
                    parameters={"username": "******"},
                    requestBody=None,
                    requestBodyParameters={},
                ),
            },
        ),
        (
            expected_nodes[2],
            expected_nodes[1],
            ("default", "200"),
            {
                "response_id": "200",
                "chain_id": "default",
                "detail": LinkDetail(
                    link_type=LinkType.LINK,
                    name="userRepositories",
                    description="Get list of repositories",
                    parameters={"username": "******"},
                    requestBody=None,
                    requestBodyParameters={},
                ),
            },
        ),
    ]
    assert sorted([node for node in apigraph.graph.nodes]) == expected_nodes
    assert [
        edge for edge in apigraph.graph.edges(data=True, keys=True)
    ] == expected_edges
예제 #3
0
def test_backlinks_request_body_params():
    doc_uri = fixture_uri("backlinks-request-body-params.yaml")

    apigraph = APIGraph(doc_uri)
    assert apigraph.docs.keys() == {doc_uri}

    expected_nodes = [
        NodeKey(doc_uri, "/users", HttpMethod.POST),
        NodeKey(doc_uri, "/pets", HttpMethod.POST),
    ]
    expected_edges = [
        (
            expected_nodes[0],
            expected_nodes[1],
            (None, "201"),
            {
                "response_id": "201",
                "chain_id": None,
                "detail": LinkDetail(
                    link_type=LinkType.BACKLINK,
                    name="New User",
                    description="",
                    parameters={},
                    requestBody=None,
                    requestBodyParameters={"/owner": "$response.body#/username"},
                ),
            },
        ),
    ]
    assert [node for node in apigraph.graph.nodes] == expected_nodes
    assert [
        edge for edge in apigraph.graph.edges(data=True, keys=True)
    ] == expected_edges
예제 #4
0
def test_links_request_body(httpx_mock):
    doc_uri = fixture_uri("links-request-body.yaml")

    apigraph = APIGraph(doc_uri)
    assert apigraph.docs.keys() == {doc_uri}

    expected_nodes = [
        NodeKey(doc_uri, "/users", HttpMethod.POST),
        NodeKey(doc_uri, "/pets/{id}/add-owner", HttpMethod.POST),
    ]
    expected_edges = [
        (
            expected_nodes[0],
            expected_nodes[1],
            (None, "201"),
            {
                "response_id": "201",
                "chain_id": None,
                "detail": LinkDetail(
                    link_type=LinkType.LINK,
                    name="Add Pet",
                    description="",
                    parameters={},
                    requestBody="$response.body",
                    requestBodyParameters={},
                ),
            },
        ),
    ]
    assert [node for node in apigraph.graph.nodes] == expected_nodes
    assert [
        edge for edge in apigraph.graph.edges(data=True, keys=True)
    ] == expected_edges
예제 #5
0
def test_cross_doc_links_circular_ref(httpx_mock):
    """
    Test that we don't get stuck in infinite loop when resolving doc refs.

    NOTE:
    cross-doc-links.yaml and cross-doc-links-circular-ref.yaml together
    form a circular dependency in that both docs contain links to each other.
    This is via a mutual backlink and a link, since both edges are directed
    identically this should form a redundant edge. So we have a circular ref
    between docs, but not a circular dependency in the request graph.

    NOTE:
    since we are re-using cross-doc-links.yaml but with a different `fixture_uri`
    pre-parse substitution, this test relies on `clear_cache` auto fixture having
    `function` scope...
    """
    doc_uri = "https://fakeurl/cross-doc-links.yaml"
    other_doc_uri = fixture_uri("cross-doc-circular-ref.yaml")

    raw_doc = str_doc_with_substitutions(
        "tests/fixtures/cross-doc-links.yaml", {"fixture_uri": other_doc_uri},
    )
    httpx_mock.add_response(url=doc_uri, data=raw_doc)

    apigraph = APIGraph(doc_uri)
    assert apigraph.docs.keys() == {doc_uri, other_doc_uri}

    expected_nodes = [
        NodeKey(doc_uri, "/2.0/users", HttpMethod.POST),
        NodeKey(other_doc_uri, "/2.0/users/{username}", HttpMethod.GET),
    ]
    # the backlink+link edges in this case are redundant
    # we expect apigraph to take the backlink over the link
    expected_edges = [
        (
            expected_nodes[0],
            expected_nodes[1],
            (None, "201"),
            {
                "response_id": "201",
                "chain_id": None,
                "detail": LinkDetail(
                    link_type=LinkType.BACKLINK,
                    name="createUser",
                    description="",
                    parameters={},
                    requestBody=None,
                    requestBodyParameters={"/username": "******"},
                ),
            },
        ),
    ]
    assert [node for node in apigraph.graph.nodes] == expected_nodes
    assert [
        edge for edge in apigraph.graph.edges(data=True, keys=True)
    ] == expected_edges
예제 #6
0
def test_links(fixture, chain_id):
    """
    NOTE: these fixtures use within-doc $refs, so that is tested too
    links.yaml and links-with-chain-id.yaml use `operationId` while
    links-local-operationref.yaml uses a relative (local) `operationRef`

    NOTE: these links all use `parameters` and not `requestBody` or
    `x-apigraph-requestBodyParameters`
    """
    doc_uri = fixture_uri(fixture)

    apigraph = APIGraph(doc_uri)
    assert apigraph.docs.keys() == {doc_uri}

    expected_nodes = [
        NodeKey(doc_uri, "/2.0/users/{username}", HttpMethod.GET),
        NodeKey(doc_uri, "/2.0/repositories/{username}", HttpMethod.GET),
    ]
    expected_edges = [
        (
            expected_nodes[0],
            expected_nodes[1],
            (chain_id, "200"),
            {
                "response_id": "200",
                "chain_id": chain_id,
                "detail": LinkDetail(
                    link_type=LinkType.LINK,
                    name="userRepositories",
                    description="Get list of repositories",
                    parameters={"username": "******"},
                    requestBody=None,
                    requestBodyParameters={},
                ),
            },
        ),
    ]
    assert [node for node in apigraph.graph.nodes] == expected_nodes
    assert [
        edge for edge in apigraph.graph.edges(data=True, keys=True)
    ] == expected_edges
예제 #7
0
def test_link_backlink_same_chain_consolidation():
    """
    in case of redundant edge key defined as both link and backlink
    we should store a single edge with detail from the backlink
    (we expect backlinks to be more explicit as they are an apigraph
    extension to OpenAPI)
    """
    doc_uri = fixture_uri("backlinks-with-links-same-chain-id.yaml")

    apigraph = APIGraph(doc_uri)
    assert apigraph.docs.keys() == {doc_uri}

    expected_nodes = [
        NodeKey(doc_uri, "/2.0/users/{username}", HttpMethod.GET),
        NodeKey(doc_uri, "/2.0/repositories/{username}", HttpMethod.GET),
    ]
    expected_edges = [
        (
            expected_nodes[0],
            expected_nodes[1],
            ("default", "200"),
            {
                "response_id": "200",
                "chain_id": "default",
                "detail": LinkDetail(
                    link_type=LinkType.BACKLINK,
                    name="Get User by Username",
                    description="",
                    parameters={"username": "******"},
                    requestBody=None,
                    requestBodyParameters={},
                ),
            },
        ),
    ]
    assert [node for node in apigraph.graph.nodes] == expected_nodes
    assert [
        edge for edge in apigraph.graph.edges(data=True, keys=True)
    ] == expected_edges
예제 #8
0
def test_backlinks(fixture, chain_id):
    """
    NOTE: these links all use `parameters` and not `requestBody` or
    `x-apigraph-requestBodyParameters`
    """
    doc_uri = fixture_uri(fixture)

    apigraph = APIGraph(doc_uri)
    assert apigraph.docs.keys() == {doc_uri}

    expected_nodes = [
        NodeKey(doc_uri, "/2.0/users/{username}", HttpMethod.GET),
        NodeKey(doc_uri, "/2.0/repositories/{username}", HttpMethod.GET),
    ]
    expected_edges = [
        (
            expected_nodes[0],
            expected_nodes[1],
            (chain_id, "200"),
            {
                "response_id": "200",
                "chain_id": chain_id,
                "detail": LinkDetail(
                    link_type=LinkType.BACKLINK,
                    name="Get User by Username",
                    description="",
                    parameters={"username": "******"},
                    requestBody=None,
                    requestBodyParameters={},
                ),
            },
        ),
    ]
    assert [node for node in apigraph.graph.nodes] == expected_nodes
    assert [
        edge for edge in apigraph.graph.edges(data=True, keys=True)
    ] == expected_edges
예제 #9
0
def test_chain_for_node(traverse_anonymous):
    """
    The test fixture contains three dependency chains, two of which end
    at `createUser` and `createUserv1` respectively and both beginning at
    `getRepository`.

    The third chain is the 'anonymous' link (no chainId specified) which
    extends the "default" chain to begin at /invite.

    We request dependencies of `getRepositoriesByOwner`, which is not at the
    end of either chain (is followed by `getRepository`) and check that we
    only select up to the requested node and no further.
    """
    doc_uri = fixture_uri("dependencies.yaml")

    apigraph = APIGraph(doc_uri)
    assert apigraph.docs.keys() == {doc_uri}

    default_deps = apigraph.chain_for_node(
        node_key=NodeKey(doc_uri, "/2.0/repositories/{username}", "get"),
        chain_id="default",
        traverse_anonymous=traverse_anonymous,
    )
    v1_deps = apigraph.chain_for_node(
        node_key=NodeKey(doc_uri, "/2.0/repositories/{username}", "get"),
        chain_id="v1",
        traverse_anonymous=traverse_anonymous,
    )

    # dependencies from the "default" chain
    # NOTE: subsequent op `/2.0/repositories/{username}/{slug}` is not included
    # (nodes here manually sorted in url order for test case)
    default_expected_nodes = [
        NodeKey(doc_uri, "/2.0/repositories/{username}", "get"),
        NodeKey(doc_uri, "/2.0/users", "post"),
        NodeKey(doc_uri, "/2.0/users/{username}", "get"),
    ]
    # the "invite" predecessor has no chainId and is only included when
    # `traverse_anonymous=True`
    if traverse_anonymous:
        default_expected_nodes.append(NodeKey(
            doc_uri,
            "/invite",
            "post",
        ))
    assert sorted([node
                   for node in default_deps.nodes]) == default_expected_nodes

    # (edges here manually sorted in from-node??? order for test case)
    default_expected_edges = [
        (
            default_expected_nodes[1],
            default_expected_nodes[2],
            ("default", "201"),
            {
                "response_id":
                "201",
                "chain_id":
                "default",
                "detail":
                LinkDetail(
                    link_type=LinkType.LINK,
                    name="userByUsername",
                    description="",
                    parameters={"username": "******"},
                    requestBody=None,
                    requestBodyParameters={},
                ),
            },
        ),
        (
            default_expected_nodes[2],
            default_expected_nodes[0],
            ("default", "200"),
            {
                "response_id":
                "200",
                "chain_id":
                "default",
                "detail":
                LinkDetail(
                    link_type=LinkType.LINK,
                    name="userRepositories",
                    description="Get list of repositories",
                    parameters={"username": "******"},
                    requestBody=None,
                    requestBodyParameters={},
                ),
            },
        ),
    ]
    # the "invite" predecessor has no chainId and is only included when
    # `traverse_anonymous=True`
    if traverse_anonymous:
        default_expected_edges.append((
            default_expected_nodes[3],
            default_expected_nodes[1],
            (None, "201"),
            {
                "response_id":
                "201",
                "chain_id":
                None,
                "detail":
                LinkDetail(
                    link_type=LinkType.BACKLINK,
                    name="Redeem Invite",
                    description="Create a user by redeeming an invite id+token",
                    parameters={"invite-id": "$response.body#/id"},
                    requestBody=None,
                    requestBodyParameters={
                        "/invite-token": "$response.body#/token"
                    },
                ),
            },
        ), )
    assert (sorted([edge for edge in default_deps.edges(data=True, keys=True)
                    ]) == default_expected_edges)

    # dependencies from the "v1" chain
    # (this chain is not extended by any anonymous links in the document)
    v1_expected_nodes = [
        NodeKey(doc_uri, "/1.0/users", "post"),
        NodeKey(doc_uri, "/1.0/users/{username}", "get"),
        NodeKey(doc_uri, "/2.0/repositories/{username}", "get"),
    ]
    v1_expected_edges = [
        (
            v1_expected_nodes[0],
            v1_expected_nodes[1],
            ("v1", "201"),
            {
                "response_id":
                "201",
                "chain_id":
                "v1",
                "detail":
                LinkDetail(
                    link_type=LinkType.LINK,
                    name="userByUsername",
                    description="",
                    parameters={"username": "******"},
                    requestBody=None,
                    requestBodyParameters={},
                ),
            },
        ),
        (
            v1_expected_nodes[1],
            v1_expected_nodes[2],
            ("v1", "200"),
            {
                "response_id":
                "200",
                "chain_id":
                "v1",
                "detail":
                LinkDetail(
                    link_type=LinkType.LINK,
                    name="userRepositories",
                    description="Get list of repositories",
                    parameters={"username": "******"},
                    requestBody=None,
                    requestBodyParameters={},
                ),
            },
        ),
    ]
    assert sorted([node for node in v1_deps.nodes]) == v1_expected_nodes
    assert (sorted([edge for edge in v1_deps.edges(data=True, keys=True)
                    ]) == v1_expected_edges)
예제 #10
0
def test_cross_doc_links(httpx_mock):
    """
    NOTE: cross-doc-links.yaml links via `operationRef` URI to links.yaml
    So between this and `test_links` both `operationRef` and `operationId`
    links are tested
    """
    doc_uri = "https://fakeurl/cross-doc-links.yaml"
    other_doc_uri = fixture_uri("links.yaml")

    raw_doc = str_doc_with_substitutions(
        "tests/fixtures/cross-doc-links.yaml", {"fixture_uri": other_doc_uri},
    )
    httpx_mock.add_response(url=doc_uri, data=raw_doc)

    apigraph = APIGraph(doc_uri)
    assert apigraph.docs.keys() == {doc_uri, other_doc_uri}

    expected_nodes = [
        NodeKey(doc_uri, "/2.0/users", HttpMethod.POST),
        NodeKey(other_doc_uri, "/2.0/users/{username}", HttpMethod.GET),
        NodeKey(other_doc_uri, "/2.0/repositories/{username}", HttpMethod.GET),
    ]
    expected_edges = [
        (
            expected_nodes[0],
            expected_nodes[1],
            (None, "201"),
            {
                "response_id": "201",
                "chain_id": None,
                "detail": LinkDetail(
                    link_type=LinkType.LINK,
                    name="userByUsername",
                    description="",
                    parameters={"username": "******"},
                    requestBody=None,
                    requestBodyParameters={},
                ),
            },
        ),
        (
            expected_nodes[1],
            expected_nodes[2],
            (None, "200"),
            {
                "response_id": "200",
                "chain_id": None,
                "detail": LinkDetail(
                    link_type=LinkType.LINK,
                    name="userRepositories",
                    description="Get list of repositories",
                    parameters={"username": "******"},
                    requestBody=None,
                    requestBodyParameters={},
                ),
            },
        ),
    ]
    assert [node for node in apigraph.graph.nodes] == expected_nodes
    assert [
        edge for edge in apigraph.graph.edges(data=True, keys=True)
    ] == expected_edges