diff --git a/.env.example b/.env.example index 7f873ca..e37a3a0 100644 --- a/.env.example +++ b/.env.example @@ -7,7 +7,7 @@ # any secret values. BOT_TOKEN= -BOT_VER=0.8.3 +BOT_VER=0.8.2 BOT_AUTHOR=Vylpes BOT_OWNERID=147392775707426816 BOT_CLIENTID=682942374040961060 diff --git a/package.json b/package.json index 32242a8..0dc2630 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "card-drop", - "version": "0.8.3", + "version": "0.8.2", "main": "./dist/bot.js", "typings": "./dist", "scripts": { diff --git a/src/buttonEvents/Effects.ts b/src/buttonEvents/Effects.ts index 0f9686b..f185009 100644 --- a/src/buttonEvents/Effects.ts +++ b/src/buttonEvents/Effects.ts @@ -2,7 +2,6 @@ import { ButtonInteraction } from "discord.js"; import { ButtonEvent } from "../type/buttonEvent"; import List from "./Effects/List"; import Use from "./Effects/Use"; -import AppLogger from "../client/appLogger"; export default class Effects extends ButtonEvent { public override async execute(interaction: ButtonInteraction) { @@ -15,8 +14,6 @@ export default class Effects extends ButtonEvent { case "use": await Use.Execute(interaction); break; - default: - AppLogger.LogError("Buttons/Effects", `Unknown action, ${action}`); } } } diff --git a/src/buttonEvents/Reroll.ts b/src/buttonEvents/Reroll.ts index 619fe2a..814cccc 100644 --- a/src/buttonEvents/Reroll.ts +++ b/src/buttonEvents/Reroll.ts @@ -52,17 +52,10 @@ export default class Reroll extends ButtonEvent { try { AppLogger.LogVerbose("Button/Reroll", `Sending next drop: ${randomCard.card.id} (${randomCard.card.name})`); - const files = []; - let imageFileName = ""; + const image = readFileSync(path.join(process.env.DATA_DIR!, "cards", randomCard.card.path)); + const imageFileName = randomCard.card.path.split("/").pop()!; - if (!(randomCard.card.path.startsWith("http://") || randomCard.card.path.startsWith("https://"))) { - const image = readFileSync(path.join(process.env.DATA_DIR!, "cards", randomCard.card.path)); - imageFileName = randomCard.card.path.split("/").pop()!; - - const attachment = new AttachmentBuilder(image, { name: imageFileName }); - - files.push(attachment); - } + const attachment = new AttachmentBuilder(image, { name: imageFileName }); const inventory = await Inventory.FetchOneByCardNumberAndUserId(interaction.user.id, randomCard.card.id); const quantityClaimed = inventory ? inventory.Quantity : 0; @@ -75,7 +68,7 @@ export default class Reroll extends ButtonEvent { await interaction.editReply({ embeds: [ embed ], - files: files, + files: [ attachment ], components: [ row ], }); diff --git a/src/buttonEvents/View.ts b/src/buttonEvents/View.ts index 007a911..f79a1af 100644 --- a/src/buttonEvents/View.ts +++ b/src/buttonEvents/View.ts @@ -19,7 +19,7 @@ export default class View extends ButtonEvent { await interaction.editReply({ embeds: [ searchResult.embed ], components: [ searchResult.row ], - files: searchResult.attachments, + files: [ searchResult.attachment ], }); } } diff --git a/src/commands/drop.ts b/src/commands/drop.ts index f2ea2f5..bd4d3f6 100644 --- a/src/commands/drop.ts +++ b/src/commands/drop.ts @@ -70,17 +70,10 @@ export default class Drop extends Command { await interaction.deferReply(); try { - const files = []; - let imageFileName = ""; + const image = readFileSync(path.join(process.env.DATA_DIR!, "cards", randomCard.card.path)); + const imageFileName = randomCard.card.path.split("/").pop()!; - if (!(randomCard.card.path.startsWith("http://") || randomCard.card.path.startsWith("https://"))) { - const image = readFileSync(path.join(process.env.DATA_DIR!, "cards", randomCard.card.path)); - imageFileName = randomCard.card.path.split("/").pop()!; - - const attachment = new AttachmentBuilder(image, { name: imageFileName }); - - files.push(attachment); - } + const attachment = new AttachmentBuilder(image, { name: imageFileName }); const inventory = await Inventory.FetchOneByCardNumberAndUserId(interaction.user.id, randomCard.card.id); const quantityClaimed = inventory ? inventory.Quantity : 0; @@ -93,7 +86,7 @@ export default class Drop extends Command { await interaction.editReply({ embeds: [ embed ], - files: files, + files: [ attachment ], components: [ row ], }); diff --git a/src/commands/id.ts b/src/commands/id.ts index 0f11aaa..072553d 100644 --- a/src/commands/id.ts +++ b/src/commands/id.ts @@ -43,20 +43,22 @@ export default class Id extends Command { const series = CoreClient.Cards .find(x => x.cards.includes(card))!; - const files = []; - let imageFileName = ""; + let image: Buffer; + const imageFileName = card.path.split("/").pop()!; - if (!(card.path.startsWith("http://") || card.path.startsWith("https://"))) { - const image = readFileSync(path.join(process.env.DATA_DIR!, "cards", card.path)); - imageFileName = card.path.split("/").pop()!; + try { + image = readFileSync(path.join(process.env.DATA_DIR!, "cards", card.path)); + } catch { + AppLogger.LogError("Commands/View", `Unable to fetch image for card ${card.id}.`); - const attachment = new AttachmentBuilder(image, { name: imageFileName }); - - files.push(attachment); + await interaction.reply(`Unable to fetch image for card ${card.id}.`); + return; } await interaction.deferReply(); + const attachment = new AttachmentBuilder(image, { name: imageFileName }); + const inventory = await Inventory.FetchOneByCardNumberAndUserId(interaction.user.id, card.id); const quantityClaimed = inventory ? inventory.Quantity : 0; @@ -65,7 +67,7 @@ export default class Id extends Command { try { await interaction.editReply({ embeds: [ embed ], - files: files, + files: [ attachment ], }); } catch (e) { AppLogger.LogError("Commands/View", `Error sending view for card ${card.id}: ${e}`); diff --git a/src/commands/stage/dropnumber.ts b/src/commands/stage/dropnumber.ts index f9391d2..6819a5c 100644 --- a/src/commands/stage/dropnumber.ts +++ b/src/commands/stage/dropnumber.ts @@ -43,20 +43,20 @@ export default class Dropnumber extends Command { const series = CoreClient.Cards .find(x => x.cards.includes(card))!; - const files = []; - let imageFileName = ""; + let image: Buffer; + const imageFileName = card.path.split("/").pop()!; - if (!(card.path.startsWith("http://") || card.path.startsWith("https://"))) { - const image = readFileSync(path.join(process.env.DATA_DIR!, "cards", card.path)); - imageFileName = card.path.split("/").pop()!; - - const attachment = new AttachmentBuilder(image, { name: imageFileName }); - - files.push(attachment); + try { + image = readFileSync(path.join(process.env.DATA_DIR!, "cards", card.path)); + } catch { + await interaction.reply(`Unable to fetch image for card ${card.id}`); + return; } await interaction.deferReply(); + const attachment = new AttachmentBuilder(image, { name: imageFileName }); + const inventory = await Inventory.FetchOneByCardNumberAndUserId(interaction.user.id, card.id); const quantityClaimed = inventory ? inventory.Quantity : 0; @@ -69,7 +69,7 @@ export default class Dropnumber extends Command { try { await interaction.editReply({ embeds: [ embed ], - files: files, + files: [ attachment ], components: [ row ], }); } catch (e) { diff --git a/src/commands/stage/droprarity.ts b/src/commands/stage/droprarity.ts index 75f8a7f..517a443 100644 --- a/src/commands/stage/droprarity.ts +++ b/src/commands/stage/droprarity.ts @@ -47,18 +47,20 @@ export default class Droprarity extends Command { return; } - const files = []; - let imageFileName = ""; + let image: Buffer; + const imageFileName = card.card.path.split("/").pop()!; - if (!(card.card.path.startsWith("http://") || card.card.path.startsWith("https://"))) { - const image = readFileSync(path.join(process.env.DATA_DIR!, "cards", card.card.path)); - imageFileName = card.card.path.split("/").pop()!; - - const attachment = new AttachmentBuilder(image, { name: imageFileName }); - - files.push(attachment); + try { + image = readFileSync(path.join(process.env.DATA_DIR!, "cards", card.card.path)); + } catch { + await interaction.reply(`Unable to fetch image for card ${card.card.id}`); + return; } + await interaction.deferReply(); + + const attachment = new AttachmentBuilder(image, { name: imageFileName }); + const inventory = await Inventory.FetchOneByCardNumberAndUserId(interaction.user.id, card.card.id); const quantityClaimed = inventory ? inventory.Quantity : 0; @@ -71,7 +73,7 @@ export default class Droprarity extends Command { try { await interaction.editReply({ embeds: [ embed ], - files: files, + files: [ attachment ], components: [ row ], }); } catch (e) { diff --git a/src/commands/view.ts b/src/commands/view.ts index 9a1d447..3ba5621 100644 --- a/src/commands/view.ts +++ b/src/commands/view.ts @@ -34,7 +34,7 @@ export default class View extends Command { await interaction.editReply({ embeds: [ searchResult.embed ], components: [ searchResult.row ], - files: searchResult.attachments, + files: [ searchResult.attachment ], }); } } \ No newline at end of file diff --git a/src/helpers/CardSearchHelper.ts b/src/helpers/CardSearchHelper.ts index df0265c..6e848f4 100644 --- a/src/helpers/CardSearchHelper.ts +++ b/src/helpers/CardSearchHelper.ts @@ -11,7 +11,7 @@ import DropEmbedHelper from "./DropHelpers/DropEmbedHelper.js"; interface ReturnedPage { embed: EmbedBuilder, row: ActionRowBuilder, - attachments: AttachmentBuilder[], + attachment: AttachmentBuilder, results: string[], } @@ -37,18 +37,19 @@ export default class CardSearchHelper { if (!card) return undefined; - const attachments = []; - let imageFileName = ""; + let image: Buffer; + const imageFileName = card.card.path.split("/").pop()!; - if (!(card.card.path.startsWith("http://") || card.card.path.startsWith("https://"))) { - const image = readFileSync(path.join(process.env.DATA_DIR!, "cards", card.card.path)); - imageFileName = card.card.path.split("/").pop()!; + try { + image = readFileSync(path.join(process.env.DATA_DIR!, "cards", card.card.path)); + } catch { + AppLogger.LogError("CardSearchHelper/GenerateSearchQuery", `Unable to fetch image for card ${card.card.id}.`); - const attachment = new AttachmentBuilder(image, { name: imageFileName }); - - attachments.push(attachment); + return undefined; } + const attachment = new AttachmentBuilder(image, { name: imageFileName }); + const inventory = await Inventory.FetchOneByCardNumberAndUserId(userid, card.card.id); const quantityClaimed = inventory?.Quantity ?? 0; @@ -67,7 +68,7 @@ export default class CardSearchHelper { .setStyle(ButtonStyle.Primary) .setDisabled(pages == 1)); - return { embed, row, attachments, results }; + return { embed, row, attachment, results }; } public static async GenerateSearchPageFromQuery(results: string[], userid: string, page: number): Promise { @@ -81,18 +82,19 @@ export default class CardSearchHelper { return undefined; } - const attachments = []; - let imageFileName = ""; + let image: Buffer; + const imageFileName = card.card.path.split("/").pop()!; - if (!(card.card.path.startsWith("http://") || card.card.path.startsWith("https://"))) { - const image = readFileSync(path.join(process.env.DATA_DIR!, "cards", card.card.path)); - imageFileName = card.card.path.split("/").pop()!; + try { + image = readFileSync(path.join(process.env.DATA_DIR!, "cards", card.card.path)); + } catch { + AppLogger.LogError("CardSearchHelper/GenerateSearchPageFromQuery", `Unable to fetch image for card ${card.card.id}.`); - const attachment = new AttachmentBuilder(image, { name: imageFileName }); - - attachments.push(attachment); + return undefined; } + const attachment = new AttachmentBuilder(image, { name: imageFileName }); + const inventory = await Inventory.FetchOneByCardNumberAndUserId(userid, card.card.id); const quantityClaimed = inventory?.Quantity ?? 0; @@ -111,6 +113,6 @@ export default class CardSearchHelper { .setStyle(ButtonStyle.Primary) .setDisabled(page == results.length)); - return { embed, row, attachments, results }; + return { embed, row, attachment, results }; } } diff --git a/src/helpers/DropHelpers/DropEmbedHelper.ts b/src/helpers/DropHelpers/DropEmbedHelper.ts index c739de7..ba1ba47 100644 --- a/src/helpers/DropHelpers/DropEmbedHelper.ts +++ b/src/helpers/DropHelpers/DropEmbedHelper.ts @@ -24,18 +24,12 @@ export default class DropEmbedHelper { AppLogger.LogWarn("CardDropHelperMetadata/GenerateDropEmbed", `Card's colour override is invalid: ${drop.card.id}, ${drop.card.colour}`); } - let imageUrl = `attachment://${imageFileName}`; - - if (drop.card.path.startsWith("http://") || drop.card.path.startsWith("https://")) { - imageUrl = drop.card.path; - } - const embed = new EmbedBuilder() .setTitle(drop.card.name) .setDescription(description) .setFooter({ text: `${CardRarityToString(drop.card.type)} ยท ${drop.card.id}` }) .setColor(colour) - .setImage(imageUrl) + .setImage(`attachment://${imageFileName}`) .addFields([ { name: "Claimed", diff --git a/tests/__functions__/discord.js/GenerateButtonInteractionMock.ts b/tests/__functions__/discord.js/GenerateButtonInteractionMock.ts deleted file mode 100644 index a1024ee..0000000 --- a/tests/__functions__/discord.js/GenerateButtonInteractionMock.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ButtonInteraction } from "../../__types__/discord.js"; - -export default function GenerateButtonInteractionMock(): ButtonInteraction { - return { - guild: {}, - guildId: "guildId", - channel: { - isSendable: jest.fn().mockReturnValue(true), - send: jest.fn(), - }, - deferUpdate: jest.fn(), - editReply: jest.fn(), - message: { - createdAt: new Date(1000 * 60 * 27), - }, - user: { - id: "userId", - }, - customId: "customId", - }; -} \ No newline at end of file diff --git a/tests/__types__/discord.js.ts b/tests/__types__/discord.js.ts index 6506b1d..22c3291 100644 --- a/tests/__types__/discord.js.ts +++ b/tests/__types__/discord.js.ts @@ -1,5 +1,5 @@ export type ButtonInteraction = { - guild: object | null, + guild: {} | null, guildId: string | null, channel: { isSendable: jest.Func, diff --git a/tests/buttonEvents/Claim.test.ts b/tests/buttonEvents/Claim.test.ts index d9e7e6f..226cee0 100644 --- a/tests/buttonEvents/Claim.test.ts +++ b/tests/buttonEvents/Claim.test.ts @@ -2,7 +2,6 @@ import { ButtonInteraction, TextChannel } from "discord.js"; import Claim from "../../src/buttonEvents/Claim"; import { ButtonInteraction as ButtonInteractionType } from "../__types__/discord.js"; import User from "../../src/database/entities/app/User"; -import GenerateButtonInteractionMock from "../__functions__/discord.js/GenerateButtonInteractionMock"; jest.mock("../../src/client/appLogger"); @@ -12,8 +11,23 @@ beforeEach(() => { jest.useFakeTimers(); jest.setSystemTime(1000 * 60 * 30); - interaction = GenerateButtonInteractionMock(); - interaction.customId = "claim cardNumber claimId droppedBy userId"; + interaction = { + guild: {}, + guildId: "guildId", + channel: { + isSendable: jest.fn().mockReturnValue(true), + send: jest.fn(), + }, + deferUpdate: jest.fn(), + editReply: jest.fn(), + message: { + createdAt: new Date(1000 * 60 * 27), + }, + user: { + id: "userId", + }, + customId: "claim cardNumber claimId droppedBy userId", + }; }); afterAll(() => { @@ -106,4 +120,16 @@ test("GIVEN user.RemoveCurrency fails, EXPECT error", async () => { expect(interaction.channel!.send).toHaveBeenCalledWith("[object Object], Not enough currency! You need 10 currency, you have 5!"); expect(interaction.editReply).not.toHaveBeenCalled(); -}); \ No newline at end of file +}); + +test.todo("GIVEN the card has already been claimed, EXPECT error"); + +test.todo("GIVEN the current drop is the latest AND the current user is NOT the one who dropped it, EXPECT error"); + +test.todo("GIVEN the user already has the card in their inventory, EXPECT the entity quantity to be +1"); + +test.todo("GIVEN user does NOT have the card in their inventory, EXPECT a new entity to be created"); + +test.todo("GIVEN card can not be found in the bot, EXPECT error logged"); + +test.todo("EXPECT message to be edited"); \ No newline at end of file diff --git a/tests/buttonEvents/Effects.test.ts b/tests/buttonEvents/Effects.test.ts index f1f86be..18ccc31 100644 --- a/tests/buttonEvents/Effects.test.ts +++ b/tests/buttonEvents/Effects.test.ts @@ -1,66 +1,5 @@ -import { ButtonInteraction } from "discord.js"; -import Effects from "../../src/buttonEvents/Effects"; -import GenerateButtonInteractionMock from "../__functions__/discord.js/GenerateButtonInteractionMock"; -import { ButtonInteraction as ButtonInteractionType } from "../__types__/discord.js"; -import List from "../../src/buttonEvents/Effects/List"; -import Use from "../../src/buttonEvents/Effects/Use"; -import AppLogger from "../../src/client/appLogger"; +test.todo("GIVEN action is list, EXPECT list function to be called"); -jest.mock("../../src/client/appLogger"); -jest.mock("../../src/buttonEvents/Effects/List"); -jest.mock("../../src/buttonEvents/Effects/Use"); +test.todo("GIVEN action is use, EXPECT use function to be called"); -let interaction: ButtonInteractionType; - -beforeEach(() => { - jest.resetAllMocks(); - - interaction = GenerateButtonInteractionMock(); - interaction.customId = "effects"; -}); - -test("GIVEN action is list, EXPECT list function to be called", async () => { - // Arrange - interaction.customId = "effects list"; - - // Act - const effects = new Effects(); - await effects.execute(interaction as unknown as ButtonInteraction); - - // Assert - expect(List).toHaveBeenCalledTimes(1); - expect(List).toHaveBeenCalledWith(interaction); - - expect(Use.Execute).not.toHaveBeenCalled(); -}); - -test("GIVEN action is use, EXPECT use function to be called", async () => { - // Arrange - interaction.customId = "effects use"; - - // Act - const effects = new Effects(); - await effects.execute(interaction as unknown as ButtonInteraction); - - // Assert - expect(Use.Execute).toHaveBeenCalledTimes(1); - expect(Use.Execute).toHaveBeenCalledWith(interaction); - - expect(List).not.toHaveBeenCalled(); -}); - -test("GIVEN action is invalid, EXPECT nothing to be called", async () => { - // Arrange - interaction.customId = "effects invalid"; - - // Act - const effects = new Effects(); - await effects.execute(interaction as unknown as ButtonInteraction); - - // Assert - expect(List).not.toHaveBeenCalled(); - expect(Use.Execute).not.toHaveBeenCalled(); - - expect(AppLogger.LogError).toHaveBeenCalledTimes(1); - expect(AppLogger.LogError).toHaveBeenCalledWith("Buttons/Effects", "Unknown action, invalid"); -}); \ No newline at end of file +test.todo("GIVEN action is unknown, EXPECT nothing to be called"); \ No newline at end of file diff --git a/tests/buttonEvents/Effects/Use.test.ts b/tests/buttonEvents/Effects/Use.test.ts index 86391cc..d5618cc 100644 --- a/tests/buttonEvents/Effects/Use.test.ts +++ b/tests/buttonEvents/Effects/Use.test.ts @@ -1,4 +1,4 @@ -import { ButtonInteraction, InteractionResponse, InteractionUpdateOptions, MessagePayload } from "discord.js"; +import { ButtonInteraction, EmbedBuilder, InteractionResponse } from "discord.js"; import Use from "../../../src/buttonEvents/Effects/Use"; import { mock } from "jest-mock-extended"; import AppLogger from "../../../src/client/appLogger"; @@ -82,7 +82,7 @@ describe("UseConfirm", () => { // Arrange interaction.customId += " unclaimed"; interaction.user.id = "userId"; - interaction.update.mockImplementation(async (opts: string | MessagePayload | InteractionUpdateOptions) => { + interaction.update.mockImplementation(async (opts: any) => { updatedWith = opts; return mock>(); @@ -133,7 +133,7 @@ describe("UseCancel", () => { // Arrange interaction.customId += " unclaimed"; interaction.user.id = "userId"; - interaction.update.mockImplementation(async (opts: string | MessagePayload | InteractionUpdateOptions) => { + interaction.update.mockImplementation(async (opts: any) => { updatedWith = opts; return mock>(); diff --git a/tests/commands/effects.test.ts b/tests/commands/effects.test.ts new file mode 100644 index 0000000..20c8f35 --- /dev/null +++ b/tests/commands/effects.test.ts @@ -0,0 +1,27 @@ +describe("constructor", () => { + test.todo("EXPECT CommandBuilder to be defined"); +}); + +describe("execute", () => { + test.todo("GIVEN interaction is NOT a ChatInputCommand, EXPECT nothing to happen"); + + test.todo("GIVEN subcommand is list, EXPECT list function to be called"); + + test.todo("GIVEN subcommand is use, EXPECT use function to be called"); +}); + +describe("List", () => { + test.todo("GIVEN pageOption is null, EXPECT page to default to 0"); + + test.todo("GIVEN pageOption.value is undefined, EXPECT page to default to 0"); + + test.todo("EXPECT interaction to be replied"); +}); + +describe("Use", () => { + test.todo("GIVEN effectDetail is not found, EXPECT error"); + + test.todo("GIVEN user can not use effect, EXPECT error"); + + test.todo("EXPECT interaction to be replied"); +}); \ No newline at end of file diff --git a/tests/helpers/EffectHelper.test.ts b/tests/helpers/EffectHelper.test.ts new file mode 100644 index 0000000..e41c2bc --- /dev/null +++ b/tests/helpers/EffectHelper.test.ts @@ -0,0 +1,41 @@ +describe("AddEffectToUserInventory", () => { + test.todo("GIVEN effect is found in database, EXPECT effect unused to be incremented"); + + test.todo("GIVEN effect is NOT found in database, EXPECT new effect to be created"); +}); + +describe("UseEffect", () => { + test.todo("GIVEN user can not use effect, EXPECT false returned"); + + test.todo("GIVEN user has effect, EXPECT entity to be updated AND true returned"); +}); + +describe("CanUseEffect", () => { + test.todo("GIVEN effect is not in database, EXPECT false returned"); + + test.todo("GIVEN user does not have any of the effect unused, EXPECT false returned"); + + test.todo("GIVEN effectDetail can not be found, EXPECT false returned"); + + test.todo("GIVEN effect has NOT passed the cooldown, EXPECT false returned"); + + test.todo("GIVEN effect has passed the cooldown, EXPECT true returned"); + + test.todo("GIVEN effect does not have a WhenExpires date supplied, EXPECT true returned"); +}); + +describe("HasEffect", () => { + test.todo("GIVEN effect is NOT found in database, EXPECT false returned"); + + test.todo("GIVEN effect does NOT have an expiry date, EXPECT false returned"); + + test.todo("GIVEN effect.WhenExpires is in the future, EXPECT false returned"); + + test.todo("GIVEN effect.WhenExpires is in the past, EXPECT true returned"); +}); + +describe("GenerateEffectEmbed", () => { + test.todo("GIVEN user has no effects, EXPECT embed to be generated"); + + test.todo("GIVEN user has some effects, EXPECT embed to be generated"); +}); \ No newline at end of file