refactor: simplify command allowlist parsing and parameterize tests

- Replace non-null assertion with nullish coalescing in `commandAllowlist.ts`.
- Parameterize identical test cases in `ToolConfirmationMessage.test.tsx`.
This commit is contained in:
Spencer
2026-03-20 07:15:23 +00:00
parent 2e3e062a72
commit b903b00e9d
2 changed files with 85 additions and 105 deletions
@@ -22,6 +22,7 @@ import {
initializeShellParsers,
} from '@google/gemini-cli-core';
import { renderWithProviders } from '../../../test-utils/render.js';
import '../../../test-utils/customMatchers.js';
import { createMockSettings } from '../../../test-utils/settings.js';
import { useToolActions } from '../../contexts/ToolActionsContext.js';
import { act } from 'react';
@@ -54,122 +55,101 @@ describe('ToolConfirmationMessage', () => {
getApprovalMode: () => 'default',
} as unknown as Config;
it('shows permanent approval option for safe commands', async () => {
const execSafe: SerializableConfirmationDetails = {
type: 'exec',
title: 'Confirm Execution',
it.each([
{
description: 'safe commands',
command: 'ls -la',
rootCommand: 'ls',
rootCommands: ['ls'],
};
approvalMode: 'default',
},
{
description: 'edit commands in AUTO_EDIT mode',
command: 'mkdir test',
rootCommand: 'mkdir',
approvalMode: 'autoEdit',
},
])(
'shows permanent approval option for $description',
async ({ command, rootCommand, approvalMode }) => {
const mockConfigDynamic = {
...mockConfigWithPermanent,
getApprovalMode: () => approvalMode,
} as unknown as Config;
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
<ToolConfirmationMessage
callId="test-call-id"
confirmationDetails={execSafe}
config={mockConfigWithPermanent}
getPreferredEditor={vi.fn()}
availableTerminalHeight={30}
terminalWidth={80}
/>,
{ settings: mockSettingsWithPermanent },
);
const exec: SerializableConfirmationDetails = {
type: 'exec',
title: 'Confirm Execution',
command,
rootCommand,
rootCommands: [rootCommand],
};
await waitUntilReady();
const output = lastFrame();
expect(output).toContain('Allow this command for all future sessions');
unmount();
});
const { lastFrame, waitUntilReady, unmount } =
await renderWithProviders(
<ToolConfirmationMessage
callId="test-call-id"
confirmationDetails={exec}
config={mockConfigDynamic}
getPreferredEditor={vi.fn()}
availableTerminalHeight={30}
terminalWidth={80}
/>,
{ settings: mockSettingsWithPermanent },
);
it('hides permanent approval option for unsafe commands', async () => {
const execUnsafe: SerializableConfirmationDetails = {
type: 'exec',
title: 'Confirm Execution',
await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
},
);
it.each([
{
description: 'unsafe commands',
command: 'rm -rf /',
rootCommand: 'rm',
rootCommands: ['rm'],
};
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
<ToolConfirmationMessage
callId="test-call-id"
confirmationDetails={execUnsafe}
config={mockConfigWithPermanent}
getPreferredEditor={vi.fn()}
availableTerminalHeight={30}
terminalWidth={80}
/>,
{ settings: mockSettingsWithPermanent },
);
await waitUntilReady();
const output = lastFrame();
expect(output).not.toContain(
'Allow this command for all future sessions',
);
unmount();
});
it('shows permanent approval option for edit commands in AUTO_EDIT mode', async () => {
const mockConfigAutoEdit = {
...mockConfigWithPermanent,
getApprovalMode: () => 'autoEdit',
} as unknown as Config;
const execEdit: SerializableConfirmationDetails = {
type: 'exec',
title: 'Confirm Execution',
approvalMode: 'default',
},
{
description: 'edit commands in DEFAULT mode',
command: 'mkdir test',
rootCommand: 'mkdir',
rootCommands: ['mkdir'],
};
approvalMode: 'default',
},
])(
'hides permanent approval option for $description',
async ({ command, rootCommand, approvalMode }) => {
const mockConfigDynamic = {
...mockConfigWithPermanent,
getApprovalMode: () => approvalMode,
} as unknown as Config;
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
<ToolConfirmationMessage
callId="test-call-id"
confirmationDetails={execEdit}
config={mockConfigAutoEdit}
getPreferredEditor={vi.fn()}
availableTerminalHeight={30}
terminalWidth={80}
/>,
{ settings: mockSettingsWithPermanent },
);
const exec: SerializableConfirmationDetails = {
type: 'exec',
title: 'Confirm Execution',
command,
rootCommand,
rootCommands: [rootCommand],
};
await waitUntilReady();
const output = lastFrame();
expect(output).toContain('Allow this command for all future sessions');
unmount();
});
const { lastFrame, waitUntilReady, unmount } =
await renderWithProviders(
<ToolConfirmationMessage
callId="test-call-id"
confirmationDetails={exec}
config={mockConfigDynamic}
getPreferredEditor={vi.fn()}
availableTerminalHeight={30}
terminalWidth={80}
/>,
{ settings: mockSettingsWithPermanent },
);
it('hides permanent approval option for edit commands in DEFAULT mode', async () => {
const execEdit: SerializableConfirmationDetails = {
type: 'exec',
title: 'Confirm Execution',
command: 'mkdir test',
rootCommand: 'mkdir',
rootCommands: ['mkdir'],
};
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
<ToolConfirmationMessage
callId="test-call-id"
confirmationDetails={execEdit}
config={mockConfigWithPermanent}
getPreferredEditor={vi.fn()}
availableTerminalHeight={30}
terminalWidth={80}
/>,
{ settings: mockSettingsWithPermanent },
);
await waitUntilReady();
const output = lastFrame();
expect(output).not.toContain(
'Allow this command for all future sessions',
);
unmount();
});
await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
},
);
});
const mockConfirm = vi.fn();
+1 -1
View File
@@ -77,7 +77,7 @@ export function canShowAutoApproveCheckbox(
// EVERY root must be on an allowlist
return roots.every((root) => {
// Strip path prefixes (e.g., /usr/bin/ls → ls)
const base = root.includes('/') ? root.split('/').pop()! : root;
const base = root.split('/').pop() ?? root;
if (safeCommandAllowlist.has(base)) return true;
if (isAutoEdit && editCommandAllowlist.has(base)) return true;