import { beforeEach, describe, expect, it, vi } from "vitest"; import { listAssignmentBookings } from "@capakraken/application"; import { SystemRole } from "@capakraken/shared"; vi.mock("@capakraken/application", async (importOriginal) => { const actual = await importOriginal(); return { ...actual, approveEstimateVersion: vi.fn(), cloneEstimate: vi.fn(), commitDispoImportBatch: vi.fn(), countPlanningEntries: vi.fn().mockResolvedValue({ countsByRoleId: new Map() }), createEstimateExport: vi.fn(), createEstimatePlanningHandoff: vi.fn(), createEstimateRevision: vi.fn(), assessDispoImportReadiness: vi.fn(), loadResourceDailyAvailabilityContexts: vi.fn().mockResolvedValue(new Map()), getDashboardDemand: vi.fn().mockResolvedValue([]), getDashboardBudgetForecast: vi.fn().mockResolvedValue([]), getDashboardOverview: vi.fn(), getDashboardSkillGapSummary: vi.fn().mockResolvedValue({ roleGaps: [], totalOpenPositions: 0, skillSupplyTop10: [], resourcesByRole: [], }), getDashboardProjectHealth: vi.fn().mockResolvedValue([]), getDashboardPeakTimes: vi.fn().mockResolvedValue([]), getDashboardTopValueResources: vi.fn().mockResolvedValue([]), getEstimateById: vi.fn(), listAssignmentBookings: vi.fn().mockResolvedValue([]), stageDispoImportBatch: vi.fn(), submitEstimateVersion: vi.fn(), updateEstimateDraft: vi.fn(), }; }); import { executeTool } from "../router/assistant-tools.js"; import { createToolContext } from "./assistant-tools-insights-scenarios-test-helpers.js"; describe("assistant scenario tools", () => { beforeEach(() => { vi.clearAllMocks(); vi.mocked(listAssignmentBookings).mockResolvedValue([]); }); it("routes scenario simulation through the scenario router path", async () => { const db = { project: { findUnique: vi.fn().mockResolvedValue({ id: "project_1", name: "Gelddruckmaschine", budgetCents: 200_000, orderType: "CHARGEABLE", startDate: new Date("2026-04-01T00:00:00.000Z"), endDate: new Date("2026-04-30T00:00:00.000Z"), }), }, assignment: { findMany: vi .fn() .mockResolvedValueOnce([ { id: "assign_1", resourceId: "res_1", projectId: "project_1", hoursPerDay: 4, startDate: new Date("2026-04-01T00:00:00.000Z"), endDate: new Date("2026-04-03T00:00:00.000Z"), status: "ACTIVE", roleId: null, role: "Pipeline TD", resource: { id: "res_1", displayName: "Bruce Banner", eid: "EMP-001", lcrCents: 10_000, availability: { monday: 8, tuesday: 8, wednesday: 8, thursday: 8, friday: 8 }, chargeabilityTarget: 80, skills: [{ skill: "Houdini" }], countryId: null, federalState: null, metroCityId: null, country: null, metroCity: null, }, roleEntity: null, }, ]) .mockResolvedValueOnce([ { id: "assign_1", resourceId: "res_1", projectId: "project_1", hoursPerDay: 4, startDate: new Date("2026-04-01T00:00:00.000Z"), endDate: new Date("2026-04-03T00:00:00.000Z"), }, ]), }, resource: { findMany: vi.fn().mockResolvedValue([ { id: "res_1", displayName: "Bruce Banner", eid: "EMP-001", lcrCents: 10_000, availability: { monday: 8, tuesday: 8, wednesday: 8, thursday: 8, friday: 8 }, chargeabilityTarget: 80, skills: [{ skill: "Houdini" }], countryId: null, federalState: null, metroCityId: null, country: null, metroCity: null, }, ]), }, role: { findMany: vi.fn(), }, }; const ctx = createToolContext(db, { userRole: SystemRole.CONTROLLER }); const result = await executeTool( "simulate_scenario", JSON.stringify({ projectId: "project_1", changes: [ { assignmentId: "assign_1", resourceId: "res_1", startDate: "2026-04-01", endDate: "2026-04-03", hoursPerDay: 6, }, ], }), ctx, ); expect(db.assignment.findMany).toHaveBeenNthCalledWith(1, { where: { projectId: "project_1", status: { not: "CANCELLED" } }, include: { resource: { select: { id: true, displayName: true, eid: true, lcrCents: true, availability: true, chargeabilityTarget: true, skills: true, countryId: true, federalState: true, metroCityId: true, country: { select: { code: true } }, metroCity: { select: { name: true } }, }, }, }, }); expect(db.assignment.findMany).toHaveBeenNthCalledWith(2, { where: { resourceId: { in: ["res_1"] }, status: { not: "CANCELLED" }, }, select: { id: true, resourceId: true, projectId: true, hoursPerDay: true, startDate: true, endDate: true, }, }); expect(db.role.findMany).not.toHaveBeenCalled(); expect(JSON.parse(result.content)).toEqual({ baseline: { totalCostCents: 120000, totalHours: 12, headcount: 1, skillCount: 1, totalCost: "1.200,00 EUR", }, scenario: { totalCostCents: 180000, totalHours: 18, headcount: 1, skillCount: 1, totalCost: "1.800,00 EUR", }, delta: { costCents: 60000, hours: 6, headcount: 0, skillCoveragePct: 100, cost: "600,00 EUR", }, resourceImpacts: [ { resourceId: "res_1", resourceName: "Bruce Banner", chargeabilityTarget: 80, currentUtilization: 6.8, scenarioUtilization: 10.2, utilizationDelta: 3.4, isOverallocated: false, }, ], warnings: [], budgetCents: 200000, }); }); });