Spaces:
Sleeping
Sleeping
| """ | |
| tests/test_deployment_rules.py | |
| ---------------------------------- | |
| Regression test for a real bug: build_architecture_graph() used to chain | |
| cache -> database -> vector_db serially off each other instead of | |
| connecting each directly from the "app" node. That produced a | |
| topologically incorrect graph, which fed both the Mermaid diagram and | |
| the ArchitectureAdvisor's prompt — causing the AI to reason about a | |
| system that didn't exist (observed in practice: a real Hugging Face | |
| response claiming "the database is not directly accessible from the | |
| application; instead, it's accessed via the Weaviate vector database"). | |
| This test locks in the correct, parallel topology so that bug can't | |
| silently come back. | |
| Run with: python -m unittest discover -s tests | |
| """ | |
| from __future__ import annotations | |
| import unittest | |
| from utils.config import InfraConfig | |
| from rules.deployment_rules import build_architecture_graph | |
| def _make_config(**overrides) -> InfraConfig: | |
| defaults = dict( | |
| project_name="test-svc", | |
| app_framework="FastAPI", | |
| cloud_provider="AWS", | |
| deployment_target="Kubernetes", | |
| database="Cloud SQL (PostgreSQL)", | |
| vector_db="Qdrant", | |
| cache="Redis", | |
| cicd="GitHub Actions", | |
| monitoring="Prometheus + Grafana", | |
| auth="None", | |
| num_users="1,000-10,000/day", | |
| budget="$500-$2,000/mo", | |
| high_availability=True, | |
| ) | |
| defaults.update(overrides) | |
| return InfraConfig(**defaults) | |
| class TestArchitectureGraphTopology(unittest.TestCase): | |
| def test_cache_database_vector_db_are_parallel_children_of_app(self): | |
| """ | |
| cache, database, and vector_db must each connect directly FROM | |
| "app" — not from each other. This is the exact bug: a prior | |
| version produced app -> cache -> database -> vector_db (a chain), | |
| which implied (both visually and to the AI reading the topology) | |
| that e.g. the vector database sits between the app and the | |
| relational database. | |
| """ | |
| graph = build_architecture_graph(_make_config()) | |
| edge_pairs = {(e.source, e.target) for e in graph.edges} | |
| # Each data service must be a direct child of "app"... | |
| self.assertIn(("app", "cache"), edge_pairs) | |
| self.assertIn(("app", "database"), edge_pairs) | |
| self.assertIn(("app", "vector_db"), edge_pairs) | |
| # ...and must NOT be chained off each other, in any order. | |
| for a, b in [("cache", "database"), ("database", "vector_db"), ("cache", "vector_db"), | |
| ("database", "cache"), ("vector_db", "database"), ("vector_db", "cache")]: | |
| self.assertNotIn((a, b), edge_pairs, f"found unexpected chained edge {a} -> {b}") | |
| def test_absent_services_produce_no_dangling_nodes_or_edges(self): | |
| """If cache/database/vector_db are 'None', they must not appear at all.""" | |
| graph = build_architecture_graph(_make_config(cache="None", database="None", vector_db="None")) | |
| node_ids = {n.id for n in graph.nodes} | |
| self.assertNotIn("cache", node_ids) | |
| self.assertNotIn("database", node_ids) | |
| self.assertNotIn("vector_db", node_ids) | |
| # Every edge must reference nodes that actually exist. | |
| for edge in graph.edges: | |
| self.assertIn(edge.source, node_ids) | |
| self.assertIn(edge.target, node_ids) | |
| def test_partial_data_services_still_parallel(self): | |
| """With only some data services active, the active ones are still parallel, not chained.""" | |
| graph = build_architecture_graph(_make_config(cache="Redis", database="None", vector_db="Pinecone")) | |
| edge_pairs = {(e.source, e.target) for e in graph.edges} | |
| self.assertIn(("app", "cache"), edge_pairs) | |
| self.assertNotIn("database", {n.id for n in graph.nodes}) | |
| # Pinecone (managed/no local container) still gets a graph node — | |
| # the graph models logical architecture, not deployability. | |
| self.assertIn(("app", "vector_db"), edge_pairs) | |
| self.assertNotIn(("cache", "vector_db"), edge_pairs) | |
| def test_monitoring_and_cicd_remain_dashed_side_branches(self): | |
| """Monitoring/CI-CD must stay dashed side branches, not on the main data path.""" | |
| graph = build_architecture_graph(_make_config()) | |
| monitoring_edges = [e for e in graph.edges if "monitoring" in (e.source, e.target)] | |
| self.assertTrue(monitoring_edges, "expected a monitoring edge for this config") | |
| self.assertTrue(all(e.style == "dashed" for e in monitoring_edges)) | |
| cicd_edges = [e for e in graph.edges if "cicd" in (e.source, e.target)] | |
| self.assertTrue(cicd_edges, "expected a cicd edge for this config") | |
| self.assertTrue(all(e.style == "dashed" for e in cicd_edges)) | |
| def test_main_request_path_unchanged(self): | |
| """The client -> lb -> deploy_target -> app spine should be untouched by this fix.""" | |
| graph = build_architecture_graph(_make_config()) | |
| edge_pairs = [(e.source, e.target) for e in graph.edges] | |
| self.assertIn(("client", "lb"), edge_pairs) | |
| self.assertIn(("lb", "deploy_target"), edge_pairs) | |
| self.assertIn(("deploy_target", "app"), edge_pairs) | |
| if __name__ == "__main__": | |
| unittest.main() | |