Feature: postman to bru translator (#4534)

This commit is contained in:
sanish chirayath
2025-05-08 21:51:21 +05:30
committed by GitHub
parent 76779e6f95
commit dfa951e574
37 changed files with 4529 additions and 308 deletions

3
.gitignore vendored
View File

@@ -46,4 +46,5 @@ yarn-error.log*
#dev editor
bruno.iml
.idea
.idea
.vscode

397
package-lock.json generated
View File

@@ -1703,9 +1703,9 @@
}
},
"node_modules/@babel/helper-plugin-utils": {
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.9.tgz",
"integrity": "sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw==",
"version": "7.26.5",
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz",
"integrity": "sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
@@ -2007,6 +2007,21 @@
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-syntax-flow": {
"version": "7.26.0",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.26.0.tgz",
"integrity": "sha512-B+O2DnPc0iG+YXFqOxv2WNuNU97ToWjOomUQ78DouOENWUaM5sVrmet9mcomUGQFwpJd//gvUagXBSdzO1fRKg==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.25.9"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-syntax-import-assertions": {
"version": "7.26.0",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.26.0.tgz",
@@ -2192,7 +2207,6 @@
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.9.tgz",
"integrity": "sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.25.9"
@@ -2483,6 +2497,22 @@
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-flow-strip-types": {
"version": "7.26.5",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.26.5.tgz",
"integrity": "sha512-eGK26RsbIkYUns3Y8qKl362juDDYK+wEdPGHGrhzUl6CewZFo55VZ7hg+CyMFU4dd5QQakBN86nBMpRsFpRvbQ==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.26.5",
"@babel/plugin-syntax-flow": "^7.26.0"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-for-of": {
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.25.9.tgz",
@@ -2957,7 +2987,6 @@
"version": "7.26.3",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.26.3.tgz",
"integrity": "sha512-6+5hpdr6mETwSKjmJUdYw0EIkATiQhnELWlE3kJFBwSg/BGIVwVaVbX+gOXBCdc7Ln1RXZxyWGecIXhUfnl7oA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-annotate-as-pure": "^7.25.9",
@@ -3119,6 +3148,23 @@
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/preset-flow": {
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/preset-flow/-/preset-flow-7.25.9.tgz",
"integrity": "sha512-EASHsAhE+SSlEzJ4bzfusnXSHiU+JfAYzj+jbw2vgQKgq5HrUr8qs+vgtiEL5dOH6sEweI+PNt2D7AqrDSHyqQ==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.25.9",
"@babel/helper-validator-option": "^7.25.9",
"@babel/plugin-transform-flow-strip-types": "^7.25.9"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/preset-modules": {
"version": "0.1.6-no-external-plugins",
"resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz",
@@ -3137,7 +3183,6 @@
"version": "7.26.0",
"resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.26.0.tgz",
"integrity": "sha512-NMk1IGZ5I/oHhoXEElcm+xUnL/szL6xflkFZmoEU9xj1qSJXpiS7rsspYo92B4DRCDvZn2erT5LdsCeXAKNCkg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.25.9",
@@ -3153,6 +3198,56 @@
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/register": {
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/register/-/register-7.25.9.tgz",
"integrity": "sha512-8D43jXtGsYmEeDvm4MWHYUpWf8iiXgWYx3fW7E7Wb7Oe6FWqJPl5K6TuFW0dOwNZzEE5rjlaSJYH9JjrUKJszA==",
"license": "MIT",
"dependencies": {
"clone-deep": "^4.0.1",
"find-cache-dir": "^2.0.0",
"make-dir": "^2.1.0",
"pirates": "^4.0.6",
"source-map-support": "^0.5.16"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/register/node_modules/make-dir": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
"integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==",
"license": "MIT",
"dependencies": {
"pify": "^4.0.1",
"semver": "^5.6.0"
},
"engines": {
"node": ">=6"
}
},
"node_modules/@babel/register/node_modules/pify": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
"integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/@babel/register/node_modules/semver": {
"version": "5.7.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
"integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
"license": "ISC",
"bin": {
"semver": "bin/semver"
}
},
"node_modules/@babel/runtime": {
"version": "7.26.0",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz",
@@ -8195,6 +8290,7 @@
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz",
"integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/lodash": {
@@ -8217,6 +8313,7 @@
"version": "12.2.3",
"resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-12.2.3.tgz",
"integrity": "sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/linkify-it": "*",
@@ -8227,6 +8324,7 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz",
"integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/ms": {
@@ -8440,6 +8538,66 @@
"node": ">=6.0"
}
},
"node_modules/@web/rollup-plugin-copy": {
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/@web/rollup-plugin-copy/-/rollup-plugin-copy-0.5.1.tgz",
"integrity": "sha512-crDMXiT/Okn5nVQIEtXShB3NmGS9gQipD1s4kZwrTG+lu5TV665gfDzMFBzezo1e7ARTGd4d4p4EB0gy/JuNJQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"glob": "^10.0.0"
},
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/@web/rollup-plugin-copy/node_modules/glob": {
"version": "10.4.5",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
"integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
"dev": true,
"license": "ISC",
"dependencies": {
"foreground-child": "^3.1.0",
"jackspeak": "^3.1.2",
"minimatch": "^9.0.4",
"minipass": "^7.1.2",
"package-json-from-dist": "^1.0.0",
"path-scurry": "^1.11.1"
},
"bin": {
"glob": "dist/esm/bin.mjs"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/@web/rollup-plugin-copy/node_modules/minimatch": {
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
"dev": true,
"license": "ISC",
"dependencies": {
"brace-expansion": "^2.0.1"
},
"engines": {
"node": ">=16 || 14 >=14.17"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/@web/rollup-plugin-copy/node_modules/minipass": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
"integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
"dev": true,
"license": "ISC",
"engines": {
"node": ">=16 || 14 >=14.17"
}
},
"node_modules/@webassemblyjs/ast": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz",
@@ -9166,6 +9324,18 @@
"node": "*"
}
},
"node_modules/ast-types": {
"version": "0.16.1",
"resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.16.1.tgz",
"integrity": "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==",
"license": "MIT",
"dependencies": {
"tslib": "^2.0.1"
},
"engines": {
"node": ">=4"
}
},
"node_modules/astral-regex": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz",
@@ -11033,7 +11203,6 @@
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz",
"integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"is-plain-object": "^2.0.4",
@@ -11180,7 +11349,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
"integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==",
"dev": true,
"license": "MIT"
},
"node_modules/compare-version": {
@@ -13098,6 +13266,7 @@
"version": "0.1.13",
"resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz",
"integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
@@ -14401,6 +14570,124 @@
"node": ">= 0.8"
}
},
"node_modules/find-cache-dir": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz",
"integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==",
"license": "MIT",
"dependencies": {
"commondir": "^1.0.1",
"make-dir": "^2.0.0",
"pkg-dir": "^3.0.0"
},
"engines": {
"node": ">=6"
}
},
"node_modules/find-cache-dir/node_modules/find-up": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
"integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
"license": "MIT",
"dependencies": {
"locate-path": "^3.0.0"
},
"engines": {
"node": ">=6"
}
},
"node_modules/find-cache-dir/node_modules/locate-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
"integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
"license": "MIT",
"dependencies": {
"p-locate": "^3.0.0",
"path-exists": "^3.0.0"
},
"engines": {
"node": ">=6"
}
},
"node_modules/find-cache-dir/node_modules/make-dir": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
"integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==",
"license": "MIT",
"dependencies": {
"pify": "^4.0.1",
"semver": "^5.6.0"
},
"engines": {
"node": ">=6"
}
},
"node_modules/find-cache-dir/node_modules/p-limit": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
"license": "MIT",
"dependencies": {
"p-try": "^2.0.0"
},
"engines": {
"node": ">=6"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/find-cache-dir/node_modules/p-locate": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
"integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
"license": "MIT",
"dependencies": {
"p-limit": "^2.0.0"
},
"engines": {
"node": ">=6"
}
},
"node_modules/find-cache-dir/node_modules/path-exists": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
"integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==",
"license": "MIT",
"engines": {
"node": ">=4"
}
},
"node_modules/find-cache-dir/node_modules/pify": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
"integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/find-cache-dir/node_modules/pkg-dir": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz",
"integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==",
"license": "MIT",
"dependencies": {
"find-up": "^3.0.0"
},
"engines": {
"node": ">=6"
}
},
"node_modules/find-cache-dir/node_modules/semver": {
"version": "5.7.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
"integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
"license": "ISC",
"bin": {
"semver": "bin/semver"
}
},
"node_modules/find-up": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
@@ -15922,7 +16209,6 @@
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
"integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.8.19"
@@ -17541,6 +17827,71 @@
"integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==",
"license": "MIT"
},
"node_modules/jscodeshift": {
"version": "17.3.0",
"resolved": "https://registry.npmjs.org/jscodeshift/-/jscodeshift-17.3.0.tgz",
"integrity": "sha512-LjFrGOIORqXBU+jwfC9nbkjmQfFldtMIoS6d9z2LG/lkmyNXsJAySPT+2SWXJEoE68/bCWcxKpXH37npftgmow==",
"license": "MIT",
"dependencies": {
"@babel/core": "^7.24.7",
"@babel/parser": "^7.24.7",
"@babel/plugin-transform-class-properties": "^7.24.7",
"@babel/plugin-transform-modules-commonjs": "^7.24.7",
"@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7",
"@babel/plugin-transform-optional-chaining": "^7.24.7",
"@babel/plugin-transform-private-methods": "^7.24.7",
"@babel/preset-flow": "^7.24.7",
"@babel/preset-typescript": "^7.24.7",
"@babel/register": "^7.24.6",
"flow-parser": "0.*",
"graceful-fs": "^4.2.4",
"micromatch": "^4.0.7",
"neo-async": "^2.5.0",
"picocolors": "^1.0.1",
"recast": "^0.23.11",
"tmp": "^0.2.3",
"write-file-atomic": "^5.0.1"
},
"bin": {
"jscodeshift": "bin/jscodeshift.js"
},
"engines": {
"node": ">=16"
},
"peerDependencies": {
"@babel/preset-env": "^7.1.6"
},
"peerDependenciesMeta": {
"@babel/preset-env": {
"optional": true
}
}
},
"node_modules/jscodeshift/node_modules/signal-exit": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
"integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
"license": "ISC",
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/jscodeshift/node_modules/write-file-atomic": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz",
"integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==",
"license": "ISC",
"dependencies": {
"imurmurhash": "^0.1.4",
"signal-exit": "^4.0.1"
},
"engines": {
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
}
},
"node_modules/jsep": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/jsep/-/jsep-1.4.0.tgz",
@@ -17863,7 +18214,6 @@
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
"integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
@@ -19697,7 +20047,6 @@
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
"dev": true,
"license": "MIT"
},
"node_modules/new-github-issue-url": {
@@ -20838,7 +21187,6 @@
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz",
"integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 6"
@@ -22784,6 +23132,22 @@
"node": ">=8.10.0"
}
},
"node_modules/recast": {
"version": "0.23.11",
"resolved": "https://registry.npmjs.org/recast/-/recast-0.23.11.tgz",
"integrity": "sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA==",
"license": "MIT",
"dependencies": {
"ast-types": "^0.16.1",
"esprima": "~4.0.0",
"source-map": "~0.6.1",
"tiny-invariant": "^1.3.3",
"tslib": "^2.0.1"
},
"engines": {
"node": ">= 4"
}
},
"node_modules/rechoir": {
"version": "0.7.1",
"resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.1.tgz",
@@ -24293,7 +24657,6 @@
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz",
"integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==",
"dev": true,
"license": "MIT",
"dependencies": {
"kind-of": "^6.0.2"
@@ -24605,7 +24968,6 @@
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true,
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.10.0"
@@ -24624,7 +24986,6 @@
"version": "0.5.21",
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
"integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
"dev": true,
"license": "MIT",
"dependencies": {
"buffer-from": "^1.0.0",
@@ -25840,7 +26201,6 @@
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz",
"integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=14.14"
@@ -26109,7 +26469,7 @@
"version": "4.9.5",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
"integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
"devOptional": true,
"dev": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
@@ -28795,6 +29155,7 @@
"dependencies": {
"@usebruno/schema": "^0.7.0",
"js-yaml": "^4.1.0",
"jscodeshift": "^17.3.0",
"lodash": "^4.17.21",
"nanoid": "3.3.8"
},
@@ -28805,6 +29166,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",
@@ -28919,6 +29281,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",

View File

@@ -19,7 +19,7 @@ export default defineConfig({
})
],
source: {
tsconfigPath: './jsconfig.json', // Specifies the path to the JavaScript/TypeScript configuration file
tsconfigPath: './jsconfig.json', // Specifies the path to the JavaScript/TypeScript configuration file,
},
html: {
title: 'Bruno'
@@ -34,6 +34,16 @@ export default defineConfig({
},
},
},
ignoreWarnings: [
(warning) => warning.message.includes('Critical dependency: the request of a dependency is an expression') && warning?.moduleDescriptor?.name?.includes('flow-parser')
],
// Add externals configuration to exclude Node.js libraries
externals: {
// List specific Node.js modules you want to exclude
// Format: 'module-name': 'commonjs module-name'
'worker_threads': 'commonjs worker_threads',
// 'path': 'commonjs path'
}
},
}
});

