Demonstrates the built-in SQL test runner (npgsqlrest --test): it discovers *.test.sql files,
runs each in an isolated connection, can invoke endpoints in-process (no network) from an embedded
HTTP request, captures the response into a temp table, and asserts on it from SQL.
⚠️ This example exercises the--testfeature, which is in development. The files below are the intended usage;bun run testworks once the runner ships.
| Path | What it is |
|---|---|
sql/get_users.sql |
endpoint: GET /get-users (anonymous) |
sql/get_users.test.sql |
its co-located test |
sql/create_user.sql |
endpoint: POST /create-user (@authorize admin) |
sql/create_user.test.sql |
its co-located test (auth + multi-step flow) |
sql/normalize_email.test.sql |
standalone test for a DB function (classic assert style) |
migrations_19/ |
schema + seed |
Endpoints and tests live side by side. SqlFileSource serves the .sql files and skips *.test.sql
(SkipPattern); the test runner does the opposite (FilePattern: **/*.test.sql).
cd examples
bun install # once
cd 19_testing_basic
bun run db:up # create schema + seed
bun run test # runs npgsqlrest --test- Embedded HTTP request — any
/* … */block whose first line is a request line is invoked in-process (the path is the endpoint's full path, including theUrlPathPrefix— here/api):/* GET /api/get-users */ - Response capture — each HTTP block's response is captured into its own temp table (
status,body,content_type,headers,is_success). A file with one HTTP block uses_response; a file with several uses_response_1,_response_2, … (one per block). Use# @response <name>to capture a block into a named table instead. - Boolean-select assertions — a
SELECTwhose first column is boolean is an assertion;false/NULLfails, and the second column is the failure message:(The classicselect status = 200, 'expected 200' from _response; -- single-block test
do $$ begin assert …; end $$;style also works — seenormalize_email.test.sql.) - Auth via claims —
# @claim name=valueruns the request as a principal carrying those claims. No claims ⇒ anonymous (401 on protected endpoints);# @claim roles=adminsatisfies@authorize admin:/* POST /api/create-user # @claim roles=admin {"name": "Grace Hopper", "email": "GRACE@Example.com"} */ - Isolation — the test file owns its transaction (
begin … rollback). The in-process call runs on the same connection, so it sees this transaction's uncommitted rows;rollbackdiscards them, leaving the seeded baseline for the next test. (Each test gets its own non-pooled connection.)