test(api): cover holiday-aware budget and shoring tools
This commit is contained in:
@@ -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,
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user