Files
supabase__server/e2e
Katerina Skroumpelou 09a67506db test: add E2E tests for all four adapters against a local Supabase stack (#99)
* test: add E2E tests for all four adapters against a local Supabase stack

Adds an e2e vitest project (SDK-1143) covering what the mocked unit tests
cannot: real GoTrue-issued JWTs verified against the live JWKS endpoint,
real Supabase client operations via supabaseAdmin, resolveEnv() reading
process.env, and imports from dist/ so packaging regressions fail here.

One scenario set (auth + data access + isolation) runs over real HTTP
against minimal Hono, H3, Elysia, and NestJS apps. Elysia runs behind a
node:http server (srvx) so CI needs no Bun. A separate E2E workflow
starts the local stack with the Supabase CLI, builds, and runs the suite.

* test: grant explicit table privileges in the e2e notes migration

Newer Supabase stacks make new tables private by default — the API roles
(anon/authenticated/service_role) no longer receive DML grants on table
creation. CI installs the latest CLI, so all supabaseAdmin queries failed
with "permission denied for table notes" while JWT scenarios passed.
Reproduced locally on CLI 2.109.1; explicit grants fix it on both old and
new stacks.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test: align NestJS missing-body handling and cover it in the scenarios

The NestJS app silently inserted an empty note when the body was missing,
while the other three adapters returned 400 — and no scenario exercised
those 400 branches. NestJS now throws BadRequestException like the rest,
and a shared missing-body scenario keeps all four aligned.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test: cover forged JWTs, the RLS-scoped client, and optional-auth rejection

Closes the three gaps from PR review: the garbage-token scenario failed at
header decode without ever reaching signature verification, ctx.supabase
(the RLS-scoped client) was never exercised, and nothing pinned that a
present-but-invalid token on an optional route is rejected rather than
downgraded to anonymous.

- Mint a well-formed JWT with the live JWKS kid but a wrong signing key in
  global setup; every adapter must 401 it — proving signature verification
  end-to-end, not just structure checks.
- Add GET /my-notes reading through ctx.supabase with no WHERE clause,
  backed by a user_id = auth.uid() select policy — proving the caller's
  token reaches PostgREST and Postgres RLS scopes the rows.
- Assert GET /me-optional with an invalid token → 401.

10 → 14 scenarios per adapter (56 tests).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test: add core-wrapper app, admin-bypass proof, and sign-in readability

Addresses PR review comments:

- New fifth app on the core withSupabase(config, handler) fetch wrapper —
  the exact programming model Supabase Edge Functions deploy — running the
  full scenario set behind node:http. A real Deno runtime e2e via
  `supabase functions serve` is tracked in SDK-1280.
- New GET /all-notes route (admin client, no filter) + scenario: user2's
  request sees user1's rows through supabaseAdmin, directly proving the
  admin client is not scoped to the caller's identity.
- Replace the `;({ data, error } = ...)` destructuring-reassignment in the
  sign-in helper with a plain result variable.

15 scenarios × 5 apps (75 tests).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 10:36:13 +03:00
..

E2E tests

End-to-end coverage for @supabase/server: real GoTrue-issued JWTs, real JWKS validation over HTTP, real Supabase client operations — across all four adapters (Hono, H3, Elysia, NestJS) plus the core withSupabase fetch wrapper, the programming model Supabase Edge Functions use.

Unlike the unit/integration tests (mocked env, jwks: null), this suite:

  • imports the library from dist/, not src/, so packaging regressions fail here
  • reads config from process.env via resolveEnv() — no mocked env objects
  • verifies JWTs against the local stack's live JWKS endpoint — including rejecting a forged token (real kid, wrong signing key), so signature verification itself is exercised, not just structure checks
  • covers both context clients: supabaseAdmin (app-layer scoping) and the user-scoped supabase client, where the caller's JWT travels to PostgREST and a Postgres RLS policy scopes the rows
  • runs each adapter app on a real HTTP server and asserts over fetch

Running locally

pnpm build                  # e2e imports from dist/
cd e2e && supabase start    # local stack (Docker) on ports 5433x
cd .. && pnpm gen:env       # writes e2e/.env from `supabase status`
pnpm test:e2e

Run a single adapter with pnpm test:e2e h3.

Layout

  • supabase/ — local stack config + notes table migration
  • apps/<adapter>/app.ts — minimal app per adapter, identical route surface: GET /health (public), GET /me (user), GET /me-optional (user or none), GET|POST /notes (user, admin client scoped by userClaims.id), GET /my-notes (user, RLS-scoped client — no WHERE clause), GET /all-notes (user, admin client with no filter — proves the admin client is not scoped to the caller)
  • apps/core/app.ts — same surface on the core withSupabase(config, handler) fetch wrapper (no adapter) — what an Edge Function deploys. A real Deno supabase functions serve e2e is tracked as a follow-up issue.
  • scenarios.ts — the single scenario set run against every adapter
  • setup/global-setup.ts — checks the stack is up, signs in two test users, provides their tokens to the tests
  • scripts/gen-env.sh — writes .env (gitignored) from the running stack
  • scripts/get-token.ts — prints a real user JWT for manual curl testing: TOKEN=$(node e2e/scripts/get-token.ts) (Node 22.18+)

Elysia is Bun-first, but its app.handle is a plain fetch handler, so the app runs behind a node:http server (via srvx) and CI needs no Bun.