import { beforeEach, describe, expect, it, vi } from "vitest"; import { PermissionKey, SystemRole } from "@capakraken/shared"; import { VacationStatus, VacationType } from "@capakraken/db"; import { TRPCError } from "@trpc/server"; import { apiRateLimiter } from "../middleware/rate-limit.js"; const { totpValidateMock } = vi.hoisted(() => ({ totpValidateMock: vi.fn(), })); vi.mock("@capakraken/application", async (importOriginal) => { const actual = await importOriginal(); return { ...actual, approveEstimateVersion: vi.fn(), cloneEstimate: vi.fn(), countPlanningEntries: vi.fn().mockResolvedValue({ countsByRoleId: new Map() }), createEstimateExport: vi.fn(), createEstimatePlanningHandoff: vi.fn(), createEstimateRevision: vi.fn(), loadResourceDailyAvailabilityContexts: vi.fn().mockResolvedValue(new Map()), getDashboardDemand: vi.fn().mockResolvedValue([]), getDashboardBudgetForecast: vi.fn().mockResolvedValue([]), getDashboardOverview: vi.fn(), getDashboardSkillGapSummary: vi.fn().mockResolvedValue({ roleGaps: [], totalOpenPositions: 0, skillSupplyTop10: [], resourcesByRole: [], }), getDashboardProjectHealth: vi.fn().mockResolvedValue([]), getDashboardPeakTimes: vi.fn().mockResolvedValue([]), getDashboardTopValueResources: vi.fn().mockResolvedValue([]), getEstimateById: vi.fn(), listAssignmentBookings: vi.fn().mockResolvedValue([]), submitEstimateVersion: vi.fn(), updateEstimateDraft: vi.fn(), }; }); vi.mock("../lib/cache.js", () => ({ cacheGet: vi.fn().mockResolvedValue(null), cacheSet: vi.fn().mockResolvedValue(undefined), cacheInvalidate: vi.fn().mockResolvedValue(undefined), invalidateDashboardCache: vi.fn().mockResolvedValue(undefined), })); vi.mock("../ai-client.js", async (importOriginal) => { const actual = await importOriginal(); return { ...actual, createAiClient: vi.fn(() => ({ chat: { completions: { create: vi.fn().mockResolvedValue({ choices: [ { message: { content: "Project is on track overall, but staffing remains partially open.", }, }, ], }), }, }, })), createDalleClient: vi.fn(() => ({ images: { generate: vi.fn().mockResolvedValue({ data: [{ b64_json: "ZmFrZQ==" }], }), }, })), loggedAiCall: vi.fn(async (_provider, _model, _promptLength, fn) => fn()), }; }); vi.mock("otpauth", () => { class Secret { base32: string; constructor() { this.base32 = "MOCKSECRET"; } static fromBase32(value: string) { return value; } } class TOTP { validate(args: { token: string; window: number }) { return totpValidateMock(args); } toString() { return "otpauth://mock"; } } return { Secret, TOTP }; }); import { approveEstimateVersion, cloneEstimate, countPlanningEntries, createEstimateExport, createEstimatePlanningHandoff, createEstimateRevision, getDashboardDemand, getDashboardOverview, getDashboardProjectHealth, getDashboardPeakTimes, getDashboardSkillGapSummary, getDashboardTopValueResources, getEstimateById, listAssignmentBookings, submitEstimateVersion, updateEstimateDraft, } from "@capakraken/application"; import { executeTool, type ToolContext } from "../router/assistant-tools.js"; function createToolContext( db: Record, options?: { permissions?: PermissionKey[]; userRole?: SystemRole; }, ): ToolContext { const userRole = options?.userRole ?? SystemRole.ADMIN; return { db: db as ToolContext["db"], userId: "user_1", userRole, permissions: new Set(options?.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, }; } describe("assistant import/export and dispo tools", () => { beforeEach(async () => { vi.clearAllMocks(); vi.unstubAllEnvs(); await apiRateLimiter.reset(); totpValidateMock.mockReset(); vi.mocked(approveEstimateVersion).mockReset(); vi.mocked(cloneEstimate).mockReset(); vi.mocked(countPlanningEntries).mockResolvedValue({ countsByRoleId: new Map() }); vi.mocked(createEstimateExport).mockReset(); vi.mocked(createEstimatePlanningHandoff).mockReset(); vi.mocked(createEstimateRevision).mockReset(); vi.mocked(getEstimateById).mockReset(); vi.mocked(submitEstimateVersion).mockReset(); vi.mocked(updateEstimateDraft).mockReset(); }); it("exports resources CSV through the real import/export router path", async () => { const ctx = createToolContext( { resource: { findMany: vi.fn().mockResolvedValue([ { eid: "EMP-001", displayName: "Carol Danvers", email: "carol@example.com", chapter: "Delivery", lcrCents: 8000, ucrCents: 12000, currency: "EUR", chargeabilityTarget: 0.8, dynamicFields: {}, }, ]), }, blueprint: { findMany: vi.fn().mockResolvedValue([]), }, }, { userRole: SystemRole.CONTROLLER }, ); const result = await executeTool("export_resources_csv", "{}", ctx); expect(JSON.parse(result.content)).toEqual({ format: "csv", lineCount: 2, csv: "eid,displayName,email,chapter,lcrCents,ucrCents,currency,chargeabilityTarget\nEMP-001,Carol Danvers,carol@example.com,Delivery,8000,12000,EUR,0.8", }); }); it("requires importData permission for CSV imports", async () => { const ctx = createToolContext( { auditLog: { create: vi.fn() }, }, { userRole: SystemRole.MANAGER, permissions: [], }, ); const result = await executeTool( "import_csv_data", JSON.stringify({ entityType: "resources", rows: [{ eid: "EMP-001", displayName: "Carol Danvers" }], dryRun: true, }), ctx, ); expect(JSON.parse(result.content)).toEqual( expect.objectContaining({ error: expect.stringContaining(PermissionKey.IMPORT_DATA), }), ); }); it("enforces admin access for dispo batch inspection via the backing router", async () => { const ctx = createToolContext( { importBatch: { findUnique: vi.fn(), }, }, { userRole: SystemRole.USER }, ); const result = await executeTool( "get_dispo_import_batch", JSON.stringify({ id: "batch_1" }), ctx, ); expect(JSON.parse(result.content)).toEqual( expect.objectContaining({ error: "You do not have permission to perform this action.", }), ); }); it("returns a stable assistant error for a missing dispo import batch", async () => { const ctx = createToolContext( { importBatch: { findUnique: vi.fn().mockResolvedValue(null), }, }, { userRole: SystemRole.ADMIN }, ); const result = await executeTool( "get_dispo_import_batch", JSON.stringify({ id: "batch_missing" }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "Import batch not found with the given criteria.", }); }); it("surfaces protected AI configuration checks to non-admin users", async () => { const ctx = createToolContext( { systemSettings: { findUnique: vi.fn().mockResolvedValue({ aiProvider: "openai", azureOpenAiDeployment: "gpt-4o-mini", azureOpenAiApiKey: "secret", }), }, }, { userRole: SystemRole.USER }, ); const result = await executeTool("get_ai_configured", "{}", ctx); expect(JSON.parse(result.content)).toEqual({ configured: true }); }); it("treats environment-backed AI configuration as configured for assistant checks", async () => { vi.stubEnv("OPENAI_API_KEY", "env-secret"); const ctx = createToolContext( { systemSettings: { findUnique: vi.fn().mockResolvedValue({ aiProvider: "openai", azureOpenAiDeployment: "gpt-4o-mini", azureOpenAiApiKey: null, }), }, }, { userRole: SystemRole.USER }, ); const result = await executeTool("get_ai_configured", "{}", ctx); expect(JSON.parse(result.content)).toEqual({ configured: true }); }); it("clears legacy runtime secrets through the real settings router path", async () => { const findUnique = vi.fn().mockResolvedValue({ azureOpenAiApiKey: "db-openai", azureDalleApiKey: null, geminiApiKey: "db-gemini", smtpPassword: null, anonymizationSeed: "db-seed", }); const update = vi.fn().mockResolvedValue({ id: "singleton" }); const auditCreate = vi.fn().mockResolvedValue(undefined); const ctx = createToolContext( { systemSettings: { findUnique, update, }, auditLog: { create: auditCreate, }, }, { userRole: SystemRole.ADMIN }, ); const result = await executeTool("clear_stored_runtime_secrets", "{}", ctx); expect(JSON.parse(result.content)).toEqual({ ok: true, clearedFields: ["azureOpenAiApiKey", "geminiApiKey", "anonymizationSeed"], }); expect(update).toHaveBeenCalledWith({ where: { id: "singleton" }, data: { azureOpenAiApiKey: null, geminiApiKey: null, anonymizationSeed: null, }, }); expect(auditCreate).toHaveBeenCalled(); }); it("masks webhook secrets in assistant responses", async () => { const ctx = createToolContext( { webhook: { findMany: vi.fn().mockResolvedValue([ { id: "wh_1", name: "Primary", url: "https://example.com/hook", secret: "super-secret", events: ["project.updated"], isActive: true, createdAt: new Date("2026-03-29T00:00:00.000Z"), }, ]), findUnique: vi.fn().mockResolvedValue({ id: "wh_1", name: "Primary", url: "https://example.com/hook", secret: "super-secret", events: ["project.updated"], isActive: true, createdAt: new Date("2026-03-29T00:00:00.000Z"), }), }, }, { userRole: SystemRole.ADMIN }, ); const listResult = await executeTool("list_webhooks", "{}", ctx); const getResult = await executeTool("get_webhook", JSON.stringify({ id: "wh_1" }), ctx); expect(JSON.parse(listResult.content)).toEqual([ expect.objectContaining({ id: "wh_1", hasSecret: true, }), ]); expect(JSON.parse(listResult.content)[0]).not.toHaveProperty("secret"); expect(JSON.parse(getResult.content)).toEqual( expect.objectContaining({ id: "wh_1", hasSecret: true, }), ); expect(JSON.parse(getResult.content)).not.toHaveProperty("secret"); }); it("returns stable assistant errors for missing webhooks", async () => { const commands = [ ["get_webhook", { id: "wh_missing" }], ["update_webhook", { id: "wh_missing", data: { name: "Renamed" } }], ["delete_webhook", { id: "wh_missing" }], ["test_webhook", { id: "wh_missing" }], ] as const; for (const [toolName, payload] of commands) { const ctx = createToolContext( { webhook: { findUnique: vi.fn().mockResolvedValue(null), }, }, { userRole: SystemRole.ADMIN }, ); const result = await executeTool(toolName, JSON.stringify(payload), ctx); expect(JSON.parse(result.content)).toEqual({ error: "Webhook not found with the given criteria.", }); } }); it("returns a stable assistant error for invalid webhook creation input", async () => { const ctx = createToolContext( { webhook: { create: vi.fn(), }, }, { userRole: SystemRole.ADMIN }, ); const result = await executeTool( "create_webhook", JSON.stringify({ name: "Primary", url: "not-a-url", events: ["project.updated"], }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "Webhook input is invalid.", }); expect(ctx.db.webhook.create).not.toHaveBeenCalled(); }); it("returns a stable assistant error for invalid webhook update input", async () => { const ctx = createToolContext( { webhook: { findUnique: vi.fn().mockResolvedValue({ id: "wh_1", name: "Primary", url: "https://example.com/hook", secret: null, events: ["project.updated"], isActive: true, createdAt: new Date("2026-03-29T00:00:00.000Z"), updatedAt: new Date("2026-03-29T00:00:00.000Z"), }), update: vi.fn(), }, }, { userRole: SystemRole.ADMIN }, ); const result = await executeTool( "update_webhook", JSON.stringify({ id: "wh_1", data: { url: "not-a-url", }, }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "Webhook update input is invalid.", }); expect(ctx.db.webhook.update).not.toHaveBeenCalled(); }); it("returns a stable assistant error when a webhook disappears during update", async () => { const ctx = createToolContext( { webhook: { findUnique: vi.fn().mockResolvedValue({ id: "wh_1", name: "Primary", url: "https://example.com/hook", secret: null, events: ["project.updated"], isActive: true, createdAt: new Date("2026-03-29T00:00:00.000Z"), updatedAt: new Date("2026-03-29T00:00:00.000Z"), }), update: vi.fn().mockRejectedValue({ code: "P2025", message: "Record to update not found.", }), }, }, { userRole: SystemRole.ADMIN }, ); const result = await executeTool( "update_webhook", JSON.stringify({ id: "wh_1", data: { name: "Renamed", }, }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "Webhook not found with the given criteria.", }); }); it("returns stable assistant errors for missing audit log entries", async () => { const ctx = createToolContext( { auditLog: { findUniqueOrThrow: vi.fn().mockRejectedValue( new TRPCError({ code: "NOT_FOUND", message: "Audit log entry not found" }), ), }, }, { userRole: SystemRole.ADMIN }, ); const result = await executeTool( "get_audit_log_entry", JSON.stringify({ id: "audit_missing" }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "Audit log entry not found with the given criteria.", }); }); it("forwards staged dispo resource queries through the real dispo router path", async () => { const ctx = createToolContext( { stagedResource: { findMany: vi.fn().mockResolvedValue([ { id: "sr_1", importBatchId: "batch_1", canonicalExternalId: "EMP-001", status: "PARSED", }, ]), }, }, { userRole: SystemRole.ADMIN }, ); const result = await executeTool( "list_dispo_staged_resources", JSON.stringify({ importBatchId: "batch_1", status: "PARSED", limit: 10 }), ctx, ); expect(JSON.parse(result.content)).toEqual({ items: [ expect.objectContaining({ id: "sr_1", importBatchId: "batch_1", canonicalExternalId: "EMP-001", status: "PARSED", }), ], }); }); it("lists notifications for the current session user through the real notification router path", async () => { const db = { user: { findUnique: vi.fn().mockResolvedValue({ id: "user_1" }), }, notification: { findMany: vi.fn().mockResolvedValue([ { id: "note_1", title: "Check staffing", body: "Review the new staffing suggestion", readAt: null, createdAt: new Date("2026-03-29T08:00:00.000Z"), }, ]), }, }; const ctx = createToolContext(db, { userRole: SystemRole.CONTROLLER }); const result = await executeTool( "list_notifications", JSON.stringify({ unreadOnly: true, limit: 5 }), ctx, ); expect(db.user.findUnique).toHaveBeenCalledWith({ where: { email: "assistant@example.com" }, select: { id: true }, }); expect(db.notification.findMany).toHaveBeenCalledWith({ where: { userId: "user_1", readAt: null, }, orderBy: { createdAt: "desc" }, take: 5, }); expect(JSON.parse(result.content)).toEqual([ expect.objectContaining({ id: "note_1", title: "Check staffing", }), ]); }); it("routes statistics through the dashboard overview path", async () => { vi.mocked(getDashboardOverview).mockResolvedValue({ totalResources: 12, activeResources: 10, inactiveResources: 2, totalProjects: 7, activeProjects: 4, inactiveProjects: 3, totalAllocations: 21, activeAllocations: 18, cancelledAllocations: 3, approvedVacations: 6, totalEstimates: 9, budgetSummary: { totalBudgetCents: 1_234_56, totalCostCents: 654_32, avgUtilizationPercent: 53, }, budgetBasis: { remainingBudgetCents: 58_024, budgetedProjects: 5, unbudgetedProjects: 2, trackedAssignmentCount: 18, windowStart: null, windowEnd: null, }, projectsByStatus: [ { status: "ACTIVE", count: 4 }, { status: "DRAFT", count: 2 }, { status: "DONE", count: 1 }, ], chapterUtilization: [ { chapter: "CGI", resourceCount: 5, avgChargeabilityTarget: 78 }, { chapter: "Compositing", resourceCount: 3, avgChargeabilityTarget: 74 }, { chapter: "Unassigned", resourceCount: 2, avgChargeabilityTarget: 0 }, ], recentActivity: [], }); const ctx = createToolContext({}, { userRole: SystemRole.CONTROLLER }); const result = await executeTool("get_statistics", "{}", ctx); expect(JSON.parse(result.content)).toEqual({ activeResources: 10, totalProjects: 7, activeProjects: 4, totalAllocations: 21, approvedVacations: 6, totalEstimates: 9, totalBudget: "1.234,56 EUR", projectsByStatus: { ACTIVE: 4, DRAFT: 2, DONE: 1, }, topChapters: [ { chapter: "CGI", count: 5 }, { chapter: "Compositing", count: 3 }, { chapter: "Unassigned", count: 2 }, ], }); }); it("marks all unread notifications as read through the notification router", async () => { const db = { user: { findUnique: vi.fn().mockResolvedValue({ id: "user_1" }), }, notification: { updateMany: vi.fn().mockResolvedValue({ count: 3 }), }, }; const ctx = createToolContext(db, { userRole: SystemRole.CONTROLLER }); const result = await executeTool("mark_notification_read", "{}", ctx); expect(db.notification.updateMany).toHaveBeenCalledWith({ where: { userId: "user_1", readAt: null }, data: { readAt: expect.any(Date) }, }); expect(JSON.parse(result.content)).toEqual( expect.objectContaining({ success: true, message: "All unread notifications marked as read.", }), ); }); it("returns a stable assistant error when marking a missing notification as read", async () => { const update = vi.fn().mockRejectedValue({ code: "P2025", message: "No record was found for an update.", }); const ctx = createToolContext( { user: { findUnique: vi.fn().mockResolvedValue({ id: "user_1" }), }, notification: { update, }, }, { userRole: SystemRole.CONTROLLER }, ); const result = await executeTool( "mark_notification_read", JSON.stringify({ notificationId: "notif_missing" }), ctx, ); expect(update).toHaveBeenCalledWith({ where: { id: "notif_missing", userId: "user_1" }, data: expect.objectContaining({ readAt: expect.any(Date), }), }); expect(JSON.parse(result.content)).toEqual({ error: "Notification not found with the given criteria.", }); }); it("creates a notification through the notification router", async () => { const db = { notification: { create: vi.fn().mockResolvedValue({ id: "notification_1", userId: "user_2" }), findUnique: vi.fn().mockResolvedValue({ id: "notification_1", title: "Need review", userId: "user_2", category: "TASK", }), }, user: { findUnique: vi.fn().mockResolvedValue({ id: "user_2", email: "user2@example.com", name: "User Two" }), }, }; const ctx = createToolContext(db, { userRole: SystemRole.ADMIN }); const result = await executeTool( "create_notification", JSON.stringify({ userId: "user_2", type: "TASK_CREATED", title: "Need review", category: "TASK", taskStatus: "OPEN", dueDate: "2026-04-02T09:30:00.000Z", channel: "in_app", }), ctx, ); expect(db.notification.create).toHaveBeenCalledWith({ data: expect.objectContaining({ userId: "user_2", type: "TASK_CREATED", title: "Need review", category: "TASK", taskStatus: "OPEN", dueDate: new Date("2026-04-02T09:30:00.000Z"), senderId: "user_1", channel: "in_app", }), }); expect(db.notification.findUnique).toHaveBeenCalledWith({ where: { id: "notification_1" }, }); expect(JSON.parse(result.content)).toEqual( expect.objectContaining({ success: true, notificationId: "notification_1", message: 'Created notification "Need review".', }), ); expect(result.action).toEqual({ type: "invalidate", scope: ["notification"], }); }); it("returns a stable assistant error when notification dueDate is invalid", async () => { const ctx = createToolContext({}, { userRole: SystemRole.MANAGER }); const result = await executeTool( "create_notification", JSON.stringify({ userId: "user_2", type: "TASK_CREATED", title: "Need review", dueDate: "not-a-datetime", }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "Invalid dueDate: not-a-datetime", }); }); it("returns a stable assistant error when notification recipient user is missing", async () => { const ctx = createToolContext( { notification: { create: vi.fn().mockRejectedValue({ code: "P2003", message: "Foreign key constraint failed", meta: { field_name: "Notification_userId_fkey" }, }), }, }, { userRole: SystemRole.MANAGER }, ); const result = await executeTool( "create_notification", JSON.stringify({ userId: "user_missing", type: "TASK_CREATED", title: "Need review", }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "Notification recipient user not found with the given criteria.", }); }); it("returns a stable assistant error when notification assignee user is missing", async () => { const ctx = createToolContext( { notification: { create: vi.fn().mockRejectedValue({ code: "P2003", message: "Foreign key constraint failed", meta: { field_name: "Notification_assigneeId_fkey" }, }), }, }, { userRole: SystemRole.MANAGER }, ); const result = await executeTool( "create_notification", JSON.stringify({ userId: "user_2", assigneeId: "user_missing", type: "TASK_CREATED", title: "Need review", category: "TASK", }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "Assignee user not found with the given criteria.", }); }); it("returns a stable assistant error when notification sender user is missing", async () => { const ctx = createToolContext( { notification: { create: vi.fn().mockRejectedValue({ code: "P2003", message: "Foreign key constraint failed", meta: { field_name: "Notification_senderId_fkey" }, }), }, }, { userRole: SystemRole.MANAGER }, ); const result = await executeTool( "create_notification", JSON.stringify({ userId: "user_2", senderId: "user_missing", type: "TASK_CREATED", title: "Need review", }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "Sender user not found with the given criteria.", }); }); it("routes audit timeline reads through the real audit router detail path", async () => { const ctx = createToolContext({ auditLog: { findMany: vi.fn().mockResolvedValue([ { id: "audit_1", entityType: "project", entityId: "project_1", entityName: "Apollo", action: "updated", userId: "user_1", source: "ui", summary: "Changed budget", changes: { budget: [1000, 1200] }, createdAt: new Date("2026-03-29T12:00:00.000Z"), user: { id: "user_1", name: "Assistant User", email: "assistant@example.com", }, }, ]), }, }, { userRole: SystemRole.CONTROLLER }); const result = await executeTool( "get_audit_log_timeline", JSON.stringify({ limit: 10 }), ctx, ); expect(JSON.parse(result.content)).toEqual({ "2026-03-29": [ { id: "audit_1", entityType: "project", entityId: "project_1", entityName: "Apollo", action: "updated", userId: "user_1", source: "ui", summary: "Changed budget", createdAt: "2026-03-29T12:00:00.000Z", changes: { budget: [1000, 1200] }, user: { id: "user_1", name: "Assistant User", email: "assistant@example.com", }, }, ], }); }); it("returns task lists and counts for the current user through the notification router", async () => { const db = { user: { findUnique: vi.fn().mockResolvedValue({ id: "user_1" }), }, notification: { findMany: vi.fn().mockResolvedValue([ { id: "task_1", title: "Approve vacation", category: "APPROVAL", taskStatus: "OPEN", priority: "HIGH", }, ]), groupBy: vi.fn().mockResolvedValue([ { taskStatus: "OPEN", _count: 2 }, { taskStatus: "IN_PROGRESS", _count: 1 }, ]), count: vi.fn().mockResolvedValue(1), }, }; const ctx = createToolContext(db, { userRole: SystemRole.CONTROLLER }); const listResult = await executeTool( "list_tasks", JSON.stringify({ status: "OPEN", includeAssigned: false, limit: 10 }), ctx, ); const countsResult = await executeTool("get_task_counts", "{}", ctx); expect(db.notification.findMany).toHaveBeenCalledWith({ where: { userId: "user_1", category: { in: ["TASK", "APPROVAL"] }, taskStatus: "OPEN", }, orderBy: [{ priority: "desc" }, { dueDate: "asc" }, { createdAt: "desc" }], take: 10, }); expect(JSON.parse(listResult.content)).toEqual([ expect.objectContaining({ id: "task_1", title: "Approve vacation", }), ]); expect(JSON.parse(countsResult.content)).toEqual({ open: 2, inProgress: 1, done: 0, dismissed: 0, overdue: 1, }); }); it("reads task details through the real notification router path", async () => { const db = { user: { findUnique: vi.fn().mockResolvedValue({ id: "user_1" }), }, notification: { findFirst: vi.fn().mockResolvedValue({ id: "task_1", title: "Approve vacation", body: "Please approve the request", type: "TASK_CREATED", priority: "HIGH", category: "APPROVAL", taskStatus: "OPEN", taskAction: "approve_vacation:vac_1", dueDate: new Date("2026-04-01T12:00:00.000Z"), entityId: "vac_1", entityType: "vacation", completedAt: null, completedBy: null, createdAt: new Date("2026-03-29T08:00:00.000Z"), userId: "user_1", assigneeId: null, sender: { id: "sender_1", name: "Scheduler", email: "scheduler@example.com" }, }), }, }; const ctx = createToolContext(db, { userRole: SystemRole.ADMIN }); const result = await executeTool( "get_task_detail", JSON.stringify({ taskId: "task_1" }), ctx, ); expect(db.notification.findFirst).toHaveBeenCalledWith({ where: { id: "task_1", OR: [{ userId: "user_1" }, { assigneeId: "user_1" }], category: { in: ["TASK", "APPROVAL"] }, }, select: expect.objectContaining({ id: true, title: true, sender: { select: { id: true, name: true, email: true } }, }), }); expect(JSON.parse(result.content)).toEqual( expect.objectContaining({ id: "task_1", title: "Approve vacation", sender: expect.objectContaining({ id: "sender_1", name: "Scheduler", }), }), ); }); it("returns a stable assistant error for a missing task detail", async () => { const ctx = createToolContext( { user: { findUnique: vi.fn().mockResolvedValue({ id: "user_1" }), }, notification: { findFirst: vi.fn().mockResolvedValue(null), }, }, { userRole: SystemRole.ADMIN }, ); const result = await executeTool( "get_task_detail", JSON.stringify({ taskId: "task_missing" }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "Task not found with the given criteria.", }); }); it("routes task action execution through the real notification router path", async () => { const db = { user: { findUnique: vi.fn().mockResolvedValue({ id: "user_1" }), }, notification: { findFirst: vi.fn().mockResolvedValue({ id: "task_1", userId: "user_1", assigneeId: null, taskAction: "approve_vacation:vac_1", taskStatus: "OPEN", }), update: vi.fn().mockResolvedValue({ id: "task_1", taskStatus: "DONE", completedBy: "user_1", }), }, vacation: { findUnique: vi.fn().mockResolvedValue({ id: "vac_1", status: "PENDING", }), update: vi.fn().mockResolvedValue({ id: "vac_1", status: "APPROVED", }), }, }; const ctx = createToolContext(db, { userRole: SystemRole.ADMIN }); const result = await executeTool( "execute_task_action", JSON.stringify({ taskId: "task_1" }), ctx, ); expect(db.notification.findFirst).toHaveBeenCalledWith({ where: { id: "task_1", OR: [{ userId: "user_1" }, { assigneeId: "user_1" }], category: { in: ["TASK", "APPROVAL"] }, }, select: { id: true, userId: true, assigneeId: true, taskAction: true, taskStatus: true, }, }); expect(db.vacation.update).toHaveBeenCalledWith({ where: { id: "vac_1" }, data: { status: "APPROVED" }, }); expect(db.notification.update).toHaveBeenCalledWith({ where: { id: "task_1" }, data: expect.objectContaining({ taskStatus: "DONE", completedBy: "user_1", completedAt: expect.any(Date), }), }); expect(JSON.parse(result.content)).toEqual( expect.objectContaining({ success: true, message: "Vacation approved", task: expect.objectContaining({ id: "task_1", taskStatus: "DONE", }), }), ); }); it("returns a stable assistant error when updating a missing task", async () => { const ctx = createToolContext( { user: { findUnique: vi.fn().mockResolvedValue({ id: "user_1" }), }, notification: { findFirst: vi.fn().mockResolvedValue(null), }, }, { userRole: SystemRole.ADMIN }, ); const result = await executeTool( "update_task_status", JSON.stringify({ taskId: "task_missing", status: "DONE" }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "Task not found with the given criteria.", }); }); it("returns a stable assistant error when executing a missing task action", async () => { const ctx = createToolContext( { user: { findUnique: vi.fn().mockResolvedValue({ id: "user_1" }), }, notification: { findFirst: vi.fn().mockResolvedValue(null), }, }, { userRole: SystemRole.ADMIN }, ); const result = await executeTool( "execute_task_action", JSON.stringify({ taskId: "task_missing" }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "Task not found with the given criteria.", }); }); it("returns a stable assistant error when executing an already completed task action", async () => { const ctx = createToolContext( { user: { findUnique: vi.fn().mockResolvedValue({ id: "user_1" }), }, notification: { findFirst: vi.fn().mockResolvedValue({ id: "task_1", userId: "user_1", assigneeId: null, taskAction: "approve_vacation:vac_1", taskStatus: "DONE", }), }, }, { userRole: SystemRole.ADMIN }, ); const result = await executeTool( "execute_task_action", JSON.stringify({ taskId: "task_1" }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "Task is already completed.", }); }); it("returns a stable assistant error when a task has no executable action", async () => { const ctx = createToolContext( { user: { findUnique: vi.fn().mockResolvedValue({ id: "user_1" }), }, notification: { findFirst: vi.fn().mockResolvedValue({ id: "task_1", userId: "user_1", assigneeId: null, taskAction: null, taskStatus: "OPEN", }), }, }, { userRole: SystemRole.ADMIN }, ); const result = await executeTool( "execute_task_action", JSON.stringify({ taskId: "task_1" }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "Task has no executable action.", }); }); it("returns a stable assistant error when a task action format is invalid", async () => { const ctx = createToolContext( { user: { findUnique: vi.fn().mockResolvedValue({ id: "user_1" }), }, notification: { findFirst: vi.fn().mockResolvedValue({ id: "task_1", userId: "user_1", assigneeId: null, taskAction: "not-a-valid-task-action", taskStatus: "OPEN", }), }, }, { userRole: SystemRole.ADMIN }, ); const result = await executeTool( "execute_task_action", JSON.stringify({ taskId: "task_1" }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "Task action is invalid and cannot be executed.", }); }); it("returns a stable assistant error when executing a task action without permission", async () => { const ctx = createToolContext( { user: { findUnique: vi.fn().mockResolvedValue({ id: "user_1" }), }, notification: { findFirst: vi.fn().mockResolvedValue({ id: "task_1", userId: "user_1", assigneeId: null, taskAction: "approve_vacation:vac_1", taskStatus: "OPEN", }), }, }, { userRole: SystemRole.USER }, ); const result = await executeTool( "execute_task_action", JSON.stringify({ taskId: "task_1" }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "You do not have permission to execute this task action.", }); }); it("returns a stable assistant error when a vacation task action target disappears", async () => { const ctx = createToolContext( { user: { findUnique: vi.fn().mockResolvedValue({ id: "user_1" }), }, notification: { findFirst: vi.fn().mockResolvedValue({ id: "task_1", userId: "user_1", assigneeId: null, taskAction: "approve_vacation:vac_missing", taskStatus: "OPEN", }), }, vacation: { findUnique: vi.fn().mockResolvedValue(null), }, }, { userRole: SystemRole.ADMIN }, ); const result = await executeTool( "execute_task_action", JSON.stringify({ taskId: "task_1" }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "Vacation not found with the given criteria.", }); }); it("returns a stable assistant error when a vacation task action is no longer pending", async () => { const ctx = createToolContext( { user: { findUnique: vi.fn().mockResolvedValue({ id: "user_1" }), }, notification: { findFirst: vi.fn().mockResolvedValue({ id: "task_1", userId: "user_1", assigneeId: null, taskAction: "approve_vacation:vac_1", taskStatus: "OPEN", }), }, vacation: { findUnique: vi.fn().mockResolvedValue({ id: "vac_1", status: VacationStatus.APPROVED, }), }, }, { userRole: SystemRole.ADMIN }, ); const result = await executeTool( "execute_task_action", JSON.stringify({ taskId: "task_1" }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "Vacation is not pending and cannot be approved or rejected via this task action.", }); }); it("returns a stable assistant error when an assignment task action target disappears", async () => { const ctx = createToolContext( { user: { findUnique: vi.fn().mockResolvedValue({ id: "user_1" }), }, notification: { findFirst: vi.fn().mockResolvedValue({ id: "task_1", userId: "user_1", assigneeId: null, taskAction: "confirm_assignment:asg_missing", taskStatus: "OPEN", }), }, assignment: { findUnique: vi.fn().mockResolvedValue(null), }, }, { userRole: SystemRole.ADMIN }, ); const result = await executeTool( "execute_task_action", JSON.stringify({ taskId: "task_1" }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "Assignment not found with the given criteria.", }); }); it("returns a stable assistant error when an assignment is already confirmed", async () => { const ctx = createToolContext( { user: { findUnique: vi.fn().mockResolvedValue({ id: "user_1" }), }, notification: { findFirst: vi.fn().mockResolvedValue({ id: "task_1", userId: "user_1", assigneeId: null, taskAction: "confirm_assignment:asg_1", taskStatus: "OPEN", }), }, assignment: { findUnique: vi.fn().mockResolvedValue({ id: "asg_1", status: "CONFIRMED", }), }, }, { userRole: SystemRole.ADMIN }, ); const result = await executeTool( "execute_task_action", JSON.stringify({ taskId: "task_1" }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "Assignment is already confirmed.", }); }); it("creates a task for a user through the notification router", async () => { const db = { notification: { create: vi.fn().mockResolvedValue({ id: "task_2", userId: "user_2" }), findUnique: vi.fn().mockResolvedValue({ id: "task_2", title: "Follow up", category: "TASK", taskStatus: "OPEN", }), }, user: { findUnique: vi.fn().mockResolvedValue({ id: "user_2", email: "user2@example.com", name: "User Two" }), }, }; const ctx = createToolContext(db, { userRole: SystemRole.ADMIN }); const result = await executeTool( "create_task_for_user", JSON.stringify({ userId: "user_2", title: "Follow up", dueDate: "2026-04-03T11:00:00.000Z", channel: "in_app", }), ctx, ); expect(db.notification.create).toHaveBeenCalledWith({ data: expect.objectContaining({ userId: "user_2", type: "TASK_CREATED", category: "TASK", taskStatus: "OPEN", title: "Follow up", dueDate: new Date("2026-04-03T11:00:00.000Z"), senderId: "user_1", channel: "in_app", }), }); expect(db.notification.findUnique).toHaveBeenCalledWith({ where: { id: "task_2" }, }); expect(JSON.parse(result.content)).toEqual( expect.objectContaining({ success: true, taskId: "task_2", message: 'Created task "Follow up" for user_2.', }), ); }); it("returns a stable assistant error when task dueDate is invalid", async () => { const ctx = createToolContext({}, { userRole: SystemRole.MANAGER }); const result = await executeTool( "create_task_for_user", JSON.stringify({ userId: "user_2", title: "Follow up", dueDate: "not-a-datetime", }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "Invalid dueDate: not-a-datetime", }); }); it("returns a stable assistant error when task recipient user is missing", async () => { const ctx = createToolContext( { notification: { create: vi.fn().mockRejectedValue({ code: "P2003", message: "Foreign key constraint failed", meta: { field_name: "Notification_userId_fkey" }, }), }, }, { userRole: SystemRole.MANAGER }, ); const result = await executeTool( "create_task_for_user", JSON.stringify({ userId: "user_missing", title: "Follow up", }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "Task recipient user not found with the given criteria.", }); }); it("returns a stable assistant error when task sender user is missing", async () => { const ctx = createToolContext( { notification: { create: vi.fn().mockRejectedValue({ code: "P2003", message: "Foreign key constraint failed", meta: { field_name: "Notification_senderId_fkey" }, }), }, }, { userRole: SystemRole.MANAGER }, ); const result = await executeTool( "create_task_for_user", JSON.stringify({ userId: "user_2", title: "Follow up", }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "Sender user not found with the given criteria.", }); }); it("creates, lists, updates, and deletes reminders through the notification router", async () => { const db = { user: { findUnique: vi.fn().mockResolvedValue({ id: "user_1" }), }, notification: { create: vi.fn().mockResolvedValue({ id: "rem_1", title: "Submit report", category: "REMINDER", }), findMany: vi.fn().mockResolvedValue([ { id: "rem_1", title: "Submit report", category: "REMINDER", }, ]), findFirst: vi.fn() .mockResolvedValueOnce({ id: "rem_1", userId: "user_1", category: "REMINDER", }) .mockResolvedValueOnce({ id: "rem_1", userId: "user_1", category: "REMINDER", }), update: vi.fn().mockResolvedValue({ id: "rem_1", title: "Submit updated report", category: "REMINDER", }), delete: vi.fn().mockResolvedValue({ id: "rem_1" }), }, }; const ctx = createToolContext(db, { userRole: SystemRole.ADMIN }); const createResult = await executeTool( "create_reminder", JSON.stringify({ title: "Submit report", remindAt: "2026-04-01T09:00:00.000Z", recurrence: "weekly", }), ctx, ); const listResult = await executeTool("list_reminders", JSON.stringify({ limit: 10 }), ctx); const updateResult = await executeTool( "update_reminder", JSON.stringify({ id: "rem_1", title: "Submit updated report", recurrence: null, }), ctx, ); const deleteResult = await executeTool( "delete_reminder", JSON.stringify({ id: "rem_1" }), ctx, ); expect(db.notification.create).toHaveBeenCalledWith({ data: expect.objectContaining({ userId: "user_1", title: "Submit report", category: "REMINDER", recurrence: "weekly", remindAt: new Date("2026-04-01T09:00:00.000Z"), nextRemindAt: new Date("2026-04-01T09:00:00.000Z"), }), }); expect(db.notification.findMany).toHaveBeenCalledWith({ where: { userId: "user_1", category: "REMINDER" }, orderBy: { nextRemindAt: "asc" }, take: 10, }); expect(db.notification.update).toHaveBeenCalledWith({ where: { id: "rem_1" }, data: { title: "Submit updated report", recurrence: null, }, }); expect(db.notification.delete).toHaveBeenCalledWith({ where: { id: "rem_1" } }); expect(JSON.parse(createResult.content)).toEqual( expect.objectContaining({ reminderId: "rem_1", success: true }), ); expect(JSON.parse(listResult.content)).toEqual([ expect.objectContaining({ id: "rem_1", title: "Submit report" }), ]); expect(JSON.parse(updateResult.content)).toEqual( expect.objectContaining({ reminderId: "rem_1", success: true }), ); expect(JSON.parse(deleteResult.content)).toEqual( expect.objectContaining({ id: "rem_1", success: true }), ); }); it("returns a stable assistant error when reminder creation receives an invalid datetime", async () => { const ctx = createToolContext( { user: { findUnique: vi.fn().mockResolvedValue({ id: "user_1" }), }, }, { userRole: SystemRole.ADMIN }, ); const result = await executeTool( "create_reminder", JSON.stringify({ title: "Submit report", remindAt: "not-a-datetime", }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "Invalid remindAt: not-a-datetime", }); }); it("returns a stable assistant error when reminder creation loses its authenticated user", async () => { const ctx = createToolContext( { user: { findUnique: vi.fn().mockResolvedValue({ id: "user_1" }), }, notification: { create: vi.fn().mockRejectedValue({ code: "P2003", message: "Foreign key constraint failed", meta: { field_name: "Notification_userId_fkey" }, }), }, }, { userRole: SystemRole.ADMIN }, ); const result = await executeTool( "create_reminder", JSON.stringify({ title: "Submit report", remindAt: "2026-04-01T09:00:00.000Z", }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "Authenticated user not found with the given criteria.", }); }); it("returns a stable assistant error when reminder creation is rejected by the backing router", async () => { const ctx = createToolContext( { user: { findUnique: vi.fn().mockResolvedValue({ id: "user_1" }), }, notification: { create: vi.fn(), }, }, { userRole: SystemRole.ADMIN }, ); ctx.db.notification.create.mockRejectedValue( new TRPCError({ code: "BAD_REQUEST", message: "Reminder payload is invalid", }), ); const result = await executeTool( "create_reminder", JSON.stringify({ title: "Submit report", remindAt: "2026-04-01T09:00:00.000Z", }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "Reminder input is invalid.", }); }); it("returns stable assistant errors for reminder validation edge cases", async () => { const cases = [ { payload: { title: " ", remindAt: "2026-04-01T09:00:00.000Z", }, expected: "Reminder title is required.", }, { payload: { title: "x".repeat(201), remindAt: "2026-04-01T09:00:00.000Z", }, expected: "Reminder title must be at most 200 characters.", }, { payload: { title: "Submit report", body: "x".repeat(2001), remindAt: "2026-04-01T09:00:00.000Z", }, expected: "Reminder body must be at most 2000 characters.", }, { payload: { title: "Submit report", remindAt: "2026-04-01T09:00:00.000Z", recurrence: "yearly", }, expected: "Invalid recurrence: yearly. Valid values: daily, weekly, monthly.", }, ] as const; for (const testCase of cases) { const ctx = createToolContext({}, { userRole: SystemRole.ADMIN }); const result = await executeTool( "create_reminder", JSON.stringify(testCase.payload), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: testCase.expected }); } }); it("returns a stable assistant error when updating a missing reminder", async () => { const ctx = createToolContext( { user: { findUnique: vi.fn().mockResolvedValue({ id: "user_1" }), }, notification: { findFirst: vi.fn().mockResolvedValue(null), }, }, { userRole: SystemRole.ADMIN }, ); const result = await executeTool( "update_reminder", JSON.stringify({ id: "rem_missing", title: "Updated" }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "Reminder not found with the given criteria.", }); }); it("returns a stable assistant error when deleting a missing reminder", async () => { const ctx = createToolContext( { user: { findUnique: vi.fn().mockResolvedValue({ id: "user_1" }), }, notification: { findFirst: vi.fn().mockResolvedValue(null), }, }, { userRole: SystemRole.ADMIN }, ); const result = await executeTool( "delete_reminder", JSON.stringify({ id: "rem_missing" }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "Reminder not found with the given criteria.", }); }); it("returns a stable assistant error when assigning a non-task notification", async () => { const ctx = createToolContext( { notification: { findUnique: vi.fn().mockResolvedValue({ id: "notification_1", category: "NOTIFICATION", }), }, }, { userRole: SystemRole.MANAGER }, ); const result = await executeTool( "assign_task", JSON.stringify({ id: "notification_1", assigneeId: "user_2" }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "Only tasks and approvals can be assigned.", }); }); it("returns a stable assistant error when assigning a task to a missing assignee", async () => { const ctx = createToolContext( { notification: { findUnique: vi.fn().mockResolvedValue({ id: "task_1", category: "TASK", }), update: vi.fn().mockRejectedValue({ code: "P2003", message: "Foreign key constraint failed", meta: { field_name: "Notification_assigneeId_fkey" }, }), }, }, { userRole: SystemRole.MANAGER }, ); const result = await executeTool( "assign_task", JSON.stringify({ id: "task_1", assigneeId: "user_missing" }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "Assignee user not found with the given criteria.", }); }); it("returns a stable assistant error when deleting a missing notification", async () => { const ctx = createToolContext( { user: { findUnique: vi.fn().mockResolvedValue({ id: "user_1" }), }, notification: { findFirst: vi.fn().mockResolvedValue(null), }, }, { userRole: SystemRole.ADMIN }, ); const result = await executeTool( "delete_notification", JSON.stringify({ id: "notification_missing" }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "Notification not found with the given criteria.", }); }); it("returns a stable assistant error when deleting a task created by another user", async () => { const ctx = createToolContext( { user: { findUnique: vi.fn().mockResolvedValue({ id: "user_1" }), }, notification: { findFirst: vi.fn().mockResolvedValue({ id: "notification_task", category: "TASK", senderId: "user_2", }), delete: vi.fn(), }, }, { userRole: SystemRole.ADMIN }, ); const result = await executeTool( "delete_notification", JSON.stringify({ id: "notification_task" }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "Tasks created by other users cannot be deleted.", }); expect(ctx.db.notification.delete).not.toHaveBeenCalled(); }); it("creates and sends a broadcast through the notification router", async () => { const db = { notificationBroadcast: { create: vi.fn().mockResolvedValue({ id: "broadcast_1", title: "Office update", targetType: "user", createdAt: new Date("2026-03-30T09:00:00.000Z"), }), update: vi.fn().mockResolvedValue({ id: "broadcast_1", recipientCount: 1, }), }, notification: { create: vi.fn().mockResolvedValue({ id: "notification_2", userId: "user_2" }), }, }; const ctx = createToolContext(db, { userRole: SystemRole.ADMIN }); const result = await executeTool( "send_broadcast", JSON.stringify({ title: "Office update", body: "New schedule", targetType: "user", targetValue: "user_2", category: "TASK", priority: "HIGH", channel: "in_app", dueDate: "2026-04-04T10:00:00.000Z", }), ctx, ); expect(db.notificationBroadcast.create).toHaveBeenCalledWith({ data: expect.objectContaining({ senderId: "user_1", title: "Office update", body: "New schedule", category: "TASK", priority: "HIGH", channel: "in_app", targetType: "user", targetValue: "user_2", }), }); expect(db.notification.create).toHaveBeenCalledWith({ data: expect.objectContaining({ userId: "user_2", type: "BROADCAST_TASK", title: "Office update", body: "New schedule", category: "TASK", priority: "HIGH", channel: "in_app", sourceId: "broadcast_1", senderId: "user_1", taskStatus: "OPEN", dueDate: new Date("2026-04-04T10:00:00.000Z"), }), }); expect(db.notificationBroadcast.update).toHaveBeenCalledWith({ where: { id: "broadcast_1" }, data: expect.objectContaining({ sentAt: expect.any(Date), recipientCount: 1, }), }); expect(JSON.parse(result.content)).toEqual( expect.objectContaining({ success: true, broadcastId: "broadcast_1", recipientCount: 1, message: 'Broadcast "Office update" created.', }), ); expect(result.action).toEqual({ type: "invalidate", scope: ["notification"], }); }); it("creates a scheduled broadcast without immediate recipient fan-out", async () => { const db = { notificationBroadcast: { create: vi.fn().mockResolvedValue({ id: "broadcast_future", title: "Planned update", targetType: "all", scheduledAt: new Date("2026-04-10T08:00:00.000Z"), }), update: vi.fn(), }, notification: { create: vi.fn(), }, }; const ctx = createToolContext(db, { userRole: SystemRole.ADMIN }); const result = await executeTool( "send_broadcast", JSON.stringify({ title: "Planned update", targetType: "all", scheduledAt: "2026-04-10T08:00:00.000Z", }), ctx, ); expect(db.notificationBroadcast.create).toHaveBeenCalledWith({ data: expect.objectContaining({ senderId: "user_1", title: "Planned update", targetType: "all", scheduledAt: new Date("2026-04-10T08:00:00.000Z"), }), }); expect(db.notification.create).not.toHaveBeenCalled(); expect(db.notificationBroadcast.update).not.toHaveBeenCalled(); expect(JSON.parse(result.content)).toEqual( expect.objectContaining({ success: true, broadcastId: "broadcast_future", recipientCount: 0, message: 'Broadcast "Planned update" created.', }), ); }); it("returns a stable assistant error when broadcast scheduledAt is invalid", async () => { const ctx = createToolContext({}, { userRole: SystemRole.MANAGER }); const result = await executeTool( "send_broadcast", JSON.stringify({ title: "Office update", targetType: "all", scheduledAt: "not-a-datetime", }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "Invalid scheduledAt: not-a-datetime", }); }); it("returns a stable assistant error when broadcast recipient user is missing", async () => { const ctx = createToolContext( { notificationBroadcast: { create: vi.fn().mockResolvedValue({ id: "broadcast_missing_user", title: "Office update", targetType: "user", }), }, notification: { create: vi.fn().mockRejectedValue({ code: "P2003", message: "Foreign key constraint failed", meta: { field_name: "Notification_userId_fkey" }, }), }, }, { userRole: SystemRole.MANAGER }, ); const result = await executeTool( "send_broadcast", JSON.stringify({ title: "Office update", targetType: "user", targetValue: "user_missing", }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "Broadcast recipient user not found with the given criteria.", }); }); it("returns a stable assistant error when broadcast sender user is missing", async () => { const ctx = createToolContext( { notificationBroadcast: { create: vi.fn().mockResolvedValue({ id: "broadcast_missing_sender", title: "Office update", targetType: "user", }), }, notification: { create: vi.fn().mockRejectedValue({ code: "P2003", message: "Foreign key constraint failed", meta: { field_name: "Notification_senderId_fkey" }, }), }, }, { userRole: SystemRole.MANAGER }, ); const result = await executeTool( "send_broadcast", JSON.stringify({ title: "Office update", targetType: "user", targetValue: "user_2", }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "Sender user not found with the given criteria.", }); }); it("returns stable assistant errors when broadcast fan-out loses sender or recipient rows inside the router transaction", async () => { const senderMissingTx = { notificationBroadcast: { create: vi.fn().mockRejectedValue( Object.assign(new Error("Foreign key constraint failed"), { code: "P2003", meta: { field_name: "NotificationBroadcast_senderId_fkey" }, }), ), update: vi.fn(), }, notification: { create: vi.fn(), }, }; const senderMissingCtx = createToolContext( { user: { findMany: vi.fn().mockResolvedValue([{ id: "user_2" }]), }, $transaction: vi.fn(async (callback: (db: typeof senderMissingTx) => Promise) => callback(senderMissingTx)), notificationBroadcast: { create: vi.fn(), update: vi.fn(), }, notification: { create: vi.fn(), }, }, { userRole: SystemRole.MANAGER }, ); const senderMissingResult = await executeTool( "send_broadcast", JSON.stringify({ title: "Office update", targetType: "all", }), senderMissingCtx, ); expect(JSON.parse(senderMissingResult.content)).toEqual({ error: "Sender user not found with the given criteria.", }); const recipientMissingTx = { notificationBroadcast: { create: vi.fn().mockResolvedValue({ id: "broadcast_missing_recipient", title: "Office update", targetType: "all", }), update: vi.fn(), }, notification: { create: vi.fn().mockRejectedValue( Object.assign(new Error("Foreign key constraint failed"), { code: "P2003", meta: { field_name: "Notification_userId_fkey" }, }), ), }, }; const recipientMissingCtx = createToolContext( { user: { findMany: vi.fn().mockResolvedValue([{ id: "user_2" }]), }, $transaction: vi.fn(async (callback: (db: typeof recipientMissingTx) => Promise) => callback(recipientMissingTx)), notificationBroadcast: { create: vi.fn(), update: vi.fn(), }, notification: { create: vi.fn(), }, }, { userRole: SystemRole.MANAGER }, ); const recipientMissingResult = await executeTool( "send_broadcast", JSON.stringify({ title: "Office update", targetType: "all", }), recipientMissingCtx, ); expect(JSON.parse(recipientMissingResult.content)).toEqual({ error: "Broadcast recipient user not found with the given criteria.", }); }); it("returns a stable assistant error when a broadcast target resolves to no recipients", async () => { const create = vi.fn().mockResolvedValue({ id: "broadcast_empty", title: "Office update", targetType: "user", }); const ctx = createToolContext( { notificationBroadcast: { create, }, }, { userRole: SystemRole.MANAGER }, ); const result = await executeTool( "send_broadcast", JSON.stringify({ title: "Office update", targetType: "user", }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "No recipients matched the broadcast target.", }); expect(create).not.toHaveBeenCalled(); }); it("returns a stable assistant error when broadcast creation fails because the sender user is missing", async () => { const ctx = createToolContext( { user: { findMany: vi.fn().mockResolvedValue([{ id: "user_2" }]), }, notificationBroadcast: { create: vi.fn().mockRejectedValue( Object.assign(new Error("Foreign key constraint failed"), { code: "P2003", meta: { field_name: "NotificationBroadcast_senderId_fkey" }, }), ), }, }, { userRole: SystemRole.MANAGER }, ); const result = await executeTool( "send_broadcast", JSON.stringify({ title: "Office update", targetType: "all", }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "Sender user not found with the given criteria.", }); }); it("returns a stable assistant error when broadcast finalization loses the broadcast row", async () => { const txCreateBroadcast = vi.fn().mockResolvedValue({ id: "broadcast_missing_after_create", title: "Office update", targetType: "all", }); const txCreateNotification = vi.fn().mockResolvedValue({ id: "notification_2", userId: "user_2" }); const txUpdateBroadcast = vi.fn().mockRejectedValue( Object.assign(new Error("Record to update not found"), { code: "P2025", meta: { modelName: "NotificationBroadcast" }, }), ); const tx = { notificationBroadcast: { create: txCreateBroadcast, update: txUpdateBroadcast, }, notification: { create: txCreateNotification, }, }; const ctx = createToolContext( { user: { findMany: vi.fn().mockResolvedValue([{ id: "user_2" }]), }, $transaction: vi.fn(async (callback: (db: typeof tx) => Promise) => callback(tx)), notificationBroadcast: { create: vi.fn(), update: vi.fn(), }, notification: { create: vi.fn(), }, }, { userRole: SystemRole.MANAGER }, ); const result = await executeTool( "send_broadcast", JSON.stringify({ title: "Office update", targetType: "all", }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "Broadcast not found with the given criteria.", }); }); it("reads broadcast details through the real notification router and rejects plain users", async () => { const db = { notificationBroadcast: { findUnique: vi.fn().mockResolvedValue({ id: "broadcast_1", title: "Office update", body: "New schedule", sender: { id: "sender_1", name: "Manager", email: "manager@example.com" }, }), }, }; const managerCtx = createToolContext(db, { userRole: SystemRole.MANAGER }); const userCtx = createToolContext({}, { userRole: SystemRole.USER }); const successResult = await executeTool( "get_broadcast_detail", JSON.stringify({ id: "broadcast_1" }), managerCtx, ); const deniedResult = await executeTool( "get_broadcast_detail", JSON.stringify({ id: "broadcast_1" }), userCtx, ); expect(db.notificationBroadcast.findUnique).toHaveBeenCalledWith({ where: { id: "broadcast_1" }, include: { sender: { select: { id: true, name: true, email: true } }, }, }); expect(JSON.parse(successResult.content)).toEqual( expect.objectContaining({ id: "broadcast_1", title: "Office update", }), ); expect(JSON.parse(deniedResult.content)).toEqual( expect.objectContaining({ error: "You do not have permission to perform this action.", }), ); }); it("returns a stable assistant error for a missing broadcast", async () => { const ctx = createToolContext( { notificationBroadcast: { findUnique: vi.fn().mockResolvedValue(null), }, }, { userRole: SystemRole.MANAGER }, ); const result = await executeTool( "get_broadcast_detail", JSON.stringify({ id: "broadcast_missing" }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "Broadcast not found with the given criteria.", }); }); it("lists users only for admins through the real user router", async () => { const db = { user: { findMany: vi.fn().mockResolvedValue([ { id: "user_1", name: "Alice", email: "alice@example.com" }, { id: "user_2", name: "Bob", email: "bob@example.com" }, ]), }, }; const adminCtx = createToolContext(db, { userRole: SystemRole.ADMIN }); const managerCtx = createToolContext({}, { userRole: SystemRole.MANAGER }); const adminResult = await executeTool( "list_users", JSON.stringify({ limit: 1 }), adminCtx, ); const deniedResult = await executeTool("list_users", "{}", managerCtx); expect(db.user.findMany).toHaveBeenCalledWith({ select: { id: true, name: true, email: true, systemRole: true, createdAt: true, lastLoginAt: true, lastActiveAt: true, permissionOverrides: true, totpEnabled: true, }, orderBy: { name: "asc" }, }); expect(JSON.parse(adminResult.content)).toEqual([ expect.objectContaining({ id: "user_1", name: "Alice" }), ]); expect(JSON.parse(deniedResult.content)).toEqual( expect.objectContaining({ error: "You do not have permission to perform this action.", }), ); }); it("returns a stable assistant error when authenticated assistant context is missing", async () => { const ctx = { ...createToolContext({}, { userRole: SystemRole.ADMIN }), session: null, dbUser: null, } as unknown as ToolContext; const result = await executeTool("list_users", "{}", ctx); expect(JSON.parse(result.content)).toEqual({ error: "Authenticated assistant context is required for this tool.", }); }); it("reads estimate details through the real estimate router and rejects plain users", async () => { vi.mocked(getEstimateById).mockResolvedValue({ id: "est_1", name: "North Cluster Estimate", status: "DRAFT", versions: [], } as Awaited>); const controllerCtx = createToolContext({}, { userRole: SystemRole.CONTROLLER, permissions: [PermissionKey.VIEW_COSTS], }); const userCtx = createToolContext({}, { userRole: SystemRole.USER }); const successResult = await executeTool( "get_estimate_detail", JSON.stringify({ estimateId: "est_1" }), controllerCtx, ); const deniedResult = await executeTool( "get_estimate_detail", JSON.stringify({ estimateId: "est_1" }), userCtx, ); expect(vi.mocked(getEstimateById)).toHaveBeenCalledWith(controllerCtx.db, "est_1"); expect(JSON.parse(successResult.content)).toEqual( expect.objectContaining({ id: "est_1", name: "North Cluster Estimate", }), ); expect(JSON.parse(deniedResult.content)).toEqual( expect.objectContaining({ error: expect.stringContaining(PermissionKey.VIEW_COSTS), }), ); }); it("lists estimate versions through the real estimate router and rejects plain users", async () => { const db = { estimate: { findUnique: vi.fn().mockResolvedValue({ id: "est_1", name: "North Cluster Estimate", status: "DRAFT", latestVersionNumber: 4, versions: [ { id: "ver_4", versionNumber: 4, label: "Rev 4", status: "IN_REVIEW", notes: "Latest", lockedAt: null, createdAt: new Date("2026-03-28T00:00:00.000Z"), updatedAt: new Date("2026-03-29T00:00:00.000Z"), _count: { assumptions: 2, scopeItems: 3, demandLines: 4, resourceSnapshots: 1, exports: 0, }, }, ], }), }, }; const controllerCtx = createToolContext(db, { userRole: SystemRole.CONTROLLER }); const userCtx = createToolContext({}, { userRole: SystemRole.USER }); const successResult = await executeTool( "list_estimate_versions", JSON.stringify({ estimateId: "est_1" }), controllerCtx, ); const deniedResult = await executeTool( "list_estimate_versions", JSON.stringify({ estimateId: "est_1" }), userCtx, ); expect(db.estimate.findUnique).toHaveBeenCalledWith({ where: { id: "est_1" }, select: expect.objectContaining({ id: true, versions: expect.any(Object), }), }); expect(JSON.parse(successResult.content)).toEqual( expect.objectContaining({ id: "est_1", latestVersionNumber: 4, versions: [ expect.objectContaining({ id: "ver_4", versionNumber: 4, }), ], }), ); expect(JSON.parse(deniedResult.content)).toEqual( expect.objectContaining({ error: "You do not have permission to perform this action.", }), ); }); it("reads estimate version snapshots through the real estimate router, requires viewCosts, and rejects plain users", async () => { const db = { estimate: { findUnique: vi.fn().mockResolvedValue({ id: "est_1", name: "North Cluster Estimate", status: "APPROVED", baseCurrency: "EUR", versions: [ { id: "ver_4", versionNumber: 4, label: "Rev 4", status: "APPROVED", notes: "Latest", lockedAt: new Date("2026-03-29T00:00:00.000Z"), createdAt: new Date("2026-03-28T00:00:00.000Z"), updatedAt: new Date("2026-03-29T00:00:00.000Z"), assumptions: [ { id: "ass_1", category: "DELIVERY", key: "onsite", label: "Onsite support" }, ], scopeItems: [ { id: "scope_1", scopeType: "EPIC", sequenceNo: 1, name: "Pipeline" }, ], demandLines: [ { id: "dl_1", name: "Modeling", chapter: "3D", hours: 40, costTotalCents: 400000, priceTotalCents: 600000, currency: "EUR", }, ], resourceSnapshots: [ { id: "snap_1", displayName: "Alice", chapter: "3D", currency: "EUR", lcrCents: 10000, ucrCents: 15000, }, ], exports: [ { id: "exp_1", format: "XLSX", fileName: "estimate.xlsx", createdAt: new Date("2026-03-29T10:00:00.000Z"), }, ], }, ], }), }, }; const controllerCtx = createToolContext(db, { userRole: SystemRole.CONTROLLER, permissions: [PermissionKey.VIEW_COSTS], }); const controllerWithoutCostsCtx = createToolContext(db, { userRole: SystemRole.CONTROLLER, permissions: [], }); const userCtx = createToolContext({}, { userRole: SystemRole.USER }); const successResult = await executeTool( "get_estimate_version_snapshot", JSON.stringify({ estimateId: "est_1", versionId: "ver_4" }), controllerCtx, ); const missingPermissionResult = await executeTool( "get_estimate_version_snapshot", JSON.stringify({ estimateId: "est_1", versionId: "ver_4" }), controllerWithoutCostsCtx, ); const deniedResult = await executeTool( "get_estimate_version_snapshot", JSON.stringify({ estimateId: "est_1", versionId: "ver_4" }), userCtx, ); expect(db.estimate.findUnique).toHaveBeenCalledWith({ where: { id: "est_1" }, select: expect.objectContaining({ id: true, versions: expect.objectContaining({ where: { id: "ver_4" }, }), }), }); expect(JSON.parse(successResult.content)).toEqual( expect.objectContaining({ estimate: expect.objectContaining({ id: "est_1", baseCurrency: "EUR", }), version: expect.objectContaining({ id: "ver_4", versionNumber: 4, }), totals: expect.objectContaining({ hours: 40, costTotalCents: 400000, priceTotalCents: 600000, }), chapterBreakdown: [ expect.objectContaining({ chapter: "3D", lineCount: 1, }), ], }), ); expect(JSON.parse(missingPermissionResult.content)).toEqual( expect.objectContaining({ error: expect.stringContaining(PermissionKey.VIEW_COSTS), }), ); expect(JSON.parse(deniedResult.content)).toEqual( expect.objectContaining({ error: expect.stringContaining(PermissionKey.VIEW_COSTS), }), ); }); it("returns a stable error when estimate details are requested for a missing estimate", async () => { vi.mocked(getEstimateById).mockResolvedValueOnce(null as never); const ctx = createToolContext({}, { userRole: SystemRole.CONTROLLER, permissions: [PermissionKey.VIEW_COSTS], }); const result = await executeTool( "get_estimate_detail", JSON.stringify({ estimateId: "missing_estimate" }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "Estimate not found with the given criteria.", }); }); it("returns a stable error when estimate versions are requested for a missing estimate", async () => { const db = { estimate: { findUnique: vi.fn().mockResolvedValue(null), }, }; const ctx = createToolContext(db, { userRole: SystemRole.CONTROLLER }); const result = await executeTool( "list_estimate_versions", JSON.stringify({ estimateId: "missing_estimate" }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "Estimate not found with the given criteria.", }); }); it("returns a stable error when an estimate version snapshot is missing", async () => { const db = { estimate: { findUnique: vi.fn().mockResolvedValue(null), }, }; const ctx = createToolContext(db, { userRole: SystemRole.CONTROLLER, permissions: [PermissionKey.VIEW_COSTS], }); const result = await executeTool( "get_estimate_version_snapshot", JSON.stringify({ estimateId: "missing_estimate", versionId: "missing_version" }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "Estimate version not found with the given criteria.", }); }); it("returns a stable assistant error when the selected project disappears during estimate creation", async () => { const ctx = createToolContext( { project: { findUnique: vi.fn() .mockResolvedValueOnce({ id: "project_1" }) .mockResolvedValueOnce({ id: "project_1", shortCode: "PROJ-1", name: "Project One", status: "DRAFT", startDate: new Date("2026-01-01T00:00:00.000Z"), endDate: new Date("2026-12-31T00:00:00.000Z"), orderType: "TIME_MATERIAL", allocationType: "INT", winProbability: 100, budgetCents: 100000, responsiblePerson: "Peter Parker", }), }, estimate: { create: vi.fn().mockRejectedValue({ code: "P2003", message: "Foreign key constraint failed", meta: { field_name: "Estimate_projectId_fkey" }, }), }, }, { userRole: SystemRole.MANAGER, permissions: [PermissionKey.MANAGE_PROJECTS], }, ); const result = await executeTool( "create_estimate", JSON.stringify({ name: "Delivery Estimate", projectId: "project_1" }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "Project not found with the given criteria.", }); }); it("returns a stable assistant error when a referenced role disappears during estimate creation", async () => { const ctx = createToolContext( { estimate: { create: vi.fn().mockRejectedValue({ code: "P2003", message: "Foreign key constraint failed", meta: { field_name: "EstimateDemandLine_roleId_fkey" }, }), }, rateCardLine: { findMany: vi.fn().mockResolvedValue([]), }, }, { userRole: SystemRole.MANAGER, permissions: [PermissionKey.MANAGE_PROJECTS], }, ); const result = await executeTool( "create_estimate", JSON.stringify({ name: "Delivery Estimate", demandLines: [ { roleId: "role_1", lineType: "LABOR", name: "Design", hours: 40, costRateCents: 0, billRateCents: 0, currency: "EUR", costTotalCents: 0, priceTotalCents: 0, monthlySpread: {}, staffingAttributes: {}, metadata: {}, }, ], }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "Role not found with the given criteria.", }); }); it("returns stable assistant errors when estimate creation loses referenced records mid-write", async () => { const cases = [ { name: "missing resource reference", payload: { name: "Delivery Estimate", demandLines: [ { resourceId: "resource_1", lineType: "LABOR", name: "Animation", hours: 40, costRateCents: 0, billRateCents: 0, currency: "EUR", costTotalCents: 0, priceTotalCents: 0, monthlySpread: {}, staffingAttributes: {}, metadata: {}, }, ], }, rejection: { code: "P2003", message: "Foreign key constraint failed", meta: { field_name: "EstimateDemandLine_resourceId_fkey" }, }, expected: "Resource not found with the given criteria.", }, { name: "missing scope item reference", payload: { name: "Delivery Estimate", scopeItems: [ { sequenceNo: 1, scopeType: "SHOT", name: "Shot 010", technicalSpec: {}, metadata: {}, }, ], demandLines: [ { scopeItemId: "scope_item_missing", lineType: "LABOR", name: "Lighting", hours: 24, costRateCents: 0, billRateCents: 0, currency: "EUR", costTotalCents: 0, priceTotalCents: 0, monthlySpread: {}, staffingAttributes: {}, metadata: {}, }, ], }, rejection: { code: "P2003", message: "Foreign key constraint failed", meta: { field_name: "EstimateDemandLine_scopeItemId_fkey" }, }, expected: "Estimate scope item not found with the given criteria.", }, { name: "generic referenced record race", payload: { name: "Delivery Estimate" }, rejection: { code: "P2025", message: "Record to create no longer references a valid row", meta: { cause: "Dependent record disappeared during nested estimate create" }, }, expected: "One of the referenced project, role, resource, or scope items no longer exists.", }, ] as const; for (const testCase of cases) { const ctx = createToolContext( { estimate: { create: vi.fn().mockRejectedValue(testCase.rejection), }, rateCardLine: { findMany: vi.fn().mockResolvedValue([]), }, }, { userRole: SystemRole.MANAGER, permissions: [PermissionKey.MANAGE_PROJECTS], }, ); const result = await executeTool( "create_estimate", JSON.stringify(testCase.payload), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: testCase.expected, }); } }); it("returns stable assistant errors for estimate mutation tools backed by estimate application use-cases", async () => { const cases = [ { name: "clone_estimate missing source estimate", toolName: "clone_estimate", payload: { sourceEstimateId: "est_missing" }, permission: PermissionKey.MANAGE_PROJECTS, setup: () => vi.mocked(cloneEstimate).mockRejectedValueOnce(new Error("Source estimate not found")), expected: "Estimate not found with the given criteria.", }, { name: "clone_estimate without source versions", toolName: "clone_estimate", payload: { sourceEstimateId: "est_empty" }, permission: PermissionKey.MANAGE_PROJECTS, setup: () => vi.mocked(cloneEstimate).mockRejectedValueOnce(new Error("Source estimate has no versions")), expected: "Source estimate has no versions and cannot be cloned.", }, { name: "update_estimate_draft missing estimate", toolName: "update_estimate_draft", payload: { id: "est_missing", baseCurrency: "EUR", assumptions: [], scopeItems: [], demandLines: [], resourceSnapshots: [], metrics: [], }, permission: PermissionKey.MANAGE_PROJECTS, db: { estimate: { findUnique: vi.fn().mockResolvedValue({ projectId: null }), }, }, setup: () => vi.mocked(updateEstimateDraft).mockRejectedValueOnce(new Error("Estimate not found")), expected: "Estimate not found with the given criteria.", }, { name: "update_estimate_draft without working version", toolName: "update_estimate_draft", payload: { id: "est_locked", baseCurrency: "EUR", assumptions: [], scopeItems: [], demandLines: [], resourceSnapshots: [], metrics: [], }, permission: PermissionKey.MANAGE_PROJECTS, db: { estimate: { findUnique: vi.fn().mockResolvedValue({ projectId: null }), }, }, setup: () => vi.mocked(updateEstimateDraft).mockRejectedValueOnce(new Error("Estimate has no working version")), expected: "Estimate has no working version.", }, { name: "update_estimate_draft missing scope item reference", toolName: "update_estimate_draft", payload: { id: "est_scope_missing", baseCurrency: "EUR", assumptions: [], scopeItems: [], demandLines: [], resourceSnapshots: [], metrics: [], }, permission: PermissionKey.MANAGE_PROJECTS, db: { estimate: { findUnique: vi.fn().mockResolvedValue({ projectId: null }), }, }, setup: () => vi.mocked(updateEstimateDraft).mockRejectedValueOnce( Object.assign(new Error("Foreign key constraint failed"), { code: "P2003", meta: { field_name: "EstimateScopeItem_scopeItemId_fkey" }, }), ), expected: "Estimate scope item not found with the given criteria.", }, { name: "update_estimate_draft missing project reference", toolName: "update_estimate_draft", payload: { id: "est_project_missing", baseCurrency: "EUR", assumptions: [], scopeItems: [], demandLines: [], resourceSnapshots: [], metrics: [], }, permission: PermissionKey.MANAGE_PROJECTS, db: { estimate: { findUnique: vi.fn().mockResolvedValue({ projectId: null }), }, }, setup: () => vi.mocked(updateEstimateDraft).mockRejectedValueOnce( Object.assign(new Error("Foreign key constraint failed"), { code: "P2003", meta: { field_name: "Estimate_projectId_fkey" }, }), ), expected: "Project not found with the given criteria.", }, { name: "update_estimate_draft missing role reference", toolName: "update_estimate_draft", payload: { id: "est_role_missing", baseCurrency: "EUR", assumptions: [], scopeItems: [], demandLines: [], resourceSnapshots: [], metrics: [], }, permission: PermissionKey.MANAGE_PROJECTS, db: { estimate: { findUnique: vi.fn().mockResolvedValue({ projectId: null }), }, }, setup: () => vi.mocked(updateEstimateDraft).mockRejectedValueOnce( Object.assign(new Error("Foreign key constraint failed"), { code: "P2003", meta: { field_name: "EstimateDemandLine_roleId_fkey" }, }), ), expected: "Role not found with the given criteria.", }, { name: "update_estimate_draft missing resource reference", toolName: "update_estimate_draft", payload: { id: "est_resource_missing", baseCurrency: "EUR", assumptions: [], scopeItems: [], demandLines: [], resourceSnapshots: [], metrics: [], }, permission: PermissionKey.MANAGE_PROJECTS, db: { estimate: { findUnique: vi.fn().mockResolvedValue({ projectId: null }), }, }, setup: () => vi.mocked(updateEstimateDraft).mockRejectedValueOnce( Object.assign(new Error("Foreign key constraint failed"), { code: "P2003", meta: { field_name: "EstimateDemandLine_resourceId_fkey" }, }), ), expected: "Resource not found with the given criteria.", }, { name: "update_estimate_draft generic missing estimate reference", toolName: "update_estimate_draft", payload: { id: "est_reference_missing", baseCurrency: "EUR", assumptions: [], scopeItems: [], demandLines: [], resourceSnapshots: [], metrics: [], }, permission: PermissionKey.MANAGE_PROJECTS, db: { estimate: { findUnique: vi.fn().mockResolvedValue({ projectId: null }), }, }, setup: () => vi.mocked(updateEstimateDraft).mockRejectedValueOnce( Object.assign(new Error("Foreign key constraint failed"), { code: "P2003", meta: { field_name: "EstimateVersion_estimateId_fkey" }, }), ), expected: "One of the referenced project, role, resource, or scope items no longer exists.", }, { name: "submit_estimate_version missing version", toolName: "submit_estimate_version", payload: { estimateId: "est_1", versionId: "ver_missing" }, permission: PermissionKey.MANAGE_PROJECTS, setup: () => vi.mocked(submitEstimateVersion).mockRejectedValueOnce(new Error("Estimate version not found")), expected: "Estimate version not found with the given criteria.", }, { name: "submit_estimate_version deleted version race", toolName: "submit_estimate_version", payload: { estimateId: "est_1", versionId: "ver_deleted" }, permission: PermissionKey.MANAGE_PROJECTS, setup: () => vi.mocked(submitEstimateVersion).mockRejectedValueOnce( Object.assign(new Error("Record to update not found"), { code: "P2025", meta: { modelName: "EstimateVersion" }, }), ), expected: "Estimate version not found with the given criteria.", }, { name: "submit_estimate_version without working version", toolName: "submit_estimate_version", payload: { estimateId: "est_1" }, permission: PermissionKey.MANAGE_PROJECTS, setup: () => vi.mocked(submitEstimateVersion).mockRejectedValueOnce(new Error("Estimate has no working version")), expected: "Estimate has no working version.", }, { name: "submit_estimate_version wrong source status", toolName: "submit_estimate_version", payload: { estimateId: "est_1" }, permission: PermissionKey.MANAGE_PROJECTS, setup: () => vi.mocked(submitEstimateVersion).mockRejectedValueOnce(new Error("Only working versions can be submitted")), expected: "Only working versions can be submitted.", }, { name: "approve_estimate_version missing version", toolName: "approve_estimate_version", payload: { estimateId: "est_1", versionId: "ver_missing" }, permission: PermissionKey.MANAGE_PROJECTS, setup: () => vi.mocked(approveEstimateVersion).mockRejectedValueOnce(new Error("Estimate version not found")), expected: "Estimate version not found with the given criteria.", }, { name: "approve_estimate_version deleted version race", toolName: "approve_estimate_version", payload: { estimateId: "est_1", versionId: "ver_deleted" }, permission: PermissionKey.MANAGE_PROJECTS, setup: () => vi.mocked(approveEstimateVersion).mockRejectedValueOnce( Object.assign(new Error("Record to update not found"), { code: "P2025", meta: { modelName: "EstimateVersion" }, }), ), expected: "Estimate version not found with the given criteria.", }, { name: "approve_estimate_version without submitted version", toolName: "approve_estimate_version", payload: { estimateId: "est_1" }, permission: PermissionKey.MANAGE_PROJECTS, setup: () => vi.mocked(approveEstimateVersion).mockRejectedValueOnce(new Error("Estimate has no submitted version")), expected: "Estimate has no submitted version.", }, { name: "approve_estimate_version wrong source status", toolName: "approve_estimate_version", payload: { estimateId: "est_1" }, permission: PermissionKey.MANAGE_PROJECTS, setup: () => vi.mocked(approveEstimateVersion).mockRejectedValueOnce(new Error("Only submitted versions can be approved")), expected: "Only submitted versions can be approved.", }, { name: "create_estimate_revision with existing working version", toolName: "create_estimate_revision", payload: { estimateId: "est_1" }, permission: PermissionKey.MANAGE_PROJECTS, setup: () => vi.mocked(createEstimateRevision).mockRejectedValueOnce(new Error("Estimate already has a working version")), expected: "Estimate already has a working version.", }, { name: "create_estimate_revision deleted source version race", toolName: "create_estimate_revision", payload: { estimateId: "est_1", sourceVersionId: "ver_deleted" }, permission: PermissionKey.MANAGE_PROJECTS, setup: () => vi.mocked(createEstimateRevision).mockRejectedValueOnce( Object.assign(new Error("Record to update not found"), { code: "P2025", meta: { modelName: "EstimateVersion" }, }), ), expected: "Estimate version not found with the given criteria.", }, { name: "create_estimate_revision with unlocked source version", toolName: "create_estimate_revision", payload: { estimateId: "est_1", sourceVersionId: "ver_1" }, permission: PermissionKey.MANAGE_PROJECTS, setup: () => vi.mocked(createEstimateRevision).mockRejectedValueOnce(new Error("Source version must be locked before creating a revision")), expected: "Source version must be locked before creating a revision.", }, { name: "create_estimate_revision without locked source version", toolName: "create_estimate_revision", payload: { estimateId: "est_1" }, permission: PermissionKey.MANAGE_PROJECTS, setup: () => vi.mocked(createEstimateRevision).mockRejectedValueOnce(new Error("Estimate has no locked version to revise")), expected: "Estimate has no locked version to revise.", }, { name: "create_estimate_export missing version", toolName: "create_estimate_export", payload: { estimateId: "est_1", versionId: "ver_missing", format: "XLSX" }, permission: PermissionKey.MANAGE_PROJECTS, setup: () => vi.mocked(createEstimateExport).mockRejectedValueOnce(new Error("Estimate version not found")), expected: "Estimate version not found with the given criteria.", }, { name: "create_estimate_export deleted version race", toolName: "create_estimate_export", payload: { estimateId: "est_1", versionId: "ver_deleted", format: "XLSX" }, permission: PermissionKey.MANAGE_PROJECTS, setup: () => vi.mocked(createEstimateExport).mockRejectedValueOnce( Object.assign(new Error("Record to update not found"), { code: "P2025", meta: { modelName: "EstimateVersion" }, }), ), expected: "Estimate version not found with the given criteria.", }, { name: "create_estimate_planning_handoff with missing linked project", toolName: "create_estimate_planning_handoff", payload: { estimateId: "est_1" }, permission: PermissionKey.MANAGE_ALLOCATIONS, setup: () => vi.mocked(createEstimatePlanningHandoff).mockRejectedValueOnce(new Error("Linked project not found")), expected: "Project not found with the given criteria.", }, { name: "create_estimate_planning_handoff without approved version", toolName: "create_estimate_planning_handoff", payload: { estimateId: "est_1" }, permission: PermissionKey.MANAGE_ALLOCATIONS, setup: () => vi.mocked(createEstimatePlanningHandoff).mockRejectedValueOnce(new Error("Estimate has no approved version")), expected: "Estimate has no approved version.", }, { name: "create_estimate_planning_handoff duplicate", toolName: "create_estimate_planning_handoff", payload: { estimateId: "est_1" }, permission: PermissionKey.MANAGE_ALLOCATIONS, setup: () => vi.mocked(createEstimatePlanningHandoff).mockRejectedValueOnce(new Error("Planning handoff already exists for this approved version")), expected: "Planning handoff already exists for this approved version.", }, { name: "create_estimate_planning_handoff without working days", toolName: "create_estimate_planning_handoff", payload: { estimateId: "est_1" }, permission: PermissionKey.MANAGE_ALLOCATIONS, setup: () => vi.mocked(createEstimatePlanningHandoff).mockRejectedValueOnce(new Error("Project window has no working days for demand line dl_1")), expected: "The linked project window has no working days for at least one demand line.", }, { name: "create_estimate_planning_handoff requires approved version", toolName: "create_estimate_planning_handoff", payload: { estimateId: "est_1" }, permission: PermissionKey.MANAGE_ALLOCATIONS, setup: () => vi.mocked(createEstimatePlanningHandoff).mockRejectedValueOnce(new Error("Only approved versions can be handed off to planning")), expected: "Only approved versions can be handed off to planning.", }, { name: "create_estimate_planning_handoff requires linked project", toolName: "create_estimate_planning_handoff", payload: { estimateId: "est_1" }, permission: PermissionKey.MANAGE_ALLOCATIONS, setup: () => vi.mocked(createEstimatePlanningHandoff).mockRejectedValueOnce(new Error("Estimate must be linked to a project before planning handoff")), expected: "Estimate must be linked to a project before planning handoff.", }, { name: "create_estimate_planning_handoff requires valid linked project dates", toolName: "create_estimate_planning_handoff", payload: { estimateId: "est_1" }, permission: PermissionKey.MANAGE_ALLOCATIONS, setup: () => vi.mocked(createEstimatePlanningHandoff).mockRejectedValueOnce(new Error("Linked project has an invalid date range")), expected: "The linked project has an invalid date range for planning handoff.", }, ] as const; for (const testCase of cases) { testCase.setup(); const ctx = createToolContext(testCase.db ?? {}, { userRole: SystemRole.MANAGER, permissions: [testCase.permission], }); const result = await executeTool( testCase.toolName, JSON.stringify(testCase.payload), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: testCase.expected }); } }); it("returns stable assistant errors for estimate phasing and commercial terms edge cases", async () => { vi.mocked(getEstimateById).mockResolvedValueOnce(null as never); const missingEstimateCtx = createToolContext({}, { userRole: SystemRole.MANAGER, permissions: [PermissionKey.MANAGE_PROJECTS], }); const missingEstimateResult = await executeTool( "generate_estimate_weekly_phasing", JSON.stringify({ estimateId: "est_missing", startDate: "2026-04-01", endDate: "2026-04-30", }), missingEstimateCtx, ); expect(JSON.parse(missingEstimateResult.content)).toEqual({ error: "Estimate not found with the given criteria.", }); vi.mocked(getEstimateById).mockResolvedValueOnce({ id: "est_1", name: "Estimate One", versions: [], } as Awaited>); const noWorkingVersionResult = await executeTool( "generate_estimate_weekly_phasing", JSON.stringify({ estimateId: "est_1", startDate: "2026-04-01", endDate: "2026-04-30", }), missingEstimateCtx, ); expect(JSON.parse(noWorkingVersionResult.content)).toEqual({ error: "Estimate has no working version.", }); vi.mocked(getEstimateById).mockResolvedValueOnce({ id: "est_2", name: "Estimate Two", versions: [ { id: "ver_1", status: "WORKING", demandLines: [ { id: "line_missing", hours: 40, metadata: {}, }, ], }, ], } as Awaited>); const missingDemandLineCtx = createToolContext( { estimateDemandLine: { update: vi.fn().mockRejectedValue({ code: "P2025", message: "Record not found", meta: { modelName: "EstimateDemandLine" }, }), }, }, { userRole: SystemRole.MANAGER, permissions: [PermissionKey.MANAGE_PROJECTS], }, ); const missingDemandLineResult = await executeTool( "generate_estimate_weekly_phasing", JSON.stringify({ estimateId: "est_2", startDate: "2026-04-01", endDate: "2026-04-30", }), missingDemandLineCtx, ); expect(JSON.parse(missingDemandLineResult.content)).toEqual({ error: "Estimate demand line not found with the given criteria.", }); const commercialTermsCases = [ { db: { estimate: { findUnique: vi.fn().mockResolvedValue(null), }, }, expected: "Estimate version not found with the given criteria.", }, { db: { estimate: { findUnique: vi.fn().mockResolvedValue({ id: "est_1", versions: [{ id: "ver_1", status: "APPROVED" }], }), }, }, expected: "Commercial terms can only be edited on working versions.", }, ] as const; for (const testCase of commercialTermsCases) { const ctx = createToolContext(testCase.db, { userRole: SystemRole.MANAGER, permissions: [PermissionKey.MANAGE_PROJECTS], }); const result = await executeTool( "update_estimate_commercial_terms", JSON.stringify({ estimateId: "est_1", terms: {}, }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: testCase.expected }); } const invalidTermsCtx = createToolContext({}, { userRole: SystemRole.MANAGER, permissions: [PermissionKey.MANAGE_PROJECTS], }); const invalidTermsResult = await executeTool( "update_estimate_commercial_terms", JSON.stringify({ estimateId: "est_1", terms: { contingencyPercent: -1, }, }), invalidTermsCtx, ); expect(JSON.parse(invalidTermsResult.content)).toEqual({ error: "Commercial terms input is invalid.", }); }); it("returns a stable assistant error when commercial term persistence loses the working version", async () => { const ctx = createToolContext( { estimate: { findUnique: vi.fn().mockResolvedValue({ id: "est_1", versions: [{ id: "ver_working", status: "WORKING" }], }), }, estimateVersion: { update: vi.fn().mockRejectedValue({ code: "P2025", message: "Record to update not found", meta: { modelName: "EstimateVersion" }, }), }, }, { userRole: SystemRole.MANAGER, permissions: [PermissionKey.MANAGE_PROJECTS], }, ); const result = await executeTool( "update_estimate_commercial_terms", JSON.stringify({ estimateId: "est_1", terms: {}, }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "Estimate version not found with the given criteria.", }); }); it("returns stable assistant errors for estimate weekly phasing and commercial term reads", async () => { vi.mocked(getEstimateById).mockResolvedValueOnce(null as never); const missingEstimateCtx = createToolContext({}, { userRole: SystemRole.CONTROLLER, }); const missingEstimateResult = await executeTool( "get_estimate_weekly_phasing", JSON.stringify({ estimateId: "est_missing" }), missingEstimateCtx, ); expect(JSON.parse(missingEstimateResult.content)).toEqual({ error: "Estimate not found with the given criteria.", }); vi.mocked(getEstimateById).mockResolvedValueOnce({ id: "est_empty", name: "Estimate Empty", versions: [], } as Awaited>); const noVersionResult = await executeTool( "get_estimate_weekly_phasing", JSON.stringify({ estimateId: "est_empty" }), missingEstimateCtx, ); expect(JSON.parse(noVersionResult.content)).toEqual({ error: "Estimate version not found with the given criteria.", }); const missingCommercialTermsCtx = createToolContext( { estimate: { findUnique: vi.fn().mockResolvedValue(null), }, }, { userRole: SystemRole.CONTROLLER, }, ); const missingCommercialTermsResult = await executeTool( "get_estimate_commercial_terms", JSON.stringify({ estimateId: "est_missing" }), missingCommercialTermsCtx, ); expect(JSON.parse(missingCommercialTermsResult.content)).toEqual({ error: "Estimate version not found with the given criteria.", }); }); it("reads countries through the real country router identifier path", async () => { const db = { country: { findUnique: vi.fn().mockResolvedValue(null), findFirst: vi.fn() .mockResolvedValueOnce(null) .mockResolvedValueOnce({ id: "country_de", code: "DE", name: "Germany", dailyWorkingHours: 8, scheduleRules: null, isActive: true, metroCities: [{ id: "city_muc", name: "Munich" }], _count: { resources: 12 }, }), }, }; const ctx = createToolContext(db, { userRole: SystemRole.CONTROLLER }); const result = await executeTool( "get_country", JSON.stringify({ identifier: "Germany" }), ctx, ); expect(db.country.findUnique).toHaveBeenCalledWith({ where: { id: "Germany" }, include: { metroCities: { orderBy: { name: "asc" } }, _count: { select: { resources: true } }, }, }); expect(db.country.findFirst).toHaveBeenNthCalledWith(1, { where: { code: { equals: "GERMANY", mode: "insensitive" } }, include: { metroCities: { orderBy: { name: "asc" } }, _count: { select: { resources: true } }, }, }); expect(db.country.findFirst).toHaveBeenNthCalledWith(2, { where: { name: { equals: "Germany", mode: "insensitive" } }, include: { metroCities: { orderBy: { name: "asc" } }, _count: { select: { resources: true } }, }, }); expect(JSON.parse(result.content)).toEqual({ id: "country_de", code: "DE", name: "Germany", dailyWorkingHours: 8, scheduleRules: null, isActive: true, resourceCount: 12, metroCities: [{ id: "city_muc", name: "Munich" }], cities: ["Munich"], }); }); it("creates and updates countries through the real country router path", async () => { const db = { country: { findUnique: vi.fn() .mockResolvedValueOnce(null) .mockResolvedValueOnce({ id: "country_de", code: "DE", name: "Germany" }), create: vi.fn().mockResolvedValue({ id: "country_de", code: "DE", name: "Germany", dailyWorkingHours: 8, scheduleRules: null, isActive: true, metroCities: [], _count: { resources: 0 }, }), update: vi.fn().mockResolvedValue({ id: "country_de", code: "DE", name: "Germany Updated", dailyWorkingHours: 7.5, scheduleRules: { shortFriday: true }, isActive: false, metroCities: [], _count: { resources: 0 }, }), }, auditLog: { create: vi.fn().mockResolvedValue({ id: "audit_1" }), }, }; const ctx = createToolContext(db, { userRole: SystemRole.ADMIN }); const createResult = await executeTool( "create_country", JSON.stringify({ code: "DE", name: "Germany", dailyWorkingHours: 8 }), ctx, ); const updateResult = await executeTool( "update_country", JSON.stringify({ id: "country_de", data: { name: "Germany Updated", dailyWorkingHours: 7.5, scheduleRules: null, isActive: false, }, }), ctx, ); expect(db.country.create).toHaveBeenCalledWith({ data: { code: "DE", name: "Germany", dailyWorkingHours: 8, }, include: { metroCities: true }, }); expect(db.country.update).toHaveBeenCalledWith({ where: { id: "country_de" }, data: { name: "Germany Updated", dailyWorkingHours: 7.5, scheduleRules: expect.anything(), isActive: false, }, include: { metroCities: true }, }); expect(JSON.parse(createResult.content)).toEqual( expect.objectContaining({ success: true, message: "Created country: Germany", country: expect.objectContaining({ id: "country_de", code: "DE", }), }), ); expect(createResult.action).toEqual({ type: "invalidate", scope: ["country", "resource", "holidayCalendar", "vacation"], }); expect(JSON.parse(updateResult.content)).toEqual( expect.objectContaining({ success: true, message: "Updated country: Germany Updated", country: expect.objectContaining({ id: "country_de", isActive: false, }), }), ); }); it("creates, updates, and deletes metro cities through the real country router path", async () => { const metroCityFindUnique = vi.fn() .mockResolvedValueOnce({ id: "city_muc", name: "Munich", countryId: "country_de" }) .mockResolvedValueOnce({ id: "city_muc", name: "Munich", _count: { resources: 0 } }); const db = { country: { findUnique: vi.fn().mockResolvedValue({ id: "country_de", name: "Germany" }), }, metroCity: { create: vi.fn().mockResolvedValue({ id: "city_muc", name: "Munich", countryId: "country_de" }), findUnique: metroCityFindUnique, update: vi.fn().mockResolvedValue({ id: "city_muc", name: "Muenchen", countryId: "country_de" }), delete: vi.fn().mockResolvedValue({ id: "city_muc" }), }, auditLog: { create: vi.fn().mockResolvedValue({ id: "audit_1" }), }, }; const ctx = createToolContext(db, { userRole: SystemRole.ADMIN }); const createResult = await executeTool( "create_metro_city", JSON.stringify({ countryId: "country_de", name: "Munich" }), ctx, ); const updateResult = await executeTool( "update_metro_city", JSON.stringify({ id: "city_muc", data: { name: "Muenchen" } }), ctx, ); const deleteResult = await executeTool( "delete_metro_city", JSON.stringify({ id: "city_muc" }), ctx, ); expect(db.metroCity.create).toHaveBeenCalledWith({ data: { name: "Munich", countryId: "country_de" }, }); expect(db.metroCity.update).toHaveBeenCalledWith({ where: { id: "city_muc" }, data: { name: "Muenchen" }, }); expect(db.metroCity.delete).toHaveBeenCalledWith({ where: { id: "city_muc" } }); expect(JSON.parse(createResult.content)).toEqual( expect.objectContaining({ success: true, message: "Created metro city: Munich", metroCity: expect.objectContaining({ id: "city_muc", name: "Munich" }), }), ); expect(JSON.parse(updateResult.content)).toEqual( expect.objectContaining({ success: true, message: "Updated metro city: Muenchen", metroCity: expect.objectContaining({ id: "city_muc", name: "Muenchen" }), }), ); expect(JSON.parse(deleteResult.content)).toEqual( expect.objectContaining({ success: true, message: "Deleted metro city: Munich", }), ); }); it("routes role, client, and org unit mutations through their backing routers", async () => { const db = { role: { findUnique: vi.fn().mockImplementation(async (args?: any) => { if (args?.where?.name === "Pipeline TD") return null; if (args?.where?.name === "Pipeline Lead") return null; if (args?.where?.id === "role_pipeline") { return { id: "role_pipeline", name: "Pipeline TD", description: "Pipeline craft", color: "#123456", isActive: true, _count: { resourceRoles: 0 }, resourceRoles: [], }; } return null; }), create: vi.fn().mockResolvedValue({ id: "role_pipeline", name: "Pipeline TD", description: "Pipeline craft", color: "#123456", isActive: true, _count: { resourceRoles: 0 }, }), update: vi.fn().mockResolvedValue({ id: "role_pipeline", name: "Pipeline Lead", description: "Lead pipeline craft", color: "#654321", isActive: false, _count: { resourceRoles: 0 }, }), delete: vi.fn().mockResolvedValue({ id: "role_pipeline" }), }, client: { findUnique: vi.fn().mockImplementation(async (args?: any) => { if (args?.where?.id === "client_1") { return { id: "client_1", name: "Acme", code: "ACME", parentId: null, isActive: true, sortOrder: 0, tags: [], }; } if (args?.where?.code === "ACME-NEW") return null; return null; }), create: vi.fn().mockResolvedValue({ id: "client_1", name: "Acme", code: "ACME", parentId: null, isActive: true, sortOrder: 2, tags: ["key"], }), update: vi.fn().mockResolvedValue({ id: "client_1", name: "Acme Updated", code: "ACME-NEW", parentId: null, isActive: false, sortOrder: 3, tags: ["vip"], }), }, orgUnit: { findUnique: vi.fn().mockImplementation(async (args?: any) => { if (args?.where?.id === "ou_1") { return { id: "ou_1", name: "Operations", shortName: "OPS", level: 5, parentId: null, sortOrder: 1, isActive: true, }; } return null; }), create: vi.fn().mockResolvedValue({ id: "ou_1", name: "Operations", shortName: "OPS", level: 5, parentId: null, sortOrder: 1, isActive: true, }), update: vi.fn().mockResolvedValue({ id: "ou_1", name: "Operations EU", shortName: null, level: 5, parentId: null, sortOrder: 4, isActive: false, }), }, auditLog: { create: vi.fn().mockResolvedValue({ id: "audit_1" }), }, }; const ctx = createToolContext(db, { userRole: SystemRole.ADMIN }); const createRoleResult = await executeTool( "create_role", JSON.stringify({ name: "Pipeline TD", description: "Pipeline craft", color: "#123456" }), ctx, ); const updateRoleResult = await executeTool( "update_role", JSON.stringify({ id: "role_pipeline", name: "Pipeline Lead", description: "Lead pipeline craft", color: "#654321", isActive: false }), ctx, ); const deleteRoleResult = await executeTool( "delete_role", JSON.stringify({ id: "role_pipeline" }), ctx, ); const createClientResult = await executeTool( "create_client", JSON.stringify({ name: "Acme", code: "ACME", sortOrder: 2, tags: ["key"] }), ctx, ); const updateClientResult = await executeTool( "update_client", JSON.stringify({ id: "client_1", name: "Acme Updated", code: "ACME-NEW", sortOrder: 3, isActive: false, tags: ["vip"] }), ctx, ); const createOrgUnitResult = await executeTool( "create_org_unit", JSON.stringify({ name: "Operations", shortName: "OPS", level: 5, sortOrder: 1 }), ctx, ); const updateOrgUnitResult = await executeTool( "update_org_unit", JSON.stringify({ id: "ou_1", name: "Operations EU", shortName: null, sortOrder: 4, isActive: false }), ctx, ); expect(JSON.parse(createRoleResult.content)).toEqual(expect.objectContaining({ success: true, roleId: "role_pipeline", message: "Created role: Pipeline TD", })); expect(JSON.parse(updateRoleResult.content)).toEqual(expect.objectContaining({ success: true, roleId: "role_pipeline", message: "Updated role: Pipeline Lead", })); expect(JSON.parse(deleteRoleResult.content)).toEqual(expect.objectContaining({ success: true, message: "Deleted role: Pipeline TD", })); expect(JSON.parse(createClientResult.content)).toEqual(expect.objectContaining({ success: true, clientId: "client_1", message: "Created client: Acme", })); expect(JSON.parse(updateClientResult.content)).toEqual(expect.objectContaining({ success: true, clientId: "client_1", message: "Updated client: Acme Updated", })); expect(JSON.parse(createOrgUnitResult.content)).toEqual(expect.objectContaining({ success: true, orgUnitId: "ou_1", message: "Created org unit: Operations", })); expect(JSON.parse(updateOrgUnitResult.content)).toEqual(expect.objectContaining({ success: true, orgUnitId: "ou_1", message: "Updated org unit: Operations EU", })); expect(db.role.create).toHaveBeenCalled(); expect(db.role.update).toHaveBeenCalled(); expect(db.role.delete).toHaveBeenCalledWith({ where: { id: "role_pipeline" } }); expect(db.client.create).toHaveBeenCalled(); expect(db.client.update).toHaveBeenCalled(); expect(db.orgUnit.create).toHaveBeenCalled(); expect(db.orgUnit.update).toHaveBeenCalled(); expect(db.auditLog.create).toHaveBeenCalled(); }); it("routes vacation balance, requests, approvals, rejections, and entitlements through the real routers", async () => { const vacationFindMany = vi.fn().mockImplementation(async (args?: any) => { if (args?.where?.type === VacationType.PUBLIC_HOLIDAY) { return []; } if (args?.where?.type?.in) { return [ { startDate: new Date("2026-01-05T00:00:00.000Z"), endDate: new Date("2026-01-06T00:00:00.000Z"), status: "APPROVED", isHalfDay: false, }, { startDate: new Date("2026-02-03T00:00:00.000Z"), endDate: new Date("2026-02-03T00:00:00.000Z"), status: "PENDING", isHalfDay: true, }, ]; } if (args?.where?.type === VacationType.SICK) { return [ { startDate: new Date("2026-03-10T00:00:00.000Z"), endDate: new Date("2026-03-10T00:00:00.000Z"), isHalfDay: false, }, ]; } if (args?.where?.resource?.chapter) { return []; } return []; }); const vacationFindUnique = vi.fn().mockImplementation(async (args?: any) => { const id = args?.where?.id; if (id === "vac_cancelled") { return { id, resourceId: "res_1", requestedById: "user_1", status: "CANCELLED", startDate: new Date("2026-07-01T00:00:00.000Z"), endDate: new Date("2026-07-02T00:00:00.000Z"), type: VacationType.ANNUAL, isHalfDay: false, resource: { id: "res_1", displayName: "Alice Example", eid: "EMP-001", chapter: "Delivery", }, requestedBy: { id: "user_1", name: "Assistant User", email: "assistant@example.com" }, approvedBy: null, }; } if (id === "vac_pending") { return { id, resourceId: "res_1", requestedById: "user_1", status: "PENDING", startDate: new Date("2026-08-03T00:00:00.000Z"), endDate: new Date("2026-08-04T00:00:00.000Z"), type: VacationType.ANNUAL, isHalfDay: false, resource: { id: "res_1", displayName: "Alice Example", eid: "EMP-001", chapter: "Delivery", }, requestedBy: { id: "user_1", name: "Assistant User", email: "assistant@example.com" }, approvedBy: null, }; } return null; }); const db = { user: { findUnique: vi.fn().mockResolvedValue({ id: "user_1", systemRole: "MANAGER" }), }, resource: { findUnique: vi.fn().mockImplementation(async (args?: any) => { if (args?.where?.id === "res_1") { return { id: "res_1", eid: "EMP-001", displayName: "Alice Example", userId: "user_1", chapter: "Delivery", federalState: "BY", countryId: "country_de", metroCityId: null, country: { code: "DE", name: "Germany" }, metroCity: null, }; } return null; }), count: vi.fn().mockResolvedValue(1), }, holidayCalendar: { findMany: vi.fn().mockResolvedValue([]), }, systemSettings: { findUnique: vi.fn().mockResolvedValue({ vacationDefaultDays: 30 }), }, vacationEntitlement: { findUnique: vi.fn().mockResolvedValue(null), create: vi.fn().mockImplementation(async (args?: any) => ({ id: `ent_${args?.data?.year ?? "unknown"}`, resourceId: args?.data?.resourceId ?? "res_1", year: args?.data?.year ?? 2026, entitledDays: args?.data?.entitledDays ?? 30, carryoverDays: args?.data?.carryoverDays ?? 0, usedDays: args?.data?.usedDays ?? 0, pendingDays: args?.data?.pendingDays ?? 0, })), update: vi.fn().mockImplementation(async (args?: any) => ({ id: args?.where?.id ?? "ent_2026", resourceId: "res_1", year: 2026, entitledDays: 30, carryoverDays: 0, usedDays: args?.data?.usedDays ?? 0, pendingDays: args?.data?.pendingDays ?? 0, })), }, vacation: { findUnique: vacationFindUnique, findMany: vacationFindMany, findFirst: vi.fn().mockResolvedValue(null), create: vi.fn().mockResolvedValue({ id: "vac_created", resourceId: "res_1", status: "APPROVED", type: VacationType.ANNUAL, startDate: new Date("2026-07-01T00:00:00.000Z"), endDate: new Date("2026-07-02T00:00:00.000Z"), isHalfDay: false, resource: { id: "res_1", displayName: "Alice Example", eid: "EMP-001", chapter: "Delivery", }, requestedBy: { id: "user_1", name: "Assistant User", email: "assistant@example.com" }, effectiveDays: 2, }), update: vi.fn().mockImplementation(async (args?: any) => ({ id: args?.where?.id ?? "vac_unknown", resourceId: "res_1", startDate: args?.where?.id === "vac_pending" ? new Date("2026-08-03T00:00:00.000Z") : new Date("2026-07-01T00:00:00.000Z"), endDate: args?.where?.id === "vac_pending" ? new Date("2026-08-04T00:00:00.000Z") : new Date("2026-07-02T00:00:00.000Z"), type: VacationType.ANNUAL, isHalfDay: false, status: args?.data?.status ?? "APPROVED", rejectionReason: args?.data?.rejectionReason ?? null, approvedById: args?.data?.approvedById ?? null, approvedAt: args?.data?.approvedAt ?? null, })), }, notification: { updateMany: vi.fn().mockResolvedValue({ count: 1 }), create: vi.fn().mockResolvedValue({ id: "note_1", userId: "user_1" }), }, auditLog: { create: vi.fn().mockResolvedValue({ id: "audit_1" }), }, webhook: { findMany: vi.fn().mockResolvedValue([]), }, }; const ctx = createToolContext(db, { userRole: SystemRole.ADMIN }); const balanceResult = await executeTool( "get_vacation_balance", JSON.stringify({ resourceId: "res_1", year: 2026 }), ctx, ); const createResult = await executeTool( "create_vacation", JSON.stringify({ resourceId: "res_1", type: "ANNUAL", startDate: "2026-07-01", endDate: "2026-07-02", }), ctx, ); const approveResult = await executeTool( "approve_vacation", JSON.stringify({ vacationId: "vac_cancelled" }), ctx, ); const rejectResult = await executeTool( "reject_vacation", JSON.stringify({ vacationId: "vac_pending", reason: "Capacity freeze" }), ctx, ); const setEntitlementResult = await executeTool( "set_entitlement", JSON.stringify({ resourceId: "res_1", year: 2027, entitledDays: 32 }), ctx, ); expect(JSON.parse(balanceResult.content)).toEqual({ resource: "Alice Example", eid: "EMP-001", year: 2026, entitlement: 30, carryOver: 0, taken: 1, pending: 0.5, remaining: 28.5, sickDays: 1, }); expect(JSON.parse(createResult.content)).toEqual(expect.objectContaining({ success: true, vacationId: "vac_created", message: "Created ANNUAL for Alice Example: 2026-07-01 to 2026-07-02 (status: APPROVED, deducted 2 day(s))", })); expect(JSON.parse(approveResult.content)).toEqual(expect.objectContaining({ success: true, message: "Approved vacation for Alice Example", })); expect(JSON.parse(rejectResult.content)).toEqual(expect.objectContaining({ success: true, message: "Rejected vacation for Alice Example: Capacity freeze", })); expect(JSON.parse(setEntitlementResult.content)).toEqual(expect.objectContaining({ success: true, message: "Set entitlement for Alice Example (2027): 32 days", })); expect(db.vacation.create).toHaveBeenCalledWith(expect.objectContaining({ data: expect.objectContaining({ resourceId: "res_1", type: VacationType.ANNUAL, status: "APPROVED", requestedById: "user_1", approvedById: "user_1", }), })); expect(db.vacation.update).toHaveBeenCalledWith(expect.objectContaining({ where: { id: "vac_cancelled" }, data: expect.objectContaining({ status: "APPROVED" }), })); expect(db.vacation.update).toHaveBeenCalledWith(expect.objectContaining({ where: { id: "vac_pending" }, data: expect.objectContaining({ status: "REJECTED", rejectionReason: "Capacity freeze" }), })); expect(db.vacationEntitlement.create).toHaveBeenCalledWith(expect.objectContaining({ data: expect.objectContaining({ resourceId: "res_1", year: 2027, entitledDays: 32, }), })); }); it("routes entitlement summary through the entitlement year summary workflow", async () => { const db = { systemSettings: { findUnique: vi.fn().mockResolvedValue({ vacationDefaultDays: 28 }), }, resource: { findMany: vi.fn().mockResolvedValue([ { id: "res_1", displayName: "Alice Example", eid: "EMP-001", lcrCents: 0, chapter: "Delivery", }, { id: "res_2", displayName: "Bob Example", eid: "EMP-002", lcrCents: 0, chapter: "CGI", }, ]), }, vacationEntitlement: { findUnique: vi.fn().mockResolvedValue(null), create: vi.fn().mockImplementation(async (args?: any) => ({ id: `ent_${args?.data?.resourceId ?? "unknown"}_${args?.data?.year ?? "unknown"}`, resourceId: args?.data?.resourceId ?? "res_1", year: args?.data?.year ?? 2026, entitledDays: args?.data?.entitledDays ?? 28, carryoverDays: args?.data?.carryoverDays ?? 0, usedDays: args?.data?.usedDays ?? 0, pendingDays: args?.data?.pendingDays ?? 0, })), update: vi.fn().mockImplementation(async (args?: any) => ({ id: args?.where?.id ?? "ent_unknown", resourceId: args?.where?.id?.includes("res_2") ? "res_2" : "res_1", year: 2026, entitledDays: 28, carryoverDays: 0, usedDays: args?.data?.usedDays ?? 0, pendingDays: args?.data?.pendingDays ?? 0, })), }, vacation: { findMany: vi.fn().mockResolvedValue([]), }, }; const ctx = createToolContext(db, { userRole: SystemRole.ADMIN }); const result = await executeTool( "get_entitlement_summary", JSON.stringify({ year: 2026, resourceName: "alice" }), ctx, ); expect(db.resource.findMany).toHaveBeenCalledWith({ where: { isActive: true }, select: { id: true, displayName: true, eid: true, lcrCents: true, chapter: true }, orderBy: [{ chapter: "asc" }, { displayName: "asc" }], }); expect(JSON.parse(result.content)).toEqual([ { resource: "Alice Example", eid: "EMP-001", chapter: "Delivery", year: 2026, entitled: 28, carryover: 0, used: 0, pending: 0, remaining: 28, }, ]); }); it("allows self-service vacation cancellation through the real vacation router path", async () => { const db = { user: { findUnique: vi.fn().mockResolvedValue({ id: "user_1", systemRole: "USER" }), }, vacation: { findUnique: vi.fn().mockResolvedValue({ id: "vac_self", resourceId: "res_1", requestedById: "user_1", status: "APPROVED", startDate: new Date("2026-09-07T00:00:00.000Z"), endDate: new Date("2026-09-09T00:00:00.000Z"), resource: { id: "res_1", displayName: "Alice Example", eid: "EMP-001", chapter: "Delivery", }, requestedBy: { id: "user_1", name: "Assistant User", email: "assistant@example.com" }, approvedBy: null, }), update: vi.fn().mockResolvedValue({ id: "vac_self", resourceId: "res_1", status: "CANCELLED", }), }, resource: { findUnique: vi.fn().mockResolvedValue({ userId: "user_1" }), }, auditLog: { create: vi.fn().mockResolvedValue({ id: "audit_1" }), }, systemSettings: { findUnique: vi.fn().mockResolvedValue({ anonymizationEnabled: false }), }, }; const ctx = createToolContext(db, { userRole: SystemRole.USER, permissions: [] }); const result = await executeTool( "cancel_vacation", JSON.stringify({ vacationId: "vac_self" }), ctx, ); expect(JSON.parse(result.content)).toEqual(expect.objectContaining({ success: true, message: "Cancelled vacation for Alice Example", })); expect(db.vacation.update).toHaveBeenCalledWith({ where: { id: "vac_self" }, data: { status: "CANCELLED" }, }); }); it("returns a stable assistant error when vacation approval cannot resolve the request", async () => { const ctx = createToolContext( { vacation: { findUnique: vi.fn().mockRejectedValue( new TRPCError({ code: "NOT_FOUND", message: "Vacation not found" }), ), }, }, { userRole: SystemRole.MANAGER }, ); const result = await executeTool( "approve_vacation", JSON.stringify({ vacationId: "vac_missing" }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "Vacation not found with the given criteria.", }); }); it("returns a stable assistant error when vacation approval violates lifecycle preconditions", async () => { const ctx = createToolContext( { vacation: { findUnique: vi .fn() .mockResolvedValue({ id: "vac_approved", resource: { displayName: "Alice Example" }, }), }, }, { userRole: SystemRole.MANAGER }, ); const result = await executeTool( "approve_vacation", JSON.stringify({ vacationId: "vac_approved" }), { ...ctx, db: { ...ctx.db, vacation: { ...((ctx.db as Record).vacation as Record), findUnique: vi .fn() .mockResolvedValueOnce({ id: "vac_approved", resource: { displayName: "Alice Example" }, }) .mockResolvedValueOnce({ id: "vac_approved", resource: { displayName: "Alice Example" }, }), update: vi.fn().mockRejectedValue( new TRPCError({ code: "BAD_REQUEST", message: "Only PENDING, CANCELLED, or REJECTED vacations can be approved", }), ), }, } as ToolContext["db"], }, ); expect(JSON.parse(result.content)).toEqual({ error: "Vacation cannot be approved in its current status.", }); }); it("returns a stable assistant error when vacation rejection cannot resolve the request", async () => { const ctx = createToolContext( { vacation: { findUnique: vi.fn().mockRejectedValue( new TRPCError({ code: "NOT_FOUND", message: "Vacation not found" }), ), }, }, { userRole: SystemRole.MANAGER }, ); const result = await executeTool( "reject_vacation", JSON.stringify({ vacationId: "vac_missing", reason: "Capacity freeze" }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "Vacation not found with the given criteria.", }); }); it("returns a stable assistant error when vacation rejection violates lifecycle preconditions", async () => { const ctx = createToolContext( { vacation: { findUnique: vi .fn() .mockResolvedValue({ id: "vac_approved", resource: { displayName: "Alice Example" }, }), }, }, { userRole: SystemRole.MANAGER }, ); const result = await executeTool( "reject_vacation", JSON.stringify({ vacationId: "vac_approved", reason: "Capacity freeze" }), { ...ctx, db: { ...ctx.db, vacation: { ...((ctx.db as Record).vacation as Record), findUnique: vi .fn() .mockResolvedValueOnce({ id: "vac_approved", resource: { displayName: "Alice Example" }, }) .mockResolvedValueOnce({ id: "vac_approved", resource: { displayName: "Alice Example" }, }), update: vi.fn().mockRejectedValue( new TRPCError({ code: "BAD_REQUEST", message: "Only PENDING vacations can be rejected", }), ), }, } as ToolContext["db"], }, ); expect(JSON.parse(result.content)).toEqual({ error: "Vacation cannot be rejected in its current status.", }); }); it("returns a stable assistant error when vacation cancellation cannot resolve the request", async () => { const ctx = createToolContext( { vacation: { findUnique: vi.fn().mockRejectedValue( new TRPCError({ code: "NOT_FOUND", message: "Vacation not found" }), ), }, }, { userRole: SystemRole.MANAGER }, ); const result = await executeTool( "cancel_vacation", JSON.stringify({ vacationId: "vac_missing" }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "Vacation not found with the given criteria.", }); }); it("returns a stable assistant error when vacation cancellation violates lifecycle preconditions", async () => { const ctx = createToolContext( { vacation: { findUnique: vi .fn() .mockResolvedValue({ id: "vac_cancelled", requestedById: "user_1", resource: { displayName: "Alice Example", userId: "user_1" }, }), }, }, { userRole: SystemRole.USER, permissions: [] }, ); const result = await executeTool( "cancel_vacation", JSON.stringify({ vacationId: "vac_cancelled" }), { ...ctx, db: { ...ctx.db, vacation: { ...((ctx.db as Record).vacation as Record), findUnique: vi .fn() .mockResolvedValueOnce({ id: "vac_cancelled", requestedById: "user_1", resource: { displayName: "Alice Example", userId: "user_1" }, }) .mockResolvedValueOnce({ id: "vac_cancelled", status: VacationStatus.CANCELLED, resource: { displayName: "Alice Example" }, }), update: vi.fn().mockRejectedValue( new TRPCError({ code: "BAD_REQUEST", message: "Already cancelled", }), ), }, } as ToolContext["db"], }, ); expect(JSON.parse(result.content)).toEqual({ error: "Vacation cannot be cancelled in its current status.", }); }); it("returns a stable assistant error when vacation creation receives an invalid end date", async () => { const ctx = createToolContext( { resource: { findUnique: vi.fn() .mockResolvedValueOnce(null) .mockResolvedValueOnce({ id: "res_1", eid: "EMP-001", displayName: "Alice Example", chapter: "Delivery", }), findFirst: vi.fn().mockResolvedValue(null), }, }, { userRole: SystemRole.ADMIN }, ); const result = await executeTool( "create_vacation", JSON.stringify({ resourceId: "EMP-001", type: "ANNUAL", startDate: "2026-09-07", endDate: "2026-09-99", }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "Invalid endDate: 2026-09-99", }); }); it("returns a stable assistant error when vacation creation overlaps an existing request", async () => { const ctx = createToolContext( { resource: { findUnique: vi.fn() .mockResolvedValueOnce(null) .mockResolvedValueOnce({ id: "res_1", eid: "EMP-001", displayName: "Alice Example", chapter: "Delivery", }), findFirst: vi.fn().mockResolvedValue(null), }, user: { findUnique: vi.fn().mockResolvedValue({ id: "user_1", systemRole: "MANAGER" }), }, vacation: { findFirst: vi.fn().mockResolvedValue({ id: "vac_existing", resourceId: "res_1", }), }, }, { userRole: SystemRole.MANAGER }, ); const result = await executeTool( "create_vacation", JSON.stringify({ resourceId: "EMP-001", type: "ANNUAL", startDate: "2026-09-07", endDate: "2026-09-09", }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "Overlapping vacation already exists for this resource in the selected period", }); }); it("returns a stable assistant error when a user tries to create vacation for another resource", async () => { const ctx = createToolContext( { resource: { findUnique: vi.fn() .mockResolvedValueOnce(null) .mockResolvedValueOnce({ id: "res_1", eid: "EMP-001", displayName: "Alice Example", chapter: "Delivery", isActive: true, }) .mockResolvedValueOnce({ userId: "user_2", }), findFirst: vi.fn().mockResolvedValue({ id: "res_1", }), }, user: { findUnique: vi.fn().mockResolvedValue({ id: "user_1", systemRole: "USER" }), }, }, { userRole: SystemRole.USER }, ); const result = await executeTool( "create_vacation", JSON.stringify({ resourceId: "EMP-001", type: "ANNUAL", startDate: "2026-09-07", endDate: "2026-09-09", }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "You can only create vacation requests for your own resource.", }); }); it("returns a stable assistant error when the entitlement resource disappears before saving", async () => { const ctx = createToolContext( { resource: { findUnique: vi.fn() .mockResolvedValueOnce(null) .mockResolvedValueOnce({ id: "res_1", eid: "EMP-001", displayName: "Alice Example", chapter: "Delivery", }), findFirst: vi.fn().mockResolvedValue(null), }, vacationEntitlement: { findUnique: vi.fn().mockResolvedValue(null), create: vi.fn().mockRejectedValue({ code: "P2003", message: "Foreign key constraint failed", meta: { field_name: "VacationEntitlement_resourceId_fkey" }, }), }, }, { userRole: SystemRole.MANAGER }, ); const result = await executeTool( "set_entitlement", JSON.stringify({ resourceId: "EMP-001", year: 2027, entitledDays: 32 }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "Resource not found with the given criteria.", }); }); it("returns a stable assistant error when entitlement carryover is passed manually", async () => { const ctx = createToolContext( { resource: { findUnique: vi.fn(), findFirst: vi.fn(), }, }, { userRole: SystemRole.MANAGER }, ); const result = await executeTool( "set_entitlement", JSON.stringify({ resourceId: "EMP-001", year: 2027, entitledDays: 32, carryoverDays: 3, }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "Manual carryoverDays is not supported here. Carryover is computed automatically from prior-year balances.", }); expect(ctx.db.resource.findUnique).not.toHaveBeenCalled(); expect(ctx.db.resource.findFirst).not.toHaveBeenCalled(); }); it("routes comment listing, creation, and resolution through the real comment router path", async () => { const commentFindUnique = vi.fn().mockResolvedValue({ id: "comment_1", authorId: "user_1", entityType: "estimate", entityId: "est_1", }); const db = { estimate: { findUnique: vi.fn().mockResolvedValue({ id: "est_1" }), }, comment: { findMany: vi.fn().mockResolvedValue([ { id: "comment_1", body: "Initial note", resolved: false, createdAt: new Date("2026-03-29T09:00:00.000Z"), author: { id: "user_1", name: "Assistant User", email: "assistant@example.com", image: null }, replies: [ { id: "comment_reply_1", body: "Reply", resolved: false, createdAt: new Date("2026-03-29T10:00:00.000Z"), author: { id: "user_2", name: "Reviewer", email: "reviewer@example.com", image: null }, }, ], }, ]), findUnique: commentFindUnique, create: vi.fn().mockResolvedValue({ id: "comment_created", body: "Please review this estimate.", resolved: false, createdAt: new Date("2026-03-29T11:00:00.000Z"), author: { id: "user_1", name: "Assistant User", email: "assistant@example.com", image: null }, }), update: vi.fn().mockResolvedValue({ id: "comment_1", body: "Initial note", resolved: true, createdAt: new Date("2026-03-29T09:00:00.000Z"), author: { id: "user_1", name: "Assistant User", email: "assistant@example.com", image: null }, }), }, notification: { create: vi.fn(), }, auditLog: { create: vi.fn().mockResolvedValue({ id: "audit_1" }), }, }; const ctx = createToolContext(db, { userRole: SystemRole.CONTROLLER }); const listResult = await executeTool( "list_comments", JSON.stringify({ entityType: "estimate", entityId: "est_1" }), ctx, ); const createResult = await executeTool( "create_comment", JSON.stringify({ entityType: "estimate", entityId: "est_1", body: "Please review this estimate.", }), ctx, ); const resolveResult = await executeTool( "resolve_comment", JSON.stringify({ commentId: "comment_1", resolved: true }), ctx, ); expect(db.comment.findMany).toHaveBeenCalledWith({ where: { entityType: "estimate", entityId: "est_1", parentId: null, }, include: { author: { select: { id: true, name: true, email: true, image: true } }, replies: { include: { author: { select: { id: true, name: true, email: true, image: true } }, }, orderBy: { createdAt: "asc" }, }, }, orderBy: { createdAt: "asc" }, }); expect(db.estimate.findUnique).toHaveBeenCalledTimes(3); expect(db.estimate.findUnique).toHaveBeenNthCalledWith(1, { where: { id: "est_1" }, select: { id: true }, }); expect(db.estimate.findUnique).toHaveBeenNthCalledWith(2, { where: { id: "est_1" }, select: { id: true }, }); expect(db.estimate.findUnique).toHaveBeenNthCalledWith(3, { where: { id: "est_1" }, select: { id: true }, }); expect(db.comment.create).toHaveBeenCalledWith({ data: { entityType: "estimate", entityId: "est_1", authorId: "user_1", body: "Please review this estimate.", mentions: [], }, include: { author: { select: { id: true, name: true, email: true, image: true } }, }, }); expect(commentFindUnique).toHaveBeenCalledWith({ where: { id: "comment_1" }, select: { id: true, authorId: true, entityType: true, entityId: true }, }); expect(db.comment.update).toHaveBeenCalledWith({ where: { id: "comment_1" }, data: { resolved: true }, include: { author: { select: { id: true, name: true, email: true, image: true } }, }, }); expect(JSON.parse(listResult.content)).toEqual([ { id: "comment_1", author: "Assistant User", body: "Initial note", resolved: false, createdAt: "2026-03-29T09:00:00.000Z", replyCount: 1, replies: [ { id: "comment_reply_1", author: "Reviewer", body: "Reply", resolved: false, createdAt: "2026-03-29T10:00:00.000Z", }, ], }, ]); expect(JSON.parse(createResult.content)).toEqual({ id: "comment_created", author: "Assistant User", body: "Please review this estimate.", createdAt: "2026-03-29T11:00:00.000Z", }); expect(createResult.action).toEqual({ type: "invalidate", scope: ["comment"], }); expect(JSON.parse(resolveResult.content)).toEqual({ id: "comment_1", resolved: true, author: "Assistant User", body: "Initial note", }); expect(resolveResult.action).toEqual({ type: "invalidate", scope: ["comment"], }); }); it("returns a stable assistant error when creating a comment with an empty body", async () => { const ctx = createToolContext( { estimate: { findUnique: vi.fn(), }, comment: { create: vi.fn(), }, }, { userRole: SystemRole.CONTROLLER }, ); const result = await executeTool( "create_comment", JSON.stringify({ entityType: "estimate", entityId: "est_1", body: "", }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "Comment body is required.", }); expect(ctx.db.comment.create).not.toHaveBeenCalled(); }); it("returns a stable assistant error when creating a comment with a body that is too long", async () => { const ctx = createToolContext( { estimate: { findUnique: vi.fn(), }, comment: { create: vi.fn(), }, }, { userRole: SystemRole.CONTROLLER }, ); const result = await executeTool( "create_comment", JSON.stringify({ entityType: "estimate", entityId: "est_1", body: "x".repeat(10_001), }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "Comment body must be at most 10000 characters.", }); expect(ctx.db.comment.create).not.toHaveBeenCalled(); }); it("returns a stable assistant error when the comment author disappears during creation", async () => { const ctx = createToolContext( { estimate: { findUnique: vi.fn().mockResolvedValue({ id: "est_1" }), }, comment: { create: vi.fn().mockRejectedValue({ code: "P2003", message: "Foreign key constraint failed", meta: { field_name: "Comment_authorId_fkey" }, }), }, }, { userRole: SystemRole.CONTROLLER }, ); const result = await executeTool( "create_comment", JSON.stringify({ entityType: "estimate", entityId: "est_1", body: "Please review this estimate.", }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "Comment author not found with the given criteria.", }); }); it("returns a stable assistant error when a mentioned user disappears during comment creation", async () => { const ctx = createToolContext( { estimate: { findUnique: vi.fn().mockResolvedValue({ id: "est_1" }), }, comment: { create: vi.fn().mockResolvedValue({ id: "comment_created", body: "Hello @[Peter Parker](user_missing)", resolved: false, createdAt: new Date("2026-03-29T11:00:00.000Z"), author: { id: "user_1", name: "Assistant User", email: "assistant@example.com", image: null }, }), }, notification: { create: vi.fn().mockRejectedValue({ code: "P2003", message: "Foreign key constraint failed", meta: { field_name: "Notification_userId_fkey" }, }), }, auditLog: { create: vi.fn(), }, }, { userRole: SystemRole.CONTROLLER }, ); const result = await executeTool( "create_comment", JSON.stringify({ entityType: "estimate", entityId: "est_1", body: "Hello @[Peter Parker](user_missing)", }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "Mentioned user not found with the given criteria.", }); }); it("returns a stable assistant error when resolving a missing comment", async () => { const ctx = createToolContext( { user: { findUnique: vi.fn().mockResolvedValue({ id: "user_1" }), }, comment: { findUnique: vi.fn().mockResolvedValue(null), }, }, { userRole: SystemRole.CONTROLLER }, ); const result = await executeTool( "resolve_comment", JSON.stringify({ commentId: "comment_missing", resolved: true }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "Comment not found with the given criteria.", }); }); it("returns a stable assistant error when a non-author resolves a comment", async () => { const ctx = createToolContext( { user: { findUnique: vi.fn().mockResolvedValue({ id: "user_1" }), }, comment: { findUnique: vi.fn().mockResolvedValue({ id: "comment_1", authorId: "user_2", entityType: "estimate", entityId: "est_1", }), }, estimate: { findUnique: vi.fn().mockResolvedValue({ id: "est_1" }), }, }, { userRole: SystemRole.CONTROLLER }, ); const result = await executeTool( "resolve_comment", JSON.stringify({ commentId: "comment_1", resolved: true }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "Only the comment author or an admin can resolve comments.", }); }); it("routes taxonomy and rule read tools through their backing routers", async () => { const db = { managementLevelGroup: { findMany: vi.fn().mockResolvedValue([ { id: "mlg_exec", name: "Executive", targetPercentage: 75, levels: [{ id: "ml_partner", name: "Partner" }], }, ]), }, utilizationCategory: { findMany: vi.fn().mockResolvedValue([ { id: "util_billable", code: "BILLABLE", name: "Billable", description: "Client work", isActive: true, sortOrder: 1, }, ]), findUnique: vi.fn().mockResolvedValue({ id: "util_billable", code: "BILLABLE", name: "Billable", description: "Client work", isActive: true, sortOrder: 1, _count: { projects: 3 }, }), }, calculationRule: { findMany: vi.fn().mockResolvedValue([ { id: "calc_1", name: "Rush Fee", description: "Adds cost for rush work", isActive: true, triggerType: "RUSH", orderType: null, costEffect: "INCREASE", costReductionPercent: null, chargeabilityEffect: "NONE", priority: 90, project: { id: "proj_1", name: "Falcon", shortCode: "FAL" }, }, ]), }, effortRuleSet: { findMany: vi.fn().mockResolvedValue([ { id: "ers_default", name: "Default Effort", isDefault: true, rules: [ { id: "eff_1", description: "Animation per shot", scopeType: "SHOT", discipline: "Animation", chapter: "3D", unitMode: "per_item", hoursPerUnit: 12, sortOrder: 0, }, ], }, ]), }, experienceMultiplierSet: { findMany: vi.fn().mockResolvedValue([ { id: "ems_default", name: "Default Multipliers", isDefault: true, rules: [ { id: "exp_1", description: "Senior DE uplift", chapter: "3D", location: "DE", level: "Senior", costMultiplier: 1.2, billMultiplier: 1.15, shoringRatio: 0.4, additionalEffortRatio: 0.1, sortOrder: 0, }, ], }, ]), }, }; const ctx = createToolContext(db, { userRole: SystemRole.CONTROLLER }); const managementLevelsResult = await executeTool("list_management_levels", "{}", ctx); const utilizationCategoriesResult = await executeTool("list_utilization_categories", "{}", ctx); const calculationRulesResult = await executeTool("list_calculation_rules", "{}", ctx); const effortRulesResult = await executeTool("list_effort_rules", "{}", ctx); const experienceMultipliersResult = await executeTool("list_experience_multipliers", "{}", ctx); expect(db.managementLevelGroup.findMany).toHaveBeenCalledWith({ include: { levels: { orderBy: { name: "asc" } } }, orderBy: { sortOrder: "asc" }, }); expect(db.utilizationCategory.findMany).toHaveBeenCalledWith({ where: {}, orderBy: { sortOrder: "asc" }, }); expect(db.utilizationCategory.findUnique).toHaveBeenCalledWith({ where: { id: "util_billable" }, include: { _count: { select: { projects: true } } }, }); expect(db.calculationRule.findMany).toHaveBeenCalledWith({ orderBy: [{ priority: "desc" }, { name: "asc" }], include: { project: { select: { id: true, shortCode: true, name: true, status: true, endDate: true, }, }, }, }); expect(db.effortRuleSet.findMany).toHaveBeenCalledWith({ include: { rules: { orderBy: { sortOrder: "asc" } }, }, orderBy: [{ isDefault: "desc" }, { name: "asc" }], }); expect(db.experienceMultiplierSet.findMany).toHaveBeenCalledWith({ include: { rules: { orderBy: { sortOrder: "asc" } }, }, orderBy: [{ isDefault: "desc" }, { name: "asc" }], }); expect(JSON.parse(managementLevelsResult.content)).toEqual([ { id: "mlg_exec", name: "Executive", target: "75%", levels: [{ id: "ml_partner", name: "Partner" }], }, ]); expect(JSON.parse(utilizationCategoriesResult.content)).toEqual([ { id: "util_billable", code: "BILLABLE", name: "Billable", description: "Client work", projectCount: 3, }, ]); expect(JSON.parse(calculationRulesResult.content)).toEqual([ expect.objectContaining({ id: "calc_1", name: "Rush Fee", project: expect.objectContaining({ id: "proj_1", shortCode: "FAL", }), }), ]); expect(JSON.parse(effortRulesResult.content)).toEqual([ { id: "eff_1", description: "Animation per shot", scopeType: "SHOT", discipline: "Animation", chapter: "3D", unitMode: "per_item", hoursPerUnit: 12, sortOrder: 0, ruleSet: { name: "Default Effort", isDefault: true }, }, ]); expect(JSON.parse(experienceMultipliersResult.content)).toEqual([ { id: "exp_1", description: "Senior DE uplift", chapter: "3D", location: "DE", level: "Senior", costMultiplier: 1.2, billMultiplier: 1.15, shoringRatio: 0.4, additionalEffortRatio: 0.1, sortOrder: 0, multiplierSet: { name: "Default Multipliers", isDefault: true }, }, ]); }); it("lists and reads holiday calendars through the real holiday router paths", async () => { const db = { holidayCalendar: { findMany: vi.fn().mockResolvedValue([ { id: "cal_de", name: "Germany National", scopeType: "COUNTRY", stateCode: null, isActive: true, priority: 0, country: { id: "country_de", code: "DE", name: "Germany" }, metroCity: null, _count: { entries: 1 }, entries: [ { id: "entry_1", date: new Date("2026-01-01T00:00:00.000Z"), name: "New Year", isRecurringAnnual: true, source: "seed", }, ], }, ]), findUnique: vi.fn().mockResolvedValue(null), findFirst: vi.fn() .mockResolvedValueOnce({ id: "cal_de", name: "Germany National", scopeType: "COUNTRY", stateCode: null, isActive: true, priority: 0, country: { id: "country_de", code: "DE", name: "Germany" }, metroCity: null, entries: [ { id: "entry_1", date: new Date("2026-01-01T00:00:00.000Z"), name: "New Year", isRecurringAnnual: true, source: "seed", }, ], }), }, }; const ctx = createToolContext(db, { userRole: SystemRole.ADMIN }); const listResult = await executeTool( "list_holiday_calendars", JSON.stringify({ countryCode: "de", scopeType: "COUNTRY" }), ctx, ); const getResult = await executeTool( "get_holiday_calendar", JSON.stringify({ identifier: "Germany National" }), ctx, ); expect(db.holidayCalendar.findMany).toHaveBeenCalledWith({ where: { isActive: true, country: { code: { equals: "DE", mode: "insensitive" } }, scopeType: "COUNTRY", }, include: { country: { select: { id: true, code: true, name: true } }, metroCity: { select: { id: true, name: true } }, _count: { select: { entries: true } }, entries: { orderBy: [{ date: "asc" }, { name: "asc" }] }, }, orderBy: [ { country: { name: "asc" } }, { scopeType: "asc" }, { priority: "desc" }, { name: "asc" }, ], }); expect(db.holidayCalendar.findUnique).toHaveBeenCalledWith({ where: { id: "Germany National" }, include: { country: { select: { id: true, code: true, name: true } }, metroCity: { select: { id: true, name: true } }, entries: { orderBy: [{ date: "asc" }, { name: "asc" }] }, }, }); expect(db.holidayCalendar.findFirst).toHaveBeenCalledWith({ where: { name: { equals: "Germany National", mode: "insensitive" } }, include: { country: { select: { id: true, code: true, name: true } }, metroCity: { select: { id: true, name: true } }, entries: { orderBy: [{ date: "asc" }, { name: "asc" }] }, }, }); expect(JSON.parse(listResult.content)).toEqual({ count: 1, calendars: [ expect.objectContaining({ id: "cal_de", name: "Germany National", entryCount: 1, }), ], }); expect(JSON.parse(getResult.content)).toEqual( expect.objectContaining({ id: "cal_de", name: "Germany National", }), ); }); it("previews resolved holiday calendars through the real holiday router path", async () => { const holidayCallerDb = { country: { findUnique: vi.fn().mockResolvedValue({ id: "country_de", code: "DE", name: "Germany" }), }, metroCity: { findUnique: vi.fn().mockResolvedValue({ id: "city_muc", name: "Munich", countryId: "country_de" }), }, holidayCalendar: { findMany: vi.fn().mockResolvedValue([]), }, }; const ctx = createToolContext(holidayCallerDb, { userRole: SystemRole.USER }); const result = await executeTool( "preview_resolved_holiday_calendar", JSON.stringify({ countryId: "country_de", stateCode: "BY", metroCityId: "city_muc", year: 2026, }), ctx, ); expect(holidayCallerDb.country.findUnique).toHaveBeenCalledWith({ where: { id: "country_de" }, select: { id: true, code: true, name: true }, }); expect(holidayCallerDb.metroCity.findUnique).toHaveBeenCalledWith({ where: { id: "city_muc" }, select: { id: true, name: true, countryId: true }, }); expect(JSON.parse(result.content)).toEqual(expect.objectContaining({ count: expect.any(Number), locationContext: { countryId: "country_de", countryCode: "DE", stateCode: "BY", metroCityId: "city_muc", metroCity: "Munich", year: 2026, }, summary: expect.objectContaining({ byCalendar: expect.any(Array), byScope: expect.any(Array), bySourceType: expect.arrayContaining([ expect.objectContaining({ sourceType: "BUILTIN" }), ]), }), holidays: expect.arrayContaining([ expect.objectContaining({ date: "2026-01-01", name: "Neujahr", scope: "COUNTRY", sourceType: "BUILTIN", }), expect.objectContaining({ date: "2026-01-06", name: "Heilige Drei Könige", scope: "STATE", sourceType: "BUILTIN", }), ]), })); }); it("returns a stable error when a holiday calendar cannot be found by identifier", async () => { const db = { holidayCalendar: { findUnique: vi.fn().mockResolvedValue(null), findFirst: vi.fn().mockResolvedValue(null), }, }; const ctx = createToolContext(db, { userRole: SystemRole.ADMIN }); const result = await executeTool( "get_holiday_calendar", JSON.stringify({ identifier: "Missing Calendar" }), ctx, ); expect(JSON.parse(result.content)).toEqual(expect.objectContaining({ error: "Holiday calendar not found: Missing Calendar", })); }); it("routes role, client, and org unit reads through their backing routers", async () => { const db = { role: { findMany: vi.fn().mockResolvedValue([ { id: "role_anim", name: "Animation", color: "#112233", _count: { resourceRoles: 2 }, }, ]), }, client: { findMany: vi.fn().mockResolvedValue([ { id: "client_1", name: "Acme Mobility", code: "ACM", parentId: null, isActive: true, sortOrder: 1, tags: [], createdAt: new Date("2026-01-01T00:00:00.000Z"), updatedAt: new Date("2026-01-02T00:00:00.000Z"), _count: { children: 0, projects: 4 }, }, ]), }, orgUnit: { findMany: vi.fn().mockResolvedValue([ { id: "ou_delivery", name: "Delivery", shortName: "DEL", level: 5, parentId: null, sortOrder: 1, isActive: true, createdAt: new Date("2026-01-01T00:00:00.000Z"), updatedAt: new Date("2026-01-02T00:00:00.000Z"), }, ]), findUnique: vi.fn().mockResolvedValue({ id: "ou_delivery", name: "Delivery", shortName: "DEL", level: 5, parentId: null, sortOrder: 1, isActive: true, createdAt: new Date("2026-01-01T00:00:00.000Z"), updatedAt: new Date("2026-01-02T00:00:00.000Z"), parent: null, children: [], _count: { resources: 7 }, }), }, }; const ctx = createToolContext(db, { userRole: SystemRole.CONTROLLER }); const rolesResult = await executeTool("list_roles", "{}", ctx); const clientsResult = await executeTool( "list_clients", JSON.stringify({ query: "ACM", limit: 5 }), ctx, ); const orgUnitsResult = await executeTool( "list_org_units", JSON.stringify({ level: 5 }), ctx, ); expect(db.role.findMany).toHaveBeenCalledWith({ where: {}, include: { _count: { select: { resourceRoles: true }, }, }, orderBy: { name: "asc" }, }); expect(db.client.findMany).toHaveBeenCalledWith({ where: { isActive: true, OR: [ { name: { contains: "ACM", mode: "insensitive" } }, { code: { contains: "ACM", mode: "insensitive" } }, ], }, include: { _count: { select: { children: true, projects: true } } }, orderBy: [{ sortOrder: "asc" }, { name: "asc" }], }); expect(db.orgUnit.findMany).toHaveBeenCalledWith({ where: { level: 5, isActive: true, }, orderBy: [{ level: "asc" }, { sortOrder: "asc" }, { name: "asc" }], }); expect(db.orgUnit.findUnique).toHaveBeenCalledWith({ where: { id: "ou_delivery" }, include: { parent: true, children: { orderBy: { sortOrder: "asc" } }, _count: { select: { resources: true } }, }, }); expect(JSON.parse(rolesResult.content)).toEqual([ { id: "role_anim", name: "Animation", color: "#112233", }, ]); expect(JSON.parse(clientsResult.content)).toEqual([ { id: "client_1", name: "Acme Mobility", code: "ACM", projectCount: 4, }, ]); expect(JSON.parse(orgUnitsResult.content)).toEqual([ { id: "ou_delivery", name: "Delivery", shortName: "DEL", level: 5, parent: null, resourceCount: 7, }, ]); }); it("routes blueprint and rate card reads through their backing routers", async () => { const db = { blueprint: { findMany: vi.fn().mockResolvedValue([ { id: "bp_project", name: "Project Default", _count: { projects: 3 }, }, ]), findUnique: vi.fn().mockResolvedValue({ id: "bp_project", name: "Project Default", fieldDefs: [{ key: "market", type: "text" }], rolePresets: [{ role: "Consulting", share: 0.5 }], }), }, rateCard: { findMany: vi.fn().mockResolvedValue([ { id: "rc_2026", name: "Standard 2026", effectiveFrom: new Date("2026-01-01T00:00:00.000Z"), effectiveTo: null, _count: { lines: 12 }, client: null, }, ]), }, }; const ctx = createToolContext(db, { userRole: SystemRole.ADMIN }); const blueprintsResult = await executeTool("list_blueprints", "{}", ctx); const blueprintResult = await executeTool( "get_blueprint", JSON.stringify({ identifier: "bp_project" }), ctx, ); const rateCardsResult = await executeTool( "list_rate_cards", JSON.stringify({ query: "Standard", limit: 10 }), ctx, ); expect(db.blueprint.findMany).toHaveBeenCalledWith({ select: { id: true, name: true, _count: { select: { projects: true } }, }, orderBy: { name: "asc" }, }); expect(db.blueprint.findUnique).toHaveBeenCalledWith({ where: { id: "bp_project" }, }); expect(db.rateCard.findMany).toHaveBeenCalledWith({ where: { isActive: true, name: { contains: "Standard", mode: "insensitive" }, }, include: { _count: { select: { lines: true } }, client: { select: { id: true, name: true, code: true } }, }, orderBy: [{ isActive: "desc" }, { effectiveFrom: "desc" }, { name: "asc" }], }); expect(JSON.parse(blueprintsResult.content)).toEqual([ { id: "bp_project", name: "Project Default", projectCount: 3, }, ]); expect(JSON.parse(blueprintResult.content)).toEqual( expect.objectContaining({ id: "bp_project", name: "Project Default", fieldDefs: [{ key: "market", type: "text" }], rolePresets: [{ role: "Consulting", share: 0.5 }], }), ); expect(JSON.parse(rateCardsResult.content)).toEqual([ { id: "rc_2026", name: "Standard 2026", effectiveFrom: "2026-01-01", effectiveTo: null, lineCount: 12, }, ]); }); it("returns a stable assistant error when a blueprint cannot be resolved for read access", async () => { const ctx = createToolContext( { blueprint: { findUnique: vi.fn().mockResolvedValue(null), findFirst: vi.fn().mockResolvedValue(null), }, }, { userRole: SystemRole.ADMIN }, ); const result = await executeTool( "get_blueprint", JSON.stringify({ identifier: "Missing Blueprint" }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "Blueprint not found: Missing Blueprint", }); }); it("routes project health reads through the dashboard router path", async () => { vi.mocked(getDashboardProjectHealth).mockResolvedValue([ { id: "project_1", projectName: "Apollo", shortCode: "APL", status: "ACTIVE", clientId: "client_1", clientName: "Acme Mobility", budgetHealth: 74, staffingHealth: 88, timelineHealth: 63, compositeScore: 75, }, ]); const ctx = createToolContext({}, { userRole: SystemRole.CONTROLLER }); const result = await executeTool("get_project_health", "{}", ctx); expect(getDashboardProjectHealth).toHaveBeenCalledTimes(1); expect(JSON.parse(result.content)).toEqual({ projects: [ { projectId: "project_1", projectName: "Apollo", shortCode: "APL", status: "ACTIVE", overall: 75, budget: 74, staffing: 88, timeline: 63, rating: "at_risk", }, ], summary: { healthy: 0, atRisk: 1, critical: 0, }, }); }); it("routes dashboard detail reads through dashboard router callers", async () => { vi.mocked(getDashboardOverview).mockResolvedValue({ totalResources: 12, activeResources: 10, inactiveResources: 2, totalProjects: 4, activeProjects: 3, inactiveProjects: 1, totalAllocations: 8, activeAllocations: 7, cancelledAllocations: 1, approvedVacations: 2, totalEstimates: 5, budgetSummary: { totalBudgetCents: 500_000, totalCostCents: 240_000, avgUtilizationPercent: 48, }, budgetBasis: { remainingBudgetCents: 260_000, budgetedProjects: 3, unbudgetedProjects: 1, trackedAssignmentCount: 8, windowStart: new Date("2026-01-01T00:00:00.000Z"), windowEnd: new Date("2026-06-30T00:00:00.000Z"), }, recentActivity: [], projectsByStatus: [], chapterUtilization: [ { chapter: "Delivery", resourceCount: 4, avgChargeabilityTarget: 78, }, ], }); vi.mocked(getDashboardPeakTimes).mockResolvedValue([ { period: "2026-03", groups: [], totalHours: 320.4, capacityHours: 400.2, utilizationPct: 80, derivation: { periodStart: "2026-03-01", periodEnd: "2026-03-31", resourceCount: 4, groupCount: 1, bookedHours: 320.4, capacityHours: 400.2, remainingCapacityHours: 79.8, overbookedHours: 0, utilizationPct: 80, }, }, ]); vi.mocked(getDashboardTopValueResources).mockResolvedValue([ { id: "res_1", eid: "pparker", displayName: "Peter Parker", chapter: "Delivery", valueScore: 91, lcrCents: 9_500, }, ]); vi.mocked(getDashboardDemand).mockResolvedValue([ { id: "project_1", name: "Gelddruckmaschine", shortCode: "GDM", allocatedHours: 120, requiredFTEs: 4, resourceCount: 2, derivation: { periodStart: "2026-01-01", periodEnd: "2026-06-30", periodWorkingHoursBase: 1040, requiredHours: 2080, requiredFTEs: 4, fillPct: 50, demandSource: "DEMAND_REQUIREMENTS", calendarLocations: [ { countryCode: "DE", federalState: "BY", metroCityName: "Augsburg", resourceCount: 2, allocatedHours: 120, }, ], }, }, ]); const ctx = createToolContext( { systemSettings: { findUnique: vi.fn().mockResolvedValue(null), }, }, { userRole: SystemRole.CONTROLLER }, ); const result = await executeTool("get_dashboard_detail", JSON.stringify({ section: "all" }), ctx); expect(getDashboardOverview).toHaveBeenCalledTimes(1); expect(getDashboardPeakTimes).toHaveBeenCalledWith( ctx.db, expect.objectContaining({ startDate: new Date("2026-01-01T00:00:00.000Z"), endDate: new Date("2026-06-30T00:00:00.000Z"), granularity: "month", groupBy: "project", }), ); expect(getDashboardTopValueResources).toHaveBeenCalledWith( ctx.db, expect.objectContaining({ limit: 10, userRole: SystemRole.CONTROLLER, }), ); expect(getDashboardDemand).toHaveBeenCalledWith( ctx.db, expect.objectContaining({ startDate: new Date("2026-01-01T00:00:00.000Z"), endDate: new Date("2026-06-30T00:00:00.000Z"), groupBy: "project", }), ); expect(JSON.parse(result.content)).toEqual({ peakTimes: [ { month: "2026-03", totalHours: 320.4, totalHoursPerDay: 320.4, capacityHours: 400.2, utilizationPct: 80, }, ], topResources: [ { name: "Peter Parker", eid: "pparker", chapter: "Delivery", lcr: "95,00 EUR", valueScore: 91, }, ], demandPipeline: [ { project: "Gelddruckmaschine (GDM)", needed: 2, requiredFTEs: 4, allocatedResources: 2, allocatedHours: 120, calendarLocations: [ { countryCode: "DE", federalState: "BY", metroCityName: "Augsburg", resourceCount: 2, allocatedHours: 120, }, ], }, ], chargeabilityByChapter: [ { chapter: "Delivery", headcount: 4, avgTarget: "78%", }, ], }); }); it("routes skill gap reads through the dashboard router path", async () => { vi.mocked(getDashboardSkillGapSummary).mockResolvedValue({ roleGaps: [ { role: "Pipeline TD", needed: 4, filled: 1, gap: 3, fillRate: 25 }, ], totalOpenPositions: 3, skillSupplyTop10: [ { skill: "houdini", resourceCount: 5 }, ], resourcesByRole: [ { role: "Pipeline TD", count: 2 }, ], }); const ctx = createToolContext({}, { userRole: SystemRole.CONTROLLER }); const result = await executeTool("get_skill_gaps", "{}", ctx); expect(getDashboardSkillGapSummary).toHaveBeenCalledTimes(1); expect(JSON.parse(result.content)).toEqual({ roleGaps: [ { role: "Pipeline TD", needed: 4, filled: 1, gap: 3, fillRate: 25 }, ], totalOpenPositions: 3, skillSupplyTop10: [ { skill: "houdini", resourceCount: 5 }, ], resourcesByRole: [ { role: "Pipeline TD", count: 2 }, ], }); }); it("routes insights summary reads through the insights router path", async () => { const db = { project: { findMany: vi.fn().mockResolvedValue([ { budgetCents: 100_000, startDate: new Date("2026-03-01T00:00:00.000Z"), endDate: new Date("2026-03-31T00:00:00.000Z"), demandRequirements: [ { headcount: 3, startDate: new Date("2026-03-20T00:00:00.000Z"), endDate: new Date("2026-04-05T00:00:00.000Z"), _count: { assignments: 1 }, }, ], assignments: [ { resourceId: "res_1", startDate: new Date("2026-03-01T00:00:00.000Z"), endDate: new Date("2026-03-31T00:00:00.000Z"), hoursPerDay: 8, dailyCostCents: 10_000, status: "ACTIVE", }, ], }, ]), }, resource: { findMany: vi.fn().mockResolvedValue([ { id: "res_1", availability: { monday: 8, tuesday: 8, wednesday: 8, thursday: 8, friday: 8 }, }, ]), }, assignment: { findMany: vi.fn().mockResolvedValue([ { resourceId: "res_1", hoursPerDay: 2, }, ]), }, }; const ctx = createToolContext(db, { userRole: SystemRole.CONTROLLER }); const result = await executeTool("get_insights_summary", "{}", ctx); expect(db.project.findMany).toHaveBeenCalledWith({ where: { status: { in: ["ACTIVE", "DRAFT"] } }, include: { demandRequirements: { select: { headcount: true, startDate: true, endDate: true, _count: { select: { assignments: true } }, }, }, assignments: { select: { resourceId: true, startDate: true, endDate: true, hoursPerDay: true, dailyCostCents: true, status: true, }, }, }, }); expect(db.resource.findMany).toHaveBeenCalledWith({ where: { isActive: true }, select: { id: true, displayName: true, availability: true }, }); expect(db.assignment.findMany).toHaveBeenCalledWith({ where: { status: { in: ["ACTIVE", "CONFIRMED"] }, startDate: { lte: expect.any(Date) }, endDate: { gte: expect.any(Date) }, }, select: { resourceId: true, hoursPerDay: true }, }); expect(JSON.parse(result.content)).toEqual({ total: 3, criticalCount: 2, budget: 1, staffing: 1, timeline: 0, utilization: 1, }); }); it("returns anomaly details and count from the insights-backed detector", async () => { vi.useFakeTimers(); vi.setSystemTime(new Date("2026-03-29T12:00:00.000Z")); try { const ctx = createToolContext({ project: { findMany: vi.fn().mockResolvedValue([ { id: "project_1", name: "Apollo", budgetCents: 100_000, startDate: new Date("2026-03-01T00:00:00.000Z"), endDate: new Date("2026-03-31T00:00:00.000Z"), demandRequirements: [ { id: "demand_1", headcount: 3, startDate: new Date("2026-03-20T00:00:00.000Z"), endDate: new Date("2026-04-05T00:00:00.000Z"), status: "CONFIRMED", _count: { assignments: 1 }, }, ], assignments: [ { id: "assignment_1", resourceId: "res_1", startDate: new Date("2026-03-01T00:00:00.000Z"), endDate: new Date("2026-04-05T00:00:00.000Z"), hoursPerDay: 12, dailyCostCents: 10_000, status: "ACTIVE", }, ], }, ]), }, resource: { findMany: vi.fn().mockResolvedValue([ { id: "res_1", displayName: "Peter Parker", availability: { monday: 8, tuesday: 8, wednesday: 8, thursday: 8, friday: 8, }, }, ]), }, assignment: { findMany: vi.fn().mockResolvedValue([ { resourceId: "res_1", hoursPerDay: 12, }, ]), }, }); const result = await executeTool("detect_anomalies", "{}", ctx); const parsed = JSON.parse(result.content) as { count: number; anomalies: Array<{ type: string; severity: string; entityName: string }>; }; expect(parsed.count).toBe(4); expect(parsed.anomalies).toEqual([ expect.objectContaining({ type: "budget", severity: "critical", entityName: "Apollo", }), expect.objectContaining({ type: "staffing", severity: "critical", entityName: "Apollo", }), expect.objectContaining({ type: "utilization", severity: "critical", entityName: "Peter Parker", }), expect.objectContaining({ type: "timeline", severity: "warning", entityName: "Apollo", }), ]); } finally { vi.useRealTimers(); } }); it("routes project search and detail reads through the project router path", async () => { const db = { project: { findMany: vi.fn().mockResolvedValue([ { id: "project_1", shortCode: "GDM", name: "Gelddruckmaschine", status: "ACTIVE", budgetCents: 500_000, winProbability: 100, startDate: new Date("2026-01-01T00:00:00.000Z"), endDate: new Date("2026-03-31T00:00:00.000Z"), client: { name: "Acme Mobility" }, _count: { assignments: 3, estimates: 1 }, }, ]), findUnique: vi.fn() .mockResolvedValueOnce(null) .mockResolvedValueOnce({ id: "project_1", shortCode: "GDM", name: "Gelddruckmaschine", status: "ACTIVE", startDate: new Date("2026-01-01T00:00:00.000Z"), endDate: new Date("2026-03-31T00:00:00.000Z"), }) .mockResolvedValueOnce({ id: "project_1", shortCode: "GDM", name: "Gelddruckmaschine", status: "ACTIVE", orderType: "CHARGEABLE", allocationType: "INT", budgetCents: 500_000, winProbability: 100, startDate: new Date("2026-01-01T00:00:00.000Z"), endDate: new Date("2026-03-31T00:00:00.000Z"), responsiblePerson: "Bruce Banner", client: { name: "Acme Mobility" }, utilizationCategory: { code: "BILL", name: "Billable" }, _count: { assignments: 3, estimates: 1 }, }), }, assignment: { findMany: vi.fn().mockResolvedValue([ { resource: { displayName: "Bruce Banner", eid: "EMP-001" }, role: "Lead", status: "ACTIVE", hoursPerDay: 8, startDate: new Date("2026-02-01T00:00:00.000Z"), endDate: new Date("2026-02-28T00:00:00.000Z"), }, ]), }, }; const ctx = createToolContext(db, { userRole: SystemRole.CONTROLLER }); const searchResult = await executeTool( "search_projects", JSON.stringify({ query: "Gelddruckmaschine", limit: 10 }), ctx, ); const detailResult = await executeTool( "get_project", JSON.stringify({ identifier: "GDM" }), ctx, ); expect(db.project.findMany).toHaveBeenCalledWith({ where: { OR: [ { name: { contains: "Gelddruckmaschine", mode: "insensitive" } }, { shortCode: { contains: "Gelddruckmaschine", mode: "insensitive" } }, ], }, select: { id: true, shortCode: true, name: true, status: true, budgetCents: true, winProbability: true, startDate: true, endDate: true, client: { select: { name: true } }, _count: { select: { assignments: true, estimates: true } }, }, take: 10, orderBy: { name: "asc" }, }); expect(db.project.findUnique).toHaveBeenNthCalledWith(1, { where: { id: "GDM" }, select: { id: true, shortCode: true, name: true, status: true, startDate: true, endDate: true, }, }); expect(db.project.findUnique).toHaveBeenNthCalledWith(2, { where: { shortCode: "GDM" }, select: { id: true, shortCode: true, name: true, status: true, startDate: true, endDate: true, }, }); expect(db.project.findUnique).toHaveBeenNthCalledWith(3, { where: { id: "project_1" }, select: { id: true, shortCode: true, name: true, status: true, orderType: true, allocationType: true, budgetCents: true, winProbability: true, startDate: true, endDate: true, responsiblePerson: true, client: { select: { name: true } }, utilizationCategory: { select: { code: true, name: true } }, _count: { select: { assignments: true, estimates: true } }, }, }); expect(db.assignment.findMany).toHaveBeenCalledWith({ where: { projectId: "project_1", status: { not: "CANCELLED" }, }, select: { resource: { select: { displayName: true, eid: true } }, role: true, status: true, hoursPerDay: true, startDate: true, endDate: true, }, take: 10, orderBy: { startDate: "desc" }, }); expect(JSON.parse(searchResult.content)).toEqual([ { id: "project_1", code: "GDM", name: "Gelddruckmaschine", status: "ACTIVE", budget: "5.000,00 EUR", winProbability: "100%", start: "2026-01-01", end: "2026-03-31", client: "Acme Mobility", assignmentCount: 3, estimateCount: 1, }, ]); expect(JSON.parse(detailResult.content)).toEqual({ id: "project_1", code: "GDM", name: "Gelddruckmaschine", status: "ACTIVE", orderType: "CHARGEABLE", allocationType: "INT", budget: "5.000,00 EUR", budgetCents: 500000, winProbability: "100%", start: "2026-01-01", end: "2026-03-31", responsible: "Bruce Banner", client: "Acme Mobility", category: "Billable", assignmentCount: 3, estimateCount: 1, topAllocations: [ { resource: "Bruce Banner", eid: "EMP-001", role: "Lead", status: "ACTIVE", hoursPerDay: 8, start: "2026-02-01", end: "2026-02-28", }, ], }); }); it("returns a generic assistant error for internal project lookup failures", async () => { const ctx = createToolContext( { project: { findUnique: vi.fn().mockRejectedValue( new TRPCError({ code: "INTERNAL_SERVER_ERROR", message: "database connection pool exhausted", }), ), }, }, { userRole: SystemRole.CONTROLLER }, ); const result = await executeTool( "get_project", JSON.stringify({ identifier: "GDM" }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "The tool could not complete due to an internal error.", }); }); it("routes resource search and detail reads through the resource router path", async () => { const db = { resource: { findMany: vi.fn().mockResolvedValue([ { id: "res_1", eid: "EMP-001", displayName: "Bruce Banner", chapter: "Delivery", fte: 1, lcrCents: 8_500, chargeabilityTarget: 80, isActive: true, areaRole: { name: "Pipeline TD" }, country: { code: "DE", name: "Germany", dailyWorkingHours: 8 }, metroCity: { name: "Munich" }, orgUnit: { name: "Operations", level: 5 }, email: "bruce@example.com", ucrCents: 10_500, availability: { monday: 8 }, skills: [{ name: "Houdini", level: 5 }], postalCode: "80331", federalState: "BY", managementLevelGroup: { name: "Senior", targetPercentage: 75 }, _count: { assignments: 4, vacations: 2 }, }, ]), findUnique: vi.fn() .mockResolvedValueOnce(null) .mockResolvedValueOnce({ id: "res_1", eid: "EMP-001", displayName: "Bruce Banner", chapter: "Delivery", isActive: true, }) .mockResolvedValueOnce({ id: "res_1", eid: "EMP-001", displayName: "Bruce Banner", email: "bruce@example.com", chapter: "Delivery", fte: 1, lcrCents: 8_500, ucrCents: 10_500, chargeabilityTarget: 80, isActive: true, availability: { monday: 8 }, skills: [{ name: "Houdini", level: 5 }], postalCode: "80331", federalState: "BY", areaRole: { name: "Pipeline TD", color: "#112233" }, country: { code: "DE", name: "Germany", dailyWorkingHours: 8 }, metroCity: { name: "Munich" }, managementLevelGroup: { name: "Senior", targetPercentage: 75 }, orgUnit: { name: "Operations", level: 5 }, _count: { assignments: 4, vacations: 2 }, }), findFirst: vi.fn(), }, }; const ctx = createToolContext(db, { userRole: SystemRole.CONTROLLER }); const searchResult = await executeTool( "search_resources", JSON.stringify({ query: "Bruce", country: "DE", roleName: "Pipeline", limit: 5 }), ctx, ); const detailResult = await executeTool( "get_resource", JSON.stringify({ identifier: "EMP-001" }), ctx, ); expect(db.resource.findMany).toHaveBeenCalledWith({ where: { isActive: true, OR: [ { displayName: { contains: "Bruce", mode: "insensitive" } }, { eid: { contains: "Bruce", mode: "insensitive" } }, { chapter: { contains: "Bruce", mode: "insensitive" } }, ], country: { OR: [ { code: { equals: "DE", mode: "insensitive" } }, { name: { contains: "DE", mode: "insensitive" } }, ], }, areaRole: { name: { contains: "Pipeline", mode: "insensitive" } }, }, select: { id: true, eid: true, displayName: true, chapter: true, fte: true, lcrCents: true, chargeabilityTarget: true, isActive: true, areaRole: { select: { name: true } }, country: { select: { code: true, name: true } }, metroCity: { select: { name: true } }, orgUnit: { select: { name: true } }, }, take: 5, orderBy: { displayName: "asc" }, }); expect(db.resource.findUnique).toHaveBeenNthCalledWith(1, { where: { id: "EMP-001" }, select: { id: true, eid: true, displayName: true, chapter: true, isActive: true, }, }); expect(db.resource.findUnique).toHaveBeenNthCalledWith(2, { where: { eid: "EMP-001" }, select: { id: true, eid: true, displayName: true, chapter: true, isActive: true, }, }); expect(db.resource.findUnique).toHaveBeenNthCalledWith(3, { where: { id: "res_1" }, select: { id: true, eid: true, displayName: true, email: true, chapter: true, fte: true, lcrCents: true, ucrCents: true, chargeabilityTarget: true, isActive: true, availability: true, skills: true, postalCode: true, federalState: true, areaRole: { select: { name: true, color: true } }, country: { select: { code: true, name: true, dailyWorkingHours: true } }, metroCity: { select: { name: true } }, managementLevelGroup: { select: { name: true, targetPercentage: true } }, orgUnit: { select: { name: true, level: true } }, _count: { select: { assignments: true, vacations: true } }, }, }); expect(JSON.parse(searchResult.content)).toEqual([ { id: "res_1", eid: "EMP-001", name: "Bruce Banner", chapter: "Delivery", role: "Pipeline TD", country: "Germany", countryCode: "DE", metroCity: "Munich", orgUnit: "Operations", fte: 1, lcr: "85,00 EUR", chargeabilityTarget: "80%", active: true, }, ]); expect(JSON.parse(detailResult.content)).toEqual({ id: "res_1", eid: "EMP-001", name: "Bruce Banner", email: "bruce@example.com", chapter: "Delivery", role: "Pipeline TD", country: "Germany", countryCode: "DE", countryHours: 8, metroCity: "Munich", fte: 1, lcr: "85,00 EUR", ucr: "105,00 EUR", chargeabilityTarget: "80%", managementLevel: "Senior", orgUnit: "Operations", postalCode: "80331", federalState: "BY", active: true, totalAssignments: 4, totalVacations: 2, skillCount: 1, topSkills: ["Houdini (5)"], }); }); 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, }); }); it("routes report reads through the report router path", async () => { const db = { resource: { findMany: vi.fn().mockResolvedValue([ { id: "res_1", displayName: "Bruce Banner", chapter: "Delivery", country: { name: "Germany" }, }, ]), count: vi.fn().mockResolvedValue(1), }, }; const ctx = createToolContext(db, { userRole: SystemRole.CONTROLLER }); const result = await executeTool( "run_report", JSON.stringify({ entity: "resource", columns: ["displayName", "chapter", "country.name"], filters: [{ field: "displayName", op: "contains", value: "Bruce" }], limit: 25, }), ctx, ); expect(db.resource.findMany).toHaveBeenCalledWith({ select: { id: true, displayName: true, chapter: true, country: { select: { name: true } }, }, where: { displayName: { contains: "Bruce", mode: "insensitive" }, }, take: 25, skip: 0, }); expect(db.resource.count).toHaveBeenCalledWith({ where: { displayName: { contains: "Bruce", mode: "insensitive" }, }, }); expect(JSON.parse(result.content)).toEqual({ rows: [ { id: "res_1", displayName: "Bruce Banner", chapter: "Delivery", "country.name": "Germany", }, ], rowCount: 1, columns: ["id", "displayName", "chapter", "country.name"], }); }); it("routes budget status reads through the timeline and project router paths", async () => { vi.mocked(listAssignmentBookings).mockResolvedValue([ { projectId: "project_1", status: "CONFIRMED", dailyCostCents: 10_000, hoursPerDay: 8, startDate: new Date("2026-04-01T00:00:00.000Z"), endDate: new Date("2026-04-02T00:00:00.000Z"), project: { id: "project_1", status: "ACTIVE", }, }, { projectId: "project_1", status: "PROPOSED", dailyCostCents: 5_000, hoursPerDay: 8, startDate: new Date("2026-04-03T00:00:00.000Z"), endDate: new Date("2026-04-03T00:00:00.000Z"), project: { id: "project_1", status: "ACTIVE", }, }, ] as Awaited>); const db = { project: { findUnique: vi .fn() .mockResolvedValueOnce({ id: "project_1", name: "Gelddruckmaschine", shortCode: "GDM", status: "ACTIVE", responsiblePerson: "Larissa", }) .mockResolvedValueOnce({ id: "project_1", name: "Gelddruckmaschine", shortCode: "GDM", budgetCents: 100_000, winProbability: 80, startDate: new Date("2026-04-01T00:00:00.000Z"), endDate: new Date("2026-04-30T00:00:00.000Z"), }), findFirst: vi.fn(), }, assignment: { findMany: vi.fn().mockResolvedValue([]), }, }; const ctx = createToolContext(db, { userRole: SystemRole.CONTROLLER }); const result = await executeTool( "get_budget_status", JSON.stringify({ projectId: "project_1" }), ctx, ); expect(vi.mocked(listAssignmentBookings)).toHaveBeenCalledWith(db, { projectIds: ["project_1"], }); expect(JSON.parse(result.content)).toEqual({ project: "Gelddruckmaschine", code: "GDM", budget: "1.000,00 EUR", confirmed: "200,00 EUR", proposed: "50,00 EUR", allocated: "250,00 EUR", remaining: "750,00 EUR", utilization: "25.0%", winWeighted: "200,00 EUR", }); }); it("routes scenario simulation through the scenario router path", async () => { const db = { project: { findUnique: vi.fn().mockResolvedValue({ id: "project_1", name: "Gelddruckmaschine", budgetCents: 200_000, orderType: "CHARGEABLE", startDate: new Date("2026-04-01T00:00:00.000Z"), endDate: new Date("2026-04-30T00:00:00.000Z"), }), }, assignment: { findMany: vi.fn() .mockResolvedValueOnce([ { id: "assign_1", resourceId: "res_1", projectId: "project_1", hoursPerDay: 4, startDate: new Date("2026-04-01T00:00:00.000Z"), endDate: new Date("2026-04-03T00:00:00.000Z"), status: "ACTIVE", roleId: null, role: "Pipeline TD", resource: { id: "res_1", displayName: "Bruce Banner", eid: "EMP-001", lcrCents: 10_000, availability: { monday: 8, tuesday: 8, wednesday: 8, thursday: 8, friday: 8 }, chargeabilityTarget: 80, skills: [{ skill: "Houdini" }], countryId: null, federalState: null, metroCityId: null, country: null, metroCity: null, }, roleEntity: null, }, ]) .mockResolvedValueOnce([ { id: "assign_1", resourceId: "res_1", projectId: "project_1", hoursPerDay: 4, startDate: new Date("2026-04-01T00:00:00.000Z"), endDate: new Date("2026-04-03T00:00:00.000Z"), }, ]), }, resource: { findMany: vi.fn().mockResolvedValue([ { id: "res_1", displayName: "Bruce Banner", eid: "EMP-001", lcrCents: 10_000, availability: { monday: 8, tuesday: 8, wednesday: 8, thursday: 8, friday: 8 }, chargeabilityTarget: 80, skills: [{ skill: "Houdini" }], countryId: null, federalState: null, metroCityId: null, country: null, metroCity: null, }, ]), }, role: { findMany: vi.fn(), }, }; const ctx = createToolContext(db, { userRole: SystemRole.CONTROLLER }); const result = await executeTool( "simulate_scenario", JSON.stringify({ projectId: "project_1", changes: [ { assignmentId: "assign_1", resourceId: "res_1", startDate: "2026-04-01", endDate: "2026-04-03", hoursPerDay: 6, }, ], }), ctx, ); expect(db.assignment.findMany).toHaveBeenNthCalledWith(1, { where: { projectId: "project_1", status: { not: "CANCELLED" } }, include: { resource: { select: { id: true, displayName: true, eid: true, lcrCents: true, availability: true, chargeabilityTarget: true, skills: true, countryId: true, federalState: true, metroCityId: true, country: { select: { code: true } }, metroCity: { select: { name: true } }, }, }, }, }); expect(db.assignment.findMany).toHaveBeenNthCalledWith(2, { where: { resourceId: { in: ["res_1"] }, status: { not: "CANCELLED" }, }, select: { id: true, resourceId: true, projectId: true, hoursPerDay: true, startDate: true, endDate: true, }, }); expect(db.role.findMany).not.toHaveBeenCalled(); expect(JSON.parse(result.content)).toEqual({ baseline: { totalCostCents: 120000, totalHours: 12, headcount: 1, skillCount: 1, totalCost: "1.200,00 EUR", }, scenario: { totalCostCents: 180000, totalHours: 18, headcount: 1, skillCount: 1, totalCost: "1.800,00 EUR", }, delta: { costCents: 60000, hours: 6, headcount: 0, skillCoveragePct: 100, cost: "600,00 EUR", }, resourceImpacts: [ { resourceId: "res_1", resourceName: "Bruce Banner", chargeabilityTarget: 80, currentUtilization: 6.8, scenarioUtilization: 10.2, utilizationDelta: 3.4, isOverallocated: false, }, ], warnings: [], budgetCents: 200000, }); }); it("routes allocation listing through the allocation router path", async () => { const db = { systemSettings: { findUnique: vi.fn().mockResolvedValue({ anonymizationEnabled: false, anonymizationDomain: null, anonymizationSeed: null, anonymizationMode: null, anonymizationAliases: null, }), }, demandRequirement: { findMany: vi.fn().mockResolvedValue([ { id: "demand_1", projectId: "project_1", startDate: new Date("2026-04-01T00:00:00.000Z"), endDate: new Date("2026-04-30T00:00:00.000Z"), hoursPerDay: 8, percentage: 100, role: "TD", roleId: null, headcount: 1, status: "OPEN", metadata: null, createdAt: new Date("2026-03-20T00:00:00.000Z"), updatedAt: new Date("2026-03-20T00:00:00.000Z"), project: { id: "project_1", name: "Gelddruckmaschine", shortCode: "GDM", status: "ACTIVE", endDate: new Date("2026-04-30T00:00:00.000Z") }, roleEntity: null, assignments: [], }, ]), }, assignment: { findMany: vi.fn().mockResolvedValue([ { id: "assign_1", demandRequirementId: null, resourceId: "res_1", projectId: "project_1", startDate: new Date("2026-04-01T00:00:00.000Z"), endDate: new Date("2026-04-10T00:00:00.000Z"), hoursPerDay: 6, percentage: 75, role: "Pipeline TD", roleId: null, dailyCostCents: 12_000, status: "ACTIVE", metadata: null, createdAt: new Date("2026-03-21T00:00:00.000Z"), updatedAt: new Date("2026-03-21T00:00:00.000Z"), resource: { id: "res_1", displayName: "Bruce Banner", eid: "EMP-001", lcrCents: 8_500 }, project: { id: "project_1", name: "Gelddruckmaschine", shortCode: "GDM", status: "ACTIVE", endDate: new Date("2026-04-30T00:00:00.000Z") }, roleEntity: null, demandRequirement: null, }, ]), }, }; const ctx = createToolContext(db, { userRole: SystemRole.CONTROLLER }); const result = await executeTool( "list_allocations", JSON.stringify({ projectId: "project_1", resourceName: "Bruce", projectCode: "GDM", status: "ACTIVE", limit: 10, }), ctx, ); expect(db.demandRequirement.findMany).toHaveBeenCalledWith({ where: { projectId: "project_1", status: "ACTIVE", }, include: { project: { select: { id: true, name: true, shortCode: true, status: true, endDate: true, }, }, roleEntity: { select: { id: true, name: true, color: true }, }, assignments: { include: { resource: { select: { id: true, displayName: true, eid: true, lcrCents: true, chapter: true, }, }, project: { select: { id: true, name: true, shortCode: true, status: true, endDate: true, }, }, roleEntity: { select: { id: true, name: true, color: true }, }, }, }, }, orderBy: { startDate: "asc" }, }); expect(db.assignment.findMany).toHaveBeenCalledWith({ where: { projectId: "project_1", status: "ACTIVE", }, include: { resource: { select: { id: true, displayName: true, eid: true, lcrCents: true, chapter: true, }, }, project: { select: { id: true, name: true, shortCode: true, status: true, endDate: true, }, }, roleEntity: { select: { id: true, name: true, color: true }, }, demandRequirement: { select: { id: true, projectId: true, startDate: true, endDate: true, hoursPerDay: true, percentage: true, role: true, roleId: true, headcount: true, status: true, }, }, }, orderBy: { startDate: "asc" }, }); expect(JSON.parse(result.content)).toEqual([ { id: "assign_1", resource: "Bruce Banner", resourceEid: "EMP-001", project: "Gelddruckmaschine", projectCode: "GDM", role: "Pipeline TD", status: "ACTIVE", hoursPerDay: 6, dailyCost: "120,00 EUR", start: "2026-04-01", end: "2026-04-10", }, ]); }); it("routes upcoming vacation reads through the vacation router path and post-filters by chapter", async () => { vi.useFakeTimers(); vi.setSystemTime(new Date("2026-03-01T00:00:00.000Z")); try { const db = { vacation: { findMany: vi.fn().mockResolvedValue([ { id: "vac_1", resourceId: "res_1", status: "APPROVED", type: VacationType.ANNUAL, startDate: new Date("2026-03-05T00:00:00.000Z"), endDate: new Date("2026-03-06T00:00:00.000Z"), isHalfDay: false, halfDayPart: null, resource: { id: "res_1", displayName: "Bruce Banner", eid: "EMP-001", lcrCents: 8_000, chapter: "Delivery", }, requestedBy: null, approvedBy: null, }, { id: "vac_2", resourceId: "res_2", status: "APPROVED", type: VacationType.ANNUAL, startDate: new Date("2026-03-07T00:00:00.000Z"), endDate: new Date("2026-03-07T00:00:00.000Z"), isHalfDay: true, halfDayPart: "MORNING", resource: { id: "res_2", displayName: "Tony Stark", eid: "EMP-002", lcrCents: 9_000, chapter: "Finance", }, requestedBy: null, approvedBy: null, }, ]), }, }; const ctx = createToolContext(db, { userRole: SystemRole.MANAGER }); const result = await executeTool( "list_vacations_upcoming", JSON.stringify({ chapter: "Delivery", daysAhead: 14, limit: 10 }), ctx, ); expect(db.vacation.findMany).toHaveBeenCalledWith({ where: { status: "APPROVED", endDate: { gte: new Date("2026-03-01T00:00:00.000Z") }, startDate: { lte: new Date("2026-03-15T00:00:00.000Z") }, }, include: { resource: { select: { id: true, displayName: true, eid: true, lcrCents: true, chapter: true, }, }, requestedBy: { select: { id: true, name: true, email: true } }, approvedBy: { select: { id: true, name: true, email: true } }, }, orderBy: { startDate: "asc" }, take: 10, }); expect(JSON.parse(result.content)).toEqual([ { resource: "Bruce Banner", eid: "EMP-001", chapter: "Delivery", type: VacationType.ANNUAL, start: "2026-03-05", end: "2026-03-06", isHalfDay: false, halfDayPart: null, }, ]); } finally { vi.useRealTimers(); } }); it("routes skill searches through the resource router path", async () => { const db = { resource: { findMany: vi.fn().mockResolvedValue([ { id: "res_1", eid: "EMP-001", displayName: "Bruce Banner", chapter: "Delivery", skills: [{ skill: "Houdini FX", proficiency: 4, category: "FX" }], }, { id: "res_2", eid: "EMP-002", displayName: "Tony Stark", chapter: "Tech", skills: [{ skill: "Nuke", proficiency: 5, category: "Comp" }], }, ]), }, }; const ctx = createToolContext(db, { userRole: SystemRole.CONTROLLER }); const result = await executeTool( "search_by_skill", JSON.stringify({ skill: "houdini" }), ctx, ); expect(db.resource.findMany).toHaveBeenCalledWith({ where: { isActive: true }, select: { id: true, eid: true, displayName: true, chapter: true, skills: true, }, }); expect(JSON.parse(result.content)).toEqual([ { id: "res_1", eid: "EMP-001", name: "Bruce Banner", matchedSkill: "Houdini FX", level: 4, chapter: "Delivery", }, ]); }); it("routes availability checks through the allocation and vacation router paths", async () => { const db = { resource: { findUnique: vi.fn().mockResolvedValue({ id: "res_1", eid: "EMP-001", displayName: "Bruce Banner", fte: 1, availability: { monday: 8, tuesday: 8, wednesday: 8, thursday: 8, friday: 8 }, countryId: null, federalState: null, metroCityId: null, country: null, metroCity: null, }), findFirst: vi.fn(), }, assignment: { findMany: vi.fn().mockResolvedValue([ { id: "assign_1", startDate: new Date("2026-04-01T00:00:00.000Z"), endDate: new Date("2026-04-01T00:00:00.000Z"), hoursPerDay: 4, status: "CONFIRMED", project: { name: "Gelddruckmaschine", shortCode: "GDM" }, }, ]), }, vacation: { findMany: vi.fn().mockResolvedValue([ { id: "vac_1", resourceId: "res_1", status: "APPROVED", type: VacationType.ANNUAL, startDate: new Date("2026-04-02T00:00:00.000Z"), endDate: new Date("2026-04-02T00:00:00.000Z"), isHalfDay: true, halfDayPart: "AFTERNOON", resource: { id: "res_1", displayName: "Bruce Banner", eid: "EMP-001", lcrCents: 8_000, }, requestedBy: null, approvedBy: null, }, ]), }, }; const ctx = createToolContext(db, { userRole: SystemRole.CONTROLLER }); const result = await executeTool( "check_resource_availability", JSON.stringify({ resourceId: "res_1", startDate: "2026-04-01", endDate: "2026-04-02" }), ctx, ); expect(db.assignment.findMany).toHaveBeenCalledWith({ where: { resourceId: "res_1", status: { not: "CANCELLED" }, startDate: { lte: new Date("2026-04-02T00:00:00.000Z") }, endDate: { gte: new Date("2026-04-01T00:00:00.000Z") }, }, select: { id: true, startDate: true, endDate: true, hoursPerDay: true, status: true, project: { select: { name: true, shortCode: true } }, }, orderBy: { startDate: "asc" }, }); expect(JSON.parse(result.content)).toEqual( expect.objectContaining({ resource: "Bruce Banner", workingDays: 2, periodAvailableHours: 16, periodBookedHours: 4, periodRemainingHours: 12, availableHoursPerDay: 6, isFullyAvailable: false, existingAllocations: [ { project: "Gelddruckmaschine (GDM)", hoursPerDay: 4, status: "CONFIRMED", start: "2026-04-01", end: "2026-04-01", }, ], vacations: [ { type: VacationType.ANNUAL, start: "2026-04-02", end: "2026-04-02", isHalfDay: true, }, ], }), ); }); it("routes rate resolution through the rate-card router path", async () => { const db = { resource: { findUnique: vi.fn().mockResolvedValue({ id: "res_1", eid: "EMP-001", displayName: "Bruce Banner", chapter: "Delivery", lcrCents: 8_000, areaRole: { name: "Pipeline TD" }, managementLevel: { id: "ml_senior" }, }), findFirst: vi.fn(), }, role: { findFirst: vi.fn().mockResolvedValue({ id: "role_1" }), }, rateCard: { findMany: vi.fn().mockResolvedValue([ { id: "rc_2026", name: "Standard 2026", client: null, lines: [ { id: "line_1", chapter: "Delivery", seniority: "Senior", costRateCents: 12_000, billRateCents: 18_000, role: { id: "role_1", name: "Pipeline TD" }, managementLevelId: "ml_senior", }, ], }, ]), findUnique: vi.fn(), }, }; const ctx = createToolContext(db, { userRole: SystemRole.CONTROLLER, permissions: [PermissionKey.VIEW_COSTS], }); const result = await executeTool( "resolve_rate", JSON.stringify({ resourceId: "res_1" }), ctx, ); expect(db.resource.findUnique).toHaveBeenNthCalledWith(1, { where: { id: "res_1" }, select: { id: true, eid: true, displayName: true, chapter: true, isActive: true, }, }); expect(db.resource.findUnique).toHaveBeenNthCalledWith(2, { where: { id: "res_1" }, select: { id: true, displayName: true, chapter: true, areaRole: { select: { name: true } }, }, }); expect(JSON.parse(result.content)).toEqual({ rateCard: "Standard 2026", resource: "Bruce Banner", rate: "120,00 EUR", rateCents: 12000, matchedBy: "role: Pipeline TD", }); }); it("returns a stable assistant error when rate resolution receives an invalid date", async () => { const ctx = createToolContext( { rateCard: { findMany: vi.fn(), findUnique: vi.fn(), }, }, { userRole: SystemRole.CONTROLLER, permissions: [PermissionKey.VIEW_COSTS], }, ); const result = await executeTool( "resolve_rate", JSON.stringify({ roleName: "Pipeline TD", date: "2026-99-01" }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "Invalid date: 2026-99-01", }); }); it("creates, updates, and deletes holiday calendars and entries through the real holiday router path", async () => { const db = { country: { findUnique: vi.fn().mockResolvedValue({ id: "country_de", name: "Germany" }), }, metroCity: { findUnique: vi.fn().mockResolvedValue({ id: "city_muc", countryId: "country_de" }), }, holidayCalendar: { findFirst: vi.fn() .mockResolvedValueOnce(null) .mockResolvedValueOnce(null), findUnique: vi.fn() .mockResolvedValueOnce({ id: "cal_de", name: "Germany National", scopeType: "COUNTRY", countryId: "country_de", stateCode: null, metroCityId: null, }) .mockResolvedValueOnce({ id: "cal_de", name: "Germany National", }) .mockResolvedValueOnce({ id: "cal_de", name: "Germany National Updated", entries: [], }), create: vi.fn().mockResolvedValue({ id: "cal_de", name: "Germany National", scopeType: "COUNTRY", stateCode: null, isActive: true, priority: 0, country: { id: "country_de", code: "DE", name: "Germany" }, metroCity: null, entries: [], }), update: vi.fn().mockResolvedValue({ id: "cal_de", name: "Germany National Updated", scopeType: "COUNTRY", stateCode: null, isActive: false, priority: 1, country: { id: "country_de", code: "DE", name: "Germany" }, metroCity: null, entries: [], }), delete: vi.fn().mockResolvedValue({ id: "cal_de" }), }, holidayCalendarEntry: { findFirst: vi.fn() .mockResolvedValueOnce(null) .mockResolvedValueOnce(null), findUnique: vi.fn() .mockResolvedValueOnce({ id: "entry_1", name: "New Year", date: new Date("2026-01-01T00:00:00.000Z"), holidayCalendarId: "cal_de", }) .mockResolvedValueOnce({ id: "entry_1", name: "New Year Observed", }), create: vi.fn().mockResolvedValue({ id: "entry_1", date: new Date("2026-01-01T00:00:00.000Z"), name: "New Year", isRecurringAnnual: true, source: "seed", }), update: vi.fn().mockResolvedValue({ id: "entry_1", date: new Date("2026-01-02T00:00:00.000Z"), name: "New Year Observed", isRecurringAnnual: false, source: null, }), delete: vi.fn().mockResolvedValue({ id: "entry_1" }), }, auditLog: { create: vi.fn().mockResolvedValue({ id: "audit_1" }), }, }; const ctx = createToolContext(db, { userRole: SystemRole.ADMIN }); const createCalendarResult = await executeTool( "create_holiday_calendar", JSON.stringify({ name: "Germany National", scopeType: "COUNTRY", countryId: "country_de" }), ctx, ); const updateCalendarResult = await executeTool( "update_holiday_calendar", JSON.stringify({ id: "cal_de", data: { name: "Germany National Updated", isActive: false, priority: 1 } }), ctx, ); const createEntryResult = await executeTool( "create_holiday_calendar_entry", JSON.stringify({ holidayCalendarId: "cal_de", date: "2026-01-01", name: "New Year", isRecurringAnnual: true, source: "seed", }), ctx, ); const updateEntryResult = await executeTool( "update_holiday_calendar_entry", JSON.stringify({ id: "entry_1", data: { date: "2026-01-02", name: "New Year Observed", isRecurringAnnual: false, source: null }, }), ctx, ); const deleteEntryResult = await executeTool( "delete_holiday_calendar_entry", JSON.stringify({ id: "entry_1" }), ctx, ); const deleteCalendarResult = await executeTool( "delete_holiday_calendar", JSON.stringify({ id: "cal_de" }), ctx, ); expect(db.holidayCalendar.create).toHaveBeenCalledWith({ data: { name: "Germany National", scopeType: "COUNTRY", countryId: "country_de", isActive: true, priority: 0, }, include: { country: { select: { id: true, code: true, name: true } }, metroCity: { select: { id: true, name: true } }, entries: true, }, }); expect(db.holidayCalendar.update).toHaveBeenCalledWith({ where: { id: "cal_de" }, data: { name: "Germany National Updated", isActive: false, priority: 1, }, include: { country: { select: { id: true, code: true, name: true } }, metroCity: { select: { id: true, name: true } }, entries: { orderBy: [{ date: "asc" }, { name: "asc" }] }, }, }); expect(db.holidayCalendarEntry.create).toHaveBeenCalledWith({ data: { holidayCalendarId: "cal_de", date: new Date("2026-01-01T00:00:00.000Z"), name: "New Year", isRecurringAnnual: true, source: "seed", }, }); expect(db.holidayCalendarEntry.update).toHaveBeenCalledWith({ where: { id: "entry_1" }, data: { date: new Date("2026-01-02T00:00:00.000Z"), name: "New Year Observed", isRecurringAnnual: false, source: null, }, }); expect(db.holidayCalendarEntry.delete).toHaveBeenCalledWith({ where: { id: "entry_1" } }); expect(db.holidayCalendar.delete).toHaveBeenCalledWith({ where: { id: "cal_de" } }); expect(JSON.parse(createCalendarResult.content)).toEqual( expect.objectContaining({ success: true, message: "Created holiday calendar: Germany National" }), ); expect(JSON.parse(updateCalendarResult.content)).toEqual( expect.objectContaining({ success: true, message: "Updated holiday calendar: Germany National Updated" }), ); expect(JSON.parse(createEntryResult.content)).toEqual( expect.objectContaining({ success: true, message: "Created holiday entry: New Year" }), ); expect(JSON.parse(updateEntryResult.content)).toEqual( expect.objectContaining({ success: true, message: "Updated holiday entry: New Year Observed" }), ); expect(JSON.parse(deleteEntryResult.content)).toEqual( expect.objectContaining({ success: true, message: "Deleted holiday entry: New Year Observed" }), ); expect(JSON.parse(deleteCalendarResult.content)).toEqual( expect.objectContaining({ success: true, message: "Deleted holiday calendar: Germany National Updated" }), ); }); it("returns a stable error when a holiday calendar scope already exists", async () => { const ctx = createToolContext( { country: { findUnique: vi.fn().mockResolvedValue({ id: "country_de", name: "Germany" }), }, holidayCalendar: { findFirst: vi.fn().mockResolvedValue({ id: "cal_existing" }), }, }, { userRole: SystemRole.ADMIN }, ); const result = await executeTool( "create_holiday_calendar", JSON.stringify({ name: "Germany National", scopeType: "COUNTRY", countryId: "country_de" }), ctx, ); expect(JSON.parse(result.content)).toEqual(expect.objectContaining({ error: "A holiday calendar for this scope already exists.", })); }); it("returns a stable error when a holiday entry calendar cannot be found", async () => { const ctx = createToolContext( { holidayCalendar: { findUnique: vi.fn().mockResolvedValue(null), }, }, { userRole: SystemRole.ADMIN }, ); const result = await executeTool( "create_holiday_calendar_entry", JSON.stringify({ holidayCalendarId: "cal_missing", date: "2026-01-01", name: "New Year", }), ctx, ); expect(JSON.parse(result.content)).toEqual(expect.objectContaining({ error: "Holiday calendar not found with the given criteria.", })); }); it("returns a stable error when a holiday entry date conflicts", async () => { const ctx = createToolContext( { holidayCalendarEntry: { findUnique: vi.fn().mockResolvedValue({ id: "entry_1", name: "New Year", date: new Date("2026-01-01T00:00:00.000Z"), holidayCalendarId: "cal_de", }), findFirst: vi.fn().mockResolvedValue({ id: "entry_conflict" }), }, }, { userRole: SystemRole.ADMIN }, ); const result = await executeTool( "update_holiday_calendar_entry", JSON.stringify({ id: "entry_1", data: { date: "2026-01-02" }, }), ctx, ); expect(JSON.parse(result.content)).toEqual(expect.objectContaining({ error: "A holiday entry for this calendar and date already exists.", })); }); it("returns a stable error when deleting a missing holiday calendar entry", async () => { const ctx = createToolContext( { holidayCalendarEntry: { findUnique: vi.fn().mockResolvedValue(null), }, }, { userRole: SystemRole.ADMIN }, ); const result = await executeTool( "delete_holiday_calendar_entry", JSON.stringify({ id: "entry_missing" }), ctx, ); expect(JSON.parse(result.content)).toEqual(expect.objectContaining({ error: "Holiday calendar entry not found with the given criteria.", })); }); it("returns the expected assistant payloads for role, client, and org unit mutations", async () => { const db = { role: { findUnique: vi.fn() .mockResolvedValueOnce(null) .mockResolvedValueOnce({ id: "role_1", name: "CG Artist", description: null, color: "#111111", isActive: true }) .mockResolvedValueOnce(null) .mockResolvedValueOnce({ id: "role_1", name: "Senior CG Artist", description: "Pipeline lead", color: "#222222", isActive: true, _count: { resourceRoles: 0 }, resourceRoles: [], }) .mockResolvedValueOnce({ id: "role_1", name: "Senior CG Artist", description: "Pipeline lead", color: "#222222", isActive: true, _count: { resourceRoles: 0 }, resourceRoles: [], }) .mockResolvedValueOnce({ id: "role_1", name: "Senior CG Artist", description: "Pipeline lead", color: "#222222", isActive: true, _count: { resourceRoles: 0 }, }), create: vi.fn().mockResolvedValue({ id: "role_1", name: "CG Artist", description: null, color: "#111111", isActive: true, _count: { resourceRoles: 0 }, }), update: vi.fn().mockResolvedValue({ id: "role_1", name: "Senior CG Artist", description: "Pipeline lead", color: "#222222", isActive: true, _count: { resourceRoles: 0 }, }), delete: vi.fn().mockResolvedValue({ id: "role_1" }), }, client: { findUnique: vi.fn(async ({ where, }: { where: { id?: string; code?: string }; }) => { if (where.id === "client_1") { return { id: "client_1", name: "Acme", code: "AC", sortOrder: 0, isActive: true, parentId: null, tags: [], _count: { projects: 0, children: 0 }, }; } return null; }), create: vi.fn().mockResolvedValue({ id: "client_1", name: "Acme", code: "AC", parentId: null, sortOrder: 2, tags: ["auto"], }), update: vi.fn().mockResolvedValue({ id: "client_1", name: "Acme Mobility", code: "ACM", parentId: null, sortOrder: 3, tags: ["auto", "priority"], isActive: true, }), delete: vi.fn().mockResolvedValue({ id: "client_1", name: "Acme Mobility", code: "ACM", parentId: null, sortOrder: 3, tags: ["auto", "priority"], isActive: true, _count: { projects: 0, children: 0 }, }), }, orgUnit: { findUnique: vi.fn(async ({ where, }: { where: { id: string }; }) => ( where.id === "ou_1" ? { id: "ou_1", name: "Delivery", shortName: "DEL", level: 5, parentId: null, sortOrder: 1, isActive: true, } : null )), create: vi.fn().mockResolvedValue({ id: "ou_1", name: "Delivery", shortName: "DEL", level: 5, parentId: null, sortOrder: 1, isActive: true, }), update: vi.fn().mockResolvedValue({ id: "ou_1", name: "Delivery Europe", shortName: "DEU", level: 5, parentId: null, sortOrder: 2, isActive: true, }), }, auditLog: { create: vi.fn().mockResolvedValue({ id: "audit_1" }), }, demandRequirement: { groupBy: vi.fn().mockResolvedValue([]), }, assignment: { groupBy: vi.fn().mockResolvedValue([]), }, }; const ctx = createToolContext(db, { userRole: SystemRole.ADMIN }); const createRoleResult = await executeTool( "create_role", JSON.stringify({ name: "CG Artist", color: "#111111" }), ctx, ); const updateRoleResult = await executeTool( "update_role", JSON.stringify({ id: "role_1", name: "Senior CG Artist", description: "Pipeline lead", color: "#222222" }), ctx, ); const deleteRoleResult = await executeTool( "delete_role", JSON.stringify({ id: "role_1" }), ctx, ); const createClientResult = await executeTool( "create_client", JSON.stringify({ name: "Acme", code: "AC", sortOrder: 2, tags: ["auto"] }), ctx, ); const updateClientResult = await executeTool( "update_client", JSON.stringify({ id: "client_1", name: "Acme Mobility", code: "ACM", sortOrder: 3, tags: ["auto", "priority"] }), ctx, ); const deleteClientResult = await executeTool( "delete_client", JSON.stringify({ id: "client_1" }), ctx, ); const createOrgUnitResult = await executeTool( "create_org_unit", JSON.stringify({ name: "Delivery", shortName: "DEL", level: 5, sortOrder: 1 }), ctx, ); const updateOrgUnitResult = await executeTool( "update_org_unit", JSON.stringify({ id: "ou_1", name: "Delivery Europe", shortName: "DEU", sortOrder: 2 }), ctx, ); expect(JSON.parse(createRoleResult.content)).toEqual(expect.objectContaining({ success: true, message: "Created role: CG Artist", })); expect(JSON.parse(updateRoleResult.content)).toEqual(expect.objectContaining({ success: true, message: "Updated role: Senior CG Artist", })); expect(JSON.parse(deleteRoleResult.content)).toEqual(expect.objectContaining({ success: true, message: "Deleted role: Senior CG Artist", })); expect(JSON.parse(createClientResult.content)).toEqual(expect.objectContaining({ success: true, message: "Created client: Acme", })); expect(JSON.parse(updateClientResult.content)).toEqual(expect.objectContaining({ success: true, message: "Updated client: Acme Mobility", })); expect(JSON.parse(deleteClientResult.content)).toEqual(expect.objectContaining({ success: true, message: "Deleted client: Acme", })); expect(JSON.parse(createOrgUnitResult.content)).toEqual(expect.objectContaining({ success: true, message: "Created org unit: Delivery", })); expect(JSON.parse(updateOrgUnitResult.content)).toEqual(expect.objectContaining({ success: true, message: "Updated org unit: Delivery Europe", })); }); it("returns a stable error when creating a duplicate role", async () => { const ctx = createToolContext( { role: { findUnique: vi.fn().mockResolvedValue({ id: "role_existing", name: "CG Artist" }), }, }, { userRole: SystemRole.MANAGER, permissions: [PermissionKey.MANAGE_ROLES], }, ); const result = await executeTool( "create_role", JSON.stringify({ name: "CG Artist" }), ctx, ); expect(JSON.parse(result.content)).toEqual(expect.objectContaining({ error: "A role with this name already exists.", })); }); it("returns a stable error when updating a missing role", async () => { const ctx = createToolContext( { role: { findUnique: vi.fn().mockResolvedValue(null), }, }, { userRole: SystemRole.MANAGER, permissions: [PermissionKey.MANAGE_ROLES], }, ); const result = await executeTool( "update_role", JSON.stringify({ id: "role_missing", name: "Senior CG Artist" }), ctx, ); expect(JSON.parse(result.content)).toEqual(expect.objectContaining({ error: "Role not found with the given criteria.", })); }); it("returns a stable error when deleting an assigned role", async () => { const roleRecord = { id: "role_1", name: "Senior CG Artist", description: "Pipeline lead", color: "#222222", isActive: true, _count: { resourceRoles: 1 }, resourceRoles: [], }; const ctx = createToolContext( { role: { findUnique: vi.fn() .mockResolvedValueOnce(roleRecord) .mockResolvedValueOnce(roleRecord), }, demandRequirement: { groupBy: vi.fn().mockResolvedValue([]), }, assignment: { groupBy: vi.fn().mockResolvedValue([]), }, }, { userRole: SystemRole.MANAGER, permissions: [PermissionKey.MANAGE_ROLES], }, ); const result = await executeTool( "delete_role", JSON.stringify({ id: "role_1" }), ctx, ); expect(JSON.parse(result.content)).toEqual(expect.objectContaining({ error: "Role cannot be deleted while it is still assigned. Deactivate it instead.", })); }); it("returns a stable error when creating a client with a duplicate code", async () => { const ctx = createToolContext( { client: { findUnique: vi.fn(async ({ where, }: { where: { id?: string; code?: string }; }) => { if (where.code === "ACM") { return { id: "client_existing", code: "ACM", name: "Existing Client" }; } return null; }), }, }, { userRole: SystemRole.MANAGER }, ); const result = await executeTool( "create_client", JSON.stringify({ name: "Acme Mobility", code: "ACM" }), ctx, ); expect(JSON.parse(result.content)).toEqual(expect.objectContaining({ error: "A client with this code already exists.", })); }); it("returns a stable error when creating a client with a missing parent", async () => { const ctx = createToolContext( { client: { findUnique: vi.fn().mockResolvedValue(null), }, }, { userRole: SystemRole.MANAGER }, ); const result = await executeTool( "create_client", JSON.stringify({ name: "Acme Mobility", parentId: "client_missing" }), ctx, ); expect(JSON.parse(result.content)).toEqual(expect.objectContaining({ error: "Parent client not found with the given criteria.", })); }); it("returns a stable error when updating a missing client", async () => { const ctx = createToolContext( { client: { findUnique: vi.fn().mockResolvedValue(null), }, }, { userRole: SystemRole.MANAGER }, ); const result = await executeTool( "update_client", JSON.stringify({ id: "client_missing", name: "Acme Mobility" }), ctx, ); expect(JSON.parse(result.content)).toEqual(expect.objectContaining({ error: "Client not found with the given criteria.", })); }); it("returns a stable error when deleting a missing client", async () => { const ctx = createToolContext( { client: { findUnique: vi.fn().mockResolvedValue(null), }, }, { userRole: SystemRole.ADMIN }, ); const result = await executeTool( "delete_client", JSON.stringify({ id: "client_missing" }), ctx, ); expect(JSON.parse(result.content)).toEqual(expect.objectContaining({ error: "Client not found with the given criteria.", })); }); it("returns a stable error when deleting a client that still has projects", async () => { const clientRecord = { id: "client_1", name: "Acme Mobility", code: "ACM", parentId: null, isActive: true, sortOrder: 3, tags: ["auto", "priority"], _count: { projects: 2, children: 0 }, }; const ctx = createToolContext( { client: { findUnique: vi.fn() .mockResolvedValueOnce(clientRecord) .mockResolvedValueOnce(clientRecord), }, }, { userRole: SystemRole.ADMIN }, ); const result = await executeTool( "delete_client", JSON.stringify({ id: "client_1" }), ctx, ); expect(JSON.parse(result.content)).toEqual(expect.objectContaining({ error: "Client cannot be deleted while it still has projects. Deactivate it instead.", })); }); it("returns a stable error when deleting a client that still has child clients", async () => { const clientRecord = { id: "client_1", name: "Acme Mobility", code: "ACM", parentId: null, isActive: true, sortOrder: 3, tags: ["auto", "priority"], _count: { projects: 0, children: 2 }, }; const ctx = createToolContext( { client: { findUnique: vi.fn() .mockResolvedValueOnce(clientRecord) .mockResolvedValueOnce(clientRecord), }, }, { userRole: SystemRole.ADMIN }, ); const result = await executeTool( "delete_client", JSON.stringify({ id: "client_1" }), ctx, ); expect(JSON.parse(result.content)).toEqual(expect.objectContaining({ error: "Client cannot be deleted while it still has child clients. Remove or reassign them first.", })); }); it("returns a stable error when creating an org unit with a missing parent", async () => { const ctx = createToolContext( { orgUnit: { findUnique: vi.fn().mockResolvedValue(null), }, }, { userRole: SystemRole.ADMIN }, ); const result = await executeTool( "create_org_unit", JSON.stringify({ name: "Delivery", level: 6, parentId: "ou_missing" }), ctx, ); expect(JSON.parse(result.content)).toEqual(expect.objectContaining({ error: "Parent org unit not found with the given criteria.", })); }); it("returns a stable error when creating an org unit with an invalid child level", async () => { const ctx = createToolContext( { orgUnit: { findUnique: vi.fn().mockResolvedValue({ id: "ou_parent", name: "Delivery", shortName: "DEL", level: 6, parentId: null, sortOrder: 1, isActive: true, }), }, }, { userRole: SystemRole.ADMIN }, ); const result = await executeTool( "create_org_unit", JSON.stringify({ name: "Delivery Germany", level: 5, parentId: "ou_parent" }), ctx, ); expect(JSON.parse(result.content)).toEqual(expect.objectContaining({ error: "Org unit level must be greater than the parent org unit level.", })); }); it("returns a stable error when updating a missing org unit", async () => { const ctx = createToolContext( { orgUnit: { findUnique: vi.fn().mockResolvedValue(null), }, }, { userRole: SystemRole.ADMIN }, ); const result = await executeTool( "update_org_unit", JSON.stringify({ id: "ou_missing", name: "Delivery Europe" }), ctx, ); expect(JSON.parse(result.content)).toEqual(expect.objectContaining({ error: "Org unit not found with the given criteria.", })); }); it("returns a stable error when creating a country with a duplicate code", async () => { const ctx = createToolContext( { country: { findUnique: vi.fn(async ({ where, }: { where: { id?: string; code?: string }; }) => { if (where.code === "DE") { return { id: "country_existing", code: "DE", name: "Germany" }; } return null; }), }, }, { userRole: SystemRole.ADMIN }, ); const result = await executeTool( "create_country", JSON.stringify({ code: "DE", name: "Germany" }), ctx, ); expect(JSON.parse(result.content)).toEqual(expect.objectContaining({ error: "A country with this code already exists.", })); }); it("returns a stable error when updating a missing country", async () => { const ctx = createToolContext( { country: { findUnique: vi.fn().mockResolvedValue(null), }, }, { userRole: SystemRole.ADMIN }, ); const result = await executeTool( "update_country", JSON.stringify({ id: "country_missing", data: { name: "Germany Updated" } }), ctx, ); expect(JSON.parse(result.content)).toEqual(expect.objectContaining({ error: "Country not found with the given criteria.", })); }); it("returns a stable error when creating a metro city for a missing country", async () => { const ctx = createToolContext( { country: { findUnique: vi.fn().mockResolvedValue(null), }, }, { userRole: SystemRole.ADMIN }, ); const result = await executeTool( "create_metro_city", JSON.stringify({ countryId: "country_missing", name: "Munich" }), ctx, ); expect(JSON.parse(result.content)).toEqual(expect.objectContaining({ error: "Country not found with the given criteria.", })); }); it("returns a stable error when updating a missing metro city", async () => { const ctx = createToolContext( { metroCity: { findUnique: vi.fn().mockResolvedValue(null), }, }, { userRole: SystemRole.ADMIN }, ); const result = await executeTool( "update_metro_city", JSON.stringify({ id: "city_missing", data: { name: "Muenchen" } }), ctx, ); expect(JSON.parse(result.content)).toEqual(expect.objectContaining({ error: "Metro city not found with the given criteria.", })); }); it("returns a stable error when deleting an assigned metro city", async () => { const ctx = createToolContext( { metroCity: { findUnique: vi.fn().mockResolvedValue({ id: "city_muc", name: "Munich", countryId: "country_de", _count: { resources: 2 }, }), }, }, { userRole: SystemRole.ADMIN }, ); const result = await executeTool( "delete_metro_city", JSON.stringify({ id: "city_muc" }), ctx, ); expect(JSON.parse(result.content)).toEqual(expect.objectContaining({ error: "Metro city cannot be deleted while it is still assigned to resources.", })); }); it("returns a stable error when creating a user with a duplicate email", async () => { const ctx = createToolContext( { user: { findUnique: vi.fn().mockResolvedValue({ id: "user_existing", email: "peter.parker@example.com", name: "Peter Parker", }), }, }, { userRole: SystemRole.ADMIN }, ); const result = await executeTool( "create_user", JSON.stringify({ email: "peter.parker@example.com", name: "Peter Parker", password: "secret123", }), ctx, ); expect(JSON.parse(result.content)).toEqual(expect.objectContaining({ error: "User with this email already exists.", })); }); it("returns a stable error when creating a user without a name", async () => { const ctx = createToolContext( { user: { findUnique: vi.fn(), }, }, { userRole: SystemRole.ADMIN }, ); const result = await executeTool( "create_user", JSON.stringify({ email: "miles.morales@example.com", name: "", password: "secret123", }), ctx, ); expect(JSON.parse(result.content)).toEqual(expect.objectContaining({ error: "Name is required.", })); expect(ctx.db.user.findUnique).not.toHaveBeenCalled(); }); it("returns a stable error when creating a user with a password that is too short", async () => { const ctx = createToolContext( { user: { findUnique: vi.fn(), }, }, { userRole: SystemRole.ADMIN }, ); const result = await executeTool( "create_user", JSON.stringify({ email: "miles.morales@example.com", name: "Miles Morales", password: "short", }), ctx, ); expect(JSON.parse(result.content)).toEqual(expect.objectContaining({ error: "Password must be at least 8 characters.", })); expect(ctx.db.user.findUnique).not.toHaveBeenCalled(); }); it("returns a stable error when resetting the password of a missing user", async () => { const ctx = createToolContext( { user: { findUnique: vi.fn().mockResolvedValue(null), }, }, { userRole: SystemRole.ADMIN }, ); const result = await executeTool( "set_user_password", JSON.stringify({ userId: "user_missing", password: "secret123" }), ctx, ); expect(JSON.parse(result.content)).toEqual(expect.objectContaining({ error: "User not found with the given criteria.", })); }); it("returns a stable error when resetting a password that is too short", async () => { const ctx = createToolContext( { user: { findUnique: vi.fn(), }, }, { userRole: SystemRole.ADMIN }, ); const result = await executeTool( "set_user_password", JSON.stringify({ userId: "user_1", password: "short" }), ctx, ); expect(JSON.parse(result.content)).toEqual(expect.objectContaining({ error: "Password must be at least 8 characters.", })); expect(ctx.db.user.findUnique).not.toHaveBeenCalled(); }); it("returns a stable error when updating the role of a missing user", async () => { const ctx = createToolContext( { user: { findUnique: vi.fn().mockResolvedValue(null), }, }, { userRole: SystemRole.ADMIN }, ); const result = await executeTool( "update_user_role", JSON.stringify({ id: "user_missing", systemRole: SystemRole.MANAGER }), ctx, ); expect(JSON.parse(result.content)).toEqual(expect.objectContaining({ error: "User not found with the given criteria.", })); }); it("returns a stable error when renaming a missing user", async () => { const ctx = createToolContext( { user: { findUnique: vi.fn().mockResolvedValue(null), }, }, { userRole: SystemRole.ADMIN }, ); const result = await executeTool( "update_user_name", JSON.stringify({ id: "user_missing", name: "Miles Morales" }), ctx, ); expect(JSON.parse(result.content)).toEqual(expect.objectContaining({ error: "User not found with the given criteria.", })); }); it("returns a stable error when renaming a user without a name", async () => { const ctx = createToolContext( { user: { findUnique: vi.fn(), }, }, { userRole: SystemRole.ADMIN }, ); const result = await executeTool( "update_user_name", JSON.stringify({ id: "user_1", name: "" }), ctx, ); expect(JSON.parse(result.content)).toEqual(expect.objectContaining({ error: "Name is required.", })); expect(ctx.db.user.findUnique).not.toHaveBeenCalled(); }); it("returns a stable error when renaming a user with a name that is too long", async () => { const ctx = createToolContext( { user: { findUnique: vi.fn(), }, }, { userRole: SystemRole.ADMIN }, ); const result = await executeTool( "update_user_name", JSON.stringify({ id: "user_1", name: "x".repeat(201) }), ctx, ); expect(JSON.parse(result.content)).toEqual(expect.objectContaining({ error: "Name must be at most 200 characters.", })); expect(ctx.db.user.findUnique).not.toHaveBeenCalled(); }); it("returns a stable error when linking a missing user to a resource", async () => { const ctx = createToolContext( { user: { findUnique: vi.fn().mockResolvedValue(null), }, }, { userRole: SystemRole.ADMIN }, ); const result = await executeTool( "link_user_resource", JSON.stringify({ userId: "user_missing", resourceId: "res_1" }), ctx, ); expect(JSON.parse(result.content)).toEqual(expect.objectContaining({ error: "User not found with the given criteria.", })); }); it("returns a stable error when linking a user to a missing resource", async () => { const ctx = createToolContext( { user: { findUnique: vi.fn().mockResolvedValue({ id: "user_1" }), }, resource: { findUnique: vi.fn().mockResolvedValue(null), }, }, { userRole: SystemRole.ADMIN }, ); const result = await executeTool( "link_user_resource", JSON.stringify({ userId: "user_1", resourceId: "res_missing" }), ctx, ); expect(JSON.parse(result.content)).toEqual(expect.objectContaining({ error: "Resource not found with the given criteria.", })); }); it("returns a stable error when linking a user to a resource that disappears during persistence", async () => { const ctx = createToolContext( { user: { findUnique: vi.fn().mockResolvedValue({ id: "user_1" }), }, resource: { findUnique: vi.fn().mockResolvedValue({ id: "res_1" }), updateMany: vi.fn().mockResolvedValue({ count: 0 }), update: vi.fn().mockRejectedValue({ code: "P2025", message: "No record was found for an update.", }), }, }, { userRole: SystemRole.ADMIN }, ); const result = await executeTool( "link_user_resource", JSON.stringify({ userId: "user_1", resourceId: "res_1" }), ctx, ); expect(JSON.parse(result.content)).toEqual(expect.objectContaining({ error: "Resource not found with the given criteria.", })); }); it("returns a stable error when linking a user after the user disappears during persistence", async () => { const ctx = createToolContext( { user: { findUnique: vi.fn().mockResolvedValue({ id: "user_1" }), }, resource: { findUnique: vi.fn().mockResolvedValue({ id: "res_1" }), updateMany: vi.fn().mockResolvedValue({ count: 0 }), update: vi.fn().mockRejectedValue({ code: "P2003", message: "Foreign key constraint failed", meta: { field_name: "Resource_userId_fkey" }, }), }, }, { userRole: SystemRole.ADMIN }, ); const result = await executeTool( "link_user_resource", JSON.stringify({ userId: "user_1", resourceId: "res_1" }), ctx, ); expect(JSON.parse(result.content)).toEqual(expect.objectContaining({ error: "User not found with the given criteria.", })); }); it("returns a stable error when linking a user after the user disappears and prisma reports an array target", async () => { const ctx = createToolContext( { user: { findUnique: vi.fn().mockResolvedValue({ id: "user_1" }), }, resource: { findUnique: vi.fn().mockResolvedValue({ id: "res_1" }), updateMany: vi.fn().mockResolvedValue({ count: 0 }), update: vi.fn().mockRejectedValue({ code: "P2003", message: "Foreign key constraint failed", meta: { target: ["Resource_userId_fkey"] }, }), }, }, { userRole: SystemRole.ADMIN }, ); const result = await executeTool( "link_user_resource", JSON.stringify({ userId: "user_1", resourceId: "res_1" }), ctx, ); expect(JSON.parse(result.content)).toEqual(expect.objectContaining({ error: "User not found with the given criteria.", })); }); it("returns a stable error when setting permissions for a missing user", async () => { const ctx = createToolContext( { user: { findUnique: vi.fn().mockResolvedValue(null), }, }, { userRole: SystemRole.ADMIN }, ); const result = await executeTool( "set_user_permissions", JSON.stringify({ userId: "user_missing", overrides: { granted: ["manageProjects"] } }), ctx, ); expect(JSON.parse(result.content)).toEqual(expect.objectContaining({ error: "User not found with the given criteria.", })); }); it("returns a stable error when resetting permissions for a missing user", async () => { const ctx = createToolContext( { user: { findUnique: vi.fn().mockResolvedValue(null), }, }, { userRole: SystemRole.ADMIN }, ); const result = await executeTool( "reset_user_permissions", JSON.stringify({ userId: "user_missing" }), ctx, ); expect(JSON.parse(result.content)).toEqual(expect.objectContaining({ error: "User not found with the given criteria.", })); }); it("returns a stable error when reading effective permissions for a missing user", async () => { const ctx = createToolContext( { user: { findUnique: vi.fn().mockResolvedValue(null), }, }, { userRole: SystemRole.ADMIN }, ); const result = await executeTool( "get_effective_user_permissions", JSON.stringify({ userId: "user_missing" }), ctx, ); expect(JSON.parse(result.content)).toEqual(expect.objectContaining({ error: "User not found with the given criteria.", })); }); it("returns a stable error when disabling TOTP for a missing user", async () => { const ctx = createToolContext( { user: { findUnique: vi.fn().mockResolvedValue(null), }, }, { userRole: SystemRole.ADMIN }, ); const result = await executeTool( "disable_user_totp", JSON.stringify({ userId: "user_missing" }), ctx, ); expect(JSON.parse(result.content)).toEqual(expect.objectContaining({ error: "User not found with the given criteria.", })); }); it("returns a stable error when enabling TOTP without a generated secret", async () => { const ctx = createToolContext( { user: { findUniqueOrThrow: vi.fn().mockResolvedValue({ id: "user_1", name: "Assistant User", email: "assistant@example.com", totpSecret: null, totpEnabled: false, }), }, }, { userRole: SystemRole.ADMIN }, ); const result = await executeTool( "verify_and_enable_totp", JSON.stringify({ token: "123456" }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "No TOTP secret generated. Call generate_totp_secret first.", }); }); it("returns a stable error when enabling TOTP for a missing user", async () => { const ctx = createToolContext( { user: { findUniqueOrThrow: vi.fn().mockRejectedValue( new TRPCError({ code: "NOT_FOUND", message: "User not found", }), ), }, }, { userRole: SystemRole.ADMIN }, ); const result = await executeTool( "verify_and_enable_totp", JSON.stringify({ token: "123456" }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "User not found with the given criteria.", }); }); it("returns a stable error when enabling TOTP that is already enabled", async () => { const ctx = createToolContext( { user: { findUniqueOrThrow: vi.fn().mockResolvedValue({ id: "user_1", name: "Assistant User", email: "assistant@example.com", totpSecret: "MOCKSECRET", totpEnabled: true, }), }, }, { userRole: SystemRole.ADMIN }, ); const result = await executeTool( "verify_and_enable_totp", JSON.stringify({ token: "123456" }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "TOTP is already enabled.", }); }); it("returns a stable error when a provided TOTP token is invalid", async () => { totpValidateMock.mockReturnValue(null); const update = vi.fn(); const ctx = createToolContext( { user: { findUniqueOrThrow: vi.fn().mockResolvedValue({ id: "user_1", name: "Assistant User", email: "assistant@example.com", totpSecret: "MOCKSECRET", totpEnabled: false, }), update, }, }, { userRole: SystemRole.ADMIN }, ); const result = await executeTool( "verify_and_enable_totp", JSON.stringify({ token: "123456" }), ctx, ); expect(update).not.toHaveBeenCalled(); expect(JSON.parse(result.content)).toEqual({ error: "Invalid TOTP token.", }); }); it("routes resource creation through the real resource router path and writes an audit log", async () => { const auditCreate = vi.fn().mockResolvedValue({ id: "audit_1" }); const resourceFindFirst = vi.fn().mockResolvedValue(null); const resourceCreate = vi.fn().mockResolvedValue({ id: "res_1", eid: "EMP-001", displayName: "Carol Danvers", email: "carol@example.com", resourceRoles: [], }); const ctx = createToolContext( { resource: { findFirst: resourceFindFirst, create: resourceCreate, }, role: { findUnique: vi.fn().mockResolvedValue({ id: "role_1", name: "Designer", _count: { resourceRoles: 0 } }), findFirst: vi.fn().mockResolvedValue(null), }, country: { findUnique: vi.fn().mockResolvedValue({ id: "country_de", code: "DE", name: "Germany", metroCities: [], _count: { resources: 0 } }), findFirst: vi.fn().mockResolvedValue(null), }, orgUnit: { findUnique: vi.fn().mockResolvedValue({ id: "ou_1", name: "Delivery", shortName: "DEL", _count: { resources: 0 } }), findFirst: vi.fn().mockResolvedValue(null), }, assignment: { findMany: vi.fn().mockResolvedValue([]), }, demandRequirement: { findMany: vi.fn().mockResolvedValue([]), }, auditLog: { create: auditCreate, }, }, { userRole: SystemRole.ADMIN, permissions: [PermissionKey.MANAGE_RESOURCES], }, ); const result = await executeTool( "create_resource", JSON.stringify({ eid: "EMP-001", displayName: "Carol Danvers", email: "carol@example.com", lcrCents: 8000, roleName: "Designer", countryCode: "DE", orgUnitName: "Delivery", }), ctx, ); expect(JSON.parse(result.content)).toEqual(expect.objectContaining({ success: true, message: "Created resource: Carol Danvers (EMP-001)", })); expect(resourceFindFirst).toHaveBeenCalled(); expect(resourceCreate).toHaveBeenCalledWith(expect.objectContaining({ data: expect.objectContaining({ roleId: "role_1", countryId: "country_de", orgUnitId: "ou_1", }), })); expect(auditCreate).toHaveBeenCalledTimes(1); }); it("routes project creation through the real project, blueprint, and client router paths", async () => { const auditCreate = vi.fn().mockResolvedValue({ id: "audit_1" }); const projectCreate = vi.fn().mockResolvedValue({ id: "project_1", shortCode: "PROJ-1", name: "Project One", status: "DRAFT", }); const ctx = createToolContext( { resource: { findFirst: vi.fn().mockResolvedValue({ displayName: "Peter Parker", }), findMany: vi.fn().mockResolvedValue([]), }, project: { findUnique: vi.fn().mockResolvedValue(null), create: projectCreate, }, blueprint: { findUnique: vi.fn().mockResolvedValue({ id: "bp_1", name: "Consulting Blueprint", target: "PROJECT", fieldDefs: [] }), findFirst: vi.fn().mockResolvedValue(null), }, client: { findUnique: vi.fn().mockResolvedValue({ id: "client_1", name: "Acme", code: "ACME", _count: { projects: 0, children: 0 } }), findFirst: vi.fn().mockResolvedValue(null), }, webhook: { findMany: vi.fn().mockResolvedValue([]), }, auditLog: { create: auditCreate, }, }, { userRole: SystemRole.ADMIN, permissions: [PermissionKey.MANAGE_PROJECTS], }, ); const result = await executeTool( "create_project", JSON.stringify({ shortCode: "PROJ-1", name: "Project One", orderType: "CHARGEABLE", budgetCents: 150000, startDate: "2026-05-01", endDate: "2026-06-30", responsiblePerson: "Peter Parker", blueprintName: "Consulting Blueprint", clientName: "ACME", }), ctx, ); expect(JSON.parse(result.content)).toEqual(expect.objectContaining({ success: true, message: expect.stringContaining("Created project: Project One (PROJ-1), budget "), projectId: "project_1", })); expect(projectCreate).toHaveBeenCalledWith(expect.objectContaining({ data: expect.objectContaining({ shortCode: "PROJ-1", blueprintId: "bp_1", clientId: "client_1", responsiblePerson: "Peter Parker", }), })); expect(auditCreate).toHaveBeenCalledTimes(1); }); it("returns a stable assistant error when the blueprint cannot be resolved", async () => { const projectCreate = vi.fn(); const ctx = createToolContext( { resource: { findFirst: vi.fn().mockResolvedValue({ displayName: "Peter Parker", }), findMany: vi.fn().mockResolvedValue([]), }, project: { findUnique: vi.fn().mockResolvedValue(null), create: projectCreate, }, blueprint: { findUnique: vi.fn().mockResolvedValue(null), findFirst: vi.fn().mockResolvedValue(null), }, client: { findUnique: vi.fn().mockResolvedValue(null), findFirst: vi.fn().mockResolvedValue(null), }, auditLog: { create: vi.fn(), }, }, { userRole: SystemRole.ADMIN, permissions: [PermissionKey.MANAGE_PROJECTS], }, ); const result = await executeTool( "create_project", JSON.stringify({ shortCode: "PROJ-404", name: "Missing Blueprint Project", orderType: "CHARGEABLE", budgetCents: 150000, startDate: "2026-05-01", endDate: "2026-06-30", responsiblePerson: "Peter Parker", blueprintName: "Missing Blueprint", }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: 'Blueprint not found: "Missing Blueprint"', }); expect(projectCreate).not.toHaveBeenCalled(); }); it("returns a generic assistant error when blueprint resolution fails internally during project creation", async () => { const projectCreate = vi.fn(); const ctx = createToolContext( { resource: { findFirst: vi.fn().mockResolvedValue({ displayName: "Peter Parker", }), findMany: vi.fn().mockResolvedValue([]), }, project: { findUnique: vi.fn().mockResolvedValue(null), create: projectCreate, }, blueprint: { findUnique: vi.fn().mockRejectedValue( new TRPCError({ code: "INTERNAL_SERVER_ERROR", message: "blueprint resolver connection exhausted", }), ), }, client: { findUnique: vi.fn().mockResolvedValue(null), findFirst: vi.fn().mockResolvedValue(null), }, auditLog: { create: vi.fn(), }, }, { userRole: SystemRole.ADMIN, permissions: [PermissionKey.MANAGE_PROJECTS], }, ); const result = await executeTool( "create_project", JSON.stringify({ shortCode: "PROJ-500", name: "Blueprint Failure Project", orderType: "CHARGEABLE", budgetCents: 150000, startDate: "2026-05-01", endDate: "2026-06-30", responsiblePerson: "Peter Parker", blueprintName: "Consulting Blueprint", }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "The tool could not complete due to an internal error.", }); expect(projectCreate).not.toHaveBeenCalled(); }); it("returns a stable assistant error when creating a duplicate project short code", async () => { const projectCreate = vi.fn(); const ctx = createToolContext( { resource: { findFirst: vi.fn().mockResolvedValue({ displayName: "Peter Parker", }), findMany: vi.fn().mockResolvedValue([]), }, project: { findUnique: vi.fn().mockResolvedValue({ id: "project_existing", shortCode: "PROJ-1", name: "Existing Project", }), create: projectCreate, }, }, { userRole: SystemRole.ADMIN, permissions: [PermissionKey.MANAGE_PROJECTS], }, ); const result = await executeTool( "create_project", JSON.stringify({ shortCode: "PROJ-1", name: "Duplicate Project", orderType: "CHARGEABLE", budgetCents: 150000, startDate: "2026-05-01", endDate: "2026-06-30", responsiblePerson: "Peter Parker", }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: 'A project with short code "PROJ-1" already exists.', }); expect(projectCreate).not.toHaveBeenCalled(); }); it("requires a responsible person before creating a project", async () => { const projectCreate = vi.fn(); const resourceFindFirst = vi.fn(); const ctx = createToolContext( { resource: { findFirst: resourceFindFirst, findMany: vi.fn().mockResolvedValue([]), }, project: { findUnique: vi.fn().mockResolvedValue(null), create: projectCreate, }, }, { userRole: SystemRole.ADMIN, permissions: [PermissionKey.MANAGE_PROJECTS], }, ); const result = await executeTool( "create_project", JSON.stringify({ shortCode: "PROJ-NO-RP", name: "Missing Responsible Person", orderType: "CHARGEABLE", budgetCents: 150000, startDate: "2026-05-01", endDate: "2026-06-30", }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "responsiblePerson is required to create a project.", }); expect(resourceFindFirst).not.toHaveBeenCalled(); expect(projectCreate).not.toHaveBeenCalled(); }); it("returns a stable assistant error when the project blueprint disappears before creation", async () => { const projectCreate = vi.fn(); const blueprintFindUnique = vi.fn() .mockResolvedValueOnce({ id: "bp_1", name: "Consulting Blueprint", target: "PROJECT", isActive: true }) .mockResolvedValueOnce(null); const ctx = createToolContext( { resource: { findFirst: vi.fn().mockResolvedValue({ displayName: "Peter Parker", }), findMany: vi.fn().mockResolvedValue([]), }, project: { findUnique: vi.fn().mockResolvedValue(null), create: projectCreate, }, blueprint: { findUnique: blueprintFindUnique, findFirst: vi.fn().mockResolvedValue(null), }, }, { userRole: SystemRole.ADMIN, permissions: [PermissionKey.MANAGE_PROJECTS], }, ); const result = await executeTool( "create_project", JSON.stringify({ shortCode: "PROJ-BP", name: "Blueprint Race Project", orderType: "CHARGEABLE", budgetCents: 150000, startDate: "2026-05-01", endDate: "2026-06-30", responsiblePerson: "Peter Parker", blueprintName: "Consulting Blueprint", }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "Blueprint not found with the given criteria.", }); expect(projectCreate).not.toHaveBeenCalled(); }); it("routes resource updates through the real resource router path and writes an audit log", async () => { const auditCreate = vi.fn().mockResolvedValue({ id: "audit_1" }); const resourceFindUnique = vi.fn() .mockResolvedValueOnce({ id: "res_1", eid: "EMP-001", displayName: "Carol Danvers", }) .mockResolvedValueOnce({ id: "res_1", eid: "EMP-001", displayName: "Carol Danvers", dynamicFields: {}, blueprintId: null, }); const resourceUpdate = vi.fn().mockResolvedValue({ id: "res_1", eid: "EMP-001", displayName: "Captain Marvel", resourceRoles: [], }); const ctx = createToolContext( { resource: { findUnique: resourceFindUnique, update: resourceUpdate, }, auditLog: { create: auditCreate, }, }, { userRole: SystemRole.ADMIN, permissions: [PermissionKey.MANAGE_RESOURCES], }, ); const result = await executeTool( "update_resource", JSON.stringify({ id: "res_1", displayName: "Captain Marvel", chapter: "Delivery", }), ctx, ); expect(JSON.parse(result.content)).toEqual(expect.objectContaining({ success: true, message: "Updated resource Captain Marvel (EMP-001)", updatedFields: ["displayName", "chapter"], })); expect(resourceUpdate).toHaveBeenCalled(); expect(auditCreate).toHaveBeenCalledTimes(1); }); it("returns a stable assistant error when the resource disappears during update", async () => { const ctx = createToolContext( { resource: { findUnique: vi.fn() .mockResolvedValueOnce({ id: "res_1", eid: "EMP-001", displayName: "Carol Danvers", }) .mockResolvedValueOnce({ id: "res_1", eid: "EMP-001", displayName: "Carol Danvers", dynamicFields: {}, blueprintId: null, }), update: vi.fn().mockRejectedValue({ code: "P2025", message: "Record to update not found.", }), }, }, { userRole: SystemRole.ADMIN, permissions: [PermissionKey.MANAGE_RESOURCES], }, ); const result = await executeTool( "update_resource", JSON.stringify({ id: "res_1", displayName: "Captain Marvel" }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "Resource not found with the given criteria.", }); }); it("returns a stable assistant error when the resource disappears during deactivation", async () => { const ctx = createToolContext( { resource: { findUnique: vi.fn().mockResolvedValue({ id: "res_1", eid: "EMP-001", displayName: "Carol Danvers", }), update: vi.fn().mockRejectedValue({ code: "P2025", message: "Record to update not found.", }), }, }, { userRole: SystemRole.ADMIN, permissions: [PermissionKey.MANAGE_RESOURCES], }, ); const result = await executeTool( "deactivate_resource", JSON.stringify({ identifier: "res_1" }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "Resource not found with the given criteria.", }); }); it("returns a stable assistant error when the role cannot be resolved during resource creation", async () => { const resourceCreate = vi.fn(); const ctx = createToolContext( { resource: { findFirst: vi.fn().mockResolvedValue(null), create: resourceCreate, }, role: { findUnique: vi.fn().mockResolvedValue(null), findFirst: vi.fn().mockResolvedValue(null), }, assignment: { findMany: vi.fn().mockResolvedValue([]), }, demandRequirement: { findMany: vi.fn().mockResolvedValue([]), }, }, { userRole: SystemRole.ADMIN, permissions: [PermissionKey.MANAGE_RESOURCES], }, ); const result = await executeTool( "create_resource", JSON.stringify({ eid: "EMP-404", displayName: "Role Missing", email: "missing@example.com", lcrCents: 8000, roleName: "Missing Role", }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: 'Role not found: "Missing Role"', }); expect(resourceCreate).not.toHaveBeenCalled(); }); it("returns a generic assistant error when role resolution fails internally during resource creation", async () => { const resourceCreate = vi.fn(); const ctx = createToolContext( { resource: { findFirst: vi.fn().mockResolvedValue(null), create: resourceCreate, }, role: { findUnique: vi.fn().mockRejectedValue( new TRPCError({ code: "INTERNAL_SERVER_ERROR", message: "role resolver connection exhausted", }), ), }, assignment: { findMany: vi.fn().mockResolvedValue([]), }, demandRequirement: { findMany: vi.fn().mockResolvedValue([]), }, }, { userRole: SystemRole.ADMIN, permissions: [PermissionKey.MANAGE_RESOURCES], }, ); const result = await executeTool( "create_resource", JSON.stringify({ eid: "EMP-500", displayName: "Role Failure", email: "role-failure@example.com", lcrCents: 8000, roleName: "Designer", }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "The tool could not complete due to an internal error.", }); expect(resourceCreate).not.toHaveBeenCalled(); }); it("returns a stable assistant error when creating a duplicate resource", async () => { const resourceCreate = vi.fn(); const ctx = createToolContext( { resource: { findFirst: vi.fn().mockResolvedValue({ id: "resource_existing", eid: "EMP-001", email: "carol@example.com", }), create: resourceCreate, }, assignment: { findMany: vi.fn().mockResolvedValue([]), }, demandRequirement: { findMany: vi.fn().mockResolvedValue([]), }, }, { userRole: SystemRole.ADMIN, permissions: [PermissionKey.MANAGE_RESOURCES], }, ); const result = await executeTool( "create_resource", JSON.stringify({ eid: "EMP-001", displayName: "Carol Danvers", email: "carol@example.com", lcrCents: 8000, }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "A resource with this EID or email already exists.", }); expect(resourceCreate).not.toHaveBeenCalled(); }); it("requires an email address before creating a resource", async () => { const resourceCreate = vi.fn(); const roleFindUnique = vi.fn(); const ctx = createToolContext( { resource: { findFirst: vi.fn().mockResolvedValue(null), create: resourceCreate, }, role: { findUnique: roleFindUnique, findFirst: vi.fn().mockResolvedValue(null), }, assignment: { findMany: vi.fn().mockResolvedValue([]), }, demandRequirement: { findMany: vi.fn().mockResolvedValue([]), }, }, { userRole: SystemRole.ADMIN, permissions: [PermissionKey.MANAGE_RESOURCES], }, ); const result = await executeTool( "create_resource", JSON.stringify({ eid: "EMP-NO-MAIL", displayName: "Missing Email", lcrCents: 8000, }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "email is required to create a resource.", }); expect(roleFindUnique).not.toHaveBeenCalled(); expect(resourceCreate).not.toHaveBeenCalled(); }); it("returns a stable assistant error when the selected resource role disappears before creation", async () => { const resourceCreate = vi.fn().mockRejectedValue({ code: "P2003", message: "Foreign key constraint failed", meta: { field_name: "Resource_roleId_fkey" }, }); const ctx = createToolContext( { resource: { findFirst: vi.fn().mockResolvedValue(null), create: resourceCreate, }, role: { findUnique: vi.fn().mockResolvedValue({ id: "role_1", name: "Designer" }), findFirst: vi.fn().mockResolvedValue(null), }, assignment: { findMany: vi.fn().mockResolvedValue([]), }, demandRequirement: { findMany: vi.fn().mockResolvedValue([]), }, }, { userRole: SystemRole.ADMIN, permissions: [PermissionKey.MANAGE_RESOURCES], }, ); const result = await executeTool( "create_resource", JSON.stringify({ eid: "EMP-002", displayName: "Carol Danvers", email: "carol@example.com", lcrCents: 8000, roleName: "Designer", }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "Role not found with the given criteria.", }); }); it("routes project updates through the real project router path and resolves short codes before updating", async () => { const auditCreate = vi.fn().mockResolvedValue({ id: "audit_1" }); const projectFindUnique = vi.fn() .mockResolvedValueOnce(null) .mockResolvedValueOnce({ id: "project_1", shortCode: "PROJ-1", name: "Project One", status: "DRAFT", responsiblePerson: "Peter Parker", }) .mockResolvedValueOnce({ id: "project_1", shortCode: "PROJ-1", name: "Project One", status: "DRAFT", responsiblePerson: "Peter Parker", dynamicFields: {}, blueprintId: null, }); const projectUpdate = vi.fn().mockResolvedValue({ id: "project_1", shortCode: "PROJ-1", name: "Project One Reloaded", }); const ctx = createToolContext( { project: { findUnique: projectFindUnique, findFirst: vi.fn().mockResolvedValue(null), update: projectUpdate, }, assignment: { findMany: vi.fn().mockResolvedValue([]), }, resource: { findFirst: vi.fn().mockResolvedValue({ displayName: "Peter Parker", }), findMany: vi.fn().mockResolvedValue([]), }, auditLog: { create: auditCreate, }, }, { userRole: SystemRole.ADMIN, permissions: [PermissionKey.MANAGE_PROJECTS], }, ); const result = await executeTool( "update_project", JSON.stringify({ id: "PROJ-1", name: "Project One Reloaded", responsiblePerson: "Peter Parker", }), ctx, ); expect(JSON.parse(result.content)).toEqual(expect.objectContaining({ success: true, message: "Updated project Project One Reloaded (PROJ-1)", updatedFields: ["name", "responsiblePerson"], })); expect(projectUpdate).toHaveBeenCalledWith(expect.objectContaining({ where: { id: "project_1" }, data: expect.objectContaining({ name: "Project One Reloaded", responsiblePerson: "Peter Parker", }), })); expect(auditCreate).toHaveBeenCalledTimes(1); }); it("returns a stable assistant error when the project disappears during update", async () => { const ctx = createToolContext( { project: { findUnique: vi.fn() .mockResolvedValueOnce(null) .mockResolvedValueOnce({ id: "project_1", shortCode: "PROJ-1", name: "Project One", status: "DRAFT", responsiblePerson: "Peter Parker", }) .mockResolvedValueOnce({ id: "project_1", shortCode: "PROJ-1", name: "Project One", status: "DRAFT", responsiblePerson: "Peter Parker", dynamicFields: {}, blueprintId: null, }), findFirst: vi.fn().mockResolvedValue(null), update: vi.fn().mockRejectedValue({ code: "P2025", message: "Record to update not found.", }), }, assignment: { findMany: vi.fn().mockResolvedValue([]), }, }, { userRole: SystemRole.ADMIN, permissions: [PermissionKey.MANAGE_PROJECTS], }, ); const result = await executeTool( "update_project", JSON.stringify({ id: "PROJ-1", name: "Project One Reloaded" }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "Project not found with the given criteria.", }); }); it("enforces admin-only project deletion through the real project router path", async () => { const ctx = createToolContext( { project: { findUnique: vi.fn().mockResolvedValue({ id: "project_1", shortCode: "PROJ-1", name: "Project One", status: "ACTIVE", responsiblePerson: "Peter Parker", }), }, assignment: { findMany: vi.fn().mockResolvedValue([]), }, }, { userRole: SystemRole.MANAGER, permissions: [PermissionKey.MANAGE_PROJECTS], }, ); const result = await executeTool( "delete_project", JSON.stringify({ projectId: "project_1" }), ctx, ); expect(JSON.parse(result.content)).toEqual(expect.objectContaining({ error: "You do not have permission to perform this action.", })); }); it("routes project deletion through the real project router path for admins", async () => { const auditCreate = vi.fn().mockResolvedValue({ id: "audit_1" }); const tx = { assignment: { deleteMany: vi.fn().mockResolvedValue({ count: 0 }) }, demandRequirement: { deleteMany: vi.fn().mockResolvedValue({ count: 0 }) }, calculationRule: { updateMany: vi.fn().mockResolvedValue({ count: 0 }) }, project: { delete: vi.fn().mockResolvedValue({ id: "project_1" }) }, auditLog: { create: auditCreate }, }; const transaction = vi.fn().mockImplementation(async (callback: (inner: typeof tx) => Promise) => callback(tx)); const projectFindUnique = vi.fn() .mockResolvedValueOnce({ id: "project_1", shortCode: "PROJ-1", name: "Project One", status: "ACTIVE", responsiblePerson: "Peter Parker", }) .mockResolvedValueOnce({ id: "project_1", shortCode: "PROJ-1", name: "Project One", }); const ctx = createToolContext( { project: { findUnique: projectFindUnique, }, assignment: { findMany: vi.fn().mockResolvedValue([]), }, $transaction: transaction, }, { userRole: SystemRole.ADMIN, permissions: [PermissionKey.MANAGE_PROJECTS], }, ); const result = await executeTool( "delete_project", JSON.stringify({ projectId: "project_1" }), ctx, ); expect(JSON.parse(result.content)).toEqual(expect.objectContaining({ success: true, message: "Deleted project: Project One (PROJ-1)", })); expect(transaction).toHaveBeenCalledTimes(1); expect(auditCreate).toHaveBeenCalledTimes(1); }); it("returns a stable error when a project disappears before deletion completes", async () => { const ctx = createToolContext( { project: { findUnique: vi.fn() .mockResolvedValueOnce({ id: "project_1", shortCode: "PROJ-1", name: "Project One", status: "ACTIVE", responsiblePerson: "Peter Parker", }) .mockResolvedValueOnce({ id: "project_1", shortCode: "PROJ-1", name: "Project One", }), }, assignment: { findMany: vi.fn().mockResolvedValue([]), }, $transaction: vi.fn().mockRejectedValue( new TRPCError({ code: "NOT_FOUND", message: "Project not found" }), ), }, { userRole: SystemRole.ADMIN, permissions: [PermissionKey.MANAGE_PROJECTS], }, ); const result = await executeTool( "delete_project", JSON.stringify({ projectId: "project_1" }), ctx, ); expect(JSON.parse(result.content)).toEqual(expect.objectContaining({ error: "Project not found: project_1", })); }); it("routes demand creation through the real allocation router path and writes an audit log", async () => { const auditCreate = vi.fn().mockResolvedValue({ id: "audit_1" }); const notificationCreate = vi.fn().mockResolvedValue({ id: "task_1" }); const demandCreate = vi.fn().mockResolvedValue({ id: "demand_1", projectId: "project_1", roleId: "role_1", role: null, headcount: 2, hoursPerDay: 6, percentage: 75, budgetCents: 0, status: "PROPOSED", metadata: {}, startDate: new Date("2026-05-01T00:00:00.000Z"), endDate: new Date("2026-05-15T00:00:00.000Z"), project: { id: "project_1", name: "Project One", shortCode: "PROJ-1" }, roleEntity: { id: "role_1", name: "Designer", color: "#00AAFF" }, }); const tx = { project: { findUnique: vi.fn().mockResolvedValue({ id: "project_1", name: "Project One", shortCode: "PROJ-1", budgetCents: 0, }), }, demandRequirement: { create: demandCreate }, auditLog: { create: auditCreate }, }; const transaction = vi.fn().mockImplementation(async (callback: (inner: typeof tx) => Promise) => callback(tx)); const projectFindFirst = vi.fn().mockResolvedValue({ id: "project_1", name: "Project One", shortCode: "PROJ-1", }); const projectFindUnique = vi.fn().mockResolvedValue({ id: "project_1", name: "Project One", shortCode: "PROJ-1", budgetCents: 0, }); const roleFindFirst = vi.fn().mockResolvedValue({ id: "role_1", name: "Designer" }); const roleFindUnique = vi.fn().mockResolvedValue({ id: "role_1", name: "Designer" }); const ctx = createToolContext( { project: { findFirst: projectFindFirst, findUnique: projectFindUnique, }, role: { findFirst: roleFindFirst, findUnique: roleFindUnique, }, assignment: { findMany: vi.fn().mockResolvedValue([]), }, demandRequirement: { findMany: vi.fn().mockResolvedValue([]), }, user: { findMany: vi.fn().mockResolvedValue([{ id: "manager_1" }]), }, notification: { findFirst: vi.fn().mockResolvedValue(null), create: notificationCreate, }, $transaction: transaction, }, { userRole: SystemRole.ADMIN, permissions: [PermissionKey.MANAGE_ALLOCATIONS], }, ); const result = await executeTool( "create_demand", JSON.stringify({ projectId: "PROJ-1", roleName: "Designer", headcount: 2, hoursPerDay: 6, startDate: "2026-05-01", endDate: "2026-05-15", }), ctx, ); expect(JSON.parse(result.content)).toEqual(expect.objectContaining({ success: true, message: "Created demand: Designer × 2 for Project One (PROJ-1), 6h/day, 2026-05-01 to 2026-05-15", demandId: "demand_1", })); expect(demandCreate).toHaveBeenCalledWith(expect.objectContaining({ data: expect.objectContaining({ projectId: "project_1", roleId: "role_1", headcount: 2, hoursPerDay: 6, percentage: 75, }), })); expect(auditCreate).toHaveBeenCalledTimes(1); expect(notificationCreate).toHaveBeenCalledTimes(1); }); it("routes demand filling through the real allocation router path", async () => { const auditCreate = vi.fn().mockResolvedValue({ id: "audit_1" }); const assignmentCreate = vi.fn().mockResolvedValue({ id: "assignment_1", demandRequirementId: "demand_1", resourceId: "resource_1", projectId: "project_1", startDate: new Date("2026-05-01T00:00:00.000Z"), endDate: new Date("2026-05-15T00:00:00.000Z"), hoursPerDay: 6, percentage: 75, role: "Designer", roleId: "role_1", dailyCostCents: 42000, status: "PROPOSED", metadata: {}, createdAt: new Date("2026-03-29T00:00:00.000Z"), updatedAt: new Date("2026-03-29T00:00:00.000Z"), resource: { id: "resource_1", displayName: "Carol Danvers", eid: "EMP-001", lcrCents: 7000 }, project: { id: "project_1", name: "Project One", shortCode: "PROJ-1" }, roleEntity: { id: "role_1", name: "Designer", color: "#00AAFF" }, demandRequirement: { id: "demand_1", projectId: "project_1", startDate: new Date("2026-05-01T00:00:00.000Z"), endDate: new Date("2026-05-15T00:00:00.000Z"), hoursPerDay: 6, percentage: 75, role: "Designer", roleId: "role_1", headcount: 1, status: "PROPOSED", }, }); const txDemandFindUnique = vi.fn().mockResolvedValue({ id: "demand_1", projectId: "project_1", }); const txDemandUpdate = vi.fn().mockResolvedValue({ id: "demand_1", projectId: "project_1", headcount: 1, status: "COMPLETED", }); const tx = { project: { findUnique: vi.fn().mockResolvedValue({ id: "project_1" }), }, resource: { findUnique: vi.fn().mockResolvedValue({ id: "resource_1", lcrCents: 7000, availability: { monday: 8, tuesday: 8, wednesday: 8, thursday: 8, friday: 8, saturday: 0, sunday: 0, }, }), }, demandRequirement: { findUnique: txDemandFindUnique, update: txDemandUpdate, }, assignment: { findMany: vi.fn().mockResolvedValue([]), create: assignmentCreate, }, vacation: { findMany: vi.fn().mockResolvedValue([]), }, auditLog: { create: auditCreate, }, }; const transaction = vi.fn().mockImplementation(async (callback: (inner: typeof tx) => Promise) => callback(tx)); const demandRecord = { id: "demand_1", projectId: "project_1", startDate: new Date("2026-05-01T00:00:00.000Z"), endDate: new Date("2026-05-15T00:00:00.000Z"), hoursPerDay: 6, percentage: 75, role: "Designer", roleId: "role_1", headcount: 1, status: "PROPOSED", metadata: {}, roleEntity: { name: "Designer" }, project: { id: "project_1", name: "Project One", shortCode: "PROJ-1" }, }; const demandFindUnique = vi.fn() .mockResolvedValueOnce(demandRecord) .mockResolvedValueOnce(demandRecord); const resourceFindUnique = vi.fn().mockResolvedValue({ id: "resource_1", displayName: "Carol Danvers", lcrCents: 7000, availability: { monday: 8, tuesday: 8, wednesday: 8, thursday: 8, friday: 8, saturday: 0, sunday: 0, }, }); const ctx = createToolContext( { demandRequirement: { findUnique: demandFindUnique, }, project: { findUnique: vi.fn().mockResolvedValue({ id: "project_1", name: "Project One", shortCode: "PROJ-1", budgetCents: 100000, }), }, user: { findMany: vi.fn().mockResolvedValue([]), }, notification: { findFirst: vi.fn().mockResolvedValue(null), create: vi.fn(), }, resource: { findUnique: resourceFindUnique, findFirst: vi.fn().mockResolvedValue(null), }, assignment: { findMany: vi.fn().mockResolvedValue([]), }, $transaction: transaction, }, { userRole: SystemRole.ADMIN, permissions: [PermissionKey.MANAGE_ALLOCATIONS], }, ); const result = await executeTool( "fill_demand", JSON.stringify({ demandId: "demand_1", resourceId: "resource_1" }), ctx, ); expect(JSON.parse(result.content)).toEqual(expect.objectContaining({ success: true, message: "Assigned Carol Danvers to Designer on Project One (PROJ-1)", assignmentId: "assignment_1", })); expect(assignmentCreate).toHaveBeenCalledTimes(1); expect(txDemandUpdate).toHaveBeenCalledWith(expect.objectContaining({ where: { id: "demand_1" }, data: { status: "COMPLETED" }, })); expect(auditCreate).toHaveBeenCalledTimes(1); }); it("returns a stable assistant error when the role cannot be resolved during demand creation", async () => { const ctx = createToolContext( { project: { findUnique: vi.fn() .mockResolvedValueOnce(null) .mockResolvedValueOnce({ id: "project_1", name: "Project One", shortCode: "PROJ-1", status: "ACTIVE", responsiblePerson: "Peter Parker", }), findFirst: vi.fn().mockResolvedValue(null), }, role: { findUnique: vi.fn().mockResolvedValue(null), findFirst: vi.fn().mockResolvedValue(null), }, }, { userRole: SystemRole.ADMIN, permissions: [PermissionKey.MANAGE_ALLOCATIONS], }, ); const result = await executeTool( "create_demand", JSON.stringify({ projectId: "PROJ-1", roleName: "Missing Role", headcount: 2, hoursPerDay: 6, startDate: "2026-05-01", endDate: "2026-05-15", }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "Role not found: Missing Role", }); }); it("returns a stable assistant error when demand filling cannot resolve the demand", async () => { const ctx = createToolContext( { demandRequirement: { findUnique: vi.fn().mockResolvedValue(null), }, resource: { findUnique: vi.fn() .mockResolvedValueOnce(null) .mockResolvedValueOnce({ id: "resource_1", displayName: "Carol Danvers", eid: "EMP-001", chapter: "Delivery", isActive: true, }), findFirst: vi.fn().mockResolvedValue(null), }, }, { userRole: SystemRole.ADMIN, permissions: [PermissionKey.MANAGE_ALLOCATIONS], }, ); const result = await executeTool( "fill_demand", JSON.stringify({ demandId: "demand_missing", resourceId: "resource_1" }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "Demand not found with the given criteria.", }); }); it("returns a stable assistant error when demand filling violates lifecycle preconditions", async () => { const ctx = createToolContext( { demandRequirement: { findUnique: vi.fn().mockResolvedValue({ id: "demand_1", projectId: "project_1", startDate: new Date("2026-05-01T00:00:00.000Z"), endDate: new Date("2026-05-15T00:00:00.000Z"), hoursPerDay: 6, role: "Designer", roleId: "role_1", headcount: 1, status: "COMPLETED", metadata: {}, }), }, resource: { findUnique: vi.fn() .mockResolvedValueOnce(null) .mockResolvedValueOnce({ id: "resource_1", displayName: "Carol Danvers", eid: "EMP-001", chapter: "Delivery", isActive: true, }), findFirst: vi.fn().mockResolvedValue(null), }, }, { userRole: SystemRole.ADMIN, permissions: [PermissionKey.MANAGE_ALLOCATIONS], }, ); const result = await executeTool( "fill_demand", JSON.stringify({ demandId: "demand_1", resourceId: "resource_1" }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "Demand cannot be filled in its current status.", }); }); it("returns a generic assistant error when role resolution fails internally during demand creation", async () => { const demandCreate = vi.fn(); const ctx = createToolContext( { project: { findUnique: vi.fn() .mockResolvedValueOnce(null) .mockResolvedValueOnce({ id: "project_1", name: "Project One", shortCode: "PROJ-1", status: "ACTIVE", responsiblePerson: "Peter Parker", }), findFirst: vi.fn().mockResolvedValue(null), }, role: { findUnique: vi.fn().mockRejectedValue( new TRPCError({ code: "INTERNAL_SERVER_ERROR", message: "role resolver connection exhausted", }), ), }, demandRequirement: { create: demandCreate, }, }, { userRole: SystemRole.ADMIN, permissions: [PermissionKey.MANAGE_ALLOCATIONS], }, ); const result = await executeTool( "create_demand", JSON.stringify({ projectId: "PROJ-1", roleName: "Designer", headcount: 2, hoursPerDay: 6, startDate: "2026-05-01", endDate: "2026-05-15", }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "The tool could not complete due to an internal error.", }); expect(demandCreate).not.toHaveBeenCalled(); }); it("returns a stable assistant error when the demand project disappears before creation", async () => { const tx = { project: { findUnique: vi.fn().mockResolvedValue(null), }, demandRequirement: { create: vi.fn(), }, auditLog: { create: vi.fn(), }, }; const ctx = createToolContext( { project: { findUnique: vi.fn() .mockResolvedValueOnce(null) .mockResolvedValueOnce({ id: "project_1", name: "Project One", shortCode: "PROJ-1", status: "ACTIVE", responsiblePerson: "Peter Parker", }), findFirst: vi.fn().mockResolvedValue(null), }, role: { findUnique: vi.fn().mockResolvedValue({ id: "role_1", name: "Designer" }), findFirst: vi.fn().mockResolvedValue(null), }, $transaction: vi.fn().mockImplementation(async (callback: (inner: typeof tx) => Promise) => callback(tx)), }, { userRole: SystemRole.ADMIN, permissions: [PermissionKey.MANAGE_ALLOCATIONS], }, ); const result = await executeTool( "create_demand", JSON.stringify({ projectId: "PROJ-1", roleName: "Designer", headcount: 2, hoursPerDay: 6, startDate: "2026-05-01", endDate: "2026-05-15", }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "Project not found with the given criteria.", }); }); it("returns a stable assistant error when the selected demand role disappears before creation", async () => { const tx = { project: { findUnique: vi.fn().mockResolvedValue({ id: "project_1", name: "Project One", shortCode: "PROJ-1", budgetCents: 0, }), }, demandRequirement: { create: vi.fn().mockRejectedValue({ code: "P2003", message: "Foreign key constraint failed", meta: { field_name: "DemandRequirement_roleId_fkey" }, }), }, auditLog: { create: vi.fn(), }, }; const ctx = createToolContext( { project: { findUnique: vi.fn() .mockResolvedValueOnce(null) .mockResolvedValueOnce({ id: "project_1", name: "Project One", shortCode: "PROJ-1", status: "ACTIVE", responsiblePerson: "Peter Parker", }), findFirst: vi.fn().mockResolvedValue(null), }, role: { findUnique: vi.fn().mockResolvedValue({ id: "role_1", name: "Designer" }), findFirst: vi.fn().mockResolvedValue(null), }, $transaction: vi.fn().mockImplementation(async (callback: (inner: typeof tx) => Promise) => callback(tx)), }, { userRole: SystemRole.ADMIN, permissions: [PermissionKey.MANAGE_ALLOCATIONS], }, ); const result = await executeTool( "create_demand", JSON.stringify({ projectId: "PROJ-1", roleName: "Designer", headcount: 2, hoursPerDay: 6, startDate: "2026-05-01", endDate: "2026-05-15", }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "Role not found with the given criteria.", }); }); it("routes allocation creation through the real allocation router path", async () => { const auditCreate = vi.fn().mockResolvedValue({ id: "audit_1" }); const assignmentCreate = vi.fn().mockResolvedValue({ id: "assignment_1", demandRequirementId: null, resourceId: "resource_1", projectId: "project_1", startDate: new Date("2026-06-01T00:00:00.000Z"), endDate: new Date("2026-06-05T00:00:00.000Z"), hoursPerDay: 6, percentage: 75, role: "Designer", roleId: null, dailyCostCents: 42000, status: "PROPOSED", metadata: {}, createdAt: new Date("2026-03-29T00:00:00.000Z"), updatedAt: new Date("2026-03-29T00:00:00.000Z"), resource: { id: "resource_1", displayName: "Carol Danvers", eid: "EMP-001", lcrCents: 7000 }, project: { id: "project_1", name: "Project One", shortCode: "PROJ-1" }, roleEntity: null, demandRequirement: null, }); const tx = { project: { findUnique: vi.fn().mockResolvedValue({ id: "project_1" }), }, resource: { findUnique: vi.fn().mockResolvedValue({ id: "resource_1", lcrCents: 7000, availability: { monday: 8, tuesday: 8, wednesday: 8, thursday: 8, friday: 8, saturday: 0, sunday: 0, }, }), }, assignment: { findMany: vi.fn().mockResolvedValue([]), create: assignmentCreate, }, vacation: { findMany: vi.fn().mockResolvedValue([]), }, auditLog: { create: auditCreate, }, }; const transaction = vi.fn().mockImplementation(async (callback: (inner: typeof tx) => Promise) => callback(tx)); const ctx = createToolContext( { resource: { findUnique: vi.fn() .mockResolvedValue({ id: "resource_1", displayName: "Carol Danvers", eid: "EMP-001", lcrCents: 7000, }), findFirst: vi.fn().mockResolvedValue(null), }, project: { findUnique: vi.fn().mockResolvedValue({ id: "project_1", name: "Project One", shortCode: "PROJ-1", status: "ACTIVE", responsiblePerson: "Peter Parker", budgetCents: 0, }), findFirst: vi.fn().mockResolvedValue(null), }, assignment: { findMany: vi.fn().mockResolvedValue([]), findUnique: vi.fn().mockResolvedValue(null), }, $transaction: transaction, }, { userRole: SystemRole.ADMIN, permissions: [PermissionKey.MANAGE_ALLOCATIONS], }, ); const result = await executeTool( "create_allocation", JSON.stringify({ resourceId: "resource_1", projectId: "project_1", startDate: "2026-06-01", endDate: "2026-06-05", hoursPerDay: 6, role: "Designer", }), ctx, ); expect(JSON.parse(result.content)).toEqual(expect.objectContaining({ success: true, message: "Created allocation: Carol Danvers → Project One (PROJ-1), 6h/day, 2026-06-01 to 2026-06-05", allocationId: "assignment_1", status: "PROPOSED", })); expect(ctx.db.resource.findUnique).toHaveBeenCalledWith({ where: { id: "resource_1" }, select: { id: true, displayName: true, eid: true, chapter: true, isActive: true, }, }); expect(ctx.db.project.findUnique).toHaveBeenCalledWith({ where: { id: "project_1" }, select: expect.objectContaining({ id: true, shortCode: true, name: true, }), }); expect(ctx.db.assignment.findMany).toHaveBeenCalledWith(expect.objectContaining({ where: { resourceId: "resource_1", projectId: "project_1", }, orderBy: { startDate: "asc" }, include: expect.objectContaining({ resource: { select: expect.objectContaining({ id: true, displayName: true, eid: true, chapter: true, lcrCents: true, }), }, project: { select: expect.objectContaining({ id: true, name: true, shortCode: true, status: true, }), }, }), })); expect(assignmentCreate).toHaveBeenCalledTimes(1); expect(auditCreate).toHaveBeenCalledTimes(1); }); it("routes allocation cancellation through the real allocation router path", async () => { const auditCreate = vi.fn().mockResolvedValue({ id: "audit_1" }); const assignmentUpdate = vi.fn().mockResolvedValue({ id: "assignment_1", resourceId: "resource_1", projectId: "project_1", demandRequirementId: null, startDate: new Date("2026-06-01T00:00:00.000Z"), endDate: new Date("2026-06-05T00:00:00.000Z"), hoursPerDay: 6, percentage: 75, role: "Designer", roleId: null, dailyCostCents: 42000, status: "CANCELLED", metadata: {}, createdAt: new Date("2026-03-29T00:00:00.000Z"), updatedAt: new Date("2026-03-29T00:00:00.000Z"), resource: { id: "resource_1", displayName: "Carol Danvers", eid: "EMP-001", lcrCents: 7000 }, project: { id: "project_1", name: "Project One", shortCode: "PROJ-1" }, roleEntity: null, demandRequirement: null, }); const tx = { assignment: { findUnique: vi.fn().mockResolvedValue({ id: "assignment_1", resourceId: "resource_1", projectId: "project_1", demandRequirementId: null, startDate: new Date("2026-06-01T00:00:00.000Z"), endDate: new Date("2026-06-05T00:00:00.000Z"), hoursPerDay: 6, percentage: 75, role: "Designer", roleId: null, dailyCostCents: 42000, status: "PROPOSED", metadata: {}, createdAt: new Date("2026-03-29T00:00:00.000Z"), updatedAt: new Date("2026-03-29T00:00:00.000Z"), resource: { id: "resource_1", displayName: "Carol Danvers", eid: "EMP-001", lcrCents: 7000 }, project: { id: "project_1", name: "Project One", shortCode: "PROJ-1" }, roleEntity: null, demandRequirement: null, }), update: assignmentUpdate, }, auditLog: { create: auditCreate, }, }; const transaction = vi.fn().mockImplementation(async (callback: (inner: typeof tx) => Promise) => callback(tx)); const ctx = createToolContext( { assignment: { findUnique: vi.fn().mockResolvedValue({ id: "assignment_1", resourceId: "resource_1", projectId: "project_1", demandRequirementId: null, startDate: new Date("2026-06-01T00:00:00.000Z"), endDate: new Date("2026-06-05T00:00:00.000Z"), hoursPerDay: 6, percentage: 75, role: "Designer", roleId: null, dailyCostCents: 42000, status: "PROPOSED", metadata: {}, createdAt: new Date("2026-03-29T00:00:00.000Z"), updatedAt: new Date("2026-03-29T00:00:00.000Z"), resource: { id: "resource_1", displayName: "Carol Danvers", eid: "EMP-001", lcrCents: 7000 }, project: { id: "project_1", name: "Project One", shortCode: "PROJ-1" }, roleEntity: null, demandRequirement: null, }), }, project: { findUnique: vi.fn().mockResolvedValue({ id: "project_1", name: "Project One", shortCode: "PROJ-1", budgetCents: 0, }), }, webhook: { findMany: vi.fn().mockResolvedValue([]), }, $transaction: transaction, }, { userRole: SystemRole.ADMIN, permissions: [PermissionKey.MANAGE_ALLOCATIONS], }, ); const result = await executeTool( "cancel_allocation", JSON.stringify({ allocationId: "assignment_1" }), ctx, ); expect(JSON.parse(result.content)).toEqual(expect.objectContaining({ success: true, message: "Cancelled allocation: Carol Danvers → Project One (PROJ-1), 2026-06-01 to 2026-06-05", })); expect(ctx.db.assignment.findUnique).toHaveBeenCalledWith({ where: { id: "assignment_1" }, include: expect.objectContaining({ resource: expect.any(Object), project: expect.any(Object), }), }); expect(assignmentUpdate).toHaveBeenCalledWith(expect.objectContaining({ where: { id: "assignment_1" }, data: expect.objectContaining({ status: "CANCELLED", }), })); expect(auditCreate).toHaveBeenCalledTimes(1); }); it("returns a stable assistant error for duplicate allocations", async () => { const ctx = createToolContext( { resource: { findUnique: vi.fn() .mockResolvedValueOnce({ id: "resource_1", eid: "EMP-001", displayName: "Carol Danvers", }) .mockResolvedValueOnce({ id: "resource_1", eid: "EMP-001", displayName: "Carol Danvers", lcrCents: 7000, availability: { monday: 8, tuesday: 8, wednesday: 8, thursday: 8, friday: 8 }, }), findFirst: vi.fn().mockResolvedValue(null), }, project: { findUnique: vi.fn() .mockResolvedValueOnce(null) .mockResolvedValueOnce({ id: "project_1", name: "Project One", shortCode: "PROJ-1", status: "ACTIVE", responsiblePerson: "Peter Parker", }), findFirst: vi.fn().mockResolvedValue(null), }, assignment: { findMany: vi.fn().mockResolvedValue([ { id: "assignment_existing", resourceId: "resource_1", projectId: "project_1", startDate: new Date("2026-06-01T00:00:00.000Z"), endDate: new Date("2026-06-05T00:00:00.000Z"), status: "PROPOSED", resource: { id: "resource_1", displayName: "Carol Danvers", eid: "EMP-001" }, project: { id: "project_1", name: "Project One", shortCode: "PROJ-1" }, }, ]), }, }, { userRole: SystemRole.ADMIN, permissions: [PermissionKey.MANAGE_ALLOCATIONS], }, ); const result = await executeTool( "create_allocation", JSON.stringify({ resourceId: "resource_1", projectId: "project_1", startDate: "2026-06-01", endDate: "2026-06-05", hoursPerDay: 6, }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "Allocation already exists for this resource/project/dates. No new allocation created.", }); }); it("returns a stable assistant error when allocation cancellation cannot resolve an assignment", async () => { const ctx = createToolContext( { assignment: { findUnique: vi.fn().mockRejectedValue( new TRPCError({ code: "NOT_FOUND", message: "Assignment not found" }), ), }, }, { userRole: SystemRole.ADMIN, permissions: [PermissionKey.MANAGE_ALLOCATIONS], }, ); const result = await executeTool( "cancel_allocation", JSON.stringify({ allocationId: "assignment_missing" }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "Allocation not found with the given criteria.", }); }); it("returns stable assistant errors when allocation cancellation fails during the update step", async () => { const assignment = { id: "assignment_1", resourceId: "resource_1", projectId: "project_1", demandRequirementId: null, startDate: new Date("2026-06-01T00:00:00.000Z"), endDate: new Date("2026-06-05T00:00:00.000Z"), hoursPerDay: 6, percentage: 75, role: "Designer", roleId: null, dailyCostCents: 42000, status: "PROPOSED", metadata: {}, createdAt: new Date("2026-03-29T00:00:00.000Z"), updatedAt: new Date("2026-03-29T00:00:00.000Z"), resource: { id: "resource_1", displayName: "Carol Danvers", eid: "EMP-001", lcrCents: 7000 }, project: { id: "project_1", name: "Project One", shortCode: "PROJ-1" }, roleEntity: null, demandRequirement: null, }; const errors = [ new TRPCError({ code: "NOT_FOUND", message: "Assignment not found" }), { code: "P2025", message: "Record not found", meta: { modelName: "Assignment" }, }, ] as const; for (const error of errors) { const tx = { assignment: { findUnique: vi.fn().mockResolvedValue(assignment), update: vi.fn().mockRejectedValue(error), }, auditLog: { create: vi.fn(), }, }; const ctx = createToolContext( { assignment: { findUnique: vi.fn().mockResolvedValue(assignment), }, webhook: { findMany: vi.fn().mockResolvedValue([]), }, $transaction: vi.fn().mockImplementation(async (callback: (inner: typeof tx) => Promise) => callback(tx)), }, { userRole: SystemRole.ADMIN, permissions: [PermissionKey.MANAGE_ALLOCATIONS], }, ); const result = await executeTool( "cancel_allocation", JSON.stringify({ allocationId: "assignment_1" }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "Allocation not found with the given criteria.", }); } }); it("returns a stable assistant error when allocation creation receives an invalid start date", async () => { const ctx = createToolContext( { resource: { findUnique: vi.fn() .mockResolvedValueOnce(null) .mockResolvedValueOnce({ id: "resource_1", displayName: "Carol Danvers", eid: "EMP-001", chapter: "Delivery", isActive: true, fte: 1, lcrCents: 7000, availability: { monday: 8, tuesday: 8, wednesday: 8, thursday: 8, friday: 8 }, }), findFirst: vi.fn().mockResolvedValue(null), }, project: { findUnique: vi.fn() .mockResolvedValueOnce(null) .mockResolvedValueOnce({ id: "project_1", name: "Project One", shortCode: "PROJ-1", status: "ACTIVE", responsiblePerson: "Peter Parker", }), findFirst: vi.fn().mockResolvedValue(null), }, }, { userRole: SystemRole.ADMIN, permissions: [PermissionKey.MANAGE_ALLOCATIONS], }, ); const result = await executeTool( "create_allocation", JSON.stringify({ resourceId: "resource_1", projectId: "project_1", startDate: "2026-13-01", endDate: "2026-06-05", hoursPerDay: 6, }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "Invalid startDate: 2026-13-01", }); }); it("routes allocation status updates through the real allocation router path", async () => { const auditCreate = vi.fn().mockResolvedValue({ id: "audit_1" }); const assignmentUpdate = vi.fn().mockResolvedValue({ id: "assignment_1", resourceId: "resource_1", projectId: "project_1", demandRequirementId: null, startDate: new Date("2026-06-01T00:00:00.000Z"), endDate: new Date("2026-06-05T00:00:00.000Z"), hoursPerDay: 6, percentage: 75, role: "Designer", roleId: null, dailyCostCents: 42000, status: "ACTIVE", metadata: {}, createdAt: new Date("2026-03-29T00:00:00.000Z"), updatedAt: new Date("2026-03-29T00:00:00.000Z"), resource: { id: "resource_1", displayName: "Carol Danvers", eid: "EMP-001", lcrCents: 7000 }, project: { id: "project_1", name: "Project One", shortCode: "PROJ-1" }, roleEntity: null, demandRequirement: null, }); const tx = { assignment: { findUnique: vi.fn().mockResolvedValue({ id: "assignment_1", resourceId: "resource_1", projectId: "project_1", demandRequirementId: null, startDate: new Date("2026-06-01T00:00:00.000Z"), endDate: new Date("2026-06-05T00:00:00.000Z"), hoursPerDay: 6, percentage: 75, role: "Designer", roleId: null, dailyCostCents: 42000, status: "PROPOSED", metadata: {}, createdAt: new Date("2026-03-29T00:00:00.000Z"), updatedAt: new Date("2026-03-29T00:00:00.000Z"), resource: { id: "resource_1", displayName: "Carol Danvers", eid: "EMP-001", lcrCents: 7000 }, project: { id: "project_1", name: "Project One", shortCode: "PROJ-1" }, roleEntity: null, demandRequirement: null, }), update: assignmentUpdate, }, auditLog: { create: auditCreate, }, }; const transaction = vi.fn().mockImplementation(async (callback: (inner: typeof tx) => Promise) => callback(tx)); const ctx = createToolContext( { assignment: { findUnique: vi.fn().mockResolvedValue({ id: "assignment_1", resourceId: "resource_1", projectId: "project_1", demandRequirementId: null, startDate: new Date("2026-06-01T00:00:00.000Z"), endDate: new Date("2026-06-05T00:00:00.000Z"), hoursPerDay: 6, percentage: 75, role: "Designer", roleId: null, dailyCostCents: 42000, status: "PROPOSED", metadata: {}, createdAt: new Date("2026-03-29T00:00:00.000Z"), updatedAt: new Date("2026-03-29T00:00:00.000Z"), resource: { id: "resource_1", displayName: "Carol Danvers", eid: "EMP-001", lcrCents: 7000 }, project: { id: "project_1", name: "Project One", shortCode: "PROJ-1" }, roleEntity: null, demandRequirement: null, }), }, project: { findUnique: vi.fn().mockResolvedValue({ id: "project_1", name: "Project One", shortCode: "PROJ-1", budgetCents: 0, }), }, webhook: { findMany: vi.fn().mockResolvedValue([]), }, $transaction: transaction, }, { userRole: SystemRole.ADMIN, permissions: [PermissionKey.MANAGE_ALLOCATIONS], }, ); const result = await executeTool( "update_allocation_status", JSON.stringify({ allocationId: "assignment_1", newStatus: "ACTIVE" }), ctx, ); expect(JSON.parse(result.content)).toEqual(expect.objectContaining({ success: true, message: "Updated allocation status: Carol Danvers → Project One (PROJ-1), 2026-06-01 to 2026-06-05: PROPOSED → ACTIVE", })); expect(ctx.db.assignment.findUnique).toHaveBeenCalledWith({ where: { id: "assignment_1" }, include: expect.objectContaining({ resource: expect.any(Object), project: expect.any(Object), }), }); expect(assignmentUpdate).toHaveBeenCalledWith(expect.objectContaining({ where: { id: "assignment_1" }, data: expect.objectContaining({ status: "ACTIVE", }), })); expect(auditCreate).toHaveBeenCalledTimes(1); }); it("returns a stable assistant error when allocation status update cannot resolve an assignment", async () => { const ctx = createToolContext( { assignment: { findUnique: vi.fn().mockRejectedValue( new TRPCError({ code: "NOT_FOUND", message: "Assignment not found" }), ), }, }, { userRole: SystemRole.ADMIN, permissions: [PermissionKey.MANAGE_ALLOCATIONS], }, ); const result = await executeTool( "update_allocation_status", JSON.stringify({ allocationId: "assignment_missing", newStatus: "ACTIVE" }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "Allocation not found with the given criteria.", }); }); it("returns stable assistant errors when allocation status updates fail during the update step", async () => { const assignment = { id: "assignment_1", resourceId: "resource_1", projectId: "project_1", demandRequirementId: null, startDate: new Date("2026-06-01T00:00:00.000Z"), endDate: new Date("2026-06-05T00:00:00.000Z"), hoursPerDay: 6, percentage: 75, role: "Designer", roleId: null, dailyCostCents: 42000, status: "PROPOSED", metadata: {}, createdAt: new Date("2026-03-29T00:00:00.000Z"), updatedAt: new Date("2026-03-29T00:00:00.000Z"), resource: { id: "resource_1", displayName: "Carol Danvers", eid: "EMP-001", lcrCents: 7000 }, project: { id: "project_1", name: "Project One", shortCode: "PROJ-1" }, roleEntity: null, demandRequirement: null, }; const errors = [ new TRPCError({ code: "NOT_FOUND", message: "Assignment not found" }), { code: "P2025", message: "Record not found", meta: { modelName: "Assignment" }, }, ] as const; for (const error of errors) { const tx = { assignment: { findUnique: vi.fn().mockResolvedValue(assignment), update: vi.fn().mockRejectedValue(error), }, auditLog: { create: vi.fn(), }, }; const ctx = createToolContext( { assignment: { findUnique: vi.fn().mockResolvedValue(assignment), }, webhook: { findMany: vi.fn().mockResolvedValue([]), }, $transaction: vi.fn().mockImplementation(async (callback: (inner: typeof tx) => Promise) => callback(tx)), }, { userRole: SystemRole.ADMIN, permissions: [PermissionKey.MANAGE_ALLOCATIONS], }, ); const result = await executeTool( "update_allocation_status", JSON.stringify({ allocationId: "assignment_1", newStatus: "ACTIVE" }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: "Allocation not found with the given criteria.", }); } }); it("routes project cover generation through the real project router path", async () => { const projectFindUnique = vi.fn() .mockResolvedValueOnce({ id: "project_1", name: "Project One", shortCode: "PROJ-1", status: "ACTIVE", orderType: "CHARGEABLE", allocationType: "INT", budgetCents: 0, winProbability: 100, startDate: new Date("2026-05-01T00:00:00.000Z"), endDate: new Date("2026-06-30T00:00:00.000Z"), responsiblePerson: "Peter Parker", client: null, utilizationCategory: null, _count: { assignments: 0, estimates: 0 }, }) .mockResolvedValueOnce({ id: "project_1", name: "Project One", client: { name: "Wayne Enterprises" }, }); const projectUpdate = vi.fn().mockResolvedValue({ id: "project_1", coverImageUrl: "data:image/png;base64,ZmFrZQ==", }); const ctx = createToolContext( { project: { findUnique: projectFindUnique, update: projectUpdate, }, assignment: { findMany: vi.fn().mockResolvedValue([]), }, systemSettings: { findUnique: vi.fn().mockResolvedValue({ id: "singleton", imageProvider: "dalle", aiProvider: "openai", azureOpenAiApiKey: "sk-test", azureOpenAiDeployment: "gpt-4o-mini", }), }, }, { userRole: SystemRole.ADMIN, permissions: [PermissionKey.MANAGE_PROJECTS], }, ); const result = await executeTool( "generate_project_cover", JSON.stringify({ projectId: "project_1", prompt: "Blue studio lighting" }), ctx, ); expect(projectUpdate).toHaveBeenCalledWith({ where: { id: "project_1" }, data: { coverImageUrl: "data:image/png;base64,ZmFrZQ==" }, }); expect(projectFindUnique).toHaveBeenCalledWith({ where: { id: "project_1" }, select: expect.objectContaining({ id: true, shortCode: true, name: true, status: true, responsiblePerson: true, startDate: true, endDate: true, }), }); expect(JSON.parse(result.content)).toEqual(expect.objectContaining({ success: true, message: 'Generated cover art for project "Project One"', })); }); it("routes project cover removal through the real project router path", async () => { const projectFindUnique = vi.fn() .mockResolvedValueOnce({ id: "project_1", name: "Project One", shortCode: "PROJ-1", status: "ACTIVE", orderType: "CHARGEABLE", allocationType: "INT", budgetCents: 0, winProbability: 100, startDate: new Date("2026-05-01T00:00:00.000Z"), endDate: new Date("2026-06-30T00:00:00.000Z"), responsiblePerson: "Peter Parker", client: null, utilizationCategory: null, _count: { assignments: 0, estimates: 0 }, }) .mockResolvedValueOnce({ id: "project_1", name: "Project One", }); const projectUpdate = vi.fn().mockResolvedValue({ id: "project_1", coverImageUrl: null, }); const ctx = createToolContext( { project: { findUnique: projectFindUnique, update: projectUpdate, }, assignment: { findMany: vi.fn().mockResolvedValue([]), }, }, { userRole: SystemRole.ADMIN, permissions: [PermissionKey.MANAGE_PROJECTS], }, ); const result = await executeTool( "remove_project_cover", JSON.stringify({ projectId: "project_1" }), ctx, ); expect(projectUpdate).toHaveBeenCalledWith({ where: { id: "project_1" }, data: { coverImageUrl: null }, }); expect(projectFindUnique).toHaveBeenCalledWith({ where: { id: "project_1" }, select: expect.objectContaining({ id: true, shortCode: true, name: true, status: true, responsiblePerson: true, startDate: true, endDate: true, }), }); expect(JSON.parse(result.content)).toEqual(expect.objectContaining({ success: true, message: 'Removed cover art from project "Project One"', })); }); it("routes project narrative generation through the real insights router path", async () => { const projectFindUnique = vi.fn().mockResolvedValue({ id: "project_1", name: "Project One", shortCode: "PROJ-1", status: "ACTIVE", startDate: new Date("2026-03-01T00:00:00.000Z"), endDate: new Date("2026-06-30T00:00:00.000Z"), budgetCents: 250_000_00, dynamicFields: { existingFlag: true }, demandRequirements: [ { headcount: 2, _count: { assignments: 1 }, }, ], assignments: [ { status: "ACTIVE", dailyCostCents: 42_000, startDate: new Date("2026-03-10T00:00:00.000Z"), endDate: new Date("2026-03-20T00:00:00.000Z"), resource: { displayName: "Carol Danvers" }, }, ], }); const projectUpdate = vi.fn().mockResolvedValue({ id: "project_1", dynamicFields: { existingFlag: true, aiNarrative: "Project is on track overall, but staffing remains partially open.", }, }); const ctx = createToolContext( { project: { findUnique: projectFindUnique, update: projectUpdate, }, systemSettings: { findUnique: vi.fn().mockResolvedValue({ id: "singleton", aiProvider: "openai", azureOpenAiApiKey: "sk-test", azureOpenAiDeployment: "gpt-4o-mini", }), }, }, { userRole: SystemRole.CONTROLLER, }, ); const result = await executeTool( "generate_project_narrative", JSON.stringify({ projectId: "project_1" }), ctx, ); expect(projectFindUnique).toHaveBeenCalledWith({ where: { id: "project_1" }, include: { demandRequirements: { select: { id: true, role: true, headcount: true, hoursPerDay: true, startDate: true, endDate: true, status: true, _count: { select: { assignments: true } }, }, }, assignments: { select: { id: true, role: true, hoursPerDay: true, startDate: true, endDate: true, status: true, dailyCostCents: true, resource: { select: { displayName: true } }, }, }, }, }); expect(projectUpdate).toHaveBeenCalledWith({ where: { id: "project_1" }, data: { dynamicFields: expect.objectContaining({ existingFlag: true, aiNarrative: "Project is on track overall, but staffing remains partially open.", aiNarrativeGeneratedAt: expect.any(String), }), }, }); expect(JSON.parse(result.content)).toEqual(expect.objectContaining({ narrative: "Project is on track overall, but staffing remains partially open.", generatedAt: expect.any(String), })); }); });