test: clear discord lifecycle broad matchers

This commit is contained in:
Peter Steinberger
2026-05-10 16:12:58 +01:00
parent 93b5fcb48b
commit 69982d4e73
@@ -184,6 +184,35 @@ describe("runDiscordGatewayLifecycle", () => {
expect(params.gatewaySupervisor.detachLifecycle).toHaveBeenCalledTimes(params.detachCalls ?? 1);
}
function mockMessages(mock: ReturnType<typeof vi.fn>): string[] {
return mock.mock.calls.map((call) => String(call[0] ?? ""));
}
function expectMockMessageContains(mock: ReturnType<typeof vi.fn>, expected: string): void {
expect(mockMessages(mock).some((message) => message.includes(expected))).toBe(true);
}
function expectMockMessageNotContains(mock: ReturnType<typeof vi.fn>, expected: string): void {
expect(mockMessages(mock).every((message) => !message.includes(expected))).toBe(true);
}
type StatusPatch = {
connected?: boolean;
lastDisconnect?: null | Record<string, unknown>;
lastError?: string | null;
};
function statusPatches(statusSink: ReturnType<typeof vi.fn>): StatusPatch[] {
return statusSink.mock.calls.map((call) => call[0] as StatusPatch);
}
function expectStatusPatch(
statusSink: ReturnType<typeof vi.fn>,
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();