import { describe, test, expect, mock, beforeEach } from "bun:test"; import { dashboardService } from "./dashboard.service"; // Mock DrizzleClient const mockSelect = mock(() => ({ from: mock(() => Promise.resolve([{ count: "5" }])), })); const mockQuery = { transactions: { findMany: mock((): Promise => Promise.resolve([])), }, moderationCases: { findMany: mock((): Promise => Promise.resolve([])), }, }; mock.module("@shared/db/DrizzleClient", () => ({ DrizzleClient: { select: mockSelect, query: mockQuery, }, })); describe("dashboardService", () => { beforeEach(() => { mockSelect.mockClear(); mockQuery.transactions.findMany.mockClear(); mockQuery.moderationCases.findMany.mockClear(); // Reset default mock implementation mockSelect.mockImplementation(() => ({ from: mock(() => Promise.resolve([{ count: "5" }])), })); }); describe("getActiveUserCount", () => { test("should return active user count from database", async () => { mockSelect.mockImplementationOnce(() => ({ // @ts-ignore ts(2322) from: mock(() => ({ where: mock(() => Promise.resolve([{ count: "5" }])), })), })); const count = await dashboardService.getActiveUserCount(); expect(count).toBe(5); expect(mockSelect).toHaveBeenCalled(); }); test("should return 0 when no users found", async () => { mockSelect.mockImplementationOnce(() => ({ // @ts-ignore ts(2322) from: mock(() => ({ where: mock(() => Promise.resolve([{ count: "0" }])), })), })); const count = await dashboardService.getActiveUserCount(); expect(count).toBe(0); }); }); describe("getTotalUserCount", () => { test("should return total user count", async () => { const count = await dashboardService.getTotalUserCount(); expect(count).toBe(5); }); }); describe("getRecentTransactions", () => { test("should return formatted transaction events", async () => { const mockTx = [ { type: "DAILY_REWARD", description: "Daily reward", createdAt: new Date(), user: { username: "testuser" }, }, ] as any; mockQuery.transactions.findMany.mockResolvedValueOnce(mockTx); const events = await dashboardService.getRecentTransactions(10); expect(events).toHaveLength(1); expect(events[0]?.type).toBe("info"); expect(events[0]?.message).toContain("testuser"); expect(events[0]?.icon).toBe("☀️"); }); test("should handle empty transactions", async () => { mockQuery.transactions.findMany.mockResolvedValueOnce([]); const events = await dashboardService.getRecentTransactions(10); expect(events).toHaveLength(0); }); }); describe("getRecentModerationCases", () => { test("should return formatted moderation events", async () => { const mockCases = [ { type: "warn", username: "baduser", reason: "Spam", createdAt: new Date(), }, ] as any; mockQuery.moderationCases.findMany.mockResolvedValueOnce(mockCases); const events = await dashboardService.getRecentModerationCases(10); expect(events).toHaveLength(1); expect(events[0]?.type).toBe("error"); expect(events[0]?.message).toContain("WARN"); expect(events[0]?.message).toContain("baduser"); expect(events[0]?.icon).toBe("⚠️"); }); }); describe("getRecentEvents", () => { test("should combine and sort transactions and moderation events", async () => { const now = new Date(); const earlier = new Date(now.getTime() - 1000); mockQuery.transactions.findMany.mockResolvedValueOnce([ { type: "DAILY_REWARD", description: "Daily", createdAt: now, user: { username: "user1" }, }, ] as unknown as any[]); mockQuery.moderationCases.findMany.mockResolvedValueOnce([ { type: "warn", username: "user2", reason: "Test", createdAt: earlier, }, ] as unknown as any[]); const events = await dashboardService.getRecentEvents(10); expect(events).toHaveLength(2); // Should be sorted by timestamp (newest first) const t0 = events[0]?.timestamp instanceof Date ? events[0].timestamp.getTime() : new Date(events[0]?.timestamp ?? 0).getTime(); const t1 = events[1]?.timestamp instanceof Date ? events[1].timestamp.getTime() : new Date(events[1]?.timestamp ?? 0).getTime(); expect(t0).toBeGreaterThanOrEqual(t1); }); }); describe("recordEvent", () => { test("should emit NEW_EVENT to systemEvents", async () => { const mockEmit = mock((_event: string, _data: unknown) => { }); mock.module("@shared/lib/events", () => ({ systemEvents: { emit: mockEmit, }, EVENTS: { DASHBOARD: { NEW_EVENT: "dashboard:new_event", } } })); await dashboardService.recordEvent({ type: 'info', message: 'Test Event', icon: '🚀' }); expect(mockEmit).toHaveBeenCalled(); const calls = mockEmit.mock.calls; if (calls.length > 0 && calls[0]) { expect(calls[0][0]).toBe("dashboard:new_event"); const data = calls[0][1] as { message: string, timestamp: string }; expect(data.message).toBe("Test Event"); expect(data.timestamp).toBeDefined(); // Verify it's an ISO string expect(() => new Date(data.timestamp).toISOString()).not.toThrow(); } else { throw new Error("mockEmit was not called with expected arguments"); } }); }); });