@@ -11,6 +11,11 @@ import type { Settings } from './settings.js';
1111import {
1212 type ExtensionLoader ,
1313 FileDiscoveryService ,
14+ getCodeAssistServer ,
15+ Config ,
16+ ExperimentFlags ,
17+ fetchAdminControlsOnce ,
18+ type FetchAdminControlsResponse ,
1419} from '@google/gemini-cli-core' ;
1520
1621// Mock dependencies
@@ -19,18 +24,35 @@ vi.mock('@google/gemini-cli-core', async (importOriginal) => {
1924 await importOriginal < typeof import ( '@google/gemini-cli-core' ) > ( ) ;
2025 return {
2126 ...actual ,
22- Config : vi . fn ( ) . mockImplementation ( ( params ) => ( {
23- initialize : vi . fn ( ) ,
24- refreshAuth : vi . fn ( ) ,
25- ...params , // Expose params for assertion
26- } ) ) ,
27+ Config : vi . fn ( ) . mockImplementation ( ( params ) => {
28+ const mockConfig = {
29+ ...params ,
30+ initialize : vi . fn ( ) ,
31+ refreshAuth : vi . fn ( ) ,
32+ getExperiments : vi . fn ( ) . mockReturnValue ( {
33+ flags : {
34+ [ actual . ExperimentFlags . ENABLE_ADMIN_CONTROLS ] : {
35+ boolValue : false ,
36+ } ,
37+ } ,
38+ } ) ,
39+ getRemoteAdminSettings : vi . fn ( ) ,
40+ setRemoteAdminSettings : vi . fn ( ) ,
41+ } ;
42+ return mockConfig ;
43+ } ) ,
2744 loadServerHierarchicalMemory : vi
2845 . fn ( )
2946 . mockResolvedValue ( { memoryContent : '' , fileCount : 0 , filePaths : [ ] } ) ,
3047 startupProfiler : {
3148 flush : vi . fn ( ) ,
3249 } ,
3350 FileDiscoveryService : vi . fn ( ) ,
51+ getCodeAssistServer : vi . fn ( ) ,
52+ fetchAdminControlsOnce : vi . fn ( ) ,
53+ coreEvents : {
54+ emitAdminSettingsChanged : vi . fn ( ) ,
55+ } ,
3456 } ;
3557} ) ;
3658
@@ -56,6 +78,121 @@ describe('loadConfig', () => {
5678 delete process . env [ 'GEMINI_API_KEY' ] ;
5779 } ) ;
5880
81+ describe ( 'admin settings overrides' , ( ) => {
82+ it ( 'should not fetch admin controls if experiment is disabled' , async ( ) => {
83+ await loadConfig ( mockSettings , mockExtensionLoader , taskId ) ;
84+ expect ( fetchAdminControlsOnce ) . not . toHaveBeenCalled ( ) ;
85+ } ) ;
86+
87+ describe ( 'when admin controls experiment is enabled' , ( ) => {
88+ beforeEach ( ( ) => {
89+ // We need to cast to any here to modify the mock implementation
90+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
91+ ( Config as any ) . mockImplementation ( ( params : unknown ) => {
92+ const mockConfig = {
93+ ...( params as object ) ,
94+ initialize : vi . fn ( ) ,
95+ refreshAuth : vi . fn ( ) ,
96+ getExperiments : vi . fn ( ) . mockReturnValue ( {
97+ flags : {
98+ [ ExperimentFlags . ENABLE_ADMIN_CONTROLS ] : {
99+ boolValue : true ,
100+ } ,
101+ } ,
102+ } ) ,
103+ getRemoteAdminSettings : vi . fn ( ) . mockReturnValue ( { } ) ,
104+ setRemoteAdminSettings : vi . fn ( ) ,
105+ } ;
106+ return mockConfig ;
107+ } ) ;
108+ } ) ;
109+
110+ it ( 'should fetch admin controls and apply them' , async ( ) => {
111+ const mockAdminSettings : FetchAdminControlsResponse = {
112+ mcpSetting : {
113+ mcpEnabled : false ,
114+ } ,
115+ cliFeatureSetting : {
116+ extensionsSetting : {
117+ extensionsEnabled : false ,
118+ } ,
119+ } ,
120+ strictModeDisabled : false ,
121+ } ;
122+ vi . mocked ( fetchAdminControlsOnce ) . mockResolvedValue ( mockAdminSettings ) ;
123+
124+ await loadConfig ( mockSettings , mockExtensionLoader , taskId ) ;
125+
126+ expect ( Config ) . toHaveBeenCalledWith (
127+ expect . objectContaining ( {
128+ disableYoloMode : ! mockAdminSettings . strictModeDisabled ,
129+ mcpEnabled : mockAdminSettings . mcpSetting ?. mcpEnabled ,
130+ extensionsEnabled :
131+ mockAdminSettings . cliFeatureSetting ?. extensionsSetting
132+ ?. extensionsEnabled ,
133+ } ) ,
134+ ) ;
135+ } ) ;
136+
137+ it ( 'should treat unset admin settings as false when admin settings are passed' , async ( ) => {
138+ const mockAdminSettings : FetchAdminControlsResponse = {
139+ mcpSetting : {
140+ mcpEnabled : true ,
141+ } ,
142+ } ;
143+ vi . mocked ( fetchAdminControlsOnce ) . mockResolvedValue ( mockAdminSettings ) ;
144+
145+ await loadConfig ( mockSettings , mockExtensionLoader , taskId ) ;
146+
147+ expect ( Config ) . toHaveBeenCalledWith (
148+ expect . objectContaining ( {
149+ disableYoloMode : ! false ,
150+ mcpEnabled : mockAdminSettings . mcpSetting ?. mcpEnabled ,
151+ extensionsEnabled : false ,
152+ } ) ,
153+ ) ;
154+ } ) ;
155+
156+ it ( 'should not pass default unset admin settings when no admin settings are present' , async ( ) => {
157+ const mockAdminSettings : FetchAdminControlsResponse = { } ;
158+ vi . mocked ( fetchAdminControlsOnce ) . mockResolvedValue ( mockAdminSettings ) ;
159+
160+ await loadConfig ( mockSettings , mockExtensionLoader , taskId ) ;
161+
162+ expect ( Config ) . toHaveBeenCalledWith ( expect . objectContaining ( { } ) ) ;
163+ } ) ;
164+
165+ it ( 'should fetch admin controls using the code assist server when available' , async ( ) => {
166+ const mockAdminSettings : FetchAdminControlsResponse = {
167+ mcpSetting : {
168+ mcpEnabled : true ,
169+ } ,
170+ strictModeDisabled : true ,
171+ } ;
172+ const mockCodeAssistServer = { projectId : 'test-project' } ;
173+ vi . mocked ( getCodeAssistServer ) . mockReturnValue (
174+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
175+ mockCodeAssistServer as any ,
176+ ) ;
177+ vi . mocked ( fetchAdminControlsOnce ) . mockResolvedValue ( mockAdminSettings ) ;
178+
179+ await loadConfig ( mockSettings , mockExtensionLoader , taskId ) ;
180+
181+ expect ( fetchAdminControlsOnce ) . toHaveBeenCalledWith (
182+ mockCodeAssistServer ,
183+ true ,
184+ ) ;
185+ expect ( Config ) . toHaveBeenCalledWith (
186+ expect . objectContaining ( {
187+ disableYoloMode : ! mockAdminSettings . strictModeDisabled ,
188+ mcpEnabled : mockAdminSettings . mcpSetting ?. mcpEnabled ,
189+ extensionsEnabled : false ,
190+ } ) ,
191+ ) ;
192+ } ) ;
193+ } ) ;
194+ } ) ;
195+
59196 it ( 'should set customIgnoreFilePaths when CUSTOM_IGNORE_FILE_PATHS env var is present' , async ( ) => {
60197 const testPath = '/tmp/ignore' ;
61198 process . env [ 'CUSTOM_IGNORE_FILE_PATHS' ] = testPath ;
0 commit comments