fix(approval): replay immutable action responses

This commit is contained in:
caoxiaozhu
2026-07-16 15:49:43 +08:00
parent 4940ebc419
commit 28b834edd3
20 changed files with 552 additions and 60 deletions

View File

@@ -228,6 +228,37 @@ def test_risk_disposition_idempotency_and_optimistic_version_are_enforced(
assert replay.replayed is True
assert replay.event.id == first.event.id
assert db.scalar(select(func.count()).select_from(RiskDispositionEvent)) == 1
stored_event = db.get(RiskDispositionEvent, first.event.id)
assert stored_event is not None
assert stored_event.response_json is not None
assert stored_event.response_json["replayed"] is False
assert stored_event.response_json["disposition"]["version"] == 1
advanced = service.execute_action(
observation.id,
_action(
"start_remediation",
version=1,
request_id="request-remediation-after-idempotent",
),
tenant_id="default",
actor_id="finance-1",
actor_name="财务甲",
)
late_replay = service.execute_action(
observation.id,
payload,
tenant_id="default",
actor_id="finance-1",
actor_name="财务甲",
)
assert advanced.disposition.version == 2
assert late_replay.replayed is True
assert late_replay.disposition.version == 1
assert late_replay.disposition.lifecycle_status == "open"
assert [item.version for item in late_replay.disposition.events] == [1]
assert db.scalar(select(func.count()).select_from(RiskDispositionEvent)) == 2
with pytest.raises(RiskDispositionIdempotencyConflictError):
service.execute_action(
@@ -268,7 +299,56 @@ def test_risk_disposition_idempotency_and_optimistic_version_are_enforced(
actor_id="finance-1",
actor_name="财务甲",
)
assert error.value.current_version == 1
assert error.value.current_version == 2
def test_legacy_event_replay_rebuilds_only_target_version_history(
monkeypatch: pytest.MonkeyPatch,
) -> None:
monkeypatch.setenv("FEW_SHOT_INJECTION_ENABLED", "false")
with _build_session() as db:
observation = RiskObservationService(db).upsert_observation(
_observation_payload("risk:typed:legacy-replay")
)
db.commit()
service = RiskDispositionService(db)
payload = _action("confirm", version=0, request_id="request-legacy-replay-001")
first = service.execute_action(
observation.id,
payload,
tenant_id="default",
actor_id="finance-1",
actor_name="财务甲",
)
legacy_event = db.get(RiskDispositionEvent, first.event.id)
assert legacy_event is not None
legacy_event.response_json = None
db.commit()
service.execute_action(
observation.id,
_action(
"start_remediation",
version=1,
request_id="request-legacy-replay-later",
),
tenant_id="default",
actor_id="finance-1",
actor_name="财务甲",
)
replay = service.execute_action(
observation.id,
payload,
tenant_id="default",
actor_id="finance-1",
actor_name="财务甲",
)
assert replay.replayed is True
assert replay.event.id == first.event.id
assert replay.disposition.version == 1
assert replay.disposition.lifecycle_status == "open"
assert [item.version for item in replay.disposition.events] == [1]
def test_risk_observation_api_enforces_pool_claim_and_typed_action_permissions(
@@ -360,6 +440,26 @@ def test_risk_observation_api_enforces_pool_claim_and_typed_action_permissions(
"comment": "人工复核确认",
},
)
advanced = client.post(
f"/api/v1/risk-observations/{observation_id}/disposition/actions",
headers=finance_headers,
json={
"action": "request_supplement",
"expected_version": 1,
"request_id": "api-supplement-request-001",
"comment": "请补充完整行程材料",
},
)
late_replay = client.post(
f"/api/v1/risk-observations/{observation_id}/disposition/actions",
headers=finance_headers,
json={
"action": "confirm",
"expected_version": 0,
"request_id": "api-confirm-request-001",
"comment": "人工复核确认",
},
)
stale = client.post(
f"/api/v1/risk-observations/{observation_id}/disposition/actions",
headers=finance_headers,
@@ -410,6 +510,13 @@ def test_risk_observation_api_enforces_pool_claim_and_typed_action_permissions(
assert replay.status_code == 200
assert replay.json()["replayed"] is True
assert replay.json()["event"]["id"] == first.json()["event"]["id"]
assert advanced.status_code == 200
assert advanced.json()["disposition"]["version"] == 2
assert late_replay.status_code == 200
assert late_replay.json()["replayed"] is True
assert late_replay.json()["disposition"]["version"] == 1
assert late_replay.json()["disposition"]["lifecycle_status"] == "open"
assert [item["version"] for item in late_replay.json()["disposition"]["events"]] == [1]
assert stale.status_code == 409
assert stale.json()["detail"]["code"] == "RISK_DISPOSITION_VERSION_CONFLICT"
assert stale.json()["detail"]["message"] == "风险处置状态已更新,请刷新证据链后重试。"
@@ -423,8 +530,8 @@ def test_risk_observation_api_enforces_pool_claim_and_typed_action_permissions(
headers=finance_headers,
)
assert detail.status_code == 200
assert detail.json()["disposition"]["version"] == 1
assert len(detail.json()["disposition"]["events"]) == 1
assert detail.json()["disposition"]["version"] == 2
assert len(detail.json()["disposition"]["events"]) == 2
def test_current_claim_approver_can_manage_disposition_without_pool_access() -> None: