import { createClient } from '@supabase/supabase-js'; import { SignJWT } from 'jose'; import type { Database } from '@colectivo/types'; function requiredEnv(key: string): string { const value = process.env[key]; if (!value) throw new Error(`Missing required env var: ${key}`); return value; } /** * Sign a GoTrue-compatible JWT for a seed user using the shared SUPABASE_JWT_SECRET. * This lets integration tests act as any seed user without going through Keycloak. */ async function signToken(userId: string): Promise { const secret = new TextEncoder().encode(requiredEnv('SUPABASE_JWT_SECRET')); return new SignJWT({ role: 'authenticated' }) .setProtectedHeader({ alg: 'HS256' }) .setSubject(userId) .setAudience('authenticated') .setIssuedAt() .setExpirationTime('1h') .sign(secret); } /** * Create a Supabase client that authenticates as the given seed user. * RLS policies see auth.uid() = userId. * * Also calls `realtime.setAuth(token)` so Realtime subscriptions carry the * same identity — the server evaluates RLS against this JWT when deciding * which Postgres-changes events to forward. */ export async function createClientAs(userId: string) { const token = await signToken(userId); const client = createClient( requiredEnv('PUBLIC_SUPABASE_URL'), requiredEnv('PUBLIC_SUPABASE_ANON_KEY'), { global: { headers: { Authorization: `Bearer ${token}` } }, auth: { persistSession: false, autoRefreshToken: false } } ); // @ts-expect-error — realtime.setAuth exists at runtime on supabase-js v2 client.realtime.setAuth(token); return client; } /** * Create a service-role client that bypasses RLS entirely. * Use only for test setup and cleanup — never in assertions. */ export function createAdminClient() { return createClient( requiredEnv('PUBLIC_SUPABASE_URL'), requiredEnv('SUPABASE_SERVICE_ROLE_KEY'), { auth: { persistSession: false, autoRefreshToken: false } } ); }