From 1a2e2d85463d9c16dcf7ed6a4ed477cf81f6cf8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=81=B5=E5=BA=94?= Date: Wed, 8 Jul 2026 08:44:52 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BC=98=E5=8C=96=E7=94=9F=E6=88=90?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3=E9=94=99=E8=AF=AF=E4=BF=A1=E6=81=AF=EF=BC=8C?= =?UTF-8?q?=E5=B0=86=E6=8A=80=E6=9C=AF=E7=8A=B6=E6=80=81=E6=9C=BA=E6=9C=AF?= =?UTF-8?q?=E8=AF=AD=E6=94=B9=E4=B8=BA=E7=94=A8=E6=88=B7=E5=8F=8B=E5=A5=BD?= =?UTF-8?q?=E6=8F=90=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - edit_plan_service.py: can_generate() 错误信息改为'请先编辑并保存模板后再生成视频' - edit_plans.py: 7处错误信息改为用户友好中文提示,不再暴露内部状态名和异常类型 - test_edit_plan_generation_api.py: 更新3个测试用例以匹配新的用户友好错误信息 Co-Authored-By: Claude Fable 5 --- apps/api/app/api/routes/edit_plans.py | 14 ++++++------ apps/api/app/services/edit_plan_service.py | 2 +- tests/unit/test_edit_plan_generation_api.py | 25 ++++++++++++--------- 3 files changed, 22 insertions(+), 19 deletions(-) diff --git a/apps/api/app/api/routes/edit_plans.py b/apps/api/app/api/routes/edit_plans.py index 8f86b0fce..2ee4b04d8 100644 --- a/apps/api/app/api/routes/edit_plans.py +++ b/apps/api/app/api/routes/edit_plans.py @@ -200,9 +200,9 @@ def _check_project_access(project_id: str, user_id: str, project_repository: Any return project = project_repository.find_by_id(project_id) if project is None: - raise HTTPException(status_code=404, detail=f"Project {project_id} not found") + raise HTTPException(status_code=404, detail="项目不存在") if not project.can_access(user_id): - raise HTTPException(status_code=403, detail="Access denied to project") + raise HTTPException(status_code=403, detail="无权访问该项目") def _to_response(p: EditPlan) -> EditPlanResponse: @@ -253,7 +253,7 @@ def list_plans( except ValueError: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, - detail=(f"无效的状态值: {status_filter}," f"可选值: draft, editing, rendering, completed, failed"), + detail="无效的筛选条件,请选择正确的状态", ) # 项目鉴权:如果指定了 project_id,校验用户是否有权访问 @@ -379,7 +379,7 @@ def update_plan( except ValueError: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, - detail=(f"无效的状态值: {body.status}," f"可选值: draft, editing, rendering, completed, failed"), + detail="无效的状态值,请选择正确的状态", ) svc.transition_status(plan_id, target_status) except ValueError as exc: @@ -524,7 +524,7 @@ def generate_plan( logger.warning("标记计划失败状态时异常: plan_id=%s", plan_id) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail=f"触发生成失败: {type(exc).__name__}: {exc}", + detail="生成失败,请稍后重试", ) @@ -672,7 +672,7 @@ def ai_recommend_clips( if plan_status not in ("draft", "editing"): raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, - detail=f"AI 推荐仅支持 draft/editing 状态的计划,当前状态: {plan_status}", + detail="当前计划状态不支持AI推荐,请先创建或编辑计划后再试", ) # 调用 AI 推荐服务(同步调用 stub,后续改为 Celery 异步) @@ -723,7 +723,7 @@ def ai_recommend_clips( pass raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail=f"AI 推荐结果写入失败: {exc}", + detail="AI推荐结果保存失败,请稍后重试", ) logger.info( diff --git a/apps/api/app/services/edit_plan_service.py b/apps/api/app/services/edit_plan_service.py index 4b88bc265..5f55e3de4 100644 --- a/apps/api/app/services/edit_plan_service.py +++ b/apps/api/app/services/edit_plan_service.py @@ -447,7 +447,7 @@ class EditPlanService: # 检查状态 if plan.status != EditPlanStatus.EDITING: - return False, f"只有 editing 状态的计划可以触发渲染,当前状态: {plan.status}" + return False, "请先编辑并保存模板后再生成视频" # 检查是否有片段 clips = self._clip_repo.list_by_plan(plan_id) diff --git a/tests/unit/test_edit_plan_generation_api.py b/tests/unit/test_edit_plan_generation_api.py index 84c666bb7..4884058e4 100644 --- a/tests/unit/test_edit_plan_generation_api.py +++ b/tests/unit/test_edit_plan_generation_api.py @@ -337,12 +337,12 @@ class TestGeneratePlan: client: TestClient, plan_repo: StubEditPlanRepository, ) -> None: - """draft 状态 → 400""" + """draft 状态 → 400 + 用户友好的错误信息""" plan = _make_plan(status=EditPlanStatus.DRAFT) plan_repo.create(plan) resp = client.post(f"/api/v1/edit-plans/{plan.id}/generate") assert resp.status_code == 400 - assert "editing" in resp.json()["detail"] + assert "请先编辑并保存模板后再生成视频" in resp.json()["detail"] def test_generate_wrong_status_rendering( self, @@ -601,7 +601,7 @@ class TestGeneratePlanErrorHandling: plan_repo: StubEditPlanRepository, clip_repo: StubEditPlanClipRepository, ) -> None: - """核心流程抛异常 → 500 + 明确错误信息(非裸 500)""" + """核心流程抛异常 → 500 + 用户友好的错误信息(不暴露技术细节)""" plan = _make_plan(status=EditPlanStatus.EDITING) plan_repo.create(plan) clip = _make_clip(plan.id, order=1) @@ -614,10 +614,10 @@ class TestGeneratePlanErrorHandling: assert resp.status_code == 500 data = resp.json() - # 验证返回了明确的错误信息,包含异常类型和描述 - assert "触发生成失败" in data["detail"] - assert "RuntimeError" in data["detail"] - assert "Redis 连接超时" in data["detail"] + # 验证返回了用户友好的错误信息,不暴露技术细节 + assert "生成失败" in data["detail"] + assert "RuntimeError" not in data["detail"] + assert "Redis" not in data["detail"] def test_generate_error_rolls_back_plan_status( self, @@ -640,13 +640,13 @@ class TestGeneratePlanErrorHandling: updated = plan_repo.get(plan.id) assert updated.status == EditPlanStatus.FAILED - def test_generate_error_detail_includes_exception_type( + def test_generate_error_detail_is_user_friendly( self, client: TestClient, plan_repo: StubEditPlanRepository, clip_repo: StubEditPlanClipRepository, ) -> None: - """错误信息包含异常类型名,方便前端/运维定位""" + """错误信息对用户友好,不暴露技术细节(异常类型、内部错误信息)""" plan = _make_plan(status=EditPlanStatus.EDITING) plan_repo.create(plan) clip = _make_clip(plan.id, order=1) @@ -658,5 +658,8 @@ class TestGeneratePlanErrorHandling: assert resp.status_code == 500 detail = resp.json()["detail"] - assert "ConnectionError" in detail - assert "Broker 不可达" in detail + # 验证不暴露技术细节 + assert "ConnectionError" not in detail + assert "Broker 不可达" not in detail + # 验证返回了用户友好的提示 + assert "生成失败" in detail