basant307/AI_Governance_Project
048
1/**2 * @license3 * Copyright 2025 Google LLC4 * SPDX-License-Identifier: Apache-2.05 */6 7import { describe, it, expect, beforeEach, afterEach } from 'vitest';8import {9 resolveEnvVarsInString,10 resolveEnvVarsInObject,11} from './envVarResolver.js';12 13describe('resolveEnvVarsInString', () => {14 let originalEnv: NodeJS.ProcessEnv;15 16 beforeEach(() => {17 originalEnv = { ...process.env };18 });19 20 afterEach(() => {21 process.env = originalEnv;22 });23 24 it('should resolve $VAR_NAME format', () => {25 process.env['TEST_VAR'] = 'test-value';26 27 const result = resolveEnvVarsInString('Value is $TEST_VAR');28 29 expect(result).toBe('Value is test-value');30 });31 32 it('should resolve ${VAR_NAME} format', () => {33 process.env['TEST_VAR'] = 'test-value';34 35 const result = resolveEnvVarsInString('Value is ${TEST_VAR}');36 37 expect(result).toBe('Value is test-value');38 });39 40 it('should resolve multiple variables in the same string', () => {41 process.env['HOST'] = 'localhost';42 process.env['PORT'] = '3000';43 44 const result = resolveEnvVarsInString('URL: http://$HOST:${PORT}/api');45 46 expect(result).toBe('URL: http://localhost:3000/api');47 });48 49 it('should leave undefined variables unchanged', () => {50 const result = resolveEnvVarsInString('Value is $UNDEFINED_VAR');51 52 expect(result).toBe('Value is $UNDEFINED_VAR');53 });54 55 it('should leave undefined variables with braces unchanged', () => {56 const result = resolveEnvVarsInString('Value is ${UNDEFINED_VAR}');57 58 expect(result).toBe('Value is ${UNDEFINED_VAR}');59 });60 61 it('should handle empty string', () => {62 const result = resolveEnvVarsInString('');63 64 expect(result).toBe('');65 });66 67 it('should handle string without variables', () => {68 const result = resolveEnvVarsInString('No variables here');69 70 expect(result).toBe('No variables here');71 });72 73 it('should handle mixed defined and undefined variables', () => {74 process.env['DEFINED'] = 'value';75 76 const result = resolveEnvVarsInString('$DEFINED and $UNDEFINED mixed');77 78 expect(result).toBe('value and $UNDEFINED mixed');79 });80});81 82describe('resolveEnvVarsInObject', () => {83 let originalEnv: NodeJS.ProcessEnv;84 85 beforeEach(() => {86 originalEnv = { ...process.env };87 });88 89 afterEach(() => {90 process.env = originalEnv;91 });92 93 it('should resolve variables in nested objects', () => {94 process.env['API_KEY'] = 'secret-123';95 process.env['DB_URL'] = 'postgresql://localhost/test';96 97 const config = {98 server: {99 auth: {100 key: '$API_KEY',101 },102 database: '${DB_URL}',103 },104 port: 3000,105 };106 107 const result = resolveEnvVarsInObject(config);108 109 expect(result).toEqual({110 server: {111 auth: {112 key: 'secret-123',113 },114 database: 'postgresql://localhost/test',115 },116 port: 3000,117 });118 });119 120 it('should resolve variables in arrays', () => {121 process.env['ENV'] = 'production';122 process.env['VERSION'] = '1.0.0';123 124 const config = {125 tags: ['$ENV', 'app', '${VERSION}'],126 metadata: {127 env: '$ENV',128 },129 };130 131 const result = resolveEnvVarsInObject(config);132 133 expect(result).toEqual({134 tags: ['production', 'app', '1.0.0'],135 metadata: {136 env: 'production',137 },138 });139 });140 141 it('should preserve non-string types', () => {142 const config = {143 enabled: true,144 count: 42,145 value: null,146 data: undefined,147 tags: ['item1', 'item2'],148 };149 150 const result = resolveEnvVarsInObject(config);151 152 expect(result).toEqual(config);153 });154 155 it('should handle MCP server config structure', () => {156 process.env['API_TOKEN'] = 'token-123';157 process.env['SERVER_PORT'] = '8080';158 159 const extensionConfig = {160 name: 'test-extension',161 version: '1.0.0',162 mcpServers: {163 'test-server': {164 command: 'node',165 args: ['server.js', '--port', '${SERVER_PORT}'],166 env: {167 API_KEY: '$API_TOKEN',168 STATIC_VALUE: 'unchanged',169 },170 timeout: 5000,171 },172 },173 };174 175 const result = resolveEnvVarsInObject(extensionConfig);176 177 expect(result).toEqual({178 name: 'test-extension',179 version: '1.0.0',180 mcpServers: {181 'test-server': {182 command: 'node',183 args: ['server.js', '--port', '8080'],184 env: {185 API_KEY: 'token-123',186 STATIC_VALUE: 'unchanged',187 },188 timeout: 5000,189 },190 },191 });192 });193 194 it('should handle empty and null values', () => {195 const config = {196 empty: '',197 nullValue: null,198 undefinedValue: undefined,199 zero: 0,200 false: false,201 };202 203 const result = resolveEnvVarsInObject(config);204 205 expect(result).toEqual(config);206 });207 208 it('should handle circular references in objects without infinite recursion', () => {209 process.env['TEST_VAR'] = 'resolved-value';210 211 type ConfigWithCircularRef = {212 name: string;213 value: number;214 self?: ConfigWithCircularRef;215 };216 217 const config: ConfigWithCircularRef = {218 name: '$TEST_VAR',219 value: 42,220 };221 // Create circular reference222 config.self = config;223 224 const result = resolveEnvVarsInObject(config);225 226 expect(result.name).toBe('resolved-value');227 expect(result.value).toBe(42);228 expect(result.self).toBeDefined();229 expect(result.self?.name).toBe('$TEST_VAR'); // Circular reference should be shallow copied230 expect(result.self?.value).toBe(42);231 // Verify it doesn't create infinite recursion by checking it's not the same object232 expect(result.self).not.toBe(result);233 });234 235 it('should handle circular references in arrays without infinite recursion', () => {236 process.env['ARRAY_VAR'] = 'array-value';237 238 type ArrayWithCircularRef = Array<string | number | ArrayWithCircularRef>;239 const arr: ArrayWithCircularRef = ['$ARRAY_VAR', 123];240 // Create circular reference241 arr.push(arr);242 243 const result = resolveEnvVarsInObject(arr) as ArrayWithCircularRef;244 245 expect(result[0]).toBe('array-value');246 expect(result[1]).toBe(123);247 expect(Array.isArray(result[2])).toBe(true);248 const subArray = result[2] as ArrayWithCircularRef;249 expect(subArray[0]).toBe('$ARRAY_VAR'); // Circular reference should be shallow copied250 expect(subArray[1]).toBe(123);251 // Verify it doesn't create infinite recursion252 expect(result[2]).not.toBe(result);253 });254 255 it('should handle complex nested circular references', () => {256 process.env['NESTED_VAR'] = 'nested-resolved';257 258 type ObjWithRef = {259 name: string;260 id: number;261 ref?: ObjWithRef;262 };263 264 const obj1: ObjWithRef = { name: '$NESTED_VAR', id: 1 };265 const obj2: ObjWithRef = { name: 'static', id: 2 };266 267 // Create cross-references268 obj1.ref = obj2;269 obj2.ref = obj1;270 271 const config = {272 primary: obj1,273 secondary: obj2,274 value: '$NESTED_VAR',275 };276 277 const result = resolveEnvVarsInObject(config);278 279 expect(result.value).toBe('nested-resolved');280 expect(result.primary.name).toBe('nested-resolved');281 expect(result.primary.id).toBe(1);282 expect(result.secondary.name).toBe('static');283 expect(result.secondary.id).toBe(2);284 285 // Check that circular references are handled (shallow copied)286 expect(result.primary.ref).toBeDefined();287 expect(result.secondary.ref).toBeDefined();288 expect(result.primary.ref?.name).toBe('static'); // Should be shallow copy289 expect(result.secondary.ref?.name).toBe('nested-resolved'); // The shallow copy still gets processed290 291 // Most importantly: verify no infinite recursion by checking objects are different292 expect(result.primary.ref).not.toBe(result.secondary);293 expect(result.secondary.ref).not.toBe(result.primary);294 expect(result.primary).not.toBe(obj1); // New object created295 expect(result.secondary).not.toBe(obj2); // New object created296 });297});298 