chunker and ui
This commit is contained in:
149
tests/chunker/test_chunker.py
Normal file
149
tests/chunker/test_chunker.py
Normal file
@@ -0,0 +1,149 @@
|
||||
"""
|
||||
Tests for Chunker — time-based segmentation, chunk counts, sequence numbers, generator behavior.
|
||||
|
||||
Demonstrates: TDD (Interview Topic 8) — parametrized tests, edge cases, mocking.
|
||||
"""
|
||||
|
||||
from unittest.mock import patch, MagicMock
|
||||
|
||||
import pytest
|
||||
|
||||
from core.chunker import Chunker
|
||||
from core.chunker.exceptions import ChunkReadError
|
||||
|
||||
|
||||
def mock_probe(duration):
|
||||
"""Create a mock probe_file that returns the given duration."""
|
||||
result = MagicMock()
|
||||
result.duration = duration
|
||||
return result
|
||||
|
||||
|
||||
class TestChunker:
|
||||
@patch("core.chunker.chunker.probe_file")
|
||||
def test_basic_chunking(self, mock_pf, temp_file):
|
||||
"""File splits into expected number of time-based chunks."""
|
||||
path = temp_file(b"x" * 1000)
|
||||
mock_pf.return_value = mock_probe(30.0)
|
||||
|
||||
chunker = Chunker(path, chunk_duration=10.0)
|
||||
chunks = list(chunker.chunks())
|
||||
|
||||
assert len(chunks) == 3
|
||||
assert chunks[0].start_time == 0.0
|
||||
assert chunks[0].end_time == 10.0
|
||||
assert chunks[0].duration == 10.0
|
||||
assert chunks[1].start_time == 10.0
|
||||
assert chunks[2].start_time == 20.0
|
||||
|
||||
@patch("core.chunker.chunker.probe_file")
|
||||
def test_sequence_numbers(self, mock_pf, temp_file):
|
||||
"""Chunks have sequential sequence numbers starting at 0."""
|
||||
path = temp_file(b"x" * 100)
|
||||
mock_pf.return_value = mock_probe(40.0)
|
||||
|
||||
chunker = Chunker(path, chunk_duration=10.0)
|
||||
chunks = list(chunker.chunks())
|
||||
sequences = [c.sequence for c in chunks]
|
||||
|
||||
assert sequences == [0, 1, 2, 3]
|
||||
|
||||
@patch("core.chunker.chunker.probe_file")
|
||||
def test_time_ranges(self, mock_pf, temp_file):
|
||||
"""Each chunk has correct start_time and end_time."""
|
||||
path = temp_file(b"x" * 100)
|
||||
mock_pf.return_value = mock_probe(25.0)
|
||||
|
||||
chunker = Chunker(path, chunk_duration=10.0)
|
||||
chunks = list(chunker.chunks())
|
||||
|
||||
assert chunks[0].start_time == 0.0
|
||||
assert chunks[0].end_time == 10.0
|
||||
assert chunks[1].start_time == 10.0
|
||||
assert chunks[1].end_time == 20.0
|
||||
assert chunks[2].start_time == 20.0
|
||||
assert chunks[2].end_time == 25.0 # last chunk shorter
|
||||
assert chunks[2].duration == 5.0
|
||||
|
||||
@patch("core.chunker.chunker.probe_file")
|
||||
def test_expected_chunks_property(self, mock_pf, temp_file):
|
||||
"""expected_chunks calculates correctly before iteration."""
|
||||
path = temp_file(b"x" * 100)
|
||||
mock_pf.return_value = mock_probe(25.0)
|
||||
|
||||
chunker = Chunker(path, chunk_duration=10.0)
|
||||
assert chunker.expected_chunks == 3 # ceil(25/10)
|
||||
|
||||
@patch("core.chunker.chunker.probe_file")
|
||||
def test_source_path_on_chunks(self, mock_pf, temp_file):
|
||||
"""Each chunk carries the source file path."""
|
||||
path = temp_file(b"x" * 100)
|
||||
mock_pf.return_value = mock_probe(10.0)
|
||||
|
||||
chunker = Chunker(path, chunk_duration=10.0)
|
||||
chunks = list(chunker.chunks())
|
||||
|
||||
assert all(c.source_path == path for c in chunks)
|
||||
|
||||
def test_file_not_found(self):
|
||||
"""Non-existent file raises ChunkReadError."""
|
||||
with pytest.raises(ChunkReadError, match="File not found"):
|
||||
Chunker("/nonexistent/file.mp4")
|
||||
|
||||
@patch("core.chunker.chunker.probe_file")
|
||||
def test_invalid_chunk_duration(self, mock_pf, temp_file):
|
||||
"""Zero or negative chunk_duration raises ValueError."""
|
||||
path = temp_file(b"x" * 100)
|
||||
|
||||
with pytest.raises(ValueError, match="chunk_duration must be positive"):
|
||||
Chunker(path, chunk_duration=0)
|
||||
|
||||
with pytest.raises(ValueError, match="chunk_duration must be positive"):
|
||||
Chunker(path, chunk_duration=-1)
|
||||
|
||||
@patch("core.chunker.chunker.probe_file")
|
||||
def test_generator_laziness(self, mock_pf, temp_file):
|
||||
"""Chunks are yielded lazily, not pre-loaded."""
|
||||
path = temp_file(b"x" * 100)
|
||||
mock_pf.return_value = mock_probe(30.0)
|
||||
|
||||
chunker = Chunker(path, chunk_duration=10.0)
|
||||
gen = chunker.chunks()
|
||||
first = next(gen)
|
||||
assert first.sequence == 0
|
||||
# Generator is not exhausted — remaining chunks still pending
|
||||
|
||||
@pytest.mark.parametrize("duration,chunk_dur,expected", [
|
||||
(10.0, 10.0, 1),
|
||||
(10.1, 10.0, 2),
|
||||
(1.0, 1.0, 1),
|
||||
(100.0, 1.0, 100),
|
||||
(5.0, 100.0, 1),
|
||||
])
|
||||
@patch("core.chunker.chunker.probe_file")
|
||||
def test_expected_chunks_parametrized(self, mock_pf, temp_file, duration, chunk_dur, expected):
|
||||
"""Parametrized: various duration/chunk_duration combos."""
|
||||
path = temp_file(b"x" * 100)
|
||||
mock_pf.return_value = mock_probe(duration)
|
||||
chunker = Chunker(path, chunk_duration=chunk_dur)
|
||||
assert chunker.expected_chunks == expected
|
||||
|
||||
@patch("core.chunker.chunker.probe_file")
|
||||
def test_exact_multiple(self, mock_pf, temp_file):
|
||||
"""Duration exactly divisible by chunk_duration."""
|
||||
path = temp_file(b"x" * 100)
|
||||
mock_pf.return_value = mock_probe(30.0)
|
||||
|
||||
chunker = Chunker(path, chunk_duration=10.0)
|
||||
chunks = list(chunker.chunks())
|
||||
assert len(chunks) == 3
|
||||
assert all(c.duration == 10.0 for c in chunks)
|
||||
|
||||
@patch("core.chunker.chunker.probe_file")
|
||||
def test_probe_failure(self, mock_pf, temp_file):
|
||||
"""Probe failure raises ChunkReadError."""
|
||||
path = temp_file(b"x" * 100)
|
||||
mock_pf.side_effect = Exception("ffprobe failed")
|
||||
|
||||
with pytest.raises(ChunkReadError, match="Failed to probe"):
|
||||
Chunker(path, chunk_duration=10.0)
|
||||
Reference in New Issue
Block a user