194 lines
5.4 KiB
TypeScript
194 lines
5.4 KiB
TypeScript
import { TRPCError } from "@trpc/server";
|
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
|
const {
|
|
loadAllocationEntryMock,
|
|
updateAllocationEntryMock,
|
|
calculateTimelineAllocationDailyCostMock,
|
|
} = vi.hoisted(() => ({
|
|
loadAllocationEntryMock: vi.fn(),
|
|
updateAllocationEntryMock: vi.fn(),
|
|
calculateTimelineAllocationDailyCostMock: vi.fn(),
|
|
}));
|
|
|
|
vi.mock("@capakraken/application", async (importOriginal) => {
|
|
const actual = await importOriginal<typeof import("@capakraken/application")>();
|
|
return {
|
|
...actual,
|
|
loadAllocationEntry: loadAllocationEntryMock,
|
|
updateAllocationEntry: updateAllocationEntryMock,
|
|
};
|
|
});
|
|
|
|
vi.mock("../router/timeline-cost-support.js", () => ({
|
|
calculateTimelineAllocationDailyCost: calculateTimelineAllocationDailyCostMock,
|
|
}));
|
|
|
|
import { applyTimelineInlineAllocationUpdate } from "../router/timeline-allocation-inline-support.js";
|
|
|
|
describe("timeline allocation inline support", () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
it("updates staffed allocations with recalculated daily cost and audit changes", async () => {
|
|
loadAllocationEntryMock.mockResolvedValue({
|
|
entry: {
|
|
id: "allocation_1",
|
|
hoursPerDay: 4,
|
|
startDate: new Date("2026-04-01T00:00:00.000Z"),
|
|
endDate: new Date("2026-04-05T00:00:00.000Z"),
|
|
metadata: {},
|
|
},
|
|
resourceId: "resource_1",
|
|
});
|
|
calculateTimelineAllocationDailyCostMock.mockResolvedValue(54000);
|
|
updateAllocationEntryMock.mockResolvedValue({
|
|
allocation: {
|
|
id: "allocation_1",
|
|
projectId: "project_1",
|
|
resourceId: "resource_1",
|
|
},
|
|
});
|
|
|
|
const tx = {
|
|
auditLog: {
|
|
create: vi.fn().mockResolvedValue({}),
|
|
},
|
|
};
|
|
const db = {
|
|
resource: {
|
|
findUnique: vi.fn().mockResolvedValue({
|
|
id: "resource_1",
|
|
lcrCents: 5000,
|
|
availability: { monday: 8, tuesday: 8, wednesday: 8, thursday: 8, friday: 8 },
|
|
}),
|
|
},
|
|
$transaction: vi.fn(async (callback: (client: typeof tx) => unknown) => callback(tx)),
|
|
};
|
|
|
|
const result = await applyTimelineInlineAllocationUpdate({
|
|
db: db as never,
|
|
allocationId: "allocation_1",
|
|
hoursPerDay: 6,
|
|
endDate: new Date("2026-04-06T00:00:00.000Z"),
|
|
includeSaturday: true,
|
|
role: "Architect",
|
|
});
|
|
|
|
expect(result).toEqual({
|
|
id: "allocation_1",
|
|
projectId: "project_1",
|
|
resourceId: "resource_1",
|
|
});
|
|
expect(calculateTimelineAllocationDailyCostMock).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
db,
|
|
resourceId: "resource_1",
|
|
lcrCents: 5000,
|
|
hoursPerDay: 6,
|
|
includeSaturday: true,
|
|
}),
|
|
);
|
|
expect(updateAllocationEntryMock).toHaveBeenCalledWith(
|
|
tx,
|
|
{
|
|
id: "allocation_1",
|
|
demandRequirementUpdate: {
|
|
hoursPerDay: 6,
|
|
startDate: new Date("2026-04-01T00:00:00.000Z"),
|
|
endDate: new Date("2026-04-06T00:00:00.000Z"),
|
|
metadata: { includeSaturday: true },
|
|
role: "Architect",
|
|
},
|
|
assignmentUpdate: {
|
|
hoursPerDay: 6,
|
|
startDate: new Date("2026-04-01T00:00:00.000Z"),
|
|
endDate: new Date("2026-04-06T00:00:00.000Z"),
|
|
dailyCostCents: 54000,
|
|
metadata: { includeSaturday: true },
|
|
role: "Architect",
|
|
},
|
|
},
|
|
);
|
|
expect(tx.auditLog.create).toHaveBeenCalledWith({
|
|
data: expect.objectContaining({
|
|
entityType: "Allocation",
|
|
entityId: "allocation_1",
|
|
action: "UPDATE",
|
|
}),
|
|
});
|
|
});
|
|
|
|
it("updates placeholder allocations without recalculating cost", async () => {
|
|
loadAllocationEntryMock.mockResolvedValue({
|
|
entry: {
|
|
id: "demand_1",
|
|
hoursPerDay: 4,
|
|
startDate: new Date("2026-04-01T00:00:00.000Z"),
|
|
endDate: new Date("2026-04-05T00:00:00.000Z"),
|
|
metadata: {},
|
|
},
|
|
resourceId: null,
|
|
});
|
|
updateAllocationEntryMock.mockResolvedValue({
|
|
allocation: {
|
|
id: "demand_1",
|
|
projectId: "project_1",
|
|
resourceId: null,
|
|
},
|
|
});
|
|
|
|
const tx = {
|
|
auditLog: {
|
|
create: vi.fn().mockResolvedValue({}),
|
|
},
|
|
};
|
|
const db = {
|
|
resource: {
|
|
findUnique: vi.fn(),
|
|
},
|
|
$transaction: vi.fn(async (callback: (client: typeof tx) => unknown) => callback(tx)),
|
|
};
|
|
|
|
await expect(applyTimelineInlineAllocationUpdate({
|
|
db: db as never,
|
|
allocationId: "demand_1",
|
|
hoursPerDay: 5,
|
|
})).resolves.toEqual({
|
|
id: "demand_1",
|
|
projectId: "project_1",
|
|
resourceId: null,
|
|
});
|
|
|
|
expect(db.resource.findUnique).not.toHaveBeenCalled();
|
|
expect(calculateTimelineAllocationDailyCostMock).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("throws when a staffed allocation references a missing resource", async () => {
|
|
loadAllocationEntryMock.mockResolvedValue({
|
|
entry: {
|
|
id: "allocation_1",
|
|
hoursPerDay: 4,
|
|
startDate: new Date("2026-04-01T00:00:00.000Z"),
|
|
endDate: new Date("2026-04-05T00:00:00.000Z"),
|
|
metadata: {},
|
|
},
|
|
resourceId: "resource_1",
|
|
});
|
|
|
|
await expect(applyTimelineInlineAllocationUpdate({
|
|
db: {
|
|
resource: {
|
|
findUnique: vi.fn().mockResolvedValue(null),
|
|
},
|
|
$transaction: vi.fn(),
|
|
} as never,
|
|
allocationId: "allocation_1",
|
|
})).rejects.toThrowError(new TRPCError({
|
|
code: "NOT_FOUND",
|
|
message: "Resource not found",
|
|
}));
|
|
});
|
|
});
|