basant307/AI_Governance_Project
048
1/**2 * @license3 * Copyright 2025 Qwen4 * SPDX-License-Identifier: Apache-2.05 */6 7import { describe, it, expect, vi, beforeEach } from 'vitest';8import { EnterPlanModeTool } from './enterPlanMode.js';9import { ApprovalMode, type Config } from '../config/config.js';10import { runWithAgentContext } from '../agents/runtime/agent-context.js';11import { runWithTeammateIdentity } from '../agents/team/identity.js';12 13describe('EnterPlanModeTool', () => {14 let tool: EnterPlanModeTool;15 let mockConfig: Config;16 let approvalMode: ApprovalMode;17 let savedPrePlanMode: ApprovalMode | undefined;18 19 beforeEach(() => {20 approvalMode = ApprovalMode.DEFAULT;21 savedPrePlanMode = undefined;22 mockConfig = {23 getApprovalMode: vi.fn(() => approvalMode),24 getPrePlanMode: vi.fn(() => savedPrePlanMode ?? ApprovalMode.DEFAULT),25 setApprovalMode: vi.fn((mode: ApprovalMode) => {26 if (mode === ApprovalMode.PLAN && approvalMode !== ApprovalMode.PLAN) {27 savedPrePlanMode = approvalMode;28 }29 approvalMode = mode;30 }),31 isInteractive: vi.fn(() => true),32 getExperimentalZedIntegration: vi.fn(() => false),33 getInputFormat: vi.fn(() => undefined),34 } as unknown as Config;35 36 tool = new EnterPlanModeTool(mockConfig);37 });38 39 describe('constructor and metadata', () => {40 it('should have correct tool name', () => {41 expect(tool.name).toBe('enter_plan_mode');42 expect(EnterPlanModeTool.Name).toBe('enter_plan_mode');43 });44 45 it('should have correct display name', () => {46 expect(tool.displayName).toBe('EnterPlanMode');47 });48 49 it('should have correct kind', () => {50 expect(tool.kind).toBe('think');51 });52 53 it('should require user opt-in in the tool description', () => {54 expect(tool.description).toContain(55 'only after the user explicitly asks to switch into plan mode',56 );57 expect(tool.description).toContain(58 'If plan mode seems helpful but the user has not asked for it, ask first',59 );60 expect(tool.description).not.toContain(61 'before doing uncertain or complex work',62 );63 expect(tool.description).not.toContain('if complexity rises');64 });65 66 it('should not defer (always visible)', () => {67 expect(tool.shouldDefer).toBe(false);68 });69 70 it('should have empty-object schema', () => {71 expect(tool.schema.parametersJsonSchema).toEqual({72 type: 'object',73 properties: {},74 additionalProperties: false,75 $schema: 'http://json-schema.org/draft-07/schema#',76 });77 });78 });79 80 describe('getDefaultPermission', () => {81 it('should always return allow', async () => {82 const invocation = tool.build({});83 const permission = await invocation.getDefaultPermission();84 expect(permission).toBe('allow');85 });86 });87 88 describe('execute', () => {89 it('should switch from DEFAULT to PLAN and save prePlanMode', async () => {90 approvalMode = ApprovalMode.DEFAULT;91 const invocation = tool.build({});92 const result = await invocation.execute(new AbortController().signal);93 94 expect(mockConfig.setApprovalMode).toHaveBeenCalledWith(95 ApprovalMode.PLAN,96 { enteredByModel: true },97 );98 expect(approvalMode).toBe(ApprovalMode.PLAN);99 expect(savedPrePlanMode).toBe(ApprovalMode.DEFAULT);100 expect(result.llmContent).toContain('Plan mode is now active');101 });102 103 it('should switch from AUTO_EDIT to PLAN', async () => {104 approvalMode = ApprovalMode.AUTO_EDIT;105 const invocation = tool.build({});106 await invocation.execute(new AbortController().signal);107 108 expect(mockConfig.setApprovalMode).toHaveBeenCalledWith(109 ApprovalMode.PLAN,110 { enteredByModel: true },111 );112 expect(savedPrePlanMode).toBe(ApprovalMode.AUTO_EDIT);113 });114 115 it('should switch from AUTO to PLAN', async () => {116 approvalMode = ApprovalMode.AUTO;117 const invocation = tool.build({});118 await invocation.execute(new AbortController().signal);119 120 expect(mockConfig.setApprovalMode).toHaveBeenCalledWith(121 ApprovalMode.PLAN,122 { enteredByModel: true },123 );124 expect(savedPrePlanMode).toBe(ApprovalMode.AUTO);125 });126 127 it('should switch from YOLO to PLAN', async () => {128 approvalMode = ApprovalMode.YOLO;129 const invocation = tool.build({});130 await invocation.execute(new AbortController().signal);131 132 expect(mockConfig.setApprovalMode).toHaveBeenCalledWith(133 ApprovalMode.PLAN,134 { enteredByModel: true },135 );136 expect(savedPrePlanMode).toBe(ApprovalMode.YOLO);137 });138 139 it('should be idempotent: already in PLAN does not call setApprovalMode', async () => {140 approvalMode = ApprovalMode.PLAN;141 savedPrePlanMode = ApprovalMode.AUTO;142 const invocation = tool.build({});143 const result = await invocation.execute(new AbortController().signal);144 145 expect(mockConfig.setApprovalMode).not.toHaveBeenCalled();146 expect(savedPrePlanMode).toBe(ApprovalMode.AUTO);147 expect(result.llmContent).toContain('Plan mode is now active');148 });149 150 it('should return error when setApprovalMode throws', async () => {151 (152 mockConfig.setApprovalMode as ReturnType<typeof vi.fn>153 ).mockImplementation(() => {154 throw new Error('trust gate');155 });156 const invocation = tool.build({});157 const result = await invocation.execute(new AbortController().signal);158 159 expect(result.llmContent).toContain('Failed to enter plan mode');160 expect(result.llmContent).toContain('trust gate');161 });162 163 it('rejects inside subagent context without changing approval mode', async () => {164 approvalMode = ApprovalMode.DEFAULT;165 const invocation = tool.build({});166 167 const result = await runWithAgentContext('agent-1', () =>168 invocation.execute(new AbortController().signal),169 );170 171 expect(result.llmContent).toContain('not available inside subagents');172 expect(result.llmContent).toContain('return your plan');173 expect(result.error?.message).toBe(result.llmContent);174 expect(mockConfig.setApprovalMode).not.toHaveBeenCalled();175 expect(approvalMode).toBe(ApprovalMode.DEFAULT);176 });177 178 it('rejects inside teammate context without changing approval mode', async () => {179 approvalMode = ApprovalMode.AUTO_EDIT;180 const invocation = tool.build({});181 182 const result = await runWithTeammateIdentity(183 {184 agentId: 'agent@test',185 agentName: 'agent',186 teamName: 'test',187 isTeamLead: false,188 },189 () => invocation.execute(new AbortController().signal),190 );191 192 expect(result.llmContent).toContain('not available inside subagents');193 expect(result.llmContent).toContain('return your plan');194 expect(result.error?.message).toBe(result.llmContent);195 expect(mockConfig.setApprovalMode).not.toHaveBeenCalled();196 expect(approvalMode).toBe(ApprovalMode.AUTO_EDIT);197 });198 });199});200 