Audience: engineering, QA, and release/audit reviewers. This documents the formal test harness in tests/ that supersedes the ad-hoc test-api.sh smoke script. It validates normal/typical product operations end to end.

How the harness is organised

The harness separates what an operation is from how it is verified, in three layers:

Procedures

One API operation per file (e.g. createPolicy, registerDevice). Reusable, typed building blocks under src/procedures/.

Cases

Ordered, asserted scenarios composing procedures (e.g. Policy CRUD) under src/cases/*.case.test.ts. Each step is individually reported.

Plans

Grouping of cases by capability with an objective. Catalogued in src/catalogue.ts for traceability.

Pass / fail determination

The harness uses Vitest, giving rigorous, machine-gradeable results — a step up from the old script which printed messages but never aggregated a verdict.

Type awareness

Tests are validated against the same generated database types the application uses. tests/src/types/database.ts re-exports Database, Tables, TablesInsert, TablesUpdate from the frontend’s database.types.ts, and the Supabase clients are typed with Database. REST/RPC procedures (.from('policies'), .update(...)) are therefore checked column-by-column at compile time.
Newly added tables not yet in the generated types (currently organisation_deletion_log) use a clearly-marked untyped escape hatch until supabase gen types is re-run.

API fidelity (no direct SQL)

A core principle of this harness is that tests exercise the product the way real clients do — through edge functions and PostgREST/RPC with a user JWT (RLS enforced) — never via privileged SQL. The web and mobile apps talk to PostgREST with the user’s token; the harness does the same via ctx.asAdmin. Every feature procedure uses a real API. The service-role client (which bypasses RLS, a path no client can take) is confined to two non-product roles: With email confirmation enabled, the bootstrap admin is created in USER_SEED_MODE=signup_confirm: real public signup + an admin email-confirm that mimics clicking the link. Members are added through the real invite-user function. So the only irreducible admin access is the single confirmation step, cleanup, and post-deletion verification.
All operations under test are driven through edge functions or user-JWT PostgREST/RPC.
Service-role access appears only in scaffolding and post-deletion verification, each clearly commented in code.

Environment & safety

These tests perform destructive operations (they create and then delete organisations and users).
Running against any non-local target requires ALLOW_DESTRUCTIVE_TESTS=true. Test data is namespaced (e2e-test-*, qa+*) and the safety-net teardown can only remove resources the run itself created — never a pre-existing tenant.
User accounts are seeded directly via the Admin API with known passwords and no invitation email, which is how the harness avoids the email round-trip that made the production invite flow untestable.

Running

Test Plan Catalogue

Ten Test Plans cover the typical product operations ported from test-api.sh, plus the onboard/offboard lifecycle.

TP-ORG — Organisation Lifecycle

Verify an organisation can be onboarded and fully erased (GDPR), with cascade deletion and an audit record.

TP-DEVICE — Device Management

Verify device registration, idempotent re-registration, capability sync, and validation.

TP-TAG — NFC Tag Management

Verify NFC tag creation, listing, and duplicate rejection.

TP-SCAN — Scan Resolution

Verify NFC scan profile resolution, event logging, and error paths.

TP-RPROFILE — Restriction Profiles

Verify CRUD for restriction profiles.

TP-POLICY — Policies

Verify the full policy lifecycle including the schedule variant.

TP-USER — User Management

Verify user invitation, listing, validation, deletion, and the self-deletion guard.

TP-APPCAT — App Catalogue

Verify app search and category listing (external iTunes proxy) contracts.

TP-LIMITS — Subscription Limits

Verify plan-limit enforcement on resource creation.

TP-ORGINFO — Organisation Info

Verify organisation read operations.

Coverage vs test-api.sh

Every operation in the original script is represented as a procedure and exercised by a case. The key improvements:
Each operation is a reusable, typed procedure (not inline curl).
Cases run procedures in order with real pass/fail assertions.
Per-run organisation isolation replaces shared-org mutation.
Onboarding and offboarding are first-class tested lifecycles.
Limit enforcement runs against a fresh org filled to its plan maximum.

Roadmap

The directory layout is structured so the next layers slot in without rework:

Load testing (k6)

Reuse the seeded-user + edge-function patterns to script throughput/latency runs under load/.

E2E (Playwright)

Drive the web UI (e.g. the Settings → Danger Zone offboard flow) under src/e2e/, sharing .env.test.