Files
CapaKraken/packages/api/src/__tests__/insights-router.test.ts
T

199 lines
6.4 KiB
TypeScript

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<string, unknown>) {
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();
}
});
});