Files
CapaKraken/packages/api/src/__tests__/timeline-allocation-mutation-support.test.ts
T

175 lines
5.3 KiB
TypeScript

import { TRPCError } from "@trpc/server";
import { describe, expect, it } from "vitest";
import {
assertTimelineDateRangeValid,
buildTimelineAllocationEntryUpdate,
buildTimelineAllocationMetadata,
buildTimelineBatchShiftAuditChanges,
buildTimelineQuickAssignAssignmentInput,
buildTimelineQuickAssignMetadata,
calculateTimelineAllocationPercentage,
shiftTimelineAllocationWindow,
validateTimelineAllocationDateRanges,
} from "../router/timeline-allocation-mutation-support.js";
describe("timeline allocation mutation support", () => {
it("preserves existing metadata while updating includeSaturday", () => {
const result = buildTimelineAllocationMetadata({
existingMetadata: {
recurrence: { frequency: "weekly", interval: 2 },
includeSaturday: false,
},
includeSaturday: true,
});
expect(result).toEqual({
metadata: {
recurrence: { frequency: "weekly", interval: 2 },
includeSaturday: true,
},
includeSaturday: true,
});
});
it("rejects inverted date ranges", () => {
expect(() =>
assertTimelineDateRangeValid(
new Date("2026-04-10T00:00:00.000Z"),
new Date("2026-04-09T00:00:00.000Z"),
)).toThrowError(TRPCError);
});
it("rounds allocation percentages and clamps them to 100", () => {
expect(calculateTimelineAllocationPercentage(3.9)).toBe(49);
expect(calculateTimelineAllocationPercentage(9)).toBe(100);
});
it("builds source metadata for quick assign operations", () => {
expect(buildTimelineQuickAssignMetadata("quickAssign")).toEqual({ source: "quickAssign" });
expect(buildTimelineQuickAssignMetadata("batchQuickAssign")).toEqual({ source: "batchQuickAssign" });
});
it("builds assignment create payloads for quick assign flows", () => {
expect(
buildTimelineQuickAssignAssignmentInput({
resourceId: "resource_1",
projectId: "project_1",
startDate: new Date("2026-04-01T00:00:00.000Z"),
endDate: new Date("2026-04-05T00:00:00.000Z"),
hoursPerDay: 6,
role: "Engineer",
roleId: "role_1",
status: "PROPOSED",
source: "quickAssign",
}),
).toEqual({
resourceId: "resource_1",
projectId: "project_1",
startDate: new Date("2026-04-01T00:00:00.000Z"),
endDate: new Date("2026-04-05T00:00:00.000Z"),
hoursPerDay: 6,
percentage: 75,
role: "Engineer",
roleId: "role_1",
status: "PROPOSED",
metadata: { source: "quickAssign" },
});
});
it("shifts and clamps allocation windows for each batch-shift mode", () => {
expect(
shiftTimelineAllocationWindow({
startDate: new Date("2026-04-10T00:00:00.000Z"),
endDate: new Date("2026-04-12T00:00:00.000Z"),
daysDelta: 2,
mode: "move",
}),
).toEqual({
startDate: new Date("2026-04-12T00:00:00.000Z"),
endDate: new Date("2026-04-14T00:00:00.000Z"),
});
expect(
shiftTimelineAllocationWindow({
startDate: new Date("2026-04-10T00:00:00.000Z"),
endDate: new Date("2026-04-12T00:00:00.000Z"),
daysDelta: 5,
mode: "resize-start",
}),
).toEqual({
startDate: new Date("2026-04-12T00:00:00.000Z"),
endDate: new Date("2026-04-12T00:00:00.000Z"),
});
expect(
shiftTimelineAllocationWindow({
startDate: new Date("2026-04-10T00:00:00.000Z"),
endDate: new Date("2026-04-12T00:00:00.000Z"),
daysDelta: -5,
mode: "resize-end",
}),
).toEqual({
startDate: new Date("2026-04-10T00:00:00.000Z"),
endDate: new Date("2026-04-10T00:00:00.000Z"),
});
});
it("builds batch-shift audit payloads", () => {
expect(
buildTimelineBatchShiftAuditChanges({
mode: "move",
daysDelta: 3,
count: 2,
}),
).toEqual({
operation: "batchShift",
mode: "move",
daysDelta: 3,
count: 2,
});
});
it("validates date ranges in bulk", () => {
expect(() =>
validateTimelineAllocationDateRanges([
{
startDate: new Date("2026-04-10T00:00:00.000Z"),
endDate: new Date("2026-04-11T00:00:00.000Z"),
},
{
startDate: new Date("2026-04-12T00:00:00.000Z"),
endDate: new Date("2026-04-09T00:00:00.000Z"),
},
])).toThrowError(TRPCError);
});
it("builds shared update payloads for demand and assignment changes", () => {
expect(
buildTimelineAllocationEntryUpdate({
hoursPerDay: 7.5,
startDate: new Date("2026-04-01T00:00:00.000Z"),
endDate: new Date("2026-04-10T00:00:00.000Z"),
metadata: { includeSaturday: true },
dailyCostCents: 48000,
role: "Architect",
}),
).toEqual({
demandRequirementUpdate: {
hoursPerDay: 7.5,
startDate: new Date("2026-04-01T00:00:00.000Z"),
endDate: new Date("2026-04-10T00:00:00.000Z"),
metadata: { includeSaturday: true },
role: "Architect",
},
assignmentUpdate: {
hoursPerDay: 7.5,
startDate: new Date("2026-04-01T00:00:00.000Z"),
endDate: new Date("2026-04-10T00:00:00.000Z"),
dailyCostCents: 48000,
metadata: { includeSaturday: true },
role: "Architect",
},
});
});
});