From 9627ac9cf9be98d198dc678ee404b74587a453d8 Mon Sep 17 00:00:00 2001 From: iomgaa Date: Sat, 11 Jul 2026 23:23:48 -0400 Subject: [PATCH] fix(question_gen): raise ValueError on UPDATE of missing rows record_run_end, update_gates, and update_difficulty now check cursor.rowcount after UPDATE+commit and raise ValueError if 0 rows were affected. Prevents silent telemetry loss. Adds three negative-path tests: - test_record_run_end_missing_run_raises - test_update_gates_missing_item_raises - test_update_difficulty_missing_item_raises Co-Authored-By: Claude Opus 4.6 (1M context) --- app/question_gen/run_store.py | 12 +++++++++--- tests/unit/test_run_store.py | 24 ++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/app/question_gen/run_store.py b/app/question_gen/run_store.py index a1c697d..2d57974 100644 --- a/app/question_gen/run_store.py +++ b/app/question_gen/run_store.py @@ -212,7 +212,7 @@ class QuestionGenStore: 批次统计摘要。 """ now = datetime.now(tz=UTC).isoformat(timespec="seconds") - self._conn.execute( + cursor = self._conn.execute( """ UPDATE question_gen_runs SET ended_at=?, status=?, total_slots=?, accepted=?, rejected=?, heavy_sampled=? @@ -229,6 +229,8 @@ class QuestionGenStore: ), ) self._conn.commit() + if cursor.rowcount == 0: + raise ValueError(f"run_id 不存在: {run_id}") logger.info( "出题批次已结束: run_id={}, status={}, accepted={}/{}", run_id, @@ -307,7 +309,7 @@ class QuestionGenStore: 属性,每个属性具有 .verdict.value 和 .reason;以及 passed/reject_reason 属性)。 """ final_status = "accepted" if report.passed else "rejected" - self._conn.execute( + cursor = self._conn.execute( """ UPDATE question_gen_items SET gate_key_verify=?, gate_blind_answer=?, gate_multi_true=?, @@ -325,6 +327,8 @@ class QuestionGenStore: ), ) self._conn.commit() + if cursor.rowcount == 0: + raise ValueError(f"item_id 不存在: {item_id}") def update_difficulty(self, item_id: str, difficulty_steps: int) -> None: """更新重量抽检产出的 Agent 步数。 @@ -336,11 +340,13 @@ class QuestionGenStore: difficulty_steps : int Agent 完成该题所需步数。 """ - self._conn.execute( + cursor = self._conn.execute( "UPDATE question_gen_items SET difficulty_steps=? WHERE item_id=?", (difficulty_steps, item_id), ) self._conn.commit() + if cursor.rowcount == 0: + raise ValueError(f"item_id 不存在: {item_id}") def get_run_stats(self, run_id: str) -> RunStats: """查询批次统计摘要。 diff --git a/tests/unit/test_run_store.py b/tests/unit/test_run_store.py index 902a0d7..12d9d12 100644 --- a/tests/unit/test_run_store.py +++ b/tests/unit/test_run_store.py @@ -242,3 +242,27 @@ class TestQuestionGenStore: """查询不存在的 run_id 应报错。""" with pytest.raises(ValueError, match="run_id"): store.get_run_stats("nonexistent-run") + + def test_record_run_end_missing_run_raises(self, store: QuestionGenStore) -> None: + """对不存在的 run_id 调用 record_run_end 应报错。""" + stats = RunStats(total_slots=10, accepted=5, rejected=3, heavy_sampled=2) + with pytest.raises(ValueError, match="run_id"): + store.record_run_end( + run_id="ghost-run", status="completed", stats=stats + ) + + def test_update_gates_missing_item_raises(self, store: QuestionGenStore) -> None: + """对不存在的 item_id 调用 update_gates 应报错。""" + report = _MockGateReport( + key_verify=_GateResult(_Verdict.PASS, "ok"), + blind_answer=_GateResult(_Verdict.PASS, "ok"), + multi_true=_GateResult(_Verdict.PASS, "ok"), + leak_test=_GateResult(_Verdict.PASS, "ok"), + ) + with pytest.raises(ValueError, match="item_id"): + store.update_gates(item_id="ghost-item", report=report) + + def test_update_difficulty_missing_item_raises(self, store: QuestionGenStore) -> None: + """对不存在的 item_id 调用 update_difficulty 应报错。""" + with pytest.raises(ValueError, match="item_id"): + store.update_difficulty(item_id="ghost-item", difficulty_steps=5)