""" Tests for Pipeline — end-to-end orchestration, stats, error handling. Demonstrates: TDD (Interview Topic 8) — integration testing with mocked FFmpeg probe. """ from unittest.mock import MagicMock, patch import pytest from core.chunker import Pipeline from core.chunker.exceptions import PipelineError def mock_probe(duration): """Create a mock ProbeResult with the given duration.""" result = MagicMock() result.duration = duration return result class TestPipeline: @patch("core.chunker.chunker.probe_file") def test_end_to_end(self, mock_pf, temp_file): """Full pipeline processes a file successfully.""" path = temp_file(b"x" * 4096) mock_pf.return_value = mock_probe(40.0) result = Pipeline( source=path, chunk_duration=10.0, num_workers=2, processor_type="checksum", ).run() assert result.total_chunks == 4 assert result.processed == 4 assert result.failed == 0 assert result.elapsed_time > 0 assert result.chunks_in_order is True @patch("core.chunker.chunker.probe_file") def test_throughput_calculated(self, mock_pf, temp_file): """Pipeline calculates throughput.""" path = temp_file(b"x" * 10000) mock_pf.return_value = mock_probe(30.0) result = Pipeline(source=path, chunk_duration=10.0, num_workers=2).run() assert result.throughput_mbps > 0 @patch("core.chunker.chunker.probe_file") def test_worker_stats(self, mock_pf, temp_file): """Pipeline reports per-worker stats.""" path = temp_file(b"x" * 4000) mock_pf.return_value = mock_probe(40.0) result = Pipeline( source=path, chunk_duration=10.0, num_workers=2 ).run() assert len(result.worker_stats) == 2 for worker_id, stats in result.worker_stats.items(): assert "processed" in stats assert "errors" in stats def test_nonexistent_file(self): """Non-existent file raises PipelineError.""" with pytest.raises(PipelineError): Pipeline(source="/nonexistent/file.mp4").run() @patch("core.chunker.chunker.probe_file") def test_event_callback(self, mock_pf, temp_file): """Pipeline emits events through callback.""" path = temp_file(b"x" * 2048) mock_pf.return_value = mock_probe(20.0) events = [] def capture(event_type, data): events.append(event_type) Pipeline( source=path, chunk_duration=10.0, num_workers=1, event_callback=capture, ).run() assert "pipeline_start" in events assert "pipeline_complete" in events assert "chunk_queued" in events @patch("core.chunker.chunker.probe_file") def test_simulated_decode_processor(self, mock_pf, temp_file): """Pipeline works with simulated_decode processor.""" path = temp_file(b"x" * 2048) mock_pf.return_value = mock_probe(20.0) result = Pipeline( source=path, chunk_duration=10.0, num_workers=2, processor_type="simulated_decode", ).run() assert result.total_chunks == 2 assert result.failed == 0 @patch("core.chunker.chunker.probe_file") def test_single_chunk_file(self, mock_pf, temp_file): """Duration shorter than chunk_duration produces one chunk.""" path = temp_file(b"x" * 100) mock_pf.return_value = mock_probe(5.0) result = Pipeline(source=path, chunk_duration=10.0).run() assert result.total_chunks == 1 assert result.processed == 1 @patch("core.chunker.chunker.probe_file") def test_retries_tracked(self, mock_pf, temp_file): """Pipeline result tracks total retries.""" path = temp_file(b"x" * 2048) mock_pf.return_value = mock_probe(20.0) result = Pipeline(source=path, chunk_duration=10.0).run() assert result.retries >= 0 # Might be 0 if no failures @patch("core.chunker.chunker.probe_file") def test_output_dir_and_chunk_files(self, mock_pf, temp_file): """Pipeline tracks output_dir and chunk_files when set.""" path = temp_file(b"x" * 1024) mock_pf.return_value = mock_probe(10.0) result = Pipeline( source=path, chunk_duration=10.0, processor_type="checksum", ).run() # No output_dir set, so chunk_files should be empty assert result.output_dir is None assert result.chunk_files == []