161 lines
4.8 KiB
TypeScript
161 lines
4.8 KiB
TypeScript
import { describe, expect, it, mock, beforeEach, afterEach, jest } from "bun:test";
|
|
import { type WebServerInstance } from "./server";
|
|
|
|
// Mock the dependencies
|
|
const mockConfig = {
|
|
leveling: {
|
|
base: 100,
|
|
exponent: 1.5,
|
|
chat: { minXp: 10, maxXp: 20, cooldownMs: 60000 }
|
|
},
|
|
economy: {
|
|
daily: { amount: 100n, streakBonus: 10n, weeklyBonus: 50n, cooldownMs: 86400000 },
|
|
transfers: { allowSelfTransfer: false, minAmount: 50n },
|
|
exam: { multMin: 1.5, multMax: 2.5 }
|
|
},
|
|
inventory: { maxStackSize: 99n, maxSlots: 20 },
|
|
lootdrop: {
|
|
spawnChance: 0.1,
|
|
cooldownMs: 3600000,
|
|
minMessages: 10,
|
|
reward: { min: 100, max: 500, currency: "gold" }
|
|
},
|
|
commands: { "help": true },
|
|
system: {},
|
|
moderation: {
|
|
prune: { maxAmount: 100, confirmThreshold: 50, batchSize: 100, batchDelayMs: 1000 },
|
|
cases: { dmOnWarn: true }
|
|
}
|
|
};
|
|
|
|
const mockSaveConfig = jest.fn();
|
|
|
|
// Mock @shared/lib/config using mock.module
|
|
mock.module("@shared/lib/config", () => ({
|
|
config: mockConfig,
|
|
saveConfig: mockSaveConfig,
|
|
GameConfigType: {}
|
|
}));
|
|
|
|
// Mock BotClient
|
|
const mockGuild = {
|
|
roles: {
|
|
cache: [
|
|
{ id: "role1", name: "Admin", hexColor: "#ffffff", position: 1 },
|
|
{ id: "role2", name: "User", hexColor: "#000000", position: 0 }
|
|
]
|
|
},
|
|
channels: {
|
|
cache: [
|
|
{ id: "chan1", name: "general", type: 0 }
|
|
]
|
|
}
|
|
};
|
|
|
|
mock.module("../../bot/lib/BotClient", () => ({
|
|
AuroraClient: {
|
|
guilds: {
|
|
cache: {
|
|
get: () => mockGuild
|
|
}
|
|
},
|
|
commands: [
|
|
{ data: { name: "ping" } }
|
|
]
|
|
}
|
|
}));
|
|
|
|
mock.module("@shared/lib/env", () => ({
|
|
env: {
|
|
DISCORD_GUILD_ID: "123456789"
|
|
}
|
|
}));
|
|
|
|
// Mock spawn
|
|
mock.module("bun", () => {
|
|
return {
|
|
spawn: jest.fn(() => ({
|
|
unref: () => { }
|
|
})),
|
|
serve: Bun.serve
|
|
};
|
|
});
|
|
|
|
// Import createWebServer after mocks
|
|
import { createWebServer } from "./server";
|
|
|
|
describe("Settings API", () => {
|
|
let serverInstance: WebServerInstance;
|
|
const PORT = 3009;
|
|
const BASE_URL = `http://localhost:${PORT}`;
|
|
|
|
beforeEach(async () => {
|
|
jest.clearAllMocks();
|
|
serverInstance = await createWebServer({ port: PORT });
|
|
});
|
|
|
|
afterEach(async () => {
|
|
if (serverInstance) {
|
|
await serverInstance.stop();
|
|
}
|
|
});
|
|
|
|
it("GET /api/settings should return current configuration", async () => {
|
|
const res = await fetch(`${BASE_URL}/api/settings`);
|
|
expect(res.status).toBe(200);
|
|
|
|
const data = await res.json();
|
|
// Check if BigInts are converted to strings
|
|
expect(data.economy.daily.amount).toBe("100");
|
|
expect(data.leveling.base).toBe(100);
|
|
});
|
|
|
|
it("POST /api/settings should save valid configuration via merge", async () => {
|
|
// We only send a partial update, expecting the server to merge it
|
|
// Note: For now the server implementation might still default to overwrite if we haven't updated it yet.
|
|
// But the user requested "partial vs full" fix.
|
|
// Let's assume we implement the merge logic.
|
|
const partialConfig = { studentRole: "new-role-partial" };
|
|
|
|
const res = await fetch(`${BASE_URL}/api/settings`, {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify(partialConfig)
|
|
});
|
|
|
|
expect(res.status).toBe(200);
|
|
// Expect saveConfig to be called with the MERGED result
|
|
expect(mockSaveConfig).toHaveBeenCalledWith(expect.objectContaining({
|
|
studentRole: "new-role-partial",
|
|
leveling: mockConfig.leveling // Should keep existing values
|
|
}));
|
|
});
|
|
|
|
it("POST /api/settings should return 400 when save fails", async () => {
|
|
mockSaveConfig.mockImplementationOnce(() => {
|
|
throw new Error("Validation failed");
|
|
});
|
|
|
|
const res = await fetch(`${BASE_URL}/api/settings`, {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({}) // Empty might be valid partial, but mocks throw
|
|
});
|
|
|
|
expect(res.status).toBe(400);
|
|
const data = await res.json();
|
|
expect(data.details).toBe("Validation failed");
|
|
});
|
|
|
|
it("GET /api/settings/meta should return simplified metadata", async () => {
|
|
const res = await fetch(`${BASE_URL}/api/settings/meta`);
|
|
expect(res.status).toBe(200);
|
|
|
|
const data = await res.json();
|
|
expect(data.roles).toHaveLength(2);
|
|
expect(data.roles[0]).toEqual({ id: "role1", name: "Admin", color: "#ffffff" });
|
|
expect(data.channels[0]).toEqual({ id: "chan1", name: "general", type: 0 });
|
|
expect(data.commands).toContain("ping");
|
|
});
|
|
});
|