Files
HealthLog/tests/integration/environment-setup.ts
T
Marc-André Bombeck bdf3ffb5da test: settle detached background work before a test file tears down
The unit suite reported all 1937 files green and then failed the job with
`EnvironmentTeardownError: Closing rpc while "onUserConsoleLog" was
pending`, naming a different scoring test each time.

Vitest replaces globalThis.console with a sink that forwards every write
to the main thread as an onUserConsoleLog RPC call. When a worker
finishes a test file it awaits the calls in flight and then rejects
whatever was started after that point, so a console write from work that
outlived its test lands in the gap and fails the run. The blame goes to
the file being torn down rather than to the test that started the work,
which is why the named file never logged anything itself.

Three paths detach a promise and log on the handle: fireAndForget, the
stale-while-revalidate cache refresh, and the offline query persister.
They now register the handled handle in a set that both suite setups
drain in afterEach, so no test leaves work in flight. The registry is
inert outside test: nothing there awaits it, and holding a reference to
an unsettled promise would retain it for the life of a worker. No
production behaviour changes, so there is no changelog entry.

Evidence. A focused harness that starts failing background tasks and
returns hit the teardown error 10 runs out of 10 before the change and
0 out of 10 after. Instrumenting the real suite showed two detached
console writes per run escaping their test, both from the rollup tail;
after the change, none, and thirteen writes per run now complete inside
the test that started them.

The guard asserts the behaviour, not a count: a registered task must
keep settleBackgroundTasks from resolving ahead of it, and the set must
drain back to empty. It goes red on unregistered work, on a disabled
registry, on an unwrapped call site, on a sweep that matches nothing,
and on a setup that drops the hook.
2026-09-03 05:30:08 +02:00

35 lines
1.4 KiB
TypeScript

import { afterEach, inject } from "vitest";
declare module "vitest" {
export interface ProvidedContext {
integrationDatabaseUrl: string;
}
}
process.env.DATABASE_URL = inject("integrationDatabaseUrl");
process.env.ENCRYPTION_KEY =
"0000000000000000000000000000000000000000000000000000000000000000";
delete process.env.ENCRYPTION_KEYS;
delete process.env.ENCRYPTION_ACTIVE_KEY_ID;
process.env.API_TOKEN_HMAC_KEY = "integration-test-hmac-key-32-bytes-minimum";
process.env.SESSION_SECRET = "integration-test-session-secret-32-bytes";
/**
* Leave no background work in flight when an integration test ends — the same
* contract the unit setup keeps, for the same reason: detached background work
* logs on its own handle, and a console write that reaches vitest after the
* worker has begun closing its RPC channel fails the run with
* `EnvironmentTeardownError: Closing rpc while "onUserConsoleLog" was pending`.
* This suite has its own config and its own setup file, so the unit hook does
* not cover it.
*
* The import is deferred: this file's whole job is to bridge DATABASE_URL into
* the worker BEFORE any application module reads it at import time, and a
* static import here would pull part of the application tree in first.
*/
afterEach(async () => {
const { settleBackgroundTasks } =
await import("@/lib/logging/background-tasks");
await settleBackgroundTasks();
});