refactor(api): extract timeline project detail artifact loader

This commit is contained in:
2026-03-31 15:44:13 +02:00
parent 231d3f3124
commit 49bf3f3214
3 changed files with 234 additions and 30 deletions
@@ -1,5 +1,18 @@
import { TRPCError } from "@trpc/server";
import { describe, expect, it } from "vitest";
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 {
buildTimelineBudgetStatusAllocations,
buildTimelineBudgetStatusResponse,
@@ -9,10 +22,22 @@ import {
buildTimelineProjectContextSummary,
buildTimelineShiftValidationBookings,
buildTimelineShiftPreviewDetailResponse,
loadTimelineProjectContextDetailArtifacts,
resolveTimelineProjectContextPeriod,
} from "../router/timeline-project-context-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",
@@ -451,4 +476,127 @@ describe("timeline project context support", () => {
budgetCents: 120000,
});
});
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([]);
});
});