""" Tests for Worker — processing, retry with backoff, error handling. Demonstrates: TDD (Interview Topic 8) — mocking processors, testing retry logic. """ from unittest.mock import MagicMock import pytest from core.chunker.models import Chunk, ChunkResult from core.chunker.processor import Processor from core.chunker.queue import ChunkQueue from core.chunker.worker import Worker class FailNTimesProcessor(Processor): """Test processor that fails N times then succeeds.""" def __init__(self, fail_count: int): self.fail_count = fail_count self.call_count = 0 def process(self, chunk: Chunk) -> ChunkResult: self.call_count += 1 if self.call_count <= self.fail_count: raise RuntimeError(f"Simulated failure #{self.call_count}") return ChunkResult( sequence=chunk.sequence, success=True, processing_time=0.001, ) class AlwaysFailProcessor(Processor): """Test processor that always fails.""" def process(self, chunk: Chunk) -> ChunkResult: raise RuntimeError("Always fails") class TestWorker: def test_processes_chunks(self, make_chunk): """Worker processes all chunks from queue.""" q = ChunkQueue(maxsize=5) for i in range(3): q.put(make_chunk(i)) q.close() from core.chunker.processor import ChecksumProcessor worker = Worker("w-0", q, ChecksumProcessor(), max_retries=0) results = worker.run() assert len(results) == 3 assert all(r.success for r in results) def test_retry_on_failure(self, make_chunk): """Worker retries on processor failure.""" q = ChunkQueue(maxsize=5) q.put(make_chunk(0)) q.close() proc = FailNTimesProcessor(fail_count=2) worker = Worker("w-0", q, proc, max_retries=3) results = worker.run() assert len(results) == 1 assert results[0].success is True assert results[0].retries == 2 assert proc.call_count == 3 # 2 failures + 1 success def test_max_retries_exceeded(self, make_chunk): """Worker gives up after max retries.""" q = ChunkQueue(maxsize=5) q.put(make_chunk(0)) q.close() worker = Worker("w-0", q, AlwaysFailProcessor(), max_retries=2) results = worker.run() assert len(results) == 1 assert results[0].success is False assert results[0].error is not None assert worker.error_count == 1 def test_worker_id_on_results(self, make_chunk): """Worker stamps its ID on results.""" q = ChunkQueue(maxsize=5) q.put(make_chunk(0)) q.close() from core.chunker.processor import ChecksumProcessor worker = Worker("worker-7", q, ChecksumProcessor()) results = worker.run() assert results[0].worker_id == "worker-7" def test_event_callback(self, make_chunk): """Worker emits events via callback.""" q = ChunkQueue(maxsize=5) q.put(make_chunk(0)) q.close() events = [] callback = MagicMock(side_effect=lambda t, d: events.append((t, d))) from core.chunker.processor import ChecksumProcessor worker = Worker("w-0", q, ChecksumProcessor(), event_callback=callback) worker.run() event_types = [e[0] for e in events] assert "worker_status" in event_types assert "chunk_processing" in event_types assert "chunk_done" in event_types def test_processed_count(self, make_chunk): """Worker tracks processed count.""" q = ChunkQueue(maxsize=10) for i in range(5): q.put(make_chunk(i)) q.close() from core.chunker.processor import ChecksumProcessor worker = Worker("w-0", q, ChecksumProcessor()) worker.run() assert worker.processed_count == 5