\set ON_ERROR_STOP on -- Regression for 20260929000003_meeting_template_delete.sql. -- A custom meeting-document template that was used to create a meeting -- (mobile_create_meeting_workspace_v2) must still be deletable through -- delete_user_template_v1 and sync_delete_user_template_v1. The meeting stays -- and is detached (template_id = NULL) with its creation identity intact. -- Re-pointing an RPC-created meeting at a different template stays forbidden. BEGIN; CREATE OR REPLACE FUNCTION pg_temp.assert_true(condition boolean, message text) RETURNS void LANGUAGE plpgsql AS $$ BEGIN IF condition IS NOT TRUE THEN RAISE EXCEPTION 'assertion_failed: %', message; END IF; END; $$; CREATE OR REPLACE FUNCTION pg_temp.act_as(uid uuid) RETURNS void LANGUAGE sql AS $$ SELECT set_config( 'request.jwt.claims', json_build_object('sub', uid, 'role', 'authenticated')::text, true ); $$; INSERT INTO auth.users ( id, aud, role, email, encrypted_password, email_confirmed_at, raw_app_meta_data, raw_user_meta_data, created_at, updated_at ) VALUES ( '32000000-0000-4000-8000-000000000001', 'authenticated', 'authenticated', 'meeting-template-delete@example.invalid', crypt('fixture-password', gen_salt('bf')), now(), '{"provider":"email","providers":["email"]}'::jsonb, '{}'::jsonb, now(), now() ); CREATE TEMP TABLE fixture_ids ( label text PRIMARY KEY, id uuid NOT NULL ) ON COMMIT DROP; GRANT ALL ON fixture_ids TO authenticated; SELECT pg_temp.act_as('32000000-0000-4000-8000-000000000001'); SET LOCAL ROLE authenticated; -- Three custom meeting-document templates: one deleted via the mobile RPC, -- one via the desktop sync RPC, one used as a re-point target. INSERT INTO fixture_ids(label, id) SELECT 'weekly', (public.create_user_template_v1( 'meeting_document', 'Weekly sync', NULL, '[]'::jsonb, NULL, 'Summarize the weekly sync.' )).id; INSERT INTO fixture_ids(label, id) SELECT 'retro', (public.create_user_template_v1( 'meeting_document', 'Retro', NULL, '[]'::jsonb, NULL, 'Summarize the retrospective.' )).id; INSERT INTO fixture_ids(label, id) SELECT 'other', (public.create_user_template_v1( 'meeting_document', 'Other', NULL, '[]'::jsonb, NULL, 'Summarize.' )).id; INSERT INTO fixture_ids(label, id) SELECT 'weekly_meeting', (public.mobile_create_meeting_workspace_v2( 'Weekly sync 09-28', '["Alice"]'::jsonb, 'en', (SELECT id FROM fixture_ids WHERE label = 'weekly'), '32000000-0000-4000-8000-0000000000a1' )).id; INSERT INTO fixture_ids(label, id) SELECT 'retro_meeting', (public.mobile_create_meeting_workspace_v2( 'Retro 09-28', '[]'::jsonb, 'ko', (SELECT id FROM fixture_ids WHERE label = 'retro'), '32000000-0000-4000-8000-0000000000a2' )).id; CREATE TEMP TABLE original_hashes ON COMMIT DROP AS SELECT id, creation_idempotency_key, creation_request_hash FROM public.meetings WHERE id IN (SELECT id FROM fixture_ids WHERE label LIKE '%_meeting'); -- Re-pointing an RPC-created meeting at a different template stays forbidden. DO $$ BEGIN UPDATE public.meetings SET template_id = (SELECT id FROM fixture_ids WHERE label = 'other') WHERE id = (SELECT id FROM fixture_ids WHERE label = 'weekly_meeting'); RAISE EXCEPTION 'assertion_failed: template re-point was allowed'; EXCEPTION WHEN insufficient_privilege THEN PERFORM pg_temp.assert_true( SQLERRM = 'meeting_creation_template_immutable', format('re-point denied by the identity trigger, got %s', SQLERRM) ); END; $$; -- 1) Mobile delete: delete_user_template_v1 no longer fails with 23503. SELECT pg_temp.assert_true( public.delete_user_template_v1( (SELECT id FROM fixture_ids WHERE label = 'weekly'), (SELECT revision FROM public.user_templates WHERE id = (SELECT id FROM fixture_ids WHERE label = 'weekly')) ), 'template used by a meeting can be deleted' ); -- 2) Desktop sync delete: sync_delete_user_template_v1 drains the outbox. SELECT pg_temp.assert_true( public.sync_delete_user_template_v1((SELECT id FROM fixture_ids WHERE label = 'retro')), 'sync delete of a template used by a meeting succeeds' ); SELECT pg_temp.assert_true( NOT EXISTS ( SELECT 1 FROM public.user_templates WHERE id IN (SELECT id FROM fixture_ids WHERE label IN ('weekly', 'retro')) ), 'deleted templates are gone on the server' ); SELECT pg_temp.assert_true( (SELECT count(*) FROM public.meetings m JOIN original_hashes o ON o.id = m.id WHERE m.template_id IS NULL AND m.creation_idempotency_key = o.creation_idempotency_key AND m.creation_request_hash = o.creation_request_hash) = 2, 'meetings survive, are detached, and keep their creation identity' ); -- Manual detach to NULL is allowed on an RPC-created meeting. INSERT INTO fixture_ids(label, id) SELECT 'other_meeting', (public.mobile_create_meeting_workspace_v2( 'Other 09-28', '[]'::jsonb, 'en', (SELECT id FROM fixture_ids WHERE label = 'other'), '32000000-0000-4000-8000-0000000000a3' )).id; UPDATE public.meetings SET template_id = NULL WHERE id = (SELECT id FROM fixture_ids WHERE label = 'other_meeting'); SELECT pg_temp.assert_true( (SELECT template_id IS NULL FROM public.meetings WHERE id = (SELECT id FROM fixture_ids WHERE label = 'other_meeting')), 'RPC-created meeting can be detached from its template' ); RESET ROLE; -- Schema contract: the FK detaches instead of restricting. SELECT pg_temp.assert_true( (SELECT confdeltype = 'n' FROM pg_constraint WHERE conrelid = 'public.meetings'::regclass AND conname = 'meetings_template_id_fkey'), 'meetings.template_id FK is ON DELETE SET NULL' ); ROLLBACK;