From 69982d4e73143513d75b600f13be4fff8ef5be32 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 10 May 2026 16:12:58 +0100 Subject: [PATCH] test: clear discord lifecycle broad matchers --- .../src/monitor/provider.lifecycle.test.ts | 136 ++++++++++-------- 1 file changed, 77 insertions(+), 59 deletions(-) diff --git a/extensions/discord/src/monitor/provider.lifecycle.test.ts b/extensions/discord/src/monitor/provider.lifecycle.test.ts index 0eeaf5d6d88f..77a3cbf69a11 100644 --- a/extensions/discord/src/monitor/provider.lifecycle.test.ts +++ b/extensions/discord/src/monitor/provider.lifecycle.test.ts @@ -184,6 +184,35 @@ describe("runDiscordGatewayLifecycle", () => { expect(params.gatewaySupervisor.detachLifecycle).toHaveBeenCalledTimes(params.detachCalls ?? 1); } + function mockMessages(mock: ReturnType): string[] { + return mock.mock.calls.map((call) => String(call[0] ?? "")); + } + + function expectMockMessageContains(mock: ReturnType, expected: string): void { + expect(mockMessages(mock).some((message) => message.includes(expected))).toBe(true); + } + + function expectMockMessageNotContains(mock: ReturnType, expected: string): void { + expect(mockMessages(mock).every((message) => !message.includes(expected))).toBe(true); + } + + type StatusPatch = { + connected?: boolean; + lastDisconnect?: null | Record; + lastError?: string | null; + }; + + function statusPatches(statusSink: ReturnType): StatusPatch[] { + return statusSink.mock.calls.map((call) => call[0] as StatusPatch); + } + + function expectStatusPatch( + statusSink: ReturnType, + predicate: (patch: StatusPatch) => boolean, + ): void { + expect(statusPatches(statusSink).some(predicate)).toBe(true); + } + it("resolves gateway READY timeouts from config, env, then defaults", () => { expect(resolveDiscordGatewayReadyTimeoutMs({ configuredTimeoutMs: 45_000 })).toBe(45_000); expect( @@ -240,11 +269,9 @@ describe("runDiscordGatewayLifecycle", () => { const { lifecycleParams, statusSink } = createLifecycleHarness({ gateway }); await expect(runDiscordGatewayLifecycle(lifecycleParams)).resolves.toBeUndefined(); - expect(statusSink).toHaveBeenCalledWith( - expect.objectContaining({ - connected: true, - lastDisconnect: null, - }), + expectStatusPatch( + statusSink, + (patch) => patch.connected === true && patch.lastDisconnect === null, ); }); @@ -266,11 +293,7 @@ describe("runDiscordGatewayLifecycle", () => { await expect(lifecyclePromise).rejects.toThrow( "discord gateway did not reach READY within 5000ms", ); - expect(statusSink).not.toHaveBeenCalledWith( - expect.objectContaining({ - connected: true, - }), - ); + expect(statusPatches(statusSink).every((patch) => patch.connected !== true)).toBe(true); expectLifecycleCleanup({ threadStop, waitCalls: 0, @@ -352,21 +375,18 @@ describe("runDiscordGatewayLifecycle", () => { await vi.advanceTimersByTimeAsync(18_500); await expect(lifecyclePromise).resolves.toBeUndefined(); - expect(runtimeError).toHaveBeenCalledWith( - expect.stringContaining("gateway READY wait timed out after 15000ms"), - ); - expect(runtimeError).not.toHaveBeenCalledWith( - expect.stringContaining("gateway was not ready after 15000ms; restarting gateway"), + expectMockMessageContains(runtimeError, "gateway READY wait timed out after 15000ms"); + expectMockMessageNotContains( + runtimeError, + "gateway was not ready after 15000ms; restarting gateway", ); expect(gateway.disconnect).toHaveBeenCalledTimes(1); expect(gateway.connect).toHaveBeenCalledTimes(1); expect(gateway.connect).toHaveBeenCalledWith(false); - expect(statusSink).toHaveBeenCalledWith( - expect.objectContaining({ - connected: true, - lastDisconnect: null, - lastError: null, - }), + expectStatusPatch( + statusSink, + (patch) => + patch.connected === true && patch.lastDisconnect === null && patch.lastError === null, ); } finally { vi.useRealTimers(); @@ -447,9 +467,7 @@ describe("runDiscordGatewayLifecycle", () => { await expect(runDiscordGatewayLifecycle(lifecycleParams)).resolves.toBeUndefined(); - expect(runtimeError).toHaveBeenCalledWith( - expect.stringContaining("discord: gateway closed with code 4014"), - ); + expectMockMessageContains(runtimeError, "discord: gateway closed with code 4014"); expectLifecycleCleanup({ threadStop, waitCalls: 0, @@ -466,8 +484,9 @@ describe("runDiscordGatewayLifecycle", () => { await expect(runDiscordGatewayLifecycle(lifecycleParams)).resolves.toBeUndefined(); - expect(runtimeError).toHaveBeenCalledWith( - expect.stringContaining("discord gateway error: Error: transient startup error"), + expectMockMessageContains( + runtimeError, + "discord gateway error: Error: transient startup error", ); expectLifecycleCleanup({ threadStop, @@ -550,15 +569,15 @@ describe("runDiscordGatewayLifecycle", () => { await expect(runDiscordGatewayLifecycle(lifecycleParams)).resolves.toBeUndefined(); expect(gatewaySupervisor.attachLifecycle).toHaveBeenCalledTimes(1); - expect(runtimeLog).toHaveBeenCalledWith( - expect.stringContaining("treating reconnect-exhausted during expected shutdown as clean"), + expectMockMessageContains( + runtimeLog, + "treating reconnect-exhausted during expected shutdown as clean", ); - expect(runtimeLog).toHaveBeenCalledWith( - expect.stringContaining("Max reconnect attempts (50) reached after close code 1005"), - ); - expect(runtimeError).not.toHaveBeenCalledWith( - expect.stringContaining("discord gateway reconnect-exhausted"), + expectMockMessageContains( + runtimeLog, + "Max reconnect attempts (50) reached after close code 1005", ); + expectMockMessageNotContains(runtimeError, "discord gateway reconnect-exhausted"); expectLifecycleCleanup({ threadStop, waitCalls: 1, @@ -590,8 +609,9 @@ describe("runDiscordGatewayLifecycle", () => { await expect(lifecyclePromise).rejects.toThrow( "discord gateway fatal: Error: Fatal Gateway error: 4001", ); - expect(runtimeError).toHaveBeenCalledWith( - expect.stringContaining("discord gateway fatal: Error: Fatal Gateway error: 4001"), + expectMockMessageContains( + runtimeError, + "discord gateway fatal: Error: Fatal Gateway error: 4001", ); expect(gateway.disconnect).not.toHaveBeenCalled(); expect(gateway.connect).not.toHaveBeenCalled(); @@ -617,11 +637,12 @@ describe("runDiscordGatewayLifecycle", () => { await expect(runDiscordGatewayLifecycle(lifecycleParams)).resolves.toBeUndefined(); - expect(statusSink).toHaveBeenCalledWith( - expect.objectContaining({ - connected: false, - lastDisconnect: expect.objectContaining({ status: 1006 }), - }), + expectStatusPatch( + statusSink, + (patch) => + patch.connected === false && + patch.lastDisconnect !== null && + patch.lastDisconnect?.status === 1006, ); }); @@ -637,11 +658,11 @@ describe("runDiscordGatewayLifecycle", () => { await expect(runDiscordGatewayLifecycle(lifecycleParams)).resolves.toBeUndefined(); - expect(statusSink).toHaveBeenCalledWith( - expect.objectContaining({ - connected: false, - lastError: "Gateway reconnect scheduled in 1000ms (zombie, resume=true)", - }), + expectStatusPatch( + statusSink, + (patch) => + patch.connected === false && + patch.lastError === "Gateway reconnect scheduled in 1000ms (zombie, resume=true)", ); }); @@ -664,12 +685,10 @@ describe("runDiscordGatewayLifecycle", () => { await expect(runDiscordGatewayLifecycle(lifecycleParams)).resolves.toBeUndefined(); - expect(statusSink).toHaveBeenCalledWith(expect.objectContaining({ connected: false })); - expect(statusSink).toHaveBeenCalledWith( - expect.objectContaining({ - connected: true, - lastDisconnect: null, - }), + expectStatusPatch(statusSink, (patch) => patch.connected === false); + expectStatusPatch( + statusSink, + (patch) => patch.connected === true && patch.lastDisconnect === null, ); } finally { vi.useRealTimers(); @@ -700,14 +719,13 @@ describe("runDiscordGatewayLifecycle", () => { await expect(lifecyclePromise).rejects.toThrow( "discord gateway opened but did not reach READY within 5000ms", ); - expect(runtimeError).toHaveBeenCalledWith( - expect.stringContaining("did not reach READY within 5000ms"), - ); - expect(statusSink).toHaveBeenCalledWith( - expect.objectContaining({ - connected: false, - lastDisconnect: expect.objectContaining({ error: "runtime-not-ready" }), - }), + expectMockMessageContains(runtimeError, "did not reach READY within 5000ms"); + expectStatusPatch( + statusSink, + (patch) => + patch.connected === false && + patch.lastDisconnect !== null && + patch.lastDisconnect?.error === "runtime-not-ready", ); } finally { vi.useRealTimers();