From 3f3a6c88e88b956187a73676aa4b1b99b818b0ea Mon Sep 17 00:00:00 2001 From: syntaxbullet Date: Thu, 8 Jan 2026 21:12:41 +0100 Subject: [PATCH] fix(dash): resolve test regressions, await promises, and improve TypeScript strictness --- .../dashboard/dashboard.service.test.ts | 10 +-- shared/modules/economy/economy.service.ts | 4 +- web/src/server.test.ts | 82 ++++++++++++++----- web/src/server.ts | 13 ++- 4 files changed, 78 insertions(+), 31 deletions(-) diff --git a/shared/modules/dashboard/dashboard.service.test.ts b/shared/modules/dashboard/dashboard.service.test.ts index 600a799..795b4e4 100644 --- a/shared/modules/dashboard/dashboard.service.test.ts +++ b/shared/modules/dashboard/dashboard.service.test.ts @@ -8,10 +8,10 @@ const mockSelect = mock(() => ({ const mockQuery = { transactions: { - findMany: mock(() => Promise.resolve([])), + findMany: mock((): Promise => Promise.resolve([])), }, moderationCases: { - findMany: mock(() => Promise.resolve([])), + findMany: mock((): Promise => Promise.resolve([])), }, }; @@ -133,7 +133,7 @@ describe("dashboardService", () => { createdAt: now, user: { username: "user1" }, }, - ] as any); + ] as unknown as any[]); mockQuery.moderationCases.findMany.mockResolvedValueOnce([ { @@ -142,7 +142,7 @@ describe("dashboardService", () => { reason: "Test", createdAt: earlier, }, - ] as any); + ] as unknown as any[]); const events = await dashboardService.getRecentEvents(10); @@ -156,7 +156,7 @@ describe("dashboardService", () => { describe("recordEvent", () => { 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", () => ({ systemEvents: { diff --git a/shared/modules/economy/economy.service.ts b/shared/modules/economy/economy.service.ts index a56cc59..b093401 100644 --- a/shared/modules/economy/economy.service.ts +++ b/shared/modules/economy/economy.service.ts @@ -63,7 +63,7 @@ export const economyService = { // Record dashboard event const { dashboardService } = await import("@shared/modules/dashboard/dashboard.service"); - dashboardService.recordEvent({ + await dashboardService.recordEvent({ type: 'info', message: `${sender.username} transferred ${amount.toLocaleString()} AU to User ID ${toUserId}`, icon: '💸' @@ -159,7 +159,7 @@ export const economyService = { // Record dashboard event const { dashboardService } = await import("@shared/modules/dashboard/dashboard.service"); - dashboardService.recordEvent({ + await dashboardService.recordEvent({ type: 'success', message: `${user.username} claimed daily reward: ${totalReward.toLocaleString()} AU`, icon: '☀️' diff --git a/web/src/server.test.ts b/web/src/server.test.ts index f376019..69dd706 100644 --- a/web/src/server.test.ts +++ b/web/src/server.test.ts @@ -1,17 +1,46 @@ 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 -mock.module("@shared/modules/dashboard/dashboard.service", () => ({ - dashboardService: { - getActiveUserCount: mock(() => Promise.resolve(5)), - getTotalUserCount: mock(() => Promise.resolve(10)), - getEconomyStats: mock(() => Promise.resolve({ totalWealth: 1000n, avgLevel: 5, topStreak: 2 })), - getRecentEvents: mock(() => Promise.resolve([])), - } -})); +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 }]), + }; + + 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", () => ({ - getClientStats: mock(() => ({ + getClientStats: mock((): MockBotStats => ({ bot: { name: "TestBot", avatarUrl: null }, guilds: 5, 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", () => ({ - systemEvents: { on: mock(() => { }) }, - EVENTS: { DASHBOARD: { NEW_EVENT: "dashboard:new_event" } } + systemEvents: { + on: mock(() => { }), + emit: mock(() => { }), + }, + EVENTS: { + DASHBOARD: { + NEW_EVENT: "dashboard:new_event", + } + } })); -import { createWebServer } from "./server"; - describe("WebServer Security & Limits", () => { const port = 3001; - let serverInstance: any; + let serverInstance: WebServerInstance | null = null; afterAll(async () => { if (serverInstance) { @@ -50,16 +84,20 @@ describe("WebServer Security & Limits", () => { for (let i = 0; i < 12; i++) { const ws = new WebSocket(wsUrl); sockets.push(ws); - await new Promise(resolve => setTimeout(resolve, 10)); + await new Promise(resolve => setTimeout(resolve, 5)); } // 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 - expect(serverInstance.server.pendingWebSockets).toBeLessThanOrEqual(10); + const pendingCount = serverInstance.server.pendingWebSockets; + expect(pendingCount).toBeLessThanOrEqual(10); } 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`); expect(response.status).toBe(200); - const data = await response.json(); + const data = (await response.json()) as { status: string }; expect(data.status).toBe("ok"); }); }); diff --git a/web/src/server.ts b/web/src/server.ts index aa0ed6c..425af82 100644 --- a/web/src/server.ts +++ b/web/src/server.ts @@ -151,8 +151,17 @@ export async function createWebServer(config: WebServerConfig = {}): Promise 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 parsed = WsMessageSchema.safeParse(JSON.parse(message.toString())); + const parsed = WsMessageSchema.safeParse(rawData); if (!parsed.success) { console.error("❌ [WS] Invalid message format:", parsed.error.issues); @@ -163,7 +172,7 @@ export async function createWebServer(config: WebServerConfig = {}): Promise