Files
SnapOtter/tests/unit/wait-for-service.test.ts
T
SnapOtterandGitHub 4ac89fe650 fix(api): wait for Postgres and Redis at startup instead of crash-looping (#537)
Briefly retry Postgres and Redis connectivity at boot (waitForService, DB_STARTUP_TIMEOUT_MS) so an ordered-but-not-yet-ready dependency recovers cleanly instead of crash-looping.
2026-07-16 22:50:11 +08:00

80 lines
2.3 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { waitForService } from "../../apps/api/src/lib/wait-for-service.js";
// Deterministic fake clock: `sleep` advances virtual time and `now` reads it,
// so we can assert retry timing without real timers slowing the suite down.
function fakeClock() {
let ms = 0;
return {
now: () => ms,
sleep: async (delta: number) => {
ms += delta;
},
elapsed: () => ms,
};
}
describe("waitForService", () => {
it("resolves immediately when the probe succeeds on the first attempt", async () => {
const clock = fakeClock();
let attempts = 0;
await waitForService(
async () => {
attempts++;
},
{ timeoutMs: 30_000, intervalMs: 1_000, now: clock.now, sleep: clock.sleep },
);
expect(attempts).toBe(1);
expect(clock.elapsed()).toBe(0); // never slept
});
it("retries until the probe succeeds, then resolves", async () => {
const clock = fakeClock();
let attempts = 0;
await waitForService(
async () => {
attempts++;
if (attempts < 3) throw new Error("not ready");
},
{ timeoutMs: 30_000, intervalMs: 1_000, now: clock.now, sleep: clock.sleep },
);
expect(attempts).toBe(3);
expect(clock.elapsed()).toBe(2_000); // slept twice between three attempts
});
it("rejects with the last probe error once the timeout elapses", async () => {
const clock = fakeClock();
let attempts = 0;
await expect(
waitForService(
async () => {
attempts++;
throw new Error(`refused #${attempts}`);
},
{ timeoutMs: 3_000, intervalMs: 1_000, now: clock.now, sleep: clock.sleep },
),
).rejects.toThrow("refused #4"); // attempts fire at t=0,1000,2000,3000
expect(attempts).toBe(4);
});
it("reports each failed attempt through the onRetry callback", async () => {
const clock = fakeClock();
let attempts = 0;
const retried: number[] = [];
await waitForService(
async () => {
attempts++;
if (attempts < 3) throw new Error("not ready");
},
{
timeoutMs: 30_000,
intervalMs: 1_000,
now: clock.now,
sleep: clock.sleep,
onRetry: (attempt) => retried.push(attempt),
},
);
expect(retried).toEqual([1, 2]); // two failures before the third-attempt success
});
});