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