mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 04:32:42 +00:00
169 of 364 mutating handlers met no rate limit, every PATCH and DELETE on an [id] among them. The shared per-account write bucket (300 a minute, keyed on the acting account) was called by hand in a dozen creates; it is now charged by the identity resolvers for any POST, PUT, PATCH or DELETE, once per request, so a route is metered by resolving its caller. The bulk ingest routes the native client syncs through in bursts (measurements, workouts, intake, mood and cycle bulk, nutrients, the ECG sweep) declare writeBucket: "own" and keep their dedicated ceilings, so a large HealthKit backfill cannot lock the person out of logging a dose. A read-over-POST stays free. write-rate-limit-guard fails on any mutating route that neither resolves its caller through a metering resolver nor calls a limiter, unless it is on a short reasoned exception list; an opt-out must be on its own reviewed list and call its own limiter.
62 lines
2.8 KiB
TypeScript
62 lines
2.8 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, vi } from "vitest";
|
|
import { settleBackgroundTasks } from "@/lib/logging/background-tasks";
|
|
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();
|
|
});
|
|
|
|
/**
|
|
* 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) };
|
|
});
|