import { SystemRole } from "@capakraken/shared"; import { beforeEach, describe, expect, it, vi } from "vitest"; import { insightsRouter } from "../router/insights.js"; import { createCallerFactory } from "../trpc.js"; const createCaller = createCallerFactory(insightsRouter); 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_controller", systemRole: SystemRole.CONTROLLER, permissionOverrides: null, }, roleDefaults: null, }); } describe("insights router", () => { beforeEach(() => { vi.clearAllMocks(); }); it("derives the summary from the same canonical anomaly snapshot", async () => { vi.useFakeTimers(); vi.setSystemTime(new Date("2026-03-29T12:00:00.000Z")); try { const db = { project: { findMany: vi.fn().mockResolvedValue([ { id: "project_1", name: "Apollo", budgetCents: 100_000, startDate: new Date("2026-03-01T00:00:00.000Z"), endDate: new Date("2026-03-31T00:00:00.000Z"), demandRequirements: [ { headcount: 3, startDate: new Date("2026-03-20T00:00:00.000Z"), endDate: new Date("2026-04-05T00:00:00.000Z"), _count: { assignments: 1 }, }, ], assignments: [ { resourceId: "res_1", startDate: new Date("2026-03-01T00:00:00.000Z"), endDate: new Date("2026-04-05T00:00:00.000Z"), hoursPerDay: 12, dailyCostCents: 10_000, status: "ACTIVE", }, ], }, ]), }, resource: { findMany: vi.fn().mockResolvedValue([ { id: "res_1", displayName: "Peter Parker", availability: { monday: 8, tuesday: 8, wednesday: 8, thursday: 8, friday: 8, }, }, ]), }, assignment: { findMany: vi.fn().mockResolvedValue([ { resourceId: "res_1", hoursPerDay: 12, }, ]), }, }; const caller = createControllerCaller(db); const anomalies = await caller.detectAnomalies(); const summary = await caller.getInsightsSummary(); expect(anomalies).toEqual([ expect.objectContaining({ type: "budget", severity: "critical", entityName: "Apollo" }), expect.objectContaining({ type: "staffing", severity: "critical", entityName: "Apollo" }), expect.objectContaining({ type: "utilization", severity: "critical", entityName: "Peter Parker" }), expect.objectContaining({ type: "timeline", severity: "warning", entityName: "Apollo" }), ]); expect(summary).toEqual({ total: anomalies.length, criticalCount: anomalies.filter((anomaly) => anomaly.severity === "critical").length, budget: anomalies.filter((anomaly) => anomaly.type === "budget").length, staffing: anomalies.filter((anomaly) => anomaly.type === "staffing").length, timeline: anomalies.filter((anomaly) => anomaly.type === "timeline").length, utilization: anomalies.filter((anomaly) => anomaly.type === "utilization").length, }); expect(db.project.findMany).toHaveBeenCalledTimes(2); expect(db.resource.findMany).toHaveBeenCalledWith({ where: { isActive: true }, select: { id: true, displayName: true, availability: true, }, }); } finally { vi.useRealTimers(); } }); it("returns assistant-friendly anomaly detail from the canonical snapshot", async () => { vi.useFakeTimers(); vi.setSystemTime(new Date("2026-03-15T00:00:00.000Z")); try { const db = { project: { findMany: vi.fn().mockResolvedValue([ { id: "project_1", name: "Apollo", budgetCents: 100_000, startDate: new Date("2026-03-01T00:00:00.000Z"), endDate: new Date("2026-03-31T00:00:00.000Z"), demandRequirements: [ { headcount: 3, startDate: new Date("2026-03-10T00:00:00.000Z"), endDate: new Date("2026-03-20T00:00:00.000Z"), _count: { assignments: 1 }, }, ], assignments: [ { resourceId: "res_1", startDate: new Date("2026-03-01T00:00:00.000Z"), endDate: new Date("2026-04-05T00:00:00.000Z"), hoursPerDay: 12, dailyCostCents: 10_000, status: "ACTIVE", }, ], }, ]), }, resource: { findMany: vi.fn().mockResolvedValue([ { id: "res_1", displayName: "Peter Parker", availability: { monday: 8, tuesday: 8, wednesday: 8, thursday: 8, friday: 8, }, }, ]), }, assignment: { findMany: vi.fn().mockResolvedValue([ { resourceId: "res_1", hoursPerDay: 12, }, ]), }, }; const caller = createControllerCaller(db); const result = await caller.getAnomalyDetail(); expect(result).toEqual({ count: 4, anomalies: [ expect.objectContaining({ type: "budget", severity: "critical", entityName: "Apollo" }), expect.objectContaining({ type: "staffing", severity: "critical", entityName: "Apollo" }), expect.objectContaining({ type: "utilization", severity: "critical", entityName: "Peter Parker" }), expect.objectContaining({ type: "timeline", severity: "warning", entityName: "Apollo" }), ], }); expect(db.project.findMany).toHaveBeenCalledTimes(1); } finally { vi.useRealTimers(); } }); it("returns the cached narrative through the controller router", async () => { const projectFindUnique = vi.fn().mockResolvedValue({ dynamicFields: { aiNarrative: "Cached summary", aiNarrativeGeneratedAt: "2026-03-30T08:00:00.000Z", }, }); const caller = createControllerCaller({ project: { findUnique: projectFindUnique, }, }); const result = await caller.getCachedNarrative({ projectId: "project_1" }); expect(projectFindUnique).toHaveBeenCalledWith({ where: { id: "project_1" }, select: { dynamicFields: true }, }); expect(result).toEqual({ narrative: "Cached summary", generatedAt: "2026-03-30T08:00:00.000Z", }); }); });