"""P0 #1922 默认模板自动兜底单元测试。 覆盖 resolve_draft_plan_id 的核心路径(使用可mock的外部依赖): - 空 tid 时调用 get_or_create_default_template_id 兜底 - auto_create_default=False 时空tid抛400 - 模板不存在/无权访问抛404 - 已有草稿直接返回 plan_id - 旧模板(templates表)走 generate_from_template 创建草稿 _get_or_create_default_template_id 是薄SQL封装,其逻辑分支通过依赖注入路径间接覆盖; 直接SQL分支通过integration test/容器验证,不做单元级mock(避免内部import脆弱mock)。 """ from __future__ import annotations from unittest.mock import MagicMock, patch import pytest from app.api.routes.templates_editor.dependencies import resolve_draft_plan_id class TestResolveDraftPlanId: """resolve_draft_plan_id 行为。""" def _make_services(self, global_tpl=None, draft=None): tpl_svc = MagicMock() plan_svc = MagicMock() tpl_svc.get_template.return_value = global_tpl tpl_svc.get_template_draft.return_value = draft return (tpl_svc, plan_svc) def _make_user(self, uid: str = "user-1"): u = MagicMock() u.user.id = uid return u def test_empty_tid_auto_fallback_creates_default_then_draft(self): """template_id 为空 + auto_create_default=True:先兜底拿到tid,再走旧模板草稿创建。""" db = MagicMock() services = self._make_services() user = self._make_user() old_tpl = MagicMock() old_tpl.id = "tpl-auto" old_tpl.name = "默认配音模板" old_tpl.mode = "voice_over" old_tpl.segments = [MagicMock(id="seg-1", segment_order=0, duration_min=1.0, duration_max=30.0)] mock_repo = MagicMock() mock_repo.get_active.return_value = old_tpl mock_plan = MagicMock() mock_plan.id = "plan-new" mock_generator = MagicMock() mock_generator.generate_from_template.return_value = {"plan": mock_plan} with ( patch("app.api.routes.templates_editor.dependencies.SQLAlchemyTemplateRepository", return_value=mock_repo), patch( "app.api.routes.templates_editor.dependencies.get_or_create_default_template_id", return_value="tpl-auto", ) as mock_fb, patch("app.services.plan_generator_service.PlanGeneratorService", return_value=mock_generator), ): plan_id = resolve_draft_plan_id( template_id="", services=services, current_user=user, db=db, auto_create_default=True, ) assert plan_id == "plan-new" mock_fb.assert_called_once_with(db, "user-1") def test_empty_tid_without_auto_raises_400(self): """auto_create_default=False 且 tid 为空 → 400。""" from fastapi import HTTPException db = MagicMock() services = self._make_services() with pytest.raises(HTTPException) as exc: resolve_draft_plan_id( template_id="", services=services, current_user=self._make_user(), db=db, auto_create_default=False, ) assert exc.value.status_code == 400 def test_missing_template_raises_404(self): """tid 存在但模板找不到/无权访问 → 404。""" from fastapi import HTTPException db = MagicMock() services = self._make_services() # get_template returns None mock_repo = MagicMock() mock_repo.get_active.return_value = None with patch("app.api.routes.templates_editor.dependencies.SQLAlchemyTemplateRepository", return_value=mock_repo): with pytest.raises(HTTPException) as exc: resolve_draft_plan_id( template_id="tpl-ghost", services=services, current_user=self._make_user(), db=db, auto_create_default=False, ) assert exc.value.status_code == 404 def test_existing_draft_returned_directly(self): """已有模板草稿直接返回 plan_id,不重复创建。""" db = MagicMock() draft = MagicMock() draft.id = "plan-existing" services = self._make_services(draft=draft) old_tpl = MagicMock() old_tpl.id = "tpl-1" old_tpl.segments = [] mock_repo = MagicMock() mock_repo.get_active.return_value = old_tpl with patch("app.api.routes.templates_editor.dependencies.SQLAlchemyTemplateRepository", return_value=mock_repo): plan_id = resolve_draft_plan_id( template_id="tpl-1", services=services, current_user=self._make_user(), db=db, auto_create_default=False, ) assert plan_id == "plan-existing" # 没触发generate services[1].update_plan_config.assert_not_called() def test_global_template_creates_draft_via_new_service(self): """全局模板(新系统edit_templates表存在)走tpl_svc.create_template_draft。""" db = MagicMock() services = self._make_services(global_tpl=MagicMock(), draft=None) new_draft = MagicMock() new_draft.id = "plan-global" services[0].create_template_draft.return_value = new_draft plan_id = resolve_draft_plan_id( template_id="tpl-global", services=services, current_user=self._make_user(), db=db, auto_create_default=False, ) assert plan_id == "plan-global" services[0].create_template_draft.assert_called_once_with("tpl-global", user_id="user-1")