Add support for trustedFolders.json config file (#6073)

This commit is contained in:
shrutip90
2025-08-13 11:06:31 -07:00
committed by GitHub
parent b61a63aef4
commit 38876b738f
8 changed files with 644 additions and 63 deletions

View File

@@ -252,8 +252,10 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => {
const { isSettingsDialogOpen, openSettingsDialog, closeSettingsDialog } =
useSettingsCommand();
const { isFolderTrustDialogOpen, handleFolderTrustSelect } =
useFolderTrust(settings);
const { isFolderTrustDialogOpen, handleFolderTrustSelect } = useFolderTrust(
settings,
config,
);
const {
isAuthDialogOpen,

View File

@@ -4,15 +4,33 @@
* SPDX-License-Identifier: Apache-2.0
*/
import { renderHook, act } from '@testing-library/react';
import { vi } from 'vitest';
import { renderHook, act } from '@testing-library/react';
import { useFolderTrust } from './useFolderTrust.js';
import { LoadedSettings, SettingScope } from '../../config/settings.js';
import { type Config } from '@google/gemini-cli-core';
import { LoadedSettings } from '../../config/settings.js';
import { FolderTrustChoice } from '../components/FolderTrustDialog.js';
import {
LoadedTrustedFolders,
TrustLevel,
} from '../../config/trustedFolders.js';
import * as process from 'process';
import * as trustedFolders from '../../config/trustedFolders.js';
vi.mock('process', () => ({
cwd: vi.fn(),
platform: 'linux',
}));
describe('useFolderTrust', () => {
it('should set isFolderTrustDialogOpen to true when folderTrustFeature is true and folderTrust is undefined', () => {
const settings = {
let mockSettings: LoadedSettings;
let mockConfig: Config;
let mockTrustedFolders: LoadedTrustedFolders;
let loadTrustedFoldersSpy: vi.SpyInstance;
beforeEach(() => {
mockSettings = {
merged: {
folderTrustFeature: true,
folderTrust: undefined,
@@ -20,59 +38,110 @@ describe('useFolderTrust', () => {
setValue: vi.fn(),
} as unknown as LoadedSettings;
const { result } = renderHook(() => useFolderTrust(settings));
mockConfig = {
isTrustedFolder: vi.fn().mockReturnValue(undefined),
} as unknown as Config;
mockTrustedFolders = {
setValue: vi.fn(),
} as unknown as LoadedTrustedFolders;
loadTrustedFoldersSpy = vi
.spyOn(trustedFolders, 'loadTrustedFolders')
.mockReturnValue(mockTrustedFolders);
(process.cwd as vi.Mock).mockReturnValue('/test/path');
});
afterEach(() => {
vi.clearAllMocks();
});
it('should not open dialog when folder is already trusted', () => {
(mockConfig.isTrustedFolder as vi.Mock).mockReturnValue(true);
const { result } = renderHook(() =>
useFolderTrust(mockSettings, mockConfig),
);
expect(result.current.isFolderTrustDialogOpen).toBe(false);
});
it('should not open dialog when folder is already untrusted', () => {
(mockConfig.isTrustedFolder as vi.Mock).mockReturnValue(false);
const { result } = renderHook(() =>
useFolderTrust(mockSettings, mockConfig),
);
expect(result.current.isFolderTrustDialogOpen).toBe(false);
});
it('should open dialog when folder trust is undefined', () => {
(mockConfig.isTrustedFolder as vi.Mock).mockReturnValue(undefined);
const { result } = renderHook(() =>
useFolderTrust(mockSettings, mockConfig),
);
expect(result.current.isFolderTrustDialogOpen).toBe(true);
});
it('should set isFolderTrustDialogOpen to false when folderTrustFeature is false', () => {
const settings = {
merged: {
folderTrustFeature: false,
folderTrust: undefined,
},
setValue: vi.fn(),
} as unknown as LoadedSettings;
const { result } = renderHook(() => useFolderTrust(settings));
expect(result.current.isFolderTrustDialogOpen).toBe(false);
});
it('should set isFolderTrustDialogOpen to false when folderTrust is defined', () => {
const settings = {
merged: {
folderTrustFeature: true,
folderTrust: true,
},
setValue: vi.fn(),
} as unknown as LoadedSettings;
const { result } = renderHook(() => useFolderTrust(settings));
expect(result.current.isFolderTrustDialogOpen).toBe(false);
});
it('should call setValue and set isFolderTrustDialogOpen to false on handleFolderTrustSelect', () => {
const settings = {
merged: {
folderTrustFeature: true,
folderTrust: undefined,
},
setValue: vi.fn(),
} as unknown as LoadedSettings;
const { result } = renderHook(() => useFolderTrust(settings));
it('should handle TRUST_FOLDER choice', () => {
const { result } = renderHook(() =>
useFolderTrust(mockSettings, mockConfig),
);
act(() => {
result.current.handleFolderTrustSelect(FolderTrustChoice.TRUST_FOLDER);
});
expect(settings.setValue).toHaveBeenCalledWith(
SettingScope.User,
'folderTrust',
true,
expect(loadTrustedFoldersSpy).toHaveBeenCalled();
expect(mockTrustedFolders.setValue).toHaveBeenCalledWith(
'/test/path',
TrustLevel.TRUST_FOLDER,
);
expect(result.current.isFolderTrustDialogOpen).toBe(false);
});
it('should handle TRUST_PARENT choice', () => {
const { result } = renderHook(() =>
useFolderTrust(mockSettings, mockConfig),
);
act(() => {
result.current.handleFolderTrustSelect(FolderTrustChoice.TRUST_PARENT);
});
expect(mockTrustedFolders.setValue).toHaveBeenCalledWith(
'/test/path',
TrustLevel.TRUST_PARENT,
);
expect(result.current.isFolderTrustDialogOpen).toBe(false);
});
it('should handle DO_NOT_TRUST choice', () => {
const { result } = renderHook(() =>
useFolderTrust(mockSettings, mockConfig),
);
act(() => {
result.current.handleFolderTrustSelect(FolderTrustChoice.DO_NOT_TRUST);
});
expect(mockTrustedFolders.setValue).toHaveBeenCalledWith(
'/test/path',
TrustLevel.DO_NOT_TRUST,
);
expect(result.current.isFolderTrustDialogOpen).toBe(false);
});
it('should do nothing for default choice', () => {
const { result } = renderHook(() =>
useFolderTrust(mockSettings, mockConfig),
);
act(() => {
result.current.handleFolderTrustSelect(
'invalid_choice' as FolderTrustChoice,
);
});
expect(mockTrustedFolders.setValue).not.toHaveBeenCalled();
expect(mockSettings.setValue).not.toHaveBeenCalled();
expect(result.current.isFolderTrustDialogOpen).toBe(true);
});
});

View File

@@ -5,24 +5,39 @@
*/
import { useState, useCallback } from 'react';
import { LoadedSettings, SettingScope } from '../../config/settings.js';
import { type Config } from '@google/gemini-cli-core';
import { LoadedSettings } from '../../config/settings.js';
import { FolderTrustChoice } from '../components/FolderTrustDialog.js';
import { loadTrustedFolders, TrustLevel } from '../../config/trustedFolders.js';
import * as process from 'process';
export const useFolderTrust = (settings: LoadedSettings) => {
export const useFolderTrust = (settings: LoadedSettings, config: Config) => {
const [isFolderTrustDialogOpen, setIsFolderTrustDialogOpen] = useState(
!!settings.merged.folderTrustFeature &&
// TODO: Update to avoid showing dialog for folders that are trusted.
settings.merged.folderTrust === undefined,
config.isTrustedFolder() === undefined,
);
const handleFolderTrustSelect = useCallback(
(_choice: FolderTrustChoice) => {
// TODO: Store folderPath in the trusted folders config file based on the choice.
settings.setValue(SettingScope.User, 'folderTrust', true);
setIsFolderTrustDialogOpen(false);
},
[settings],
);
const handleFolderTrustSelect = useCallback((choice: FolderTrustChoice) => {
const trustedFolders = loadTrustedFolders();
const cwd = process.cwd();
let trustLevel: TrustLevel;
switch (choice) {
case FolderTrustChoice.TRUST_FOLDER:
trustLevel = TrustLevel.TRUST_FOLDER;
break;
case FolderTrustChoice.TRUST_PARENT:
trustLevel = TrustLevel.TRUST_PARENT;
break;
case FolderTrustChoice.DO_NOT_TRUST:
trustLevel = TrustLevel.DO_NOT_TRUST;
break;
default:
return;
}
trustedFolders.setValue(cwd, trustLevel);
setIsFolderTrustDialogOpen(false);
}, []);
return {
isFolderTrustDialogOpen,