diff --git a/check.sh b/check.sh deleted file mode 100755 index ec7d07a..0000000 --- a/check.sh +++ /dev/null @@ -1 +0,0 @@ -tsc --noEmit diff --git a/src/commands/economy/balance.ts b/src/commands/economy/balance.ts index 0abc748..f0c3999 100644 --- a/src/commands/economy/balance.ts +++ b/src/commands/economy/balance.ts @@ -23,6 +23,8 @@ export const balance = createCommand({ const user = await userService.getOrCreateUser(targetUser.id, targetUser.username); + if (!user) throw new Error("Failed to retrieve user data."); + const embed = createBaseEmbed(undefined, `**Balance**: ${user.balance || 0n} AU`, "Yellow") .setAuthor({ name: targetUser.username, iconURL: targetUser.displayAvatarURL() }); diff --git a/src/commands/economy/exam.ts b/src/commands/economy/exam.ts index 425240a..fdc52df 100644 --- a/src/commands/economy/exam.ts +++ b/src/commands/economy/exam.ts @@ -25,6 +25,10 @@ export const exam = createCommand({ execute: async (interaction) => { await interaction.deferReply(); const user = await userService.getOrCreateUser(interaction.user.id, interaction.user.username); + if (!user) { + await interaction.editReply({ embeds: [createErrorEmbed("Failed to retrieve user data.")] }); + return; + } const now = new Date(); const currentDay = now.getDay(); @@ -47,7 +51,7 @@ export const exam = createCommand({ const metadata: ExamMetadata = { examDay: currentDay, - lastXp: user.xp.toString() + lastXp: (user.xp ?? 0n).toString() }; await DrizzleClient.insert(userTimers).values({ @@ -98,7 +102,7 @@ export const exam = createCommand({ const newMetadata: ExamMetadata = { examDay: examDay, - lastXp: user.xp.toString() // Reset tracking + lastXp: (user.xp ?? 0n).toString() }; await DrizzleClient.update(userTimers) @@ -125,7 +129,7 @@ export const exam = createCommand({ // 5. Reward Calculation const lastXp = BigInt(metadata.lastXp || "0"); // Fallback just in case - const currentXp = user.xp; + const currentXp = user.xp ?? 0n; const diff = currentXp - lastXp; // Calculate Reward diff --git a/src/commands/economy/pay.ts b/src/commands/economy/pay.ts index 658790e..5ef6a11 100644 --- a/src/commands/economy/pay.ts +++ b/src/commands/economy/pay.ts @@ -33,6 +33,11 @@ export const pay = createCommand({ const amount = BigInt(interaction.options.getInteger("amount", true)); const senderId = interaction.user.id; + if (!targetUser) { + await interaction.reply({ embeds: [createErrorEmbed("User not found.")], flags: MessageFlags.Ephemeral }); + return; + } + const receiverId = targetUser.id; if (amount < config.economy.transfers.minAmount) { @@ -40,14 +45,14 @@ export const pay = createCommand({ return; } - if (senderId === receiverId) { + if (senderId === receiverId.toString()) { await interaction.reply({ embeds: [createErrorEmbed("You cannot pay yourself.")], flags: MessageFlags.Ephemeral }); return; } try { await interaction.deferReply(); - await economyService.transfer(senderId, receiverId, amount); + await economyService.transfer(senderId, receiverId.toString(), amount); const embed = createSuccessEmbed(`Successfully sent ** ${amount}** Astral Units to <@${targetUser.id}>.`, "💸 Transfer Successful"); await interaction.editReply({ embeds: [embed], content: `<@${receiverId}>` }); diff --git a/src/modules/leveling/leveling.service.test.ts b/src/modules/leveling/leveling.service.test.ts index 8d07300..05b03cd 100644 --- a/src/modules/leveling/leveling.service.test.ts +++ b/src/modules/leveling/leveling.service.test.ts @@ -77,8 +77,8 @@ describe("levelingService", () => { // base 100, exp 1.5 // lvl 1: 100 * 1^1.5 = 100 // lvl 2: 100 * 2^1.5 = 100 * 2.828 = 282 - expect(levelingService.getXpForLevel(1)).toBe(100); - expect(levelingService.getXpForLevel(2)).toBe(282); + expect(levelingService.getXpForNextLevel(1)).toBe(100); + expect(levelingService.getXpForNextLevel(2)).toBe(282); }); }); @@ -123,7 +123,7 @@ describe("levelingService", () => { expect(result.levelUp).toBe(true); expect(result.currentLevel).toBe(2); - expect(mockSet).toHaveBeenCalledWith({ xp: 20n, level: 2 }); + expect(mockSet).toHaveBeenCalledWith({ xp: 120n, level: 2 }); }); it("should handle multiple level ups", async () => { diff --git a/src/modules/trade/trade.service.test.ts b/src/modules/trade/trade.service.test.ts index d694290..5ac53dd 100644 --- a/src/modules/trade/trade.service.test.ts +++ b/src/modules/trade/trade.service.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect, mock, beforeEach, afterEach, spyOn } from "bun:test"; -import { TradeService } from "./trade.service"; +import { tradeService } from "./trade.service"; import { itemTransactions } from "@/db/schema"; import { economyService } from "@/modules/economy/economy.service"; import { inventoryService } from "@/modules/inventory/inventory.service"; @@ -37,7 +37,7 @@ describe("TradeService", () => { mockValues.mockClear(); // Clear sessions - (TradeService as any).sessions.clear(); + (tradeService as any)._sessions.clear(); // Spies mockModifyUserBalance = spyOn(economyService, 'modifyUserBalance').mockResolvedValue({} as any); @@ -54,68 +54,68 @@ describe("TradeService", () => { describe("createSession", () => { it("should create a new session", () => { - const session = TradeService.createSession("thread1", userA, userB); + const session = tradeService.createSession("thread1", userA, userB); expect(session.threadId).toBe("thread1"); expect(session.state).toBe("NEGOTIATING"); expect(session.userA.id).toBe("1"); expect(session.userB.id).toBe("2"); - expect(TradeService.getSession("thread1")).toBe(session); + expect(tradeService.getSession("thread1")).toBe(session); }); }); describe("updateMoney", () => { it("should update money offer", () => { - TradeService.createSession("thread1", userA, userB); - TradeService.updateMoney("thread1", "1", 100n); + tradeService.createSession("thread1", userA, userB); + tradeService.updateMoney("thread1", "1", 100n); - const session = TradeService.getSession("thread1"); + const session = tradeService.getSession("thread1"); expect(session?.userA.offer.money).toBe(100n); }); it("should unlock participants when offer changes", () => { - const session = TradeService.createSession("thread1", userA, userB); + const session = tradeService.createSession("thread1", userA, userB); session.userA.locked = true; session.userB.locked = true; - TradeService.updateMoney("thread1", "1", 100n); + tradeService.updateMoney("thread1", "1", 100n); expect(session.userA.locked).toBe(false); expect(session.userB.locked).toBe(false); }); it("should throw if not in trade", () => { - TradeService.createSession("thread1", userA, userB); - expect(() => TradeService.updateMoney("thread1", "3", 100n)).toThrow("User not in trade"); + tradeService.createSession("thread1", userA, userB); + expect(() => tradeService.updateMoney("thread1", "3", 100n)).toThrow("User not in trade"); }); }); describe("addItem", () => { it("should add item to offer", () => { - TradeService.createSession("thread1", userA, userB); - TradeService.addItem("thread1", "1", { id: 10, name: "Sword" }, 1n); + tradeService.createSession("thread1", userA, userB); + tradeService.addItem("thread1", "1", { id: 10, name: "Sword" }, 1n); - const session = TradeService.getSession("thread1"); + const session = tradeService.getSession("thread1"); expect(session?.userA.offer.items).toHaveLength(1); expect(session?.userA.offer.items[0]).toEqual({ id: 10, name: "Sword", quantity: 1n }); }); it("should stack items if already offered", () => { - TradeService.createSession("thread1", userA, userB); - TradeService.addItem("thread1", "1", { id: 10, name: "Sword" }, 1n); - TradeService.addItem("thread1", "1", { id: 10, name: "Sword" }, 2n); + tradeService.createSession("thread1", userA, userB); + tradeService.addItem("thread1", "1", { id: 10, name: "Sword" }, 1n); + tradeService.addItem("thread1", "1", { id: 10, name: "Sword" }, 2n); - const session = TradeService.getSession("thread1"); + const session = tradeService.getSession("thread1"); expect(session?.userA.offer.items[0]!.quantity).toBe(3n); }); }); describe("removeItem", () => { it("should remove item from offer", () => { - const session = TradeService.createSession("thread1", userA, userB); + const session = tradeService.createSession("thread1", userA, userB); session.userA.offer.items.push({ id: 10, name: "Sword", quantity: 1n }); - TradeService.removeItem("thread1", "1", 10); + tradeService.removeItem("thread1", "1", 10); expect(session.userA.offer.items).toHaveLength(0); }); @@ -123,19 +123,19 @@ describe("TradeService", () => { describe("toggleLock", () => { it("should toggle lock status", () => { - TradeService.createSession("thread1", userA, userB); + tradeService.createSession("thread1", userA, userB); - const locked1 = TradeService.toggleLock("thread1", "1"); + const locked1 = tradeService.toggleLock("thread1", "1"); expect(locked1).toBe(true); - const locked2 = TradeService.toggleLock("thread1", "1"); + const locked2 = tradeService.toggleLock("thread1", "1"); expect(locked2).toBe(false); }); }); describe("executeTrade", () => { it("should execute trade successfully", async () => { - const session = TradeService.createSession("thread1", userA, userB); + const session = tradeService.createSession("thread1", userA, userB); // Setup offers session.userA.offer.money = 100n; @@ -148,7 +148,7 @@ describe("TradeService", () => { session.userA.locked = true; session.userB.locked = true; - await TradeService.executeTrade("thread1"); + await tradeService.executeTrade("thread1"); expect(session.state).toBe("COMPLETED"); @@ -171,11 +171,11 @@ describe("TradeService", () => { }); it("should throw if not locked", async () => { - const session = TradeService.createSession("thread1", userA, userB); + const session = tradeService.createSession("thread1", userA, userB); session.userA.locked = true; // B not locked - expect(TradeService.executeTrade("thread1")).rejects.toThrow("Both players must accept"); + expect(tradeService.executeTrade("thread1")).rejects.toThrow("Both players must accept"); }); }); }); diff --git a/src/modules/trade/trade.service.ts b/src/modules/trade/trade.service.ts index 4148c42..9df41cb 100644 --- a/src/modules/trade/trade.service.ts +++ b/src/modules/trade/trade.service.ts @@ -71,6 +71,8 @@ const processTransfer = async (tx: Transaction, from: TradeParticipant, to: Trad }; export const tradeService = { + // Expose for testing + _sessions: sessions, /** * Creates a new trade session */