diff --git a/src/gateway/server/readiness.test.ts b/src/gateway/server/readiness.test.ts index 2ad29d3655a..b333277f158 100644 --- a/src/gateway/server/readiness.test.ts +++ b/src/gateway/server/readiness.test.ts @@ -46,172 +46,188 @@ function createHealthyDiscordManager(startedAt: number, lastEventAt: number): Ch ); } +function withReadinessClock(run: () => void) { + vi.useFakeTimers(); + vi.setSystemTime(new Date("2026-03-06T12:00:00Z")); + try { + run(); + } finally { + vi.useRealTimers(); + } +} + +function createReadinessHarness(params: { + startedAgoMs: number; + accounts: Record>; + cacheTtlMs?: number; +}) { + const startedAt = Date.now() - params.startedAgoMs; + const manager = createManager(snapshotWith(params.accounts)); + return { + manager, + readiness: createReadinessChecker({ + channelManager: manager, + startedAt, + cacheTtlMs: params.cacheTtlMs, + }), + }; +} + describe("createReadinessChecker", () => { it("reports ready when all managed channels are healthy", () => { - vi.useFakeTimers(); - vi.setSystemTime(new Date("2026-03-06T12:00:00Z")); - const startedAt = Date.now() - 5 * 60_000; - const manager = createHealthyDiscordManager(startedAt, Date.now() - 1_000); + withReadinessClock(() => { + const startedAt = Date.now() - 5 * 60_000; + const manager = createHealthyDiscordManager(startedAt, Date.now() - 1_000); - const readiness = createReadinessChecker({ channelManager: manager, startedAt }); - expect(readiness()).toEqual({ ready: true, failing: [], uptimeMs: 300_000 }); - vi.useRealTimers(); + const readiness = createReadinessChecker({ channelManager: manager, startedAt }); + expect(readiness()).toEqual({ ready: true, failing: [], uptimeMs: 300_000 }); + }); }); it("ignores disabled and unconfigured channels", () => { - vi.useFakeTimers(); - vi.setSystemTime(new Date("2026-03-06T12:00:00Z")); - const startedAt = Date.now() - 5 * 60_000; - const manager = createManager( - snapshotWith({ - discord: { - running: false, - enabled: false, - configured: true, - lastStartAt: startedAt, + withReadinessClock(() => { + const { readiness } = createReadinessHarness({ + startedAgoMs: 5 * 60_000, + accounts: { + discord: { + running: false, + enabled: false, + configured: true, + lastStartAt: Date.now() - 5 * 60_000, + }, + telegram: { + running: false, + enabled: true, + configured: false, + lastStartAt: Date.now() - 5 * 60_000, + }, }, - telegram: { - running: false, - enabled: true, - configured: false, - lastStartAt: startedAt, - }, - }), - ); - - const readiness = createReadinessChecker({ channelManager: manager, startedAt }); - expect(readiness()).toEqual({ ready: true, failing: [], uptimeMs: 300_000 }); - vi.useRealTimers(); + }); + expect(readiness()).toEqual({ ready: true, failing: [], uptimeMs: 300_000 }); + }); }); it("uses startup grace before marking disconnected channels not ready", () => { - vi.useFakeTimers(); - vi.setSystemTime(new Date("2026-03-06T12:00:00Z")); - const startedAt = Date.now() - 30_000; - const manager = createManager( - snapshotWith({ - discord: { - running: true, - connected: false, - enabled: true, - configured: true, - lastStartAt: startedAt, + withReadinessClock(() => { + const { readiness } = createReadinessHarness({ + startedAgoMs: 30_000, + accounts: { + discord: { + running: true, + connected: false, + enabled: true, + configured: true, + lastStartAt: Date.now() - 30_000, + }, }, - }), - ); - - const readiness = createReadinessChecker({ channelManager: manager, startedAt }); - expect(readiness()).toEqual({ ready: true, failing: [], uptimeMs: 30_000 }); - vi.useRealTimers(); + }); + expect(readiness()).toEqual({ ready: true, failing: [], uptimeMs: 30_000 }); + }); }); it("reports disconnected managed channels after startup grace", () => { - vi.useFakeTimers(); - vi.setSystemTime(new Date("2026-03-06T12:00:00Z")); - const startedAt = Date.now() - 5 * 60_000; - const manager = createManager( - snapshotWith({ - discord: { - running: true, - connected: false, - enabled: true, - configured: true, - lastStartAt: startedAt, + withReadinessClock(() => { + const { readiness } = createReadinessHarness({ + startedAgoMs: 5 * 60_000, + accounts: { + discord: { + running: true, + connected: false, + enabled: true, + configured: true, + lastStartAt: Date.now() - 5 * 60_000, + }, }, - }), - ); - - const readiness = createReadinessChecker({ channelManager: manager, startedAt }); - expect(readiness()).toEqual({ ready: false, failing: ["discord"], uptimeMs: 300_000 }); - vi.useRealTimers(); + }); + expect(readiness()).toEqual({ ready: false, failing: ["discord"], uptimeMs: 300_000 }); + }); }); it("keeps restart-pending channels ready during reconnect backoff", () => { - vi.useFakeTimers(); - vi.setSystemTime(new Date("2026-03-06T12:00:00Z")); - const startedAt = Date.now() - 5 * 60_000; - const manager = createManager( - snapshotWith({ - discord: { - running: false, - restartPending: true, - reconnectAttempts: 3, - enabled: true, - configured: true, - lastStartAt: startedAt - 30_000, - lastStopAt: Date.now() - 5_000, + withReadinessClock(() => { + const startedAt = Date.now() - 5 * 60_000; + const { readiness } = createReadinessHarness({ + startedAgoMs: 5 * 60_000, + accounts: { + discord: { + running: false, + restartPending: true, + reconnectAttempts: 3, + enabled: true, + configured: true, + lastStartAt: startedAt - 30_000, + lastStopAt: Date.now() - 5_000, + }, }, - }), - ); - - const readiness = createReadinessChecker({ channelManager: manager, startedAt }); - expect(readiness()).toEqual({ ready: true, failing: [], uptimeMs: 300_000 }); - vi.useRealTimers(); + }); + expect(readiness()).toEqual({ ready: true, failing: [], uptimeMs: 300_000 }); + }); }); it("treats stale-socket channels as ready to avoid pulling healthy idle pods", () => { - vi.useFakeTimers(); - vi.setSystemTime(new Date("2026-03-06T12:00:00Z")); - const startedAt = Date.now() - 31 * 60_000; - const manager = createManager( - snapshotWith({ - discord: { - running: true, - connected: true, - enabled: true, - configured: true, - lastStartAt: startedAt, - lastEventAt: Date.now() - 31 * 60_000, + withReadinessClock(() => { + const startedAt = Date.now() - 31 * 60_000; + const { readiness } = createReadinessHarness({ + startedAgoMs: 31 * 60_000, + accounts: { + discord: { + running: true, + connected: true, + enabled: true, + configured: true, + lastStartAt: startedAt, + lastEventAt: Date.now() - 31 * 60_000, + }, }, - }), - ); - - const readiness = createReadinessChecker({ channelManager: manager, startedAt }); - expect(readiness()).toEqual({ ready: true, failing: [], uptimeMs: 1_860_000 }); - vi.useRealTimers(); + }); + expect(readiness()).toEqual({ ready: true, failing: [], uptimeMs: 1_860_000 }); + }); }); it("keeps telegram long-polling channels ready without stale-socket classification", () => { - vi.useFakeTimers(); - vi.setSystemTime(new Date("2026-03-06T12:00:00Z")); - const startedAt = Date.now() - 31 * 60_000; - const manager = createManager( - snapshotWith({ - telegram: { - running: true, - connected: true, - enabled: true, - configured: true, - lastStartAt: startedAt, - lastEventAt: null, + withReadinessClock(() => { + const startedAt = Date.now() - 31 * 60_000; + const { readiness } = createReadinessHarness({ + startedAgoMs: 31 * 60_000, + accounts: { + telegram: { + running: true, + connected: true, + enabled: true, + configured: true, + lastStartAt: startedAt, + lastEventAt: null, + }, }, - }), - ); - - const readiness = createReadinessChecker({ channelManager: manager, startedAt }); - expect(readiness()).toEqual({ ready: true, failing: [], uptimeMs: 1_860_000 }); - vi.useRealTimers(); + }); + expect(readiness()).toEqual({ ready: true, failing: [], uptimeMs: 1_860_000 }); + }); }); it("caches readiness snapshots briefly to keep repeated probes cheap", () => { - vi.useFakeTimers(); - vi.setSystemTime(new Date("2026-03-06T12:00:00Z")); - const startedAt = Date.now() - 5 * 60_000; - const manager = createHealthyDiscordManager(startedAt, Date.now() - 1_000); + withReadinessClock(() => { + const { manager, readiness } = createReadinessHarness({ + startedAgoMs: 5 * 60_000, + accounts: { + discord: { + running: true, + connected: true, + enabled: true, + configured: true, + lastStartAt: Date.now() - 5 * 60_000, + lastEventAt: Date.now() - 1_000, + }, + }, + cacheTtlMs: 1_000, + }); + expect(readiness()).toEqual({ ready: true, failing: [], uptimeMs: 300_000 }); + vi.advanceTimersByTime(500); + expect(readiness()).toEqual({ ready: true, failing: [], uptimeMs: 300_500 }); + expect(manager.getRuntimeSnapshot).toHaveBeenCalledTimes(1); - const readiness = createReadinessChecker({ - channelManager: manager, - startedAt, - cacheTtlMs: 1_000, + vi.advanceTimersByTime(600); + expect(readiness()).toEqual({ ready: true, failing: [], uptimeMs: 301_100 }); + expect(manager.getRuntimeSnapshot).toHaveBeenCalledTimes(2); }); - expect(readiness()).toEqual({ ready: true, failing: [], uptimeMs: 300_000 }); - vi.advanceTimersByTime(500); - expect(readiness()).toEqual({ ready: true, failing: [], uptimeMs: 300_500 }); - expect(manager.getRuntimeSnapshot).toHaveBeenCalledTimes(1); - - vi.advanceTimersByTime(600); - expect(readiness()).toEqual({ ready: true, failing: [], uptimeMs: 301_100 }); - expect(manager.getRuntimeSnapshot).toHaveBeenCalledTimes(2); - vi.useRealTimers(); }); });