From 740ef0ecdb17af3a6ebfecb5fa7c90aebf5ea87e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hartmut=20N=C3=B6renberg?= Date: Wed, 1 Apr 2026 00:41:40 +0200 Subject: [PATCH] test(api): cover assistant master data rate lookup --- ...tant-tools-master-data-rate-lookup.test.ts | 139 ++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 packages/api/src/__tests__/assistant-tools-master-data-rate-lookup.test.ts diff --git a/packages/api/src/__tests__/assistant-tools-master-data-rate-lookup.test.ts b/packages/api/src/__tests__/assistant-tools-master-data-rate-lookup.test.ts new file mode 100644 index 0000000..ed8bc50 --- /dev/null +++ b/packages/api/src/__tests__/assistant-tools-master-data-rate-lookup.test.ts @@ -0,0 +1,139 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; +import { PermissionKey, SystemRole } from "@capakraken/shared"; + +vi.mock("@capakraken/application", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + countPlanningEntries: vi.fn().mockResolvedValue({ countsByRoleId: new Map() }), + getDashboardBudgetForecast: vi.fn().mockResolvedValue([]), + getDashboardPeakTimes: vi.fn().mockResolvedValue([]), + listAssignmentBookings: vi.fn().mockResolvedValue([]), + }; +}); + +import { countPlanningEntries } from "@capakraken/application"; +import { executeTool } from "../router/assistant-tools.js"; +import { createToolContext } from "./assistant-tools-master-data-read-test-helpers.js"; + +describe("assistant master data rate lookup tools", () => { + beforeEach(() => { + vi.clearAllMocks(); + vi.mocked(countPlanningEntries).mockResolvedValue({ countsByRoleId: new Map() }); + }); + + it("routes rate lookup reads through the rate-card router path", async () => { + const db = { + rateCard: { + findMany: vi.fn().mockResolvedValue([ + { + id: "rc_client", + name: "Client 2026", + client: { id: "client_1", name: "Acme Mobility" }, + lines: [ + { + id: "line_best", + chapter: "Delivery", + seniority: "Senior", + costRateCents: 12_000, + billRateCents: 18_000, + role: { id: "role_1", name: "Pipeline TD" }, + }, + ], + }, + { + id: "rc_default", + name: "Default 2026", + client: null, + lines: [ + { + id: "line_alt", + chapter: "Delivery", + seniority: "Senior", + costRateCents: 11_000, + billRateCents: 17_000, + role: { id: "role_1", name: "Pipeline TD" }, + }, + ], + }, + ]), + }, + role: { + findFirst: vi.fn().mockResolvedValue({ id: "role_1" }), + }, + }; + const ctx = createToolContext(db, { + userRole: SystemRole.CONTROLLER, + permissions: [PermissionKey.VIEW_COSTS], + }); + + const result = await executeTool( + "lookup_rate", + JSON.stringify({ + clientId: "client_1", + chapter: "Delivery", + roleName: "Pipeline", + seniority: "Senior", + }), + ctx, + ); + + expect(db.rateCard.findMany).toHaveBeenCalledWith({ + where: { + isActive: true, + OR: [{ clientId: "client_1" }, { clientId: null }], + }, + include: { + lines: { + select: { + id: true, + chapter: true, + seniority: true, + costRateCents: true, + billRateCents: true, + role: { select: { id: true, name: true } }, + }, + }, + client: { select: { id: true, name: true } }, + }, + orderBy: [{ effectiveFrom: "desc" }], + }); + expect(db.role.findFirst).toHaveBeenCalledWith({ + where: { name: { contains: "Pipeline", mode: "insensitive" } }, + select: { id: true }, + }); + expect(JSON.parse(result.content)).toEqual({ + bestMatch: { + rateCardName: "Client 2026", + clientId: "client_1", + clientName: "Acme Mobility", + lineId: "line_best", + chapter: "Delivery", + seniority: "Senior", + roleName: "Pipeline TD", + costRateCents: 12000, + billRateCents: 18000, + score: 10, + costRate: "120,00 EUR", + billRate: "180,00 EUR", + }, + alternatives: [ + { + rateCardName: "Default 2026", + clientId: null, + clientName: null, + lineId: "line_alt", + chapter: "Delivery", + seniority: "Senior", + roleName: "Pipeline TD", + costRateCents: 11000, + billRateCents: 17000, + score: 7, + costRate: "110,00 EUR", + billRate: "170,00 EUR", + }, + ], + totalCandidates: 2, + }); + }); +});