Pull 2nd lot of develop changes into 0.9.0 #439

Merged
Vylpes merged 30 commits from develop into release/0.9.0 2025-05-18 11:05:56 +01:00
15 changed files with 188 additions and 54 deletions
Showing only changes of commit 730af871a0 - Show all commits

View file

@ -1,7 +1,6 @@
import { ButtonInteraction } from "discord.js";
import { ButtonEvent } from "../type/buttonEvent";
import Inventory from "../database/entities/app/Inventory";
import { CoreClient } from "../client/client";
import { default as eClaim } from "../database/entities/app/Claim";
import AppLogger from "../client/appLogger";
import User from "../database/entities/app/User";
@ -23,10 +22,10 @@ export default class Claim extends ButtonEvent {
const userId = interaction.user.id;
const whenDropped = interaction.message.createdAt;
const lastClaimableDate = new Date(Date.now() - (1000 * 60 * 5)); // 5 minutes ago
const lastClaimableDate = new Date(Date.now() - (1000 * 60 * 2)); // 2 minutes ago
if (whenDropped < lastClaimableDate) {
await interaction.channel.send(`${interaction.user}, Cards can only be claimed within 5 minutes of it being dropped!`);
await interaction.channel.send(`${interaction.user}, Cards can only be claimed within 2 minutes of it being dropped!`);
return;
}
@ -36,11 +35,6 @@ export default class Claim extends ButtonEvent {
AppLogger.LogSilly("Button/Claim", `${user.Id} has ${user.Currency} currency`);
if (!user.RemoveCurrency(CardConstants.ClaimCost)) {
await interaction.channel.send(`${interaction.user}, Not enough currency! You need ${CardConstants.ClaimCost} currency, you have ${user.Currency}!`);
return;
}
const claimed = await eClaim.FetchOneByClaimId(claimId);
if (claimed) {
@ -48,13 +42,6 @@ export default class Claim extends ButtonEvent {
return;
}
if (claimId == CoreClient.ClaimId && userId != droppedBy) {
await interaction.channel.send(`${interaction.user}, The latest dropped card can only be claimed by the user who dropped it!`);
return;
}
await user.Save(User, user);
let inventory = await Inventory.FetchOneByCardNumberAndUserId(userId, cardNumber);
if (!inventory) {

View file

@ -35,11 +35,13 @@ export default class Reroll extends ButtonEvent {
AppLogger.LogInfo("Commands/Drop", `New user (${interaction.user.id}) saved to the database`);
}
if (user.Currency < CardConstants.ClaimCost) {
if (!user.RemoveCurrency(CardConstants.ClaimCost)) {
await interaction.reply(`Not enough currency! You need ${CardConstants.ClaimCost} currency, you have ${user.Currency}!`);
return;
}
await user.Save(User, user);
const randomCard = await GetCardsHelper.FetchCard(interaction.user.id);
if (!randomCard) {
@ -78,8 +80,6 @@ export default class Reroll extends ButtonEvent {
files: files,
components: [ row ],
});
CoreClient.ClaimId = claimId;
} catch (e) {
AppLogger.LogError("Button/Reroll", `Error sending next drop for card ${randomCard.card.id}: ${e}`);

View file

@ -31,7 +31,6 @@ export class CoreClient extends Client {
private _webhooks: Webhooks;
private _timerHelper: TimerHelper;
public static ClaimId: string;
public static Environment: Environment;
public static AllowDrops: boolean;
public static Cards: SeriesMetadata[];

View file

@ -43,11 +43,13 @@ export default class Drop extends Command {
AppLogger.LogInfo("Commands/Drop", `New user (${interaction.user.id}) saved to the database`);
}
if (user.Currency < CardConstants.ClaimCost) {
if (!user.RemoveCurrency(CardConstants.ClaimCost)) {
await interaction.reply(ErrorMessages.NotEnoughCurrency(CardConstants.ClaimCost, user.Currency));
return;
}
await user.Save(User, user);
const randomCard = await GetCardsHelper.FetchCard(interaction.user.id);
if (!randomCard) {
@ -86,8 +88,6 @@ export default class Drop extends Command {
components: [ row ],
});
CoreClient.ClaimId = claimId;
} catch (e) {
AppLogger.LogError("Commands/Drop", `Error sending next drop for card ${randomCard.card.id}: ${e}`);

View file

@ -76,7 +76,5 @@ export default class Dropnumber extends Command {
AppLogger.CatchError("Dropnumber", e);
await interaction.editReply("Unable to send next drop. Please try again, and report this if it keeps happening");
}
CoreClient.ClaimId = claimId;
}
}

View file

@ -4,7 +4,6 @@ import { CardRarity, CardRarityChoices, CardRarityParse } from "../../constants/
import { readFileSync } from "fs";
import Inventory from "../../database/entities/app/Inventory";
import { v4 } from "uuid";
import { CoreClient } from "../../client/client";
import path from "path";
import GetCardsHelper from "../../helpers/DropHelpers/GetCardsHelper";
import DropEmbedHelper from "../../helpers/DropHelpers/DropEmbedHelper";
@ -42,7 +41,7 @@ export default class Droprarity extends Command {
return;
}
const card = await GetCardsHelper.GetRandomCardByRarity(rarityType);
const card = GetCardsHelper.GetRandomCardByRarity(rarityType);
if (!card) {
await interaction.reply("Card not found");
@ -81,7 +80,5 @@ export default class Droprarity extends Command {
AppLogger.CatchError("Droprarity", e);
await interaction.editReply("Unable to send next drop. Please try again, and report this if it keeps happening");
}
CoreClient.ClaimId = claimId;
}
}

View file

@ -3,7 +3,6 @@ import { DropResult } from "../../contracts/SeriesMetadata";
import AppLogger from "../../client/appLogger";
import { CardRarityToColour, CardRarityToString } from "../../constants/CardRarity";
import StringTools from "../StringTools";
import CardConstants from "../../constants/CardConstants";
export default class DropEmbedHelper {
public static GenerateDropEmbed(drop: DropResult, quantityClaimed: number, imageFileName: string, claimedBy?: string, currency?: number): EmbedBuilder {
@ -74,12 +73,16 @@ export default class DropEmbedHelper {
.addComponents(
new ButtonBuilder()
.setCustomId(`claim ${drop.card.id} ${claimId} ${userId}`)
.setLabel(`Claim (${CardConstants.ClaimCost} 🪙)`)
.setStyle(ButtonStyle.Primary)
.setLabel("Claim")
.setStyle(ButtonStyle.Success)
.setDisabled(disabled),
new ButtonBuilder()
.setCustomId(`sacrifice confirm ${userId} ${drop.card.id} 1`)
.setLabel(`Sacrifice`)
.setStyle(ButtonStyle.Danger),
new ButtonBuilder()
.setCustomId("reroll")
.setLabel("Reroll")
.setStyle(ButtonStyle.Secondary));
.setEmoji("🔁")
.setStyle(ButtonStyle.Primary),);
}
}

View file

@ -4,7 +4,7 @@ import Claim from "../database/entities/app/Claim";
export default async function PurgeClaims() {
const claims = await Claim.FetchAll(Claim);
const whenLastClaimable = new Date(Date.now() - (1000 * 60 * 5)); // 5 minutes ago
const whenLastClaimable = new Date(Date.now() - (1000 * 60 * 2)); // 2 minutes ago
const expiredClaims = claims.filter(x => x.WhenCreated < whenLastClaimable);

View file

@ -4,9 +4,14 @@ export default function GenerateCommandInteractionMock(options?: {
subcommand?: string,
}): CommandInteraction {
return {
deferReply: jest.fn(),
editReply: jest.fn(),
isChatInputCommand: jest.fn().mockReturnValue(true),
options: {
getSubcommand: jest.fn().mockReturnValue(options?.subcommand),
},
user: {
id: "userId",
},
};
}

View file

@ -19,8 +19,13 @@ export type ButtonInteraction = {
}
export type CommandInteraction = {
deferReply: jest.Func,
editReply: jest.Func,
isChatInputCommand: jest.Func,
options: {
getSubcommand: jest.Func,
},
user: {
id: string,
},
}

View file

@ -1,7 +1,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");
@ -85,25 +84,7 @@ test("GIVEN interaction.message was created more than 5 minutes ago, EXPECT erro
// Assert
expect(interaction.channel!.send).toHaveBeenCalledTimes(1);
expect(interaction.channel!.send).toHaveBeenCalledWith("[object Object], Cards can only be claimed within 5 minutes of it being dropped!");
expect(interaction.editReply).not.toHaveBeenCalled();
});
test("GIVEN user.RemoveCurrency fails, EXPECT error", async () => {
// Arrange
User.FetchOneById = jest.fn().mockResolvedValue({
RemoveCurrency: jest.fn().mockReturnValue(false),
Currency: 5,
});
// Act
const claim = new Claim();
await claim.execute(interaction as unknown as ButtonInteraction);
// Assert
expect(interaction.channel!.send).toHaveBeenCalledTimes(1);
expect(interaction.channel!.send).toHaveBeenCalledWith("[object Object], Not enough currency! You need 10 currency, you have 5!");
expect(interaction.channel!.send).toHaveBeenCalledWith("[object Object], Cards can only be claimed within 2 minutes of it being dropped!");
expect(interaction.editReply).not.toHaveBeenCalled();
});

View file

@ -0,0 +1,7 @@
describe("GIVEN valid conditions", () => {
test.todo("EXPECT user.RemoveCurrency to be called");
test.todo("GIVEN user is saved");
});
test.todo("GIVEN user.RemoveCurrency fails, EXPECT error replied");

142
tests/commands/drop.test.ts Normal file
View file

@ -0,0 +1,142 @@
import { CommandInteraction } from "discord.js";
import Drop from "../../src/commands/drop";
import GenerateCommandInteractionMock from "../__functions__/discord.js/GenerateCommandInteractionMock";
import { CommandInteraction as CommandInteractionMock } from "../__types__/discord.js";
import { CoreClient } from "../../src/client/client";
import Config from "../../src/database/entities/app/Config";
import User from "../../src/database/entities/app/User";
import GetCardsHelper from "../../src/helpers/DropHelpers/GetCardsHelper";
import Inventory from "../../src/database/entities/app/Inventory";
import DropEmbedHelper from "../../src/helpers/DropHelpers/DropEmbedHelper";
import CardConstants from "../../src/constants/CardConstants";
import * as uuid from "uuid";
jest.mock("../../src/database/entities/app/Config");
jest.mock("../../src/database/entities/app/User");
jest.mock("../../src/helpers/DropHelpers/GetCardsHelper");
jest.mock("../../src/database/entities/app/Inventory");
jest.mock("../../src/helpers/DropHelpers/DropEmbedHelper");
jest.mock("uuid");
beforeEach(() => {
(Config.GetValue as jest.Mock).mockResolvedValue("false");
});
describe("execute", () => {
describe("GIVEN user is in the database", () => {
let interaction: CommandInteractionMock;
let user: User;
const randomCard = {
card: {
id: "cardId",
path: "https://google.com/",
}
};
beforeAll(async () => {
// Arrange
CoreClient.AllowDrops = true;
interaction = GenerateCommandInteractionMock();
user = {
Currency: 500,
RemoveCurrency: jest.fn().mockReturnValue(true),
Save: jest.fn(),
} as unknown as User;
(User.FetchOneById as jest.Mock).mockResolvedValue(user);
(GetCardsHelper.FetchCard as jest.Mock).mockResolvedValue(randomCard);
(Inventory.FetchOneByCardNumberAndUserId as jest.Mock).mockResolvedValue({
Quantity: 1,
});
(DropEmbedHelper.GenerateDropEmbed as jest.Mock).mockReturnValue({
type: "Embed",
});
(DropEmbedHelper.GenerateDropButtons as jest.Mock).mockReturnValue({
type: "Button",
});
(uuid.v4 as jest.Mock).mockReturnValue("uuid");
// Act
const drop = new Drop();
await drop.execute(interaction as unknown as CommandInteraction);
});
test("EXPECT user to be fetched", () => {
expect(User.FetchOneById).toHaveBeenCalledTimes(1);
expect(User.FetchOneById).toHaveBeenCalledWith(User, "userId");
});
test("EXPECT user.RemoveCurrency to be called", () => {
expect(user.RemoveCurrency).toHaveBeenCalledTimes(1);
expect(user.RemoveCurrency).toHaveBeenCalledWith(CardConstants.ClaimCost);
});
test("EXPECT user to be saved", () => {
expect(user.Save).toHaveBeenCalledTimes(1);
expect(user.Save).toHaveBeenCalledWith(User, user);
});
test("EXPECT random card to be fetched", () => {
expect(GetCardsHelper.FetchCard).toHaveBeenCalledTimes(1);
expect(GetCardsHelper.FetchCard).toHaveBeenCalledWith("userId");
});
test("EXPECT interaction to be deferred", () => {
expect(interaction.deferReply).toHaveBeenCalledTimes(1);
});
test("EXPECT Inventory.FetchOneByCardNumberAndUserId to be called", () => {
expect(Inventory.FetchOneByCardNumberAndUserId).toHaveBeenCalledTimes(1);
expect(Inventory.FetchOneByCardNumberAndUserId).toHaveBeenCalledWith("userId", "cardId");
});
test("EXPECT DropEmbedHelper.GenerateDropEmbed to be called", () => {
expect(DropEmbedHelper.GenerateDropEmbed).toHaveBeenCalledTimes(1);
expect(DropEmbedHelper.GenerateDropEmbed).toHaveBeenCalledWith(randomCard, 1, "", undefined, 500);
});
test("EXPECT DropEmbedHelper.GenerateDropButtons to be called", () => {
expect(DropEmbedHelper.GenerateDropButtons).toHaveBeenCalledTimes(1);
expect(DropEmbedHelper.GenerateDropButtons).toHaveBeenCalledWith(randomCard, "uuid", "userId");
});
test("EXPECT interaction to be edited", () => {
expect(interaction.editReply).toHaveBeenCalledTimes(1);
expect(interaction.editReply).toHaveBeenCalledWith({
embeds: [ { type: "Embed" } ],
files: [],
components: [ { type: "Button" } ],
});
});
describe("AND randomCard path is not a url", () => {
test.todo("EXPECT image read from file system");
test.todo("EXPECT files on the embed to contain the image as an attachment");
});
});
describe("GIVEN user is not in the database", () => {
test.todo("EXPECT new user to be created");
});
describe("GIVEN user.RemoveCurrency fails", () => {
test.todo("EXPECT error replied");
});
describe("GIVEN randomCard returns null", () => {
test.todo("EXPECT error logged");
test.todo("EXPECT error replied");
});
describe("GIVEN the code throws an error", () => {
test.todo("EXPECT error logged");
test.todo("EXPECT interaction edited with error");
});
});

View file

@ -0,0 +1,3 @@
describe("GenerateDropButtons", () => {
test.todo("EXPECT row to be returned");
});

View file

@ -0,0 +1,7 @@
describe("PurgeClaims", () => {
test.todo("EXPECT claims to be fetched");
test.todo("EXPECT Claim.RemoveMany to remove the claims older than 2 minutes");
test.todo("EXPECT info logged");
});