feat: Add user existence checks to economy commands and refactor trade service to expose sessions for testing.

This commit is contained in:
syntaxbullet
2026-01-05 12:57:22 +01:00
parent 599684cde8
commit d0b4cb80de
7 changed files with 48 additions and 36 deletions

View File

@@ -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 () => {

View File

@@ -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");
});
});
});

View File

@@ -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
*/