test(api): cover holiday-aware budget and shoring tools

This commit is contained in:
2026-03-31 23:52:59 +02:00
parent 24b5e60169
commit 6a95a0105b
2 changed files with 186 additions and 0 deletions
@@ -0,0 +1,160 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
vi.mock("@capakraken/application", async (importOriginal) => {
const actual = await importOriginal<typeof import("@capakraken/application")>();
return {
...actual,
getDashboardBudgetForecast: vi.fn().mockResolvedValue([]),
listAssignmentBookings: vi.fn().mockResolvedValue([]),
};
});
vi.mock("../lib/audit.js", () => ({
createAuditEntry: vi.fn().mockResolvedValue(undefined),
}));
import { getDashboardBudgetForecast } from "@capakraken/application";
import { executeTool } from "../router/assistant-tools.js";
import { createToolContext } from "./assistant-tools-holiday-capacity-test-helpers.js";
describe("assistant holiday-aware budget and shoring tools", () => {
beforeEach(() => {
vi.clearAllMocks();
vi.mocked(getDashboardBudgetForecast).mockResolvedValue([]);
});
it("returns holiday-aware budget forecast data from the dashboard use-case", async () => {
vi.mocked(getDashboardBudgetForecast).mockResolvedValue([
{
projectId: "project_1",
projectName: "Gelddruckmaschine",
shortCode: "GDM",
budgetCents: 100_000,
spentCents: 60_000,
burnRate: 5_000,
pctUsed: 60,
estimatedExhaustionDate: "2026-02-20",
derivation: {
periodStart: "2026-02-01",
periodEnd: "2026-02-28",
calendarContextCount: 1,
holidayAwareAssignmentCount: 1,
fallbackAssignmentCount: 0,
baseBurnRateCents: 6_000,
adjustedBurnRateCents: 5_000,
publicHolidayDayEquivalent: 1,
publicHolidayCostDeductionCents: 1_000,
absenceDayEquivalent: 0,
absenceCostDeductionCents: 0,
},
},
]);
const ctx = createToolContext({}, ["viewCosts"]);
const result = await executeTool("get_budget_forecast", "{}", ctx);
const parsed = JSON.parse(result.content) as {
forecasts: Array<{
projectName: string;
shortCode: string;
budgetCents: number;
spentCents: number;
remainingCents: number;
projectedCents: number;
burnRateCents: number;
burnStatus: string;
derivation: {
baseBurnRateCents: number;
adjustedBurnRateCents: number;
publicHolidayCostDeductionCents: number;
} | null;
}>;
};
expect(parsed.forecasts).toEqual([
expect.objectContaining({
projectName: "Gelddruckmaschine",
shortCode: "GDM",
budgetCents: 100_000,
spentCents: 60_000,
remainingCents: 40_000,
projectedCents: 100_000,
burnRateCents: 5_000,
derivation: expect.objectContaining({
baseBurnRateCents: 6_000,
adjustedBurnRateCents: 5_000,
publicHolidayCostDeductionCents: 1_000,
}),
burnStatus: "on_track",
}),
]);
});
it("uses holiday-aware assignment hours for assistant shoring ratio", async () => {
const db = {
project: {
findUnique: vi
.fn()
.mockResolvedValueOnce({
id: "project_1",
name: "Holiday Project",
shortCode: "HP",
status: "ACTIVE",
responsiblePerson: null,
})
.mockResolvedValueOnce({
id: "project_1",
name: "Holiday Project",
shoringThreshold: 55,
onshoreCountryCode: "DE",
}),
},
assignment: {
findMany: vi.fn().mockResolvedValue([
{
resourceId: "res_by",
hoursPerDay: 8,
startDate: new Date("2026-01-06T00:00:00.000Z"),
endDate: new Date("2026-01-06T00:00:00.000Z"),
resource: {
id: "res_by",
availability: { monday: 8, tuesday: 8, wednesday: 8, thursday: 8, friday: 8 },
countryId: "country_de",
federalState: "BY",
metroCityId: null,
country: { code: "DE" },
metroCity: null,
},
},
{
resourceId: "res_in",
hoursPerDay: 8,
startDate: new Date("2026-01-06T00:00:00.000Z"),
endDate: new Date("2026-01-06T00:00:00.000Z"),
resource: {
id: "res_in",
availability: { monday: 8, tuesday: 8, wednesday: 8, thursday: 8, friday: 8 },
countryId: "country_in",
federalState: null,
metroCityId: null,
country: { code: "IN" },
metroCity: null,
},
},
]),
},
vacation: {
findMany: vi.fn().mockResolvedValue([]),
},
};
const ctx = createToolContext(db);
const result = await executeTool(
"get_shoring_ratio",
JSON.stringify({ projectId: "project_1" }),
ctx,
);
expect(result.content).toContain("0% onshore (DE), 100% offshore");
expect(result.content).toContain("IN 100% (1 people)");
});
});
@@ -0,0 +1,26 @@
import { SystemRole } from "@capakraken/shared";
import type { ToolContext } from "../router/assistant-tools.js";
export function createToolContext(
db: Record<string, unknown>,
permissions: string[] = [],
userRole: SystemRole = SystemRole.ADMIN,
): ToolContext {
return {
db: db as ToolContext["db"],
userId: "user_1",
userRole,
permissions: new Set(permissions) as ToolContext["permissions"],
session: {
user: { email: "assistant@example.com", name: "Assistant User", image: null },
expires: "2026-03-29T00:00:00.000Z",
},
dbUser: {
id: "user_1",
systemRole: userRole,
permissionOverrides: null,
},
roleDefaults: null,
};
}