|
0981a08…
|
noreply
|
1 |
"""Tests for video_processor.cli.output_formatter.OutputFormatter.""" |
|
0981a08…
|
noreply
|
2 |
|
|
0981a08…
|
noreply
|
3 |
from pathlib import Path |
|
0981a08…
|
noreply
|
4 |
|
|
0981a08…
|
noreply
|
5 |
import pytest |
|
0981a08…
|
noreply
|
6 |
|
|
0981a08…
|
noreply
|
7 |
from video_processor.cli.output_formatter import OutputFormatter |
|
0981a08…
|
noreply
|
8 |
|
|
0981a08…
|
noreply
|
9 |
|
|
0981a08…
|
noreply
|
10 |
@pytest.fixture() |
|
0981a08…
|
noreply
|
11 |
def tmp_dir(tmp_path): |
|
0981a08…
|
noreply
|
12 |
"""Return a fresh temp directory that is cleaned up automatically.""" |
|
0981a08…
|
noreply
|
13 |
return tmp_path |
|
0981a08…
|
noreply
|
14 |
|
|
0981a08…
|
noreply
|
15 |
|
|
0981a08…
|
noreply
|
16 |
@pytest.fixture() |
|
0981a08…
|
noreply
|
17 |
def formatter(tmp_dir): |
|
0981a08…
|
noreply
|
18 |
"""Return an OutputFormatter pointed at a temp output directory.""" |
|
0981a08…
|
noreply
|
19 |
return OutputFormatter(tmp_dir / "output") |
|
0981a08…
|
noreply
|
20 |
|
|
0981a08…
|
noreply
|
21 |
|
|
0981a08…
|
noreply
|
22 |
# --- Constructor --- |
|
0981a08…
|
noreply
|
23 |
|
|
0981a08…
|
noreply
|
24 |
|
|
0981a08…
|
noreply
|
25 |
def test_constructor_creates_output_dir(tmp_dir): |
|
0981a08…
|
noreply
|
26 |
out = tmp_dir / "new_output" |
|
0981a08…
|
noreply
|
27 |
assert not out.exists() |
|
0981a08…
|
noreply
|
28 |
OutputFormatter(out) |
|
0981a08…
|
noreply
|
29 |
assert out.is_dir() |
|
0981a08…
|
noreply
|
30 |
|
|
0981a08…
|
noreply
|
31 |
|
|
0981a08…
|
noreply
|
32 |
def test_constructor_accepts_string(tmp_dir): |
|
0981a08…
|
noreply
|
33 |
fmt = OutputFormatter(str(tmp_dir / "str_output")) |
|
0981a08…
|
noreply
|
34 |
assert fmt.output_dir.is_dir() |
|
0981a08…
|
noreply
|
35 |
|
|
0981a08…
|
noreply
|
36 |
|
|
0981a08…
|
noreply
|
37 |
# --- organize_outputs --- |
|
0981a08…
|
noreply
|
38 |
|
|
0981a08…
|
noreply
|
39 |
|
|
0981a08…
|
noreply
|
40 |
def _create_file(path: Path, content: str = "test") -> Path: |
|
0981a08…
|
noreply
|
41 |
path.parent.mkdir(parents=True, exist_ok=True) |
|
0981a08…
|
noreply
|
42 |
path.write_text(content) |
|
0981a08…
|
noreply
|
43 |
return path |
|
0981a08…
|
noreply
|
44 |
|
|
0981a08…
|
noreply
|
45 |
|
|
0981a08…
|
noreply
|
46 |
def test_organize_outputs_basic(formatter, tmp_dir): |
|
0981a08…
|
noreply
|
47 |
md = _create_file(tmp_dir / "analysis.md", "# Title") |
|
0981a08…
|
noreply
|
48 |
kg = _create_file(tmp_dir / "kg.json", "{}") |
|
0981a08…
|
noreply
|
49 |
|
|
0981a08…
|
noreply
|
50 |
result = formatter.organize_outputs( |
|
0981a08…
|
noreply
|
51 |
markdown_path=md, |
|
0981a08…
|
noreply
|
52 |
knowledge_graph_path=kg, |
|
0981a08…
|
noreply
|
53 |
diagrams=[], |
|
0981a08…
|
noreply
|
54 |
) |
|
0981a08…
|
noreply
|
55 |
|
|
0981a08…
|
noreply
|
56 |
assert "markdown" in result |
|
0981a08…
|
noreply
|
57 |
assert "knowledge_graph" in result |
|
0981a08…
|
noreply
|
58 |
assert Path(result["markdown"]).exists() |
|
0981a08…
|
noreply
|
59 |
assert Path(result["knowledge_graph"]).exists() |
|
0981a08…
|
noreply
|
60 |
assert result["diagram_images"] == [] |
|
0981a08…
|
noreply
|
61 |
assert result["frames"] == [] |
|
0981a08…
|
noreply
|
62 |
assert result["transcript"] is None |
|
0981a08…
|
noreply
|
63 |
|
|
0981a08…
|
noreply
|
64 |
|
|
0981a08…
|
noreply
|
65 |
def test_organize_outputs_with_transcript(formatter, tmp_dir): |
|
0981a08…
|
noreply
|
66 |
md = _create_file(tmp_dir / "analysis.md") |
|
0981a08…
|
noreply
|
67 |
kg = _create_file(tmp_dir / "kg.json") |
|
0981a08…
|
noreply
|
68 |
transcript = _create_file(tmp_dir / "transcript.txt", "Hello world") |
|
0981a08…
|
noreply
|
69 |
|
|
0981a08…
|
noreply
|
70 |
result = formatter.organize_outputs( |
|
0981a08…
|
noreply
|
71 |
markdown_path=md, |
|
0981a08…
|
noreply
|
72 |
knowledge_graph_path=kg, |
|
0981a08…
|
noreply
|
73 |
diagrams=[], |
|
0981a08…
|
noreply
|
74 |
transcript_path=transcript, |
|
0981a08…
|
noreply
|
75 |
) |
|
0981a08…
|
noreply
|
76 |
|
|
0981a08…
|
noreply
|
77 |
assert result["transcript"] is not None |
|
0981a08…
|
noreply
|
78 |
assert Path(result["transcript"]).exists() |
|
0981a08…
|
noreply
|
79 |
|
|
0981a08…
|
noreply
|
80 |
|
|
0981a08…
|
noreply
|
81 |
def test_organize_outputs_with_diagrams(formatter, tmp_dir): |
|
0981a08…
|
noreply
|
82 |
md = _create_file(tmp_dir / "analysis.md") |
|
0981a08…
|
noreply
|
83 |
kg = _create_file(tmp_dir / "kg.json") |
|
0981a08…
|
noreply
|
84 |
img = _create_file(tmp_dir / "diagram1.png", "fake-png") |
|
0981a08…
|
noreply
|
85 |
|
|
0981a08…
|
noreply
|
86 |
result = formatter.organize_outputs( |
|
0981a08…
|
noreply
|
87 |
markdown_path=md, |
|
0981a08…
|
noreply
|
88 |
knowledge_graph_path=kg, |
|
0981a08…
|
noreply
|
89 |
diagrams=[{"image_path": str(img)}], |
|
0981a08…
|
noreply
|
90 |
) |
|
0981a08…
|
noreply
|
91 |
|
|
0981a08…
|
noreply
|
92 |
assert len(result["diagram_images"]) == 1 |
|
0981a08…
|
noreply
|
93 |
assert Path(result["diagram_images"][0]).exists() |
|
0981a08…
|
noreply
|
94 |
|
|
0981a08…
|
noreply
|
95 |
|
|
0981a08…
|
noreply
|
96 |
def test_organize_outputs_skips_missing_diagram(formatter, tmp_dir): |
|
0981a08…
|
noreply
|
97 |
md = _create_file(tmp_dir / "analysis.md") |
|
0981a08…
|
noreply
|
98 |
kg = _create_file(tmp_dir / "kg.json") |
|
0981a08…
|
noreply
|
99 |
|
|
0981a08…
|
noreply
|
100 |
result = formatter.organize_outputs( |
|
0981a08…
|
noreply
|
101 |
markdown_path=md, |
|
0981a08…
|
noreply
|
102 |
knowledge_graph_path=kg, |
|
0981a08…
|
noreply
|
103 |
diagrams=[{"image_path": "/nonexistent/diagram.png"}], |
|
0981a08…
|
noreply
|
104 |
) |
|
0981a08…
|
noreply
|
105 |
|
|
0981a08…
|
noreply
|
106 |
assert result["diagram_images"] == [] |
|
0981a08…
|
noreply
|
107 |
|
|
0981a08…
|
noreply
|
108 |
|
|
0981a08…
|
noreply
|
109 |
def test_organize_outputs_diagram_without_image_path(formatter, tmp_dir): |
|
0981a08…
|
noreply
|
110 |
md = _create_file(tmp_dir / "analysis.md") |
|
0981a08…
|
noreply
|
111 |
kg = _create_file(tmp_dir / "kg.json") |
|
0981a08…
|
noreply
|
112 |
|
|
0981a08…
|
noreply
|
113 |
result = formatter.organize_outputs( |
|
0981a08…
|
noreply
|
114 |
markdown_path=md, |
|
0981a08…
|
noreply
|
115 |
knowledge_graph_path=kg, |
|
0981a08…
|
noreply
|
116 |
diagrams=[{"description": "A diagram"}], |
|
0981a08…
|
noreply
|
117 |
) |
|
0981a08…
|
noreply
|
118 |
|
|
0981a08…
|
noreply
|
119 |
assert result["diagram_images"] == [] |
|
0981a08…
|
noreply
|
120 |
|
|
0981a08…
|
noreply
|
121 |
|
|
0981a08…
|
noreply
|
122 |
def test_organize_outputs_with_frames(formatter, tmp_dir): |
|
0981a08…
|
noreply
|
123 |
md = _create_file(tmp_dir / "analysis.md") |
|
0981a08…
|
noreply
|
124 |
kg = _create_file(tmp_dir / "kg.json") |
|
0981a08…
|
noreply
|
125 |
frames_dir = tmp_dir / "frames" |
|
0981a08…
|
noreply
|
126 |
frames_dir.mkdir() |
|
0981a08…
|
noreply
|
127 |
for i in range(5): |
|
0981a08…
|
noreply
|
128 |
_create_file(frames_dir / f"frame_{i:03d}.jpg", f"frame{i}") |
|
0981a08…
|
noreply
|
129 |
|
|
0981a08…
|
noreply
|
130 |
result = formatter.organize_outputs( |
|
0981a08…
|
noreply
|
131 |
markdown_path=md, |
|
0981a08…
|
noreply
|
132 |
knowledge_graph_path=kg, |
|
0981a08…
|
noreply
|
133 |
diagrams=[], |
|
0981a08…
|
noreply
|
134 |
frames_dir=frames_dir, |
|
0981a08…
|
noreply
|
135 |
) |
|
0981a08…
|
noreply
|
136 |
|
|
0981a08…
|
noreply
|
137 |
assert len(result["frames"]) == 5 |
|
0981a08…
|
noreply
|
138 |
|
|
0981a08…
|
noreply
|
139 |
|
|
0981a08…
|
noreply
|
140 |
def test_organize_outputs_limits_frames_to_10(formatter, tmp_dir): |
|
0981a08…
|
noreply
|
141 |
md = _create_file(tmp_dir / "analysis.md") |
|
0981a08…
|
noreply
|
142 |
kg = _create_file(tmp_dir / "kg.json") |
|
0981a08…
|
noreply
|
143 |
frames_dir = tmp_dir / "frames" |
|
0981a08…
|
noreply
|
144 |
frames_dir.mkdir() |
|
0981a08…
|
noreply
|
145 |
for i in range(25): |
|
0981a08…
|
noreply
|
146 |
_create_file(frames_dir / f"frame_{i:03d}.jpg", f"frame{i}") |
|
0981a08…
|
noreply
|
147 |
|
|
0981a08…
|
noreply
|
148 |
result = formatter.organize_outputs( |
|
0981a08…
|
noreply
|
149 |
markdown_path=md, |
|
0981a08…
|
noreply
|
150 |
knowledge_graph_path=kg, |
|
0981a08…
|
noreply
|
151 |
diagrams=[], |
|
0981a08…
|
noreply
|
152 |
frames_dir=frames_dir, |
|
0981a08…
|
noreply
|
153 |
) |
|
0981a08…
|
noreply
|
154 |
|
|
0981a08…
|
noreply
|
155 |
assert len(result["frames"]) <= 10 |
|
0981a08…
|
noreply
|
156 |
|
|
0981a08…
|
noreply
|
157 |
|
|
0981a08…
|
noreply
|
158 |
def test_organize_outputs_missing_frames_dir(formatter, tmp_dir): |
|
0981a08…
|
noreply
|
159 |
md = _create_file(tmp_dir / "analysis.md") |
|
0981a08…
|
noreply
|
160 |
kg = _create_file(tmp_dir / "kg.json") |
|
0981a08…
|
noreply
|
161 |
|
|
0981a08…
|
noreply
|
162 |
result = formatter.organize_outputs( |
|
0981a08…
|
noreply
|
163 |
markdown_path=md, |
|
0981a08…
|
noreply
|
164 |
knowledge_graph_path=kg, |
|
0981a08…
|
noreply
|
165 |
diagrams=[], |
|
0981a08…
|
noreply
|
166 |
frames_dir=tmp_dir / "nonexistent_frames", |
|
0981a08…
|
noreply
|
167 |
) |
|
0981a08…
|
noreply
|
168 |
|
|
0981a08…
|
noreply
|
169 |
assert result["frames"] == [] |
|
0981a08…
|
noreply
|
170 |
|
|
0981a08…
|
noreply
|
171 |
|
|
0981a08…
|
noreply
|
172 |
# --- create_html_index --- |
|
0981a08…
|
noreply
|
173 |
|
|
0981a08…
|
noreply
|
174 |
|
|
0981a08…
|
noreply
|
175 |
def test_create_html_index_returns_path(formatter, tmp_dir): |
|
0981a08…
|
noreply
|
176 |
outputs = { |
|
0981a08…
|
noreply
|
177 |
"markdown": str(formatter.output_dir / "markdown" / "analysis.md"), |
|
0981a08…
|
noreply
|
178 |
"knowledge_graph": str(formatter.output_dir / "data" / "kg.json"), |
|
0981a08…
|
noreply
|
179 |
"diagram_images": [], |
|
0981a08…
|
noreply
|
180 |
"frames": [], |
|
0981a08…
|
noreply
|
181 |
"transcript": None, |
|
0981a08…
|
noreply
|
182 |
} |
|
0981a08…
|
noreply
|
183 |
# Create the referenced files so relative_to works |
|
0981a08…
|
noreply
|
184 |
for key in ("markdown", "knowledge_graph"): |
|
0981a08…
|
noreply
|
185 |
_create_file(Path(outputs[key])) |
|
0981a08…
|
noreply
|
186 |
|
|
0981a08…
|
noreply
|
187 |
index = formatter.create_html_index(outputs) |
|
0981a08…
|
noreply
|
188 |
assert index.exists() |
|
0981a08…
|
noreply
|
189 |
assert index.name == "index.html" |
|
0981a08…
|
noreply
|
190 |
|
|
0981a08…
|
noreply
|
191 |
|
|
0981a08…
|
noreply
|
192 |
def test_create_html_index_contains_analysis_link(formatter, tmp_dir): |
|
0981a08…
|
noreply
|
193 |
md_path = formatter.output_dir / "markdown" / "analysis.md" |
|
0981a08…
|
noreply
|
194 |
_create_file(md_path) |
|
0981a08…
|
noreply
|
195 |
outputs = { |
|
0981a08…
|
noreply
|
196 |
"markdown": str(md_path), |
|
0981a08…
|
noreply
|
197 |
"knowledge_graph": None, |
|
0981a08…
|
noreply
|
198 |
"diagram_images": [], |
|
0981a08…
|
noreply
|
199 |
"frames": [], |
|
0981a08…
|
noreply
|
200 |
"transcript": None, |
|
0981a08…
|
noreply
|
201 |
} |
|
0981a08…
|
noreply
|
202 |
|
|
0981a08…
|
noreply
|
203 |
index = formatter.create_html_index(outputs) |
|
0981a08…
|
noreply
|
204 |
content = index.read_text() |
|
0981a08…
|
noreply
|
205 |
assert "Analysis Report" in content |
|
0981a08…
|
noreply
|
206 |
assert "analysis.md" in content |
|
0981a08…
|
noreply
|
207 |
|
|
0981a08…
|
noreply
|
208 |
|
|
0981a08…
|
noreply
|
209 |
def test_create_html_index_with_diagrams(formatter, tmp_dir): |
|
0981a08…
|
noreply
|
210 |
img_path = formatter.output_dir / "diagrams" / "d1.png" |
|
0981a08…
|
noreply
|
211 |
_create_file(img_path) |
|
0981a08…
|
noreply
|
212 |
outputs = { |
|
0981a08…
|
noreply
|
213 |
"markdown": None, |
|
0981a08…
|
noreply
|
214 |
"knowledge_graph": None, |
|
0981a08…
|
noreply
|
215 |
"diagram_images": [str(img_path)], |
|
0981a08…
|
noreply
|
216 |
"frames": [], |
|
0981a08…
|
noreply
|
217 |
"transcript": None, |
|
0981a08…
|
noreply
|
218 |
} |
|
0981a08…
|
noreply
|
219 |
|
|
0981a08…
|
noreply
|
220 |
index = formatter.create_html_index(outputs) |
|
0981a08…
|
noreply
|
221 |
content = index.read_text() |
|
0981a08…
|
noreply
|
222 |
assert "Diagrams" in content |
|
0981a08…
|
noreply
|
223 |
assert "d1.png" in content |
|
0981a08…
|
noreply
|
224 |
|
|
0981a08…
|
noreply
|
225 |
|
|
0981a08…
|
noreply
|
226 |
def test_create_html_index_with_frames(formatter, tmp_dir): |
|
0981a08…
|
noreply
|
227 |
frame_path = formatter.output_dir / "frames" / "frame_001.jpg" |
|
0981a08…
|
noreply
|
228 |
_create_file(frame_path) |
|
0981a08…
|
noreply
|
229 |
outputs = { |
|
0981a08…
|
noreply
|
230 |
"markdown": None, |
|
0981a08…
|
noreply
|
231 |
"knowledge_graph": None, |
|
0981a08…
|
noreply
|
232 |
"diagram_images": [], |
|
0981a08…
|
noreply
|
233 |
"frames": [str(frame_path)], |
|
0981a08…
|
noreply
|
234 |
"transcript": None, |
|
0981a08…
|
noreply
|
235 |
} |
|
0981a08…
|
noreply
|
236 |
|
|
0981a08…
|
noreply
|
237 |
index = formatter.create_html_index(outputs) |
|
0981a08…
|
noreply
|
238 |
content = index.read_text() |
|
0981a08…
|
noreply
|
239 |
assert "Key Frames" in content |
|
0981a08…
|
noreply
|
240 |
assert "frame_001.jpg" in content |
|
0981a08…
|
noreply
|
241 |
|
|
0981a08…
|
noreply
|
242 |
|
|
0981a08…
|
noreply
|
243 |
def test_create_html_index_with_data_files(formatter, tmp_dir): |
|
0981a08…
|
noreply
|
244 |
kg_path = formatter.output_dir / "data" / "kg.json" |
|
0981a08…
|
noreply
|
245 |
transcript_path = formatter.output_dir / "data" / "transcript.txt" |
|
0981a08…
|
noreply
|
246 |
_create_file(kg_path) |
|
0981a08…
|
noreply
|
247 |
_create_file(transcript_path) |
|
0981a08…
|
noreply
|
248 |
outputs = { |
|
0981a08…
|
noreply
|
249 |
"markdown": None, |
|
0981a08…
|
noreply
|
250 |
"knowledge_graph": str(kg_path), |
|
0981a08…
|
noreply
|
251 |
"diagram_images": [], |
|
0981a08…
|
noreply
|
252 |
"frames": [], |
|
0981a08…
|
noreply
|
253 |
"transcript": str(transcript_path), |
|
0981a08…
|
noreply
|
254 |
} |
|
0981a08…
|
noreply
|
255 |
|
|
0981a08…
|
noreply
|
256 |
index = formatter.create_html_index(outputs) |
|
0981a08…
|
noreply
|
257 |
content = index.read_text() |
|
0981a08…
|
noreply
|
258 |
assert "Data Files" in content |
|
0981a08…
|
noreply
|
259 |
assert "kg.json" in content |
|
0981a08…
|
noreply
|
260 |
assert "transcript.txt" in content |
|
0981a08…
|
noreply
|
261 |
|
|
0981a08…
|
noreply
|
262 |
|
|
0981a08…
|
noreply
|
263 |
def test_create_html_index_empty_outputs(formatter): |
|
0981a08…
|
noreply
|
264 |
outputs = { |
|
0981a08…
|
noreply
|
265 |
"markdown": None, |
|
0981a08…
|
noreply
|
266 |
"knowledge_graph": None, |
|
0981a08…
|
noreply
|
267 |
"diagram_images": [], |
|
0981a08…
|
noreply
|
268 |
"frames": [], |
|
0981a08…
|
noreply
|
269 |
"transcript": None, |
|
0981a08…
|
noreply
|
270 |
} |
|
0981a08…
|
noreply
|
271 |
|
|
0981a08…
|
noreply
|
272 |
index = formatter.create_html_index(outputs) |
|
0981a08…
|
noreply
|
273 |
content = index.read_text() |
|
0981a08…
|
noreply
|
274 |
assert "PlanOpticon Analysis Results" in content |
|
0981a08…
|
noreply
|
275 |
assert "<!DOCTYPE html>" in content |