import unittest from types import SimpleNamespace from unittest.mock import Mock, patch from api.tasks.memoir_tasks import MemoirImageSettings, process_memoir_segments class ProcessMemoirSegmentsImageEnqueueTest(unittest.TestCase): @patch("api.tasks.memoir_tasks._update_task_status_sync") @patch("api.tasks.memoir_tasks._release_chapter_lock") @patch("api.tasks.memoir_tasks._acquire_chapter_lock", return_value=True) @patch("api.tasks.memoir_tasks._classify_chapter_category", return_value="childhood") @patch("api.tasks.memoir_tasks._get_or_create_state_sync") @patch("api.tasks.memoir_tasks.llm_service.get_llm", return_value=None) @patch("api.tasks.memoir_tasks.generate_chapter_images.delay") @patch("api.tasks.memoir_tasks.SessionLocal") @patch("api.tasks.memoir_tasks.MemoirImageSettings.from_env") def test_process_memoir_segments_enqueues_image_jobs_after_commit( self, settings_from_env, session_local_cls, delay_mock, _get_llm, get_state_mock, _classify_mock, _acquire_lock_mock, _release_lock_mock, _update_status_mock, ): settings_from_env.return_value = 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", ) get_state_mock.return_value = SimpleNamespace(current_stage="childhood", slots={}) segment = SimpleNamespace( id="segment-1", transcript_text="那条路我一直记得。\n\n{{{{IMAGE:南方小镇的青石板路}}}}", processed=False, ) segments_result = Mock() segments_result.scalars.return_value.all.return_value = [segment] chapter_result = Mock() chapter_result.scalar_one_or_none.return_value = None book_result = Mock() book_result.scalar_one_or_none.return_value = None db = Mock() db.execute.side_effect = [segments_result, chapter_result, book_result] db.get.return_value = None session_local_cls.return_value = db events: list[str] = [] db.commit.side_effect = lambda: events.append("commit") delay_mock.side_effect = lambda chapter_id: events.append(f"delay:{chapter_id}") task_self = SimpleNamespace( request=SimpleNamespace(id="task-1"), retry=Mock(side_effect=AssertionError("retry should not be called")), ) process_memoir_segments.run.__func__(task_self, "user-1", ["segment-1"]) self.assertIn("commit", events) delay_events = [event for event in events if event.startswith("delay:")] self.assertEqual(len(delay_events), 1) self.assertGreater(events.index(delay_events[0]), events.index("commit")) @patch("api.tasks.memoir_tasks._update_task_status_sync") @patch("api.tasks.memoir_tasks._release_chapter_lock") @patch("api.tasks.memoir_tasks._acquire_chapter_lock", return_value=True) @patch("api.tasks.memoir_tasks._classify_chapter_category", return_value="childhood") @patch("api.tasks.memoir_tasks._get_or_create_state_sync") @patch("api.tasks.memoir_tasks.llm_service.get_llm", return_value=None) @patch("api.tasks.memoir_tasks.generate_chapter_images.delay") @patch("api.tasks.memoir_tasks.SessionLocal") @patch("api.tasks.memoir_tasks.MemoirImageSettings.from_env") def test_process_memoir_segments_does_not_enqueue_image_jobs_when_feature_disabled( self, settings_from_env, session_local_cls, delay_mock, _get_llm, get_state_mock, _classify_mock, _acquire_lock_mock, _release_lock_mock, _update_status_mock, ): settings_from_env.return_value = MemoirImageSettings( enabled=False, max_per_chapter=2, provider="liblib", default_style="watercolor", default_size="1024x1024", poll_interval_seconds=3, max_attempts=20, liblib_template_uuid="tpl-uuid", ) get_state_mock.return_value = SimpleNamespace(current_stage="childhood", slots={}) segment = SimpleNamespace( id="segment-1", transcript_text="那条路我一直记得。\n\n{{{{IMAGE:南方小镇的青石板路}}}}", processed=False, ) segments_result = Mock() segments_result.scalars.return_value.all.return_value = [segment] chapter_result = Mock() chapter_result.scalar_one_or_none.return_value = None book_result = Mock() book_result.scalar_one_or_none.return_value = None db = Mock() db.execute.side_effect = [segments_result, chapter_result, book_result] db.get.return_value = None session_local_cls.return_value = db task_self = SimpleNamespace( request=SimpleNamespace(id="task-1"), retry=Mock(side_effect=AssertionError("retry should not be called")), ) process_memoir_segments.run.__func__(task_self, "user-1", ["segment-1"]) delay_mock.assert_not_called()