forked from syntaxbullet/AuroraBot-discord
fix(dash): address safety constraints, validation, and test quality issues
This commit is contained in:
@@ -148,15 +148,15 @@ describe("dashboardService", () => {
|
||||
|
||||
expect(events).toHaveLength(2);
|
||||
// Should be sorted by timestamp (newest first)
|
||||
expect(events[0]?.timestamp.getTime()).toBeGreaterThanOrEqual(
|
||||
events[1]?.timestamp.getTime() ?? 0
|
||||
);
|
||||
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(() => { });
|
||||
const mockEmit = mock((_event: string, _data: any) => { });
|
||||
|
||||
mock.module("@shared/lib/events", () => ({
|
||||
systemEvents: {
|
||||
@@ -176,10 +176,17 @@ describe("dashboardService", () => {
|
||||
});
|
||||
|
||||
expect(mockEmit).toHaveBeenCalled();
|
||||
const [eventName, data] = mockEmit.mock.calls[0] as any;
|
||||
expect(eventName).toBe("dashboard:new_event");
|
||||
expect(data.message).toBe("Test Event");
|
||||
expect(data.timestamp).toBeDefined();
|
||||
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");
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,49 +1,69 @@
|
||||
export interface DashboardStats {
|
||||
bot: {
|
||||
name: string;
|
||||
avatarUrl: string | null;
|
||||
};
|
||||
guilds: {
|
||||
count: number;
|
||||
changeFromLastMonth?: number;
|
||||
};
|
||||
users: {
|
||||
active: number;
|
||||
total: number;
|
||||
changePercentFromLastMonth?: number;
|
||||
};
|
||||
commands: {
|
||||
total: number;
|
||||
changePercentFromLastMonth?: number;
|
||||
};
|
||||
ping: {
|
||||
avg: number;
|
||||
changeFromLastHour?: number;
|
||||
};
|
||||
economy: {
|
||||
totalWealth: string; // bigint as string for JSON
|
||||
avgLevel: number;
|
||||
topStreak: number;
|
||||
};
|
||||
recentEvents: RecentEvent[];
|
||||
}
|
||||
import { z } from "zod";
|
||||
|
||||
export interface RecentEvent {
|
||||
type: 'success' | 'error' | 'info';
|
||||
message: string;
|
||||
timestamp: Date;
|
||||
icon?: string;
|
||||
}
|
||||
export const RecentEventSchema = z.object({
|
||||
type: z.enum(['success', 'error', 'info']),
|
||||
message: z.string(),
|
||||
timestamp: z.union([z.date(), z.string().datetime()]),
|
||||
icon: z.string().optional(),
|
||||
});
|
||||
|
||||
export interface ClientStats {
|
||||
bot: {
|
||||
name: string;
|
||||
avatarUrl: string | null;
|
||||
};
|
||||
guilds: number;
|
||||
ping: number;
|
||||
cachedUsers: number;
|
||||
commandsRegistered: number;
|
||||
uptime: number;
|
||||
lastCommandTimestamp: number | null;
|
||||
}
|
||||
export type RecentEvent = z.infer<typeof RecentEventSchema>;
|
||||
|
||||
export const DashboardStatsSchema = z.object({
|
||||
bot: z.object({
|
||||
name: z.string(),
|
||||
avatarUrl: z.string().nullable(),
|
||||
}),
|
||||
guilds: z.object({
|
||||
count: z.number(),
|
||||
changeFromLastMonth: z.number().optional(),
|
||||
}),
|
||||
users: z.object({
|
||||
active: z.number(),
|
||||
total: z.number(),
|
||||
changePercentFromLastMonth: z.number().optional(),
|
||||
}),
|
||||
commands: z.object({
|
||||
total: z.number(),
|
||||
changePercentFromLastMonth: z.number().optional(),
|
||||
}),
|
||||
ping: z.object({
|
||||
avg: z.number(),
|
||||
changeFromLastHour: z.number().optional(),
|
||||
}),
|
||||
economy: z.object({
|
||||
totalWealth: z.string(),
|
||||
avgLevel: z.number(),
|
||||
topStreak: z.number(),
|
||||
}),
|
||||
recentEvents: z.array(RecentEventSchema),
|
||||
uptime: z.number(),
|
||||
lastCommandTimestamp: z.number().nullable(),
|
||||
});
|
||||
|
||||
export type DashboardStats = z.infer<typeof DashboardStatsSchema>;
|
||||
|
||||
export const ClientStatsSchema = z.object({
|
||||
bot: z.object({
|
||||
name: z.string(),
|
||||
avatarUrl: z.string().nullable(),
|
||||
}),
|
||||
guilds: z.number(),
|
||||
ping: z.number(),
|
||||
cachedUsers: z.number(),
|
||||
commandsRegistered: z.number(),
|
||||
uptime: z.number(),
|
||||
lastCommandTimestamp: z.number().nullable(),
|
||||
});
|
||||
|
||||
export type ClientStats = z.infer<typeof ClientStatsSchema>;
|
||||
|
||||
// WebSocket Message Schemas
|
||||
export const WsMessageSchema = z.discriminatedUnion("type", [
|
||||
z.object({ type: z.literal("PING") }),
|
||||
z.object({ type: z.literal("PONG") }),
|
||||
z.object({ type: z.literal("STATS_UPDATE"), data: DashboardStatsSchema }),
|
||||
z.object({ type: z.literal("NEW_EVENT"), data: RecentEventSchema }),
|
||||
]);
|
||||
|
||||
export type WsMessage = z.infer<typeof WsMessageSchema>;
|
||||
|
||||
Reference in New Issue
Block a user