import io import json from pathlib import Path import tempfile import unittest from unittest.mock import patch import voicestudio_client as adapter class VoiceStudioTests(unittest.TestCase): def test_bounded_parts_preserve_text_order(self): text = ('Это длинный раздел книги. ' * 2000).strip() parts = adapter.split_sections([('Глава', text)]) self.assertGreater(len(parts), 20) self.assertTrue(all(0 < len(part) <= 1600 for _, part in parts)) self.assertEqual(text, ' '.join(part for _, part in parts)) self.assertEqual([], adapter.split_sections([('Пустая', ' ')])) def test_stream_progress_and_completed_container(self): with tempfile.TemporaryDirectory() as root: outputs = Path(root) audio = b'\x00\x00\x00\x18ftypM4A ' + b'\0' * 128 (outputs / 'result.m4b').write_bytes(audio) events = [{'type': 'chapter', 'index': 0, 'total': 1}, {'type': 'assembling'}, {'type': 'done', 'output': 'result.m4b', 'duration_s': 1, 'failed_chapters': []}] stream = io.BytesIO(b''.join(('data: ' + json.dumps(e) + '\n\n').encode() for e in events)) seen = [] with patch('audio_cleanup.remember_output') as remember, patch.object(adapter, 'check_available'), patch.object(adapter, 'OUTPUTS', outputs), patch.object(adapter, 'urlopen', return_value=stream): adapter.render_audiobook([('Глава', 'Текст')], 'Книга', 'Автор', outputs / 'book.m4a', seen.append, lambda: False) remember.assert_called_once_with(outputs / 'book.m4a', outputs / 'result.m4b') self.assertEqual(events, seen) self.assertEqual(audio, (outputs / 'book.m4a').read_bytes()) def test_failed_chapters_never_become_ready(self): stream = io.BytesIO(b'data: {"type":"done","failed_chapters":[1]}\n') with patch.object(adapter, 'check_available'), patch.object(adapter, 'urlopen', return_value=stream): with self.assertRaises(RuntimeError): adapter.render_audiobook([('Chapter', 'Text')], 'Book', '', Path('unused.m4a'), lambda event: None, lambda: False)