diff --git a/tests/unit/test_exporters.py b/tests/unit/test_exporters.py new file mode 100644 index 0000000..b71dc5f --- /dev/null +++ b/tests/unit/test_exporters.py @@ -0,0 +1,158 @@ +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