PlanOpticon

planopticon / tests / test_pipeline.py
Source Blame History 534 lines
ccf32cc… leo 1 """Tests for the core video processing pipeline."""
ccf32cc… leo 2
ccf32cc… leo 3 import json
0981a08… noreply 4 from pathlib import Path
0981a08… noreply 5 from unittest.mock import MagicMock, patch
ccf32cc… leo 6
0981a08… noreply 7 import pytest
0981a08… noreply 8
0981a08… noreply 9 from video_processor.pipeline import (
0981a08… noreply 10 _extract_action_items,
0981a08… noreply 11 _extract_key_points,
0981a08… noreply 12 _format_srt_time,
0981a08… noreply 13 process_single_video,
0981a08… noreply 14 )
ccf32cc… leo 15
ccf32cc… leo 16
ccf32cc… leo 17 class TestFormatSrtTime:
ccf32cc… leo 18 def test_zero(self):
ccf32cc… leo 19 assert _format_srt_time(0) == "00:00:00,000"
ccf32cc… leo 20
ccf32cc… leo 21 def test_seconds(self):
ccf32cc… leo 22 assert _format_srt_time(5.5) == "00:00:05,500"
ccf32cc… leo 23
ccf32cc… leo 24 def test_minutes(self):
ccf32cc… leo 25 assert _format_srt_time(90.0) == "00:01:30,000"
ccf32cc… leo 26
ccf32cc… leo 27 def test_hours(self):
ccf32cc… leo 28 assert _format_srt_time(3661.123) == "01:01:01,123"
ccf32cc… leo 29
ccf32cc… leo 30 def test_large_value(self):
ccf32cc… leo 31 result = _format_srt_time(7200.0)
ccf32cc… leo 32 assert result == "02:00:00,000"
ccf32cc… leo 33
ccf32cc… leo 34
ccf32cc… leo 35 class TestExtractKeyPoints:
ccf32cc… leo 36 def test_parses_valid_response(self):
ccf32cc… leo 37 pm = MagicMock()
829e24a… leo 38 pm.chat.return_value = json.dumps(
829e24a… leo 39 [
829e24a… leo 40 {"point": "Main point", "topic": "Architecture", "details": "Some details"},
829e24a… leo 41 {"point": "Second point", "topic": None, "details": None},
829e24a… leo 42 ]
829e24a… leo 43 )
ccf32cc… leo 44 result = _extract_key_points(pm, "Some transcript text here")
ccf32cc… leo 45 assert len(result) == 2
ccf32cc… leo 46 assert result[0].point == "Main point"
ccf32cc… leo 47 assert result[0].topic == "Architecture"
ccf32cc… leo 48 assert result[1].point == "Second point"
ccf32cc… leo 49
ccf32cc… leo 50 def test_skips_invalid_items(self):
ccf32cc… leo 51 pm = MagicMock()
829e24a… leo 52 pm.chat.return_value = json.dumps(
829e24a… leo 53 [
829e24a… leo 54 {"point": "Valid", "topic": None},
829e24a… leo 55 {"topic": "No point field"},
829e24a… leo 56 {"point": "", "topic": "Empty point"},
829e24a… leo 57 ]
829e24a… leo 58 )
ccf32cc… leo 59 result = _extract_key_points(pm, "text")
ccf32cc… leo 60 assert len(result) == 1
ccf32cc… leo 61 assert result[0].point == "Valid"
ccf32cc… leo 62
ccf32cc… leo 63 def test_handles_error(self):
ccf32cc… leo 64 pm = MagicMock()
ccf32cc… leo 65 pm.chat.side_effect = Exception("API error")
ccf32cc… leo 66 result = _extract_key_points(pm, "text")
ccf32cc… leo 67 assert result == []
ccf32cc… leo 68
ccf32cc… leo 69 def test_handles_non_list_response(self):
ccf32cc… leo 70 pm = MagicMock()
ccf32cc… leo 71 pm.chat.return_value = '{"not": "a list"}'
ccf32cc… leo 72 result = _extract_key_points(pm, "text")
ccf32cc… leo 73 assert result == []
ccf32cc… leo 74
ccf32cc… leo 75
ccf32cc… leo 76 class TestExtractActionItems:
ccf32cc… leo 77 def test_parses_valid_response(self):
ccf32cc… leo 78 pm = MagicMock()
829e24a… leo 79 pm.chat.return_value = json.dumps(
829e24a… leo 80 [
829e24a… leo 81 {
829e24a… leo 82 "action": "Deploy fix",
829e24a… leo 83 "assignee": "Bob",
829e24a… leo 84 "deadline": "Friday",
829e24a… leo 85 "priority": "high",
829e24a… leo 86 "context": "Production",
829e24a… leo 87 },
829e24a… leo 88 ]
829e24a… leo 89 )
ccf32cc… leo 90 result = _extract_action_items(pm, "Some transcript text")
ccf32cc… leo 91 assert len(result) == 1
ccf32cc… leo 92 assert result[0].action == "Deploy fix"
ccf32cc… leo 93 assert result[0].assignee == "Bob"
ccf32cc… leo 94
ccf32cc… leo 95 def test_skips_invalid_items(self):
ccf32cc… leo 96 pm = MagicMock()
829e24a… leo 97 pm.chat.return_value = json.dumps(
829e24a… leo 98 [
829e24a… leo 99 {"action": "Valid action"},
829e24a… leo 100 {"assignee": "No action field"},
829e24a… leo 101 {"action": ""},
829e24a… leo 102 ]
829e24a… leo 103 )
ccf32cc… leo 104 result = _extract_action_items(pm, "text")
ccf32cc… leo 105 assert len(result) == 1
ccf32cc… leo 106
ccf32cc… leo 107 def test_handles_error(self):
ccf32cc… leo 108 pm = MagicMock()
ccf32cc… leo 109 pm.chat.side_effect = Exception("API down")
ccf32cc… leo 110 result = _extract_action_items(pm, "text")
ccf32cc… leo 111 assert result == []
0981a08… noreply 112
0981a08… noreply 113
0981a08… noreply 114 # ---------------------------------------------------------------------------
0981a08… noreply 115 # process_single_video tests (heavily mocked)
0981a08… noreply 116 # ---------------------------------------------------------------------------
0981a08… noreply 117
0981a08… noreply 118
0981a08… noreply 119 def _make_mock_pm():
0981a08… noreply 120 """Build a mock ProviderManager with usage tracker and predictable responses."""
0981a08… noreply 121 pm = MagicMock()
0981a08… noreply 122
0981a08… noreply 123 # Usage tracker stub
0981a08… noreply 124 pm.usage = MagicMock()
0981a08… noreply 125 pm.usage.start_step = MagicMock()
0981a08… noreply 126 pm.usage.end_step = MagicMock()
0981a08… noreply 127
0981a08… noreply 128 # transcribe_audio returns a simple transcript
0981a08… noreply 129 pm.transcribe_audio.return_value = {
0981a08… noreply 130 "text": "Alice discussed the Python deployment strategy with Bob.",
0981a08… noreply 131 "segments": [
0981a08… noreply 132 {"start": 0.0, "end": 5.0, "text": "Alice discussed the Python deployment strategy."},
0981a08… noreply 133 {"start": 5.0, "end": 10.0, "text": "Bob agreed on the timeline."},
0981a08… noreply 134 ],
0981a08… noreply 135 "duration": 10.0,
0981a08… noreply 136 "language": "en",
0981a08… noreply 137 "provider": "mock",
0981a08… noreply 138 "model": "mock-whisper",
0981a08… noreply 139 }
0981a08… noreply 140
0981a08… noreply 141 # chat returns predictable JSON depending on the call
0981a08… noreply 142 def _chat_side_effect(messages, **kwargs):
0981a08… noreply 143 content = messages[0]["content"] if messages else ""
0981a08… noreply 144 if "key points" in content.lower():
0981a08… noreply 145 return json.dumps(
0981a08… noreply 146 [{"point": "Deployment strategy discussed", "topic": "DevOps", "details": "Python"}]
0981a08… noreply 147 )
0981a08… noreply 148 if "action items" in content.lower():
0981a08… noreply 149 return json.dumps(
0981a08… noreply 150 [{"action": "Deploy to production", "assignee": "Bob", "priority": "high"}]
0981a08… noreply 151 )
0981a08… noreply 152 # Default: entity extraction for knowledge graph
0981a08… noreply 153 return json.dumps(
0981a08… noreply 154 {
0981a08… noreply 155 "entities": [
0981a08… noreply 156 {"name": "Python", "type": "technology", "description": "Programming language"},
0981a08… noreply 157 {"name": "Alice", "type": "person", "description": "Engineer"},
0981a08… noreply 158 ],
0981a08… noreply 159 "relationships": [
0981a08… noreply 160 {"source": "Alice", "target": "Python", "type": "uses"},
0981a08… noreply 161 ],
0981a08… noreply 162 }
0981a08… noreply 163 )
0981a08… noreply 164
0981a08… noreply 165 pm.chat.side_effect = _chat_side_effect
0981a08… noreply 166 pm.get_models_used.return_value = {"chat": "mock-gpt", "transcription": "mock-whisper"}
0981a08… noreply 167 return pm
0981a08… noreply 168
0981a08… noreply 169
0981a08… noreply 170 def _make_tqdm_passthrough(mock_tqdm):
0981a08… noreply 171 """Configure mock tqdm to pass through iterables while supporting .set_description() etc."""
0981a08… noreply 172
0981a08… noreply 173 def _tqdm_side_effect(iterable, **kw):
0981a08… noreply 174 wrapper = MagicMock()
0981a08… noreply 175 wrapper.__iter__ = lambda self: iter(iterable)
0981a08… noreply 176 return wrapper
0981a08… noreply 177
0981a08… noreply 178 mock_tqdm.side_effect = _tqdm_side_effect
0981a08… noreply 179
0981a08… noreply 180
0981a08… noreply 181 def _create_fake_video(path: Path) -> Path:
0981a08… noreply 182 """Create a tiny file that stands in for a video (all extractors are mocked)."""
0981a08… noreply 183 path.parent.mkdir(parents=True, exist_ok=True)
0981a08… noreply 184 path.write_bytes(b"\x00" * 64)
0981a08… noreply 185 return path
0981a08… noreply 186
0981a08… noreply 187
0981a08… noreply 188 class TestProcessSingleVideo:
0981a08… noreply 189 """Integration-level tests for process_single_video with heavy mocking."""
0981a08… noreply 190
0981a08… noreply 191 @pytest.fixture
0981a08… noreply 192 def setup(self, tmp_path):
0981a08… noreply 193 """Create fake video, output dir, and mock PM."""
0981a08… noreply 194 video_path = _create_fake_video(tmp_path / "input" / "meeting.mp4")
0981a08… noreply 195 output_dir = tmp_path / "output"
0981a08… noreply 196 pm = _make_mock_pm()
0981a08… noreply 197 return video_path, output_dir, pm
0981a08… noreply 198
0981a08… noreply 199 @patch("video_processor.pipeline.export_all_formats")
0981a08… noreply 200 @patch("video_processor.pipeline.PlanGenerator")
0981a08… noreply 201 @patch("video_processor.pipeline.DiagramAnalyzer")
0981a08… noreply 202 @patch("video_processor.pipeline.AudioExtractor")
0981a08… noreply 203 @patch("video_processor.pipeline.filter_people_frames")
0981a08… noreply 204 @patch("video_processor.pipeline.save_frames")
0981a08… noreply 205 @patch("video_processor.pipeline.extract_frames")
0981a08… noreply 206 @patch("video_processor.pipeline.tqdm")
0981a08… noreply 207 def test_returns_manifest(
0981a08… noreply 208 self,
0981a08… noreply 209 mock_tqdm,
0981a08… noreply 210 mock_extract_frames,
0981a08… noreply 211 mock_save_frames,
0981a08… noreply 212 mock_filter_people,
0981a08… noreply 213 mock_audio_extractor_cls,
0981a08… noreply 214 mock_diagram_analyzer_cls,
0981a08… noreply 215 mock_plan_gen_cls,
0981a08… noreply 216 mock_export,
0981a08… noreply 217 setup,
0981a08… noreply 218 ):
0981a08… noreply 219 video_path, output_dir, pm = setup
0981a08… noreply 220
0981a08… noreply 221 # tqdm pass-through
0981a08… noreply 222 _make_tqdm_passthrough(mock_tqdm)
0981a08… noreply 223
0981a08… noreply 224 # Frame extraction mocks
0981a08… noreply 225 mock_extract_frames.return_value = [b"fake_frame_1", b"fake_frame_2"]
0981a08… noreply 226 mock_filter_people.return_value = ([b"fake_frame_1", b"fake_frame_2"], 0)
0981a08… noreply 227
0981a08… noreply 228 frames_dir = output_dir / "frames"
0981a08… noreply 229 frames_dir.mkdir(parents=True, exist_ok=True)
0981a08… noreply 230 frame_paths = []
0981a08… noreply 231 for i in range(2):
0981a08… noreply 232 fp = frames_dir / f"frame_{i:04d}.jpg"
0981a08… noreply 233 fp.write_bytes(b"\xff")
0981a08… noreply 234 frame_paths.append(fp)
0981a08… noreply 235 mock_save_frames.return_value = frame_paths
0981a08… noreply 236
0981a08… noreply 237 # Audio extractor mock
0981a08… noreply 238 audio_ext = MagicMock()
0981a08… noreply 239 audio_ext.extract_audio.return_value = output_dir / "audio" / "meeting.wav"
0981a08… noreply 240 audio_ext.get_audio_properties.return_value = {"duration": 10.0}
0981a08… noreply 241 mock_audio_extractor_cls.return_value = audio_ext
0981a08… noreply 242
0981a08… noreply 243 # Diagram analyzer mock
0981a08… noreply 244 diag_analyzer = MagicMock()
0981a08… noreply 245 diag_analyzer.process_frames.return_value = ([], [])
0981a08… noreply 246 mock_diagram_analyzer_cls.return_value = diag_analyzer
0981a08… noreply 247
0981a08… noreply 248 # Plan generator mock
0981a08… noreply 249 plan_gen = MagicMock()
0981a08… noreply 250 mock_plan_gen_cls.return_value = plan_gen
0981a08… noreply 251
0981a08… noreply 252 # export_all_formats returns the manifest it receives
0981a08… noreply 253 mock_export.side_effect = lambda out_dir, manifest: manifest
0981a08… noreply 254
0981a08… noreply 255 manifest = process_single_video(
0981a08… noreply 256 input_path=video_path,
0981a08… noreply 257 output_dir=output_dir,
0981a08… noreply 258 provider_manager=pm,
0981a08… noreply 259 depth="standard",
0981a08… noreply 260 )
0981a08… noreply 261
0981a08… noreply 262 from video_processor.models import VideoManifest
0981a08… noreply 263
0981a08… noreply 264 assert isinstance(manifest, VideoManifest)
0981a08… noreply 265 assert manifest.video.title == "Analysis of meeting"
0981a08… noreply 266 assert manifest.stats.frames_extracted == 2
0981a08… noreply 267 assert manifest.transcript_json == "transcript/transcript.json"
0981a08… noreply 268 assert manifest.knowledge_graph_json == "results/knowledge_graph.json"
0981a08… noreply 269
0981a08… noreply 270 @patch("video_processor.pipeline.export_all_formats")
0981a08… noreply 271 @patch("video_processor.pipeline.PlanGenerator")
0981a08… noreply 272 @patch("video_processor.pipeline.DiagramAnalyzer")
0981a08… noreply 273 @patch("video_processor.pipeline.AudioExtractor")
0981a08… noreply 274 @patch("video_processor.pipeline.filter_people_frames")
0981a08… noreply 275 @patch("video_processor.pipeline.save_frames")
0981a08… noreply 276 @patch("video_processor.pipeline.extract_frames")
0981a08… noreply 277 @patch("video_processor.pipeline.tqdm")
0981a08… noreply 278 def test_creates_output_directories(
0981a08… noreply 279 self,
0981a08… noreply 280 mock_tqdm,
0981a08… noreply 281 mock_extract_frames,
0981a08… noreply 282 mock_save_frames,
0981a08… noreply 283 mock_filter_people,
0981a08… noreply 284 mock_audio_extractor_cls,
0981a08… noreply 285 mock_diagram_analyzer_cls,
0981a08… noreply 286 mock_plan_gen_cls,
0981a08… noreply 287 mock_export,
0981a08… noreply 288 setup,
0981a08… noreply 289 ):
0981a08… noreply 290 video_path, output_dir, pm = setup
0981a08… noreply 291
0981a08… noreply 292 _make_tqdm_passthrough(mock_tqdm)
0981a08… noreply 293 mock_extract_frames.return_value = []
0981a08… noreply 294 mock_filter_people.return_value = ([], 0)
0981a08… noreply 295 mock_save_frames.return_value = []
0981a08… noreply 296
0981a08… noreply 297 audio_ext = MagicMock()
0981a08… noreply 298 audio_ext.extract_audio.return_value = output_dir / "audio" / "meeting.wav"
0981a08… noreply 299 audio_ext.get_audio_properties.return_value = {"duration": 5.0}
0981a08… noreply 300 mock_audio_extractor_cls.return_value = audio_ext
0981a08… noreply 301
0981a08… noreply 302 diag_analyzer = MagicMock()
0981a08… noreply 303 diag_analyzer.process_frames.return_value = ([], [])
0981a08… noreply 304 mock_diagram_analyzer_cls.return_value = diag_analyzer
0981a08… noreply 305
0981a08… noreply 306 plan_gen = MagicMock()
0981a08… noreply 307 mock_plan_gen_cls.return_value = plan_gen
0981a08… noreply 308
0981a08… noreply 309 mock_export.side_effect = lambda out_dir, manifest: manifest
0981a08… noreply 310
0981a08… noreply 311 process_single_video(
0981a08… noreply 312 input_path=video_path,
0981a08… noreply 313 output_dir=output_dir,
0981a08… noreply 314 provider_manager=pm,
0981a08… noreply 315 )
0981a08… noreply 316
0981a08… noreply 317 # Verify standard output directories were created
0981a08… noreply 318 assert (output_dir / "transcript").is_dir()
0981a08… noreply 319 assert (output_dir / "frames").is_dir()
0981a08… noreply 320 assert (output_dir / "results").is_dir()
0981a08… noreply 321
0981a08… noreply 322 @patch("video_processor.pipeline.export_all_formats")
0981a08… noreply 323 @patch("video_processor.pipeline.PlanGenerator")
0981a08… noreply 324 @patch("video_processor.pipeline.DiagramAnalyzer")
0981a08… noreply 325 @patch("video_processor.pipeline.AudioExtractor")
0981a08… noreply 326 @patch("video_processor.pipeline.filter_people_frames")
0981a08… noreply 327 @patch("video_processor.pipeline.save_frames")
0981a08… noreply 328 @patch("video_processor.pipeline.extract_frames")
0981a08… noreply 329 @patch("video_processor.pipeline.tqdm")
0981a08… noreply 330 def test_resume_existing_frames(
0981a08… noreply 331 self,
0981a08… noreply 332 mock_tqdm,
0981a08… noreply 333 mock_extract_frames,
0981a08… noreply 334 mock_save_frames,
0981a08… noreply 335 mock_filter_people,
0981a08… noreply 336 mock_audio_extractor_cls,
0981a08… noreply 337 mock_diagram_analyzer_cls,
0981a08… noreply 338 mock_plan_gen_cls,
0981a08… noreply 339 mock_export,
0981a08… noreply 340 setup,
0981a08… noreply 341 ):
0981a08… noreply 342 """When frames already exist on disk, extraction should be skipped."""
0981a08… noreply 343 video_path, output_dir, pm = setup
0981a08… noreply 344
0981a08… noreply 345 _make_tqdm_passthrough(mock_tqdm)
0981a08… noreply 346
0981a08… noreply 347 # Pre-create frames directory with existing frames
0981a08… noreply 348 frames_dir = output_dir / "frames"
0981a08… noreply 349 frames_dir.mkdir(parents=True, exist_ok=True)
0981a08… noreply 350 for i in range(3):
0981a08… noreply 351 (frames_dir / f"frame_{i:04d}.jpg").write_bytes(b"\xff")
0981a08… noreply 352
0981a08… noreply 353 audio_ext = MagicMock()
0981a08… noreply 354 audio_ext.extract_audio.return_value = output_dir / "audio" / "meeting.wav"
0981a08… noreply 355 audio_ext.get_audio_properties.return_value = {"duration": 10.0}
0981a08… noreply 356 mock_audio_extractor_cls.return_value = audio_ext
0981a08… noreply 357
0981a08… noreply 358 diag_analyzer = MagicMock()
0981a08… noreply 359 diag_analyzer.process_frames.return_value = ([], [])
0981a08… noreply 360 mock_diagram_analyzer_cls.return_value = diag_analyzer
0981a08… noreply 361
0981a08… noreply 362 plan_gen = MagicMock()
0981a08… noreply 363 mock_plan_gen_cls.return_value = plan_gen
0981a08… noreply 364 mock_export.side_effect = lambda out_dir, manifest: manifest
0981a08… noreply 365
0981a08… noreply 366 manifest = process_single_video(
0981a08… noreply 367 input_path=video_path,
0981a08… noreply 368 output_dir=output_dir,
0981a08… noreply 369 provider_manager=pm,
0981a08… noreply 370 )
0981a08… noreply 371
0981a08… noreply 372 # extract_frames should NOT have been called (resume path)
0981a08… noreply 373 mock_extract_frames.assert_not_called()
0981a08… noreply 374 assert manifest.stats.frames_extracted == 3
0981a08… noreply 375
0981a08… noreply 376 @patch("video_processor.pipeline.export_all_formats")
0981a08… noreply 377 @patch("video_processor.pipeline.PlanGenerator")
0981a08… noreply 378 @patch("video_processor.pipeline.DiagramAnalyzer")
0981a08… noreply 379 @patch("video_processor.pipeline.AudioExtractor")
0981a08… noreply 380 @patch("video_processor.pipeline.filter_people_frames")
0981a08… noreply 381 @patch("video_processor.pipeline.save_frames")
0981a08… noreply 382 @patch("video_processor.pipeline.extract_frames")
0981a08… noreply 383 @patch("video_processor.pipeline.tqdm")
0981a08… noreply 384 def test_resume_existing_transcript(
0981a08… noreply 385 self,
0981a08… noreply 386 mock_tqdm,
0981a08… noreply 387 mock_extract_frames,
0981a08… noreply 388 mock_save_frames,
0981a08… noreply 389 mock_filter_people,
0981a08… noreply 390 mock_audio_extractor_cls,
0981a08… noreply 391 mock_diagram_analyzer_cls,
0981a08… noreply 392 mock_plan_gen_cls,
0981a08… noreply 393 mock_export,
0981a08… noreply 394 setup,
0981a08… noreply 395 ):
0981a08… noreply 396 """When transcript exists on disk, transcription should be skipped."""
0981a08… noreply 397 video_path, output_dir, pm = setup
0981a08… noreply 398
0981a08… noreply 399 _make_tqdm_passthrough(mock_tqdm)
0981a08… noreply 400 mock_extract_frames.return_value = []
0981a08… noreply 401 mock_filter_people.return_value = ([], 0)
0981a08… noreply 402 mock_save_frames.return_value = []
0981a08… noreply 403
0981a08… noreply 404 audio_ext = MagicMock()
0981a08… noreply 405 audio_ext.extract_audio.return_value = output_dir / "audio" / "meeting.wav"
0981a08… noreply 406 audio_ext.get_audio_properties.return_value = {"duration": 10.0}
0981a08… noreply 407 mock_audio_extractor_cls.return_value = audio_ext
0981a08… noreply 408
0981a08… noreply 409 # Pre-create transcript file
0981a08… noreply 410 transcript_dir = output_dir / "transcript"
0981a08… noreply 411 transcript_dir.mkdir(parents=True, exist_ok=True)
0981a08… noreply 412 transcript_data = {
0981a08… noreply 413 "text": "Pre-existing transcript text.",
0981a08… noreply 414 "segments": [{"start": 0.0, "end": 5.0, "text": "Pre-existing transcript text."}],
0981a08… noreply 415 "duration": 5.0,
0981a08… noreply 416 }
0981a08… noreply 417 (transcript_dir / "transcript.json").write_text(json.dumps(transcript_data))
0981a08… noreply 418
0981a08… noreply 419 diag_analyzer = MagicMock()
0981a08… noreply 420 diag_analyzer.process_frames.return_value = ([], [])
0981a08… noreply 421 mock_diagram_analyzer_cls.return_value = diag_analyzer
0981a08… noreply 422
0981a08… noreply 423 plan_gen = MagicMock()
0981a08… noreply 424 mock_plan_gen_cls.return_value = plan_gen
0981a08… noreply 425 mock_export.side_effect = lambda out_dir, manifest: manifest
0981a08… noreply 426
0981a08… noreply 427 process_single_video(
0981a08… noreply 428 input_path=video_path,
0981a08… noreply 429 output_dir=output_dir,
0981a08… noreply 430 provider_manager=pm,
0981a08… noreply 431 )
0981a08… noreply 432
0981a08… noreply 433 # transcribe_audio should NOT have been called (resume path)
0981a08… noreply 434 pm.transcribe_audio.assert_not_called()
0981a08… noreply 435
0981a08… noreply 436 @patch("video_processor.pipeline.export_all_formats")
0981a08… noreply 437 @patch("video_processor.pipeline.PlanGenerator")
0981a08… noreply 438 @patch("video_processor.pipeline.DiagramAnalyzer")
0981a08… noreply 439 @patch("video_processor.pipeline.AudioExtractor")
0981a08… noreply 440 @patch("video_processor.pipeline.filter_people_frames")
0981a08… noreply 441 @patch("video_processor.pipeline.save_frames")
0981a08… noreply 442 @patch("video_processor.pipeline.extract_frames")
0981a08… noreply 443 @patch("video_processor.pipeline.tqdm")
0981a08… noreply 444 def test_custom_title(
0981a08… noreply 445 self,
0981a08… noreply 446 mock_tqdm,
0981a08… noreply 447 mock_extract_frames,
0981a08… noreply 448 mock_save_frames,
0981a08… noreply 449 mock_filter_people,
0981a08… noreply 450 mock_audio_extractor_cls,
0981a08… noreply 451 mock_diagram_analyzer_cls,
0981a08… noreply 452 mock_plan_gen_cls,
0981a08… noreply 453 mock_export,
0981a08… noreply 454 setup,
0981a08… noreply 455 ):
0981a08… noreply 456 video_path, output_dir, pm = setup
0981a08… noreply 457
0981a08… noreply 458 _make_tqdm_passthrough(mock_tqdm)
0981a08… noreply 459 mock_extract_frames.return_value = []
0981a08… noreply 460 mock_filter_people.return_value = ([], 0)
0981a08… noreply 461 mock_save_frames.return_value = []
0981a08… noreply 462
0981a08… noreply 463 audio_ext = MagicMock()
0981a08… noreply 464 audio_ext.extract_audio.return_value = output_dir / "audio" / "meeting.wav"
0981a08… noreply 465 audio_ext.get_audio_properties.return_value = {"duration": 5.0}
0981a08… noreply 466 mock_audio_extractor_cls.return_value = audio_ext
0981a08… noreply 467
0981a08… noreply 468 diag_analyzer = MagicMock()
0981a08… noreply 469 diag_analyzer.process_frames.return_value = ([], [])
0981a08… noreply 470 mock_diagram_analyzer_cls.return_value = diag_analyzer
0981a08… noreply 471
0981a08… noreply 472 plan_gen = MagicMock()
0981a08… noreply 473 mock_plan_gen_cls.return_value = plan_gen
0981a08… noreply 474 mock_export.side_effect = lambda out_dir, manifest: manifest
0981a08… noreply 475
0981a08… noreply 476 manifest = process_single_video(
0981a08… noreply 477 input_path=video_path,
0981a08… noreply 478 output_dir=output_dir,
0981a08… noreply 479 provider_manager=pm,
0981a08… noreply 480 title="My Custom Title",
0981a08… noreply 481 )
0981a08… noreply 482
0981a08… noreply 483 assert manifest.video.title == "My Custom Title"
0981a08… noreply 484
0981a08… noreply 485 @patch("video_processor.pipeline.export_all_formats")
0981a08… noreply 486 @patch("video_processor.pipeline.PlanGenerator")
0981a08… noreply 487 @patch("video_processor.pipeline.DiagramAnalyzer")
0981a08… noreply 488 @patch("video_processor.pipeline.AudioExtractor")
0981a08… noreply 489 @patch("video_processor.pipeline.filter_people_frames")
0981a08… noreply 490 @patch("video_processor.pipeline.save_frames")
0981a08… noreply 491 @patch("video_processor.pipeline.extract_frames")
0981a08… noreply 492 @patch("video_processor.pipeline.tqdm")
0981a08… noreply 493 def test_key_points_and_action_items_extracted(
0981a08… noreply 494 self,
0981a08… noreply 495 mock_tqdm,
0981a08… noreply 496 mock_extract_frames,
0981a08… noreply 497 mock_save_frames,
0981a08… noreply 498 mock_filter_people,
0981a08… noreply 499 mock_audio_extractor_cls,
0981a08… noreply 500 mock_diagram_analyzer_cls,
0981a08… noreply 501 mock_plan_gen_cls,
0981a08… noreply 502 mock_export,
0981a08… noreply 503 setup,
0981a08… noreply 504 ):
0981a08… noreply 505 video_path, output_dir, pm = setup
0981a08… noreply 506
0981a08… noreply 507 _make_tqdm_passthrough(mock_tqdm)
0981a08… noreply 508 mock_extract_frames.return_value = []
0981a08… noreply 509 mock_filter_people.return_value = ([], 0)
0981a08… noreply 510 mock_save_frames.return_value = []
0981a08… noreply 511
0981a08… noreply 512 audio_ext = MagicMock()
0981a08… noreply 513 audio_ext.extract_audio.return_value = output_dir / "audio" / "meeting.wav"
0981a08… noreply 514 audio_ext.get_audio_properties.return_value = {"duration": 10.0}
0981a08… noreply 515 mock_audio_extractor_cls.return_value = audio_ext
0981a08… noreply 516
0981a08… noreply 517 diag_analyzer = MagicMock()
0981a08… noreply 518 diag_analyzer.process_frames.return_value = ([], [])
0981a08… noreply 519 mock_diagram_analyzer_cls.return_value = diag_analyzer
0981a08… noreply 520
0981a08… noreply 521 plan_gen = MagicMock()
0981a08… noreply 522 mock_plan_gen_cls.return_value = plan_gen
0981a08… noreply 523 mock_export.side_effect = lambda out_dir, manifest: manifest
0981a08… noreply 524
0981a08… noreply 525 manifest = process_single_video(
0981a08… noreply 526 input_path=video_path,
0981a08… noreply 527 output_dir=output_dir,
0981a08… noreply 528 provider_manager=pm,
0981a08… noreply 529 )
0981a08… noreply 530
0981a08… noreply 531 assert len(manifest.key_points) == 1
0981a08… noreply 532 assert manifest.key_points[0].point == "Deployment strategy discussed"
0981a08… noreply 533 assert len(manifest.action_items) == 1
0981a08… noreply 534 assert manifest.action_items[0].action == "Deploy to production"

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button