diff --git a/backend/agenteval/intelligent_eval/lifecycle.py b/backend/agenteval/intelligent_eval/lifecycle.py index ee8db62..cf7bca2 100644 --- a/backend/agenteval/intelligent_eval/lifecycle.py +++ b/backend/agenteval/intelligent_eval/lifecycle.py @@ -197,9 +197,15 @@ def cancel(session: Session, eval_id: str) -> IntelligentEval: def submit_report(session: Session, eval_id: str, report: dict[str, Any]) -> IntelligentEval: """OpenClaw 提交结构化报告:executing → completed。 - 报告结构校验在路由层(pydantic),此处只负责落库与状态迁移。 + 报告结构校验在路由层(pydantic),此处只负责落库与状态迁移。提交前必须 + 满足:存在会话时须全部 close(completed)——否则拒绝,杜绝"评估 completed + 但 completed_sessions=0(进度 0%)"的不一致(worker 建会话未 close 时, + analyst 不应提前把评估标完成)。 """ repo = IntelligentEvalRepository(session) + sessions = IntelligentEvalSessionRepository(session).list_by_eval(eval_id) + if sessions and any(s.status != IntelligentEvalSessionStatus.COMPLETED for s in sessions): + raise IntelligentEvalTransitionError("会话未全部关闭,不能提交报告") result = repo._submit_report_if_executing(eval_id, report) return _resolve_write( eval_id, diff --git a/tests/integration/test_intelligent_evals_api.py b/tests/integration/test_intelligent_evals_api.py index a2f36f5..cab77a5 100644 --- a/tests/integration/test_intelligent_evals_api.py +++ b/tests/integration/test_intelligent_evals_api.py @@ -537,6 +537,31 @@ class TestReport: resp = await client.get(f"/api/intelligent-evals/{eval_id}/report") assert resp.status_code == 404 + async def test_submit_report_rejects_when_sessions_not_closed(self, client, seeded_db): + """评估不应在会话未全部 close 时被标 completed(否则进度 0% 但状态完成)。 + + 复现 t480 真实问题:worker 建会话但未 close,analyst 仍提交报告 → + submit_report 无条件 executing→completed,completed_sessions=0。 + """ + eval_id = await _create_executing_eval(client) + await _create_session(client, eval_id) # 建 1 个 running 会话,不 close + resp = await client.put(f"/api/intelligent-evals/{eval_id}/report", json={"report": _report_payload()}) + # 会话未全部 close → 应拒绝(409),而非 200 直接 completed + assert resp.status_code == 409, resp.text + + async def test_submit_report_allows_when_sessions_closed(self, client, seeded_db): + """会话全部 close 后提交报告应成功(正常 analyst 路径不被破坏)。""" + eval_id = await _create_executing_eval(client) + session = await _create_session(client, eval_id) + resp = await client.post( + f"/api/intelligent-evals/{eval_id}/sessions/{session['id']}/close", + json={"verdict": {"goal_achieved": True, "summary": "完成", "severity": "low"}}, + ) + assert resp.status_code == 200, resp.text + resp = await client.put(f"/api/intelligent-evals/{eval_id}/report", json={"report": _report_payload()}) + assert resp.status_code == 200, resp.text + assert resp.json()["status"] == "completed" + async def test_markdown_before_submission_returns_404(self, client, seeded_db): eval_id = await _create_executing_eval(client) resp = await client.get(f"/api/intelligent-evals/{eval_id}/report/markdown")