diff --git a/apps/api/app/api/routes/edit_plans.py b/apps/api/app/api/routes/edit_plans.py index cc7d211ad..de0a5c5bf 100755 --- a/apps/api/app/api/routes/edit_plans.py +++ b/apps/api/app/api/routes/edit_plans.py @@ -368,7 +368,8 @@ def create_plan( """创建剪辑计划 基于模板自动生成片段结构: - - 从模板的 clip_configs 生成初始 clips + - 模板存在时:从模板的 clip_configs 生成初始 clips + - 模板不存在时:降级为空计划(保持向后兼容) - 用户传入的 config 与模板 config 合并(用户配置优先级更高) - total_duration 自动根据 clips 总时长计算 """ @@ -380,24 +381,39 @@ def create_plan( if project_id: check_project_access(project_id, current_user.user.id, project_repository) - template_svc = EditTemplateService(db) - - # 校验模板存在 - try: - template = template_svc.get_template_or_raise(body.template_id) - except ValueError as exc: - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail=str(exc), - ) from exc - - # 获取模板片段配置 - clip_configs = template_svc.list_clip_configs(body.template_id, skip=0, limit=200) - # 标准化用户传入的 config normalized_config = normalize_plan_config(body.config or {}) - # 从模板生成剪辑计划(包含 clips) + template_svc = EditTemplateService(db) + svc = EditPlanService(db) + + # 尝试从模板生成(模板不存在时降级为空计划) + template = None + clips = [] + try: + template = template_svc.get_template_or_raise(body.template_id) + except ValueError: + # 模板不存在,降级为普通空计划 + logger.info("模板不存在,创建空计划: template_id=%s", body.template_id) + plan = svc.create_plan( + template_id=body.template_id, + name=body.name, + config=normalized_config, + project_id=project_id, + created_by_user_id=current_user.user.id, + total_duration=body.total_duration if body.total_duration > 0 else 0.0, + ) + logger.info( + "创建空剪辑计划: id=%s name=%s by user=%s", + plan.id, + plan.name, + current_user.user.id, + ) + return _to_response(plan) + + # 模板存在,从模板生成计划+片段 + clip_configs = template_svc.list_clip_configs(body.template_id, skip=0, limit=200) + generator = PlanGeneratorService(db) try: result = generator.generate_from_template( @@ -419,7 +435,6 @@ def create_plan( # 如果用户传入了自定义 config,合并覆盖模板配置 if body.config: - svc = EditPlanService(db) base_config = template.config or {} merged_config = {**base_config, **normalized_config} # 重新标准化确保默认值填充正确 diff --git a/tests/unit/test_edit_plans_api.py b/tests/unit/test_edit_plans_api.py index db83d4a05..3314a3ce8 100755 --- a/tests/unit/test_edit_plans_api.py +++ b/tests/unit/test_edit_plans_api.py @@ -245,7 +245,9 @@ class TestCreatePlan: mock_template_svc = MagicMock() mock_template_svc.get_template_or_raise.return_value = MagicMock( - id="tpl-001", name="测试模板", config={}, + id="tpl-001", + name="测试模板", + config={}, ) mock_template_svc.list_clip_configs.return_value = [] mock_template_svc_cls.return_value = mock_template_svc @@ -273,12 +275,11 @@ class TestCreatePlan: mock_gen.generate_from_template.assert_called_once() @patch("app.api.routes.edit_plans.EditTemplateService") - def test_create_template_not_found_returns_400(self, mock_template_svc_cls, client): - """模板不存在时返回 400""" + def test_create_template_not_found_falls_back_empty(self, mock_template_svc_cls, client): + """模板不存在时降级为空计划(向后兼容)""" c, repo = client mock_template_svc = MagicMock() - from packages.domain.edit_template import EditTemplateStatus mock_template_svc.get_template_or_raise.side_effect = ValueError("模板不存在") mock_template_svc_cls.return_value = mock_template_svc @@ -286,7 +287,12 @@ class TestCreatePlan: "/api/v1/edit-plans", json={"template_id": "nonexistent", "name": "测试"}, ) - assert resp.status_code == 400 + assert resp.status_code == 201 + data = resp.json() + assert data["name"] == "测试" + assert data["template_id"] == "nonexistent" + # 空计划没有片段 + assert "clips" not in data or len(data.get("clips", [])) == 0 def test_create_empty_name_returns_422(self, client): c, repo = client