generated from nhcarrigan/template
feat: first version of bot (#2)
### Explanation This should set up everything we need for our initial launch. Test coverage is at 100% to ensure nothing breaks. ### Issue _No response_ ### Attestations - [x] I have read and agree to the [Code of Conduct](https://docs.nhcarrigan.com/community/coc/) - [x] I have read and agree to the [Community Guidelines](https://docs.nhcarrigan.com/community/guide/). - [x] My contribution complies with the [Contributor Covenant](https://docs.nhcarrigan.com/dev/covenant/). ### Dependencies - [x] I have pinned the dependencies to a specific patch version. ### Style - [x] I have run the linter and resolved any errors. - [x] My pull request uses an appropriate title, matching the conventional commit standards. - [x] My scope of feat/fix/chore/etc. correctly matches the nature of changes in my pull request. ### Tests - [x] My contribution adds new code, and I have added tests to cover it. - [ ] My contribution modifies existing code, and I have updated the tests to reflect these changes. - [x] All new and existing tests pass locally with my changes. - [x] Code coverage remains at or above the configured threshold. ### Documentation Coming soon - I'm working on the infra for docs next ### Versioning Major - My pull request introduces a breaking change. Reviewed-on: https://codeberg.org/nhcarrigan/rig-task-bot/pulls/2 Co-authored-by: Naomi Carrigan <commits@nhcarrigan.com> Co-committed-by: Naomi Carrigan <commits@nhcarrigan.com>
This commit is contained in:
@@ -0,0 +1,251 @@
|
||||
/**
|
||||
* @copyright nhcarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
|
||||
import {
|
||||
ApplicationCommandOptionType,
|
||||
InteractionContextType,
|
||||
} from "discord.js";
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
import { assign } from "../../src/commands/assign.js";
|
||||
import { errorHandler } from "../../src/utils/errorHandler.js";
|
||||
|
||||
vi.mock("../../src/utils/errorHandler.ts", () => {
|
||||
return {
|
||||
errorHandler: vi.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
describe("assign command", () => {
|
||||
it("should have the correct data", () => {
|
||||
expect.assertions(16);
|
||||
expect(assign.data.name, "did not have the correct name").toBe("assign");
|
||||
expect(assign.data.name.length, "name is too long").toBeLessThanOrEqual(32);
|
||||
expect(assign.data.name, "name has invalid characters").toMatch(
|
||||
/^[-_\p{L}\p{N}\p{sc=Deva}\p{sc=Thai}]{1,32}$/u,
|
||||
);
|
||||
expect(
|
||||
assign.data.description,
|
||||
"did not have the correct description",
|
||||
).toBe("Add or remove someone as a task assignee.");
|
||||
expect(
|
||||
assign.data.description.length,
|
||||
"description is too long",
|
||||
).toBeLessThanOrEqual(100);
|
||||
expect(
|
||||
assign.data.contexts,
|
||||
"did not have the correct context",
|
||||
).toStrictEqual([ InteractionContextType.Guild ]);
|
||||
expect(assign.data.options, "should have 2 options").toHaveLength(2);
|
||||
expect(
|
||||
assign.data.options[0].toJSON().name,
|
||||
"should have the correct name",
|
||||
).toBe("task");
|
||||
expect(
|
||||
assign.data.options[0].toJSON().description,
|
||||
"should have the correct description",
|
||||
).toBe("The task number.");
|
||||
expect(
|
||||
assign.data.options[0].toJSON().required,
|
||||
"should be required",
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
assign.data.options[0].toJSON()["min_value"],
|
||||
"should have a min value of 1",
|
||||
).toBe(1);
|
||||
expect(
|
||||
assign.data.options[0].toJSON().type,
|
||||
"should be a number option",
|
||||
).toBe(ApplicationCommandOptionType.Integer);
|
||||
|
||||
expect(
|
||||
assign.data.options[1].toJSON().name,
|
||||
"should have the correct name",
|
||||
).toBe("assignee");
|
||||
expect(
|
||||
assign.data.options[1].toJSON().description,
|
||||
"should have the correct description",
|
||||
).toBe("The user to (un)assign.");
|
||||
expect(
|
||||
assign.data.options[1].toJSON().required,
|
||||
"should be required",
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
assign.data.options[1].toJSON().type,
|
||||
"should be a user option",
|
||||
).toBe(ApplicationCommandOptionType.User);
|
||||
});
|
||||
|
||||
it("should execute correctly when adding assign", async() => {
|
||||
expect.assertions(4);
|
||||
const mockBot = {
|
||||
database: {
|
||||
tasks: {
|
||||
findFirst: vi.fn().mockResolvedValue({
|
||||
assignees: [],
|
||||
numericalId: 1,
|
||||
}),
|
||||
update: vi.fn().mockResolvedValue({}),
|
||||
},
|
||||
},
|
||||
} as never;
|
||||
const mockInteraction = {
|
||||
deferReply: vi.fn(),
|
||||
editReply: vi.fn(),
|
||||
options: {
|
||||
getInteger: vi.fn().mockReturnValue(1),
|
||||
getUser: vi.fn().mockReturnValue({ id: "123" }),
|
||||
},
|
||||
} as never;
|
||||
await assign.run(mockBot, mockInteraction);
|
||||
expect(
|
||||
mockInteraction.deferReply,
|
||||
"should defer the reply",
|
||||
).toHaveBeenCalledWith({ ephemeral: true });
|
||||
expect(
|
||||
mockBot.database.tasks.findFirst,
|
||||
"should call findFirst",
|
||||
).toHaveBeenCalledWith({
|
||||
where: {
|
||||
numericalId: 1,
|
||||
},
|
||||
});
|
||||
expect(
|
||||
mockBot.database.tasks.update,
|
||||
"should call update",
|
||||
).toHaveBeenCalledWith({
|
||||
data: {
|
||||
assignees: {
|
||||
push: "123",
|
||||
},
|
||||
},
|
||||
where: {
|
||||
numericalId: 1,
|
||||
},
|
||||
});
|
||||
expect(
|
||||
mockInteraction.editReply,
|
||||
"should call editReply",
|
||||
).toHaveBeenCalledWith({
|
||||
content: "User <@123> assigned to task 1.",
|
||||
});
|
||||
});
|
||||
|
||||
it("should execute correctly when removing assign", async() => {
|
||||
expect.assertions(4);
|
||||
const mockBot = {
|
||||
database: {
|
||||
tasks: {
|
||||
findFirst: vi.fn().mockResolvedValue({
|
||||
assignees: [ "123", "456" ],
|
||||
numericalId: 1,
|
||||
}),
|
||||
update: vi.fn().mockResolvedValue({}),
|
||||
},
|
||||
},
|
||||
} as never;
|
||||
const mockInteraction = {
|
||||
deferReply: vi.fn(),
|
||||
editReply: vi.fn(),
|
||||
options: {
|
||||
getInteger: vi.fn().mockReturnValue(1),
|
||||
getUser: vi.fn().mockReturnValue({ id: "123" }),
|
||||
},
|
||||
} as never;
|
||||
await assign.run(mockBot, mockInteraction);
|
||||
expect(
|
||||
mockInteraction.deferReply,
|
||||
"should defer the reply",
|
||||
).toHaveBeenCalledWith({ ephemeral: true });
|
||||
expect(
|
||||
mockBot.database.tasks.findFirst,
|
||||
"should call findFirst",
|
||||
).toHaveBeenCalledWith({
|
||||
where: {
|
||||
numericalId: 1,
|
||||
},
|
||||
});
|
||||
expect(
|
||||
mockBot.database.tasks.update,
|
||||
"should call update",
|
||||
).toHaveBeenCalledWith({
|
||||
data: {
|
||||
assignees: [ "456" ],
|
||||
},
|
||||
where: {
|
||||
numericalId: 1,
|
||||
},
|
||||
});
|
||||
expect(
|
||||
mockInteraction.editReply,
|
||||
"should call editReply",
|
||||
).toHaveBeenCalledWith({
|
||||
content: "User <@123> unassigned from task 1.",
|
||||
});
|
||||
});
|
||||
|
||||
it("should execute correctly when task not found", async() => {
|
||||
expect.assertions(4);
|
||||
vi.resetAllMocks();
|
||||
const mockBot = {
|
||||
database: {
|
||||
tasks: {
|
||||
findFirst: vi.fn().mockResolvedValue(null),
|
||||
update: vi.fn().mockResolvedValue({}),
|
||||
},
|
||||
},
|
||||
} as never;
|
||||
const mockInteraction = {
|
||||
deferReply: vi.fn(),
|
||||
editReply: vi.fn(),
|
||||
options: {
|
||||
getInteger: vi.fn().mockReturnValue(1),
|
||||
getUser: vi.fn().mockReturnValue({ id: "123" }),
|
||||
},
|
||||
} as never;
|
||||
await assign.run(mockBot, mockInteraction);
|
||||
expect(
|
||||
mockInteraction.deferReply,
|
||||
"should defer the reply",
|
||||
).toHaveBeenCalledWith({ ephemeral: true });
|
||||
expect(
|
||||
mockBot.database.tasks.findFirst,
|
||||
"should call findFirst",
|
||||
).toHaveBeenCalledWith({
|
||||
where: {
|
||||
numericalId: 1,
|
||||
},
|
||||
});
|
||||
expect(
|
||||
mockBot.database.tasks.update,
|
||||
"should not call update",
|
||||
).not.toHaveBeenCalled();
|
||||
expect(
|
||||
mockInteraction.editReply,
|
||||
"should call editReply",
|
||||
).toHaveBeenCalledWith({
|
||||
content: "Task 1 not found.",
|
||||
});
|
||||
});
|
||||
|
||||
it("should handle errors correctly", async() => {
|
||||
expect.assertions(1);
|
||||
await assign.run(
|
||||
{} as never,
|
||||
{ editReply: vi.fn(), replied: false, reply: vi.fn() } as never,
|
||||
);
|
||||
expect(errorHandler, "should call error handler").toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("should handle errors with interaction.reply correctly", async() => {
|
||||
expect.assertions(1);
|
||||
vi.resetAllMocks();
|
||||
await assign.run(
|
||||
{} as never,
|
||||
{ editReply: vi.fn(), replied: true, reply: vi.fn() } as never,
|
||||
);
|
||||
expect(errorHandler, "should call error handler").toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,166 @@
|
||||
/**
|
||||
* @copyright nhcarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
|
||||
import {
|
||||
ApplicationCommandOptionType,
|
||||
InteractionContextType,
|
||||
} from "discord.js";
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
import { complete } from "../../src/commands/complete.js";
|
||||
import { errorHandler } from "../../src/utils/errorHandler.js";
|
||||
|
||||
vi.mock("../../src/utils/errorHandler.ts", () => {
|
||||
return {
|
||||
errorHandler: vi.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
describe("complete command", () => {
|
||||
it("should have the correct data", () => {
|
||||
expect.assertions(11);
|
||||
expect(complete.data.name, "did not have the correct name").toBe(
|
||||
"complete",
|
||||
);
|
||||
expect(complete.data.name.length, "name is too long").toBeLessThanOrEqual(
|
||||
32,
|
||||
);
|
||||
expect(complete.data.name, "name has invalid characters").toMatch(
|
||||
/^[-_\p{L}\p{N}\p{sc=Deva}\p{sc=Thai}]{1,32}$/u,
|
||||
);
|
||||
expect(
|
||||
complete.data.description,
|
||||
"did not have the correct description",
|
||||
).toBe("Mark a task as completed.");
|
||||
expect(
|
||||
complete.data.description.length,
|
||||
"description is too long",
|
||||
).toBeLessThanOrEqual(100);
|
||||
expect(
|
||||
complete.data.contexts,
|
||||
"did not have the correct context",
|
||||
).toStrictEqual([ InteractionContextType.Guild ]);
|
||||
expect(complete.data.options, "should have 1 option").toHaveLength(1);
|
||||
expect(
|
||||
complete.data.options[0].toJSON().name,
|
||||
"did not have the correct option name",
|
||||
).toBe("id");
|
||||
expect(
|
||||
complete.data.options[0].toJSON().description,
|
||||
"did not have the correct option description",
|
||||
).toBe("The ID of the task to complete.");
|
||||
expect(
|
||||
complete.data.options[0].toJSON().required,
|
||||
"did not have the correct option required value",
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
complete.data.options[0].toJSON().type,
|
||||
"did not have the correct option type",
|
||||
).toBe(ApplicationCommandOptionType.Integer);
|
||||
});
|
||||
|
||||
it("should execute correctly", async() => {
|
||||
expect.assertions(3);
|
||||
const mockBot = {
|
||||
database: {
|
||||
tasks: {
|
||||
update: vi.fn().mockReturnValue({
|
||||
catch: vi.fn().mockReturnValue([ { assignees: [ "123" ] } ]),
|
||||
}),
|
||||
},
|
||||
},
|
||||
} as never;
|
||||
const mockInteraction = {
|
||||
deferReply: vi.fn(),
|
||||
editReply: vi.fn(),
|
||||
options: { getInteger: vi.fn().mockReturnValue(1) },
|
||||
} as never;
|
||||
await complete.run(mockBot, mockInteraction);
|
||||
expect(
|
||||
mockInteraction.deferReply,
|
||||
"should defer the reply",
|
||||
).toHaveBeenCalledWith({ ephemeral: true });
|
||||
expect(
|
||||
mockBot.database.tasks.update,
|
||||
"should update database",
|
||||
).toHaveBeenCalledWith({
|
||||
data: {
|
||||
completed: true,
|
||||
},
|
||||
where: {
|
||||
numericalId: 1,
|
||||
},
|
||||
});
|
||||
expect(
|
||||
mockInteraction.editReply,
|
||||
"should call editReply",
|
||||
).toHaveBeenCalledWith({
|
||||
content: "Task 1 has been marked as complete.",
|
||||
});
|
||||
});
|
||||
|
||||
it("should execute correctly when task not found", async() => {
|
||||
expect.assertions(3);
|
||||
const mockBot = {
|
||||
database: {
|
||||
tasks: {
|
||||
update: vi.fn().mockReturnValue({
|
||||
catch: vi.fn().mockImplementation((callback) => {
|
||||
return callback();
|
||||
}),
|
||||
}),
|
||||
},
|
||||
},
|
||||
} as never;
|
||||
const mockInteraction = {
|
||||
deferReply: vi.fn(),
|
||||
editReply: vi.fn(),
|
||||
options: {
|
||||
getInteger: vi.fn().mockReturnValue(1),
|
||||
},
|
||||
} as never;
|
||||
await complete.run(mockBot, mockInteraction);
|
||||
expect(
|
||||
mockInteraction.deferReply,
|
||||
"should defer the reply",
|
||||
).toHaveBeenCalledWith({ ephemeral: true });
|
||||
expect(
|
||||
mockBot.database.tasks.update,
|
||||
"should update database",
|
||||
).toHaveBeenCalledWith({
|
||||
data: {
|
||||
completed: true,
|
||||
},
|
||||
where: {
|
||||
numericalId: 1,
|
||||
},
|
||||
});
|
||||
expect(
|
||||
mockInteraction.editReply,
|
||||
"should call editReply",
|
||||
).toHaveBeenCalledWith({
|
||||
content: "Task 1 does not exist.",
|
||||
});
|
||||
});
|
||||
|
||||
it("should handle errors correctly", async() => {
|
||||
expect.assertions(1);
|
||||
await complete.run(
|
||||
{} as never,
|
||||
{ editReply: vi.fn(), replied: false, reply: vi.fn() } as never,
|
||||
);
|
||||
expect(errorHandler, "should call error handler").toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("should handle errors with interaction.reply correctly", async() => {
|
||||
expect.assertions(1);
|
||||
vi.resetAllMocks();
|
||||
await complete.run(
|
||||
{} as never,
|
||||
{ editReply: vi.fn(), replied: true, reply: vi.fn() } as never,
|
||||
);
|
||||
expect(errorHandler, "should call error handler").toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,196 @@
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
/**
|
||||
* @copyright nhcarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
|
||||
import {
|
||||
ActionRowBuilder,
|
||||
InteractionContextType,
|
||||
ModalBuilder,
|
||||
TextInputBuilder,
|
||||
TextInputStyle,
|
||||
} from "discord.js";
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
import { create } from "../../src/commands/create.js";
|
||||
import { errorHandler } from "../../src/utils/errorHandler.js";
|
||||
|
||||
vi.mock("discord.js", async() => {
|
||||
const actual = await vi.importActual("discord.js");
|
||||
return {
|
||||
...actual,
|
||||
ActionRowBuilder: vi.fn(() => {
|
||||
return {
|
||||
addComponents: vi.fn().mockReturnThis(),
|
||||
};
|
||||
}),
|
||||
ModalBuilder: vi.fn(() => {
|
||||
return {
|
||||
addComponents: vi.fn().mockReturnThis(),
|
||||
setCustomId: vi.fn().mockReturnThis(),
|
||||
setTitle: vi.fn().mockReturnThis(),
|
||||
};
|
||||
}),
|
||||
TextInputBuilder: vi.fn(() => {
|
||||
return {
|
||||
setCustomId: vi.fn().mockReturnThis(),
|
||||
setLabel: vi.fn().mockReturnThis(),
|
||||
setRequired: vi.fn().mockReturnThis(),
|
||||
setStyle: vi.fn().mockReturnThis(),
|
||||
};
|
||||
}),
|
||||
TextInputStyle: { Paragraph: "Paragraph", Short: "Short" },
|
||||
};
|
||||
});
|
||||
vi.mock("../../src/utils/errorHandler.ts", () => {
|
||||
return {
|
||||
errorHandler: vi.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
describe("create command", () => {
|
||||
it("should have the correct data", () => {
|
||||
expect.assertions(7);
|
||||
expect(create.data.name, "did not have the correct name").toBe("create");
|
||||
expect(create.data.name.length, "name is too long").toBeLessThanOrEqual(32);
|
||||
expect(create.data.name, "name has invalid characters").toMatch(
|
||||
/^[-_\p{L}\p{N}\p{sc=Deva}\p{sc=Thai}]{1,32}$/u,
|
||||
);
|
||||
expect(
|
||||
create.data.description,
|
||||
"did not have the correct description",
|
||||
).toBe("Create a new task.");
|
||||
expect(
|
||||
create.data.description.length,
|
||||
"description is too long",
|
||||
).toBeLessThanOrEqual(100);
|
||||
expect(
|
||||
create.data.contexts,
|
||||
"did not have the correct context",
|
||||
).toStrictEqual([ InteractionContextType.Guild ]);
|
||||
expect(create.data.options, "should not have options").toHaveLength(0);
|
||||
});
|
||||
|
||||
it("should execute correctly", async() => {
|
||||
expect.assertions(23);
|
||||
const mockBot = {} as never;
|
||||
const mockInteraction = {
|
||||
showModal: vi.fn(),
|
||||
} as never;
|
||||
await create.run(mockBot, mockInteraction);
|
||||
expect(TextInputBuilder, "should create text inputs").toHaveBeenCalledTimes(
|
||||
3,
|
||||
);
|
||||
const [ titleCall, descriptionCall, dueCall ]
|
||||
= vi.mocked(TextInputBuilder).mock.results;
|
||||
expect(
|
||||
titleCall.value.setLabel,
|
||||
"should set title label",
|
||||
).toHaveBeenCalledWith("Title");
|
||||
expect(
|
||||
titleCall.value.setRequired,
|
||||
"should set title required",
|
||||
).toHaveBeenCalledWith(true);
|
||||
expect(
|
||||
titleCall.value.setStyle,
|
||||
"should set title style",
|
||||
).toHaveBeenCalledWith(TextInputStyle.Short);
|
||||
expect(
|
||||
titleCall.value.setCustomId,
|
||||
"should set title custom id",
|
||||
).toHaveBeenCalledWith("title");
|
||||
expect(
|
||||
descriptionCall.value.setLabel,
|
||||
"should set description label",
|
||||
).toHaveBeenCalledWith("Description");
|
||||
expect(
|
||||
descriptionCall.value.setRequired,
|
||||
"should set description required",
|
||||
).toHaveBeenCalledWith(true);
|
||||
expect(
|
||||
descriptionCall.value.setStyle,
|
||||
"should set description style",
|
||||
).toHaveBeenCalledWith(TextInputStyle.Paragraph);
|
||||
expect(
|
||||
descriptionCall.value.setCustomId,
|
||||
"should set description custom id",
|
||||
).toHaveBeenCalledWith("description");
|
||||
expect(dueCall.value.setLabel, "should set due label").toHaveBeenCalledWith(
|
||||
"Due Date",
|
||||
);
|
||||
expect(
|
||||
dueCall.value.setRequired,
|
||||
"should set due required",
|
||||
).toHaveBeenCalledWith(true);
|
||||
expect(dueCall.value.setStyle, "should set due style").toHaveBeenCalledWith(
|
||||
TextInputStyle.Short,
|
||||
);
|
||||
expect(
|
||||
dueCall.value.setCustomId,
|
||||
"should set due custom id",
|
||||
).toHaveBeenCalledWith("dueDate");
|
||||
expect(ActionRowBuilder, "should create action rows").toHaveBeenCalledTimes(
|
||||
3,
|
||||
);
|
||||
const [ rowOneCall, rowTwoCall, rowThreeCall ]
|
||||
= vi.mocked(ActionRowBuilder).mock.results;
|
||||
expect(
|
||||
rowOneCall.value.addComponents,
|
||||
"should add title to row",
|
||||
).toHaveBeenCalledWith(titleCall.value);
|
||||
expect(
|
||||
rowTwoCall.value.addComponents,
|
||||
"should add description to row",
|
||||
).toHaveBeenCalledWith(descriptionCall.value);
|
||||
expect(
|
||||
rowThreeCall.value.addComponents,
|
||||
"should add due to row",
|
||||
).toHaveBeenCalledWith(dueCall.value);
|
||||
expect(ModalBuilder, "should create modal").toHaveBeenCalledTimes(1);
|
||||
expect(
|
||||
vi.mocked(ModalBuilder).mock.results[0].value.setTitle,
|
||||
"should set modal title",
|
||||
).toHaveBeenCalledWith("New Task");
|
||||
expect(
|
||||
vi.mocked(ModalBuilder).mock.results[0].value.addComponents,
|
||||
"should add components to modal",
|
||||
).toHaveBeenCalledTimes(1);
|
||||
|
||||
expect(
|
||||
vi.mocked(ModalBuilder).mock.results[0].value.addComponents,
|
||||
"should add components to modal",
|
||||
).toHaveBeenCalledWith(
|
||||
rowOneCall.value,
|
||||
rowTwoCall.value,
|
||||
rowThreeCall.value,
|
||||
);
|
||||
expect(
|
||||
vi.mocked(ModalBuilder).mock.results[0].value.setCustomId,
|
||||
"should set modal custom id",
|
||||
).toHaveBeenCalledWith("create-task");
|
||||
expect(
|
||||
mockInteraction.showModal,
|
||||
"should display the modal",
|
||||
).toHaveBeenCalledWith(ModalBuilder.mock.results[0].value);
|
||||
});
|
||||
|
||||
it("should handle errors correctly", async() => {
|
||||
expect.assertions(1);
|
||||
await create.run(
|
||||
{} as never,
|
||||
{ editReply: vi.fn(), replied: false, reply: vi.fn() } as never,
|
||||
);
|
||||
expect(errorHandler, "should call error handler").toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("should handle errors with interaction.reply correctly", async() => {
|
||||
expect.assertions(1);
|
||||
vi.resetAllMocks();
|
||||
await create.run(
|
||||
{} as never,
|
||||
{ editReply: vi.fn(), replied: true, reply: vi.fn() } as never,
|
||||
);
|
||||
expect(errorHandler, "should call error handler").toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,182 @@
|
||||
/**
|
||||
* @copyright nhcarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
|
||||
import {
|
||||
ApplicationCommandOptionType,
|
||||
InteractionContextType,
|
||||
} from "discord.js";
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
import { deleteCommand } from "../../src/commands/delete.js";
|
||||
import { errorHandler } from "../../src/utils/errorHandler.js";
|
||||
|
||||
vi.mock("../../src/utils/errorHandler.ts", () => {
|
||||
return {
|
||||
errorHandler: vi.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
describe("delete command", () => {
|
||||
it("should have the correct data", () => {
|
||||
expect.assertions(11);
|
||||
expect(deleteCommand.data.name, "did not have the correct name").toBe(
|
||||
"delete",
|
||||
);
|
||||
expect(
|
||||
deleteCommand.data.name.length,
|
||||
"name is too long",
|
||||
).toBeLessThanOrEqual(32);
|
||||
expect(deleteCommand.data.name, "name has invalid characters").toMatch(
|
||||
/^[-_\p{L}\p{N}\p{sc=Deva}\p{sc=Thai}]{1,32}$/u,
|
||||
);
|
||||
expect(
|
||||
deleteCommand.data.description,
|
||||
"did not have the correct description",
|
||||
).toBe(
|
||||
// eslint-disable-next-line stylistic/max-len
|
||||
"Mark a task as deleted. WARNING: This will scrub all PII from the task and CANNOT be undone.",
|
||||
);
|
||||
expect(
|
||||
deleteCommand.data.description.length,
|
||||
"description is too long",
|
||||
).toBeLessThanOrEqual(100);
|
||||
expect(
|
||||
deleteCommand.data.contexts,
|
||||
"did not have the correct context",
|
||||
).toStrictEqual([ InteractionContextType.Guild ]);
|
||||
expect(deleteCommand.data.options, "should have 1 option").toHaveLength(1);
|
||||
expect(
|
||||
deleteCommand.data.options[0].toJSON().name,
|
||||
"did not have the correct option name",
|
||||
).toBe("id");
|
||||
expect(
|
||||
deleteCommand.data.options[0].toJSON().description,
|
||||
"did not have the correct option description",
|
||||
).toBe("The ID of the task to delete.");
|
||||
expect(
|
||||
deleteCommand.data.options[0].toJSON().required,
|
||||
"did not have the correct option required value",
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
deleteCommand.data.options[0].toJSON().type,
|
||||
"did not have the correct option type",
|
||||
).toBe(ApplicationCommandOptionType.Integer);
|
||||
});
|
||||
|
||||
it("should execute correctly", async() => {
|
||||
expect.assertions(3);
|
||||
const mockBot = {
|
||||
database: {
|
||||
tasks: {
|
||||
update: vi.fn().mockReturnValue({
|
||||
catch: vi.fn().mockReturnValue([ { assignees: [ "123" ] } ]),
|
||||
}),
|
||||
},
|
||||
},
|
||||
} as never;
|
||||
const mockInteraction = {
|
||||
deferReply: vi.fn(),
|
||||
editReply: vi.fn(),
|
||||
options: { getInteger: vi.fn().mockReturnValue(1) },
|
||||
} as never;
|
||||
await deleteCommand.run(mockBot, mockInteraction);
|
||||
expect(
|
||||
mockInteraction.deferReply,
|
||||
"should defer the reply",
|
||||
).toHaveBeenCalledWith({ ephemeral: true });
|
||||
expect(
|
||||
mockBot.database.tasks.update,
|
||||
"should update database",
|
||||
).toHaveBeenCalledWith({
|
||||
data: {
|
||||
assignees: [],
|
||||
deleted: true,
|
||||
description: "This task has been deleted.",
|
||||
dueAt: expect.any(Date),
|
||||
priority: "deleted",
|
||||
tags: [],
|
||||
title: "Deleted Task",
|
||||
},
|
||||
where: {
|
||||
numericalId: 1,
|
||||
},
|
||||
});
|
||||
expect(
|
||||
mockInteraction.editReply,
|
||||
"should call editReply",
|
||||
).toHaveBeenCalledWith({
|
||||
content: "Task 1 has been marked as deleted.",
|
||||
});
|
||||
});
|
||||
|
||||
it("should execute correctly when task not found", async() => {
|
||||
expect.assertions(3);
|
||||
const mockBot = {
|
||||
database: {
|
||||
tasks: {
|
||||
update: vi.fn().mockReturnValue({
|
||||
catch: vi.fn().mockImplementation((callback) => {
|
||||
return callback();
|
||||
}),
|
||||
}),
|
||||
},
|
||||
},
|
||||
} as never;
|
||||
const mockInteraction = {
|
||||
deferReply: vi.fn(),
|
||||
editReply: vi.fn(),
|
||||
options: {
|
||||
getInteger: vi.fn().mockReturnValue(1),
|
||||
},
|
||||
} as never;
|
||||
await deleteCommand.run(mockBot, mockInteraction);
|
||||
expect(
|
||||
mockInteraction.deferReply,
|
||||
"should defer the reply",
|
||||
).toHaveBeenCalledWith({ ephemeral: true });
|
||||
expect(
|
||||
mockBot.database.tasks.update,
|
||||
"should update database",
|
||||
).toHaveBeenCalledWith({
|
||||
data: {
|
||||
assignees: [],
|
||||
deleted: true,
|
||||
description: "This task has been deleted.",
|
||||
dueAt: expect.any(Date),
|
||||
priority: "deleted",
|
||||
tags: [],
|
||||
title: "Deleted Task",
|
||||
},
|
||||
where: {
|
||||
numericalId: 1,
|
||||
},
|
||||
});
|
||||
expect(
|
||||
mockInteraction.editReply,
|
||||
"should call editReply",
|
||||
).toHaveBeenCalledWith({
|
||||
content: "Task 1 does not exist.",
|
||||
});
|
||||
});
|
||||
|
||||
it("should handle errors correctly", async() => {
|
||||
expect.assertions(1);
|
||||
await deleteCommand.run(
|
||||
{} as never,
|
||||
{ editReply: vi.fn(), replied: false, reply: vi.fn() } as never,
|
||||
);
|
||||
expect(errorHandler, "should call error handler").toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("should handle errors with interaction.reply correctly", async() => {
|
||||
expect.assertions(1);
|
||||
vi.resetAllMocks();
|
||||
await deleteCommand.run(
|
||||
{} as never,
|
||||
{ editReply: vi.fn(), replied: true, reply: vi.fn() } as never,
|
||||
);
|
||||
expect(errorHandler, "should call error handler").toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,381 @@
|
||||
/**
|
||||
* @copyright nhcarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
|
||||
import {
|
||||
ApplicationCommandOptionType,
|
||||
InteractionContextType,
|
||||
} from "discord.js";
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
import { list } from "../../src/commands/list.js";
|
||||
import { errorHandler } from "../../src/utils/errorHandler.js";
|
||||
|
||||
vi.mock("../../src/utils/errorHandler.ts", () => {
|
||||
return {
|
||||
errorHandler: vi.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
describe("list command", () => {
|
||||
it("should have the correct data", () => {
|
||||
expect.assertions(34);
|
||||
expect(list.data.name, "did not have the correct name").toBe("list");
|
||||
expect(list.data.name.length, "name is too long").toBeLessThanOrEqual(32);
|
||||
expect(list.data.name, "name has invalid characters").toMatch(
|
||||
/^[-_\p{L}\p{N}\p{sc=Deva}\p{sc=Thai}]{1,32}$/u,
|
||||
);
|
||||
expect(list.data.description, "did not have the correct description").toBe(
|
||||
"List all tasks, with optional filters.",
|
||||
);
|
||||
expect(
|
||||
list.data.description.length,
|
||||
"description is too long",
|
||||
).toBeLessThanOrEqual(100);
|
||||
expect(
|
||||
list.data.contexts,
|
||||
"did not have the correct context",
|
||||
).toStrictEqual([ InteractionContextType.Guild ]);
|
||||
expect(list.data.options, "should have 4 options").toHaveLength(4);
|
||||
expect(
|
||||
list.data.options[0].toJSON().name,
|
||||
"did not have the correct option name",
|
||||
).toBe("priority");
|
||||
expect(
|
||||
list.data.options[0].toJSON().description,
|
||||
"did not have the correct option description",
|
||||
).toBe("List tasks under this priority.");
|
||||
expect(
|
||||
list.data.options[0].toJSON().required,
|
||||
"did not have the correct option required value",
|
||||
).toBeFalsy();
|
||||
expect(
|
||||
list.data.options[0].toJSON().type,
|
||||
"did not have the correct option type",
|
||||
).toBe(ApplicationCommandOptionType.String);
|
||||
expect(
|
||||
list.data.options[0].toJSON().choices,
|
||||
"should have 5 choices",
|
||||
).toHaveLength(5);
|
||||
expect(
|
||||
list.data.options[0].toJSON().choices[0].name,
|
||||
"should have the correct name",
|
||||
).toBe("Low");
|
||||
expect(
|
||||
list.data.options[0].toJSON().choices[0].value,
|
||||
"should have the correct value",
|
||||
).toBe("low");
|
||||
expect(
|
||||
list.data.options[0].toJSON().choices[1].name,
|
||||
"should have the correct name",
|
||||
).toBe("Medium");
|
||||
expect(
|
||||
list.data.options[0].toJSON().choices[1].value,
|
||||
"should have the correct value",
|
||||
).toBe("medium");
|
||||
expect(
|
||||
list.data.options[0].toJSON().choices[2].name,
|
||||
"should have the correct name",
|
||||
).toBe("High");
|
||||
expect(
|
||||
list.data.options[0].toJSON().choices[2].value,
|
||||
"should have the correct value",
|
||||
).toBe("high");
|
||||
expect(
|
||||
list.data.options[0].toJSON().choices[3].name,
|
||||
"should have the correct name",
|
||||
).toBe("Critical");
|
||||
expect(
|
||||
list.data.options[0].toJSON().choices[3].value,
|
||||
"should have the correct value",
|
||||
).toBe("critical");
|
||||
expect(
|
||||
list.data.options[0].toJSON().choices[4].name,
|
||||
"should have the correct name",
|
||||
).toBe("None");
|
||||
expect(
|
||||
list.data.options[0].toJSON().choices[4].value,
|
||||
"should have the correct value",
|
||||
).toBe("none");
|
||||
expect(
|
||||
list.data.options[1].toJSON().name,
|
||||
"did not have the correct option name",
|
||||
).toBe("tag");
|
||||
expect(
|
||||
list.data.options[1].toJSON().description,
|
||||
"did not have the correct option description",
|
||||
).toBe("List tasks with this tag.");
|
||||
expect(
|
||||
list.data.options[1].toJSON().required,
|
||||
"did not have the correct option required value",
|
||||
).toBeFalsy();
|
||||
expect(
|
||||
list.data.options[1].toJSON().type,
|
||||
"did not have the correct option type",
|
||||
).toBe(ApplicationCommandOptionType.String);
|
||||
expect(
|
||||
list.data.options[2].toJSON().name,
|
||||
"did not have the correct option name",
|
||||
).toBe("assignee");
|
||||
expect(
|
||||
list.data.options[2].toJSON().description,
|
||||
"did not have the correct option description",
|
||||
).toBe("List tasks assigned to this user.");
|
||||
expect(
|
||||
list.data.options[2].toJSON().required,
|
||||
"did not have the correct option required value",
|
||||
).toBeFalsy();
|
||||
expect(
|
||||
list.data.options[2].toJSON().type,
|
||||
"did not have the correct option type",
|
||||
).toBe(ApplicationCommandOptionType.User);
|
||||
expect(
|
||||
list.data.options[3].toJSON().name,
|
||||
"did not have the correct option name",
|
||||
).toBe("completed");
|
||||
expect(
|
||||
list.data.options[3].toJSON().description,
|
||||
"did not have the correct option description",
|
||||
).toBe("List completed tasks.");
|
||||
expect(
|
||||
list.data.options[3].toJSON().required,
|
||||
"did not have the correct option required value",
|
||||
).toBeFalsy();
|
||||
expect(
|
||||
list.data.options[3].toJSON().type,
|
||||
"did not have the correct option type",
|
||||
).toBe(ApplicationCommandOptionType.Boolean);
|
||||
});
|
||||
|
||||
it("should execute correctly with no filters", async() => {
|
||||
expect.assertions(3);
|
||||
const mockBot = {
|
||||
database: {
|
||||
tasks: {
|
||||
findMany: vi.fn().mockImplementation((data) => {
|
||||
return [
|
||||
{
|
||||
assignees: [],
|
||||
completed: false,
|
||||
deleted: false,
|
||||
description: "Task 1 description",
|
||||
dueAt: new Date("September 4, 2000"),
|
||||
numericalId: 1,
|
||||
priority: "none",
|
||||
tags: [],
|
||||
title: "Task 1",
|
||||
},
|
||||
{
|
||||
assignees: [ "123", "456" ],
|
||||
completed: true,
|
||||
deleted: true,
|
||||
description: "Task 2 description",
|
||||
dueAt: new Date(),
|
||||
numericalId: 2,
|
||||
priority: "low",
|
||||
tags: [ "tag1", "tag2" ],
|
||||
title: "Task 2",
|
||||
},
|
||||
{
|
||||
assignees: [ "789" ],
|
||||
completed: false,
|
||||
deleted: false,
|
||||
description: "Task 3 description",
|
||||
dueAt: new Date("October 8, 2001"),
|
||||
numericalId: 3,
|
||||
priority: "medium",
|
||||
tags: [ "tag1" ],
|
||||
title: "Task 3",
|
||||
},
|
||||
].filter((task) => {
|
||||
return Object.entries(data.where).every(([ key, value ]) => {
|
||||
return task[key] === value;
|
||||
});
|
||||
});
|
||||
}),
|
||||
},
|
||||
},
|
||||
} as never;
|
||||
const mockInteraction = {
|
||||
deferReply: vi.fn(),
|
||||
editReply: vi.fn(),
|
||||
options: {
|
||||
getBoolean: vi.fn().mockReturnValue(null),
|
||||
getString: vi.fn().mockReturnValue(null),
|
||||
getUser: vi.fn().mockReturnValue(null),
|
||||
},
|
||||
} as never;
|
||||
await list.run(mockBot, mockInteraction);
|
||||
expect(
|
||||
mockInteraction.deferReply,
|
||||
"should defer the reply",
|
||||
).toHaveBeenCalledWith({ ephemeral: true });
|
||||
expect(
|
||||
mockBot.database.tasks.findMany,
|
||||
"should query database",
|
||||
).toHaveBeenCalledWith({
|
||||
where: {
|
||||
completed: false,
|
||||
deleted: false,
|
||||
},
|
||||
});
|
||||
expect(
|
||||
mockInteraction.editReply,
|
||||
"should call editReply",
|
||||
).toHaveBeenCalledWith({
|
||||
content: "- Task 1: Task 1\n- Task 3: Task 3",
|
||||
});
|
||||
});
|
||||
|
||||
it("should execute correctly with no data", async() => {
|
||||
expect.assertions(3);
|
||||
const mockBot = {
|
||||
database: {
|
||||
tasks: {
|
||||
findMany: vi.fn().mockImplementation(() => {
|
||||
return [];
|
||||
}),
|
||||
},
|
||||
},
|
||||
} as never;
|
||||
const mockInteraction = {
|
||||
deferReply: vi.fn(),
|
||||
editReply: vi.fn(),
|
||||
options: {
|
||||
getBoolean: vi.fn().mockReturnValue(false),
|
||||
getString: vi.fn().mockReturnValue(null),
|
||||
getUser: vi.fn().mockReturnValue(null),
|
||||
},
|
||||
} as never;
|
||||
await list.run(mockBot, mockInteraction);
|
||||
expect(
|
||||
mockInteraction.deferReply,
|
||||
"should defer the reply",
|
||||
).toHaveBeenCalledWith({ ephemeral: true });
|
||||
expect(
|
||||
mockBot.database.tasks.findMany,
|
||||
"should query database",
|
||||
).toHaveBeenCalledWith({
|
||||
where: {
|
||||
completed: false,
|
||||
deleted: false,
|
||||
},
|
||||
});
|
||||
expect(
|
||||
mockInteraction.editReply,
|
||||
"should call editReply",
|
||||
).toHaveBeenCalledWith({
|
||||
content: "No tasks found with this current filter.",
|
||||
});
|
||||
});
|
||||
|
||||
it("should execute correctly with filters", async() => {
|
||||
expect.assertions(3);
|
||||
const mockBot = {
|
||||
database: {
|
||||
tasks: {
|
||||
findMany: vi.fn().mockImplementation((data) => {
|
||||
return [
|
||||
{
|
||||
assignees: [],
|
||||
completed: false,
|
||||
deleted: false,
|
||||
description: "Task 1 description",
|
||||
dueAt: new Date(),
|
||||
numericalId: 1,
|
||||
priority: "none",
|
||||
tags: [],
|
||||
title: "Task 1",
|
||||
},
|
||||
{
|
||||
assignees: [ "123", "456" ],
|
||||
completed: true,
|
||||
deleted: false,
|
||||
description: "Task 2 description",
|
||||
dueAt: new Date(),
|
||||
numericalId: 2,
|
||||
priority: "low",
|
||||
tags: [ "tag1", "tag2" ],
|
||||
title: "Task 2 title",
|
||||
},
|
||||
{
|
||||
assignees: [ "789" ],
|
||||
completed: false,
|
||||
deleted: true,
|
||||
description: "Task 3 description",
|
||||
dueAt: new Date(),
|
||||
numericalId: 3,
|
||||
priority: "medium",
|
||||
tags: [ "tag1" ],
|
||||
title: "Task 3",
|
||||
},
|
||||
].filter((task) => {
|
||||
return Object.entries(data.where).every(([ key, value ]) => {
|
||||
if (typeof value === "object") {
|
||||
return task[key].includes(value.has);
|
||||
}
|
||||
return task[key] === value;
|
||||
});
|
||||
});
|
||||
}),
|
||||
},
|
||||
},
|
||||
} as never;
|
||||
const mockInteraction = {
|
||||
deferReply: vi.fn(),
|
||||
editReply: vi.fn(),
|
||||
options: {
|
||||
getBoolean: vi.fn().mockReturnValue(true),
|
||||
getString: vi.fn().mockImplementation((name) => {
|
||||
return name === "priority"
|
||||
? "low"
|
||||
: "tag1";
|
||||
}),
|
||||
getUser: vi.fn().mockReturnValue({ id: "123" }),
|
||||
},
|
||||
} as never;
|
||||
await list.run(mockBot, mockInteraction);
|
||||
expect(
|
||||
mockInteraction.deferReply,
|
||||
"should defer the reply",
|
||||
).toHaveBeenCalledWith({ ephemeral: true });
|
||||
expect(
|
||||
mockBot.database.tasks.findMany,
|
||||
"should query database",
|
||||
).toHaveBeenCalledWith({
|
||||
where: {
|
||||
assignees: { has: "123" },
|
||||
completed: true,
|
||||
deleted: false,
|
||||
priority: "low",
|
||||
tags: { has: "tag1" },
|
||||
},
|
||||
});
|
||||
expect(
|
||||
mockInteraction.editReply,
|
||||
"should call editReply",
|
||||
).toHaveBeenCalledWith({
|
||||
content: "- Task 2: Task 2 title",
|
||||
});
|
||||
});
|
||||
|
||||
it("should handle errors correctly", async() => {
|
||||
expect.assertions(1);
|
||||
await list.run(
|
||||
{} as never,
|
||||
{ editReply: vi.fn(), replied: false, reply: vi.fn() } as never,
|
||||
);
|
||||
expect(errorHandler, "should call error handler").toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("should handle errors with interaction.reply correctly", async() => {
|
||||
expect.assertions(1);
|
||||
vi.resetAllMocks();
|
||||
await list.run(
|
||||
{} as never,
|
||||
{ editReply: vi.fn(), replied: true, reply: vi.fn() } as never,
|
||||
);
|
||||
expect(errorHandler, "should call error handler").toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,243 @@
|
||||
/**
|
||||
* @copyright nhcarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
|
||||
import {
|
||||
ApplicationCommandOptionType,
|
||||
InteractionContextType,
|
||||
} from "discord.js";
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
import { priority } from "../../src/commands/priority.js";
|
||||
import { errorHandler } from "../../src/utils/errorHandler.js";
|
||||
|
||||
vi.mock("../../src/utils/errorHandler.ts", () => {
|
||||
return {
|
||||
errorHandler: vi.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
describe("priority command", () => {
|
||||
it("should have the correct data", () => {
|
||||
expect.assertions(27);
|
||||
expect(priority.data.name, "did not have the correct name").toBe(
|
||||
"priority",
|
||||
);
|
||||
expect(priority.data.name.length, "name is too long").toBeLessThanOrEqual(
|
||||
32,
|
||||
);
|
||||
expect(priority.data.name, "name has invalid characters").toMatch(
|
||||
/^[-_\p{L}\p{N}\p{sc=Deva}\p{sc=Thai}]{1,32}$/u,
|
||||
);
|
||||
expect(
|
||||
priority.data.description,
|
||||
"did not have the correct description",
|
||||
).toBe("Set the priority of a task.");
|
||||
expect(
|
||||
priority.data.description.length,
|
||||
"description is too long",
|
||||
).toBeLessThanOrEqual(100);
|
||||
expect(
|
||||
priority.data.contexts,
|
||||
"did not have the correct context",
|
||||
).toStrictEqual([ InteractionContextType.Guild ]);
|
||||
expect(priority.data.options, "should have 2 options").toHaveLength(2);
|
||||
expect(
|
||||
priority.data.options[0].toJSON().name,
|
||||
"should have the correct name",
|
||||
).toBe("task");
|
||||
expect(
|
||||
priority.data.options[0].toJSON().description,
|
||||
"should have the correct description",
|
||||
).toBe("The task number.");
|
||||
expect(
|
||||
priority.data.options[0].toJSON().required,
|
||||
"should be required",
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
priority.data.options[0].toJSON()["min_value"],
|
||||
"should have a min value of 1",
|
||||
).toBe(1);
|
||||
expect(
|
||||
priority.data.options[0].toJSON().type,
|
||||
"should be a number option",
|
||||
).toBe(ApplicationCommandOptionType.Integer);
|
||||
|
||||
expect(
|
||||
priority.data.options[1].toJSON().name,
|
||||
"should have the correct name",
|
||||
).toBe("priority");
|
||||
expect(
|
||||
priority.data.options[1].toJSON().description,
|
||||
"should have the correct description",
|
||||
).toBe("The priority level.");
|
||||
expect(
|
||||
priority.data.options[1].toJSON().required,
|
||||
"should be required",
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
priority.data.options[1].toJSON().choices,
|
||||
"should have choices",
|
||||
).toHaveLength(5);
|
||||
expect(
|
||||
priority.data.options[1].toJSON().choices[0].name,
|
||||
"should have the correct name",
|
||||
).toBe("Low");
|
||||
expect(
|
||||
priority.data.options[1].toJSON().choices[0].value,
|
||||
"should have the correct value",
|
||||
).toBe("low");
|
||||
expect(
|
||||
priority.data.options[1].toJSON().choices[1].name,
|
||||
"should have the correct name",
|
||||
).toBe("Medium");
|
||||
expect(
|
||||
priority.data.options[1].toJSON().choices[1].value,
|
||||
"should have the correct value",
|
||||
).toBe("medium");
|
||||
expect(
|
||||
priority.data.options[1].toJSON().choices[2].name,
|
||||
"should have the correct name",
|
||||
).toBe("High");
|
||||
expect(
|
||||
priority.data.options[1].toJSON().choices[2].value,
|
||||
"should have the correct value",
|
||||
).toBe("high");
|
||||
expect(
|
||||
priority.data.options[1].toJSON().choices[3].name,
|
||||
"should have the correct name",
|
||||
).toBe("Critical");
|
||||
expect(
|
||||
priority.data.options[1].toJSON().choices[3].value,
|
||||
"should have the correct value",
|
||||
).toBe("critical");
|
||||
expect(
|
||||
priority.data.options[1].toJSON().choices[4].name,
|
||||
"should have the correct name",
|
||||
).toBe("None");
|
||||
expect(
|
||||
priority.data.options[1].toJSON().choices[4].value,
|
||||
"should have the correct value",
|
||||
).toBe("none");
|
||||
expect(
|
||||
priority.data.options[1].toJSON().type,
|
||||
"should be a string option",
|
||||
).toBe(ApplicationCommandOptionType.String);
|
||||
});
|
||||
|
||||
it("should execute correctly", async() => {
|
||||
expect.assertions(4);
|
||||
const mockBot = {
|
||||
database: {
|
||||
tasks: {
|
||||
findFirst: vi.fn().mockResolvedValue({
|
||||
numericalId: 1,
|
||||
}),
|
||||
update: vi.fn().mockResolvedValue({}),
|
||||
},
|
||||
},
|
||||
} as never;
|
||||
const mockInteraction = {
|
||||
deferReply: vi.fn(),
|
||||
editReply: vi.fn(),
|
||||
options: {
|
||||
getInteger: vi.fn().mockReturnValue(1),
|
||||
getString: vi.fn().mockReturnValue("low"),
|
||||
},
|
||||
} as never;
|
||||
await priority.run(mockBot, mockInteraction);
|
||||
expect(
|
||||
mockInteraction.deferReply,
|
||||
"should defer the reply",
|
||||
).toHaveBeenCalledWith({ ephemeral: true });
|
||||
expect(
|
||||
mockBot.database.tasks.findFirst,
|
||||
"should call findFirst",
|
||||
).toHaveBeenCalledWith({
|
||||
where: {
|
||||
numericalId: 1,
|
||||
},
|
||||
});
|
||||
expect(
|
||||
mockBot.database.tasks.update,
|
||||
"should call update",
|
||||
).toHaveBeenCalledWith({
|
||||
data: {
|
||||
priority: "low",
|
||||
},
|
||||
where: {
|
||||
numericalId: 1,
|
||||
},
|
||||
});
|
||||
expect(
|
||||
mockInteraction.editReply,
|
||||
"should call editReply",
|
||||
).toHaveBeenCalledWith({
|
||||
content: "Task 1 priority set to low.",
|
||||
});
|
||||
});
|
||||
|
||||
it("should execute correctly when task not found", async() => {
|
||||
expect.assertions(4);
|
||||
vi.resetAllMocks();
|
||||
const mockBot = {
|
||||
database: {
|
||||
tasks: {
|
||||
findFirst: vi.fn().mockResolvedValue(null),
|
||||
update: vi.fn().mockResolvedValue({}),
|
||||
},
|
||||
},
|
||||
} as never;
|
||||
const mockInteraction = {
|
||||
deferReply: vi.fn(),
|
||||
editReply: vi.fn(),
|
||||
options: {
|
||||
getInteger: vi.fn().mockReturnValue(1),
|
||||
getString: vi.fn().mockReturnValue("low"),
|
||||
},
|
||||
} as never;
|
||||
await priority.run(mockBot, mockInteraction);
|
||||
expect(
|
||||
mockInteraction.deferReply,
|
||||
"should defer the reply",
|
||||
).toHaveBeenCalledWith({ ephemeral: true });
|
||||
expect(
|
||||
mockBot.database.tasks.findFirst,
|
||||
"should call findFirst",
|
||||
).toHaveBeenCalledWith({
|
||||
where: {
|
||||
numericalId: 1,
|
||||
},
|
||||
});
|
||||
expect(
|
||||
mockBot.database.tasks.update,
|
||||
"should not call update",
|
||||
).not.toHaveBeenCalled();
|
||||
expect(
|
||||
mockInteraction.editReply,
|
||||
"should call editReply",
|
||||
).toHaveBeenCalledWith({
|
||||
content: "Task 1 not found.",
|
||||
});
|
||||
});
|
||||
|
||||
it("should handle errors correctly", async() => {
|
||||
expect.assertions(1);
|
||||
await priority.run(
|
||||
{} as never,
|
||||
{ editReply: vi.fn(), replied: false, reply: vi.fn() } as never,
|
||||
);
|
||||
expect(errorHandler, "should call error handler").toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("should handle errors with interaction.reply correctly", async() => {
|
||||
expect.assertions(1);
|
||||
vi.resetAllMocks();
|
||||
await priority.run(
|
||||
{} as never,
|
||||
{ editReply: vi.fn(), replied: true, reply: vi.fn() } as never,
|
||||
);
|
||||
expect(errorHandler, "should call error handler").toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,251 @@
|
||||
/**
|
||||
* @copyright nhcarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
|
||||
import {
|
||||
ApplicationCommandOptionType,
|
||||
InteractionContextType,
|
||||
} from "discord.js";
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
import { tag } from "../../src/commands/tag.js";
|
||||
import { errorHandler } from "../../src/utils/errorHandler.js";
|
||||
|
||||
vi.mock("../../src/utils/errorHandler.ts", () => {
|
||||
return {
|
||||
errorHandler: vi.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
describe("tag command", () => {
|
||||
it("should have the correct data", () => {
|
||||
expect.assertions(17);
|
||||
expect(tag.data.name, "did not have the correct name").toBe("tag");
|
||||
expect(tag.data.name.length, "name is too long").toBeLessThanOrEqual(32);
|
||||
expect(tag.data.name, "name has invalid characters").toMatch(
|
||||
/^[-_\p{L}\p{N}\p{sc=Deva}\p{sc=Thai}]{1,32}$/u,
|
||||
);
|
||||
expect(tag.data.description, "did not have the correct description").toBe(
|
||||
"Add or remove a tag from a task.",
|
||||
);
|
||||
expect(
|
||||
tag.data.description.length,
|
||||
"description is too long",
|
||||
).toBeLessThanOrEqual(100);
|
||||
expect(tag.data.contexts, "did not have the correct context").toStrictEqual(
|
||||
[ InteractionContextType.Guild ],
|
||||
);
|
||||
expect(tag.data.options, "should have 2 options").toHaveLength(2);
|
||||
expect(
|
||||
tag.data.options[0].toJSON().name,
|
||||
"should have the correct name",
|
||||
).toBe("task");
|
||||
expect(
|
||||
tag.data.options[0].toJSON().description,
|
||||
"should have the correct description",
|
||||
).toBe("The task number.");
|
||||
expect(
|
||||
tag.data.options[0].toJSON().required,
|
||||
"should be required",
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
tag.data.options[0].toJSON()["min_value"],
|
||||
"should have a min value of 1",
|
||||
).toBe(1);
|
||||
expect(tag.data.options[0].toJSON().type, "should be a number option").toBe(
|
||||
ApplicationCommandOptionType.Integer,
|
||||
);
|
||||
|
||||
expect(
|
||||
tag.data.options[1].toJSON().name,
|
||||
"should have the correct name",
|
||||
).toBe("tag");
|
||||
expect(
|
||||
tag.data.options[1].toJSON().description,
|
||||
"should have the correct description",
|
||||
).toBe("The tag.");
|
||||
expect(
|
||||
tag.data.options[1].toJSON().required,
|
||||
"should be required",
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
tag.data.options[1].toJSON().choices,
|
||||
"should not have choices",
|
||||
).toBeUndefined();
|
||||
expect(tag.data.options[1].toJSON().type, "should be a string option").toBe(
|
||||
ApplicationCommandOptionType.String,
|
||||
);
|
||||
});
|
||||
|
||||
it("should execute correctly when adding tag", async() => {
|
||||
expect.assertions(4);
|
||||
const mockBot = {
|
||||
database: {
|
||||
tasks: {
|
||||
findFirst: vi.fn().mockResolvedValue({
|
||||
numericalId: 1,
|
||||
tags: [],
|
||||
}),
|
||||
update: vi.fn().mockResolvedValue({}),
|
||||
},
|
||||
},
|
||||
} as never;
|
||||
const mockInteraction = {
|
||||
deferReply: vi.fn(),
|
||||
editReply: vi.fn(),
|
||||
options: {
|
||||
getInteger: vi.fn().mockReturnValue(1),
|
||||
getString: vi.fn().mockReturnValue("discord"),
|
||||
},
|
||||
} as never;
|
||||
await tag.run(mockBot, mockInteraction);
|
||||
expect(
|
||||
mockInteraction.deferReply,
|
||||
"should defer the reply",
|
||||
).toHaveBeenCalledWith({ ephemeral: true });
|
||||
expect(
|
||||
mockBot.database.tasks.findFirst,
|
||||
"should call findFirst",
|
||||
).toHaveBeenCalledWith({
|
||||
where: {
|
||||
numericalId: 1,
|
||||
},
|
||||
});
|
||||
expect(
|
||||
mockBot.database.tasks.update,
|
||||
"should call update",
|
||||
).toHaveBeenCalledWith({
|
||||
data: {
|
||||
tags: {
|
||||
push: "discord",
|
||||
},
|
||||
},
|
||||
where: {
|
||||
numericalId: 1,
|
||||
},
|
||||
});
|
||||
expect(
|
||||
mockInteraction.editReply,
|
||||
"should call editReply",
|
||||
).toHaveBeenCalledWith({
|
||||
content: "Tag discord added to task 1.",
|
||||
});
|
||||
});
|
||||
|
||||
it("should execute correctly when removing tag", async() => {
|
||||
expect.assertions(4);
|
||||
const mockBot = {
|
||||
database: {
|
||||
tasks: {
|
||||
findFirst: vi.fn().mockResolvedValue({
|
||||
numericalId: 1,
|
||||
tags: [ "discord", "website" ],
|
||||
}),
|
||||
update: vi.fn().mockResolvedValue({}),
|
||||
},
|
||||
},
|
||||
} as never;
|
||||
const mockInteraction = {
|
||||
deferReply: vi.fn(),
|
||||
editReply: vi.fn(),
|
||||
options: {
|
||||
getInteger: vi.fn().mockReturnValue(1),
|
||||
getString: vi.fn().mockReturnValue("discord"),
|
||||
},
|
||||
} as never;
|
||||
await tag.run(mockBot, mockInteraction);
|
||||
expect(
|
||||
mockInteraction.deferReply,
|
||||
"should defer the reply",
|
||||
).toHaveBeenCalledWith({ ephemeral: true });
|
||||
expect(
|
||||
mockBot.database.tasks.findFirst,
|
||||
"should call findFirst",
|
||||
).toHaveBeenCalledWith({
|
||||
where: {
|
||||
numericalId: 1,
|
||||
},
|
||||
});
|
||||
expect(
|
||||
mockBot.database.tasks.update,
|
||||
"should call update",
|
||||
).toHaveBeenCalledWith({
|
||||
data: {
|
||||
tags: [ "website" ],
|
||||
},
|
||||
where: {
|
||||
numericalId: 1,
|
||||
},
|
||||
});
|
||||
expect(
|
||||
mockInteraction.editReply,
|
||||
"should call editReply",
|
||||
).toHaveBeenCalledWith({
|
||||
content: "Tag discord removed from task 1.",
|
||||
});
|
||||
});
|
||||
|
||||
it("should execute correctly when task not found", async() => {
|
||||
expect.assertions(4);
|
||||
vi.resetAllMocks();
|
||||
const mockBot = {
|
||||
database: {
|
||||
tasks: {
|
||||
findFirst: vi.fn().mockResolvedValue(null),
|
||||
update: vi.fn().mockResolvedValue({}),
|
||||
},
|
||||
},
|
||||
} as never;
|
||||
const mockInteraction = {
|
||||
deferReply: vi.fn(),
|
||||
editReply: vi.fn(),
|
||||
options: {
|
||||
getInteger: vi.fn().mockReturnValue(1),
|
||||
getString: vi.fn().mockReturnValue("low"),
|
||||
},
|
||||
} as never;
|
||||
await tag.run(mockBot, mockInteraction);
|
||||
expect(
|
||||
mockInteraction.deferReply,
|
||||
"should defer the reply",
|
||||
).toHaveBeenCalledWith({ ephemeral: true });
|
||||
expect(
|
||||
mockBot.database.tasks.findFirst,
|
||||
"should call findFirst",
|
||||
).toHaveBeenCalledWith({
|
||||
where: {
|
||||
numericalId: 1,
|
||||
},
|
||||
});
|
||||
expect(
|
||||
mockBot.database.tasks.update,
|
||||
"should not call update",
|
||||
).not.toHaveBeenCalled();
|
||||
expect(
|
||||
mockInteraction.editReply,
|
||||
"should call editReply",
|
||||
).toHaveBeenCalledWith({
|
||||
content: "Task 1 not found.",
|
||||
});
|
||||
});
|
||||
|
||||
it("should handle errors correctly", async() => {
|
||||
expect.assertions(1);
|
||||
await tag.run(
|
||||
{} as never,
|
||||
{ editReply: vi.fn(), replied: false, reply: vi.fn() } as never,
|
||||
);
|
||||
expect(errorHandler, "should call error handler").toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("should handle errors with interaction.reply correctly", async() => {
|
||||
expect.assertions(1);
|
||||
vi.resetAllMocks();
|
||||
await tag.run(
|
||||
{} as never,
|
||||
{ editReply: vi.fn(), replied: true, reply: vi.fn() } as never,
|
||||
);
|
||||
expect(errorHandler, "should call error handler").toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,217 @@
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
/**
|
||||
* @copyright nhcarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
|
||||
import {
|
||||
ActionRowBuilder,
|
||||
InteractionContextType,
|
||||
ModalBuilder,
|
||||
TextInputBuilder,
|
||||
TextInputStyle,
|
||||
} from "discord.js";
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
import { update } from "../../src/commands/update.js";
|
||||
import { errorHandler } from "../../src/utils/errorHandler.js";
|
||||
|
||||
vi.mock("discord.js", async() => {
|
||||
const actual = await vi.importActual("discord.js");
|
||||
return {
|
||||
...actual,
|
||||
ActionRowBuilder: vi.fn(() => {
|
||||
return {
|
||||
addComponents: vi.fn().mockReturnThis(),
|
||||
};
|
||||
}),
|
||||
ModalBuilder: vi.fn(() => {
|
||||
return {
|
||||
addComponents: vi.fn().mockReturnThis(),
|
||||
setCustomId: vi.fn().mockReturnThis(),
|
||||
setTitle: vi.fn().mockReturnThis(),
|
||||
};
|
||||
}),
|
||||
TextInputBuilder: vi.fn(() => {
|
||||
return {
|
||||
setCustomId: vi.fn().mockReturnThis(),
|
||||
setLabel: vi.fn().mockReturnThis(),
|
||||
setRequired: vi.fn().mockReturnThis(),
|
||||
setStyle: vi.fn().mockReturnThis(),
|
||||
};
|
||||
}),
|
||||
TextInputStyle: { Paragraph: "Paragraph", Short: "Short" },
|
||||
};
|
||||
});
|
||||
vi.mock("../../src/utils/errorHandler.ts", () => {
|
||||
return {
|
||||
errorHandler: vi.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
describe("update command", () => {
|
||||
it("should have the correct data", () => {
|
||||
expect.assertions(7);
|
||||
expect(update.data.name, "did not have the correct name").toBe("update");
|
||||
expect(update.data.name.length, "name is too long").toBeLessThanOrEqual(32);
|
||||
expect(update.data.name, "name has invalid characters").toMatch(
|
||||
/^[-_\p{L}\p{N}\p{sc=Deva}\p{sc=Thai}]{1,32}$/u,
|
||||
);
|
||||
expect(
|
||||
update.data.description,
|
||||
"did not have the correct description",
|
||||
).toBe("Update a task.");
|
||||
expect(
|
||||
update.data.description.length,
|
||||
"description is too long",
|
||||
).toBeLessThanOrEqual(100);
|
||||
expect(
|
||||
update.data.contexts,
|
||||
"did not have the correct context",
|
||||
).toStrictEqual([ InteractionContextType.Guild ]);
|
||||
expect(update.data.options, "should not have options").toHaveLength(0);
|
||||
});
|
||||
|
||||
it("should execute correctly", async() => {
|
||||
expect.assertions(28);
|
||||
const mockBot = {} as never;
|
||||
const mockInteraction = {
|
||||
showModal: vi.fn(),
|
||||
} as never;
|
||||
await update.run(mockBot, mockInteraction);
|
||||
expect(TextInputBuilder, "should create text inputs").toHaveBeenCalledTimes(
|
||||
4,
|
||||
);
|
||||
const [ taskNumberCall, titleCall, descriptionCall, dueCall ]
|
||||
= vi.mocked(TextInputBuilder).mock.results;
|
||||
expect(
|
||||
taskNumberCall.value.setLabel,
|
||||
"should set task number label",
|
||||
).toHaveBeenCalledWith("Task Number");
|
||||
expect(
|
||||
taskNumberCall.value.setRequired,
|
||||
"should set task number required",
|
||||
).toHaveBeenCalledWith(true);
|
||||
expect(
|
||||
taskNumberCall.value.setStyle,
|
||||
"should set task number style",
|
||||
).toHaveBeenCalledWith(TextInputStyle.Short);
|
||||
expect(
|
||||
taskNumberCall.value.setCustomId,
|
||||
"should set task number custom id",
|
||||
).toHaveBeenCalledWith("taskNumber");
|
||||
expect(
|
||||
titleCall.value.setLabel,
|
||||
"should set title label",
|
||||
).toHaveBeenCalledWith("Title");
|
||||
expect(
|
||||
titleCall.value.setRequired,
|
||||
"should not set title required",
|
||||
).toHaveBeenCalledWith(false);
|
||||
expect(
|
||||
titleCall.value.setStyle,
|
||||
"should set title style",
|
||||
).toHaveBeenCalledWith(TextInputStyle.Short);
|
||||
expect(
|
||||
titleCall.value.setCustomId,
|
||||
"should set title custom id",
|
||||
).toHaveBeenCalledWith("title");
|
||||
expect(
|
||||
descriptionCall.value.setLabel,
|
||||
"should set description label",
|
||||
).toHaveBeenCalledWith("Description");
|
||||
expect(
|
||||
descriptionCall.value.setRequired,
|
||||
"should not set description required",
|
||||
).toHaveBeenCalledWith(false);
|
||||
expect(
|
||||
descriptionCall.value.setStyle,
|
||||
"should set description style",
|
||||
).toHaveBeenCalledWith(TextInputStyle.Paragraph);
|
||||
expect(
|
||||
descriptionCall.value.setCustomId,
|
||||
"should set description custom id",
|
||||
).toHaveBeenCalledWith("description");
|
||||
expect(dueCall.value.setLabel, "should set due label").toHaveBeenCalledWith(
|
||||
"Due Date",
|
||||
);
|
||||
expect(
|
||||
dueCall.value.setRequired,
|
||||
"should not set due required",
|
||||
).toHaveBeenCalledWith(false);
|
||||
expect(dueCall.value.setStyle, "should set due style").toHaveBeenCalledWith(
|
||||
TextInputStyle.Short,
|
||||
);
|
||||
expect(
|
||||
dueCall.value.setCustomId,
|
||||
"should set due custom id",
|
||||
).toHaveBeenCalledWith("dueDate");
|
||||
expect(ActionRowBuilder, "should create action rows").toHaveBeenCalledTimes(
|
||||
4,
|
||||
);
|
||||
const [ rowZeroCall, rowOneCall, rowTwoCall, rowThreeCall ]
|
||||
= vi.mocked(ActionRowBuilder).mock.results;
|
||||
expect(
|
||||
rowZeroCall.value.addComponents,
|
||||
"should add number to row",
|
||||
).toHaveBeenCalledWith(taskNumberCall.value);
|
||||
expect(
|
||||
rowOneCall.value.addComponents,
|
||||
"should add title to row",
|
||||
).toHaveBeenCalledWith(titleCall.value);
|
||||
expect(
|
||||
rowTwoCall.value.addComponents,
|
||||
"should add description to row",
|
||||
).toHaveBeenCalledWith(descriptionCall.value);
|
||||
expect(
|
||||
rowThreeCall.value.addComponents,
|
||||
"should add due to row",
|
||||
).toHaveBeenCalledWith(dueCall.value);
|
||||
expect(ModalBuilder, "should update modal").toHaveBeenCalledTimes(1);
|
||||
expect(
|
||||
vi.mocked(ModalBuilder).mock.results[0].value.setTitle,
|
||||
"should set modal title",
|
||||
).toHaveBeenCalledWith("Update Task");
|
||||
expect(
|
||||
vi.mocked(ModalBuilder).mock.results[0].value.addComponents,
|
||||
"should add components to modal",
|
||||
).toHaveBeenCalledTimes(1);
|
||||
|
||||
expect(
|
||||
vi.mocked(ModalBuilder).mock.results[0].value.addComponents,
|
||||
"should add components to modal",
|
||||
).toHaveBeenCalledWith(
|
||||
rowZeroCall.value,
|
||||
rowOneCall.value,
|
||||
rowTwoCall.value,
|
||||
rowThreeCall.value,
|
||||
);
|
||||
expect(
|
||||
vi.mocked(ModalBuilder).mock.results[0].value.setCustomId,
|
||||
"should set modal custom id",
|
||||
).toHaveBeenCalledWith("update-task");
|
||||
expect(
|
||||
mockInteraction.showModal,
|
||||
"should display the modal",
|
||||
).toHaveBeenCalledWith(ModalBuilder.mock.results[0].value);
|
||||
});
|
||||
|
||||
it("should handle errors correctly", async() => {
|
||||
expect.assertions(1);
|
||||
await update.run(
|
||||
{} as never,
|
||||
{ editReply: vi.fn(), replied: false, reply: vi.fn() } as never,
|
||||
);
|
||||
expect(errorHandler, "should call error handler").toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("should handle errors with interaction.reply correctly", async() => {
|
||||
expect.assertions(1);
|
||||
vi.resetAllMocks();
|
||||
await update.run(
|
||||
{} as never,
|
||||
{ editReply: vi.fn(), replied: true, reply: vi.fn() } as never,
|
||||
);
|
||||
expect(errorHandler, "should call error handler").toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,271 @@
|
||||
/**
|
||||
* @copyright nhcarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
|
||||
import {
|
||||
ApplicationCommandOptionType,
|
||||
InteractionContextType,
|
||||
} from "discord.js";
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
import { view } from "../../src/commands/view.js";
|
||||
import { errorHandler } from "../../src/utils/errorHandler.js";
|
||||
|
||||
vi.mock("../../src/utils/errorHandler.ts", () => {
|
||||
return {
|
||||
errorHandler: vi.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
describe("view command", () => {
|
||||
it("should have the correct data", () => {
|
||||
expect.assertions(11);
|
||||
expect(view.data.name, "did not have the correct name").toBe("view");
|
||||
expect(view.data.name.length, "name is too long").toBeLessThanOrEqual(32);
|
||||
expect(view.data.name, "name has invalid characters").toMatch(
|
||||
/^[-_\p{L}\p{N}\p{sc=Deva}\p{sc=Thai}]{1,32}$/u,
|
||||
);
|
||||
expect(view.data.description, "did not have the correct description").toBe(
|
||||
"View a task by its ID.",
|
||||
);
|
||||
expect(
|
||||
view.data.description.length,
|
||||
"description is too long",
|
||||
).toBeLessThanOrEqual(100);
|
||||
expect(
|
||||
view.data.contexts,
|
||||
"did not have the correct context",
|
||||
).toStrictEqual([ InteractionContextType.Guild ]);
|
||||
expect(view.data.options, "should have 1 option").toHaveLength(1);
|
||||
expect(
|
||||
view.data.options[0].toJSON().name,
|
||||
"did not have the correct option name",
|
||||
).toBe("id");
|
||||
expect(
|
||||
view.data.options[0].toJSON().description,
|
||||
"did not have the correct option description",
|
||||
).toBe("The ID of the task to view.");
|
||||
expect(
|
||||
view.data.options[0].toJSON().required,
|
||||
"did not have the correct option required value",
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
view.data.options[0].toJSON().type,
|
||||
"did not have the correct option type",
|
||||
).toBe(ApplicationCommandOptionType.Integer);
|
||||
});
|
||||
|
||||
it("should execute correctly", async() => {
|
||||
expect.assertions(3);
|
||||
const mockBot = {
|
||||
database: {
|
||||
tasks: {
|
||||
findUnique: vi.fn().mockReturnValue({
|
||||
assignees: [ "123" ],
|
||||
completed: false,
|
||||
deleted: false,
|
||||
description: "Task 1 description",
|
||||
dueAt: new Date("2021-10-10"),
|
||||
numericalId: 1,
|
||||
priority: "critical",
|
||||
tags: [ "discord" ],
|
||||
title: "Task 1",
|
||||
}),
|
||||
},
|
||||
},
|
||||
} as never;
|
||||
const mockInteraction = {
|
||||
deferReply: vi.fn(),
|
||||
editReply: vi.fn(),
|
||||
options: { getInteger: vi.fn().mockReturnValue(1) },
|
||||
} as never;
|
||||
await view.run(mockBot, mockInteraction);
|
||||
expect(
|
||||
mockInteraction.deferReply,
|
||||
"should defer the reply",
|
||||
).toHaveBeenCalledWith({ ephemeral: true });
|
||||
expect(
|
||||
mockBot.database.tasks.findUnique,
|
||||
"should query database",
|
||||
).toHaveBeenCalledWith({
|
||||
where: {
|
||||
numericalId: 1,
|
||||
},
|
||||
});
|
||||
expect(
|
||||
mockInteraction.editReply,
|
||||
"should call editReply",
|
||||
).toHaveBeenCalledWith({
|
||||
embeds: [
|
||||
{
|
||||
description: "Task 1 description",
|
||||
fields: [
|
||||
{ inline: true, name: "Priority", value: "critical" },
|
||||
{ inline: true, name: "Completed", value: "No" },
|
||||
{ name: "Tag", value: "discord" },
|
||||
{ name: "Assignee", value: "<@123>" },
|
||||
],
|
||||
title: "Task 1",
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("should execute correctly with a completed task", async() => {
|
||||
expect.assertions(3);
|
||||
const mockBot = {
|
||||
database: {
|
||||
tasks: {
|
||||
findUnique: vi.fn().mockReturnValue({
|
||||
assignees: [ "123" ],
|
||||
completed: true,
|
||||
deleted: false,
|
||||
description: "Task 1 description",
|
||||
dueAt: new Date("2021-10-10"),
|
||||
numericalId: 1,
|
||||
priority: "critical",
|
||||
tags: [ "discord" ],
|
||||
title: "Task 1",
|
||||
}),
|
||||
},
|
||||
},
|
||||
} as never;
|
||||
const mockInteraction = {
|
||||
deferReply: vi.fn(),
|
||||
editReply: vi.fn(),
|
||||
options: { getInteger: vi.fn().mockReturnValue(1) },
|
||||
} as never;
|
||||
await view.run(mockBot, mockInteraction);
|
||||
expect(
|
||||
mockInteraction.deferReply,
|
||||
"should defer the reply",
|
||||
).toHaveBeenCalledWith({ ephemeral: true });
|
||||
expect(
|
||||
mockBot.database.tasks.findUnique,
|
||||
"should query database",
|
||||
).toHaveBeenCalledWith({
|
||||
where: {
|
||||
numericalId: 1,
|
||||
},
|
||||
});
|
||||
expect(
|
||||
mockInteraction.editReply,
|
||||
"should call editReply",
|
||||
).toHaveBeenCalledWith({
|
||||
embeds: [
|
||||
{
|
||||
description: "Task 1 description",
|
||||
fields: [
|
||||
{ inline: true, name: "Priority", value: "critical" },
|
||||
{ inline: true, name: "Completed", value: "Yes" },
|
||||
{ name: "Tag", value: "discord" },
|
||||
{ name: "Assignee", value: "<@123>" },
|
||||
],
|
||||
title: "Task 1",
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("should execute correctly with a deleted task", async() => {
|
||||
expect.assertions(3);
|
||||
const mockBot = {
|
||||
database: {
|
||||
tasks: {
|
||||
findUnique: vi.fn().mockReturnValue({
|
||||
assignees: [ "123" ],
|
||||
completed: false,
|
||||
deleted: true,
|
||||
description: "Task 1 description",
|
||||
dueAt: new Date("2021-10-10"),
|
||||
numericalId: 1,
|
||||
priority: "critical",
|
||||
tags: [ "discord" ],
|
||||
title: "Task 1",
|
||||
}),
|
||||
},
|
||||
},
|
||||
} as never;
|
||||
const mockInteraction = {
|
||||
deferReply: vi.fn(),
|
||||
editReply: vi.fn(),
|
||||
options: { getInteger: vi.fn().mockReturnValue(1) },
|
||||
} as never;
|
||||
await view.run(mockBot, mockInteraction);
|
||||
expect(
|
||||
mockInteraction.deferReply,
|
||||
"should defer the reply",
|
||||
).toHaveBeenCalledWith({ ephemeral: true });
|
||||
expect(
|
||||
mockBot.database.tasks.findUnique,
|
||||
"should query database",
|
||||
).toHaveBeenCalledWith({
|
||||
where: {
|
||||
numericalId: 1,
|
||||
},
|
||||
});
|
||||
expect(
|
||||
mockInteraction.editReply,
|
||||
"should call editReply",
|
||||
).toHaveBeenCalledWith({
|
||||
content: "Task 1 has been deleted.",
|
||||
});
|
||||
});
|
||||
|
||||
it("should execute correctly when task not found", async() => {
|
||||
expect.assertions(3);
|
||||
const mockBot = {
|
||||
database: {
|
||||
tasks: {
|
||||
findUnique: vi.fn().mockReturnValue(null),
|
||||
},
|
||||
},
|
||||
} as never;
|
||||
const mockInteraction = {
|
||||
deferReply: vi.fn(),
|
||||
editReply: vi.fn(),
|
||||
options: {
|
||||
getInteger: vi.fn().mockReturnValue(1),
|
||||
},
|
||||
} as never;
|
||||
await view.run(mockBot, mockInteraction);
|
||||
expect(
|
||||
mockInteraction.deferReply,
|
||||
"should defer the reply",
|
||||
).toHaveBeenCalledWith({ ephemeral: true });
|
||||
expect(
|
||||
mockBot.database.tasks.findUnique,
|
||||
"should query database",
|
||||
).toHaveBeenCalledWith({
|
||||
where: {
|
||||
numericalId: 1,
|
||||
},
|
||||
});
|
||||
expect(
|
||||
mockInteraction.editReply,
|
||||
"should call editReply",
|
||||
).toHaveBeenCalledWith({
|
||||
content: "Task 1 not found.",
|
||||
});
|
||||
});
|
||||
|
||||
it("should handle errors correctly", async() => {
|
||||
expect.assertions(1);
|
||||
await view.run(
|
||||
{} as never,
|
||||
{ editReply: vi.fn(), replied: false, reply: vi.fn() } as never,
|
||||
);
|
||||
expect(errorHandler, "should call error handler").toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("should handle errors with interaction.reply correctly", async() => {
|
||||
expect.assertions(1);
|
||||
vi.resetAllMocks();
|
||||
await view.run(
|
||||
{} as never,
|
||||
{ editReply: vi.fn(), replied: true, reply: vi.fn() } as never,
|
||||
);
|
||||
expect(errorHandler, "should call error handler").toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,18 @@
|
||||
|
||||
/**
|
||||
* @copyright nhcarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
|
||||
import { GatewayIntentBits } from "discord.js";
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { intents } from "../../src/config/intents.ts";
|
||||
|
||||
describe("intents", () => {
|
||||
it("should include expected intents", () => {
|
||||
expect.assertions(1);
|
||||
expect(intents, "missing guilds").
|
||||
toContain(GatewayIntentBits.Guilds);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,156 @@
|
||||
/**
|
||||
* @copyright nhcarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import { onInteractionCreate } from "../../src/events/onInteractionCreate.js";
|
||||
import { errorHandler } from "../../src/utils/errorHandler.js";
|
||||
|
||||
vi.mock("../../src/utils/sendDebugLog.ts", () => {
|
||||
return {
|
||||
sendDebugLog: vi.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
const mockBot = {
|
||||
env: {
|
||||
discordDebugWebhook: {
|
||||
send: vi.fn(),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
describe("onInteractionCreate", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("should respond with the correct message when command not in guild",
|
||||
async() => {
|
||||
expect.assertions(1);
|
||||
const mockInteraction = {
|
||||
commandName: "test",
|
||||
inCachedGuild: vi.fn().mockReturnValue(false),
|
||||
isChatInputCommand: vi.fn().mockReturnValue(true),
|
||||
reply: vi.fn(),
|
||||
};
|
||||
await onInteractionCreate(mockBot as never, mockInteraction as never);
|
||||
expect(mockInteraction.reply, "should reply with correct body").
|
||||
toHaveBeenCalledWith({
|
||||
content: "How did you run this out of a guild?",
|
||||
ephemeral: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("should handle chat input commands", async() => {
|
||||
expect.assertions(1);
|
||||
const mockInteraction = {
|
||||
commandName: "test",
|
||||
inCachedGuild: vi.fn().mockReturnValue(true),
|
||||
isChatInputCommand: vi.fn().mockReturnValue(true),
|
||||
reply: vi.fn(),
|
||||
};
|
||||
await onInteractionCreate(mockBot as never, mockInteraction as never);
|
||||
expect(mockInteraction.reply, "should reply with correct body").
|
||||
toHaveBeenCalledWith({
|
||||
content: "Interaction test not found.",
|
||||
ephemeral: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("should respond with the correct message when modal not in guild",
|
||||
async() => {
|
||||
expect.assertions(1);
|
||||
const mockInteraction = {
|
||||
customId: "test",
|
||||
inCachedGuild: vi.fn().mockReturnValue(false),
|
||||
isChatInputCommand: vi.fn().mockReturnValue(false),
|
||||
isModalSubmit: vi.fn().mockReturnValue(true),
|
||||
reply: vi.fn(),
|
||||
};
|
||||
await onInteractionCreate(mockBot as never, mockInteraction as never);
|
||||
expect(mockInteraction.reply, "should reply with correct body").
|
||||
toHaveBeenCalledWith({
|
||||
content: "How did you get a modal outside of a guild?",
|
||||
ephemeral: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("should handle modal submit interactions", async() => {
|
||||
expect.assertions(1);
|
||||
const mockInteraction = {
|
||||
customId: "test",
|
||||
inCachedGuild: vi.fn().mockReturnValue(true),
|
||||
isChatInputCommand: vi.fn().mockReturnValue(false),
|
||||
isModalSubmit: vi.fn().mockReturnValue(true),
|
||||
reply: vi.fn(),
|
||||
};
|
||||
await onInteractionCreate(mockBot as never, mockInteraction as never);
|
||||
expect(mockInteraction.reply, "should reply with correct body").
|
||||
toHaveBeenCalledWith({
|
||||
content: `Modal test has no handler.`,
|
||||
ephemeral: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("should throw an error if the interaction type is unknown", async() => {
|
||||
expect.assertions(1);
|
||||
const mockInteraction = {
|
||||
customId: "test",
|
||||
inCachedGuild: vi.fn().mockReturnValue(true),
|
||||
isAutocomplete: vi.fn().mockReturnValue(false),
|
||||
isChatInputCommand: vi.fn().mockReturnValue(false),
|
||||
isModalSubmit: vi.fn().mockReturnValue(false),
|
||||
reply: vi.fn(),
|
||||
};
|
||||
await onInteractionCreate(mockBot as never, mockInteraction as never);
|
||||
expect(errorHandler, "should call error handler").
|
||||
toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("should call the error handler if an error is thrown", async() => {
|
||||
expect.assertions(1);
|
||||
const mockInteraction = {
|
||||
commandName: "test",
|
||||
editReply: vi.fn(),
|
||||
inCachedGuild: vi.fn().mockReturnValue(true),
|
||||
isAutocomplete: vi.fn().mockReturnValue(false),
|
||||
isChatInputCommand: vi.fn().mockImplementation(() => {
|
||||
throw new Error("Test error");
|
||||
}),
|
||||
replied: true,
|
||||
reply: vi.fn(),
|
||||
};
|
||||
vi.mock("../../src/utils/errorHandler.ts", () => {
|
||||
return {
|
||||
errorHandler: vi.fn(),
|
||||
};
|
||||
});
|
||||
await onInteractionCreate(mockBot as never, mockInteraction as never);
|
||||
expect(errorHandler, "should call error handler").
|
||||
toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("should call the error handler if autocomplete is thrown", async() => {
|
||||
expect.assertions(1);
|
||||
const mockInteraction = {
|
||||
commandName: "test",
|
||||
inCachedGuild: vi.fn().mockReturnValue(true),
|
||||
isAutocomplete: vi.fn().mockReturnValue(true),
|
||||
isChatInputCommand: vi.fn().mockImplementation(() => {
|
||||
throw new Error("Test error");
|
||||
}),
|
||||
reply: vi.fn(),
|
||||
};
|
||||
vi.mock("../../src/utils/errorHandler.ts", () => {
|
||||
return {
|
||||
errorHandler: vi.fn(),
|
||||
};
|
||||
});
|
||||
await onInteractionCreate(mockBot as never, mockInteraction as never);
|
||||
expect(errorHandler, "should call error handler").
|
||||
toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,99 @@
|
||||
/**
|
||||
* @copyright nhcarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import { onReady } from "../../src/events/onReady.js";
|
||||
import { displayCommandCurl } from "../../src/utils/displayCommandCurl.js";
|
||||
import { errorHandler } from "../../src/utils/errorHandler.js";
|
||||
import { sendDebugLog } from "../../src/utils/sendDebugLog.js";
|
||||
|
||||
vi.mock("../../src/utils/sendDebugLog.ts", () => {
|
||||
return {
|
||||
sendDebugLog: vi.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
const mockBot = {
|
||||
discord: {
|
||||
user: {
|
||||
id: "123",
|
||||
},
|
||||
},
|
||||
env: {
|
||||
discordDebugWebhook: {
|
||||
send: vi.fn(),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
describe("onReady", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("should call sendDebugLog with the correct messages", async() => {
|
||||
expect.assertions(4);
|
||||
vi.mock("../../src/utils/errorHandler.ts", () => {
|
||||
return {
|
||||
errorHandler: vi.fn(),
|
||||
};
|
||||
});
|
||||
await onReady(mockBot as never);
|
||||
|
||||
expect(errorHandler, "should call error handler").not.toHaveBeenCalled();
|
||||
|
||||
expect(sendDebugLog, "should send debug message").toHaveBeenCalledTimes(2);
|
||||
expect(sendDebugLog, "should send debug message").toHaveBeenCalledWith(
|
||||
mockBot,
|
||||
{
|
||||
content: "Bot has authenticated to Discord.",
|
||||
},
|
||||
);
|
||||
expect(sendDebugLog, "should send CURL string").toHaveBeenCalledWith(
|
||||
mockBot,
|
||||
{
|
||||
files: [
|
||||
{
|
||||
attachment: Buffer.from(displayCommandCurl(mockBot as never)),
|
||||
name: "curl.sh",
|
||||
},
|
||||
],
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it("should not throw an error", async() => {
|
||||
expect.assertions(1);
|
||||
await expect(
|
||||
onReady(mockBot as never),
|
||||
"should not error",
|
||||
).resolves.not.toThrow();
|
||||
});
|
||||
|
||||
it("should call the error handler if an error is thrown", async() => {
|
||||
expect.assertions(1);
|
||||
vi.mock("../../src/utils/errorHandler.ts", () => {
|
||||
return {
|
||||
errorHandler: vi.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("../../src/utils/sendDebugLog.ts", () => {
|
||||
return {
|
||||
sendDebugLog: vi.fn().mockRejectedValue(new Error("Test error")),
|
||||
};
|
||||
});
|
||||
|
||||
await onReady({} as never);
|
||||
|
||||
expect(errorHandler, "should call error handler").toHaveBeenCalledTimes(1);
|
||||
vi.mock("../../src/utils/sendDebugLog.ts", () => {
|
||||
return {
|
||||
sendDebugLog: vi.fn(),
|
||||
};
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -3,3 +3,132 @@
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
import { Client, Events, WebhookClient } from "discord.js";
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
||||
import { intents } from "../src/config/intents.js";
|
||||
import { onInteractionCreate } from "../src/events/onInteractionCreate.js";
|
||||
import { onReady } from "../src/events/onReady.js";
|
||||
import { boot } from "../src/index.js";
|
||||
import { validateEnvironmentVariables }
|
||||
from "../src/utils/validateEnvironmentVariables.js";
|
||||
|
||||
vi.mock("@prisma/client");
|
||||
vi.mock("discord.js");
|
||||
vi.mock("../src/utils/sendDebugLog.js");
|
||||
vi.mock("../src/utils/validateEnvironmentVariables.js");
|
||||
vi.mock("../src/events/onReady.ts", () => {
|
||||
return {
|
||||
onReady: vi.fn(),
|
||||
};
|
||||
});
|
||||
vi.mock("../src/events/onInteractionCreate.ts", () => {
|
||||
return {
|
||||
onInteractionCreate: vi.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
const mockBot = {
|
||||
database: {
|
||||
$connect: vi.fn().mockResolvedValue(undefined),
|
||||
} as never as PrismaClient,
|
||||
discord: {
|
||||
login: vi.fn().mockResolvedValue("Logged in"),
|
||||
on: vi.fn(),
|
||||
} as never as Client,
|
||||
env: {
|
||||
discordToken: "mock-token",
|
||||
// Add other necessary environment variables
|
||||
},
|
||||
};
|
||||
|
||||
describe("boot function", () => {
|
||||
beforeEach(() => {
|
||||
vi.resetAllMocks();
|
||||
|
||||
vi.mocked(Client).mockImplementation(() => {
|
||||
return mockBot.discord;
|
||||
});
|
||||
vi.mocked(PrismaClient).mockImplementation(() => {
|
||||
return mockBot.database;
|
||||
});
|
||||
vi.mocked(validateEnvironmentVariables).mockReturnValue(
|
||||
mockBot.env as never,
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("should initialize the bot and connect to all platforms", async() => {
|
||||
expect.assertions(9);
|
||||
await boot();
|
||||
|
||||
expect(
|
||||
validateEnvironmentVariables,
|
||||
"should validate env",
|
||||
).toHaveBeenCalledWith();
|
||||
expect(Client, "should construct discord bot").toHaveBeenCalledWith({
|
||||
intents,
|
||||
});
|
||||
expect(PrismaClient, "should construct prisma client").
|
||||
toHaveBeenCalledTimes(1);
|
||||
expect(
|
||||
mockBot.database.$connect,
|
||||
"should connect to database",
|
||||
).toHaveBeenCalledTimes(1);
|
||||
expect(mockBot.discord.on, "did not mount interaction create").
|
||||
toHaveBeenCalledWith(
|
||||
Events.InteractionCreate,
|
||||
expect.any(Function),
|
||||
);
|
||||
const interactionCallback = mockBot.discord.on.mock.calls.find((call) => {
|
||||
return call[0] === Events.InteractionCreate;
|
||||
})?.[1];
|
||||
interactionCallback({} as never);
|
||||
expect(onInteractionCreate, "should call oninteractioncreate").
|
||||
toHaveBeenCalledWith(mockBot, {});
|
||||
expect(mockBot.discord.on, "did not mount ready").toHaveBeenCalledWith(
|
||||
Events.ClientReady,
|
||||
expect.any(Function),
|
||||
);
|
||||
|
||||
const readyCallback = mockBot.discord.on.mock.calls.find((call) => {
|
||||
return call[0] === Events.ClientReady;
|
||||
})?.[1];
|
||||
readyCallback();
|
||||
expect(onReady, "should call onready").toHaveBeenCalledWith(mockBot);
|
||||
expect(
|
||||
mockBot.discord.login,
|
||||
"should login to Discord",
|
||||
).toHaveBeenCalledWith(mockBot.env.discordToken);
|
||||
});
|
||||
|
||||
it("should handle errors and send them to the debug webhook", async() => {
|
||||
expect.assertions(1);
|
||||
const mockError = new Error("Test error");
|
||||
vi.mocked(validateEnvironmentVariables).mockImplementation(() => {
|
||||
throw mockError;
|
||||
});
|
||||
|
||||
const mockWebhookSend = vi.fn().mockResolvedValue(undefined);
|
||||
vi.mocked(WebhookClient).mockImplementation(() => {
|
||||
return {
|
||||
send: mockWebhookSend,
|
||||
} as unknown as WebhookClient;
|
||||
});
|
||||
|
||||
process.env.DISCORD_DEBUG_WEBHOOK = "https://mock-webhook-url";
|
||||
|
||||
await boot();
|
||||
|
||||
expect(
|
||||
mockWebhookSend,
|
||||
"should send directly to webhook",
|
||||
).toHaveBeenCalledWith({
|
||||
content: `Error: ${JSON.stringify(mockError, null, 2)}`,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* @copyright nhcarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
import { defaultCommand } from "../../src/modules/defaultCommand.js";
|
||||
|
||||
describe("defaultCommand", () => {
|
||||
it("should respond with expected values", async() => {
|
||||
expect.assertions(1);
|
||||
const mockInteraction = {
|
||||
commandName: "test",
|
||||
reply: vi.fn(),
|
||||
};
|
||||
await defaultCommand({} as never, mockInteraction as never);
|
||||
expect(mockInteraction.reply, "should reply with correct body").
|
||||
toHaveBeenCalledWith({
|
||||
content: "Interaction test not found.",
|
||||
ephemeral: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,491 @@
|
||||
/**
|
||||
* @copyright nhcarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
import {
|
||||
createModal,
|
||||
defaultModal,
|
||||
updateModal,
|
||||
} from "../../src/modules/modalHandlers.ts";
|
||||
import { errorHandler } from "../../src/utils/errorHandler.ts";
|
||||
|
||||
describe("default modal", () => {
|
||||
it("should send the expected response", async() => {
|
||||
expect.assertions(1);
|
||||
const mockInteraction = {
|
||||
customId: "test",
|
||||
reply: vi.fn(),
|
||||
};
|
||||
await defaultModal({} as never, mockInteraction as never);
|
||||
expect(
|
||||
mockInteraction.reply,
|
||||
"should reply with correct body",
|
||||
).toHaveBeenCalledWith({
|
||||
content: "Modal test has no handler.",
|
||||
ephemeral: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("create modal", () => {
|
||||
it("should send the expected response and save to the database", async() => {
|
||||
expect.assertions(4);
|
||||
const mockInteraction = {
|
||||
customId: "create-modal",
|
||||
deferReply: vi.fn(),
|
||||
editReply: vi.fn(),
|
||||
fields: {
|
||||
getTextInputValue: vi.fn().mockImplementation((value: string) => {
|
||||
return value === "dueDate"
|
||||
? "September 5, 2000"
|
||||
: value;
|
||||
}),
|
||||
},
|
||||
reply: vi.fn(),
|
||||
};
|
||||
const mockBot = {
|
||||
database: {
|
||||
tasks: {
|
||||
count: vi.fn().mockReturnValue(1),
|
||||
create: vi.fn().mockImplementation((data: { data: unknown }) => {
|
||||
return data.data;
|
||||
}),
|
||||
},
|
||||
},
|
||||
};
|
||||
await createModal(mockBot as never, mockInteraction as never);
|
||||
expect(
|
||||
mockInteraction.deferReply,
|
||||
"should defer reply",
|
||||
).toHaveBeenCalledWith({ ephemeral: true });
|
||||
expect(
|
||||
mockBot.database.tasks.count,
|
||||
"should generate an id",
|
||||
).toHaveBeenCalledOnce();
|
||||
expect(
|
||||
mockBot.database.tasks.create,
|
||||
"should save to the database",
|
||||
).toHaveBeenCalledWith({
|
||||
data: {
|
||||
description: "description",
|
||||
dueAt: expect.any(Date),
|
||||
numericalId: 2,
|
||||
title: "title",
|
||||
},
|
||||
});
|
||||
expect(
|
||||
mockInteraction.editReply,
|
||||
"should reply with correct body",
|
||||
).toHaveBeenCalledWith({
|
||||
content: "Task 2 created.",
|
||||
});
|
||||
});
|
||||
|
||||
it("should handle the fallback date", async() => {
|
||||
expect.assertions(4);
|
||||
const mockInteraction = {
|
||||
customId: "create-modal",
|
||||
deferReply: vi.fn(),
|
||||
editReply: vi.fn(),
|
||||
fields: {
|
||||
getTextInputValue: vi.fn().mockImplementation((value: string) => {
|
||||
return value;
|
||||
}),
|
||||
},
|
||||
reply: vi.fn(),
|
||||
};
|
||||
const mockBot = {
|
||||
database: {
|
||||
tasks: {
|
||||
count: vi.fn().mockReturnValue(1),
|
||||
create: vi.fn().mockImplementation((data: { data: unknown }) => {
|
||||
return data.data;
|
||||
}),
|
||||
},
|
||||
},
|
||||
};
|
||||
await createModal(mockBot as never, mockInteraction as never);
|
||||
expect(
|
||||
mockInteraction.deferReply,
|
||||
"should defer reply",
|
||||
).toHaveBeenCalledWith({ ephemeral: true });
|
||||
expect(
|
||||
mockBot.database.tasks.count,
|
||||
"should generate an id",
|
||||
).toHaveBeenCalledOnce();
|
||||
expect(
|
||||
mockBot.database.tasks.create,
|
||||
"should save to the database",
|
||||
).toHaveBeenCalledWith({
|
||||
data: {
|
||||
description: "description",
|
||||
dueAt: expect.any(Date),
|
||||
numericalId: 2,
|
||||
title: "title",
|
||||
},
|
||||
});
|
||||
expect(
|
||||
mockInteraction.editReply,
|
||||
"should reply with correct body",
|
||||
).toHaveBeenCalledWith({
|
||||
content: "Task 2 created.",
|
||||
});
|
||||
});
|
||||
|
||||
it("should call the error handler if an error is thrown", async() => {
|
||||
expect.assertions(1);
|
||||
const mockInteraction = {
|
||||
commandName: "test",
|
||||
inCachedGuild: vi.fn().mockReturnValue(true),
|
||||
isChatInputCommand: vi.fn().mockImplementation(() => {
|
||||
throw new Error("Test error");
|
||||
}),
|
||||
reply: vi.fn(),
|
||||
};
|
||||
vi.mock("../../src/utils/errorHandler.ts", () => {
|
||||
return {
|
||||
errorHandler: vi.fn(),
|
||||
};
|
||||
});
|
||||
await createModal({} as never, mockInteraction as never);
|
||||
expect(errorHandler, "should call error handler").toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("should call the error handler if replied and is thrown", async() => {
|
||||
expect.assertions(1);
|
||||
vi.clearAllMocks();
|
||||
const mockInteraction = {
|
||||
commandName: "test",
|
||||
deferReply: vi.fn().mockImplementation(() => {
|
||||
throw new Error("Test error");
|
||||
}),
|
||||
editReply: vi.fn(),
|
||||
replied: true,
|
||||
};
|
||||
const mockBot = {
|
||||
env: {
|
||||
debugHook: {
|
||||
send: vi.fn(),
|
||||
},
|
||||
},
|
||||
};
|
||||
vi.mock("../../src/utils/errorHandler.ts", () => {
|
||||
return {
|
||||
errorHandler: vi.fn(),
|
||||
};
|
||||
});
|
||||
await createModal(mockBot as never, mockInteraction as never);
|
||||
expect(errorHandler, "should call error handler").toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("update modal", () => {
|
||||
it("should respond when the number input is invalid", async() => {
|
||||
expect.assertions(3);
|
||||
const mockInteraction = {
|
||||
customId: "create-modal",
|
||||
deferReply: vi.fn(),
|
||||
editReply: vi.fn(),
|
||||
fields: {
|
||||
getTextInputValue: vi.fn().mockImplementation((value: string) => {
|
||||
return value === "dueDate"
|
||||
? "September 5, 2000"
|
||||
: value;
|
||||
}),
|
||||
},
|
||||
reply: vi.fn(),
|
||||
};
|
||||
const mockBot = {
|
||||
database: {
|
||||
tasks: {
|
||||
findFirst: vi.fn().mockReturnValue(null),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
await updateModal(mockBot as never, mockInteraction as never);
|
||||
expect(
|
||||
mockInteraction.deferReply,
|
||||
"should defer reply",
|
||||
).toHaveBeenCalledWith({ ephemeral: true });
|
||||
expect(
|
||||
mockBot.database.tasks.findFirst,
|
||||
"should find the task",
|
||||
).not.toHaveBeenCalledOnce();
|
||||
expect(
|
||||
mockInteraction.editReply,
|
||||
"should reply with correct body",
|
||||
).toHaveBeenCalledWith({
|
||||
content: "Invalid task number.",
|
||||
});
|
||||
});
|
||||
|
||||
it("should respond when the task number is not found", async() => {
|
||||
expect.assertions(3);
|
||||
const mockInteraction = {
|
||||
customId: "create-modal",
|
||||
deferReply: vi.fn(),
|
||||
editReply: vi.fn(),
|
||||
fields: {
|
||||
getTextInputValue: vi.fn().mockImplementation((value: string) => {
|
||||
// eslint-disable-next-line no-nested-ternary
|
||||
return value === "dueDate"
|
||||
? "September 5, 2000"
|
||||
: value === "taskNumber"
|
||||
? "1"
|
||||
: value;
|
||||
}),
|
||||
},
|
||||
reply: vi.fn(),
|
||||
};
|
||||
const mockBot = {
|
||||
database: {
|
||||
tasks: {
|
||||
findFirst: vi.fn().mockReturnValue(null),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
await updateModal(mockBot as never, mockInteraction as never);
|
||||
expect(
|
||||
mockInteraction.deferReply,
|
||||
"should defer reply",
|
||||
).toHaveBeenCalledWith({ ephemeral: true });
|
||||
expect(
|
||||
mockBot.database.tasks.findFirst,
|
||||
"should find the task",
|
||||
).toHaveBeenCalledWith({ where: { numericalId: 1 } });
|
||||
expect(
|
||||
mockInteraction.editReply,
|
||||
"should reply with correct body",
|
||||
).toHaveBeenCalledWith({
|
||||
content: "Task 1 not found.",
|
||||
});
|
||||
});
|
||||
|
||||
it("should update the task", async() => {
|
||||
expect.assertions(4);
|
||||
const mockInteraction = {
|
||||
customId: "create-modal",
|
||||
deferReply: vi.fn(),
|
||||
editReply: vi.fn(),
|
||||
fields: {
|
||||
getTextInputValue: vi.fn().mockImplementation((value: string) => {
|
||||
// eslint-disable-next-line no-nested-ternary
|
||||
return value === "dueDate"
|
||||
? "September 5, 2000"
|
||||
: value === "taskNumber"
|
||||
? "1"
|
||||
: value;
|
||||
}),
|
||||
},
|
||||
reply: vi.fn(),
|
||||
};
|
||||
const mockBot = {
|
||||
database: {
|
||||
tasks: {
|
||||
findFirst: vi.fn().mockReturnValue({
|
||||
description: "Naomi's test description",
|
||||
dueAt: new Date("October 1, 2000"),
|
||||
numericalId: 1,
|
||||
title: "Naomi's test task",
|
||||
}),
|
||||
update: vi.fn(),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
await updateModal(mockBot as never, mockInteraction as never);
|
||||
expect(
|
||||
mockInteraction.deferReply,
|
||||
"should defer reply",
|
||||
).toHaveBeenCalledWith({ ephemeral: true });
|
||||
expect(
|
||||
mockBot.database.tasks.findFirst,
|
||||
"should find the task",
|
||||
).toHaveBeenCalledWith({ where: { numericalId: 1 } });
|
||||
expect(
|
||||
mockBot.database.tasks.update,
|
||||
"should update the task",
|
||||
).toHaveBeenCalledWith({
|
||||
data: {
|
||||
description: "description",
|
||||
dueAt: new Date("September 5, 2000"),
|
||||
title: "title",
|
||||
},
|
||||
where: { numericalId: 1 },
|
||||
});
|
||||
expect(
|
||||
mockInteraction.editReply,
|
||||
"should reply with correct body",
|
||||
).toHaveBeenCalledWith({
|
||||
content: "Task 1 updated.",
|
||||
});
|
||||
});
|
||||
|
||||
it("should handle invalid dates", async() => {
|
||||
expect.assertions(4);
|
||||
const mockInteraction = {
|
||||
customId: "create-modal",
|
||||
deferReply: vi.fn(),
|
||||
editReply: vi.fn(),
|
||||
fields: {
|
||||
getTextInputValue: vi.fn().mockImplementation((value: string) => {
|
||||
return value === "taskNumber"
|
||||
? "1"
|
||||
: value;
|
||||
}),
|
||||
},
|
||||
reply: vi.fn(),
|
||||
};
|
||||
const mockBot = {
|
||||
database: {
|
||||
tasks: {
|
||||
findFirst: vi.fn().mockReturnValue({
|
||||
description: "Naomi's test description",
|
||||
dueAt: new Date("October 1, 2000"),
|
||||
numericalId: 1,
|
||||
title: "Naomi's test task",
|
||||
}),
|
||||
update: vi.fn(),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
await updateModal(mockBot as never, mockInteraction as never);
|
||||
expect(
|
||||
mockInteraction.deferReply,
|
||||
"should defer reply",
|
||||
).toHaveBeenCalledWith({ ephemeral: true });
|
||||
expect(
|
||||
mockBot.database.tasks.findFirst,
|
||||
"should find the task",
|
||||
).toHaveBeenCalledWith({ where: { numericalId: 1 } });
|
||||
expect(
|
||||
mockBot.database.tasks.update,
|
||||
"should update the task",
|
||||
).toHaveBeenCalledWith({
|
||||
data: {
|
||||
description: "description",
|
||||
dueAt: expect.any(Date),
|
||||
title: "title",
|
||||
},
|
||||
where: { numericalId: 1 },
|
||||
});
|
||||
expect(
|
||||
mockInteraction.editReply,
|
||||
"should reply with correct body",
|
||||
).toHaveBeenCalledWith({
|
||||
content: "Task 1 updated.",
|
||||
});
|
||||
});
|
||||
|
||||
it("should update the task with all fallback values", async() => {
|
||||
expect.assertions(4);
|
||||
const mockInteraction = {
|
||||
customId: "create-modal",
|
||||
deferReply: vi.fn(),
|
||||
editReply: vi.fn(),
|
||||
fields: {
|
||||
getTextInputValue: vi.fn().mockImplementation((value: string) => {
|
||||
return value === "taskNumber"
|
||||
? "1"
|
||||
: "";
|
||||
}),
|
||||
},
|
||||
reply: vi.fn(),
|
||||
};
|
||||
const mockBot = {
|
||||
database: {
|
||||
tasks: {
|
||||
findFirst: vi.fn().mockReturnValue({
|
||||
description: "Naomi's test description",
|
||||
dueAt: new Date("October 1, 2000"),
|
||||
numericalId: 1,
|
||||
title: "Naomi's test task",
|
||||
}),
|
||||
update: vi.fn(),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
await updateModal(mockBot as never, mockInteraction as never);
|
||||
expect(
|
||||
mockInteraction.deferReply,
|
||||
"should defer reply",
|
||||
).toHaveBeenCalledWith({ ephemeral: true });
|
||||
expect(
|
||||
mockBot.database.tasks.findFirst,
|
||||
"should find the task",
|
||||
).toHaveBeenCalledWith({ where: { numericalId: 1 } });
|
||||
expect(
|
||||
mockBot.database.tasks.update,
|
||||
"should update the task",
|
||||
).toHaveBeenCalledWith({
|
||||
data: {},
|
||||
where: { numericalId: 1 },
|
||||
});
|
||||
expect(
|
||||
mockInteraction.editReply,
|
||||
"should reply with correct body",
|
||||
).toHaveBeenCalledWith({
|
||||
content: "Task 1 updated.",
|
||||
});
|
||||
});
|
||||
|
||||
it("should call the error handler if an error is thrown", async() => {
|
||||
expect.assertions(1);
|
||||
vi.clearAllMocks();
|
||||
const mockInteraction = {
|
||||
commandName: "test",
|
||||
deferReply: vi.fn().mockImplementation(() => {
|
||||
throw new Error("Test error");
|
||||
}),
|
||||
reply: vi.fn(),
|
||||
};
|
||||
const mockBot = {
|
||||
env: {
|
||||
debugHook: {
|
||||
send: vi.fn(),
|
||||
},
|
||||
},
|
||||
};
|
||||
vi.mock("../../src/utils/errorHandler.ts", () => {
|
||||
return {
|
||||
errorHandler: vi.fn(),
|
||||
};
|
||||
});
|
||||
await updateModal(mockBot as never, mockInteraction as never);
|
||||
expect(errorHandler, "should call error handler").toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("should call the error handler if replied and is thrown", async() => {
|
||||
expect.assertions(1);
|
||||
vi.clearAllMocks();
|
||||
const mockInteraction = {
|
||||
commandName: "test",
|
||||
deferReply: vi.fn().mockImplementation(() => {
|
||||
throw new Error("Test error");
|
||||
}),
|
||||
editReply: vi.fn(),
|
||||
replied: true,
|
||||
};
|
||||
const mockBot = {
|
||||
env: {
|
||||
debugHook: {
|
||||
send: vi.fn(),
|
||||
},
|
||||
},
|
||||
};
|
||||
vi.mock("../../src/utils/errorHandler.ts", () => {
|
||||
return {
|
||||
errorHandler: vi.fn(),
|
||||
};
|
||||
});
|
||||
await updateModal(mockBot as never, mockInteraction as never);
|
||||
expect(errorHandler, "should call error handler").toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* @copyright nhcarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { displayCommandCurl } from "../../src/utils/displayCommandCurl.js";
|
||||
|
||||
const expectedCommandObject = `[{"options":[],"name":"create","description":"Create a new task.","contexts":[0],"type":1},{"options":[],"name":"update","description":"Update a task.","contexts":[0],"type":1},{"options":[{"min_value":1,"type":4,"name":"task","description":"The task number.","required":true},{"type":3,"choices":[{"name":"Low","value":"low"},{"name":"Medium","value":"medium"},{"name":"High","value":"high"},{"name":"Critical","value":"critical"},{"name":"None","value":"none"}],"name":"priority","description":"The priority level.","required":true}],"name":"priority","description":"Set the priority of a task.","contexts":[0],"type":1},{"options":[{"min_value":1,"type":4,"name":"task","description":"The task number.","required":true},{"type":3,"name":"tag","description":"The tag.","required":true}],"name":"tag","description":"Add or remove a tag from a task.","contexts":[0],"type":1},{"options":[{"min_value":1,"type":4,"name":"task","description":"The task number.","required":true},{"name":"assignee","description":"The user to (un)assign.","required":true,"type":6}],"name":"assign","description":"Add or remove someone as a task assignee.","contexts":[0],"type":1},{"options":[{"type":3,"choices":[{"name":"Low","value":"low"},{"name":"Medium","value":"medium"},{"name":"High","value":"high"},{"name":"Critical","value":"critical"},{"name":"None","value":"none"}],"name":"priority","description":"List tasks under this priority.","required":false},{"type":3,"name":"tag","description":"List tasks with this tag.","required":false},{"name":"assignee","description":"List tasks assigned to this user.","required":false,"type":6},{"name":"completed","description":"List completed tasks.","required":false,"type":5}],"name":"list","description":"List all tasks, with optional filters.","contexts":[0],"type":1},{"options":[{"min_value":1,"type":4,"name":"id","description":"The ID of the task to view.","required":true}],"name":"view","description":"View a task by its ID.","contexts":[0],"type":1},{"options":[{"min_value":1,"type":4,"name":"id","description":"The ID of the task to complete.","required":true}],"name":"complete","description":"Mark a task as completed.","contexts":[0],"type":1},{"options":[{"min_value":1,"type":4,"name":"id","description":"The ID of the task to delete.","required":true}],"name":"delete","description":"Mark a task as deleted. WARNING: This will scrub all PII from the task and CANNOT be undone.","contexts":[0],"type":1}]`;
|
||||
|
||||
describe("display command curl", () => {
|
||||
it("should return the expected string", () => {
|
||||
expect.assertions(1);
|
||||
const string
|
||||
= displayCommandCurl({ discord: { user: { id: "123" } } } as never);
|
||||
|
||||
expect(string, "did not return valid curl string").toBe(`curl -X PUT -H "Authorization: Bot {TOKEN}" -H "Content-Type: application/json" --data '${expectedCommandObject}' https://discord.com/api/v10/applications/123/commands`);
|
||||
});
|
||||
|
||||
it("should handle the fallback ID", () => {
|
||||
expect.assertions(1);
|
||||
const string = displayCommandCurl({ discord: { user: {} } } as never);
|
||||
|
||||
expect(string, "did not return valid curl string").toBe(`curl -X PUT -H "Authorization: Bot {TOKEN}" -H "Content-Type: application/json" --data '${expectedCommandObject}' https://discord.com/api/v10/applications/{ID}/commands`);
|
||||
});
|
||||
|
||||
it("should include all commands in the payload", () => {
|
||||
expect.assertions(1);
|
||||
const string
|
||||
= displayCommandCurl({ discord: { user: { id: "123" } } } as never);
|
||||
expect(string, "missing create command").toContain("\"name\":\"create\"");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,96 @@
|
||||
/**
|
||||
* @copyright nhcarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
|
||||
import { EmbedBuilder } from "discord.js";
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
import { errorHandler } from "../../src/utils/errorHandler.ts";
|
||||
import { sendDebugLog } from "../../src/utils/sendDebugLog.ts";
|
||||
|
||||
vi.mock("../../src/utils/sendDebugLog.ts", () => {
|
||||
return {
|
||||
sendDebugLog: vi.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
const mockBot = {
|
||||
env: {
|
||||
discordDebugWebhook: {
|
||||
send: vi.fn(),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
describe("errorHandler", () => {
|
||||
it("should call sendDebugLog", async() => {
|
||||
expect.assertions(1);
|
||||
await errorHandler(mockBot as never, "test", new Error("Test error"));
|
||||
expect(sendDebugLog, "should send debug log").toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("should properly format the embed", async() => {
|
||||
expect.assertions(1);
|
||||
const error = new Error("Test error");
|
||||
const id = await errorHandler(mockBot as never, "test", error);
|
||||
expect(sendDebugLog, "should send debug log").toHaveBeenCalledWith(
|
||||
mockBot,
|
||||
{
|
||||
embeds: [
|
||||
new EmbedBuilder({
|
||||
description: error.message,
|
||||
fields: [
|
||||
{
|
||||
name: "Stack",
|
||||
value: `\`\`\`\n${String(error.stack).slice(0, 1000)}`,
|
||||
},
|
||||
],
|
||||
footer: {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
icon_url: undefined,
|
||||
text: `Error ID: ${id}`,
|
||||
},
|
||||
title: "Error: test",
|
||||
}),
|
||||
],
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it("should handle non-error objects", async() => {
|
||||
expect.assertions(1);
|
||||
const id = await errorHandler(mockBot as never, "test", "Test error");
|
||||
expect(sendDebugLog, "should send debug log").toHaveBeenCalledWith(
|
||||
mockBot,
|
||||
{
|
||||
embeds: [
|
||||
new EmbedBuilder({
|
||||
description: "Test error",
|
||||
footer: {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
icon_url: undefined,
|
||||
text: `Error ID: ${id}`,
|
||||
},
|
||||
title: "Error: test",
|
||||
}),
|
||||
],
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it("should call the reply function if provided", async() => {
|
||||
expect.assertions(1);
|
||||
const replyFunction = vi.fn().mockName("reply");
|
||||
const id = await errorHandler(
|
||||
mockBot as never,
|
||||
"test",
|
||||
new Error("Test error"),
|
||||
replyFunction,
|
||||
);
|
||||
expect(replyFunction, "should send error ID to user").toHaveBeenCalledWith({
|
||||
content: `Oops! Something went wrong! Please reach out to us in our [support server](https://chat.nhcarrigan.com) and bring this error ID: ${id}`,
|
||||
ephemeral: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* @copyright nhcarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
import { sendDebugLog } from "../../src/utils/sendDebugLog.ts";
|
||||
|
||||
const mockBot = {
|
||||
discord: {
|
||||
user: {
|
||||
displayAvatarURL: vi.
|
||||
fn().
|
||||
mockReturnValue("https://cdn.nhcarrigan.com/nhcarrigan.png"),
|
||||
username: "Tasks",
|
||||
},
|
||||
},
|
||||
env: {
|
||||
discordDebugWebhook: {
|
||||
send: vi.fn(),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
describe("send debug log", () => {
|
||||
it("should send a message to the webhook", () => {
|
||||
expect.assertions(2);
|
||||
sendDebugLog(mockBot as never, { content: "Test message." });
|
||||
expect(
|
||||
mockBot.env.discordDebugWebhook.send,
|
||||
"should send message",
|
||||
).toHaveBeenCalledTimes(1);
|
||||
expect(
|
||||
mockBot.env.discordDebugWebhook.send,
|
||||
"should send message",
|
||||
).toHaveBeenCalledWith({
|
||||
avatarURL: "https://cdn.nhcarrigan.com/nhcarrigan.png",
|
||||
content: "Test message.",
|
||||
username: "Tasks",
|
||||
});
|
||||
});
|
||||
|
||||
it("should fallback when no user", () => {
|
||||
expect.assertions(2);
|
||||
// @ts-expect-error - Testing fallback when user is undefined.
|
||||
mockBot.discord.user = undefined;
|
||||
vi.resetAllMocks();
|
||||
sendDebugLog(mockBot as never, { content: "Test message." });
|
||||
expect(
|
||||
mockBot.env.discordDebugWebhook.send,
|
||||
"should send message",
|
||||
).toHaveBeenCalledTimes(1);
|
||||
expect(
|
||||
mockBot.env.discordDebugWebhook.send,
|
||||
"should send message",
|
||||
).toHaveBeenCalledWith({
|
||||
avatarURL: "https://cdn.nhcarrigan.com/profile.png",
|
||||
content: "Test message.",
|
||||
username: "RIG Task Bot",
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,63 @@
|
||||
|
||||
/**
|
||||
* @copyright nhcarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
|
||||
import { describe, it, expect, afterAll, beforeAll } from "vitest";
|
||||
import { validateEnvironmentVariables }
|
||||
from "../../src/utils/validateEnvironmentVariables.js";
|
||||
|
||||
describe("validate environment variables", () => {
|
||||
beforeAll(() => {
|
||||
delete process.env.DISCORD_TOKEN;
|
||||
delete process.env.DISCORD_DEBUG_WEBHOOK;
|
||||
});
|
||||
afterAll(() => {
|
||||
delete process.env.DISCORD_TOKEN;
|
||||
delete process.env.DISCORD_DEBUG_WEBHOOK;
|
||||
});
|
||||
|
||||
it("should throw when DISCORD_TOKEN is not set", () => {
|
||||
expect.assertions(1);
|
||||
expect(() => {
|
||||
validateEnvironmentVariables();
|
||||
},
|
||||
"did not throw on missing DISCORD_TOKEN").
|
||||
toThrow(new ReferenceError("DISCORD_TOKEN cannot be undefined."));
|
||||
});
|
||||
|
||||
it("should throw when DISCORD_DEBUG_WEBHOOK is not set", () => {
|
||||
expect.assertions(1);
|
||||
process.env.DISCORD_TOKEN = "test";
|
||||
expect(() => {
|
||||
validateEnvironmentVariables();
|
||||
}
|
||||
, "did not throw on missing DISCORD_DEBUG_WEBHOOK").
|
||||
toThrow(new ReferenceError("DISCORD_DEBUG_WEBHOOK cannot be undefined."));
|
||||
});
|
||||
|
||||
it("should throw when MONGO_URI is not set", () => {
|
||||
expect.assertions(1);
|
||||
process.env.DISCORD_DEBUG_WEBHOOK
|
||||
// eslint-disable-next-line stylistic/max-len
|
||||
= "https://discord.com/api/webhooks/11111111111111111/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
|
||||
expect(() => {
|
||||
validateEnvironmentVariables();
|
||||
},
|
||||
"did not throw on missing MONGO_URI").
|
||||
toThrow(new ReferenceError("MONGO_URI cannot be undefined."));
|
||||
});
|
||||
|
||||
it("should return the expected environment variables", () => {
|
||||
expect.assertions(2);
|
||||
process.env.MONGO_URI = "test";
|
||||
const result = validateEnvironmentVariables();
|
||||
expect(result.discordToken, "did not return correct token").toBe("test");
|
||||
expect(result.discordDebugWebhook.url,
|
||||
"did not correctly instantiate debug hook").
|
||||
// eslint-disable-next-line stylistic/max-len
|
||||
toBe("https://discord.com/api/webhooks/11111111111111111/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user