fix(dash): resolve test regressions, await promises, and improve TypeScript strictness
This commit is contained in:
@@ -8,10 +8,10 @@ const mockSelect = mock(() => ({
|
|||||||
|
|
||||||
const mockQuery = {
|
const mockQuery = {
|
||||||
transactions: {
|
transactions: {
|
||||||
findMany: mock(() => Promise.resolve([])),
|
findMany: mock((): Promise<any[]> => Promise.resolve([])),
|
||||||
},
|
},
|
||||||
moderationCases: {
|
moderationCases: {
|
||||||
findMany: mock(() => Promise.resolve([])),
|
findMany: mock((): Promise<any[]> => Promise.resolve([])),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -133,7 +133,7 @@ describe("dashboardService", () => {
|
|||||||
createdAt: now,
|
createdAt: now,
|
||||||
user: { username: "user1" },
|
user: { username: "user1" },
|
||||||
},
|
},
|
||||||
] as any);
|
] as unknown as any[]);
|
||||||
|
|
||||||
mockQuery.moderationCases.findMany.mockResolvedValueOnce([
|
mockQuery.moderationCases.findMany.mockResolvedValueOnce([
|
||||||
{
|
{
|
||||||
@@ -142,7 +142,7 @@ describe("dashboardService", () => {
|
|||||||
reason: "Test",
|
reason: "Test",
|
||||||
createdAt: earlier,
|
createdAt: earlier,
|
||||||
},
|
},
|
||||||
] as any);
|
] as unknown as any[]);
|
||||||
|
|
||||||
const events = await dashboardService.getRecentEvents(10);
|
const events = await dashboardService.getRecentEvents(10);
|
||||||
|
|
||||||
@@ -156,7 +156,7 @@ describe("dashboardService", () => {
|
|||||||
|
|
||||||
describe("recordEvent", () => {
|
describe("recordEvent", () => {
|
||||||
test("should emit NEW_EVENT to systemEvents", async () => {
|
test("should emit NEW_EVENT to systemEvents", async () => {
|
||||||
const mockEmit = mock((_event: string, _data: any) => { });
|
const mockEmit = mock((_event: string, _data: unknown) => { });
|
||||||
|
|
||||||
mock.module("@shared/lib/events", () => ({
|
mock.module("@shared/lib/events", () => ({
|
||||||
systemEvents: {
|
systemEvents: {
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ export const economyService = {
|
|||||||
|
|
||||||
// Record dashboard event
|
// Record dashboard event
|
||||||
const { dashboardService } = await import("@shared/modules/dashboard/dashboard.service");
|
const { dashboardService } = await import("@shared/modules/dashboard/dashboard.service");
|
||||||
dashboardService.recordEvent({
|
await dashboardService.recordEvent({
|
||||||
type: 'info',
|
type: 'info',
|
||||||
message: `${sender.username} transferred ${amount.toLocaleString()} AU to User ID ${toUserId}`,
|
message: `${sender.username} transferred ${amount.toLocaleString()} AU to User ID ${toUserId}`,
|
||||||
icon: '💸'
|
icon: '💸'
|
||||||
@@ -159,7 +159,7 @@ export const economyService = {
|
|||||||
|
|
||||||
// Record dashboard event
|
// Record dashboard event
|
||||||
const { dashboardService } = await import("@shared/modules/dashboard/dashboard.service");
|
const { dashboardService } = await import("@shared/modules/dashboard/dashboard.service");
|
||||||
dashboardService.recordEvent({
|
await dashboardService.recordEvent({
|
||||||
type: 'success',
|
type: 'success',
|
||||||
message: `${user.username} claimed daily reward: ${totalReward.toLocaleString()} AU`,
|
message: `${user.username} claimed daily reward: ${totalReward.toLocaleString()} AU`,
|
||||||
icon: '☀️'
|
icon: '☀️'
|
||||||
|
|||||||
@@ -1,17 +1,46 @@
|
|||||||
import { describe, test, expect, afterAll, mock } from "bun:test";
|
import { describe, test, expect, afterAll, mock } from "bun:test";
|
||||||
|
import type { WebServerInstance } from "./server";
|
||||||
|
import { createWebServer } from "./server";
|
||||||
|
|
||||||
// Mock the services directly to stay focused on server limits
|
interface MockBotStats {
|
||||||
mock.module("@shared/modules/dashboard/dashboard.service", () => ({
|
bot: { name: string; avatarUrl: string | null };
|
||||||
dashboardService: {
|
guilds: number;
|
||||||
getActiveUserCount: mock(() => Promise.resolve(5)),
|
ping: number;
|
||||||
getTotalUserCount: mock(() => Promise.resolve(10)),
|
cachedUsers: number;
|
||||||
getEconomyStats: mock(() => Promise.resolve({ totalWealth: 1000n, avgLevel: 5, topStreak: 2 })),
|
commandsRegistered: number;
|
||||||
getRecentEvents: mock(() => Promise.resolve([])),
|
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 }]),
|
||||||
|
};
|
||||||
|
|
||||||
|
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([])),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// 2. Mock Bot Stats Provider
|
||||||
mock.module("../../bot/lib/clientStats", () => ({
|
mock.module("../../bot/lib/clientStats", () => ({
|
||||||
getClientStats: mock(() => ({
|
getClientStats: mock((): MockBotStats => ({
|
||||||
bot: { name: "TestBot", avatarUrl: null },
|
bot: { name: "TestBot", avatarUrl: null },
|
||||||
guilds: 5,
|
guilds: 5,
|
||||||
ping: 42,
|
ping: 42,
|
||||||
@@ -22,17 +51,22 @@ mock.module("../../bot/lib/clientStats", () => ({
|
|||||||
})),
|
})),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Ensure @shared/lib/events is mocked if needed
|
// 3. Mock System Events
|
||||||
mock.module("@shared/lib/events", () => ({
|
mock.module("@shared/lib/events", () => ({
|
||||||
systemEvents: { on: mock(() => { }) },
|
systemEvents: {
|
||||||
EVENTS: { DASHBOARD: { NEW_EVENT: "dashboard:new_event" } }
|
on: mock(() => { }),
|
||||||
|
emit: mock(() => { }),
|
||||||
|
},
|
||||||
|
EVENTS: {
|
||||||
|
DASHBOARD: {
|
||||||
|
NEW_EVENT: "dashboard:new_event",
|
||||||
|
}
|
||||||
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
import { createWebServer } from "./server";
|
|
||||||
|
|
||||||
describe("WebServer Security & Limits", () => {
|
describe("WebServer Security & Limits", () => {
|
||||||
const port = 3001;
|
const port = 3001;
|
||||||
let serverInstance: any;
|
let serverInstance: WebServerInstance | null = null;
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
if (serverInstance) {
|
if (serverInstance) {
|
||||||
@@ -50,16 +84,20 @@ describe("WebServer Security & Limits", () => {
|
|||||||
for (let i = 0; i < 12; i++) {
|
for (let i = 0; i < 12; i++) {
|
||||||
const ws = new WebSocket(wsUrl);
|
const ws = new WebSocket(wsUrl);
|
||||||
sockets.push(ws);
|
sockets.push(ws);
|
||||||
await new Promise(resolve => setTimeout(resolve, 10));
|
await new Promise(resolve => setTimeout(resolve, 5));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Give connections time to settle
|
// Give connections time to settle
|
||||||
await new Promise(resolve => setTimeout(resolve, 500));
|
await new Promise(resolve => setTimeout(resolve, 800));
|
||||||
|
|
||||||
// Should be exactly 10 or less
|
const pendingCount = serverInstance.server.pendingWebSockets;
|
||||||
expect(serverInstance.server.pendingWebSockets).toBeLessThanOrEqual(10);
|
expect(pendingCount).toBeLessThanOrEqual(10);
|
||||||
} finally {
|
} finally {
|
||||||
sockets.forEach(s => s.close());
|
sockets.forEach(s => {
|
||||||
|
if (s.readyState === WebSocket.OPEN || s.readyState === WebSocket.CONNECTING) {
|
||||||
|
s.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -69,7 +107,7 @@ describe("WebServer Security & Limits", () => {
|
|||||||
}
|
}
|
||||||
const response = await fetch(`http://localhost:${port}/api/health`);
|
const response = await fetch(`http://localhost:${port}/api/health`);
|
||||||
expect(response.status).toBe(200);
|
expect(response.status).toBe(200);
|
||||||
const data = await response.json();
|
const data = (await response.json()) as { status: string };
|
||||||
expect(data.status).toBe("ok");
|
expect(data.status).toBe("ok");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -151,8 +151,17 @@ export async function createWebServer(config: WebServerConfig = {}): Promise<Web
|
|||||||
},
|
},
|
||||||
async message(ws, message) {
|
async message(ws, message) {
|
||||||
try {
|
try {
|
||||||
|
const messageStr = message.toString();
|
||||||
|
|
||||||
|
// Defense-in-depth: redundant length check before parsing
|
||||||
|
if (messageStr.length > MAX_PAYLOAD_BYTES) {
|
||||||
|
console.error("❌ [WS] Payload exceeded maximum limit");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rawData = JSON.parse(messageStr);
|
||||||
const { WsMessageSchema } = await import("@shared/modules/dashboard/dashboard.types");
|
const { WsMessageSchema } = await import("@shared/modules/dashboard/dashboard.types");
|
||||||
const parsed = WsMessageSchema.safeParse(JSON.parse(message.toString()));
|
const parsed = WsMessageSchema.safeParse(rawData);
|
||||||
|
|
||||||
if (!parsed.success) {
|
if (!parsed.success) {
|
||||||
console.error("❌ [WS] Invalid message format:", parsed.error.issues);
|
console.error("❌ [WS] Invalid message format:", parsed.error.issues);
|
||||||
@@ -163,7 +172,7 @@ export async function createWebServer(config: WebServerConfig = {}): Promise<Web
|
|||||||
ws.send(JSON.stringify({ type: "PONG" }));
|
ws.send(JSON.stringify({ type: "PONG" }));
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("❌ [WS] Failed to parse message:", e);
|
console.error("❌ [WS] Failed to handle message:", e instanceof Error ? e.message : "Malformed JSON");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
close(ws) {
|
close(ws) {
|
||||||
|
|||||||
Reference in New Issue
Block a user