-- pgTAP: delete_account() RPC — Fase 10.5 -- Run with: psql -U postgres -d postgres -f supabase/tests/013_delete_account.sql CREATE EXTENSION IF NOT EXISTS pgtap; BEGIN; SELECT plan(8); -- ── Existence ──────────────────────────────────────────────────────────────── SELECT has_function( 'public', 'delete_account', ARRAY[]::text[], 'public.delete_account() RPC exists' ); -- ── Setup A: simple solo user who deletes themselves ───────────────────────── INSERT INTO auth.users (instance_id, id, aud, role, email, email_confirmed_at, confirmation_token, recovery_token, email_change_token_new, email_change, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at) VALUES ('00000000-0000-0000-0000-000000000000', 'e3000000-0000-0000-0000-000000000001', 'authenticated', 'authenticated', 'delacc-solo@local', now(), '', '', '', '', '{}', '{}', false, now(), now()) ON CONFLICT (id) DO NOTHING; INSERT INTO public.users (id, email, display_name, language, avatar_type) VALUES ('e3000000-0000-0000-0000-000000000001', 'delacc-solo@local', 'Del Solo', 'es', 'initials') ON CONFLICT (id) DO NOTHING; -- Solo user creates a list in the seed collective via membership. INSERT INTO public.collective_members (collective_id, user_id, role) VALUES ('aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', 'e3000000-0000-0000-0000-000000000001', 'member') ON CONFLICT (collective_id, user_id) DO NOTHING; INSERT INTO public.shopping_lists (id, collective_id, name, status, created_by) VALUES ('cccccccc-5555-5555-5555-5555deadbeef', 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', 'List by deleted user', 'active', 'e3000000-0000-0000-0000-000000000001') ON CONFLICT (id) DO NOTHING; -- ── Case 1: happy path — user deletes self, content stays attributed to NULL ─ SELECT set_config('request.jwt.claim.sub', 'e3000000-0000-0000-0000-000000000001', true); SELECT lives_ok( $$ SELECT public.delete_account() $$, 'DA-01: regular user deletes their own account' ); SELECT is( (SELECT count(*)::int FROM auth.users WHERE id = 'e3000000-0000-0000-0000-000000000001'), 0, 'DA-01: auth.users row removed' ); SELECT is( (SELECT count(*)::int FROM public.users WHERE id = 'e3000000-0000-0000-0000-000000000001'), 0, 'DA-01: public.users row removed' ); SELECT is( (SELECT created_by FROM public.shopping_lists WHERE id = 'cccccccc-5555-5555-5555-5555deadbeef'), NULL, 'DA-01: content created_by set to null (orphaned)' ); -- ── Setup B: sole-admin with promotable member ─────────────────────────────── INSERT INTO auth.users (instance_id, id, aud, role, email, email_confirmed_at, confirmation_token, recovery_token, email_change_token_new, email_change, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at) VALUES ('00000000-0000-0000-0000-000000000000', 'e3000000-0000-0000-0000-000000000002', 'authenticated', 'authenticated', 'delacc-admin@local', now(), '', '', '', '', '{}', '{}', false, now(), now()) ON CONFLICT (id) DO NOTHING; INSERT INTO public.users (id, email, display_name, language, avatar_type) VALUES ('e3000000-0000-0000-0000-000000000002', 'delacc-admin@local', 'Del Admin', 'es', 'initials') ON CONFLICT (id) DO NOTHING; -- Auxiliary collective: admin is the test user, member is Carmen. INSERT INTO public.collectives (id, name, emoji, created_by) VALUES ('cccccccc-5555-5555-5555-555555555555', 'Del-acc test', '🚪', '22222222-2222-2222-2222-222222222222') ON CONFLICT (id) DO NOTHING; INSERT INTO public.collective_members (collective_id, user_id, role, joined_at) VALUES ('cccccccc-5555-5555-5555-555555555555', 'e3000000-0000-0000-0000-000000000002', 'admin', now() - INTERVAL '10 days'), ('cccccccc-5555-5555-5555-555555555555', '33333333-3333-3333-3333-333333333333', 'member', now() - INTERVAL '5 days') ON CONFLICT (collective_id, user_id) DO NOTHING; SELECT set_config('request.jwt.claim.sub', 'e3000000-0000-0000-0000-000000000002', true); SELECT lives_ok( $$ SELECT public.delete_account() $$, 'DA-02: sole admin with promotable member can delete account' ); SELECT is( (SELECT role::text FROM public.collective_members WHERE collective_id = 'cccccccc-5555-5555-5555-555555555555' AND user_id = '33333333-3333-3333-3333-333333333333'), 'admin', 'DA-02: member auto-promoted to admin after sole-admin leaves via delete' ); -- ── Setup C: sole-admin with only-guest member → should be rejected ────────── INSERT INTO auth.users (instance_id, id, aud, role, email, email_confirmed_at, confirmation_token, recovery_token, email_change_token_new, email_change, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at) VALUES ('00000000-0000-0000-0000-000000000000', 'e3000000-0000-0000-0000-000000000003', 'authenticated', 'authenticated', 'delacc-stuck@local', now(), '', '', '', '', '{}', '{}', false, now(), now()) ON CONFLICT (id) DO NOTHING; INSERT INTO public.users (id, email, display_name, language, avatar_type) VALUES ('e3000000-0000-0000-0000-000000000003', 'delacc-stuck@local', 'Del Stuck', 'es', 'initials') ON CONFLICT (id) DO NOTHING; INSERT INTO public.collectives (id, name, emoji, created_by) VALUES ('cccccccc-6666-6666-6666-666666666666', 'Stuck collective', '🚪', '22222222-2222-2222-2222-222222222222') ON CONFLICT (id) DO NOTHING; INSERT INTO public.collective_members (collective_id, user_id, role) VALUES ('cccccccc-6666-6666-6666-666666666666', 'e3000000-0000-0000-0000-000000000003', 'admin'), ('cccccccc-6666-6666-6666-666666666666', '44444444-4444-4444-4444-444444444444', 'guest') ON CONFLICT (collective_id, user_id) DO NOTHING; SELECT set_config('request.jwt.claim.sub', 'e3000000-0000-0000-0000-000000000003', true); SELECT throws_ok( $$ SELECT public.delete_account() $$, 'P0003', NULL, 'DA-03: sole admin with only-guest member rejected with P0003' ); SELECT * FROM finish(); ROLLBACK;