-- pgTAP: section visibility (feature_flags) — Fase 12.1 -- Run with: psql -U postgres -d postgres -f supabase/tests/015_section_visibility.sql -- -- Verifies: -- * `users.feature_flags` and `collectives.feature_flags` JSONB columns exist -- with NOT NULL + default '{}'. -- * `public.known_sections()` is IMMUTABLE and returns the canonical 4 sections. -- * `public.section_enabled(section, user, collective)` is STABLE, returns -- boolean, and applies the documented precedence: -- collective override > user override > default ON. -- The forward-compat server layer (Fase 13) adds another layer ABOVE the -- collective one; nothing here should make that diff awkward. -- -- These pgTAP assertions run as postgres which bypasses RLS — the RLS write -- path for `users.feature_flags` / `collectives.feature_flags` is covered by -- the Vitest integration suite (tests/section-visibility.test.ts). CREATE EXTENSION IF NOT EXISTS pgtap; BEGIN; SELECT plan(16); -- ── Schema invariants ─────────────────────────────────────────────────────── SELECT has_column( 'public', 'users', 'feature_flags', 'SV-T01: public.users has feature_flags column' ); SELECT col_type_is( 'public', 'users', 'feature_flags', 'jsonb', 'SV-T02: public.users.feature_flags is jsonb' ); SELECT col_not_null( 'public', 'users', 'feature_flags', 'SV-T03: public.users.feature_flags is NOT NULL' ); SELECT has_column( 'public', 'collectives', 'feature_flags', 'SV-T04: public.collectives has feature_flags column' ); SELECT col_type_is( 'public', 'collectives', 'feature_flags', 'jsonb', 'SV-T05: public.collectives.feature_flags is jsonb' ); SELECT col_not_null( 'public', 'collectives', 'feature_flags', 'SV-T06: public.collectives.feature_flags is NOT NULL' ); -- ── known_sections() ──────────────────────────────────────────────────────── SELECT has_function( 'public', 'known_sections', 'SV-T07: public.known_sections() exists' ); SELECT volatility_is( 'public', 'known_sections', ARRAY[]::text[], 'immutable', 'SV-T08: known_sections() is IMMUTABLE' ); SELECT results_eq( $$ SELECT public.known_sections() $$, $$ VALUES (ARRAY['lists','tasks','notes','search']) $$, 'SV-T09: known_sections() returns the canonical 4 sections in order' ); -- ── section_enabled() ─────────────────────────────────────────────────────── SELECT has_function( 'public', 'section_enabled', ARRAY['text', 'uuid', 'uuid'], 'SV-T10: public.section_enabled(text, uuid, uuid) exists' ); SELECT function_returns( 'public', 'section_enabled', ARRAY['text', 'uuid', 'uuid'], 'boolean', 'SV-T11: section_enabled() returns boolean' ); SELECT volatility_is( 'public', 'section_enabled', ARRAY['text', 'uuid', 'uuid'], 'stable', 'SV-T12: section_enabled() is STABLE (planner-cacheable in a statement)' ); -- ── Precedence semantics ──────────────────────────────────────────────────── -- Uses Ana (seed admin) + the seed collective. Reset both flag columns to {} -- at the top of each assertion so the previous one doesn't leak. UPDATE public.users SET feature_flags = '{}'::jsonb WHERE id = '11111111-1111-1111-1111-111111111111'; UPDATE public.collectives SET feature_flags = '{}'::jsonb WHERE id = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'; -- SV-T13: default ON — empty flags on both rows → tasks enabled. SELECT is( public.section_enabled( 'tasks', '11111111-1111-1111-1111-111111111111', 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa' ), true, 'SV-T13: with empty flag JSON on both rows the section defaults to ON' ); -- SV-T14: user override OFF wins when collective has no opinion. UPDATE public.users SET feature_flags = '{"tasks": false}'::jsonb WHERE id = '11111111-1111-1111-1111-111111111111'; SELECT is( public.section_enabled( 'tasks', '11111111-1111-1111-1111-111111111111', 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa' ), false, 'SV-T14: user OFF + collective absent → OFF' ); -- SV-T15: collective override OFF wins even when user explicitly enables. UPDATE public.users SET feature_flags = '{"tasks": true}'::jsonb WHERE id = '11111111-1111-1111-1111-111111111111'; UPDATE public.collectives SET feature_flags = '{"tasks": false}'::jsonb WHERE id = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'; SELECT is( public.section_enabled( 'tasks', '11111111-1111-1111-1111-111111111111', 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa' ), false, 'SV-T15: collective OFF beats user ON (collective is the higher layer)' ); -- SV-T16: unknown sections default ON (forward-compat — new code can call -- section_enabled() before a migration adds the key to known_sections()). UPDATE public.users SET feature_flags = '{}'::jsonb WHERE id = '11111111-1111-1111-1111-111111111111'; UPDATE public.collectives SET feature_flags = '{}'::jsonb WHERE id = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'; SELECT is( public.section_enabled( 'foo-unknown-section', '11111111-1111-1111-1111-111111111111', 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa' ), true, 'SV-T16: an unknown section key returns true (default ON, no migration race)' ); SELECT * FROM finish(); ROLLBACK;