import unittest from unittest.mock import Mock, patch from app.features.memoir.memoir_images.prompting import MemoirImagePromptService from app.features.memoir.memoir_images.settings import MemoirImageSettings class MemoirImagePromptingTest(unittest.TestCase): def test_prompt_service_uses_english_fallback_without_llm(self): settings = MemoirImageSettings( enabled=True, max_per_chapter=2, provider="liblib", default_style="watercolor", default_size="1024x1024", poll_interval_seconds=3, max_attempts=20, liblib_template_uuid="tpl-uuid", ) service = MemoirImagePromptService(llm=None, settings=settings) result = service.build_prompt( chapter_title="童年的夏天", chapter_category="childhood", description="奶奶坐在院子里的藤椅上", context_excerpt="梧桐树下很安静,夏天总有蝉鸣。", ) self.assertEqual(result["style"], "watercolor") self.assertEqual(result["size"], "1024x1024") self.assertIn("childhood memory", result["prompt"]) self.assertIn("watercolor", result["prompt"]) self.assertNotIn("奶奶坐在院子里的藤椅上", result["prompt"]) self.assertIn("childhood", result["prompt_context"]) def test_prompt_service_parses_structured_llm_response(self): settings = MemoirImageSettings( enabled=True, max_per_chapter=2, provider="liblib", default_style="watercolor", default_size="1024x1024", poll_interval_seconds=3, max_attempts=20, liblib_template_uuid="tpl-uuid", ) llm = Mock() bound_llm = Mock() bound_llm.invoke.return_value.content = ( '{"prompt":"A grandmother in a quiet courtyard, summer cicadas, soft watercolor",' '"style":"watercolor","size":"1024x1024"}' ) llm.bind.return_value = bound_llm service = MemoirImagePromptService(llm=llm, settings=settings) result = service.build_prompt( chapter_title="童年的夏天", chapter_category="childhood", description="奶奶坐在院子里的藤椅上", context_excerpt="梧桐树下很安静,夏天总有蝉鸣。", ) self.assertEqual( result["prompt"], "A grandmother in a quiet courtyard, summer cicadas, soft watercolor", ) self.assertEqual(result["style"], "watercolor") self.assertEqual(result["size"], "1024x1024") def test_prompt_service_parses_markdown_wrapped_json_response(self): settings = MemoirImageSettings( enabled=True, max_per_chapter=2, provider="liblib", default_style="watercolor", default_size="1024x1024", poll_interval_seconds=3, max_attempts=20, liblib_template_uuid="tpl-uuid", ) llm = Mock() bound_llm = Mock() bound_llm.invoke.return_value.content = """```json { "prompt": "A middle-aged teacher stands on the empty stage, realistic, cinematic lighting", "style": "realistic", "size": "1280x720" } ```""" llm.bind.return_value = bound_llm service = MemoirImagePromptService(llm=llm, settings=settings) result = service.build_prompt( chapter_title="二十出头 · 在小镇讲台上种下第一粒种子", chapter_category="career_early", description="空荡荡的教室讲台前,一个年轻老师站着", context_excerpt="第一次站上讲台,心里紧张又兴奋。", ) self.assertEqual( result["prompt"], "A middle-aged teacher stands on the empty stage, realistic, cinematic lighting", ) self.assertEqual(result["style"], "realistic") self.assertEqual(result["size"], "1280x720") @patch("app.features.memoir.memoir_images.prompting.logger") def test_prompt_service_logs_warning_and_falls_back_when_llm_response_is_invalid( self, logger_mock ): settings = MemoirImageSettings( enabled=True, max_per_chapter=2, provider="liblib", default_style="watercolor", default_size="1024x1024", poll_interval_seconds=3, max_attempts=20, liblib_template_uuid="tpl-uuid", ) llm = Mock() bound_llm = Mock() bound_llm.invoke.return_value.content = "not-json" llm.bind.return_value = bound_llm service = MemoirImagePromptService(llm=llm, settings=settings) result = service.build_prompt( chapter_title="童年的夏天", chapter_category="childhood", description="奶奶坐在院子里的藤椅上", context_excerpt="梧桐树下很安静,夏天总有蝉鸣。", ) self.assertIn("childhood memory", result["prompt"]) self.assertNotIn("奶奶坐在院子里的藤椅上", result["prompt"]) logger_mock.warning.assert_called_once_with( "图片 prompt 生成回退到默认模板: chapter_category=%s, title=%s, error=%s", "childhood", "童年的夏天", unittest.mock.ANY, )