feat(fase-10): account hard-delete — FK relax + RPC (10.5)
Migration 019 normalizes every \`created_by\` / \`updated_by\` FK from \`NOT NULL REFERENCES users(id) ON DELETE RESTRICT\` to \`NULL REFERENCES users(id) ON DELETE SET NULL\`. Affected tables: collectives, collective_invitations, shopping_lists, shopping_items, task_lists, tasks, notes (created_by + updated_by). Without this, delete_account() would be blocked the first time the user creates any content. Spec §6.3: content survives the user; the row is orphaned with the FK set to NULL (UI shows "Deleted user" — to be polished later). Migration 021 adds delete_account(): - two-step delete: public.users (fires the existing promote-oldest- admin trigger from migration 002, then CASCADEs collective_members and SET-NULLs content FKs), then auth.users (CASCADEs sync_conflicts + drops the GoTrue identity row + token rows) - guard: errcode P0003 when the caller is the sole admin of a collective where every other member is a guest (nobody promotable); the user must promote someone manually first - Keycloak is NOT touched (documented limitation in the UI body); the user can re-register with the same email and will receive a fresh UUID-distinct profile Settings UI (already shipped with 10.1) wires the modal: explicit body listing what is and isn't deleted; confirm button only enables when the user types DELETE exactly. Post-success calls logout() so the Keycloak SSO cookie is ended too. pgTAP 013 (8 assertions): RPC exists, regular user delete + cascade + content-orphan, sole-admin-with-promotable promotes, sole-admin-with- only-guest blocked with P0003. Playwright DEL-01 covers the type-the-word UI guard; full "delete + re-login fails" is left to pgTAP because seeding ephemeral Keycloak accounts in E2E would contaminate every downstream suite. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
49
apps/web/tests/e2e/account-deletion.test.ts
Normal file
49
apps/web/tests/e2e/account-deletion.test.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* DEL-series: /settings → Delete account (Fase 10.5).
|
||||
*
|
||||
* The full "delete + re-login fails" path needs an ephemeral Keycloak user
|
||||
* because deleting a seed user would break every downstream suite that
|
||||
* assumes Ana/Borja/etc. exist. That round-trip is heavy and brittle; the
|
||||
* pgTAP suite (supabase/tests/013_delete_account.sql) already proves the
|
||||
* DB-side contract exhaustively (cascade, role auto-promote, P0003 guard).
|
||||
*
|
||||
* This suite focuses on the UI contract that the pgTAP can't observe:
|
||||
* * the button is only enabled when the user types the exact word
|
||||
* ("DELETE") into the confirmation input
|
||||
* * the danger-zone block is visible on /settings
|
||||
*
|
||||
* Cleanup: no state changes, no afterAll needed.
|
||||
*/
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { USERS } from '../fixtures/users.js';
|
||||
import { loginAs } from '../fixtures/login.js';
|
||||
import { closePool } from '@colectivo/test-utils';
|
||||
|
||||
test.describe.configure({ mode: 'serial' });
|
||||
|
||||
test.describe('Account deletion UI (CU §6.3)', () => {
|
||||
test.afterAll(async () => {
|
||||
await closePool();
|
||||
});
|
||||
|
||||
test('DEL-01: confirm button is disabled until the exact word is typed', async ({ page }) => {
|
||||
await loginAs(page, USERS.ana);
|
||||
await page.goto('/settings');
|
||||
|
||||
// Danger zone surface.
|
||||
const opener = page.getByTestId('delete-account-open');
|
||||
await expect(opener).toBeVisible({ timeout: 15_000 });
|
||||
await opener.click();
|
||||
|
||||
const confirm = page.getByTestId('confirm-delete-account');
|
||||
const input = page.getByTestId('delete-account-confirm-input');
|
||||
|
||||
await expect(confirm).toBeDisabled();
|
||||
|
||||
await input.fill('delete'); // lowercase — must be case-sensitive
|
||||
await expect(confirm).toBeDisabled();
|
||||
|
||||
await input.fill('DELETE');
|
||||
await expect(confirm).toBeEnabled();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user