import { beforeEach, describe, expect, it, vi } from "vitest"; import { PermissionKey } from "@capakraken/shared"; vi.mock("@capakraken/application", async (importOriginal) => { const actual = await importOriginal(); return { ...actual, getDashboardBudgetForecast: vi.fn().mockResolvedValue([]), getDashboardPeakTimes: vi.fn().mockResolvedValue([]), listAssignmentBookings: vi.fn().mockResolvedValue([]), }; }); vi.mock("../sse/event-bus.js", () => ({ emitAllocationCreated: vi.fn(), emitAllocationDeleted: vi.fn(), emitAllocationUpdated: vi.fn(), emitProjectShifted: vi.fn(), })); vi.mock("../lib/budget-alerts.js", () => ({ checkBudgetThresholds: vi.fn(), })); vi.mock("../lib/cache.js", () => ({ invalidateDashboardCache: vi.fn(), })); import { executeTool } from "../router/assistant-tools.js"; import { createToolContext } from "./assistant-tools-advanced-timeline-test-helpers.js"; describe("assistant timeline resource selection tools", () => { beforeEach(() => { vi.clearAllMocks(); }); it("finds the best project resource with holiday-aware remaining capacity and LCR ranking", async () => { const assignmentFindMany = vi .fn() .mockResolvedValueOnce([ { resourceId: "res_carol", hoursPerDay: 2, startDate: new Date("2026-01-05T00:00:00.000Z"), endDate: new Date("2026-01-16T00:00:00.000Z"), status: "PROPOSED", resource: { id: "res_carol", eid: "carol.danvers", displayName: "Carol Danvers", chapter: "Delivery", lcrCents: 7664, availability: { monday: 8, tuesday: 8, wednesday: 8, thursday: 8, friday: 8 }, countryId: "country_de", federalState: "HH", metroCityId: "city_hamburg", country: { code: "DE", name: "Deutschland" }, metroCity: { name: "Hamburg" }, areaRole: { name: "Artist" }, }, }, { resourceId: "res_steve", hoursPerDay: 4, startDate: new Date("2026-01-05T00:00:00.000Z"), endDate: new Date("2026-01-16T00:00:00.000Z"), status: "CONFIRMED", resource: { id: "res_steve", eid: "steve.rogers", displayName: "Steve Rogers", chapter: "Delivery", lcrCents: 13377, availability: { monday: 8, tuesday: 8, wednesday: 8, thursday: 8, friday: 8 }, countryId: "country_de", federalState: "BY", metroCityId: "city_augsburg", country: { code: "DE", name: "Deutschland" }, metroCity: { name: "Augsburg" }, areaRole: { name: "Artist" }, }, }, ]) .mockResolvedValueOnce([ { resourceId: "res_carol", projectId: "project_lari", hoursPerDay: 2, startDate: new Date("2026-01-05T00:00:00.000Z"), endDate: new Date("2026-01-16T00:00:00.000Z"), status: "PROPOSED", project: { name: "Gelddruckmaschine", shortCode: "LARI" }, }, { resourceId: "res_steve", projectId: "project_lari", hoursPerDay: 4, startDate: new Date("2026-01-05T00:00:00.000Z"), endDate: new Date("2026-01-16T00:00:00.000Z"), status: "CONFIRMED", project: { name: "Gelddruckmaschine", shortCode: "LARI" }, }, ]); const ctx = createToolContext( { project: { findUnique: vi .fn() .mockResolvedValueOnce(null) .mockResolvedValueOnce({ id: "project_lari", name: "Gelddruckmaschine", shortCode: "LARI", status: "ACTIVE", responsiblePerson: "Larissa Joos", startDate: new Date("2026-01-05T00:00:00.000Z"), endDate: new Date("2026-01-16T00:00:00.000Z"), }) .mockResolvedValueOnce({ id: "project_lari", name: "Gelddruckmaschine", shortCode: "LARI", status: "ACTIVE", responsiblePerson: "Larissa Joos", }), findFirst: vi.fn(), }, assignment: { findMany: assignmentFindMany, }, vacation: { findMany: vi.fn().mockResolvedValue([]), }, }, [ PermissionKey.VIEW_COSTS, PermissionKey.VIEW_PLANNING, PermissionKey.USE_ASSISTANT_ADVANCED_TOOLS, ], ); const result = await executeTool( "find_best_project_resource", JSON.stringify({ projectIdentifier: "LARI", startDate: "2026-01-05", endDate: "2026-01-16", minHoursPerDay: 3, rankingMode: "lowest_lcr", }), ctx, ); const parsed = JSON.parse(result.content) as { project: { shortCode: string }; candidateCount: number; bestMatch: { name: string; remainingHoursPerDay: number; lcrCents: number | null; federalState: string | null; metroCity: string | null; baseAvailableHours: number; holidaySummary: { count: number }; }; candidates: Array<{ name: string; remainingHoursPerDay: number; workingDays: number; baseAvailableHours: number; holidaySummary: { count: number; hoursDeduction: number }; capacityBreakdown: { holidayHoursDeduction: number }; }>; }; expect(parsed.project.shortCode).toBe("LARI"); expect(parsed.candidateCount).toBe(2); expect(parsed.bestMatch).toEqual( expect.objectContaining({ name: "Carol Danvers", remainingHoursPerDay: 6, lcrCents: 7664, federalState: "HH", metroCity: "Hamburg", baseAvailableHours: 80, holidaySummary: expect.objectContaining({ count: 0 }), }), ); expect(parsed.candidates).toEqual([ expect.objectContaining({ name: "Carol Danvers", remainingHoursPerDay: 6, workingDays: 10, baseAvailableHours: 80, holidaySummary: expect.objectContaining({ count: 0, hoursDeduction: 0 }), capacityBreakdown: expect.objectContaining({ holidayHoursDeduction: 0 }), }), expect.objectContaining({ name: "Steve Rogers", remainingHoursPerDay: 4, workingDays: 9, baseAvailableHours: 80, holidaySummary: expect.objectContaining({ count: 1, hoursDeduction: 8 }), capacityBreakdown: expect.objectContaining({ holidayHoursDeduction: 8 }), }), ]); }); });