import pytest import tempfile import subprocess import json from pathlib import Path from src.graph.builder import GraphBuilder, GraphType, GraphNode, NodeType from src.exporters.dot import DOTExporter from src.exporters.json_exporter import JSONExporter from src.exporters.png import PNGExporter def graphviz_available(): try: subprocess.run(["dot", "-V"], capture_output=True, check=True) return True except (subprocess.CalledProcessError, FileNotFoundError): return False class TestDOTExporter: def setup_method(self): self.builder = GraphBuilder(GraphType.DIRECTED) self.exporter = DOTExporter(self.builder) def test_export_creates_file(self): node = GraphNode(node_id="test", node_type=NodeType.FILE, name="test.py") self.builder.add_node(node) with tempfile.NamedTemporaryFile(suffix=".dot", delete=False) as f: output_path = Path(f.name) try: self.exporter.export(output_path) assert output_path.exists() content = output_path.read_text() assert "digraph codegraph" in content assert "test" in content finally: output_path.unlink() def test_get_string_returns_dot_format(self): node = GraphNode(node_id="node1", node_type=NodeType.FILE, name="file.py") self.builder.add_node(node) content = self.exporter.get_string() assert "digraph codegraph" in content assert "node1" in content def test_export_with_layout(self): node = GraphNode(node_id="test", node_type=NodeType.FILE, name="test.py") self.builder.add_node(node) content = self.exporter.get_string(layout="circo") assert "digraph codegraph" in content def test_export_with_rankdir(self): node = GraphNode(node_id="test", node_type=NodeType.FILE, name="test.py") self.builder.add_node(node) content = self.exporter.get_string(rankdir="LR") assert "rankdir=LR" in content class TestJSONExporter: def setup_method(self): self.builder = GraphBuilder(GraphType.DIRECTED) self.exporter = JSONExporter(self.builder) def test_export_creates_json_file(self): node = GraphNode(node_id="test", node_type=NodeType.FILE, name="test.py") self.builder.add_node(node) with tempfile.NamedTemporaryFile(suffix=".json", delete=False) as f: output_path = Path(f.name) try: self.exporter.export(output_path) assert output_path.exists() data = json.loads(output_path.read_text()) assert "nodes" in data assert "edges" in data finally: output_path.unlink() def test_get_string_returns_json(self): node = GraphNode(node_id="test", node_type=NodeType.FILE, name="test.py") self.builder.add_node(node) content = self.exporter.get_string() data = json.loads(content) assert "nodes" in data assert "edges" in data def test_export_contains_correct_node_data(self): node = GraphNode( node_id="test_file", node_type=NodeType.FILE, name="test.py", file_path=Path("/test.py"), start_line=1, end_line=10, ) self.builder.add_node(node) content = self.exporter.get_string() data = json.loads(content) assert len(data["nodes"]) == 1 assert data["nodes"][0]["name"] == "test.py" assert data["nodes"][0]["type"] == "file" def test_export_for_d3(self): node = GraphNode(node_id="test", node_type=NodeType.FILE, name="test.py") self.builder.add_node(node) with tempfile.NamedTemporaryFile(suffix=".json", delete=False) as f: output_path = Path(f.name) try: self.exporter.export_for_d3(output_path) data = json.loads(output_path.read_text()) assert "nodes" in data assert "links" in data finally: output_path.unlink() def test_export_includes_statistics(self): node = GraphNode(node_id="test", node_type=NodeType.FILE, name="test.py") self.builder.add_node(node) content = self.exporter.get_string() data = json.loads(content) assert "statistics" in data assert data["statistics"]["total_nodes"] == 1 class TestPNGExporter: def setup_method(self): self.builder = GraphBuilder(GraphType.DIRECTED) self.exporter = PNGExporter(self.builder) @pytest.mark.skipif(not graphviz_available(), reason="Graphviz not installed") def test_export_creates_png_file(self): node = GraphNode(node_id="test", node_type=NodeType.FILE, name="test.py") self.builder.add_node(node) with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as f: output_path = Path(f.name) try: self.exporter.export(output_path) assert output_path.exists() content = output_path.read_bytes() assert len(content) > 0 finally: if output_path.exists(): output_path.unlink() def test_get_available_layouts(self): layouts = self.exporter.get_available_layouts() assert "dot" in layouts assert "circo" in layouts assert "neato" in layouts