diff --git a/packages/bruno-app/src/utils/collections/index.js b/packages/bruno-app/src/utils/collections/index.js index 4059cb8281e..fe9b60e42fb 100644 --- a/packages/bruno-app/src/utils/collections/index.js +++ b/packages/bruno-app/src/utils/collections/index.js @@ -1148,6 +1148,7 @@ export const getGlobalEnvironmentVariables = ({ globalEnvironments, activeGlobal } }); } + variables.__name__ = environment?.name; return variables; }; diff --git a/packages/bruno-converters/src/postman/postman-translations.js b/packages/bruno-converters/src/postman/postman-translations.js index 1e96c2e9b42..a02404bd2fe 100644 --- a/packages/bruno-converters/src/postman/postman-translations.js +++ b/packages/bruno-converters/src/postman/postman-translations.js @@ -4,11 +4,35 @@ import translateCode from '../utils/postman-to-bruno-translator'; // Currently these APIs only work within the request lifecycle but fail to update the UI tables. // e.g., setCollectionVar only sets the variable in the request lifecycle, fails to update the table in the UI. const replacements = { - 'pm\\.environment\\.get\\(': 'bru.getEnvVar(', - 'pm\\.environment\\.set\\(': 'bru.setEnvVar(', - 'pm\\.variables\\.get\\(': 'bru.getVar(', - 'pm\\.variables\\.set\\(': 'bru.setVar(', + // Environment variables + 'pm\\.environment\\.get\\(': 'bru.environment.get(', + 'pm\\.environment\\.set\\(': 'bru.environment.set(', + 'pm\\.environment\\.has\\(': 'bru.environment.has(', + 'pm\\.environment\\.unset\\(': 'bru.environment.unset(', + 'pm\\.environment\\.replaceIn\\(': 'bru.interpolate(', + 'pm\\.environment\\.toObject\\(': 'bru.environment.toObject(', + 'pm\\.environment\\.clear\\(': 'bru.environment.clear(', + 'pm\\.environment\\.name': 'bru.environment.name', + + // Runtime variables + 'pm\\.variables\\.get\\(': 'bru.variables.get(', + 'pm\\.variables\\.set\\(': 'bru.variables.set(', + 'pm\\.variables\\.has\\(': 'bru.variables.has(', + 'pm\\.variables\\.unset\\(': 'bru.variables.unset(', 'pm\\.variables\\.replaceIn\\(': 'bru.interpolate(', + 'pm\\.variables\\.toObject\\(': 'bru.variables.toObject(', + 'pm\\.variables\\.clear\\(': 'bru.variables.clear(', + + // Global variables + 'pm\\.globals\\.get\\(': 'bru.globals.get(', + 'pm\\.globals\\.set\\(': 'bru.globals.set(', + 'pm\\.globals\\.has\\(': 'bru.globals.has(', + // 'pm\\.globals\\.unset\\(': 'bru.globals.unset(', // TODO: Re-enable once UI sync issue is resolved + 'pm\\.globals\\.replaceIn\\(': 'bru.interpolate(', + 'pm\\.globals\\.toObject\\(': 'bru.globals.toObject(', + // 'pm\\.globals\\.clear\\(': 'bru.globals.clear(', // TODO: Re-enable once UI sync issue is resolved + + // Collection variables 'pm\\.collectionVariables\\.get\\(': 'bru.getCollectionVar(', // 'pm\\.collectionVariables\\.set\\(': 'bru.setCollectionVar(', 'pm\\.collectionVariables\\.has\\(': 'bru.hasCollectionVar(', @@ -21,19 +45,10 @@ const replacements = { 'pm\\.response\\.to\\.have\\.status\\(': 'expect(res.getStatus()).to.equal(', 'pm\\.response\\.json\\(': 'res.getBody(', 'pm\\.expect\\(': 'expect(', - 'pm\\.environment\\.has\\(([^)]+)\\)': 'bru.getEnvVar($1) !== undefined && bru.getEnvVar($1) !== null', 'pm\\.response\\.code': 'res.getStatus()', 'pm\\.response\\.text\\(\\)': 'JSON.stringify(res.getBody())', 'pm\\.expect\\.fail\\(': 'expect.fail(', 'pm\\.response\\.responseTime': 'res.getResponseTime()', - 'pm\\.globals\\.set\\(': 'bru.setGlobalEnvVar(', - 'pm\\.globals\\.get\\(': 'bru.getGlobalEnvVar(', - // 'pm\\.globals\\.unset\\(': 'bru.deleteGlobalEnvVar(', - 'pm\\.globals\\.toObject\\(': 'bru.getAllGlobalEnvVars(', - // 'pm\\.globals\\.clear\\(': 'bru.deleteAllGlobalEnvVars(', - 'pm\\.environment\\.toObject\\(': 'bru.getAllEnvVars(', - 'pm\\.environment\\.clear\\(': 'bru.deleteAllEnvVars(', - 'pm\\.variables\\.toObject\\(': 'bru.getAllVars(', 'pm\\.request\\.headers\\.remove\\(': 'req.deleteHeader(', 'pm\\.response\\.headers\\.get\\(': 'res.getHeader(', 'pm\\.response\\.to\\.have\\.jsonSchema\\(': 'expect(res.getBody()).to.have.jsonSchema(', @@ -51,7 +66,6 @@ const replacements = { 'pm\\.response\\.responseSize': 'res.getSize().body', 'pm\\.response\\.size\\(\\)\\.header': 'res.getSize().header', 'pm\\.response\\.size\\(\\)\\.total': 'res.getSize().total', - 'pm\\.environment\\.name': 'bru.getEnvName()', 'pm\\.response\\.status': 'res.statusText', 'pm\\.response\\.headers': 'res.getHeaders()', 'tests\\[\'([^\']+)\'\\]\\s*=\\s*([^;]+);': 'test("$1", function() { expect(Boolean($2)).to.be.true; });', @@ -81,9 +95,9 @@ const replacements = { 'request\\.body': 'req.getBody()', 'request\\.name': 'req.getName()', // deprecated translations - 'postman\\.setEnvironmentVariable\\(': 'bru.setEnvVar(', - 'postman\\.getEnvironmentVariable\\(': 'bru.getEnvVar(', - 'postman\\.clearEnvironmentVariable\\(': 'bru.deleteEnvVar(', + 'postman\\.setEnvironmentVariable\\(': 'bru.environment.set(', + 'postman\\.getEnvironmentVariable\\(': 'bru.environment.get(', + 'postman\\.clearEnvironmentVariable\\(': 'bru.environment.unset(', 'pm\\.execution\\.skipRequest\\(\\)': 'bru.runner.skipRequest()', 'pm\\.execution\\.skipRequest': 'bru.runner.skipRequest', 'pm\\.execution\\.setNextRequest\\(null\\)': 'bru.runner.stopExecution()', diff --git a/packages/bruno-converters/src/utils/postman-to-bruno-translator.js b/packages/bruno-converters/src/utils/postman-to-bruno-translator.js index df27aea4c0e..cb665ad1fab 100644 --- a/packages/bruno-converters/src/utils/postman-to-bruno-translator.js +++ b/packages/bruno-converters/src/utils/postman-to-bruno-translator.js @@ -9,28 +9,32 @@ const cloneDeep = require('lodash/cloneDeep'); // e.g., setCollectionVar only sets the variable in the request lifecycle, fails to update the table in the UI. const simpleTranslations = { // Global Variables - 'pm.globals.get': 'bru.getGlobalEnvVar', - 'pm.globals.set': 'bru.setGlobalEnvVar', + 'pm.globals.get': 'bru.globals.get', + 'pm.globals.set': 'bru.globals.set', + 'pm.globals.has': 'bru.globals.has', + // 'pm.globals.unset': 'bru.globals.unset', // TODO: Re-enable once UI sync issue is resolved 'pm.globals.replaceIn': 'bru.interpolate', - // 'pm.globals.unset': 'bru.deleteGlobalEnvVar', - 'pm.globals.toObject': 'bru.getAllGlobalEnvVars', - // 'pm.globals.clear': 'bru.deleteAllGlobalEnvVars', + 'pm.globals.toObject': 'bru.globals.toObject', + // 'pm.globals.clear': 'bru.globals.clear', // TODO: Re-enable once UI sync issue is resolved // Environment variables - 'pm.environment.get': 'bru.getEnvVar', - 'pm.environment.set': 'bru.setEnvVar', - 'pm.environment.name': 'bru.getEnvName()', - 'pm.environment.unset': 'bru.deleteEnvVar', + 'pm.environment.get': 'bru.environment.get', + 'pm.environment.set': 'bru.environment.set', + 'pm.environment.has': 'bru.environment.has', + 'pm.environment.name': 'bru.environment.name', + 'pm.environment.unset': 'bru.environment.unset', 'pm.environment.replaceIn': 'bru.interpolate', - 'pm.environment.toObject': 'bru.getAllEnvVars', - 'pm.environment.clear': 'bru.deleteAllEnvVars', + 'pm.environment.toObject': 'bru.environment.toObject', + 'pm.environment.clear': 'bru.environment.clear', // Variables - 'pm.variables.get': 'bru.getVar', - 'pm.variables.set': 'bru.setVar', - 'pm.variables.has': 'bru.hasVar', - 'pm.variables.toObject': 'bru.getAllVars', + 'pm.variables.get': 'bru.variables.get', + 'pm.variables.set': 'bru.variables.set', + 'pm.variables.has': 'bru.variables.has', + 'pm.variables.unset': 'bru.variables.unset', + 'pm.variables.toObject': 'bru.variables.toObject', 'pm.variables.replaceIn': 'bru.interpolate', + 'pm.variables.clear': 'bru.variables.clear', // Collection variables 'pm.collectionVariables.get': 'bru.getCollectionVar', // 'pm.collectionVariables.set': 'bru.setCollectionVar', @@ -121,9 +125,9 @@ const simpleTranslations = { 'pm.execution.skipRequest': 'bru.runner.skipRequest', // Legacy Postman API (deprecated) (we can use pm instead of postman, as we are converting all postman references to pm in the code as the part of pre-processing) - 'pm.setEnvironmentVariable': 'bru.setEnvVar', - 'pm.getEnvironmentVariable': 'bru.getEnvVar', - 'pm.clearEnvironmentVariable': 'bru.deleteEnvVar', + 'pm.setEnvironmentVariable': 'bru.environment.set', + 'pm.getEnvironmentVariable': 'bru.environment.get', + 'pm.clearEnvironmentVariable': 'bru.environment.unset', // Legacy response properties 'responseCode.code': 'res.getStatus()', @@ -144,31 +148,6 @@ const complexTransformations = [ transform: sendRequestTransformer }, - // pm.environment.has requires special handling - { - pattern: 'pm.environment.has', - transform: (path, j) => { - const callExpr = path.parent.value; - - const args = callExpr.arguments; - - // Create: bru.getEnvVar(arg) !== undefined && bru.getEnvVar(arg) !== null - return j.logicalExpression( - '&&', - j.binaryExpression( - '!==', - j.callExpression(j.identifier('bru.getEnvVar'), args), - j.identifier('undefined') - ), - j.binaryExpression( - '!==', - j.callExpression(j.identifier('bru.getEnvVar'), args), - j.identifier('null') - ) - ); - } - }, - { pattern: 'pm.response.text', transform: (_, j) => { @@ -289,30 +268,6 @@ const complexTransformations = [ } }, - // pm.globals.has requires special handling - { - pattern: 'pm.globals.has', - transform: (path, j) => { - const callExpr = path.parent.value; - const args = callExpr.arguments; - - // Create: bru.getGlobalEnvVar(arg) !== undefined && bru.getGlobalEnvVar(arg) !== null - return j.logicalExpression( - '&&', - j.binaryExpression( - '!==', - j.callExpression(j.identifier('bru.getGlobalEnvVar'), args), - j.identifier('undefined') - ), - j.binaryExpression( - '!==', - j.callExpression(j.identifier('bru.getGlobalEnvVar'), args), - j.identifier('null') - ) - ); - } - }, - // pm.request.headers.add({key, value}) -> req.setHeader(key, value) { pattern: 'pm.request.headers.add', diff --git a/packages/bruno-converters/tests/postman/postman-translations/postman-comments.spec.js b/packages/bruno-converters/tests/postman/postman-translations/postman-comments.spec.js index e65075c27ce..5ff70102103 100644 --- a/packages/bruno-converters/tests/postman/postman-translations/postman-comments.spec.js +++ b/packages/bruno-converters/tests/postman/postman-translations/postman-comments.spec.js @@ -9,7 +9,7 @@ describe('postmanTranslations - comment handling', () => { `; const result = postmanTranslation(inputScript); expect(result).toContain('console.log(\'This script does not contain pm commands.\');'); - expect(result).toContain('const data = bru.getEnvVar(\'key\');'); + expect(result).toContain('const data = bru.environment.get(\'key\');'); }); // TODO: Restore once UI update fixes are live for setCollectionVar @@ -27,7 +27,7 @@ describe('postmanTranslations - comment handling', () => { test('should handle multiple pm commands on the same line', () => { const inputScript = 'pm.environment.get(\'key\'); pm.environment.set(\'key\', \'value\');'; - const expectedOutput = 'bru.getEnvVar(\'key\'); bru.setEnvVar(\'key\', \'value\');'; + const expectedOutput = 'bru.environment.get(\'key\'); bru.environment.set(\'key\', \'value\');'; expect(postmanTranslation(inputScript)).toBe(expectedOutput); }); @@ -45,11 +45,11 @@ describe('postmanTranslations - comment handling', () => { const expectedOutput = ` // This is a comment const value = 'test'; - bru.setEnvVar('key', value); + bru.environment.set('key', value); /* Multi-line comment */ - const result = bru.getEnvVar('key'); + const result = bru.environment.get('key'); console.log('Result:', result); `; expect(postmanTranslation(inputScript)).toBe(expectedOutput); diff --git a/packages/bruno-converters/tests/postman/postman-translations/postman-edge-cases.spec.js b/packages/bruno-converters/tests/postman/postman-translations/postman-edge-cases.spec.js index 22c567c5d85..83224d4c63a 100644 --- a/packages/bruno-converters/tests/postman/postman-translations/postman-edge-cases.spec.js +++ b/packages/bruno-converters/tests/postman/postman-translations/postman-edge-cases.spec.js @@ -25,8 +25,8 @@ describe('postmanTranslations - edge cases', () => { const expectedOutput = ` const sampleObjects = [ { - key: bru.getEnvVar('key'), - value: bru.getVar('value') + key: bru.environment.get('key'), + value: bru.variables.get('value') }, { key: bru.getCollectionVar('key'), @@ -35,11 +35,11 @@ describe('postmanTranslations - edge cases', () => { ]; const dataTesting = Object.entries(sampleObjects || {}).reduce((acc, [key, value]) => { // this is a comment - acc[key] = bru.getCollectionVar(bru.getEnvVar(value)); + acc[key] = bru.getCollectionVar(bru.environment.get(value)); return acc; // Return the accumulator }, {}); Object.values(dataTesting).forEach((data) => { - bru.setEnvVar(data.key, bru.getVar(data.value)); + bru.environment.set(data.key, bru.variables.get(data.value)); }); `; expect(postmanTranslation(inputScript)).toBe(expectedOutput); diff --git a/packages/bruno-converters/tests/postman/postman-translations/postman-variables.spec.js b/packages/bruno-converters/tests/postman/postman-translations/postman-variables.spec.js index a178d422dee..c93463e7714 100644 --- a/packages/bruno-converters/tests/postman/postman-translations/postman-variables.spec.js +++ b/packages/bruno-converters/tests/postman/postman-translations/postman-variables.spec.js @@ -7,8 +7,8 @@ describe('postmanTranslations - variables commands', () => { pm.environment.set('key', 'value'); `; const result = postmanTranslation(inputScript); - expect(result).toContain('bru.getEnvVar(\'key\')'); - expect(result).toContain('bru.setEnvVar(\'key\', \'value\')'); + expect(result).toContain('bru.environment.get(\'key\')'); + expect(result).toContain('bru.environment.set(\'key\', \'value\')'); }); test('should translate runtime variable commands', () => { @@ -17,8 +17,8 @@ describe('postmanTranslations - variables commands', () => { pm.variables.set('key', 'value'); `; const result = postmanTranslation(inputScript); - expect(result).toContain('bru.getVar(\'key\')'); - expect(result).toContain('bru.setVar(\'key\', \'value\')'); + expect(result).toContain('bru.variables.get(\'key\')'); + expect(result).toContain('bru.variables.set(\'key\', \'value\')'); }); test('should translate pm.collectionVariables.get', () => { @@ -30,7 +30,7 @@ describe('postmanTranslations - variables commands', () => { test('should translate pm.expect with pm.environment.has', () => { const inputScript = 'pm.expect(pm.environment.has(\'key\')).to.be.true;'; const result = postmanTranslation(inputScript); - expect(result).toContain('bru.getEnvVar(\'key\') !== undefined && bru.getEnvVar(\'key\') !== null'); + expect(result).toContain('bru.environment.has(\'key\')'); expect(result).toContain('.to.be.true'); }); diff --git a/packages/bruno-converters/tests/postman/postman-translations/transpiler-tests/combined.test.js b/packages/bruno-converters/tests/postman/postman-translations/transpiler-tests/combined.test.js index 82cafe46fdf..21586b0c0cb 100644 --- a/packages/bruno-converters/tests/postman/postman-translations/transpiler-tests/combined.test.js +++ b/packages/bruno-converters/tests/postman/postman-translations/transpiler-tests/combined.test.js @@ -93,11 +93,11 @@ describe('Combined API Features Translation', () => { expect(translatedCode).not.toContain('pm.test("Auth flow works", function() {'); expect(translatedCode).not.toContain('pm.expect(response.authenticated).to.be.true;'); expect(translatedCode).not.toContain('pm.environment.set("userId", response.user.id);'); - expect(translatedCode).toContain('const token = bru.getEnvVar("authToken");'); + expect(translatedCode).toContain('const token = bru.environment.get("authToken");'); expect(translatedCode).toContain('test("Auth flow works", function() {'); expect(translatedCode).toContain('const response = res.getBody();'); expect(translatedCode).toContain('expect(response.authenticated).to.be.true;'); - expect(translatedCode).toContain('bru.setEnvVar("userId", response.user.id);'); + expect(translatedCode).toContain('bru.environment.set("userId", response.user.id);'); }); // TODO: Restore once UI update fixes are live for setCollectionVar @@ -111,14 +111,14 @@ describe('Combined API Features Translation', () => { it('should handle nested Postman API calls', () => { const code = 'pm.environment.set("computed", pm.variables.get("base") + "-suffix");'; const translatedCode = translateCode(code); - expect(translatedCode).toBe('bru.setEnvVar("computed", bru.getVar("base") + "-suffix");'); + expect(translatedCode).toBe('bru.environment.set("computed", bru.variables.get("base") + "-suffix");'); }); // TODO: Restore once UI update fixes are live for setCollectionVar it.skip('should handle more complex nested expressions', () => { const code = 'pm.collectionVariables.set("fullPath", pm.environment.get("baseUrl") + pm.variables.get("endpoint"));'; const translatedCode = translateCode(code); - expect(translatedCode).toBe('bru.setCollectionVar("fullPath", bru.getEnvVar("baseUrl") + bru.getVar("endpoint"));'); + expect(translatedCode).toBe('bru.setCollectionVar("fullPath", bru.environment.get("baseUrl") + bru.variables.get("endpoint"));'); }); // Unrelated code @@ -141,7 +141,7 @@ describe('Combined API Features Translation', () => { }; `; const translatedCode = translateCode(code); - expect(translatedCode).toContain('return "Bearer " + bru.getEnvVar("token");'); + expect(translatedCode).toContain('return "Bearer " + bru.environment.get("token");'); }); it('should handle aliases with object destructuring', () => { @@ -153,8 +153,8 @@ describe('Combined API Features Translation', () => { const translatedCode = translateCode(code); expect(translatedCode).toBe(` - bru.setEnvVar("token", "abc123"); - bru.getVar("userId"); + bru.environment.set("token", "abc123"); + bru.variables.get("userId"); `); }); @@ -169,7 +169,7 @@ describe('Combined API Features Translation', () => { const translatedCode = translateCode(code); expect(translatedCode).toBe(` function getAuthHeader() { - return "Bearer " + bru.getEnvVar("token"); + return "Bearer " + bru.environment.get("token"); } `); }); @@ -354,8 +354,8 @@ describe('Combined API Features Translation', () => { const translatedCode = translateCode(code); expect(translatedCode).toContain('test("Status code is 200", function() { expect(res.getStatus()).to.equal(200); });'); - expect(translatedCode).toContain('bru.setEnvVar("userId", res.getBody().userId);'); - expect(translatedCode).toContain('bru.setVar("token", res.getBody().token);'); + expect(translatedCode).toContain('bru.environment.set("userId", res.getBody().userId);'); + expect(translatedCode).toContain('bru.variables.set("token", res.getBody().token);'); }); // TODO: Restore once UI update fixes are live for setCollectionVar @@ -377,7 +377,7 @@ describe('Combined API Features Translation', () => { `; const translatedCode = translateCode(code); expect(translatedCode).toBe(` - bru.getCollectionVar(bru.getEnvVar('key')) + bru.getCollectionVar(bru.environment.get('key')) test("Status code is 200", function() { expect(res.getStatus()).to.equal(200); }); @@ -393,8 +393,8 @@ describe('Combined API Features Translation', () => { `; const translatedCode = translateCode(code); - expect(translatedCode).toContain('const baseUrl = bru.getEnvVar("baseUrl");'); - expect(translatedCode).toContain('const endpoint = bru.getVar("endpoint");'); + expect(translatedCode).toContain('const baseUrl = bru.environment.get("baseUrl");'); + expect(translatedCode).toContain('const endpoint = bru.variables.get("endpoint");'); expect(translatedCode).toContain('const url = `${baseUrl}/api/${endpoint}`;'); expect(translatedCode).toContain('console.log(`Response status: ${res.getStatus()}`);'); }); @@ -408,9 +408,9 @@ describe('Combined API Features Translation', () => { `; const translatedCode = translateCode(code); - expect(translatedCode).toContain('const getAuthHeader = () => "Bearer " + bru.getEnvVar("token");'); + expect(translatedCode).toContain('const getAuthHeader = () => "Bearer " + bru.environment.get("token");'); expect(translatedCode).toContain('const processItems = items => items.forEach(item => {'); - expect(translatedCode).toContain('bru.setVar(item.key, item.value);'); + expect(translatedCode).toContain('bru.variables.set(item.key, item.value);'); }); it('test', () => { @@ -420,7 +420,7 @@ describe('Combined API Features Translation', () => { `; const translatedCode = translateCode(code); expect(translatedCode).toBe(` - const key = bru.getGlobalEnvVar("key"); + const key = bru.globals.get("key"); `); }); @@ -460,8 +460,8 @@ describe('Combined API Features Translation', () => { const expectedOutput = ` const expectedResponse = { - id: bru.getEnvVar("userId"), - token: bru.getVar("authToken"), + id: bru.environment.get("userId"), + token: bru.variables.get("authToken"), timestamp: new Date().getTime() }; diff --git a/packages/bruno-converters/tests/postman/postman-translations/transpiler-tests/environment.test.js b/packages/bruno-converters/tests/postman/postman-translations/transpiler-tests/environment.test.js index 1060886b8aa..f0cfd414bae 100644 --- a/packages/bruno-converters/tests/postman/postman-translations/transpiler-tests/environment.test.js +++ b/packages/bruno-converters/tests/postman/postman-translations/transpiler-tests/environment.test.js @@ -4,49 +4,49 @@ describe('Environment Variable Translation', () => { it('should translate pm.environment.get', () => { const code = 'pm.environment.get("test");'; const translatedCode = translateCode(code); - expect(translatedCode).toBe('bru.getEnvVar("test");'); + expect(translatedCode).toBe('bru.environment.get("test");'); }); it('should translate pm.environment.set', () => { const code = 'pm.environment.set("test", "value");'; const translatedCode = translateCode(code); - expect(translatedCode).toBe('bru.setEnvVar("test", "value");'); + expect(translatedCode).toBe('bru.environment.set("test", "value");'); }); it('should translate pm.environment.has', () => { const code = 'pm.environment.has("test")'; const translatedCode = translateCode(code); - expect(translatedCode).toBe('bru.getEnvVar("test") !== undefined && bru.getEnvVar("test") !== null'); + expect(translatedCode).toBe('bru.environment.has("test")'); }); it('should translate pm.environment.unset', () => { const code = 'pm.environment.unset("test");'; const translatedCode = translateCode(code); - expect(translatedCode).toBe('bru.deleteEnvVar("test");'); + expect(translatedCode).toBe('bru.environment.unset("test");'); }); it('should translate pm.environment.name', () => { const code = 'pm.environment.name;'; const translatedCode = translateCode(code); - expect(translatedCode).toBe('bru.getEnvName();'); + expect(translatedCode).toBe('bru.environment.name;'); }); it('should handle nested Postman API calls with environment', () => { const code = 'pm.environment.set("computed", pm.variables.get("base") + "-suffix");'; const translatedCode = translateCode(code); - expect(translatedCode).toBe('bru.setEnvVar("computed", bru.getVar("base") + "-suffix");'); + expect(translatedCode).toBe('bru.environment.set("computed", bru.variables.get("base") + "-suffix");'); }); it('should handle JSON operations with environment variables', () => { const code = 'pm.environment.set("user", JSON.stringify({ id: 123, name: "John" }));'; const translatedCode = translateCode(code); - expect(translatedCode).toBe('bru.setEnvVar("user", JSON.stringify({ id: 123, name: "John" }));'); + expect(translatedCode).toBe('bru.environment.set("user", JSON.stringify({ id: 123, name: "John" }));'); }); it('should handle JSON.parse with environment variables', () => { const code = 'const userData = JSON.parse(pm.environment.get("user"));'; const translatedCode = translateCode(code); - expect(translatedCode).toBe('const userData = JSON.parse(bru.getEnvVar("user"));'); + expect(translatedCode).toBe('const userData = JSON.parse(bru.environment.get("user"));'); }); it('should translate pm.environment.name with different access patterns', () => { @@ -58,9 +58,9 @@ describe('Environment Variable Translation', () => { `; const translatedCode = translateCode(code); expect(translatedCode).toBe(` - const envName1 = bru.getEnvName(); - const envName2 = bru.getEnvName(); - console.log(bru.getEnvName()); + const envName1 = bru.environment.name; + const envName2 = bru.environment.name; + console.log(bru.environment.name); `); }); @@ -75,11 +75,11 @@ describe('Environment Variable Translation', () => { `; const translatedCode = translateCode(code); expect(translatedCode).toBe(` - const name = bru.getEnvName(); - const has = bru.getEnvVar("test") !== undefined && bru.getEnvVar("test") !== null; - const set = bru.setEnvVar("test", "value"); - const get = bru.getEnvVar("test"); - const unset = bru.deleteEnvVar("test"); + const name = bru.environment.name; + const has = bru.environment.has("test"); + const set = bru.environment.set("test", "value"); + const get = bru.environment.get("test"); + const unset = bru.environment.unset("test"); `); }); @@ -87,19 +87,19 @@ describe('Environment Variable Translation', () => { it('should translate postman.setEnvironmentVariable', () => { const code = 'postman.setEnvironmentVariable("apiKey", "abc123");'; const translatedCode = translateCode(code); - expect(translatedCode).toBe('bru.setEnvVar("apiKey", "abc123");'); + expect(translatedCode).toBe('bru.environment.set("apiKey", "abc123");'); }); it('should translate postman.getEnvironmentVariable', () => { const code = 'const baseUrl = postman.getEnvironmentVariable("baseUrl");'; const translatedCode = translateCode(code); - expect(translatedCode).toBe('const baseUrl = bru.getEnvVar("baseUrl");'); + expect(translatedCode).toBe('const baseUrl = bru.environment.get("baseUrl");'); }); it('should translate postman.clearEnvironmentVariable', () => { const code = 'postman.clearEnvironmentVariable("tempToken");'; const translatedCode = translateCode(code); - expect(translatedCode).toBe('bru.deleteEnvVar("tempToken");'); + expect(translatedCode).toBe('bru.environment.unset("tempToken");'); }); it('should handle all environment variable methods together', () => { @@ -114,10 +114,10 @@ describe('Environment Variable Translation', () => { `; const translatedCode = translateCode(code); - expect(translatedCode).toContain('const envName = bru.getEnvName();'); - expect(translatedCode).toContain('const hasToken = bru.getEnvVar("token") !== undefined && bru.getEnvVar("token") !== null;'); - expect(translatedCode).toContain('const token = bru.getEnvVar("token");'); - expect(translatedCode).toContain('bru.setEnvVar("timestamp", new Date().toISOString());'); + expect(translatedCode).toContain('const envName = bru.environment.name;'); + expect(translatedCode).toContain('const hasToken = bru.environment.has("token");'); + expect(translatedCode).toContain('const token = bru.environment.get("token");'); + expect(translatedCode).toContain('bru.environment.set("timestamp", new Date().toISOString());'); }); // Additional robust tests for environment variables @@ -129,8 +129,8 @@ describe('Environment Variable Translation', () => { const computedValue = pm.environment.get(prefix + "_" + suffix); `; const translatedCode = translateCode(code); - expect(translatedCode).toContain('bru.setEnvVar(prefix + "_" + suffix, "abc123");'); - expect(translatedCode).toContain('const computedValue = bru.getEnvVar(prefix + "_" + suffix);'); + expect(translatedCode).toContain('bru.environment.set(prefix + "_" + suffix, "abc123");'); + expect(translatedCode).toContain('const computedValue = bru.environment.get(prefix + "_" + suffix);'); }); it('should handle environment variables in complex object structures', () => { @@ -146,11 +146,11 @@ describe('Environment Variable Translation', () => { }; `; const translatedCode = translateCode(code); - expect(translatedCode).toContain('baseUrl: bru.getEnvVar("apiUrl"),'); - expect(translatedCode).toContain('"Authorization": "Bearer " + bru.getEnvVar("token"),'); - expect(translatedCode).toContain('"X-Api-Key": bru.getEnvVar("apiKey") || "default-key"'); - expect(translatedCode).toContain('timeout: parseInt(bru.getEnvVar("timeout") || "5000"),'); - expect(translatedCode).toContain('validate: bru.getEnvVar("validateResponses") !== undefined && bru.getEnvVar("validateResponses") !== null'); + expect(translatedCode).toContain('baseUrl: bru.environment.get("apiUrl"),'); + expect(translatedCode).toContain('"Authorization": "Bearer " + bru.environment.get("token"),'); + expect(translatedCode).toContain('"X-Api-Key": bru.environment.get("apiKey") || "default-key"'); + expect(translatedCode).toContain('timeout: parseInt(bru.environment.get("timeout") || "5000"),'); + expect(translatedCode).toContain('validate: bru.environment.has("validateResponses")'); }); it('should handle environment variables in conditionals correctly', () => { @@ -166,8 +166,8 @@ describe('Environment Variable Translation', () => { } `; const translatedCode = translateCode(code); - expect(translatedCode).toContain('if (bru.getEnvVar("apiKey") !== undefined && bru.getEnvVar("apiKey") !== null) {'); - expect(translatedCode).toContain('if (bru.getEnvVar("apiKey").length > 0) {'); + expect(translatedCode).toContain('if (bru.environment.has("apiKey")) {'); + expect(translatedCode).toContain('if (bru.environment.get("apiKey").length > 0) {'); }); it('should handle multiple levels of environment variable aliasing', () => { @@ -180,9 +180,9 @@ describe('Environment Variable Translation', () => { `; const translatedCode = translateCode(code); expect(translatedCode).toBe(` - bru.setEnvVar("key", "value"); - const value = bru.getEnvVar("key"); - const exists = bru.getEnvVar("key") !== undefined && bru.getEnvVar("key") !== null; + bru.environment.set("key", "value"); + const value = bru.environment.get("key"); + const exists = bru.environment.has("key"); `); }); @@ -202,9 +202,9 @@ describe('Environment Variable Translation', () => { pm.environment.set("tokenExpiry", expiryTime.getTime()); `; const translatedCode = translateCode(code); - expect(translatedCode).toContain('bru.setEnvVar("requestTimestamp", timestamp);'); - expect(translatedCode).toContain('bru.setEnvVar("requestId", uniqueId);'); - expect(translatedCode).toContain('bru.setEnvVar("tokenExpiry", expiryTime.getTime());'); + expect(translatedCode).toContain('bru.environment.set("requestTimestamp", timestamp);'); + expect(translatedCode).toContain('bru.environment.set("requestId", uniqueId);'); + expect(translatedCode).toContain('bru.environment.set("tokenExpiry", expiryTime.getTime());'); }); it('should handle environment variables in try-catch blocks', () => { @@ -219,8 +219,8 @@ describe('Environment Variable Translation', () => { } `; const translatedCode = translateCode(code); - expect(translatedCode).toContain('const configStr = bru.getEnvVar("config");'); - expect(translatedCode).toContain('bru.setEnvVar("configError", error.message);'); + expect(translatedCode).toContain('const configStr = bru.environment.get("config");'); + expect(translatedCode).toContain('bru.environment.set("configError", error.message);'); }); it('should handle legacy environment and pm.setEnvironmentVariable together', () => { @@ -235,8 +235,8 @@ describe('Environment Variable Translation', () => { pm.setEnvironmentVariable("thirdKey", "thirdValue"); `; const translatedCode = translateCode(code); - expect(translatedCode).toContain('bru.setEnvVar("legacyKey", "legacyValue");'); - expect(translatedCode).toContain('const value = bru.getEnvVar("anotherKey");'); - expect(translatedCode).toContain('bru.setEnvVar("thirdKey", "thirdValue");'); + expect(translatedCode).toContain('bru.environment.set("legacyKey", "legacyValue");'); + expect(translatedCode).toContain('const value = bru.environment.get("anotherKey");'); + expect(translatedCode).toContain('bru.environment.set("thirdKey", "thirdValue");'); }); }); diff --git a/packages/bruno-converters/tests/postman/postman-translations/transpiler-tests/legacy-tests-syntax.test.js b/packages/bruno-converters/tests/postman/postman-translations/transpiler-tests/legacy-tests-syntax.test.js index b8beb8aea85..df903706998 100644 --- a/packages/bruno-converters/tests/postman/postman-translations/transpiler-tests/legacy-tests-syntax.test.js +++ b/packages/bruno-converters/tests/postman/postman-translations/transpiler-tests/legacy-tests-syntax.test.js @@ -83,7 +83,7 @@ describe('Legacy Tests[] Syntax Translation', () => { const translatedCode = translateCode(code); expect(translatedCode).toBe(` test("Response matches environment variable", function() { - expect(Boolean(res.getBody().id === bru.getEnvVar("expectedId"))).to.be.true; + expect(Boolean(res.getBody().id === bru.environment.get("expectedId"))).to.be.true; });`); }); @@ -98,7 +98,7 @@ describe('Legacy Tests[] Syntax Translation', () => { // but we can check for key transformations expect(translatedCode).toContain('test("Authentication header is present"'); expect(translatedCode).toContain('test("Data count is correct"'); - expect(translatedCode).toContain('res.getBody().items.length === bru.getVar("expectedCount")'); + expect(translatedCode).toContain('res.getBody().items.length === bru.variables.get("expectedCount")'); }); // Additional robust tests for legacy tests[] syntax @@ -274,10 +274,10 @@ describe('Legacy Tests[] Syntax Translation', () => { expect(translatedCode).toContain('expect(Boolean(res.getHeaders().has("Content-Type"))).to.be.true;'); expect(translatedCode).toContain('test("Content-Type is JSON", function() {'); expect(translatedCode).toContain('expect(Boolean(res.getHeader("Content-Type").includes("application/json"))).to.be.true;'); - expect(translatedCode).toContain('const expectedItems = parseInt(bru.getEnvVar("expectedItemCount"));'); + expect(translatedCode).toContain('const expectedItems = parseInt(bru.environment.get("expectedItemCount"));'); expect(translatedCode).toContain('test("Has correct number of items", function() {'); expect(translatedCode).toContain('expect(Boolean(response.items.length === expectedItems)).to.be.true;'); - expect(translatedCode).toContain('const targetItem = response.items.find(item => item.name === bru.getVar("targetItemName"));'); - expect(translatedCode).toContain('bru.setEnvVar("targetItemId", targetItem.id);'); + expect(translatedCode).toContain('const targetItem = response.items.find(item => item.name === bru.variables.get("targetItemName"));'); + expect(translatedCode).toContain('bru.environment.set("targetItemId", targetItem.id);'); }); }); diff --git a/packages/bruno-converters/tests/postman/postman-translations/transpiler-tests/multiline-syntax.test.js b/packages/bruno-converters/tests/postman/postman-translations/transpiler-tests/multiline-syntax.test.js index 88a3b9db92a..445d6d384f9 100644 --- a/packages/bruno-converters/tests/postman/postman-translations/transpiler-tests/multiline-syntax.test.js +++ b/packages/bruno-converters/tests/postman/postman-translations/transpiler-tests/multiline-syntax.test.js @@ -12,9 +12,9 @@ describe('Multiline Syntax Handling', () => { `; const translatedCode = translateCode(code); expect(translatedCode).toBe(` - const userId = bru.getVar("userId"); - bru.setVar("timestamp", new Date().toISOString()); - const hasToken = bru.hasVar("token"); + const userId = bru.variables.get("userId"); + bru.variables.set("timestamp", new Date().toISOString()); + const hasToken = bru.variables.has("token"); `); }); @@ -29,8 +29,8 @@ describe('Multiline Syntax Handling', () => { `; const translatedCode = translateCode(code); expect(translatedCode).toBe(` - const baseUrl = bru.getEnvVar("baseUrl"); - bru.setEnvVar("requestTime", Date.now()); + const baseUrl = bru.environment.get("baseUrl"); + bru.environment.set("requestTime", Date.now()); `); }); @@ -62,7 +62,7 @@ describe('Multiline Syntax Handling', () => { `; const translatedCode = translateCode(code); expect(translatedCode).toBe(` - if (bru.getEnvVar("apiKey") !== undefined && bru.getEnvVar("apiKey") !== null) { + if (bru.environment.has("apiKey")) { console.log("API Key exists"); } `); @@ -152,16 +152,16 @@ describe('Multiline Syntax Handling', () => { const translatedCode = translateCode(code); expect(translatedCode).toBe(` // Normal syntax - const normalVar = bru.getVar("normal"); + const normalVar = bru.variables.get("normal"); // Multiline syntax - const multilineVar = bru.getVar("multiline"); + const multilineVar = bru.variables.get("multiline"); // Normal syntax again - bru.setVar("normalSet", "value"); + bru.variables.set("normalSet", "value"); // Multiline syntax again - bru.setVar("multilineSet", "value"); + bru.variables.set("multilineSet", "value"); `); }); @@ -261,17 +261,17 @@ describe('Multiline Syntax Handling', () => { const translatedCode = translateCode(code); - expect(translatedCode).toContain('const baseUrl = bru.getEnvVar("baseUrl")'); - expect(translatedCode).toContain('const apiKey = bru.getEnvVar("apiKey")'); - expect(translatedCode).toContain('const userId = bru.getEnvVar("userId")'); + expect(translatedCode).toContain('const baseUrl = bru.environment.get("baseUrl")'); + expect(translatedCode).toContain('const apiKey = bru.environment.get("apiKey")'); + expect(translatedCode).toContain('const userId = bru.environment.get("userId")'); // Check variables translations - expect(translatedCode).toContain('bru.setVar("testId", "test-" + Date.now())'); - expect(translatedCode).toContain('bru.setVar("timestamp", new Date().toISOString())'); + expect(translatedCode).toContain('bru.variables.set("testId", "test-" + Date.now())'); + expect(translatedCode).toContain('bru.variables.set("timestamp", new Date().toISOString())'); // Check complex conditionals - expect(translatedCode).toContain('if (bru.getEnvVar("apiKey") !== undefined && bru.getEnvVar("apiKey") !== null &&'); - expect(translatedCode).toContain('bru.hasVar("testId"))'); + expect(translatedCode).toContain('if (bru.environment.has("apiKey") &&'); + expect(translatedCode).toContain('bru.variables.has("testId"))'); // Check response testing expect(translatedCode).toContain('expect(res.getStatus()).to.equal(200)'); diff --git a/packages/bruno-converters/tests/postman/postman-translations/transpiler-tests/postman-references.test.js b/packages/bruno-converters/tests/postman/postman-translations/transpiler-tests/postman-references.test.js index a7618561f8a..8ce349a1898 100644 --- a/packages/bruno-converters/tests/postman/postman-translations/transpiler-tests/postman-references.test.js +++ b/packages/bruno-converters/tests/postman/postman-translations/transpiler-tests/postman-references.test.js @@ -5,7 +5,7 @@ describe('Postman to PM References Conversion', () => { it('should convert basic postman references to pm', () => { const code = 'postman.setEnvironmentVariable("key", "value");'; const translatedCode = translateCode(code); - expect(translatedCode).toBe('bru.setEnvVar("key", "value");'); + expect(translatedCode).toBe('bru.environment.set("key", "value");'); // The key part is that it should convert postman.* to pm.* internally before // translating to bru.* APIs }); @@ -13,7 +13,7 @@ describe('Postman to PM References Conversion', () => { it('should convert postman variable access to pm', () => { const code = 'const value = postman.getEnvironmentVariable("key");'; const translatedCode = translateCode(code); - expect(translatedCode).toBe('const value = bru.getEnvVar("key");'); + expect(translatedCode).toBe('const value = bru.environment.get("key");'); }); it('should handle postman variable assignments', () => { @@ -22,8 +22,8 @@ describe('Postman to PM References Conversion', () => { const baseUrl = postman.environment.get("baseUrl"); `; const translatedCode = translateCode(code); - expect(translatedCode).toContain('const envVar = bru.getEnvVar("apiKey");'); - expect(translatedCode).toContain('const baseUrl = bru.getEnvVar("baseUrl");'); + expect(translatedCode).toContain('const envVar = bru.environment.get("apiKey");'); + expect(translatedCode).toContain('const baseUrl = bru.environment.get("baseUrl");'); }); // More complex patterns @@ -40,8 +40,8 @@ describe('Postman to PM References Conversion', () => { `; const translatedCode = translateCode(code); - expect(translatedCode).toContain('const apiKey = bru.getEnvVar("apiKey");'); - expect(translatedCode).toContain('const baseUrl = bru.getEnvVar("baseUrl");'); + expect(translatedCode).toContain('const apiKey = bru.environment.get("apiKey");'); + expect(translatedCode).toContain('const baseUrl = bru.environment.get("baseUrl");'); expect(translatedCode).toContain('test("Status code is 200", function() {'); expect(translatedCode).toContain('expect(res.getStatus()).to.equal(200);'); }); @@ -53,7 +53,7 @@ describe('Postman to PM References Conversion', () => { `; const translatedCode = translateCode(code); - expect(translatedCode).toContain('bru.setEnvVar("key", "value");'); + expect(translatedCode).toContain('bru.environment.set("key", "value");'); }); // Complex control flows @@ -69,10 +69,10 @@ describe('Postman to PM References Conversion', () => { `; const translatedCode = translateCode(code); - expect(translatedCode).toContain('if (bru.getEnvVar("isProduction") === "true") {'); - expect(translatedCode).toContain('const apiUrl = bru.getEnvVar("prodUrl");'); + expect(translatedCode).toContain('if (bru.environment.get("isProduction") === "true") {'); + expect(translatedCode).toContain('const apiUrl = bru.environment.get("prodUrl");'); expect(translatedCode).toContain('bru.setNextRequest("Production Flow");'); - expect(translatedCode).toContain('const apiUrl = bru.getEnvVar("devUrl");'); + expect(translatedCode).toContain('const apiUrl = bru.environment.get("devUrl");'); expect(translatedCode).toContain('bru.setNextRequest("Development Flow");'); }); @@ -90,7 +90,7 @@ describe('Postman to PM References Conversion', () => { const translatedCode = translateCode(code); expect(translatedCode).toContain('const responseCode = res.getStatus();'); expect(translatedCode).toContain('const responseBody = res.getBody();'); - expect(translatedCode).toContain('bru.setEnvVar("lastResponseCode", responseCode);'); + expect(translatedCode).toContain('bru.environment.set("lastResponseCode", responseCode);'); }); // Postman in string literals should be untouched @@ -124,8 +124,8 @@ describe('Postman to PM References Conversion', () => { const translatedCode = translateCode(code); // Should handle the aliases properly - expect(translatedCode).toContain('const apiKey = bru.getEnvVar("apiKey");'); - expect(translatedCode).toContain('const userId = bru.getEnvVar("userId");'); + expect(translatedCode).toContain('const apiKey = bru.environment.get("apiKey");'); + expect(translatedCode).toContain('const userId = bru.environment.get("userId");'); expect(translatedCode).toContain('test("Response is valid", function() {'); expect(translatedCode).toContain('expect(code).to.equal(200);'); }); diff --git a/packages/bruno-converters/tests/postman/postman-translations/transpiler-tests/request.test.js b/packages/bruno-converters/tests/postman/postman-translations/transpiler-tests/request.test.js index c7802001781..036b18a7e07 100644 --- a/packages/bruno-converters/tests/postman/postman-translations/transpiler-tests/request.test.js +++ b/packages/bruno-converters/tests/postman/postman-translations/transpiler-tests/request.test.js @@ -102,8 +102,8 @@ describe('Request Translation', () => { `; const translatedCode = translateCode(code); expect(translatedCode).toContain('const requestData = req.getBody();'); - expect(translatedCode).toContain('bru.setVar("lastRequestBody", JSON.stringify(requestData));'); - expect(translatedCode).toContain('bru.setEnvVar("lastContentType", contentType);'); + expect(translatedCode).toContain('bru.variables.set("lastRequestBody", JSON.stringify(requestData));'); + expect(translatedCode).toContain('bru.environment.set("lastContentType", contentType);'); }); it('should translate legacy request.* properties', () => { diff --git a/packages/bruno-converters/tests/postman/postman-translations/transpiler-tests/response.test.js b/packages/bruno-converters/tests/postman/postman-translations/transpiler-tests/response.test.js index 8e11b873d28..76091d68989 100644 --- a/packages/bruno-converters/tests/postman/postman-translations/transpiler-tests/response.test.js +++ b/packages/bruno-converters/tests/postman/postman-translations/transpiler-tests/response.test.js @@ -238,7 +238,7 @@ describe('Response Translation', () => { expect(translatedCode).not.toContain('const resp = pm.response;'); expect(translatedCode).toContain('const data = res.getBody();'); - expect(translatedCode).toContain('bru.setEnvVar("userId", data.user.id);'); + expect(translatedCode).toContain('bru.environment.set("userId", data.user.id);'); }); it('should handle all response property methods together', () => { @@ -338,7 +338,7 @@ describe('Response Translation', () => { expect(translatedCode).toContain('const { id, name, items } = res.getBody();'); expect(translatedCode).toContain('const [first, second] = items;'); - expect(translatedCode).toContain('bru.setEnvVar("userId", id);'); + expect(translatedCode).toContain('bru.environment.set("userId", id);'); }); it('should handle response in complex conditionals', () => { @@ -364,7 +364,7 @@ describe('Response Translation', () => { expect(translatedCode).toContain('if (res.getStatus() >= 200 && res.getStatus() < 300) {'); expect(translatedCode).toContain('if (res.getHeader(\'Content-Type\').includes(\'application/json\')) {'); expect(translatedCode).toContain('const data = res.getBody();'); - expect(translatedCode).toContain('bru.setEnvVar("authToken", data.token);'); + expect(translatedCode).toContain('bru.environment.set("authToken", data.token);'); expect(translatedCode).toContain('} else if (res.getStatus() === 404) {'); expect(translatedCode).toContain('console.error("Request failed with status:", res.getStatus());'); }); @@ -383,9 +383,9 @@ describe('Response Translation', () => { const translatedCode = translateCode(code); expect(translatedCode).toContain('const data = res.getBody();'); - expect(translatedCode).toContain('bru.setEnvVar("userData", JSON.stringify(data.user));'); + expect(translatedCode).toContain('bru.environment.set("userData", JSON.stringify(data.user));'); expect(translatedCode).toContain('const text = JSON.stringify(res.getBody());'); - expect(translatedCode).toContain('bru.setEnvVar("rawResponse", text);'); + expect(translatedCode).toContain('bru.environment.set("rawResponse", text);'); }); it('should handle JSON path style access to response data', () => { @@ -403,7 +403,7 @@ describe('Response Translation', () => { expect(translatedCode).toContain('const userId = data.user.id;'); expect(translatedCode).toContain('const userEmail = data.user.contact.email;'); expect(translatedCode).toContain('const firstItem = data.items[0];'); - expect(translatedCode).toContain('bru.setEnvVar("userId", userId);'); + expect(translatedCode).toContain('bru.environment.set("userId", userId);'); }); it('should handle template literals with response data', () => { @@ -417,7 +417,7 @@ describe('Response Translation', () => { expect(translatedCode).toContain('const data = res.getBody();'); expect(translatedCode).toContain('const welcomeMessage = `Hello, ${data.user.name}! Your ID is ${data.user.id}.`;'); - expect(translatedCode).toContain('bru.setEnvVar("welcomeMessage", welcomeMessage);'); + expect(translatedCode).toContain('bru.environment.set("welcomeMessage", welcomeMessage);'); }); it('should handle response processing in arrow functions', () => { @@ -435,7 +435,7 @@ describe('Response Translation', () => { expect(translatedCode).toContain('const items = res.getBody().items;'); expect(translatedCode).toContain('return items.map(item => item.id);'); expect(translatedCode).toContain('const itemIds = processItems();'); - expect(translatedCode).toContain('bru.setEnvVar("itemIds", JSON.stringify(itemIds));'); + expect(translatedCode).toContain('bru.environment.set("itemIds", JSON.stringify(itemIds));'); }); it('should handle complex inline operations with response data', () => { @@ -454,8 +454,8 @@ describe('Response Translation', () => { expect(translatedCode).toContain('const totalValue = items.reduce((sum, item) => sum + item.price, 0);'); expect(translatedCode).toContain('const highValueItems = items.filter(item => item.price > 100);'); expect(translatedCode).toContain('const itemNames = items.map(item => item.name);'); - expect(translatedCode).toContain('bru.setEnvVar("totalValue", totalValue);'); - expect(translatedCode).toContain('bru.setEnvVar("highValueItemCount", highValueItems.length);'); + expect(translatedCode).toContain('bru.environment.set("totalValue", totalValue);'); + expect(translatedCode).toContain('bru.environment.set("highValueItemCount", highValueItems.length);'); }); it('should handle complex test structure with pm.response.to.have.header', () => { diff --git a/packages/bruno-converters/tests/postman/postman-translations/transpiler-tests/testing-framework.test.js b/packages/bruno-converters/tests/postman/postman-translations/transpiler-tests/testing-framework.test.js index a1f189b6f94..57d4df8b7fc 100644 --- a/packages/bruno-converters/tests/postman/postman-translations/transpiler-tests/testing-framework.test.js +++ b/packages/bruno-converters/tests/postman/postman-translations/transpiler-tests/testing-framework.test.js @@ -30,7 +30,7 @@ describe('Testing Framework Translation', () => { const translatedCode = translateCode(code); expect(translatedCode).toBe(` test("Check environment and call successful", function () { - expect(bru.getEnvName()).to.equal("ENVIRONMENT_NAME"); + expect(bru.environment.name).to.equal("ENVIRONMENT_NAME"); expect(res.getStatus()).to.equal(200); });`); }); @@ -66,7 +66,7 @@ describe('Testing Framework Translation', () => { expect(translatedCode).toContain('test("Auth flow works", function() {'); expect(translatedCode).toContain('const response = res.getBody();'); expect(translatedCode).toContain('expect(response.authenticated).to.be.true;'); - expect(translatedCode).toContain('bru.setEnvVar("userId", response.user.id);'); + expect(translatedCode).toContain('bru.environment.set("userId", response.user.id);'); }); // TODO: Restore once UI update fixes are live for setCollectionVar @@ -164,7 +164,7 @@ describe('Testing Framework Translation', () => { `; const translatedCode = translateCode(code); - expect(translatedCode).toContain('const endpoint = bru.getVar("currentEndpoint");'); + expect(translatedCode).toContain('const endpoint = bru.variables.get("currentEndpoint");'); expect(translatedCode).toContain('test(`${endpoint} returns correct data`, function() {'); expect(translatedCode).toContain('const responseJson = res.getBody();'); expect(translatedCode).toContain('expect(responseJson).to.be.an(\'object\');'); @@ -400,6 +400,6 @@ describe('Testing Framework Translation', () => { expect(translatedCode).toContain('expect(res.getStatus()).to.equal(200);'); expect(translatedCode).toContain('test("Related users validation", function() {'); expect(translatedCode).toContain('test(`User at index ${index}`, function() {'); - expect(translatedCode).toContain('bru.setEnvVar("validUsers", JSON.stringify(validUsers));'); + expect(translatedCode).toContain('bru.environment.set("validUsers", JSON.stringify(validUsers));'); }); }); diff --git a/packages/bruno-converters/tests/postman/postman-translations/transpiler-tests/variable-chaining.test.js b/packages/bruno-converters/tests/postman/postman-translations/transpiler-tests/variable-chaining.test.js index b2d6a58f182..f3fa2f76667 100644 --- a/packages/bruno-converters/tests/postman/postman-translations/transpiler-tests/variable-chaining.test.js +++ b/packages/bruno-converters/tests/postman/postman-translations/transpiler-tests/variable-chaining.test.js @@ -37,7 +37,7 @@ describe('Variable Chaining Resolution', () => { expect(translatedCode).not.toContain('const respVar'); expect(translatedCode).not.toContain('const envVar'); expect(translatedCode).toContain('const statusCode = res.getStatus();'); - expect(translatedCode).toContain('const envValue = bru.getEnvVar("key");'); + expect(translatedCode).toContain('const envValue = bru.environment.get("key");'); // Check that unrelated variables are preserved expect(translatedCode).toContain('const unrelatedVar = "some value";'); @@ -82,7 +82,7 @@ describe('Variable Chaining Resolution', () => { // References to Postman objects should be properly translated expect(translatedCode).toContain('const statusCode = res.getStatus();'); - expect(translatedCode).toContain('const baseUrl = bru.getEnvVar("baseUrl");'); + expect(translatedCode).toContain('const baseUrl = bru.environment.get("baseUrl");'); // Console logs with regular variables should be preserved expect(translatedCode).toContain('console.log("Counter value:", counter);'); diff --git a/packages/bruno-converters/tests/postman/postman-translations/transpiler-tests/variables.test.js b/packages/bruno-converters/tests/postman/postman-translations/transpiler-tests/variables.test.js index 30e12f2496b..a6bcaf05a87 100644 --- a/packages/bruno-converters/tests/postman/postman-translations/transpiler-tests/variables.test.js +++ b/packages/bruno-converters/tests/postman/postman-translations/transpiler-tests/variables.test.js @@ -6,21 +6,21 @@ describe('Variables Translation', () => { const code = 'pm.variables.get("test");'; const translatedCode = translateCode(code); - expect(translatedCode).toBe('bru.getVar("test");'); + expect(translatedCode).toBe('bru.variables.get("test");'); }); it('should translate pm.variables.set', () => { const code = 'pm.variables.set("test", "value");'; const translatedCode = translateCode(code); - expect(translatedCode).toBe('bru.setVar("test", "value");'); + expect(translatedCode).toBe('bru.variables.set("test", "value");'); }); it('should translate pm.variables.has', () => { const code = 'pm.variables.has("userId");'; const translatedCode = translateCode(code); - expect(translatedCode).toBe('bru.hasVar("userId");'); + expect(translatedCode).toBe('bru.variables.has("userId");'); }); it('should translate pm.variables.replaceIn', () => { @@ -98,14 +98,14 @@ describe('Variables Translation', () => { const code = 'pm.globals.get("test");'; const translatedCode = translateCode(code); - expect(translatedCode).toBe('bru.getGlobalEnvVar("test");'); + expect(translatedCode).toBe('bru.globals.get("test");'); }); it('should handle pm.globals.set', () => { const code = 'pm.globals.set("test", "value");'; const translatedCode = translateCode(code); - expect(translatedCode).toBe('bru.setGlobalEnvVar("test", "value");'); + expect(translatedCode).toBe('bru.globals.set("test", "value");'); }); // Alias tests for variables @@ -119,9 +119,9 @@ describe('Variables Translation', () => { const translatedCode = translateCode(code); expect(translatedCode).toBe(` - const has = bru.hasVar("test"); - const set = bru.setVar("test", "value"); - const get = bru.getVar("test"); + const has = bru.variables.has("test"); + const set = bru.variables.set("test", "value"); + const get = bru.variables.get("test"); `); }); @@ -154,8 +154,8 @@ describe('Variables Translation', () => { const translatedCode = translateCode(code); expect(translatedCode).toBe(` - const get = bru.getGlobalEnvVar("test"); - const set = bru.setGlobalEnvVar("test", "value"); + const get = bru.globals.get("test"); + const set = bru.globals.set("test", "value"); `); }); @@ -164,7 +164,7 @@ describe('Variables Translation', () => { const code = 'const userStatus = pm.variables.has("userId") ? "logged-in" : "guest";'; const translatedCode = translateCode(code); - expect(translatedCode).toBe('const userStatus = bru.hasVar("userId") ? "logged-in" : "guest";'); + expect(translatedCode).toBe('const userStatus = bru.variables.has("userId") ? "logged-in" : "guest";'); }); it('should handle all variable methods together', () => { @@ -178,9 +178,9 @@ describe('Variables Translation', () => { `; const translatedCode = translateCode(code); - expect(translatedCode).toContain('const hasUserId = bru.hasVar("userId");'); - expect(translatedCode).toContain('const userId = bru.getVar("userId");'); - expect(translatedCode).toContain('bru.setVar("requestTime", new Date().toISOString());'); + expect(translatedCode).toContain('const hasUserId = bru.variables.has("userId");'); + expect(translatedCode).toContain('const userId = bru.variables.get("userId");'); + expect(translatedCode).toContain('bru.variables.set("requestTime", new Date().toISOString());'); }); // TODO: Restore once UI update fixes are live for setCollectionVar/deleteCollectionVar @@ -207,7 +207,7 @@ describe('Variables Translation', () => { const code = 'pm.collectionVariables.set("fullPath", pm.environment.get("baseUrl") + pm.variables.get("endpoint"));'; const translatedCode = translateCode(code); - expect(translatedCode).toBe('bru.setCollectionVar("fullPath", bru.getEnvVar("baseUrl") + bru.getVar("endpoint"));'); + expect(translatedCode).toBe('bru.setCollectionVar("fullPath", bru.environment.get("baseUrl") + bru.variables.get("endpoint"));'); }); // replaceIn tests for different variable scopes @@ -244,16 +244,14 @@ describe('Variables Translation', () => { const code = 'pm.globals.has("token");'; const translatedCode = translateCode(code); - expect(translatedCode).toContain('bru.getGlobalEnvVar("token") !== undefined'); - expect(translatedCode).toContain('bru.getGlobalEnvVar("token") !== null'); + expect(translatedCode).toBe('bru.globals.has("token");'); }); it('should translate pm.globals.has in conditional', () => { const code = 'if (pm.globals.has("authToken")) { console.log("Token exists"); }'; const translatedCode = translateCode(code); - expect(translatedCode).toContain('bru.getGlobalEnvVar("authToken") !== undefined'); - expect(translatedCode).toContain('bru.getGlobalEnvVar("authToken") !== null'); + expect(translatedCode).toContain('bru.globals.has("authToken")'); expect(translatedCode).toContain('console.log("Token exists");'); }); @@ -261,6 +259,6 @@ describe('Variables Translation', () => { const code = 'const hasGlobal = pm.globals.has("config");'; const translatedCode = translateCode(code); - expect(translatedCode).toContain('const hasGlobal = bru.getGlobalEnvVar("config") !== undefined && bru.getGlobalEnvVar("config") !== null'); + expect(translatedCode).toBe('const hasGlobal = bru.globals.has("config");'); }); }); diff --git a/packages/bruno-js/src/bru.js b/packages/bruno-js/src/bru.js index f728f15c02f..7923e277d8a 100644 --- a/packages/bruno-js/src/bru.js +++ b/packages/bruno-js/src/bru.js @@ -4,6 +4,7 @@ const { interpolate: _interpolate } = require('@usebruno/common'); const { sendRequest, createSendRequest } = require('@usebruno/requests').scripting; const { jar: createCookieJar, getCookiesForUrl } = require('@usebruno/requests').cookies; const CookieList = require('./cookie-list'); +const VariableList = require('./variable-list'); const variableNameRegex = /^[\w-.]*$/; @@ -67,6 +68,49 @@ class Bru { createCookieJar, getCookiesForUrl }); + + const validateKey = (key) => { + if (variableNameRegex.test(key) === false) { + throw new Error( + `Variable name: "${key}" contains invalid characters!` + + ' Names must only contain alpha-numeric characters, "-", "_", "."' + ); + } + }; + + this.variables = new VariableList(this.runtimeVariables, { + interpolateFn: (val) => this.interpolate(val), + validateKey + }); + + this.environment = new VariableList(this.envVariables, { + interpolateFn: (val) => this.interpolate(val), + validateKey, + filterKeys: ['__name__'] + }); + Object.defineProperty(this.environment, 'name', { + get: () => this.envVariables.__name__, + enumerable: true + }); + + this.globals = new VariableList(this.globalEnvironmentVariables, { + interpolateFn: (val) => this.interpolate(val), + validateKey, + filterKeys: ['__name__'] + }); + Object.defineProperty(this.globals, 'name', { + get: () => this.globalEnvironmentVariables.__name__, + enumerable: true + }); + // TODO: globals.unset/clear work in the request lifecycle but do not update the UI. + // Re-enable once the UI sync issue is resolved. + this.globals.unset = () => { + throw new Error('globals.unset is not implemented yet'); + }; + this.globals.clear = () => { + throw new Error('globals.clear is not implemented yet'); + }; + // Holds variables that are marked as persistent by scripts this.persistentEnvVariables = {}; // Holds credential IDs to be reset after script execution @@ -161,6 +205,10 @@ class Bru { return this.envVariables.__name__; } + getGlobalEnvName() { + return this.globalEnvironmentVariables.__name__; + } + getProcessEnv(key) { return this.processEnvVars[key]; } diff --git a/packages/bruno-js/src/sandbox/quickjs/shims/bru.js b/packages/bruno-js/src/sandbox/quickjs/shims/bru.js index 708ef755ee7..04c2bfc142d 100644 --- a/packages/bruno-js/src/sandbox/quickjs/shims/bru.js +++ b/packages/bruno-js/src/sandbox/quickjs/shims/bru.js @@ -18,6 +18,12 @@ const addBruShimToContext = (vm, bru) => { vm.setProp(bruObject, 'getEnvName', getEnvName); getEnvName.dispose(); + let getGlobalEnvName = vm.newFunction('getGlobalEnvName', function () { + return marshallToVm(bru.getGlobalEnvName(), vm); + }); + vm.setProp(bruObject, 'getGlobalEnvName', getGlobalEnvName); + getGlobalEnvName.dispose(); + let getCollectionName = vm.newFunction('getCollectionName', function () { return marshallToVm(bru.getCollectionName(), vm); }); @@ -500,6 +506,43 @@ const addBruShimToContext = (vm, bru) => { vm.setProp(bruObject, 'cookies', bruCookiesObject); bruCookiesObject.dispose(); + // ── bru.variables (runtime variables) ───────────────────────────────── + let bruVariablesObject = vm.newObject(); + const { evalCode: variablesEvalCode } = createPropertyListBridge(vm, bru.variables, bruVariablesObject, { + globalPath: 'globalThis.bru.variables', + syncReadMethods: ['has', 'count', 'indexOf', 'toString'], + syncReadObjectMethods: ['get', 'one', 'all', 'idx', 'toObject', 'toJSON'], + syncWriteMethods: ['set', 'unset', 'clear'], + withIterators: true + }); + vm.setProp(bruObject, 'variables', bruVariablesObject); + bruVariablesObject.dispose(); + + // ── bru.environment (active collection environment) ─────────────────── + let bruEnvironmentObject = vm.newObject(); + const { evalCode: environmentEvalCode } = createPropertyListBridge(vm, bru.environment, bruEnvironmentObject, { + globalPath: 'globalThis.bru.environment', + syncReadMethods: ['has', 'count', 'indexOf', 'toString'], + syncReadObjectMethods: ['get', 'one', 'all', 'idx', 'toObject', 'toJSON'], + syncWriteMethods: ['set', 'unset', 'clear'], + withIterators: true + }); + vm.setProp(bruObject, 'environment', bruEnvironmentObject); + bruEnvironmentObject.dispose(); + + // ── bru.globals (active global environment) ─────────────────────────── + // TODO: Add 'unset' and 'clear' to syncWriteMethods once the UI sync issue is resolved. + let bruGlobalsObject = vm.newObject(); + const { evalCode: globalsEvalCode } = createPropertyListBridge(vm, bru.globals, bruGlobalsObject, { + globalPath: 'globalThis.bru.globals', + syncReadMethods: ['has', 'count', 'indexOf', 'toString'], + syncReadObjectMethods: ['get', 'one', 'all', 'idx', 'toObject', 'toJSON'], + syncWriteMethods: ['set'], + withIterators: true + }); + vm.setProp(bruObject, 'globals', bruGlobalsObject); + bruGlobalsObject.dispose(); + vm.setProp(bruObject, 'runner', bruRunnerObject); vm.setProp(vm.global, 'bru', bruObject); bruObject.dispose(); @@ -536,6 +579,26 @@ const addBruShimToContext = (vm, bru) => { ${cookiesEvalCode} } + { + ${variablesEvalCode} + } + + { + ${environmentEvalCode} + } + Object.defineProperty(globalThis.bru.environment, 'name', { + get: () => globalThis.bru.getEnvName(), + enumerable: true + }); + + { + ${globalsEvalCode} + } + Object.defineProperty(globalThis.bru.globals, 'name', { + get: () => globalThis.bru.getGlobalEnvName(), + enumerable: true + }); + globalThis.bru.cookies.jar = () => { const _jar = globalThis.bru.cookies._jar(); diff --git a/packages/bruno-js/src/sandbox/quickjs/utils/property-list-bridge.js b/packages/bruno-js/src/sandbox/quickjs/utils/property-list-bridge.js index ed6bf725e69..d4199abf969 100644 --- a/packages/bruno-js/src/sandbox/quickjs/utils/property-list-bridge.js +++ b/packages/bruno-js/src/sandbox/quickjs/utils/property-list-bridge.js @@ -72,6 +72,7 @@ const createAsyncBridge = (vm, targetObj, propName, nativeMethod) => { * @param {string} options.globalPath - Global path in QuickJS (e.g. 'globalThis.bru.cookies') * @param {string[]} [options.syncReadMethods] - Methods that return primitive values * @param {string[]} [options.syncReadObjectMethods] - Methods that return objects (need cleanCircularJson) + * @param {string[]} [options.syncWriteMethods] - Sync write methods (call native, return undefined) * @param {string[]} [options.asyncWriteMethods] - Async write methods (use _prefix bridge) * @param {boolean} [options.withIterators] - Whether to add each/find/filter/map/reduce * @returns {{ evalCode: string }} - JavaScript code to eval in the VM for async wrappers and iterators @@ -81,6 +82,7 @@ const createPropertyListBridge = (vm, nativeList, targetObj, options) => { globalPath, syncReadMethods = [], syncReadObjectMethods = [], + syncWriteMethods = [], asyncWriteMethods = [], withIterators = false } = options; @@ -103,6 +105,16 @@ const createPropertyListBridge = (vm, nativeList, targetObj, options) => { fn.consume((handle) => vm.setProp(targetObj, methodName, handle)); } + // Sync write methods — call native method, return undefined + for (const methodName of syncWriteMethods) { + const fn = vm.newFunction(methodName, (...vmArgs) => { + const args = vmArgs.map((a) => vm.dump(a)); + nativeList[methodName](...args); + return vm.undefined; + }); + fn.consume((handle) => vm.setProp(targetObj, methodName, handle)); + } + // Async write methods — two-phase setup: // Phase 1 (native): Register `_prefixed` bridge functions (e.g. `_add`, `_remove`) via // createAsyncBridge. These are QuickJS promise-based wrappers that call the native method's diff --git a/packages/bruno-js/src/variable-list.js b/packages/bruno-js/src/variable-list.js new file mode 100644 index 00000000000..1d44670f03f --- /dev/null +++ b/packages/bruno-js/src/variable-list.js @@ -0,0 +1,111 @@ +const PropertyList = require('./property-list'); + +const variableNameRegex = /^[\w-.]*$/; + +/** + * VariableList — a PropertyList adapter for Bruno's variable scopes. + * + * Wraps a plain `{ key: value }` object (e.g. runtimeVariables, envVariables, + * globalEnvironmentVariables) and exposes the full PropertyList read interface + * plus synchronous write methods that mutate the original object in place. + * + * Runs in **dynamic mode**: a dataSource function converts the object to + * `[{ key, value }]` items on every read, so reads always reflect the latest state + * (including mutations made through the legacy bru.setVar() / bru.setEnvVar() path). + * + * @example + * const list = new VariableList(runtimeVariables, { + * interpolateFn: (val) => bru.interpolate(val) + * }); + * list.set('host', 'example.com'); + * list.get('host'); // 'example.com' (interpolated) + * list.has('host'); // true + * list.toObject(); // { host: 'example.com' } + * list.unset('host'); + */ +class VariableList extends PropertyList { + /** + * @param {object} variablesObj - The plain { key: value } object to wrap (mutated in place) + * @param {object} [options] + * @param {Function} [options.interpolateFn] - Interpolation function (bru.interpolate) + * @param {Function} [options.validateKey] - Custom key validation function (throws on invalid) + * @param {string[]} [options.filterKeys] - Internal keys to exclude from reads (e.g. ['__name__']) + */ + constructor(variablesObj, { interpolateFn, validateKey, filterKeys } = {}) { + super({ + keyProperty: 'key', + valueProperty: 'value', + dataSource: () => { + return Object.entries(variablesObj) + .filter(([k]) => !filterKeys || !filterKeys.includes(k)) + .map(([key, value]) => ({ key, value })); + } + }); + this._variablesObj = variablesObj; + this._interpolateFn = interpolateFn || ((v) => v); + this._validateKeyFn = validateKey || null; + this._filterKeys = filterKeys || []; + } + + // ── Read overrides ────────────────────────────────────────────────── + + /** + * Get the interpolated value of a variable by key. + * @param {string} key + * @returns {*} The interpolated value, or undefined if not found + */ + get(key) { + return this._interpolateFn(this._variablesObj[key]); + } + + // ── Write methods ─────────────────────────────────────────────────── + + /** + * Set a variable. Validates the key name and mutates the underlying object. + * @param {string} key + * @param {*} value + */ + set(key, value) { + if (!key) { + throw new Error('Creating a variable without specifying a name is not allowed.'); + } + this.#validateKey(key); + this._variablesObj[key] = value; + } + + /** + * Remove a variable by key. + * @param {string} key + */ + unset(key) { + delete this._variablesObj[key]; + } + + /** + * Remove all variables. Preserves keys listed in filterKeys. + */ + clear() { + for (const key of Object.keys(this._variablesObj)) { + if (!this._filterKeys.includes(key)) { + delete this._variablesObj[key]; + } + } + } + + // ── Internal helpers ──────────────────────────────────────────────── + + #validateKey(key) { + if (this._validateKeyFn) { + this._validateKeyFn(key); + return; + } + if (variableNameRegex.test(key) === false) { + throw new Error( + `Variable name: "${key}" contains invalid characters!` + + ' Names must only contain alpha-numeric characters, "-", "_", "."' + ); + } + } +} + +module.exports = VariableList; diff --git a/packages/bruno-js/tests/variable-list.spec.js b/packages/bruno-js/tests/variable-list.spec.js new file mode 100644 index 00000000000..b80c69b0a77 --- /dev/null +++ b/packages/bruno-js/tests/variable-list.spec.js @@ -0,0 +1,264 @@ +const VariableList = require('../src/variable-list'); +const PropertyList = require('../src/property-list'); +const ReadOnlyPropertyList = require('../src/readonly-property-list'); + +describe('VariableList', () => { + // ── Inheritance ─────────────────────────────────────────────────────── + + test('instanceof PropertyList and ReadOnlyPropertyList', () => { + const list = new VariableList({}); + expect(list).toBeInstanceOf(PropertyList); + expect(list).toBeInstanceOf(ReadOnlyPropertyList); + }); + + // ── Basic CRUD ──────────────────────────────────────────────────────── + + describe('get / set / has / unset', () => { + let vars; + let list; + + beforeEach(() => { + vars = { host: 'example.com', port: '8080' }; + list = new VariableList(vars, { + interpolateFn: (val) => val + }); + }); + + test('get() returns value from underlying object', () => { + expect(list.get('host')).toBe('example.com'); + expect(list.get('port')).toBe('8080'); + }); + + test('get() returns undefined for missing key', () => { + expect(list.get('missing')).toBeUndefined(); + }); + + test('get() uses interpolateFn', () => { + const interpolated = new VariableList({ url: '{{host}}/api' }, { + interpolateFn: (val) => typeof val === 'string' ? val.replace('{{host}}', 'example.com') : val + }); + expect(interpolated.get('url')).toBe('example.com/api'); + }); + + test('set() writes to underlying object', () => { + list.set('newKey', 'newValue'); + expect(vars.newKey).toBe('newValue'); + expect(list.get('newKey')).toBe('newValue'); + }); + + test('set() throws on empty key', () => { + expect(() => list.set('', 'value')).toThrow('without specifying a name'); + }); + + test('set() validates key format', () => { + expect(() => list.set('invalid key!', 'value')).toThrow('invalid characters'); + }); + + test('set() allows valid key characters', () => { + list.set('my-var_name.v2', 'ok'); + expect(vars['my-var_name.v2']).toBe('ok'); + }); + + test('has() returns true for existing key', () => { + expect(list.has('host')).toBe(true); + }); + + test('has() returns false for missing key', () => { + expect(list.has('missing')).toBe(false); + }); + + test('unset() removes key from underlying object', () => { + list.unset('host'); + expect(vars.host).toBeUndefined(); + expect(list.has('host')).toBe(false); + }); + + test('unset() is a no-op for missing key', () => { + list.unset('nonexistent'); + expect(Object.keys(vars)).toEqual(['host', 'port']); + }); + }); + + // ── clear ───────────────────────────────────────────────────────────── + + describe('clear', () => { + test('removes all keys from underlying object', () => { + const vars = { a: '1', b: '2', c: '3' }; + const list = new VariableList(vars); + list.clear(); + expect(Object.keys(vars)).toEqual([]); + }); + + test('preserves filterKeys', () => { + const vars = { __name__: 'dev', host: 'example.com', port: '8080' }; + const list = new VariableList(vars, { filterKeys: ['__name__'] }); + list.clear(); + expect(vars.__name__).toBe('dev'); + expect(vars.host).toBeUndefined(); + expect(vars.port).toBeUndefined(); + }); + }); + + // ── filterKeys ──────────────────────────────────────────────────────── + + describe('filterKeys', () => { + let vars; + let list; + + beforeEach(() => { + vars = { __name__: 'dev', host: 'example.com', port: '8080' }; + list = new VariableList(vars, { filterKeys: ['__name__'] }); + }); + + test('toObject() excludes filtered keys', () => { + expect(list.toObject()).toEqual({ host: 'example.com', port: '8080' }); + }); + + test('all() excludes filtered keys', () => { + const items = list.all(); + expect(items).toHaveLength(2); + expect(items.find((i) => i.key === '__name__')).toBeUndefined(); + }); + + test('count() excludes filtered keys', () => { + expect(list.count()).toBe(2); + }); + + test('has() still checks filtered keys via dataSource', () => { + // __name__ is filtered from reads, so has() won't find it + expect(list.has('__name__')).toBe(false); + expect(list.has('host')).toBe(true); + }); + }); + + // ── ReadOnlyPropertyList inherited methods ──────────────────────────── + + describe('inherited read methods', () => { + let list; + + beforeEach(() => { + list = new VariableList({ a: '1', b: '2', c: '3' }, { + interpolateFn: (val) => val + }); + }); + + test('one() returns full item object', () => { + expect(list.one('b')).toEqual({ key: 'b', value: '2' }); + }); + + test('one() returns undefined for missing key', () => { + expect(list.one('missing')).toBeUndefined(); + }); + + test('all() returns array of { key, value } items', () => { + const items = list.all(); + expect(items).toHaveLength(3); + expect(items).toEqual([ + { key: 'a', value: '1' }, + { key: 'b', value: '2' }, + { key: 'c', value: '3' } + ]); + }); + + test('idx() returns item by position', () => { + expect(list.idx(1)).toEqual({ key: 'b', value: '2' }); + }); + + test('count() returns number of items', () => { + expect(list.count()).toBe(3); + }); + + test('indexOf() finds item by structural equality', () => { + expect(list.indexOf({ key: 'b', value: '2' })).toBe(1); + expect(list.indexOf({ key: 'missing', value: 'x' })).toBe(-1); + }); + + test('toObject() returns plain { key: value } map', () => { + expect(list.toObject()).toEqual({ a: '1', b: '2', c: '3' }); + }); + + test('toString() returns "key=value; ..." format', () => { + expect(list.toString()).toBe('a=1; b=2; c=3'); + }); + + test('toJSON() returns same as all()', () => { + expect(list.toJSON()).toEqual(list.all()); + }); + }); + + // ── Iteration methods ───────────────────────────────────────────────── + + describe('iteration methods', () => { + let list; + + beforeEach(() => { + list = new VariableList({ a: '1', b: '2', c: '3' }, { + interpolateFn: (val) => val + }); + }); + + test('each() iterates over all items', () => { + const keys = []; + list.each((item) => keys.push(item.key)); + expect(keys).toEqual(['a', 'b', 'c']); + }); + + test('map() transforms items', () => { + const values = list.map((item) => item.value); + expect(values).toEqual(['1', '2', '3']); + }); + + test('filter() selects matching items', () => { + const filtered = list.filter((item) => item.key !== 'b'); + expect(filtered).toHaveLength(2); + expect(filtered.map((i) => i.key)).toEqual(['a', 'c']); + }); + + test('find() returns first match', () => { + const found = list.find((item) => item.value === '2'); + expect(found).toEqual({ key: 'b', value: '2' }); + }); + + test('reduce() accumulates values', () => { + const sum = list.reduce((acc, item) => acc + Number(item.value), 0); + expect(sum).toBe(6); + }); + }); + + // ── Cross-path mutation visibility ──────────────────────────────────── + + describe('shared underlying object', () => { + test('mutations via VariableList are visible on the original object', () => { + const vars = { x: '10' }; + const list = new VariableList(vars); + list.set('y', '20'); + expect(vars.y).toBe('20'); + list.unset('x'); + expect(vars.x).toBeUndefined(); + }); + + test('mutations on the original object are visible via VariableList', () => { + const vars = { x: '10' }; + const list = new VariableList(vars, { interpolateFn: (v) => v }); + vars.y = '20'; + expect(list.get('y')).toBe('20'); + expect(list.has('y')).toBe(true); + delete vars.x; + expect(list.has('x')).toBe(false); + }); + }); + + // ── Custom validateKey ──────────────────────────────────────────────── + + describe('custom validateKey', () => { + test('uses custom validator when provided', () => { + const list = new VariableList({}, { + validateKey: (key) => { + if (key.startsWith('_')) throw new Error('No leading underscores'); + } + }); + expect(() => list.set('_bad', 'val')).toThrow('No leading underscores'); + expect(() => list.set('good', 'val')).not.toThrow(); + }); + }); +});