Files
Moheeb Zara 6403220c92 test(integration): DB-backed harness validating shipped SQL (045/042/044/043)
Root-cause fix for 'correctness depended on post-deploy manual audits'.
Separate psql-driven suite (pnpm test:integration, own vitest config,
integration/ outside __tests__ so the hermetic pnpm test gate is
untouched; self-skips without a DB). 18 tests run against real Postgres:
- 045 class_signup_tx/class_cancel_tx: RETURNS TABLE->array shape the
  actions destructure, capacity/waitlist/dedupe, and the per-session
  advisory lock under TWO concurrent connections (D11 proven, never
  over-enrolls).
- 042 equipment exclusion constraint (overlap 23P01; cancelled/adjacent
  ok). 044 self-change trigger (role + payment cols blocked; service
  bypass). 043 members_update WITH CHECK (cross-space move blocked).
Disproves the pass-52 flagged risk (signup rpc-shape). Non-runtime;
default pnpm test still 532/26 hermetic; build clean.
2026-05-19 13:01:16 -07:00

7.8 KiB
Raw Permalink Blame History

Local development quickstart

Fresh-clone to running app in under five minutes. Uses the Supabase CLI to spin up a complete local stack (Postgres + Auth + PostgREST + Realtime + Storage + Studio) in Docker. No Supabase account needed.

Prerequisites

  • macOS, Linux, or WSL2 on Windows.
  • Docker (running). docker info should print a server section.
  • Homebrew on macOS, or follow the Supabase CLI install docs for your OS.
  • Node 20+ and pnpm 9+. nvm works fine.

1. Install the Supabase CLI

brew install supabase/tap/supabase
supabase --version    # 1.x or 2.x

2. Clone and install

git clone <repo-url>
cd hackerspace-management
pnpm install

3. Initialize and start the local Supabase stack

supabase init        # writes supabase/config.toml; safe if it already exists
supabase start

First run pulls a few GB of images and takes 1–3 minutes. Subsequent starts are seconds.

When it finishes, the CLI prints something like:

         API URL: http://127.0.0.1:54321
     GraphQL URL: http://127.0.0.1:54321/graphql/v1
  S3 Storage URL: http://127.0.0.1:54321/storage/v1/s3
          DB URL: postgresql://postgres:postgres@127.0.0.1:54322/postgres
      Studio URL: http://127.0.0.1:54323
    Inbucket URL: http://127.0.0.1:54324
      JWT secret: super-secret-jwt-token-with-at-least-32-characters-long
        anon key: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
service_role key: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

Save the anon key and service_role key. You can re-print them anytime with supabase status.

The DB is at postgresql://postgres:postgres@127.0.0.1:54322/postgres. Studio (Supabase dashboard, locally) is at http://127.0.0.1:54323. The Inbucket email catcher is at http://127.0.0.1:54324 — every email the app would send shows up there.

4. Apply the schema and the seed

# Schema (canonical, covers every migration through 019)
psql "postgresql://postgres:postgres@127.0.0.1:54322/postgres" -f scripts/schema.sql

# Optional but recommended for local: load a demo space with members, tasks,
# proposals, an incident, a policy, and activity-log entries.
psql "postgresql://postgres:postgres@127.0.0.1:54322/postgres" -f scripts/seed.sql

You can verify with Studio: open http://127.0.0.1:54323, switch to the public schema, and see 18 tables including proposals, incidents, policies.

5. Generate TypeScript types from the live local schema

supabase gen types typescript --local > types/database.ts

This clears the masked TS errors that next.config.mjs: typescript.ignoreBuildErrors: true was hiding. After this you can drop that ignoreBuildErrors flag and any future schema drift surfaces in CI.

6. Write .env.local

cp .env.example .env.local

Edit .env.local:

NEXT_PUBLIC_SUPABASE_URL=http://127.0.0.1:54321
NEXT_PUBLIC_SUPABASE_ANON_KEY=<paste anon key from step 3>
SUPABASE_SERVICE_ROLE_KEY=<paste service_role key from step 3>
NEXT_PUBLIC_APP_URL=http://localhost:3000
NODE_ENV=development

7. Run the dev server

pnpm dev

Open http://localhost:3000. Health check: curl http://localhost:3000/api/health returns {"status":"ok"}.

