Files
markitect-main/tests/unit/prompts/test_visualization.py
tegwick 7b4bd461c9 feat(prompts): implement Phase 8 - Observability & Traceability (FR-11)
Complete implementation of Phase 8, the final phase of prompt dependency
resolution infrastructure, adding full observability and traceability.

## Features (FR-11)

### FR-11.1: Complete Artifact Provenance Tracing
- TraceabilityService: composition layer for full artifact lineage
- Trace any artifact to producing PromptTemplate, input artifacts,
  generator runs, and quality validation results
- ProvenanceTrace model with complete dependency chain reconstruction
- RunSummary and ArtifactLineage models for structured trace output

### FR-11.2: Recomputation Query Infrastructure
- PromptQueryService: cross-service complex queries
- Run history queries with template and status filters
- Stale artifact detection via impact debt analysis
- Dependency graph statistics (nodes, edges, cycles, roots, leaves)
- Content-based artifact lookups by digest

### Visualization Support
- GraphExporter: DOT (Graphviz) and Mermaid format export
- Supports all edge types (requires, generates, includes)
- Handles isolated nodes, linear chains, diamonds, and complex graphs

### CLI Commands (prompt group)
- `prompt trace <artifact_id>` - Full provenance trace as JSON
- `prompt graph <artifact_id>` - Dependency graph (DOT/Mermaid)
- `prompt runs` - List execution runs with filters
- `prompt debt` - Show impact debt and stale artifacts
- `prompt stats` - Dependency graph statistics

## Implementation

Source files (8):
- markitect/prompts/traceability/models.py - Trace data models
- markitect/prompts/traceability/service.py - TraceabilityService
- markitect/prompts/visualization/graph.py - Graph export
- markitect/prompts/queries/operations.py - PromptQueryService
- markitect/prompts/cli.py - Click CLI commands
- Package __init__.py files (3)

Tests (64 total, all passing):
- tests/unit/prompts/test_traceability_service.py (21 tests)
- tests/unit/prompts/test_visualization.py (14 tests)
- tests/unit/prompts/test_query_operations.py (12 tests)
- tests/integration/prompts/test_traceability_workflow.py (7 tests)
- tests/integration/prompts/test_prompt_cli.py (10 tests)

## Architecture

TraceabilityService is a composition layer that delegates to:
- DependencyQueryService (transitive dependency lookups)
- QualityValidator (validation history)
- IncrementalExecutionEngine (impact debt queries)
- Direct repository access (artifacts, edges)

No duplicate data storage - all data comes from existing Phase 1-7
infrastructure (artifact repo, dependency repo, validation DB, debt DB).

## Verification

All 2250 tests pass with 0 regressions.
Phase 8 completes the full 8-phase implementation roadmap.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-09 20:32:18 +01:00

142 lines
4.7 KiB
Python

"""
Unit tests for GraphExporter.
Tests DOT and Mermaid export with various graph shapes.
"""
import pytest
from markitect.prompts.dependencies.models import DependencyGraph, EdgeType
from markitect.prompts.visualization.graph import GraphExporter
@pytest.fixture
def empty_graph():
"""Empty graph with no nodes."""
return DependencyGraph()
@pytest.fixture
def single_node_graph():
"""Graph with a single isolated node."""
g = DependencyGraph()
g._forward["node-a"] = set()
g._reverse["node-a"] = set()
return g
@pytest.fixture
def linear_graph():
"""Linear chain: A -> B -> C."""
g = DependencyGraph()
g.add_edge("A", "B", EdgeType.REQUIRES)
g.add_edge("B", "C", EdgeType.GENERATES)
return g
@pytest.fixture
def diamond_graph():
"""Diamond: A -> B, A -> C, B -> D, C -> D."""
g = DependencyGraph()
g.add_edge("A", "B", EdgeType.REQUIRES)
g.add_edge("A", "C", EdgeType.REQUIRES)
g.add_edge("B", "D", EdgeType.GENERATES)
g.add_edge("C", "D", EdgeType.INCLUDES)
return g
class TestToDot:
"""Tests for to_dot export."""
def test_empty_graph(self, empty_graph):
"""Test DOT output for empty graph."""
dot = GraphExporter.to_dot(empty_graph)
assert 'digraph "Dependencies"' in dot
assert "rankdir=LR" in dot
def test_single_node(self, single_node_graph):
"""Test DOT output with single node."""
dot = GraphExporter.to_dot(single_node_graph)
assert "node_a" in dot
assert 'label="node-a"' in dot
def test_linear_graph(self, linear_graph):
"""Test DOT output for linear chain."""
dot = GraphExporter.to_dot(linear_graph)
assert "A -> B" in dot
assert "B -> C" in dot
assert 'label="requires"' in dot
assert 'label="generates"' in dot
def test_diamond_graph(self, diamond_graph):
"""Test DOT output for diamond graph."""
dot = GraphExporter.to_dot(diamond_graph)
assert "A -> B" in dot
assert "A -> C" in dot
assert "B -> D" in dot
assert "C -> D" in dot
def test_custom_title(self, linear_graph):
"""Test DOT output with custom title."""
dot = GraphExporter.to_dot(linear_graph, title="My Graph")
assert 'digraph "My Graph"' in dot
assert 'label="My Graph"' in dot
def test_edge_styles(self, linear_graph):
"""Test DOT edge styles for different edge types."""
dot = GraphExporter.to_dot(linear_graph)
assert 'style="solid"' in dot # REQUIRES
assert 'style="dashed"' in dot # GENERATES
def test_dot_is_valid_structure(self, diamond_graph):
"""Test DOT output has valid opening/closing braces."""
dot = GraphExporter.to_dot(diamond_graph)
assert dot.startswith('digraph')
assert dot.endswith("}")
class TestToMermaid:
"""Tests for to_mermaid export."""
def test_empty_graph(self, empty_graph):
"""Test Mermaid output for empty graph."""
mermaid = GraphExporter.to_mermaid(empty_graph)
assert "graph LR" in mermaid
def test_single_node(self, single_node_graph):
"""Test Mermaid output with single node."""
mermaid = GraphExporter.to_mermaid(single_node_graph)
assert "node-a" in mermaid
def test_linear_graph(self, linear_graph):
"""Test Mermaid output for linear chain."""
mermaid = GraphExporter.to_mermaid(linear_graph)
assert "A-->|requires|B" in mermaid
assert "B-.->|generates|C" in mermaid
def test_diamond_graph(self, diamond_graph):
"""Test Mermaid output for diamond graph."""
mermaid = GraphExporter.to_mermaid(diamond_graph)
assert "A-->|requires|B" in mermaid
assert "A-->|requires|C" in mermaid
assert "B-.->|generates|D" in mermaid
assert "C==>|includes|D" in mermaid
def test_custom_title(self, linear_graph):
"""Test Mermaid output with custom title."""
mermaid = GraphExporter.to_mermaid(linear_graph, title="Build Graph")
assert "Build Graph" in mermaid
def test_edge_arrows(self, diamond_graph):
"""Test Mermaid edge arrows for different types."""
mermaid = GraphExporter.to_mermaid(diamond_graph)
assert "-->" in mermaid # REQUIRES
assert "-.->" in mermaid # GENERATES
assert "==>" in mermaid # INCLUDES
def test_mermaid_starts_with_graph(self, linear_graph):
"""Test Mermaid output starts with graph directive."""
mermaid = GraphExporter.to_mermaid(linear_graph)
lines = mermaid.strip().split("\n")
assert "graph LR" in lines[1]