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