mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
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.
80 lines
2.3 KiB
TypeScript
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
|
|
});
|
|
});
|