feat: integrate real data into dashboard
- Created dashboard service with DB queries for users, economy, events - Added client stats provider with 30s caching for Discord metrics - Implemented /api/stats endpoint aggregating all dashboard data - Created useDashboardStats React hook with auto-refresh - Updated Dashboard.tsx to display real data with loading/error states - Added comprehensive test coverage (11 tests passing) - Replaced all mock values with live Discord and database metrics
This commit is contained in:
153
shared/modules/dashboard/dashboard.service.test.ts
Normal file
153
shared/modules/dashboard/dashboard.service.test.ts
Normal file
@@ -0,0 +1,153 @@
|
||||
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.resolve([])),
|
||||
},
|
||||
moderationCases: {
|
||||
findMany: mock(() => 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(() => ({
|
||||
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(() => ({
|
||||
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 any);
|
||||
|
||||
mockQuery.moderationCases.findMany.mockResolvedValueOnce([
|
||||
{
|
||||
type: "warn",
|
||||
username: "user2",
|
||||
reason: "Test",
|
||||
createdAt: earlier,
|
||||
},
|
||||
] as any);
|
||||
|
||||
const events = await dashboardService.getRecentEvents(10);
|
||||
|
||||
expect(events).toHaveLength(2);
|
||||
// Should be sorted by timestamp (newest first)
|
||||
expect(events[0]?.timestamp.getTime()).toBeGreaterThanOrEqual(
|
||||
events[1]?.timestamp.getTime() ?? 0
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user