import type { ShiftValidationResult } from "@capakraken/shared"; import { TRPCError } from "@trpc/server"; import { describe, expect, it, vi } from "vitest"; const { validateShiftMock } = vi.hoisted(() => ({ validateShiftMock: vi.fn(), })); vi.mock("@capakraken/engine", async (importOriginal) => { const actual = await importOriginal(); return { ...actual, validateShift: validateShiftMock, }; }); import { assertTimelineProjectShiftValid, buildTimelineProjectShiftEventPayload, buildTimelineProjectShiftValidation, } from "../router/timeline-shift-support.js"; function createValidValidationResult( overrides: Partial = {}, ): ShiftValidationResult { return { valid: true, errors: [], warnings: [], conflictDetails: [], costImpact: { currentTotalCents: 100_000, newTotalCents: 112_000, deltaCents: 12_000, budgetCents: 200_000, budgetUtilizationBefore: 50, budgetUtilizationAfter: 56, wouldExceedBudget: false, }, ...overrides, }; } describe("timeline shift support", () => { it("builds validation requests from loaded shift context", () => { const expectedValidation = createValidValidationResult(); validateShiftMock.mockReturnValue(expectedValidation); const result = buildTimelineProjectShiftValidation({ context: { project: { id: "project_1", budgetCents: 200_000, winProbability: 80, startDate: new Date("2026-04-01"), endDate: new Date("2026-04-10"), }, demandRequirements: [], assignments: [], shiftPlan: { validationAllocations: [ { id: "assignment_1", resourceId: "resource_1", startDate: new Date("2026-04-01"), endDate: new Date("2026-04-05"), hoursPerDay: 8, percentage: 100, role: "Compositing", dailyCostCents: 40_000, resource: { id: "resource_1", displayName: "Alice", lcrCents: 5_000, availability: { monday: 8, tuesday: 8, wednesday: 8, thursday: 8, friday: 8, }, }, allAllocationsForResource: [], }, ], }, }, newStartDate: new Date("2026-04-03"), newEndDate: new Date("2026-04-12"), }); expect(result).toBe(expectedValidation); expect(validateShiftMock).toHaveBeenCalledWith({ project: { id: "project_1", budgetCents: 200_000, winProbability: 80, startDate: new Date("2026-04-01"), endDate: new Date("2026-04-10"), }, newStartDate: new Date("2026-04-03"), newEndDate: new Date("2026-04-12"), allocations: [ expect.objectContaining({ id: "assignment_1", resourceId: "resource_1", }), ], }); }); it("throws a BAD_REQUEST error for invalid shift validations", () => { expect(() => assertTimelineProjectShiftValid(createValidValidationResult({ valid: false, errors: [ { code: "AVAILABILITY_CONFLICT", message: "Alice exceeds capacity", }, ], })), ).toThrowError(new TRPCError({ code: "BAD_REQUEST", message: "Shift validation failed: Alice exceeds capacity", })); }); it("builds project shifted event payloads", () => { const validation = createValidValidationResult(); expect( buildTimelineProjectShiftEventPayload({ projectId: "project_1", newStartDate: new Date("2026-04-03T00:00:00.000Z"), newEndDate: new Date("2026-04-12T00:00:00.000Z"), validation, assignments: [ { resourceId: "resource_1" }, { resourceId: null }, ] as never, }), ).toEqual({ projectId: "project_1", newStartDate: "2026-04-03T00:00:00.000Z", newEndDate: "2026-04-12T00:00:00.000Z", costDeltaCents: 12_000, resourceIds: ["resource_1", null], }); }); });