""" Integration tests for dependency graph full workflow. Tests the complete workflow: artifacts + edges + graph + queries across multiple information spaces. """ import pytest import tempfile from pathlib import Path from markitect.prompts.models import Artifact, ArtifactType from markitect.prompts.repositories.sqlite import SQLiteArtifactRepository 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.dependencies.queries import DependencyQueryService from markitect.prompts.execution.manifest import 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 artifact_repo(temp_db): """Create artifact repository.""" return SQLiteArtifactRepository(temp_db) @pytest.fixture def dep_repo(temp_db): """Create dependency repository (shares same DB).""" return SQLiteDependencyRepository(temp_db) @pytest.fixture def builder(dep_repo): """Create GraphBuilder.""" return GraphBuilder(dep_repo) @pytest.fixture def query_service(dep_repo): """Create DependencyQueryService.""" return DependencyQueryService(dep_repo) def _create_artifact(artifact_repo, space_id, name, content="test content"): """Helper to create and persist an artifact.""" artifact = Artifact.create( space_id=space_id, name=name, content=content, artifact_type=ArtifactType.CONTENT, ) return artifact_repo.create(artifact) class TestFullWorkflow: """Tests for the complete dependency tracking workflow.""" def test_manifest_to_graph_workflow( self, artifact_repo, dep_repo, builder, query_service ): """Test full workflow: create artifacts, manifest, persist edges, query.""" # Step 1: Create artifacts in a space art_a = _create_artifact(artifact_repo, "space-1", "artifact-a", "content-a") art_b = _create_artifact(artifact_repo, "space-1", "artifact-b", "content-b") art_c = _create_artifact(artifact_repo, "space-1", "artifact-c", "content-c") # Step 2: Create a manifest with dependency edges manifest = RunManifest.create( run_id="run-1", template_id=art_a.id, template_name="artifact-a", template_digest=art_a.content_digest, ) manifest.add_dependency_edge(art_a.id, art_b.id, "requires") manifest.add_dependency_edge(art_b.id, art_c.id, "requires") # Step 3: Persist edges from manifest persisted = builder.persist_edges(manifest) assert len(persisted) == 2 # Step 4: Build graph graph = builder.build_graph() assert graph.has_edge(art_a.id, art_b.id) assert graph.has_edge(art_b.id, art_c.id) # Step 5: Query dependencies deps = query_service.find_dependencies(art_a.id) assert art_b.id in deps transitive_deps = query_service.find_transitive_dependencies(art_a.id) assert art_b.id in transitive_deps assert art_c.id in transitive_deps # Step 6: Query dependents dependents = query_service.find_dependents(art_c.id) assert art_b.id in dependents transitive_dependents = query_service.find_transitive_dependents(art_c.id) assert art_a.id in transitive_dependents assert art_b.id in transitive_dependents def test_build_order_workflow( self, artifact_repo, dep_repo, builder, query_service ): """Test build order across a dependency graph.""" art_a = _create_artifact(artifact_repo, "space-1", "lib-core") art_b = _create_artifact(artifact_repo, "space-1", "lib-utils") art_c = _create_artifact(artifact_repo, "space-1", "app") # app depends on lib-utils, lib-utils depends on lib-core manifest = RunManifest.create( run_id="run-build", template_id=art_c.id, template_name="app", template_digest=art_c.content_digest, ) manifest.add_dependency_edge(art_c.id, art_b.id, "requires") manifest.add_dependency_edge(art_b.id, art_a.id, "requires") builder.persist_edges(manifest) # Build order should have core first, then utils, then app order = query_service.get_build_order() assert order.index(art_a.id) < order.index(art_b.id) assert order.index(art_b.id) < order.index(art_c.id) def test_dependency_chain_workflow( self, artifact_repo, dep_repo, builder, query_service ): """Test finding dependency chains between artifacts.""" art_a = _create_artifact(artifact_repo, "space-1", "source") art_b = _create_artifact(artifact_repo, "space-1", "intermediate") art_c = _create_artifact(artifact_repo, "space-1", "target") manifest = RunManifest.create( run_id="run-chain", template_id=art_a.id, template_name="source", template_digest=art_a.content_digest, ) manifest.add_dependency_edge(art_a.id, art_b.id, "requires") manifest.add_dependency_edge(art_b.id, art_c.id, "requires") builder.persist_edges(manifest) chain = query_service.get_dependency_chain(art_a.id, art_c.id) assert chain is not None assert chain[0] == art_a.id assert chain[-1] == art_c.id assert len(chain) == 3 class TestCrossSpaceGraph: """Tests for dependency tracking across information spaces (FR-6.2).""" def test_cross_space_dependencies( self, artifact_repo, dep_repo, builder, query_service ): """Test dependencies between artifacts in different spaces.""" # Create artifacts in different spaces shared = _create_artifact(artifact_repo, "shared", "common-lib") team_a = _create_artifact(artifact_repo, "team-a", "service-a") team_b = _create_artifact(artifact_repo, "team-b", "service-b") # Both services depend on shared lib manifest = RunManifest.create( run_id="run-cross-space", template_id=team_a.id, template_name="service-a", template_digest=team_a.content_digest, ) manifest.add_dependency_edge(team_a.id, shared.id, "requires") manifest.add_dependency_edge(team_b.id, shared.id, "requires") builder.persist_edges(manifest) # Shared lib should have both services as dependents dependents = query_service.find_dependents(shared.id) assert team_a.id in dependents assert team_b.id in dependents # Each service should have shared as a dependency assert shared.id in query_service.find_dependencies(team_a.id) assert shared.id in query_service.find_dependencies(team_b.id) def test_cross_space_transitive( self, artifact_repo, dep_repo, builder, query_service ): """Test transitive dependencies across spaces.""" # shared/foundation -> shared/utils -> team-a/app foundation = _create_artifact(artifact_repo, "shared", "foundation") utils = _create_artifact(artifact_repo, "shared", "utils") app = _create_artifact(artifact_repo, "team-a", "app") manifest = RunManifest.create( run_id="run-trans", template_id=app.id, template_name="app", template_digest=app.content_digest, ) manifest.add_dependency_edge(app.id, utils.id, "requires") manifest.add_dependency_edge(utils.id, foundation.id, "requires") builder.persist_edges(manifest) # App should transitively depend on foundation transitive = query_service.find_transitive_dependencies(app.id) assert utils.id in transitive assert foundation.id in transitive def test_cross_space_build_order( self, artifact_repo, dep_repo, builder, query_service ): """Test build order across spaces.""" core = _create_artifact(artifact_repo, "shared", "core") api = _create_artifact(artifact_repo, "team-a", "api") web = _create_artifact(artifact_repo, "team-b", "web") manifest = RunManifest.create( run_id="run-order", template_id=web.id, template_name="web", template_digest=web.content_digest, ) manifest.add_dependency_edge(api.id, core.id, "requires") manifest.add_dependency_edge(web.id, api.id, "requires") builder.persist_edges(manifest) order = query_service.get_build_order() assert order.index(core.id) < order.index(api.id) assert order.index(api.id) < order.index(web.id) class TestMultipleRuns: """Tests for dependency tracking across multiple runs.""" def test_edges_from_multiple_runs( self, artifact_repo, dep_repo, builder, query_service ): """Test edges from multiple runs are combined in graph.""" art_a = _create_artifact(artifact_repo, "space-1", "a") art_b = _create_artifact(artifact_repo, "space-1", "b") art_c = _create_artifact(artifact_repo, "space-1", "c") # Run 1: A -> B manifest1 = RunManifest.create( run_id="run-1", template_id=art_a.id, template_name="a", template_digest=art_a.content_digest, ) manifest1.add_dependency_edge(art_a.id, art_b.id, "requires") builder.persist_edges(manifest1) # Run 2: B -> C manifest2 = RunManifest.create( run_id="run-2", template_id=art_b.id, template_name="b", template_digest=art_b.content_digest, ) manifest2.add_dependency_edge(art_b.id, art_c.id, "requires") builder.persist_edges(manifest2) # Full graph should have A -> B -> C graph = builder.build_graph() assert graph.has_edge(art_a.id, art_b.id) assert graph.has_edge(art_b.id, art_c.id) # Transitive query should work across runs transitive = query_service.find_transitive_dependencies(art_a.id) assert art_b.id in transitive assert art_c.id in transitive def test_run_scoped_graph(self, artifact_repo, dep_repo, builder): """Test building graph scoped to a specific run.""" art_a = _create_artifact(artifact_repo, "space-1", "a") art_b = _create_artifact(artifact_repo, "space-1", "b") art_c = _create_artifact(artifact_repo, "space-1", "c") # Persist edges for different runs edge1 = DependencyEdge.create( source_artifact_id=art_a.id, target_artifact_id=art_b.id, run_id="run-1", edge_type=EdgeType.REQUIRES, ) dep_repo.create(edge1) edge2 = DependencyEdge.create( source_artifact_id=art_b.id, target_artifact_id=art_c.id, run_id="run-2", edge_type=EdgeType.GENERATES, ) dep_repo.create(edge2) # Graph for run-1 only graph1 = builder.build_graph_for_run("run-1") assert graph1.has_edge(art_a.id, art_b.id) assert not graph1.has_edge(art_b.id, art_c.id) # Graph for run-2 only graph2 = builder.build_graph_for_run("run-2") assert not graph2.has_edge(art_a.id, art_b.id) assert graph2.has_edge(art_b.id, art_c.id)