import json import tempfile import unittest from pathlib import Path from unittest import mock from app.services import subtitle_translator as translator SAMPLE_SRT = """1 00:00:01,000 --> 00:00:03,000 Hello, everyone. 2 00:00:04,000 --> 00:00:06,000 We are going to Beijing. """ BATCH_SAMPLE_SRT = """1 00:00:01,000 --> 00:00:02,000 Line one. 2 00:00:02,000 --> 00:00:03,000 Line two. 3 00:00:03,000 --> 00:00:04,000 Line three. 4 00:00:04,000 --> 00:00:05,000 Line four. 5 00:00:05,000 --> 00:00:06,000 Line five. """ class SubtitleTranslatorTests(unittest.TestCase): def test_translate_srt_content_preserves_timecodes_and_rebuilds_text(self): llm_output = { "items": [ {"id": 1, "text": "大家好。"}, {"id": 2, "text": "我们要去北京。"}, ] } with ( mock.patch("app.services.subtitle_translator._ensure_llm_providers_registered"), mock.patch( "app.services.subtitle_translator._run_async_safely", return_value=json.dumps(llm_output, ensure_ascii=False), ) as run_llm, ): translated = translator.translate_srt_content( SAMPLE_SRT, target_language="中文", provider="openai", api_key="sk-test", base_url="https://llm.example/v1", ) self.assertIn("00:00:01,000 --> 00:00:03,000", translated) self.assertIn("大家好。", translated) self.assertIn("我们要去北京。", translated) self.assertNotIn("Hello, everyone.", translated) call_kwargs = run_llm.call_args.kwargs self.assertEqual("openai", call_kwargs["provider"]) self.assertEqual("sk-test", call_kwargs["api_key"]) self.assertEqual("https://llm.example/v1", call_kwargs["api_base"]) self.assertEqual("json", call_kwargs["response_format"]) self.assertIn("专业字幕翻译员", call_kwargs["system_prompt"]) self.assertIn("翻译为中文", call_kwargs["prompt"]) def test_translate_srt_content_rejects_missing_items(self): llm_output = {"items": [{"id": 1, "text": "大家好。"}]} with ( mock.patch("app.services.subtitle_translator._ensure_llm_providers_registered"), mock.patch( "app.services.subtitle_translator._run_async_safely", return_value=json.dumps(llm_output, ensure_ascii=False), ), ): with self.assertRaises(translator.SubtitleTranslationError): translator.translate_srt_content(SAMPLE_SRT, provider="openai") def test_translate_subtitle_file_writes_translated_srt(self): llm_output = { "items": [ {"id": 1, "text": "大家好。"}, {"id": 2, "text": "我们要去北京。"}, ] } with tempfile.TemporaryDirectory() as tmp_dir: input_file = Path(tmp_dir) / "input.srt" output_file = Path(tmp_dir) / "output.srt" input_file.write_text(SAMPLE_SRT, encoding="utf-8") with ( mock.patch("app.services.subtitle_translator._ensure_llm_providers_registered"), mock.patch( "app.services.subtitle_translator._run_async_safely", return_value=json.dumps(llm_output, ensure_ascii=False), ), ): result_path = translator.translate_subtitle_file( str(input_file), str(output_file), target_language="中文", provider="openai", ) self.assertEqual(str(output_file), result_path) self.assertIn("大家好。", output_file.read_text(encoding="utf-8")) def test_translate_srt_content_batches_requests_and_reports_progress(self): progress_events = [] def fake_run_llm(*args, **kwargs): payload_text = kwargs["prompt"].rsplit("待翻译字幕条目:", 1)[1].strip() payload = json.loads(payload_text) translated = {key: f"译文{key}" for key in payload} return json.dumps(translated, ensure_ascii=False) with ( mock.patch("app.services.subtitle_translator._ensure_llm_providers_registered"), mock.patch( "app.services.subtitle_translator._run_async_safely", side_effect=fake_run_llm, ) as run_llm, ): translated = translator.translate_srt_content( BATCH_SAMPLE_SRT, target_language="中文", provider="openai", batch_size=2, max_workers=1, progress_callback=lambda completed, total, message: progress_events.append( (completed, total, message) ), ) self.assertEqual(3, run_llm.call_count) self.assertIn("译文1", translated) self.assertIn("译文5", translated) self.assertEqual((0, 5), progress_events[0][:2]) self.assertEqual((5, 5), progress_events[-1][:2]) self.assertTrue(any("完成批次" in event[2] for event in progress_events)) if __name__ == "__main__": unittest.main()