11// npx jest src/core/__tests__/contextProxy.test.ts
22
3+ import fs from "fs/promises"
4+
35import * as vscode from "vscode"
46import { ContextProxy } from "../contextProxy"
57
68import { logger } from "../../utils/logging"
7- import { GLOBAL_STATE_KEYS , SECRET_KEYS , ConfigurationKey , GlobalStateKey } from "../../shared/globalState"
9+ import { GLOBAL_STATE_KEYS , SECRET_STATE_KEYS } from "../../shared/globalState"
810
911jest . mock ( "vscode" , ( ) => ( {
1012 Uri : {
@@ -77,8 +79,8 @@ describe("ContextProxy", () => {
7779 } )
7880
7981 it ( "should initialize secret cache with all secret keys" , ( ) => {
80- expect ( mockSecrets . get ) . toHaveBeenCalledTimes ( SECRET_KEYS . length )
81- for ( const key of SECRET_KEYS ) {
82+ expect ( mockSecrets . get ) . toHaveBeenCalledTimes ( SECRET_STATE_KEYS . length )
83+ for ( const key of SECRET_STATE_KEYS ) {
8284 expect ( mockSecrets . get ) . toHaveBeenCalledWith ( key )
8385 }
8486 } )
@@ -87,28 +89,28 @@ describe("ContextProxy", () => {
8789 describe ( "getGlobalState" , ( ) => {
8890 it ( "should return value from cache when it exists" , async ( ) => {
8991 // Manually set a value in the cache
90- await proxy . updateGlobalState ( "apiProvider" , "cached-value " )
92+ await proxy . updateGlobalState ( "apiProvider" , "deepseek " )
9193
9294 // Should return the cached value
9395 const result = proxy . getGlobalState ( "apiProvider" )
94- expect ( result ) . toBe ( "cached-value " )
96+ expect ( result ) . toBe ( "deepseek " )
9597
9698 // Original context should be called once during updateGlobalState
9799 expect ( mockGlobalState . get ) . toHaveBeenCalledTimes ( GLOBAL_STATE_KEYS . length ) // Only from initialization
98100 } )
99101
100102 it ( "should handle default values correctly" , async ( ) => {
101103 // No value in cache
102- const result = proxy . getGlobalState ( "apiProvider" , "default-value " )
103- expect ( result ) . toBe ( "default-value " )
104+ const result = proxy . getGlobalState ( "apiProvider" , "deepseek " )
105+ expect ( result ) . toBe ( "deepseek " )
104106 } )
105107
106108 it ( "should bypass cache for pass-through state keys" , async ( ) => {
107109 // Setup mock return value
108110 mockGlobalState . get . mockReturnValue ( "pass-through-value" )
109111
110112 // Use a pass-through key (taskHistory)
111- const result = proxy . getGlobalState ( "taskHistory" as GlobalStateKey )
113+ const result = proxy . getGlobalState ( "taskHistory" )
112114
113115 // Should get value directly from original context
114116 expect ( result ) . toBe ( "pass-through-value" )
@@ -120,37 +122,61 @@ describe("ContextProxy", () => {
120122 mockGlobalState . get . mockReturnValue ( undefined )
121123
122124 // Use a pass-through key with default value
123- const result = proxy . getGlobalState ( "taskHistory" as GlobalStateKey , "default-value" )
125+ const historyItems = [
126+ {
127+ id : "1" ,
128+ number : 1 ,
129+ ts : 1 ,
130+ task : "test" ,
131+ tokensIn : 1 ,
132+ tokensOut : 1 ,
133+ totalCost : 1 ,
134+ } ,
135+ ]
136+
137+ const result = proxy . getGlobalState ( "taskHistory" , historyItems )
124138
125139 // Should return default value when original context returns undefined
126- expect ( result ) . toBe ( "default-value" )
140+ expect ( result ) . toBe ( historyItems )
127141 } )
128142 } )
129143
130144 describe ( "updateGlobalState" , ( ) => {
131145 it ( "should update state directly in original context" , async ( ) => {
132- await proxy . updateGlobalState ( "apiProvider" , "new-value " )
146+ await proxy . updateGlobalState ( "apiProvider" , "deepseek " )
133147
134148 // Should have called original context
135- expect ( mockGlobalState . update ) . toHaveBeenCalledWith ( "apiProvider" , "new-value " )
149+ expect ( mockGlobalState . update ) . toHaveBeenCalledWith ( "apiProvider" , "deepseek " )
136150
137151 // Should have stored the value in cache
138152 const storedValue = await proxy . getGlobalState ( "apiProvider" )
139- expect ( storedValue ) . toBe ( "new-value " )
153+ expect ( storedValue ) . toBe ( "deepseek " )
140154 } )
141155
142156 it ( "should bypass cache for pass-through state keys" , async ( ) => {
143- await proxy . updateGlobalState ( "taskHistory" as GlobalStateKey , "new-value" )
157+ const historyItems = [
158+ {
159+ id : "1" ,
160+ number : 1 ,
161+ ts : 1 ,
162+ task : "test" ,
163+ tokensIn : 1 ,
164+ tokensOut : 1 ,
165+ totalCost : 1 ,
166+ } ,
167+ ]
168+
169+ await proxy . updateGlobalState ( "taskHistory" , historyItems )
144170
145171 // Should update original context
146- expect ( mockGlobalState . update ) . toHaveBeenCalledWith ( "taskHistory" , "new-value" )
172+ expect ( mockGlobalState . update ) . toHaveBeenCalledWith ( "taskHistory" , historyItems )
147173
148174 // Setup mock for subsequent get
149- mockGlobalState . get . mockReturnValue ( "new-value" )
175+ mockGlobalState . get . mockReturnValue ( historyItems )
150176
151177 // Should get fresh value from original context
152- const storedValue = proxy . getGlobalState ( "taskHistory" as GlobalStateKey )
153- expect ( storedValue ) . toBe ( "new-value" )
178+ const storedValue = proxy . getGlobalState ( "taskHistory" )
179+ expect ( storedValue ) . toBe ( historyItems )
154180 expect ( mockGlobalState . get ) . toHaveBeenCalledWith ( "taskHistory" )
155181 } )
156182 } )
@@ -220,27 +246,6 @@ describe("ContextProxy", () => {
220246 const storedValue = proxy . getGlobalState ( "apiModelId" )
221247 expect ( storedValue ) . toBe ( "gpt-4" )
222248 } )
223-
224- it ( "should handle unknown keys as global state with warning" , async ( ) => {
225- // Spy on the logger
226- const warnSpy = jest . spyOn ( logger , "warn" )
227-
228- // Spy on updateGlobalState
229- const updateGlobalStateSpy = jest . spyOn ( proxy , "updateGlobalState" )
230-
231- // Test with an unknown key
232- await proxy . setValue ( "unknownKey" as ConfigurationKey , "some-value" )
233-
234- // Should have logged a warning
235- expect ( warnSpy ) . toHaveBeenCalledWith ( expect . stringContaining ( "Unknown key: unknownKey" ) )
236-
237- // Should have called updateGlobalState
238- expect ( updateGlobalStateSpy ) . toHaveBeenCalledWith ( "unknownKey" , "some-value" )
239-
240- // Should have stored the value in state cache
241- const storedValue = proxy . getGlobalState ( "unknownKey" as GlobalStateKey )
242- expect ( storedValue ) . toBe ( "some-value" )
243- } )
244249 } )
245250
246251 describe ( "setValues" , ( ) => {
@@ -288,7 +293,7 @@ describe("ContextProxy", () => {
288293 } )
289294 } )
290295
291- describe ( "setApiConfiguration " , ( ) => {
296+ describe ( "setProviderSettings " , ( ) => {
292297 it ( "should clear old API configuration values and set new ones" , async ( ) => {
293298 // Set up initial API configuration values
294299 await proxy . updateGlobalState ( "apiModelId" , "old-model" )
@@ -298,8 +303,8 @@ describe("ContextProxy", () => {
298303 // Spy on setValues
299304 const setValuesSpy = jest . spyOn ( proxy , "setValues" )
300305
301- // Call setApiConfiguration with new configuration
302- await proxy . setApiConfiguration ( {
306+ // Call setProviderSettings with new configuration
307+ await proxy . setProviderSettings ( {
303308 apiModelId : "new-model" ,
304309 apiProvider : "anthropic" ,
305310 // Note: openAiBaseUrl is not included in the new config
@@ -332,8 +337,8 @@ describe("ContextProxy", () => {
332337 // Spy on setValues
333338 const setValuesSpy = jest . spyOn ( proxy , "setValues" )
334339
335- // Call setApiConfiguration with empty configuration
336- await proxy . setApiConfiguration ( { } )
340+ // Call setProviderSettings with empty configuration
341+ await proxy . setProviderSettings ( { } )
337342
338343 // Verify setValues was called with undefined for all existing API config keys
339344 expect ( setValuesSpy ) . toHaveBeenCalledWith (
@@ -397,12 +402,12 @@ describe("ContextProxy", () => {
397402 await proxy . resetAllState ( )
398403
399404 // Should have called delete for each key
400- for ( const key of SECRET_KEYS ) {
405+ for ( const key of SECRET_STATE_KEYS ) {
401406 expect ( mockSecrets . delete ) . toHaveBeenCalledWith ( key )
402407 }
403408
404409 // Total calls should equal the number of secret keys
405- expect ( mockSecrets . delete ) . toHaveBeenCalledTimes ( SECRET_KEYS . length )
410+ expect ( mockSecrets . delete ) . toHaveBeenCalledTimes ( SECRET_STATE_KEYS . length )
406411 } )
407412
408413 it ( "should reinitialize caches after reset" , async ( ) => {
@@ -416,4 +421,88 @@ describe("ContextProxy", () => {
416421 expect ( initializeSpy ) . toHaveBeenCalledTimes ( 1 )
417422 } )
418423 } )
424+
425+ describe ( "exportGlobalSettings" , ( ) => {
426+ it ( "should write global settings to a file when filePath is provided" , async ( ) => {
427+ await proxy . setValues ( {
428+ apiModelId : "gpt-4" ,
429+ apiProvider : "openai" ,
430+ openAiApiKey : "test-api-key" ,
431+ autoApprovalEnabled : true ,
432+ } )
433+
434+ const filePath = `/tmp/roo-global-config-${ Date . now ( ) } .json`
435+ const result = await proxy . exportGlobalSettings ( filePath )
436+ expect ( result ) . toEqual ( { autoApprovalEnabled : true } )
437+ const fileContent = await fs . readFile ( filePath , "utf-8" )
438+ expect ( fileContent ) . toContain ( '"autoApprovalEnabled": true' )
439+
440+ await proxy . setValue ( "autoApprovalEnabled" , false )
441+ expect ( proxy . getValue ( "autoApprovalEnabled" ) ) . toBe ( false )
442+
443+ const importedConfig = await proxy . importGlobalSettings ( filePath )
444+ expect ( importedConfig ) . toEqual ( { autoApprovalEnabled : true } )
445+ expect ( proxy . getValue ( "autoApprovalEnabled" ) ) . toBe ( true )
446+
447+ await fs . unlink ( filePath )
448+ } )
449+ } )
450+
451+ describe ( "exportProviderSettings" , ( ) => {
452+ it ( "should write provider settings to a file when filePath is provided" , async ( ) => {
453+ await proxy . setValues ( {
454+ apiModelId : "gpt-4" ,
455+ apiProvider : "openai" ,
456+ openAiApiKey : "test-api-key" ,
457+ autoApprovalEnabled : true ,
458+ } )
459+
460+ const filePath = `/tmp/roo-api-config-${ Date . now ( ) } .json`
461+ const result = await proxy . exportProviderSettings ( filePath )
462+ expect ( result ) . toEqual ( {
463+ apiModelId : "gpt-4" ,
464+ apiProvider : "openai" ,
465+ openAiApiKey : "test-api-key" ,
466+ apiKey : "test-secret" ,
467+ awsAccessKey : "test-secret" ,
468+ awsSecretKey : "test-secret" ,
469+ awsSessionToken : "test-secret" ,
470+ deepSeekApiKey : "test-secret" ,
471+ geminiApiKey : "test-secret" ,
472+ glamaApiKey : "test-secret" ,
473+ mistralApiKey : "test-secret" ,
474+ openAiNativeApiKey : "test-secret" ,
475+ openRouterApiKey : "test-secret" ,
476+ requestyApiKey : "test-secret" ,
477+ unboundApiKey : "test-secret" ,
478+ } )
479+ const fileContent = await fs . readFile ( filePath , "utf-8" )
480+ expect ( fileContent ) . toContain ( '"openAiApiKey": "test-api-key"' )
481+
482+ await proxy . setValue ( "openAiApiKey" , "new-test-api-key" )
483+ expect ( proxy . getValue ( "openAiApiKey" ) ) . toBe ( "new-test-api-key" )
484+
485+ const importedConfig = await proxy . importProviderSettings ( filePath )
486+ expect ( importedConfig ) . toEqual ( {
487+ apiModelId : "gpt-4" ,
488+ apiProvider : "openai" ,
489+ openAiApiKey : "test-api-key" ,
490+ apiKey : "test-secret" ,
491+ awsAccessKey : "test-secret" ,
492+ awsSecretKey : "test-secret" ,
493+ awsSessionToken : "test-secret" ,
494+ deepSeekApiKey : "test-secret" ,
495+ geminiApiKey : "test-secret" ,
496+ glamaApiKey : "test-secret" ,
497+ mistralApiKey : "test-secret" ,
498+ openAiNativeApiKey : "test-secret" ,
499+ openRouterApiKey : "test-secret" ,
500+ requestyApiKey : "test-secret" ,
501+ unboundApiKey : "test-secret" ,
502+ } )
503+ expect ( proxy . getValue ( "openAiApiKey" ) ) . toBe ( "test-api-key" )
504+
505+ await fs . unlink ( filePath )
506+ } )
507+ } )
419508} )
0 commit comments