diff options
Diffstat (limited to 'packages/cli/src/config/settings.test.ts')
| -rw-r--r-- | packages/cli/src/config/settings.test.ts | 160 |
1 files changed, 160 insertions, 0 deletions
diff --git a/packages/cli/src/config/settings.test.ts b/packages/cli/src/config/settings.test.ts index c6943dbe..350c5d33 100644 --- a/packages/cli/src/config/settings.test.ts +++ b/packages/cli/src/config/settings.test.ts @@ -322,6 +322,166 @@ describe('Settings Loading and Merging', () => { consoleErrorSpy.mockRestore(); }); + + it('should resolve environment variables in user settings', () => { + process.env.TEST_API_KEY = 'user_api_key_from_env'; + const userSettingsContent = { + apiKey: '$TEST_API_KEY', + someUrl: 'https://test.com/${TEST_API_KEY}', + }; + (mockFsExistsSync as Mock).mockImplementation( + (p: fs.PathLike) => p === USER_SETTINGS_PATH, + ); + (fs.readFileSync as Mock).mockImplementation( + (p: fs.PathOrFileDescriptor) => { + if (p === USER_SETTINGS_PATH) + return JSON.stringify(userSettingsContent); + return '{}'; + }, + ); + + const settings = loadSettings(MOCK_WORKSPACE_DIR); + expect(settings.user.settings.apiKey).toBe('user_api_key_from_env'); + expect(settings.user.settings.someUrl).toBe( + 'https://test.com/user_api_key_from_env', + ); + expect(settings.merged.apiKey).toBe('user_api_key_from_env'); + delete process.env.TEST_API_KEY; + }); + + it('should resolve environment variables in workspace settings', () => { + process.env.WORKSPACE_ENDPOINT = 'workspace_endpoint_from_env'; + const workspaceSettingsContent = { + endpoint: '${WORKSPACE_ENDPOINT}/api', + nested: { value: '$WORKSPACE_ENDPOINT' }, + }; + (mockFsExistsSync as Mock).mockImplementation( + (p: fs.PathLike) => p === MOCK_WORKSPACE_SETTINGS_PATH, + ); + (fs.readFileSync as Mock).mockImplementation( + (p: fs.PathOrFileDescriptor) => { + if (p === MOCK_WORKSPACE_SETTINGS_PATH) + return JSON.stringify(workspaceSettingsContent); + return '{}'; + }, + ); + + const settings = loadSettings(MOCK_WORKSPACE_DIR); + expect(settings.workspace.settings.endpoint).toBe( + 'workspace_endpoint_from_env/api', + ); + expect(settings.workspace.settings.nested.value).toBe( + 'workspace_endpoint_from_env', + ); + expect(settings.merged.endpoint).toBe('workspace_endpoint_from_env/api'); + delete process.env.WORKSPACE_ENDPOINT; + }); + + it('should prioritize workspace env variables over user env variables if keys clash after resolution', () => { + const userSettingsContent = { configValue: '$SHARED_VAR' }; + const workspaceSettingsContent = { configValue: '$SHARED_VAR' }; + + (mockFsExistsSync as Mock).mockReturnValue(true); + const originalSharedVar = process.env.SHARED_VAR; + // Temporarily delete to ensure a clean slate for the test's specific manipulations + delete process.env.SHARED_VAR; + + (fs.readFileSync as Mock).mockImplementation( + (p: fs.PathOrFileDescriptor) => { + if (p === USER_SETTINGS_PATH) { + process.env.SHARED_VAR = 'user_value_for_user_read'; // Set for user settings read + return JSON.stringify(userSettingsContent); + } + if (p === MOCK_WORKSPACE_SETTINGS_PATH) { + process.env.SHARED_VAR = 'workspace_value_for_workspace_read'; // Set for workspace settings read + return JSON.stringify(workspaceSettingsContent); + } + return '{}'; + }, + ); + + const settings = loadSettings(MOCK_WORKSPACE_DIR); + + expect(settings.user.settings.configValue).toBe( + 'user_value_for_user_read', + ); + expect(settings.workspace.settings.configValue).toBe( + 'workspace_value_for_workspace_read', + ); + // Merged should take workspace's resolved value + expect(settings.merged.configValue).toBe( + 'workspace_value_for_workspace_read', + ); + + // Restore original environment variable state + if (originalSharedVar !== undefined) { + process.env.SHARED_VAR = originalSharedVar; + } else { + delete process.env.SHARED_VAR; // Ensure it's deleted if it wasn't there before + } + }); + + it('should leave unresolved environment variables as is', () => { + const userSettingsContent = { apiKey: '$UNDEFINED_VAR' }; + (mockFsExistsSync as Mock).mockImplementation( + (p: fs.PathLike) => p === USER_SETTINGS_PATH, + ); + (fs.readFileSync as Mock).mockImplementation( + (p: fs.PathOrFileDescriptor) => { + if (p === USER_SETTINGS_PATH) + return JSON.stringify(userSettingsContent); + return '{}'; + }, + ); + + const settings = loadSettings(MOCK_WORKSPACE_DIR); + expect(settings.user.settings.apiKey).toBe('$UNDEFINED_VAR'); + expect(settings.merged.apiKey).toBe('$UNDEFINED_VAR'); + }); + + it('should resolve multiple environment variables in a single string', () => { + process.env.VAR_A = 'valueA'; + process.env.VAR_B = 'valueB'; + const userSettingsContent = { path: '/path/$VAR_A/${VAR_B}/end' }; + (mockFsExistsSync as Mock).mockImplementation( + (p: fs.PathLike) => p === USER_SETTINGS_PATH, + ); + (fs.readFileSync as Mock).mockImplementation( + (p: fs.PathOrFileDescriptor) => { + if (p === USER_SETTINGS_PATH) + return JSON.stringify(userSettingsContent); + return '{}'; + }, + ); + const settings = loadSettings(MOCK_WORKSPACE_DIR); + expect(settings.user.settings.path).toBe('/path/valueA/valueB/end'); + delete process.env.VAR_A; + delete process.env.VAR_B; + }); + + it('should resolve environment variables in arrays', () => { + process.env.ITEM_1 = 'item1_env'; + process.env.ITEM_2 = 'item2_env'; + const userSettingsContent = { list: ['$ITEM_1', '${ITEM_2}', 'literal'] }; + (mockFsExistsSync as Mock).mockImplementation( + (p: fs.PathLike) => p === USER_SETTINGS_PATH, + ); + (fs.readFileSync as Mock).mockImplementation( + (p: fs.PathOrFileDescriptor) => { + if (p === USER_SETTINGS_PATH) + return JSON.stringify(userSettingsContent); + return '{}'; + }, + ); + const settings = loadSettings(MOCK_WORKSPACE_DIR); + expect(settings.user.settings.list).toEqual([ + 'item1_env', + 'item2_env', + 'literal', + ]); + delete process.env.ITEM_1; + delete process.env.ITEM_2; + }); }); describe('LoadedSettings class', () => { |
