mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-14 13:53:02 -07:00
test(core): add unit tests for request() fail-fast and log sanitization
- Added test case to verify request() rejects immediately upon publication failure. - Added test cases to verify redaction of sensitive data in debug logs and error messages. - Added test cases for pattern resiliency to handle non-promise returns and sync throws.
This commit is contained in:
@@ -8,6 +8,7 @@ import { describe, it, expect, beforeEach, vi } from 'vitest';
|
||||
import { MessageBus } from './message-bus.js';
|
||||
import { PolicyEngine } from '../policy/policy-engine.js';
|
||||
import { PolicyDecision } from '../policy/types.js';
|
||||
import { debugLogger } from '../utils/debugLogger.js';
|
||||
import {
|
||||
MessageBusType,
|
||||
type Message,
|
||||
@@ -167,6 +168,62 @@ describe('MessageBus', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should sanitize sensitive data in error messages', async () => {
|
||||
const errorHandler = vi.fn();
|
||||
messageBus.on('error', errorHandler);
|
||||
|
||||
const invalidMessage = {
|
||||
type: MessageBusType.TOOL_CONFIRMATION_REQUEST,
|
||||
toolCall: {
|
||||
name: 'test-tool',
|
||||
args: { password: 'secret-password' },
|
||||
},
|
||||
// missing correlationId makes it invalid
|
||||
} as unknown as Message;
|
||||
|
||||
await expect(messageBus.publish(invalidMessage)).rejects.toThrow(
|
||||
/\[REDACTED\]/,
|
||||
);
|
||||
await expect(messageBus.publish(invalidMessage)).rejects.not.toThrow(
|
||||
/secret-password/,
|
||||
);
|
||||
|
||||
expect(errorHandler).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
message: expect.stringContaining('[REDACTED]'),
|
||||
}),
|
||||
);
|
||||
expect(errorHandler).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
message: expect.not.stringContaining('secret-password'),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should sanitize sensitive data in debug logs', async () => {
|
||||
const debugSpy = vi.spyOn(debugLogger, 'debug');
|
||||
// @ts-expect-error - access private member for test
|
||||
messageBus.debug = true;
|
||||
|
||||
const message: ToolExecutionSuccess<string> = {
|
||||
type: MessageBusType.TOOL_EXECUTION_SUCCESS as const,
|
||||
toolCall: {
|
||||
name: 'test-tool',
|
||||
args: { api_key: 'my-api-key' },
|
||||
},
|
||||
result: 'success',
|
||||
};
|
||||
|
||||
await messageBus.publish(message);
|
||||
|
||||
expect(debugSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining('[REDACTED]'),
|
||||
);
|
||||
expect(debugSpy).toHaveBeenCalledWith(
|
||||
expect.not.stringContaining('my-api-key'),
|
||||
);
|
||||
});
|
||||
|
||||
it('should emit other message types directly', async () => {
|
||||
const successHandler = vi.fn();
|
||||
messageBus.subscribe(
|
||||
@@ -268,6 +325,101 @@ describe('MessageBus', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('request', () => {
|
||||
it('should fail fast if publish fails', async () => {
|
||||
// Mock publish to throw
|
||||
vi.spyOn(messageBus, 'publish').mockRejectedValue(
|
||||
new Error('Publish failed'),
|
||||
);
|
||||
|
||||
const request: Omit<ToolConfirmationRequest, 'correlationId'> = {
|
||||
type: MessageBusType.TOOL_CONFIRMATION_REQUEST,
|
||||
toolCall: { name: 'test-tool', args: {} },
|
||||
};
|
||||
|
||||
const start = Date.now();
|
||||
const requestPromise = messageBus.request(
|
||||
request,
|
||||
MessageBusType.TOOL_CONFIRMATION_RESPONSE,
|
||||
60000,
|
||||
);
|
||||
|
||||
await expect(requestPromise).rejects.toThrow('Publish failed');
|
||||
const duration = Date.now() - start;
|
||||
|
||||
// Should have failed way before the 60s timeout
|
||||
expect(duration).toBeLessThan(1000);
|
||||
});
|
||||
|
||||
it('should handle timeout if no response is received', async () => {
|
||||
const request: Message = {
|
||||
type: MessageBusType.SUBAGENT_ACTIVITY,
|
||||
subagentName: 'test',
|
||||
activity: {
|
||||
id: '1',
|
||||
type: 'thought',
|
||||
status: 'running',
|
||||
content: 'thinking',
|
||||
},
|
||||
};
|
||||
|
||||
const requestPromise = messageBus.request(
|
||||
request,
|
||||
MessageBusType.ASK_USER_RESPONSE,
|
||||
10, // 10ms timeout
|
||||
);
|
||||
|
||||
await expect(requestPromise).rejects.toThrow(
|
||||
/Request timed out waiting for ask-user-response/,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('pattern resiliency', () => {
|
||||
it('should handle publish returning non-promise (mock behavior)', () => {
|
||||
// @ts-expect-error - mock returning undefined
|
||||
vi.spyOn(messageBus, 'publish').mockReturnValue(undefined);
|
||||
|
||||
const publishAndCatch = () => {
|
||||
const p = messageBus.publish({
|
||||
type: MessageBusType.TOOL_CALLS_UPDATE,
|
||||
toolCalls: [],
|
||||
schedulerId: 'test',
|
||||
} as Message);
|
||||
|
||||
if (p instanceof Promise) {
|
||||
p.catch(() => {});
|
||||
}
|
||||
};
|
||||
|
||||
expect(publishAndCatch).not.toThrow();
|
||||
});
|
||||
|
||||
it('should handle publish throwing synchronously (mock behavior)', () => {
|
||||
vi.spyOn(messageBus, 'publish').mockImplementation(() => {
|
||||
throw new Error('Sync throw');
|
||||
});
|
||||
|
||||
const publishAndCatch = () => {
|
||||
try {
|
||||
const p = messageBus.publish({
|
||||
type: MessageBusType.TOOL_CALLS_UPDATE,
|
||||
toolCalls: [],
|
||||
schedulerId: 'test',
|
||||
} as Message);
|
||||
|
||||
if (p instanceof Promise) {
|
||||
p.catch(() => {});
|
||||
}
|
||||
} catch {
|
||||
// handled
|
||||
}
|
||||
};
|
||||
|
||||
expect(publishAndCatch).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('derive', () => {
|
||||
it('should receive responses from parent bus on derived bus', async () => {
|
||||
vi.spyOn(policyEngine, 'check').mockResolvedValue({
|
||||
|
||||
Reference in New Issue
Block a user