Split up moon counter from the database (#489)
All checks were successful
Deploy To Stage / build (push) Successful in 32s
Deploy To Stage / deploy (push) Successful in 14s

# Description

Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change.

- Create a separate moon counter to track how many moons a user has
- This is so we can start a user at a specific counter
- Added to the list command a counter to specify how many moons aren't being tracked with the bot

#300

## Type of change

Please delete options that are not relevant.

- [x] New feature (non-breaking change which adds functionality)

# How Has This Been Tested?

Please describe the tests that you ran to verify the changes. Provide instructions so we can reproduce. Please also list any relevant details to your test configuration.

# Checklist

- [ ] My code follows the style guidelines of this project
- [ ] I have performed a self-review of my own code
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [ ] My changes generate no new warnings
- [ ] I have added tests that provide my fix is effective or that my feature works
- [ ] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream modules

Reviewed-on: #489
Reviewed-by: VylpesTester <tester@vylpes.com>
Co-authored-by: Ethan Lane <ethan@vylpes.com>
Co-committed-by: Ethan Lane <ethan@vylpes.com>
This commit is contained in:
Ethan Lane 2025-02-03 17:42:11 +00:00 committed by Vylpes
parent dfae2fd2e4
commit 93ef8a8ae7
22 changed files with 1218 additions and 21 deletions

View file

@ -0,0 +1,25 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`GIVEN happy flow EXPECT embed to be replied 1`] = `
[
{
"color": 5294200,
"description": "**2 -** Test Description",
"thumbnail": {
"url": "https://cdn.discordapp.com/emojis/374131312182689793.webp?size=96&quality=lossless",
},
"title": "undefined Got A Moon!",
},
]
`;
exports[`GIVEN happy flow EXPECT moon to be saved 1`] = `
{
"Description": "Test Description",
"Id": Any<String>,
"MoonNumber": 2,
"UserId": "userId",
"WhenCreated": Any<Date>,
"WhenUpdated": Any<Date>,
}
`;

View file

@ -0,0 +1,40 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`GIVEN happy flow EXPECT interaction to be replied 1`] = `
{
"components": [
{
"components": [
{
"custom_id": "moons list userId -1",
"disabled": true,
"emoji": undefined,
"label": "Previous",
"style": 1,
"type": 2,
},
{
"custom_id": "moons list userId 01",
"disabled": true,
"emoji": undefined,
"label": "Next",
"style": 1,
"type": 2,
},
],
"type": 1,
},
],
"embeds": [
{
"color": 3166394,
"description": "**1 -** Test Descriptio",
"footer": {
"icon_url": undefined,
"text": "Page 01 of 1 · 0 moons",
},
"title": "undefined's Moons",
},
],
}
`;

View file

