""" Unit tests for PromptQueryService. Tests run history, impact analysis queries, dependency stats, and artifact digest lookups. """ import pytest import tempfile from pathlib import Path from markitect.prompts.dependencies.models import DependencyEdge, EdgeType from markitect.prompts.dependencies.repository import SQLiteDependencyRepository from markitect.prompts.execution.models import PromptRun, RunStatus from markitect.prompts.models import Artifact, ArtifactType from markitect.prompts.repositories.sqlite import SQLiteArtifactRepository from markitect.prompts.queries.operations import PromptQueryService @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): return SQLiteArtifactRepository(temp_db) @pytest.fixture def dep_repo(temp_db): return SQLiteDependencyRepository(temp_db) @pytest.fixture def query_service(artifact_repo, dep_repo, temp_db): return PromptQueryService(artifact_repo, dep_repo, db_path=temp_db) def _make_artifact(repo, space_id, name, content="content"): artifact = Artifact.create( space_id=space_id, name=name, content=content, artifact_type=ArtifactType.CONTENT, ) return repo.create(artifact) def _make_run(template_id, status=RunStatus.SUCCESS): run = PromptRun.create( template_id=template_id, input_bundle_hash="hash-abc", ) if status == RunStatus.SUCCESS: run.mark_complete() elif status == RunStatus.FAILED: run.mark_failed("error") return run def _make_edge(repo, source, target, run_id): edge = DependencyEdge.create( source_artifact_id=source, target_artifact_id=target, run_id=run_id, edge_type=EdgeType.REQUIRES, ) return repo.create(edge) class TestRunHistory: """Tests for get_run_history.""" def test_returns_all_runs(self, query_service): """Test returning all registered runs.""" run1 = _make_run("tmpl-1") run2 = _make_run("tmpl-2") query_service.register_run(run1) query_service.register_run(run2) history = query_service.get_run_history() assert len(history) == 2 def test_filter_by_template(self, query_service): """Test filtering by template_id.""" run1 = _make_run("tmpl-1") run2 = _make_run("tmpl-2") query_service.register_run(run1) query_service.register_run(run2) history = query_service.get_run_history(template_id="tmpl-1") assert len(history) == 1 assert history[0]["template_id"] == "tmpl-1" def test_filter_by_status(self, query_service): """Test filtering by status.""" run_ok = _make_run("tmpl-1", RunStatus.SUCCESS) run_fail = _make_run("tmpl-1", RunStatus.FAILED) query_service.register_run(run_ok) query_service.register_run(run_fail) history = query_service.get_run_history(status="success") assert len(history) == 1 assert history[0]["status"] == "success" def test_limit(self, query_service): """Test limit parameter.""" for i in range(10): run = _make_run(f"tmpl-{i}") query_service.register_run(run) history = query_service.get_run_history(limit=3) assert len(history) == 3 def test_empty_history(self, query_service): """Test empty run history.""" assert query_service.get_run_history() == [] class TestStaleArtifacts: """Tests for get_stale_artifacts.""" def test_no_debt_returns_empty(self, query_service): """Test returns empty when no debt exists.""" assert query_service.get_stale_artifacts() == [] def test_no_engine_returns_empty(self, artifact_repo, dep_repo): """Test returns empty without db_path.""" svc = PromptQueryService(artifact_repo, dep_repo, db_path=None) assert svc.get_stale_artifacts() == [] class TestDependencyStats: """Tests for get_dependency_stats.""" def test_empty_graph(self, query_service): """Test stats for empty graph.""" stats = query_service.get_dependency_stats() assert stats["total_nodes"] == 0 assert stats["total_edges"] == 0 assert stats["has_cycles"] is False def test_simple_graph(self, query_service, artifact_repo, dep_repo): """Test stats for a simple graph.""" a = _make_artifact(artifact_repo, "s", "a") b = _make_artifact(artifact_repo, "s", "b") c = _make_artifact(artifact_repo, "s", "c") _make_edge(dep_repo, a.id, b.id, "r1") _make_edge(dep_repo, b.id, c.id, "r1") stats = query_service.get_dependency_stats() assert stats["total_nodes"] == 3 assert stats["total_edges"] == 2 assert stats["root_count"] == 1 # 'a' is root assert stats["leaf_count"] == 1 # 'c' is leaf assert stats["has_cycles"] is False class TestFindArtifactsByDigest: """Tests for find_artifacts_by_digest.""" def test_finds_matching(self, query_service, artifact_repo): """Test finding artifacts by content digest.""" art = _make_artifact(artifact_repo, "s", "test-art", "hello") results = query_service.find_artifacts_by_digest(art.content_digest) assert len(results) == 1 assert results[0]["artifact_id"] == art.id def test_no_match(self, query_service): """Test returns empty for non-matching digest.""" assert query_service.find_artifacts_by_digest("nonexistent") == [] def test_multiple_matches(self, query_service, artifact_repo): """Test finding multiple artifacts with same digest.""" _make_artifact(artifact_repo, "s1", "a", "same-content") _make_artifact(artifact_repo, "s2", "a", "same-content") art = _make_artifact(artifact_repo, "s3", "a", "same-content") results = query_service.find_artifacts_by_digest(art.content_digest) assert len(results) == 3