From 3871ca9edddc0a40ac03ee54b97c68f1ab62e356 Mon Sep 17 00:00:00 2001 From: sanish chirayath Date: Thu, 12 Feb 2026 17:17:39 +0530 Subject: [PATCH] feat: enhance translation capabilities for Bruno to Postman conversion (#7052) * feat: enhance translation capabilities for Bruno to Postman conversion - Added support for translating req.getHost(), req.getPath(), and req.getQueryString() to their Postman equivalents. - Implemented translation for req.getPathParams() to pm.request.url.variables. - Introduced handling for bru.visualize() to pm.visualizer.set() with various argument types. - Added tests to validate new translation features and ensure correct behavior for URL-related methods and visualizer functionality. * rm: duplicates * refactor: remove bru.visualize transformation and associated tests * feat: enhance BDD-style assertion translations in Postman converter - Updated transformation logic to translate BDD-style assertions like pm.response.to.be.ok, pm.response.to.be.success, and others to their corresponding expect statements. - Added comprehensive tests to validate the new translations for various response status checks. - Improved handling of BDD assertions within test blocks to ensure accurate translation. * fix: correct variable naming in transformation logic for Postman converter - Updated variable names in the transformation logic to improve clarity and consistency. - Ensured that the correct nodes are replaced and added to the transformedNodes set during processing. * fix: improve AST mutation handling in Postman to Bruno translation - Enhanced the processTransformations function to capture stable references before mutating the AST, ensuring correct node replacement and insertion. - Added a defensive guard for ExpressionStatements to prevent errors when accessing undefined properties. - Improved the logic for inserting remaining nodes after the grandparent in reverse order to maintain the correct sequence. * fix: remove unnecessary defensive guard in AST mutation for Postman to Bruno translation --- .../src/utils/postman-to-bruno-translator.js | 278 +++++++++++++++++- .../request.test.js | 14 + .../legacy-global-apis.test.js | 51 ++++ .../transpiler-tests/request.test.js | 35 +++ .../transpiler-tests/response.test.js | 93 ++++++ .../transpiler-tests/variables.test.js | 54 ++++ 6 files changed, 509 insertions(+), 16 deletions(-) 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 fac8ac03b..a6c40ffc3 100644 --- a/packages/bruno-converters/src/utils/postman-to-bruno-translator.js +++ b/packages/bruno-converters/src/utils/postman-to-bruno-translator.js @@ -8,12 +8,14 @@ const simpleTranslations = { // Global Variables 'pm.globals.get': 'bru.getGlobalEnvVar', 'pm.globals.set': 'bru.setGlobalEnvVar', + 'pm.globals.replaceIn': 'bru.interpolate', // Environment variables 'pm.environment.get': 'bru.getEnvVar', 'pm.environment.set': 'bru.setEnvVar', 'pm.environment.name': 'bru.getEnvName()', 'pm.environment.unset': 'bru.deleteEnvVar', + 'pm.environment.replaceIn': 'bru.interpolate', // Variables 'pm.variables.get': 'bru.getVar', @@ -25,6 +27,7 @@ const simpleTranslations = { 'pm.collectionVariables.set': 'bru.setVar', 'pm.collectionVariables.has': 'bru.hasVar', 'pm.collectionVariables.unset': 'bru.deleteVar', + 'pm.collectionVariables.replaceIn': 'bru.interpolate', // Request flow control 'pm.setNextRequest': 'bru.setNextRequest', @@ -80,7 +83,11 @@ const simpleTranslations = { // 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.clearEnvironmentVariable': 'bru.deleteEnvVar', + + // Legacy response properties + 'responseCode.code': 'res.getStatus()', + 'responseCode.name': 'res.statusText' }; /* Complex transformations that need custom handling @@ -328,6 +335,229 @@ const complexTransformations = [ [reduceFn, j.objectExpression([])] ); } + }, + + // 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', + transform: (path, j) => { + const callExpr = path.parent.value; + const args = callExpr.arguments; + + // Check if the argument is an object with key and value properties + if (args.length > 0 && args[0].type === 'ObjectExpression') { + const obj = args[0]; + let keyProp = null; + let valueProp = null; + + obj.properties.forEach((prop) => { + if (prop.key.name === 'key' || prop.key.value === 'key') { + keyProp = prop.value; + } + if (prop.key.name === 'value' || prop.key.value === 'value') { + valueProp = prop.value; + } + }); + + if (keyProp && valueProp) { + return j.callExpression( + j.identifier('req.setHeader'), + [keyProp, valueProp] + ); + } + } + + // Fallback: keep original args + return j.callExpression(j.identifier('req.setHeader'), args); + } + }, + + // pm.request.headers.upsert({key, value}) -> req.setHeader(key, value) + { + pattern: 'pm.request.headers.upsert', + transform: (path, j) => { + const callExpr = path.parent.value; + const args = callExpr.arguments; + + // Check if the argument is an object with key and value properties + if (args.length > 0 && args[0].type === 'ObjectExpression') { + const obj = args[0]; + let keyProp = null; + let valueProp = null; + + obj.properties.forEach((prop) => { + if (prop.key.name === 'key' || prop.key.value === 'key') { + keyProp = prop.value; + } + if (prop.key.name === 'value' || prop.key.value === 'value') { + valueProp = prop.value; + } + }); + + if (keyProp && valueProp) { + return j.callExpression( + j.identifier('req.setHeader'), + [keyProp, valueProp] + ); + } + } + + // Fallback: keep original args + return j.callExpression(j.identifier('req.setHeader'), args); + } + }, + + // pm.response.to.be.ok -> expect(res.getStatus()).to.be.within(200, 299) + { + pattern: 'pm.response.to.be.ok', + transform: (path, j) => { + return j.callExpression( + j.memberExpression( + j.callExpression(j.identifier('expect'), [j.callExpression(j.identifier('res.getStatus'), [])]), + j.identifier('to.be.within') + ), + [j.literal(200), j.literal(299)] + ); + } + }, + + // pm.response.to.be.success -> expect(res.getStatus()).to.be.within(200, 299) + { + pattern: 'pm.response.to.be.success', + transform: (path, j) => { + return j.callExpression( + j.memberExpression( + j.callExpression(j.identifier('expect'), [j.callExpression(j.identifier('res.getStatus'), [])]), + j.identifier('to.be.within') + ), + [j.literal(200), j.literal(299)] + ); + } + }, + + // pm.response.to.be.redirection -> expect(res.getStatus()).to.be.within(300, 399) + { + pattern: 'pm.response.to.be.redirection', + transform: (path, j) => { + return j.callExpression( + j.memberExpression( + j.callExpression(j.identifier('expect'), [j.callExpression(j.identifier('res.getStatus'), [])]), + j.identifier('to.be.within') + ), + [j.literal(300), j.literal(399)] + ); + } + }, + + // pm.response.to.be.clientError -> expect(res.getStatus()).to.be.within(400, 499) + { + pattern: 'pm.response.to.be.clientError', + transform: (path, j) => { + return j.callExpression( + j.memberExpression( + j.callExpression(j.identifier('expect'), [j.callExpression(j.identifier('res.getStatus'), [])]), + j.identifier('to.be.within') + ), + [j.literal(400), j.literal(499)] + ); + } + }, + + // pm.response.to.be.serverError -> expect(res.getStatus()).to.be.within(500, 599) + { + pattern: 'pm.response.to.be.serverError', + transform: (path, j) => { + return j.callExpression( + j.memberExpression( + j.callExpression(j.identifier('expect'), [j.callExpression(j.identifier('res.getStatus'), [])]), + j.identifier('to.be.within') + ), + [j.literal(500), j.literal(599)] + ); + } + }, + + // pm.response.to.be.error -> expect(res.getStatus()).to.be.at.least(400) + { + pattern: 'pm.response.to.be.error', + transform: (path, j) => { + return j.callExpression( + j.memberExpression( + j.callExpression(j.identifier('expect'), [j.callExpression(j.identifier('res.getStatus'), [])]), + j.identifier('to.be.at.least') + ), + [j.literal(400)] + ); + } + }, + + // pm.response.to.have.jsonBody(path) -> expect(res.getBody()).to.have.nested.property(path) + { + pattern: 'pm.response.to.have.jsonBody', + transform: (path, j) => { + const callExpr = path.parent.value; + const args = callExpr.arguments; + + if (args.length === 0) { + // No path provided, just check that body exists + return j.memberExpression( + j.callExpression(j.identifier('expect'), [j.callExpression(j.identifier('res.getBody'), [])]), + j.identifier('to.exist') + ); + } else if (args.length === 1) { + // Path provided, check property exists + return j.callExpression( + j.memberExpression( + j.callExpression(j.identifier('expect'), [j.callExpression(j.identifier('res.getBody'), [])]), + j.identifier('to.have.nested.property') + ), + args + ); + } else { + // Path and value provided, check property equals value + return j.callExpression( + j.memberExpression( + j.callExpression(j.identifier('expect'), [j.callExpression(j.identifier('res.getBody'), [])]), + j.identifier('to.have.nested.property') + ), + args + ); + } + } + }, + + // Legacy postman.getResponseHeader(name) -> res.getHeader(name) + { + pattern: 'pm.getResponseHeader', + transform: (path, j) => { + const callExpr = path.parent.value; + const args = callExpr.arguments; + return j.callExpression(j.identifier('res.getHeader'), args); + } } ]; @@ -360,24 +590,40 @@ function processTransformations(ast, transformedNodes) { } // Then check for complex transformations (O(1)) - if (complexTransformationsMap.hasOwnProperty(memberExprStr) - && path.parent.value.type === 'CallExpression') { - const transform = complexTransformationsMap[memberExprStr]; - const replacement = transform.transform(path, j); - if (Array.isArray(replacement)) { - replacement.forEach((nodePath, index) => { - if (index === 0) { - j(path.parent).replaceWith(nodePath); - } else { - j(path.parent.parent).insertAfter(nodePath); + if (complexTransformationsMap.hasOwnProperty(memberExprStr)) { + const parentType = path.parent.value.type; + + // Call-based patterns (e.g., pm.response.to.have.jsonBody("path")) + if (parentType === 'CallExpression') { + const transform = complexTransformationsMap[memberExprStr]; + const replacement = transform.transform(path, j); + if (Array.isArray(replacement)) { + // Capture stable references before mutating the AST + const parentPath = path.parent; + const grandParentPath = parentPath.parent; + + // Replace the original CallExpression with the first node + j(parentPath).replaceWith(replacement[0]); + transformedNodes.add(replacement[0]); + transformedNodes.add(parentPath.node); + + // Insert remaining nodes after the grandparent in reverse order + // so that repeated insertAfter on the same anchor yields correct sequence + for (let i = replacement.length - 1; i >= 1; i--) { + j(grandParentPath).insertAfter(replacement[i]); + transformedNodes.add(replacement[i]); } - transformedNodes.add(nodePath.node); + } else { + j(path.parent).replaceWith(replacement); + transformedNodes.add(path.node); transformedNodes.add(path.parent.node); - }); - } else { - j(path.parent).replaceWith(replacement); + } + } else if (parentType === 'ExpressionStatement') { + // Property-access patterns used as statements (e.g., pm.response.to.be.ok;) + const transform = complexTransformationsMap[memberExprStr]; + const replacement = transform.transform(path, j); + j(path).replaceWith(replacement); transformedNodes.add(path.node); - transformedNodes.add(path.parent.node); } } }); diff --git a/packages/bruno-converters/tests/bruno/bruno-to-postman-translations/request.test.js b/packages/bruno-converters/tests/bruno/bruno-to-postman-translations/request.test.js index e6fb159c2..8024f7913 100644 --- a/packages/bruno-converters/tests/bruno/bruno-to-postman-translations/request.test.js +++ b/packages/bruno-converters/tests/bruno/bruno-to-postman-translations/request.test.js @@ -203,4 +203,18 @@ console.log("Headers:", JSON.stringify(pm.request.headers)); const translatedCode = translateBruToPostman(code); expect(translatedCode).toBe('const pathParams = pm.request.url.variables;'); }); + + it('should handle URL methods in complex expressions', () => { + const code = 'const fullUrl = req.getHost() + req.getPath() + "?" + req.getQueryString();'; + const translatedCode = translateBruToPostman(code); + expect(translatedCode).toContain('pm.request.url.getHost()'); + expect(translatedCode).toContain('pm.request.url.getPath()'); + expect(translatedCode).toContain('pm.request.url.getQueryString()'); + }); + + it('should handle req.getPathParams() in conditional', () => { + const code = 'if (req.getPathParams().id) { console.log("Has ID"); }'; + const translatedCode = translateBruToPostman(code); + expect(translatedCode).toContain('pm.request.url.variables.id'); + }); }); diff --git a/packages/bruno-converters/tests/postman/postman-translations/transpiler-tests/legacy-global-apis.test.js b/packages/bruno-converters/tests/postman/postman-translations/transpiler-tests/legacy-global-apis.test.js index c4e72d4fa..f4686a287 100644 --- a/packages/bruno-converters/tests/postman/postman-translations/transpiler-tests/legacy-global-apis.test.js +++ b/packages/bruno-converters/tests/postman/postman-translations/transpiler-tests/legacy-global-apis.test.js @@ -295,4 +295,55 @@ describe('Legacy Postman API Translation', () => { expect(result).toEqual(expected); }); }); + + describe('responseCode translations', () => { + test('should translate responseCode.code', () => { + const input = 'const status = responseCode.code;'; + const result = translateCode(input); + expect(result).toBe('const status = res.getStatus();'); + }); + + test('should translate responseCode.name', () => { + const input = 'const statusName = responseCode.name;'; + const result = translateCode(input); + expect(result).toBe('const statusName = res.statusText;'); + }); + + test('should translate responseCode.code in conditional', () => { + const input = 'if (responseCode.code === 200) { console.log("Success"); }'; + const result = translateCode(input); + expect(result).toBe('if (res.getStatus() === 200) { console.log("Success"); }'); + }); + + test('should translate both responseCode.code and responseCode.name together', () => { + const input = ` + const code = responseCode.code; + const name = responseCode.name; + console.log(code, name); + `; + const result = translateCode(input); + expect(result).toContain('const code = res.getStatus();'); + expect(result).toContain('const name = res.statusText;'); + }); + }); + + describe('postman.getResponseHeader translations', () => { + test('should translate postman.getResponseHeader', () => { + const input = 'postman.getResponseHeader("Content-Type");'; + const result = translateCode(input); + expect(result).toBe('res.getHeader("Content-Type");'); + }); + + test('should translate postman.getResponseHeader in assignment', () => { + const input = 'const contentType = postman.getResponseHeader("Content-Type");'; + const result = translateCode(input); + expect(result).toBe('const contentType = res.getHeader("Content-Type");'); + }); + + test('should translate postman.getResponseHeader with variable argument', () => { + const input = 'const headerName = "Authorization"; const value = postman.getResponseHeader(headerName);'; + const result = translateCode(input); + expect(result).toContain('res.getHeader(headerName)'); + }); + }); }); 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 56a80be39..c78020017 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 @@ -121,4 +121,39 @@ describe('Request Translation', () => { const name = req.getName(); `); }); + + // --- pm.request.headers.add and upsert --------------------------- + it('should translate pm.request.headers.add with object argument', () => { + const code = 'pm.request.headers.add({key: "Authorization", value: "Bearer token"});'; + const translatedCode = translateCode(code); + expect(translatedCode).toBe('req.setHeader("Authorization", "Bearer token");'); + }); + + it('should translate pm.request.headers.upsert with object argument', () => { + const code = 'pm.request.headers.upsert({key: "Content-Type", value: "application/json"});'; + const translatedCode = translateCode(code); + expect(translatedCode).toBe('req.setHeader("Content-Type", "application/json");'); + }); + + it('should translate pm.request.headers.add with quoted key property', () => { + const code = 'pm.request.headers.add({"key": "X-Custom-Header", "value": "custom-value"});'; + const translatedCode = translateCode(code); + expect(translatedCode).toBe('req.setHeader("X-Custom-Header", "custom-value");'); + }); + + it('should translate pm.request.headers.upsert with variable values', () => { + const code = 'const headerName = "Authorization"; const headerValue = "Bearer " + token; pm.request.headers.upsert({key: headerName, value: headerValue});'; + const translatedCode = translateCode(code); + expect(translatedCode).toContain('req.setHeader(headerName, headerValue)'); + }); + + it('should translate multiple headers.add calls', () => { + const code = ` + pm.request.headers.add({key: "Authorization", value: "Bearer token"}); + pm.request.headers.add({key: "Content-Type", value: "application/json"}); + `; + const translatedCode = translateCode(code); + expect(translatedCode).toContain('req.setHeader("Authorization", "Bearer token")'); + expect(translatedCode).toContain('req.setHeader("Content-Type", "application/json")'); + }); }); 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 dd191ea9f..661a064d2 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 @@ -583,4 +583,97 @@ describe('Response Translation', () => { const translatedCode = translateCode(code); expect(translatedCode).toBe('const responseSize = res.getSize().body;'); }); + + // --- BDD-style response assertions --------------------------- + + it('should translate pm.response.to.be.ok', () => { + const code = 'pm.response.to.be.ok;'; + const translatedCode = translateCode(code); + expect(translatedCode).toContain('expect(res.getStatus()).to.be.within(200, 299)'); + }); + + it('should translate pm.response.to.be.success', () => { + const code = 'pm.response.to.be.success;'; + const translatedCode = translateCode(code); + expect(translatedCode).toContain('expect(res.getStatus()).to.be.within(200, 299)'); + }); + + it('should translate pm.response.to.be.redirection', () => { + const code = 'pm.response.to.be.redirection;'; + const translatedCode = translateCode(code); + expect(translatedCode).toContain('expect(res.getStatus()).to.be.within(300, 399)'); + }); + + it('should translate pm.response.to.be.clientError', () => { + const code = 'pm.response.to.be.clientError;'; + const translatedCode = translateCode(code); + expect(translatedCode).toContain('expect(res.getStatus()).to.be.within(400, 499)'); + }); + + it('should translate pm.response.to.be.serverError', () => { + const code = 'pm.response.to.be.serverError;'; + const translatedCode = translateCode(code); + expect(translatedCode).toContain('expect(res.getStatus()).to.be.within(500, 599)'); + }); + + it('should translate pm.response.to.be.error', () => { + const code = 'pm.response.to.be.error;'; + const translatedCode = translateCode(code); + expect(translatedCode).toContain('expect(res.getStatus()).to.be.at.least(400)'); + }); + + it('should handle BDD-style assertions inside test blocks', () => { + const code = ` + pm.test("Status check", function() { + pm.response.to.be.ok; + pm.response.to.be.success; + }); + `; + const translatedCode = translateCode(code); + expect(translatedCode).toContain('test("Status check", function() {'); + expect(translatedCode).toContain('expect(res.getStatus()).to.be.within(200, 299)'); + }); + + it('should translate pm.response.to.have.jsonBody with path', () => { + const code = 'pm.response.to.have.jsonBody("user.id");'; + const translatedCode = translateCode(code); + expect(translatedCode).toContain('expect(res.getBody()).to.have.nested.property("user.id")'); + }); + + it('should translate pm.response.to.have.jsonBody with path and value', () => { + const code = 'pm.response.to.have.jsonBody("status", "success");'; + const translatedCode = translateCode(code); + expect(translatedCode).toContain('expect(res.getBody()).to.have.nested.property("status", "success")'); + }); + + it('should translate pm.response.to.have.jsonBody without arguments', () => { + const code = 'pm.response.to.have.jsonBody();'; + const translatedCode = translateCode(code); + expect(translatedCode).toContain('expect(res.getBody()).to.exist'); + }); + + it('should handle pm.response.to.have.jsonBody inside test blocks', () => { + const code = ` + pm.test("Response validation", function() { + pm.response.to.have.jsonBody("data"); + pm.response.to.have.jsonBody("data.id", 123); + }); + `; + const translatedCode = translateCode(code); + expect(translatedCode).toContain('test("Response validation", function() {'); + expect(translatedCode).toContain('expect(res.getBody()).to.have.nested.property("data")'); + expect(translatedCode).toContain('expect(res.getBody()).to.have.nested.property("data.id", 123)'); + }); + + it('should translate pm.response.to.have.jsonBody with nested path', () => { + const code = 'pm.response.to.have.jsonBody("response.data.items[0].name");'; + const translatedCode = translateCode(code); + expect(translatedCode).toContain('expect(res.getBody()).to.have.nested.property("response.data.items[0].name")'); + }); + + it('should translate pm.response.to.have.jsonBody with variable path', () => { + const code = 'const path = "user.id"; pm.response.to.have.jsonBody(path);'; + const translatedCode = translateCode(code); + expect(translatedCode).toContain('expect(res.getBody()).to.have.nested.property(path)'); + }); }); 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 1acee22d1..5bdf927e7 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 @@ -204,4 +204,58 @@ describe('Variables Translation', () => { expect(translatedCode).toBe('bru.setVar("fullPath", bru.getEnvVar("baseUrl") + bru.getVar("endpoint"));'); }); + + // replaceIn tests for different variable scopes + it('should translate pm.globals.replaceIn', () => { + const code = 'pm.globals.replaceIn("Hello {{name}}");'; + const translatedCode = translateCode(code); + + expect(translatedCode).toBe('bru.interpolate("Hello {{name}}");'); + }); + + it('should translate pm.environment.replaceIn', () => { + const code = 'pm.environment.replaceIn("{{baseUrl}}/api");'; + const translatedCode = translateCode(code); + + expect(translatedCode).toBe('bru.interpolate("{{baseUrl}}/api");'); + }); + + it('should translate pm.collectionVariables.replaceIn', () => { + const code = 'pm.collectionVariables.replaceIn("{{apiKey}}");'; + const translatedCode = translateCode(code); + + expect(translatedCode).toBe('bru.interpolate("{{apiKey}}");'); + }); + + it('should translate pm.globals.replaceIn in assignment', () => { + const code = 'const message = pm.globals.replaceIn("Welcome {{username}}!");'; + const translatedCode = translateCode(code); + + expect(translatedCode).toBe('const message = bru.interpolate("Welcome {{username}}!");'); + }); + + // pm.globals.has tests + it('should translate pm.globals.has', () => { + const code = 'pm.globals.has("token");'; + const translatedCode = translateCode(code); + + expect(translatedCode).toContain('bru.getGlobalEnvVar("token") !== undefined'); + expect(translatedCode).toContain('bru.getGlobalEnvVar("token") !== null'); + }); + + 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('console.log("Token exists");'); + }); + + it('should translate pm.globals.has with variable assignment', () => { + 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'); + }); });