Files
HealthLog/vitest.setup.ts
T
Marc-André Bombeck 28f9b1948b test: a flaky-test quarantine with an expiry the guard enforces
tests/quarantine/quarantine.json is the only way to take a flaky unit or
integration test out of a run. An entry names the file, the full test name,
the issue, the reason, the day it was added and the day it expires, at most
14 days later. Both setup files skip a listed test while its entry is live,
reported as skipped with its issue rather than retried into green. Past its
expiry date the test runs again, and quarantine-guard.test.ts fails until the
entry is removed; it also fails on an entry naming a file or test that does
not exist. The list starts empty.
2026-10-22 05:33:09 +02:00

98 lines
4.2 KiB
TypeScript

/**
* Unit-test setup.
*
* Seed the client-side locale cache with the EN bundle. In production
* only the active locale reaches the client (boot script, then dynamic
* import on switch — see `src/lib/i18n/load-locale.ts`). Tests mount
* `I18nProvider` standalone — often with `renderToStaticMarkup`, which
* is synchronous and cannot await a dynamic import — so EN, the fallback
* floor of the t() chain, is primed up front.
*
* Only EN. Every catalog is several hundred kilobytes and this file runs
* once per test file, so priming all of them made setup the largest share
* of a unit run. A test that renders another locale primes it itself:
*
* import deMessages from "../../../messages/de.json";
* primeMessages("de", deMessages);
*
* Server-side translation (`getServerTranslator`, `shared-resolve`) holds
* every catalog on its own and needs no priming.
*/
import { afterEach, beforeEach, vi } from "vitest";
import { settleBackgroundTasks } from "@/lib/logging/background-tasks";
import { quarantineEntryFor } from "./tests/quarantine/quarantine";
import { primeMessages } from "@/lib/i18n/load-locale";
import enMessages from "./messages/en.json";
primeMessages("en", enMessages);
/**
* Leave no background work in flight when a test ends.
*
* Several paths start a promise, return without awaiting it, and log on the
* detached handle so the failure is not swallowed. Vitest forwards every
* console write to the main thread as an `onUserConsoleLog` RPC call and
* rejects whatever is still pending on that channel while it tears a test file
* down, so a write that lands in that window fails the whole run with
* `EnvironmentTeardownError: Closing rpc while "onUserConsoleLog" was pending`
* — blamed on the file the worker was tearing down, which is not the test that
* started the work and need not log anything of its own. Awaiting the
* registered tasks here removes the precondition instead of muting the
* console, which would have thrown away a real signal and left the
* non-determinism in place. See `src/lib/logging/background-tasks.ts`.
*/
afterEach(async () => {
await settleBackgroundTasks();
});
/**
* Skip a test on the flaky-test quarantine list until its entry expires. See
* `tests/quarantine/quarantine.ts` and docs/contributing/testing.md.
*/
beforeEach((context) => {
const entry = quarantineEntryFor(context.task);
if (entry) {
context.skip(`quarantined until ${entry.expires} (${entry.issue})`);
}
});
/**
* The shared write ceiling is charged by every identity resolver on a write
* (`src/lib/auth/write-meter.ts`), which is a database round-trip most route
* tests have no fake for: their Prisma mock carries the models the handler
* touches and nothing else. The meter is therefore a no-op in unit tests by
* default. Its own behaviour is pinned by
* `src/lib/auth/__tests__/write-meter.test.ts`, which loads the real module,
* and against Postgres by `tests/integration/write-meter.test.ts`; a route test
* that wants the refusal mocks `meterAccountWrite` to throw.
*/
vi.mock("@/lib/auth/write-meter", async (importOriginal) => {
const actual =
await importOriginal<typeof import("@/lib/auth/write-meter")>();
return { ...actual, meterAccountWrite: vi.fn(async () => undefined) };
});
/**
* The end of the legacy Fitbit Web API is a calendar date
* (`src/lib/fitbit/retirement.ts`). Read off the real clock, every suite that
* exercises the Fitbit sync, its jobs or its routes would turn red on that day
* and stay red until the provider code is removed. The suites therefore run
* before the turndown by default; a test about the turndown itself flips
* `isFitbitRetired` (`vi.mocked(isFitbitRetired).mockReturnValue(true)`) or
* loads the real module with `vi.importActual`, as
* `src/lib/fitbit/__tests__/retirement.test.ts` does.
*/
vi.mock("@/lib/fitbit/retirement", async (importOriginal) => {
const actual =
await importOriginal<typeof import("@/lib/fitbit/retirement")>();
const isFitbitRetired = vi.fn((_now?: Date) => false);
return {
...actual,
isFitbitRetired,
fitbitRetirement: vi.fn((now?: Date) => ({
...actual.fitbitRetirement(now),
ended: isFitbitRetired(now),
})),
};
});