153 lines
7.0 KiB
Python
153 lines
7.0 KiB
Python
import sys
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parents[3]
|
|
sys.path.insert(0, str(ROOT / "harness" / "lib"))
|
|
import impl_operation as op
|
|
|
|
|
|
class ImplOperationTests(unittest.TestCase):
|
|
def test_reentrant_preflight_cannot_authorize_duplicate_mutation(self):
|
|
coordinator = op.Coordinator()
|
|
intent = make_intent()
|
|
approval = make_approval(intent)
|
|
writes = []
|
|
entered = []
|
|
def preflight(value):
|
|
if not entered:
|
|
entered.append(True)
|
|
coordinator.execute(intent, approval, provider)
|
|
return {"fresh": True}
|
|
provider = adapter(preflight=preflight, mutate=lambda value: writes.append("write"))
|
|
coordinator.execute(intent, approval, provider)
|
|
self.assertEqual(writes, ["write"])
|
|
|
|
def test_preflight_cannot_change_approved_payload_before_mutation(self):
|
|
intent = make_intent()
|
|
calls = []
|
|
def preflight(i):
|
|
i.payload["body"] = "unapproved"
|
|
return {"fresh": True}
|
|
c = op.Coordinator()
|
|
result = c.execute(intent, make_approval(intent), adapter(preflight=preflight,
|
|
mutate=lambda i: calls.append(i.payload["body"])), calls.append)
|
|
self.assertEqual(calls[-1], "ok")
|
|
self.assertEqual(result.status, "verified")
|
|
|
|
def test_attempt_callback_must_be_callable_before_preflight(self):
|
|
calls = []
|
|
result = op.Coordinator().execute(make_intent(), make_approval(),
|
|
adapter(preflight=lambda i: calls.append("preflight") or {"fresh": True}), on_attempt=3)
|
|
self.assertEqual(calls, [])
|
|
self.assertEqual(result.status, "partial")
|
|
|
|
def test_attempt_callback_failure_consumes_authority_without_write(self):
|
|
calls = []
|
|
c = op.Coordinator()
|
|
a = adapter(mutate=lambda i: calls.append("write"))
|
|
result = c.execute(make_intent(), make_approval(), a,
|
|
lambda record: (_ for _ in ()).throw(RuntimeError("secret credential")))
|
|
self.assertEqual(calls, [])
|
|
self.assertNotIn("secret credential", result.reason)
|
|
self.assertEqual(c.execute(make_intent(), make_approval(), a).status, "partial")
|
|
|
|
def test_malformed_canonical_target_rejected(self):
|
|
for target in ("owner/..", "owner/.", "owner/repo\\x", "owner/repo\n"):
|
|
with self.subTest(target=target):
|
|
i = op.Intent("v1", "push", target, 1, "a"*40, {})
|
|
self.assertRaises(ValueError, op.validate, i, op.Approval("v1", "0"*64, "approved"))
|
|
|
|
def test_unserializable_ack_does_not_lose_verified_result(self):
|
|
result = op.Coordinator().execute(make_intent(), make_approval(), adapter(mutate=lambda i: object()))
|
|
self.assertEqual(result.status, "verified")
|
|
|
|
def test_verify_exception_returns_partial_and_no_retry(self):
|
|
calls = []
|
|
c = op.Coordinator()
|
|
a = adapter(mutate=lambda i: calls.append("write"), verify=lambda *args: (_ for _ in ()).throw(RuntimeError("private")))
|
|
result = c.execute(make_intent(), make_approval(), a)
|
|
self.assertEqual(result.status, "partial")
|
|
self.assertNotIn("private", result.reason)
|
|
c.execute(make_intent(), make_approval(), a)
|
|
self.assertEqual(calls, ["write"])
|
|
|
|
def test_versioned_intent_and_approval_validate(self):
|
|
intent = op.Intent("v1", "comment.patch", "owner/repo", 12, "a" * 40, {"body": "ok"})
|
|
approval = op.Approval("v1", intent.digest, "approved")
|
|
self.assertEqual(op.validate(intent, approval).digest, intent.digest)
|
|
|
|
def test_invalid_full_sha_rejected_before_callbacks(self):
|
|
calls = []
|
|
intent = op.Intent("v1", "push", "owner/repo", 1, "a" * 39, {})
|
|
result = op.Coordinator().execute(intent, op.Approval("v1", "0" * 64, "approved"), adapter(), calls.append)
|
|
self.assertEqual(result.status, "partial")
|
|
self.assertEqual(calls, [])
|
|
|
|
def test_stale_preflight_fails_without_mutation(self):
|
|
calls = []
|
|
result = op.Coordinator().execute(make_intent(), make_approval(), adapter(preflight=lambda i: {"fresh": False}), calls.append)
|
|
self.assertEqual(result.status, "partial")
|
|
self.assertEqual(calls, [])
|
|
|
|
def test_unsupported_capability_fails_before_write(self):
|
|
calls = []
|
|
result = op.Coordinator().execute(make_intent(), make_approval(), adapter(capability=False), calls.append)
|
|
self.assertEqual(result.status, "partial")
|
|
self.assertEqual(calls, [])
|
|
|
|
def test_uncertain_ack_reconciles_without_second_write(self):
|
|
calls = []
|
|
a = adapter(mutate=lambda i: (_ for _ in ()).throw(op.UncertainAcknowledgement()), readback=lambda i: {"state": "applied"}, verify=lambda i, p, r: True)
|
|
result = op.Coordinator().execute(make_intent(), make_approval(), a, calls.append)
|
|
self.assertEqual(len(calls), 1)
|
|
self.assertEqual(result.status, "verified")
|
|
self.assertEqual(result.assurance["durable_at_most_once"], False)
|
|
|
|
def test_reuse_cannot_mutate_twice(self):
|
|
calls = []
|
|
c = op.Coordinator()
|
|
a = adapter()
|
|
c.execute(make_intent(), make_approval(), a, calls.append)
|
|
again = c.execute(make_intent(), make_approval(), a, calls.append)
|
|
self.assertEqual(len(calls), 1)
|
|
self.assertEqual(again.status, "partial")
|
|
|
|
def test_verification_requires_trusted_adapter_callback(self):
|
|
calls = []
|
|
result = op.Coordinator().execute(make_intent(), make_approval(), adapter(verify=lambda *args: False), calls.append)
|
|
self.assertEqual(result.status, "partial")
|
|
self.assertNotEqual(result.status, "verified")
|
|
|
|
def test_correction_needs_separate_approval_unless_explicit_patch_exception(self):
|
|
calls = []
|
|
correction = make_intent(operation="correction")
|
|
result = op.Coordinator().execute(correction, make_approval(correction), adapter(capability="ordinary_patch"), calls.append)
|
|
self.assertEqual(result.status, "partial")
|
|
self.assertEqual(calls, [])
|
|
approval = make_approval(correction, correction=True, ordinary_patch=True)
|
|
result = op.Coordinator().execute(correction, approval, adapter(capability="ordinary_patch"), calls.append)
|
|
self.assertEqual(result.assurance["atomic_client_precondition"], False)
|
|
self.assertIn("residual_race", result.assurance)
|
|
|
|
|
|
def make_intent(operation="comment.patch"):
|
|
return op.Intent("v1", operation, "owner/repo", 12, "a" * 40, {"body": "ok"})
|
|
|
|
|
|
def make_approval(intent=None, correction=False, ordinary_patch=False):
|
|
intent = intent or make_intent()
|
|
return op.Approval("v1", intent.digest, "approved", correction=correction, ordinary_patch=ordinary_patch)
|
|
|
|
|
|
def adapter(**kwargs):
|
|
defaults = dict(preflight=lambda i: {"fresh": True}, capability=True,
|
|
mutate=lambda i: {"written": True}, readback=lambda i: {"state": "applied"},
|
|
verify=lambda i, p, r: True)
|
|
defaults.update(kwargs)
|
|
return op.Adapter(**defaults)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|