Files
HealthLog/vitest.integration.config.mts
T
Marc-André Bombeck e9f89bbebf ci: shard both suites by measured duration and split the unit suite
Vitest's --shard cut the file list into slices of equal length, which gave
the integration shards 954 s to 1 449 s. A sequencer now deals files out by
their measured duration, plus a fixed per-file cost for import and setup,
longest first to the shard with the least work, and starts the longest files
first inside a shard. Timings live in tests/durations/*.json and are
regenerated from Vitest JSON reports with scripts/test-durations.mjs; every
CI shard uploads its report.

Integration shards run three workers each, one cloned database per worker.
The unit suite runs as three projects decided per file from its source:
guards and unit share a module graph inside a worker, isolated keeps a fresh
one for every file that mocks, spies, stubs or resets. In CI it is three
duration-balanced shards next to a job for lint, format, types and the
OpenAPI drift check.

The required check names stay as they were. "Lint, Typecheck & Test" is now
the job that answers for the static checks and every unit shard, the way
"integration" already answers for its shards. The browser-install guard
recognises a direct, flags-only vitest run as a run of the unit suite.
2026-10-22 05:39:35 +02:00

89 lines
3.8 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";
import { createDurationSequencer } from "./tests/durations/sequencer";
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(),
// `--shard` deals files out by measured duration (CI runs four shards), and
// files start longest first so a parallel run does not end on one slow
// file. Order dependence is surfaced on purpose by the nightly shuffled
// run (`.github/workflows/test-nightly.yml`), which passes
// `--sequence.shuffle` and prints the seed it used.
sequence: {
sequencer: createDurationSequencer(
"tests/durations/integration.json",
0.6,
),
},
// 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"),
},
},
});