import { TRPCError } from "@trpc/server"; import { beforeEach, describe, expect, it, vi } from "vitest"; vi.mock("../router/timeline-holiday-read.js", async () => { const actual = await vi.importActual( "../router/timeline-holiday-read.js", ); return { ...actual, loadTimelineHolidayOverlays: vi.fn(), formatHolidayOverlays: vi.fn(actual.formatHolidayOverlays), }; }); import { loadTimelineProjectContextDetailArtifacts, resolveTimelineProjectContextPeriod, } from "../router/timeline-project-context-support.js"; import { buildTimelineProjectAssignmentConflicts } from "../router/timeline-project-conflict-support.js"; import { formatHolidayOverlays, loadTimelineHolidayOverlays, } from "../router/timeline-holiday-read.js"; const loadTimelineHolidayOverlaysMock = vi.mocked(loadTimelineHolidayOverlays); const formatHolidayOverlaysMock = vi.mocked(formatHolidayOverlays); describe("timeline project context support", () => { beforeEach(() => { vi.clearAllMocks(); }); it("derives the detail period from explicit input and validates order", () => { expect(resolveTimelineProjectContextPeriod({ requestedStartDate: "2026-04-03", requestedEndDate: "2026-04-12", projectStartDate: new Date("2026-04-01T00:00:00.000Z"), projectEndDate: new Date("2026-04-10T00:00:00.000Z"), })).toEqual({ startDate: new Date("2026-04-03T00:00:00.000Z"), endDate: new Date("2026-04-12T00:00:00.000Z"), }); expect(() => resolveTimelineProjectContextPeriod({ requestedStartDate: "2026-04-12", requestedEndDate: "2026-04-03", }), ).toThrowError(new TRPCError({ code: "BAD_REQUEST", message: "endDate must be on or after startDate.", })); }); it("builds assignment conflict summaries with cross-project overlap counts", () => { expect(buildTimelineProjectAssignmentConflicts({ projectId: "project_1", assignments: [ { id: "assignment_1", resourceId: "resource_1", resource: { displayName: "Alice" }, startDate: new Date("2026-04-01T00:00:00.000Z"), endDate: new Date("2026-04-05T00:00:00.000Z"), hoursPerDay: 8, }, { id: "placeholder_1", resourceId: null, resource: null, startDate: new Date("2026-04-01T00:00:00.000Z"), endDate: new Date("2026-04-05T00:00:00.000Z"), hoursPerDay: 4, }, ], allResourceAllocations: [ { id: "assignment_1", resourceId: "resource_1", projectId: "project_1", project: { name: "Current", shortCode: "CUR" }, startDate: new Date("2026-04-01T00:00:00.000Z"), endDate: new Date("2026-04-05T00:00:00.000Z"), hoursPerDay: 8, status: "ACTIVE", }, { id: "booking_2", resourceId: "resource_1", projectId: "project_2", project: { name: "Other", shortCode: "OTH" }, startDate: new Date("2026-04-04T00:00:00.000Z"), endDate: new Date("2026-04-07T00:00:00.000Z"), hoursPerDay: 6, status: "ACTIVE", }, { id: "booking_3", resourceId: "resource_1", projectId: "project_1", project: { name: "Current", shortCode: "CUR" }, startDate: new Date("2026-04-02T00:00:00.000Z"), endDate: new Date("2026-04-03T00:00:00.000Z"), hoursPerDay: 2, status: "PROPOSED", }, ], })).toEqual([ { assignmentId: "assignment_1", resourceId: "resource_1", resourceName: "Alice", startDate: "2026-04-01", endDate: "2026-04-05", hoursPerDay: 8, overlapCount: 2, crossProjectOverlapCount: 1, overlaps: [ { id: "booking_2", projectId: "project_2", projectName: "Other", projectShortCode: "OTH", startDate: "2026-04-04", endDate: "2026-04-07", hoursPerDay: 6, status: "ACTIVE", sameProject: false, }, { id: "booking_3", projectId: "project_1", projectName: "Current", projectShortCode: "CUR", startDate: "2026-04-02", endDate: "2026-04-03", hoursPerDay: 2, status: "PROPOSED", sameProject: true, }, ], }, ]); }); it("loads detail artifacts with formatted holiday overlays only when resources exist", async () => { loadTimelineHolidayOverlaysMock.mockResolvedValueOnce([ { id: "overlay_1", resourceId: "resource_1", startDate: new Date("2026-04-03T00:00:00.000Z"), endDate: new Date("2026-04-03T00:00:00.000Z"), note: "Holiday", scope: "COUNTRY", calendarName: "DE", sourceType: "CALENDAR", countryCode: "DE", countryName: "Germany", federalState: null, metroCityName: null, }, ] as never); const artifacts = await loadTimelineProjectContextDetailArtifacts({} as never, { projectId: "project_1", requestedStartDate: "2026-04-01", durationDays: 5, projectStartDate: new Date("2026-04-01T00:00:00.000Z"), projectEndDate: new Date("2026-04-10T00:00:00.000Z"), firstAssignmentStartDate: new Date("2026-04-02T00:00:00.000Z"), firstDemandStartDate: new Date("2026-04-01T00:00:00.000Z"), assignments: [ { id: "assignment_1", resourceId: "resource_1", resource: { displayName: "Alice" }, startDate: new Date("2026-04-01T00:00:00.000Z"), endDate: new Date("2026-04-05T00:00:00.000Z"), hoursPerDay: 8, }, ], allResourceAllocations: [ { id: "booking_1", resourceId: "resource_1", projectId: "project_2", project: { name: "Other", shortCode: "OTH" }, startDate: new Date("2026-04-04T00:00:00.000Z"), endDate: new Date("2026-04-07T00:00:00.000Z"), hoursPerDay: 6, status: "ACTIVE", }, ], resourceIds: ["resource_1"], }); expect(loadTimelineHolidayOverlaysMock).toHaveBeenCalledWith( {}, { startDate: new Date("2026-04-01T00:00:00.000Z"), endDate: new Date("2026-04-10T00:00:00.000Z"), resourceIds: ["resource_1"], projectIds: ["project_1"], }, ); expect(formatHolidayOverlaysMock).toHaveBeenCalledTimes(1); expect(artifacts).toEqual({ period: { startDate: new Date("2026-04-01T00:00:00.000Z"), endDate: new Date("2026-04-10T00:00:00.000Z"), }, holidayOverlays: [ { id: "overlay_1", resourceId: "resource_1", startDate: "2026-04-03", endDate: "2026-04-03", note: "Holiday", scope: "COUNTRY", calendarName: "DE", sourceType: "CALENDAR", countryCode: "DE", countryName: "Germany", federalState: null, metroCityName: null, }, ], assignmentConflicts: [ { assignmentId: "assignment_1", resourceId: "resource_1", resourceName: "Alice", startDate: "2026-04-01", endDate: "2026-04-05", hoursPerDay: 8, overlapCount: 1, crossProjectOverlapCount: 1, overlaps: [ { id: "booking_1", projectId: "project_2", projectName: "Other", projectShortCode: "OTH", startDate: "2026-04-04", endDate: "2026-04-07", hoursPerDay: 6, status: "ACTIVE", sameProject: false, }, ], }, ], }); const emptyArtifacts = await loadTimelineProjectContextDetailArtifacts({} as never, { projectId: "project_1", requestedStartDate: "2026-04-01", durationDays: 5, assignments: [], allResourceAllocations: [], resourceIds: [], }); expect(loadTimelineHolidayOverlaysMock).toHaveBeenCalledTimes(1); expect(emptyArtifacts.holidayOverlays).toEqual([]); expect(emptyArtifacts.assignmentConflicts).toEqual([]); }); });