mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-07 04:12:04 +00:00
The window-reset test burned its budget in a 50 ms window: four sequential
round-trips to a containerised Postgres had to finish inside a twentieth of a
second or the window closed under them, the counter reset, and the fourth call
came back allowed. Three failures in thirty days, two of them on `main` — run
33614804032 shows `expected true to be false` at rate-limit.test.ts:56 in a file
that took 1278 ms to run.
The short window bought nothing: the expiry this test is about is forced by hand
a few lines further down, by writing `reset_at` into the past. The burn now runs
in a 60 s window, so no round-trip can outlast it, and the test asserts the row's
count is 4 after the denial — a window that closed mid-burn would also have
produced three allowed calls, and the old assertion could not tell the two
apart.
Gates:
pnpm exec vitest run --config vitest.integration.config.mts \
tests/integration/rate-limit.test.ts
three consecutive runs, 2 passed / 2 passed / 2 passed.
Mutation: with the burn window back at 1 ms the run fails exactly as CI did —
"AssertionError: expected true to be false" on the denial assertion.
92 lines
3.4 KiB
TypeScript
92 lines
3.4 KiB
TypeScript
/**
|
|
* Integration regression guard for `src/lib/rate-limit.ts`.
|
|
*
|
|
* The limiter relies on a single atomic SQL upsert (`INSERT ... ON
|
|
* CONFLICT DO UPDATE`) so concurrent calls cannot exceed the cap. These
|
|
* tests prove that contract against a real Postgres — a unit test with
|
|
* a mocked client could not detect a missing UPSERT or a misplaced
|
|
* window-reset branch.
|
|
*/
|
|
import { beforeEach, describe, expect, it } from "vitest";
|
|
|
|
import { getPrismaClient, truncateAllTables } from "./setup";
|
|
|
|
beforeEach(async () => {
|
|
await truncateAllTables(getPrismaClient());
|
|
});
|
|
|
|
describe("checkRateLimit (real Postgres)", () => {
|
|
it("permits exactly `limit` of N concurrent calls in the same window", async () => {
|
|
// Lazy import so `process.env.DATABASE_URL` is set by startTestDb()
|
|
// *before* the module pulls in `@/lib/db`.
|
|
const { checkRateLimit } = await import("@/lib/rate-limit");
|
|
|
|
const key = "test:concurrent:1.2.3.4";
|
|
const limit = 5;
|
|
const windowMs = 60_000;
|
|
|
|
const results = await Promise.all(
|
|
Array.from({ length: 6 }, () => checkRateLimit(key, limit, windowMs)),
|
|
);
|
|
|
|
const allowed = results.filter((r) => r.allowed).length;
|
|
const denied = results.filter((r) => !r.allowed).length;
|
|
|
|
expect(allowed).toBe(5);
|
|
expect(denied).toBe(1);
|
|
|
|
const row = await getPrismaClient().rateLimit.findUnique({
|
|
where: { key },
|
|
});
|
|
expect(row?.count).toBe(6);
|
|
});
|
|
|
|
it("resets the counter once the window expires", async () => {
|
|
const { checkRateLimit } = await import("@/lib/rate-limit");
|
|
|
|
const key = "test:reset:1.2.3.4";
|
|
const limit = 3;
|
|
|
|
// Burn the budget in a window wide enough that it cannot close under the
|
|
// four calls that fill it. It used to be 50 ms, which asked the four
|
|
// round-trips to a containerised Postgres to finish inside a twentieth of
|
|
// a second: on a loaded runner the window expired mid-burn, the counter
|
|
// reset, and the fourth call came back allowed — three failures in thirty
|
|
// days, two of them on `main` (run 33614804032, `expected true to be
|
|
// false` on the line below, in a file that took 1278 ms to run). The short
|
|
// window bought nothing either way, because the expiry this test is about
|
|
// is forced by hand a few lines down rather than waited for.
|
|
const windowMs = 60_000;
|
|
for (let i = 0; i < 3; i++) {
|
|
const r = await checkRateLimit(key, limit, windowMs);
|
|
expect(r.allowed).toBe(true);
|
|
}
|
|
const denied = await checkRateLimit(key, limit, windowMs);
|
|
expect(denied.allowed).toBe(false);
|
|
|
|
// The denial has to be the cap, not a window that closed under the burn —
|
|
// a reset would also have produced three allowed calls before it.
|
|
const burned = await getPrismaClient().rateLimit.findUnique({
|
|
where: { key },
|
|
});
|
|
expect(burned?.count).toBe(4);
|
|
|
|
// Manually expire the window: cheaper than sleeping and avoids
|
|
// flaky timing on slow CI runners. The branch under test compares
|
|
// `reset_at < NOW()`, so any past timestamp triggers the reset.
|
|
await getPrismaClient().rateLimit.update({
|
|
where: { key },
|
|
data: { resetAt: new Date(Date.now() - 1_000) },
|
|
});
|
|
|
|
const afterReset = await checkRateLimit(key, limit, 60_000);
|
|
expect(afterReset.allowed).toBe(true);
|
|
|
|
const row = await getPrismaClient().rateLimit.findUnique({
|
|
where: { key },
|
|
});
|
|
expect(row?.count).toBe(1);
|
|
expect(row?.resetAt.getTime()).toBeGreaterThan(Date.now());
|
|
});
|
|
});
|