|
b2df90e…
|
leo
|
1 |
"""Tests for pydantic data models.""" |
|
b2df90e…
|
leo
|
2 |
|
|
b2df90e…
|
leo
|
3 |
from video_processor.models import ( |
|
b2df90e…
|
leo
|
4 |
ActionItem, |
|
b2df90e…
|
leo
|
5 |
BatchManifest, |
|
b2df90e…
|
leo
|
6 |
BatchVideoEntry, |
|
b2df90e…
|
leo
|
7 |
DiagramResult, |
|
b2df90e…
|
leo
|
8 |
DiagramType, |
|
b2df90e…
|
leo
|
9 |
Entity, |
|
b2df90e…
|
leo
|
10 |
KeyPoint, |
|
b2df90e…
|
leo
|
11 |
KnowledgeGraphData, |
|
b2df90e…
|
leo
|
12 |
OutputFormat, |
|
b2df90e…
|
leo
|
13 |
ProcessingStats, |
|
b2df90e…
|
leo
|
14 |
Relationship, |
|
b2df90e…
|
leo
|
15 |
ScreenCapture, |
|
b2df90e…
|
leo
|
16 |
TranscriptSegment, |
|
b2df90e…
|
leo
|
17 |
VideoManifest, |
|
b2df90e…
|
leo
|
18 |
VideoMetadata, |
|
b2df90e…
|
leo
|
19 |
) |
|
b2df90e…
|
leo
|
20 |
|
|
b2df90e…
|
leo
|
21 |
|
|
b2df90e…
|
leo
|
22 |
class TestEnums: |
|
b2df90e…
|
leo
|
23 |
def test_diagram_type_values(self): |
|
b2df90e…
|
leo
|
24 |
assert DiagramType.flowchart == "flowchart" |
|
b2df90e…
|
leo
|
25 |
assert DiagramType.unknown == "unknown" |
|
b2df90e…
|
leo
|
26 |
assert len(DiagramType) == 9 |
|
b2df90e…
|
leo
|
27 |
|
|
b2df90e…
|
leo
|
28 |
def test_output_format_values(self): |
|
b2df90e…
|
leo
|
29 |
assert OutputFormat.markdown == "markdown" |
|
b2df90e…
|
leo
|
30 |
assert OutputFormat.pdf == "pdf" |
|
c461c78…
|
noreply
|
31 |
assert len(OutputFormat) == 7 |
|
b2df90e…
|
leo
|
32 |
|
|
b2df90e…
|
leo
|
33 |
|
|
b2df90e…
|
leo
|
34 |
class TestTranscriptSegment: |
|
b2df90e…
|
leo
|
35 |
def test_basic(self): |
|
b2df90e…
|
leo
|
36 |
seg = TranscriptSegment(start=0.0, end=5.0, text="Hello world") |
|
b2df90e…
|
leo
|
37 |
assert seg.start == 0.0 |
|
b2df90e…
|
leo
|
38 |
assert seg.speaker is None |
|
b2df90e…
|
leo
|
39 |
|
|
b2df90e…
|
leo
|
40 |
def test_round_trip(self): |
|
b2df90e…
|
leo
|
41 |
seg = TranscriptSegment(start=1.0, end=2.0, text="Hi", speaker="Alice", confidence=0.95) |
|
b2df90e…
|
leo
|
42 |
restored = TranscriptSegment.model_validate_json(seg.model_dump_json()) |
|
b2df90e…
|
leo
|
43 |
assert restored == seg |
|
b2df90e…
|
leo
|
44 |
|
|
b2df90e…
|
leo
|
45 |
|
|
b2df90e…
|
leo
|
46 |
class TestActionItem: |
|
b2df90e…
|
leo
|
47 |
def test_minimal(self): |
|
b2df90e…
|
leo
|
48 |
item = ActionItem(action="Fix the bug") |
|
b2df90e…
|
leo
|
49 |
assert item.assignee is None |
|
b2df90e…
|
leo
|
50 |
assert item.priority is None |
|
b2df90e…
|
leo
|
51 |
|
|
b2df90e…
|
leo
|
52 |
def test_full(self): |
|
b2df90e…
|
leo
|
53 |
item = ActionItem( |
|
b2df90e…
|
leo
|
54 |
action="Deploy to prod", |
|
b2df90e…
|
leo
|
55 |
assignee="Bob", |
|
b2df90e…
|
leo
|
56 |
deadline="Friday", |
|
b2df90e…
|
leo
|
57 |
priority="high", |
|
b2df90e…
|
leo
|
58 |
context="After QA passes", |
|
b2df90e…
|
leo
|
59 |
source="transcript", |
|
b2df90e…
|
leo
|
60 |
) |
|
b2df90e…
|
leo
|
61 |
restored = ActionItem.model_validate_json(item.model_dump_json()) |
|
b2df90e…
|
leo
|
62 |
assert restored == item |
|
b2df90e…
|
leo
|
63 |
|
|
b2df90e…
|
leo
|
64 |
|
|
b2df90e…
|
leo
|
65 |
class TestKeyPoint: |
|
b2df90e…
|
leo
|
66 |
def test_with_related_diagrams(self): |
|
829e24a…
|
leo
|
67 |
kp = KeyPoint( |
|
829e24a…
|
leo
|
68 |
point="System uses microservices", topic="Architecture", related_diagrams=[0, 2] |
|
829e24a…
|
leo
|
69 |
) |
|
b2df90e…
|
leo
|
70 |
assert kp.related_diagrams == [0, 2] |
|
b2df90e…
|
leo
|
71 |
|
|
b2df90e…
|
leo
|
72 |
def test_round_trip(self): |
|
b2df90e…
|
leo
|
73 |
kp = KeyPoint(point="Test", details="Detail", timestamp=42.0, source="diagram") |
|
b2df90e…
|
leo
|
74 |
restored = KeyPoint.model_validate_json(kp.model_dump_json()) |
|
b2df90e…
|
leo
|
75 |
assert restored == kp |
|
b2df90e…
|
leo
|
76 |
|
|
b2df90e…
|
leo
|
77 |
|
|
b2df90e…
|
leo
|
78 |
class TestDiagramResult: |
|
b2df90e…
|
leo
|
79 |
def test_defaults(self): |
|
b2df90e…
|
leo
|
80 |
dr = DiagramResult(frame_index=0) |
|
b2df90e…
|
leo
|
81 |
assert dr.diagram_type == DiagramType.unknown |
|
b2df90e…
|
leo
|
82 |
assert dr.confidence == 0.0 |
|
b2df90e…
|
leo
|
83 |
assert dr.chart_data is None |
|
b2df90e…
|
leo
|
84 |
assert dr.elements == [] |
|
b2df90e…
|
leo
|
85 |
|
|
b2df90e…
|
leo
|
86 |
def test_chart_data(self): |
|
b2df90e…
|
leo
|
87 |
dr = DiagramResult( |
|
b2df90e…
|
leo
|
88 |
frame_index=5, |
|
b2df90e…
|
leo
|
89 |
diagram_type=DiagramType.chart, |
|
b2df90e…
|
leo
|
90 |
confidence=0.9, |
|
b2df90e…
|
leo
|
91 |
chart_data={"labels": ["A", "B"], "values": [10, 20], "chart_type": "bar"}, |
|
b2df90e…
|
leo
|
92 |
) |
|
b2df90e…
|
leo
|
93 |
restored = DiagramResult.model_validate_json(dr.model_dump_json()) |
|
b2df90e…
|
leo
|
94 |
assert restored.chart_data["chart_type"] == "bar" |
|
b2df90e…
|
leo
|
95 |
|
|
b2df90e…
|
leo
|
96 |
def test_full_round_trip(self): |
|
b2df90e…
|
leo
|
97 |
dr = DiagramResult( |
|
b2df90e…
|
leo
|
98 |
frame_index=3, |
|
b2df90e…
|
leo
|
99 |
timestamp=15.5, |
|
b2df90e…
|
leo
|
100 |
diagram_type=DiagramType.flowchart, |
|
b2df90e…
|
leo
|
101 |
confidence=0.85, |
|
b2df90e…
|
leo
|
102 |
description="Login flow", |
|
b2df90e…
|
leo
|
103 |
text_content="Start -> Auth -> Dashboard", |
|
b2df90e…
|
leo
|
104 |
elements=["Start", "Auth", "Dashboard"], |
|
b2df90e…
|
leo
|
105 |
relationships=["Start->Auth", "Auth->Dashboard"], |
|
b2df90e…
|
leo
|
106 |
mermaid="graph LR\n A-->B-->C", |
|
b2df90e…
|
leo
|
107 |
image_path="diagrams/diagram_3.jpg", |
|
b2df90e…
|
leo
|
108 |
svg_path="diagrams/diagram_3.svg", |
|
b2df90e…
|
leo
|
109 |
png_path="diagrams/diagram_3.png", |
|
b2df90e…
|
leo
|
110 |
mermaid_path="diagrams/diagram_3.mermaid", |
|
b2df90e…
|
leo
|
111 |
) |
|
b2df90e…
|
leo
|
112 |
restored = DiagramResult.model_validate_json(dr.model_dump_json()) |
|
b2df90e…
|
leo
|
113 |
assert restored == dr |
|
b2df90e…
|
leo
|
114 |
|
|
b2df90e…
|
leo
|
115 |
|
|
b2df90e…
|
leo
|
116 |
class TestScreenCapture: |
|
b2df90e…
|
leo
|
117 |
def test_basic(self): |
|
b2df90e…
|
leo
|
118 |
sc = ScreenCapture(frame_index=10, caption="Architecture overview slide", confidence=0.5) |
|
b2df90e…
|
leo
|
119 |
assert sc.image_path is None |
|
2a1b11a…
|
noreply
|
120 |
assert sc.content_type is None |
|
2a1b11a…
|
noreply
|
121 |
assert sc.text_content is None |
|
2a1b11a…
|
noreply
|
122 |
assert sc.entities == [] |
|
2a1b11a…
|
noreply
|
123 |
assert sc.topics == [] |
|
2a1b11a…
|
noreply
|
124 |
|
|
2a1b11a…
|
noreply
|
125 |
def test_with_extraction(self): |
|
2a1b11a…
|
noreply
|
126 |
sc = ScreenCapture( |
|
2a1b11a…
|
noreply
|
127 |
frame_index=5, |
|
2a1b11a…
|
noreply
|
128 |
caption="Code editor showing Python", |
|
2a1b11a…
|
noreply
|
129 |
confidence=0.5, |
|
2a1b11a…
|
noreply
|
130 |
content_type="code", |
|
2a1b11a…
|
noreply
|
131 |
text_content="def main():\n print('hello')", |
|
2a1b11a…
|
noreply
|
132 |
entities=["Python", "main function"], |
|
2a1b11a…
|
noreply
|
133 |
topics=["programming"], |
|
2a1b11a…
|
noreply
|
134 |
) |
|
2a1b11a…
|
noreply
|
135 |
assert sc.content_type == "code" |
|
2a1b11a…
|
noreply
|
136 |
assert "Python" in sc.entities |
|
2a1b11a…
|
noreply
|
137 |
assert sc.text_content is not None |
|
b2df90e…
|
leo
|
138 |
|
|
b2df90e…
|
leo
|
139 |
def test_round_trip(self): |
|
b2df90e…
|
leo
|
140 |
sc = ScreenCapture( |
|
829e24a…
|
leo
|
141 |
frame_index=7, |
|
829e24a…
|
leo
|
142 |
timestamp=30.0, |
|
829e24a…
|
leo
|
143 |
caption="Timeline", |
|
829e24a…
|
leo
|
144 |
image_path="captures/capture_0.jpg", |
|
829e24a…
|
leo
|
145 |
confidence=0.45, |
|
2a1b11a…
|
noreply
|
146 |
content_type="slide", |
|
2a1b11a…
|
noreply
|
147 |
text_content="Q4 Roadmap", |
|
2a1b11a…
|
noreply
|
148 |
entities=["Roadmap"], |
|
2a1b11a…
|
noreply
|
149 |
topics=["planning"], |
|
b2df90e…
|
leo
|
150 |
) |
|
b2df90e…
|
leo
|
151 |
restored = ScreenCapture.model_validate_json(sc.model_dump_json()) |
|
b2df90e…
|
leo
|
152 |
assert restored == sc |
|
b2df90e…
|
leo
|
153 |
|
|
b2df90e…
|
leo
|
154 |
|
|
b2df90e…
|
leo
|
155 |
class TestEntity: |
|
b2df90e…
|
leo
|
156 |
def test_defaults(self): |
|
b2df90e…
|
leo
|
157 |
e = Entity(name="Python") |
|
b2df90e…
|
leo
|
158 |
assert e.type == "concept" |
|
b2df90e…
|
leo
|
159 |
assert e.descriptions == [] |
|
b2df90e…
|
leo
|
160 |
assert e.occurrences == [] |
|
b2df90e…
|
leo
|
161 |
|
|
b2df90e…
|
leo
|
162 |
def test_round_trip(self): |
|
b2df90e…
|
leo
|
163 |
e = Entity( |
|
b2df90e…
|
leo
|
164 |
name="Alice", |
|
b2df90e…
|
leo
|
165 |
type="person", |
|
b2df90e…
|
leo
|
166 |
descriptions=["Team lead", "Engineer"], |
|
b2df90e…
|
leo
|
167 |
source="both", |
|
b2df90e…
|
leo
|
168 |
occurrences=[{"source": "transcript", "timestamp": 5.0, "text": "Alice said..."}], |
|
b2df90e…
|
leo
|
169 |
) |
|
b2df90e…
|
leo
|
170 |
restored = Entity.model_validate_json(e.model_dump_json()) |
|
b2df90e…
|
leo
|
171 |
assert restored == e |
|
b2df90e…
|
leo
|
172 |
|
|
b2df90e…
|
leo
|
173 |
|
|
b2df90e…
|
leo
|
174 |
class TestKnowledgeGraphData: |
|
b2df90e…
|
leo
|
175 |
def test_empty(self): |
|
b2df90e…
|
leo
|
176 |
kg = KnowledgeGraphData() |
|
b2df90e…
|
leo
|
177 |
assert kg.nodes == [] |
|
b2df90e…
|
leo
|
178 |
assert kg.relationships == [] |
|
b2df90e…
|
leo
|
179 |
|
|
b2df90e…
|
leo
|
180 |
def test_round_trip(self): |
|
b2df90e…
|
leo
|
181 |
kg = KnowledgeGraphData( |
|
b2df90e…
|
leo
|
182 |
nodes=[Entity(name="A"), Entity(name="B")], |
|
b2df90e…
|
leo
|
183 |
relationships=[Relationship(source="A", target="B", type="depends_on")], |
|
b2df90e…
|
leo
|
184 |
) |
|
b2df90e…
|
leo
|
185 |
restored = KnowledgeGraphData.model_validate_json(kg.model_dump_json()) |
|
b2df90e…
|
leo
|
186 |
assert len(restored.nodes) == 2 |
|
b2df90e…
|
leo
|
187 |
assert restored.relationships[0].type == "depends_on" |
|
b2df90e…
|
leo
|
188 |
|
|
b2df90e…
|
leo
|
189 |
|
|
b2df90e…
|
leo
|
190 |
class TestVideoManifest: |
|
b2df90e…
|
leo
|
191 |
def test_minimal(self): |
|
b2df90e…
|
leo
|
192 |
m = VideoManifest(video=VideoMetadata(title="Test Video")) |
|
b2df90e…
|
leo
|
193 |
assert m.version == "1.0" |
|
b2df90e…
|
leo
|
194 |
assert m.diagrams == [] |
|
b2df90e…
|
leo
|
195 |
assert m.screen_captures == [] |
|
b2df90e…
|
leo
|
196 |
assert m.stats.frames_extracted == 0 |
|
b2df90e…
|
leo
|
197 |
|
|
b2df90e…
|
leo
|
198 |
def test_full_round_trip(self): |
|
b2df90e…
|
leo
|
199 |
m = VideoManifest( |
|
829e24a…
|
leo
|
200 |
video=VideoMetadata( |
|
829e24a…
|
leo
|
201 |
title="Meeting", source_path="/tmp/video.mp4", duration_seconds=3600.0 |
|
829e24a…
|
leo
|
202 |
), |
|
b2df90e…
|
leo
|
203 |
stats=ProcessingStats( |
|
b2df90e…
|
leo
|
204 |
frames_extracted=50, |
|
b2df90e…
|
leo
|
205 |
diagrams_detected=3, |
|
b2df90e…
|
leo
|
206 |
screen_captures=2, |
|
b2df90e…
|
leo
|
207 |
models_used={"vision": "gpt-4o", "chat": "claude-sonnet-4-5"}, |
|
b2df90e…
|
leo
|
208 |
), |
|
b2df90e…
|
leo
|
209 |
transcript_json="transcript/transcript.json", |
|
b2df90e…
|
leo
|
210 |
analysis_md="results/analysis.md", |
|
b2df90e…
|
leo
|
211 |
key_points=[KeyPoint(point="Important thing")], |
|
b2df90e…
|
leo
|
212 |
action_items=[ActionItem(action="Do the thing")], |
|
b2df90e…
|
leo
|
213 |
diagrams=[DiagramResult(frame_index=0, confidence=0.9)], |
|
b2df90e…
|
leo
|
214 |
screen_captures=[ScreenCapture(frame_index=5, caption="Slide")], |
|
b2df90e…
|
leo
|
215 |
frame_paths=["frames/frame_0000.jpg", "frames/frame_0001.jpg"], |
|
b2df90e…
|
leo
|
216 |
) |
|
b2df90e…
|
leo
|
217 |
json_str = m.model_dump_json() |
|
b2df90e…
|
leo
|
218 |
restored = VideoManifest.model_validate_json(json_str) |
|
b2df90e…
|
leo
|
219 |
assert restored.video.title == "Meeting" |
|
b2df90e…
|
leo
|
220 |
assert len(restored.diagrams) == 1 |
|
b2df90e…
|
leo
|
221 |
assert len(restored.screen_captures) == 1 |
|
b2df90e…
|
leo
|
222 |
assert restored.stats.models_used["vision"] == "gpt-4o" |
|
b2df90e…
|
leo
|
223 |
|
|
b2df90e…
|
leo
|
224 |
|
|
b2df90e…
|
leo
|
225 |
class TestBatchManifest: |
|
b2df90e…
|
leo
|
226 |
def test_minimal(self): |
|
b2df90e…
|
leo
|
227 |
m = BatchManifest() |
|
b2df90e…
|
leo
|
228 |
assert m.total_videos == 0 |
|
b2df90e…
|
leo
|
229 |
assert m.videos == [] |
|
b2df90e…
|
leo
|
230 |
|
|
b2df90e…
|
leo
|
231 |
def test_full_round_trip(self): |
|
b2df90e…
|
leo
|
232 |
m = BatchManifest( |
|
b2df90e…
|
leo
|
233 |
title="Weekly Meetings", |
|
b2df90e…
|
leo
|
234 |
total_videos=3, |
|
b2df90e…
|
leo
|
235 |
completed_videos=2, |
|
b2df90e…
|
leo
|
236 |
failed_videos=1, |
|
b2df90e…
|
leo
|
237 |
total_diagrams=5, |
|
b2df90e…
|
leo
|
238 |
total_action_items=10, |
|
b2df90e…
|
leo
|
239 |
total_key_points=15, |
|
b2df90e…
|
leo
|
240 |
videos=[ |
|
b2df90e…
|
leo
|
241 |
BatchVideoEntry( |
|
b2df90e…
|
leo
|
242 |
video_name="meeting_1", |
|
b2df90e…
|
leo
|
243 |
manifest_path="videos/meeting_1/manifest.json", |
|
b2df90e…
|
leo
|
244 |
status="completed", |
|
b2df90e…
|
leo
|
245 |
diagrams_count=3, |
|
b2df90e…
|
leo
|
246 |
action_items_count=5, |
|
b2df90e…
|
leo
|
247 |
), |
|
b2df90e…
|
leo
|
248 |
BatchVideoEntry( |
|
b2df90e…
|
leo
|
249 |
video_name="meeting_2", |
|
b2df90e…
|
leo
|
250 |
manifest_path="videos/meeting_2/manifest.json", |
|
b2df90e…
|
leo
|
251 |
status="failed", |
|
b2df90e…
|
leo
|
252 |
error="Audio extraction failed", |
|
b2df90e…
|
leo
|
253 |
), |
|
b2df90e…
|
leo
|
254 |
], |
|
b2df90e…
|
leo
|
255 |
batch_summary_md="batch_summary.md", |
|
b2df90e…
|
leo
|
256 |
merged_knowledge_graph_json="knowledge_graph.json", |
|
b2df90e…
|
leo
|
257 |
) |
|
b2df90e…
|
leo
|
258 |
json_str = m.model_dump_json() |
|
b2df90e…
|
leo
|
259 |
restored = BatchManifest.model_validate_json(json_str) |
|
b2df90e…
|
leo
|
260 |
assert restored.total_videos == 3 |
|
b2df90e…
|
leo
|
261 |
assert restored.videos[1].status == "failed" |
|
b2df90e…
|
leo
|
262 |
assert restored.videos[1].error == "Audio extraction failed" |