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; 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(); }); });