diff --git a/packages/bruno-converters/src/utils/jscode-shift-translator.js b/packages/bruno-converters/src/utils/jscode-shift-translator.js index 405a8d540..14450e054 100644 --- a/packages/bruno-converters/src/utils/jscode-shift-translator.js +++ b/packages/bruno-converters/src/utils/jscode-shift-translator.js @@ -350,6 +350,9 @@ function translateCode(code) { // Process all transformations in a single pass processTransformations(ast, transformedNodes); + // Handle legacy Postman global APIs + handleLegacyGlobalAPIs(ast, transformedNodes, code); + // Handle special Postman syntax patterns handleTestsBracketNotation(ast); @@ -787,5 +790,102 @@ function handleTestsBracketNotation(ast) { }); } +/** + * Handle legacy Postman global API transformations + * This function processes legacy Postman globals like responseBody, responseHeaders, responseTime + * while preserving user-defined variables with the same names + * + * @param {Object} ast - jscodeshift AST + * @param {Set} transformedNodes - Set of already transformed nodes + * @param {string} code - The original Postman script code + */ +function handleLegacyGlobalAPIs(ast, transformedNodes, code) { + // regex check before the ast traversal + const legacyGlobalRegex = /responseBody|responseHeaders|responseTime/; + + if (!legacyGlobalRegex.test(code)) { + return; + } + + // Check for variable declarations with legacy global names - track which ones have conflicts + const conflictingNames = new Set(); + + // Check variable declarations + ast.find(j.VariableDeclarator).forEach(path => { + if (path.value.id.type === 'Identifier') { + const varName = path.value.id.name; + if (legacyGlobalRegex.test(varName)) { + conflictingNames.add(varName); + } + } + }); + + // Handle JSON.parse(responseBody) → res.getBody() + // Only transform if responseBody doesn't have a user variable conflict + if (!conflictingNames.has('responseBody')) { + ast.find(j.CallExpression).forEach(path => { + if (transformedNodes.has(path.node)) return; + + const callExpr = path.value; + if (callExpr.callee.type === 'MemberExpression' && callExpr.callee.object.name === 'JSON' && callExpr.callee.property.name === 'parse') { + const args = callExpr.arguments; + + // Check if the argument is 'responseBody' + if (args.length > 0 && args[0].type === 'Identifier' && args[0].name === 'responseBody') { + // Replace JSON.parse(responseBody) with res.getBody() + j(path).replaceWith(j.identifier('res.getBody()')); + transformedNodes.add(path.node); + } + } + }); + } + + // Handle standalone legacy Postman global variables + const legacyGlobals = [ + { name: 'responseBody', replacement: 'res.getBody()' }, + { name: 'responseHeaders', replacement: 'res.getHeaders()' }, + { name: 'responseTime', replacement: 'res.getResponseTime()' } + ]; + + legacyGlobals.forEach(({ name, replacement }) => { + // Skip transformation if this name has a user variable conflict + if (conflictingNames.has(name)) { + return; + } + + ast.find(j.Identifier, { name }).forEach(path => { + if (transformedNodes.has(path.node)) return; + + // Only transform identifiers that are being used as values, not as variable names + const parent = path.parent.value; + + // Skip if this is part of a variable declaration (const responseBody = ...) + if (parent.type === 'VariableDeclarator' && parent.id === path.node) { + return; // Keep unchanged + } + + // Skip if this is part of an assignment (responseBody = ...) + if (parent.type === 'AssignmentExpression' && parent.left === path.node) { + return; // Keep unchanged + } + + // Skip if this is part of a function parameter + if (parent.type === 'FunctionDeclaration' || parent.type === 'FunctionExpression') { + return; // Keep unchanged + } + + // Skip if this is part of an object property + if (parent.type === 'Property' && (parent.key === path.node || parent.value === path.node)) { + return; // Keep unchanged + } + + // Transform all other references (including function call arguments) + // This will transform console.log(responseBody) → console.log(res.getBody()) + j(path).replaceWith(j.identifier(replacement)); + transformedNodes.add(path.node); + }); + }); +} + export { getMemberExpressionString }; export default translateCode; \ No newline at end of file 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 new file mode 100644 index 000000000..489bf734a --- /dev/null +++ b/packages/bruno-converters/tests/postman/postman-translations/transpiler-tests/legacy-global-apis.test.js @@ -0,0 +1,299 @@ +import translateCode from '../../../../src/utils/jscode-shift-translator.js'; + +describe('Legacy Postman API Translation', () => { + describe('handleLegacyGlobalAPIs - No Conflicts', () => { + test('should translate responseBody when no user variables exist', () => { + const input = ` + const data = JSON.parse(responseBody); +`; + + const result = translateCode(input); + const expected = ` + const data = res.getBody(); +`; + + expect(result).toEqual(expected); + }); + + test('should translate responseHeaders when no user variables exist', () => { + const input = ` + console.log(responseHeaders); + const headers = responseHeaders; + `; + + const result = translateCode(input); + + expect(result).toContain('res.getHeaders()'); + expect(result).not.toContain('responseHeaders'); + }); + + test('should translate responseTime when no user variables exist', () => { + const input = ` + console.log(responseTime); + const time = responseTime; + `; + + const result = translateCode(input); + + expect(result).toContain('res.getResponseTime()'); + expect(result).not.toContain('responseTime'); + }); + + test('should translate JSON.parse(responseBody) when no user variables exist', () => { + const input = ` + const data = JSON.parse(responseBody); + console.log(data); + `; + + const result = translateCode(input); + + expect(result).toContain('res.getBody()'); + expect(result).not.toContain('JSON.parse(responseBody)'); + expect(result).not.toContain('responseBody'); + }); + + test('should translate JSON.parse(responseBody) usage without assignment when no user variables exist', () => { + const input = ` + console.log(JSON.parse(responseBody)); + `; + + const result = translateCode(input); + const expected = ` + console.log(res.getBody()); + `; + + expect(result).toContain(expected); + }); + + test('should translate all legacy APIs when no conflicts exist', () => { + const input = ` + const data = JSON.parse(responseBody); + const headers = responseHeaders; + const time = responseTime; + + console.log(data, headers, time); + `; + + const result = translateCode(input); + + expect(result).toContain('res.getBody()'); + expect(result).toContain('res.getHeaders()'); + expect(result).toContain('res.getResponseTime()'); + expect(result).not.toContain('responseBody'); + expect(result).not.toContain('responseHeaders'); + expect(result).not.toContain('responseTime'); + }); + }); + + describe('handleLegacyGlobalAPIs - With Conflicts', () => { + test('should NOT translate responseBody when user variable exists', () => { + const input = ` + const responseBody = pm.response.json(); + console.log(responseBody); + `; + + const result = translateCode(input); + const expected = ` + const responseBody = res.getBody(); + console.log(responseBody); + `; + + // pm.response.json() should be transformed to res.getBody() (Postman API transformation) + expect(result).toEqual(expected); + }); + + test('should NOT translate responseHeaders when user variable exists', () => { + const input = ` + const responseHeaders = pm.response.headers; + console.log(responseHeaders); + `; + + const result = translateCode(input); + const expected = ` + const responseHeaders = res.getHeaders(); + console.log(responseHeaders); + `; + + expect(result).toEqual(expected); + }); + + test('should NOT translate responseTime when user variable exists', () => { + const input = ` + const responseTime = pm.response.responseTime; + console.log(responseTime); + `; + + const result = translateCode(input); + const expected = ` + const responseTime = res.getResponseTime(); + console.log(responseTime); + `; + + expect(result).toEqual(expected); + }); + + test('should NOT translate JSON.parse(responseBody) when user variable exists', () => { + const input = ` + const responseBody = pm.response.json(); + const data = JSON.parse(responseBody); + console.log(data); + `; + + const result = translateCode(input); + const expected = ` + const responseBody = res.getBody(); + const data = JSON.parse(responseBody); + console.log(data); + `; + + expect(result).toEqual(expected); + }); + }); + + describe('handleLegacyGlobalAPIs - Partial Conflicts', () => { + test('should translate non-conflicting APIs when some conflicts exist', () => { + const input = ` + const responseBody = pm.response.json(); + console.log(responseBody); + console.log(responseHeaders); + console.log(responseTime); + `; + + const result = translateCode(input); + const expected = ` + const responseBody = res.getBody(); + console.log(responseBody); + console.log(res.getHeaders()); + console.log(res.getResponseTime()); + `; + + expect(result).toEqual(expected); + + }); + + test('should translate JSON.parse(responseBody) only when no conflict exists', () => { + const input = ` + const responseHeaders = pm.response.headers; + const data = JSON.parse(responseBody); + console.log(responseHeaders); + `; + + const result = translateCode(input); + const expected = ` + const responseHeaders = res.getHeaders(); + const data = res.getBody(); + console.log(responseHeaders); + `; + + expect(result).toEqual(expected); + }); + }); + + describe('handleLegacyGlobalAPIs - Edge Cases', () => { + test.skip('should handle function parameters with legacy names', () => { + const input = ` + function test(responseBody) { + console.log(responseBody); + console.log(responseHeaders); + } + `; + + const result = translateCode(input); + const expected = ` + function test(responseBody) { + console.log(responseBody); + console.log(res.getHeaders()); + } + `; + + expect(result).toEqual(expected); + }); + + test('should handle object properties with legacy names', () => { + const input = ` + const config = { + responseBody: 'custom', + responseHeaders: 'custom' + }; + console.log(responseTime); + `; + + const result = translateCode(input); + + const expected = ` + const config = { + responseBody: 'custom', + responseHeaders: 'custom' + }; + console.log(res.getResponseTime()); + `; + + expect(result).toEqual(expected); + }); + + test('should handle assignments with legacy names', () => { + const input = ` + responseBody = 'new value'; + responseHeaders = 'new headers'; + console.log(responseTime); + `; + + const result = translateCode(input); + + const expected = ` + responseBody = 'new value'; + responseHeaders = 'new headers'; + console.log(res.getResponseTime()); + `; + + expect(result).toEqual(expected); + }); + + test('should handle mixed usage patterns', () => { + const input = ` + const responseBody = pm.response.json(); + const data = JSON.parse(responseBody); + console.log(responseHeaders); + console.log(responseTime); + + function test(data) { + console.log(responseBody); + console.log(responseHeaders); + } + `; + + const result = translateCode(input); + + const expected = ` + const responseBody = res.getBody(); + const data = JSON.parse(responseBody); + console.log(res.getHeaders()); + console.log(res.getResponseTime()); + + function test(data) { + console.log(responseBody); + console.log(res.getHeaders()); + } + `; + + expect(result).toEqual(expected); + }); + }); + + describe('handleLegacyGlobalAPIs - No Legacy APIs', () => { + test('should not modify code when no legacy APIs are present', () => { + const input = ` + const data = { name: 'test' }; + console.log(data.name); + `; + + const result = translateCode(input); + const expected = ` + const data = { name: 'test' }; + console.log(data.name); + `; + + expect(result).toEqual(expected); + }); + }); +});