-
Bruno Collection
-
Postman Collection
-
Insomnia Collection
-
OpenAPI V3 Spec
+
+ const FullscreenLoader = () => {
+ const [loadingMessage, setLoadingMessage] = useState('');
+
+ // Messages to cycle through while loading
+ const loadingMessages = [
+ 'Processing collection...',
+ 'Analyzing requests...',
+ 'Translating scripts...',
+ 'Preparing collection...',
+ 'Almost done...'
+ ];
+
+
+ // Cycle through loading messages for better UX
+ useEffect(() => {
+ if (!isLoading) return;
+
+ let messageIndex = 0;
+ const interval = setInterval(() => {
+ messageIndex = (messageIndex + 1) % loadingMessages.length;
+ setLoadingMessage(loadingMessages[messageIndex]);
+ }, 2000);
+
+ setLoadingMessage(loadingMessages[0]);
+
+ return () => clearInterval(interval);
+ }, [isLoading]);
+
+ return (
+
+
+
+
+ {loadingMessage}
+
+
+ This may take a moment depending on the collection size
+
-
+ );
+ };
+
+ return (
+ <>
+ {isLoading &&
}
+ {!isLoading && (
+
+
+
Select the type of your existing collection :
+
+ Bruno Collection
+ Postman Collection
+ Insomnia Collection
+ OpenAPI V3 Spec
+
+
+
+ )}
+ >
);
};
diff --git a/packages/bruno-app/src/utils/importers/postman-collection.js b/packages/bruno-app/src/utils/importers/postman-collection.js
index 75db9aaee..b9cceed65 100644
--- a/packages/bruno-app/src/utils/importers/postman-collection.js
+++ b/packages/bruno-app/src/utils/importers/postman-collection.js
@@ -1,6 +1,5 @@
import fileDialog from 'file-dialog';
import { BrunoError } from 'utils/common/error';
-import { postmanToBruno } from '@usebruno/converters';
import { safeParseJSON } from 'utils/common/index';
const readFile = (files) => {
@@ -12,18 +11,15 @@ const readFile = (files) => {
});
};
-
-const importCollection = () => {
+const postmanToBruno = (collection) => {
return new Promise((resolve, reject) => {
- fileDialog({ accept: 'application/json' })
- .then(readFile)
- .then((collection) => postmanToBruno(collection))
- .then((collection) => resolve({ collection }))
- .catch((err) => {
- console.log(err);
- reject(new BrunoError('Import collection failed'));
- })
+ window.ipcRenderer.invoke('renderer:convert-postman-to-bruno', collection)
+ .then(result => resolve(result))
+ .catch(err => {
+ console.error('Error converting Postman to Bruno via Electron:', err);
+ reject(new BrunoError('Conversion failed'));
+ });
});
};
-export default importCollection;
+export { postmanToBruno, readFile };
diff --git a/packages/bruno-converters/package.json b/packages/bruno-converters/package.json
index 3c4051849..b942dea98 100644
--- a/packages/bruno-converters/package.json
+++ b/packages/bruno-converters/package.json
@@ -21,6 +21,7 @@
"dependencies": {
"@usebruno/schema": "^0.7.0",
"js-yaml": "^4.1.0",
+ "jscodeshift": "^17.3.0",
"lodash": "^4.17.21",
"nanoid": "3.3.8"
},
@@ -31,6 +32,7 @@
"@rollup/plugin-commonjs": "^23.0.2",
"@rollup/plugin-node-resolve": "^15.0.1",
"@rollup/plugin-typescript": "^9.0.2",
+ "@web/rollup-plugin-copy": "^0.5.1",
"babel-jest": "^29.7.0",
"rimraf": "^5.0.7",
"rollup": "3.2.5",
diff --git a/packages/bruno-converters/rollup.config.js b/packages/bruno-converters/rollup.config.js
index d0c0aad44..ec9a7a4c9 100644
--- a/packages/bruno-converters/rollup.config.js
+++ b/packages/bruno-converters/rollup.config.js
@@ -2,6 +2,7 @@ const { nodeResolve } = require('@rollup/plugin-node-resolve');
const commonjs = require('@rollup/plugin-commonjs');
const { terser } = require('rollup-plugin-terser');
const peerDepsExternal = require('rollup-plugin-peer-deps-external');
+const { copy } = require('@web/rollup-plugin-copy');
const packageJson = require('./package.json');
const alias = require('@rollup/plugin-alias');
@@ -12,12 +13,12 @@ module.exports = [
input: 'src/index.js',
output: [
{
- file: packageJson.main,
+ dir: path.dirname(packageJson.main),
format: 'cjs',
sourcemap: true
},
{
- file: packageJson.module,
+ dir: path.dirname(packageJson.module),
format: 'esm',
sourcemap: true
}
@@ -32,6 +33,10 @@ module.exports = [
terser(),
alias({
entries: [{ find: 'src', replacement: path.resolve(__dirname, 'src') }]
+ }),
+ copy({
+ patterns: 'src/workers/scripts/**/*',
+ rootDir: '.'
})
]
}
diff --git a/packages/bruno-converters/src/common/index.js b/packages/bruno-converters/src/common/index.js
index c2a3d76aa..bc8c32cb4 100644
--- a/packages/bruno-converters/src/common/index.js
+++ b/packages/bruno-converters/src/common/index.js
@@ -47,6 +47,7 @@ export const validateSchema = (collection = {}) => {
collectionSchema.validateSync(collection);
return collection;
} catch (err) {
+ console.log("Error validating schema", err);
throw new Error('The Collection has an invalid schema');
}
};
diff --git a/packages/bruno-converters/src/index.js b/packages/bruno-converters/src/index.js
index fa89457ed..d5b3d3a3b 100644
--- a/packages/bruno-converters/src/index.js
+++ b/packages/bruno-converters/src/index.js
@@ -2,4 +2,5 @@ export { default as postmanToBruno } from './postman/postman-to-bruno.js';
export { default as postmanToBrunoEnvironment } from './postman/postman-env-to-bruno-env.js';
export { default as brunoToPostman } from './postman/bruno-to-postman.js';
export { default as openApiToBruno } from './openapi/openapi-to-bruno.js';
-export { default as insomniaToBruno } from './insomnia/insomnia-to-bruno.js';
\ No newline at end of file
+export { default as insomniaToBruno } from './insomnia/insomnia-to-bruno.js';
+export { default as postmanTranslation } from './postman/postman-translations.js';
\ No newline at end of file
diff --git a/packages/bruno-converters/src/postman/postman-to-bruno.js b/packages/bruno-converters/src/postman/postman-to-bruno.js
index 6eb832c24..c14e12172 100644
--- a/packages/bruno-converters/src/postman/postman-to-bruno.js
+++ b/packages/bruno-converters/src/postman/postman-to-bruno.js
@@ -93,17 +93,10 @@ const importScriptsFromEvents = (events, requestObject) => {
requestObject.script = {};
}
- if (Array.isArray(event.script.exec)) {
- if (event.script.exec.length > 0) {
- requestObject.script.req = event.script.exec
- .map((line) => postmanTranslation(line))
- .join('\n');
- } else {
- requestObject.script.req = '';
- }
- } else if (typeof event.script.exec === 'string') {
- requestObject.script.req = postmanTranslation(event.script.exec);
+ if (event.script.exec && event.script.exec.length > 0) {
+ requestObject.script.req = postmanTranslation(event.script.exec)
} else {
+ requestObject.script.req = '';
console.warn('Unexpected event.script.exec type', typeof event.script.exec);
}
}
@@ -113,17 +106,10 @@ const importScriptsFromEvents = (events, requestObject) => {
requestObject.tests = {};
}
- if (Array.isArray(event.script.exec)) {
- if (event.script.exec.length > 0) {
- requestObject.tests = event.script.exec
- .map((line) => postmanTranslation(line))
- .join('\n');
- } else {
- requestObject.tests = '';
- }
- } else if (typeof event.script.exec === 'string') {
- requestObject.tests = postmanTranslation(event.script.exec);
+ if (event.script.exec && event.script.exec.length > 0) {
+ requestObject.tests = postmanTranslation(event.script.exec)
} else {
+ requestObject.tests = '';
console.warn('Unexpected event.script.exec type', typeof event.script.exec);
}
}
@@ -246,13 +232,13 @@ const processAuth = (auth, requestObject) => {
}
};
-const importPostmanV2CollectionItem = (brunoParent, item, parentAuth) => {
+const importPostmanV2CollectionItem = (brunoParent, item, parentAuth, { useWorkers = false } = {}, scriptMap)=> {
brunoParent.items = brunoParent.items || [];
const folderMap = {};
const requestMap = {};
const requestMethods = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS', 'TRACE']
- each(item, (i, index) => {
+ item.forEach((i, index) => {
if (isItemAFolder(i)) {
const baseFolderName = i.name || 'Untitled Folder';
let folderName = baseFolderName;
@@ -292,6 +278,8 @@ const importPostmanV2CollectionItem = (brunoParent, item, parentAuth) => {
}
};
+ brunoParent.items.push(brunoFolderItem);
+
// Folder level auth
if (i.auth) {
processAuth(i.auth, brunoFolderItem.root.request);
@@ -301,222 +289,221 @@ const importPostmanV2CollectionItem = (brunoParent, item, parentAuth) => {
}
if (i.item && i.item.length) {
- importPostmanV2CollectionItem(brunoFolderItem, i.item, i.auth ?? parentAuth);
+ importPostmanV2CollectionItem(brunoFolderItem, i.item, i.auth ?? parentAuth, { useWorkers }, scriptMap);
}
if (i.event) {
- importScriptsFromEvents(i.event, brunoFolderItem.root.request);
+ if(useWorkers) {
+ scriptMap.set(brunoFolderItem.uid, {
+ events: i.event,
+ request: brunoFolderItem.root.request
+ });
+ } else {
+ importScriptsFromEvents(i.event, brunoFolderItem.root.request);
+ }
}
- brunoParent.items.push(brunoFolderItem);
folderMap[folderName] = brunoFolderItem;
- } else {
- if (i.request) {
- if (!requestMethods.includes(i?.request?.method.toUpperCase())) {
- console.warn('Unexpected request.method', i?.request?.method);
- return;
+ } else if (i.request) {
+ if (!requestMethods.includes(i?.request?.method.toUpperCase())) {
+ console.warn('Unexpected request.method', i?.request?.method);
+ return;
+ }
+
+ const baseRequestName = i.name || 'Untitled Request';
+ let requestName = baseRequestName;
+ let count = 1;
+
+ while (requestMap[requestName]) {
+ requestName = `${baseRequestName}_${count}`;
+ count++;
+ }
+
+ const url = constructUrl(i.request.url);
+
+ const brunoRequestItem = {
+ uid: uuid(),
+ name: requestName,
+ type: 'http-request',
+ seq: index + 1,
+ request: {
+ url: url,
+ method: i?.request?.method?.toUpperCase(),
+ auth: {
+ mode: 'none',
+ basic: null,
+ bearer: null,
+ awsv4: null,
+ apikey: null,
+ oauth2: null,
+ digest: null
+ },
+ headers: [],
+ params: [],
+ body: {
+ mode: 'none',
+ json: null,
+ text: null,
+ xml: null,
+ formUrlEncoded: [],
+ multipartForm: []
+ },
+ docs: i.request.description || ''
}
+ };
- const baseRequestName = i.name || 'Untitled Request';
- let requestName = baseRequestName;
- let count = 1;
+ brunoParent.items.push(brunoRequestItem);
- while (requestMap[requestName]) {
- requestName = `${baseRequestName}_${count}`;
- count++;
- }
-
- const url = constructUrl(i.request.url);
-
- const brunoRequestItem = {
- uid: uuid(),
- name: requestName,
- type: 'http-request',
- seq: index + 1,
- request: {
- url: url,
- method: i?.request?.method?.toUpperCase(),
- auth: {
- mode: 'none',
- basic: null,
- bearer: null,
- awsv4: null,
- apikey: null,
- oauth2: null,
- digest: null
- },
- headers: [],
- params: [],
- body: {
- mode: 'none',
- json: null,
- text: null,
- xml: null,
- formUrlEncoded: [],
- multipartForm: []
- },
- docs: i.request.description || ''
- }
- };
-
- if (i.event) {
+ if (i.event) {
+ if(useWorkers) {
+ scriptMap.set(brunoRequestItem.uid, {
+ events: i.event,
+ request: brunoRequestItem.request
+ });
+ } else {
i.event.forEach((event) => {
if (event.listen === 'prerequest' && event.script && event.script.exec) {
- if (!brunoRequestItem.request.script) {
+ if (!brunoRequestItem.request?.script) {
brunoRequestItem.request.script = {};
}
- if (Array.isArray(event.script.exec)) {
- if (event.script.exec.length > 0) {
- brunoRequestItem.request.script.req = event.script.exec
- .map((line) => postmanTranslation(line))
- .join('\n');
- } else {
- brunoRequestItem.request.script.req = '';
- }
- } else if (typeof event.script.exec === 'string') {
- brunoRequestItem.request.script.req = postmanTranslation(event.script.exec);
+ if (event.script.exec && event.script.exec.length > 0) {
+ brunoRequestItem.request.script.req = postmanTranslation(event.script.exec)
} else {
+ brunoRequestItem.request.script.req = '';
console.warn('Unexpected event.script.exec type', typeof event.script.exec);
}
}
if (event.listen === 'test' && event.script && event.script.exec) {
- if (!brunoRequestItem.request.tests) {
+ if (!brunoRequestItem.request?.tests) {
brunoRequestItem.request.tests = {};
}
- if (Array.isArray(event.script.exec)) {
- if (event.script.exec.length > 0) {
- brunoRequestItem.request.tests = event.script.exec
- .map((line) => postmanTranslation(line))
- .join('\n');
- } else {
- brunoRequestItem.request.tests = '';
- }
- } else if (typeof event.script.exec === 'string') {
- brunoRequestItem.request.tests = postmanTranslation(event.script.exec);
+ if (event.script.exec && event.script.exec.length > 0) {
+ brunoRequestItem.request.tests = postmanTranslation(event.script.exec)
} else {
+ brunoRequestItem.request.tests = '';
console.warn('Unexpected event.script.exec type', typeof event.script.exec);
}
}
});
}
-
- const bodyMode = get(i, 'request.body.mode');
- if (bodyMode) {
- if (bodyMode === 'formdata') {
- brunoRequestItem.request.body.mode = 'multipartForm';
-
- each(i.request.body.formdata, (param) => {
- const isFile = param.type === 'file';
- let value;
- let type;
-
- if (isFile) {
- // If param.src is an array, keep it as it is.
- // If param.src is a string, convert it into an array with a single element.
- value = Array.isArray(param.src) ? param.src : typeof param.src === 'string' ? [param.src] : null;
- type = 'file';
- } else {
- value = param.value;
- type = 'text';
- }
-
- brunoRequestItem.request.body.multipartForm.push({
- uid: uuid(),
- type: type,
- name: param.key,
- value: value,
- description: param.description,
- enabled: !param.disabled
- });
- });
- }
-
- if (bodyMode === 'urlencoded') {
- brunoRequestItem.request.body.mode = 'formUrlEncoded';
- each(i.request.body.urlencoded, (param) => {
- brunoRequestItem.request.body.formUrlEncoded.push({
- uid: uuid(),
- name: param.key,
- value: param.value,
- description: param.description,
- enabled: !param.disabled
- });
- });
- }
-
- if (bodyMode === 'raw') {
- let language = get(i, 'request.body.options.raw.language');
- if (!language) {
- language = searchLanguageByHeader(i.request.header);
- }
- if (language === 'json') {
- brunoRequestItem.request.body.mode = 'json';
- brunoRequestItem.request.body.json = i.request.body.raw;
- } else if (language === 'xml') {
- brunoRequestItem.request.body.mode = 'xml';
- brunoRequestItem.request.body.xml = i.request.body.raw;
- } else {
- brunoRequestItem.request.body.mode = 'text';
- brunoRequestItem.request.body.text = i.request.body.raw;
- }
- }
- }
-
- if (bodyMode === 'graphql') {
- brunoRequestItem.type = 'graphql-request';
- brunoRequestItem.request.body.mode = 'graphql';
- brunoRequestItem.request.body.graphql = parseGraphQLRequest(i.request.body.graphql);
- }
-
- each(i.request.header, (header) => {
- brunoRequestItem.request.headers.push({
- uid: uuid(),
- name: header.key,
- value: header.value,
- description: header.description,
- enabled: !header.disabled
- });
- });
-
- // Handle request-level auth or inherit from parent
- const auth = i.request.auth ?? parentAuth;
- processAuth(auth, brunoRequestItem.request);
-
- each(get(i, 'request.url.query'), (param) => {
- brunoRequestItem.request.params.push({
- uid: uuid(),
- name: param.key,
- value: param.value,
- description: param.description,
- type: 'query',
- enabled: !param.disabled
- });
- });
-
- each(get(i, 'request.url.variable', []), (param) => {
- if (!param.key) {
- // If no key, skip this iteration and discard the param
- return;
- }
-
- brunoRequestItem.request.params.push({
- uid: uuid(),
- name: param.key,
- value: param.value ?? '',
- description: param.description ?? '',
- type: 'path',
- enabled: true
- });
- });
-
- brunoParent.items.push(brunoRequestItem);
- requestMap[requestName] = brunoRequestItem;
}
+
+ const bodyMode = get(i, 'request.body.mode');
+ if (bodyMode) {
+ if (bodyMode === 'formdata') {
+ brunoRequestItem.request.body.mode = 'multipartForm';
+
+ each(i.request.body.formdata, (param) => {
+ const isFile = param.type === 'file';
+ let value;
+ let type;
+
+ if (isFile) {
+ // If param.src is an array, keep it as it is.
+ // If param.src is a string, convert it into an array with a single element.
+ value = Array.isArray(param.src) ? param.src : typeof param.src === 'string' ? [param.src] : null;
+ type = 'file';
+ } else {
+ value = param.value;
+ type = 'text';
+ }
+
+ brunoRequestItem.request.body.multipartForm.push({
+ uid: uuid(),
+ type: type,
+ name: param.key,
+ value: value,
+ description: param.description,
+ enabled: !param.disabled
+ });
+ });
+ }
+
+ if (bodyMode === 'urlencoded') {
+ brunoRequestItem.request.body.mode = 'formUrlEncoded';
+ each(i.request.body.urlencoded, (param) => {
+ brunoRequestItem.request.body.formUrlEncoded.push({
+ uid: uuid(),
+ name: param.key,
+ value: param.value,
+ description: param.description,
+ enabled: !param.disabled
+ });
+ });
+ }
+
+ if (bodyMode === 'raw') {
+ let language = get(i, 'request.body.options.raw.language');
+ if (!language) {
+ language = searchLanguageByHeader(i.request.header);
+ }
+ if (language === 'json') {
+ brunoRequestItem.request.body.mode = 'json';
+ brunoRequestItem.request.body.json = i.request.body.raw;
+ } else if (language === 'xml') {
+ brunoRequestItem.request.body.mode = 'xml';
+ brunoRequestItem.request.body.xml = i.request.body.raw;
+ } else {
+ brunoRequestItem.request.body.mode = 'text';
+ brunoRequestItem.request.body.text = i.request.body.raw;
+ }
+ }
+ }
+
+ if (bodyMode === 'graphql') {
+ brunoRequestItem.type = 'graphql-request';
+ brunoRequestItem.request.body.mode = 'graphql';
+ brunoRequestItem.request.body.graphql = parseGraphQLRequest(i.request.body.graphql);
+ }
+
+ each(i.request.header, (header) => {
+ brunoRequestItem.request.headers.push({
+ uid: uuid(),
+ name: header.key,
+ value: header.value,
+ description: header.description,
+ enabled: !header.disabled
+ });
+ });
+
+ // Handle request-level auth or inherit from parent
+ const auth = i.request.auth ?? parentAuth;
+ processAuth(auth, brunoRequestItem.request);
+
+ each(get(i, 'request.url.query'), (param) => {
+ brunoRequestItem.request.params.push({
+ uid: uuid(),
+ name: param.key,
+ value: param.value,
+ description: param.description,
+ type: 'query',
+ enabled: !param.disabled
+ });
+ });
+
+ each(get(i, 'request.url.variable', []), (param) => {
+ if (!param.key) {
+ // If no key, skip this iteration and discard the param
+ return;
+ }
+
+ brunoRequestItem.request.params.push({
+ uid: uuid(),
+ name: param.key,
+ value: param.value ?? '',
+ description: param.description ?? '',
+ type: 'path',
+ enabled: true
+ });
+ });
+
+ requestMap[requestName] = brunoRequestItem;
}
});
};
+
const searchLanguageByHeader = (headers) => {
let contentType;
each(headers, (header) => {
@@ -532,7 +519,7 @@ const searchLanguageByHeader = (headers) => {
return contentType;
};
-const importPostmanV2Collection = (collection) => {
+const importPostmanV2Collection = async (collection, { useWorkers = false }) => {
const brunoCollection = {
name: collection.info.name || 'Untitled Collection',
uid: uuid(),
@@ -573,12 +560,74 @@ const importPostmanV2Collection = (collection) => {
// Collection level auth
processAuth(collection.auth, brunoCollection.root.request);
- importPostmanV2CollectionItem(brunoCollection, collection.item, collection.auth);
-
+ // Create a single scriptMap for all items
+ const scriptMap = useWorkers ? new Map() : null;
+
+ importPostmanV2CollectionItem(brunoCollection, collection.item, collection.auth, { useWorkers }, scriptMap);
+
+ // Process all scripts in a single call at the top level
+ if (useWorkers && scriptMap && scriptMap.size > 0) {
+ try {
+ const { default: scriptTranslationWorker } = await import('../workers/postman-translator-worker');
+ const translatedScripts = await scriptTranslationWorker(scriptMap);
+
+ // Apply translated scripts to all items in the collection
+ const applyScriptsToItems = (items) => {
+ items.forEach(item => {
+ if (item.type === 'folder') {
+ // Apply scripts to the folder
+ if (translatedScripts.has(item.uid)) {
+ if (!item.root.request.script) {
+ item.root.request.script = {};
+ }
+ if (!item.root.request.tests) {
+ item.root.request.tests = '';
+ }
+
+ const script = translatedScripts.get(item.uid).request?.script?.req;
+ const tests = translatedScripts.get(item.uid).request?.tests;
+
+ item.root.request.script.req = script && script.length > 0 ? script : '';
+ item.root.request.tests = tests && tests.length > 0 ? tests : '';
+ }
+
+ // Recursively apply to nested items
+ if (item.items && item.items.length > 0) {
+ applyScriptsToItems(item.items);
+ }
+ } else {
+ if (translatedScripts.has(item.uid)) {
+ if (!item.request.script) {
+ item.request.script = {};
+ }
+ if (!item.request.tests) {
+ item.request.tests = '';
+ }
+
+ const script = translatedScripts.get(item.uid).request?.script?.req;
+ const tests = translatedScripts.get(item.uid).request?.tests;
+
+ item.request.script.req = script && script.length > 0 ? script : '';
+ item.request.tests = tests && tests.length > 0 ? tests : '';
+ }
+ }
+ });
+ };
+
+ applyScriptsToItems(brunoCollection.items);
+
+ } catch (error) {
+ console.error('Error in script translation worker:', error);
+ } finally {
+ scriptMap.clear();
+ }
+ }
+
return brunoCollection;
};
-const parsePostmanCollection = (collection) => {
+
+const parsePostmanCollection = async (collection, { useWorkers = false }) => {
try {
let schema = get(collection, 'info.schema');
@@ -590,7 +639,7 @@ const parsePostmanCollection = (collection) => {
];
if (v2Schemas.includes(schema)) {
- return importPostmanV2Collection(collection);
+ return await importPostmanV2Collection(collection, { useWorkers });
}
throw new Error('Unsupported Postman schema version. Only Postman Collection v2.0 and v2.1 are supported.');
@@ -604,9 +653,10 @@ const parsePostmanCollection = (collection) => {
}
};
-const postmanToBruno = (postmanCollection) => {
+const postmanToBruno = async (postmanCollection, { useWorkers = false } = {}) => {
try {
- const parsedPostmanCollection = parsePostmanCollection(postmanCollection);
+
+ const parsedPostmanCollection = await parsePostmanCollection(postmanCollection, { useWorkers });
const transformedCollection = transformItemsInCollection(parsedPostmanCollection);
const hydratedCollection = hydrateSeqInCollection(transformedCollection);
const validatedCollection = validateSchema(hydratedCollection);
@@ -617,4 +667,5 @@ const postmanToBruno = (postmanCollection) => {
}
};
+
export default postmanToBruno;
\ No newline at end of file
diff --git a/packages/bruno-converters/src/postman/postman-translations.js b/packages/bruno-converters/src/postman/postman-translations.js
index b741cd3b2..23195b5b8 100644
--- a/packages/bruno-converters/src/postman/postman-translations.js
+++ b/packages/bruno-converters/src/postman/postman-translations.js
@@ -1,3 +1,5 @@
+import translateCode from '../utils/jscode-shift-translator';
+
const replacements = {
'pm\\.environment\\.get\\(': 'bru.getEnvVar(',
'pm\\.environment\\.set\\(': 'bru.setEnvVar(',
@@ -20,7 +22,7 @@ const replacements = {
'pm\\.response\\.responseTime': 'res.getResponseTime()',
'pm\\.environment\\.name': 'bru.getEnvName()',
'pm\\.response\\.status': 'res.statusText',
- 'pm\\.response\\.headers': 'req.getHeaders()',
+ 'pm\\.response\\.headers': 'res.getHeaders()',
"tests\\['([^']+)'\\]\\s*=\\s*([^;]+);": 'test("$1", function() { expect(Boolean($2)).to.be.true; });',
'pm\\.request\\.url': 'req.getUrl()',
'pm\\.request\\.method': 'req.getMethod()',
@@ -48,22 +50,38 @@ const compiledReplacements = Object.entries(extendedReplacements).map(([pattern,
replacement
}));
-const postmanTranslation = (script) => {
+const processRegexReplacement = (code) => {
+ for (const { regex, replacement } of compiledReplacements) {
+ if (regex.test(code)) {
+ code = code.replace(regex, replacement);
+
+ }
+ }
+ if ((code.includes('pm.') || code.includes('postman.'))) {
+ code = code.replace(/^(.*(pm\.|postman\.).*)$/gm, '// $1');
+ }
+ return code;
+}
+
+
+const postmanTranslation = (script, options = {}) => {
+ let modifiedScript = Array.isArray(script) ? script.join('\n') : script;
+
try {
- let modifiedScript = script;
- let modified = false;
- for (const { regex, replacement } of compiledReplacements) {
- if (regex.test(modifiedScript)) {
- modifiedScript = modifiedScript.replace(regex, replacement);
- modified = true;
- }
+ let translatedCode = translateCode(modifiedScript);
+ if ((translatedCode.includes('pm.') || translatedCode.includes('postman.'))) {
+ translatedCode = translatedCode.replace(/^(.*(pm\.|postman\.).*)$/gm, '// $1');
}
- if (modifiedScript.includes('pm.') || modifiedScript.includes('postman.')) {
- modifiedScript = modifiedScript.replace(/^(.*(pm\.|postman\.).*)$/gm, '// $1');
- }
- return modifiedScript;
+ return translatedCode;
} catch (e) {
- return script;
+ console.warn('Error in postman translation:', e);
+
+ try {
+ return processRegexReplacement(modifiedScript);
+ } catch (e) {
+ console.warn('Error in postman translation:', e);
+ return modifiedScript;
+ }
}
};
diff --git a/packages/bruno-converters/src/utils/jscode-shift-translator.js b/packages/bruno-converters/src/utils/jscode-shift-translator.js
new file mode 100644
index 000000000..924f4eb1d
--- /dev/null
+++ b/packages/bruno-converters/src/utils/jscode-shift-translator.js
@@ -0,0 +1,680 @@
+const j = require('jscodeshift');
+const cloneDeep = require('lodash/cloneDeep');
+
+/**
+ * Efficiently builds a string representation of a member expression without using toSource()
+ *
+ * @param {Object} node - The member expression node from the AST
+ * @return {string} - String representation of the member expression (e.g., "pm.environment.get")
+ */
+function getMemberExpressionString(node) {
+ // Handle base case: if this is an Identifier
+ if (node.type === 'Identifier') {
+ return node.name;
+ }
+
+ // Handle member expressions
+ if (node.type === 'MemberExpression') {
+ const objectStr = getMemberExpressionString(node.object);
+
+ // For computed properties like obj[prop], we need special handling
+ if (node.computed) {
+ // For literals like obj["prop"], we can include them in the string
+ if (node.property.type === 'Literal' && typeof node.property.value === 'string') {
+ return `${objectStr}.${node.property.value}`;
+ }
+ // For other computed properties, we can't reliably represent them as a simple string
+ return `${objectStr}.[computed]`;
+ }
+
+ // For regular property access like obj.prop
+ if (node.property.type === 'Identifier') {
+ return `${objectStr}.${node.property.name}`;
+ }
+ }
+
+ return '[unsupported]';
+}
+
+// Simple 1:1 translations for straightforward replacements
+const simpleTranslations = {
+ // Environment variables
+ 'pm.environment.get': 'bru.getEnvVar',
+ 'pm.environment.set': 'bru.setEnvVar',
+ 'pm.environment.name': 'bru.getEnvName()',
+ 'pm.environment.unset': 'bru.deleteEnvVar',
+
+ // Variables
+ 'pm.variables.get': 'bru.getVar',
+ 'pm.variables.set': 'bru.setVar',
+ 'pm.variables.has': 'bru.hasVar',
+
+ // Collection variables
+ 'pm.collectionVariables.get': 'bru.getVar',
+ 'pm.collectionVariables.set': 'bru.setVar',
+ 'pm.collectionVariables.has': 'bru.hasVar',
+ 'pm.collectionVariables.unset': 'bru.deleteVar',
+
+ // Request flow control
+ 'pm.setNextRequest': 'bru.setNextRequest',
+
+ // Testing
+ 'pm.test': 'test',
+ 'pm.expect': 'expect',
+ 'pm.expect.fail': 'expect.fail',
+
+ // Request properties
+ 'pm.request.url': 'req.getUrl()',
+ 'pm.request.method': 'req.getMethod()',
+ 'pm.request.headers': 'req.getHeaders()',
+ 'pm.request.body': 'req.getBody()',
+
+ // Response properties
+ 'pm.response.json': 'res.getBody',
+ 'pm.response.code': 'res.getStatus()',
+ 'pm.response.status': 'res.statusText',
+ 'pm.response.responseTime': 'res.getResponseTime()',
+ 'pm.response.statusText': 'res.statusText',
+ 'pm.response.headers': 'res.getHeaders()',
+
+ // Execution control
+ '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',
+};
+
+/* Complex transformations that need custom handling
+* Note: Transform functions can return either a single node or an array of nodes.
+* When returning an array of nodes, each node in the array will be inserted
+* as a separate statement, which allows a single Postman expression to be
+* transformed into multiple Bruno statements (e.g. for complex assertions).
+*/
+const complexTransformations = [
+ // 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) => {
+ return j.callExpression(j.identifier('JSON.stringify'), [j.identifier('res.getBody()')]);
+ }
+ },
+
+ // Handle pm.response.to.have.status
+ {
+ pattern: 'pm.response.to.have.status',
+ transform: (path, j) => {
+ const callExpr = path.parent.value;
+
+ const args = callExpr.arguments;
+
+ // Create: expect(res.getStatus()).to.equal(arg)
+ return j.callExpression(
+ j.memberExpression(
+ j.callExpression(
+ j.identifier('expect'),
+ [
+ j.callExpression(
+ j.identifier('res.getStatus'),
+ []
+ )
+ ]
+ ),
+ j.identifier('to.equal')
+ ),
+ args
+ );
+ }
+ },
+
+ // handle 'pm.response.to.have.header' to expect(res.getHeaders()).to.have.property(args)
+ {
+ pattern: 'pm.response.to.have.header',
+ transform: (path, j) => {
+ const callExpr = path.parent.value;
+
+ const args = callExpr.arguments;
+
+
+ if (args.length > 0) {
+ // Apply toLowerCase() to the first argument
+ args[0] = j.callExpression(
+ j.memberExpression(
+ args[0],
+ j.identifier('toLowerCase')
+ ),
+ []
+ );
+ }
+
+ // Create: expect(res.getHeaders()).to.have.property(args)
+ return j.callExpression(
+ j.memberExpression(
+ j.callExpression(
+ j.identifier('expect'),
+ [
+ j.callExpression(
+ j.identifier('res.getHeaders'),
+ []
+ )
+ ]
+ ),
+ j.identifier('to.have.property')
+ ),
+ args
+ );
+
+ }
+ },
+
+ // Handle pm.execution.setNextRequest(null)
+ {
+ pattern: 'pm.execution.setNextRequest',
+ transform: (path, j) => {
+ const callExpr = path.parent.value;
+
+ const args = callExpr.arguments;
+
+ // If argument is null or 'null', transform to bru.runner.stopExecution()
+ if (
+ args[0].type === 'Literal' && (args[0].value === null || args[0].value === 'null')
+ ) {
+ return j.callExpression(
+ j.identifier('bru.runner.stopExecution'),
+ []
+ );
+ }
+
+ // Otherwise, keep as bru.runner.setNextRequest with the same argument
+ return j.callExpression(
+ j.identifier('bru.runner.setNextRequest'),
+ args
+ );
+ }
+ },
+];
+
+// Create a map for complex transformations to enable O(1) lookups
+const complexTransformationsMap = {};
+complexTransformations.forEach(transform => {
+ complexTransformationsMap[transform.pattern] = transform;
+});
+
+const varInitsToReplace = new Set(['pm', 'postman', 'pm.request','pm.response', 'pm.test', 'pm.expect', 'pm.environment', 'pm.variables', 'pm.collectionVariables', 'pm.execution']);
+
+/**
+ * Process all transformations (both simple and complex) in the AST in a single pass
+ * @param {Object} ast - jscodeshift AST
+ * @param {Set} transformedNodes - Set of already transformed nodes
+ */
+function processTransformations(ast, transformedNodes) {
+ ast.find(j.MemberExpression).forEach(path => {
+ if (transformedNodes.has(path.node)) return;
+
+ // Get string representation using our utility function
+ const memberExprStr = getMemberExpressionString(path.value);
+
+ // First check for simple transformations (O(1))
+ if (simpleTranslations.hasOwnProperty(memberExprStr)) {
+ const replacement = simpleTranslations[memberExprStr];
+ j(path).replaceWith(j.identifier(replacement));
+ transformedNodes.add(path.node);
+ return; // Skip complex transformation check if simple transformation applied
+ }
+
+ // 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);
+ }
+ transformedNodes.add(nodePath.node);
+ transformedNodes.add(path.parent.node);
+ });
+ } else {
+ j(path.parent).replaceWith(replacement);
+ transformedNodes.add(path.node);
+ transformedNodes.add(path.parent.node);
+ }
+ }
+ });
+}
+
+/**
+ * Translates Postman script code to Bruno script code
+ * @param {string} code - The Postman script code to translate
+ * @returns {string} The translated Bruno script code
+ */
+function translateCode(code) {
+ // Replace 'postman' with 'pm' using regex before creating the AST
+ // This is more efficient than an AST traversal
+ code = code.replace(/\bpostman\b/g, 'pm');
+
+ const ast = j(code);
+
+ // Keep track of transformed nodes to avoid double-processing
+ const transformedNodes = new Set();
+
+ // Preprocess the code to resolve all aliases
+ preprocessAliases(ast);
+
+ // Process all transformations in a single pass
+ processTransformations(ast, transformedNodes);
+
+ // Handle special Postman syntax patterns
+ handleTestsBracketNotation(ast);
+
+ return ast.toSource();
+}
+
+/**
+ * Preprocess all variable aliases in the AST to simplify later transformations
+ * @param {Object} ast - jscodeshift AST
+ */
+function preprocessAliases(ast) {
+ // Create a symbol table to track what each variable references
+ const symbolTable = new Map();
+ const MAX_ITERATIONS = 5;
+ let iterations = 0;
+
+ // Keep preprocessing until no more changes can be made
+ let changesMade;
+ do {
+ changesMade = false;
+
+ // First pass: Identify all variables
+ findVariableDefinitions(ast, symbolTable);
+
+ // Second pass: Replace all variable references with their resolved values
+ changesMade = resolveVariableReferences(ast, symbolTable) || false;
+
+ // Third pass: Clean up variable declarations that are no longer needed
+ changesMade = removeResolvedDeclarations(ast, symbolTable) || false;
+
+ iterations++;
+
+ } while (changesMade && iterations < MAX_ITERATIONS);
+}
+
+/**
+ * Find all variable definitions and track what they reference
+ * @param {Object} ast - jscodeshift AST
+ * @param {Map} symbolTable - Map to track variable references
+ */
+function findVariableDefinitions(ast, symbolTable) {
+ // Use a single traversal to handle both direct assignments and object destructuring
+ ast.find(j.VariableDeclarator).forEach(path => {
+ // Only process nodes that have an initializer
+ if (!path.value.init) return;
+
+ // Handle direct assignments: const response = pm.response
+ if (path.value.id.type === 'Identifier') {
+ const varName = path.value.id.name;
+
+ // If it's a direct identifier, just map it
+ if (path.value.init.type === 'Identifier') {
+ symbolTable.set(varName, {
+ type: 'identifier',
+ value: path.value.init.name
+ });
+ }
+ // If it's a member expression, store both parts
+ else if (path.value.init.type === 'MemberExpression') {
+ const sourceCode = getMemberExpressionString(path.value.init);
+ symbolTable.set(varName, {
+ type: 'memberExpression',
+ value: sourceCode,
+ node: path.value.init
+ });
+ }
+ }
+ // Handle object destructuring: const { response } = pm
+ else if (path.value.id.type === 'ObjectPattern' && path.value.init.type === 'Identifier') {
+ const source = path.value.init.name;
+
+ path.value.id.properties.forEach(prop => {
+ if (prop.key.name && prop.value.type === 'Identifier') {
+ const destVarName = prop.value.name;
+ symbolTable.set(destVarName, {
+ type: 'memberExpression',
+ value: `${source}.${prop.key.name}`,
+ node: j.memberExpression(
+ j.identifier(source),
+ j.identifier(prop.key.name)
+ )
+ });
+ }
+ });
+ }
+ });
+}
+
+/**
+ * Resolve variable references by replacing them with their original values
+ * @param {Object} ast - jscodeshift AST
+ * @param {Map} symbolTable - Map of variable references
+ * @returns {boolean} Whether any changes were made
+ */
+function resolveVariableReferences(ast, symbolTable) {
+ let changesMade = false;
+
+ /**
+ * Example of what this function does:
+ *
+ * Input Postman code:
+ * const response = pm.response;
+ * const jsonData = response.json(); // response is a reference to pm.response
+ *
+ * After resolution:
+ * const response = pm.response;
+ * const jsonData = pm.response.json(); // response reference is replaced with pm.response
+ *
+ * Then in the next preprocessing phase, unnecessary variables like 'response' will be removed.
+ */
+
+ // Replace all identifier references with their resolved values
+ ast.find(j.Identifier).forEach(path => {
+ const varName = path.value.name;
+
+ /**
+ * Skip specific types of identifiers that shouldn't be replaced:
+ *
+ * Case 1: Variable definitions (left side of declarations)
+ * -----------------------------------------------------
+ * In code like:
+ * const response = pm.response;
+ * ^
+ * We shouldn't replace 'response' on the left side with pm.response,
+ * which would result in: const pm.response = pm.response; (invalid syntax)
+ *
+ * Case 2: Property names in member expressions
+ * -----------------------------------------------------
+ * In code like:
+ * console.log(response.status);
+ * ^
+ * We shouldn't replace the 'status' property name with anything,
+ * only the 'response' object reference should be replaced.
+ *
+ * We only want to replace identifiers that are being used as references,
+ * not the ones being defined or used as property names.
+ */
+
+ // Skip if this is a variable definition or property name
+ if (path.parent.value.type === 'VariableDeclarator' && path.parent.value.id === path.value) {
+ return;
+ }
+ if (path.parent.value.type === 'MemberExpression' && path.parent.value.property === path.value && !path.parent.value.computed) {
+ return;
+ }
+
+ // Only replace if this is a known variable
+ if (!symbolTable.has(varName)) return;
+
+ const symbolInfo = symbolTable.get(varName);
+ if(!varInitsToReplace.has(symbolInfo.value)) {
+ return;
+ }
+ const newNode = cloneDeep(symbolInfo.node);
+ j(path).replaceWith(newNode);
+ symbolTable.set(varName, {
+ type: 'memberExpression',
+ value: symbolInfo.value,
+ node: newNode
+ });
+ changesMade = true;
+
+ });
+
+ return changesMade;
+}
+
+/**
+ * Remove variable declarations that have been resolved
+ * @param {Object} ast - jscodeshift AST
+ * @param {Map} symbolTable - Map of variable references
+ * @returns {boolean} Whether any changes were made
+ */
+function removeResolvedDeclarations(ast, symbolTable) {
+ let changesMade = false;
+
+ /**
+ * Example of what this function does:
+ *
+ * Original Postman code:
+ * const response = pm.response;
+ * const jsonData = response.json();
+ * console.log(jsonData.name);
+ *
+ * After variable resolution:
+ * const response = pm.response; // This declaration is now redundant
+ * const jsonData = pm.response.json(); // This value has been resolved
+ * console.log(jsonData.name); // This still references jsonData
+ *
+ * Final code after this cleanup step:
+ * const jsonData = pm.response.json(); // response variable declaration is removed
+ * console.log(jsonData.name); // jsonData is kept since it's still referenced
+ *
+ * We only remove declarations that:
+ * 1. Have been fully resolved (references to pm.* objects)
+ * 2. No longer provide any value (since all references were replaced with resolved values)
+ */
+
+ // Use a single traversal to handle both regular variable declarations and destructuring
+ ast.find(j.VariableDeclarator).forEach(path => {
+ // Case 1: Handle regular variable declarations
+ if (path.value.id.type === 'Identifier') {
+ const varName = path.value.id.name;
+ const replacement = symbolTable.get(varName);
+ if(!replacement || !varInitsToReplace.has(replacement.value)) return;
+
+ /**
+ * This code differentiates between two types of variable declarations:
+ *
+ * Example 1: Single variable declaration
+ * -----------------------------------
+ * Input: const response = pm.response;
+ * Action: The entire statement can be removed
+ * Output: [statement removed]
+ *
+ * Example 2: Multiple variables in one declaration
+ * -----------------------------------
+ * Input: const response = pm.response, unrelated = 5;
+ * Action: Only remove the 'response' declarator, keep the others
+ * Output: const unrelated = 5;
+ *
+ * We need this distinction to ensure we don't accidentally remove
+ * unrelated variables that happen to be declared in the same statement.
+ */
+ const declarationPath = j(path).closest(j.VariableDeclaration);
+ if (declarationPath.get().value.declarations.length === 1) {
+ declarationPath.remove();
+ } else {
+ // Otherwise just remove this declarator
+ j(path).remove();
+ }
+
+ changesMade = true;
+ }
+ // Case 2: Handle destructuring of pm
+ else if (path.value.id.type === 'ObjectPattern' &&
+ path.value.init &&
+ path.value.init.type === 'Identifier' &&
+ path.value.init.name === 'pm') {
+
+ /**
+ * Example of destructuring removal:
+ *
+ * Original Postman code:
+ * const { response, environment } = pm;
+ * console.log(response.json().name);
+ * console.log(environment.get("variable"));
+ *
+ * After variable resolution steps:
+ * const { response, environment } = pm; // This destructuring is now redundant
+ * console.log(pm.response.json().name); // 'response' references already replaced with pm.response
+ * console.log(pm.environment.get("variable")); // 'environment' references replaced
+ *
+ * Final code after this cleanup step:
+ * console.log(pm.response.json().name); // Destructuring declaration is completely removed
+ * console.log(pm.environment.get("variable"));
+ *
+ * This step specifically targets the Postman pattern of destructuring the pm object,
+ * which is common in Postman scripts but needs to be removed in the Bruno conversion.
+ */
+
+ const declarationPath = j(path).closest(j.VariableDeclaration);
+ if (declarationPath.get().value.declarations.length === 1) {
+ declarationPath.remove();
+ } else {
+ j(path).remove();
+ }
+
+ changesMade = true;
+ }
+ });
+
+ return changesMade;
+}
+
+/**
+ * Handle Postman's tests["..."] = ... syntax
+ * @param {Object} ast - jscodeshift AST
+ */
+function handleTestsBracketNotation(ast) {
+ // Find the ExpressionStatement that contains the assignment
+ ast.find(j.ExpressionStatement, {
+ expression: {
+ type: 'AssignmentExpression',
+ left: {
+ type: 'MemberExpression',
+ object: { name: 'tests' },
+ computed: true,
+ property: {} // Accept any property type
+ }
+ }
+ }).forEach(path => {
+ // Get the assignment expression
+ const assignment = path.value.expression;
+ const left = assignment.left;
+
+ // Verify it's a valid tests[] expression
+ if (left.object.type === 'Identifier' &&
+ left.object.name === 'tests' &&
+ left.computed === true) {
+
+ const property = left.property;
+ const rightSide = assignment.right;
+
+ // Handle string literals
+ if (property.type === 'Literal' && typeof property.value === 'string') {
+ const testName = property.value;
+
+ // Replace with test() function call
+ j(path).replaceWith(
+ j.expressionStatement(
+ j.callExpression(
+ j.identifier('test'),
+ [
+ j.literal(testName),
+ j.functionExpression(
+ null,
+ [],
+ j.blockStatement([
+ j.expressionStatement(
+ j.memberExpression(
+ j.callExpression(
+ j.identifier('expect'),
+ [
+ j.callExpression(
+ j.identifier('Boolean'),
+ [rightSide]
+ )
+ ]
+ ),
+ j.identifier('to.be.true')
+ )
+ )
+ ])
+ )
+ ]
+ )
+ )
+ );
+ }
+ // Handle template literals
+ else if (property.type === 'TemplateLiteral') {
+ // Create a template literal with the same quasi and expressions
+ const templateLiteral = j.templateLiteral(
+ property.quasis,
+ property.expressions
+ );
+
+ // Replace with test() function call using template literal
+ j(path).replaceWith(
+ j.expressionStatement(
+ j.callExpression(
+ j.identifier('test'),
+ [
+ templateLiteral,
+ j.functionExpression(
+ null,
+ [],
+ j.blockStatement([
+ j.expressionStatement(
+ j.memberExpression(
+ j.callExpression(
+ j.identifier('expect'),
+ [
+ j.callExpression(
+ j.identifier('Boolean'),
+ [rightSide]
+ )
+ ]
+ ),
+ j.identifier('to.be.true')
+ )
+ )
+ ])
+ )
+ ]
+ )
+ )
+ );
+ }
+ }
+ });
+}
+
+export { getMemberExpressionString };
+export default translateCode;
\ No newline at end of file
diff --git a/packages/bruno-converters/src/workers/postman-translator-worker.js b/packages/bruno-converters/src/workers/postman-translator-worker.js
new file mode 100644
index 000000000..1c9ebea79
--- /dev/null
+++ b/packages/bruno-converters/src/workers/postman-translator-worker.js
@@ -0,0 +1,211 @@
+const { Worker } = require('node:worker_threads');
+const path = require('node:path');
+const os = require('node:os');
+
+function getMaxWorkers() {
+ return Math.max(os.availableParallelism(), 1)
+}
+
+class WorkerPool {
+ constructor(scriptPath, size) {
+ this.workers = [];
+ this.idle = [];
+ this.queue = [];
+ this.scriptPath = scriptPath;
+ this.size = size;
+ }
+
+ // Initialize the worker pool
+ initialize() {
+ for (let i = 0; i < this.size; i++) {
+ const worker = new Worker(this.scriptPath);
+ this.workers.push(worker);
+ this.idle.push(i);
+ }
+ }
+
+ // Run a task on a worker
+ runTask(data) {
+ return new Promise((resolve, reject) => {
+ const task = { data, resolve, reject };
+
+ if (this.idle.length > 0) {
+ this._runTaskOnWorker(this.idle.shift(), task);
+ } else {
+ this.queue.push(task);
+ }
+ });
+ }
+
+ // Run a task on a specific worker
+ _runTaskOnWorker(workerId, task) {
+ const worker = this.workers[workerId];
+
+ const messageHandler = (result) => {
+ // Cleanup listeners
+ worker.removeListener('message', messageHandler);
+ worker.removeListener('error', errorHandler);
+
+ // Mark worker as idle
+ this.idle.push(workerId);
+
+ // Process queue if tasks are waiting
+ if (this.queue.length > 0) {
+ this._runTaskOnWorker(workerId, this.queue.shift());
+ }
+
+ // Resolve the task
+ task.resolve(result);
+ };
+
+ const errorHandler = (err) => {
+ worker.removeListener('message', messageHandler);
+ worker.removeListener('error', errorHandler);
+
+ this.idle.push(workerId);
+
+ if (this.queue.length > 0) {
+ this._runTaskOnWorker(workerId, this.queue.shift());
+ }
+
+ task.reject(err);
+ };
+
+ worker.on('message', messageHandler);
+ worker.on('error', errorHandler);
+ worker.postMessage(task.data);
+ }
+
+ // Terminate all workers
+ terminate() {
+ for (const worker of this.workers) {
+ worker.terminate();
+ }
+ this.workers = [];
+ this.idle = [];
+ }
+}
+
+// Helper function to count lines in a script
+function countScriptLines(script) {
+ if (!script) return 0;
+ return Array.isArray(script) ? script.length : script.split('\n').length;
+}
+
+// Calculate complexity of a script entry
+function calculateScriptComplexity([uid, entry]) {
+ let totalLines = 0;
+ const { events } = entry
+
+ if (events && Array.isArray(events)) {
+ events.forEach(({ script }) => {
+ if (script && script.exec) {
+ totalLines += countScriptLines(script.exec);
+ }
+ });
+ }
+
+ return { uid, entry, complexity: totalLines || 1 }; // Minimum complexity of 1
+}
+
+// Create balanced batches based on script complexity
+function createBalancedBatches(scriptEntries, workerCount) {
+ // Calculate complexity for each script
+ const scriptsWithComplexity = scriptEntries.map(calculateScriptComplexity);
+
+ // Sort scripts by complexity (descending)
+ scriptsWithComplexity.sort((a, b) => b.complexity - a.complexity);
+
+ // Initialize batches
+ const batches = Array.from({ length: workerCount }, () => ({
+ entries: [],
+ totalComplexity: 0
+ }));
+
+ // Algorithm: Greedy load balancing
+ // 1. Process scripts in descending order of complexity
+ // 2. Always assign each script to the batch with lowest current load
+ // 3. This minimizes the maximum workload across all workers
+ for (const { uid, entry, complexity } of scriptsWithComplexity) {
+
+ const batchWithLowestComplexity = batches.reduce(
+ (target, current) => current.totalComplexity < target.totalComplexity ? current : target
+ );
+
+ // Add the script to this batch
+ batchWithLowestComplexity.entries.push({uid, entry});
+ batchWithLowestComplexity.totalComplexity += complexity;
+ }
+
+ return batches.map(batch =>
+ batch.entries.map(({ uid, entry }) => [uid, entry])
+ ).filter(batch => batch.length > 0);
+}
+
+const scriptTranslationWorker = async (scriptMap) => {
+ // Convert the Map to an array of entries
+ const scriptEntries = Array.from(scriptMap.entries());
+ const maxWorkers = getMaxWorkers();
+
+ // For very small collections, don't parallelize
+ if (scriptEntries.length <= 50) {
+ const workerPool = new WorkerPool(path.join(__dirname,'./src/workers/scripts/translate-postman-scripts.js'), 1);
+ workerPool.initialize();
+
+ try {
+ const translatedScripts = new Map();
+ const result = await workerPool.runTask({ scripts: scriptEntries });
+
+ if (result.error) {
+ console.error('Error in script translation worker:', result.error);
+ throw new Error(result.error);
+ }
+
+ result.forEach(([uid, { request }]) => {
+ translatedScripts.set(uid, { request });
+ });
+
+ return translatedScripts;
+ } finally {
+ workerPool.terminate();
+ }
+ }
+
+
+ const workerCount = Math.min(maxWorkers, 4);
+
+ // Create balanced batches based on script complexity
+ const batches = createBalancedBatches(scriptEntries, workerCount);
+
+ const translatedScripts = new Map();
+
+ // Create worker pool with optimal size
+ const workerPool = new WorkerPool(path.join(__dirname,'./src/workers/scripts/translate-postman-scripts.js'), workerCount);
+ workerPool.initialize();
+
+ // Process all batches in parallel using worker pool
+ const batchPromises = batches.map(batch => {
+ return workerPool.runTask({ scripts: batch })
+ .then(modScripts => {
+ modScripts.forEach(([name, { request }]) => {
+ translatedScripts.set(name, { request });
+ });
+ })
+ .catch(err => {
+ console.error('Error in script translation worker:', err);
+ throw new Error(err);
+ });
+ });
+
+ // Wait for all batches to complete
+ try {
+ await Promise.allSettled(batchPromises);
+ } finally {
+ // Clean up worker pool
+ workerPool.terminate();
+ }
+
+ return translatedScripts;
+};
+
+export default scriptTranslationWorker
\ No newline at end of file
diff --git a/packages/bruno-converters/src/workers/scripts/translate-postman-scripts.js b/packages/bruno-converters/src/workers/scripts/translate-postman-scripts.js
new file mode 100644
index 000000000..31b7d9008
--- /dev/null
+++ b/packages/bruno-converters/src/workers/scripts/translate-postman-scripts.js
@@ -0,0 +1,44 @@
+const { parentPort } = require('node:worker_threads');
+const { postmanTranslation } = require('@usebruno/converters');
+
+parentPort.on('message', (workerData) => {
+ try {
+ const { scripts } = workerData;
+ const modScripts = scripts.map(([uid, { events }]) => {
+ const requestObject = {
+ script: {},
+ tests: {}
+ }
+
+ if (events && Array.isArray(events)) {
+ events.forEach((event) => {
+ if(event?.script && event.script.exec) {
+ if(event.listen === 'prerequest') {
+ if(event.script.exec && event.script.exec.length > 0) {
+ requestObject.script.req = postmanTranslation(event.script.exec);
+ } else {
+ requestObject.script.req = '';
+ }
+ }
+
+ if(event.listen === 'test') {
+ if(event.script.exec && event.script.exec.length > 0) {
+ requestObject.tests = postmanTranslation(event.script.exec);
+ } else {
+ requestObject.tests = '';
+ }
+ }
+ }
+ });
+ }
+
+ return [uid, { request: requestObject }];
+ });
+
+ parentPort.postMessage(modScripts);
+ }
+ catch(error) {
+ console.error(error);
+ parentPort.postMessage({ error: error?.message });
+ }
+});
\ No newline at end of file
diff --git a/packages/bruno-converters/tests/postman/postman-to-bruno/collection-auth.spec.js b/packages/bruno-converters/tests/postman/postman-to-bruno/collection-auth.spec.js
index ef98774de..d1a5caa7a 100644
--- a/packages/bruno-converters/tests/postman/postman-to-bruno/collection-auth.spec.js
+++ b/packages/bruno-converters/tests/postman/postman-to-bruno/collection-auth.spec.js
@@ -2,7 +2,7 @@ import { describe, it, expect } from '@jest/globals';
import postmanToBruno from '../../../src/postman/postman-to-bruno';
describe('Collection Authentication', () => {
- it('should handle basic auth at collection level', () => {
+ it('should handle basic auth at collection level', async() => {
const postmanCollection = {
info: {
name: 'Collection level basic auth',
@@ -44,7 +44,7 @@ describe('Collection Authentication', () => {
]
};
- const result = postmanToBruno(postmanCollection);
+ const result = await postmanToBruno(postmanCollection);
// console.log('result', JSON.stringify(result, null, 2));
expect(result.root.request.auth).toEqual({
@@ -61,7 +61,7 @@ describe('Collection Authentication', () => {
});
});
- it('should handle bearer token auth at collection level', () => {
+ it('should handle bearer token auth at collection level', async() => {
const postmanCollection = {
info: {
name: 'Collection level bearer token',
@@ -98,7 +98,7 @@ describe('Collection Authentication', () => {
]
};
- const result = postmanToBruno(postmanCollection);
+ const result = await postmanToBruno(postmanCollection);
// console.log('result', JSON.stringify(result, null, 2));
expect(result.root.request.auth).toEqual({
@@ -112,9 +112,9 @@ describe('Collection Authentication', () => {
oauth2: null,
digest: null
});
- });
+ });
- it('should handle API key auth at collection level', () => {
+ it('should handle API key auth at collection level', async() => {
const postmanCollection = {
info: {
name: 'Collection level api key',
@@ -156,7 +156,7 @@ describe('Collection Authentication', () => {
]
};
- const result = postmanToBruno(postmanCollection);
+ const result = await postmanToBruno(postmanCollection);
expect(result.root.request.auth).toEqual({
mode: 'apikey',
@@ -173,7 +173,7 @@ describe('Collection Authentication', () => {
});
});
- it('should handle digest auth at collection level', () => {
+ it('should handle digest auth at collection level', async() => {
const postmanCollection = {
info: {
name: 'Collection level digest auth',
@@ -220,7 +220,7 @@ describe('Collection Authentication', () => {
]
};
- const result = postmanToBruno(postmanCollection);
+ const result = await postmanToBruno(postmanCollection);
expect(result.root.request.auth).toEqual({
mode: 'digest',
diff --git a/packages/bruno-converters/tests/postman/postman-to-bruno/folder-auth.spec.js b/packages/bruno-converters/tests/postman/postman-to-bruno/folder-auth.spec.js
index b403d22d8..ba6f86596 100644
--- a/packages/bruno-converters/tests/postman/postman-to-bruno/folder-auth.spec.js
+++ b/packages/bruno-converters/tests/postman/postman-to-bruno/folder-auth.spec.js
@@ -2,7 +2,7 @@ import { describe, it, expect } from '@jest/globals';
import postmanToBruno from '../../../src/postman/postman-to-bruno';
describe('Folder Authentication', () => {
- it('should handle basic auth at folder level', () => {
+ it('should handle basic auth at folder level', async() => {
const postmanCollection = {
info: {
name: 'Folder level basic auth',
@@ -49,7 +49,7 @@ describe('Folder Authentication', () => {
]
};
- const result = postmanToBruno(postmanCollection);
+ const result = await postmanToBruno(postmanCollection);
expect(result.items[0].root.request.auth).toEqual({
mode: 'basic',
@@ -65,7 +65,7 @@ describe('Folder Authentication', () => {
});
});
- it('should handle bearer token auth at folder level', () => {
+ it('should handle bearer token auth at folder level', async() => {
const postmanCollection = {
info: {
name: 'Folder level bearer token',
@@ -107,7 +107,7 @@ describe('Folder Authentication', () => {
]
};
- const result = postmanToBruno(postmanCollection);
+ const result = await postmanToBruno(postmanCollection);
expect(result.items[0].root.request.auth).toEqual({
mode: 'bearer',
@@ -120,7 +120,7 @@ describe('Folder Authentication', () => {
});
});
- it('should handle API key auth at folder level', () => {
+ it('should handle API key auth at folder level', async() => {
const postmanCollection = {
info: {
name: 'Folder level API key',
@@ -167,7 +167,7 @@ describe('Folder Authentication', () => {
]
};
- const result = postmanToBruno(postmanCollection);
+ const result = await postmanToBruno(postmanCollection);
expect(result.items[0].root.request.auth).toEqual({
mode: 'apikey',
@@ -180,7 +180,7 @@ describe('Folder Authentication', () => {
});
});
- it('should handle digest auth at folder level', () => {
+ it('should handle digest auth at folder level', async() => {
const postmanCollection = {
info: {
name: 'Folder level digest auth',
@@ -232,7 +232,7 @@ describe('Folder Authentication', () => {
]
};
- const result = postmanToBruno(postmanCollection);
+ const result = await postmanToBruno(postmanCollection);
expect(result.items[0].root.request.auth).toEqual({
mode: 'digest',
diff --git a/packages/bruno-converters/tests/postman/postman-to-bruno/postman-to-bruno.spec.js b/packages/bruno-converters/tests/postman/postman-to-bruno/postman-to-bruno.spec.js
index f8f52538e..3ac79476c 100644
--- a/packages/bruno-converters/tests/postman/postman-to-bruno/postman-to-bruno.spec.js
+++ b/packages/bruno-converters/tests/postman/postman-to-bruno/postman-to-bruno.spec.js
@@ -3,7 +3,7 @@ import postmanToBruno from '../../../src/postman/postman-to-bruno';
describe('postman-collection', () => {
it('should correctly import a valid Postman collection file', async () => {
- const brunoCollection = postmanToBruno(postmanCollection);
+ const brunoCollection = await postmanToBruno(postmanCollection);
expect(brunoCollection).toMatchObject(expectedOutput);
});
});
diff --git a/packages/bruno-converters/tests/postman/postman-to-bruno/postman-translations/postman-response.spec.js b/packages/bruno-converters/tests/postman/postman-to-bruno/postman-translations/postman-response.spec.js
index 4f365589c..a57b8435a 100644
--- a/packages/bruno-converters/tests/postman/postman-to-bruno/postman-translations/postman-response.spec.js
+++ b/packages/bruno-converters/tests/postman/postman-to-bruno/postman-translations/postman-response.spec.js
@@ -20,7 +20,7 @@ describe('postmanTranslations - response commands', () => {
const responseText = JSON.stringify(res.getBody());
const responseJson = res.getBody();
const responseStatus = res.statusText;
- const responseHeaders = req.getHeaders();
+ const responseHeaders = res.getHeaders();
test('Status code is 200', function() {
expect(res.getStatus()).to.equal(200);
diff --git a/packages/bruno-converters/tests/postman/postman-to-bruno/request-auth.spec.js b/packages/bruno-converters/tests/postman/postman-to-bruno/request-auth.spec.js
index a542a6b61..2a71301d3 100644
--- a/packages/bruno-converters/tests/postman/postman-to-bruno/request-auth.spec.js
+++ b/packages/bruno-converters/tests/postman/postman-to-bruno/request-auth.spec.js
@@ -2,7 +2,7 @@ import { describe, it, expect } from '@jest/globals';
import postmanToBruno from '../../../src/postman/postman-to-bruno';
describe('Request Authentication', () => {
- it('should handle basic auth at request level', () => {
+ it('should handle basic auth at request level', async() => {
const postmanCollection = {
info: {
name: 'Request Auth Collection',
@@ -26,7 +26,7 @@ describe('Request Authentication', () => {
]
};
- const result = postmanToBruno(postmanCollection);
+ const result = await postmanToBruno(postmanCollection);
expect(result.items[0].request.auth).toEqual({
mode: 'basic',
@@ -42,7 +42,7 @@ describe('Request Authentication', () => {
});
});
- it('should inherit folder auth when request has no auth', () => {
+ it('should inherit folder auth when request has no auth', async() => {
const postmanCollection = {
info: {
name: 'Inherit Request Auth Collection',
@@ -68,7 +68,7 @@ describe('Request Authentication', () => {
]
};
- const result = postmanToBruno(postmanCollection);
+ const result = await postmanToBruno(postmanCollection);
expect(result.items[0].items[0].request.auth).toEqual({
mode: 'bearer',
@@ -83,7 +83,7 @@ describe('Request Authentication', () => {
});
});
- it('should override folder auth with request auth', () => {
+ it('should override folder auth with request auth', async() => {
const postmanCollection = {
info: {
name: 'Override Request Auth Collection',
@@ -116,7 +116,7 @@ describe('Request Authentication', () => {
]
};
- const result = postmanToBruno(postmanCollection);
+ const result = await postmanToBruno(postmanCollection);
expect(result.items[0].items[0].request.auth).toEqual({
mode: 'bearer',
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 6d98ce6e8..1c1686bf2 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
@@ -17,7 +17,7 @@ describe('postmanTranslations - comment handling', () => {
test('should comment non-translated pm commands', () => {
const inputScript = "pm.test('random test', () => postman.variables.replaceIn('{{$guid}}'));";
- const expectedOutput = "// test('random test', () => postman.variables.replaceIn('{{$guid}}'));";
+ const expectedOutput = "// test('random test', () => pm.variables.replaceIn('{{$guid}}'));";
expect(postmanTranslation(inputScript)).toBe(expectedOutput);
});
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
new file mode 100644
index 000000000..8d3508e05
--- /dev/null
+++ b/packages/bruno-converters/tests/postman/postman-translations/transpiler-tests/combined.test.js
@@ -0,0 +1,418 @@
+import translateCode from '../../../../src/utils/jscode-shift-translator';
+
+describe('Combined API Features Translation', () => {
+ // Basic translation test
+ it('should translate code', () => {
+ const code = 'console.log("Hello, world!");';
+ const translatedCode = translateCode(code);
+ expect(translatedCode).toBe(code);
+ });
+
+ // Preserving comments
+ it('should preserve comments', () => {
+ const code = '// This is a comment';
+ const translatedCode = translateCode(code);
+ expect(translatedCode).toBe('// This is a comment');
+ });
+
+ it('should preserve comments inside functions', () => {
+ const code = `
+ function getUserDetails() {
+ // Get user details from API
+ const response = pm.response.json();
+ }
+ `;
+ const translatedCode = translateCode(code);
+ expect(translatedCode).toBe(`
+ function getUserDetails() {
+ // Get user details from API
+ const response = res.getBody();
+ }
+ `);
+ });
+
+ it('should preserve comments inside if statements', () => {
+ const code = `
+ if (pm.response.code === 200) {
+ // Success
+ console.log("Success");
+ }
+ `;
+ const translatedCode = translateCode(code);
+ expect(translatedCode).toBe(`
+ if (res.getStatus() === 200) {
+ // Success
+ console.log("Success");
+ }
+ `);
+ });
+
+ it('should preserve multiline comments', () => {
+ const code = `
+ /*
+ This is a multiline comment
+ */
+ `;
+ const translatedCode = translateCode(code);
+ expect(translatedCode).toBe(`
+ /*
+ This is a multiline comment
+ */
+ `);
+ });
+
+ it('should preserve comments inside for loops', () => {
+ const code = `
+ for (let i = 0; i < 10; i++) {
+ // Loop iteration
+ console.log(pm.response.json()[i]);
+ }
+ `;
+ const translatedCode = translateCode(code);
+ expect(translatedCode).toBe(`
+ for (let i = 0; i < 10; i++) {
+ // Loop iteration
+ console.log(res.getBody()[i]);
+ }
+ `);
+ });
+
+ // Multiple transformations in the same code block
+ it('should handle multiple translations in the same code block', () => {
+ const code = `
+ const token = pm.environment.get("authToken");
+ pm.test("Auth flow works", function() {
+ const response = pm.response.json();
+ pm.expect(response.authenticated).to.be.true;
+ pm.environment.set("userId", response.user.id);
+ pm.collectionVariables.set("sessionId", response.session.id);
+ });
+ `;
+ const translatedCode = translateCode(code);
+
+ 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).not.toContain('pm.collectionVariables.set("sessionId", response.session.id);');
+ expect(translatedCode).toContain('const token = bru.getEnvVar("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.setVar("sessionId", response.session.id);');
+ });
+
+ // Nested expressions
+ 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");');
+ });
+
+ it('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.setVar("fullPath", bru.getEnvVar("baseUrl") + bru.getVar("endpoint"));');
+ });
+
+ // Unrelated code
+ it('should leave unrelated code untouched', () => {
+ const code = `
+ function calculateTotal(items) {
+ return items.reduce((sum, item) => sum + item.price, 0);
+ }
+ `;
+ const translatedCode = translateCode(code);
+ expect(translatedCode).toBe(code);
+ });
+
+ it('should handle Postman API calls within JavaScript methods', () => {
+ const code = `
+ const helpers = {
+ getAuthHeader: function() {
+ return "Bearer " + pm.environment.get("token");
+ }
+ };
+ `;
+ const translatedCode = translateCode(code);
+ expect(translatedCode).toContain('return "Bearer " + bru.getEnvVar("token");');
+ });
+
+
+ it('should handle aliases with object destructuring', () => {
+ const code = `
+ const { environment, variables } = pm;
+ environment.set("token", "abc123");
+ variables.get("userId");
+ `;
+ const translatedCode = translateCode(code);
+
+ expect(translatedCode).toBe(`
+ bru.setEnvVar("token", "abc123");
+ bru.getVar("userId");
+ `);
+ });
+
+ // Code context tests
+ it('should translate pm commands inside functions', () => {
+ const code = `
+ function getAuthHeader() {
+ return "Bearer " + pm.environment.get("token");
+ }
+ `;
+
+ const translatedCode = translateCode(code);
+ expect(translatedCode).toBe(`
+ function getAuthHeader() {
+ return "Bearer " + bru.getEnvVar("token");
+ }
+ `);
+ });
+
+ it('should translate pm commands inside if statements', () => {
+ const code = `
+ if (pm.response.code === 200) {
+ console.log("Success");
+ }
+ `;
+ const translatedCode = translateCode(code);
+ expect(translatedCode).toBe(`
+ if (res.getStatus() === 200) {
+ console.log("Success");
+ }
+ `);
+ });
+
+
+ it('should translate pm commands inside if statements', () => {
+ const code = `
+ const json = pm.response.json();
+ if (json.code === 200) {
+ console.log("Success");
+ }
+ `;
+ const translatedCode = translateCode(code);
+ expect(translatedCode).toBe(`
+ const json = res.getBody();
+ if (json.code === 200) {
+ console.log("Success");
+ }
+ `);
+ });
+
+ it('should translate pm commands inside else statements', () => {
+ const code = `
+ if (pm.response.code === 200) {
+ console.log("Success");
+ pm.response.to.have.status(200);
+ } else {
+ console.log("Failure");
+ expect(res.getStatus()).to.equal(400);
+ }
+ `;
+ const translatedCode = translateCode(code);
+ expect(translatedCode).toBe(`
+ if (res.getStatus() === 200) {
+ console.log("Success");
+ expect(res.getStatus()).to.equal(200);
+ } else {
+ console.log("Failure");
+ expect(res.getStatus()).to.equal(400);
+ }
+ `);
+ });
+
+ it('should translate pm commands inside for loops', () => {
+ const code = `
+ for (let i = 0; i < pm.response.json().length; i++) {
+ console.log(pm.response.json()[i]);
+ }
+ `;
+
+ const translatedCode = translateCode(code);
+ expect(translatedCode).toBe(`
+ for (let i = 0; i < res.getBody().length; i++) {
+ console.log(res.getBody()[i]);
+ }
+ `);
+ });
+
+ it('should translate pm commands inside while loops', () => {
+ const code = `
+ while (pm.response.code === 200) {
+ console.log("Success");
+ }
+ `;
+
+ const translatedCode = translateCode(code);
+ expect(translatedCode).toBe(`
+ while (res.getStatus() === 200) {
+ console.log("Success");
+ }
+ `);
+ });
+
+ it('should translate pm commands inside switch statements', () => {
+ const code = `
+ switch (pm.response.code) {
+ case 200:
+ console.log("Success");
+ break;
+ }
+ `;
+
+ const translatedCode = translateCode(code);
+ expect(translatedCode).toBe(`
+ switch (res.getStatus()) {
+ case 200:
+ console.log("Success");
+ break;
+ }
+ `);
+ });
+
+ it('should translate pm commands inside try catch statements', () => {
+ const code = `
+ try {
+ pm.response.to.have.status(200);
+ } catch (error) {
+ console.log("Failure");
+ expect(res.getStatus()).to.equal(400);
+ }
+ `;
+
+ const translatedCode = translateCode(code);
+ expect(translatedCode).toBe(`
+ try {
+ expect(res.getStatus()).to.equal(200);
+ } catch (error) {
+ console.log("Failure");
+ expect(res.getStatus()).to.equal(400);
+ }
+ `);
+ });
+
+ it('should translate aliases within if statements block', () => {
+ const code = `
+ const env = pm.environment;
+ const vars = pm.variables;
+ const collVars = pm.collectionVariables;
+ const test = pm.test;
+ const expect = pm.expect;
+ const response = pm.response;
+
+ function processResponse() {
+ if(response.code === 200) {
+ console.log("Success");
+ } else if(response.code === 400) {
+ console.log("Failure");
+ expect(response.code).to.equal(400);
+ } else {
+ console.log("Unknown status code");
+ expect(response.code).to.equal(500);
+ }
+ }
+ `;
+
+ const translatedCode = translateCode(code);
+ expect(translatedCode).toBe(`
+ function processResponse() {
+ if(res.getStatus() === 200) {
+ console.log("Success");
+ } else if(res.getStatus() === 400) {
+ console.log("Failure");
+ expect(res.getStatus()).to.equal(400);
+ } else {
+ console.log("Unknown status code");
+ expect(res.getStatus()).to.equal(500);
+ }
+ }
+ `);
+ });
+
+ it('should handle pm aliases inside functions', () => {
+ const code = `
+ const tempRes = pm.response;
+ const tempTest = pm.test;
+ const tempExpect = pm.expect;
+ const tempEnv = pm.environment;
+ const tempVars = pm.variables;
+ const tempCollVars = pm.collectionVariables;
+
+ function processResponse() {
+ tempTest("Status code is 200", function() { expect(tempRes.code).to.equal(200); });
+ tempEnv.set("userId", tempRes.json().userId);
+ tempVars.set("token", tempRes.json().token);
+ tempCollVars.set("sessionId", tempRes.json().sessionId);
+ }
+ `;
+
+ const translatedCode = translateCode(code);
+
+ expect(translatedCode).toBe(`
+ function processResponse() {
+ test("Status code is 200", function() { expect(res.getStatus()).to.equal(200); });
+ bru.setEnvVar("userId", res.getBody().userId);
+ bru.setVar("token", res.getBody().token);
+ bru.setVar("sessionId", res.getBody().sessionId);
+ }
+ `);
+ });
+
+ it('should nested pm commands', () => {
+ const code = `
+ pm.collectionVariables.get(pm.environment.get('key'))
+ pm.test("Status code is 200", function() {
+ pm.response.to.have.status(200);
+ });
+ `;
+ const translatedCode = translateCode(code);
+ expect(translatedCode).toBe(`
+ bru.getVar(bru.getEnvVar('key'))
+ test("Status code is 200", function() {
+ expect(res.getStatus()).to.equal(200);
+ });
+ `);
+ });
+
+ it('should handle pm objects in template literals', () => {
+ const code = `
+ const baseUrl = pm.environment.get("baseUrl");
+ const endpoint = pm.variables.get("endpoint");
+ const url = \`\${baseUrl}/api/\${endpoint}\`;
+ console.log(\`Response status: \${pm.response.code}\`);
+ `;
+ const translatedCode = translateCode(code);
+
+ expect(translatedCode).toContain('const baseUrl = bru.getEnvVar("baseUrl");');
+ expect(translatedCode).toContain('const endpoint = bru.getVar("endpoint");');
+ expect(translatedCode).toContain('const url = `${baseUrl}/api/${endpoint}`;');
+ expect(translatedCode).toContain('console.log(`Response status: ${res.getStatus()}`);');
+ });
+
+ it('should handle pm objects in arrow functions', () => {
+ const code = `
+ const getAuthHeader = () => "Bearer " + pm.environment.get("token");
+ const processItems = items => items.forEach(item => {
+ pm.variables.set(item.key, item.value);
+ });
+ `;
+ const translatedCode = translateCode(code);
+
+ expect(translatedCode).toContain('const getAuthHeader = () => "Bearer " + bru.getEnvVar("token");');
+ expect(translatedCode).toContain('const processItems = items => items.forEach(item => {');
+ expect(translatedCode).toContain('bru.setVar(item.key, item.value);');
+ });
+
+ it('test', () => {
+ const code = `
+ const globals = pm.globals;
+ const key = globals.get("key");
+ `;
+ const translatedCode = translateCode(code);
+ expect(translatedCode).toBe(`
+ const globals = pm.globals;
+ const key = globals.get("key");
+ `);
+ })
+});
\ No newline at end of file
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
new file mode 100644
index 000000000..c3461f6de
--- /dev/null
+++ b/packages/bruno-converters/tests/postman/postman-translations/transpiler-tests/environment.test.js
@@ -0,0 +1,242 @@
+import translateCode from '../../../../src/utils/jscode-shift-translator';
+
+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");');
+ });
+
+ it('should translate pm.environment.set', () => {
+ const code = 'pm.environment.set("test", "value");';
+ const translatedCode = translateCode(code);
+ expect(translatedCode).toBe('bru.setEnvVar("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');
+ });
+
+ it('should translate pm.environment.unset', () => {
+ const code = 'pm.environment.unset("test");';
+ const translatedCode = translateCode(code);
+ expect(translatedCode).toBe('bru.deleteEnvVar("test");');
+ });
+
+ it('should translate pm.environment.name', () => {
+ const code = 'pm.environment.name;';
+ const translatedCode = translateCode(code);
+ expect(translatedCode).toBe('bru.getEnvName();');
+ });
+
+ 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");');
+ });
+
+ 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" }));');
+ });
+
+ 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"));');
+ });
+
+ it('should translate pm.environment.name with different access patterns', () => {
+ const code = `
+ const envName1 = pm.environment.name;
+ const env = pm.environment;
+ const envName2 = env.name;
+ console.log(pm.environment.name);
+ `;
+ const translatedCode = translateCode(code);
+ expect(translatedCode).toBe(`
+ const envName1 = bru.getEnvName();
+ const envName2 = bru.getEnvName();
+ console.log(bru.getEnvName());
+ `);
+ });
+
+ it('should handle environment aliases', () => {
+ const code = `
+ const env = pm.environment;
+ const name = env.name;
+ const has = env.has("test");
+ const set = env.set("test", "value");
+ const get = env.get("test");
+ const unset = env.unset("test");
+ `;
+ 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");
+ `);
+ });
+
+ // Legacy API (postman.) tests related to environment
+ it('should translate postman.setEnvironmentVariable', () => {
+ const code = 'postman.setEnvironmentVariable("apiKey", "abc123");';
+ const translatedCode = translateCode(code);
+ expect(translatedCode).toBe('bru.setEnvVar("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");');
+ });
+
+ it('should translate postman.clearEnvironmentVariable', () => {
+ const code = 'postman.clearEnvironmentVariable("tempToken");';
+ const translatedCode = translateCode(code);
+ expect(translatedCode).toBe('bru.deleteEnvVar("tempToken");');
+ });
+
+ it('should handle all environment variable methods together', () => {
+ const code = `
+ // All environment variable methods
+ const envName = pm.environment.name;
+ const hasToken = pm.environment.has("token");
+ const token = pm.environment.get("token");
+ pm.environment.set("timestamp", new Date().toISOString());
+
+ console.log(\`Environment: \${envName}, Has token: \${hasToken}, Token: \${token}\`);
+ `;
+ 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());');
+ });
+
+ // Additional robust tests for environment variables
+ it('should handle environment variables with computed property names', () => {
+ const code = `
+ const prefix = "api";
+ const suffix = "Key";
+ pm.environment.set(prefix + "_" + suffix, "abc123");
+ 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);');
+ });
+
+ it('should handle environment variables in complex object structures', () => {
+ const code = `
+ const config = {
+ baseUrl: pm.environment.get("apiUrl"),
+ headers: {
+ "Authorization": "Bearer " + pm.environment.get("token"),
+ "X-Api-Key": pm.environment.get("apiKey") || "default-key"
+ },
+ timeout: parseInt(pm.environment.get("timeout") || "5000"),
+ validate: pm.environment.has("validateResponses")
+ };
+ `;
+ 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');
+ });
+
+ it('should handle environment variables in conditionals correctly', () => {
+ const code = `
+ if (pm.environment.has("apiKey")) {
+ if (pm.environment.get("apiKey").length > 0) {
+ console.log("Valid API key exists");
+ } else {
+ console.log("API key is empty");
+ }
+ } else {
+ console.log("No API key defined");
+ }
+ `;
+ 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) {');
+ });
+
+ it('should handle multiple levels of environment variable aliasing', () => {
+ const code = `
+ const env = pm.environment;
+
+ env.set("key", "value");
+ const value = env.get("key");
+ const exists = env.has("key");
+ `;
+ 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;
+ `);
+ });
+
+ it('should handle environment variables with dynamic values', () => {
+ const code = `
+ // Generate a timestamp for this request
+ const timestamp = new Date().toISOString();
+ pm.environment.set("requestTimestamp", timestamp);
+
+ // Generate a unique ID
+ const uniqueId = "req_" + Math.random().toString(36).substring(2, 15);
+ pm.environment.set("requestId", uniqueId);
+
+ // Calculate an expiry time (30 minutes from now)
+ const expiryTime = new Date();
+ expiryTime.setMinutes(expiryTime.getMinutes() + 30);
+ 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());');
+ });
+
+ it('should handle environment variables in try-catch blocks', () => {
+ const code = `
+ try {
+ const configStr = pm.environment.get("config");
+ const config = JSON.parse(configStr);
+ console.log("Config loaded:", config.version);
+ } catch (error) {
+ console.error("Failed to parse config");
+ pm.environment.set("configError", error.message);
+ }
+ `;
+ const translatedCode = translateCode(code);
+ expect(translatedCode).toContain('const configStr = bru.getEnvVar("config");');
+ expect(translatedCode).toContain('bru.setEnvVar("configError", error.message);');
+ });
+
+ it('should handle legacy environment and pm.setEnvironmentVariable together', () => {
+ const code = `
+ // Legacy style
+ postman.setEnvironmentVariable("legacyKey", "legacyValue");
+
+ // Mixed with newer style
+ const value = pm.environment.get("anotherKey");
+
+ // Another legacy form
+ 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");');
+ });
+});
\ No newline at end of file
diff --git a/packages/bruno-converters/tests/postman/postman-translations/transpiler-tests/exec-flow.test.js b/packages/bruno-converters/tests/postman/postman-translations/transpiler-tests/exec-flow.test.js
new file mode 100644
index 000000000..053e99685
--- /dev/null
+++ b/packages/bruno-converters/tests/postman/postman-translations/transpiler-tests/exec-flow.test.js
@@ -0,0 +1,64 @@
+import translateCode from '../../../../src/utils/jscode-shift-translator';
+
+describe('Execution Flow Translation', () => {
+ // Request flow control
+ it('should translate pm.setNextRequest', () => {
+ const code = 'pm.setNextRequest("Get User Details");';
+ const translatedCode = translateCode(code);
+ expect(translatedCode).toBe('bru.setNextRequest("Get User Details");');
+ });
+
+ it('should translate pm.execution.skipRequest', () => {
+ const code = 'if (condition) pm.execution.skipRequest();';
+ const translatedCode = translateCode(code);
+ expect(translatedCode).toBe('if (condition) bru.runner.skipRequest();');
+ });
+
+ it('should translate pm.execution.setNextRequest(null)', () => {
+ const code = 'pm.execution.setNextRequest(null);';
+ const translatedCode = translateCode(code);
+ expect(translatedCode).toBe('bru.runner.stopExecution();');
+ });
+
+ it('should translate pm.execution.setNextRequest("null")', () => {
+ const code = 'pm.execution.setNextRequest("null");';
+ const translatedCode = translateCode(code);
+ expect(translatedCode).toBe('bru.runner.stopExecution();');
+ });
+
+ it('should handle pm.execution.setNextRequest with non-null parameters', () => {
+ const code = `
+ // Continue normal flow
+ pm.execution.setNextRequest("Get user details");
+
+ // With variable
+ const nextReq = "Update profile";
+ pm.execution.setNextRequest(nextReq);
+ `;
+ const translatedCode = translateCode(code);
+
+ expect(translatedCode).toContain('bru.runner.setNextRequest("Get user details");');
+ expect(translatedCode).toContain('bru.runner.setNextRequest(nextReq);');
+ });
+
+ it('should handle all execution control methods together', () => {
+ const code = `
+ // All execution control methods
+ if (pm.response.code === 401) {
+ pm.execution.skipRequest();
+ } else if (pm.response.code === 500) {
+ pm.execution.setNextRequest(null);
+ } else {
+ pm.setNextRequest("Get User Details");
+ }
+ `;
+ const translatedCode = translateCode(code);
+
+ expect(translatedCode).toContain('if (res.getStatus() === 401) {');
+ expect(translatedCode).toContain('bru.runner.skipRequest();');
+ expect(translatedCode).toContain('} else if (res.getStatus() === 500) {');
+ expect(translatedCode).toContain('bru.runner.stopExecution();');
+ expect(translatedCode).toContain('} else {');
+ expect(translatedCode).toContain('bru.setNextRequest("Get User Details");');
+ });
+});
\ No newline at end of file
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
new file mode 100644
index 000000000..e548aa03c
--- /dev/null
+++ b/packages/bruno-converters/tests/postman/postman-translations/transpiler-tests/legacy-tests-syntax.test.js
@@ -0,0 +1,283 @@
+import translateCode from '../../../../src/utils/jscode-shift-translator';
+
+describe('Legacy Tests[] Syntax Translation', () => {
+ it('should handle tests[] commands', () => {
+ const code = `
+ tests["Status code is 200"] = pm.response.code === 200;`;
+ const translatedCode = translateCode(code);
+ expect(translatedCode).toBe(`
+ test("Status code is 200", function() {
+ expect(Boolean(res.getStatus() === 200)).to.be.true;
+ });`);
+ });
+
+ it('should handle tests[] with complex expressions', () => {
+ const code = `
+ tests["Response has valid data"] = pm.response.json().data && pm.response.json().data.length > 0;`;
+ const translatedCode = translateCode(code);
+ expect(translatedCode).toBe(`
+ test("Response has valid data", function() {
+ expect(Boolean(res.getBody().data && res.getBody().data.length > 0)).to.be.true;
+ });`);
+ });
+
+ it('should handle tests[] with string equality', () => {
+ const code = `
+ tests["Content-Type is application/json"] = pm.response.headers.get("Content-Type") === "application/json";`;
+ const translatedCode = translateCode(code);
+ expect(translatedCode).toBe(`
+ test("Content-Type is application/json", function() {
+ expect(Boolean(res.getHeaders().get("Content-Type") === "application/json")).to.be.true;
+ });`);
+ });
+
+ it('should handle tests[] with function calls', () => {
+ const code = `
+ tests["Response time is acceptable"] = pm.response.responseTime < 500;`;
+ const translatedCode = translateCode(code);
+ expect(translatedCode).toBe(`
+ test("Response time is acceptable", function() {
+ expect(Boolean(res.getResponseTime() < 500)).to.be.true;
+ });`);
+ });
+
+ it('should handle tests[] with variable references', () => {
+ const code = `
+ const expectedStatus = 201;
+ tests["Status code is correct"] = pm.response.code === expectedStatus;`;
+ const translatedCode = translateCode(code);
+ expect(translatedCode).toBe(`
+ const expectedStatus = 201;
+ test("Status code is correct", function() {
+ expect(Boolean(res.getStatus() === expectedStatus)).to.be.true;
+ });`);
+ });
+
+ it('should handle multiple tests[] statements', () => {
+ const code = `
+ tests["Status code is 200"] = pm.response.code === 200;
+ tests["Response has data"] = pm.response.json().hasOwnProperty("data");`;
+ const translatedCode = translateCode(code);
+ expect(translatedCode).toBe(`
+ test("Status code is 200", function() {
+ expect(Boolean(res.getStatus() === 200)).to.be.true;
+ });
+ test("Response has data", function() {
+ expect(Boolean(res.getBody().hasOwnProperty("data"))).to.be.true;
+ });`);
+ });
+
+ it('should handle tests[] with special characters in name', () => {
+ const code = `
+ tests["Special characters: !@#$%^&*()"] = true;`;
+ const translatedCode = translateCode(code);
+ expect(translatedCode).toBe(`
+ test("Special characters: !@#$%^&*()", function() {
+ expect(Boolean(true)).to.be.true;
+ });`);
+ });
+
+ it('should handle tests[] with pm.environment variables', () => {
+ const code = `
+ tests["Response matches environment variable"] = pm.response.json().id === pm.environment.get("expectedId");`;
+ const translatedCode = translateCode(code);
+ expect(translatedCode).toBe(`
+ test("Response matches environment variable", function() {
+ expect(Boolean(res.getBody().id === bru.getEnvVar("expectedId"))).to.be.true;
+ });`);
+ });
+
+ it('should handle nested pm objects in tests[] assignments', () => {
+ const code = `
+ tests["Authentication header is present"] = pm.request.headers.has("Authorization");
+ tests["Data count is correct"] = pm.response.json().items.length === pm.variables.get("expectedCount");
+ `;
+ const translatedCode = translateCode(code);
+
+ // The exact translation might vary depending on implementation details,
+ // 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")');
+ });
+
+ // Additional robust tests for legacy tests[] syntax
+ it('should handle tests[] with complex boolean expressions', () => {
+ const code = `
+ tests["Complex validation"] = (pm.response.code >= 200 && pm.response.code < 300) ||
+ (pm.response.json().success === true && pm.response.json().data !== null);`;
+ const translatedCode = translateCode(code);
+
+ expect(translatedCode).toContain('test("Complex validation", function() {');
+ expect(translatedCode).toContain('expect(Boolean((res.getStatus() >= 200 && res.getStatus() < 300) ||');
+ expect(translatedCode).toContain('(res.getBody().success === true && res.getBody().data !== null))).to.be.true;');
+ });
+
+ it('should handle tests[] with array methods', () => {
+ const code = `
+ tests["All items have an ID"] = pm.response.json().items.every(item => item.hasOwnProperty('id'));
+ tests["Has premium item"] = pm.response.json().items.some(item => item.type === 'premium');`;
+ const translatedCode = translateCode(code);
+
+ expect(translatedCode).toContain('test("All items have an ID", function() {');
+ expect(translatedCode).toContain('expect(Boolean(res.getBody().items.every(item => item.hasOwnProperty(\'id\')))).to.be.true;');
+ expect(translatedCode).toContain('test("Has premium item", function() {');
+ expect(translatedCode).toContain('expect(Boolean(res.getBody().items.some(item => item.type === \'premium\'))).to.be.true;');
+ });
+
+ it('should handle tests[] with template literals in the name', () => {
+ const code = `
+ const endpoint = "users";
+ tests[\`Endpoint \${endpoint} returns valid response\`] = pm.response.code === 200;`;
+ const translatedCode = translateCode(code);
+
+ expect(translatedCode).toContain('const endpoint = "users";');
+ expect(translatedCode).toContain('test(`Endpoint ${endpoint} returns valid response`, function() {');
+ expect(translatedCode).toContain('expect(Boolean(res.getStatus() === 200)).to.be.true;');
+ });
+
+ it('should handle tests[] with deep property access', () => {
+ const code = `
+ tests["User has admin role"] = pm.response.json().user &&
+ pm.response.json().user.roles &&
+ pm.response.json().user.roles.includes('admin');`;
+ const translatedCode = translateCode(code);
+
+ expect(translatedCode).toContain('test("User has admin role", function() {');
+ expect(translatedCode).toContain('expect(Boolean(res.getBody().user &&');
+ expect(translatedCode).toContain('res.getBody().user.roles &&');
+ expect(translatedCode).toContain('res.getBody().user.roles.includes(\'admin\'))).to.be.true;');
+ });
+
+ it('should handle tests[] with JSON schema validation patterns', () => {
+ const code = `
+ const schema = {
+ type: "object",
+ required: ["id", "name"],
+ properties: {
+ id: { type: "string" },
+ name: { type: "string" }
+ }
+ };
+
+ const data = pm.response.json();
+
+ // Basic schema validation patterns
+ tests["Has required fields"] = data.hasOwnProperty('id') && data.hasOwnProperty('name');
+ tests["ID is string"] = typeof data.id === 'string';
+ tests["Name is string"] = typeof data.name === 'string';`;
+ const translatedCode = translateCode(code);
+
+ expect(translatedCode).toContain('const schema = {');
+ expect(translatedCode).toContain('type: "object",');
+ expect(translatedCode).toContain('required: ["id", "name"],');
+ expect(translatedCode).toContain('const data = res.getBody();');
+ expect(translatedCode).toContain('test("Has required fields", function() {');
+ expect(translatedCode).toContain('expect(Boolean(data.hasOwnProperty(\'id\') && data.hasOwnProperty(\'name\'))).to.be.true;');
+ expect(translatedCode).toContain('test("ID is string", function() {');
+ expect(translatedCode).toContain('expect(Boolean(typeof data.id === \'string\')).to.be.true;');
+ });
+
+ it('should handle tests[] within conditional blocks', () => {
+ const code = `
+ const data = pm.response.json();
+
+ if (pm.response.code === 200) {
+ tests["Success response has data"] = data.hasOwnProperty('items');
+
+ if (data.items.length > 0) {
+ tests["First item has ID"] = data.items[0].hasOwnProperty('id');
+ }
+ } else {
+ tests["Error response has message"] = data.hasOwnProperty('message');
+ }`;
+ const translatedCode = translateCode(code);
+
+ expect(translatedCode).toContain('const data = res.getBody();');
+ expect(translatedCode).toContain('if (res.getStatus() === 200) {');
+ expect(translatedCode).toContain('test("Success response has data", function() {');
+ expect(translatedCode).toContain('expect(Boolean(data.hasOwnProperty(\'items\'))).to.be.true;');
+ expect(translatedCode).toContain('if (data.items.length > 0) {');
+ expect(translatedCode).toContain('test("First item has ID", function() {');
+ expect(translatedCode).toContain('expect(Boolean(data.items[0].hasOwnProperty(\'id\'))).to.be.true;');
+ expect(translatedCode).toContain('} else {');
+ expect(translatedCode).toContain('test("Error response has message", function() {');
+ expect(translatedCode).toContain('expect(Boolean(data.hasOwnProperty(\'message\'))).to.be.true;');
+ });
+
+ it('should handle tests[] with combination of legacy and modern styles', () => {
+ const code = `
+ // Legacy style
+ tests["Status code is 200"] = pm.response.code === 200;
+
+ // Modern style
+ pm.test("Response has valid data", function() {
+ const json = pm.response.json();
+ pm.expect(json).to.be.an('object');
+ pm.expect(json.items).to.be.an('array');
+
+ // Mix by using tests[] inside pm.test
+ tests["All items have price"] = json.items.every(item => item.hasOwnProperty('price'));
+ });`;
+ const translatedCode = translateCode(code);
+
+ expect(translatedCode).toContain('test("Status code is 200", function() {');
+ expect(translatedCode).toContain('expect(Boolean(res.getStatus() === 200)).to.be.true;');
+ expect(translatedCode).toContain('test("Response has valid data", function() {');
+ expect(translatedCode).toContain('const json = res.getBody();');
+ expect(translatedCode).toContain('expect(json).to.be.an(\'object\');');
+ expect(translatedCode).toContain('expect(json.items).to.be.an(\'array\');');
+ expect(translatedCode).toContain('test("All items have price", function() {');
+ expect(translatedCode).toContain('expect(Boolean(json.items.every(item => item.hasOwnProperty(\'price\')))).to.be.true;');
+ });
+
+ it('should handle complex real-world tests[] example', () => {
+ const code = `
+ // Parse response
+ const response = pm.response.json();
+
+ // Basic response validation
+ tests["Status code is 200"] = pm.response.code === 200;
+ tests["Response is valid JSON"] = response !== null && typeof response === 'object';
+
+ // Check headers
+ tests["Has content-type header"] = pm.response.headers.has("Content-Type");
+ tests["Content-Type is JSON"] = pm.response.headers.get("Content-Type").includes("application/json");
+
+ // Validate against expected values
+ const expectedItems = parseInt(pm.environment.get("expectedItemCount"));
+ tests["Has correct number of items"] = response.items.length === expectedItems;
+
+ // Check for required fields on all items
+ const requiredFields = ["id", "name", "price", "category"];
+ tests["All items have required fields"] = response.items.every(item => {
+ return requiredFields.every(field => item.hasOwnProperty(field));
+ });
+
+ // Validate specific business rules
+ tests["No items with zero price"] = response.items.every(item => parseFloat(item.price) > 0);
+ tests["Has at least one featured item"] = response.items.some(item => item.featured === true);
+
+ // If we find a specific item we're looking for, save its ID for later
+ const targetItem = response.items.find(item => item.name === pm.variables.get("targetItemName"));
+ if (targetItem) {
+ pm.environment.set("targetItemId", targetItem.id);
+ tests["Found target item"] = true;
+ }`;
+ const translatedCode = translateCode(code);
+
+ // Check key transformations
+ expect(translatedCode).toContain('const response = res.getBody();');
+ expect(translatedCode).toContain('test("Status code is 200", function() {');
+ expect(translatedCode).toContain('expect(Boolean(res.getStatus() === 200)).to.be.true;');
+ expect(translatedCode).toContain('test("Has content-type header", function() {');
+ 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.getHeaders().get("Content-Type").includes("application/json"))).to.be.true;');
+ expect(translatedCode).toContain('const expectedItems = parseInt(bru.getEnvVar("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);');
+ });
+});
\ No newline at end of file
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
new file mode 100644
index 000000000..a9be82130
--- /dev/null
+++ b/packages/bruno-converters/tests/postman/postman-translations/transpiler-tests/multiline-syntax.test.js
@@ -0,0 +1,283 @@
+import translateCode from '../../../../src/utils/jscode-shift-translator';
+
+describe('Multiline Syntax Handling', () => {
+ it('should handle basic multiline variable syntax with indentation', () => {
+ const code = `
+ const userId = pm.variables
+ .get("userId");
+ pm.variables
+ .set("timestamp", new Date().toISOString());
+ const hasToken = pm.variables
+ .has("token");
+ `;
+ const translatedCode = translateCode(code);
+ expect(translatedCode).toBe(`
+ const userId = bru.getVar("userId");
+ bru.setVar("timestamp", new Date().toISOString());
+ const hasToken = bru.hasVar("token");
+ `);
+ });
+
+ it('should handle multiline environment variable syntax', () => {
+ const code = `
+ const baseUrl = pm
+ .environment
+ .get("baseUrl");
+ pm
+ .environment
+ .set("requestTime", Date.now());
+ `;
+ const translatedCode = translateCode(code);
+ expect(translatedCode).toBe(`
+ const baseUrl = bru.getEnvVar("baseUrl");
+ bru.setEnvVar("requestTime", Date.now());
+ `);
+ });
+
+ it('should handle multiline collection variable syntax', () => {
+ const code = `
+ const apiKey = pm.collectionVariables
+ .get("apiKey");
+ pm.collectionVariables
+ .set("lastRun", new Date().toISOString());
+ `;
+ const translatedCode = translateCode(code);
+ expect(translatedCode).toBe(`
+ const apiKey = bru.getVar("apiKey");
+ bru.setVar("lastRun", new Date().toISOString());
+ `);
+ });
+
+ it('should handle complex environment.has transformation with multiline syntax', () => {
+ const code = `
+ if (pm.environment
+ .has("apiKey")) {
+ console.log("API Key exists");
+ }
+ `;
+ const translatedCode = translateCode(code);
+ expect(translatedCode).toBe(`
+ if (bru.getEnvVar("apiKey") !== undefined && bru.getEnvVar("apiKey") !== null) {
+ console.log("API Key exists");
+ }
+ `);
+ });
+
+ it('should handle response.to.have.status with multiline formatting', () => {
+ const code = `
+ pm.test("Status code is correct", function() {
+ pm
+ .response
+ .to
+ .have
+ .status(200);
+ });
+ `;
+ const translatedCode = translateCode(code);
+ expect(translatedCode).toContain('expect(res.getStatus()).to.equal(200)');
+ });
+
+ it('should handle response.to.have.header with multiline formatting', () => {
+ const code = `
+ pm.test("Content type is present", function() {
+ pm
+ .response
+ .to
+ .have
+ .header("content-type");
+ });
+ `;
+ const translatedCode = translateCode(code);
+ expect(translatedCode).toContain('expect(res.getHeaders()).to.have.property("content-type".toLowerCase())');
+ });
+
+ it('should handle response properties with multiline syntax', () => {
+ const code = `
+ const responseBody = pm
+ .response
+ .json();
+ const responseText = pm
+ .response
+ .text;
+ const responseTime = pm
+ .response
+ .responseTime;
+ `;
+ const translatedCode = translateCode(code);
+ expect(translatedCode).toContain('const responseBody = res.getBody()');
+ expect(translatedCode).toContain('const responseText = ');
+ expect(translatedCode).toContain('const responseTime = res.getResponseTime()');
+ });
+
+ it('should handle execution flow control with multiline syntax', () => {
+ const code = `
+ // Stop execution
+ pm
+ .execution
+ .setNextRequest(null);
+
+ // Continue to next request
+ pm
+ .execution
+ .setNextRequest("Next API Call");
+ `;
+ const translatedCode = translateCode(code);
+ expect(translatedCode).toContain('// Stop execution');
+ expect(translatedCode).toContain('// Continue to next request');
+ expect(translatedCode).toContain('bru.runner.stopExecution()');
+ expect(translatedCode).toContain('bru.runner.setNextRequest("Next API Call")');
+ });
+
+ it('should handle mixed normal and multiline syntax in the same code', () => {
+ const code = `
+ // Normal syntax
+ const normalVar = pm.variables.get("normal");
+
+ // Multiline syntax
+ const multilineVar = pm.variables
+ .get("multiline");
+
+ // Normal syntax again
+ pm.variables.set("normalSet", "value");
+
+ // Multiline syntax again
+ pm.variables
+ .set("multilineSet", "value");
+ `;
+ const translatedCode = translateCode(code);
+ expect(translatedCode).toBe(`
+ // Normal syntax
+ const normalVar = bru.getVar("normal");
+
+ // Multiline syntax
+ const multilineVar = bru.getVar("multiline");
+
+ // Normal syntax again
+ bru.setVar("normalSet", "value");
+
+ // Multiline syntax again
+ bru.setVar("multilineSet", "value");
+ `);
+ });
+
+ it('should handle complex multiline method chaining', () => {
+ const code = `
+ pm
+ .test("Test with chaining", function() {
+ pm
+ .response
+ .to
+ .have
+ .status(200);
+
+ const body = pm
+ .response
+ .json();
+
+ pm
+ .expect(body)
+ .to
+ .have
+ .property('success')
+ .equal(true);
+ });
+ `;
+ const translatedCode = translateCode(code);
+ expect(translatedCode).toContain('test("Test with chaining", function() {');
+ expect(translatedCode).toContain('expect(res.getStatus()).to.equal(200)');
+ expect(translatedCode).toContain('const body = res.getBody()');
+ expect(translatedCode).toContain('.property(\'success\')');
+ expect(translatedCode).toContain('.equal(true)');
+ });
+
+ it('should handle a comprehensive script with various multiline formats', () => {
+ const code = `
+ // This comprehensive script tests different multiline styles and whitespace variations
+
+ // Environment variables with different formatting styles
+ const baseUrl = pm.environment.get("baseUrl");
+ const apiKey = pm
+ .environment
+ .get("apiKey");
+ const userId = pm.environment
+ .get("userId");
+
+ // Mix of variable styles
+ pm.variables.set("testId", "test-" + Date.now());
+ pm
+ .variables
+ .set("timestamp", new Date().toISOString());
+
+ // Collection variables with inconsistent spacing
+ pm.collectionVariables
+ .set("lastRun", new Date());
+
+ // Complex conditionals with multiline expressions
+ if (pm
+ .environment
+ .has("apiKey") &&
+ pm.variables.has("testId")) {
+
+ // Testing response with mixed syntax styles
+ pm.test("Response validation", function() {
+ // Normal style
+ pm.response.to.have.status(200);
+
+ // Multiline with different indentation
+ pm
+ .response
+ .to
+ .have
+ .header("content-type");
+
+ pm.response
+ .to.have
+ .jsonBody("success", true);
+
+ // Extreme indentation
+ pm
+ .response
+ .to
+ .not
+ .have
+ .jsonBody("error");
+ });
+
+ // Flow control with mixed styles
+ if (pm.response.code === 401) {
+ pm.execution.setNextRequest(null);
+ } else {
+ pm
+ .execution
+ .setNextRequest("Next API Call");
+ }
+ }
+ `;
+
+ 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")');
+
+ // Check variables translations
+ expect(translatedCode).toContain('bru.setVar("testId", "test-" + Date.now())');
+ expect(translatedCode).toContain('bru.setVar("timestamp", new Date().toISOString())');
+
+ // Check collection variables
+ expect(translatedCode).toContain('bru.setVar("lastRun", new Date())');
+
+ // Check complex conditionals
+ expect(translatedCode).toContain('if (bru.getEnvVar("apiKey") !== undefined && bru.getEnvVar("apiKey") !== null &&');
+ expect(translatedCode).toContain('bru.hasVar("testId"))');
+
+ // Check response testing
+ expect(translatedCode).toContain('expect(res.getStatus()).to.equal(200)');
+ expect(translatedCode).toContain('expect(res.getHeaders()).to.have.property("content-type".toLowerCase())');
+
+ // Check flow control
+ expect(translatedCode).toContain('if (res.getStatus() === 401)');
+ expect(translatedCode).toContain('bru.runner.stopExecution()');
+ expect(translatedCode).toContain('bru.runner.setNextRequest("Next API Call")');
+ });
+});
\ No newline at end of file
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
new file mode 100644
index 000000000..20e7890a7
--- /dev/null
+++ b/packages/bruno-converters/tests/postman/postman-translations/transpiler-tests/postman-references.test.js
@@ -0,0 +1,132 @@
+import translateCode from '../../../../src/utils/jscode-shift-translator';
+
+describe('Postman to PM References Conversion', () => {
+ // Basic conversions
+ 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");');
+ // The key part is that it should convert postman.* to pm.* internally before
+ // translating to bru.* APIs
+ });
+
+ 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");');
+ });
+
+ it('should handle postman variable assignments', () => {
+ const code = `
+ const envVar = postman.environment.get("apiKey");
+ 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");');
+ });
+
+ // More complex patterns
+ it('should handle mixed postman and pm references in the same code', () => {
+ const code = `
+ // Using both postman and pm APIs
+ const apiKey = postman.environment.get("apiKey");
+ const baseUrl = pm.environment.get("baseUrl");
+
+ // Using both formats in a test
+ postman.test("Status code is 200", function() {
+ pm.expect(pm.response.code).to.equal(200);
+ });
+ `;
+
+ const translatedCode = translateCode(code);
+ expect(translatedCode).toContain('const apiKey = bru.getEnvVar("apiKey");');
+ expect(translatedCode).toContain('const baseUrl = bru.getEnvVar("baseUrl");');
+ expect(translatedCode).toContain('test("Status code is 200", function() {');
+ expect(translatedCode).toContain('expect(res.getStatus()).to.equal(200);');
+ });
+
+ it('should handle postman references in object destructuring', () => {
+ const code = `
+ const { environment } = postman;
+ environment.set("key", "value");
+ `;
+
+ const translatedCode = translateCode(code);
+ expect(translatedCode).toContain('bru.setEnvVar("key", "value");');
+ });
+
+ // Complex control flows
+ it('should handle postman references in control flow statements', () => {
+ const code = `
+ if (postman.environment.get("isProduction") === "true") {
+ const apiUrl = postman.environment.get("prodUrl");
+ postman.setNextRequest("Production Flow");
+ } else {
+ const apiUrl = postman.environment.get("devUrl");
+ postman.setNextRequest("Development Flow");
+ }
+ `;
+
+ const translatedCode = translateCode(code);
+ expect(translatedCode).toContain('if (bru.getEnvVar("isProduction") === "true") {');
+ expect(translatedCode).toContain('const apiUrl = bru.getEnvVar("prodUrl");');
+ expect(translatedCode).toContain('bru.setNextRequest("Production Flow");');
+ expect(translatedCode).toContain('const apiUrl = bru.getEnvVar("devUrl");');
+ expect(translatedCode).toContain('bru.setNextRequest("Development Flow");');
+ });
+
+ // Legacy response handling
+ it('should handle legacy postman response methods', () => {
+ const code = `
+ // Using legacy response handling
+ const responseCode = postman.response.code;
+ const responseBody = postman.response.json();
+
+ // Set environment variables with response data
+ postman.setEnvironmentVariable("lastResponseCode", responseCode);
+ `;
+
+ 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);');
+ });
+
+ // Postman in string literals should be untouched
+ it('should not convert postman references in string literals', () => {
+ const code = `
+ console.log("This is a pm script");
+ const message = "We're using pm to test our API";
+ `;
+
+ const translatedCode = translateCode(code);
+ expect(translatedCode).toContain('console.log("This is a pm script");');
+ expect(translatedCode).toContain('const message = "We\'re using pm to test our API";');
+ });
+
+ // Complex example with aliasing
+ it('should handle complex postman reference patterns with aliasing', () => {
+ const code = `
+ // Aliasing the postman object
+ const env = postman.environment;
+ const code = postman.code;
+
+ // Using the alias
+ const apiKey = env.get("apiKey");
+ const userId = env.get("userId");
+
+ // Using alias in tests
+ postman.test("Response is valid", function() {
+ postman.expect(code).to.equal(200);
+ });
+ `;
+
+ 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('test("Response is valid", function() {');
+ expect(translatedCode).toContain('expect(code).to.equal(200);');
+ });
+});
\ No newline at end of file
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
new file mode 100644
index 000000000..a81bc6c72
--- /dev/null
+++ b/packages/bruno-converters/tests/postman/postman-translations/transpiler-tests/request.test.js
@@ -0,0 +1,108 @@
+import translateCode from '../../../../src/utils/jscode-shift-translator';
+
+describe('Request Translation', () => {
+ it('should translate pm.request.url', () => {
+ const code = 'const requestUrl = pm.request.url;';
+ const translatedCode = translateCode(code);
+ expect(translatedCode).toBe('const requestUrl = req.getUrl();');
+ });
+
+ it('should translate pm.request.method', () => {
+ const code = 'const method = pm.request.method;';
+ const translatedCode = translateCode(code);
+ expect(translatedCode).toBe('const method = req.getMethod();');
+ });
+
+ it('should translate pm.request.headers', () => {
+ const code = 'const headers = pm.request.headers;';
+ const translatedCode = translateCode(code);
+ expect(translatedCode).toBe('const headers = req.getHeaders();');
+ });
+
+ it('should translate pm.request.body', () => {
+ const code = 'const body = pm.request.body;';
+ const translatedCode = translateCode(code);
+ expect(translatedCode).toBe('const body = req.getBody();');
+ });
+
+ it('should translate pm.response.statusText', () => {
+ const code = 'const statusText = pm.response.statusText;';
+ const translatedCode = translateCode(code);
+ expect(translatedCode).toBe('const statusText = res.statusText;');
+ });
+
+ it('should translate multiple request methods in one block', () => {
+ const code = `
+ const url = pm.request.url;
+ const method = pm.request.method;
+ const headers = pm.request.headers;
+ const body = pm.request.body;
+ `;
+ const translatedCode = translateCode(code);
+ expect(translatedCode).toBe(`
+ const url = req.getUrl();
+ const method = req.getMethod();
+ const headers = req.getHeaders();
+ const body = req.getBody();
+ `);
+ });
+
+ it('should handle request and response properties together', () => {
+ const code = `
+ // Get request data
+ const url = pm.request.url;
+ const method = pm.request.method;
+
+ // Get response data
+ const statusCode = pm.response.code;
+ const statusText = pm.response.statusText;
+
+ // Verify expectations
+ pm.test("Request was made correctly", function() {
+ pm.expect(method).to.equal("POST");
+ pm.expect(url).to.include("/api/items");
+ });
+ `;
+ const translatedCode = translateCode(code);
+ expect(translatedCode).toContain('const url = req.getUrl();');
+ expect(translatedCode).toContain('const method = req.getMethod();');
+ expect(translatedCode).toContain('const statusCode = res.getStatus();');
+ expect(translatedCode).toContain('const statusText = res.statusText;');
+ expect(translatedCode).toContain('test("Request was made correctly", function() {');
+ expect(translatedCode).toContain('expect(method).to.equal("POST");');
+ expect(translatedCode).toContain('expect(url).to.include("/api/items");');
+ });
+
+ it('should handle request properties in conditional blocks', () => {
+ const code = `
+ if (pm.request.method === "POST") {
+ console.log("This is a POST request to " + pm.request.url);
+ pm.test("Request has correct content-type", function() {
+ pm.expect(pm.request.headers.has("Content-Type")).to.be.true;
+ });
+ }
+ `;
+ const translatedCode = translateCode(code);
+ expect(translatedCode).toContain('if (req.getMethod() === "POST") {');
+ expect(translatedCode).toContain('console.log("This is a POST request to " + req.getUrl());');
+ expect(translatedCode).toContain('test("Request has correct content-type", function() {');
+ // Note: The expectation for headers.has might be transformed differently
+ // depending on how complex transformations are handled
+ });
+
+ it('should handle request data extraction and variable setting', () => {
+ const code = `
+ // Extract request data
+ const requestData = pm.request.body;
+ const contentType = pm.request.headers.get("Content-Type");
+
+ // Save for later use
+ pm.variables.set("lastRequestBody", JSON.stringify(requestData));
+ pm.environment.set("lastContentType", contentType);
+ `;
+ 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);');
+ });
+});
\ No newline at end of file
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
new file mode 100644
index 000000000..7fd4d902b
--- /dev/null
+++ b/packages/bruno-converters/tests/postman/postman-translations/transpiler-tests/response.test.js
@@ -0,0 +1,489 @@
+import translateCode from '../../../../src/utils/jscode-shift-translator';
+
+describe('Response Translation', () => {
+ // Basic response property tests
+ it('should translate pm.response.json', () => {
+ const code = 'const jsonData = pm.response.json();';
+ const translatedCode = translateCode(code);
+ expect(translatedCode).toBe('const jsonData = res.getBody();');
+ });
+
+ it('should translate pm.response.code', () => {
+ const code = 'if (pm.response.code === 200) { console.log("Success"); }';
+ const translatedCode = translateCode(code);
+ expect(translatedCode).toBe('if (res.getStatus() === 200) { console.log("Success"); }');
+ });
+
+ it('should translate pm.response.text', () => {
+ const code = 'const responseText = pm.response.text();';
+ const translatedCode = translateCode(code);
+ expect(translatedCode).toBe('const responseText = JSON.stringify(res.getBody());');
+ });
+
+ it('should translate pm.response.responseTime', () => {
+ const code = 'console.log("Response time:", pm.response.responseTime);';
+ const translatedCode = translateCode(code);
+ expect(translatedCode).toBe('console.log("Response time:", res.getResponseTime());');
+ });
+
+ it('should translate pm.response.statusText', () => {
+ const code = 'console.log("Status text:", pm.response.statusText);';
+ const translatedCode = translateCode(code);
+ expect(translatedCode).toBe('console.log("Status text:", res.statusText);');
+ });
+
+ // Complex response transformations
+ it('should transform pm.response.to.have.status', () => {
+ const code = 'pm.response.to.have.status(201);';
+ const translatedCode = translateCode(code);
+ expect(translatedCode).toBe('expect(res.getStatus()).to.equal(201);');
+ });
+
+ it('should transform pm.response.to.have.header with single argument', () => {
+ const code = 'pm.response.to.have.header("Content-Type");';
+ const translatedCode = translateCode(code);
+ expect(translatedCode).toBe('expect(res.getHeaders()).to.have.property("Content-Type".toLowerCase());');
+ });
+
+ it('should transform multiple pm.response.to.have.header statements', () => {
+ const code = `
+ pm.response.to.have.header("Content-Type", "application/json");
+ pm.response.to.have.header("Cache-Control", "no-cache");
+ `;
+ const translatedCode = translateCode(code);
+
+ // Check for the existence of all four assertions (two pairs)
+ expect(translatedCode).toContain('expect(res.getHeaders()).to.have.property("Content-Type".toLowerCase(), "application/json");');
+ expect(translatedCode).toContain('expect(res.getHeaders()).to.have.property("Cache-Control".toLowerCase(), "no-cache");');
+ });
+
+ it('should transform pm.response.to.have.header inside control structures', () => {
+ const code = `
+ if (pm.response.code === 200) {
+ pm.response.to.have.header("Content-Type", "application/json");
+ }
+ `;
+ const translatedCode = translateCode(code);
+
+ // The assertions should be inside the if block
+ expect(translatedCode).toContain('if (res.getStatus() === 200) {');
+ expect(translatedCode).toContain('expect(res.getHeaders()).to.have.property("Content-Type".toLowerCase(), "application/json");');
+ });
+
+ it('should transform pm.response.to.have.header with variable parameters', () => {
+ const code = `
+ const headerName = "Content-Type";
+ const expectedValue = "application/json";
+ pm.response.to.have.header(headerName, expectedValue);
+ `;
+ const translatedCode = translateCode(code);
+
+ expect(translatedCode).toContain('const headerName = "Content-Type";');
+ expect(translatedCode).toContain('const expectedValue = "application/json";');
+ expect(translatedCode).toContain('expect(res.getHeaders()).to.have.property(headerName.toLowerCase(), expectedValue);');
+ });
+
+ // Response aliases tests
+ it('should handle response aliases', () => {
+ const code = `
+ const response = pm.response;
+ const status = response.status;
+ const body = response.json();
+ `;
+ const translatedCode = translateCode(code);
+ expect(translatedCode).toBe(`
+ const status = res.statusText;
+ const body = res.getBody();
+ `);
+ });
+
+ // Response to.have.status with different formats
+ it('should handle pm.response.to.have.status with different status codes', () => {
+ const code = `
+ // Test different status codes
+ pm.response.to.have.status(200); // OK
+ pm.response.to.have.status(201); // Created
+ pm.response.to.have.status(400); // Bad Request
+ pm.response.to.have.status(404); // Not Found
+ pm.response.to.have.status(500); // Server Error
+
+ // With variables
+ const expectedStatus = 200;
+ pm.response.to.have.status(expectedStatus);
+ `;
+ const translatedCode = translateCode(code);
+
+ expect(translatedCode).toContain('expect(res.getStatus()).to.equal(200);');
+ expect(translatedCode).toContain('expect(res.getStatus()).to.equal(201);');
+ expect(translatedCode).toContain('expect(res.getStatus()).to.equal(400);');
+ expect(translatedCode).toContain('expect(res.getStatus()).to.equal(404);');
+ expect(translatedCode).toContain('expect(res.getStatus()).to.equal(500);');
+ expect(translatedCode).toContain('expect(res.getStatus()).to.equal(expectedStatus);');
+ });
+
+ // Alias for pm.response.to.have.status
+ it('should handle pm.response.to.have.status alias', () => {
+ const code = `
+ const resp = pm.response;
+ resp.to.have.status(200);
+ `;
+ const translatedCode = translateCode(code);
+ expect(translatedCode).toBe(`
+ expect(res.getStatus()).to.equal(200);
+ `);
+ });
+
+ it('should handle pm.response.to.have.header alias', () => {
+ const code = `
+ const resp = pm.response;
+ resp.to.have.header("Content-Type");
+ `;
+ const translatedCode = translateCode(code);
+ expect(translatedCode).toBe(`
+ expect(res.getHeaders()).to.have.property("Content-Type".toLowerCase());
+ `);
+ });
+
+ it('should handle pm.response.to.have.header alias with value check', () => {
+ const code = `
+ const resp = pm.response;
+ resp.to.have.header("Content-Type", "application/json");
+ `;
+ const translatedCode = translateCode(code);
+
+ // Check for both assertions when using an alias
+ expect(translatedCode).toContain('expect(res.getHeaders()).to.have.property("Content-Type".toLowerCase(), "application/json");');
+ });
+
+
+ it('should translate response.status', () => {
+ const code = `
+ const resp = pm.response;
+ const statusCode = resp.status;
+ `;
+ const translatedCode = translateCode(code);
+ expect(translatedCode).toBe(`
+ const statusCode = res.statusText;
+ `);
+ });
+
+ it('should translate response.body', () => {
+ const code = `
+ const resp = pm.response;
+ const responseBody = resp.json();
+ `;
+ const translatedCode = translateCode(code);
+ expect(translatedCode).toBe(`
+ const responseBody = res.getBody();
+ `);
+ });
+
+ it('should translate pm.response.statusText', () => {
+ const code = `
+ const resp = pm.response;
+ const statusText = resp.statusText;
+ `;
+ const translatedCode = translateCode(code);
+ expect(translatedCode).toBe(`
+ const statusText = res.statusText;
+ `);
+ });
+
+ it('should translate multiple response methods in one block', () => {
+ const code = `
+ const resp = pm.response;
+ const statusCode = resp.code;
+ const statusText = resp.statusText;
+ const jsonData = resp.json();
+ const responseText = resp.text();
+ const time = resp.responseTime;
+ resp.to.have.status(200);
+ `;
+ const translatedCode = translateCode(code);
+ expect(translatedCode).toBe(`
+ const statusCode = res.getStatus();
+ const statusText = res.statusText;
+ const jsonData = res.getBody();
+ const responseText = JSON.stringify(res.getBody());
+ const time = res.getResponseTime();
+ expect(res.getStatus()).to.equal(200);
+ `);
+ });
+
+ it('should handle accessing nested properties on response objects', () => {
+ const code = `
+ const resp = pm.response;
+ const data = resp.json();
+ if (data && data.user && data.user.id) {
+ pm.environment.set("userId", data.user.id);
+ }
+ `;
+ const translatedCode = translateCode(code);
+
+ expect(translatedCode).not.toContain('const resp = pm.response;');
+ expect(translatedCode).toContain('const data = res.getBody();');
+ expect(translatedCode).toContain('bru.setEnvVar("userId", data.user.id);');
+ });
+
+ it('should handle all response property methods together', () => {
+ const code = `
+ // All response property methods
+ const statusCode = pm.response.code;
+ const responseBody = pm.response.json();
+ const responseText = pm.response.text();
+ const statusText = pm.response.statusText;
+ const responseTime = pm.response.responseTime;
+
+ pm.test("Response is valid", function() {
+ pm.response.to.have.status(200);
+ pm.expect(responseBody).to.be.an('object');
+ pm.expect(responseTime).to.be.below(1000);
+ pm.expect(statusText).to.equal('OK');
+ });
+ `;
+ const translatedCode = translateCode(code);
+
+ expect(translatedCode).toContain('const statusCode = res.getStatus();');
+ expect(translatedCode).toContain('const responseBody = res.getBody();');
+ expect(translatedCode).toContain('const responseText = JSON.stringify(res.getBody());');
+ expect(translatedCode).toContain('const responseTime = res.getResponseTime();');
+ expect(translatedCode).toContain('const statusText = res.statusText;');
+ expect(translatedCode).toContain('test("Response is valid", function() {');
+ expect(translatedCode).toContain('expect(res.getStatus()).to.equal(200);');
+ expect(translatedCode).toContain('expect(responseBody).to.be.an(\'object\');');
+ expect(translatedCode).toContain('expect(responseTime).to.be.below(1000);');
+ expect(translatedCode).toContain('expect(statusText).to.equal(\'OK\');');
+ });
+
+ it('should handle pm objects with array access on response', () => {
+ const code = `
+ const items = pm.response.json().items;
+ for (let i = 0; i < items.length; i++) {
+ pm.collectionVariables.set("item_" + i, items[i].id);
+ }
+ `;
+ const translatedCode = translateCode(code);
+
+ expect(translatedCode).toContain('const items = res.getBody().items;');
+ expect(translatedCode).toContain('bru.setVar("item_" + i, items[i].id);');
+ });
+
+ it('should handle response JSON with optional chaining and nullish coalescing', () => {
+ const code = `
+ const userId = pm.response.json()?.user?.id ?? "anonymous";
+ const items = pm.response.json()?.data?.items || [];
+ `;
+ const translatedCode = translateCode(code);
+
+ expect(translatedCode).toContain('const userId = res.getBody()?.user?.id ?? "anonymous";');
+ expect(translatedCode).toContain('const items = res.getBody()?.data?.items || [];');
+ });
+
+ it('should handle response headers with different access patterns', () => {
+ // will need to handle get, set methods, bruno does not support this yet
+ const code = `
+ const contentType = pm.response.headers.get('Content-Type');
+ const contentLength = pm.response.headers.get('Content-Length');
+ console.log("contentType", contentType);
+ console.log("contentLength", contentLength);
+
+ pm.test("Headers are correct", function() {
+ pm.response.to.have.header('Content-Type');
+ pm.response.to.have.header('Content-Length');
+ pm.expect(contentType).to.include('application/json');
+ });
+ `;
+ const translatedCode = translateCode(code);
+
+ // Check how header access is translated
+ expect(translatedCode).toContain('const contentType = res.getHeaders().get(\'Content-Type\');');
+ expect(translatedCode).toContain('const contentLength = res.getHeaders().get(\'Content-Length\');');
+ expect(translatedCode).toContain('console.log("contentType", contentType);');
+ expect(translatedCode).toContain('console.log("contentLength", contentLength);');
+ expect(translatedCode).not.toContain('pm.test')
+ expect(translatedCode).toContain('expect(res.getHeaders()).to.have.property(\'Content-Type\'.toLowerCase())');
+ expect(translatedCode).toContain('expect(res.getHeaders()).to.have.property(\'Content-Length\'.toLowerCase())');
+ expect(translatedCode).toContain('expect(contentType).to.include(\'application/json\')');
+ });
+
+ it('should transform response data with array destructuring', () => {
+ const code = `
+ const { id, name, items } = pm.response.json();
+ const [first, second] = items;
+ pm.environment.set("userId", id);
+ `;
+ const translatedCode = translateCode(code);
+
+ expect(translatedCode).toContain('const { id, name, items } = res.getBody();');
+ expect(translatedCode).toContain('const [first, second] = items;');
+ expect(translatedCode).toContain('bru.setEnvVar("userId", id);');
+ });
+
+ it('should handle response in complex conditionals', () => {
+ const code = `
+ if (pm.response.code >= 200 && pm.response.code < 300) {
+ if (pm.response.headers.get('Content-Type').includes('application/json')) {
+ const data = pm.response.json();
+
+ if (data.success === true && data.token) {
+ pm.environment.set("authToken", data.token);
+ } else if (data.error) {
+ console.error("API error:", data.error);
+ }
+ }
+ } else if (pm.response.code === 404) {
+ console.log("Resource not found");
+ } else {
+ console.error("Request failed with status:", pm.response.code);
+ }
+ `;
+ const translatedCode = translateCode(code);
+
+ expect(translatedCode).toContain('if (res.getStatus() >= 200 && res.getStatus() < 300) {');
+ expect(translatedCode).toContain('if (res.getHeaders().get(\'Content-Type\').includes(\'application/json\')) {');
+ expect(translatedCode).toContain('const data = res.getBody();');
+ expect(translatedCode).toContain('bru.setEnvVar("authToken", data.token);');
+ expect(translatedCode).toContain('} else if (res.getStatus() === 404) {');
+ expect(translatedCode).toContain('console.error("Request failed with status:", res.getStatus());');
+ });
+
+ it('should handle response processing with try-catch', () => {
+ const code = `
+ try {
+ const data = pm.response.json();
+ pm.environment.set("userData", JSON.stringify(data.user));
+ } catch (error) {
+ console.error("Failed to parse response:", error);
+ const text = pm.response.text();
+ pm.environment.set("rawResponse", text);
+ }
+ `;
+ const translatedCode = translateCode(code);
+
+ expect(translatedCode).toContain('const data = res.getBody();');
+ expect(translatedCode).toContain('bru.setEnvVar("userData", JSON.stringify(data.user));');
+ expect(translatedCode).toContain('const text = JSON.stringify(res.getBody());');
+ expect(translatedCode).toContain('bru.setEnvVar("rawResponse", text);');
+ });
+
+ it('should handle JSON path style access to response data', () => {
+ const code = `
+ const data = pm.response.json();
+ const userId = data.user.id;
+ const userEmail = data.user.contact.email;
+ const firstItem = data.items[0];
+
+ pm.environment.set("userId", userId);
+ `;
+ const translatedCode = translateCode(code);
+
+ expect(translatedCode).toContain('const data = res.getBody();');
+ 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);');
+ });
+
+ it('should handle template literals with response data', () => {
+ const code = `
+ const data = pm.response.json();
+ const welcomeMessage = \`Hello, \${data.user.name}! Your ID is \${data.user.id}.\`;
+
+ pm.environment.set("welcomeMessage", welcomeMessage);
+ `;
+ const translatedCode = translateCode(code);
+
+ 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);');
+ });
+
+ it('should handle response processing in arrow functions', () => {
+ const code = `
+ const processItems = () => {
+ const items = pm.response.json().items;
+ return items.map(item => item.id);
+ };
+
+ const itemIds = processItems();
+ pm.environment.set("itemIds", JSON.stringify(itemIds));
+ `;
+ const translatedCode = translateCode(code);
+
+ 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));');
+ });
+
+ it('should handle complex inline operations with response data', () => {
+ const code = `
+ const items = pm.response.json().items;
+ const totalValue = items.reduce((sum, item) => sum + item.price, 0);
+ const highValueItems = items.filter(item => item.price > 100);
+ const itemNames = items.map(item => item.name);
+
+ pm.environment.set("totalValue", totalValue);
+ pm.environment.set("highValueItemCount", highValueItems.length);
+ `;
+ const translatedCode = translateCode(code);
+
+ expect(translatedCode).toContain('const items = res.getBody().items;');
+ 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);');
+ });
+
+ it('should handle complex test structure with pm.response.to.have.header', () => {
+ const code = `
+ pm.test("Response headers validation", function() {
+ pm.response.to.have.header("Content-Type", "application/json");
+ pm.response.to.have.header("Cache-Control");
+
+ const responseTime = pm.response.responseTime;
+ pm.expect(responseTime).to.be.below(1000);
+ });
+ `;
+ const translatedCode = translateCode(code);
+
+ // Check for test function conversion
+ expect(translatedCode).toContain('test("Response headers validation", function() {');
+
+ // Check for header assertions inside the test callback
+ expect(translatedCode).toContain('expect(res.getHeaders()).to.have.property("Content-Type".toLowerCase(), "application/json");');
+ expect(translatedCode).toContain('expect(res.getHeaders()).to.have.property("Cache-Control".toLowerCase())');
+
+ // Check that other test assertions are preserved
+ expect(translatedCode).toContain('const responseTime = res.getResponseTime();');
+ expect(translatedCode).toContain('expect(responseTime).to.be.below(1000);');
+ });
+
+ it('should handle dynamic header names in pm.response.to.have.header', () => {
+ const code = `
+ function checkHeaderPresent(headerName) {
+ pm.response.to.have.header(headerName);
+ }
+
+ function validateHeader(headerName, expectedValue) {
+ pm.response.to.have.header(headerName, expectedValue);
+ }
+
+ checkHeaderPresent("Authorization");
+ validateHeader("Content-Type", "application/json");
+ `;
+ const translatedCode = translateCode(code);
+
+ // Check function transformations
+ expect(translatedCode).toContain('function checkHeaderPresent(headerName) {');
+ expect(translatedCode).toContain('expect(res.getHeaders()).to.have.property(headerName.toLowerCase())');
+
+ expect(translatedCode).toContain('function validateHeader(headerName, expectedValue) {');
+ expect(translatedCode).toContain('expect(res.getHeaders()).to.have.property(headerName.toLowerCase(), expectedValue);');
+
+ // Check function calls
+ expect(translatedCode).toContain('checkHeaderPresent("Authorization");');
+ expect(translatedCode).toContain('validateHeader("Content-Type", "application/json");');
+ });
+});
\ No newline at end of file
diff --git a/packages/bruno-converters/tests/postman/postman-translations/transpiler-tests/scoped-variables.test.js b/packages/bruno-converters/tests/postman/postman-translations/transpiler-tests/scoped-variables.test.js
new file mode 100644
index 000000000..9ed5ed700
--- /dev/null
+++ b/packages/bruno-converters/tests/postman/postman-translations/transpiler-tests/scoped-variables.test.js
@@ -0,0 +1,51 @@
+import translateCode from '../../../../src/utils/jscode-shift-translator';
+
+describe('Scoped Variables', () => {
+ it.skip('should handle scoped variables correctly', () => {
+ const code = `
+ const response = pm.response;
+ const status = response.status;
+
+ function test() {
+ const response = delta.response;
+ const status = response.status;
+ console.log(status);
+ }
+ `
+ const result = translateCode(code);
+ console.log(result);
+ expect(result).toBe(`
+ const status = res.statusText;
+
+ function test() {
+ const response = delta.response;
+ const status = response.status;
+ console.log(status);
+ }
+ `)
+ })
+
+ it.skip('should handle scoped variables correctly', () => {
+ const code = `
+ const response = delta.response;
+ const status = response.status;
+
+ function test() {
+ const response = pm.response;
+ const status = response.status;
+ console.log(status);
+ }
+ `
+ const result = translateCode(code);
+ console.log(result);
+ expect(result).toBe(`
+ const response = delta.response;
+ const status = response.status;
+
+ function test() {
+ const status = res.statusText;
+ console.log(status);
+ }
+ `)
+ })
+})
\ No newline at end of file
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
new file mode 100644
index 000000000..fc3988f1f
--- /dev/null
+++ b/packages/bruno-converters/tests/postman/postman-translations/transpiler-tests/testing-framework.test.js
@@ -0,0 +1,399 @@
+import translateCode from '../../../../src/utils/jscode-shift-translator';
+
+describe('Testing Framework Translation', () => {
+ // Basic testing framework translations
+ it('should translate pm.test', () => {
+ const code = 'pm.test("Status code is 200", function() { pm.response.to.have.status(200); });';
+ const translatedCode = translateCode(code);
+ expect(translatedCode).toBe('test("Status code is 200", function() { expect(res.getStatus()).to.equal(200); });');
+ });
+
+ it('should translate pm.expect', () => {
+ const code = 'pm.expect(jsonData.success).to.be.true;';
+ const translatedCode = translateCode(code);
+ expect(translatedCode).toBe('expect(jsonData.success).to.be.true;');
+ });
+
+ it('should translate pm.expect.fail', () => {
+ const code = 'if (!isValid) pm.expect.fail("Data is invalid");';
+ const translatedCode = translateCode(code);
+ expect(translatedCode).toBe('if (!isValid) expect.fail("Data is invalid");');
+ });
+
+ // Tests with response assertions
+ it('should translate pm.response.to.have.status in tests', () => {
+ const code = `
+ pm.test("Check environment and call successful", function () {
+ pm.expect(pm.environment.name).to.equal("ENVIRONMENT_NAME");
+ pm.response.to.have.status(200);
+ });`;
+ const translatedCode = translateCode(code);
+ expect(translatedCode).toBe(`
+ test("Check environment and call successful", function () {
+ expect(bru.getEnvName()).to.equal("ENVIRONMENT_NAME");
+ expect(res.getStatus()).to.equal(200);
+ });`);
+ });
+
+ // Test aliases
+ it('should handle test aliases', () => {
+ const code = `
+ const { test, expect } = pm;
+
+ test("Status code is 200", function () {
+ expect(pm.response.code).to.equal(200);
+ });
+ `;
+ const translatedCode = translateCode(code);
+
+ expect(translatedCode).not.toContain('const { test, expect } = pm');
+ expect(translatedCode).toContain('test("Status code is 200", function () {');
+ expect(translatedCode).toContain('expect(res.getStatus()).to.equal(200);');
+ });
+
+ // Tests inside different code structures
+ it('should translate pm commands inside tests with nested functions', () => {
+ const code = `
+ pm.test("Auth flow works", function() {
+ const response = pm.response.json();
+ pm.expect(response.authenticated).to.be.true;
+ pm.environment.set("userId", response.user.id);
+ pm.collectionVariables.set("sessionId", response.session.id);
+ });
+ `;
+ const translatedCode = translateCode(code);
+
+ 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.setVar("sessionId", response.session.id);');
+ });
+
+ it('should translate pm.test with arrow functions', () => {
+ const code = `
+ pm.test("Status code is 200", () => {
+ pm.expect(pm.response.code).to.eql(200);
+ });
+ `;
+ const translatedCode = translateCode(code);
+
+ expect(translatedCode).toContain('test("Status code is 200", () => {');
+ expect(translatedCode).toContain('expect(res.getStatus()).to.eql(200);');
+ });
+
+ it('should handle multiple test assertions in one function', () => {
+ const code = `
+ pm.test("The response has all properties", () => {
+ const responseJson = pm.response.json();
+ pm.expect(responseJson.type).to.eql('vip');
+ pm.expect(responseJson.name).to.be.a('string');
+ pm.expect(responseJson.id).to.have.lengthOf(1);
+ });
+ `;
+ const translatedCode = translateCode(code);
+
+ expect(translatedCode).toContain('test("The response has all properties", () => {');
+ expect(translatedCode).toContain('const responseJson = res.getBody();');
+ expect(translatedCode).toContain('expect(responseJson.type).to.eql(\'vip\');');
+ expect(translatedCode).toContain('expect(responseJson.name).to.be.a(\'string\');');
+ expect(translatedCode).toContain('expect(responseJson.id).to.have.lengthOf(1);');
+ });
+
+ // Test with aliased variables
+ it('should translate aliases within test functions', () => {
+ const code = `
+ const tempRes = pm.response;
+ const tempTest = pm.test;
+ const tempExpect = pm.expect;
+
+ tempTest("Status code is 200", function() {
+ tempExpect(tempRes.code).to.equal(200);
+ });
+ `;
+ const translatedCode = translateCode(code);
+
+ expect(translatedCode).not.toContain('const tempRes = pm.response;');
+ expect(translatedCode).not.toContain('const tempTest = pm.test;');
+ expect(translatedCode).not.toContain('const tempExpect = pm.expect;');
+ expect(translatedCode).toContain('test("Status code is 200", function() {');
+ expect(translatedCode).toContain('expect(res.getStatus()).to.equal(200);');
+ });
+
+ // Additional robust tests for testing framework
+ it('should handle nested test functions', () => {
+ const code = `
+ pm.test("Main test group", function() {
+ const responseJson = pm.response.json();
+
+ pm.test("User data validation", function() {
+ pm.expect(responseJson.user).to.be.an('object');
+ pm.expect(responseJson.user.id).to.be.a('string');
+ });
+
+ pm.test("Settings validation", function() {
+ pm.expect(responseJson.settings).to.be.an('object');
+ pm.expect(responseJson.settings.notifications).to.be.a('boolean');
+ });
+ });
+ `;
+ const translatedCode = translateCode(code);
+
+ expect(translatedCode).toContain('test("Main test group", function() {');
+ expect(translatedCode).toContain('const responseJson = res.getBody();');
+ expect(translatedCode).toContain('test("User data validation", function() {');
+ expect(translatedCode).toContain('expect(responseJson.user).to.be.an(\'object\');');
+ expect(translatedCode).toContain('test("Settings validation", function() {');
+ expect(translatedCode).toContain('expect(responseJson.settings.notifications).to.be.a(\'boolean\');');
+ });
+
+ it('should handle test with dynamic test names', () => {
+ const code = `
+ const endpoint = pm.variables.get("currentEndpoint");
+
+ pm.test(\`\${endpoint} returns correct data\`, function() {
+ const responseJson = pm.response.json();
+ pm.expect(responseJson).to.be.an('object');
+ });
+ `;
+ const translatedCode = translateCode(code);
+
+ expect(translatedCode).toContain('const endpoint = bru.getVar("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\');');
+ });
+
+ it('should handle test with conditional execution', () => {
+ const code = `
+ const responseJson = pm.response.json();
+
+ if (responseJson.type === 'user') {
+ pm.test("User validation", function() {
+ pm.expect(responseJson.name).to.be.a('string');
+ pm.expect(responseJson.email).to.be.a('string');
+ });
+ } else if (responseJson.type === 'admin') {
+ pm.test("Admin validation", function() {
+ pm.expect(responseJson.accessLevel).to.be.above(5);
+ pm.expect(responseJson.permissions).to.be.an('array');
+ });
+ }
+ `;
+ const translatedCode = translateCode(code);
+
+ expect(translatedCode).toContain('const responseJson = res.getBody();');
+ expect(translatedCode).toContain('if (responseJson.type === \'user\') {');
+ expect(translatedCode).toContain('test("User validation", function() {');
+ expect(translatedCode).toContain('expect(responseJson.name).to.be.a(\'string\');');
+ expect(translatedCode).toContain('} else if (responseJson.type === \'admin\') {');
+ expect(translatedCode).toContain('test("Admin validation", function() {');
+ expect(translatedCode).toContain('expect(responseJson.accessLevel).to.be.above(5);');
+ });
+
+ it('should handle assertions with logical operators', () => {
+ const code = `
+ pm.test("Response has valid structure", function() {
+ const data = pm.response.json();
+
+ pm.expect(data.id && data.name).to.be.ok;
+ pm.expect(data.active || data.pending).to.be.true;
+ pm.expect(!data.deleted).to.be.true;
+ });
+ `;
+ const translatedCode = translateCode(code);
+
+ expect(translatedCode).toContain('test("Response has valid structure", function() {');
+ expect(translatedCode).toContain('const data = res.getBody();');
+ expect(translatedCode).toContain('expect(data.id && data.name).to.be.ok;');
+ expect(translatedCode).toContain('expect(data.active || data.pending).to.be.true;');
+ expect(translatedCode).toContain('expect(!data.deleted).to.be.true;');
+ });
+
+ it('should handle array and object assertions', () => {
+ const code = `
+ pm.test("Array and object validations", function() {
+ const data = pm.response.json();
+
+ // Array validations
+ pm.expect(data.items).to.be.an('array');
+ pm.expect(data.items).to.have.lengthOf.at.least(1);
+ pm.expect(data.items[0]).to.have.property('id');
+
+ // Object validations
+ pm.expect(data.user).to.be.an('object');
+ pm.expect(data.user).to.have.all.keys('id', 'name', 'email');
+ pm.expect(data.user).to.include({active: true});
+ });
+ `;
+ const translatedCode = translateCode(code);
+
+ expect(translatedCode).toContain('test("Array and object validations", function() {');
+ expect(translatedCode).toContain('const data = res.getBody();');
+ expect(translatedCode).toContain('expect(data.items).to.be.an(\'array\');');
+ expect(translatedCode).toContain('expect(data.items).to.have.lengthOf.at.least(1);');
+ expect(translatedCode).toContain('expect(data.items[0]).to.have.property(\'id\');');
+ expect(translatedCode).toContain('expect(data.user).to.be.an(\'object\');');
+ expect(translatedCode).toContain('expect(data.user).to.have.all.keys(\'id\', \'name\', \'email\');');
+ expect(translatedCode).toContain('expect(data.user).to.include({active: true});');
+ });
+
+ it('should handle chai assertions with deep equality', () => {
+ const code = `
+ pm.test("Deep equality checks", function() {
+ const data = pm.response.json();
+
+ pm.expect(data.config).to.deep.equal({
+ version: "1.0",
+ active: true,
+ features: ["search", "export"]
+ });
+
+ pm.expect(data.tags).to.have.members(['api', 'test']);
+ pm.expect(data.meta).to.deep.include({format: 'json'});
+ });
+ `;
+ const translatedCode = translateCode(code);
+
+ expect(translatedCode).toContain('test("Deep equality checks", function() {');
+ expect(translatedCode).toContain('const data = res.getBody();');
+ expect(translatedCode).toContain('expect(data.config).to.deep.equal({');
+ expect(translatedCode).toContain('version: "1.0",');
+ expect(translatedCode).toContain('active: true,');
+ expect(translatedCode).toContain('features: ["search", "export"]');
+ expect(translatedCode).toContain('expect(data.tags).to.have.members([\'api\', \'test\']);');
+ expect(translatedCode).toContain('expect(data.meta).to.deep.include({format: \'json\'});');
+ });
+
+ it('should handle chai assertions with string comparisons', () => {
+ const code = `
+ pm.test("String validations", function() {
+ const data = pm.response.json();
+
+ pm.expect(data.id).to.be.a('string');
+ pm.expect(data.name).to.match(/^[A-Za-z\\s]+$/);
+ pm.expect(data.description).to.include('API');
+ pm.expect(data.url).to.have.string('api/v1');
+ pm.expect(data.code).to.have.lengthOf(8);
+ });
+ `;
+ const translatedCode = translateCode(code);
+
+ expect(translatedCode).toContain('test("String validations", function() {');
+ expect(translatedCode).toContain('const data = res.getBody();');
+ expect(translatedCode).toContain('expect(data.id).to.be.a(\'string\');');
+ expect(translatedCode).toContain('expect(data.name).to.match(/^[A-Za-z\\s]+$/);');
+ expect(translatedCode).toContain('expect(data.description).to.include(\'API\');');
+ expect(translatedCode).toContain('expect(data.url).to.have.string(\'api/v1\');');
+ expect(translatedCode).toContain('expect(data.code).to.have.lengthOf(8);');
+ });
+
+ it('should handle assertions with numeric comparisons', () => {
+ const code = `
+ pm.test("Numeric validations", function() {
+ const data = pm.response.json();
+
+ pm.expect(data.count).to.be.a('number');
+ pm.expect(data.count).to.be.above(0);
+ pm.expect(data.price).to.be.within(10, 100);
+ pm.expect(data.discount).to.be.at.most(25);
+ pm.expect(data.quantity * data.price).to.equal(data.total);
+ });
+ `;
+ const translatedCode = translateCode(code);
+
+ expect(translatedCode).toContain('test("Numeric validations", function() {');
+ expect(translatedCode).toContain('const data = res.getBody();');
+ expect(translatedCode).toContain('expect(data.count).to.be.a(\'number\');');
+ expect(translatedCode).toContain('expect(data.count).to.be.above(0);');
+ expect(translatedCode).toContain('expect(data.price).to.be.within(10, 100);');
+ expect(translatedCode).toContain('expect(data.discount).to.be.at.most(25);');
+ expect(translatedCode).toContain('expect(data.quantity * data.price).to.equal(data.total);');
+ });
+
+ it('should handle pm.expect.fail with conditions', () => {
+ const code = `
+ pm.test("Validate critical fields", function() {
+ const data = pm.response.json();
+
+ if (!data.id) {
+ pm.expect.fail("Missing ID field");
+ }
+
+ if (data.status !== 'active' && data.status !== 'pending') {
+ pm.expect.fail("Invalid status: " + data.status);
+ }
+
+ // Continue with normal assertions
+ pm.expect(data.name).to.be.a('string');
+ });
+ `;
+ const translatedCode = translateCode(code);
+
+ expect(translatedCode).toContain('test("Validate critical fields", function() {');
+ expect(translatedCode).toContain('const data = res.getBody();');
+ expect(translatedCode).toContain('if (!data.id) {');
+ expect(translatedCode).toContain('expect.fail("Missing ID field");');
+ expect(translatedCode).toContain('if (data.status !== \'active\' && data.status !== \'pending\') {');
+ expect(translatedCode).toContain('expect.fail("Invalid status: " + data.status);');
+ expect(translatedCode).toContain('expect(data.name).to.be.a(\'string\');');
+ });
+
+ it('should handle complex test compositions', () => {
+ const code = `
+ // Helper function
+ function validateUserObject(user) {
+ pm.expect(user).to.be.an('object');
+ pm.expect(user.id).to.be.a('string');
+ pm.expect(user.name).to.be.a('string');
+ return user.id && user.name;
+ }
+
+ pm.test("Response validation", function() {
+ const response = pm.response.json();
+ const validUsers = [];
+
+ // Test status code
+ pm.response.to.have.status(200);
+
+ // Test main user
+ if (response.user) {
+ const isValid = validateUserObject(response.user);
+ if (isValid) {
+ validUsers.push(response.user);
+ }
+ }
+
+ // Test related users
+ if (response.relatedUsers && Array.isArray(response.relatedUsers)) {
+ pm.test("Related users validation", function() {
+ response.relatedUsers.forEach((user, index) => {
+ pm.test(\`User at index \${index}\`, function() {
+ const isValid = validateUserObject(user);
+ if (isValid) {
+ validUsers.push(user);
+ }
+ });
+ });
+ });
+ }
+
+ // Set the valid users for later use
+ if (validUsers.length > 0) {
+ pm.environment.set("validUsers", JSON.stringify(validUsers));
+ }
+ });
+ `;
+ const translatedCode = translateCode(code);
+
+ // Test key transformations
+ expect(translatedCode).toContain('function validateUserObject(user) {');
+ expect(translatedCode).toContain('expect(user).to.be.an(\'object\');');
+ expect(translatedCode).toContain('test("Response validation", function() {');
+ expect(translatedCode).toContain('const response = res.getBody();');
+ 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));');
+ });
+});
\ No newline at end of file
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
new file mode 100644
index 000000000..3c700000e
--- /dev/null
+++ b/packages/bruno-converters/tests/postman/postman-translations/transpiler-tests/variable-chaining.test.js
@@ -0,0 +1,91 @@
+import translateCode from '../../../../src/utils/jscode-shift-translator';
+
+describe('Variable Chaining Resolution', () => {
+ test('should resolve a simple variable chain (variable pointing to another variable)', () => {
+ const code = `
+ const original = pm.response;
+ const alias = original;
+ const data = alias.json();
+ `;
+
+ const translatedCode = translateCode(code);
+
+ // Check that alias.json() was properly resolved to res.getBody()
+ expect(translatedCode).toContain('const data = res.getBody();');
+ // The original variable declarations should be removed
+ expect(translatedCode).not.toContain('const original =');
+ expect(translatedCode).not.toContain('const alias =');
+ });
+
+ test('should handle mixed variable references correctly', () => {
+ const code = `
+ const respVar = pm.response;
+ const envVar = pm.environment;
+ const respAlias = respVar;
+
+ // These should be replaced
+ const statusCode = respAlias.code;
+ const envValue = envVar.get("key");
+
+ // This should not be replaced
+ const unrelatedVar = "some value";
+ `;
+
+ const translatedCode = translateCode(code);
+
+ // Check correct replacements
+ 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");');
+
+ // Check that unrelated variables are preserved
+ expect(translatedCode).toContain('const unrelatedVar = "some value";');
+ });
+
+ /**
+ * This test verifies that when multiple variables are declared in a single statement,
+ * only the ones referencing Postman objects are removed and the others are preserved.
+ *
+ * For example, in a statement like:
+ * const response = pm.response, counter = 5, helper = "test";
+ *
+ * Only 'response' should be removed, resulting in:
+ * const counter = 5, helper = "test";
+ */
+ test('should handle multiple variables in one declaration statement', () => {
+ const code = `
+ // Multiple variables in one declaration, with a mix of Postman objects and regular variables
+ const response = pm.response, counter = 5, helper = "test";
+
+ // Using both the Postman reference (should be replaced) and regular values (should be preserved)
+ const statusCode = response.code;
+ console.log("Counter value:", counter);
+ console.log("Helper string:", helper);
+
+ // Another example with different Postman object
+ let env = pm.environment, timeout = 1000, isValid = true;
+ const baseUrl = env.get("baseUrl");
+ `;
+
+ const translatedCode = translateCode(code);
+
+ // Postman references should be replaced
+ expect(translatedCode).not.toContain('response = pm.response');
+ expect(translatedCode).not.toContain('env = pm.environment');
+
+ // Regular variables should be preserved
+ expect(translatedCode).toContain('const counter = 5');
+ expect(translatedCode).toContain('helper = "test"');
+ expect(translatedCode).toContain('timeout = 1000');
+ expect(translatedCode).toContain('isValid = true');
+
+ // References to Postman objects should be properly translated
+ expect(translatedCode).toContain('const statusCode = res.getStatus();');
+ expect(translatedCode).toContain('const baseUrl = bru.getEnvVar("baseUrl");');
+
+ // Console logs with regular variables should be preserved
+ expect(translatedCode).toContain('console.log("Counter value:", counter);');
+ expect(translatedCode).toContain('console.log("Helper string:", helper);');
+ });
+});
\ No newline at end of file
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
new file mode 100644
index 000000000..b704e4a7e
--- /dev/null
+++ b/packages/bruno-converters/tests/postman/postman-translations/transpiler-tests/variables.test.js
@@ -0,0 +1,128 @@
+import translateCode from '../../../../src/utils/jscode-shift-translator';
+
+describe('Variables Translation', () => {
+ // Regular variables tests
+ it('should translate pm.variables.get', () => {
+ const code = 'pm.variables.get("test");';
+ const translatedCode = translateCode(code);
+ expect(translatedCode).toBe('bru.getVar("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");');
+ });
+
+ it('should translate pm.variables.has', () => {
+ const code = 'pm.variables.has("userId");';
+ const translatedCode = translateCode(code);
+ expect(translatedCode).toBe('bru.hasVar("userId");');
+ });
+
+ // Collection variables tests
+ it('should translate pm.collectionVariables.get', () => {
+ const code = 'pm.collectionVariables.get("apiUrl");';
+ const translatedCode = translateCode(code);
+ expect(translatedCode).toBe('bru.getVar("apiUrl");');
+ });
+
+ it('should translate pm.collectionVariables.set', () => {
+ const code = 'pm.collectionVariables.set("token", jsonData.token);';
+ const translatedCode = translateCode(code);
+ expect(translatedCode).toBe('bru.setVar("token", jsonData.token);');
+ });
+
+ it('should translate pm.collectionVariables.has', () => {
+ const code = 'pm.collectionVariables.has("authToken");';
+ const translatedCode = translateCode(code);
+ expect(translatedCode).toBe('bru.hasVar("authToken");');
+ });
+
+ it('should translate pm.collectionVariables.unset', () => {
+ const code = 'pm.collectionVariables.unset("tempVar");';
+ const translatedCode = translateCode(code);
+ expect(translatedCode).toBe('bru.deleteVar("tempVar");');
+ });
+
+ // Alias tests for variables
+ it('should handle variables aliases', () => {
+ const code = `
+ const vars = pm.variables;
+ const has = vars.has("test");
+ const set = vars.set("test", "value");
+ const get = vars.get("test");
+ `;
+ const translatedCode = translateCode(code);
+ expect(translatedCode).toBe(`
+ const has = bru.hasVar("test");
+ const set = bru.setVar("test", "value");
+ const get = bru.getVar("test");
+ `);
+ });
+
+ // Alias tests for collection variables
+ it('should handle collection variables aliases', () => {
+ const code = `
+ const collVars = pm.collectionVariables;
+ const has = collVars.has("test");
+ const set = collVars.set("test", "value");
+ const get = collVars.get("test");
+ const unset = collVars.unset("test");
+ `;
+ const translatedCode = translateCode(code);
+ expect(translatedCode).toBe(`
+ const has = bru.hasVar("test");
+ const set = bru.setVar("test", "value");
+ const get = bru.getVar("test");
+ const unset = bru.deleteVar("test");
+ `);
+ });
+
+ // Combined tests
+ it('should handle conditional expressions with variable calls', () => {
+ 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";');
+ });
+
+ it('should handle all variable methods together', () => {
+ const code = `
+ // All variable methods
+ const hasUserId = pm.variables.has("userId");
+ const userId = pm.variables.get("userId");
+ pm.variables.set("requestTime", new Date().toISOString());
+
+ console.log(\`Has userId: \${hasUserId}, User ID: \${userId}\`);
+ `;
+ 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());');
+ });
+
+ it('should handle all collection variable methods together', () => {
+ const code = `
+ // All collection variable methods
+ const hasApiUrl = pm.collectionVariables.has("apiUrl");
+ const apiUrl = pm.collectionVariables.get("apiUrl");
+ pm.collectionVariables.set("requestTime", new Date().toISOString());
+ pm.collectionVariables.unset("tempVar");
+
+ console.log(\`Has API URL: \${hasApiUrl}, API URL: \${apiUrl}\`);
+ `;
+ const translatedCode = translateCode(code);
+
+ expect(translatedCode).toContain('const hasApiUrl = bru.hasVar("apiUrl");');
+ expect(translatedCode).toContain('const apiUrl = bru.getVar("apiUrl");');
+ expect(translatedCode).toContain('bru.setVar("requestTime", new Date().toISOString());');
+ expect(translatedCode).toContain('bru.deleteVar("tempVar");');
+ });
+
+ it('should handle more complex nested expressions with variables', () => {
+ const code = 'pm.collectionVariables.set("fullPath", pm.environment.get("baseUrl") + pm.variables.get("endpoint"));';
+ const translatedCode = translateCode(code);
+ expect(translatedCode).toBe('bru.setVar("fullPath", bru.getEnvVar("baseUrl") + bru.getVar("endpoint"));');
+ });
+});
\ No newline at end of file
diff --git a/packages/bruno-converters/tests/utils/getMemberExpressionString.test.js b/packages/bruno-converters/tests/utils/getMemberExpressionString.test.js
new file mode 100644
index 000000000..f5dd69f09
--- /dev/null
+++ b/packages/bruno-converters/tests/utils/getMemberExpressionString.test.js
@@ -0,0 +1,53 @@
+import { describe, it, expect } from '@jest/globals';
+import { getMemberExpressionString } from '../../src/utils/jscode-shift-translator';
+const j = require('jscodeshift');
+
+describe('getMemberExpressionString', () => {
+ it('should correctly convert simple member expressions to strings', () => {
+ // Create a simple member expression: pm.environment.get
+ const memberExpr = j.memberExpression(
+ j.memberExpression(
+ j.identifier('pm'),
+ j.identifier('environment')
+ ),
+ j.identifier('get')
+ );
+
+ const result = getMemberExpressionString(memberExpr);
+ expect(result).toBe('pm.environment.get');
+ });
+
+ it('should handle computed properties with string literals', () => {
+ // Create a computed member expression: pm["environment"]["get"]
+ const memberExpr = j.memberExpression(
+ j.memberExpression(
+ j.identifier('pm'),
+ j.literal('environment'),
+ true // computed
+ ),
+ j.literal('get'),
+ true // computed
+ );
+
+ const result = getMemberExpressionString(memberExpr);
+ expect(result).toBe('pm.environment.get');
+ });
+
+ it('should mark non-string computed properties as [computed]', () => {
+ // Create a computed member expression with variable: obj[varName]
+ const memberExpr = j.memberExpression(
+ j.identifier('obj'),
+ j.identifier('varName'),
+ true // computed
+ );
+
+ const result = getMemberExpressionString(memberExpr);
+ expect(result).toBe('obj.[computed]');
+ });
+
+ it('should handle basic identifiers', () => {
+ const identifier = j.identifier('pm');
+ const result = getMemberExpressionString(identifier);
+ expect(result).toBe('pm');
+ });
+});
\ No newline at end of file
diff --git a/packages/bruno-electron/package.json b/packages/bruno-electron/package.json
index dbc921211..26f327b73 100644
--- a/packages/bruno-electron/package.json
+++ b/packages/bruno-electron/package.json
@@ -28,6 +28,7 @@
"@aws-sdk/credential-providers": "3.750.0",
"@faker-js/faker": "^9.5.1",
"@usebruno/common": "0.1.0",
+ "@usebruno/converters": "^0.1.0",
"@usebruno/js": "0.12.0",
"@usebruno/lang": "0.12.0",
"@usebruno/node-machine-id": "^2.0.0",
diff --git a/packages/bruno-electron/src/ipc/collection.js b/packages/bruno-electron/src/ipc/collection.js
index 443c5e956..b0924bb6b 100644
--- a/packages/bruno-electron/src/ipc/collection.js
+++ b/packages/bruno-electron/src/ipc/collection.js
@@ -6,6 +6,8 @@ const os = require('os');
const path = require('path');
const { ipcMain, shell, dialog, app } = require('electron');
const { envJsonToBru, bruToJson, jsonToBru, jsonToBruViaWorker, collectionBruToJson, jsonToCollectionBru, bruToJsonViaWorker } = require('../bru');
+const brunoConverters = require('@usebruno/converters');
+const { postmanToBruno } = brunoConverters;
const {
writeFile,
@@ -1151,6 +1153,19 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection
throw error;
}
});
+
+ // Implement the Postman to Bruno conversion handler
+ ipcMain.handle('renderer:convert-postman-to-bruno', async (event, postmanCollection) => {
+ try {
+ // Convert Postman collection to Bruno format
+ const brunoCollection = await postmanToBruno(postmanCollection, { useWorkers: true});
+
+ return brunoCollection;
+ } catch (error) {
+ console.error('Error converting Postman to Bruno:', error);
+ return Promise.reject(error);
+ }
+ });
};
const registerMainEventHandlers = (mainWindow, watcher, lastOpenedCollections) => {
diff --git a/packages/bruno-tests/collection/bruno.json b/packages/bruno-tests/collection/bruno.json
index ada36145a..d2aa0a97a 100644
--- a/packages/bruno-tests/collection/bruno.json
+++ b/packages/bruno-tests/collection/bruno.json
@@ -28,4 +28,4 @@
"requestType": "http",
"requestUrl": "http://localhost:6000"
}
-}
+}
\ No newline at end of file
diff --git a/packages/bruno-tests/collection/echo/echo headers.bru b/packages/bruno-tests/collection/echo/echo headers.bru
new file mode 100644
index 000000000..9f6571109
--- /dev/null
+++ b/packages/bruno-tests/collection/echo/echo headers.bru
@@ -0,0 +1,22 @@
+meta {
+ name: echo headers
+ type: http
+ seq: 13
+}
+
+post {
+ url: {{echo-host}}
+ body: none
+ auth: inherit
+}
+
+headers {
+ Custom-Header-String: bruno
+}
+
+tests {
+ test("test headers",function() {
+ expect(res.getHeaders()).to.have.property("Custom-Header-String".toLowerCase())
+ expect(res.getHeaders()).to.have.property("Custom-Header-String".toLowerCase(), "bruno")
+ })
+}