View File

@@ -1,34 +1,43 @@
import React from 'react';
import React, { useState, useEffect } from 'react';
import { IconLoader2 } from '@tabler/icons';
import importBrunoCollection from 'utils/importers/bruno-collection';
import importPostmanCollection from 'utils/importers/postman-collection';
import { postmanToBruno, readFile } from 'utils/importers/postman-collection';
import importInsomniaCollection from 'utils/importers/insomnia-collection';
import importOpenapiCollection from 'utils/importers/openapi-collection';
import { toastError } from 'utils/common/error';
import Modal from 'components/Modal';
import fileDialog from 'file-dialog';
const ImportCollection = ({ onClose, handleSubmit }) => {
const [isLoading, setIsLoading] = useState(false)
const handleImportBrunoCollection = () => {
importBrunoCollection()
.then(({ collection }) => {
handleSubmit({ collection });
})
.catch((err) => toastError(err, 'Import collection failed'));
.catch((err) => toastError(err, 'Import collection failed'))
};
const handleImportPostmanCollection = () => {
importPostmanCollection()
.then(({ collection }) => {
handleSubmit({ collection });
fileDialog({ accept: 'application/json' })
.then((...args) => {
setIsLoading(true);
return readFile(...args);
})
.catch((err) => toastError(err, 'Postman Import collection failed'));
};
.then((collection) => postmanToBruno(collection))
.then((collection) => handleSubmit({ collection }))
.catch((err) => toastError(err, 'Postman Import collection failed'))
.finally(() => setIsLoading(false));
}
const handleImportInsomniaCollection = () => {
importInsomniaCollection()
.then(({ collection }) => {
handleSubmit({ collection });
})
.catch((err) => toastError(err, 'Insomnia Import collection failed'));
.catch((err) => toastError(err, 'Insomnia Import collection failed'))
};
const handleImportOpenapiCollection = () => {
@@ -36,8 +45,9 @@ const ImportCollection = ({ onClose, handleSubmit }) => {
.then(({ collection }) => {
handleSubmit({ collection });
})
.catch((err) => toastError(err, 'OpenAPI v3 Import collection failed'));
.catch((err) => toastError(err, 'OpenAPI v3 Import collection failed'))
};
const CollectionButton = ({ children, className, onClick }) => {
return (
<button
@@ -50,18 +60,67 @@ const ImportCollection = ({ onClose, handleSubmit }) => {
</button>
);
};
return (
<Modal size="sm" title="Import Collection" hideFooter={true} handleCancel={onClose}>
<div className="flex flex-col">
<h3 className="text-sm">Select the type of your existing collection :</h3>
<div className="mt-4 grid grid-rows-2 grid-flow-col gap-2">
<CollectionButton onClick={handleImportBrunoCollection}>Bruno Collection</CollectionButton>
<CollectionButton onClick={handleImportPostmanCollection}>Postman Collection</CollectionButton>
<CollectionButton onClick={handleImportInsomniaCollection}>Insomnia Collection</CollectionButton>
<CollectionButton onClick={handleImportOpenapiCollection}>OpenAPI V3 Spec</CollectionButton>
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 (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-white/80 dark:bg-zinc-900/80 backdrop-blur-sm transition-all duration-300">
<div className="flex flex-col items-center p-8 rounded-lg bg-white dark:bg-zinc-800 shadow-lg max-w-md text-center">
<IconLoader2 className="animate-spin h-12 w-12 mb-4" strokeWidth={1.5} />
<h3 className="text-lg font-medium text-zinc-900 dark:text-zinc-50 mb-2">
{loadingMessage}
</h3>
<p className="text-sm text-zinc-500 dark:text-zinc-400">
This may take a moment depending on the collection size
</p>
</div>
</div>
</Modal>
);
};
return (
<>
{isLoading && <FullscreenLoader />}
{!isLoading && (
<Modal size="sm" title="Import Collection" hideFooter={true} handleCancel={onClose}>
<div className="flex flex-col">
<h3 className="text-sm">Select the type of your existing collection :</h3>
<div className="mt-4 grid grid-rows-2 grid-flow-col gap-2">
<CollectionButton onClick={handleImportBrunoCollection}>Bruno Collection</CollectionButton>
<CollectionButton onClick={handleImportPostmanCollection}>Postman Collection</CollectionButton>
<CollectionButton onClick={handleImportInsomniaCollection}>Insomnia Collection</CollectionButton>
<CollectionButton onClick={handleImportOpenapiCollection}>OpenAPI V3 Spec</CollectionButton>
</div>
</div>
</Modal>
)}
</>
);
};

View File

@@ -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 };

View File

@@ -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",

View File

@@ -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: '.'
})
]
}

View File

@@ -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');
}
};

View File

@@ -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';
export { default as insomniaToBruno } from './insomnia/insomnia-to-bruno.js';
export { default as postmanTranslation } from './postman/postman-translations.js';

View File

@@ -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;

View File

@@ -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;
}
}
};

View File

@@ -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;

View File

@@ -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

View File

@@ -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 });
}
});

View File

@@ -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',

View File

@@ -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',

View File

@@ -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);
});
});

View File

@@ -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);

View File

@@ -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',

View File

@@ -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);
});

View File

@@ -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");
`);
})
});

View File

@@ -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");');
});
});

View File

@@ -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");');
});
});

View File

@@ -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);');
});
});

View File

@@ -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")');
});
});

View File

@@ -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);');
});
});

View File

@@ -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);');
});
});

View File

@@ -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");');
});
});

View File

@@ -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);
}
`)
})
})

View File

@@ -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));');
});
});

View File

@@ -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);');
});
});

View File

@@ -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"));');
});
});

View File

@@ -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');
});
});

View File

@@ -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",

View File

@@ -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) => {

View File

@@ -28,4 +28,4 @@
"requestType": "http",
"requestUrl": "http://localhost:6000"
}
}
}

View File

@@ -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")
})
}