""" Unit tests for GraphBuilder. Tests extracting edges from manifest, persisting them, building graphs, and artifact-scoped subgraphs. """ import pytest import tempfile from pathlib import Path from markitect.prompts.dependencies.models import ( DependencyEdge, DependencyGraph, EdgeType, ) from markitect.prompts.dependencies.repository import SQLiteDependencyRepository from markitect.prompts.dependencies.graph import GraphBuilder from markitect.prompts.execution.manifest import ( DependencyEdge as ManifestDependencyEdge, RunManifest, ) @pytest.fixture def temp_db(): """Create temporary database for testing.""" with tempfile.NamedTemporaryFile(suffix=".db", delete=False) as f: db_path = f.name yield db_path Path(db_path).unlink(missing_ok=True) @pytest.fixture def repository(temp_db): """Create repository instance with temp database.""" return SQLiteDependencyRepository(temp_db) @pytest.fixture def builder(repository): """Create GraphBuilder instance.""" return GraphBuilder(repository) @pytest.fixture def sample_manifest(): """Create a sample RunManifest with dependency edges.""" manifest = RunManifest.create( run_id="run-1", template_id="template-1", template_name="test-template", template_digest="abc123", ) manifest.add_dependency_edge("artifact-1", "artifact-2", "requires") manifest.add_dependency_edge("artifact-2", "artifact-3", "generates") manifest.add_dependency_edge("artifact-1", "artifact-3", "includes") return manifest class TestGraphBuilderExtract: """Tests for extracting edges from manifests.""" def test_extract_edges_from_manifest(self, builder, sample_manifest): """Test extracting persistent edges from a manifest.""" edges = builder.extract_edges(sample_manifest) assert len(edges) == 3 assert all(isinstance(e, DependencyEdge) for e in edges) def test_extracted_edge_fields(self, builder, sample_manifest): """Test extracted edges have correct fields.""" edges = builder.extract_edges(sample_manifest) # First edge: artifact-1 -> artifact-2, requires edge = edges[0] assert edge.source_artifact_id == "artifact-1" assert edge.target_artifact_id == "artifact-2" assert edge.run_id == "run-1" assert edge.edge_type == EdgeType.REQUIRES assert edge.id # UUID assigned def test_extracted_edges_have_unique_ids(self, builder, sample_manifest): """Test each extracted edge gets a unique ID.""" edges = builder.extract_edges(sample_manifest) ids = [e.id for e in edges] assert len(set(ids)) == len(ids) def test_extract_edges_empty_manifest(self, builder): """Test extracting from manifest with no edges.""" manifest = RunManifest.create( run_id="run-empty", template_id="template-1", template_name="empty", template_digest="xyz", ) edges = builder.extract_edges(manifest) assert edges == [] def test_extract_edges_preserves_types(self, builder, sample_manifest): """Test all edge types are correctly mapped.""" edges = builder.extract_edges(sample_manifest) types = [e.edge_type for e in edges] assert EdgeType.REQUIRES in types assert EdgeType.GENERATES in types assert EdgeType.INCLUDES in types class TestGraphBuilderPersist: """Tests for persisting edges from manifests.""" def test_persist_edges(self, builder, repository, sample_manifest): """Test persisting edges to repository.""" persisted = builder.persist_edges(sample_manifest) assert len(persisted) == 3 # Verify in repository all_edges = repository.get_all() assert len(all_edges) == 3 def test_persisted_edges_retrievable(self, builder, repository, sample_manifest): """Test persisted edges can be retrieved from repository.""" builder.persist_edges(sample_manifest) edges_from_source = repository.get_by_source("artifact-1") assert len(edges_from_source) == 2 # artifact-1 -> artifact-2 and artifact-3 edges_from_run = repository.get_by_run("run-1") assert len(edges_from_run) == 3 class TestGraphBuilderBuildGraph: """Tests for building dependency graphs.""" def test_build_graph_from_all_edges(self, builder, repository): """Test building graph from all stored edges.""" # Manually create some edges for src, tgt in [("A", "B"), ("B", "C"), ("A", "C")]: edge = DependencyEdge.create( source_artifact_id=src, target_artifact_id=tgt, run_id="run-1", edge_type=EdgeType.REQUIRES, ) repository.create(edge) graph = builder.build_graph() assert len(graph.nodes) == 3 assert graph.has_edge("A", "B") assert graph.has_edge("B", "C") assert graph.has_edge("A", "C") def test_build_graph_scoped(self, builder, repository): """Test building scoped graph with subset of artifacts.""" for src, tgt in [("A", "B"), ("B", "C"), ("C", "D")]: edge = DependencyEdge.create( source_artifact_id=src, target_artifact_id=tgt, run_id="run-1", edge_type=EdgeType.REQUIRES, ) repository.create(edge) graph = builder.build_graph(artifact_ids={"A", "B", "C"}) assert graph.has_edge("A", "B") assert graph.has_edge("B", "C") assert not graph.has_edge("C", "D") assert "D" not in graph.nodes def test_build_graph_scoped_includes_isolated_nodes(self, builder, repository): """Test scoped graph includes nodes with no edges in scope.""" edge = DependencyEdge.create( source_artifact_id="A", target_artifact_id="B", run_id="run-1", edge_type=EdgeType.REQUIRES, ) repository.create(edge) graph = builder.build_graph(artifact_ids={"A", "B", "C"}) assert "C" in graph.nodes def test_build_graph_empty(self, builder): """Test building graph from empty repository.""" graph = builder.build_graph() assert len(graph.nodes) == 0 assert graph.edge_count == 0 def test_build_graph_for_run(self, builder, repository): """Test building graph from a specific run's edges.""" # Run 1 edges for src, tgt in [("A", "B"), ("B", "C")]: edge = DependencyEdge.create( source_artifact_id=src, target_artifact_id=tgt, run_id="run-1", edge_type=EdgeType.REQUIRES, ) repository.create(edge) # Run 2 edges edge = DependencyEdge.create( source_artifact_id="X", target_artifact_id="Y", run_id="run-2", edge_type=EdgeType.GENERATES, ) repository.create(edge) graph = builder.build_graph_for_run("run-1") assert graph.has_edge("A", "B") assert graph.has_edge("B", "C") assert not graph.has_edge("X", "Y") def test_build_graph_preserves_edge_types(self, builder, repository): """Test built graph preserves edge types.""" edge = DependencyEdge.create( source_artifact_id="A", target_artifact_id="B", run_id="run-1", edge_type=EdgeType.GENERATES, ) repository.create(edge) graph = builder.build_graph() assert graph.get_edge_type("A", "B") == EdgeType.GENERATES