- Create withCommandErrorHandling utility in bot/lib/commandUtils.ts - Migrate economy commands: daily, exam, pay, trivia - Migrate inventory command: use - Migrate admin/moderation commands: warn, case, cases, clearwarning, warnings, note, notes, create_color, listing, webhook, refresh, terminal, featureflags, settings, prune - Add 9 unit tests for the utility - Update AGENTS.md with new recommended error handling pattern
148 lines
4.3 KiB
TypeScript
148 lines
4.3 KiB
TypeScript
import { describe, it, expect, mock, beforeEach, spyOn } from "bun:test";
|
|
import { UserError } from "@shared/lib/errors";
|
|
|
|
// --- Mocks ---
|
|
|
|
const mockDeferReply = mock(() => Promise.resolve());
|
|
const mockEditReply = mock(() => Promise.resolve());
|
|
|
|
const mockInteraction = {
|
|
deferReply: mockDeferReply,
|
|
editReply: mockEditReply,
|
|
} as any;
|
|
|
|
const mockCreateErrorEmbed = mock((msg: string) => ({ description: msg, type: "error" }));
|
|
|
|
mock.module("./embeds", () => ({
|
|
createErrorEmbed: mockCreateErrorEmbed,
|
|
}));
|
|
|
|
// Import AFTER mocking
|
|
const { withCommandErrorHandling } = await import("./commandUtils");
|
|
|
|
// --- Tests ---
|
|
|
|
describe("withCommandErrorHandling", () => {
|
|
let consoleErrorSpy: ReturnType<typeof spyOn>;
|
|
|
|
beforeEach(() => {
|
|
mockDeferReply.mockClear();
|
|
mockEditReply.mockClear();
|
|
mockCreateErrorEmbed.mockClear();
|
|
consoleErrorSpy = spyOn(console, "error").mockImplementation(() => { });
|
|
});
|
|
|
|
it("should always call deferReply", async () => {
|
|
await withCommandErrorHandling(
|
|
mockInteraction,
|
|
async () => "result"
|
|
);
|
|
|
|
expect(mockDeferReply).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it("should pass ephemeral option to deferReply", async () => {
|
|
await withCommandErrorHandling(
|
|
mockInteraction,
|
|
async () => "result",
|
|
{ ephemeral: true }
|
|
);
|
|
|
|
expect(mockDeferReply).toHaveBeenCalledWith({ ephemeral: true });
|
|
});
|
|
|
|
it("should return the operation result on success", async () => {
|
|
const result = await withCommandErrorHandling(
|
|
mockInteraction,
|
|
async () => ({ data: "test" })
|
|
);
|
|
|
|
expect(result).toEqual({ data: "test" });
|
|
});
|
|
|
|
it("should call onSuccess with the result", async () => {
|
|
const onSuccess = mock(async (_result: string) => { });
|
|
|
|
await withCommandErrorHandling(
|
|
mockInteraction,
|
|
async () => "hello",
|
|
{ onSuccess }
|
|
);
|
|
|
|
expect(onSuccess).toHaveBeenCalledWith("hello");
|
|
});
|
|
|
|
it("should send successMessage when no onSuccess is provided", async () => {
|
|
await withCommandErrorHandling(
|
|
mockInteraction,
|
|
async () => "result",
|
|
{ successMessage: "It worked!" }
|
|
);
|
|
|
|
expect(mockEditReply).toHaveBeenCalledWith({
|
|
content: "It worked!",
|
|
});
|
|
});
|
|
|
|
it("should prefer onSuccess over successMessage", async () => {
|
|
const onSuccess = mock(async (_result: string) => { });
|
|
|
|
await withCommandErrorHandling(
|
|
mockInteraction,
|
|
async () => "result",
|
|
{ successMessage: "This should not be sent", onSuccess }
|
|
);
|
|
|
|
expect(onSuccess).toHaveBeenCalledTimes(1);
|
|
// editReply should NOT have been called with the successMessage
|
|
expect(mockEditReply).not.toHaveBeenCalledWith({
|
|
content: "This should not be sent",
|
|
});
|
|
});
|
|
|
|
it("should show error embed for UserError", async () => {
|
|
const result = await withCommandErrorHandling(
|
|
mockInteraction,
|
|
async () => {
|
|
throw new UserError("You can't do that!");
|
|
}
|
|
);
|
|
|
|
expect(result).toBeUndefined();
|
|
expect(mockCreateErrorEmbed).toHaveBeenCalledWith("You can't do that!");
|
|
expect(mockEditReply).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it("should show generic error and log for unexpected errors", async () => {
|
|
const unexpectedError = new Error("Database exploded");
|
|
|
|
const result = await withCommandErrorHandling(
|
|
mockInteraction,
|
|
async () => {
|
|
throw unexpectedError;
|
|
}
|
|
);
|
|
|
|
expect(result).toBeUndefined();
|
|
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
|
"Unexpected error in command:",
|
|
unexpectedError
|
|
);
|
|
expect(mockCreateErrorEmbed).toHaveBeenCalledWith(
|
|
"An unexpected error occurred."
|
|
);
|
|
expect(mockEditReply).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it("should return undefined on error", async () => {
|
|
const result = await withCommandErrorHandling(
|
|
mockInteraction,
|
|
async () => {
|
|
throw new Error("fail");
|
|
}
|
|
);
|
|
|
|
expect(result).toBeUndefined();
|
|
});
|
|
});
|