mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 12:32:43 +00:00
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.
35 lines
1.4 KiB
TypeScript
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();
|
|
});
|