Files
markitect-main/tests/unit/prompts/test_dependency_repository.py
tegwick 9ce157400e feat(prompts): implement Phase 5 - Dependency Tracking (FR-6)
Add directed dependency graph with cycle detection, topological sort,
and query service for finding dependents/dependencies transitively.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 13:18:18 +01:00

282 lines
9.5 KiB
Python

"""
Unit tests for dependency repository.
Tests CRUD operations, duplicate handling, and query by
source, target, run, and type.
"""
import pytest
import tempfile
from pathlib import Path
from markitect.prompts.dependencies.models import DependencyEdge, EdgeType
from markitect.prompts.dependencies.repository import (
SQLiteDependencyRepository,
DuplicateDependencyError,
)
@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 sample_edge():
"""Create sample dependency edge for testing."""
return DependencyEdge.create(
source_artifact_id="artifact-1",
target_artifact_id="artifact-2",
run_id="run-1",
edge_type=EdgeType.REQUIRES,
)
class TestSQLiteDependencyRepository:
"""Tests for SQLiteDependencyRepository."""
def test_create_edge(self, repository, sample_edge):
"""Test creating a dependency edge."""
created = repository.create(sample_edge)
assert created.id == sample_edge.id
assert created.source_artifact_id == "artifact-1"
assert created.target_artifact_id == "artifact-2"
def test_create_duplicate_edge_raises_error(self, repository):
"""Test creating duplicate edge raises error."""
edge1 = DependencyEdge.create(
source_artifact_id="a",
target_artifact_id="b",
run_id="r1",
edge_type=EdgeType.REQUIRES,
)
repository.create(edge1)
edge2 = DependencyEdge.create(
source_artifact_id="a",
target_artifact_id="b",
run_id="r1",
edge_type=EdgeType.REQUIRES,
)
with pytest.raises(DuplicateDependencyError, match="already exists"):
repository.create(edge2)
def test_same_edge_different_runs_allowed(self, repository):
"""Test same source/target with different runs is allowed."""
edge1 = DependencyEdge.create(
source_artifact_id="a",
target_artifact_id="b",
run_id="run-1",
edge_type=EdgeType.REQUIRES,
)
edge2 = DependencyEdge.create(
source_artifact_id="a",
target_artifact_id="b",
run_id="run-2",
edge_type=EdgeType.REQUIRES,
)
repository.create(edge1)
repository.create(edge2) # Should not raise
def test_get_by_id(self, repository, sample_edge):
"""Test retrieving edge by ID."""
repository.create(sample_edge)
retrieved = repository.get_by_id(sample_edge.id)
assert retrieved is not None
assert retrieved.id == sample_edge.id
assert retrieved.source_artifact_id == sample_edge.source_artifact_id
assert retrieved.edge_type == sample_edge.edge_type
def test_get_by_id_not_found(self, repository):
"""Test retrieving non-existent edge returns None."""
assert repository.get_by_id("nonexistent") is None
def test_get_by_source(self, repository):
"""Test querying edges by source artifact."""
edge1 = DependencyEdge.create(
source_artifact_id="src-1",
target_artifact_id="tgt-1",
run_id="run-1",
edge_type=EdgeType.REQUIRES,
)
edge2 = DependencyEdge.create(
source_artifact_id="src-1",
target_artifact_id="tgt-2",
run_id="run-1",
edge_type=EdgeType.GENERATES,
)
edge3 = DependencyEdge.create(
source_artifact_id="src-2",
target_artifact_id="tgt-3",
run_id="run-1",
edge_type=EdgeType.REQUIRES,
)
repository.create(edge1)
repository.create(edge2)
repository.create(edge3)
edges = repository.get_by_source("src-1")
assert len(edges) == 2
assert all(e.source_artifact_id == "src-1" for e in edges)
def test_get_by_target(self, repository):
"""Test querying edges by target artifact."""
edge1 = DependencyEdge.create(
source_artifact_id="src-1",
target_artifact_id="tgt-1",
run_id="run-1",
edge_type=EdgeType.REQUIRES,
)
edge2 = DependencyEdge.create(
source_artifact_id="src-2",
target_artifact_id="tgt-1",
run_id="run-1",
edge_type=EdgeType.REQUIRES,
)
repository.create(edge1)
repository.create(edge2)
edges = repository.get_by_target("tgt-1")
assert len(edges) == 2
assert all(e.target_artifact_id == "tgt-1" for e in edges)
def test_get_by_run(self, repository):
"""Test querying edges by run ID."""
edge1 = DependencyEdge.create(
source_artifact_id="a",
target_artifact_id="b",
run_id="run-1",
edge_type=EdgeType.REQUIRES,
)
edge2 = DependencyEdge.create(
source_artifact_id="c",
target_artifact_id="d",
run_id="run-1",
edge_type=EdgeType.GENERATES,
)
edge3 = DependencyEdge.create(
source_artifact_id="e",
target_artifact_id="f",
run_id="run-2",
edge_type=EdgeType.REQUIRES,
)
repository.create(edge1)
repository.create(edge2)
repository.create(edge3)
edges = repository.get_by_run("run-1")
assert len(edges) == 2
assert all(e.run_id == "run-1" for e in edges)
def test_get_by_type(self, repository):
"""Test querying edges by type."""
edge1 = DependencyEdge.create(
source_artifact_id="a",
target_artifact_id="b",
run_id="run-1",
edge_type=EdgeType.REQUIRES,
)
edge2 = DependencyEdge.create(
source_artifact_id="c",
target_artifact_id="d",
run_id="run-1",
edge_type=EdgeType.GENERATES,
)
edge3 = DependencyEdge.create(
source_artifact_id="e",
target_artifact_id="f",
run_id="run-1",
edge_type=EdgeType.REQUIRES,
)
repository.create(edge1)
repository.create(edge2)
repository.create(edge3)
requires_edges = repository.get_by_type(EdgeType.REQUIRES)
assert len(requires_edges) == 2
assert all(e.edge_type == EdgeType.REQUIRES for e in requires_edges)
generates_edges = repository.get_by_type(EdgeType.GENERATES)
assert len(generates_edges) == 1
def test_get_all(self, repository):
"""Test getting all edges."""
for i in range(3):
edge = DependencyEdge.create(
source_artifact_id=f"src-{i}",
target_artifact_id=f"tgt-{i}",
run_id="run-1",
edge_type=EdgeType.REQUIRES,
)
repository.create(edge)
all_edges = repository.get_all()
assert len(all_edges) == 3
def test_get_all_empty(self, repository):
"""Test getting all edges from empty repository."""
assert repository.get_all() == []
def test_delete_edge(self, repository, sample_edge):
"""Test deleting an edge."""
repository.create(sample_edge)
assert repository.delete(sample_edge.id) is True
assert repository.get_by_id(sample_edge.id) is None
def test_delete_nonexistent_edge(self, repository):
"""Test deleting non-existent edge returns False."""
assert repository.delete("nonexistent") is False
def test_delete_by_run(self, repository):
"""Test deleting all edges for a run."""
for i in range(3):
edge = DependencyEdge.create(
source_artifact_id=f"src-{i}",
target_artifact_id=f"tgt-{i}",
run_id="run-1",
edge_type=EdgeType.REQUIRES,
)
repository.create(edge)
other_edge = DependencyEdge.create(
source_artifact_id="other-src",
target_artifact_id="other-tgt",
run_id="run-2",
edge_type=EdgeType.REQUIRES,
)
repository.create(other_edge)
deleted = repository.delete_by_run("run-1")
assert deleted == 3
assert len(repository.get_by_run("run-1")) == 0
assert len(repository.get_by_run("run-2")) == 1
def test_delete_by_run_no_matches(self, repository):
"""Test deleting by run with no matches returns 0."""
assert repository.delete_by_run("nonexistent") == 0
def test_edge_type_preserved(self, repository):
"""Test that edge type is correctly stored and retrieved."""
for edge_type in EdgeType:
edge = DependencyEdge.create(
source_artifact_id=f"src-{edge_type.value}",
target_artifact_id=f"tgt-{edge_type.value}",
run_id="run-1",
edge_type=edge_type,
)
repository.create(edge)
retrieved = repository.get_by_id(edge.id)
assert retrieved.edge_type == edge_type