Files
CapaKraken/packages/api/src/__tests__/timeline-project-context-support.test.ts
T

270 lines
8.3 KiB
TypeScript

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<typeof import("../router/timeline-holiday-read.js")>(
"../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([]);
});
});