Files
CapaKraken/packages/api/src/__tests__/timeline-project-procedure-support.test.ts
T

156 lines
5.8 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from "vitest";
vi.mock("../router/timeline-project-load-support.js", () => ({
previewTimelineProjectShift: vi.fn(),
}));
vi.mock("@capakraken/application", () => ({
listAssignmentBookings: vi.fn(),
}));
vi.mock("@capakraken/engine", () => ({
computeBudgetStatus: vi.fn(),
}));
vi.mock("../router/timeline-project-query-support.js", () => ({
findTimelineProjectOrThrow: vi.fn(),
timelineBudgetStatusProjectSelect: { id: true },
timelineShiftPreviewProjectSelect: { id: true },
}));
vi.mock("../router/timeline-project-read-support.js", () => ({
buildTimelineBudgetStatusAllocations: vi.fn(),
buildTimelineBudgetStatusResponse: vi.fn(),
buildTimelineShiftPreviewDetailResponse: vi.fn(),
}));
import { listAssignmentBookings } from "@capakraken/application";
import { computeBudgetStatus } from "@capakraken/engine";
import {
previewTimelineProjectShift,
} from "../router/timeline-project-load-support.js";
import {
buildTimelineBudgetStatusAllocations,
buildTimelineBudgetStatusResponse,
buildTimelineShiftPreviewDetailResponse,
} from "../router/timeline-project-read-support.js";
import {
findTimelineProjectOrThrow,
timelineBudgetStatusProjectSelect,
timelineShiftPreviewProjectSelect,
} from "../router/timeline-project-query-support.js";
import {
readTimelineProjectBudgetStatusResponse,
readTimelineProjectShiftPreview,
readTimelineProjectShiftPreviewDetail,
} from "../router/timeline-project-procedure-support.js";
const previewTimelineProjectShiftMock = vi.mocked(previewTimelineProjectShift);
const listAssignmentBookingsMock = vi.mocked(listAssignmentBookings);
const computeBudgetStatusMock = vi.mocked(computeBudgetStatus);
const findTimelineProjectOrThrowMock = vi.mocked(findTimelineProjectOrThrow);
const buildTimelineBudgetStatusAllocationsMock = vi.mocked(buildTimelineBudgetStatusAllocations);
const buildTimelineBudgetStatusResponseMock = vi.mocked(buildTimelineBudgetStatusResponse);
const buildTimelineShiftPreviewDetailResponseMock = vi.mocked(buildTimelineShiftPreviewDetailResponse);
describe("timeline project procedure support", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("builds shift preview detail responses", async () => {
findTimelineProjectOrThrowMock.mockResolvedValueOnce({ id: "project_1" } as never);
previewTimelineProjectShiftMock.mockResolvedValueOnce({ valid: true } as never);
buildTimelineShiftPreviewDetailResponseMock.mockReturnValueOnce({ preview: true } as never);
await expect(
readTimelineProjectShiftPreviewDetail({} as never, {
projectId: "project_1",
newStartDate: new Date("2026-04-03T00:00:00.000Z"),
newEndDate: new Date("2026-04-12T00:00:00.000Z"),
}),
).resolves.toEqual({ preview: true });
expect(findTimelineProjectOrThrowMock).toHaveBeenCalledWith({}, {
projectId: "project_1",
select: timelineShiftPreviewProjectSelect,
});
expect(previewTimelineProjectShiftMock).toHaveBeenCalledWith({}, {
projectId: "project_1",
newStartDate: new Date("2026-04-03T00:00:00.000Z"),
newEndDate: new Date("2026-04-12T00:00:00.000Z"),
});
expect(buildTimelineShiftPreviewDetailResponseMock).toHaveBeenCalledWith({
project: { id: "project_1" },
requestedShift: {
newStartDate: new Date("2026-04-03T00:00:00.000Z"),
newEndDate: new Date("2026-04-12T00:00:00.000Z"),
},
preview: { valid: true },
});
});
it("loads plain shift preview responses through procedure support", async () => {
previewTimelineProjectShiftMock.mockResolvedValueOnce({ valid: true } as never);
await expect(
readTimelineProjectShiftPreview({} as never, {
projectId: "project_1",
newStartDate: new Date("2026-04-03T00:00:00.000Z"),
newEndDate: new Date("2026-04-12T00:00:00.000Z"),
}),
).resolves.toEqual({ valid: true });
expect(previewTimelineProjectShiftMock).toHaveBeenCalledWith({}, {
projectId: "project_1",
newStartDate: new Date("2026-04-03T00:00:00.000Z"),
newEndDate: new Date("2026-04-12T00:00:00.000Z"),
});
});
it("builds budget status responses from project bookings", async () => {
findTimelineProjectOrThrowMock.mockResolvedValueOnce({
id: "project_1",
budgetCents: 100_000,
winProbability: 80,
startDate: new Date("2026-04-01T00:00:00.000Z"),
endDate: new Date("2026-04-10T00:00:00.000Z"),
} as never);
listAssignmentBookingsMock.mockResolvedValueOnce([{ id: "booking_1" }] as never);
buildTimelineBudgetStatusAllocationsMock.mockReturnValueOnce([{ id: "allocation_1" }] as never);
computeBudgetStatusMock.mockReturnValueOnce({ withinBudget: true } as never);
buildTimelineBudgetStatusResponseMock.mockReturnValueOnce({ budget: true } as never);
await expect(readTimelineProjectBudgetStatusResponse({} as never, "project_1")).resolves.toEqual({
budget: true,
});
expect(findTimelineProjectOrThrowMock).toHaveBeenCalledWith({}, {
projectId: "project_1",
select: timelineBudgetStatusProjectSelect,
});
expect(listAssignmentBookingsMock).toHaveBeenCalledWith({}, {
projectIds: ["project_1"],
});
expect(buildTimelineBudgetStatusAllocationsMock).toHaveBeenCalledWith([{ id: "booking_1" }]);
expect(computeBudgetStatusMock).toHaveBeenCalledWith(
100_000,
80,
[{ id: "allocation_1" }],
new Date("2026-04-01T00:00:00.000Z"),
new Date("2026-04-10T00:00:00.000Z"),
);
expect(buildTimelineBudgetStatusResponseMock).toHaveBeenCalledWith({
project: {
id: "project_1",
budgetCents: 100_000,
winProbability: 80,
startDate: new Date("2026-04-01T00:00:00.000Z"),
endDate: new Date("2026-04-10T00:00:00.000Z"),
},
budgetStatus: { withinBudget: true },
totalAllocations: 1,
});
});
});