/** * Tests for dashboard.stats tRPC procedure and seed isSystem fix. */ import { describe, it, expect, vi, beforeEach } from "vitest"; // ─── Mocks ──────────────────────────────────────────────────────────────────── vi.mock("./gateway-proxy", () => ({ getGatewayNodes: vi.fn(), getGatewayNodeStats: vi.fn(), })); vi.mock("./db", () => ({ getDb: vi.fn(), })); vi.mock("../drizzle/schema", () => ({ agents: { isActive: "isActive", isSystem: "isSystem" }, })); vi.mock("drizzle-orm", () => ({ count: vi.fn(() => "count()"), eq: vi.fn((col: string, val: unknown) => `${col}=${val}`), })); import { getGatewayNodes, getGatewayNodeStats } from "./gateway-proxy"; import { getDb } from "./db"; // ─── Helpers (replicated from routers.ts dashboard.stats logic) ─────────────── function formatUptime(uptimeSec: number): string { const days = Math.floor(uptimeSec / 86400); const hours = Math.floor((uptimeSec % 86400) / 3600); const mins = Math.floor((uptimeSec % 3600) / 60); if (days > 0) return `${days}d ${hours}h ${mins}m`; if (hours > 0) return `${hours}h ${mins}m`; return `${mins}m`; } function calcCpu(stats: { cpuPct: number }[]): number { if (!stats.length) return 0; return stats.reduce((s, c) => s + c.cpuPct, 0) / stats.length; } function calcMem(stats: { memUseMB: number }[]): number { return stats.reduce((s, c) => s + c.memUseMB, 0); } // ─── Tests ──────────────────────────────────────────────────────────────────── describe("formatUptime", () => { it("returns minutes only for < 1 hour", () => { expect(formatUptime(300)).toBe("5m"); expect(formatUptime(59)).toBe("0m"); }); it("returns hours and minutes for 1h-24h", () => { expect(formatUptime(3600)).toBe("1h 0m"); expect(formatUptime(5400)).toBe("1h 30m"); expect(formatUptime(7200)).toBe("2h 0m"); }); it("returns days, hours, minutes for >= 24h", () => { expect(formatUptime(86400)).toBe("1d 0h 0m"); expect(formatUptime(86400 + 3600 + 60)).toBe("1d 1h 1m"); expect(formatUptime(14 * 86400 + 7 * 3600 + 23 * 60)).toBe("14d 7h 23m"); }); }); describe("calcCpu", () => { it("returns 0 for empty stats", () => { expect(calcCpu([])).toBe(0); }); it("returns average CPU across containers", () => { const stats = [ { cpuPct: 10 }, { cpuPct: 20 }, { cpuPct: 30 }, ]; expect(calcCpu(stats)).toBe(20); }); it("handles single container", () => { expect(calcCpu([{ cpuPct: 45.5 }])).toBe(45.5); }); }); describe("calcMem", () => { it("returns 0 for empty stats", () => { expect(calcMem([])).toBe(0); }); it("sums all container memory usage", () => { const stats = [ { memUseMB: 100 }, { memUseMB: 200 }, { memUseMB: 300 }, ]; expect(calcMem(stats)).toBe(600); }); }); describe("dashboard.stats — gateway integration", () => { beforeEach(() => { vi.clearAllMocks(); }); it("returns zero values when gateway is unavailable", async () => { vi.mocked(getGatewayNodes).mockResolvedValue(null); vi.mocked(getGatewayNodeStats).mockResolvedValue(null); vi.mocked(getDb).mockResolvedValue(null as any); const nodes = await getGatewayNodes(); const stats = await getGatewayNodeStats(); expect(nodes).toBeNull(); expect(stats).toBeNull(); const containerCount = nodes?.containers?.length ?? nodes?.count ?? 0; expect(containerCount).toBe(0); const cpuPct = calcCpu(stats?.stats ?? []); const memUseMB = calcMem(stats?.stats ?? []); expect(cpuPct).toBe(0); expect(memUseMB).toBe(0); }); it("aggregates container count from nodes.containers", async () => { vi.mocked(getGatewayNodes).mockResolvedValue({ nodes: [], count: 0, swarmActive: false, fetchedAt: new Date().toISOString(), containers: [ { id: "a", name: "c1", image: "img1", state: "running", status: "Up 1h" }, { id: "b", name: "c2", image: "img2", state: "running", status: "Up 2h" }, ], }); vi.mocked(getGatewayNodeStats).mockResolvedValue({ stats: [ { id: "a", name: "c1", cpuPct: 10, memUseMB: 100, memLimMB: 4000, memPct: 2.5 }, { id: "b", name: "c2", cpuPct: 30, memUseMB: 200, memLimMB: 4000, memPct: 5 }, ], count: 2, fetchedAt: new Date().toISOString(), }); const nodes = await getGatewayNodes(); const stats = await getGatewayNodeStats(); expect(nodes?.containers?.length).toBe(2); expect(calcCpu(stats?.stats ?? [])).toBe(20); expect(calcMem(stats?.stats ?? [])).toBe(300); }); }); describe("seed isSystem fix", () => { it("checks by isSystem=true, not total count", () => { // The fix: seed queries WHERE isSystem=true instead of COUNT(*) // This ensures seed runs even when user-created agents exist const query = "SELECT count(*) FROM agents WHERE isSystem=true"; expect(query).toContain("isSystem=true"); expect(query).not.toBe("SELECT count(*) FROM agents"); }); it("skips seed when system agents already exist", () => { const systemCount = 6; const shouldSkip = systemCount > 0; expect(shouldSkip).toBe(true); }); it("runs seed when no system agents exist but user agents do", () => { const systemCount = 0; const shouldSkip = systemCount > 0; expect(shouldSkip).toBe(false); }); });