Implement approval engine production readiness
Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a05e2e-805b-7042-a750-71f473bceea2
This commit is contained in:
parent
ebce5abb27
commit
2bd2d19a98
30 changed files with 1679 additions and 53 deletions
105
tests/test_pep.py
Normal file
105
tests/test_pep.py
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
import json
|
||||
|
||||
import pytest
|
||||
|
||||
from approval_engine.pep import (
|
||||
ApprovalHTTPClient,
|
||||
ApprovalProtocolError,
|
||||
ProtectedActionHarness,
|
||||
)
|
||||
|
||||
|
||||
class Client:
|
||||
def __init__(self, claim=None, consume=None, failure=None):
|
||||
self.claim_result = claim or {"valid_now": True, "consumed": False}
|
||||
self.consume_result = consume
|
||||
self.failure = failure
|
||||
self.calls = []
|
||||
|
||||
def claim(self, approval_id):
|
||||
self.calls.append("claim")
|
||||
if self.failure == "claim":
|
||||
raise ApprovalProtocolError("down")
|
||||
return self.claim_result
|
||||
|
||||
def consume(self, approval_id, digest, decision_id):
|
||||
self.calls.append("consume")
|
||||
if self.failure == "consume":
|
||||
raise ApprovalProtocolError("conflict")
|
||||
return self.consume_result or {"status": "consumed", "request_digest": digest}
|
||||
|
||||
|
||||
DIGEST = "sha256:" + "ab" * 32
|
||||
|
||||
|
||||
class Response:
|
||||
def __init__(self, body):
|
||||
self.body = json.dumps(body).encode()
|
||||
|
||||
def getcode(self):
|
||||
return 200
|
||||
|
||||
def read(self, _size):
|
||||
return self.body
|
||||
|
||||
def close(self):
|
||||
pass
|
||||
|
||||
|
||||
def test_http_client_rereads_mounted_token(tmp_path):
|
||||
token = tmp_path / "token"
|
||||
token.write_text("first")
|
||||
seen = []
|
||||
|
||||
def opener(request, timeout):
|
||||
seen.append((request.get_header("Authorization"), timeout))
|
||||
return Response({"valid_now": True, "consumed": False})
|
||||
|
||||
client = ApprovalHTTPClient("http://approval-engine:8080", token, opener=opener)
|
||||
client.claim("approval-1")
|
||||
token.write_text("second")
|
||||
client.claim("approval-1")
|
||||
assert [item[0] for item in seen] == ["Bearer first", "Bearer second"]
|
||||
|
||||
|
||||
def allow(_claim):
|
||||
return {"effect": "ALLOW", "decision_id": "decision:1", "request_digest": DIGEST}
|
||||
|
||||
|
||||
def test_side_effect_occurs_only_after_claim_decision_and_consume():
|
||||
client = Client()
|
||||
order = client.calls
|
||||
result = ProtectedActionHarness(client).execute(
|
||||
"approval:1",
|
||||
DIGEST,
|
||||
lambda claim: (order.append("decision"), allow(claim))[1],
|
||||
lambda: (order.append("side-effect"), "dry-run-only")[1],
|
||||
)
|
||||
assert result == "dry-run-only"
|
||||
assert order == ["claim", "decision", "consume", "side-effect"]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("failure", ["claim", "consume"])
|
||||
def test_unavailable_or_conflicting_engine_prevents_side_effect(failure):
|
||||
client = Client(failure=failure)
|
||||
effects = []
|
||||
with pytest.raises(ApprovalProtocolError):
|
||||
ProtectedActionHarness(client).execute(
|
||||
"approval:1", DIGEST, allow, lambda: effects.append("called")
|
||||
)
|
||||
assert effects == []
|
||||
|
||||
|
||||
def test_deny_or_digest_mismatch_prevents_consume_and_side_effect():
|
||||
for decision in (
|
||||
{"effect": "DENY", "decision_id": "decision:1", "request_digest": DIGEST},
|
||||
{"effect": "ALLOW", "decision_id": "decision:1", "request_digest": "sha256:" + "cd" * 32},
|
||||
):
|
||||
client = Client()
|
||||
effects = []
|
||||
with pytest.raises(ApprovalProtocolError):
|
||||
ProtectedActionHarness(client).execute(
|
||||
"approval:1", DIGEST, lambda _claim, value=decision: value, lambda: effects.append("called")
|
||||
)
|
||||
assert client.calls == ["claim"]
|
||||
assert effects == []
|
||||
Loading…
Add table
Add a link
Reference in a new issue