/** * LANG-series: language bootstrap (Fase 10.8). * * The bootstrap path is a two-step in the client: * 1. detectLanguage(...) — pure parser (unit-tested in apps/web) * 2. update public.users set language = ... where id = $1 * * This integration suite checks (2): the UPDATE wire-format the layout * relies on works against the live RLS-enabled table, both as the user * themselves (allowed) and as another seed user (rejected). The full * end-to-end login dance can't be reproduced here without bringing up * a real OIDC flow, so we focus on the contract the bootstrap depends * on. */ import { describe, it, expect, afterAll } from 'vitest'; import { createClientAs, createAdminClient } from '../src/supabase-clients.js'; import { sql, closePool } from '../src/db-helpers.js'; import { ANA_ID, BORJA_ID } from '../src/seed-constants.js'; const admin = createAdminClient(); afterAll(async () => { // Restore Ana's seed language. await sql(`UPDATE public.users SET language = 'es' WHERE id = $1`, [ANA_ID]); await closePool(); }); describe('Language bootstrap UPDATE', () => { it('LANG-01: user can update their own language to es', async () => { const ana = await createClientAs(ANA_ID); // Reset to en first via admin. await admin.from('users').update({ language: 'en' }).eq('id', ANA_ID); const { error } = await ana .from('users') .update({ language: 'es' }) .eq('id', ANA_ID); expect(error).toBeNull(); const { data } = await admin .from('users') .select('language') .eq('id', ANA_ID) .single(); expect(data?.language).toBe('es'); }); it('LANG-02: user cannot update another user\'s language', async () => { const ana = await createClientAs(ANA_ID); await ana.from('users').update({ language: 'en' }).eq('id', BORJA_ID); const { data } = await admin .from('users') .select('language') .eq('id', BORJA_ID) .single(); // Borja's seed language is 'es'; RLS must have blocked Ana's update. expect(data?.language).toBe('es'); }); });