| from pathlib import Path | |
| from backend.render_pipeline import RenderPipeline | |
| class FakeSynthesizer: | |
| def __init__(self) -> None: | |
| self.calls = [] | |
| def synthesize(self, *, text: str, output_path: Path, **kwargs) -> dict: | |
| self.calls.append({"text": text, "output_path": output_path, **kwargs}) | |
| output_path.write_bytes(b"RIFFfakewave") | |
| return { | |
| "duration_seconds": max(1, len(text.split()) // 2), | |
| "sample_rate": 24000, | |
| "backend": "local", | |
| "model": kwargs["voice_config"].model, | |
| } | |
| def _chapters() -> list: | |
| return [ | |
| {"id": "c1", "title": "One", "text": "hello world " * 20, "included": True, "est_minutes": 1}, | |
| {"id": "c2", "title": "Two", "text": "next chapter " * 20, "included": True, "est_minutes": 1}, | |
| ] | |
| def test_render_pipeline_streams_ordered_progress_events(tmp_path: Path) -> None: | |
| synthesizer = FakeSynthesizer() | |
| pipeline = RenderPipeline(session_root=tmp_path, synthesizer=synthesizer) | |
| events = list( | |
| pipeline.render( | |
| session_id="session-a", | |
| book={"title": "Book"}, | |
| chapters=_chapters(), | |
| voice_config={"mode": "auto"}, | |
| diffusion_steps=32, | |
| speed=1.0, | |
| ) | |
| ) | |
| event_types = [event["type"] for event in events] | |
| assert event_types[0] == "started" | |
| assert "chapter_started" in event_types | |
| assert "chapter_done" in event_types | |
| assert event_types[-1] == "completed" | |
| assert len(synthesizer.calls) == 2 | |
| assert all(event["backend"] == "local" for event in events) | |
| assert all(event["model"] == "omnivoice" for event in events) | |
| def test_render_pipeline_can_be_cancelled(tmp_path: Path) -> None: | |
| synthesizer = FakeSynthesizer() | |
| pipeline = RenderPipeline(session_root=tmp_path, synthesizer=synthesizer) | |
| iterator = pipeline.render( | |
| session_id="session-a", | |
| book={"title": "Book"}, | |
| chapters=_chapters(), | |
| voice_config={"mode": "auto"}, | |
| diffusion_steps=32, | |
| speed=1.0, | |
| ) | |
| first = next(iterator) | |
| pipeline.cancel("session-a") | |
| remaining = list(iterator) | |
| assert first["type"] == "started" | |
| assert remaining[-1]["type"] == "cancelled" | |