import { describe, expect, it } from "vitest"; import { PermissionKey, SystemRole, type PermissionKey as PermissionKeyValue } from "@nexus/shared"; import { getAvailableAssistantTools } from "../router/assistant-tool-policy.js"; function getToolNames(permissions: PermissionKeyValue[], userRole: SystemRole = SystemRole.ADMIN) { return getAvailableAssistantTools(new Set(permissions), userRole).map( (tool) => tool.function.name, ); } describe("assistant tool policy planning flows", () => { it("requires both controller role and advanced assistant access for timeline detail tools", () => { const controllerWithAdvanced = getToolNames( [PermissionKey.USE_ASSISTANT_ADVANCED_TOOLS], SystemRole.CONTROLLER, ); const controllerWithoutAdvanced = getToolNames([], SystemRole.CONTROLLER); const userWithAdvanced = getToolNames( [PermissionKey.USE_ASSISTANT_ADVANCED_TOOLS], SystemRole.USER, ); expect(controllerWithAdvanced).toContain("get_timeline_entries_view"); expect(controllerWithAdvanced).toContain("get_timeline_holiday_overlays"); expect(controllerWithAdvanced).toContain("get_project_timeline_context"); expect(controllerWithAdvanced).toContain("preview_project_shift"); expect(controllerWithoutAdvanced).not.toContain("get_timeline_entries_view"); expect(controllerWithoutAdvanced).not.toContain("get_timeline_holiday_overlays"); expect(controllerWithoutAdvanced).not.toContain("get_project_timeline_context"); expect(controllerWithoutAdvanced).not.toContain("preview_project_shift"); expect(userWithAdvanced).not.toContain("get_timeline_entries_view"); expect(userWithAdvanced).not.toContain("get_timeline_holiday_overlays"); expect(userWithAdvanced).not.toContain("get_project_timeline_context"); expect(userWithAdvanced).not.toContain("preview_project_shift"); }); it("exposes self-service timeline tools to authenticated users without advanced assistant access", () => { const userNames = getToolNames([], SystemRole.USER); const viewerNames = getToolNames([], SystemRole.VIEWER); const controllerNames = getToolNames([], SystemRole.CONTROLLER); expect(userNames).toContain("get_my_timeline_entries_view"); expect(userNames).toContain("get_my_timeline_holiday_overlays"); expect(viewerNames).toContain("get_my_timeline_entries_view"); expect(viewerNames).toContain("get_my_timeline_holiday_overlays"); expect(controllerNames).toContain("get_my_timeline_entries_view"); expect(controllerNames).toContain("get_my_timeline_holiday_overlays"); }); it("keeps timeline write parity tools behind manager/admin role, manageAllocations, and advanced assistant access", () => { const managerNames = getToolNames( [PermissionKey.MANAGE_ALLOCATIONS, PermissionKey.USE_ASSISTANT_ADVANCED_TOOLS], SystemRole.MANAGER, ); const userNames = getToolNames( [PermissionKey.MANAGE_ALLOCATIONS, PermissionKey.USE_ASSISTANT_ADVANCED_TOOLS], SystemRole.USER, ); const missingAdvancedNames = getToolNames( [PermissionKey.MANAGE_ALLOCATIONS], SystemRole.MANAGER, ); expect(managerNames).toContain("update_timeline_allocation_inline"); expect(managerNames).toContain("apply_timeline_project_shift"); expect(managerNames).toContain("quick_assign_timeline_resource"); expect(managerNames).toContain("batch_quick_assign_timeline_resources"); expect(managerNames).toContain("batch_shift_timeline_allocations"); expect(userNames).not.toContain("update_timeline_allocation_inline"); expect(userNames).not.toContain("apply_timeline_project_shift"); expect(userNames).not.toContain("quick_assign_timeline_resource"); expect(userNames).not.toContain("batch_quick_assign_timeline_resources"); expect(userNames).not.toContain("batch_shift_timeline_allocations"); expect(missingAdvancedNames).not.toContain("update_timeline_allocation_inline"); expect(missingAdvancedNames).not.toContain("quick_assign_timeline_resource"); }); it("keeps estimate lifecycle mutations behind manager/admin role and their router permissions", () => { const managerProjectNames = getToolNames([PermissionKey.MANAGE_PROJECTS], SystemRole.MANAGER); const managerAllocationNames = getToolNames( [PermissionKey.MANAGE_ALLOCATIONS], SystemRole.MANAGER, ); const userProjectNames = getToolNames([PermissionKey.MANAGE_PROJECTS], SystemRole.USER); expect(managerProjectNames).toContain("create_estimate"); expect(managerProjectNames).toContain("clone_estimate"); expect(managerProjectNames).toContain("update_estimate_draft"); expect(managerProjectNames).toContain("submit_estimate_version"); expect(managerProjectNames).toContain("approve_estimate_version"); expect(managerProjectNames).toContain("create_estimate_revision"); expect(managerProjectNames).toContain("create_estimate_export"); expect(managerProjectNames).toContain("generate_estimate_weekly_phasing"); expect(managerProjectNames).toContain("update_estimate_commercial_terms"); expect(managerProjectNames).not.toContain("create_estimate_planning_handoff"); expect(managerAllocationNames).toContain("create_estimate_planning_handoff"); expect(managerAllocationNames).not.toContain("create_estimate"); expect(userProjectNames).not.toContain("create_estimate"); expect(userProjectNames).not.toContain("clone_estimate"); expect(userProjectNames).not.toContain("update_estimate_draft"); expect(userProjectNames).not.toContain("submit_estimate_version"); expect(userProjectNames).not.toContain("approve_estimate_version"); expect(userProjectNames).not.toContain("create_estimate_revision"); expect(userProjectNames).not.toContain("create_estimate_export"); expect(userProjectNames).not.toContain("generate_estimate_weekly_phasing"); expect(userProjectNames).not.toContain("update_estimate_commercial_terms"); expect(userProjectNames).not.toContain("create_estimate_planning_handoff"); }); it("keeps estimate read tools aligned to controller/manager/admin visibility and cost requirements", () => { const controllerNames = getToolNames([PermissionKey.VIEW_COSTS], SystemRole.CONTROLLER); const controllerWithoutCosts = getToolNames([], SystemRole.CONTROLLER); const managerNames = getToolNames([PermissionKey.VIEW_COSTS], SystemRole.MANAGER); const managerWithoutCosts = getToolNames([], SystemRole.MANAGER); const userNames = getToolNames([PermissionKey.VIEW_COSTS], SystemRole.USER); expect(controllerNames).toContain("search_estimates"); expect(controllerNames).toContain("get_estimate_detail"); expect(controllerNames).toContain("list_estimate_versions"); expect(controllerNames).toContain("get_estimate_version_snapshot"); expect(controllerNames).toContain("get_estimate_weekly_phasing"); expect(controllerNames).toContain("get_estimate_commercial_terms"); expect(controllerWithoutCosts).toContain("search_estimates"); expect(controllerWithoutCosts).not.toContain("get_estimate_detail"); expect(controllerWithoutCosts).toContain("list_estimate_versions"); expect(controllerWithoutCosts).not.toContain("get_estimate_version_snapshot"); expect(controllerWithoutCosts).toContain("get_estimate_weekly_phasing"); expect(controllerWithoutCosts).toContain("get_estimate_commercial_terms"); expect(managerNames).toContain("search_estimates"); expect(managerNames).toContain("get_estimate_detail"); expect(managerNames).toContain("list_estimate_versions"); expect(managerNames).toContain("get_estimate_version_snapshot"); expect(managerNames).toContain("get_estimate_weekly_phasing"); expect(managerNames).toContain("get_estimate_commercial_terms"); expect(managerWithoutCosts).toContain("search_estimates"); expect(managerWithoutCosts).toContain("list_estimate_versions"); expect(managerWithoutCosts).not.toContain("get_estimate_version_snapshot"); expect(userNames).not.toContain("search_estimates"); expect(userNames).not.toContain("get_estimate_detail"); expect(userNames).not.toContain("list_estimate_versions"); expect(userNames).not.toContain("get_estimate_version_snapshot"); expect(userNames).not.toContain("get_estimate_weekly_phasing"); expect(userNames).not.toContain("get_estimate_commercial_terms"); }); });