mirror of
https://github.com/usebruno/bruno.git
synced 2025-05-05 15:32:58 +00:00
Feat: Standalone Package to convert to Bruno collection (#2341)
added bruno-converters package --------- Co-authored-by: lohit <lohit@usebruno.com>
This commit is contained in:
parent
5dc7f1ae2f
commit
95af071f09
24689
package-lock.json
generated
24689
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -6,6 +6,7 @@
|
||||
"packages/bruno-electron",
|
||||
"packages/bruno-cli",
|
||||
"packages/bruno-common",
|
||||
"packages/bruno-converters",
|
||||
"packages/bruno-schema",
|
||||
"packages/bruno-query",
|
||||
"packages/bruno-js",
|
||||
@ -38,6 +39,7 @@
|
||||
"dev:electron": "npm run dev --workspace=packages/bruno-electron",
|
||||
"dev:electron:debug": "npm run debug --workspace=packages/bruno-electron",
|
||||
"build:bruno-common": "npm run build --workspace=packages/bruno-common",
|
||||
"build:bruno-converters": "npm run build --workspace=packages/bruno-converters",
|
||||
"build:bruno-query": "npm run build --workspace=packages/bruno-query",
|
||||
"build:graphql-docs": "npm run build --workspace=packages/bruno-graphql-docs",
|
||||
"build:electron": "node ./scripts/build-electron.js",
|
||||
|
22
packages/bruno-converters/.gitignore
vendored
Normal file
22
packages/bruno-converters/.gitignore
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
# dependencies
|
||||
node_modules
|
||||
yarn.lock
|
||||
pnpm-lock.yaml
|
||||
package-lock.json
|
||||
.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
coverage
|
||||
|
||||
# production
|
||||
dist
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
3
packages/bruno-converters/babel.config.js
Normal file
3
packages/bruno-converters/babel.config.js
Normal file
@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
presets: [['@babel/preset-env', { targets: { node: 'current' } }]],
|
||||
};
|
6
packages/bruno-converters/jest.config.js
Normal file
6
packages/bruno-converters/jest.config.js
Normal file
@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
transform: {
|
||||
'^.+\\.js$': 'babel-jest',
|
||||
},
|
||||
setupFiles: ['<rootDir>/jest.setup.js'],
|
||||
};
|
11
packages/bruno-converters/jest.setup.js
Normal file
11
packages/bruno-converters/jest.setup.js
Normal file
@ -0,0 +1,11 @@
|
||||
// Mock the uuid function
|
||||
jest.mock('./src/common', () => {
|
||||
// Import the original module to keep other functions intact
|
||||
const originalModule = jest.requireActual('./src/common');
|
||||
|
||||
return {
|
||||
__esModule: true, // Use this property to indicate it's an ES module
|
||||
...originalModule,
|
||||
uuid: jest.fn(() => 'mockeduuidvalue123456'), // Mock uuid to return a fixed value
|
||||
};
|
||||
});
|
21
packages/bruno-converters/license.md
Normal file
21
packages/bruno-converters/license.md
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 Anoop M D, Anusree P S and Contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
45
packages/bruno-converters/package.json
Normal file
45
packages/bruno-converters/package.json
Normal file
@ -0,0 +1,45 @@
|
||||
{
|
||||
"name": "@usebruno/converters",
|
||||
"version": "0.1.0",
|
||||
"license": "MIT",
|
||||
"main": "dist/cjs/index.js",
|
||||
"module": "dist/esm/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"files": [
|
||||
"dist",
|
||||
"src",
|
||||
"package.json"
|
||||
],
|
||||
"scripts": {
|
||||
"clean": "rimraf dist",
|
||||
"test": "jest --colors --collectCoverage",
|
||||
"prebuild": "npm run clean",
|
||||
"build": "rollup -c",
|
||||
"watch": "rollup -c -w",
|
||||
"prepack": "npm run test && npm run build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@usebruno/schema": "^0.7.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"lodash": "^4.17.21",
|
||||
"nanoid": "3.3.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.25.2",
|
||||
"@babel/preset-env": "^7.25.4",
|
||||
"@rollup/plugin-alias": "^5.1.0",
|
||||
"@rollup/plugin-commonjs": "^23.0.2",
|
||||
"@rollup/plugin-node-resolve": "^15.0.1",
|
||||
"@rollup/plugin-typescript": "^9.0.2",
|
||||
"babel-jest": "^29.7.0",
|
||||
"rimraf": "^5.0.7",
|
||||
"rollup": "3.2.5",
|
||||
"rollup-plugin-dts": "^5.0.0",
|
||||
"rollup-plugin-peer-deps-external": "^2.2.4",
|
||||
"rollup-plugin-terser": "^7.0.2",
|
||||
"typescript": "^4.8.4"
|
||||
},
|
||||
"overrides": {
|
||||
"rollup": "3.2.5"
|
||||
}
|
||||
}
|
58
packages/bruno-converters/readme.md
Normal file
58
packages/bruno-converters/readme.md
Normal file
@ -0,0 +1,58 @@
|
||||
# bruno-converters
|
||||
|
||||
The converters package is responsible for converting collections from one format to a Bruno collection.
|
||||
It can be used as a standalone package or as a part of the Bruno framework.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npm install @bruno-converters
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Convert Postman collection to Bruno collection
|
||||
|
||||
```javascript
|
||||
const { importPostmanCollection, exportBrunoCollection } = require('@usebruno/converters');
|
||||
|
||||
// Convert Postman collection to Bruno collection
|
||||
const postmanCollectionFile = './example.postman_collection.json';
|
||||
const brunoCollection = await importPostmanCollection(postmanCollectionFile, {
|
||||
enablePostmanTranslations: { enabled: true }
|
||||
});
|
||||
|
||||
// Export Bruno collection as a JSON file
|
||||
exportBrunoCollection(brunoCollection);
|
||||
```
|
||||
|
||||
### Convert Postman Environment to Bruno Environment
|
||||
|
||||
```javascript
|
||||
const { importPostmanEnvironment } = require('@usebruno/converters');
|
||||
|
||||
const postmanEnvFile = './example.postman_environment.json';
|
||||
const brunoEnvironment = await importPostmanEnvironment(postmanEnvFile);
|
||||
```
|
||||
|
||||
### Convert Insomnia collection to Bruno collection
|
||||
|
||||
```javascript
|
||||
import { importInsomniaCollection, exportBrunoCollection } from '@usebruno/converters';
|
||||
|
||||
const insomniaCollectionFile = './example.insomnia.json';
|
||||
const brunoCollection = await importInsomniaCollection(insomniaCollectionFile);
|
||||
|
||||
exportBrunoCollection(brunoCollection);
|
||||
```
|
||||
|
||||
### Convert OpenAPI specification to Bruno collection
|
||||
|
||||
```javascript
|
||||
import { importOpenAPICollection, exportBrunoCollection } from '@usebruno/converters';
|
||||
|
||||
const openAPIFile = './example.openapi.yaml';
|
||||
const brunoCollection = await importOpenAPICollection(openAPIFile);
|
||||
|
||||
exportBrunoCollection(brunoCollection);
|
||||
```
|
38
packages/bruno-converters/rollup.config.js
Normal file
38
packages/bruno-converters/rollup.config.js
Normal file
@ -0,0 +1,38 @@
|
||||
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 packageJson = require('./package.json');
|
||||
const alias = require('@rollup/plugin-alias');
|
||||
const path = require('path');
|
||||
|
||||
module.exports = [
|
||||
{
|
||||
input: 'src/index.js',
|
||||
output: [
|
||||
{
|
||||
file: packageJson.main,
|
||||
format: 'cjs',
|
||||
sourcemap: true
|
||||
},
|
||||
{
|
||||
file: packageJson.module,
|
||||
format: 'esm',
|
||||
sourcemap: true
|
||||
}
|
||||
],
|
||||
plugins: [
|
||||
peerDepsExternal(),
|
||||
nodeResolve({
|
||||
preferBuiltins: true,
|
||||
extensions: ['.js', '.css'] // Resolve .js files
|
||||
}),
|
||||
commonjs(),
|
||||
terser(),
|
||||
alias({
|
||||
entries: [{ find: 'src', replacement: path.resolve(__dirname, 'src') }]
|
||||
})
|
||||
]
|
||||
}
|
||||
];
|
87
packages/bruno-converters/src/collections/export.js
Normal file
87
packages/bruno-converters/src/collections/export.js
Normal file
@ -0,0 +1,87 @@
|
||||
import get from 'lodash/get';
|
||||
import each from 'lodash/each';
|
||||
import { saveFile } from '../common/file';
|
||||
|
||||
export const deleteUidsInItems = (items) => {
|
||||
each(items, (item) => {
|
||||
delete item.uid;
|
||||
|
||||
if (['http-request', 'graphql-request'].includes(item.type)) {
|
||||
each(get(item, 'request.headers'), (header) => delete header.uid);
|
||||
each(get(item, 'request.params'), (param) => delete param.uid);
|
||||
each(get(item, 'request.vars.req'), (v) => delete v.uid);
|
||||
each(get(item, 'request.vars.res'), (v) => delete v.uid);
|
||||
each(get(item, 'request.vars.assertions'), (a) => delete a.uid);
|
||||
each(get(item, 'request.body.multipartForm'), (param) => delete param.uid);
|
||||
each(get(item, 'request.body.formUrlEncoded'), (param) => delete param.uid);
|
||||
}
|
||||
|
||||
if (item.items && item.items.length) {
|
||||
deleteUidsInItems(item.items);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Some of the models in the app are not consistent with the Collection Json format
|
||||
* This function is used to transform the models to the Collection Json format
|
||||
*/
|
||||
export const transformItem = (items = []) => {
|
||||
each(items, (item) => {
|
||||
if (['http-request', 'graphql-request'].includes(item.type)) {
|
||||
item.request.query = item.request.params;
|
||||
delete item.request.params;
|
||||
|
||||
if (item.type === 'graphql-request') {
|
||||
item.type = 'graphql';
|
||||
}
|
||||
|
||||
if (item.type === 'http-request') {
|
||||
item.type = 'http';
|
||||
}
|
||||
}
|
||||
|
||||
if (item.items && item.items.length) {
|
||||
transformItem(item.items);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const deleteUidsInEnvs = (envs) => {
|
||||
each(envs, (env) => {
|
||||
delete env.uid;
|
||||
each(env.variables, (variable) => delete variable.uid);
|
||||
});
|
||||
};
|
||||
|
||||
export const deleteSecretsInEnvs = (envs) => {
|
||||
each(envs, (env) => {
|
||||
each(env.variables, (variable) => {
|
||||
if (variable.secret) {
|
||||
variable.value = '';
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const exportCollection = (collection, file) => {
|
||||
// delete uids
|
||||
delete collection.uid;
|
||||
|
||||
// delete process variables
|
||||
delete collection?.processEnvVariables;
|
||||
|
||||
deleteUidsInItems(collection.items);
|
||||
deleteUidsInEnvs(collection.environments);
|
||||
deleteSecretsInEnvs(collection.environments);
|
||||
transformItem(collection.items);
|
||||
|
||||
const fileName = file || `${collection.name}.json`;
|
||||
const fileBlob = JSON.stringify(collection, null, 2);
|
||||
|
||||
saveFile(fileBlob, fileName).then(() => {
|
||||
console.log(`Collection exported as ${fileName}`);
|
||||
});
|
||||
};
|
||||
|
||||
export default exportCollection;
|
867
packages/bruno-converters/src/collections/index.js
Normal file
867
packages/bruno-converters/src/collections/index.js
Normal file
@ -0,0 +1,867 @@
|
||||
import get from 'lodash/get';
|
||||
import each from 'lodash/each';
|
||||
import find from 'lodash/find';
|
||||
import findIndex from 'lodash/findIndex';
|
||||
import isString from 'lodash/isString';
|
||||
import map from 'lodash/map';
|
||||
import filter from 'lodash/filter';
|
||||
import sortBy from 'lodash/sortBy';
|
||||
import isEqual from 'lodash/isEqual';
|
||||
import cloneDeep from 'lodash/cloneDeep';
|
||||
import { uuid } from '../common';
|
||||
import path from 'path';
|
||||
import slash from '../common/slash';
|
||||
|
||||
const replaceTabsWithSpaces = (str, numSpaces = 2) => {
|
||||
if (!str || !str.length || !isString(str)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return str.replaceAll('\t', ' '.repeat(numSpaces));
|
||||
};
|
||||
|
||||
export const addDepth = (items = []) => {
|
||||
const depth = (itms, initialDepth) => {
|
||||
each(itms, (i) => {
|
||||
i.depth = initialDepth;
|
||||
|
||||
if (i.items && i.items.length) {
|
||||
depth(i.items, initialDepth + 1);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
depth(items, 1);
|
||||
};
|
||||
|
||||
export const collapseCollection = (collection) => {
|
||||
collection.collapsed = true;
|
||||
|
||||
const collapseItem = (items) => {
|
||||
each(items, (i) => {
|
||||
i.collapsed = true;
|
||||
|
||||
if (i.items && i.items.length) {
|
||||
collapseItem(i.items);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
collapseItem(collection.items, 1);
|
||||
};
|
||||
|
||||
export const sortItems = (collection) => {
|
||||
const sort = (obj) => {
|
||||
if (obj.items && obj.items.length) {
|
||||
obj.items = sortBy(obj.items, 'type');
|
||||
}
|
||||
|
||||
each(obj.items, (i) => sort(i));
|
||||
};
|
||||
|
||||
sort(collection);
|
||||
};
|
||||
|
||||
export const flattenItems = (items = []) => {
|
||||
const flattenedItems = [];
|
||||
|
||||
const flatten = (itms, flattened) => {
|
||||
each(itms, (i) => {
|
||||
flattened.push(i);
|
||||
|
||||
if (i.items && i.items.length) {
|
||||
flatten(i.items, flattened);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
flatten(items, flattenedItems);
|
||||
|
||||
return flattenedItems;
|
||||
};
|
||||
|
||||
export const findItem = (items = [], itemUid) => {
|
||||
return find(items, (i) => i.uid === itemUid);
|
||||
};
|
||||
|
||||
export const findCollectionByUid = (collections, collectionUid) => {
|
||||
return find(collections, (c) => c.uid === collectionUid);
|
||||
};
|
||||
|
||||
export const findCollectionByPathname = (collections, pathname) => {
|
||||
return find(collections, (c) => c.pathname === pathname);
|
||||
};
|
||||
|
||||
export const findCollectionByItemUid = (collections, itemUid) => {
|
||||
return find(collections, (c) => {
|
||||
return findItemInCollection(c, itemUid);
|
||||
});
|
||||
};
|
||||
|
||||
export const findItemByPathname = (items = [], pathname) => {
|
||||
return find(items, (i) => slash(i.pathname) === slash(pathname));
|
||||
};
|
||||
|
||||
export const findItemInCollectionByPathname = (collection, pathname) => {
|
||||
let flattenedItems = flattenItems(collection.items);
|
||||
|
||||
return findItemByPathname(flattenedItems, pathname);
|
||||
};
|
||||
|
||||
export const findItemInCollection = (collection, itemUid) => {
|
||||
let flattenedItems = flattenItems(collection.items);
|
||||
|
||||
return findItem(flattenedItems, itemUid);
|
||||
};
|
||||
|
||||
export const findParentItemInCollection = (collection, itemUid) => {
|
||||
let flattenedItems = flattenItems(collection.items);
|
||||
|
||||
return find(flattenedItems, (item) => {
|
||||
return item.items && find(item.items, (i) => i.uid === itemUid);
|
||||
});
|
||||
};
|
||||
|
||||
export const recursivelyGetAllItemUids = (items = []) => {
|
||||
let flattenedItems = flattenItems(items);
|
||||
|
||||
return map(flattenedItems, (i) => i.uid);
|
||||
};
|
||||
|
||||
export const findEnvironmentInCollection = (collection, envUid) => {
|
||||
return find(collection.environments, (e) => e.uid === envUid);
|
||||
};
|
||||
|
||||
export const moveCollectionItem = (collection, draggedItem, targetItem) => {
|
||||
let draggedItemParent = findParentItemInCollection(collection, draggedItem.uid);
|
||||
|
||||
if (draggedItemParent) {
|
||||
draggedItemParent.items = sortBy(draggedItemParent.items, (item) => item.seq);
|
||||
draggedItemParent.items = filter(draggedItemParent.items, (i) => i.uid !== draggedItem.uid);
|
||||
draggedItem.pathname = path.join(draggedItemParent.pathname, draggedItem.filename);
|
||||
} else {
|
||||
collection.items = sortBy(collection.items, (item) => item.seq);
|
||||
collection.items = filter(collection.items, (i) => i.uid !== draggedItem.uid);
|
||||
}
|
||||
|
||||
if (targetItem.type === 'folder') {
|
||||
targetItem.items = sortBy(targetItem.items || [], (item) => item.seq);
|
||||
targetItem.items.push(draggedItem);
|
||||
draggedItem.pathname = path.join(targetItem.pathname, draggedItem.filename);
|
||||
} else {
|
||||
let targetItemParent = findParentItemInCollection(collection, targetItem.uid);
|
||||
|
||||
if (targetItemParent) {
|
||||
targetItemParent.items = sortBy(targetItemParent.items, (item) => item.seq);
|
||||
let targetItemIndex = findIndex(targetItemParent.items, (i) => i.uid === targetItem.uid);
|
||||
targetItemParent.items.splice(targetItemIndex + 1, 0, draggedItem);
|
||||
draggedItem.pathname = path.join(targetItemParent.pathname, draggedItem.filename);
|
||||
} else {
|
||||
collection.items = sortBy(collection.items, (item) => item.seq);
|
||||
let targetItemIndex = findIndex(collection.items, (i) => i.uid === targetItem.uid);
|
||||
collection.items.splice(targetItemIndex + 1, 0, draggedItem);
|
||||
draggedItem.pathname = path.join(collection.pathname, draggedItem.filename);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const moveCollectionItemToRootOfCollection = (collection, draggedItem) => {
|
||||
let draggedItemParent = findParentItemInCollection(collection, draggedItem.uid);
|
||||
|
||||
// If the dragged item is already at the root of the collection, do nothing
|
||||
if (!draggedItemParent) {
|
||||
return;
|
||||
}
|
||||
|
||||
draggedItemParent.items = sortBy(draggedItemParent.items, (item) => item.seq);
|
||||
draggedItemParent.items = filter(draggedItemParent.items, (i) => i.uid !== draggedItem.uid);
|
||||
collection.items = sortBy(collection.items, (item) => item.seq);
|
||||
collection.items.push(draggedItem);
|
||||
if (draggedItem.type == 'folder') {
|
||||
draggedItem.pathname = path.join(collection.pathname, draggedItem.name);
|
||||
} else {
|
||||
draggedItem.pathname = path.join(collection.pathname, draggedItem.filename);
|
||||
}
|
||||
};
|
||||
|
||||
export const getItemsToResequence = (parent, collection) => {
|
||||
let itemsToResequence = [];
|
||||
|
||||
if (!parent) {
|
||||
let index = 1;
|
||||
each(collection.items, (item) => {
|
||||
if (isItemARequest(item)) {
|
||||
itemsToResequence.push({
|
||||
pathname: item.pathname,
|
||||
seq: index++
|
||||
});
|
||||
}
|
||||
});
|
||||
return itemsToResequence;
|
||||
}
|
||||
|
||||
if (parent.items && parent.items.length) {
|
||||
let index = 1;
|
||||
each(parent.items, (item) => {
|
||||
if (isItemARequest(item)) {
|
||||
itemsToResequence.push({
|
||||
pathname: item.pathname,
|
||||
seq: index++
|
||||
});
|
||||
}
|
||||
});
|
||||
return itemsToResequence;
|
||||
}
|
||||
|
||||
return itemsToResequence;
|
||||
};
|
||||
|
||||
export const transformCollectionToSaveToExportAsFile = (collection, options = {}) => {
|
||||
const copyHeaders = (headers) => {
|
||||
return map(headers, (header) => {
|
||||
return {
|
||||
uid: header.uid,
|
||||
name: header.name,
|
||||
value: header.value,
|
||||
description: header.description,
|
||||
enabled: header.enabled
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const copyParams = (params) => {
|
||||
return map(params, (param) => {
|
||||
return {
|
||||
uid: param.uid,
|
||||
name: param.name,
|
||||
value: param.value,
|
||||
description: param.description,
|
||||
type: param.type,
|
||||
enabled: param.enabled
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const copyFormUrlEncodedParams = (params = []) => {
|
||||
return map(params, (param) => {
|
||||
return {
|
||||
uid: param.uid,
|
||||
name: param.name,
|
||||
value: param.value,
|
||||
description: param.description,
|
||||
enabled: param.enabled
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const copyMultipartFormParams = (params = []) => {
|
||||
return map(params, (param) => {
|
||||
return {
|
||||
uid: param.uid,
|
||||
type: param.type,
|
||||
name: param.name,
|
||||
value: param.value,
|
||||
description: param.description,
|
||||
enabled: param.enabled
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const copyItems = (sourceItems, destItems) => {
|
||||
each(sourceItems, (si) => {
|
||||
if (!isItemAFolder(si) && !isItemARequest(si) && si.type !== 'js') {
|
||||
return;
|
||||
}
|
||||
|
||||
const di = {
|
||||
uid: si.uid,
|
||||
type: si.type,
|
||||
name: si.name,
|
||||
seq: si.seq
|
||||
};
|
||||
|
||||
if (si.request) {
|
||||
di.request = {
|
||||
url: si.request.url,
|
||||
method: si.request.method,
|
||||
headers: copyHeaders(si.request.headers),
|
||||
params: copyParams(si.request.params),
|
||||
body: {
|
||||
mode: si.request.body.mode,
|
||||
json: si.request.body.json,
|
||||
text: si.request.body.text,
|
||||
xml: si.request.body.xml,
|
||||
graphql: si.request.body.graphql,
|
||||
sparql: si.request.body.sparql,
|
||||
formUrlEncoded: copyFormUrlEncodedParams(si.request.body.formUrlEncoded),
|
||||
multipartForm: copyMultipartFormParams(si.request.body.multipartForm)
|
||||
},
|
||||
script: si.request.script,
|
||||
vars: si.request.vars,
|
||||
assertions: si.request.assertions,
|
||||
tests: si.request.tests
|
||||
};
|
||||
|
||||
// Handle auth object dynamically
|
||||
di.request.auth = {
|
||||
mode: get(si.request, 'auth.mode', 'none')
|
||||
};
|
||||
|
||||
switch (di.request.auth.mode) {
|
||||
case 'awsv4':
|
||||
di.request.auth.awsv4 = {
|
||||
accessKeyId: get(si.request, 'auth.awsv4.accessKeyId', ''),
|
||||
secretAccessKey: get(si.request, 'auth.awsv4.secretAccessKey', ''),
|
||||
sessionToken: get(si.request, 'auth.awsv4.sessionToken', ''),
|
||||
service: get(si.request, 'auth.awsv4.service', ''),
|
||||
region: get(si.request, 'auth.awsv4.region', ''),
|
||||
profileName: get(si.request, 'auth.awsv4.profileName', '')
|
||||
};
|
||||
break;
|
||||
case 'basic':
|
||||
di.request.auth.basic = {
|
||||
username: get(si.request, 'auth.basic.username', ''),
|
||||
password: get(si.request, 'auth.basic.password', '')
|
||||
};
|
||||
break;
|
||||
case 'bearer':
|
||||
di.request.auth.bearer = {
|
||||
token: get(si.request, 'auth.bearer.token', '')
|
||||
};
|
||||
break;
|
||||
case 'digest':
|
||||
di.request.auth.digest = {
|
||||
username: get(si.request, 'auth.digest.username', ''),
|
||||
password: get(si.request, 'auth.digest.password', '')
|
||||
};
|
||||
break;
|
||||
case 'oauth2':
|
||||
let grantType = get(si.request, 'auth.oauth2.grantType', '');
|
||||
switch (grantType) {
|
||||
case 'password':
|
||||
di.request.auth.oauth2 = {
|
||||
grantType: grantType,
|
||||
accessTokenUrl: get(si.request, 'auth.oauth2.accessTokenUrl', ''),
|
||||
username: get(si.request, 'auth.oauth2.username', ''),
|
||||
password: get(si.request, 'auth.oauth2.password', ''),
|
||||
clientId: get(si.request, 'auth.oauth2.clientId', ''),
|
||||
clientSecret: get(si.request, 'auth.oauth2.clientSecret', ''),
|
||||
scope: get(si.request, 'auth.oauth2.scope', '')
|
||||
};
|
||||
break;
|
||||
case 'authorization_code':
|
||||
di.request.auth.oauth2 = {
|
||||
grantType: grantType,
|
||||
callbackUrl: get(si.request, 'auth.oauth2.callbackUrl', ''),
|
||||
authorizationUrl: get(si.request, 'auth.oauth2.authorizationUrl', ''),
|
||||
accessTokenUrl: get(si.request, 'auth.oauth2.accessTokenUrl', ''),
|
||||
clientId: get(si.request, 'auth.oauth2.clientId', ''),
|
||||
clientSecret: get(si.request, 'auth.oauth2.clientSecret', ''),
|
||||
scope: get(si.request, 'auth.oauth2.scope', ''),
|
||||
pkce: get(si.request, 'auth.oauth2.pkce', false)
|
||||
};
|
||||
break;
|
||||
case 'client_credentials':
|
||||
di.request.auth.oauth2 = {
|
||||
grantType: grantType,
|
||||
accessTokenUrl: get(si.request, 'auth.oauth2.accessTokenUrl', ''),
|
||||
clientId: get(si.request, 'auth.oauth2.clientId', ''),
|
||||
clientSecret: get(si.request, 'auth.oauth2.clientSecret', ''),
|
||||
scope: get(si.request, 'auth.oauth2.scope', '')
|
||||
};
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (di.request.body.mode === 'json') {
|
||||
di.request.body.json = replaceTabsWithSpaces(di.request.body.json);
|
||||
}
|
||||
}
|
||||
|
||||
if (si.type == 'folder' && si?.root) {
|
||||
di.root = {
|
||||
request: {}
|
||||
};
|
||||
|
||||
let { request, meta } = si?.root || {};
|
||||
let { headers, script = {}, vars = {}, tests } = request || {};
|
||||
|
||||
// folder level headers
|
||||
if (headers?.length) {
|
||||
di.root.request.headers = headers;
|
||||
}
|
||||
// folder level script
|
||||
if (Object.keys(script)?.length) {
|
||||
di.root.request.script = {};
|
||||
if (script?.req?.length) {
|
||||
di.root.request.script.req = script?.req;
|
||||
}
|
||||
if (script?.res?.length) {
|
||||
di.root.request.script.res = script?.res;
|
||||
}
|
||||
}
|
||||
// folder level vars
|
||||
if (Object.keys(vars)?.length) {
|
||||
di.root.request.vars = {};
|
||||
if (vars?.req?.length) {
|
||||
di.root.request.vars.req = vars?.req;
|
||||
}
|
||||
if (vars?.res?.length) {
|
||||
di.root.request.vars.res = vars?.res;
|
||||
}
|
||||
}
|
||||
// folder level tests
|
||||
if (tests?.length) {
|
||||
di.root.request.tests = tests;
|
||||
}
|
||||
|
||||
if (meta?.name) {
|
||||
di.root.meta = {};
|
||||
di.root.meta.name = meta?.name;
|
||||
}
|
||||
if (!Object.keys(di.root.request)?.length) {
|
||||
delete di.root.request;
|
||||
}
|
||||
if (!Object.keys(di.root)?.length) {
|
||||
delete di.root;
|
||||
}
|
||||
}
|
||||
|
||||
if (si.type === 'js') {
|
||||
di.fileContent = si.raw;
|
||||
}
|
||||
|
||||
destItems.push(di);
|
||||
|
||||
if (si.items && si.items.length) {
|
||||
di.items = [];
|
||||
copyItems(si.items, di.items);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const collectionToSave = {};
|
||||
collectionToSave.name = collection.name;
|
||||
collectionToSave.uid = collection.uid;
|
||||
|
||||
// todo: move this to the place where collection gets created
|
||||
collectionToSave.version = '1';
|
||||
collectionToSave.items = [];
|
||||
collectionToSave.activeEnvironmentUid = collection.activeEnvironmentUid;
|
||||
collectionToSave.environments = collection.environments || [];
|
||||
|
||||
collectionToSave.root = {
|
||||
request: {}
|
||||
};
|
||||
|
||||
let { request, docs, meta } = collection?.root || {};
|
||||
let { auth, headers, script = {}, vars = {}, tests } = request || {};
|
||||
|
||||
// collection level auth
|
||||
if (auth?.mode) {
|
||||
collectionToSave.root.request.auth = auth;
|
||||
}
|
||||
// collection level headers
|
||||
if (headers?.length) {
|
||||
collectionToSave.root.request.headers = headers;
|
||||
}
|
||||
// collection level script
|
||||
if (Object.keys(script)?.length) {
|
||||
collectionToSave.root.request.script = {};
|
||||
if (script?.req?.length) {
|
||||
collectionToSave.root.request.script.req = script?.req;
|
||||
}
|
||||
if (script?.res?.length) {
|
||||
collectionToSave.root.request.script.res = script?.res;
|
||||
}
|
||||
}
|
||||
// collection level vars
|
||||
if (Object.keys(vars)?.length) {
|
||||
collectionToSave.root.request.vars = {};
|
||||
if (vars?.req?.length) {
|
||||
collectionToSave.root.request.vars.req = vars?.req;
|
||||
}
|
||||
if (vars?.res?.length) {
|
||||
collectionToSave.root.request.vars.res = vars?.res;
|
||||
}
|
||||
}
|
||||
// collection level tests
|
||||
if (tests?.length) {
|
||||
collectionToSave.root.request.tests = tests;
|
||||
}
|
||||
// collection level docs
|
||||
if (docs?.length) {
|
||||
collectionToSave.root.docs = docs;
|
||||
}
|
||||
if (meta?.name) {
|
||||
collectionToSave.root.meta = {};
|
||||
collectionToSave.root.meta.name = meta?.name;
|
||||
}
|
||||
if (!Object.keys(collectionToSave.root.request)?.length) {
|
||||
delete collectionToSave.root.request;
|
||||
}
|
||||
if (!Object.keys(collectionToSave.root)?.length) {
|
||||
delete collectionToSave.root;
|
||||
}
|
||||
|
||||
collectionToSave.brunoConfig = cloneDeep(collection?.brunoConfig);
|
||||
|
||||
// delete proxy password if present
|
||||
if (collectionToSave?.brunoConfig?.proxy?.auth?.password) {
|
||||
delete collectionToSave.brunoConfig.proxy.auth.password;
|
||||
}
|
||||
|
||||
copyItems(collection.items, collectionToSave.items);
|
||||
return collectionToSave;
|
||||
};
|
||||
|
||||
export const transformRequestToSaveToFilesystem = (item) => {
|
||||
const _item = item.draft ? item.draft : item;
|
||||
const itemToSave = {
|
||||
uid: _item.uid,
|
||||
type: _item.type,
|
||||
name: _item.name,
|
||||
seq: _item.seq,
|
||||
request: {
|
||||
method: _item.request.method,
|
||||
url: _item.request.url,
|
||||
params: [],
|
||||
headers: [],
|
||||
auth: _item.request.auth,
|
||||
body: _item.request.body,
|
||||
script: _item.request.script,
|
||||
vars: _item.request.vars,
|
||||
assertions: _item.request.assertions,
|
||||
tests: _item.request.tests,
|
||||
docs: _item.request.docs
|
||||
}
|
||||
};
|
||||
|
||||
each(_item.request.params, (param) => {
|
||||
itemToSave.request.params.push({
|
||||
uid: param.uid,
|
||||
name: param.name,
|
||||
value: param.value,
|
||||
description: param.description,
|
||||
type: param.type,
|
||||
enabled: param.enabled
|
||||
});
|
||||
});
|
||||
|
||||
each(_item.request.headers, (header) => {
|
||||
itemToSave.request.headers.push({
|
||||
uid: header.uid,
|
||||
name: header.name,
|
||||
value: header.value,
|
||||
description: header.description,
|
||||
enabled: header.enabled
|
||||
});
|
||||
});
|
||||
|
||||
if (itemToSave.request.body.mode === 'json') {
|
||||
itemToSave.request.body = {
|
||||
...itemToSave.request.body,
|
||||
json: replaceTabsWithSpaces(itemToSave.request.body.json)
|
||||
};
|
||||
}
|
||||
|
||||
return itemToSave;
|
||||
};
|
||||
|
||||
// todo: optimize this
|
||||
export const deleteItemInCollection = (itemUid, collection) => {
|
||||
collection.items = filter(collection.items, (i) => i.uid !== itemUid);
|
||||
|
||||
let flattenedItems = flattenItems(collection.items);
|
||||
each(flattenedItems, (i) => {
|
||||
if (i.items && i.items.length) {
|
||||
i.items = filter(i.items, (i) => i.uid !== itemUid);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const deleteItemInCollectionByPathname = (pathname, collection) => {
|
||||
collection.items = filter(collection.items, (i) => i.pathname !== pathname);
|
||||
|
||||
let flattenedItems = flattenItems(collection.items);
|
||||
each(flattenedItems, (i) => {
|
||||
if (i.items && i.items.length) {
|
||||
i.items = filter(i.items, (i) => i.pathname !== pathname);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const isItemARequest = (item) => {
|
||||
return item.hasOwnProperty('request') && ['http-request', 'graphql-request'].includes(item.type) && !item.items;
|
||||
};
|
||||
|
||||
export const isItemAFolder = (item) => {
|
||||
return !item.hasOwnProperty('request') && item.type === 'folder';
|
||||
};
|
||||
|
||||
export const humanizeRequestBodyMode = (mode) => {
|
||||
let label = 'No Body';
|
||||
switch (mode) {
|
||||
case 'json': {
|
||||
label = 'JSON';
|
||||
break;
|
||||
}
|
||||
case 'text': {
|
||||
label = 'TEXT';
|
||||
break;
|
||||
}
|
||||
case 'xml': {
|
||||
label = 'XML';
|
||||
break;
|
||||
}
|
||||
case 'sparql': {
|
||||
label = 'SPARQL';
|
||||
break;
|
||||
}
|
||||
case 'formUrlEncoded': {
|
||||
label = 'Form URL Encoded';
|
||||
break;
|
||||
}
|
||||
case 'multipartForm': {
|
||||
label = 'Multipart Form';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return label;
|
||||
};
|
||||
|
||||
export const humanizeRequestAuthMode = (mode) => {
|
||||
let label = 'No Auth';
|
||||
switch (mode) {
|
||||
case 'inherit': {
|
||||
label = 'Inherit';
|
||||
break;
|
||||
}
|
||||
case 'awsv4': {
|
||||
label = 'AWS Sig V4';
|
||||
break;
|
||||
}
|
||||
case 'basic': {
|
||||
label = 'Basic Auth';
|
||||
break;
|
||||
}
|
||||
case 'bearer': {
|
||||
label = 'Bearer Token';
|
||||
break;
|
||||
}
|
||||
case 'digest': {
|
||||
label = 'Digest Auth';
|
||||
break;
|
||||
}
|
||||
case 'oauth2': {
|
||||
label = 'OAuth 2.0';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return label;
|
||||
};
|
||||
|
||||
export const humanizeGrantType = (mode) => {
|
||||
let label = 'No Auth';
|
||||
switch (mode) {
|
||||
case 'password': {
|
||||
label = 'Password Credentials';
|
||||
break;
|
||||
}
|
||||
case 'authorization_code': {
|
||||
label = 'Authorization Code';
|
||||
break;
|
||||
}
|
||||
case 'client_credentials': {
|
||||
label = 'Client Credentials';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return label;
|
||||
};
|
||||
|
||||
export const refreshUidsInItem = (item) => {
|
||||
item.uid = uuid();
|
||||
|
||||
each(get(item, 'request.headers'), (header) => (header.uid = uuid()));
|
||||
each(get(item, 'request.params'), (param) => (param.uid = uuid()));
|
||||
each(get(item, 'request.body.multipartForm'), (param) => (param.uid = uuid()));
|
||||
each(get(item, 'request.body.formUrlEncoded'), (param) => (param.uid = uuid()));
|
||||
|
||||
return item;
|
||||
};
|
||||
|
||||
export const deleteUidsInItem = (item) => {
|
||||
delete item.uid;
|
||||
const params = get(item, 'request.params', []);
|
||||
const headers = get(item, 'request.headers', []);
|
||||
const bodyFormUrlEncoded = get(item, 'request.body.formUrlEncoded', []);
|
||||
const bodyMultipartForm = get(item, 'request.body.multipartForm', []);
|
||||
|
||||
params.forEach((param) => delete param.uid);
|
||||
headers.forEach((header) => delete header.uid);
|
||||
bodyFormUrlEncoded.forEach((param) => delete param.uid);
|
||||
bodyMultipartForm.forEach((param) => delete param.uid);
|
||||
|
||||
return item;
|
||||
};
|
||||
|
||||
export const areItemsTheSameExceptSeqUpdate = (_item1, _item2) => {
|
||||
let item1 = cloneDeep(_item1);
|
||||
let item2 = cloneDeep(_item2);
|
||||
|
||||
// remove seq from both items
|
||||
delete item1.seq;
|
||||
delete item2.seq;
|
||||
|
||||
// remove draft from both items
|
||||
delete item1.draft;
|
||||
delete item2.draft;
|
||||
|
||||
// get projection of both items
|
||||
item1 = transformRequestToSaveToFilesystem(item1);
|
||||
item2 = transformRequestToSaveToFilesystem(item2);
|
||||
|
||||
// delete uids from both items
|
||||
deleteUidsInItem(item1);
|
||||
deleteUidsInItem(item2);
|
||||
|
||||
return isEqual(item1, item2);
|
||||
};
|
||||
|
||||
export const getDefaultRequestPaneTab = (item) => {
|
||||
if (item.type === 'http-request') {
|
||||
return 'params';
|
||||
}
|
||||
|
||||
if (item.type === 'graphql-request') {
|
||||
return 'query';
|
||||
}
|
||||
};
|
||||
|
||||
export const getEnvironmentVariables = (collection) => {
|
||||
let variables = {};
|
||||
if (collection) {
|
||||
const environment = findEnvironmentInCollection(collection, collection.activeEnvironmentUid);
|
||||
if (environment) {
|
||||
each(environment.variables, (variable) => {
|
||||
if (variable.name && variable.value && variable.enabled) {
|
||||
variables[variable.name] = variable.value;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return variables;
|
||||
};
|
||||
|
||||
const getPathParams = (item) => {
|
||||
let pathParams = {};
|
||||
if (item && item.request && item.request.params) {
|
||||
item.request.params.forEach((param) => {
|
||||
if (param.type === 'path' && param.name && param.value) {
|
||||
pathParams[param.name] = param.value;
|
||||
}
|
||||
});
|
||||
}
|
||||
return pathParams;
|
||||
};
|
||||
|
||||
export const getTotalRequestCountInCollection = (collection) => {
|
||||
let count = 0;
|
||||
each(collection.items, (item) => {
|
||||
if (isItemARequest(item)) {
|
||||
count++;
|
||||
} else if (isItemAFolder(item)) {
|
||||
count += getTotalRequestCountInCollection(item);
|
||||
}
|
||||
});
|
||||
|
||||
return count;
|
||||
};
|
||||
|
||||
export const getAllVariables = (collection, item) => {
|
||||
const envVariables = getEnvironmentVariables(collection);
|
||||
const requestTreePath = getTreePathFromCollectionToItem(collection, item);
|
||||
let { collectionVariables, folderVariables, requestVariables } = mergeVars(collection, requestTreePath);
|
||||
const pathParams = getPathParams(item);
|
||||
|
||||
const { processEnvVariables = {}, runtimeVariables = {} } = collection;
|
||||
|
||||
return {
|
||||
...collectionVariables,
|
||||
...envVariables,
|
||||
...folderVariables,
|
||||
...requestVariables,
|
||||
...runtimeVariables,
|
||||
pathParams: {
|
||||
...pathParams
|
||||
},
|
||||
process: {
|
||||
env: {
|
||||
...processEnvVariables
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export const maskInputValue = (value) => {
|
||||
if (!value || typeof value !== 'string') {
|
||||
return '';
|
||||
}
|
||||
|
||||
return value
|
||||
.split('')
|
||||
.map(() => '*')
|
||||
.join('');
|
||||
};
|
||||
|
||||
const getTreePathFromCollectionToItem = (collection, _item) => {
|
||||
let path = [];
|
||||
let item = findItemInCollection(collection, _item?.uid);
|
||||
while (item) {
|
||||
path.unshift(item);
|
||||
item = findParentItemInCollection(collection, item?.uid);
|
||||
}
|
||||
return path;
|
||||
};
|
||||
|
||||
const mergeVars = (collection, requestTreePath = []) => {
|
||||
let collectionVariables = {};
|
||||
let folderVariables = {};
|
||||
let requestVariables = {};
|
||||
let collectionRequestVars = get(collection, 'root.request.vars.req', []);
|
||||
collectionRequestVars.forEach((_var) => {
|
||||
if (_var.enabled) {
|
||||
collectionVariables[_var.name] = _var.value;
|
||||
}
|
||||
});
|
||||
for (let i of requestTreePath) {
|
||||
if (i.type === 'folder') {
|
||||
let vars = get(i, 'root.request.vars.req', []);
|
||||
vars.forEach((_var) => {
|
||||
if (_var.enabled) {
|
||||
folderVariables[_var.name] = _var.value;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
let vars = get(i, 'request.vars.req', []);
|
||||
vars.forEach((_var) => {
|
||||
if (_var.enabled) {
|
||||
requestVariables[_var.name] = _var.value;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
return {
|
||||
collectionVariables,
|
||||
folderVariables,
|
||||
requestVariables
|
||||
};
|
||||
};
|
122
packages/bruno-converters/src/common/common.js
Normal file
122
packages/bruno-converters/src/common/common.js
Normal file
@ -0,0 +1,122 @@
|
||||
import each from 'lodash/each';
|
||||
import get from 'lodash/get';
|
||||
|
||||
import cloneDeep from 'lodash/cloneDeep';
|
||||
import { uuid, normalizeFileName } from './index.js';
|
||||
import { isItemARequest } from '../collections';
|
||||
import { collectionSchema } from '@usebruno/schema';
|
||||
|
||||
export class BrunoError extends Error {
|
||||
constructor(message, level) {
|
||||
super(message);
|
||||
this.name = 'BrunoError';
|
||||
this.level = level || 'error';
|
||||
}
|
||||
}
|
||||
|
||||
export const validateSchema = (collection = {}) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
collectionSchema
|
||||
.validate(collection)
|
||||
.then(() => resolve(collection))
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
reject(new BrunoError('The Collection file is corrupted'));
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const updateUidsInCollection = (_collection) => {
|
||||
const collection = cloneDeep(_collection);
|
||||
|
||||
collection.uid = uuid();
|
||||
|
||||
const updateItemUids = (items = []) => {
|
||||
each(items, (item) => {
|
||||
item.uid = uuid();
|
||||
|
||||
each(get(item, 'request.headers'), (header) => (header.uid = uuid()));
|
||||
each(get(item, 'request.query'), (param) => (param.uid = uuid()));
|
||||
each(get(item, 'request.params'), (param) => (param.uid = uuid()));
|
||||
each(get(item, 'request.vars.req'), (v) => (v.uid = uuid()));
|
||||
each(get(item, 'request.vars.res'), (v) => (v.uid = uuid()));
|
||||
each(get(item, 'request.assertions'), (a) => (a.uid = uuid()));
|
||||
each(get(item, 'request.body.multipartForm'), (param) => (param.uid = uuid()));
|
||||
each(get(item, 'request.body.formUrlEncoded'), (param) => (param.uid = uuid()));
|
||||
|
||||
if (item.items && item.items.length) {
|
||||
updateItemUids(item.items);
|
||||
}
|
||||
});
|
||||
};
|
||||
updateItemUids(collection.items);
|
||||
|
||||
const updateEnvUids = (envs = []) => {
|
||||
each(envs, (env) => {
|
||||
env.uid = uuid();
|
||||
each(env.variables, (variable) => (variable.uid = uuid()));
|
||||
});
|
||||
};
|
||||
updateEnvUids(collection.environments);
|
||||
|
||||
return collection;
|
||||
};
|
||||
|
||||
// todo
|
||||
// need to eventually get rid of supporting old collection app models
|
||||
// 1. start with making request type a constant fetched from a single place
|
||||
// 2. move references of param and replace it with query inside the app
|
||||
export const transformItemsInCollection = (collection) => {
|
||||
const transformItems = (items = []) => {
|
||||
each(items, (item) => {
|
||||
item.name = normalizeFileName(item.name);
|
||||
|
||||
if (['http', 'graphql'].includes(item.type)) {
|
||||
item.type = `${item.type}-request`;
|
||||
if (item.request.query) {
|
||||
item.request.params = item.request.query;
|
||||
}
|
||||
|
||||
delete item.request.query;
|
||||
|
||||
// from 5 feb 2024, multipartFormData needs to have a type
|
||||
// this was introduced when we added support for file uploads
|
||||
// below logic is to make older collection exports backward compatible
|
||||
let multipartFormData = _.get(item, 'request.body.multipartForm');
|
||||
if (multipartFormData) {
|
||||
_.each(multipartFormData, (form) => {
|
||||
if (!form.type) {
|
||||
form.type = 'text';
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (item.items && item.items.length) {
|
||||
transformItems(item.items);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
transformItems(collection.items);
|
||||
|
||||
return collection;
|
||||
};
|
||||
|
||||
export const hydrateSeqInCollection = (collection) => {
|
||||
const hydrateSeq = (items = []) => {
|
||||
let index = 1;
|
||||
each(items, (item) => {
|
||||
if (isItemARequest(item) && !item.seq) {
|
||||
item.seq = index;
|
||||
index++;
|
||||
}
|
||||
if (item.items && item.items.length) {
|
||||
hydrateSeq(item.items);
|
||||
}
|
||||
});
|
||||
};
|
||||
hydrateSeq(collection.items);
|
||||
|
||||
return collection;
|
||||
};
|
8
packages/bruno-converters/src/common/error.js
Normal file
8
packages/bruno-converters/src/common/error.js
Normal file
@ -0,0 +1,8 @@
|
||||
// levels: 'warning, error'
|
||||
export class BrunoError extends Error {
|
||||
constructor(message, level) {
|
||||
super(message);
|
||||
this.name = 'BrunoError';
|
||||
this.level = level || 'error';
|
||||
}
|
||||
}
|
40
packages/bruno-converters/src/common/file.js
Normal file
40
packages/bruno-converters/src/common/file.js
Normal file
@ -0,0 +1,40 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import jsyaml from 'js-yaml';
|
||||
import { safeParseJSON } from './index';
|
||||
|
||||
export const readFile = async (file) => {
|
||||
try {
|
||||
return await fs.promises.readFile(file, 'utf8');
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
export const parseFile = async (file) => {
|
||||
try {
|
||||
const data = await readFile(file);
|
||||
const ext = path.extname(file).toLowerCase();
|
||||
|
||||
if (ext === '.json') {
|
||||
return safeParseJSON(data);
|
||||
} else if (ext === '.yaml' || ext === '.yml') {
|
||||
return jsyaml.load(data,null);
|
||||
} else {
|
||||
throw new Error('Unsupported file format');
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
export const saveFile = async (data, fileName) => {
|
||||
try {
|
||||
await fs.promises.writeFile(fileName, data);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
throw err;
|
||||
}
|
||||
};
|
160
packages/bruno-converters/src/common/index.js
Normal file
160
packages/bruno-converters/src/common/index.js
Normal file
@ -0,0 +1,160 @@
|
||||
import { customAlphabet } from 'nanoid';
|
||||
import xmlFormat from 'xml-formatter';
|
||||
|
||||
// a customized version of nanoid without using _ and -
|
||||
export const uuid = () => {
|
||||
// https://github.com/ai/nanoid/blob/main/url-alphabet/index.js
|
||||
const urlAlphabet = 'useandom26T198340PX75pxJACKVERYMINDBUSHWOLFGQZbfghjklqvwyzrict';
|
||||
const customNanoId = customAlphabet(urlAlphabet, 21);
|
||||
|
||||
return customNanoId();
|
||||
};
|
||||
|
||||
export const simpleHash = (str) => {
|
||||
let hash = 0;
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
const char = str.charCodeAt(i);
|
||||
hash = (hash << 5) - hash + char;
|
||||
hash &= hash; // Convert to 32bit integer
|
||||
}
|
||||
return new Uint32Array([hash])[0].toString(36);
|
||||
};
|
||||
|
||||
export const waitForNextTick = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
setTimeout(() => resolve(), 0);
|
||||
});
|
||||
};
|
||||
|
||||
export const safeParseJSON = (str) => {
|
||||
if (!str || !str.length || typeof str !== 'string') {
|
||||
return str;
|
||||
}
|
||||
try {
|
||||
return JSON.parse(str);
|
||||
} catch (e) {
|
||||
return str;
|
||||
}
|
||||
};
|
||||
|
||||
export const safeStringifyJSON = (obj, indent = false) => {
|
||||
if (obj === undefined) {
|
||||
return obj;
|
||||
}
|
||||
try {
|
||||
if (indent) {
|
||||
return JSON.stringify(obj, null, 2);
|
||||
}
|
||||
return JSON.stringify(obj);
|
||||
} catch (e) {
|
||||
return obj;
|
||||
}
|
||||
};
|
||||
|
||||
export const convertToCodeMirrorJson = (obj) => {
|
||||
try {
|
||||
return JSON5.stringify(obj).slice(1, -1);
|
||||
} catch (e) {
|
||||
return obj;
|
||||
}
|
||||
};
|
||||
|
||||
export const safeParseXML = (str, options) => {
|
||||
if (!str || !str.length || typeof str !== 'string') {
|
||||
return str;
|
||||
}
|
||||
try {
|
||||
return xmlFormat(str, options);
|
||||
} catch (e) {
|
||||
return str;
|
||||
}
|
||||
};
|
||||
|
||||
// Remove any characters that are not alphanumeric, spaces, hyphens, or underscores
|
||||
export const normalizeFileName = (name) => {
|
||||
if (!name) {
|
||||
return name;
|
||||
}
|
||||
|
||||
const validChars = /[^\w\s-]/g;
|
||||
const formattedName = name.replace(validChars, '-');
|
||||
|
||||
return formattedName;
|
||||
};
|
||||
|
||||
export const getContentType = (headers) => {
|
||||
const headersArray = typeof headers === 'object' ? Object.entries(headers) : [];
|
||||
|
||||
if (headersArray.length > 0) {
|
||||
let contentType = headersArray
|
||||
.filter((header) => header[0].toLowerCase() === 'content-type')
|
||||
.map((header) => {
|
||||
return header[1];
|
||||
});
|
||||
if (contentType && contentType.length) {
|
||||
if (typeof contentType[0] == 'string' && /^[\w\-]+\/([\w\-]+\+)?json/.test(contentType[0])) {
|
||||
return 'application/ld+json';
|
||||
} else if (typeof contentType[0] == 'string' && /^[\w\-]+\/([\w\-]+\+)?xml/.test(contentType[0])) {
|
||||
return 'application/xml';
|
||||
}
|
||||
|
||||
return contentType[0];
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
};
|
||||
|
||||
export const startsWith = (str, search) => {
|
||||
if (!str || !str.length || typeof str !== 'string') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!search || !search.length || typeof search !== 'string') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return str.substr(0, search.length) === search;
|
||||
};
|
||||
|
||||
export const pluralizeWord = (word, count) => {
|
||||
return count === 1 ? word : `${word}s`;
|
||||
};
|
||||
|
||||
export const relativeDate = (dateString) => {
|
||||
const date = new Date(dateString);
|
||||
const currentDate = new Date();
|
||||
|
||||
const difference = currentDate - date;
|
||||
const secondsDifference = Math.floor(difference / 1000);
|
||||
const minutesDifference = Math.floor(secondsDifference / 60);
|
||||
const hoursDifference = Math.floor(minutesDifference / 60);
|
||||
const daysDifference = Math.floor(hoursDifference / 24);
|
||||
const weeksDifference = Math.floor(daysDifference / 7);
|
||||
const monthsDifference = Math.floor(daysDifference / 30);
|
||||
|
||||
if (secondsDifference < 60) {
|
||||
return 'Few seconds ago';
|
||||
} else if (minutesDifference < 60) {
|
||||
return `${minutesDifference} minute${minutesDifference > 1 ? 's' : ''} ago`;
|
||||
} else if (hoursDifference < 24) {
|
||||
return `${hoursDifference} hour${hoursDifference > 1 ? 's' : ''} ago`;
|
||||
} else if (daysDifference < 7) {
|
||||
return `${daysDifference} day${daysDifference > 1 ? 's' : ''} ago`;
|
||||
} else if (weeksDifference < 4) {
|
||||
return `${weeksDifference} week${weeksDifference > 1 ? 's' : ''} ago`;
|
||||
} else {
|
||||
return `${monthsDifference} month${monthsDifference > 1 ? 's' : ''} ago`;
|
||||
}
|
||||
};
|
||||
|
||||
export const humanizeDate = (dateString) => {
|
||||
// See this discussion for why .split is necessary
|
||||
// https://stackoverflow.com/questions/7556591/is-the-javascript-date-object-always-one-day-off
|
||||
const date = new Date(dateString.split('-'));
|
||||
return date.toLocaleDateString('en-US', {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric'
|
||||
});
|
||||
};
|
20
packages/bruno-converters/src/common/slash.js
Normal file
20
packages/bruno-converters/src/common/slash.js
Normal file
@ -0,0 +1,20 @@
|
||||
/**
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
export const slash = (path) => {
|
||||
const isExtendedLengthPath = /^\\\\\?\\/.test(path);
|
||||
|
||||
if (isExtendedLengthPath) {
|
||||
return path;
|
||||
}
|
||||
|
||||
return path.replace(/\\/g, '/');
|
||||
};
|
||||
|
||||
export default slash;
|
256
packages/bruno-converters/src/exporters/postman-collection.js
Normal file
256
packages/bruno-converters/src/exporters/postman-collection.js
Normal file
@ -0,0 +1,256 @@
|
||||
import map from 'lodash/map';
|
||||
import { deleteSecretsInEnvs, deleteUidsInEnvs, deleteUidsInItems } from '../collections/export';
|
||||
import { saveFile } from '../common/file';
|
||||
|
||||
export const exportCollection = (collection) => {
|
||||
delete collection.uid;
|
||||
delete collection.processEnvVariables;
|
||||
deleteUidsInItems(collection.items);
|
||||
deleteUidsInEnvs(collection.environments);
|
||||
deleteSecretsInEnvs(collection.environments);
|
||||
|
||||
const generateInfoSection = () => {
|
||||
return {
|
||||
name: collection.name,
|
||||
description: collection.root?.docs,
|
||||
schema: 'https://schema.getpostman.com/json/collection/v2.1.0/collection.json'
|
||||
};
|
||||
};
|
||||
|
||||
const generateCollectionVars = (collection) => {
|
||||
const pattern = /{{[^{}]+}}/g;
|
||||
let listOfVars = [];
|
||||
|
||||
const findOccurrences = (obj, results) => {
|
||||
if (typeof obj === 'object') {
|
||||
if (Array.isArray(obj)) {
|
||||
obj.forEach((item) => findOccurrences(item, results));
|
||||
} else {
|
||||
for (const key in obj) {
|
||||
findOccurrences(obj[key], results);
|
||||
}
|
||||
}
|
||||
} else if (typeof obj === 'string') {
|
||||
obj.replace(pattern, (match) => {
|
||||
results.push(match.replace(/{{|}}/g, ''));
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
findOccurrences(collection, listOfVars);
|
||||
|
||||
const finalArrayOfVars = [...new Set(listOfVars)];
|
||||
|
||||
return finalArrayOfVars.map((variable) => ({
|
||||
key: variable,
|
||||
value: '',
|
||||
type: 'default'
|
||||
}));
|
||||
};
|
||||
|
||||
const generateEventSection = (item) => {
|
||||
const eventArray = [];
|
||||
if (item?.request?.tests?.length) {
|
||||
eventArray.push({
|
||||
listen: 'test',
|
||||
script: {
|
||||
exec: item.request.tests.split('\n')
|
||||
// type: 'text/javascript'
|
||||
}
|
||||
});
|
||||
}
|
||||
if (item?.request?.script?.req) {
|
||||
eventArray.push({
|
||||
listen: 'prerequest',
|
||||
script: {
|
||||
exec: item.request.script.req.split('\n')
|
||||
// type: 'text/javascript'
|
||||
}
|
||||
});
|
||||
}
|
||||
return eventArray;
|
||||
};
|
||||
|
||||
const generateHeaders = (headersArray) => {
|
||||
return map(headersArray, (item) => {
|
||||
return {
|
||||
key: item.name,
|
||||
value: item.value,
|
||||
disabled: !item.enabled,
|
||||
type: 'default'
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const generateBody = (body) => {
|
||||
switch (body.mode) {
|
||||
case 'formUrlEncoded':
|
||||
return {
|
||||
mode: 'urlencoded',
|
||||
urlencoded: map(body.formUrlEncoded, (bodyItem) => {
|
||||
return {
|
||||
key: bodyItem.name,
|
||||
value: bodyItem.value,
|
||||
disabled: !bodyItem.enabled,
|
||||
type: 'default'
|
||||
};
|
||||
})
|
||||
};
|
||||
case 'multipartForm':
|
||||
return {
|
||||
mode: 'formdata',
|
||||
formdata: map(body.multipartForm, (bodyItem) => {
|
||||
return {
|
||||
key: bodyItem.name,
|
||||
value: bodyItem.value,
|
||||
disabled: !bodyItem.enabled,
|
||||
type: 'default'
|
||||
};
|
||||
})
|
||||
};
|
||||
case 'json':
|
||||
return {
|
||||
mode: 'raw',
|
||||
raw: body.json,
|
||||
options: {
|
||||
raw: {
|
||||
language: 'json'
|
||||
}
|
||||
}
|
||||
};
|
||||
case 'xml':
|
||||
return {
|
||||
mode: 'raw',
|
||||
raw: body.xml,
|
||||
options: {
|
||||
raw: {
|
||||
language: 'xml'
|
||||
}
|
||||
}
|
||||
};
|
||||
case 'text':
|
||||
return {
|
||||
mode: 'raw',
|
||||
raw: body.text,
|
||||
options: {
|
||||
raw: {
|
||||
language: 'text'
|
||||
}
|
||||
}
|
||||
};
|
||||
case 'graphql':
|
||||
return {
|
||||
mode: 'graphql',
|
||||
graphql: body.graphql
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const generateAuth = (itemAuth) => {
|
||||
switch (itemAuth) {
|
||||
case 'bearer':
|
||||
return {
|
||||
type: 'bearer',
|
||||
bearer: {
|
||||
key: 'token',
|
||||
value: itemAuth.bearer.token,
|
||||
type: 'string'
|
||||
}
|
||||
};
|
||||
case 'basic': {
|
||||
return {
|
||||
type: 'basic',
|
||||
basic: [
|
||||
{
|
||||
key: 'password',
|
||||
value: itemAuth.basic.password,
|
||||
type: 'string'
|
||||
},
|
||||
{
|
||||
key: 'username',
|
||||
value: itemAuth.basic.username,
|
||||
type: 'string'
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const generateHost = (url) => {
|
||||
try {
|
||||
const { hostname } = new URL(url);
|
||||
return hostname.split('.');
|
||||
} catch (error) {
|
||||
console.error(`Invalid URL: ${url}`, error);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
const generatePathParams = (params) => {
|
||||
return params.filter((param) => param.type === 'path').map((param) => `:${param.name}`);
|
||||
};
|
||||
|
||||
const generateQueryParams = (params) => {
|
||||
return params
|
||||
.filter((param) => param.type === 'query')
|
||||
.map(({ name, value, description }) => ({ key: name, value, description }));
|
||||
};
|
||||
|
||||
const generateVariables = (params) => {
|
||||
return params
|
||||
.filter((param) => param.type === 'path')
|
||||
.map(({ name, value, description }) => ({ key: name, value, description }));
|
||||
};
|
||||
|
||||
const generateRequestSection = (itemRequest) => {
|
||||
const requestObject = {
|
||||
method: itemRequest.method,
|
||||
header: generateHeaders(itemRequest.headers),
|
||||
auth: generateAuth(itemRequest.auth),
|
||||
description: itemRequest.docs,
|
||||
url: {
|
||||
raw: itemRequest.url,
|
||||
host: generateHost(itemRequest.url),
|
||||
path: generatePathParams(itemRequest.params),
|
||||
query: generateQueryParams(itemRequest.params),
|
||||
variable: generateVariables(itemRequest.params)
|
||||
},
|
||||
};
|
||||
|
||||
if (itemRequest.body.mode !== 'none') {
|
||||
requestObject.body = generateBody(itemRequest.body);
|
||||
}
|
||||
return requestObject;
|
||||
};
|
||||
|
||||
const generateItemSection = (itemsArray) => {
|
||||
return map(itemsArray, (item) => {
|
||||
if (item.type === 'folder') {
|
||||
return {
|
||||
name: item.name,
|
||||
item: item.items.length ? generateItemSection(item.items) : []
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
name: item.name,
|
||||
event: generateEventSection(item),
|
||||
request: generateRequestSection(item.request)
|
||||
};
|
||||
}
|
||||
});
|
||||
};
|
||||
const collectionToExport = {};
|
||||
collectionToExport.info = generateInfoSection();
|
||||
collectionToExport.item = generateItemSection(collection.items);
|
||||
collectionToExport.variable = generateCollectionVars(collection);
|
||||
|
||||
const fileName = `${collection.name}.json`;
|
||||
const fileBlob = new Blob([JSON.stringify(collectionToExport, null, 2)], { type: 'application/json' });
|
||||
|
||||
saveFile(fileBlob, fileName).then(() => {
|
||||
console.log(`Collection exported as ${fileName}`);
|
||||
});
|
||||
};
|
||||
|
||||
export default exportCollection;
|
235
packages/bruno-converters/src/importers/insomnia-collection.js
Normal file
235
packages/bruno-converters/src/importers/insomnia-collection.js
Normal file
@ -0,0 +1,235 @@
|
||||
import each from 'lodash/each';
|
||||
import get from 'lodash/get';
|
||||
|
||||
import { uuid } from '../common';
|
||||
import { validateSchema, transformItemsInCollection, hydrateSeqInCollection, BrunoError } from '../common/common';
|
||||
import { parseFile } from '../common/file';
|
||||
|
||||
const parseGraphQL = (text) => {
|
||||
try {
|
||||
const graphql = JSON.parse(text);
|
||||
|
||||
return {
|
||||
query: graphql.query,
|
||||
variables: JSON.stringify(graphql.variables, null, 2)
|
||||
};
|
||||
} catch (e) {
|
||||
return {
|
||||
query: '',
|
||||
variables: ''
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const addSuffixToDuplicateName = (item, index, allItems) => {
|
||||
// Check if the request name already exist and if so add a number suffix
|
||||
const nameSuffix = allItems.reduce((nameSuffix, otherItem, otherIndex) => {
|
||||
if (otherItem.name === item.name && otherIndex < index) {
|
||||
nameSuffix++;
|
||||
}
|
||||
return nameSuffix;
|
||||
}, 0);
|
||||
return nameSuffix !== 0 ? `${item.name}_${nameSuffix}` : item.name;
|
||||
};
|
||||
|
||||
const regexVariable = new RegExp('{{.*?}}', 'g');
|
||||
|
||||
const normalizeVariables = (value) => {
|
||||
const variables = value.match(regexVariable) || [];
|
||||
each(variables, (variable) => {
|
||||
value = value.replace(variable, variable.replace('_.', '').replaceAll(' ', ''));
|
||||
});
|
||||
return value;
|
||||
};
|
||||
|
||||
const transformInsomniaRequestItem = (request, index, allRequests) => {
|
||||
const name = addSuffixToDuplicateName(request, index, allRequests);
|
||||
|
||||
const brunoRequestItem = {
|
||||
uid: uuid(),
|
||||
name,
|
||||
type: 'http-request',
|
||||
request: {
|
||||
url: request.url,
|
||||
method: request.method,
|
||||
auth: {
|
||||
mode: 'none',
|
||||
basic: null,
|
||||
bearer: null,
|
||||
digest: null
|
||||
},
|
||||
headers: [],
|
||||
params: [],
|
||||
body: {
|
||||
mode: 'none',
|
||||
json: null,
|
||||
text: null,
|
||||
xml: null,
|
||||
formUrlEncoded: [],
|
||||
multipartForm: []
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
each(request.headers, (header) => {
|
||||
brunoRequestItem.request.headers.push({
|
||||
uid: uuid(),
|
||||
name: header.name,
|
||||
value: header.value,
|
||||
description: header.description,
|
||||
enabled: !header.disabled
|
||||
});
|
||||
});
|
||||
|
||||
each(request.parameters, (param) => {
|
||||
brunoRequestItem.request.params.push({
|
||||
uid: uuid(),
|
||||
name: param.name,
|
||||
value: param.value,
|
||||
description: param.description,
|
||||
type: 'query',
|
||||
enabled: !param.disabled
|
||||
});
|
||||
});
|
||||
|
||||
each(request.pathParameters, (param) => {
|
||||
brunoRequestItem.request.params.push({
|
||||
uid: uuid(),
|
||||
name: param.name,
|
||||
value: param.value,
|
||||
description: '',
|
||||
type: 'path',
|
||||
enabled: true
|
||||
});
|
||||
});
|
||||
|
||||
const authType = get(request, 'authentication.type', '');
|
||||
|
||||
if (authType === 'basic') {
|
||||
brunoRequestItem.request.auth.mode = 'basic';
|
||||
brunoRequestItem.request.auth.basic = {
|
||||
username: normalizeVariables(get(request, 'authentication.username', '')),
|
||||
password: normalizeVariables(get(request, 'authentication.password', ''))
|
||||
};
|
||||
} else if (authType === 'bearer') {
|
||||
brunoRequestItem.request.auth.mode = 'bearer';
|
||||
brunoRequestItem.request.auth.bearer = {
|
||||
token: normalizeVariables(get(request, 'authentication.token', ''))
|
||||
};
|
||||
}
|
||||
|
||||
const mimeType = get(request, 'body.mimeType', '').split(';')[0];
|
||||
|
||||
if (mimeType === 'application/json') {
|
||||
brunoRequestItem.request.body.mode = 'json';
|
||||
brunoRequestItem.request.body.json = request.body.text;
|
||||
} else if (mimeType === 'application/x-www-form-urlencoded') {
|
||||
brunoRequestItem.request.body.mode = 'formUrlEncoded';
|
||||
each(request.body.params, (param) => {
|
||||
brunoRequestItem.request.body.formUrlEncoded.push({
|
||||
uid: uuid(),
|
||||
name: param.name,
|
||||
value: param.value,
|
||||
description: param.description,
|
||||
enabled: !param.disabled
|
||||
});
|
||||
});
|
||||
} else if (mimeType === 'multipart/form-data') {
|
||||
brunoRequestItem.request.body.mode = 'multipartForm';
|
||||
each(request.body.params, (param) => {
|
||||
brunoRequestItem.request.body.multipartForm.push({
|
||||
uid: uuid(),
|
||||
type: 'text',
|
||||
name: param.name,
|
||||
value: param.value,
|
||||
description: param.description,
|
||||
enabled: !param.disabled
|
||||
});
|
||||
});
|
||||
} else if (mimeType === 'text/plain') {
|
||||
brunoRequestItem.request.body.mode = 'text';
|
||||
brunoRequestItem.request.body.text = request.body.text;
|
||||
} else if (mimeType === 'text/xml') {
|
||||
brunoRequestItem.request.body.mode = 'xml';
|
||||
brunoRequestItem.request.body.xml = request.body.text;
|
||||
} else if (mimeType === 'application/graphql') {
|
||||
brunoRequestItem.type = 'graphql-request';
|
||||
brunoRequestItem.request.body.mode = 'graphql';
|
||||
brunoRequestItem.request.body.graphql = parseGraphQL(request.body.text);
|
||||
}
|
||||
|
||||
return brunoRequestItem;
|
||||
};
|
||||
|
||||
const parseInsomniaCollection = (data) => {
|
||||
const brunoCollection = {
|
||||
name: '',
|
||||
uid: uuid(),
|
||||
version: '1',
|
||||
items: [],
|
||||
environments: []
|
||||
};
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
const insomniaExport = data;
|
||||
const insomniaResources = get(insomniaExport, 'resources', []);
|
||||
const insomniaCollection = insomniaResources.find((resource) => resource._type === 'workspace');
|
||||
|
||||
if (!insomniaCollection) {
|
||||
reject(new BrunoError('Collection not found inside Insomnia export'));
|
||||
}
|
||||
|
||||
brunoCollection.name = insomniaCollection.name;
|
||||
|
||||
const requestsAndFolders =
|
||||
insomniaResources.filter((resource) => resource._type === 'request' || resource._type === 'request_group') ||
|
||||
[];
|
||||
|
||||
function createFolderStructure(resources, parentId = null) {
|
||||
const requestGroups =
|
||||
resources.filter((resource) => resource._type === 'request_group' && resource.parentId === parentId) || [];
|
||||
const requests = resources.filter((resource) => resource._type === 'request' && resource.parentId === parentId);
|
||||
|
||||
const folders = requestGroups.map((folder, index, allFolder) => {
|
||||
const name = addSuffixToDuplicateName(folder, index, allFolder);
|
||||
const requests = resources.filter(
|
||||
(resource) => resource._type === 'request' && resource.parentId === folder._id
|
||||
);
|
||||
|
||||
return {
|
||||
uid: uuid(),
|
||||
name,
|
||||
type: 'folder',
|
||||
items: createFolderStructure(resources, folder._id).concat(requests.map(transformInsomniaRequestItem))
|
||||
};
|
||||
});
|
||||
|
||||
return folders.concat(requests.map(transformInsomniaRequestItem));
|
||||
}
|
||||
|
||||
(brunoCollection.items = createFolderStructure(requestsAndFolders, insomniaCollection._id)),
|
||||
resolve(brunoCollection);
|
||||
} catch (err) {
|
||||
reject(new BrunoError('An error occurred while parsing the Insomnia collection'));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const importCollection = (fileName) => {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
const obj = await parseFile(fileName);
|
||||
const collection = await parseInsomniaCollection(obj);
|
||||
const transformedCollection = await transformItemsInCollection(collection);
|
||||
const hydratedCollection = await hydrateSeqInCollection(transformedCollection);
|
||||
const validatedCollection = await validateSchema(hydratedCollection);
|
||||
resolve(validatedCollection);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
reject(new BrunoError('Import collection failed'));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export default importCollection;
|
417
packages/bruno-converters/src/importers/openapi-collection.js
Normal file
417
packages/bruno-converters/src/importers/openapi-collection.js
Normal file
@ -0,0 +1,417 @@
|
||||
import each from 'lodash/each';
|
||||
import get from 'lodash/get';
|
||||
|
||||
import { uuid } from '../common';
|
||||
import { validateSchema, transformItemsInCollection, hydrateSeqInCollection, BrunoError } from '../common/common';
|
||||
import { parseFile } from '../common/file';
|
||||
|
||||
const ensureUrl = (url) => {
|
||||
// Removing multiple slashes after the protocol if it exists, or after the beginning of the string otherwise
|
||||
return url.replace(/(^\w+:|^)\/{2,}/, '$1/');
|
||||
};
|
||||
|
||||
const buildEmptyJsonBody = (bodySchema) => {
|
||||
let _jsonBody = {};
|
||||
each(bodySchema.properties || {}, (prop, name) => {
|
||||
if (prop.type === 'object') {
|
||||
_jsonBody[name] = buildEmptyJsonBody(prop);
|
||||
} else if (prop.type === 'array') {
|
||||
if (prop.items && prop.items.type === 'object') {
|
||||
_jsonBody[name] = [buildEmptyJsonBody(prop.items)];
|
||||
} else {
|
||||
_jsonBody[name] = [];
|
||||
}
|
||||
} else {
|
||||
_jsonBody[name] = '';
|
||||
}
|
||||
});
|
||||
return _jsonBody;
|
||||
};
|
||||
|
||||
const transformOpenapiRequestItem = (request) => {
|
||||
let _operationObject = request.operationObject;
|
||||
|
||||
let operationName = _operationObject.summary || _operationObject.operationId || _operationObject.description;
|
||||
if (!operationName) {
|
||||
operationName = `${request.method} ${request.path}`;
|
||||
}
|
||||
|
||||
// replace OpenAPI links in path by Bruno variables
|
||||
let path = request.path.replace(/{([a-zA-Z]+)}/g, `{{${_operationObject.operationId}_$1}}`);
|
||||
|
||||
const brunoRequestItem = {
|
||||
uid: uuid(),
|
||||
name: operationName,
|
||||
type: 'http-request',
|
||||
request: {
|
||||
url: ensureUrl(request.global.server + path),
|
||||
method: request.method.toUpperCase(),
|
||||
auth: {
|
||||
mode: 'none',
|
||||
basic: null,
|
||||
bearer: null,
|
||||
digest: null
|
||||
},
|
||||
headers: [],
|
||||
params: [],
|
||||
body: {
|
||||
mode: 'none',
|
||||
json: null,
|
||||
text: null,
|
||||
xml: null,
|
||||
formUrlEncoded: [],
|
||||
multipartForm: []
|
||||
},
|
||||
script: {
|
||||
res: null
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
each(_operationObject.parameters || [], (param) => {
|
||||
if (param.in === 'query') {
|
||||
brunoRequestItem.request.params.push({
|
||||
uid: uuid(),
|
||||
name: param.name,
|
||||
value: '',
|
||||
description: param.description || '',
|
||||
enabled: param.required,
|
||||
type: 'query'
|
||||
});
|
||||
} else if (param.in === 'path') {
|
||||
brunoRequestItem.request.params.push({
|
||||
uid: uuid(),
|
||||
name: param.name,
|
||||
value: '',
|
||||
description: param.description || '',
|
||||
enabled: param.required,
|
||||
type: 'path'
|
||||
});
|
||||
} else if (param.in === 'header') {
|
||||
brunoRequestItem.request.headers.push({
|
||||
uid: uuid(),
|
||||
name: param.name,
|
||||
value: '',
|
||||
description: param.description || '',
|
||||
enabled: param.required
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
let auth;
|
||||
// allow operation override
|
||||
if (_operationObject.security && _operationObject.security.length > 0) {
|
||||
let schemeName = Object.keys(_operationObject.security[0])[0];
|
||||
auth = request.global.security.getScheme(schemeName);
|
||||
} else if (request.global.security.supported.length > 0) {
|
||||
auth = request.global.security.supported[0];
|
||||
}
|
||||
|
||||
if (auth) {
|
||||
if (auth.type === 'http' && auth.scheme === 'basic') {
|
||||
brunoRequestItem.request.auth.mode = 'basic';
|
||||
brunoRequestItem.request.auth.basic = {
|
||||
username: '{{username}}',
|
||||
password: '{{password}}'
|
||||
};
|
||||
} else if (auth.type === 'http' && auth.scheme === 'bearer') {
|
||||
brunoRequestItem.request.auth.mode = 'bearer';
|
||||
brunoRequestItem.request.auth.bearer = {
|
||||
token: '{{token}}'
|
||||
};
|
||||
} else if (auth.type === 'apiKey' && auth.in === 'header') {
|
||||
brunoRequestItem.request.headers.push({
|
||||
uid: uuid(),
|
||||
name: auth.name,
|
||||
value: '{{apiKey}}',
|
||||
description: 'Authentication header',
|
||||
enabled: true
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: handle allOf/anyOf/oneOf
|
||||
if (_operationObject.requestBody) {
|
||||
let content = get(_operationObject, 'requestBody.content', {});
|
||||
let mimeType = Object.keys(content)[0];
|
||||
let body = content[mimeType] || {};
|
||||
let bodySchema = body.schema;
|
||||
if (mimeType === 'application/json') {
|
||||
brunoRequestItem.request.body.mode = 'json';
|
||||
if (bodySchema && bodySchema.type === 'object') {
|
||||
let _jsonBody = buildEmptyJsonBody(bodySchema);
|
||||
brunoRequestItem.request.body.json = JSON.stringify(_jsonBody, null, 2);
|
||||
}
|
||||
if (bodySchema && bodySchema.type === 'array') {
|
||||
brunoRequestItem.request.body.json = JSON.stringify([buildEmptyJsonBody(bodySchema.items)], null, 2);
|
||||
}
|
||||
} else if (mimeType === 'application/x-www-form-urlencoded') {
|
||||
brunoRequestItem.request.body.mode = 'formUrlEncoded';
|
||||
if (bodySchema && bodySchema.type === 'object') {
|
||||
each(bodySchema.properties || {}, (prop, name) => {
|
||||
brunoRequestItem.request.body.formUrlEncoded.push({
|
||||
uid: uuid(),
|
||||
name: name,
|
||||
value: '',
|
||||
description: prop.description || '',
|
||||
enabled: true
|
||||
});
|
||||
});
|
||||
}
|
||||
} else if (mimeType === 'multipart/form-data') {
|
||||
brunoRequestItem.request.body.mode = 'multipartForm';
|
||||
if (bodySchema && bodySchema.type === 'object') {
|
||||
each(bodySchema.properties || {}, (prop, name) => {
|
||||
brunoRequestItem.request.body.multipartForm.push({
|
||||
uid: uuid(),
|
||||
type: 'text',
|
||||
name: name,
|
||||
value: '',
|
||||
description: prop.description || '',
|
||||
enabled: true
|
||||
});
|
||||
});
|
||||
}
|
||||
} else if (mimeType === 'text/plain') {
|
||||
brunoRequestItem.request.body.mode = 'text';
|
||||
brunoRequestItem.request.body.text = '';
|
||||
} else if (mimeType === 'text/xml') {
|
||||
brunoRequestItem.request.body.mode = 'xml';
|
||||
brunoRequestItem.request.body.xml = '';
|
||||
}
|
||||
}
|
||||
|
||||
// build the extraction scripts from responses that have links
|
||||
// https://swagger.io/docs/specification/links/
|
||||
let script = [];
|
||||
each(_operationObject.responses || [], (response, responseStatus) => {
|
||||
if (Object.hasOwn(response, 'links')) {
|
||||
// only extract if the status code matches the response
|
||||
script.push(`if (res.status === ${responseStatus}) {`);
|
||||
each(response.links, (link) => {
|
||||
each(link.parameters || [], (expression, parameter) => {
|
||||
let value = openAPIRuntimeExpressionToScript(expression);
|
||||
script.push(` bru.setVar('${link.operationId}_${parameter}', ${value});`);
|
||||
});
|
||||
});
|
||||
script.push(`}`);
|
||||
}
|
||||
});
|
||||
if (script.length > 0) {
|
||||
brunoRequestItem.request.script.res = script.join('\n');
|
||||
}
|
||||
|
||||
return brunoRequestItem;
|
||||
};
|
||||
|
||||
const resolveRefs = (spec, components = spec?.components, visitedItems = new Set()) => {
|
||||
if (!spec || typeof spec !== 'object') {
|
||||
return spec;
|
||||
}
|
||||
|
||||
if (Array.isArray(spec)) {
|
||||
return spec.map((item) => resolveRefs(item, components, visitedItems));
|
||||
}
|
||||
|
||||
if ('$ref' in spec) {
|
||||
const refPath = spec.$ref;
|
||||
|
||||
if (visitedItems.has(refPath)) {
|
||||
return spec;
|
||||
} else {
|
||||
visitedItems.add(refPath);
|
||||
}
|
||||
|
||||
if (refPath.startsWith('#/components/')) {
|
||||
// Local reference within components
|
||||
const refKeys = refPath.replace('#/components/', '').split('/');
|
||||
let ref = components;
|
||||
|
||||
for (const key of refKeys) {
|
||||
if (ref && ref[key]) {
|
||||
ref = ref[key];
|
||||
} else {
|
||||
// Handle invalid references gracefully?
|
||||
return spec;
|
||||
}
|
||||
}
|
||||
|
||||
return resolveRefs(ref, components, visitedItems);
|
||||
} else {
|
||||
// Handle external references (not implemented here)
|
||||
// You would need to fetch the external reference and resolve it.
|
||||
// Example: Fetch and resolve an external reference from a URL.
|
||||
}
|
||||
}
|
||||
|
||||
// Recursively resolve references in nested objects
|
||||
for (const prop in spec) {
|
||||
spec[prop] = resolveRefs(spec[prop], components, visitedItems);
|
||||
}
|
||||
|
||||
return spec;
|
||||
};
|
||||
|
||||
const groupRequestsByTags = (requests) => {
|
||||
let _groups = {};
|
||||
let ungrouped = [];
|
||||
each(requests, (request) => {
|
||||
let tags = request.operationObject.tags || [];
|
||||
if (tags.length > 0) {
|
||||
let tag = tags[0]; // take first tag
|
||||
if (!_groups[tag]) {
|
||||
_groups[tag] = [];
|
||||
}
|
||||
_groups[tag].push(request);
|
||||
} else {
|
||||
ungrouped.push(request);
|
||||
}
|
||||
});
|
||||
|
||||
let groups = Object.keys(_groups).map((groupName) => {
|
||||
return {
|
||||
name: groupName,
|
||||
requests: _groups[groupName]
|
||||
};
|
||||
});
|
||||
|
||||
return [groups, ungrouped];
|
||||
};
|
||||
|
||||
const getDefaultUrl = (serverObject) => {
|
||||
let url = serverObject.url;
|
||||
if (serverObject.variables) {
|
||||
each(serverObject.variables, (variable, variableName) => {
|
||||
let sub = variable.default || (variable.enum ? variable.enum[0] : `{{${variableName}}}`);
|
||||
url = url.replace(`{${variableName}}`, sub);
|
||||
});
|
||||
}
|
||||
return url.endsWith('/') ? url : `${url}/`;
|
||||
};
|
||||
|
||||
const getSecurity = (apiSpec) => {
|
||||
let defaultSchemes = apiSpec.security || [];
|
||||
|
||||
let securitySchemes = get(apiSpec, 'components.securitySchemes', {});
|
||||
if (Object.keys(securitySchemes) === 0) {
|
||||
return {
|
||||
supported: []
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
supported: defaultSchemes.map((scheme) => {
|
||||
let schemeName = Object.keys(scheme)[0];
|
||||
return securitySchemes[schemeName];
|
||||
}),
|
||||
schemes: securitySchemes,
|
||||
getScheme: (schemeName) => {
|
||||
return securitySchemes[schemeName];
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const openAPIRuntimeExpressionToScript = (expression) => {
|
||||
// see https://swagger.io/docs/specification/links/#runtime-expressions
|
||||
if (expression === '$response.body') {
|
||||
return 'res.body';
|
||||
} else if (expression.startsWith('$response.body#')) {
|
||||
let pointer = expression.substring(15);
|
||||
// could use https://www.npmjs.com/package/json-pointer for better support
|
||||
return `res.body${pointer.replace('/', '.')}`;
|
||||
}
|
||||
return expression;
|
||||
};
|
||||
|
||||
const parseOpenApiCollection = (data) => {
|
||||
const brunoCollection = {
|
||||
name: '',
|
||||
uid: uuid(),
|
||||
version: '1',
|
||||
items: [],
|
||||
environments: []
|
||||
};
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
const collectionData = resolveRefs(data);
|
||||
if (!collectionData) {
|
||||
reject(new BrunoError('Invalid OpenAPI collection. Failed to resolve refs.'));
|
||||
return;
|
||||
}
|
||||
|
||||
// Currently parsing of openapi spec is "do your best", that is
|
||||
// allows "invalid" openapi spec
|
||||
|
||||
// assumes v3 if not defined. v2 no supported yet
|
||||
if (collectionData.openapi && !collectionData.openapi.startsWith('3')) {
|
||||
reject(new BrunoError('Only OpenAPI v3 is supported currently.'));
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO what if info.title not defined?
|
||||
brunoCollection.name = collectionData.info.title;
|
||||
let servers = collectionData.servers || [];
|
||||
let baseUrl = servers[0] ? getDefaultUrl(servers[0]) : '';
|
||||
let securityConfig = getSecurity(collectionData);
|
||||
|
||||
let allRequests = Object.entries(collectionData.paths)
|
||||
.map(([path, methods]) => {
|
||||
return Object.entries(methods)
|
||||
.filter(([method, op]) => {
|
||||
return ['get', 'put', 'post', 'delete', 'options', 'head', 'patch', 'trace'].includes(
|
||||
method.toLowerCase()
|
||||
);
|
||||
})
|
||||
.map(([method, operationObject]) => {
|
||||
return {
|
||||
method: method,
|
||||
path: path.replace(/{([^}]+)}/g, ':$1'), // Replace placeholders enclosed in curly braces with colons
|
||||
operationObject: operationObject,
|
||||
global: {
|
||||
server: baseUrl,
|
||||
security: securityConfig
|
||||
}
|
||||
};
|
||||
});
|
||||
})
|
||||
.reduce((acc, val) => acc.concat(val), []); // flatten
|
||||
|
||||
let [groups, ungroupedRequests] = groupRequestsByTags(allRequests);
|
||||
let brunoFolders = groups.map((group) => {
|
||||
return {
|
||||
uid: uuid(),
|
||||
name: group.name,
|
||||
type: 'folder',
|
||||
items: group.requests.map(transformOpenapiRequestItem)
|
||||
};
|
||||
});
|
||||
|
||||
let ungroupedItems = ungroupedRequests.map(transformOpenapiRequestItem);
|
||||
let brunoCollectionItems = brunoFolders.concat(ungroupedItems);
|
||||
brunoCollection.items = brunoCollectionItems;
|
||||
resolve(brunoCollection);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
reject(new BrunoError('An error occurred while parsing the OpenAPI collection'));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const importCollection = (fileName) => {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
const obj = await parseFile(fileName);
|
||||
const collection = await parseOpenApiCollection(obj);
|
||||
const transformedCollection = await transformItemsInCollection(collection);
|
||||
const hydratedCollection = await hydrateSeqInCollection(transformedCollection);
|
||||
const validatedCollection = await validateSchema(hydratedCollection);
|
||||
resolve(validatedCollection);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
reject(new BrunoError('Import collection failed'));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export default importCollection;
|
400
packages/bruno-converters/src/importers/postman-collection.js
Normal file
400
packages/bruno-converters/src/importers/postman-collection.js
Normal file
@ -0,0 +1,400 @@
|
||||
import each from 'lodash/each';
|
||||
import get from 'lodash/get';
|
||||
|
||||
import { uuid } from '../common/index';
|
||||
import { validateSchema, transformItemsInCollection, hydrateSeqInCollection, BrunoError } from '../common/common';
|
||||
import { postmanTranslation } from './translators/postman_translation';
|
||||
import { readFile } from '../common/file';
|
||||
|
||||
const parseGraphQLRequest = (graphqlSource) => {
|
||||
try {
|
||||
let queryResultObject = {
|
||||
query: '',
|
||||
variables: ''
|
||||
};
|
||||
|
||||
if (typeof graphqlSource === 'string') {
|
||||
graphqlSource = JSON.parse(text);
|
||||
}
|
||||
|
||||
if (graphqlSource.hasOwnProperty('variables') && graphqlSource.variables !== '') {
|
||||
queryResultObject.variables = graphqlSource.variables;
|
||||
}
|
||||
|
||||
if (graphqlSource.hasOwnProperty('query') && graphqlSource.query !== '') {
|
||||
queryResultObject.query = graphqlSource.query;
|
||||
}
|
||||
|
||||
return queryResultObject;
|
||||
} catch (e) {
|
||||
return {
|
||||
query: '',
|
||||
variables: ''
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const isItemAFolder = (item) => {
|
||||
return !item.request;
|
||||
};
|
||||
|
||||
const convertV21Auth = (array) => {
|
||||
return array.reduce((accumulator, currentValue) => {
|
||||
accumulator[currentValue.key] = currentValue.value;
|
||||
return accumulator;
|
||||
}, {});
|
||||
};
|
||||
|
||||
let translationLog = {};
|
||||
|
||||
const importPostmanV2CollectionItem = (brunoParent, item, parentAuth, options) => {
|
||||
brunoParent.items = brunoParent.items || [];
|
||||
const folderMap = {};
|
||||
|
||||
each(item, (i) => {
|
||||
if (isItemAFolder(i)) {
|
||||
const baseFolderName = i.name;
|
||||
let folderName = baseFolderName;
|
||||
let count = 1;
|
||||
|
||||
while (folderMap[folderName]) {
|
||||
folderName = `${baseFolderName}_${count}`;
|
||||
count++;
|
||||
}
|
||||
|
||||
const brunoFolderItem = {
|
||||
uid: uuid(),
|
||||
name: folderName,
|
||||
type: 'folder',
|
||||
items: []
|
||||
};
|
||||
brunoParent.items.push(brunoFolderItem);
|
||||
folderMap[folderName] = brunoFolderItem;
|
||||
if (i.item && i.item.length) {
|
||||
importPostmanV2CollectionItem(brunoFolderItem, i.item, i.auth ?? parentAuth, options);
|
||||
}
|
||||
} else {
|
||||
if (i.request) {
|
||||
let url = '';
|
||||
if (typeof i.request.url === 'string') {
|
||||
url = i.request.url;
|
||||
} else {
|
||||
url = get(i, 'request.url.raw') || '';
|
||||
}
|
||||
|
||||
const brunoRequestItem = {
|
||||
uid: uuid(),
|
||||
name: i.name,
|
||||
type: 'http-request',
|
||||
request: {
|
||||
url: url,
|
||||
method: i.request.method,
|
||||
auth: {
|
||||
mode: 'none',
|
||||
basic: null,
|
||||
bearer: null,
|
||||
awsv4: null
|
||||
},
|
||||
headers: [],
|
||||
params: [],
|
||||
body: {
|
||||
mode: 'none',
|
||||
json: null,
|
||||
text: null,
|
||||
xml: null,
|
||||
formUrlEncoded: [],
|
||||
multipartForm: []
|
||||
},
|
||||
docs: i.request.description
|
||||
}
|
||||
};
|
||||
/* struct of translation log
|
||||
{
|
||||
[collectionName]: {
|
||||
script: [index1, index2],
|
||||
test: [index1, index2]
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
// type could be script or test
|
||||
const pushTranslationLog = (type, index) => {
|
||||
if (!translationLog[i.name]) {
|
||||
translationLog[i.name] = {};
|
||||
}
|
||||
if (!translationLog[i.name][type]) {
|
||||
translationLog[i.name][type] = [];
|
||||
}
|
||||
translationLog[i.name][type].push(index + 1);
|
||||
};
|
||||
if (i.event) {
|
||||
i.event.forEach((event) => {
|
||||
if (event.listen === 'prerequest' && event.script && event.script.exec) {
|
||||
if (!brunoRequestItem.request.script) {
|
||||
brunoRequestItem.request.script = {};
|
||||
}
|
||||
if (Array.isArray(event.script.exec)) {
|
||||
brunoRequestItem.request.script.req = event.script.exec
|
||||
.map((line, index) =>
|
||||
options.enablePostmanTranslations.enabled
|
||||
? postmanTranslation(line, () => pushTranslationLog('script', index))
|
||||
: `// ${line}`
|
||||
)
|
||||
.join('\n');
|
||||
} else {
|
||||
brunoRequestItem.request.script.req = options.enablePostmanTranslations.enabled
|
||||
? postmanTranslation(event.script.exec[0], () => pushTranslationLog('script', 0))
|
||||
: `// ${event.script.exec[0]} `;
|
||||
}
|
||||
}
|
||||
if (event.listen === 'test' && event.script && event.script.exec) {
|
||||
if (!brunoRequestItem.request.tests) {
|
||||
brunoRequestItem.request.tests = {};
|
||||
}
|
||||
if (Array.isArray(event.script.exec)) {
|
||||
brunoRequestItem.request.tests = event.script.exec
|
||||
.map((line, index) =>
|
||||
options.enablePostmanTranslations.enabled
|
||||
? postmanTranslation(line, () => pushTranslationLog('test', index))
|
||||
: `// ${line}`
|
||||
)
|
||||
.join('\n');
|
||||
} else {
|
||||
brunoRequestItem.request.tests = options.enablePostmanTranslations.enabled
|
||||
? postmanTranslation(event.script.exec[0], () => pushTranslationLog('test', 0))
|
||||
: `// ${event.script.exec[0]} `;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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
|
||||
});
|
||||
});
|
||||
|
||||
const auth = i.request.auth ?? parentAuth;
|
||||
if (auth?.[auth.type] && auth.type !== 'noauth') {
|
||||
let authValues = auth[auth.type];
|
||||
if (Array.isArray(authValues)) {
|
||||
authValues = convertV21Auth(authValues);
|
||||
}
|
||||
if (auth.type === 'basic') {
|
||||
brunoRequestItem.request.auth.mode = 'basic';
|
||||
brunoRequestItem.request.auth.basic = {
|
||||
username: authValues.username,
|
||||
password: authValues.password
|
||||
};
|
||||
} else if (auth.type === 'bearer') {
|
||||
brunoRequestItem.request.auth.mode = 'bearer';
|
||||
brunoRequestItem.request.auth.bearer = {
|
||||
token: authValues.token
|
||||
};
|
||||
} else if (auth.type === 'awsv4') {
|
||||
brunoRequestItem.request.auth.mode = 'awsv4';
|
||||
brunoRequestItem.request.auth.awsv4 = {
|
||||
accessKeyId: authValues.accessKey,
|
||||
secretAccessKey: authValues.secretKey,
|
||||
sessionToken: authValues.sessionToken,
|
||||
service: authValues.service,
|
||||
region: authValues.region,
|
||||
profileName: ''
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
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) => {
|
||||
brunoRequestItem.request.params.push({
|
||||
uid: uuid(),
|
||||
name: param.key,
|
||||
value: param.value,
|
||||
description: param.description,
|
||||
type: 'path',
|
||||
enabled: true
|
||||
});
|
||||
});
|
||||
|
||||
brunoParent.items.push(brunoRequestItem);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const searchLanguageByHeader = (headers) => {
|
||||
let contentType;
|
||||
each(headers, (header) => {
|
||||
if (header.key.toLowerCase() === 'content-type' && !header.disabled) {
|
||||
if (typeof header.value == 'string' && /^[\w\-]+\/([\w\-]+\+)?json/.test(header.value)) {
|
||||
contentType = 'json';
|
||||
} else if (typeof header.value == 'string' && /^[\w\-]+\/([\w\-]+\+)?xml/.test(header.value)) {
|
||||
contentType = 'xml';
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
return contentType;
|
||||
};
|
||||
|
||||
const importPostmanV2Collection = (collection, options) => {
|
||||
const brunoCollection = {
|
||||
name: collection.info.name,
|
||||
uid: uuid(),
|
||||
version: '1',
|
||||
items: [],
|
||||
environments: []
|
||||
};
|
||||
|
||||
importPostmanV2CollectionItem(brunoCollection, collection.item, collection.auth, options);
|
||||
|
||||
return brunoCollection;
|
||||
};
|
||||
|
||||
const parsePostmanCollection = (str, options) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
let collection = JSON.parse(str);
|
||||
let schema = get(collection, 'info.schema');
|
||||
|
||||
let v2Schemas = [
|
||||
'https://schema.getpostman.com/json/collection/v2.0.0/collection.json',
|
||||
'https://schema.getpostman.com/json/collection/v2.1.0/collection.json'
|
||||
];
|
||||
|
||||
if (v2Schemas.includes(schema)) {
|
||||
return resolve(importPostmanV2Collection(collection, options));
|
||||
}
|
||||
|
||||
throw new BrunoError('Unknown postman schema');
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
if (err instanceof BrunoError) {
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
return reject(new BrunoError('Unable to parse the postman collection json file'));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const logTranslationDetails = (translationLog) => {
|
||||
if (Object.keys(translationLog || {}).length > 0) {
|
||||
console.warn(
|
||||
`[Postman Translation Logs]
|
||||
Collections incomplete : ${Object.keys(translationLog || {}).length}` +
|
||||
`\nTotal lines incomplete : ${Object.values(translationLog || {}).reduce(
|
||||
(acc, curr) => acc + (curr.script?.length || 0) + (curr.test?.length || 0),
|
||||
0
|
||||
)}` +
|
||||
`\nSee details below :`,
|
||||
translationLog
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export const importCollection = (fileName, options) => {
|
||||
// set default options, it not provided
|
||||
options = options || {
|
||||
enablePostmanTranslations: {
|
||||
enabled: false
|
||||
}
|
||||
};
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
const str = await readFile(fileName);
|
||||
const collection = await parsePostmanCollection(str, options);
|
||||
const transformedCollection = await transformItemsInCollection(collection);
|
||||
const hydratedCollection = await hydrateSeqInCollection(transformedCollection);
|
||||
const validatedCollection = await validateSchema(hydratedCollection);
|
||||
resolve(validatedCollection);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
reject(new BrunoError('Import collection failed'));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export default importCollection;
|
@ -0,0 +1,62 @@
|
||||
import each from 'lodash/each';
|
||||
import { BrunoError } from '../common/common';
|
||||
import { readFile } from '../common/file';
|
||||
|
||||
const isSecret = (type) => {
|
||||
return type === 'secret';
|
||||
};
|
||||
|
||||
const importPostmanEnvironmentVariables = (brunoEnvironment, values) => {
|
||||
brunoEnvironment.variables = brunoEnvironment.variables || [];
|
||||
|
||||
each(values, (i) => {
|
||||
const brunoEnvironmentVariable = {
|
||||
name: i.key,
|
||||
value: i.value,
|
||||
enabled: i.enabled,
|
||||
secret: isSecret(i.type)
|
||||
};
|
||||
|
||||
brunoEnvironment.variables.push(brunoEnvironmentVariable);
|
||||
});
|
||||
};
|
||||
|
||||
const importPostmanEnvironment = (environment) => {
|
||||
const brunoEnvironment = {
|
||||
name: environment.name,
|
||||
variables: []
|
||||
};
|
||||
|
||||
importPostmanEnvironmentVariables(brunoEnvironment, environment.values);
|
||||
return brunoEnvironment;
|
||||
};
|
||||
|
||||
const parsePostmanEnvironment = (str) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
let environment = JSON.parse(str);
|
||||
return resolve(importPostmanEnvironment(environment));
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
if (err instanceof BrunoError) {
|
||||
return reject(err);
|
||||
}
|
||||
return reject(new BrunoError('Unable to parse the postman environment json file'));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const importEnvironment = (fileName) => {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
const str = await readFile(fileName);
|
||||
const environment = await parsePostmanEnvironment(str);
|
||||
resolve(environment);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
reject(new BrunoError('Import Environment failed'));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export default importEnvironment;
|
@ -0,0 +1,153 @@
|
||||
const { postmanTranslation } = require('./postman_translation'); // Adjust path as needed
|
||||
|
||||
describe('postmanTranslation function', () => {
|
||||
test('should translate pm commands correctly', () => {
|
||||
const inputScript = `
|
||||
pm.environment.get('key');
|
||||
pm.environment.set('key', 'value');
|
||||
pm.variables.get('key');
|
||||
pm.variables.set('key', 'value');
|
||||
pm.collectionVariables.get('key');
|
||||
pm.collectionVariables.set('key', 'value');
|
||||
const data = pm.response.json();
|
||||
pm.expect(pm.environment.has('key')).to.be.true;
|
||||
`;
|
||||
const expectedOutput = `
|
||||
bru.getEnvVar('key');
|
||||
bru.setEnvVar('key', 'value');
|
||||
bru.getVar('key');
|
||||
bru.setVar('key', 'value');
|
||||
bru.getVar('key');
|
||||
bru.setVar('key', 'value');
|
||||
const data = res.getBody();
|
||||
expect(bru.getEnvVar('key') !== undefined && bru.getEnvVar('key') !== null).to.be.true;
|
||||
`;
|
||||
expect(postmanTranslation(inputScript)).toBe(expectedOutput);
|
||||
});
|
||||
|
||||
test('should not translate non-pm commands', () => {
|
||||
const inputScript = `
|
||||
console.log('This script does not contain pm commands.');
|
||||
const data = pm.environment.get('key');
|
||||
pm.collectionVariables.set('key', data);
|
||||
`;
|
||||
const expectedOutput = `
|
||||
console.log('This script does not contain pm commands.');
|
||||
const data = bru.getEnvVar('key');
|
||||
bru.setVar('key', data);
|
||||
`;
|
||||
expect(postmanTranslation(inputScript)).toBe(expectedOutput);
|
||||
});
|
||||
|
||||
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}}'));";
|
||||
expect(postmanTranslation(inputScript)).toBe(expectedOutput);
|
||||
});
|
||||
test('should handle multiple pm commands on the same line', () => {
|
||||
const inputScript = "pm.environment.get('key'); pm.environment.set('key', 'value');";
|
||||
const expectedOutput = "bru.getEnvVar('key'); bru.setEnvVar('key', 'value');";
|
||||
expect(postmanTranslation(inputScript)).toBe(expectedOutput);
|
||||
});
|
||||
test('should handle comments and other JavaScript code', () => {
|
||||
const inputScript = `
|
||||
// This is a comment
|
||||
const value = 'test';
|
||||
pm.environment.set('key', value);
|
||||
/*
|
||||
Multi-line comment
|
||||
*/
|
||||
const result = pm.environment.get('key');
|
||||
console.log('Result:', result);
|
||||
`;
|
||||
const expectedOutput = `
|
||||
// This is a comment
|
||||
const value = 'test';
|
||||
bru.setEnvVar('key', value);
|
||||
/*
|
||||
Multi-line comment
|
||||
*/
|
||||
const result = bru.getEnvVar('key');
|
||||
console.log('Result:', result);
|
||||
`;
|
||||
expect(postmanTranslation(inputScript)).toBe(expectedOutput);
|
||||
});
|
||||
|
||||
test('should handle nested commands and edge cases', () => {
|
||||
const inputScript = `
|
||||
const sampleObjects = [
|
||||
{
|
||||
key: pm.environment.get('key'),
|
||||
value: pm.variables.get('value')
|
||||
},
|
||||
{
|
||||
key: pm.collectionVariables.get('key'),
|
||||
value: pm.collectionVariables.get('value')
|
||||
}
|
||||
];
|
||||
const dataTesting = Object.entries(sampleObjects || {}).reduce((acc, [key, value]) => {
|
||||
// this is a comment
|
||||
acc[key] = pm.collectionVariables.get(pm.environment.get(value));
|
||||
return acc; // Return the accumulator
|
||||
}, {});
|
||||
Object.values(dataTesting).forEach((data) => {
|
||||
pm.environment.set(data.key, pm.variables.get(data.value));
|
||||
});
|
||||
`;
|
||||
const expectedOutput = `
|
||||
const sampleObjects = [
|
||||
{
|
||||
key: bru.getEnvVar('key'),
|
||||
value: bru.getVar('value')
|
||||
},
|
||||
{
|
||||
key: bru.getVar('key'),
|
||||
value: bru.getVar('value')
|
||||
}
|
||||
];
|
||||
const dataTesting = Object.entries(sampleObjects || {}).reduce((acc, [key, value]) => {
|
||||
// this is a comment
|
||||
acc[key] = bru.getVar(bru.getEnvVar(value));
|
||||
return acc; // Return the accumulator
|
||||
}, {});
|
||||
Object.values(dataTesting).forEach((data) => {
|
||||
bru.setEnvVar(data.key, bru.getVar(data.value));
|
||||
});
|
||||
`;
|
||||
expect(postmanTranslation(inputScript)).toBe(expectedOutput);
|
||||
});
|
||||
|
||||
test('should handle test commands', () => {
|
||||
const inputScript = `
|
||||
pm.test('Status code is 200', () => {
|
||||
pm.response.to.have.status(200);
|
||||
});
|
||||
pm.test('this test will fail', () => {
|
||||
return false
|
||||
});
|
||||
`;
|
||||
const expectedOutput = `
|
||||
test('Status code is 200', () => {
|
||||
expect(res.getStatus()).to.equal(200);
|
||||
});
|
||||
test('this test will fail', () => {
|
||||
return false
|
||||
});
|
||||
`;
|
||||
expect(postmanTranslation(inputScript)).toBe(expectedOutput);
|
||||
});
|
||||
});
|
||||
|
||||
test('should handle response commands', () => {
|
||||
const inputScript = `
|
||||
const responseTime = pm.response.responseTime;
|
||||
const responseCode = pm.response.code;
|
||||
const responseText = pm.response.text();
|
||||
`;
|
||||
const expectedOutput = `
|
||||
const responseTime = res.getResponseTime();
|
||||
const responseCode = res.getStatus();
|
||||
const responseText = res.getBody()?.toString();
|
||||
`;
|
||||
expect(postmanTranslation(inputScript)).toBe(expectedOutput);
|
||||
});
|
@ -0,0 +1,52 @@
|
||||
const replacements = {
|
||||
'pm\\.environment\\.get\\(': 'bru.getEnvVar(',
|
||||
'pm\\.environment\\.set\\(': 'bru.setEnvVar(',
|
||||
'pm\\.variables\\.get\\(': 'bru.getVar(',
|
||||
'pm\\.variables\\.set\\(': 'bru.setVar(',
|
||||
'pm\\.collectionVariables\\.get\\(': 'bru.getVar(',
|
||||
'pm\\.collectionVariables\\.set\\(': 'bru.setVar(',
|
||||
'pm\\.setNextRequest\\(': 'bru.setNextRequest(',
|
||||
'pm\\.test\\(': 'test(',
|
||||
'pm.response.to.have\\.status\\(': 'expect(res.getStatus()).to.equal(',
|
||||
'pm.response.to.be\\.success\\(': 'expect(res.getStatus()).to.equal(200',
|
||||
'pm\\.response\\.to\\.have\\.status\\(': 'expect(res.getStatus()).to.equal(',
|
||||
'pm\\.response\\.json\\(': 'res.getBody(',
|
||||
'pm\\.expect\\(': 'expect(',
|
||||
'pm\\.environment\\.has\\(([^)]+)\\)': 'bru.getEnvVar($1) !== undefined && bru.getEnvVar($1) !== null',
|
||||
'pm\\.response\\.code': 'res.getStatus()',
|
||||
'pm\\.response\\.text\\(': 'res.getBody()?.toString(',
|
||||
'pm\\.expect\\.fail\\(': 'expect.fail(',
|
||||
'pm\\.response\\.responseTime': 'res.getResponseTime()'
|
||||
};
|
||||
|
||||
const extendedReplacements = Object.keys(replacements).reduce((acc, key) => {
|
||||
const newKey = key.replace(/^pm\\\./, 'postman\\.');
|
||||
acc[key] = replacements[key];
|
||||
acc[newKey] = replacements[key];
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const compiledReplacements = Object.entries(extendedReplacements).map(([pattern, replacement]) => ({
|
||||
regex: new RegExp(pattern, 'g'),
|
||||
replacement
|
||||
}));
|
||||
|
||||
export const postmanTranslation = (script, logCallback) => {
|
||||
try {
|
||||
let modifiedScript = script;
|
||||
let modified = false;
|
||||
for (const { regex, replacement } of compiledReplacements) {
|
||||
if (regex.test(modifiedScript)) {
|
||||
modifiedScript = modifiedScript.replace(regex, replacement);
|
||||
modified = true;
|
||||
}
|
||||
}
|
||||
if (modifiedScript.includes('pm.') || modifiedScript.includes('postman.')) {
|
||||
modifiedScript = modifiedScript.replace(/^(.*(pm\.|postman\.).*)$/gm, '// $1');
|
||||
logCallback?.();
|
||||
}
|
||||
return modifiedScript;
|
||||
} catch (e) {
|
||||
return script;
|
||||
}
|
||||
};
|
7
packages/bruno-converters/src/index.js
Normal file
7
packages/bruno-converters/src/index.js
Normal file
@ -0,0 +1,7 @@
|
||||
export { importCollection as importPostmanCollection } from './importers/postman-collection.js';
|
||||
export { importEnvironment as importPostmanEnvironment } from './importers/postman-environment.js';
|
||||
export { importCollection as importInsomniaCollection } from './importers/insomnia-collection.js';
|
||||
export { importCollection as importOpenAPICollection } from './importers/openapi-collection.js';
|
||||
|
||||
export { exportCollection as exportBrunoCollection } from './collections/export';
|
||||
export { exportCollection as exportPostmanCollection } from './exporters/postman-collection';
|
115
packages/bruno-converters/tests/collections/export.spec.js
Normal file
115
packages/bruno-converters/tests/collections/export.spec.js
Normal file
@ -0,0 +1,115 @@
|
||||
// export.spec.js
|
||||
|
||||
import { describe, it, expect, beforeAll, afterAll } from '@jest/globals';
|
||||
import exportCollection from '../../src/collections/export';
|
||||
import { parseFile, readFile } from '../../src/common/file';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
describe('exportCollection Function', () => {
|
||||
let tempDir;
|
||||
const outputDir = path.resolve(__dirname, '../data/output');
|
||||
const outputFilePath = outputDir + '/export.json';
|
||||
|
||||
// Ensure the output directory exists
|
||||
beforeAll(() => {
|
||||
if (!fs.existsSync(outputDir)) {
|
||||
fs.mkdirSync(outputDir);
|
||||
}
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
// Clean up: remove the temporary directory and its contents
|
||||
try {
|
||||
if (fs.existsSync(outputFilePath)) {
|
||||
await fs.unlinkSync(outputFilePath);
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
console.error('Error during cleanup:', err);
|
||||
}
|
||||
});
|
||||
|
||||
it('should export the collection with UIDs removed and secrets cleared', async () => {
|
||||
// Sample collection object
|
||||
const sampleCollection = {
|
||||
uid: 'collection-uid',
|
||||
name: 'Sample Collection',
|
||||
version: '1',
|
||||
processEnvVariables: { envVar1: 'value1' }, // Should be deleted
|
||||
items: [
|
||||
{
|
||||
uid: 'item-uid-1',
|
||||
name: 'Sample Request',
|
||||
type: 'http-request',
|
||||
request: {
|
||||
url: 'https://api.example.com/data',
|
||||
method: 'GET',
|
||||
headers: [
|
||||
{ uid: 'header-uid-1', name: 'Authorization', value: 'Bearer token' }
|
||||
],
|
||||
params: [{ uid: 'param-uid-1', name: 'query', value: 'test' }],
|
||||
body: {
|
||||
mode: 'none',
|
||||
formUrlEncoded: [{ uid: 'body-param-uid-1', name: 'field', value: 'value' }],
|
||||
multipartForm: []
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
environments: [
|
||||
{
|
||||
uid: 'env-uid-1',
|
||||
name: 'Development',
|
||||
variables: [
|
||||
{ uid: 'var-uid-1', name: 'apiKey', value: 'secretKey', secret: true },
|
||||
{ uid: 'var-uid-2', name: 'timeout', value: '30', secret: false }
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
// Call exportCollection with the sample collection and output file path
|
||||
await exportCollection(sampleCollection, outputFilePath);
|
||||
|
||||
// Read the exported file
|
||||
const exportedCollection = await parseFile(outputFilePath);
|
||||
|
||||
// Expected collection after export
|
||||
const expectedCollection = {
|
||||
name: 'Sample Collection',
|
||||
version: '1',
|
||||
items: [
|
||||
{
|
||||
name: 'Sample Request',
|
||||
type: 'http',
|
||||
request: {
|
||||
url: 'https://api.example.com/data',
|
||||
method: 'GET',
|
||||
headers: [
|
||||
{ name: 'Authorization', value: 'Bearer token' }
|
||||
],
|
||||
query: [{ name: 'query', value: 'test' }],
|
||||
body: {
|
||||
mode: 'none',
|
||||
formUrlEncoded: [{ name: 'field', value: 'value' }],
|
||||
multipartForm: []
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
environments: [
|
||||
{
|
||||
name: 'Development',
|
||||
variables: [
|
||||
{ name: 'apiKey', value: '', secret: true }, // Secret value cleared
|
||||
{ name: 'timeout', value: '30', secret: false }
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
// Assert that the exported collection matches the expected collection
|
||||
expect(exportedCollection).toEqual(expectedCollection);
|
||||
});
|
||||
});
|
83
packages/bruno-converters/tests/common/file.spec.js
Normal file
83
packages/bruno-converters/tests/common/file.spec.js
Normal file
@ -0,0 +1,83 @@
|
||||
// file.spec.js
|
||||
import { parseFile, readFile, saveFile } from '../../src/common/file';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
|
||||
describe('File Operations', () => {
|
||||
const testDataDir = path.resolve(__dirname, '../data');
|
||||
const outputDir = path.resolve(__dirname, '../data/output');
|
||||
|
||||
// Ensure the output directory exists
|
||||
beforeAll(() => {
|
||||
if (!fs.existsSync(outputDir)) {
|
||||
fs.mkdirSync(outputDir);
|
||||
}
|
||||
});
|
||||
|
||||
describe('parseFile Function', () => {
|
||||
it('should parse a valid JSON file', async () => {
|
||||
const filePath = path.resolve(testDataDir, 'valid_env.json');
|
||||
const data = await parseFile(filePath);
|
||||
expect(data.id).toEqual('some-id');
|
||||
});
|
||||
|
||||
it('should parse a valid YAML file', async () => {
|
||||
const filePath = path.resolve(testDataDir, 'sample_openapi.yaml');
|
||||
const data = await parseFile(filePath);
|
||||
expect(data.openapi).toEqual('3.0.0');
|
||||
});
|
||||
|
||||
it.skip('should throw an error for invalid JSON', async () => {
|
||||
const filePath = path.resolve(testDataDir, 'invalid_json_env.json');
|
||||
await expect(await parseFile(filePath)).rejects.toThrow();
|
||||
});
|
||||
|
||||
it('should throw an error for unsupported file formats', async () => {
|
||||
const filePath = path.resolve(testDataDir, 'sample.txt');
|
||||
await expect(parseFile(filePath)).rejects.toThrow('Unsupported file format');
|
||||
});
|
||||
|
||||
it('should handle file read errors', async () => {
|
||||
const filePath = path.resolve(testDataDir, 'nonexistent.json');
|
||||
await expect(parseFile(filePath)).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('readFile Function', () => {
|
||||
it('should read a file successfully', async () => {
|
||||
const filePath = path.resolve(testDataDir, 'sample.txt');
|
||||
const data = await readFile(filePath);
|
||||
expect(data).toBe('This is a sample text file.');
|
||||
});
|
||||
|
||||
it('should throw an error when file does not exist', async () => {
|
||||
const filePath = path.resolve(testDataDir, 'nonexistent.txt');
|
||||
await expect(readFile(filePath)).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('saveFile Function', () => {
|
||||
const outputFilePath = path.resolve(outputDir, 'output.txt');
|
||||
|
||||
afterEach(async () => {
|
||||
// Clean up: delete the output file after each test
|
||||
if (fs.existsSync(outputFilePath)) {
|
||||
await fs.unlinkSync(outputFilePath);
|
||||
}
|
||||
});
|
||||
|
||||
it('should save data to a file successfully', async () => {
|
||||
const data = 'Hello, World!';
|
||||
await saveFile(data, outputFilePath);
|
||||
const savedData = fs.readFileSync(outputFilePath, 'utf8');
|
||||
expect(savedData).toBe(data);
|
||||
});
|
||||
|
||||
it('should throw an error when unable to write to file', async () => {
|
||||
// Attempt to write to an invalid directory
|
||||
const invalidFilePath = path.resolve('/invalid_directory', 'output.txt');
|
||||
const data = 'Test data';
|
||||
await expect(saveFile(data, invalidFilePath)).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
});
|
17
packages/bruno-converters/tests/data/invalid_json_env.json
Normal file
17
packages/bruno-converters/tests/data/invalid_json_env.json
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"id": "some-id",
|
||||
"name": "My Environment",
|
||||
"values": [
|
||||
{
|
||||
"key": "var1",
|
||||
"value": "value1",
|
||||
"enabled": true,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"key": "var2",
|
||||
"value": "value2",
|
||||
"enabled": false,
|
||||
"type": "secret"
|
||||
}
|
||||
] // Missing closing brace
|
1
packages/bruno-converters/tests/data/sample.txt
Normal file
1
packages/bruno-converters/tests/data/sample.txt
Normal file
@ -0,0 +1 @@
|
||||
This is a sample text file.
|
@ -0,0 +1,52 @@
|
||||
{
|
||||
"_type": "export",
|
||||
"__export_format": 4,
|
||||
"__export_date": "2024-05-20T10:02:44.123Z",
|
||||
"__export_source": "insomnia.desktop.app:v2021.5.2",
|
||||
"resources": [
|
||||
{
|
||||
"_id": "req_1",
|
||||
"_type": "request",
|
||||
"parentId": "fld_1",
|
||||
"name": "Request1",
|
||||
"method": "GET",
|
||||
"url": "https://httpbin.org/get",
|
||||
"parameters": []
|
||||
},
|
||||
{
|
||||
"_id": "req_2",
|
||||
"_type": "request",
|
||||
"parentId": "fld_2",
|
||||
"name": "Request2",
|
||||
"method": "GET",
|
||||
"url": "https://httpbin.org/get",
|
||||
"parameters": []
|
||||
},
|
||||
{
|
||||
"_id": "fld_1",
|
||||
"_type": "request_group",
|
||||
"parentId": "wrk_1",
|
||||
"name": "Folder1"
|
||||
},
|
||||
{
|
||||
"_id": "fld_2",
|
||||
"_type": "request_group",
|
||||
"parentId": "wrk_1",
|
||||
"name": "Folder2"
|
||||
},
|
||||
{
|
||||
"_id": "wrk_1",
|
||||
"_type": "workspace",
|
||||
"name": "Hello World Workspace Insomnia"
|
||||
},
|
||||
{
|
||||
"_id": "env_1",
|
||||
"_type": "environment",
|
||||
"parentId": "wrk_1",
|
||||
"data": {
|
||||
"var1": "value1",
|
||||
"var2": "value2"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
33
packages/bruno-converters/tests/data/sample_openapi.yaml
Normal file
33
packages/bruno-converters/tests/data/sample_openapi.yaml
Normal file
@ -0,0 +1,33 @@
|
||||
openapi: "3.0.0"
|
||||
info:
|
||||
version: "1.0.0"
|
||||
title: "Hello World OpenAPI"
|
||||
paths:
|
||||
/get:
|
||||
get:
|
||||
tags:
|
||||
- Folder1
|
||||
- Folder2
|
||||
summary: "Request1 and Request2"
|
||||
operationId: "getRequests"
|
||||
responses:
|
||||
'200':
|
||||
description: "Successful response"
|
||||
components:
|
||||
parameters:
|
||||
var1:
|
||||
in: "query"
|
||||
name: "var1"
|
||||
required: true
|
||||
schema:
|
||||
type: "string"
|
||||
default: "value1"
|
||||
var2:
|
||||
in: "query"
|
||||
name: "var2"
|
||||
required: true
|
||||
schema:
|
||||
type: "string"
|
||||
default: "value2"
|
||||
servers:
|
||||
- url: "https://httpbin.org"
|
@ -0,0 +1,73 @@
|
||||
{
|
||||
"info": {
|
||||
"_postman_id": "12345678-abcd-1234-abcd-1234567890ab",
|
||||
"name": "Sample Postman Collection with Folder",
|
||||
"description": "A sample collection with a folder for testing purposes.",
|
||||
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
|
||||
},
|
||||
"item": [
|
||||
{
|
||||
"name": "Sample Folder",
|
||||
"description": "This is a sample folder containing requests.",
|
||||
"item": [
|
||||
{
|
||||
"name": "Sample GET Request",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "https://postman-echo.com/get?foo1=bar1&foo2=bar2",
|
||||
"protocol": "https",
|
||||
"host": ["postman-echo", "com"],
|
||||
"path": ["get"],
|
||||
"query": [
|
||||
{ "key": "foo1", "value": "bar1" },
|
||||
{ "key": "foo2", "value": "bar2" }
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "Sample POST Request",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [
|
||||
{ "key": "Content-Type", "value": "application/json" }
|
||||
],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{ \"key\": \"value\" }",
|
||||
"options": {
|
||||
"raw": {
|
||||
"language": "json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"url": {
|
||||
"raw": "https://postman-echo.com/post",
|
||||
"protocol": "https",
|
||||
"host": ["postman-echo", "com"],
|
||||
"path": ["post"]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Standalone Request",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "https://postman-echo.com/status/200",
|
||||
"protocol": "https",
|
||||
"host": ["postman-echo", "com"],
|
||||
"path": ["status", "200"]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
}
|
||||
]
|
||||
}
|
18
packages/bruno-converters/tests/data/valid_env.json
Normal file
18
packages/bruno-converters/tests/data/valid_env.json
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"id": "some-id",
|
||||
"name": "My Environment",
|
||||
"values": [
|
||||
{
|
||||
"key": "var1",
|
||||
"value": "value1",
|
||||
"enabled": true,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"key": "var2",
|
||||
"value": "value2",
|
||||
"enabled": false,
|
||||
"type": "secret"
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,130 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`insomnia-collection should correctly import a valid Insomnia collection file 1`] = `
|
||||
{
|
||||
"environments": [],
|
||||
"items": [
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"name": "Request1",
|
||||
"request": {
|
||||
"auth": {
|
||||
"basic": null,
|
||||
"bearer": null,
|
||||
"digest": null,
|
||||
"mode": "none",
|
||||
},
|
||||
"body": {
|
||||
"formUrlEncoded": [],
|
||||
"json": null,
|
||||
"mode": "none",
|
||||
"multipartForm": [],
|
||||
"text": null,
|
||||
"xml": null,
|
||||
},
|
||||
"headers": [],
|
||||
"method": "GET",
|
||||
"params": [],
|
||||
"url": "https://httpbin.org/get",
|
||||
},
|
||||
"seq": 1,
|
||||
"type": "http-request",
|
||||
"uid": "mockeduuidvalue123456",
|
||||
},
|
||||
{
|
||||
"name": "Request1",
|
||||
"request": {
|
||||
"auth": {
|
||||
"basic": null,
|
||||
"bearer": null,
|
||||
"digest": null,
|
||||
"mode": "none",
|
||||
},
|
||||
"body": {
|
||||
"formUrlEncoded": [],
|
||||
"json": null,
|
||||
"mode": "none",
|
||||
"multipartForm": [],
|
||||
"text": null,
|
||||
"xml": null,
|
||||
},
|
||||
"headers": [],
|
||||
"method": "GET",
|
||||
"params": [],
|
||||
"url": "https://httpbin.org/get",
|
||||
},
|
||||
"seq": 2,
|
||||
"type": "http-request",
|
||||
"uid": "mockeduuidvalue123456",
|
||||
},
|
||||
],
|
||||
"name": "Folder1",
|
||||
"type": "folder",
|
||||
"uid": "mockeduuidvalue123456",
|
||||
},
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"name": "Request2",
|
||||
"request": {
|
||||
"auth": {
|
||||
"basic": null,
|
||||
"bearer": null,
|
||||
"digest": null,
|
||||
"mode": "none",
|
||||
},
|
||||
"body": {
|
||||
"formUrlEncoded": [],
|
||||
"json": null,
|
||||
"mode": "none",
|
||||
"multipartForm": [],
|
||||
"text": null,
|
||||
"xml": null,
|
||||
},
|
||||
"headers": [],
|
||||
"method": "GET",
|
||||
"params": [],
|
||||
"url": "https://httpbin.org/get",
|
||||
},
|
||||
"seq": 1,
|
||||
"type": "http-request",
|
||||
"uid": "mockeduuidvalue123456",
|
||||
},
|
||||
{
|
||||
"name": "Request2",
|
||||
"request": {
|
||||
"auth": {
|
||||
"basic": null,
|
||||
"bearer": null,
|
||||
"digest": null,
|
||||
"mode": "none",
|
||||
},
|
||||
"body": {
|
||||
"formUrlEncoded": [],
|
||||
"json": null,
|
||||
"mode": "none",
|
||||
"multipartForm": [],
|
||||
"text": null,
|
||||
"xml": null,
|
||||
},
|
||||
"headers": [],
|
||||
"method": "GET",
|
||||
"params": [],
|
||||
"url": "https://httpbin.org/get",
|
||||
},
|
||||
"seq": 2,
|
||||
"type": "http-request",
|
||||
"uid": "mockeduuidvalue123456",
|
||||
},
|
||||
],
|
||||
"name": "Folder2",
|
||||
"type": "folder",
|
||||
"uid": "mockeduuidvalue123456",
|
||||
},
|
||||
],
|
||||
"name": "Hello World Workspace Insomnia",
|
||||
"uid": "mockeduuidvalue123456",
|
||||
"version": "1",
|
||||
}
|
||||
`;
|
@ -0,0 +1,48 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`openapi-collection should correctly import a valid OpenAPI file 1`] = `
|
||||
{
|
||||
"environments": [],
|
||||
"items": [
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"name": "Request1 and Request2",
|
||||
"request": {
|
||||
"auth": {
|
||||
"basic": null,
|
||||
"bearer": null,
|
||||
"digest": null,
|
||||
"mode": "none",
|
||||
},
|
||||
"body": {
|
||||
"formUrlEncoded": [],
|
||||
"json": null,
|
||||
"mode": "none",
|
||||
"multipartForm": [],
|
||||
"text": null,
|
||||
"xml": null,
|
||||
},
|
||||
"headers": [],
|
||||
"method": "GET",
|
||||
"params": [],
|
||||
"script": {
|
||||
"res": null,
|
||||
},
|
||||
"url": "https:/httpbin.org//get",
|
||||
},
|
||||
"seq": 1,
|
||||
"type": "http-request",
|
||||
"uid": "mockeduuidvalue123456",
|
||||
},
|
||||
],
|
||||
"name": "Folder1",
|
||||
"type": "folder",
|
||||
"uid": "mockeduuidvalue123456",
|
||||
},
|
||||
],
|
||||
"name": "Hello World OpenAPI",
|
||||
"uid": "mockeduuidvalue123456",
|
||||
"version": "1",
|
||||
}
|
||||
`;
|
@ -0,0 +1,125 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`postman-collection should correctly import a valid Postman collection file 1`] = `
|
||||
{
|
||||
"environments": [],
|
||||
"items": [
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"name": "Sample GET Request",
|
||||
"request": {
|
||||
"auth": {
|
||||
"awsv4": null,
|
||||
"basic": null,
|
||||
"bearer": null,
|
||||
"mode": "none",
|
||||
},
|
||||
"body": {
|
||||
"formUrlEncoded": [],
|
||||
"json": null,
|
||||
"mode": "none",
|
||||
"multipartForm": [],
|
||||
"text": null,
|
||||
"xml": null,
|
||||
},
|
||||
"docs": undefined,
|
||||
"headers": [],
|
||||
"method": "GET",
|
||||
"params": [
|
||||
{
|
||||
"description": undefined,
|
||||
"enabled": true,
|
||||
"name": "foo1",
|
||||
"type": "query",
|
||||
"uid": "mockeduuidvalue123456",
|
||||
"value": "bar1",
|
||||
},
|
||||
{
|
||||
"description": undefined,
|
||||
"enabled": true,
|
||||
"name": "foo2",
|
||||
"type": "query",
|
||||
"uid": "mockeduuidvalue123456",
|
||||
"value": "bar2",
|
||||
},
|
||||
],
|
||||
"url": "https://postman-echo.com/get?foo1=bar1&foo2=bar2",
|
||||
},
|
||||
"seq": 1,
|
||||
"type": "http-request",
|
||||
"uid": "mockeduuidvalue123456",
|
||||
},
|
||||
{
|
||||
"name": "Sample POST Request",
|
||||
"request": {
|
||||
"auth": {
|
||||
"awsv4": null,
|
||||
"basic": null,
|
||||
"bearer": null,
|
||||
"mode": "none",
|
||||
},
|
||||
"body": {
|
||||
"formUrlEncoded": [],
|
||||
"json": "{ "key": "value" }",
|
||||
"mode": "json",
|
||||
"multipartForm": [],
|
||||
"text": null,
|
||||
"xml": null,
|
||||
},
|
||||
"docs": undefined,
|
||||
"headers": [
|
||||
{
|
||||
"description": undefined,
|
||||
"enabled": true,
|
||||
"name": "Content-Type",
|
||||
"uid": "mockeduuidvalue123456",
|
||||
"value": "application/json",
|
||||
},
|
||||
],
|
||||
"method": "POST",
|
||||
"params": [],
|
||||
"url": "https://postman-echo.com/post",
|
||||
},
|
||||
"seq": 2,
|
||||
"type": "http-request",
|
||||
"uid": "mockeduuidvalue123456",
|
||||
},
|
||||
],
|
||||
"name": "Sample Folder",
|
||||
"type": "folder",
|
||||
"uid": "mockeduuidvalue123456",
|
||||
},
|
||||
{
|
||||
"name": "Standalone Request",
|
||||
"request": {
|
||||
"auth": {
|
||||
"awsv4": null,
|
||||
"basic": null,
|
||||
"bearer": null,
|
||||
"mode": "none",
|
||||
},
|
||||
"body": {
|
||||
"formUrlEncoded": [],
|
||||
"json": null,
|
||||
"mode": "none",
|
||||
"multipartForm": [],
|
||||
"text": null,
|
||||
"xml": null,
|
||||
},
|
||||
"docs": undefined,
|
||||
"headers": [],
|
||||
"method": "GET",
|
||||
"params": [],
|
||||
"url": "https://postman-echo.com/status/200",
|
||||
},
|
||||
"seq": 1,
|
||||
"type": "http-request",
|
||||
"uid": "mockeduuidvalue123456",
|
||||
},
|
||||
],
|
||||
"name": "Sample Postman Collection with Folder",
|
||||
"uid": "mockeduuidvalue123456",
|
||||
"version": "1",
|
||||
}
|
||||
`;
|
@ -0,0 +1,16 @@
|
||||
import { describe, it, expect } from '@jest/globals';
|
||||
import path from 'path';
|
||||
import { importInsomniaCollection } from '../../src';
|
||||
|
||||
describe('insomnia-collection', () => {
|
||||
it('should correctly import a valid Insomnia collection file', async () => {
|
||||
// Path to the sample Insomnia file
|
||||
const fileName = path.resolve(__dirname, '../data', 'sample_insomnia_export.json');
|
||||
|
||||
// Call the importCollection function with the sample file
|
||||
const brunoCollection = await importInsomniaCollection(fileName);
|
||||
|
||||
// Assert that the returned collection matches the expected structure
|
||||
expect(brunoCollection).toMatchSnapshot()
|
||||
});
|
||||
});
|
@ -0,0 +1,16 @@
|
||||
import { describe, it, expect } from '@jest/globals';
|
||||
import path from 'path';
|
||||
import { importOpenAPICollection } from '../../src';
|
||||
|
||||
describe('openapi-collection', () => {
|
||||
it('should correctly import a valid OpenAPI file', async () => {
|
||||
// Path to the sample OpenAPI file
|
||||
const fileName = path.resolve(__dirname, '../data', 'sample_openapi.yaml');
|
||||
|
||||
// Call the importCollection function with the sample file
|
||||
const brunoCollection = await importOpenAPICollection(fileName);
|
||||
|
||||
// Assert that the returned collection matches the expected structure
|
||||
expect(brunoCollection).toMatchSnapshot()
|
||||
});
|
||||
});
|
@ -0,0 +1,16 @@
|
||||
import { describe, it, expect } from '@jest/globals';
|
||||
import path from 'path';
|
||||
import { importPostmanCollection } from '../../src';
|
||||
|
||||
describe('postman-collection', () => {
|
||||
it('should correctly import a valid Postman collection file', async () => {
|
||||
// Path to the sample Postman collection file
|
||||
const fileName = path.resolve(__dirname, '../data', 'sample_postman_collection.json');
|
||||
|
||||
// Call the importCollection function with the sample file
|
||||
const brunoCollection = await importPostmanCollection(fileName);
|
||||
|
||||
// Assert that the returned collection matches the expected structure
|
||||
expect(brunoCollection).toMatchSnapshot()
|
||||
});
|
||||
});
|
@ -0,0 +1,43 @@
|
||||
// postman-environment.spec.js
|
||||
|
||||
import { describe, it, expect } from '@jest/globals';
|
||||
import { BrunoError } from '../../src/common/error';
|
||||
import path from 'path';
|
||||
import { importPostmanEnvironment } from '../../src';
|
||||
|
||||
describe('importEnvironment Function', () => {
|
||||
it('should correctly import a valid Postman environment file', async () => {
|
||||
const fileName = path.resolve(__dirname, '../data', 'valid_env.json');
|
||||
|
||||
const brunoEnvironment = await importPostmanEnvironment(fileName);
|
||||
|
||||
const expectedEnvironment = {
|
||||
name: 'My Environment',
|
||||
variables: [
|
||||
{
|
||||
name: 'var1',
|
||||
value: 'value1',
|
||||
enabled: true,
|
||||
secret: false,
|
||||
},
|
||||
{
|
||||
name: 'var2',
|
||||
value: 'value2',
|
||||
enabled: false,
|
||||
secret: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
expect(brunoEnvironment).toEqual(expectedEnvironment);
|
||||
});
|
||||
|
||||
it.skip('should throw BrunoError when JSON parsing fails', async () => {
|
||||
const fileName = path.resolve(__dirname, '../data', 'invalid_json_env.json');
|
||||
|
||||
await expect(importEnvironment(fileName)).rejects.toThrow(BrunoError);
|
||||
await expect(importEnvironment(fileName)).rejects.toThrow(
|
||||
'Unable to parse the postman environment json file'
|
||||
);
|
||||
});
|
||||
});
|
19
packages/bruno-converters/tsconfig.json
Normal file
19
packages/bruno-converters/tsconfig.json
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES6",
|
||||
"esModuleInterop": true,
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"jsx": "react",
|
||||
"module": "ESNext",
|
||||
"declaration": true,
|
||||
"declarationDir": "types",
|
||||
"sourceMap": true,
|
||||
"outDir": "dist",
|
||||
"moduleResolution": "node",
|
||||
"emitDeclarationOnly": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
},
|
||||
"exclude": ["dist", "node_modules", "tests"]
|
||||
}
|
6
packages/bruno-converters/types/common.d.ts
vendored
Normal file
6
packages/bruno-converters/types/common.d.ts
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
export declare const uuid: () => string;
|
||||
export declare const normalizeFileName: (name: string) => string;
|
||||
export declare const validateSchema: (collection?: {}) => Promise<unknown>;
|
||||
export declare const updateUidsInCollection: (_collection: any) => any;
|
||||
export declare const transformItemsInCollection: (collection: any) => any;
|
||||
export declare const hydrateSeqInCollection: (collection: any) => any;
|
2
packages/bruno-converters/types/postman-collection.d.ts
vendored
Normal file
2
packages/bruno-converters/types/postman-collection.d.ts
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
declare const importCollection: (fileName: any, options: any) => Promise<unknown>;
|
||||
export default importCollection;
|
Loading…
x
Reference in New Issue
Block a user