Skip to content

Testing

The suite is end-to-end, not unit — it boots the full app against an in-memory SQLite database and drives it through app.request() (no port, no server process). 62 tests across two files:

  • tests/app.test.ts — auth, guards, roles, password reset, Inertia protocol (409/404/SSR), CSRF, health, static assets
  • tests/tus.test.ts — the tus protocol (creation, resume, checksum, termination, ownership) and the profile/avatar flows

Each file is written as an independent suite: it sets its env (DATABASE_PATH=:memory:, UPLOAD_DIR, …) in beforeAll before importing the app modules (config/db read env at import), and calls db.close() in afterAll. Bun 1.3 runs all test files in one shared process, so without --isolate one file’s teardown finalizes the next file’s prepared statements (“Statement has finalized”) and cached config leaks across files.

Terminal window
bun run test # = bun test --isolate
const xhr = { "x-inertia": "true" }; // simulate an Inertia SPA request
async function call(path, options) { // app.request with cookie/body
return app.request(`${BASE}${path}`, { method, headers, body });
}
function sessionCookie(res) { // extract the session cookie
const cookie = allSetCookies(res).find((c) => c.startsWith("session="));
return cookie ? cookie.split(";")[0]! : "";
}
async function registerUser(email, password = "password123") {
const res = await call("/register", {
method: "POST",
headers: xhr,
body: { name: "Test User", email, password },
});
expect(res.status).toBe(303);
return sessionCookie(res); // returns the cookie for later calls
}
  1. Register a user (returns a session cookie).
  2. Actcall() the endpoint with xhr + cookie.
  3. Assert — status, Inertia payload (component, props.errors, props.auth.user), redirect location, headers.

Inertia XHR requests return JSON page payloads, so assertions are structured:

const data = await res.json();
expect(data.component).toBe("Dashboard");
expect(data.props.auth.user.email).toBe("loginok@example.com");
expect(JSON.stringify(data.props)).not.toContain("passwordHash");

Mirror the helpers at the top of the file and add a case to the relevant describe block — or a new block. Env is already configured by beforeAll; if your test needs different env (e.g. tus expiration), set it in a new test FILE (each --isolate process gets fresh globals).

Keep tests deterministic: no sleeps, no external network (fetch is mocked in the Google OAuth block), and control time explicitly where it matters (the expiration sweep takes now as a parameter for this reason).