152 lines
4.2 KiB
TypeScript
152 lines
4.2 KiB
TypeScript
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<typeof import("@capakraken/engine")>();
|
|
return {
|
|
...actual,
|
|
validateShift: validateShiftMock,
|
|
};
|
|
});
|
|
|
|
import {
|
|
assertTimelineProjectShiftValid,
|
|
buildTimelineProjectShiftEventPayload,
|
|
buildTimelineProjectShiftValidation,
|
|
} from "../router/timeline-shift-support.js";
|
|
|
|
function createValidValidationResult(
|
|
overrides: Partial<ShiftValidationResult> = {},
|
|
): 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],
|
|
});
|
|
});
|
|
});
|