-- pgTAP: server admin role + audit log (Fase 13.1) -- Run with: psql -U postgres -d postgres -f supabase/tests/016_server_admin.sql -- -- Verifies: -- * `public.server_admins` table shape: PK, FKs, RLS enabled, no -- INSERT/UPDATE/DELETE policies (only SELECT). -- * `public.is_server_admin(uuid)` exists, STABLE, SECURITY DEFINER, -- returns boolean, has a default arg. -- * `public.admin_actions` table shape: append-only (SELECT policy only), -- RLS enabled, indexes present, actor_id FK is RESTRICT. -- -- The runtime semantics (non-admin denied SELECT, audit RPCs only write via -- SECURITY DEFINER) live in the Vitest integration suite which connects as a -- non-superuser. pgTAP runs as postgres and bypasses RLS. CREATE EXTENSION IF NOT EXISTS pgtap; BEGIN; SELECT plan(20); -- ── server_admins table shape ─────────────────────────────────────────────── SELECT has_table( 'public', 'server_admins', 'SA-T01: public.server_admins table exists' ); SELECT has_column( 'public', 'server_admins', 'user_id', 'SA-T02: server_admins.user_id column exists' ); SELECT col_is_pk( 'public', 'server_admins', 'user_id', 'SA-T03: server_admins.user_id is the primary key' ); SELECT col_not_null( 'public', 'server_admins', 'granted_at', 'SA-T04: server_admins.granted_at is NOT NULL' ); -- RLS enabled SELECT ok( (SELECT relrowsecurity FROM pg_class WHERE relname = 'server_admins' AND relnamespace = 'public'::regnamespace), 'SA-T05: RLS is enabled on server_admins' ); -- Only a SELECT policy — no INSERT/UPDATE/DELETE policies. SELECT results_eq( $$ SELECT cmd::text FROM pg_policies WHERE schemaname = 'public' AND tablename = 'server_admins' ORDER BY cmd $$, $$ VALUES ('SELECT') $$, 'SA-T06: server_admins has exactly one policy and it is SELECT-only' ); -- ── is_server_admin function ──────────────────────────────────────────────── SELECT has_function( 'public', 'is_server_admin', ARRAY['uuid'], 'SA-T07: public.is_server_admin(uuid) exists' ); SELECT function_returns( 'public', 'is_server_admin', ARRAY['uuid'], 'boolean', 'SA-T08: is_server_admin() returns boolean' ); SELECT volatility_is( 'public', 'is_server_admin', ARRAY['uuid'], 'stable', 'SA-T09: is_server_admin() is STABLE' ); -- Must be SECURITY DEFINER so non-admin callers can answer "am I admin?" -- without escalating to SELECT-all on server_admins. SELECT is( (SELECT prosecdef FROM pg_proc WHERE proname = 'is_server_admin' AND pronamespace = 'public'::regnamespace), true, 'SA-T10: is_server_admin() is SECURITY DEFINER' ); -- ── admin_actions table shape ─────────────────────────────────────────────── SELECT has_table( 'public', 'admin_actions', 'SA-T11: public.admin_actions table exists' ); SELECT col_is_pk( 'public', 'admin_actions', 'id', 'SA-T12: admin_actions.id is the primary key' ); SELECT col_not_null( 'public', 'admin_actions', 'actor_id', 'SA-T13: admin_actions.actor_id is NOT NULL' ); SELECT col_not_null( 'public', 'admin_actions', 'action', 'SA-T14: admin_actions.action is NOT NULL' ); SELECT col_type_is( 'public', 'admin_actions', 'payload', 'jsonb', 'SA-T15: admin_actions.payload is jsonb' ); -- RLS enabled SELECT ok( (SELECT relrowsecurity FROM pg_class WHERE relname = 'admin_actions' AND relnamespace = 'public'::regnamespace), 'SA-T16: RLS is enabled on admin_actions' ); -- Append-only: SELECT is the ONLY policy. No INSERT/UPDATE/DELETE policies → -- non-superuser cannot write to admin_actions via REST under any -- circumstances. Only SECURITY DEFINER RPCs (migration 025) write. SELECT results_eq( $$ SELECT cmd::text FROM pg_policies WHERE schemaname = 'public' AND tablename = 'admin_actions' ORDER BY cmd $$, $$ VALUES ('SELECT') $$, 'SA-T17: admin_actions has exactly one policy and it is SELECT-only (append-only)' ); -- actor_id FK must RESTRICT so the audit trail never silently loses rows -- when a user is deleted. SELECT results_eq( $$ SELECT confdeltype::text FROM pg_constraint WHERE conrelid = 'public.admin_actions'::regclass AND conname = 'admin_actions_actor_id_fkey' $$, $$ VALUES ('r') $$, -- 'r' = RESTRICT 'SA-T18: admin_actions.actor_id FK uses RESTRICT' ); -- Indexes for the common queries SELECT has_index( 'public', 'admin_actions', 'admin_actions_actor_idx', 'SA-T19: admin_actions has (actor_id, created_at DESC) index' ); SELECT has_index( 'public', 'admin_actions', 'admin_actions_target_idx', 'SA-T20: admin_actions has (target_type, target_id) index' ); SELECT * FROM finish(); ROLLBACK;