From 150c048b0a70f3a9ab9f92e7a50cf83498c0c19b Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 22 Feb 2026 00:44:28 +0100 Subject: [PATCH] refactor: unify discord listener slow-log flow and test helpers --- src/discord/monitor.test.ts | 66 ++++++++++++------------- src/discord/monitor/listeners.ts | 82 +++++++++++++++++++------------- 2 files changed, 84 insertions(+), 64 deletions(-) diff --git a/src/discord/monitor.test.ts b/src/discord/monitor.test.ts index eda94190e..423cbb74d 100644 --- a/src/discord/monitor.test.ts +++ b/src/discord/monitor.test.ts @@ -67,38 +67,51 @@ describe("registerDiscordListener", () => { }); describe("DiscordMessageListener", () => { + function createDeferred() { + let resolve: (() => void) | null = null; + const promise = new Promise((done) => { + resolve = done; + }); + return { + promise, + resolve: () => { + if (typeof resolve === "function") { + (resolve as () => void)(); + } + }, + }; + } + + async function expectPending(promise: Promise) { + let resolved = false; + void promise.then(() => { + resolved = true; + }); + await Promise.resolve(); + expect(resolved).toBe(false); + } + it("awaits the handler before returning", async () => { let handlerResolved = false; - let resolveHandler: (() => void) | null = null; - const handlerPromise = new Promise((resolve) => { - resolveHandler = () => { - handlerResolved = true; - resolve(); - }; + const deferred = createDeferred(); + const handler = vi.fn(async () => { + await deferred.promise; + handlerResolved = true; }); - const handler = vi.fn(() => handlerPromise); const listener = new DiscordMessageListener(handler); const handlePromise = listener.handle( {} as unknown as import("./monitor/listeners.js").DiscordMessageEvent, {} as unknown as import("@buape/carbon").Client, ); - let handleResolved = false; - void handlePromise.then(() => { - handleResolved = true; - }); // Handler should be called but not yet resolved expect(handler).toHaveBeenCalledOnce(); expect(handlerResolved).toBe(false); - await Promise.resolve(); - expect(handleResolved).toBe(false); + await expectPending(handlePromise); // Release the handler - const release = resolveHandler; - if (typeof release === "function") { - (release as () => void)(); - } + deferred.resolve(); // Now await handle() - it should complete only after handler resolves await handlePromise; @@ -129,11 +142,8 @@ describe("DiscordMessageListener", () => { vi.setSystemTime(0); try { - let resolveHandler: (() => void) | null = null; - const handlerPromise = new Promise((resolve) => { - resolveHandler = resolve; - }); - const handler = vi.fn(() => handlerPromise); + const deferred = createDeferred(); + const handler = vi.fn(() => deferred.promise); const logger = { warn: vi.fn(), error: vi.fn(), @@ -145,21 +155,13 @@ describe("DiscordMessageListener", () => { {} as unknown as import("./monitor/listeners.js").DiscordMessageEvent, {} as unknown as import("@buape/carbon").Client, ); - let handleResolved = false; - void handlePromise.then(() => { - handleResolved = true; - }); - await Promise.resolve(); - expect(handleResolved).toBe(false); + await expectPending(handlePromise); // Advance time past the slow listener threshold vi.setSystemTime(31_000); // Release the handler - const release = resolveHandler; - if (typeof release === "function") { - (release as () => void)(); - } + deferred.resolve(); // Now await handle() - it should complete and log the slow listener await handlePromise; diff --git a/src/discord/monitor/listeners.ts b/src/discord/monitor/listeners.ts index e9516f845..0267a26c1 100644 --- a/src/discord/monitor/listeners.ts +++ b/src/discord/monitor/listeners.ts @@ -68,6 +68,32 @@ function logSlowDiscordListener(params: { }); } +async function runDiscordListenerWithSlowLog(params: { + logger: Logger | undefined; + listener: string; + event: string; + run: () => Promise; + onError?: (err: unknown) => void; +}) { + const startedAt = Date.now(); + try { + await params.run(); + } catch (err) { + if (params.onError) { + params.onError(err); + return; + } + throw err; + } finally { + logSlowDiscordListener({ + logger: params.logger, + listener: params.listener, + event: params.event, + durationMs: Date.now() - startedAt, + }); + } +} + export function registerDiscordListener(listeners: Array, listener: object) { if (listeners.some((existing) => existing.constructor === listener.constructor)) { return false; @@ -85,20 +111,16 @@ export class DiscordMessageListener extends MessageCreateListener { } async handle(data: DiscordMessageEvent, client: Client) { - const startedAt = Date.now(); - await this.handler(data, client) - .catch((err) => { + await runDiscordListenerWithSlowLog({ + logger: this.logger, + listener: this.constructor.name, + event: this.type, + run: () => this.handler(data, client), + onError: (err) => { const logger = this.logger ?? discordEventQueueLog; logger.error(danger(`discord handler failed: ${String(err)}`)); - }) - .finally(() => { - logSlowDiscordListener({ - logger: this.logger, - listener: this.constructor.name, - event: this.type, - durationMs: Date.now() - startedAt, - }); - }); + }, + }); } } @@ -144,26 +166,22 @@ async function runDiscordReactionHandler(params: { listener: string; event: string; }): Promise { - const startedAt = Date.now(); - try { - await handleDiscordReactionEvent({ - data: params.data, - client: params.client, - action: params.action, - cfg: params.handlerParams.cfg, - accountId: params.handlerParams.accountId, - botUserId: params.handlerParams.botUserId, - guildEntries: params.handlerParams.guildEntries, - logger: params.handlerParams.logger, - }); - } finally { - logSlowDiscordListener({ - logger: params.handlerParams.logger, - listener: params.listener, - event: params.event, - durationMs: Date.now() - startedAt, - }); - } + await runDiscordListenerWithSlowLog({ + logger: params.handlerParams.logger, + listener: params.listener, + event: params.event, + run: () => + handleDiscordReactionEvent({ + data: params.data, + client: params.client, + action: params.action, + cfg: params.handlerParams.cfg, + accountId: params.handlerParams.accountId, + botUserId: params.handlerParams.botUserId, + guildEntries: params.handlerParams.guildEntries, + logger: params.handlerParams.logger, + }), + }); } async function handleDiscordReactionEvent(params: {