def test_not_visited(graph_access: GraphAccess) -> None: graph_access.node("1") graph_access.node("3") not_visited = list(graph_access.not_visited_nodes()) assert len(not_visited) == 2 assert not_visited[0]["hash"] == "0546994698f57ed4a7a4463b0353397ebe4acc7196b9e9ca1dd7bc0b72245be6" assert not_visited[1]["hash"] == "0fead7dd5aff45049878e4c1b108b78c5fab594ac503eaba2199b71725241fce"
def test_reassign_root(person_model: Model) -> None: max_m = {"id": "max", "kind": "Person", "name": "Max"} builder = GraphBuilder(person_model) builder.add_from_json({"id": "should_be_root", "reported": {"kind": "graph_root"}}) builder.add_from_json({"id": "2", "reported": max_m}) builder.add_from_json({"id": "3", "reported": max_m}) builder.add_from_json({"from": "should_be_root", "to": "2"}) builder.add_from_json({"from": "should_be_root", "to": "3"}) builder.check_complete() access = GraphAccess(builder.graph) assert access.root() == "root" assert set(access.successors("root", EdgeType.default)) == {"2", "3"}
def test_predecessors() -> None: graph = GraphAccess(multi_cloud_graph("account")) child = "child_parent_region_account_cloud_gcp_2_europe_1_0" parent = "parent_region_account_cloud_gcp_2_europe_1" region = "region_account_cloud_gcp_2_europe" # default: region -> parent -> child assert list(graph.predecessors(child, EdgeType.default)) == [parent] assert list(graph.predecessors(parent, EdgeType.default)) == [region] assert child in list(graph.successors(parent, EdgeType.default)) assert parent in list(graph.successors(region, EdgeType.default)) # delete: child -> parent -> region assert list(graph.successors(child, EdgeType.delete)) == [parent] assert list(graph.successors(parent, EdgeType.delete)) == [region] assert parent in list(graph.successors(child, EdgeType.delete)) assert region in list(graph.successors(parent, EdgeType.delete))
def test_content_hash() -> None: # the order of properties should not matter for the content hash g = MultiDiGraph() g.add_node("1", reported={"a": {"a": 1, "c": 2, "b": 3}, "c": 2, "b": 3, "d": "foo", "z": True, "kind": "a"}) g.add_node("2", reported={"z": True, "c": 2, "b": 3, "a": {"b": 3, "c": 2, "a": 1}, "d": "foo", "kind": "a"}) access = GraphAccess(g) sha1 = node(access, "1")["hash"] # type: ignore sha2 = node(access, "2")["hash"] # type: ignore assert sha1 == sha2
def test_ancestor_of() -> None: nid1 = "child_parent_region_account_cloud_gcp_1_europe_1_0" acc1 = "account_cloud_gcp_1" acc2 = "account_cloud_gcp_2" g = multi_cloud_graph("account") graph = GraphAccess(g) assert graph.ancestor_of(nid1, EdgeType.default, "root") is not None assert graph.ancestor_of(nid1, EdgeType.delete, "root") is None assert graph.ancestor_of(nid1, EdgeType.default, "foo") is None assert graph.ancestor_of(nid1, EdgeType.default, "foo") is None assert graph.ancestor_of(nid1, EdgeType.default, "account")["id"] == acc1 # type: ignore # add another "shorter" edge from acc2 -> nid1, so it is shorter that from acc1 -> nid1 key = GraphAccess.edge_key(acc2, nid1, EdgeType.default) g.add_edge(acc2, nid1, key, edge_type=EdgeType.default) assert graph.ancestor_of(nid1, EdgeType.default, "account")["id"] == acc2 # type: ignore
def test_sub_graphs_from_graph_account() -> None: graph = multi_cloud_graph("account") merges, parent, graph_it = GraphAccess.merge_graphs(graph) graphs = list(graph_it) assert len(graphs) == 6 for root, succ in graphs: assert len(parent.nodes) == 9 assert succ.root().startswith("account") assert len(list(succ.not_visited_nodes())) == 78 assert len(succ.nodes) == 79 # make sure there is no node from another subgraph for node_id in succ.not_visited_nodes(): assert succ.root() in node_id["id"] assert len(list(succ.not_visited_edges(EdgeType.default))) == 78 assert len(list(succ.not_visited_edges(EdgeType.delete))) == 78
async def test_query_graph(filled_graph_db: ArangoGraphDB, foo_model: Model) -> None: graph = await load_graph(filled_graph_db, foo_model) assert len(graph.edges) == 110 assert len(graph.nodes.values()) == 111 # filter data and tag result, and then traverse to the end of the graph in both directions around_me = Query.by( "foo", P("identifier") == "9").tag("red").traverse_inout(start=0) graph = await filled_graph_db.search_graph( QueryModel(around_me.on_section("reported"), foo_model)) assert len({x for x in graph.nodes}) == 12 assert GraphAccess.root_id(graph) == "sub_root" assert list(graph.successors("sub_root"))[0] == "9" assert set(graph.successors("9")) == {f"9_{x}" for x in range(0, 10)} for from_node, to_node, data in graph.edges.data(True): assert from_node == "9" or to_node == "9" assert data == {"edge_type": "default"} for node_id, node in graph.nodes.data(True): if node_id == "9": assert node["metadata"]["query_tag"] == "red" else: assert "tag" not in node["metadata"] async def assert_result(query: str, nodes: int, edges: int) -> None: q = parse_query(query) graph = await filled_graph_db.search_graph(QueryModel(q, foo_model)) assert len(graph.nodes) == nodes assert len(graph.edges) == edges await assert_result( "is(foo) and reported.identifier==9 <-delete[0:]default->", 11, 20) await assert_result( "is(foo) and reported.identifier==9 <-default[0:]delete->", 4, 3) await assert_result("is(foo) and reported.identifier==9 <-default[0:]->", 14, 13) await assert_result("is(foo) and reported.identifier==9 <-delete[0:]->", 11, 10) await assert_result("is(foo) and reported.identifier==9 -default[0:]->", 11, 10) await assert_result("is(foo) and reported.identifier==9 <-delete[0:]-", 11, 10) await assert_result("is(foo) and reported.identifier==9 <-default[0:]-", 4, 3) await assert_result("is(foo) and reported.identifier==9 -delete[0:]->", 1, 0)
def test_replace_nodes(person_model: Model) -> None: builder = GraphBuilder(person_model) meta = {"metadata": {"replace": True}} builder.add_from_json({"id": "root", "reported": {"kind": "graph_root"}}) builder.add_from_json({"id": "cloud", "reported": {"id": "cloud", "kind": "cloud"}, **meta}) # also mark account and region as replace node -> the flags should be ignored! builder.add_from_json({"id": "account", "reported": {"id": "account", "kind": "account"}, **meta}) builder.add_from_json({"id": "region", "reported": {"id": "region", "kind": "region"}, **meta}) builder.add_from_json({"from": "root", "to": "cloud"}) builder.add_from_json({"from": "cloud", "to": "account"}) builder.add_from_json({"from": "account", "to": "region"}) roots, _, gen = GraphAccess.merge_graphs(builder.graph) assert roots == ["cloud"] cloud, access = list(gen)[0] assert cloud == "cloud" assert set(access.nodes) == {"cloud", "account", "region"}
def test_access_node() -> None: g = MultiDiGraph() g.add_node("1", reported=to_json(FooTuple(a="1"))) access: GraphAccess = GraphAccess(g) elem: Json = node(access, "1") # type: ignore assert elem["hash"] == "153c1a5c002f6213a95383f33b63aa18b8ed6939f57418fb0f27312576f0cea4" assert elem["reported"] == { "a": "1", "b": 0, "c": [], "d": "foo", "e": {"a": 12, "b": 32}, "f": "2021-03-29", "g": 1.234567, "kind": "foo", } assert access.node("2") is None
def graph_access() -> GraphAccess: g = MultiDiGraph() def add_edge(from_node: str, to_node: str, edge_type: str) -> None: key = GraphAccess.edge_key(from_node, to_node, edge_type) g.add_edge(from_node, to_node, key, edge_type=edge_type) g.add_node("1", reported=to_json(FooTuple("1")), desired={"name": "a"}, metadata={"version": 1}, kinds=["foo"]) g.add_node("2", reported=to_json(FooTuple("2")), desired={"name": "b"}, metadata={"version": 2}, kinds=["foo"]) g.add_node("3", reported=to_json(FooTuple("3")), desired={"name": "c"}, metadata={"version": 3}, kinds=["foo"]) g.add_node("4", reported=to_json(FooTuple("4")), desired={"name": "d"}, metadata={"version": 4}, kinds=["foo"]) add_edge("1", "2", edge_type=EdgeType.default) add_edge("1", "3", edge_type=EdgeType.default) add_edge("2", "3", edge_type=EdgeType.default) add_edge("2", "4", edge_type=EdgeType.default) add_edge("3", "4", edge_type=EdgeType.default) add_edge("1", "2", edge_type=EdgeType.delete) add_edge("1", "3", edge_type=EdgeType.delete) add_edge("1", "4", edge_type=EdgeType.delete) return GraphAccess(g)
def test_resolve_graph_data() -> None: g = multi_cloud_graph("account") graph = GraphAccess(g) graph.resolve() # ancestor data should be stored in metadata n1 = AccessJson(graph.node("child_parent_region_account_cloud_gcp_1_europe_1_0")) # type: ignore assert n1.refs.region_id == "region_account_cloud_gcp_1_europe" assert n1.ancestors.account.reported.id == "id_account_cloud_gcp_1" assert n1.ancestors.account.reported.name == "name_account_cloud_gcp_1" assert n1.ancestors.region.reported.id == "id_region_account_cloud_gcp_1_europe" assert n1.ancestors.region.reported.name == "name_region_account_cloud_gcp_1_europe" # make sure there is no summary assert n1.descendant_summary == AccessNone(None) r1 = AccessJson(graph.node("region_account_cloud_gcp_1_europe")) # type: ignore assert r1.metadata.descendant_summary == {"child": 9} assert r1.metadata.descendant_count == 9 r2 = AccessJson(graph.node("account_cloud_gcp_1")) # type: ignore assert r2.metadata.descendant_summary == {"child": 54, "region": 6} assert r2.metadata.descendant_count == 60 r3 = AccessJson(graph.node("cloud_gcp")) # type: ignore assert r3.metadata.descendant_summary == {"child": 162, "region": 18, "account": 3} assert r3.metadata.descendant_count == 183
def test_root(graph_access: GraphAccess) -> None: assert graph_access.root() == "1"
def add_edge(from_node: str, to_node: str, edge_type: str) -> None: key = GraphAccess.edge_key(from_node, to_node, edge_type) g.add_edge(from_node, to_node, key, edge_type=edge_type)
def test_desired(graph_access: GraphAccess) -> None: desired = {a["id"]: a["desired"] for a in graph_access.not_visited_nodes()} assert desired == {"1": {"name": "a"}, "2": {"name": "b"}, "3": {"name": "c"}, "4": {"name": "d"}}
def test_acyclic() -> None: assert GraphAccess(cyclic_multi_graph(acyclic=False)).is_acyclic_per_edge_type() is False assert GraphAccess(cyclic_multi_graph(acyclic=True)).is_acyclic_per_edge_type() is True
def test_metadata(graph_access: GraphAccess) -> None: desired = {a["id"]: a["metadata"] for a in graph_access.not_visited_nodes()} assert desired == {"1": {"version": 1}, "2": {"version": 2}, "3": {"version": 3}, "4": {"version": 4}}
def node(access: GraphAccess, node_id: str) -> Optional[Json]: res = access.node(node_id) if res: return res else: raise AttributeError(f"Expected {node_id} to be defined!")
def add_edge(from_node: str, to_node: str, edge_type: str = EdgeType.default) -> None: key = GraphAccess.edge_key(from_node, to_node, edge_type) graph.add_edge(from_node, to_node, key, edge_type=edge_type)
def test_edges(graph_access: GraphAccess) -> None: assert graph_access.has_edge("1", "2", EdgeType.default) assert not graph_access.has_edge("1", "9", EdgeType.default) assert graph_access.has_edge("2", "3", EdgeType.default) assert list(graph_access.not_visited_edges(EdgeType.default)) == [("1", "3"), ("2", "4"), ("3", "4")] assert list(graph_access.not_visited_edges(EdgeType.delete)) == [("1", "2"), ("1", "3"), ("1", "4")]