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(), getDashboardBudgetForecast: vi.fn(), getDashboardSkillGaps: vi.fn(), getDashboardSkillGapSummary: vi.fn(), getDashboardProjectHealth: 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.map((resource) => ({ ...resource, anonymized: true }))), getAnonymizationDirectory: vi.fn().mockResolvedValue({}), })); import { getDashboardChargeabilityOverview, getDashboardDemand, getDashboardOverview, getDashboardPeakTimes, getDashboardTopValueResources, } from "@capakraken/application"; import { anonymizeResources } from "../lib/anonymization.js"; import { getDashboardChargeabilityOverviewRead, getDashboardDetail, getDashboardStatisticsDetail, } from "../router/dashboard-procedure-support.js"; function createContext() { return { db: {} as never, session: { user: { email: "controller@example.com", name: "Controller", image: null }, expires: "2099-01-01T00:00:00.000Z", }, dbUser: { id: "user_1", systemRole: SystemRole.CONTROLLER, permissionOverrides: null, }, }; } describe("dashboard procedure support", () => { beforeEach(() => { vi.clearAllMocks(); }); it("derives statistics detail from the canonical overview", async () => { vi.mocked(getDashboardOverview).mockResolvedValue({ totalResources: 12, activeResources: 10, inactiveResources: 2, totalProjects: 7, activeProjects: 4, inactiveProjects: 3, totalAllocations: 21, activeAllocations: 18, cancelledAllocations: 3, approvedVacations: 6, totalEstimates: 9, budgetSummary: { totalBudgetCents: 1_234_56, totalCostCents: 654_32, avgUtilizationPercent: 53, }, budgetBasis: { remainingBudgetCents: 58_024, budgetedProjects: 5, unbudgetedProjects: 2, trackedAssignmentCount: 18, windowStart: null, windowEnd: null, }, projectsByStatus: [ { status: "ACTIVE", count: 4 }, { status: "DRAFT", count: 2 }, { status: "DONE", count: 1 }, ], chapterUtilization: [ { chapter: "CGI", resourceCount: 5, avgChargeabilityTarget: 78 }, { chapter: "Compositing", resourceCount: 3, avgChargeabilityTarget: 74 }, ], recentActivity: [], }); const result = await getDashboardStatisticsDetail(createContext()); expect(result).toEqual({ activeResources: 10, totalProjects: 7, activeProjects: 4, totalAllocations: 21, approvedVacations: 6, totalEstimates: 9, totalBudget: "1.234,56 EUR", projectsByStatus: { ACTIVE: 4, DRAFT: 2, DONE: 1, }, topChapters: [ { chapter: "CGI", count: 5 }, { chapter: "Compositing", count: 3 }, ], }); }); it("builds the assistant-facing dashboard detail payload", async () => { vi.useFakeTimers(); vi.setSystemTime(new Date("2026-03-15T00:00:00.000Z")); vi.mocked(getDashboardOverview).mockResolvedValue({ totalResources: 12, activeResources: 10, inactiveResources: 2, totalProjects: 7, activeProjects: 4, inactiveProjects: 3, totalAllocations: 21, activeAllocations: 18, cancelledAllocations: 3, approvedVacations: 6, totalEstimates: 9, budgetSummary: { totalBudgetCents: 1_234_56, totalCostCents: 654_32, avgUtilizationPercent: 53, }, budgetBasis: { remainingBudgetCents: 58_024, budgetedProjects: 5, unbudgetedProjects: 2, trackedAssignmentCount: 18, windowStart: "2026-03-01T00:00:00.000Z", windowEnd: "2026-06-30T00:00:00.000Z", }, projectsByStatus: [], chapterUtilization: [ { chapter: "CGI", resourceCount: 5, avgChargeabilityTarget: 78 }, ], recentActivity: [], }); vi.mocked(getDashboardPeakTimes).mockResolvedValue([ { period: "2026-03", totalHours: 160.34, capacityHours: 200.12, utilizationPct: 80, derivation: { calendarContextCount: 1, calendarLocations: [{ countryCode: "DE", federalState: "BY", metroCityName: "Munich" }], }, }, ]); vi.mocked(getDashboardTopValueResources).mockResolvedValue([ { id: "res_1", eid: "E-001", displayName: "Ada Lovelace", chapter: "CGI", lcrCents: 12345, valueScore: 98, valueScoreBreakdown: { skillDepth: 94, skillBreadth: 86, costEfficiency: 82, chargeability: 88, experience: 96, total: 98, }, valueScoreUpdatedAt: new Date("2026-03-05T00:00:00.000Z"), countryCode: "DE", countryName: "Germany", federalState: "BY", metroCityName: "Munich", }, ]); vi.mocked(getDashboardDemand).mockResolvedValue([ { name: "Apollo", shortCode: "APO", requiredFTEs: 3, resourceCount: 1, allocatedHours: 80, derivation: { calendarLocations: [{ countryCode: "DE" }] }, }, ]); try { const result = await getDashboardDetail(createContext(), { section: "all" }); expect(result).toEqual({ peakTimes: [ { month: "2026-03", totalHours: 160.3, totalHoursPerDay: 160.3, capacityHours: 200.1, utilizationPct: 80, calendarContextCount: 1, calendarLocations: [{ countryCode: "DE", federalState: "BY", metroCityName: "Munich" }], }, ], topResources: [ { name: "Ada Lovelace", eid: "E-001", chapter: "CGI", lcr: "123,45 EUR", valueScore: 98, valueScoreBreakdown: { skillDepth: 94, skillBreadth: 86, costEfficiency: 82, chargeability: 88, experience: 96, total: 98, }, valueScoreUpdatedAt: "2026-03-05T00:00:00.000Z", countryCode: "DE", countryName: "Germany", federalState: "BY", metroCityName: "Munich", }, ], demandPipeline: [ { project: "Apollo (APO)", needed: 2, requiredFTEs: 3, allocatedResources: 1, allocatedHours: 80, calendarLocations: [{ countryCode: "DE" }], }, ], chargeabilityByChapter: [ { chapter: "CGI", headcount: 5, avgTarget: "78%", }, ], }); } finally { vi.useRealTimers(); } }); it("anonymizes chargeability overview payloads before returning them", async () => { vi.mocked(getDashboardChargeabilityOverview).mockResolvedValue({ avgChargeability: 72, top: [{ id: "res_1" }], watchlist: [{ id: "res_2" }], }); const result = await getDashboardChargeabilityOverviewRead(createContext(), { includeProposed: false, topN: 10, watchlistThreshold: 15, }); expect(anonymizeResources).toHaveBeenCalledTimes(2); expect(result).toEqual({ avgChargeability: 72, top: [{ id: "res_1", anonymized: true }], watchlist: [{ id: "res_2", anonymized: true }], }); }); });