|
cd8f2f9…
|
leo
|
1 |
"""Tests for rendering and export utilities.""" |
|
cd8f2f9…
|
leo
|
2 |
|
|
829e24a…
|
leo
|
3 |
from unittest.mock import patch |
|
cd8f2f9…
|
leo
|
4 |
|
|
cd8f2f9…
|
leo
|
5 |
from video_processor.models import ( |
|
cd8f2f9…
|
leo
|
6 |
ActionItem, |
|
cd8f2f9…
|
leo
|
7 |
DiagramResult, |
|
cd8f2f9…
|
leo
|
8 |
DiagramType, |
|
cd8f2f9…
|
leo
|
9 |
KeyPoint, |
|
cd8f2f9…
|
leo
|
10 |
ProcessingStats, |
|
cd8f2f9…
|
leo
|
11 |
VideoManifest, |
|
cd8f2f9…
|
leo
|
12 |
VideoMetadata, |
|
cd8f2f9…
|
leo
|
13 |
) |
|
cd8f2f9…
|
leo
|
14 |
from video_processor.utils.rendering import render_mermaid, reproduce_chart |
|
cd8f2f9…
|
leo
|
15 |
|
|
cd8f2f9…
|
leo
|
16 |
|
|
cd8f2f9…
|
leo
|
17 |
class TestRenderMermaid: |
|
cd8f2f9…
|
leo
|
18 |
def test_writes_mermaid_source(self, tmp_path): |
|
cd8f2f9…
|
leo
|
19 |
code = "graph LR\n A-->B" |
|
cd8f2f9…
|
leo
|
20 |
result = render_mermaid(code, tmp_path, "test_diagram") |
|
cd8f2f9…
|
leo
|
21 |
assert "mermaid" in result |
|
cd8f2f9…
|
leo
|
22 |
assert result["mermaid"].exists() |
|
cd8f2f9…
|
leo
|
23 |
assert result["mermaid"].read_text() == code |
|
cd8f2f9…
|
leo
|
24 |
|
|
cd8f2f9…
|
leo
|
25 |
def test_source_file_named_correctly(self, tmp_path): |
|
cd8f2f9…
|
leo
|
26 |
result = render_mermaid("graph TD\n X-->Y", tmp_path, "my_chart") |
|
cd8f2f9…
|
leo
|
27 |
assert result["mermaid"].name == "my_chart.mermaid" |
|
cd8f2f9…
|
leo
|
28 |
|
|
cd8f2f9…
|
leo
|
29 |
@patch("video_processor.utils.rendering.mmd", create=True) |
|
cd8f2f9…
|
leo
|
30 |
def test_svg_png_on_import_error(self, mock_mmd, tmp_path): |
|
cd8f2f9…
|
leo
|
31 |
"""When mermaid-py is not installed, only source is written.""" |
|
cd8f2f9…
|
leo
|
32 |
# Simulate import error by using the real code path |
|
cd8f2f9…
|
leo
|
33 |
# (mermaid-py may or may not be installed in test env) |
|
cd8f2f9…
|
leo
|
34 |
result = render_mermaid("graph LR\n A-->B", tmp_path, "test") |
|
cd8f2f9…
|
leo
|
35 |
# At minimum, mermaid source should always be written |
|
cd8f2f9…
|
leo
|
36 |
assert "mermaid" in result |
|
cd8f2f9…
|
leo
|
37 |
assert result["mermaid"].exists() |
|
cd8f2f9…
|
leo
|
38 |
|
|
cd8f2f9…
|
leo
|
39 |
def test_creates_output_dir(self, tmp_path): |
|
cd8f2f9…
|
leo
|
40 |
nested = tmp_path / "a" / "b" |
|
cd8f2f9…
|
leo
|
41 |
result = render_mermaid("graph LR\n A-->B", nested, "test") |
|
cd8f2f9…
|
leo
|
42 |
assert nested.exists() |
|
cd8f2f9…
|
leo
|
43 |
assert result["mermaid"].exists() |
|
cd8f2f9…
|
leo
|
44 |
|
|
cd8f2f9…
|
leo
|
45 |
|
|
cd8f2f9…
|
leo
|
46 |
class TestReproduceChart: |
|
cd8f2f9…
|
leo
|
47 |
def test_bar_chart(self, tmp_path): |
|
cd8f2f9…
|
leo
|
48 |
data = { |
|
cd8f2f9…
|
leo
|
49 |
"labels": ["A", "B", "C"], |
|
cd8f2f9…
|
leo
|
50 |
"values": [10, 20, 30], |
|
cd8f2f9…
|
leo
|
51 |
"chart_type": "bar", |
|
cd8f2f9…
|
leo
|
52 |
} |
|
cd8f2f9…
|
leo
|
53 |
result = reproduce_chart(data, tmp_path, "test") |
|
cd8f2f9…
|
leo
|
54 |
assert "svg" in result |
|
cd8f2f9…
|
leo
|
55 |
assert "png" in result |
|
cd8f2f9…
|
leo
|
56 |
assert result["svg"].exists() |
|
cd8f2f9…
|
leo
|
57 |
assert result["png"].exists() |
|
cd8f2f9…
|
leo
|
58 |
assert result["svg"].suffix == ".svg" |
|
cd8f2f9…
|
leo
|
59 |
assert result["png"].suffix == ".png" |
|
cd8f2f9…
|
leo
|
60 |
|
|
cd8f2f9…
|
leo
|
61 |
def test_line_chart(self, tmp_path): |
|
cd8f2f9…
|
leo
|
62 |
data = { |
|
cd8f2f9…
|
leo
|
63 |
"labels": ["Jan", "Feb", "Mar"], |
|
cd8f2f9…
|
leo
|
64 |
"values": [5, 15, 10], |
|
cd8f2f9…
|
leo
|
65 |
"chart_type": "line", |
|
cd8f2f9…
|
leo
|
66 |
} |
|
cd8f2f9…
|
leo
|
67 |
result = reproduce_chart(data, tmp_path, "line_test") |
|
cd8f2f9…
|
leo
|
68 |
assert "svg" in result |
|
cd8f2f9…
|
leo
|
69 |
assert result["svg"].exists() |
|
cd8f2f9…
|
leo
|
70 |
|
|
cd8f2f9…
|
leo
|
71 |
def test_pie_chart(self, tmp_path): |
|
cd8f2f9…
|
leo
|
72 |
data = { |
|
cd8f2f9…
|
leo
|
73 |
"labels": ["Dogs", "Cats"], |
|
cd8f2f9…
|
leo
|
74 |
"values": [60, 40], |
|
cd8f2f9…
|
leo
|
75 |
"chart_type": "pie", |
|
cd8f2f9…
|
leo
|
76 |
} |
|
cd8f2f9…
|
leo
|
77 |
result = reproduce_chart(data, tmp_path, "pie_test") |
|
cd8f2f9…
|
leo
|
78 |
assert "svg" in result |
|
cd8f2f9…
|
leo
|
79 |
|
|
cd8f2f9…
|
leo
|
80 |
def test_scatter_chart(self, tmp_path): |
|
cd8f2f9…
|
leo
|
81 |
data = { |
|
cd8f2f9…
|
leo
|
82 |
"labels": ["X1", "X2", "X3"], |
|
cd8f2f9…
|
leo
|
83 |
"values": [1, 4, 9], |
|
cd8f2f9…
|
leo
|
84 |
"chart_type": "scatter", |
|
cd8f2f9…
|
leo
|
85 |
} |
|
cd8f2f9…
|
leo
|
86 |
result = reproduce_chart(data, tmp_path, "scatter_test") |
|
cd8f2f9…
|
leo
|
87 |
assert "svg" in result |
|
cd8f2f9…
|
leo
|
88 |
|
|
cd8f2f9…
|
leo
|
89 |
def test_empty_data_returns_empty(self, tmp_path): |
|
cd8f2f9…
|
leo
|
90 |
data = {"labels": [], "values": [], "chart_type": "bar"} |
|
cd8f2f9…
|
leo
|
91 |
result = reproduce_chart(data, tmp_path, "empty") |
|
cd8f2f9…
|
leo
|
92 |
assert result == {} |
|
cd8f2f9…
|
leo
|
93 |
|
|
cd8f2f9…
|
leo
|
94 |
def test_missing_values_returns_empty(self, tmp_path): |
|
cd8f2f9…
|
leo
|
95 |
data = {"labels": ["A", "B"]} |
|
cd8f2f9…
|
leo
|
96 |
result = reproduce_chart(data, tmp_path, "no_vals") |
|
cd8f2f9…
|
leo
|
97 |
assert result == {} |
|
cd8f2f9…
|
leo
|
98 |
|
|
cd8f2f9…
|
leo
|
99 |
def test_creates_output_dir(self, tmp_path): |
|
cd8f2f9…
|
leo
|
100 |
nested = tmp_path / "charts" / "output" |
|
cd8f2f9…
|
leo
|
101 |
data = {"labels": ["A"], "values": [1], "chart_type": "bar"} |
|
829e24a…
|
leo
|
102 |
reproduce_chart(data, nested, "test") |
|
cd8f2f9…
|
leo
|
103 |
assert nested.exists() |
|
cd8f2f9…
|
leo
|
104 |
|
|
cd8f2f9…
|
leo
|
105 |
|
|
cd8f2f9…
|
leo
|
106 |
class TestExportAllFormats: |
|
cd8f2f9…
|
leo
|
107 |
def _make_manifest(self) -> VideoManifest: |
|
cd8f2f9…
|
leo
|
108 |
return VideoManifest( |
|
cd8f2f9…
|
leo
|
109 |
video=VideoMetadata(title="Test Video"), |
|
cd8f2f9…
|
leo
|
110 |
stats=ProcessingStats(frames_extracted=5, diagrams_detected=1), |
|
cd8f2f9…
|
leo
|
111 |
analysis_md="results/analysis.md", |
|
cd8f2f9…
|
leo
|
112 |
key_points=[KeyPoint(point="Important finding")], |
|
cd8f2f9…
|
leo
|
113 |
action_items=[ActionItem(action="Follow up", assignee="Alice")], |
|
cd8f2f9…
|
leo
|
114 |
diagrams=[ |
|
cd8f2f9…
|
leo
|
115 |
DiagramResult( |
|
cd8f2f9…
|
leo
|
116 |
frame_index=0, |
|
cd8f2f9…
|
leo
|
117 |
diagram_type=DiagramType.flowchart, |
|
cd8f2f9…
|
leo
|
118 |
confidence=0.9, |
|
cd8f2f9…
|
leo
|
119 |
description="Login flow", |
|
cd8f2f9…
|
leo
|
120 |
mermaid="graph LR\n Login-->Dashboard", |
|
cd8f2f9…
|
leo
|
121 |
image_path="diagrams/diagram_0.jpg", |
|
cd8f2f9…
|
leo
|
122 |
), |
|
cd8f2f9…
|
leo
|
123 |
], |
|
cd8f2f9…
|
leo
|
124 |
) |
|
cd8f2f9…
|
leo
|
125 |
|
|
cd8f2f9…
|
leo
|
126 |
def test_export_renders_mermaid(self, tmp_path): |
|
cd8f2f9…
|
leo
|
127 |
from video_processor.utils.export import export_all_formats |
|
cd8f2f9…
|
leo
|
128 |
|
|
cd8f2f9…
|
leo
|
129 |
manifest = self._make_manifest() |
|
cd8f2f9…
|
leo
|
130 |
|
|
cd8f2f9…
|
leo
|
131 |
# Create required dirs and files |
|
cd8f2f9…
|
leo
|
132 |
(tmp_path / "results").mkdir() |
|
cd8f2f9…
|
leo
|
133 |
(tmp_path / "results" / "analysis.md").write_text("# Test\nContent") |
|
cd8f2f9…
|
leo
|
134 |
(tmp_path / "diagrams").mkdir() |
|
cd8f2f9…
|
leo
|
135 |
(tmp_path / "diagrams" / "diagram_0.jpg").write_bytes(b"\xff\xd8\xff") |
|
cd8f2f9…
|
leo
|
136 |
|
|
cd8f2f9…
|
leo
|
137 |
result = export_all_formats(tmp_path, manifest) |
|
cd8f2f9…
|
leo
|
138 |
|
|
cd8f2f9…
|
leo
|
139 |
# Mermaid source should be written |
|
cd8f2f9…
|
leo
|
140 |
assert (tmp_path / "diagrams" / "diagram_0.mermaid").exists() |
|
cd8f2f9…
|
leo
|
141 |
# Manifest should be updated |
|
cd8f2f9…
|
leo
|
142 |
assert result.diagrams[0].mermaid_path is not None |
|
cd8f2f9…
|
leo
|
143 |
|
|
cd8f2f9…
|
leo
|
144 |
def test_export_generates_html(self, tmp_path): |
|
cd8f2f9…
|
leo
|
145 |
from video_processor.utils.export import export_all_formats |
|
cd8f2f9…
|
leo
|
146 |
|
|
cd8f2f9…
|
leo
|
147 |
manifest = self._make_manifest() |
|
cd8f2f9…
|
leo
|
148 |
(tmp_path / "results").mkdir() |
|
cd8f2f9…
|
leo
|
149 |
(tmp_path / "results" / "analysis.md").write_text("# Test") |
|
cd8f2f9…
|
leo
|
150 |
(tmp_path / "diagrams").mkdir() |
|
cd8f2f9…
|
leo
|
151 |
|
|
cd8f2f9…
|
leo
|
152 |
result = export_all_formats(tmp_path, manifest) |
|
cd8f2f9…
|
leo
|
153 |
assert result.analysis_html is not None |
|
cd8f2f9…
|
leo
|
154 |
html_path = tmp_path / result.analysis_html |
|
cd8f2f9…
|
leo
|
155 |
assert html_path.exists() |
|
cd8f2f9…
|
leo
|
156 |
html_content = html_path.read_text() |
|
cd8f2f9…
|
leo
|
157 |
assert "Test Video" in html_content |
|
cd8f2f9…
|
leo
|
158 |
assert "mermaid" in html_content.lower() |
|
cd8f2f9…
|
leo
|
159 |
|
|
cd8f2f9…
|
leo
|
160 |
def test_export_with_chart_data(self, tmp_path): |
|
cd8f2f9…
|
leo
|
161 |
from video_processor.utils.export import export_all_formats |
|
cd8f2f9…
|
leo
|
162 |
|
|
cd8f2f9…
|
leo
|
163 |
manifest = VideoManifest( |
|
cd8f2f9…
|
leo
|
164 |
video=VideoMetadata(title="Chart Test"), |
|
cd8f2f9…
|
leo
|
165 |
diagrams=[ |
|
cd8f2f9…
|
leo
|
166 |
DiagramResult( |
|
cd8f2f9…
|
leo
|
167 |
frame_index=0, |
|
cd8f2f9…
|
leo
|
168 |
diagram_type=DiagramType.chart, |
|
cd8f2f9…
|
leo
|
169 |
confidence=0.9, |
|
cd8f2f9…
|
leo
|
170 |
chart_data={ |
|
cd8f2f9…
|
leo
|
171 |
"labels": ["Q1", "Q2", "Q3"], |
|
cd8f2f9…
|
leo
|
172 |
"values": [100, 200, 150], |
|
cd8f2f9…
|
leo
|
173 |
"chart_type": "bar", |
|
cd8f2f9…
|
leo
|
174 |
}, |
|
cd8f2f9…
|
leo
|
175 |
), |
|
cd8f2f9…
|
leo
|
176 |
], |
|
cd8f2f9…
|
leo
|
177 |
) |
|
cd8f2f9…
|
leo
|
178 |
(tmp_path / "results").mkdir() |
|
cd8f2f9…
|
leo
|
179 |
(tmp_path / "diagrams").mkdir() |
|
cd8f2f9…
|
leo
|
180 |
|
|
829e24a…
|
leo
|
181 |
export_all_formats(tmp_path, manifest) |
|
cd8f2f9…
|
leo
|
182 |
# Chart should be reproduced |
|
cd8f2f9…
|
leo
|
183 |
chart_svg = tmp_path / "diagrams" / "diagram_0_chart.svg" |
|
cd8f2f9…
|
leo
|
184 |
assert chart_svg.exists() |
|
cd8f2f9…
|
leo
|
185 |
|
|
cd8f2f9…
|
leo
|
186 |
|
|
cd8f2f9…
|
leo
|
187 |
class TestGenerateHtmlReport: |
|
cd8f2f9…
|
leo
|
188 |
def test_html_contains_title(self, tmp_path): |
|
cd8f2f9…
|
leo
|
189 |
from video_processor.utils.export import generate_html_report |
|
cd8f2f9…
|
leo
|
190 |
|
|
cd8f2f9…
|
leo
|
191 |
manifest = VideoManifest( |
|
cd8f2f9…
|
leo
|
192 |
video=VideoMetadata(title="My Meeting"), |
|
cd8f2f9…
|
leo
|
193 |
analysis_md="results/analysis.md", |
|
cd8f2f9…
|
leo
|
194 |
) |
|
cd8f2f9…
|
leo
|
195 |
(tmp_path / "results").mkdir() |
|
cd8f2f9…
|
leo
|
196 |
(tmp_path / "results" / "analysis.md").write_text("# My Meeting\nNotes here.") |
|
cd8f2f9…
|
leo
|
197 |
|
|
cd8f2f9…
|
leo
|
198 |
path = generate_html_report(manifest, tmp_path) |
|
cd8f2f9…
|
leo
|
199 |
assert path is not None |
|
cd8f2f9…
|
leo
|
200 |
content = path.read_text() |
|
cd8f2f9…
|
leo
|
201 |
assert "My Meeting" in content |
|
cd8f2f9…
|
leo
|
202 |
|
|
cd8f2f9…
|
leo
|
203 |
def test_html_includes_key_points(self, tmp_path): |
|
cd8f2f9…
|
leo
|
204 |
from video_processor.utils.export import generate_html_report |
|
cd8f2f9…
|
leo
|
205 |
|
|
cd8f2f9…
|
leo
|
206 |
manifest = VideoManifest( |
|
cd8f2f9…
|
leo
|
207 |
video=VideoMetadata(title="Test"), |
|
cd8f2f9…
|
leo
|
208 |
key_points=[ |
|
cd8f2f9…
|
leo
|
209 |
KeyPoint(point="First point", details="Detail 1"), |
|
cd8f2f9…
|
leo
|
210 |
KeyPoint(point="Second point"), |
|
cd8f2f9…
|
leo
|
211 |
], |
|
cd8f2f9…
|
leo
|
212 |
) |
|
cd8f2f9…
|
leo
|
213 |
(tmp_path / "results").mkdir() |
|
cd8f2f9…
|
leo
|
214 |
|
|
cd8f2f9…
|
leo
|
215 |
path = generate_html_report(manifest, tmp_path) |
|
cd8f2f9…
|
leo
|
216 |
content = path.read_text() |
|
cd8f2f9…
|
leo
|
217 |
assert "First point" in content |
|
cd8f2f9…
|
leo
|
218 |
assert "Detail 1" in content |
|
cd8f2f9…
|
leo
|
219 |
assert "Second point" in content |
|
cd8f2f9…
|
leo
|
220 |
|
|
cd8f2f9…
|
leo
|
221 |
def test_html_includes_action_items(self, tmp_path): |
|
cd8f2f9…
|
leo
|
222 |
from video_processor.utils.export import generate_html_report |
|
cd8f2f9…
|
leo
|
223 |
|
|
cd8f2f9…
|
leo
|
224 |
manifest = VideoManifest( |
|
cd8f2f9…
|
leo
|
225 |
video=VideoMetadata(title="Test"), |
|
cd8f2f9…
|
leo
|
226 |
action_items=[ |
|
cd8f2f9…
|
leo
|
227 |
ActionItem(action="Do the thing", assignee="Bob", deadline="Friday"), |
|
cd8f2f9…
|
leo
|
228 |
], |
|
cd8f2f9…
|
leo
|
229 |
) |
|
cd8f2f9…
|
leo
|
230 |
(tmp_path / "results").mkdir() |
|
cd8f2f9…
|
leo
|
231 |
|
|
cd8f2f9…
|
leo
|
232 |
path = generate_html_report(manifest, tmp_path) |
|
cd8f2f9…
|
leo
|
233 |
content = path.read_text() |
|
cd8f2f9…
|
leo
|
234 |
assert "Do the thing" in content |
|
cd8f2f9…
|
leo
|
235 |
assert "Bob" in content |
|
cd8f2f9…
|
leo
|
236 |
assert "Friday" in content |
|
cd8f2f9…
|
leo
|
237 |
|
|
cd8f2f9…
|
leo
|
238 |
def test_html_includes_mermaid_js(self, tmp_path): |
|
cd8f2f9…
|
leo
|
239 |
from video_processor.utils.export import generate_html_report |
|
cd8f2f9…
|
leo
|
240 |
|
|
cd8f2f9…
|
leo
|
241 |
manifest = VideoManifest( |
|
cd8f2f9…
|
leo
|
242 |
video=VideoMetadata(title="Test"), |
|
cd8f2f9…
|
leo
|
243 |
diagrams=[ |
|
cd8f2f9…
|
leo
|
244 |
DiagramResult( |
|
cd8f2f9…
|
leo
|
245 |
frame_index=0, |
|
cd8f2f9…
|
leo
|
246 |
mermaid="graph LR\n A-->B", |
|
cd8f2f9…
|
leo
|
247 |
) |
|
cd8f2f9…
|
leo
|
248 |
], |
|
cd8f2f9…
|
leo
|
249 |
) |
|
cd8f2f9…
|
leo
|
250 |
(tmp_path / "results").mkdir() |
|
cd8f2f9…
|
leo
|
251 |
|
|
cd8f2f9…
|
leo
|
252 |
path = generate_html_report(manifest, tmp_path) |
|
cd8f2f9…
|
leo
|
253 |
content = path.read_text() |
|
cd8f2f9…
|
leo
|
254 |
assert "mermaid" in content |
|
cd8f2f9…
|
leo
|
255 |
assert "A-->B" in content |