forked from syntaxbullet/AuroraBot-discord
- 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
154 lines
5.0 KiB
TypeScript
154 lines
5.0 KiB
TypeScript
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
|
|
);
|
|
});
|
|
});
|
|
});
|