Skip to content

Commit d80d26c

Browse files
committed
Add create issue spec
1 parent 60c2ead commit d80d26c

File tree

1 file changed

+125
-0
lines changed

1 file changed

+125
-0
lines changed
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
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

Comments
 (0)