test_wp12_metering_showback_postgres.py 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447
  1. """Real PostgreSQL gateway, concurrency, restart and downgrade checks for WP12."""
  2. from __future__ import annotations
  3. import json
  4. import os
  5. import subprocess
  6. import uuid
  7. from concurrent.futures import ThreadPoolExecutor
  8. from pathlib import Path
  9. from threading import Barrier
  10. import pytest
  11. from sqlalchemy import create_engine, text
  12. from sqlalchemy.engine import make_url
  13. ROOT = Path(__file__).resolve().parents[2]
  14. def _alembic(database_url: str, command: str, revision: str) -> subprocess.CompletedProcess[str]:
  15. return subprocess.run(
  16. [str(ROOT / ".venv/bin/alembic"), "-c", str(ROOT / "alembic.ini"), command, revision],
  17. cwd=ROOT,
  18. env={**os.environ, "MIGRATION_DATABASE_URL": database_url},
  19. capture_output=True,
  20. text=True,
  21. )
  22. def _claim(engine, action: str, principal: str) -> str:
  23. with engine.begin() as connection:
  24. connection.execute(text("SET LOCAL SESSION AUTHORIZATION dataops_bi_ai_catalog_control"))
  25. return str(
  26. connection.execute(
  27. text("SELECT public.metering_showback_issue_claim(:action,:principal,'{}'::jsonb)"),
  28. {"action": action, "principal": principal},
  29. ).scalar_one()["request_claim"]
  30. )
  31. def _runtime_write(engine, action: str, payload: dict) -> dict:
  32. with engine.begin() as connection:
  33. connection.execute(text("SET LOCAL SESSION AUTHORIZATION dataops_app_runtime"))
  34. return dict(
  35. connection.execute(
  36. text("SELECT public.metering_showback_runtime_write(:action,CAST(:payload AS jsonb))"),
  37. {"action": action, "payload": json.dumps(payload, sort_keys=True)},
  38. ).scalar_one()
  39. )
  40. def _control_write(engine, action: str, principal: str, payload: dict) -> dict:
  41. with engine.begin() as connection:
  42. connection.execute(text("SET LOCAL SESSION AUTHORIZATION dataops_bi_ai_catalog_control"))
  43. return dict(
  44. connection.execute(
  45. text("SELECT public.metering_showback_control_write(:action,:principal,CAST(:payload AS jsonb))"),
  46. {"action": action, "principal": principal, "payload": json.dumps(payload, sort_keys=True)},
  47. ).scalar_one()
  48. )
  49. @pytest.mark.integration
  50. def test_wp12_upgrade_preflight_refuses_illegal_548_correction_fact():
  51. base_url = os.getenv("TEST_DATABASE_URL")
  52. if not base_url:
  53. pytest.skip("TEST_DATABASE_URL is required")
  54. base = make_url(base_url)
  55. database = f"wp12_preflight_{uuid.uuid4().hex}"
  56. admin = create_engine(base.set(database="postgres").render_as_string(hide_password=False), isolation_level="AUTOCOMMIT")
  57. quoted = admin.dialect.identifier_preparer.quote(database)
  58. try:
  59. with admin.connect() as connection:
  60. connection.execute(text(f"CREATE DATABASE {quoted}"))
  61. database_url = base.set(database=database).render_as_string(hide_password=False)
  62. with create_engine(database_url).begin() as connection:
  63. connection.execute(text("CREATE EXTENSION IF NOT EXISTS pgcrypto"))
  64. # Match the fixed-owner schema capability provided by role-init.
  65. connection.execute(text("GRANT USAGE,CREATE ON SCHEMA public TO dataops_tenant_foundation_owner"))
  66. assert _alembic(database_url, "stamp", "20260818_545").returncode == 0
  67. assert _alembic(database_url, "upgrade", "20260818_548").returncode == 0
  68. engine = create_engine(database_url)
  69. try:
  70. with engine.begin() as connection:
  71. values = {"uid": "wp12-preflight-original", "tenant": "local-engineering", "domain": "default", "correction": None}
  72. connection.execute(text("INSERT INTO public.metering_events(event_uid,tenant_ref,domain_ref,department_ref,project_ref,cost_center_ref,event_kind,occurred_at,window_start,window_end,quantity_micros,unit,idempotency_key,evidence_digest,evidence_reference,request_digest,correction_of,lease_fence) VALUES(:uid,:tenant,:domain,'engineering','local-engineering','local-fixture','query','2026-08-18T00:00:00Z','2026-08-18T00:00:00Z','2026-08-18T00:05:00Z',1,'requests',:uid,:digest,'local-fixture://wp12/v1',:digest,:correction,0)"), values | {"digest": "a" * 64})
  73. connection.execute(text("INSERT INTO public.metering_events(event_uid,tenant_ref,domain_ref,department_ref,project_ref,cost_center_ref,event_kind,occurred_at,window_start,window_end,quantity_micros,unit,idempotency_key,evidence_digest,evidence_reference,request_digest,correction_of,lease_fence) VALUES('wp12-preflight-illegal','local-engineering','other','engineering','local-engineering','local-fixture','query','2026-08-18T00:00:00Z','2026-08-18T00:00:00Z','2026-08-18T00:05:00Z',1,'requests','wp12-preflight-illegal',:digest,'local-fixture://wp12/v1',:digest,'wp12-preflight-original',0)"), {"digest": "b" * 64})
  74. finally:
  75. engine.dispose()
  76. assert _alembic(database_url, "upgrade", "20260818_549").returncode == 0
  77. rejected = _alembic(database_url, "upgrade", "20260818_550")
  78. assert rejected.returncode != 0 and "upgrade refused: WP12 integrity preflight failed" in (rejected.stderr + rejected.stdout)
  79. finally:
  80. with admin.connect() as connection:
  81. connection.execute(text("SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname=:database"), {"database": database})
  82. connection.execute(text(f"DROP DATABASE IF EXISTS {quoted}"))
  83. admin.dispose()
  84. @pytest.mark.integration
  85. def test_wp12_restricted_migrator_transfers_definer_owners_and_can_be_removed():
  86. base_url = os.getenv("TEST_DATABASE_URL")
  87. if not base_url:
  88. pytest.skip("TEST_DATABASE_URL is required")
  89. base = make_url(base_url)
  90. database = f"wp12_owner_{uuid.uuid4().hex}"
  91. migrator = f"wp12_migrator_{uuid.uuid4().hex[:12]}"
  92. password = f"migrator-{uuid.uuid4().hex}"
  93. admin = create_engine(base.set(database="postgres").render_as_string(hide_password=False), isolation_level="AUTOCOMMIT")
  94. quoted_database = admin.dialect.identifier_preparer.quote(database)
  95. quoted_migrator = admin.dialect.identifier_preparer.quote(migrator)
  96. try:
  97. with admin.connect() as connection:
  98. connection.execute(text(f"CREATE DATABASE {quoted_database}"))
  99. database_url = base.set(database=database).render_as_string(hide_password=False)
  100. with create_engine(database_url).begin() as connection:
  101. connection.execute(text("CREATE EXTENSION IF NOT EXISTS pgcrypto"))
  102. assert _alembic(database_url, "stamp", "20260818_545").returncode == 0
  103. assert _alembic(database_url, "upgrade", "20260818_551").returncode == 0
  104. with create_engine(database_url).begin() as connection:
  105. connection.execute(text(f"CREATE ROLE {quoted_migrator} LOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION PASSWORD :password"), {"password": password})
  106. connection.execute(text(f"GRANT dataops_tenant_foundation_owner TO {quoted_migrator}"))
  107. connection.execute(text(f"GRANT USAGE ON SCHEMA public TO {quoted_migrator}"))
  108. connection.execute(text("GRANT USAGE,CREATE ON SCHEMA public TO dataops_tenant_foundation_owner"))
  109. connection.execute(text(f"GRANT SELECT,UPDATE ON public.alembic_version TO {quoted_migrator}"))
  110. # 551 was created by the temporary migration login in the defect
  111. # scenario. Simulate that exact owner state before the restricted
  112. # login upgrades to 552; it may only transfer functions it owns.
  113. functions = connection.execute(text("SELECT p.proname, pg_get_function_identity_arguments(p.oid) FROM pg_proc p JOIN pg_namespace n ON n.oid=p.pronamespace WHERE n.nspname='public' AND p.prosecdef AND p.proname LIKE 'metering_showback_%'" )).all()
  114. for name, args in functions:
  115. quoted_name = connection.dialect.identifier_preparer.quote(name)
  116. connection.execute(text(f"ALTER FUNCTION public.{quoted_name}({args}) OWNER TO {quoted_migrator}"))
  117. restricted_url = base.set(database=database, username=migrator, password=password).render_as_string(hide_password=False)
  118. restricted_upgrade = _alembic(restricted_url, "upgrade", "20260818_552")
  119. assert restricted_upgrade.returncode == 0, restricted_upgrade.stderr + restricted_upgrade.stdout
  120. with create_engine(database_url).connect() as connection:
  121. assert connection.execute(text("SELECT bool_and(r.rolname='dataops_tenant_foundation_owner' AND NOT r.rolcanlogin) FROM pg_proc p JOIN pg_namespace n ON n.oid=p.pronamespace JOIN pg_roles r ON r.oid=p.proowner WHERE n.nspname='public' AND p.prosecdef AND p.proname LIKE 'metering_showback_%'" )).scalar_one()
  122. assert _alembic(restricted_url, "downgrade", "20260818_551").returncode == 0
  123. assert _alembic(restricted_url, "upgrade", "20260818_552").returncode == 0
  124. finally:
  125. with admin.connect() as connection:
  126. connection.execute(text("SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname=:database"), {"database": database})
  127. connection.execute(text(f"DROP DATABASE IF EXISTS {quoted_database}"))
  128. connection.execute(text(f"REVOKE dataops_tenant_foundation_owner FROM {quoted_migrator}")) if connection.execute(text("SELECT EXISTS(SELECT 1 FROM pg_roles WHERE rolname=:role)"), {"role": migrator}).scalar_one() else None
  129. connection.execute(text(f"DROP ROLE IF EXISTS {quoted_migrator}"))
  130. admin.dispose()
  131. @pytest.mark.integration
  132. def test_wp12_real_postgres_migration_acl_replay_fence_restart_and_downgrade(monkeypatch):
  133. base_url = os.getenv("TEST_DATABASE_URL")
  134. if not base_url:
  135. pytest.skip("TEST_DATABASE_URL is required")
  136. base = make_url(base_url)
  137. database = f"wp12_metering_{uuid.uuid4().hex}"
  138. admin = create_engine(base.set(database="postgres").render_as_string(hide_password=False), isolation_level="AUTOCOMMIT")
  139. quoted = admin.dialect.identifier_preparer.quote(database)
  140. runtime_user, control_user = (f"wp12_api_runtime_{uuid.uuid4().hex[:12]}", f"wp12_api_control_{uuid.uuid4().hex[:12]}")
  141. runtime_password, control_password = (f"runtime-{uuid.uuid4().hex}", f"control-{uuid.uuid4().hex}")
  142. try:
  143. with admin.connect() as connection:
  144. connection.execute(text(f"CREATE DATABASE {quoted}"))
  145. database_url = base.set(database=database).render_as_string(hide_password=False)
  146. with create_engine(database_url).begin() as connection:
  147. connection.execute(text("CREATE EXTENSION IF NOT EXISTS pgcrypto"))
  148. # Match the fixed-owner schema capability provided by role-init.
  149. connection.execute(text("GRANT USAGE,CREATE ON SCHEMA public TO dataops_tenant_foundation_owner"))
  150. assert _alembic(database_url, "stamp", "20260818_545").returncode == 0
  151. upgraded = _alembic(database_url, "upgrade", "20260818_552")
  152. assert upgraded.returncode == 0, upgraded.stderr
  153. engine = create_engine(database_url, pool_pre_ping=True)
  154. try:
  155. with engine.begin() as connection:
  156. quote = connection.dialect.identifier_preparer.quote
  157. connection.execute(text(f"CREATE ROLE {quote(runtime_user)} LOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION PASSWORD :password"), {"password": runtime_password})
  158. connection.execute(text(f"CREATE ROLE {quote(control_user)} LOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION INHERIT PASSWORD :password"), {"password": control_password})
  159. connection.execute(text(f"GRANT dataops_app_runtime TO {quote(runtime_user)}"))
  160. connection.execute(text(f"GRANT dataops_bi_ai_catalog_issuer TO {quote(control_user)}"))
  161. connection.execute(text(f"GRANT EXECUTE ON FUNCTION public.metering_showback_issue_claim(text,text,jsonb),public.metering_showback_control_write(text,text,jsonb) TO {quote(control_user)}"))
  162. connection.execute(text("INSERT INTO public.metering_scope_grants(principal_ref,tenant_ref,domain_ref,role_name) VALUES('wp12-operator','local-engineering','default','operator'),('01900000-0000-7000-8000-000000068912','local-engineering','default','admin'),('wp12-other-domain','local-engineering','other','operator')"))
  163. assert not connection.execute(text("SELECT has_table_privilege('dataops_app_runtime','public.metering_events','SELECT,INSERT,UPDATE,DELETE,TRUNCATE')")).scalar_one()
  164. assert connection.execute(text("SELECT bool_and(r.rolname='dataops_tenant_foundation_owner' AND NOT r.rolcanlogin) FROM pg_proc p JOIN pg_namespace n ON n.oid=p.pronamespace JOIN pg_roles r ON r.oid=p.proowner WHERE n.nspname='public' AND p.prosecdef AND p.proname LIKE 'metering_showback_%'" )).scalar_one()
  165. lease_a = _runtime_write(engine, "claim_lease", {"request_claim": _claim(engine, "lease", "wp12-operator"), "lease_owner": "worker-a", "lease_token": str(uuid.uuid4())})
  166. with pytest.raises(Exception, match="metering_lease_unavailable"):
  167. _runtime_write(engine, "claim_lease", {"request_claim": _claim(engine, "lease", "wp12-operator"), "lease_owner": "worker-b", "lease_token": str(uuid.uuid4())})
  168. with engine.connect() as connection:
  169. lease_token = connection.execute(text("SELECT lease_token::text FROM public.metering_runtime_leases WHERE tenant_ref='local-engineering'")).scalar_one()
  170. event = {"request_claim": _claim(engine, "record", "wp12-operator"), "lease_owner": "worker-a", "lease_token": lease_token, "lease_fence": lease_a["lease_fence"], "event_uid": "wp12-event-001", "event_kind": "query", "occurred_at": "2026-08-18T00:00:00Z", "window_start": "2026-08-18T00:00:00Z", "window_end": "2026-08-18T00:05:00Z", "quantity_micros": 1250000, "unit": "gb", "idempotency_key": "wp12-idempotency-001", "evidence": {"digest": "a" * 64, "reference": "local-fixture://wp12/v1"}, "mapping": {"department": "engineering", "business_domain": "default", "project": "local-engineering", "cost_center": "local-fixture"}}
  171. assert _runtime_write(engine, "record_event", event)["replay"] is False
  172. replay = event | {"request_claim": _claim(engine, "record", "wp12-operator")}
  173. assert _runtime_write(engine, "record_event", replay)["replay"] is True
  174. with engine.begin() as connection:
  175. connection.execute(text("UPDATE public.metering_runtime_leases SET lease_expires_at=clock_timestamp()-interval '1 second'"))
  176. monkeypatch.setenv("DATABASE_URL", base.set(database=database, username=runtime_user, password=runtime_password).render_as_string(hide_password=False))
  177. monkeypatch.setenv("METERING_SHOWBACK_CONTROL_DATABASE_URL", base.set(database=database, username=control_user, password=control_password).render_as_string(hide_password=False))
  178. monkeypatch.setenv("TRUSTED_METERING_DOMAIN", "default")
  179. monkeypatch.setattr("app.core.system.auth.load_identity_from_token", lambda token, secret: {"id": "01900000-0000-7000-8000-000000068912", "roles": ["admin"]} if token == "wp12-admin" else None)
  180. from app import create_app
  181. app = create_app()
  182. app.config.update(TESTING=True)
  183. client = app.test_client()
  184. flask_event = {
  185. "schema_version": 1,
  186. "event_uid": "wp12-flask-event-001",
  187. "event_kind": "query",
  188. "occurred_at": "2026-08-18T00:10:00Z",
  189. "window_start": "2026-08-18T00:10:00Z",
  190. "window_end": "2026-08-18T00:15:00Z",
  191. "quantity": "0.500000",
  192. "unit": "gb",
  193. "idempotency_key": "wp12-flask-idempotency-001",
  194. "evidence": {"digest": "c" * 64, "reference": "local-fixture://wp12/v1"},
  195. "mapping": {"department": "engineering", "project": "local-engineering", "cost_center": "local-fixture"},
  196. }
  197. created_event = client.post("/api/system/metering/events", json=flask_event, headers={"Authorization": "Bearer wp12-admin"})
  198. assert created_event.status_code == 201 and created_event.headers["Cache-Control"] == "no-store"
  199. # The client cannot claim a domain; its three-field mapping is completed only by the trusted server value.
  200. asserted_invalid = client.post("/api/system/metering/events", json=flask_event | {"event_uid": "wp12-flask-event-invalid", "idempotency_key": "wp12-flask-invalid", "mapping": flask_event["mapping"] | {"business_domain": "other"}}, headers={"Authorization": "Bearer wp12-admin"})
  201. assert asserted_invalid.status_code == 400 and asserted_invalid.headers["Cache-Control"] == "no-store"
  202. with engine.begin() as connection:
  203. connection.execute(text("UPDATE public.metering_runtime_leases SET lease_expires_at=clock_timestamp()-interval '1 second'"))
  204. changed_event = client.post("/api/system/metering/events", json=flask_event | {"quantity": "0.750000"}, headers={"Authorization": "Bearer wp12-admin"})
  205. assert changed_event.status_code == 400 and changed_event.headers["Cache-Control"] == "no-store"
  206. # A gateway failure must roll back and leave the next HTTP write usable.
  207. resumed_event = client.post("/api/system/metering/events", json=flask_event | {"event_uid": "wp12-flask-event-002", "idempotency_key": "wp12-flask-idempotency-002", "quantity": "0.250000", "evidence": {"digest": "d" * 64, "reference": "local-fixture://wp12/v1"}}, headers={"Authorization": "Bearer wp12-admin"})
  208. assert resumed_event.status_code == 201 and resumed_event.headers["Cache-Control"] == "no-store"
  209. with engine.connect() as connection:
  210. assert connection.execute(text("SELECT count(*) FROM public.metering_events WHERE event_uid IN ('wp12-flask-event-001','wp12-flask-event-002') AND domain_ref='default'" )).scalar_one() == 2
  211. flask_allocation = {"schema_version": 1, "rule_uid": "wp12-flask-allocation-001", "rule_version": 1, "effective_start": "2026-08-01T00:00:00Z", "effective_end": "2026-09-01T00:00:00Z", "mapping": {"department": "engineering", "project": "local-engineering", "cost_center": "local-fixture"}, "allocations": [{"target": "local-engineering", "weight_micros": 1000000}]}
  212. created = client.post("/api/system/metering/allocations", json=flask_allocation, headers={"Authorization": "Bearer wp12-admin"})
  213. assert created.status_code == 201 and created.headers["Cache-Control"] == "no-store"
  214. changed_allocation = client.post("/api/system/metering/allocations", json=flask_allocation | {"allocations": [{"target": "local-engineering", "weight_micros": 999999}, {"target": "shared-services", "weight_micros": 1}]}, headers={"Authorization": "Bearer wp12-admin"})
  215. assert changed_allocation.status_code == 400 and changed_allocation.headers["Cache-Control"] == "no-store"
  216. flask_budget = {"schema_version": 1, "budget_uid": "wp12-flask-budget-001", "window": "2026-08", "mapping": flask_allocation["mapping"], "limit_micros": 2000000, "threshold_micros": 1500000}
  217. persisted_budget = client.post("/api/system/metering/budgets", json=flask_budget, headers={"Authorization": "Bearer wp12-admin"})
  218. assert persisted_budget.status_code == 201 and persisted_budget.headers["Cache-Control"] == "no-store"
  219. invalid_budget = client.post("/api/system/metering/budgets", json=flask_budget | {"threshold_micros": 2000001}, headers={"Authorization": "Bearer wp12-admin"})
  220. assert invalid_budget.status_code == 400 and invalid_budget.headers["Cache-Control"] == "no-store"
  221. def concurrent_allocation(payload):
  222. allocation_barrier.wait(timeout=5)
  223. try:
  224. return "ok", _control_write(engine, "allocation", "wp12-operator", payload)
  225. except Exception as exc: # the database is the assertion boundary
  226. return "error", str(exc)
  227. atomic_mapping = {"department": "atomic-engineering", "business_domain": "default", "project": "local-engineering", "cost_center": "local-fixture"}
  228. atomic_base = {"rule_uid": "wp12-atomic-001", "rule_version": "1", "effective_start": "2026-08-01T00:00:00.000000Z", "effective_end": "2026-09-01T00:00:00.000000Z", "mapping": atomic_mapping}
  229. atomic_payloads = [atomic_base | {"allocations": [{"target": "atomic-a", "weight_micros": 1000000}]}, atomic_base | {"allocations": [{"target": "atomic-b", "weight_micros": 1000000}]}]
  230. allocation_barrier = Barrier(2)
  231. with ThreadPoolExecutor(max_workers=2) as executor:
  232. atomic_outcomes = list(executor.map(concurrent_allocation, atomic_payloads))
  233. assert sorted(outcome[0] for outcome in atomic_outcomes) == ["error", "ok"]
  234. assert any("metering_allocation_conflict" in outcome[1] for outcome in atomic_outcomes if outcome[0] == "error")
  235. winning_payload = next(payload for payload, outcome in zip(atomic_payloads, atomic_outcomes, strict=True) if outcome[0] == "ok")
  236. assert _control_write(engine, "allocation", "wp12-operator", winning_payload)["replay"] is True
  237. with engine.connect() as connection:
  238. assert connection.execute(text("SELECT count(*),sum(weight_micros) FROM public.metering_allocations WHERE rule_uid='wp12-atomic-001' AND rule_version=1")).one() == (1, 1000000)
  239. overlap_mapping = atomic_mapping | {"department": "overlap-engineering"}
  240. overlap_base = {"rule_version": "1", "effective_start": "2026-08-01T00:00:00.000000Z", "effective_end": "2026-09-01T00:00:00.000000Z", "mapping": overlap_mapping, "allocations": [{"target": "overlap-target", "weight_micros": 1000000}]}
  241. allocation_barrier = Barrier(2)
  242. with ThreadPoolExecutor(max_workers=2) as executor:
  243. overlap_outcomes = list(executor.map(concurrent_allocation, [overlap_base | {"rule_uid": "wp12-overlap-atomic-a"}, overlap_base | {"rule_uid": "wp12-overlap-atomic-b"}]))
  244. assert sorted(outcome[0] for outcome in overlap_outcomes) == ["error", "ok"]
  245. assert any("metering_allocation_window_overlap" in outcome[1] or "metering_allocation_rules_scope_window_excl" in outcome[1] for outcome in overlap_outcomes if outcome[0] == "error")
  246. # A newly constructed Flask app observes the committed gateway facts.
  247. second_app = create_app()
  248. second_app.config.update(TESTING=True)
  249. second_client = second_app.test_client()
  250. shown = second_client.get("/api/system/metering/reconciliation?window=2026-08", headers={"Authorization": "Bearer wp12-admin"})
  251. assert shown.status_code == 200 and shown.headers["Cache-Control"] == "no-store"
  252. replayed_rule = client.get("/api/system/metering/allocation-replay?window=2026-08&rule_uid=wp12-flask-allocation-001&rule_version=1", headers={"Authorization": "Bearer wp12-admin"})
  253. assert replayed_rule.status_code == 200 and replayed_rule.headers["Cache-Control"] == "no-store"
  254. assert replayed_rule.get_json()["data"]["difference_micros"] == 0
  255. allocation = {"rule_uid": "wp12-flask-allocation-001", "rule_version": "1", "effective_start": "2026-08-01T00:00:00.000000Z", "effective_end": "2026-09-01T00:00:00.000000Z", "mapping": {"department": "engineering", "business_domain": "default", "project": "local-engineering", "cost_center": "local-fixture"}, "allocations": [{"target": "local-engineering", "weight_micros": 1000000}]}
  256. assert _control_write(engine, "allocation", "wp12-operator", allocation)["persisted_before_ack"] is True
  257. with pytest.raises(Exception, match="metering_allocation_target_duplicate"):
  258. _control_write(engine, "allocation", "wp12-operator", allocation | {"allocations": [{"target": "local-engineering", "weight_micros": 500000}, {"target": "local-engineering", "weight_micros": 500000}]})
  259. with pytest.raises(Exception, match="metering_allocation_weight_invalid"):
  260. _control_write(engine, "allocation", "wp12-operator", allocation | {"allocations": [{"target": "local-engineering", "weight_micros": 0}, {"target": "shared-services", "weight_micros": 1000000}]})
  261. with pytest.raises(Exception, match="metering_allocation_window_overlap"):
  262. _control_write(engine, "allocation", "wp12-operator", allocation | {"rule_uid": "wp12-overlap-001"})
  263. expired_rule = allocation | {"rule_uid": "wp12-expired-001", "mapping": allocation["mapping"] | {"department": "expired-engineering"}, "effective_end": "2026-08-15T00:00:00.000000Z"}
  264. assert _control_write(engine, "allocation", "wp12-operator", expired_rule)["persisted_before_ack"] is True
  265. with engine.begin() as connection:
  266. connection.execute(text("UPDATE public.metering_runtime_leases SET lease_expires_at=clock_timestamp()-interval '1 second'"))
  267. reset_token = str(uuid.uuid4())
  268. reset_lease = _runtime_write(engine, "claim_lease", {"request_claim": _claim(engine, "lease", "wp12-operator"), "lease_owner": "worker-a", "lease_token": reset_token})
  269. event = event | {"lease_token": reset_token, "lease_fence": reset_lease["lease_fence"]}
  270. expired_event = event | {"request_claim": _claim(engine, "record", "wp12-operator"), "event_uid": "wp12-event-expired-rule", "idempotency_key": "wp12-idempotency-expired-rule", "quantity_micros": 1000000, "mapping": event["mapping"] | {"department": "expired-engineering"}}
  271. assert _runtime_write(engine, "record_event", expired_event)["replay"] is False
  272. assert client.get("/api/system/metering/allocation-replay?window=2026-08&rule_uid=wp12-expired-001&rule_version=1", headers={"Authorization": "Bearer wp12-admin"}).status_code == 403
  273. multi_one = allocation | {"rule_uid": "wp12-multi-001", "mapping": allocation["mapping"] | {"department": "multi-engineering"}, "effective_end": "2026-08-15T00:00:00.000000Z"}
  274. multi_two = multi_one | {"rule_uid": "wp12-multi-002", "effective_start": "2026-08-15T00:00:00.000000Z", "effective_end": "2026-09-01T00:00:00.000000Z"}
  275. assert _control_write(engine, "allocation", "wp12-operator", multi_one)["persisted_before_ack"] is True
  276. assert _control_write(engine, "allocation", "wp12-operator", multi_two)["persisted_before_ack"] is True
  277. for suffix, when in (("one", "2026-08-10T00:00:00Z"), ("two", "2026-08-18T00:00:00Z")):
  278. assert _runtime_write(engine, "record_event", event | {"request_claim": _claim(engine, "record", "wp12-operator"), "event_uid": f"wp12-event-multi-{suffix}", "idempotency_key": f"wp12-idempotency-multi-{suffix}", "quantity_micros": 1000000, "occurred_at": when, "window_start": when, "window_end": when.replace("00:00:00Z", "00:05:00Z"), "mapping": event["mapping"] | {"department": "multi-engineering"}})["replay"] is False
  279. assert client.get("/api/system/metering/allocation-replay?window=2026-08&rule_uid=wp12-multi-001&rule_version=1", headers={"Authorization": "Bearer wp12-admin"}).status_code == 403
  280. with pytest.raises(Exception, match="metering_allocation_conflict"):
  281. _control_write(engine, "allocation", "wp12-operator", allocation | {"allocations": [{"target": "local-engineering", "weight_micros": 999999}, {"target": "shared-services", "weight_micros": 1}]})
  282. with pytest.raises(Exception, match="metering_allocation_closed"):
  283. _control_write(engine, "allocation", "wp12-operator", allocation | {"mapping": allocation["mapping"] | {"business_domain": "other"}})
  284. with pytest.raises(Exception, match="metering_evidence_reference_invalid"):
  285. _runtime_write(engine, "record_event", event | {"request_claim": _claim(engine, "record", "wp12-operator"), "event_uid": "wp12-event-bad-ref", "idempotency_key": "wp12-idempotency-bad-ref", "evidence": {"digest": "b" * 64, "reference": "local-fixture://wp12/v1/SELECT"}})
  286. with pytest.raises(Exception, match="metering_correction_scope_invalid"):
  287. _runtime_write(engine, "record_event", event | {"request_claim": _claim(engine, "record", "wp12-other-domain"), "event_uid": "wp12-event-cross-domain-correction", "idempotency_key": "wp12-idempotency-cross-domain-correction", "mapping": event["mapping"] | {"business_domain": "other"}, "correction_of": "wp12-event-001"})
  288. valid_correction = event | {"request_claim": _claim(engine, "record", "wp12-operator"), "event_uid": "wp12-event-correction-001", "idempotency_key": "wp12-idempotency-correction-001", "quantity_micros": 0, "correction_of": "wp12-event-001"}
  289. assert _runtime_write(engine, "record_event", valid_correction)["replay"] is False
  290. with pytest.raises(Exception, match="metering_correction_scope_invalid"):
  291. _runtime_write(engine, "record_event", valid_correction | {"request_claim": _claim(engine, "record", "wp12-operator"), "event_uid": "wp12-event-correction-cycle", "idempotency_key": "wp12-idempotency-correction-cycle", "correction_of": "wp12-event-correction-001"})
  292. budget = {"budget_uid": "wp12-budget-001", "window": "2026-08", "mapping": allocation["mapping"], "limit_micros": "2000000", "threshold_micros": "1000000"}
  293. with ThreadPoolExecutor(max_workers=2) as executor:
  294. outcomes = list(executor.map(lambda _: _control_write(engine, "budget", "wp12-operator", budget)["alert_created"], range(2)))
  295. assert sorted(outcomes) == [False, True]
  296. with engine.begin() as connection:
  297. assert connection.execute(text("SELECT count(*) FROM public.metering_alert_outbox")).scalar_one() == 2
  298. connection.execute(text("UPDATE public.metering_runtime_leases SET lease_expires_at=clock_timestamp()-interval '1 second'"))
  299. lease_b = _runtime_write(engine, "claim_lease", {"request_claim": _claim(engine, "lease", "wp12-operator"), "lease_owner": "worker-b", "lease_token": str(uuid.uuid4())})
  300. with pytest.raises(Exception, match="metering_stale_fence"):
  301. _runtime_write(engine, "record_event", event | {"request_claim": _claim(engine, "record", "wp12-operator"), "event_uid": "wp12-event-stale", "idempotency_key": "wp12-idempotency-stale"})
  302. assert lease_b["lease_fence"] > lease_a["lease_fence"]
  303. for statement, error in (("SELECT count(*) FROM public.metering_events", "permission denied"), ("SELECT public.metering_showback_control_write('budget','wp12-operator','{}'::jsonb)", "permission denied"), ("SELECT public.metering_showback_runtime_write('chargeback','{}'::jsonb)", "chargeback_disabled")):
  304. with engine.begin() as connection:
  305. connection.execute(text("SET LOCAL SESSION AUTHORIZATION dataops_app_runtime"))
  306. with pytest.raises(Exception, match=error):
  307. connection.execute(text(statement))
  308. with engine.begin() as connection:
  309. connection.execute(text("SET LOCAL SESSION AUTHORIZATION dataops_app_runtime"))
  310. report = connection.execute(text("SELECT public.metering_showback_rollup(CAST(:payload AS jsonb))"), {"payload": json.dumps({"request_claim": _claim(engine, "read", "wp12-operator"), "window": "2026-08"})}).scalar_one()
  311. assert report["source_micros"] == 5000000 and report["allocated_micros"] == 4000000 and report["variance_micros"] == 1000000
  312. assert report["allocation_rules"][0]["rule_uid"] == "wp12-flask-allocation-001"
  313. assert not connection.execute(text("SELECT has_function_privilege('dataops_app_runtime','public.metering_showback_runtime_read_legacy(text,jsonb)','EXECUTE')")).scalar_one()
  314. with pytest.raises(Exception, match="permission denied"):
  315. connection.execute(text("SELECT public.metering_showback_runtime_read_legacy('showback','{}'::jsonb)"))
  316. finally:
  317. engine.dispose()
  318. downgraded = _alembic(database_url, "downgrade", "20260818_545")
  319. assert downgraded.returncode != 0
  320. assert "downgrade refused: WP12 allocation facts are nonempty" in (downgraded.stderr + downgraded.stdout)
  321. finally:
  322. with admin.connect() as connection:
  323. connection.execute(text("SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname=:database"), {"database": database})
  324. connection.execute(text(f"DROP DATABASE IF EXISTS {quoted}"))
  325. connection.execute(text(f"REVOKE dataops_app_runtime FROM {runtime_user}")) if connection.execute(text("SELECT EXISTS(SELECT 1 FROM pg_roles WHERE rolname=:role)"), {"role": runtime_user}).scalar_one() else None
  326. connection.execute(text(f"REVOKE dataops_bi_ai_catalog_issuer FROM {control_user}")) if connection.execute(text("SELECT EXISTS(SELECT 1 FROM pg_roles WHERE rolname=:role)"), {"role": control_user}).scalar_one() else None
  327. connection.execute(text(f"DROP ROLE IF EXISTS {runtime_user}"))
  328. connection.execute(text(f"DROP ROLE IF EXISTS {control_user}"))
  329. admin.dispose()
  330. @pytest.mark.integration
  331. def test_wp12_shared_head_roundtrip_and_role_init_replay(monkeypatch):
  332. """Exercise the actual local test DB only when it is exactly at 545/empty."""
  333. database_url = os.getenv("TEST_DATABASE_URL")
  334. if not database_url:
  335. pytest.skip("TEST_DATABASE_URL is required")
  336. engine = create_engine(database_url, pool_pre_ping=True)
  337. base = make_url(database_url)
  338. runtime_user = f"wp12_runtime_{uuid.uuid4().hex[:12]}"
  339. control_user = f"wp12_control_{uuid.uuid4().hex[:12]}"
  340. runtime_password = f"runtime-{uuid.uuid4().hex}"
  341. control_password = f"control-{uuid.uuid4().hex}"
  342. try:
  343. with engine.connect() as connection:
  344. shared_head = connection.execute(text("SELECT version_num FROM public.alembic_version")).scalar_one()
  345. assert shared_head in {"20260818_551", "20260818_552"}
  346. assert connection.execute(text("SELECT count(*) FROM public.metering_events")).scalar_one() == 0
  347. if shared_head == "20260818_552":
  348. normalized = _alembic(database_url, "downgrade", "20260818_551")
  349. assert normalized.returncode == 0, normalized.stderr
  350. from app.core.edge_gateway.runtime_roles import provision_runtime_login
  351. monkeypatch.setenv("DB_ROLE_INIT_DATABASE_URL", database_url)
  352. monkeypatch.setenv("MIGRATION_DATABASE_URL", database_url)
  353. monkeypatch.setenv("DATABASE_URL", base.set(username=runtime_user, password=runtime_password).render_as_string(hide_password=False))
  354. monkeypatch.setenv("DATAOPS_RUNTIME_USER", runtime_user)
  355. monkeypatch.setenv("DATAOPS_RUNTIME_PASSWORD", runtime_password)
  356. monkeypatch.setenv("DATAOPS_MIGRATOR_USER", str(base.username))
  357. monkeypatch.setenv("BI_AI_CATALOG_CONTROL_DATABASE_URL", base.set(username=control_user, password=control_password).render_as_string(hide_password=False))
  358. monkeypatch.setenv("DATAOPS_BI_AI_CATALOG_CONTROL_USER", control_user)
  359. monkeypatch.setenv("DATAOPS_BI_AI_CATALOG_CONTROL_PASSWORD", control_password)
  360. # Role-init precedes the restricted ownership transfer, then runs a
  361. # second time below to prove replay cannot reopen the fenced gateways.
  362. provision_runtime_login()
  363. upgraded = _alembic(database_url, "upgrade", "20260818_552")
  364. assert upgraded.returncode == 0, upgraded.stderr
  365. provision_runtime_login()
  366. with engine.connect() as connection:
  367. assert not connection.execute(
  368. text("SELECT has_table_privilege(:runtime,'public.metering_events','SELECT,INSERT,UPDATE,DELETE,TRUNCATE')"),
  369. {"runtime": runtime_user},
  370. ).scalar_one()
  371. assert not connection.execute(
  372. text("SELECT has_function_privilege(:runtime,'public.metering_showback_runtime_read_legacy(text,jsonb)','EXECUTE')"),
  373. {"runtime": runtime_user},
  374. ).scalar_one()
  375. assert connection.execute(
  376. text("SELECT bool_and(r.rolname='dataops_tenant_foundation_owner' AND NOT r.rolcanlogin) FROM pg_proc p JOIN pg_namespace n ON n.oid=p.pronamespace JOIN pg_roles r ON r.oid=p.proowner WHERE n.nspname='public' AND p.prosecdef AND p.proname LIKE 'metering_showback_%'")
  377. ).scalar_one()
  378. assert connection.execute(
  379. text("SELECT has_function_privilege(:control,'public.metering_showback_issue_claim(text,text,jsonb)','EXECUTE')"),
  380. {"control": control_user},
  381. ).scalar_one()
  382. assert connection.execute(
  383. text("SELECT has_function_privilege(:control,'public.metering_showback_control_write(text,text,jsonb)','EXECUTE')"),
  384. {"control": control_user},
  385. ).scalar_one()
  386. assert connection.execute(
  387. text(
  388. "SELECT count(*)=0 FROM ("
  389. "SELECT count(*) AS n FROM public.metering_scope_grants UNION ALL SELECT count(*) FROM public.metering_runtime_leases "
  390. "UNION ALL SELECT count(*) FROM public.metering_runtime_claims UNION ALL SELECT count(*) FROM public.metering_events "
  391. "UNION ALL SELECT count(*) FROM public.metering_allocation_rules UNION ALL SELECT count(*) FROM public.metering_allocations "
  392. "UNION ALL SELECT count(*) FROM public.metering_budgets UNION ALL SELECT count(*) FROM public.metering_alert_outbox "
  393. "UNION ALL SELECT count(*) FROM public.metering_reconciliation_reports UNION ALL SELECT count(*) FROM public.metering_audit_events"
  394. ") facts WHERE n<>0"
  395. )
  396. ).scalar_one()
  397. downgraded = _alembic(database_url, "downgrade", "20260818_551")
  398. assert downgraded.returncode == 0, downgraded.stderr
  399. replayed = _alembic(database_url, "upgrade", "20260818_552")
  400. assert replayed.returncode == 0, replayed.stderr
  401. finally:
  402. # The test has no WP12 facts; leave the shared local database at 552
  403. # and remove its short-lived login identities without printing secrets.
  404. with engine.begin() as connection:
  405. connection.execute(
  406. text(
  407. "DO $$ BEGIN IF EXISTS(SELECT 1 FROM pg_roles WHERE rolname=:control) THEN "
  408. "IF to_regprocedure('public.metering_showback_issue_claim(text,text,jsonb)') IS NOT NULL THEN "
  409. "EXECUTE 'REVOKE ALL ON FUNCTION public.metering_showback_issue_claim(text,text,jsonb) FROM ' || quote_ident(:control); END IF; "
  410. "IF to_regprocedure('public.metering_showback_control_write(text,text,jsonb)') IS NOT NULL THEN "
  411. "EXECUTE 'REVOKE ALL ON FUNCTION public.metering_showback_control_write(text,text,jsonb) FROM ' || quote_ident(:control); END IF; "
  412. "IF to_regprocedure('public.bi_ai_catalog_issue_request_claim(text,text,jsonb)') IS NOT NULL THEN "
  413. "EXECUTE 'REVOKE ALL ON FUNCTION public.bi_ai_catalog_issue_request_claim(text,text,jsonb) FROM ' || quote_ident(:control); END IF; "
  414. "END IF; END $$"
  415. ),
  416. {"control": control_user},
  417. )
  418. connection.execute(text(f"REVOKE dataops_app_runtime,dataops_agent_runtime FROM {runtime_user}"))
  419. connection.execute(text(f"REVOKE dataops_bi_ai_catalog_issuer FROM {control_user}"))
  420. connection.execute(text(f"DROP ROLE IF EXISTS {runtime_user}"))
  421. connection.execute(text(f"DROP ROLE IF EXISTS {control_user}"))
  422. engine.dispose()