fix: bind projection retries and validate exact receipts
Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a07ff8-19d0-7820-b4d0-1353833cb7fc
This commit is contained in:
parent
84df076e95
commit
0068e34644
3 changed files with 156 additions and 12 deletions
|
|
@ -1,10 +1,12 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import subprocess
|
||||
from datetime import date
|
||||
from pathlib import Path
|
||||
|
||||
import httpx
|
||||
import pytest
|
||||
|
||||
from repo_manager.commands.task import add_adhoc_task, mutate_task
|
||||
from repo_manager.commands.workplan import create_workplan
|
||||
|
|
@ -173,10 +175,7 @@ def test_sync_uses_two_requests_and_exact_pushed_commit(tmp_path: Path) -> None:
|
|||
)
|
||||
return httpx.Response(
|
||||
200,
|
||||
json={
|
||||
"outcome": {"status": "applied", "counts": {"created": 1}},
|
||||
"derived_commit": payload["expected_commit"],
|
||||
},
|
||||
json=_receipt(payload),
|
||||
)
|
||||
|
||||
result = sync_repository_projection(
|
||||
|
|
@ -234,3 +233,62 @@ def test_sync_queues_instead_of_writing_to_wrong_instance(tmp_path: Path) -> Non
|
|||
assert result["status"] == "queued"
|
||||
assert result["reason"] == "wrong_instance"
|
||||
assert Path(result["pending_path"]).is_file()
|
||||
|
||||
|
||||
def _receipt(payload, **overrides):
|
||||
return {
|
||||
"schema": "state-hub.repository-projection-reconcile.v1",
|
||||
"instance_role": "primary", "instance_label": "railiance01",
|
||||
"expected_commit": payload["expected_commit"],
|
||||
"derived_commit": payload["expected_commit"],
|
||||
"outcome": {"repo_slug": "pilot", "commit": payload["expected_commit"],
|
||||
"status": "applied", "counts": {"created": 1}},
|
||||
**overrides,
|
||||
}
|
||||
|
||||
|
||||
def test_reviewed_retry_has_distinct_key_and_identical_retry_replays(tmp_path):
|
||||
repo = _fixture(tmp_path, remote=True)
|
||||
seen = {}
|
||||
keys = []
|
||||
|
||||
def handler(request):
|
||||
if request.url.path == "/state/health":
|
||||
return httpx.Response(200, json={"instance_role": "primary", "instance_label": "railiance01"})
|
||||
payload = json.loads(request.content)
|
||||
key = request.headers["Idempotency-Key"]
|
||||
keys.append(key)
|
||||
if key in seen and seen[key] != payload:
|
||||
return httpx.Response(409, json={"detail": "idempotency key reused for different payload"})
|
||||
seen[key] = payload
|
||||
result = _receipt(payload)
|
||||
result["outcome"]["status"] = "applied" if payload["acknowledge_retirements"] else "refused"
|
||||
return httpx.Response(200, json=result)
|
||||
|
||||
kwargs = {"api_base": "http://hub.test", "push": True, "transport": httpx.MockTransport(handler)}
|
||||
refused = sync_repository_projection(repo, **kwargs)
|
||||
admitted = sync_repository_projection(repo, acknowledge_retirements=True, **kwargs)
|
||||
replay = sync_repository_projection(repo, acknowledge_retirements=True, **kwargs)
|
||||
assert refused["status"] == "refused"
|
||||
assert admitted["status"] == replay["status"] == "applied"
|
||||
assert keys[0] != keys[1] == keys[2]
|
||||
assert len(seen) == 2
|
||||
|
||||
|
||||
@pytest.mark.parametrize("overrides", [
|
||||
{"expected_commit": "f" * 40}, {"derived_commit": "e" * 40},
|
||||
{"instance_role": "cache"}, {"instance_label": "workstation"},
|
||||
{"schema": "unrelated-receipt/v1"}, {"outcome": {"status": "applied"}},
|
||||
])
|
||||
def test_sync_refuses_inconsistent_receipt(tmp_path, overrides):
|
||||
repo = _fixture(tmp_path, remote=True)
|
||||
|
||||
def handler(request):
|
||||
if request.url.path == "/state/health":
|
||||
return httpx.Response(200, json={"instance_role": "primary", "instance_label": "railiance01"})
|
||||
return httpx.Response(200, json=_receipt(json.loads(request.content), **overrides))
|
||||
|
||||
result = sync_repository_projection(repo, api_base="http://hub.test", push=True,
|
||||
transport=httpx.MockTransport(handler))
|
||||
assert result["ok"] is False
|
||||
assert result["status"] == "invalid_receipt"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue