fix(review): 调整审查领取超时时间并优化状态处理逻辑或回滚
Showing
2 changed files
with
55 additions
and
4 deletions
| ... | @@ -29,7 +29,7 @@ DASHBOARD = ROOT / "l2_review_dashboard.html" | ... | @@ -29,7 +29,7 @@ DASHBOARD = ROOT / "l2_review_dashboard.html" |
| 29 | REPORT_DIR.mkdir(parents=True, exist_ok=True) | 29 | REPORT_DIR.mkdir(parents=True, exist_ok=True) |
| 30 | GROUP_INDEX_CACHE: dict[tuple[str, int, int, str], list[dict[str, object]]] = {} | 30 | GROUP_INDEX_CACHE: dict[tuple[str, int, int, str], list[dict[str, object]]] = {} |
| 31 | load_dotenv(ROOT / ".env") | 31 | load_dotenv(ROOT / ".env") |
| 32 | REVIEW_CLAIM_TTL_SECONDS = max(60, int(os.getenv("REVIEW_CLAIM_TTL_SECONDS", "600"))) | 32 | REVIEW_CLAIM_TTL_SECONDS = max(60, int(os.getenv("REVIEW_CLAIM_TTL_SECONDS", "1800"))) |
| 33 | REVIEW_ACCESS_CODE = os.getenv("REVIEW_ACCESS_CODE", "") | 33 | REVIEW_ACCESS_CODE = os.getenv("REVIEW_ACCESS_CODE", "") |
| 34 | REVIEW_SESSION_TTL_SECONDS = max(300, int(os.getenv("REVIEW_SESSION_TTL_SECONDS", "43200"))) | 34 | REVIEW_SESSION_TTL_SECONDS = max(300, int(os.getenv("REVIEW_SESSION_TTL_SECONDS", "43200"))) |
| 35 | REVIEW_SESSIONS: dict[str, tuple[str, float]] = {} | 35 | REVIEW_SESSIONS: dict[str, tuple[str, float]] = {} |
| ... | @@ -578,7 +578,7 @@ def _claim_staging_review(staging_id: int, reviewer: str, client_token: str) -> | ... | @@ -578,7 +578,7 @@ def _claim_staging_review(staging_id: int, reviewer: str, client_token: str) -> |
| 578 | review_claim_expires_at = DATE_ADD(NOW(), INTERVAL %s SECOND), | 578 | review_claim_expires_at = DATE_ADD(NOW(), INTERVAL %s SECOND), |
| 579 | review_version = review_version + 1 | 579 | review_version = review_version + 1 |
| 580 | WHERE staging_id = %s | 580 | WHERE staging_id = %s |
| 581 | AND staging_status NOT IN ('imported', 'deleted') | 581 | AND staging_status <> 'imported' |
| 582 | AND ( | 582 | AND ( |
| 583 | biz_review_status IN ('pending', 'unsure') | 583 | biz_review_status IN ('pending', 'unsure') |
| 584 | OR biz_review_status = 'not_required' | 584 | OR biz_review_status = 'not_required' |
| ... | @@ -599,8 +599,6 @@ def _claim_staging_review(staging_id: int, reviewer: str, client_token: str) -> | ... | @@ -599,8 +599,6 @@ def _claim_staging_review(staging_id: int, reviewer: str, client_token: str) -> |
| 599 | message = f"找不到 staging_id={staging_id},请刷新页面后重试" | 599 | message = f"找不到 staging_id={staging_id},请刷新页面后重试" |
| 600 | elif current.get("staging_status") == "imported": | 600 | elif current.get("staging_status") == "imported": |
| 601 | message = "该记录已经入库,只能浏览,不能再领取审核" | 601 | message = "该记录已经入库,只能浏览,不能再领取审核" |
| 602 | elif current.get("staging_status") == "deleted": | ||
| 603 | message = "该记录已被删除,不能领取审核" | ||
| 604 | elif current.get("review_claimed_by"): | 602 | elif current.get("review_claimed_by"): |
| 605 | message = f"该记录已由 {current['review_claimed_by']} 领取" | 603 | message = f"该记录已由 {current['review_claimed_by']} 领取" |
| 606 | else: | 604 | else: | ... | ... |
| ... | @@ -96,6 +96,27 @@ def test_stale_review_returns_conflict_without_overwrite(): | ... | @@ -96,6 +96,27 @@ def test_stale_review_returns_conflict_without_overwrite(): |
| 96 | assert not conn.committed | 96 | assert not conn.committed |
| 97 | 97 | ||
| 98 | 98 | ||
| 99 | def test_pending_review_restores_a_deleted_staging_row(): | ||
| 100 | snapshot = { | ||
| 101 | "staging_id": 42, | ||
| 102 | "biz_review_status": "pending", | ||
| 103 | "staging_status": "staged", | ||
| 104 | "review_version": 6, | ||
| 105 | } | ||
| 106 | cursor = FakeCursor(update_rowcount=1, snapshot=snapshot) | ||
| 107 | conn = FakeConnection(cursor) | ||
| 108 | |||
| 109 | with patch.object(dashboard, "_target_conn", return_value=conn): | ||
| 110 | dashboard._update_staging_review( | ||
| 111 | 42, "pending", "", "alice", 5, "alice-token" | ||
| 112 | ) | ||
| 113 | |||
| 114 | update_sql, _params = cursor.executions[0] | ||
| 115 | assert "staging_status = CASE WHEN staging_status = 'deleted' THEN 'staged'" in update_sql | ||
| 116 | assert "biz_review_status = 'pending'" in update_sql | ||
| 117 | assert conn.committed | ||
| 118 | |||
| 119 | |||
| 99 | def test_claim_conflict_exposes_current_owner(): | 120 | def test_claim_conflict_exposes_current_owner(): |
| 100 | snapshot = { | 121 | snapshot = { |
| 101 | "staging_id": 42, | 122 | "staging_id": 42, |
| ... | @@ -115,6 +136,38 @@ def test_claim_conflict_exposes_current_owner(): | ... | @@ -115,6 +136,38 @@ def test_claim_conflict_exposes_current_owner(): |
| 115 | assert conn.rolled_back | 136 | assert conn.rolled_back |
| 116 | 137 | ||
| 117 | 138 | ||
| 139 | def test_deleted_review_can_be_claimed_for_undo_but_imported_review_cannot(): | ||
| 140 | snapshot = { | ||
| 141 | "staging_id": 42, | ||
| 142 | "biz_review_status": "deleted", | ||
| 143 | "staging_status": "deleted", | ||
| 144 | "review_version": 4, | ||
| 145 | } | ||
| 146 | cursor = FakeCursor(update_rowcount=1, snapshot=snapshot) | ||
| 147 | conn = FakeConnection(cursor) | ||
| 148 | |||
| 149 | with patch.object(dashboard, "_target_conn", return_value=conn): | ||
| 150 | dashboard._claim_staging_review(42, "alice", "alice-token") | ||
| 151 | |||
| 152 | claim_sql, _params = cursor.executions[0] | ||
| 153 | assert "staging_status <> 'imported'" in claim_sql | ||
| 154 | assert "staging_status NOT IN ('imported', 'deleted')" not in claim_sql | ||
| 155 | assert "biz_review_status IN ('approved_import', 'rejected_duplicate', 'deleted')" in claim_sql | ||
| 156 | assert conn.committed | ||
| 157 | |||
| 158 | imported_cursor = FakeCursor( | ||
| 159 | update_rowcount=0, | ||
| 160 | snapshot={"staging_id": 43, "staging_status": "imported"}, | ||
| 161 | ) | ||
| 162 | imported_conn = FakeConnection(imported_cursor) | ||
| 163 | with patch.object(dashboard, "_target_conn", return_value=imported_conn): | ||
| 164 | with pytest.raises(dashboard.ReviewConflictError, match="已经入库"): | ||
| 165 | dashboard._claim_staging_review(43, "alice", "alice-token") | ||
| 166 | |||
| 167 | assert imported_conn.rolled_back | ||
| 168 | assert not imported_conn.committed | ||
| 169 | |||
| 170 | |||
| 118 | def test_heartbeat_does_not_increment_review_version(): | 171 | def test_heartbeat_does_not_increment_review_version(): |
| 119 | snapshot = {"staging_id": 42, "review_version": 5} | 172 | snapshot = {"staging_id": 42, "review_version": 5} |
| 120 | cursor = FakeCursor(update_rowcount=1, snapshot=snapshot) | 173 | cursor = FakeCursor(update_rowcount=1, snapshot=snapshot) | ... | ... |
-
Please register or sign in to post a comment