@ -0,0 +1,194 @@
import { CommandInteraction, EmbedBuilder } from "discord.js";
import AddMoon from "../../../../src/commands/304276391837302787/moons/add";
import Moon from "../../../../src/database/entities/304276391837302787/Moon";
import UserSetting from "../../../../src/database/entities/UserSetting";
describe("GIVEN happy flow", () => {
let repliedWithEmbed: EmbedBuilder[] | undefined;
let savedMoon: Moon | undefined;
const interaction = {
reply: jest.fn((options: any) => {
repliedWithEmbed = options.embeds;
}),
options: {
get: jest.fn()
.mockReturnValueOnce({
value: "Test Description",
}),
},
user: {
id: "userId",
},
} as unknown as CommandInteraction;
const userSetting = {
Value: 1,
UpdateValue: jest.fn(),
Save: jest.fn(),
};
beforeAll(async () => {
Moon.FetchMoonCountByUserId = jest.fn().mockResolvedValue(1);
Moon.prototype.Save = jest.fn().mockImplementation((_, entity: Moon) => {
savedMoon = entity;
});
UserSetting.FetchOneByKey = jest.fn().mockResolvedValue(userSetting);
await AddMoon(interaction);
});
test("EXPECT description option to have been fetched", () => {
expect(interaction.options.get).toHaveBeenCalledTimes(1);
expect(interaction.options.get).toHaveBeenCalledWith("description", true);
});
test("EXPECT UserSetting to have been fetched", () => {
expect(UserSetting.FetchOneByKey).toHaveBeenCalledTimes(1);
expect(UserSetting.FetchOneByKey).toHaveBeenCalledWith("userId", "moons");
});
test("EXPECT moonCount to be updated +1", () => {
expect(userSetting.UpdateValue).toHaveBeenCalledTimes(1);
expect(userSetting.UpdateValue).toHaveBeenCalledWith("2");
});
test("EXPECT setting to be saved", () => {
expect(userSetting.Save).toHaveBeenCalledTimes(1);
expect(userSetting.Save).toHaveBeenCalledWith(UserSetting, userSetting);
});
test("EXPECT moon to be saved", () => {
expect(Moon.prototype.Save).toHaveBeenCalledTimes(1);
expect(Moon.prototype.Save).toHaveBeenCalledWith(Moon, expect.any(Moon));
expect(savedMoon).toBeDefined();
expect(savedMoon).toMatchSnapshot({
Id: expect.any(String),
WhenCreated: expect.any(Date),
WhenUpdated: expect.any(Date),
});
});
test("EXPECT embed to be replied", () => {
expect(interaction.reply).toHaveBeenCalledTimes(1);
expect(repliedWithEmbed).toBeDefined();
expect(repliedWithEmbed).toMatchSnapshot();
});
});
describe("GIVEN description is null", () => {
const interaction = {
reply: jest.fn(),
options: {
get: jest.fn().mockReturnValue({
value: null,
}),
},
} as unknown as CommandInteraction;
beforeEach(async () => {
await AddMoon(interaction);
});
test("EXPECT error replied", () => {
expect(interaction.reply).toHaveBeenCalledTimes(1);
expect(interaction.reply).toHaveBeenCalledWith("Name must be less than 255 characters!");
});
});
describe("GIVEN description is greater than 255 characters", () => {
const optionGet = jest.fn();
const interaction = {
reply: jest.fn(),
options: {
get: optionGet,
},
} as unknown as CommandInteraction;
beforeEach(async () => {
let longString = "";
for (let i = 0; i < 30; i++) {
longString += "1234567890";
}
optionGet.mockReturnValue({
value: longString,
});
await AddMoon(interaction);
});
test("EXPECT error replied", () => {
expect(interaction.reply).toHaveBeenCalledTimes(1);
expect(interaction.reply).toHaveBeenCalledWith("Name must be less than 255 characters!");
});
});
describe("GIVEN moon count setting exists", () => {
const moonSetting = {
Value: "1",
UpdateValue: jest.fn(),
Save: jest.fn(),
};
const interaction = {
reply: jest.fn(),
options: {
get: jest.fn().mockReturnValue({
value: "Test Description",
}),
},
user: {
id: "userId",
},
} as unknown as CommandInteraction;
beforeEach(async () => {
UserSetting.FetchOneByKey = jest.fn().mockResolvedValue(moonSetting);
await AddMoon(interaction);
});
test("EXPECT existing entity to be updated", () => {
expect(moonSetting.UpdateValue).toHaveBeenCalledTimes(1);
expect(moonSetting.UpdateValue).toHaveBeenCalledWith("2");
});
});
describe("GIVEN moon count setting does not exist", () => {
let savedSetting: UserSetting | undefined;
const interaction = {
reply: jest.fn(),
options: {
get: jest.fn().mockReturnValue({
value: "Test Description",
}),
},
user: {
id: "userId",
},
} as unknown as CommandInteraction;
beforeEach(async () => {
UserSetting.FetchOneByKey = jest.fn().mockResolvedValue(null);
UserSetting.prototype.Save = jest.fn().mockImplementation((_, setting: UserSetting) => {
savedSetting = setting;
});
await AddMoon(interaction);
});
test("EXPECT new entity to be created", () => {
// Expect the entity to have the new entity.
// Probably the best way we can really imply a new entity
// that I can think of
expect(savedSetting).toBeDefined();
expect(savedSetting?.Value).toBe("1");
});
});

View file

