@@ -8,7 +8,7 @@ import * as vscode from "vscode"
88import type { ProviderName } from "@roo-code/types"
99import { TelemetryService } from "@roo-code/telemetry"
1010
11- import { importSettings , exportSettings } from "../importExport"
11+ import { importSettings , importSettingsFromFile , importSettingsWithFeedback , exportSettings } from "../importExport"
1212import { ProviderSettingsManager } from "../ProviderSettingsManager"
1313import { ContextProxy } from "../ContextProxy"
1414import { CustomModesManager } from "../CustomModesManager"
@@ -20,6 +20,8 @@ vi.mock("vscode", () => ({
2020 window : {
2121 showOpenDialog : vi . fn ( ) ,
2222 showSaveDialog : vi . fn ( ) ,
23+ showErrorMessage : vi . fn ( ) ,
24+ showInformationMessage : vi . fn ( ) ,
2325 } ,
2426 Uri : {
2527 file : vi . fn ( ( filePath ) => ( { fsPath : filePath } ) ) ,
@@ -31,10 +33,20 @@ vi.mock("fs/promises", () => ({
3133 readFile : vi . fn ( ) ,
3234 mkdir : vi . fn ( ) ,
3335 writeFile : vi . fn ( ) ,
36+ access : vi . fn ( ) ,
37+ constants : {
38+ F_OK : 0 ,
39+ R_OK : 4 ,
40+ } ,
3441 } ,
3542 readFile : vi . fn ( ) ,
3643 mkdir : vi . fn ( ) ,
3744 writeFile : vi . fn ( ) ,
45+ access : vi . fn ( ) ,
46+ constants : {
47+ F_OK : 0 ,
48+ R_OK : 4 ,
49+ } ,
3850} ) )
3951
4052vi . mock ( "os" , ( ) => ( {
@@ -96,7 +108,7 @@ describe("importExport", () => {
96108 customModesManager : mockCustomModesManager ,
97109 } )
98110
99- expect ( result ) . toEqual ( { success : false } )
111+ expect ( result ) . toEqual ( { success : false , error : "User cancelled file selection" } )
100112
101113 expect ( vscode . window . showOpenDialog ) . toHaveBeenCalledWith ( {
102114 filters : { JSON : [ "json" ] } ,
@@ -146,9 +158,12 @@ describe("importExport", () => {
146158 expect ( mockProviderSettingsManager . export ) . toHaveBeenCalled ( )
147159
148160 expect ( mockProviderSettingsManager . import ) . toHaveBeenCalledWith ( {
149- ...previousProviderProfiles ,
150161 currentApiConfigName : "test" ,
151- apiConfigs : { test : { apiProvider : "openai" as ProviderName , apiKey : "test-key" , id : "test-id" } } ,
162+ apiConfigs : {
163+ default : { apiProvider : "anthropic" as ProviderName , id : "default-id" } ,
164+ test : { apiProvider : "openai" as ProviderName , apiKey : "test-key" , id : "test-id" } ,
165+ } ,
166+ modeApiConfigs : { } ,
152167 } )
153168
154169 expect ( mockContextProxy . setValues ) . toHaveBeenCalledWith ( { mode : "code" , autoApprovalEnabled : true } )
@@ -219,11 +234,12 @@ describe("importExport", () => {
219234 expect ( fs . readFile ) . toHaveBeenCalledWith ( "/mock/path/settings.json" , "utf-8" )
220235 expect ( mockProviderSettingsManager . export ) . toHaveBeenCalled ( )
221236 expect ( mockProviderSettingsManager . import ) . toHaveBeenCalledWith ( {
222- ...previousProviderProfiles ,
223237 currentApiConfigName : "test" ,
224238 apiConfigs : {
239+ default : { apiProvider : "anthropic" as ProviderName , id : "default-id" } ,
225240 test : { apiProvider : "openai" as ProviderName , apiKey : "test-key" , id : "test-id" } ,
226241 } ,
242+ modeApiConfigs : { } ,
227243 } )
228244
229245 // Should call setValues with an empty object since globalSettings is missing.
@@ -297,9 +313,11 @@ describe("importExport", () => {
297313 } )
298314
299315 expect ( result . success ) . toBe ( true )
300- expect ( result . providerProfiles ?. apiConfigs [ "openai" ] ) . toBeDefined ( )
301- expect ( result . providerProfiles ?. apiConfigs [ "default" ] ) . toBeDefined ( )
302- expect ( result . providerProfiles ?. apiConfigs [ "default" ] . apiProvider ) . toBe ( "anthropic" )
316+ if ( result . success && "providerProfiles" in result ) {
317+ expect ( result . providerProfiles ?. apiConfigs [ "openai" ] ) . toBeDefined ( )
318+ expect ( result . providerProfiles ?. apiConfigs [ "default" ] ) . toBeDefined ( )
319+ expect ( result . providerProfiles ?. apiConfigs [ "default" ] . apiProvider ) . toBe ( "anthropic" )
320+ }
303321 } )
304322
305323 it ( "should call updateCustomMode for each custom mode in config" , async ( ) => {
@@ -337,6 +355,87 @@ describe("importExport", () => {
337355 expect ( mockCustomModesManager . updateCustomMode ) . toHaveBeenCalledWith ( mode . slug , mode )
338356 } )
339357 } )
358+
359+ it ( "should import settings from provided file path without showing dialog" , async ( ) => {
360+ const filePath = "/mock/path/settings.json"
361+ const mockFileContent = JSON . stringify ( {
362+ providerProfiles : {
363+ currentApiConfigName : "test" ,
364+ apiConfigs : { test : { apiProvider : "openai" as ProviderName , apiKey : "test-key" , id : "test-id" } } ,
365+ } ,
366+ globalSettings : { mode : "code" , autoApprovalEnabled : true } ,
367+ } )
368+
369+ ; ( fs . readFile as Mock ) . mockResolvedValue ( mockFileContent )
370+ ; ( fs . access as Mock ) . mockResolvedValue ( undefined ) // File exists and is readable
371+
372+ const previousProviderProfiles = {
373+ currentApiConfigName : "default" ,
374+ apiConfigs : { default : { apiProvider : "anthropic" as ProviderName , id : "default-id" } } ,
375+ }
376+
377+ mockProviderSettingsManager . export . mockResolvedValue ( previousProviderProfiles )
378+ mockProviderSettingsManager . listConfig . mockResolvedValue ( [
379+ { name : "test" , id : "test-id" , apiProvider : "openai" as ProviderName } ,
380+ { name : "default" , id : "default-id" , apiProvider : "anthropic" as ProviderName } ,
381+ ] )
382+ mockContextProxy . export . mockResolvedValue ( { mode : "code" } )
383+
384+ const result = await importSettingsFromFile (
385+ {
386+ providerSettingsManager : mockProviderSettingsManager ,
387+ contextProxy : mockContextProxy ,
388+ customModesManager : mockCustomModesManager ,
389+ } ,
390+ vscode . Uri . file ( filePath ) ,
391+ )
392+
393+ expect ( vscode . window . showOpenDialog ) . not . toHaveBeenCalled ( )
394+ expect ( fs . readFile ) . toHaveBeenCalledWith ( filePath , "utf-8" )
395+ expect ( result . success ) . toBe ( true )
396+ expect ( mockProviderSettingsManager . import ) . toHaveBeenCalledWith ( {
397+ currentApiConfigName : "test" ,
398+ apiConfigs : {
399+ default : { apiProvider : "anthropic" as ProviderName , id : "default-id" } ,
400+ test : { apiProvider : "openai" as ProviderName , apiKey : "test-key" , id : "test-id" } ,
401+ } ,
402+ modeApiConfigs : { } ,
403+ } )
404+ expect ( mockContextProxy . setValues ) . toHaveBeenCalledWith ( { mode : "code" , autoApprovalEnabled : true } )
405+ } )
406+
407+ it ( "should return error when provided file path does not exist" , async ( ) => {
408+ const filePath = "/nonexistent/path/settings.json"
409+ const accessError = new Error ( "ENOENT: no such file or directory" )
410+
411+ ; ( fs . access as Mock ) . mockRejectedValue ( accessError )
412+
413+ // Create a mock provider for the test
414+ const mockProvider = {
415+ settingsImportedAt : 0 ,
416+ postStateToWebview : vi . fn ( ) . mockResolvedValue ( undefined ) ,
417+ }
418+
419+ // Mock the showErrorMessage to capture the error
420+ const showErrorMessageSpy = vi . spyOn ( vscode . window , "showErrorMessage" ) . mockResolvedValue ( undefined )
421+
422+ await importSettingsWithFeedback (
423+ {
424+ providerSettingsManager : mockProviderSettingsManager ,
425+ contextProxy : mockContextProxy ,
426+ customModesManager : mockCustomModesManager ,
427+ provider : mockProvider ,
428+ } ,
429+ filePath ,
430+ )
431+
432+ expect ( vscode . window . showOpenDialog ) . not . toHaveBeenCalled ( )
433+ expect ( fs . access ) . toHaveBeenCalledWith ( filePath , fs . constants . F_OK | fs . constants . R_OK )
434+ expect ( fs . readFile ) . not . toHaveBeenCalled ( )
435+ expect ( showErrorMessageSpy ) . toHaveBeenCalledWith ( expect . stringContaining ( "errors.settings_import_failed" ) )
436+
437+ showErrorMessageSpy . mockRestore ( )
438+ } )
340439 } )
341440
342441 describe ( "exportSettings" , ( ) => {
0 commit comments