|
| 1 | +import { ItemService } from "@src/items/services"; |
| 2 | +import { Ok, Err } from "ts-results"; |
| 3 | +import { execute, handleModalSubmit } from "@infrastructure/discord/commands/createIssue"; |
| 4 | +import { promptAssigneeSelection } from "@infrastructure/discord/interactions"; |
| 5 | +import { can } from "@infrastructure/discord/authz"; |
| 6 | +import { |
| 7 | + CommandInteraction, |
| 8 | + ModalSubmitInteraction, |
| 9 | +} from "discord.js"; |
| 10 | + |
| 11 | +jest.mock("@src/items/services", () => ({ |
| 12 | + ItemService: { |
| 13 | + create: jest.fn(), |
| 14 | + }, |
| 15 | +})); |
| 16 | + |
| 17 | +jest.mock("@infrastructure/discord/interactions", () => ({ |
| 18 | + promptAssigneeSelection: jest.fn(), |
| 19 | +})); |
| 20 | + |
| 21 | +jest.mock("@infrastructure/discord/authz", () => ({ |
| 22 | + can: jest.fn(), |
| 23 | +})); |
| 24 | + |
| 25 | +const mockShowModal = jest.fn(); |
| 26 | +const mockReply = jest.fn(); |
| 27 | + |
| 28 | +describe("create-issue slash command", () => { |
| 29 | + beforeEach(() => { |
| 30 | + jest.clearAllMocks(); |
| 31 | + }); |
| 32 | + |
| 33 | + describe("execute", () => { |
| 34 | + it("will block unauthorized users", async () => { |
| 35 | + (can as jest.Mock).mockReturnValue(false); |
| 36 | + |
| 37 | + const interaction = { |
| 38 | + user: { id: "unauthorized" }, |
| 39 | + reply: mockReply, |
| 40 | + } as unknown as CommandInteraction; |
| 41 | + |
| 42 | + await execute(interaction); |
| 43 | + |
| 44 | + expect(mockReply).toHaveBeenCalledWith({ |
| 45 | + content: "You do not have permission to create an issue.", |
| 46 | + ephemeral: true, |
| 47 | + }); |
| 48 | + }); |
| 49 | + |
| 50 | + it("will show modal for authorized users", async () => { |
| 51 | + (can as jest.Mock).mockReturnValue(true); |
| 52 | + |
| 53 | + const interaction = { |
| 54 | + user: { id: "authorized" }, |
| 55 | + showModal: mockShowModal, |
| 56 | + reply: mockReply, |
| 57 | + } as unknown as CommandInteraction; |
| 58 | + |
| 59 | + await execute(interaction); |
| 60 | + |
| 61 | + expect(mockShowModal).toHaveBeenCalled(); |
| 62 | + }); |
| 63 | + }); |
| 64 | + |
| 65 | + describe("handleModalSubmit", () => { |
| 66 | + const interaction = { |
| 67 | + fields: { |
| 68 | + getTextInputValue: jest.fn(), |
| 69 | + }, |
| 70 | + reply: mockReply, |
| 71 | + } as unknown as ModalSubmitInteraction; |
| 72 | + |
| 73 | + it("will reject invalid due date format", async () => { |
| 74 | + interaction.fields.getTextInputValue = jest.fn((key) => |
| 75 | + key === "dueDate" ? "bad-date" : "test value", |
| 76 | + ); |
| 77 | + |
| 78 | + await expect(() => |
| 79 | + handleModalSubmit(interaction), |
| 80 | + ).rejects.toThrow("Invalid due date format. Please use yyyy-mm-dd."); |
| 81 | + }); |
| 82 | + |
| 83 | + it("will handle error from ItemService.create", async () => { |
| 84 | + interaction.fields.getTextInputValue = jest.fn((key) => { |
| 85 | + if (key === "dueDate") return "2025-05-17"; |
| 86 | + return "test value"; |
| 87 | + }); |
| 88 | + |
| 89 | + (ItemService.create as jest.Mock).mockResolvedValue( |
| 90 | + Err(new Error("creation failed")), |
| 91 | + ); |
| 92 | + |
| 93 | + await handleModalSubmit(interaction); |
| 94 | + |
| 95 | + expect(ItemService.create).toHaveBeenCalledWith({ |
| 96 | + title: "test value", |
| 97 | + description: "test value", |
| 98 | + dueDate: new Date("2025-05-17"), |
| 99 | + }); |
| 100 | + |
| 101 | + expect(mockReply).toHaveBeenCalledWith({ |
| 102 | + content: "Failed to create issue. Please try again.", |
| 103 | + ephemeral: true, |
| 104 | + }); |
| 105 | + }); |
| 106 | + |
| 107 | + it("will call promptAssigneeSelection on success", async () => { |
| 108 | + interaction.fields.getTextInputValue = jest.fn((key) => { |
| 109 | + if (key === "dueDate") return "2025-05-17"; |
| 110 | + return "test value"; |
| 111 | + }); |
| 112 | + |
| 113 | + (ItemService.create as jest.Mock).mockResolvedValue( |
| 114 | + Ok({ githubIssueId: "abc-123" }), |
| 115 | + ); |
| 116 | + |
| 117 | + await handleModalSubmit(interaction); |
| 118 | + |
| 119 | + expect(promptAssigneeSelection).toHaveBeenCalledWith( |
| 120 | + interaction, |
| 121 | + "abc-123", |
| 122 | + ); |
| 123 | + }); |
| 124 | + }); |
| 125 | +}); |
0 commit comments