142 lines
4.7 KiB
Python
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]
|