Testing Supabase Apps
The sqlproof.contrib.supabase module bundles helpers for the parts of a
Supabase test setup that don’t generalize to plain Postgres: auth-user
seeding and JWT-claim impersonation. These live in contrib/ (not core)
because the JWT-claim shape and the auth.users table are
Supabase/PostgREST conventions, not Postgres features.
Seeding test users
Section titled “Seeding test users”You have two paths depending on whether your test environment can reach Supabase’s admin API:
Direct SQL insert (preferred locally)
Section titled “Direct SQL insert (preferred locally)”When you’re running against a local Supabase or any DB connection that has
write access to auth.users:
Admin API (preferred in CI when service-role key is available)
Section titled “Admin API (preferred in CI when service-role key is available)”When you have SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY set:
Both helpers use the same email pattern, so a test that samples from
auth.users WHERE email LIKE 'sqlproof_%@test.invalid' works regardless of
which path was taken.
Wiring into ExternalTableSpec
Section titled “Wiring into ExternalTableSpec”Once users exist, register auth.users as an external parent for FK
generation:
Now any FK column referencing auth.users(id) in your generated dataset
draws from the seeded test users.
Acting as a user (RLS testing)
Section titled “Acting as a user (RLS testing)”as_supabase_user is a context manager that sets request.jwt.claims for
the current transaction so PostgREST/Supabase auth helpers (auth.uid(),
auth.jwt(), auth.role()) resolve to the given user:
Important properties:
- Restores prior claim on exit. Nested
as_supabase_usercalls stack and unwind correctly. - Safe under exceptions. Implemented with
try/finally. - Composable with
db.savepoint()— wrap whichever you want to take precedence first. - Plain Postgres, no Supabase RPC. The helper only sets a GUC; it doesn’t talk to the auth API.
Custom claims
Section titled “Custom claims”Pass extra_claims to merge additional JWT fields:
Order: {"sub": user_id, "role": role, **extra_claims}. Pass role in
extra_claims to override the default "authenticated".
Stateful + RLS
Section titled “Stateful + RLS”The combo earns its keep on RLS regression tests, where bugs surface
across membership churn rather than a single permission check. See the
stateful testing guide for an end-to-end example
covering get_member_project_ids / get_editor_project_ids against
project_members mutations.
Caveats
Section titled “Caveats”seed_test_users_directlyrequires the DB connection to have INSERT privilege onauth.users. Local Supabase grants this by default; managed Supabase typically does not — use the admin-API path there.- Setting
request.jwt.claimsonly changes whatauth.uid()returns for the transaction; it does not bypass RLS or change the connection’s Postgres role. Assume your tests run as the connection’s role (typicallypostgresorservice_role) for non-RLS queries. - The
auth.usersschema can drift across Supabase versions. The helpers insert minimal columns (id,aud,role,email); if your test data needs richer auth metadata, insert directly with raw SQL.