feat(core,cli): enforce mandatory MessageBus injection (Phase 3 Hard Migration) (#15776)

This commit is contained in:
Abhi
2026-01-04 17:11:43 -05:00
committed by GitHub
parent 90be9c3587
commit 12c7c9cc42
57 changed files with 442 additions and 278 deletions
+20 -23
View File
@@ -10,6 +10,10 @@ import type { Config } from '../config/config.js';
import { ApprovalMode } from '../policy/types.js';
import { ToolConfirmationOutcome } from './tools.js';
import { ToolErrorType } from './tool-error.js';
import {
createMockMessageBus,
getMockMessageBusInstance,
} from '../test-utils/mock-message-bus.js';
import * as fetchUtils from '../utils/fetch.js';
import { MessageBus } from '../confirmation-bus/message-bus.js';
import { PolicyEngine } from '../policy/policy-engine.js';
@@ -126,9 +130,12 @@ describe('parsePrompt', () => {
describe('WebFetchTool', () => {
let mockConfig: Config;
let bus: MessageBus;
beforeEach(() => {
vi.resetAllMocks();
bus = createMockMessageBus();
getMockMessageBusInstance(bus).defaultToolDecision = 'ask_user';
mockConfig = {
getApprovalMode: vi.fn(),
setApprovalMode: vi.fn(),
@@ -163,12 +170,12 @@ describe('WebFetchTool', () => {
expectedError: 'Error(s) in prompt URLs:',
},
])('should throw if $name', ({ prompt, expectedError }) => {
const tool = new WebFetchTool(mockConfig);
const tool = new WebFetchTool(mockConfig, bus);
expect(() => tool.build({ prompt })).toThrow(expectedError);
});
it('should pass if prompt contains at least one valid URL', () => {
const tool = new WebFetchTool(mockConfig);
const tool = new WebFetchTool(mockConfig, bus);
expect(() =>
tool.build({ prompt: 'fetch https://example.com' }),
).not.toThrow();
@@ -181,7 +188,7 @@ describe('WebFetchTool', () => {
vi.spyOn(fetchUtils, 'fetchWithTimeout').mockRejectedValue(
new Error('fetch failed'),
);
const tool = new WebFetchTool(mockConfig);
const tool = new WebFetchTool(mockConfig, bus);
const params = { prompt: 'fetch https://private.ip' };
const invocation = tool.build(params);
const result = await invocation.execute(new AbortController().signal);
@@ -191,7 +198,7 @@ describe('WebFetchTool', () => {
it('should return WEB_FETCH_PROCESSING_ERROR on general processing failure', async () => {
vi.spyOn(fetchUtils, 'isPrivateIp').mockReturnValue(false);
mockGenerateContent.mockRejectedValue(new Error('API error'));
const tool = new WebFetchTool(mockConfig);
const tool = new WebFetchTool(mockConfig, bus);
const params = { prompt: 'fetch https://public.ip' };
const invocation = tool.build(params);
const result = await invocation.execute(new AbortController().signal);
@@ -209,7 +216,7 @@ describe('WebFetchTool', () => {
candidates: [{ content: { parts: [{ text: 'fallback response' }] } }],
});
const tool = new WebFetchTool(mockConfig);
const tool = new WebFetchTool(mockConfig, bus);
const params = { prompt: 'fetch https://private.ip' };
const invocation = tool.build(params);
await invocation.execute(new AbortController().signal);
@@ -237,7 +244,7 @@ describe('WebFetchTool', () => {
candidates: [{ content: { parts: [{ text: 'fallback response' }] } }],
});
const tool = new WebFetchTool(mockConfig);
const tool = new WebFetchTool(mockConfig, bus);
const params = { prompt: 'fetch https://public.ip' };
const invocation = tool.build(params);
await invocation.execute(new AbortController().signal);
@@ -306,7 +313,7 @@ describe('WebFetchTool', () => {
],
}));
const tool = new WebFetchTool(mockConfig);
const tool = new WebFetchTool(mockConfig, bus);
const params = { prompt: 'fetch https://example.com' };
const invocation = tool.build(params);
const result = await invocation.execute(new AbortController().signal);
@@ -330,7 +337,7 @@ describe('WebFetchTool', () => {
describe('shouldConfirmExecute', () => {
it('should return confirmation details with the correct prompt and parsed urls', async () => {
const tool = new WebFetchTool(mockConfig);
const tool = new WebFetchTool(mockConfig, bus);
const params = { prompt: 'fetch https://example.com' };
const invocation = tool.build(params);
const confirmationDetails = await invocation.shouldConfirmExecute(
@@ -347,7 +354,7 @@ describe('WebFetchTool', () => {
});
it('should convert github urls to raw format', async () => {
const tool = new WebFetchTool(mockConfig);
const tool = new WebFetchTool(mockConfig, bus);
const params = {
prompt:
'fetch https://github.com/google/gemini-react/blob/main/README.md',
@@ -373,7 +380,7 @@ describe('WebFetchTool', () => {
vi.spyOn(mockConfig, 'getApprovalMode').mockReturnValue(
ApprovalMode.AUTO_EDIT,
);
const tool = new WebFetchTool(mockConfig);
const tool = new WebFetchTool(mockConfig, bus);
const params = { prompt: 'fetch https://example.com' };
const invocation = tool.build(params);
const confirmationDetails = await invocation.shouldConfirmExecute(
@@ -384,7 +391,7 @@ describe('WebFetchTool', () => {
});
it('should call setApprovalMode when onConfirm is called with ProceedAlways', async () => {
const tool = new WebFetchTool(mockConfig);
const tool = new WebFetchTool(mockConfig, bus);
const params = { prompt: 'fetch https://example.com' };
const invocation = tool.build(params);
const confirmationDetails = await invocation.shouldConfirmExecute(
@@ -412,8 +419,8 @@ describe('WebFetchTool', () => {
let messageBus: MessageBus;
let mockUUID: Mock;
const createToolWithMessageBus = (bus?: MessageBus) => {
const tool = new WebFetchTool(mockConfig, bus);
const createToolWithMessageBus = (customBus?: MessageBus) => {
const tool = new WebFetchTool(mockConfig, customBus ?? bus);
const params = { prompt: 'fetch https://example.com' };
return { tool, invocation: tool.build(params) };
};
@@ -516,16 +523,6 @@ describe('WebFetchTool', () => {
);
});
it('should fall back to legacy confirmation when no message bus', async () => {
const { invocation } = createToolWithMessageBus(); // No message bus
const result = await invocation.shouldConfirmExecute(
new AbortController().signal,
);
expect(result).not.toBe(false);
expect(result).toHaveProperty('type', 'info');
});
it('should ignore responses with wrong correlation ID', async () => {
vi.useFakeTimers();
const { invocation } = createToolWithMessageBus(messageBus);