import { beforeEach, describe, expect, it, vi } from "vitest"; import { PermissionKey, SystemRole } from "@nexus/shared"; import { createProject, createToolContext, executeTool, } from "./assistant-tools-project-admin-test-helpers.js"; describe("assistant project admin update tools", () => { beforeEach(() => { vi.clearAllMocks(); }); 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(createProject()) .mockResolvedValueOnce(createProject({ 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(createProject()) .mockResolvedValueOnce(createProject({ 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("returns a stable assistant error when no update fields are provided", async () => { const projectUpdate = vi.fn(); const ctx = createToolContext( { project: { findUnique: vi.fn().mockResolvedValueOnce(null).mockResolvedValueOnce(createProject()), findFirst: vi.fn().mockResolvedValue(null), update: projectUpdate, }, }, { userRole: SystemRole.ADMIN, permissions: [PermissionKey.MANAGE_PROJECTS], }, ); const result = await executeTool("update_project", JSON.stringify({ id: "PROJ-1" }), ctx); expect(JSON.parse(result.content)).toEqual({ error: "No fields to update", }); expect(projectUpdate).not.toHaveBeenCalled(); }); it("returns responsible person resolver errors unchanged during update", async () => { const projectUpdate = vi.fn(); const ctx = createToolContext( { project: { findUnique: vi.fn().mockResolvedValueOnce(null).mockResolvedValueOnce(createProject()), findFirst: vi.fn().mockResolvedValue(null), update: projectUpdate, }, resource: { findFirst: vi.fn().mockResolvedValue(null), findMany: vi.fn().mockResolvedValue([]), }, }, { userRole: SystemRole.ADMIN, permissions: [PermissionKey.MANAGE_PROJECTS], }, ); const result = await executeTool( "update_project", JSON.stringify({ id: "PROJ-1", responsiblePerson: "Mary Jane" }), ctx, ); expect(JSON.parse(result.content)).toEqual({ error: 'No active resource found matching "Mary Jane". The responsible person must be an existing resource.', }); expect(projectUpdate).not.toHaveBeenCalled(); }); });