2025-08-08 11:02:27 -07:00
/ * *
* @license
* Copyright 2025 Google LLC
* SPDX - License - Identifier : Apache - 2.0
* /
2026-01-06 10:09:09 -08:00
import {
vi ,
describe ,
it ,
expect ,
beforeEach ,
afterEach ,
type Mock ,
type MockInstance ,
} from 'vitest' ;
2025-10-28 10:32:15 -07:00
import { act } from 'react' ;
import { renderHook } from '../../test-utils/render.js' ;
2025-10-30 11:50:26 -07:00
import { waitFor } from '../../test-utils/async.js' ;
2025-08-08 11:02:27 -07:00
import { useFolderTrust } from './useFolderTrust.js' ;
2025-08-26 00:04:53 +02:00
import type { LoadedSettings } from '../../config/settings.js' ;
2025-08-08 11:02:27 -07:00
import { FolderTrustChoice } from '../components/FolderTrustDialog.js' ;
2025-08-26 00:04:53 +02:00
import type { LoadedTrustedFolders } from '../../config/trustedFolders.js' ;
import { TrustLevel } from '../../config/trustedFolders.js' ;
2025-08-13 11:06:31 -07:00
import * as trustedFolders from '../../config/trustedFolders.js' ;
2025-11-26 08:13:21 +05:30
import { coreEvents , ExitCodes } from '@google/gemini-cli-core' ;
2025-08-13 11:06:31 -07:00
2025-10-07 16:29:38 +02:00
const mockedCwd = vi . hoisted ( ( ) = > vi . fn ( ) ) ;
2025-11-14 11:56:39 -08:00
const mockedExit = vi . hoisted ( ( ) = > vi . fn ( ) ) ;
2025-10-07 16:29:38 +02:00
vi . mock ( 'node:process' , async ( ) = > {
const actual =
await vi . importActual < typeof import ( 'node:process' ) > ( 'node:process' ) ;
return {
. . . actual ,
cwd : mockedCwd ,
2025-11-14 11:56:39 -08:00
exit : mockedExit ,
2025-10-07 16:29:38 +02:00
platform : 'linux' ,
} ;
} ) ;
2025-08-08 11:02:27 -07:00
describe ( 'useFolderTrust' , ( ) = > {
2025-08-13 11:06:31 -07:00
let mockSettings : LoadedSettings ;
let mockTrustedFolders : LoadedTrustedFolders ;
2025-10-19 17:16:16 -07:00
let isWorkspaceTrustedSpy : MockInstance ;
2025-08-14 11:15:48 -07:00
let onTrustChange : ( isTrusted : boolean | undefined ) = > void ;
2025-10-19 17:16:16 -07:00
let addItem : Mock ;
2025-08-13 11:06:31 -07:00
beforeEach ( ( ) = > {
2025-11-14 11:56:39 -08:00
vi . useFakeTimers ( ) ;
2025-08-13 11:06:31 -07:00
mockSettings = {
2025-08-08 11:02:27 -07:00
merged : {
2025-09-08 16:41:39 +00:00
security : {
folderTrust : {
enabled : true ,
} ,
} ,
2025-08-08 11:02:27 -07:00
} ,
setValue : vi.fn ( ) ,
} as unknown as LoadedSettings ;
2025-08-13 11:06:31 -07:00
mockTrustedFolders = {
setValue : vi.fn ( ) ,
} as unknown as LoadedTrustedFolders ;
2025-10-28 10:32:15 -07:00
vi . spyOn ( trustedFolders , 'loadTrustedFolders' ) . mockReturnValue (
mockTrustedFolders ,
) ;
2025-08-14 11:15:48 -07:00
isWorkspaceTrustedSpy = vi . spyOn ( trustedFolders , 'isWorkspaceTrusted' ) ;
2025-10-07 16:29:38 +02:00
mockedCwd . mockReturnValue ( '/test/path' ) ;
2025-08-14 11:15:48 -07:00
onTrustChange = vi . fn ( ) ;
2025-10-08 22:17:58 -07:00
addItem = vi . fn ( ) ;
2025-08-08 11:02:27 -07:00
} ) ;
2025-08-13 11:06:31 -07:00
afterEach ( ( ) = > {
2025-11-14 11:56:39 -08:00
vi . useRealTimers ( ) ;
2025-08-13 11:06:31 -07:00
vi . clearAllMocks ( ) ;
} ) ;
2025-08-08 11:02:27 -07:00
2025-08-13 11:06:31 -07:00
it ( 'should not open dialog when folder is already trusted' , ( ) = > {
2025-09-22 11:45:02 -07:00
isWorkspaceTrustedSpy . mockReturnValue ( { isTrusted : true , source : 'file' } ) ;
2025-08-13 11:06:31 -07:00
const { result } = renderHook ( ( ) = >
2025-10-08 22:17:58 -07:00
useFolderTrust ( mockSettings , onTrustChange , addItem ) ,
2025-08-13 11:06:31 -07:00
) ;
expect ( result . current . isFolderTrustDialogOpen ) . toBe ( false ) ;
2025-08-14 11:15:48 -07:00
expect ( onTrustChange ) . toHaveBeenCalledWith ( true ) ;
2025-08-13 11:06:31 -07:00
} ) ;
2025-08-08 11:02:27 -07:00
2025-08-13 11:06:31 -07:00
it ( 'should not open dialog when folder is already untrusted' , ( ) = > {
2025-09-22 11:45:02 -07:00
isWorkspaceTrustedSpy . mockReturnValue ( { isTrusted : false , source : 'file' } ) ;
2025-08-13 11:06:31 -07:00
const { result } = renderHook ( ( ) = >
2025-10-08 22:17:58 -07:00
useFolderTrust ( mockSettings , onTrustChange , addItem ) ,
2025-08-13 11:06:31 -07:00
) ;
2025-08-08 11:02:27 -07:00
expect ( result . current . isFolderTrustDialogOpen ) . toBe ( false ) ;
2025-08-14 11:15:48 -07:00
expect ( onTrustChange ) . toHaveBeenCalledWith ( false ) ;
2025-08-08 11:02:27 -07:00
} ) ;
2025-10-28 10:32:15 -07:00
it ( 'should open dialog when folder trust is undefined' , async ( ) = > {
2025-09-22 11:45:02 -07:00
isWorkspaceTrustedSpy . mockReturnValue ( {
isTrusted : undefined ,
source : undefined ,
} ) ;
2025-08-13 11:06:31 -07:00
const { result } = renderHook ( ( ) = >
2025-10-08 22:17:58 -07:00
useFolderTrust ( mockSettings , onTrustChange , addItem ) ,
2025-08-13 11:06:31 -07:00
) ;
2025-10-30 11:50:26 -07:00
await waitFor ( ( ) = > {
2025-10-28 10:32:15 -07:00
expect ( result . current . isFolderTrustDialogOpen ) . toBe ( true ) ;
} ) ;
2025-08-14 11:15:48 -07:00
expect ( onTrustChange ) . toHaveBeenCalledWith ( undefined ) ;
2025-08-13 11:06:31 -07:00
} ) ;
2025-10-08 22:17:58 -07:00
it ( 'should send a message if the folder is untrusted' , ( ) = > {
isWorkspaceTrustedSpy . mockReturnValue ( { isTrusted : false , source : 'file' } ) ;
renderHook ( ( ) = > useFolderTrust ( mockSettings , onTrustChange , addItem ) ) ;
expect ( addItem ) . toHaveBeenCalledWith (
{
2026-02-05 15:26:30 -08:00
text : 'This folder is untrusted, project settings, hooks, MCPs, and GEMINI.md files will not be applied for this folder.\nUse the `/permissions` command to change the trust level.' ,
2025-10-08 22:17:58 -07:00
type : 'info' ,
} ,
expect . any ( Number ) ,
) ;
} ) ;
it ( 'should not send a message if the folder is trusted' , ( ) = > {
isWorkspaceTrustedSpy . mockReturnValue ( { isTrusted : true , source : 'file' } ) ;
renderHook ( ( ) = > useFolderTrust ( mockSettings , onTrustChange , addItem ) ) ;
expect ( addItem ) . not . toHaveBeenCalled ( ) ;
} ) ;
2026-01-06 10:09:09 -08:00
it ( 'should handle TRUST_FOLDER choice and trigger restart' , async ( ) = > {
2025-09-22 11:45:02 -07:00
isWorkspaceTrustedSpy . mockReturnValue ( {
isTrusted : undefined ,
source : undefined ,
} ) ;
2025-10-28 10:32:15 -07:00
( mockTrustedFolders . setValue as Mock ) . mockImplementation ( ( ) = > {
isWorkspaceTrustedSpy . mockReturnValue ( {
isTrusted : true ,
source : 'file' ,
} ) ;
} ) ;
2025-08-13 11:06:31 -07:00
const { result } = renderHook ( ( ) = >
2025-10-08 22:17:58 -07:00
useFolderTrust ( mockSettings , onTrustChange , addItem ) ,
2025-08-13 11:06:31 -07:00
) ;
2025-08-08 11:02:27 -07:00
2025-10-30 11:50:26 -07:00
await waitFor ( ( ) = > {
2025-10-28 10:32:15 -07:00
expect ( result . current . isTrusted ) . toBeUndefined ( ) ;
2025-08-13 11:06:31 -07:00
} ) ;
2025-08-08 11:02:27 -07:00
2025-10-28 10:32:15 -07:00
await act ( async ( ) = > {
2025-12-16 21:28:18 -08:00
result . current . handleFolderTrustSelect ( FolderTrustChoice . TRUST_FOLDER ) ;
2025-10-28 10:32:15 -07:00
} ) ;
2025-10-30 11:50:26 -07:00
await waitFor ( ( ) = > {
2025-10-28 10:32:15 -07:00
expect ( mockTrustedFolders . setValue ) . toHaveBeenCalledWith (
'/test/path' ,
TrustLevel . TRUST_FOLDER ,
) ;
2026-01-06 10:09:09 -08:00
expect ( result . current . isRestarting ) . toBe ( true ) ;
expect ( result . current . isFolderTrustDialogOpen ) . toBe ( true ) ;
2025-10-28 10:32:15 -07:00
expect ( onTrustChange ) . toHaveBeenLastCalledWith ( true ) ;
} ) ;
2025-08-08 11:02:27 -07:00
} ) ;
2026-01-06 10:09:09 -08:00
it ( 'should handle TRUST_PARENT choice and trigger restart' , async ( ) = > {
2025-09-22 11:45:02 -07:00
isWorkspaceTrustedSpy . mockReturnValue ( {
isTrusted : undefined ,
source : undefined ,
} ) ;
2025-08-13 11:06:31 -07:00
const { result } = renderHook ( ( ) = >
2025-10-08 22:17:58 -07:00
useFolderTrust ( mockSettings , onTrustChange , addItem ) ,
2025-08-13 11:06:31 -07:00
) ;
2026-01-06 10:09:09 -08:00
await act ( async ( ) = > {
2025-08-13 11:06:31 -07:00
result . current . handleFolderTrustSelect ( FolderTrustChoice . TRUST_PARENT ) ;
} ) ;
2025-08-08 11:02:27 -07:00
2026-01-06 10:09:09 -08:00
await waitFor ( ( ) = > {
expect ( mockTrustedFolders . setValue ) . toHaveBeenCalledWith (
'/test/path' ,
TrustLevel . TRUST_PARENT ,
) ;
expect ( result . current . isRestarting ) . toBe ( true ) ;
expect ( result . current . isFolderTrustDialogOpen ) . toBe ( true ) ;
expect ( onTrustChange ) . toHaveBeenLastCalledWith ( true ) ;
} ) ;
2025-08-13 11:06:31 -07:00
} ) ;
2026-01-06 10:09:09 -08:00
it ( 'should handle DO_NOT_TRUST choice and NOT trigger restart (implicit -> explicit)' , async ( ) = > {
2025-09-22 11:45:02 -07:00
isWorkspaceTrustedSpy . mockReturnValue ( {
isTrusted : undefined ,
source : undefined ,
} ) ;
2025-08-13 11:06:31 -07:00
const { result } = renderHook ( ( ) = >
2025-10-08 22:17:58 -07:00
useFolderTrust ( mockSettings , onTrustChange , addItem ) ,
2025-08-13 11:06:31 -07:00
) ;
2025-08-08 11:02:27 -07:00
2026-01-06 10:09:09 -08:00
await act ( async ( ) = > {
2025-08-13 11:06:31 -07:00
result . current . handleFolderTrustSelect ( FolderTrustChoice . DO_NOT_TRUST ) ;
2025-08-08 11:02:27 -07:00
} ) ;
2026-01-06 10:09:09 -08:00
await waitFor ( ( ) = > {
expect ( mockTrustedFolders . setValue ) . toHaveBeenCalledWith (
'/test/path' ,
TrustLevel . DO_NOT_TRUST ,
) ;
expect ( onTrustChange ) . toHaveBeenLastCalledWith ( false ) ;
expect ( result . current . isRestarting ) . toBe ( false ) ;
expect ( result . current . isFolderTrustDialogOpen ) . toBe ( false ) ;
} ) ;
2025-08-08 11:02:27 -07:00
} ) ;
2025-08-13 11:06:31 -07:00
2025-10-28 10:32:15 -07:00
it ( 'should do nothing for default choice' , async ( ) = > {
2025-09-22 11:45:02 -07:00
isWorkspaceTrustedSpy . mockReturnValue ( {
isTrusted : undefined ,
source : undefined ,
} ) ;
2025-08-13 11:06:31 -07:00
const { result } = renderHook ( ( ) = >
2025-10-08 22:17:58 -07:00
useFolderTrust ( mockSettings , onTrustChange , addItem ) ,
2025-08-13 11:06:31 -07:00
) ;
2026-01-06 10:09:09 -08:00
await act ( async ( ) = > {
2025-08-13 11:06:31 -07:00
result . current . handleFolderTrustSelect (
'invalid_choice' as FolderTrustChoice ,
) ;
} ) ;
2025-10-30 11:50:26 -07:00
await waitFor ( ( ) = > {
2025-10-28 10:32:15 -07:00
expect ( mockTrustedFolders . setValue ) . not . toHaveBeenCalled ( ) ;
expect ( mockSettings . setValue ) . not . toHaveBeenCalled ( ) ;
expect ( result . current . isFolderTrustDialogOpen ) . toBe ( true ) ;
expect ( onTrustChange ) . toHaveBeenCalledWith ( undefined ) ;
} ) ;
2025-08-13 11:06:31 -07:00
} ) ;
2025-08-21 00:38:12 -07:00
2025-10-28 10:32:15 -07:00
it ( 'should set isRestarting to true when trust status changes from false to true' , async ( ) = > {
2025-09-22 11:45:02 -07:00
isWorkspaceTrustedSpy . mockReturnValue ( { isTrusted : false , source : 'file' } ) ; // Initially untrusted
2025-10-28 10:32:15 -07:00
( mockTrustedFolders . setValue as Mock ) . mockImplementation ( ( ) = > {
isWorkspaceTrustedSpy . mockReturnValue ( {
isTrusted : true ,
source : 'file' ,
} ) ;
} ) ;
2025-08-21 00:38:12 -07:00
const { result } = renderHook ( ( ) = >
2025-10-08 22:17:58 -07:00
useFolderTrust ( mockSettings , onTrustChange , addItem ) ,
2025-08-21 00:38:12 -07:00
) ;
2025-10-30 11:50:26 -07:00
await waitFor ( ( ) = > {
2025-10-28 10:32:15 -07:00
expect ( result . current . isTrusted ) . toBe ( false ) ;
} ) ;
2026-01-06 10:09:09 -08:00
await act ( async ( ) = > {
2025-08-21 00:38:12 -07:00
result . current . handleFolderTrustSelect ( FolderTrustChoice . TRUST_FOLDER ) ;
} ) ;
2025-10-30 11:50:26 -07:00
await waitFor ( ( ) = > {
2025-10-28 10:32:15 -07:00
expect ( result . current . isRestarting ) . toBe ( true ) ;
expect ( result . current . isFolderTrustDialogOpen ) . toBe ( true ) ; // Dialog should stay open
} ) ;
2025-08-21 00:38:12 -07:00
} ) ;
2026-01-06 10:09:09 -08:00
it ( 'should not set isRestarting to true when trust status does not change (true -> true)' , async ( ) = > {
2025-09-22 11:45:02 -07:00
isWorkspaceTrustedSpy . mockReturnValue ( {
2026-01-06 10:09:09 -08:00
isTrusted : true ,
source : 'file' ,
2025-09-22 11:45:02 -07:00
} ) ;
2025-08-21 00:38:12 -07:00
const { result } = renderHook ( ( ) = >
2025-10-08 22:17:58 -07:00
useFolderTrust ( mockSettings , onTrustChange , addItem ) ,
2025-08-21 00:38:12 -07:00
) ;
2026-01-06 10:09:09 -08:00
await act ( async ( ) = > {
2025-08-21 00:38:12 -07:00
result . current . handleFolderTrustSelect ( FolderTrustChoice . TRUST_FOLDER ) ;
} ) ;
2026-01-06 10:09:09 -08:00
await waitFor ( ( ) = > {
expect ( result . current . isRestarting ) . toBe ( false ) ;
expect ( result . current . isFolderTrustDialogOpen ) . toBe ( false ) ; // Dialog should close
} ) ;
2025-08-21 00:38:12 -07:00
} ) ;
2025-11-14 11:56:39 -08:00
2025-11-26 08:13:21 +05:30
it ( 'should emit feedback on failure to set value' , async ( ) = > {
2025-11-14 11:56:39 -08:00
isWorkspaceTrustedSpy . mockReturnValue ( {
isTrusted : undefined ,
source : undefined ,
} ) ;
( mockTrustedFolders . setValue as Mock ) . mockImplementation ( ( ) = > {
throw new Error ( 'test error' ) ;
} ) ;
const emitFeedbackSpy = vi . spyOn ( coreEvents , 'emitFeedback' ) ;
const { result } = renderHook ( ( ) = >
useFolderTrust ( mockSettings , onTrustChange , addItem ) ,
) ;
act ( ( ) = > {
result . current . handleFolderTrustSelect ( FolderTrustChoice . TRUST_FOLDER ) ;
} ) ;
2025-11-26 08:13:21 +05:30
await vi . runAllTimersAsync ( ) ;
2025-11-14 11:56:39 -08:00
expect ( emitFeedbackSpy ) . toHaveBeenCalledWith (
'error' ,
'Failed to save trust settings. Exiting Gemini CLI.' ,
) ;
2025-11-26 08:13:21 +05:30
expect ( mockedExit ) . toHaveBeenCalledWith ( ExitCodes . FATAL_CONFIG_ERROR ) ;
2025-11-14 11:56:39 -08:00
} ) ;
2025-08-08 11:02:27 -07:00
} ) ;