deploycraft-ai / tests /test_deployment_rules.py
Upshivam
Initial release of AI MLOps Architecture Designer
4b9217e
Raw
History Blame Contribute Delete
5.26 kB
"""
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()