Download packages/core/src/scheduler/tool-modifier.test.ts from SaylorTwift/gemini-cli: direct link, hf CLI and curl.
- Browser
- Download file 8.44 kB
-
https://huggingface.co/SaylorTwift/gemini-cli/resolve/main/packages/core/src/scheduler/tool-modifier.test.ts
- Command line
-
hf download hf://SaylorTwift/gemini-cli/packages/core/src/scheduler/tool-modifier.test.ts
-
curl -L -o tool-modifier.test.ts https://huggingface.co/SaylorTwift/gemini-cli/resolve/main/packages/core/src/scheduler/tool-modifier.test.ts
8.44 kB
| /** | |
| * @license | |
| * Copyright 2025 Google LLC | |
| * SPDX-License-Identifier: Apache-2.0 | |
| */ | |
| import { describe, it, expect, vi, beforeEach, type Mock } from 'vitest'; | |
| import { ToolModificationHandler } from './tool-modifier.js'; | |
| import { | |
| CoreToolCallStatus, | |
| type WaitingToolCall, | |
| type ToolCallRequestInfo, | |
| } from './types.js'; | |
| import * as modifiableToolModule from '../tools/modifiable-tool.js'; | |
| import type { ModifyContext } from '../tools/modifiable-tool.js'; | |
| import * as Diff from 'diff'; | |
| import { MockModifiableTool, MockTool } from '../test-utils/mock-tool.js'; | |
| import type { | |
| ToolResult, | |
| ToolInvocation, | |
| ToolConfirmationPayload, | |
| } from '../tools/tools.js'; | |
| // Mock the modules that export functions we need to control | |
| vi.mock('diff', () => ({ | |
| createPatch: vi.fn(), | |
| diffLines: vi.fn(), | |
| })); | |
| vi.mock('../tools/modifiable-tool.js', () => ({ | |
| isModifiableDeclarativeTool: vi.fn(), | |
| modifyWithEditor: vi.fn(), | |
| })); | |
| type MockModifyContext = { | |
| [K in keyof ModifyContext<Record<string, unknown>>]: Mock; | |
| }; | |
| function createMockWaitingToolCall( | |
| overrides: Partial<WaitingToolCall> = {}, | |
| ): WaitingToolCall { | |
| return { | |
| status: CoreToolCallStatus.AwaitingApproval, | |
| request: { | |
| callId: 'test-call-id', | |
| name: 'test-tool', | |
| args: {}, | |
| isClientInitiated: false, | |
| prompt_id: 'test-prompt-id', | |
| } as ToolCallRequestInfo, | |
| tool: new MockTool({ name: 'test-tool' }), | |
| invocation: {} as ToolInvocation<Record<string, unknown>, ToolResult>, // We generally don't check invocation details in these tests | |
| confirmationDetails: { | |
| type: 'edit', | |
| title: 'Test Confirmation', | |
| fileName: 'test.txt', | |
| filePath: '/path/to/test.txt', | |
| fileDiff: 'diff', | |
| originalContent: 'original', | |
| newContent: 'new', | |
| onConfirm: async () => {}, | |
| }, | |
| ...overrides, | |
| }; | |
| } | |
| describe('ToolModificationHandler', () => { | |
| let handler: ToolModificationHandler; | |
| let mockModifiableTool: MockModifiableTool; | |
| let mockPlainTool: MockTool; | |
| let mockModifyContext: MockModifyContext; | |
| beforeEach(() => { | |
| vi.clearAllMocks(); | |
| handler = new ToolModificationHandler(); | |
| mockModifiableTool = new MockModifiableTool(); | |
| mockPlainTool = new MockTool({ name: 'plainTool' }); | |
| mockModifyContext = { | |
| getCurrentContent: vi.fn(), | |
| getFilePath: vi.fn(), | |
| createUpdatedParams: vi.fn(), | |
| getProposedContent: vi.fn(), | |
| }; | |
| vi.spyOn(mockModifiableTool, 'getModifyContext').mockReturnValue( | |
| mockModifyContext as unknown as ModifyContext<Record<string, unknown>>, | |
| ); | |
| }); | |
| describe('handleModifyWithEditor', () => { | |
| it('should return undefined if tool is not modifiable', async () => { | |
| vi.mocked( | |
| modifiableToolModule.isModifiableDeclarativeTool, | |
| ).mockReturnValue(false); | |
| const mockWaitingToolCall = createMockWaitingToolCall({ | |
| tool: mockPlainTool, | |
| request: { | |
| callId: 'call-1', | |
| name: 'plainTool', | |
| args: { path: 'foo.txt' }, | |
| isClientInitiated: false, | |
| prompt_id: 'p1', | |
| }, | |
| }); | |
| const result = await handler.handleModifyWithEditor( | |
| mockWaitingToolCall, | |
| 'vscode', | |
| new AbortController().signal, | |
| ); | |
| expect(result).toBeUndefined(); | |
| }); | |
| it('should call modifyWithEditor and return updated params', async () => { | |
| vi.mocked( | |
| modifiableToolModule.isModifiableDeclarativeTool, | |
| ).mockReturnValue(true); | |
| vi.mocked(modifiableToolModule.modifyWithEditor).mockResolvedValue({ | |
| updatedParams: { path: 'foo.txt', content: 'new' }, | |
| updatedDiff: 'diff', | |
| }); | |
| const mockWaitingToolCall = createMockWaitingToolCall({ | |
| tool: mockModifiableTool, | |
| request: { | |
| callId: 'call-1', | |
| name: 'mockModifiableTool', | |
| args: { path: 'foo.txt' }, | |
| isClientInitiated: false, | |
| prompt_id: 'p1', | |
| }, | |
| confirmationDetails: { | |
| type: 'edit', | |
| title: 'Confirm', | |
| fileName: 'foo.txt', | |
| filePath: 'foo.txt', | |
| fileDiff: 'diff', | |
| originalContent: 'old', | |
| newContent: 'new', | |
| onConfirm: async () => {}, | |
| }, | |
| }); | |
| const result = await handler.handleModifyWithEditor( | |
| mockWaitingToolCall, | |
| 'vscode', | |
| new AbortController().signal, | |
| ); | |
| expect(modifiableToolModule.modifyWithEditor).toHaveBeenCalledWith( | |
| mockWaitingToolCall.request.args, | |
| mockModifyContext, | |
| 'vscode', | |
| expect.any(AbortSignal), | |
| { currentContent: 'old', proposedContent: 'new' }, | |
| ); | |
| expect(result).toEqual({ | |
| updatedParams: { path: 'foo.txt', content: 'new' }, | |
| updatedDiff: 'diff', | |
| }); | |
| }); | |
| }); | |
| describe('applyInlineModify', () => { | |
| it('should return undefined if tool is not modifiable', async () => { | |
| vi.mocked( | |
| modifiableToolModule.isModifiableDeclarativeTool, | |
| ).mockReturnValue(false); | |
| const mockWaitingToolCall = createMockWaitingToolCall({ | |
| tool: mockPlainTool, | |
| }); | |
| const result = await handler.applyInlineModify( | |
| mockWaitingToolCall, | |
| { newContent: 'foo' }, | |
| new AbortController().signal, | |
| ); | |
| expect(result).toBeUndefined(); | |
| }); | |
| it('should return undefined if payload has no new content', async () => { | |
| vi.mocked( | |
| modifiableToolModule.isModifiableDeclarativeTool, | |
| ).mockReturnValue(true); | |
| const mockWaitingToolCall = createMockWaitingToolCall({ | |
| tool: mockModifiableTool, | |
| }); | |
| const result = await handler.applyInlineModify( | |
| mockWaitingToolCall, | |
| {} as ToolConfirmationPayload, // no newContent property | |
| new AbortController().signal, | |
| ); | |
| expect(result).toBeUndefined(); | |
| }); | |
| it('should process empty string as valid new content', async () => { | |
| vi.mocked( | |
| modifiableToolModule.isModifiableDeclarativeTool, | |
| ).mockReturnValue(true); | |
| (Diff.createPatch as unknown as Mock).mockReturnValue('mock-diff-empty'); | |
| mockModifyContext.getCurrentContent.mockResolvedValue('old content'); | |
| mockModifyContext.getFilePath.mockReturnValue('test.txt'); | |
| mockModifyContext.createUpdatedParams.mockReturnValue({ | |
| content: '', | |
| }); | |
| const mockWaitingToolCall = createMockWaitingToolCall({ | |
| tool: mockModifiableTool, | |
| }); | |
| const result = await handler.applyInlineModify( | |
| mockWaitingToolCall, | |
| { newContent: '' }, | |
| new AbortController().signal, | |
| ); | |
| expect(mockModifyContext.createUpdatedParams).toHaveBeenCalledWith( | |
| expect.any(String), | |
| '', | |
| expect.any(Object), | |
| ); | |
| expect(result).toEqual({ | |
| updatedParams: { content: '' }, | |
| updatedDiff: 'mock-diff-empty', | |
| }); | |
| }); | |
| it('should calculate diff and return updated params', async () => { | |
| vi.mocked( | |
| modifiableToolModule.isModifiableDeclarativeTool, | |
| ).mockReturnValue(true); | |
| (Diff.createPatch as unknown as Mock).mockReturnValue('mock-diff'); | |
| mockModifyContext.getCurrentContent.mockResolvedValue('old content'); | |
| mockModifyContext.getFilePath.mockReturnValue('test.txt'); | |
| mockModifyContext.createUpdatedParams.mockReturnValue({ | |
| content: 'new content', | |
| }); | |
| const mockWaitingToolCall = createMockWaitingToolCall({ | |
| tool: mockModifiableTool, | |
| request: { | |
| callId: 'call-1', | |
| name: 'mockModifiableTool', | |
| args: { content: 'original' }, | |
| isClientInitiated: false, | |
| prompt_id: 'p1', | |
| }, | |
| }); | |
| const result = await handler.applyInlineModify( | |
| mockWaitingToolCall, | |
| { newContent: 'new content' }, | |
| new AbortController().signal, | |
| ); | |
| expect(mockModifyContext.getCurrentContent).toHaveBeenCalled(); | |
| expect(mockModifyContext.createUpdatedParams).toHaveBeenCalledWith( | |
| 'old content', | |
| 'new content', | |
| { content: 'original' }, | |
| ); | |
| expect(Diff.createPatch).toHaveBeenCalledWith( | |
| 'test.txt', | |
| 'old content', | |
| 'new content', | |
| 'Current', | |
| 'Proposed', | |
| ); | |
| expect(result).toEqual({ | |
| updatedParams: { content: 'new content' }, | |
| updatedDiff: 'mock-diff', | |
| }); | |
| }); | |
| }); | |
| }); | |