UNPKG

@apistudio/apim-cli

Version:

CLI for API Management Products

467 lines (402 loc) 13.7 kB
/** * Copyright IBM Corp. 2024, 2025 */ import { ContextManager, VCM, } from '../../../src/engine/variable-context-manager/context-manager.js'; describe('ContextManager with multiple contexts', () => { let manager: ContextManager; beforeAll(() => { manager = new ContextManager(); }); afterEach(() => { manager.clearAll(); }); it('should isolate data between contexts', () => { const contextA = manager.createContext('contextA'); const contextB = manager.createContext('contextB'); manager.getGlobalContext().setVariable('env', 'prod'); contextA.setVariable('user', 'Alice'); contextB.setVariable('user', 'Bob'); expect(manager.resolve('contextA', 'User: ${user}')).toBe('User: Alice'); expect(manager.resolve('contextB', 'User: ${user}')).toBe('User: Bob'); expect(manager.resolve('contextA', 'Env: ${env}')).toBe('Env: prod'); expect(manager.resolve('contextA', 'Env: ${env}')).toBe('Env: prod'); expect(() => manager.resolve('contextC', 'Env: ${env}')).toThrow(); const contexts = manager.listContexts(); expect(contexts.sort()).toEqual(['contextA', 'contextB']); }); it('should support array index path resolution for given expression', () => { const input = [{ name: 'Alice' }, { name: 'Bob' }]; const context = manager.createContext('s-array'); context.setVariable('users', input); const resolved = manager.resolve( 's-array', 'First: ${users.0.name}, Second: ${users.1.name}', ); expect(() => manager.resolveExpression('s-array', null)).toThrow(); expect(manager.resolveExpression('s-array', 'users')).toBe(input); expect(manager.resolveExpression('s-array', 'users.0.name')).toBe('Alice'); expect(manager.resolve('s-array', '${users.0.name}')).toBe('Alice'); expect(resolved).toBe('First: Alice, Second: Bob'); expect(manager.resolve('s-array', '${users.0.name}A')).toBe('AliceA'); }); it('should resolve given string', () => { const input = 'Alice'; const context = manager.createContext('s-array'); context.setVariable('users', input); expect(manager.resolve('s-array', '${users}A')).toBe('AliceA'); }); it('check input as undefined', () => { manager.createContext('s-array'); expect(manager.resolve('s-array', undefined)).toBe(undefined); }); }); describe('ContextManager - Negative & Error Scenarios', () => { let manager: ContextManager; beforeAll(() => { manager = new ContextManager(); }); afterEach(() => { manager.clearAll(); }); it('should throw error for no context', () => { expect(() => { manager.resolve('test', 'Hello ${missing}'); }).toThrow("Context 'test' not found."); expect(() => { manager.resolveExpression('test', 'Hello ${missing}'); }).toThrow("Context 'test' not found."); }); it('should throw context exists', () => { expect(() => { manager.createContext('test'); manager.createContext('test'); }).toBeDefined(); }); it('should not throw error for undefined variable', () => { expect(() => { manager.getContext('test'); manager.resolve('test', 'Hello ${missing}'); }).not.toThrow(); }); it('should not throw error for empty variable', () => { manager.createContext('test'); expect(() => { manager.resolve('test', ''); }).not.toThrow(); expect(manager.resolve('test', '')).toEqual(''); expect(() => manager.resolve('test', '${}')).not.toThrow(); expect(manager.resolve('test', '${}')).toEqual(''); expect(() => manager.resolve('test', '${""}')).not.toThrow(); expect(manager.resolve('test', '${""}')).toBeUndefined(); }); it('should not throw error for empty variable in expression', () => { manager.createContext('test'); expect(() => { manager.resolveExpression('test', ''); }).not.toThrow(); expect(manager.resolveExpression('test', '')).toEqual(''); expect(() => manager.resolveExpression('test', '${}')).not.toThrow(); expect(manager.resolveExpression('test', '${}')).toBeUndefined(); expect(() => manager.resolveExpression('test', '${""}')).not.toThrow(); expect(manager.resolveExpression('test', '${""}')).toBeUndefined(); }); it('should handle invalid path on object gracefully', () => { const context = manager.createContext('test'); context.setVariable('user', { name: 'Alice' }); const result = manager.resolve('test', 'Hello ${user.age.year}'); expect(result).toBe('Hello '); }); it('should handle invalid array index gracefully', () => { const context = manager.createContext('array'); context.setVariable('items', ['apple', 'banana']); const result = manager.resolve('array', 'Item: ${items.5}'); expect(result).toBe('Item: '); }); }); describe('VCM - Validate all scenarios using global store', () => { beforeAll(() => { VCM.createContext('contextA').setVariable('user', 'Alice'); VCM.createContext('contextB').setVariable('user', 'Bob'); VCM.createContext('contextA')?.setVariable('userJson', { name: 'Alice', address: { city: 'London', country: 'UK' }, }); VCM.createContext('contextA')?.setVariable('userLocJson', { cityKey: 'city', }); VCM.createContext('contextA')?.setVariable('fruits', [ 'apple', 'banana', 'cherry', ]); VCM.createContext('contextA')?.setVariable('userFav', { favorites: { index: 1 }, }); VCM.createContext('contextA')?.setVariable('scores', [10, false, 30]); }); it('should isolate data between contexts and fetch using VCM', () => { expect(VCM.resolve('contextA', 'User: ${user}')).toBe('User: Alice'); expect(VCM.resolve('contextB', 'User: ${user}')).toBe('User: Bob'); }); it('should resolve JSON using VCM', () => { const input = { greeting: 'Hello ${userJson.name}', details: { city: '${userJson.address.city}', }, }; const output = { greeting: 'Hello Alice', details: { city: 'London', }, }; expect(VCM.resolve('contextA', 'User: ${user}')).toBe('User: Alice'); expect(VCM.resolve('contextB', 'User: ${user}')).toBe('User: Bob'); expect(VCM.resolve('contextA', input)).toEqual(output); }); it('should resolve JSON with nested using VCM', () => { const input = { greeting: 'Hello ${userJson.name}', details: { city: '${userJson.address.${userLocJson.cityKey}}', }, }; const output = { greeting: 'Hello Alice', details: { city: 'London', }, }; expect(VCM.resolve('contextA', 'User: ${user}')).toBe('User: Alice'); expect(VCM.resolve('contextB', 'User: ${user}')).toBe('User: Bob'); expect(VCM.resolve('contextA', input)).toEqual(output); }); it('should resolve Array using VCM with text and variable replacement', () => { const input = ['first ${fruits.1}']; const output = ['first banana']; expect(VCM.resolve('contextA', input)).toEqual(output); }); it('should resolve Array using VCM with full multiple variable replacement', () => { const input = ['${fruits.0} ${fruits.1}']; const output = ['apple banana']; expect(VCM.resolve('contextA', input)).toEqual(output); }); it('should resolve Array using VCM with nested variable replacement', () => { const input = ['${fruits.${userFav.favorites.index}}']; const output = ['banana']; expect(VCM.resolve('contextA', input)).toEqual(output); }); it('should resolve Array using VCM with text and nest variable replacement', () => { const input = ['Second: ${fruits.${userFav.favorites.index}}']; const output = ['Second: banana']; expect(VCM.resolve('contextA', input)).toEqual(output); }); it('should resolve arrays inside an object structure', () => { const input = { best: '${scores.2}', worst: '${scores.0}', bool: '${scores.1}', }; const expected = { best: 30, worst: 10, bool: false, }; expect(VCM.resolve('contextA', input)).toEqual(expected); }); it('should handle out-of-bound index gracefully', () => { expect(VCM.resolve('contextA', 'Invalid: num ${nums.5}')).toBe( 'Invalid: num ', ); }); it('Clean all and check no state is available', () => { VCM.clearAll(); expect(() => { VCM.resolve('test', 'Hello ${missing}'); }).toThrow("Context 'test' not found."); }); it('should throw error for no context', () => { expect(() => { VCM.resolve('test', 'Hello ${missing}'); }).toThrow("Context 'test' not found."); }); it('should handle undefined variable gracefully', () => { VCM.createContext('test'); expect(VCM.resolve('test', 'Hello ${missing}')).toBe('Hello '); }); }); describe('VCM.loadEnv', () => { beforeAll(() => { VCM.createContext('testContext'); }); it('should load environment variables into testContext', () => { const envVars = [ { key: 'content-type', value: 'application/json', isSecret: true, }, { key: 'user', value: 'Alice', isSecret: false, }, ]; VCM.loadEnv('testContext', envVars); const context = VCM.createContext('testContext'); expect(context).toBeDefined(); expect(context?.get('user')?.value).toBe('Alice'); expect(context?.getValue('content-type')).toBe('application/json'); expect(context?.get('content-type')).toEqual({ value: 'application/json', isSecret: true, }); }); it('should load variables into testContext', () => { const envVars = [ { key: 'content-type', value: 'application/json', isSecret: true, }, { key: 'user', value: 'Alice', isSecret: false, }, { key: 'method', value: 'GET', }, ]; VCM.loadEnv('testContext', envVars); const context = VCM.createContext('testContext'); expect(context).toBeDefined(); expect(context?.get('user')?.value).toBe('Alice'); expect(context?.getValue('content-type')).toBe('application/json'); expect(context?.get('content-type')).toEqual({ value: 'application/json', isSecret: true, }); }); it('should resolve "TEST" request model', () => { const input = { request: [ { method: 'POST', resource: 'v2/pet', headers: [ { key: 'Content-Type', value: '${content-type}', }, ], payload: { raw: { json: '{\n "name":"Jose"\n}\n', }, }, }, { method: '${method}', headers: [ { key: 'Content-Type', value: '${content-type}', }, ], payload: { raw: { json: { name: 'Adam' }, }, }, }, ], }; const output = { request: [ { method: 'POST', resource: 'v2/pet', headers: [ { key: 'Content-Type', value: 'application/json', }, ], payload: { raw: { json: '{\n "name":"Jose"\n}\n', }, }, }, { method: 'GET', headers: [ { key: 'Content-Type', value: 'application/json', }, ], payload: { raw: { json: { name: 'Adam' }, }, }, }, ], }; expect(VCM.resolve('testContext', input)).toEqual(output); }); it('should load json variables into testContext', () => { const envVars = { user: 'Alice', age: 30, isAdmin: true, nested: { city: 'London' }, }; VCM.loadEnv('testContext', envVars); const context = VCM.createContext('testContext'); expect(context).toBeDefined(); expect(context?.getValue('user')).toBe('Alice'); expect(context?.getValue('age')).toBe(30); expect(context?.getValue('isAdmin')).toBe(true); expect(context?.getValue('nested')).toEqual({ city: 'London' }); }); it('sets key-value pairs with { value, isSecret } format', () => { const input = { A: { value: 'secretVal', isSecret: true }, B: { value: 42, isSecret: false }, }; VCM.loadEnv('testContext', input); const ctx = VCM.createContext('testContext'); expect(ctx?.getValue('A')).toBe('secretVal'); expect(ctx?.getValue('B')).toBe(42); }); it('sets key-value pairs with { value, isSecret } format', () => { const input = { A: { value: 'secretVal', isSecret: true }, }; VCM.loadEnv('testContext', input); const ctx = VCM.createContext('testContext'); expect(ctx?.getValue('A')).toBe('secretVal'); expect(ctx?.getValue('B')).toBe(42); }); it('should overwrite existing context if loadEnv called again with same contextId', () => { VCM.loadEnv('testContext', { user: 'Bob' }); let context = VCM.createContext('testContext'); expect(context?.getValue('user')).toBe('Bob'); // Load again with different data VCM.loadEnv('testContext', { user: 'Carol', isAdmin: false }); context = VCM.createContext('testContext'); expect(context?.getValue('user')).toBe('Carol'); expect(context?.getValue('isAdmin')).toBe(false); expect(context?.getValue('age')).toBe(30); context?.delete('age'); expect(context?.getValue('age')).toBeUndefined(); }); });