@ -0,0 +1,249 @@
import { ActionRowBuilder, APIEmbed, ButtonBuilder, CommandInteraction, EmbedBuilder, InteractionReplyOptions } from "discord.js";
import List from "../../../../src/commands/304276391837302787/moons/list";
import Moon from "../../../../src/database/entities/304276391837302787/Moon";
import UserSetting from "../../../../src/database/entities/UserSetting";
describe("GIVEN happy flow", () => {
let repliedWith: InteractionReplyOptions | undefined;
const interaction = {
reply: jest.fn((options) => {
repliedWith = options;
}),
options: {
get: jest.fn()
.mockReturnValueOnce(undefined) // User
.mockReturnValue({
value: "0",
}), // Page
},
user: {
id: "userId",
}
} as unknown as CommandInteraction;
beforeAll(async () => {
Moon.FetchPaginatedMoonsByUserId = jest.fn().mockResolvedValue([
[
{
MoonNumber: 1,
Description: "Test Description",
}
],
1,
]);
UserSetting.FetchOneByKey = jest.fn().mockResolvedValue({
Value: "0",
});
await List(interaction);
});
test("EXPECT interaction to be replied", () => {
expect(interaction.reply).toHaveBeenCalledTimes(1);
expect(repliedWith).toMatchSnapshot();
});
});
describe("GIVEN moons returned is empty", () => {
let repliedWith: InteractionReplyOptions | undefined;
const interaction = {
reply: jest.fn((options) => {
repliedWith = options;
}),
options: {
get: jest.fn()
.mockReturnValueOnce(undefined) // User
.mockReturnValue({
value: "0",
}), // Page
},
user: {
id: "userId",
}
} as unknown as CommandInteraction;
beforeAll(async () => {
Moon.FetchPaginatedMoonsByUserId = jest.fn().mockResolvedValue([
[],
0,
]);
UserSetting.FetchOneByKey = jest.fn().mockResolvedValue({
Value: "0",
});
await List(interaction);
});
test("EXPECT none description", () => {
expect(repliedWith).toBeDefined();
expect(repliedWith!.embeds).toBeDefined();
expect(repliedWith!.embeds!.length).toBe(1);
const repliedWithEmbed = repliedWith!.embeds![0] as EmbedBuilder;
expect(repliedWithEmbed.data.description).toBe("*none*");
});
});
describe("GIVEN it is the first page", () => {
let repliedWith: InteractionReplyOptions | undefined;
const interaction = {
reply: jest.fn((options) => {
repliedWith = options;
}),
options: {
get: jest.fn()
.mockReturnValueOnce(undefined) // User
.mockReturnValue({
value: "0",
}), // Page
},
user: {
id: "userId",
}
} as unknown as CommandInteraction;
beforeAll(async () => {
Moon.FetchPaginatedMoonsByUserId = jest.fn().mockResolvedValue([
[
{
MoonNumber: 1,
Description: "Test Description",
}
],
1,
]);
UserSetting.FetchOneByKey = jest.fn().mockResolvedValue({
Value: "0",
});
await List(interaction);
});
test("EXPECT Previous button to be disabled", () => {
expect(repliedWith).toBeDefined();
expect(repliedWith!.components).toBeDefined();
expect(repliedWith!.components!.length).toBe(1);
const repliedWithRow = repliedWith!.components![0] as ActionRowBuilder<ButtonBuilder>;
expect(repliedWithRow.components[0].data.disabled).toBe(true);
});
});
describe("GIVEN it is the last page", () => {
let repliedWith: InteractionReplyOptions | undefined;
const interaction = {
reply: jest.fn((options) => {
repliedWith = options;
}),
options: {
get: jest.fn()
.mockReturnValueOnce(undefined) // User
.mockReturnValue({
value: "0",
}), // Page
},
user: {
id: "userId",
}
} as unknown as CommandInteraction;
beforeAll(async () => {
Moon.FetchPaginatedMoonsByUserId = jest.fn().mockResolvedValue([
[
{
MoonNumber: 1,
Description: "Test Description",
}
],
1,
]);
UserSetting.FetchOneByKey = jest.fn().mockResolvedValue({
Value: "0",
});
await List(interaction);
});
test("EXPECT Next button to be disabled", () => {
expect(repliedWith).toBeDefined();
expect(repliedWith!.components).toBeDefined();
expect(repliedWith!.components!.length).toBe(1);
const repliedWithRow = repliedWith!.components![0] as ActionRowBuilder<ButtonBuilder>;
expect(repliedWithRow.components[1].data.disabled).toBe(true);
});
describe("GIVEN moon count is greater than the amount of moons in the database", () => {
beforeAll(async () => {
UserSetting.FetchOneByKey = jest.fn().mockResolvedValue({
Value: "2",
});
await List(interaction);
});
test("EXPECT untracked counter to be shown", () => {
const repliedWithEmbed = repliedWith!.embeds![0] as EmbedBuilder;
expect(repliedWithEmbed.data.description).toContain("...plus 1 more untracked");
});
});
});
describe("GIVEN moon count is empty", () => {
let repliedWith: InteractionReplyOptions | undefined;
const interaction = {
reply: jest.fn((options) => {
repliedWith = options;
}),
options: {
get: jest.fn()
.mockReturnValueOnce(undefined) // User
.mockReturnValue({
value: "0",
}), // Page
},
user: {
id: "userId",
}
} as unknown as CommandInteraction;
beforeAll(async () => {
Moon.FetchPaginatedMoonsByUserId = jest.fn().mockResolvedValue([
[],
0,
]);
UserSetting.FetchOneByKey = jest.fn().mockResolvedValue({
Value: "0",
});
await List(interaction);
});
test("EXPECT Next button to be disabled", () => {
expect(repliedWith).toBeDefined();
expect(repliedWith!.components).toBeDefined();
expect(repliedWith!.components!.length).toBe(1);
const repliedWithRow = repliedWith!.components![0] as ActionRowBuilder<ButtonBuilder>;
expect(repliedWithRow.components[1].data.disabled).toBe(true);
});
});