Files
HealthLog/vitest.integration.config.mts
T
Marc-André Bombeck a6787cfc03 test(integration): run files in parallel, one cloned database per worker
Global setup migrates one template database and reads what a fresh install
holds: its schemas, its tables and the rows the migrations seed. Each Vitest
worker clones the template on its first file with CREATE DATABASE ... TEMPLATE
and keeps the clone for the rest of the run, so files run in parallel without
seeing each other's rows, advisory locks or queue schema. Schemas created at
runtime (pg-boss, migration probes) are dropped when the next file starts.

The truncate list is the catalogue now, every table in public except the
migration bookkeeping, instead of a hand-kept list of 42 of 166 tables. The
seeded catalogues are put back after each truncate in foreign-key order, so a
test starts from what a fresh install has. The settings singleton is the one
seeded table a test starts without, declared with its reason.

next/headers and the boot compatibility probe are mocked once in the setup
file. Two lock-wait probes now look at their own database only, since other
workers' backends share pg_stat_activity.
2026-10-22 05:30:00 +02:00

78 lines
3.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { defineConfig } from "vitest/config";
import { dirname, resolve } from "node:path";
import { availableParallelism } from "node:os";
import { fileURLToPath } from "node:url";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
function integrationWorkers(): number {
const pinned = Number.parseInt(
process.env.HEALTHLOG_INTEGRATION_WORKERS ?? "",
10,
);
if (Number.isInteger(pinned) && pinned > 0) return pinned;
// Half the cores: the other half runs Postgres and the container runtime.
return Math.max(1, Math.min(8, Math.floor(availableParallelism() / 2)));
}
// Integration suite — boots a real Postgres in a testcontainer and runs
// migrations against it. Slow (container boot ~5–15s on a cold pull),
// so excluded from the default `pnpm test` and triggered explicitly via
// `pnpm test:integration`.
export default defineConfig({
test: {
globals: true,
environment: "node",
// Same host-timezone pin as the unit config — CI runs UTC, so the
// integration contracts must be read against the same clock.
env: {
TZ: "UTC",
ENCRYPTION_KEY:
"0000000000000000000000000000000000000000000000000000000000000000",
ENCRYPTION_KEYS: "",
ENCRYPTION_ACTIVE_KEY_ID: "",
API_TOKEN_HMAC_KEY:
"dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd",
SESSION_SECRET: "integration-test-session-secret-32-bytes",
},
include: ["tests/integration/**/*.test.ts"],
// Container boot + migration apply is slow; give each test a generous
// budget and beforeAll/afterAll twice that.
testTimeout: 60_000,
hookTimeout: 120_000,
// Files run in parallel, one database per worker: global setup migrates a
// template once and each worker clones it on its first file (see
// `tests/integration/worker-database.ts`). The worker count follows the
// host unless `HEALTHLOG_INTEGRATION_WORKERS` pins it; CI pins it to what
// a runner shared with the Postgres container can carry.
pool: "forks",
fileParallelism: true,
maxWorkers: integrationWorkers(),
// Order dependence is looked for on purpose by shuffling file order
// (`--sequence.shuffle`), not by keeping the suite serial.
// v1.4.37 — switched from `isolate: false` to `isolate: true`.
// The previous setting reused the module graph across files inside
// one fork; mock declarations (`vi.mock("@/lib/notifications/...")`,
// `vi.mock("@parse/node-apn")`) registered in `apns-dispatch.test.ts`
// and `integration-status.test.ts` lost to whichever earlier file
// had already pulled the real dispatcher / sender modules into the
// shared graph, so the in-suite run showed flaky 0-call mocks while
// each file passed in isolation. Re-isolating module state per file
// costs ~10 s of import-rebuild on a 56-file run; the Postgres
// testcontainer + migrations still live in `globalSetup` so the
// expensive boot cost is unaffected.
isolate: true,
// One container for the whole run; tests truncate between them.
// Point each isolated worker at its own clone before application modules
// read DATABASE_URL at import time.
setupFiles: ["./tests/integration/environment-setup.ts"],
globalSetup: ["./tests/integration/global-setup.ts"],
},
resolve: {
alias: {
"@": resolve(__dirname, "src"),
},
},
});