Revert "chore(cleanup): Consolidate MockTool definitions (#7228)" (#7283)

This commit is contained in:
Gal Zahavi
2025-08-27 21:06:50 -07:00
committed by GitHub
parent bfdddcbd99
commit 5f16541c38
6 changed files with 236 additions and 194 deletions

View File

@@ -32,8 +32,8 @@ import {
assertUniqueFinalEventIsLast, assertUniqueFinalEventIsLast,
assertTaskCreationAndWorkingStatus, assertTaskCreationAndWorkingStatus,
createStreamMessageRequest, createStreamMessageRequest,
MockTool,
} from './testing_utils.js'; } from './testing_utils.js';
import { MockTool } from '@google/gemini-cli-core';
const mockToolConfirmationFn = async () => const mockToolConfirmationFn = async () =>
({}) as unknown as ToolCallConfirmationDetails; ({}) as unknown as ToolCallConfirmationDetails;
@@ -189,10 +189,13 @@ describe('E2E Tests', () => {
yield* []; yield* [];
}); });
const mockTool = new MockTool({ const mockTool = new MockTool(
name: 'test-tool', 'test-tool',
shouldConfirmExecute: vi.fn(mockToolConfirmationFn), 'Test Tool',
}); true,
false,
mockToolConfirmationFn,
);
getToolRegistrySpy.mockReturnValue({ getToolRegistrySpy.mockReturnValue({
getAllTools: vi.fn().mockReturnValue([mockTool]), getAllTools: vi.fn().mockReturnValue([mockTool]),
@@ -281,16 +284,20 @@ describe('E2E Tests', () => {
yield* []; yield* [];
}); });
const mockTool1 = new MockTool({ const mockTool1 = new MockTool(
name: 'test-tool-1', 'test-tool-1',
displayName: 'Test Tool 1', 'Test Tool 1',
shouldConfirmExecute: vi.fn(mockToolConfirmationFn), false,
}); false,
const mockTool2 = new MockTool({ mockToolConfirmationFn,
name: 'test-tool-2', );
displayName: 'Test Tool 2', const mockTool2 = new MockTool(
shouldConfirmExecute: vi.fn(mockToolConfirmationFn), 'test-tool-2',
}); 'Test Tool 2',
false,
false,
mockToolConfirmationFn,
);
getToolRegistrySpy.mockReturnValue({ getToolRegistrySpy.mockReturnValue({
getAllTools: vi.fn().mockReturnValue([mockTool1, mockTool2]), getAllTools: vi.fn().mockReturnValue([mockTool1, mockTool2]),
@@ -397,13 +404,13 @@ describe('E2E Tests', () => {
yield* [{ type: 'content', value: 'Tool executed successfully.' }]; yield* [{ type: 'content', value: 'Tool executed successfully.' }];
}); });
const mockTool = new MockTool({ const mockTool = new MockTool(
name: 'test-tool-no-approval', 'test-tool-no-approval',
displayName: 'Test Tool No Approval', 'Test Tool No Approval',
execute: vi.fn().mockResolvedValue({ );
llmContent: 'Tool executed successfully.', mockTool.execute.mockResolvedValue({
returnDisplay: 'Tool executed successfully.', llmContent: 'Tool executed successfully.',
}), returnDisplay: 'Tool executed successfully.',
}); });
getToolRegistrySpy.mockReturnValue({ getToolRegistrySpy.mockReturnValue({
@@ -528,13 +535,15 @@ describe('E2E Tests', () => {
// Set approval mode to yolo // Set approval mode to yolo
getApprovalModeSpy.mockReturnValue(ApprovalMode.YOLO); getApprovalModeSpy.mockReturnValue(ApprovalMode.YOLO);
const mockTool = new MockTool({ const mockTool = new MockTool(
name: 'test-tool-yolo', 'test-tool-yolo',
displayName: 'Test Tool YOLO', 'Test Tool YOLO',
execute: vi.fn().mockResolvedValue({ false,
llmContent: 'Tool executed successfully.', false,
returnDisplay: 'Tool executed successfully.', );
}), mockTool.execute.mockResolvedValue({
llmContent: 'Tool executed successfully.',
returnDisplay: 'Tool executed successfully.',
}); });
getToolRegistrySpy.mockReturnValue({ getToolRegistrySpy.mockReturnValue({

View File

@@ -9,7 +9,90 @@ import type {
TaskStatusUpdateEvent, TaskStatusUpdateEvent,
SendStreamingMessageSuccessResponse, SendStreamingMessageSuccessResponse,
} from '@a2a-js/sdk'; } from '@a2a-js/sdk';
import { expect } from 'vitest'; import {
BaseDeclarativeTool,
BaseToolInvocation,
Kind,
} from '@google/gemini-cli-core';
import type {
ToolCallConfirmationDetails,
ToolResult,
ToolInvocation,
} from '@google/gemini-cli-core';
import { expect, vi } from 'vitest';
export const mockOnUserConfirmForToolConfirmation = vi.fn();
export class MockToolInvocation extends BaseToolInvocation<object, ToolResult> {
constructor(
private readonly tool: MockTool,
params: object,
) {
super(params);
}
getDescription(): string {
return JSON.stringify(this.params);
}
override shouldConfirmExecute(
abortSignal: AbortSignal,
): Promise<ToolCallConfirmationDetails | false> {
return this.tool.shouldConfirmExecute(this.params, abortSignal);
}
execute(
signal: AbortSignal,
updateOutput?: (output: string) => void,
terminalColumns?: number,
terminalRows?: number,
): Promise<ToolResult> {
return this.tool.execute(
this.params,
signal,
updateOutput,
terminalColumns,
terminalRows,
);
}
}
// TODO: dedup with gemini-cli, add shouldConfirmExecute() support in core
export class MockTool extends BaseDeclarativeTool<object, ToolResult> {
constructor(
name: string,
displayName: string,
canUpdateOutput = false,
isOutputMarkdown = false,
shouldConfirmExecute?: () => Promise<ToolCallConfirmationDetails | false>,
) {
super(
name,
displayName,
'A mock tool for testing',
Kind.Other,
{},
isOutputMarkdown,
canUpdateOutput,
);
if (shouldConfirmExecute) {
this.shouldConfirmExecute.mockImplementation(shouldConfirmExecute);
} else {
// Default to no confirmation needed
this.shouldConfirmExecute.mockResolvedValue(false);
}
}
execute = vi.fn();
shouldConfirmExecute = vi.fn();
protected createInvocation(
params: object,
): ToolInvocation<object, ToolResult> {
return new MockToolInvocation(this, params);
}
}
export function createStreamMessageRequest( export function createStreamMessageRequest(
text: string, text: string,

View File

@@ -22,13 +22,16 @@ import type {
ToolCallResponseInfo, ToolCallResponseInfo,
ToolCall, // Import from core ToolCall, // Import from core
Status as ToolCallStatusType, Status as ToolCallStatusType,
ToolInvocation,
AnyDeclarativeTool, AnyDeclarativeTool,
AnyToolInvocation, AnyToolInvocation,
} from '@google/gemini-cli-core'; } from '@google/gemini-cli-core';
import { import {
ToolConfirmationOutcome, ToolConfirmationOutcome,
ApprovalMode, ApprovalMode,
MockTool, Kind,
BaseDeclarativeTool,
BaseToolInvocation,
} from '@google/gemini-cli-core'; } from '@google/gemini-cli-core';
import type { HistoryItemWithoutId, HistoryItemToolGroup } from '../types.js'; import type { HistoryItemWithoutId, HistoryItemToolGroup } from '../types.js';
import { ToolCallStatus } from '../types.js'; import { ToolCallStatus } from '../types.js';
@@ -61,20 +64,96 @@ const mockConfig = {
}), }),
} as unknown as Config; } as unknown as Config;
const mockTool = new MockTool({ name: 'mockTool', displayName: 'Mock Tool' }); class MockToolInvocation extends BaseToolInvocation<object, ToolResult> {
const mockToolWithLiveOutput = new MockTool({ constructor(
name: 'mockToolWithLiveOutput', private readonly tool: MockTool,
displayName: 'Mock Tool With Live Output', params: object,
description: 'A mock tool for testing', ) {
params: {}, super(params);
isOutputMarkdown: true, }
canUpdateOutput: true,
}); getDescription(): string {
return JSON.stringify(this.params);
}
override shouldConfirmExecute(
abortSignal: AbortSignal,
): Promise<ToolCallConfirmationDetails | false> {
return this.tool.shouldConfirmExecute(this.params, abortSignal);
}
execute(
signal: AbortSignal,
updateOutput?: (output: string) => void,
terminalColumns?: number,
terminalRows?: number,
): Promise<ToolResult> {
return this.tool.execute(
this.params,
signal,
updateOutput,
terminalColumns,
terminalRows,
);
}
}
class MockTool extends BaseDeclarativeTool<object, ToolResult> {
constructor(
name: string,
displayName: string,
canUpdateOutput = false,
shouldConfirm = false,
isOutputMarkdown = false,
) {
super(
name,
displayName,
'A mock tool for testing',
Kind.Other,
{},
isOutputMarkdown,
canUpdateOutput,
);
if (shouldConfirm) {
this.shouldConfirmExecute.mockImplementation(
async (): Promise<ToolCallConfirmationDetails | false> => ({
type: 'edit',
title: 'Mock Tool Requires Confirmation',
onConfirm: mockOnUserConfirmForToolConfirmation,
filePath: 'mock',
fileName: 'mockToolRequiresConfirmation.ts',
fileDiff: 'Mock tool requires confirmation',
originalContent: 'Original content',
newContent: 'New content',
}),
);
}
}
execute = vi.fn();
shouldConfirmExecute = vi.fn();
protected createInvocation(
params: object,
): ToolInvocation<object, ToolResult> {
return new MockToolInvocation(this, params);
}
}
const mockTool = new MockTool('mockTool', 'Mock Tool');
const mockToolWithLiveOutput = new MockTool(
'mockToolWithLiveOutput',
'Mock Tool With Live Output',
true,
);
let mockOnUserConfirmForToolConfirmation: Mock; let mockOnUserConfirmForToolConfirmation: Mock;
const mockToolRequiresConfirmation = new MockTool({ const mockToolRequiresConfirmation = new MockTool(
name: 'mockToolRequiresConfirmation', 'mockToolRequiresConfirmation',
displayName: 'Mock Tool Requires Confirmation', 'Mock Tool Requires Confirmation',
}); false,
true,
);
describe('useReactToolScheduler in YOLO Mode', () => { describe('useReactToolScheduler in YOLO Mode', () => {
let onComplete: Mock; let onComplete: Mock;
@@ -638,23 +717,19 @@ describe('useReactToolScheduler', () => {
}); });
it('should schedule and execute multiple tool calls', async () => { it('should schedule and execute multiple tool calls', async () => {
const tool1 = new MockTool({ const tool1 = new MockTool('tool1', 'Tool 1');
name: 'tool1', tool1.execute.mockResolvedValue({
displayName: 'Tool 1', llmContent: 'Output 1',
execute: vi.fn().mockResolvedValue({ returnDisplay: 'Display 1',
llmContent: 'Output 1', } as ToolResult);
returnDisplay: 'Display 1', tool1.shouldConfirmExecute.mockResolvedValue(null);
} as ToolResult),
});
const tool2 = new MockTool({ const tool2 = new MockTool('tool2', 'Tool 2');
name: 'tool2', tool2.execute.mockResolvedValue({
displayName: 'Tool 2', llmContent: 'Output 2',
execute: vi.fn().mockResolvedValue({ returnDisplay: 'Display 2',
llmContent: 'Output 2', } as ToolResult);
returnDisplay: 'Display 2', tool2.shouldConfirmExecute.mockResolvedValue(null);
} as ToolResult),
});
mockToolRegistry.getTool.mockImplementation((name) => { mockToolRegistry.getTool.mockImplementation((name) => {
if (name === 'tool1') return tool1; if (name === 'tool1') return tool1;
@@ -795,12 +870,7 @@ describe('mapToDisplay', () => {
args: { foo: 'bar' }, args: { foo: 'bar' },
} as any; } as any;
const baseTool = new MockTool({ const baseTool = new MockTool('testTool', 'Test Tool Display');
name: 'testTool',
displayName: 'Test Tool Display',
execute: vi.fn(),
shouldConfirmExecute: vi.fn(),
});
const baseResponse: ToolCallResponseInfo = { const baseResponse: ToolCallResponseInfo = {
callId: 'testCallId', callId: 'testCallId',
@@ -958,7 +1028,7 @@ describe('mapToDisplay', () => {
expectedStatus: ToolCallStatus.Error, expectedStatus: ToolCallStatus.Error,
expectedResultDisplay: 'Execution failed display', expectedResultDisplay: 'Execution failed display',
expectedName: baseTool.displayName, // Changed from baseTool.name expectedName: baseTool.displayName, // Changed from baseTool.name
expectedDescription: JSON.stringify(baseRequest.args), expectedDescription: baseInvocation.getDescription(),
}, },
{ {
name: 'cancelled', name: 'cancelled',
@@ -1029,13 +1099,13 @@ describe('mapToDisplay', () => {
invocation: baseTool.build(baseRequest.args), invocation: baseTool.build(baseRequest.args),
response: { ...baseResponse, callId: 'call1' }, response: { ...baseResponse, callId: 'call1' },
} as ToolCall; } as ToolCall;
const toolForCall2 = new MockTool({ const toolForCall2 = new MockTool(
name: baseTool.name, baseTool.name,
displayName: baseTool.displayName, baseTool.displayName,
isOutputMarkdown: true, false,
execute: vi.fn(), false,
shouldConfirmExecute: vi.fn(), true,
}); );
const toolCall2: ToolCall = { const toolCall2: ToolCall = {
request: { ...baseRequest, callId: 'call2' }, request: { ...baseRequest, callId: 'call2' },
status: 'executing', status: 'executing',

View File

@@ -108,6 +108,3 @@ export * from './telemetry/index.js';
export { sessionId } from './utils/session.js'; export { sessionId } from './utils/session.js';
export * from './utils/browser.js'; export * from './utils/browser.js';
export { Storage } from './config/storage.js'; export { Storage } from './config/storage.js';
// Export test utils
export * from './test-utils/index.js';

View File

@@ -1,7 +0,0 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
export * from './mock-tool.js';

View File

@@ -1,110 +0,0 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { vi } from 'vitest';
import type {
ToolCallConfirmationDetails,
ToolInvocation,
ToolResult,
} from '../tools/tools.js';
import {
BaseDeclarativeTool,
BaseToolInvocation,
Kind,
} from '../tools/tools.js';
type MockToolOptions = {
name: string;
displayName?: string;
description?: string;
canUpdateOutput?: boolean;
isOutputMarkdown?: boolean;
shouldConfirmExecute?: (
...args: unknown[]
) => Promise<ToolCallConfirmationDetails | false>;
execute?: (...args: unknown[]) => Promise<ToolResult>;
params?: object;
};
class MockToolInvocation extends BaseToolInvocation<
{ [key: string]: unknown },
ToolResult
> {
constructor(
private readonly tool: MockTool,
params: { [key: string]: unknown },
) {
super(params);
}
execute(
signal: AbortSignal,
updateOutput?: (output: string) => void,
terminalColumns?: number,
terminalRows?: number,
): Promise<ToolResult> {
return this.tool.execute(
this.params,
signal,
updateOutput,
terminalColumns,
terminalRows,
);
}
override shouldConfirmExecute(
abortSignal: AbortSignal,
): Promise<ToolCallConfirmationDetails | false> {
return this.tool.shouldConfirmExecute(this.params, abortSignal);
}
getDescription(): string {
return `A mock tool invocation for ${this.tool.name}`;
}
}
/**
* A highly configurable mock tool for testing purposes.
*/
export class MockTool extends BaseDeclarativeTool<
{ [key: string]: unknown },
ToolResult
> {
execute: (...args: unknown[]) => Promise<ToolResult>;
shouldConfirmExecute: (
...args: unknown[]
) => Promise<ToolCallConfirmationDetails | false>;
constructor(options: MockToolOptions) {
super(
options.name,
options.displayName ?? options.name,
options.description ?? options.name,
Kind.Other,
options.params,
options.isOutputMarkdown ?? false,
options.canUpdateOutput ?? false,
);
if (options.shouldConfirmExecute) {
this.shouldConfirmExecute = options.shouldConfirmExecute;
} else {
this.shouldConfirmExecute = vi.fn().mockResolvedValue(false);
}
if (options.execute) {
this.execute = options.execute;
} else {
this.execute = vi.fn();
}
}
protected createInvocation(params: {
[key: string]: unknown;
}): ToolInvocation<{ [key: string]: unknown }, ToolResult> {
return new MockToolInvocation(this, params);
}
}