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 { listAssignmentBookings } from "@capakraken/application"; import { executeTool } from "../router/assistant-tools.js"; import { createToolContext } from "./assistant-tools-advanced-timeline-test-helpers.js"; describe("assistant advanced project timeline context tool", () => { beforeEach(() => { vi.clearAllMocks(); vi.mocked(listAssignmentBookings).mockResolvedValue([]); }); it("returns project timeline context with cross-project overlap summaries", async () => { const project = { id: "project_ctx", name: "Gelddruckmaschine", shortCode: "GDM", orderType: "CHARGEABLE", budgetCents: 100000, winProbability: 100, status: "ACTIVE", startDate: new Date("2026-01-05T00:00:00.000Z"), endDate: new Date("2026-01-16T00:00:00.000Z"), staffingReqs: null, }; vi.mocked(listAssignmentBookings).mockResolvedValue([ { id: "asg_project", projectId: "project_ctx", resourceId: "res_1", startDate: new Date("2026-01-05T00:00:00.000Z"), endDate: new Date("2026-01-16T00:00:00.000Z"), hoursPerDay: 6, dailyCostCents: 0, status: "CONFIRMED", project: { id: "project_ctx", name: "Gelddruckmaschine", shortCode: "GDM", status: "ACTIVE", orderType: "CHARGEABLE", dynamicFields: null }, resource: { id: "res_1", displayName: "Alice", chapter: "Delivery" }, }, { id: "asg_other", projectId: "project_other", resourceId: "res_1", startDate: new Date("2026-01-08T00:00:00.000Z"), endDate: new Date("2026-01-10T00:00:00.000Z"), hoursPerDay: 4, dailyCostCents: 0, status: "CONFIRMED", project: { id: "project_other", name: "Other Project", shortCode: "OTH", status: "ACTIVE", orderType: "CHARGEABLE", dynamicFields: null }, resource: { id: "res_1", displayName: "Alice", chapter: "Delivery" }, }, ]); const ctx = createToolContext( { project: { findUnique: vi .fn() .mockResolvedValueOnce(project) .mockResolvedValueOnce(project), }, demandRequirement: { findMany: vi.fn().mockResolvedValue([ { id: "dem_ctx", projectId: "project_ctx", resourceId: null, role: "Artist", hoursPerDay: 6, startDate: new Date("2026-01-05T00:00:00.000Z"), endDate: new Date("2026-01-16T00:00:00.000Z"), status: "OPEN", metadata: null, project, roleEntity: null, }, ]), }, assignment: { findMany: vi.fn().mockResolvedValue([ { id: "asg_project", projectId: "project_ctx", resourceId: "res_1", role: "Artist", hoursPerDay: 6, startDate: new Date("2026-01-05T00:00:00.000Z"), endDate: new Date("2026-01-16T00:00:00.000Z"), status: "CONFIRMED", metadata: null, resource: { id: "res_1", displayName: "Alice", eid: "EMP-1", chapter: "Delivery", lcrCents: 10000, availability: { monday: 8, tuesday: 8, wednesday: 8, thursday: 8, friday: 8 }, }, project, roleEntity: null, }, ]), }, resource: { findMany: vi.fn().mockResolvedValue([ { id: "res_1", countryId: "country_de", federalState: "BY", metroCityId: "city_munich", country: { code: "DE" }, metroCity: { name: "Muenchen" }, }, ]), }, }, [PermissionKey.USE_ASSISTANT_ADVANCED_TOOLS], ); const result = await executeTool( "get_project_timeline_context", JSON.stringify({ projectIdentifier: "project_ctx", }), ctx, ); const parsed = JSON.parse(result.content) as { project: { id: string; shortCode: string }; summary: { demandCount: number; assignmentCount: number; conflictedAssignmentCount: number; overlayCount: number; }; assignmentConflicts: Array<{ assignmentId: string; crossProjectOverlapCount: number; overlaps: Array<{ projectShortCode: string; sameProject: boolean }>; }>; holidayOverlays: Array<{ startDate: string }>; }; expect(parsed.project).toEqual( expect.objectContaining({ id: "project_ctx", shortCode: "GDM", }), ); expect(parsed.summary).toEqual( expect.objectContaining({ demandCount: 1, assignmentCount: 1, conflictedAssignmentCount: 1, overlayCount: 1, }), ); expect(parsed.assignmentConflicts).toEqual([ expect.objectContaining({ assignmentId: "asg_project", crossProjectOverlapCount: 1, overlaps: expect.arrayContaining([ expect.objectContaining({ projectShortCode: "OTH", sameProject: false, }), ]), }), ]); expect(parsed.holidayOverlays).toEqual([ expect.objectContaining({ startDate: "2026-01-06", }), ]); }); });