mirror of
https://github.com/usebruno/bruno.git
synced 2026-06-11 09:51:30 +00:00
Feature: postman to bru translator (#4534)
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -46,4 +46,5 @@ yarn-error.log*
|
||||
|
||||
#dev editor
|
||||
bruno.iml
|
||||
.idea
|
||||
.idea
|
||||
.vscode
|
||||
397
package-lock.json
generated
397
package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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'
|
||||
}
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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: '.'
|
||||
})
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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';
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
680
packages/bruno-converters/src/utils/jscode-shift-translator.js
Normal file
680
packages/bruno-converters/src/utils/jscode-shift-translator.js
Normal 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;
|
||||
@@ -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
|
||||
@@ -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 });
|
||||
}
|
||||
});
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
|
||||
@@ -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");
|
||||
`);
|
||||
})
|
||||
});
|
||||
@@ -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");');
|
||||
});
|
||||
});
|
||||
@@ -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");');
|
||||
});
|
||||
});
|
||||
@@ -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);');
|
||||
});
|
||||
});
|
||||
@@ -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")');
|
||||
});
|
||||
});
|
||||
@@ -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);');
|
||||
});
|
||||
});
|
||||
@@ -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);');
|
||||
});
|
||||
});
|
||||
@@ -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");');
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
@@ -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));');
|
||||
});
|
||||
});
|
||||
@@ -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);');
|
||||
});
|
||||
});
|
||||
@@ -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"));');
|
||||
});
|
||||
});
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
@@ -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",
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -28,4 +28,4 @@
|
||||
"requestType": "http",
|
||||
"requestUrl": "http://localhost:6000"
|
||||
}
|
||||
}
|
||||
}
|
||||
22
packages/bruno-tests/collection/echo/echo headers.bru
Normal file
22
packages/bruno-tests/collection/echo/echo headers.bru
Normal 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")
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user