#!/usr/bin/env python3 """ Tests for graph_visualizer.py — smoke test + subgraph logic. Run: python3 scripts/test_graph_visualizer.py """ import json, sys, tempfile from pathlib import Path sys.path.insert(0, str(Path(__file__).resolve().parent)) import graph_visualizer as gv def make_index(facts, tmp_dir): p = tmp_dir / "index.json" p.write_text(json.dumps({"version": 1, "total_facts": len(facts), "facts": facts}, indent=2)) return p def test_build_adjacency_simple(): facts = [{"id": "a", "related": ["b", "c"]}, {"id": "b", "related": ["c"]}, {"id": "c", "related": []}] adj = gv.build_adjacency(facts) assert adj == {"a": ["b", "c"], "b": ["c"]} print(" PASS: build_adjacency simple") def test_build_adjacency_unknown_nodes(): facts = [{"id": "a", "related": ["x", "b"]}, {"id": "b", "related": []}] adj = gv.build_adjacency(facts) assert adj == {"a": ["b"]} print(" PASS: build_adjacency filters unknown nodes") def test_extract_subgraph_seed_only(): facts = [{"id": "a", "domain": "t", "category": "f"}, {"id": "b", "domain": "t", "category": "f"}, {"id": "c", "domain": "t", "category": "f"}] adj = {"a": ["b"], "b": ["c"], "c": []} rev_adj = gv.build_reverse_adjacency(adj) sub = gv.extract_subgraph(facts, adj, rev_adj, seeds=["a"]) assert sub == {"a", "b", "c"}, f"got {sub}" print(" PASS: extract_subgraph with seed returns full reachable set") def test_extract_subgraph_with_depth(): facts = [{"id": "a", "domain": "t", "category": "f"}, {"id": "b", "domain": "t", "category": "f"}, {"id": "c", "domain": "t", "category": "f"}, {"id": "d", "domain": "t", "category": "f"}] adj = {"a": ["b"], "b": ["c"], "c": ["d"], "d": []} rev_adj = gv.build_reverse_adjacency(adj) sub = gv.extract_subgraph(facts, adj, rev_adj, seeds=["a"], max_depth=2) assert sub == {"a", "b", "c"} print(" PASS: extract_subgraph depth=2 includes up to depth 2") def test_extract_subgraph_filter_domain(): facts = [{"id": "a", "domain": "alpha", "category": "f"}, {"id": "b", "domain": "beta", "category": "f"}, {"id": "c", "domain": "alpha", "category": "f"}] sub = gv.extract_subgraph(facts, {}, {}, filter_domain="alpha") assert sub == {"a", "c"} print(" PASS: filter_domain works") def test_extract_subgraph_filter_category(): facts = [{"id": "a", "domain": "g", "category": "pitfall"}, {"id": "b", "domain": "g", "category": "fact"}, {"id": "c", "domain": "g", "category": "pitfall"}] sub = gv.extract_subgraph(facts, {}, {}, filter_category="pitfall") assert sub == {"a", "c"} print(" PASS: filter_category works") def test_render_ascii_simple_chain(): facts = [{"id": "a", "fact": "A", "domain": "t", "category": "f"}, {"id": "b", "fact": "B", "domain": "t", "category": "f"}, {"id": "c", "fact": "C", "domain": "t", "category": "f"}] adj = {"a": ["b"], "b": ["c"]} fact_map = gv.build_fact_map(facts) out = gv.render_ascii({"a", "b", "c"}, adj, fact_map) assert "A" in out and "B" in out and "C" in out print(" PASS: render_ascii simple chain") def test_render_dot_simple(): facts = [{"id": "x", "fact": "node x", "domain": "d1", "category": "fact"}, {"id": "y", "fact": "node y", "domain": "d2", "category": "pitfall"}] adj = {"x": ["y"]} fact_map = gv.build_fact_map(facts) out = gv.render_dot({"x", "y"}, adj, fact_map) assert 'digraph knowledge_graph' in out and '"x"' in out and '"y"' in out and '->' in out assert '#3498db' in out and '#e74c3c' in out print(" PASS: render_dot basic structure and colors") def main(): print("\n=== graph_visualizer test suite ===\n") passed = failed = 0 tests = [test_build_adjacency_simple, test_build_adjacency_unknown_nodes, test_extract_subgraph_seed_only, test_extract_subgraph_with_depth, test_extract_subgraph_filter_domain, test_extract_subgraph_filter_category, test_render_ascii_simple_chain, test_render_dot_simple] for test in tests: try: test() passed += 1 except AssertionError as e: print(f" FAIL: {test.__name__} — {e}") failed += 1 except Exception as e: print(f" ERROR: {test.__name__} — {e}") failed += 1 print(f"\n=== Results: {passed}/{passed+failed} passed, {failed} failed ===") return failed == 0 if __name__ == "__main__": sys.exit(0 if main() else 1)