user-engine/tests/test_custody_adapter.py

134 lines
5.7 KiB
Python
Raw Normal View History

import copy
import importlib.util
import json
from pathlib import Path
import unittest
from datetime import datetime, timedelta, timezone
from user_engine.custody import CustodyBaseline, CustodyObservationAdapter, CustodyRead
CANON = Path(__file__).resolve().parents[2] / "net-kingdom/tools/custody-record"
from user_engine.custody_canon import policy
class Baselines:
def __init__(self):
self.value = None
self.conflict = False
def load(self):
return copy.deepcopy(self.value)
def compare_and_store(self, previous, current):
if self.conflict or previous != self.value:
return False
self.value = copy.deepcopy(current)
return True
@unittest.skipUnless(CANON.exists(), "optional sibling NetKingdom fixtures absent")
class CustodyAdapterTests(unittest.TestCase):
def setUp(self):
self.document = json.loads((CANON / "fixtures/valid/grant.json").read_text())
self.now = datetime(2026, 10, 1, 10, tzinfo=timezone.utc)
self.read_at = self.now
self.source = "fixture-authenticated-owner"
self.baselines = Baselines()
self.reads = 0
self.failure = False
self.adapter = CustodyObservationAdapter(
reader=self, baselines=self.baselines, policy=policy,
source_id=self.source, record_id=self.document["record_id"],
allow_initial_baseline=True,
)
self.subject = dict(self.document["entries"][0]["subject"])
def read(self):
self.reads += 1
if self.failure:
raise RuntimeError("sensitive error must never escape")
return CustodyRead(self.source, self.document, self.read_at)
def observe(self, **overrides):
return self.adapter.observe(**{**self.subject, "tenant": "platform", "now": self.now, **overrides})
def test_exact_mapping_and_fresh_read_each_time(self):
result = self.observe()
self.assertEqual(result["subject"], self.subject)
self.assertEqual(result["source"], {"record_id": self.document["record_id"], "revision": 1})
result["subject"]["subject"] = "changed"
self.assertEqual(self.observe()["subject"], self.subject)
self.assertEqual(self.reads, 2)
self.assertIsNone(self.observe(issuer=self.subject["issuer"] + "/"))
self.assertIsNone(self.observe(subject="unknown"))
self.assertIsNone(self.observe(tenant="other"))
def test_failure_and_stale_read_never_refresh_old_grant(self):
self.assertIsNotNone(self.observe())
baseline = self.baselines.load()
self.failure = True
self.assertIsNone(self.observe())
self.failure = False
self.now += timedelta(seconds=901)
self.assertIsNone(self.observe())
self.assertEqual(self.baselines.load(), baseline)
def test_revocation_retained_and_rollback_denied_across_restart(self):
grant = copy.deepcopy(self.document)
self.assertIsNotNone(self.observe())
self.document = json.loads((CANON / "fixtures/valid/revocation.json").read_text())
self.now = self.read_at = datetime(2026, 10, 3, 12, tzinfo=timezone.utc)
self.assertIsNone(self.observe())
self.assertEqual(self.baselines.value.document["revision"], 2)
self.adapter = CustodyObservationAdapter(reader=self, baselines=self.baselines, policy=policy,
source_id=self.source, record_id=grant["record_id"])
self.document = grant
self.assertIsNone(self.observe())
self.assertEqual(self.baselines.value.document["revision"], 2)
def test_same_revision_mutation_and_source_change_denied(self):
self.assertIsNotNone(self.observe())
self.document["entries"][0]["granted"]["reason"] = "rewritten"
self.assertIsNone(self.observe())
self.source = "other-owner"
self.assertIsNone(self.observe())
def test_invalid_projection_and_future_clock_denied(self):
self.now -= timedelta(seconds=1)
self.assertIsNone(self.observe())
self.now = self.read_at
self.document = json.loads((CANON / "fixtures/invalid/projection-only-grant.json").read_text())
self.assertIsNone(self.observe())
self.assertIsNone(self.baselines.value)
def test_check_clock_sampled_after_owner_read(self):
self.adapter.clock = lambda: self.now + timedelta(seconds=2)
self.read_at = self.now + timedelta(seconds=1)
result = self.adapter.observe(**self.subject, tenant="platform")
self.assertIsNotNone(result)
self.assertEqual(result["observed_at"], self.read_at.isoformat().replace("+00:00", "Z"))
def test_bootstrap_and_persistence_conflict_deny(self):
self.adapter.allow_initial_baseline = False
self.assertIsNone(self.observe())
self.assertEqual(self.reads, 0)
self.adapter.allow_initial_baseline = True
self.baselines.conflict = True
self.assertIsNone(self.observe())
def test_empty_and_supersession_revisions_are_retained(self):
self.document["entries"] = []
self.assertIsNone(self.observe())
self.assertEqual(self.baselines.value.document["revision"], 1)
# Separate approved bootstrap to the grant fixture, then handover.
self.baselines.value = None
self.document = json.loads((CANON / "fixtures/valid/grant.json").read_text())
self.assertIsNotNone(self.observe())
self.document = json.loads((CANON / "fixtures/valid/supersession.json").read_text())
self.now = self.read_at = datetime(2026, 10, 3, 12, tzinfo=timezone.utc)
self.assertIsNone(self.observe())
successor = self.document["entries"][1]["subject"]
self.assertIsNotNone(self.observe(**successor))
self.assertEqual(self.baselines.value.document["revision"], 2)