test_phase3_wp06_reclaim_outbox_postgres.py 5.4 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586
  1. from __future__ import annotations
  2. import os
  3. import threading
  4. import uuid
  5. from concurrent.futures import ThreadPoolExecutor
  6. import pytest
  7. from sqlalchemy import create_engine, text
  8. from app.core.system.trusted_delivery_repository import (
  9. SqlAlchemyTrustedDeliveryRepository,
  10. )
  11. pytestmark = pytest.mark.integration
  12. def _url():
  13. value = os.environ.get("TEST_DATABASE_URL")
  14. if not value:
  15. pytest.skip("TEST_DATABASE_URL is required")
  16. return value
  17. def test_reclaim_outbox_is_fenced_replay_safe_and_restart_recoverable():
  18. """Two DB connections exercise the durable revoke outbox, not a mock lock."""
  19. engine = create_engine(_url(), pool_pre_ping=True)
  20. actor, policy, grant, asset = (str(uuid.uuid4()) for _ in range(4))
  21. prefix = f"wp06-test-outbox-{actor[:8]}"
  22. try:
  23. with engine.begin() as connection:
  24. connection.execute(text("INSERT INTO public.users(id,username,display_name,password_hash,status) VALUES(CAST(:id AS uuid),:name,:name,'test','active')"), {"id": actor, "name": prefix})
  25. repo = SqlAlchemyTrustedDeliveryRepository(connection)
  26. repo.create_policy_version({"uid": policy, "code": f"WP06_OUTBOX_{actor[:8]}", "version": "1", "status": "active", "selector": {}, "resource_rules": {}, "created_by": actor, "current_version": 1})
  27. repo.enqueue_grant({"uid": grant, "policy_uid": policy, "subject_uid": actor, "asset_uid": asset, "provider": "database", "idempotency_key": f"{prefix}-grant", "request_digest": "a" * 64, "expires_at": "2026-08-01T00:00:00+00:00", "status": "active", "reason_code": "fixture", "current_version": 1, "created_by": actor})
  28. barrier = threading.Barrier(2)
  29. calls = []
  30. def claim(key):
  31. with engine.begin() as connection:
  32. barrier.wait(timeout=5)
  33. result = SqlAlchemyTrustedDeliveryRepository(connection).claim_reclaim_operation(grant, key, "b" * 64, f"worker-{key}")
  34. if result["state"] == "claimed":
  35. calls.append(result["delivery_uid"])
  36. return result
  37. with ThreadPoolExecutor(max_workers=2) as pool:
  38. first, second = list(pool.map(claim, ("one", "two")))
  39. claimed = next(item for item in (first, second) if item["state"] == "claimed")
  40. assert sorted(item["state"] for item in (first, second)) == ["claimed", "leased"]
  41. assert calls == [claimed["delivery_uid"]] # provider may be called only by claimant
  42. with engine.begin() as connection:
  43. repo = SqlAlchemyTrustedDeliveryRepository(connection)
  44. assert repo.claim_reclaim_operation(grant, "one", "b" * 64, "worker-replay")["state"] == "leased"
  45. with pytest.raises(RuntimeError, match="idempotency conflict"):
  46. repo.claim_reclaim_operation(grant, "one", "c" * 64, "worker-conflict")
  47. connection.execute(text("UPDATE public.trusted_delivery_deliveries SET lease_expires_at=clock_timestamp()-interval '1 second' WHERE uid=CAST(:uid AS uuid)"), {"uid": claimed["delivery_uid"]})
  48. with engine.begin() as connection:
  49. repo = SqlAlchemyTrustedDeliveryRepository(connection)
  50. restarted = repo.claim_reclaim_operation(grant, "two", "b" * 64, "worker-restarted")
  51. assert restarted["state"] == "claimed"
  52. with pytest.raises(RuntimeError, match="lease conflict"):
  53. repo.complete_claimed_reclaim(grant, claimed["delivery_uid"], "worker-one", claimed["lease_fence"], {"receipt_code": "LATE", "response_digest": "d" * 64, "diff_digest": "e" * 64})
  54. repo.fail_reclaim_operation(restarted["delivery_uid"], "worker-restarted", restarted["lease_fence"])
  55. with engine.begin() as connection:
  56. repo = SqlAlchemyTrustedDeliveryRepository(connection)
  57. retry = repo.claim_reclaim_operation(grant, "two", "b" * 64, "worker-retry")
  58. assert retry["state"] == "claimed"
  59. saved = repo.complete_claimed_reclaim(grant, retry["delivery_uid"], "worker-retry", retry["lease_fence"], {"receipt_code": "REVOKED", "response_digest": "d" * 64, "diff_digest": "e" * 64})
  60. assert saved["status"] == "reclaimed"
  61. assert connection.execute(text("SELECT count(*) FROM public.trusted_delivery_receipts WHERE grant_uid=CAST(:uid AS uuid)"), {"uid": grant}).scalar_one() == 1
  62. finally:
  63. with engine.begin() as connection:
  64. connection.execute(text("ALTER TABLE public.trusted_delivery_receipts DISABLE TRIGGER USER"))
  65. connection.execute(text("DELETE FROM public.trusted_delivery_receipts WHERE grant_uid=CAST(:uid AS uuid)"), {"uid": grant})
  66. connection.execute(text("ALTER TABLE public.trusted_delivery_receipts ENABLE TRIGGER USER"))
  67. connection.execute(text("DELETE FROM public.trusted_delivery_deliveries WHERE grant_uid=CAST(:uid AS uuid)"), {"uid": grant})
  68. connection.execute(text("DELETE FROM public.trusted_delivery_policy_transitions WHERE policy_uid=CAST(:uid AS uuid)"), {"uid": policy})
  69. connection.execute(text("DELETE FROM public.trusted_delivery_grants WHERE uid=CAST(:uid AS uuid)"), {"uid": grant})
  70. connection.execute(text("DELETE FROM public.trusted_delivery_policy_versions WHERE uid=CAST(:uid AS uuid)"), {"uid": policy})
  71. connection.execute(text("DELETE FROM public.users WHERE id=CAST(:uid AS uuid)"), {"uid": actor})
  72. engine.dispose()