130 lines
4.6 KiB
TypeScript
130 lines
4.6 KiB
TypeScript
import { describe, test, expect, afterAll, mock } from "bun:test";
|
|
import type { WebServerInstance } from "./server";
|
|
import { createWebServer } from "./server";
|
|
|
|
interface MockBotStats {
|
|
bot: { name: string; avatarUrl: string | null };
|
|
guilds: number;
|
|
ping: number;
|
|
cachedUsers: number;
|
|
commandsRegistered: number;
|
|
uptime: number;
|
|
lastCommandTimestamp: number | null;
|
|
}
|
|
|
|
// 1. Mock DrizzleClient (dependency of dashboardService)
|
|
mock.module("@shared/db/DrizzleClient", () => {
|
|
const mockBuilder = {
|
|
where: mock(() => Promise.resolve([{ count: "5", balance: 1000n, level: 5, dailyStreak: 2 }])),
|
|
then: (onfulfilled: any) => onfulfilled([{ count: "5", balance: 1000n, level: 5, dailyStreak: 2 }]),
|
|
orderBy: mock(() => mockBuilder), // Chainable
|
|
limit: mock(() => Promise.resolve([])), // Terminal
|
|
};
|
|
|
|
const mockFrom = {
|
|
from: mock(() => mockBuilder),
|
|
};
|
|
|
|
return {
|
|
DrizzleClient: {
|
|
select: mock(() => mockFrom),
|
|
query: {
|
|
transactions: { findMany: mock(() => Promise.resolve([])) },
|
|
moderationCases: { findMany: mock(() => Promise.resolve([])) },
|
|
users: {
|
|
findFirst: mock(() => Promise.resolve({ username: "test" })),
|
|
findMany: mock(() => Promise.resolve([])),
|
|
},
|
|
lootdrops: { findMany: mock(() => Promise.resolve([])) },
|
|
}
|
|
},
|
|
};
|
|
});
|
|
|
|
// 2. Mock Bot Stats Provider
|
|
mock.module("../../bot/lib/clientStats", () => ({
|
|
getClientStats: mock((): MockBotStats => ({
|
|
bot: { name: "TestBot", avatarUrl: null },
|
|
guilds: 5,
|
|
ping: 42,
|
|
cachedUsers: 100,
|
|
commandsRegistered: 10,
|
|
uptime: 3600,
|
|
lastCommandTimestamp: Date.now(),
|
|
})),
|
|
}));
|
|
|
|
// 3. System Events (No mock needed, use real events)
|
|
|
|
describe("WebServer Security & Limits", () => {
|
|
const port = 3001;
|
|
let serverInstance: WebServerInstance | null = null;
|
|
|
|
afterAll(async () => {
|
|
if (serverInstance) {
|
|
await serverInstance.stop();
|
|
}
|
|
});
|
|
|
|
test("should reject more than 10 concurrent WebSocket connections", async () => {
|
|
serverInstance = await createWebServer({ port, hostname: "localhost" });
|
|
const wsUrl = `ws://localhost:${port}/ws`;
|
|
const sockets: WebSocket[] = [];
|
|
|
|
try {
|
|
// Attempt to open 12 connections (limit is 10)
|
|
for (let i = 0; i < 12; i++) {
|
|
const ws = new WebSocket(wsUrl);
|
|
sockets.push(ws);
|
|
await new Promise(resolve => setTimeout(resolve, 5));
|
|
}
|
|
|
|
// Give connections time to settle
|
|
await new Promise(resolve => setTimeout(resolve, 800));
|
|
|
|
const pendingCount = serverInstance.server.pendingWebSockets;
|
|
expect(pendingCount).toBeLessThanOrEqual(10);
|
|
} finally {
|
|
sockets.forEach(s => {
|
|
if (s.readyState === WebSocket.OPEN || s.readyState === WebSocket.CONNECTING) {
|
|
s.close();
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
test("should return 200 for health check", async () => {
|
|
if (!serverInstance) {
|
|
serverInstance = await createWebServer({ port, hostname: "localhost" });
|
|
}
|
|
const response = await fetch(`http://localhost:${port}/api/health`);
|
|
expect(response.status).toBe(200);
|
|
const data = (await response.json()) as { status: string };
|
|
expect(data.status).toBe("ok");
|
|
});
|
|
|
|
describe("Administrative Actions", () => {
|
|
test("should allow administrative actions without token", async () => {
|
|
const response = await fetch(`http://localhost:${port}/api/actions/reload-commands`, {
|
|
method: "POST"
|
|
});
|
|
// Should be 200 (OK) or 500 (if underlying service fails, but NOT 401)
|
|
expect(response.status).not.toBe(401);
|
|
expect(response.status).toBe(200);
|
|
});
|
|
|
|
test("should reject maintenance mode with invalid payload", async () => {
|
|
const response = await fetch(`http://localhost:${port}/api/actions/maintenance-mode`, {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json"
|
|
},
|
|
body: JSON.stringify({ not_enabled: true }) // Wrong field
|
|
});
|
|
expect(response.status).toBe(400);
|
|
const data = await response.json() as { error: string };
|
|
expect(data.error).toBe("Invalid payload");
|
|
});
|
|
});
|
|
});
|