Implement the engine spine: claim, outbox, machine, API
Contracts first (T02–T04): approval claim schema with issuer, freshness,
and binding digest; local transactional outbox wire; load-bearing cadence
as heartbeat or reconciliation (layer.yaml declared).
Then the object (T06–T08): SQLite closed state machine, CAS supersession,
distinct-approver fail-closed, revocation without holder cooperation,
outbox insert in the same transaction. Tests fail the mutation when
emission fails, and revoke while the drain sink is down.
Introspection GET /v1/approvals/{id}/claim is a PIP fact, not a decision.
No public consume (T05 waits on GH-WP-0002-T06). Canon T-06 coverage for
wrong binding, expiry, revoke, and supersede.
FLEX-WP-0017 T03 is unblocked on this object; T05 remains blocked only on
consumption ordering.
Assistant: grok
Assistant-Session: 01a04ceb-2057-7e20-b0f9-c282964d5dd9
This commit is contained in:
parent
624e43f554
commit
9c9528f5b2
29 changed files with 2121 additions and 26 deletions
69
tests/test_api.py
Normal file
69
tests/test_api.py
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
from approval_engine.api import call
|
||||
from tests.conftest import binding, validity
|
||||
|
||||
|
||||
def test_readyz(app):
|
||||
status, body = call(app, "GET", "/v1/readyz")
|
||||
assert status == 200
|
||||
assert body["status"] == "ok"
|
||||
|
||||
|
||||
def test_create_entry_claim_roundtrip(app):
|
||||
status, created = call(
|
||||
app,
|
||||
"POST",
|
||||
"/v1/approvals",
|
||||
{"binding": binding(), "validity": validity(), "required_count": 1},
|
||||
)
|
||||
assert status == 201
|
||||
aid = created["id"]
|
||||
status, _ = call(app, "POST", f"/v1/approvals/{aid}/entries", {"subject_id": "user:alice"})
|
||||
assert status == 200
|
||||
status, claim = call(app, "GET", f"/v1/approvals/{aid}/claim")
|
||||
assert status == 200
|
||||
assert claim["kind"] == "approval-claim"
|
||||
assert claim["valid_now"] is True
|
||||
assert "effect" not in claim
|
||||
assert "decision" not in claim
|
||||
|
||||
|
||||
def test_no_check_or_authorize_or_consume(app):
|
||||
for path in (
|
||||
"/v1/check",
|
||||
"/authorize",
|
||||
"/v1/approvals/00000000-0000-0000-0000-000000000001/consume",
|
||||
"/v1/approvals/abc/consume",
|
||||
):
|
||||
status, body = call(app, "POST", path, {})
|
||||
assert status == 404
|
||||
assert "consume is not implemented" in body.get("message", "") or body["error"] == "not_found"
|
||||
|
||||
|
||||
def test_claim_after_revoke(app):
|
||||
_, created = call(
|
||||
app,
|
||||
"POST",
|
||||
"/v1/approvals",
|
||||
{"binding": binding(), "validity": validity()},
|
||||
)
|
||||
aid = created["id"]
|
||||
call(app, "POST", f"/v1/approvals/{aid}/entries", {"subject_id": "user:alice"})
|
||||
call(app, "POST", f"/v1/approvals/{aid}/revoke", {})
|
||||
status, claim = call(app, "GET", f"/v1/approvals/{aid}/claim")
|
||||
assert status == 200
|
||||
assert claim["valid_now"] is False
|
||||
assert claim["reason_code"] == "revoked"
|
||||
|
||||
|
||||
def test_store_unavailable_is_503():
|
||||
from approval_engine.api import App
|
||||
from approval_engine.errors import StoreUnavailable
|
||||
from approval_engine.store import Engine
|
||||
|
||||
class Dead(Engine):
|
||||
def outbox_stats(self):
|
||||
raise StoreUnavailable("down")
|
||||
|
||||
status, body = call(App(Dead.__new__(Dead)), "GET", "/v1/readyz")
|
||||
assert status == 503
|
||||
assert body["error"] == "store_unavailable"
|
||||
Loading…
Add table
Add a link
Reference in a new issue