import { SystemRole } from "@capakraken/shared"; import { beforeEach, describe, expect, it, vi } from "vitest"; vi.mock("@capakraken/application", async (importOriginal) => { const actual = await importOriginal(); return { ...actual, getDashboardOverview: vi.fn(), getDashboardPeakTimes: vi.fn(), getDashboardDemand: vi.fn(), getDashboardTopValueResources: vi.fn(), getDashboardChargeabilityOverview: vi.fn(), }; }); vi.mock("../lib/cache.js", () => ({ cacheGet: vi.fn().mockResolvedValue(null), cacheSet: vi.fn().mockResolvedValue(undefined), })); vi.mock("../lib/anonymization.js", () => ({ anonymizeResources: vi.fn((resources: unknown[]) => resources), getAnonymizationDirectory: vi.fn().mockResolvedValue(null), })); import { getDashboardOverview, getDashboardPeakTimes, getDashboardDemand, getDashboardTopValueResources, getDashboardChargeabilityOverview, } from "@capakraken/application"; import { dashboardRouter } from "../router/dashboard.js"; import { createCallerFactory } from "../trpc.js"; const createCaller = createCallerFactory(dashboardRouter); function createProtectedCaller(db: Record) { return createCaller({ session: { user: { email: "user@example.com", name: "User", image: null }, expires: "2099-01-01T00:00:00.000Z", }, db: db as never, dbUser: { id: "user_1", systemRole: SystemRole.USER, permissionOverrides: null, }, }); } function createControllerCaller(db: Record) { return createCaller({ session: { user: { email: "controller@example.com", name: "Controller", image: null }, expires: "2099-01-01T00:00:00.000Z", }, db: db as never, dbUser: { id: "user_2", systemRole: SystemRole.CONTROLLER, permissionOverrides: null, }, }); } function createUnauthenticatedCaller(db: Record) { return createCaller({ session: null, db: db as never, dbUser: null, }); } describe("dashboard router", () => { beforeEach(() => { vi.clearAllMocks(); }); // ─── getOverview ────────────────────────────────────────────────────────── describe("getOverview", () => { it("returns expected shape with resource and project counts", async () => { const overview = { totalResources: 42, activeResources: 38, totalProjects: 15, activeProjects: 10, draftProjects: 3, completedProjects: 2, totalBudgetCents: 5_000_000_00, avgWinProbability: 78, }; vi.mocked(getDashboardOverview).mockResolvedValue(overview); const caller = createProtectedCaller({}); const result = await caller.getOverview(); expect(result).toMatchObject({ totalResources: 42, activeResources: 38, totalProjects: 15, activeProjects: 10, }); expect(getDashboardOverview).toHaveBeenCalledTimes(1); }); it("rejects unauthenticated users", async () => { const caller = createUnauthenticatedCaller({}); await expect(caller.getOverview()).rejects.toThrow("Authentication required"); }); }); // ─── getPeakTimes ───────────────────────────────────────────────────────── describe("getPeakTimes", () => { it("returns array of time periods", async () => { const peakData = [ { period: "2026-03", totalHours: 1200, entries: 15 }, { period: "2026-04", totalHours: 1400, entries: 18 }, ]; vi.mocked(getDashboardPeakTimes).mockResolvedValue(peakData); const caller = createProtectedCaller({}); const result = await caller.getPeakTimes({ startDate: "2026-03-01T00:00:00.000Z", endDate: "2026-06-30T00:00:00.000Z", granularity: "month", groupBy: "project", }); expect(result).toHaveLength(2); expect(result[0]).toHaveProperty("period", "2026-03"); expect(getDashboardPeakTimes).toHaveBeenCalledWith( expect.anything(), expect.objectContaining({ granularity: "month", groupBy: "project", }), ); }); it("passes week granularity to application layer", async () => { vi.mocked(getDashboardPeakTimes).mockResolvedValue([]); const caller = createProtectedCaller({}); await caller.getPeakTimes({ startDate: "2026-03-01T00:00:00.000Z", endDate: "2026-03-31T00:00:00.000Z", granularity: "week", groupBy: "chapter", }); expect(getDashboardPeakTimes).toHaveBeenCalledWith( expect.anything(), expect.objectContaining({ granularity: "week", groupBy: "chapter", }), ); }); }); // ─── getDemand ──────────────────────────────────────────────────────────── describe("getDemand", () => { it("returns demand entries grouped by project", async () => { const demandData = [ { groupKey: "Project Alpha", totalHours: 500, headcount: 3 }, { groupKey: "Project Beta", totalHours: 300, headcount: 2 }, ]; vi.mocked(getDashboardDemand).mockResolvedValue(demandData); const caller = createProtectedCaller({}); const result = await caller.getDemand({ startDate: "2026-01-01T00:00:00.000Z", endDate: "2026-12-31T00:00:00.000Z", groupBy: "project", }); expect(result).toHaveLength(2); expect(getDashboardDemand).toHaveBeenCalledWith( expect.anything(), expect.objectContaining({ groupBy: "project" }), ); }); it("supports grouping by chapter", async () => { vi.mocked(getDashboardDemand).mockResolvedValue([]); const caller = createProtectedCaller({}); await caller.getDemand({ startDate: "2026-06-01T00:00:00.000Z", endDate: "2026-06-30T00:00:00.000Z", groupBy: "chapter", }); expect(getDashboardDemand).toHaveBeenCalledWith( expect.anything(), expect.objectContaining({ groupBy: "chapter" }), ); }); }); // ─── getTopValueResources ───────────────────────────────────────────────── describe("getTopValueResources", () => { it("returns sorted resources with default limit", async () => { const resources = [ { id: "res_1", displayName: "Alice", valueScore: 95 }, { id: "res_2", displayName: "Bob", valueScore: 88 }, ]; vi.mocked(getDashboardTopValueResources).mockResolvedValue(resources); const caller = createProtectedCaller({}); const result = await caller.getTopValueResources({ limit: 10 }); expect(result).toHaveLength(2); expect(getDashboardTopValueResources).toHaveBeenCalledWith( expect.anything(), expect.objectContaining({ limit: 10 }), ); }); it("respects custom limit", async () => { vi.mocked(getDashboardTopValueResources).mockResolvedValue([]); const caller = createProtectedCaller({}); await caller.getTopValueResources({ limit: 5 }); expect(getDashboardTopValueResources).toHaveBeenCalledWith( expect.anything(), expect.objectContaining({ limit: 5 }), ); }); }); // ─── getChargeabilityOverview ───────────────────────────────────────────── describe("getChargeabilityOverview", () => { it("returns chargeability data with top and watchlist arrays", async () => { const overview = { avgChargeability: 72, top: [{ id: "res_1", displayName: "Alice", chargeability: 95 }], watchlist: [{ id: "res_3", displayName: "Carol", chargeability: 30 }], }; vi.mocked(getDashboardChargeabilityOverview).mockResolvedValue(overview); const caller = createControllerCaller({}); const result = await caller.getChargeabilityOverview({ includeProposed: false, topN: 10, watchlistThreshold: 15, }); expect(result).toHaveProperty("top"); expect(result).toHaveProperty("watchlist"); expect(result.top).toHaveLength(1); expect(result.watchlist).toHaveLength(1); }); it("passes includeProposed flag to application layer", async () => { vi.mocked(getDashboardChargeabilityOverview).mockResolvedValue({ avgChargeability: 60, top: [], watchlist: [], }); const caller = createControllerCaller({}); await caller.getChargeabilityOverview({ includeProposed: true, topN: 5, watchlistThreshold: 20, }); expect(getDashboardChargeabilityOverview).toHaveBeenCalledWith( expect.anything(), expect.objectContaining({ includeProposed: true, topN: 5, watchlistThreshold: 20, }), ); }); it("requires controller role — blocks USER", async () => { const caller = createProtectedCaller({}); await expect( caller.getChargeabilityOverview({ includeProposed: false, topN: 10, watchlistThreshold: 15, }), ).rejects.toThrow( expect.objectContaining({ code: "FORBIDDEN" }), ); }); }); });