8. Sign up as a real user and join the demo space

  1. Visit /signup.
  2. Pick Join a Space and enter invite code: DEMO-2026-TEST.
  3. Fill in your email / password.
  4. Local Supabase is configured to skip email confirmation by default, so signup takes you straight to /dashboard.
  5. The dashboard now shows the seeded activity. Visit /proposals, /incidents, /policies, /financials to see the full feature surface.

If you want admin permissions for the demo: connect to the DB (or via Studio) and run:

UPDATE public.space_members
SET role = 'admin'
WHERE email = 'your-email@example.com';

Common dev tasks

Wipe and re-seed

supabase db reset                                                        # drops & re-runs the database
psql "postgresql://postgres:postgres@127.0.0.1:54322/postgres" -f scripts/schema.sql
psql "postgresql://postgres:postgres@127.0.0.1:54322/postgres" -f scripts/seed.sql

Apply a new incremental migration

psql "postgresql://postgres:postgres@127.0.0.1:54322/postgres" -f scripts/0NN_my_change.sql

PostgREST auto-refreshes on schema changes locally; no restart needed.

Run tests

pnpm vitest run        # 259 tests across 8 files; no DB needed
pnpm exec playwright test    # E2E (Playwright will spin up the dev server)

Type check

pnpm exec tsc --noEmit

After step 5, this should be clean. If it isn't, regenerate types.

Stop the local stack

supabase stop

Containers are stopped but volumes persist. To nuke them entirely: supabase stop --no-backup then supabase start for a fresh slate.

Tail logs

supabase logs           # all services
supabase logs --type api
supabase logs --type db

Run the proposal-expiry job manually

SELECT public.expire_proposals();
-- returns the number of proposals flipped from 'open' to 'decided' or 'expired'

In production you'd schedule this via pg_cron (the schema's migration 019 sets this up automatically when pg_cron is available) or via a Next.js cron endpoint guarded by a CRON_SECRET.

Integration tests (DB-backed)

pnpm test is hermetic (pure logic, jsdom, no DB) and is the gate. The separate pnpm test:integration suite (integration/, its own vitest.integration.config.ts) exercises the shipped SQL — the class_signup_tx/class_cancel_tx RPCs (045) including the advisory lock under real concurrency, the equipment_reservations_no_overlap exclusion constraint (042), and the self-change trigger / members_update WITH CHECK (044/043) — against a real Postgres through psql (no extra npm dep). It drives both access paths the app uses: the service path (no JWT) and a logged-in member (role authenticated + request.jwt.claims.sub).

Prereqs: the Supabase CLI stack running (supabase start) with the schema applied (psql "$DB_URL" -f scripts/schema.sql). It connects to INTEGRATION_DATABASE_URL (default the local stack postgresql://postgres:postgres@127.0.0.1:54322/postgres) and self-skips cleanly when no DB is reachable, so it never blocks contributors/CI without one. It is intentionally NOT part of pnpm test or the deploy gate.

supabase start
psql "postgresql://postgres:postgres@127.0.0.1:54322/postgres" -f scripts/schema.sql
pnpm test:integration

Troubleshooting

supabase start hangs or fails

Make sure Docker Desktop is open and docker info works. Some corporate VPNs interfere with the local images; turn the VPN off, run supabase start, then turn it back on.

"relation does not exist" when the app runs

You skipped step 4. Apply scripts/schema.sql.

"Invalid API key" toasts in the app

The anon key in .env.local doesn't match supabase status. supabase stop --no-backup && supabase start will regenerate the keys cleanly; paste the new ones into .env.local.

Emails aren't arriving

Local Supabase routes outbound mail to Inbucket. Open http://127.0.0.1:54324.

Signup says "Database error saving new user"

The handle_space_signup trigger fired but couldn't insert the space_members row. Almost always because space_id metadata was missing. The app's signup flow handles this correctly via the admin client; if you're testing direct auth.users inserts, that path doesn't write a space_members row by design.

Realtime chat doesn't refresh

Confirm comms_messages and comms_channels are in supabase_realtime:

SELECT tablename FROM pg_publication_tables WHERE pubname = 'supabase_realtime';

If they aren't, re-run scripts/schema.sql (Section 8 handles it idempotently).