fix(db): allow deleting meeting templates that past meetings reference

This commit is contained in:
Yun Chan 2026-09-28 02:16:21 +09:00
parent f04a7f9944
commit 4807a5283d
2 changed files with 232 additions and 0 deletions

View file

@ -0,0 +1,162 @@
\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;