refactor: unify discord listener slow-log flow and test helpers

This commit is contained in:
Peter Steinberger
2026-02-22 00:44:28 +01:00
parent f589295a0a
commit 150c048b0a
2 changed files with 84 additions and 64 deletions

View File

@@ -67,38 +67,51 @@ describe("registerDiscordListener", () => {
}); });
describe("DiscordMessageListener", () => { describe("DiscordMessageListener", () => {
function createDeferred() {
let resolve: (() => void) | null = null;
const promise = new Promise<void>((done) => {
resolve = done;
});
return {
promise,
resolve: () => {
if (typeof resolve === "function") {
(resolve as () => void)();
}
},
};
}
async function expectPending(promise: Promise<unknown>) {
let resolved = false;
void promise.then(() => {
resolved = true;
});
await Promise.resolve();
expect(resolved).toBe(false);
}
it("awaits the handler before returning", async () => { it("awaits the handler before returning", async () => {
let handlerResolved = false; let handlerResolved = false;
let resolveHandler: (() => void) | null = null; const deferred = createDeferred();
const handlerPromise = new Promise<void>((resolve) => { const handler = vi.fn(async () => {
resolveHandler = () => { await deferred.promise;
handlerResolved = true; handlerResolved = true;
resolve();
};
}); });
const handler = vi.fn(() => handlerPromise);
const listener = new DiscordMessageListener(handler); const listener = new DiscordMessageListener(handler);
const handlePromise = listener.handle( const handlePromise = listener.handle(
{} as unknown as import("./monitor/listeners.js").DiscordMessageEvent, {} as unknown as import("./monitor/listeners.js").DiscordMessageEvent,
{} as unknown as import("@buape/carbon").Client, {} as unknown as import("@buape/carbon").Client,
); );
let handleResolved = false;
void handlePromise.then(() => {
handleResolved = true;
});
// Handler should be called but not yet resolved // Handler should be called but not yet resolved
expect(handler).toHaveBeenCalledOnce(); expect(handler).toHaveBeenCalledOnce();
expect(handlerResolved).toBe(false); expect(handlerResolved).toBe(false);
await Promise.resolve(); await expectPending(handlePromise);
expect(handleResolved).toBe(false);
// Release the handler // Release the handler
const release = resolveHandler; deferred.resolve();
if (typeof release === "function") {
(release as () => void)();
}
// Now await handle() - it should complete only after handler resolves // Now await handle() - it should complete only after handler resolves
await handlePromise; await handlePromise;
@@ -129,11 +142,8 @@ describe("DiscordMessageListener", () => {
vi.setSystemTime(0); vi.setSystemTime(0);
try { try {
let resolveHandler: (() => void) | null = null; const deferred = createDeferred();
const handlerPromise = new Promise<void>((resolve) => { const handler = vi.fn(() => deferred.promise);
resolveHandler = resolve;
});
const handler = vi.fn(() => handlerPromise);
const logger = { const logger = {
warn: vi.fn(), warn: vi.fn(),
error: vi.fn(), error: vi.fn(),
@@ -145,21 +155,13 @@ describe("DiscordMessageListener", () => {
{} as unknown as import("./monitor/listeners.js").DiscordMessageEvent, {} as unknown as import("./monitor/listeners.js").DiscordMessageEvent,
{} as unknown as import("@buape/carbon").Client, {} as unknown as import("@buape/carbon").Client,
); );
let handleResolved = false; await expectPending(handlePromise);
void handlePromise.then(() => {
handleResolved = true;
});
await Promise.resolve();
expect(handleResolved).toBe(false);
// Advance time past the slow listener threshold // Advance time past the slow listener threshold
vi.setSystemTime(31_000); vi.setSystemTime(31_000);
// Release the handler // Release the handler
const release = resolveHandler; deferred.resolve();
if (typeof release === "function") {
(release as () => void)();
}
// Now await handle() - it should complete and log the slow listener // Now await handle() - it should complete and log the slow listener
await handlePromise; await handlePromise;

View File

@@ -68,6 +68,32 @@ function logSlowDiscordListener(params: {
}); });
} }
async function runDiscordListenerWithSlowLog(params: {
logger: Logger | undefined;
listener: string;
event: string;
run: () => Promise<void>;
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<object>, listener: object) { export function registerDiscordListener(listeners: Array<object>, listener: object) {
if (listeners.some((existing) => existing.constructor === listener.constructor)) { if (listeners.some((existing) => existing.constructor === listener.constructor)) {
return false; return false;
@@ -85,19 +111,15 @@ export class DiscordMessageListener extends MessageCreateListener {
} }
async handle(data: DiscordMessageEvent, client: Client) { async handle(data: DiscordMessageEvent, client: Client) {
const startedAt = Date.now(); await runDiscordListenerWithSlowLog({
await this.handler(data, client)
.catch((err) => {
const logger = this.logger ?? discordEventQueueLog;
logger.error(danger(`discord handler failed: ${String(err)}`));
})
.finally(() => {
logSlowDiscordListener({
logger: this.logger, logger: this.logger,
listener: this.constructor.name, listener: this.constructor.name,
event: this.type, event: this.type,
durationMs: Date.now() - startedAt, run: () => this.handler(data, client),
}); onError: (err) => {
const logger = this.logger ?? discordEventQueueLog;
logger.error(danger(`discord handler failed: ${String(err)}`));
},
}); });
} }
} }
@@ -144,9 +166,12 @@ async function runDiscordReactionHandler(params: {
listener: string; listener: string;
event: string; event: string;
}): Promise<void> { }): Promise<void> {
const startedAt = Date.now(); await runDiscordListenerWithSlowLog({
try { logger: params.handlerParams.logger,
await handleDiscordReactionEvent({ listener: params.listener,
event: params.event,
run: () =>
handleDiscordReactionEvent({
data: params.data, data: params.data,
client: params.client, client: params.client,
action: params.action, action: params.action,
@@ -155,15 +180,8 @@ async function runDiscordReactionHandler(params: {
botUserId: params.handlerParams.botUserId, botUserId: params.handlerParams.botUserId,
guildEntries: params.handlerParams.guildEntries, guildEntries: params.handlerParams.guildEntries,
logger: params.handlerParams.logger, logger: params.handlerParams.logger,
}),
}); });
} finally {
logSlowDiscordListener({
logger: params.handlerParams.logger,
listener: params.listener,
event: params.event,
durationMs: Date.now() - startedAt,
});
}
} }
async function handleDiscordReactionEvent(params: { async function handleDiscordReactionEvent(params: {