"""Synthetic actual provisioner/HTTP/storage proof; no live LDAP or authority.""" import concurrent.futures from dataclasses import asdict from http.server import ThreadingHTTPServer import json import os import sqlite3 from pathlib import Path import tempfile import threading import unittest from unittest.mock import patch from urllib.error import HTTPError from urllib.request import Request, urlopen from group_provenance import GroupProvenance from provisioner import LLDAPProvisioner, dispatch from server import Handler, configured_group_provenance from test_service_groups import Directory, req def rollback(**kw): return req(ensure_idempotency_key=req()["idempotency_key"], ensure_correlation_id=req()["correlation_id"], **kw) class ProvenanceTests(unittest.TestCase): def test_inventory_excludes_preexisting_membership_and_empty_group(self): d = Directory() d.members[3].add("tegwick") result = d.ensure_service_groups(req()) self.assertEqual([{"name": "forge-coulomb-owners", "id": 4}, {"name": "forge-coulomb-readers", "id": 5}], list(result.created_groups)) self.assertEqual([{"name": "forge-coulomb-owners", "id": 4, "username": "tegwick"}], list(result.created_memberships)) d.revoke_service_groups(rollback(delete_if_empty=True)) self.assertEqual({"tegwick"}, d.members[3]) self.assertIn("forge-admins", d.groups) self.assertNotIn("forge-coulomb-owners", d.groups) def test_preexisting_empty_group_never_deleted(self): d = Directory() d.ensure_service_groups(req()) d.revoke_service_groups(rollback(delete_if_empty=True)) self.assertIn("forge-admins", d.groups) self.assertEqual(set(), d.members[3]) def test_unknown_receipt_and_digest_mismatch_before_login(self): d = Directory() with self.assertRaises(ValueError): d.revoke_service_groups(rollback()) d.ensure_service_groups(req()) d.calls.clear() with patch.object(d, "_login", side_effect=AssertionError("must not login")): with self.assertRaises(ValueError): d.revoke_service_groups(rollback(members={"forge-admins": ["agent-codex"]})) with self.assertRaises(ValueError): d.ensure_service_groups(req(groups=["another-group"], members={})) self.assertEqual([], d.calls) def test_unsigned_inventory_and_legacy_revoke_cannot_mutate(self): d = Directory() d.ensure_service_groups(req()) d.calls.clear() for payload in [req(), rollback(created_groups=[{"name": "forge-admins", "id": 3}]), rollback(created_memberships=[]), rollback(provenance={})]: with self.assertRaises(ValueError): d.revoke_service_groups(payload) self.assertEqual([], d.calls) def test_shared_membership_drift_refuses_all_destructive_calls(self): d = Directory() d.ensure_service_groups(req()) d.members[3].add("agent-codex") d.calls.clear() with self.assertRaisesRegex(RuntimeError, "postimage"): d.revoke_service_groups(rollback(delete_if_empty=True)) self.assertEqual([], d.calls) self.assertEqual({"tegwick", "agent-codex"}, d.members[3]) def test_replaced_group_identity_refuses_all_destructive_calls(self): d = Directory() d.ensure_service_groups(req()) d.groups["forge-admins"] = 10 d.members[10] = {"tegwick"} d.calls.clear() with self.assertRaisesRegex(RuntimeError, "postimage"): d.revoke_service_groups(rollback()) self.assertEqual([], d.calls) def test_successful_ensure_replay_never_recreates_later_removed_state(self): d = Directory() first = d.ensure_service_groups(req()) d.members[3].clear() d.calls.clear() replay = d.ensure_service_groups(req()) self.assertTrue(replay.resumed) self.assertEqual(first.created_memberships, replay.created_memberships) self.assertEqual([], d.calls) self.assertEqual(set(), d.members[3]) def test_interrupted_add_persists_intent_and_blocks_restart_retry(self): d = Directory() original = d._gql def lost_response(token, query, variables): result = original(token, query, variables) if "addUserToGroup" in query: raise RuntimeError("synthetic lost response") return result d._gql = lost_response with self.assertRaises(RuntimeError): d.ensure_service_groups(req()) d.group_provenance = GroupProvenance(d.group_provenance.path) receipt = d.group_provenance.get(req()["idempotency_key"], d._group_binding(req()["groups"], req()["members"], req()["correlation_id"])) self.assertEqual("uncertain", receipt["state"]) self.assertEqual("add_member", receipt["intent"]["operation"]) self.assertEqual([], receipt["created_memberships"]) d.calls.clear() for operation, payload in [(d.ensure_service_groups, req()), (d.revoke_service_groups, rollback()), (d.ensure_service_groups, req(idempotency_key="different-key-is-not-recovery"))]: with self.assertRaises(RuntimeError): operation(payload) self.assertEqual([], d.calls) def test_interrupted_rollback_retains_confirmed_inventory_and_pending_intent(self): d = Directory() d.ensure_service_groups(req()) original = d._remove_group def lost_response(*args): original(*args) raise RuntimeError("lost remove response") d._remove_group = lost_response with self.assertRaises(RuntimeError): d.revoke_service_groups(rollback()) d.group_provenance = GroupProvenance(d.group_provenance.path) receipt = d.group_provenance.get(req()["idempotency_key"], d._group_binding(req()["groups"], req()["members"], req()["correlation_id"])) self.assertEqual("uncertain", receipt["state"]) self.assertEqual("remove_member", receipt["intent"]["operation"]) self.assertEqual(2, len(receipt["created_memberships"])) d.calls.clear() with self.assertRaises(RuntimeError): d.revoke_service_groups(rollback()) self.assertEqual([], d.calls) def test_unresolved_pre_call_intent_survives_hard_process_loss(self): d = Directory() d._gql = lambda *_: (_ for _ in ()).throw(SystemExit("process lost before response")) with self.assertRaises(SystemExit): d.ensure_service_groups(req()) d.group_provenance = GroupProvenance(d.group_provenance.path) receipt = d.group_provenance.get(req()["idempotency_key"], d._group_binding(req()["groups"], req()["members"], req()["correlation_id"])) self.assertEqual("pending", receipt["state"]) self.assertEqual("add_member", receipt["intent"]["operation"]) with self.assertRaises(RuntimeError): d.ensure_service_groups(req()) def test_missing_config_private_permissions_and_lost_database_fail_closed(self): with patch.dict(os.environ, {}, clear=True): self.assertIsNone(configured_group_provenance()) d = Directory() path = d.group_provenance.path with patch.dict(os.environ, {"GROUP_PROVENANCE_DB": path}): self.assertIsInstance(configured_group_provenance(), GroupProvenance) os.chmod(path, 0o644) with self.assertRaises(RuntimeError): configured_group_provenance() os.chmod(path, 0o600) os.unlink(path) with self.assertRaises(RuntimeError): configured_group_provenance() self.assertFalse(Path(path).exists()) provider = LLDAPProvisioner(base_url="https://invalid", admin_password="synthetic") with patch.object(provider, "_login", side_effect=AssertionError("no directory")): with self.assertRaises(RuntimeError): provider.ensure_service_groups(req()) with self.assertRaises(RuntimeError): provider.revoke_service_groups(rollback()) def test_missing_intent_row_stops_before_any_directory_effect(self): d = Directory() store = d.group_provenance original_save = store.save def lose_row(receipt): if receipt["intent"] is not None: with sqlite3.connect(store.path) as db: db.execute("DELETE FROM group_receipts") original_save(receipt) store.save = lose_row with self.assertRaisesRegex(RuntimeError, "group_provenance_unavailable"): d.ensure_service_groups(req()) self.assertEqual([], d.calls) def test_live_instance_rejects_replaced_database_before_effect(self): d = Directory() original = d.group_provenance replacement = str(Path(d.storage.name) / "replacement.db") GroupProvenance(replacement, initialize=True) os.replace(replacement, original.path) with self.assertRaisesRegex(RuntimeError, "group_provenance_unavailable"): d.ensure_service_groups(req()) self.assertEqual([], d.calls) def test_failed_before_mutation_is_retained_without_stranding_other_keys(self): for failure in ("unknown-user", "oversized-prestate", "directory-read-failed"): d = Directory() old_members = d._group_members request = req(members={"forge-admins": ["nobody"]}) if failure == "unknown-user" else req() if failure == "oversized-prestate": d._group_members = lambda *_: [f"synthetic{i}" for i in range(4097)] if failure == "directory-read-failed": d._group_members = lambda *_: (_ for _ in ()).throw(RuntimeError("read failed")) with self.assertRaises((ValueError, RuntimeError)): d.ensure_service_groups(request) binding = d._group_binding(request["groups"], request["members"], request["correlation_id"]) receipt = d.group_provenance.get(request["idempotency_key"], binding) self.assertEqual("refused", receipt["state"]) self.assertEqual("before_mutation", receipt["failure_phase"]) self.assertEqual([], d.calls) d._group_members = old_members with self.assertRaises(RuntimeError): d.ensure_service_groups(request) self.assertEqual("groups_ensured", d.ensure_service_groups(req(idempotency_key="another-valid-operation-key")).status) def test_current_correlations_are_bound_to_original_ensure(self): d = Directory() d.ensure_service_groups(req()) d.calls.clear() with self.assertRaises(ValueError): d.ensure_service_groups(req(correlation_id="other-case")) payload = rollback() payload["ensure_correlation_id"] = "other-case" with self.assertRaises(ValueError): d.revoke_service_groups(payload) self.assertEqual([], d.calls) def test_readiness_separates_directory_health_from_group_capability(self): provider = LLDAPProvisioner(base_url="https://invalid", admin_password="synthetic") provider._login = lambda **_: "synthetic" provider._gql = lambda *_args, **_kw: {"groups": []} self.assertEqual("disabled", provider.preflight()["service_groups"]) d = Directory() provider.group_provenance = d.group_provenance self.assertEqual("available", provider.preflight()["service_groups"]) os.chmod(d.group_provenance.path, 0o644) with self.assertRaisesRegex(RuntimeError, "group_provenance_unavailable"): provider.preflight() def test_concurrent_sqlite_reservation_allows_one_original_attempt(self): d = Directory() path = d.group_provenance.path def reserve(_): return GroupProvenance(path).reserve("synthetic-concurrent-key", "same-binding")[1] with concurrent.futures.ThreadPoolExecutor(max_workers=8) as pool: fresh = list(pool.map(reserve, range(24))) self.assertEqual(1, sum(fresh)) def test_bounded_records_never_age_or_evict_into_authority(self): d = Directory() store = GroupProvenance(d.group_provenance.path, capacity=1) receipt, _ = store.reserve("one", "binding") receipt.update(state="ensured", requested_groups=[], ensure_correlation_id="test") store.save(receipt) with self.assertRaisesRegex(RuntimeError, "capacity"): store.reserve("two", "binding") self.assertFalse(store.reserve("one", "binding")[1]) def test_rollback_replay_and_key_reuse_do_not_recreate(self): d = Directory() d.ensure_service_groups(req()) first = d.revoke_service_groups(rollback(delete_if_empty=True)) d.calls.clear() result = d.revoke_service_groups(rollback(delete_if_empty=True)) self.assertEqual(first.status, result.status) self.assertTrue(result.resumed) with self.assertRaises(RuntimeError): d.ensure_service_groups(req()) with self.assertRaises(ValueError): d.revoke_service_groups(rollback(delete_if_empty=False)) self.assertEqual([], d.calls) def test_real_http_handler_reinitialized_from_config_retains_receipt(self): d = Directory() path = d.group_provenance.path def boot(): with patch.dict(os.environ, {"GROUP_PROVENANCE_DB": path}): provider = LLDAPProvisioner(base_url="https://synthetic.invalid", admin_password="unused", group_provenance=configured_group_provenance()) # Only directory transport is synthetic; actual initialized adapter, # dispatch, HTTP authentication, configuration and SQLite are used. for name in ("_login", "_directory", "_group_members", "_ensure_group", "_gql", "_remove_group"): setattr(provider, name, getattr(d, name)) class LocalHandler(Handler): provisioner = provider service_token = "synthetic-service-token" server = ThreadingHTTPServer(("127.0.0.1", 0), LocalHandler) thread = threading.Thread(target=server.serve_forever, daemon=True) thread.start() return server, thread def stop(server, thread): server.shutdown(); server.server_close(); thread.join() def post(server, endpoint, payload): request = Request(f"http://127.0.0.1:{server.server_port}/v1/groups/{endpoint}", data=json.dumps(payload).encode(), headers={"Authorization": "Bearer synthetic-service-token"}) with urlopen(request) as response: return json.load(response) server, thread = boot() try: created = post(server, "ensure", req()) finally: stop(server, thread) server, thread = boot() try: d.calls.clear() replay = post(server, "ensure", req()) self.assertTrue(replay["resumed"]) self.assertEqual(created["created_memberships"], replay["created_memberships"]) self.assertEqual([], d.calls) revoked = post(server, "revoke", rollback(delete_if_empty=True)) self.assertEqual("groups_revoked_and_deleted", revoked["status"]) self.assertIn("forge-admins", d.groups) with self.assertRaises(HTTPError) as error: post(server, "revoke", req()) self.assertEqual(400, error.exception.code) d.calls.clear() os.chmod(path, 0o644) with self.assertRaises(HTTPError) as error: post(server, "ensure", req(idempotency_key="different-invocation-key")) self.assertEqual(503, error.exception.code) self.assertEqual({"error": "dependency_unavailable", "dependency": "group_provenance"}, json.load(error.exception)) self.assertEqual([], d.calls) finally: stop(server, thread)