Files
collective-lists/supabase/tests/006_notes_trash_purge.sql
Oier Bravo Urtasun 104eeba02e feat(fase-3): Tareas + Notas — TDD complete (148 tests green)
3.0 Tests primero
  pgTAP: 005_tasks_rls.sql (5 — completion CHECK), 006_notes_trash_purge.sql (4 — purge SQL inline + pin/archive CHECK)
  Vitest RLS: rls-tasks.test.ts (14), rls-notes.test.ts (15)
  Playwright: tasks.test.ts (3), notes.test.ts (4)

3.1 Tareas
  Migration 008_tasks.sql: task_lists + tasks + task_list_collective_id() helper
  CHECK constraint: is_completed ⇔ completed_by ⇔ completed_at
  RLS by role + cross-collective isolation
  Store apps/web/src/lib/stores/tasks.ts (optimistic CRUD + reorder)
  /tasks overview (grid + sticky create input + 5s polling)
  /tasks/[id] detail with svelte-dnd-action reorder, flip animation,
    inline edit (dblclick), "Completed by …" attribution via lazy-loaded
    collective_members

3.2 Notas
  Migration 009_notes.sql: notes + note_color enum (8) + pin/archive CHECK
    + 7d trash window in RLS + fn_notes_touch trigger + pg_cron purge job
  Store apps/web/src/lib/stores/notes.ts (CRUD + pin/archive/trash/duplicate)
  /notes board (pinned section testid notes-pinned + 30s polling)
  /notes/[id] editor (title + textarea, 500ms debounced autosave,
    color picker, pin/archive/duplicate/trash actions)
  /notes/archive (restore + send to trash)
  /notes/trash (restore + permanent delete)

3.Z Verification
  just test-all → 148 verdes, 2 skipped:
    25 pgTAP (was 16, +9)
    88 Vitest integration (was 59, +29; 2 presence skipped)
    6 Vitest unit (unchanged)
    29 Playwright (was 22, +3 tasks +4 notes)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 10:28:19 +02:00

76 lines
2.4 KiB
PL/PgSQL

-- pgTAP: notes trash purge + pin/archive exclusivity — Fase 3
-- Run with: psql -U postgres -d postgres -f supabase/tests/006_notes_trash_purge.sql
--
-- Validates:
-- - The CHECK constraint forbidding (is_pinned AND is_archived)
-- - The pg_cron purge job (executed inline) hard-deletes notes with
-- deleted_at older than 7 days
CREATE EXTENSION IF NOT EXISTS pgtap;
BEGIN;
SELECT plan(4);
-- Insert a note that is already 8 days in the trash
INSERT INTO public.notes (id, collective_id, content, created_by, updated_by, deleted_at)
VALUES (
'99999999-1111-1111-1111-111111111111',
'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa',
'expired trash note',
'11111111-1111-1111-1111-111111111111',
'11111111-1111-1111-1111-111111111111',
now() - INTERVAL '8 days'
);
-- Insert a note still inside the 7-day window
INSERT INTO public.notes (id, collective_id, content, created_by, updated_by, deleted_at)
VALUES (
'99999999-2222-2222-2222-222222222222',
'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa',
'recent trash note',
'11111111-1111-1111-1111-111111111111',
'11111111-1111-1111-1111-111111111111',
now() - INTERVAL '2 days'
);
-- N-PG-01: pre-purge sanity — both notes exist
SELECT is(
(SELECT count(*)::int FROM public.notes
WHERE id IN ('99999999-1111-1111-1111-111111111111', '99999999-2222-2222-2222-222222222222')),
2,
'N-PG-01: both trash fixtures inserted'
);
-- Run the purge job's SQL directly (matches what pg_cron schedules)
DELETE FROM public.notes WHERE deleted_at < now() - INTERVAL '7 days';
-- N-PG-02: 8-day note is gone
SELECT is(
(SELECT count(*)::int FROM public.notes
WHERE id = '99999999-1111-1111-1111-111111111111'),
0,
'N-PG-02: note older than 7 days is purged'
);
-- N-PG-03: 2-day note still exists
SELECT is(
(SELECT count(*)::int FROM public.notes
WHERE id = '99999999-2222-2222-2222-222222222222'),
1,
'N-PG-03: note inside the 7-day window survives'
);
-- N-PG-04: pin + archive simultaneously violates CHECK
SELECT throws_ok(
$$ INSERT INTO public.notes (collective_id, content, created_by, updated_by, is_pinned, is_archived)
VALUES ('aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', 'pin+archive',
'11111111-1111-1111-1111-111111111111', '11111111-1111-1111-1111-111111111111',
true, true) $$,
'23514',
NULL,
'N-PG-04: notes with is_pinned AND is_archived are rejected by CHECK'
);
SELECT * FROM finish();
ROLLBACK;