approval-engine/tests/test_pep.py
tegwick 2bd2d19a98 Implement approval engine production readiness
Assistant: codex
Assistant-Model: gpt-5.6-sol
Assistant-Session: 01a05e2e-805b-7042-a750-71f473bceea2
2026-09-02 00:52:04 +02:00

105 lines
3.2 KiB
Python

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 == []