Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement add note command #70

Merged
merged 1 commit into from
Sep 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type ModerationModule from "../../../index.js";

import { SlashCommand } from "@barry/core";
import DeleteCommand from "./delete/index.js";
import NoteCommand from "./notes/index.js";
import ViewCommand from "./view/index.js";

/**
Expand All @@ -18,7 +19,11 @@ export default class extends SlashCommand<ModerationModule> {
name: "cases",
description: "Manage or view a case.",
guildOnly: true,
children: [DeleteCommand, ViewCommand]
children: [
DeleteCommand,
NoteCommand,
ViewCommand
]
});
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import {
type ApplicationCommandInteraction,
SlashCommand,
SlashCommandOptionBuilder
} from "@barry/core";
import type ModerationModule from "../../../../../index.js";

import { MessageFlags, PermissionFlagsBits } from "@discordjs/core";
import config from "../../../../../../../config.js";

/**
* Options for the add note command.
*/
export interface AddNoteOptions {
/**
* The ID of the case.
*/
case: number;

/**
* The content of the new note.
*/
content: string;
}

/**
* Represents a slash command that adds a note to a case.
*/
export default class extends SlashCommand<ModerationModule> {
/**
* Represents a slash command that adds a note to a case.
*
* @param module The module this command belongs to.
*/
constructor(module: ModerationModule) {
super(module, {
name: "add",
description: "Adds a note to a case.",
defaultMemberPermissions: PermissionFlagsBits.ModerateMembers,
guildOnly: true,
options: {
case: SlashCommandOptionBuilder.integer({
description: "The ID of the case.",
minimum: 1,
required: true
}),
content: SlashCommandOptionBuilder.string({
description: "The content of the new note.",
maximum: 200,
required: true
})
}
});
}

/**
* Add a new note to a case.
*
* @param interaction The interaction that triggered the command.
* @param options The options for the command.
*/
async execute(interaction: ApplicationCommandInteraction, options: AddNoteOptions): Promise<void> {
if (!interaction.isInvokedInGuild()) {
return;
}

const entity = await this.module.cases.get(interaction.guildID, options.case);
if (entity === null) {
return interaction.createMessage({
content: `${config.emotes.error} That case does not exist.`,
flags: MessageFlags.Ephemeral
});
}

const note = await this.module.caseNotes.create({
caseID: options.case,
content: options.content,
creatorID: interaction.user.id,
guildID: interaction.guildID
});

await interaction.createMessage({
content: `${config.emotes.check} Successfully added note \`${note.id}\` to case \`${options.case}\`.`,
flags: MessageFlags.Ephemeral
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import type ModerationModule from "../../../../index.js";

import { PermissionFlagsBits } from "@discordjs/core";
import { SlashCommand } from "@barry/core";
import AddNoteCommand from "./add/index.js";

/**
* Represents a slash command to manage notes on a case.
*/
export default class extends SlashCommand<ModerationModule> {
/**
* Represents a slash command to manage notes on a case.
*
* @param module The module this command belongs to.
*/
constructor(module: ModerationModule) {
super(module, {
name: "notes",
description: "Modify or add notes to a case.",
defaultMemberPermissions: PermissionFlagsBits.ModerateMembers,
guildOnly: true,
children: [AddNoteCommand]
});
}

/**
* Executes the '/cases notes' command. Will throw an error if executed.
*/
execute(): Promise<void> {
throw new Error("Method not implemented.");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { mockCase, mockCaseNote } from "../../../../../mocks/case.js";
import { ApplicationCommandInteraction } from "@barry/core";
import { MessageFlags } from "@discordjs/core";
import { createMockApplication } from "../../../../../../../mocks/application.js";
import { createMockApplicationCommandInteraction } from "@barry/testing";

import AddNoteCommand, { type AddNoteOptions } from "../../../../../../../../src/modules/moderation/commands/chatinput/cases/notes/add/index.js";
import ModerationModule from "../../../../../../../../src/modules/moderation/index.js";

describe("/cases notes add", () => {
let command: AddNoteCommand;
let interaction: ApplicationCommandInteraction;
let options: AddNoteOptions;

beforeEach(() => {
const client = createMockApplication();
const module = new ModerationModule(client);
command = new AddNoteCommand(module);

const data = createMockApplicationCommandInteraction();
interaction = new ApplicationCommandInteraction(data, client, vi.fn());

options = {
case: 34,
content: "Hello World!"
};

vi.spyOn(module.cases, "get").mockResolvedValue(mockCase);
vi.spyOn(module.caseNotes, "create").mockResolvedValue(mockCaseNote);
});

describe("execute", () => {
it("should ignore if the interaction was invoked outside a guild", async () => {
delete interaction.guildID;

await command.execute(interaction, options);

expect(interaction.acknowledged).toBe(false);
});

it("should show an error message if the provided case does not exist", async () => {
vi.spyOn(command.module.cases, "get").mockResolvedValue(null);
const createSpy = vi.spyOn(interaction, "createMessage");

await command.execute(interaction, options);

expect(createSpy).toHaveBeenCalledOnce();
expect(createSpy).toHaveBeenCalledWith({
content: expect.stringContaining("That case does not exist."),
flags: MessageFlags.Ephemeral
});
});

it("should create a new note on a case", async () => {
const createSpy = vi.spyOn(command.module.caseNotes, "create");

await command.execute(interaction, options);

expect(createSpy).toHaveBeenCalledOnce();
expect(createSpy).toHaveBeenCalledWith({
caseID: options.case,
content: options.content,
creatorID: interaction.user.id,
guildID: interaction.guildID
});
});

it("should show a success message if the note is successfully added", async () => {
const createSpy = vi.spyOn(interaction, "createMessage");

await command.execute(interaction, options);

expect(createSpy).toHaveBeenCalledOnce();
expect(createSpy).toHaveBeenCalledWith({
content: expect.stringContaining("Successfully added note `1` to case `34`."),
flags: MessageFlags.Ephemeral
});
});
});
});
28 changes: 28 additions & 0 deletions apps/barry/tests/modules/moderation/mocks/case.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import {
type Case,
type CaseNote,
CaseType
} from "@prisma/client";

const caseID = 34;
const creatorID = "257522665441460225";
const guildID = "68239102456844360";
const userID = "257522665437265920";

export const mockCaseNote: CaseNote = {
caseID: caseID,
content: "Rude!",
createdAt: new Date("01-01-2023"),
creatorID: creatorID,
guildID: guildID,
id: 1
};

export const mockCase: Case = {
createdAt: new Date("01-01-2023"),
creatorID: creatorID,
guildID: guildID,
id: caseID,
type: CaseType.Note,
userID: userID
};