Support Importing Collection Level Auth from Postman (Merge pull request #4475)

This commit is contained in:
Anoop M D
2025-04-22 21:27:06 +05:30
committed by GitHub
9 changed files with 956 additions and 850 deletions

View File

@@ -35,6 +35,7 @@
},
"scripts": {
"setup": "node ./scripts/setup.js",
"watch:converters": "npm run watch --workspace=packages/bruno-converters",
"dev": "concurrently --kill-others \"npm run dev:web\" \"npm run dev:electron\"",
"dev:web": "npm run dev --workspace=packages/bruno-app",
"build:web": "npm run build --workspace=packages/bruno-app",

View File

@@ -43,6 +43,8 @@ const convertV21Auth = (array) => {
};
const constructUrlFromParts = (url) => {
if (!url) return '';
const { protocol = 'http', host, path, port, query, hash } = url || {};
const hostStr = Array.isArray(host) ? host.filter(Boolean).join('.') : host || '';
const pathStr = Array.isArray(path) ? path.filter(Boolean).join('/') : path || '';
@@ -50,7 +52,7 @@ const constructUrlFromParts = (url) => {
const queryStr =
query && Array.isArray(query) && query.length > 0
? `?${query
.filter((q) => q.key)
.filter((q) => q && q.key)
.map((q) => `${q.key}=${q.value || ''}`)
.join('&')}`
: '';
@@ -140,6 +142,110 @@ const importCollectionLevelVariables = (variables, requestObject) => {
requestObject.vars.req = vars;
};
const processAuth = (auth, requestObject) => {
if (!auth || !auth.type || auth.type === 'noauth') {
return;
}
let authValues = auth[auth.type];
if (Array.isArray(authValues)) {
authValues = convertV21Auth(authValues);
}
if (auth.type === 'basic') {
requestObject.auth.mode = 'basic';
requestObject.auth.basic = {
username: authValues.username || '',
password: authValues.password || ''
};
} else if (auth.type === 'bearer') {
requestObject.auth.mode = 'bearer';
requestObject.auth.bearer = {
token: authValues.token || ''
};
} else if (auth.type === 'awsv4') {
requestObject.auth.mode = 'awsv4';
requestObject.auth.awsv4 = {
accessKeyId: authValues.accessKey || '',
secretAccessKey: authValues.secretKey || '',
sessionToken: authValues.sessionToken || '',
service: authValues.service || '',
region: authValues.region || '',
profileName: ''
};
} else if (auth.type === 'apikey') {
requestObject.auth.mode = 'apikey';
requestObject.auth.apikey = {
key: authValues.key || '',
value: authValues.value?.toString() || '', // Convert the value to a string as Postman's schema does not rigidly define the type of it,
placement: 'header' //By default we are placing the apikey values in headers!
};
} else if (auth.type === 'digest') {
requestObject.auth.mode = 'digest';
requestObject.auth.digest = {
username: authValues.username || '',
password: authValues.password || ''
};
} else if (auth.type === 'oauth2') {
const findValueUsingKey = (key) => {
return authValues[key] || '';
};
const oauth2GrantTypeMaps = {
authorization_code_with_pkce: 'authorization_code',
authorization_code: 'authorization_code',
client_credentials: 'client_credentials',
password_credentials: 'password_credentials'
};
const grantType = oauth2GrantTypeMaps[findValueUsingKey('grant_type')] || 'authorization_code';
requestObject.auth.mode = 'oauth2';
if (grantType === 'authorization_code') {
requestObject.auth.oauth2 = {
grantType: 'authorization_code',
authorizationUrl: findValueUsingKey('authUrl'),
callbackUrl: findValueUsingKey('redirect_uri'),
accessTokenUrl: findValueUsingKey('accessTokenUrl'),
refreshTokenUrl: findValueUsingKey('refreshTokenUrl'),
clientId: findValueUsingKey('clientId'),
clientSecret: findValueUsingKey('clientSecret'),
scope: findValueUsingKey('scope'),
state: findValueUsingKey('state'),
pkce: Boolean(findValueUsingKey('grant_type') == 'authorization_code_with_pkce'),
tokenPlacement: findValueUsingKey('addTokenTo') == 'header' ? 'header' : 'url',
credentialsPlacement: findValueUsingKey('client_authentication') == 'body' ? 'body' : 'basic_auth_header'
};
} else if (grantType === 'password_credentials') {
requestObject.auth.oauth2 = {
grantType: 'password',
accessTokenUrl: findValueUsingKey('accessTokenUrl'),
refreshTokenUrl: findValueUsingKey('refreshTokenUrl'),
username: findValueUsingKey('username'),
password: findValueUsingKey('password'),
clientId: findValueUsingKey('clientId'),
clientSecret: findValueUsingKey('clientSecret'),
scope: findValueUsingKey('scope'),
state: findValueUsingKey('state'),
tokenPlacement: findValueUsingKey('addTokenTo') == 'header' ? 'header' : 'url',
credentialsPlacement: findValueUsingKey('client_authentication') == 'body' ? 'body' : 'basic_auth_header'
};
} else if (grantType === 'client_credentials') {
requestObject.auth.oauth2 = {
grantType: 'client_credentials',
accessTokenUrl: findValueUsingKey('accessTokenUrl'),
refreshTokenUrl: findValueUsingKey('refreshTokenUrl'),
clientId: findValueUsingKey('clientId'),
clientSecret: findValueUsingKey('clientSecret'),
scope: findValueUsingKey('scope'),
state: findValueUsingKey('state'),
tokenPlacement: findValueUsingKey('addTokenTo') == 'header' ? 'header' : 'url',
credentialsPlacement: findValueUsingKey('client_authentication') == 'body' ? 'body' : 'basic_auth_header'
};
}
} else {
console.warn('Unexpected auth.type', auth.type);
}
};
const importPostmanV2CollectionItem = (brunoParent, item, parentAuth) => {
brunoParent.items = brunoParent.items || [];
const folderMap = {};
@@ -172,7 +278,10 @@ const importPostmanV2CollectionItem = (brunoParent, item, parentAuth) => {
mode: 'none',
basic: null,
bearer: null,
awsv4: null
awsv4: null,
apikey: null,
oauth2: null,
digest: null
},
headers: [],
script: {},
@@ -181,6 +290,15 @@ const importPostmanV2CollectionItem = (brunoParent, item, parentAuth) => {
}
}
};
// Folder level auth
if (i.auth) {
processAuth(i.auth, brunoFolderItem.root.request);
} else if (parentAuth) {
// Inherit parent auth if folder doesn't define its own
processAuth(parentAuth, brunoFolderItem.root.request);
}
if (i.item && i.item.length) {
importPostmanV2CollectionItem(brunoFolderItem, i.item, i.auth ?? parentAuth);
}
@@ -194,8 +312,8 @@ const importPostmanV2CollectionItem = (brunoParent, item, parentAuth) => {
} else {
if (i.request) {
if(!requestMethods.includes(i?.request?.method.toUpperCase())){
console.warn("Unexpected request.method")
if (!requestMethods.includes(i?.request?.method.toUpperCase())) {
console.warn('Unexpected request.method', i?.request?.method);
return;
}
@@ -221,7 +339,10 @@ const importPostmanV2CollectionItem = (brunoParent, item, parentAuth) => {
mode: 'none',
basic: null,
bearer: null,
awsv4: null
awsv4: null,
apikey: null,
oauth2: null,
digest: null
},
headers: [],
params: [],
@@ -356,102 +477,9 @@ const importPostmanV2CollectionItem = (brunoParent, item, parentAuth) => {
});
});
// Handle request-level auth or inherit from parent
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: ''
};
} else if (auth.type === 'apikey'){
brunoRequestItem.request.auth.mode = 'apikey';
brunoRequestItem.request.auth.apikey = {
key: authValues.key,
value: authValues.value?.toString(), // Convert the value to a string as Postman's schema does not rigidly define the type of it,
placement: "header" //By default we are placing the apikey values in headers!
}
} else if (auth.type === 'oauth2'){
const findValueUsingKey = (key) => {
return auth?.oauth2?.find(v => v?.key == key)?.value || ''
}
const oauth2GrantTypeMaps = {
'authorization_code_with_pkce': 'authorization_code',
'authorization_code': 'authorization_code',
'client_credentials': 'client_credentials',
'password_credentials': 'password_credentials'
}
const grantType = oauth2GrantTypeMaps[findValueUsingKey('grant_type')] || 'authorization_code';
if (grantType) {
brunoRequestItem.request.auth.mode = 'oauth2';
switch(grantType) {
case 'authorization_code':
brunoRequestItem.request.auth.oauth2 = {
grantType: 'authorization_code',
authorizationUrl: findValueUsingKey('authUrl'),
callbackUrl: findValueUsingKey('redirect_uri'),
accessTokenUrl: findValueUsingKey('accessTokenUrl'),
refreshTokenUrl: findValueUsingKey('refreshTokenUrl'),
clientId: findValueUsingKey('clientId'),
clientSecret: findValueUsingKey('clientSecret'),
scope: findValueUsingKey('scope'),
state: findValueUsingKey('state'),
pkce: Boolean(findValueUsingKey('grant_type') == 'authorization_code_with_pkce'),
tokenPlacement: findValueUsingKey('addTokenTo') == 'header' ? 'header' : 'url',
credentialsPlacement: findValueUsingKey('client_authentication') == 'body' ? 'body' : 'basic_auth_header'
};
break;
case 'password_credentials':
brunoRequestItem.request.auth.oauth2 = {
grantType: 'password',
accessTokenUrl: findValueUsingKey('accessTokenUrl'),
refreshTokenUrl: findValueUsingKey('refreshTokenUrl'),
username: findValueUsingKey('username'),
password: findValueUsingKey('password'),
clientId: findValueUsingKey('clientId'),
clientSecret: findValueUsingKey('clientSecret'),
scope: findValueUsingKey('scope'),
state: findValueUsingKey('state'),
tokenPlacement: findValueUsingKey('addTokenTo') == 'header' ? 'header' : 'url',
credentialsPlacement: findValueUsingKey('client_authentication') == 'body' ? 'body' : 'basic_auth_header'
};
break;
case 'client_credentials':
brunoRequestItem.request.auth.oauth2 = {
grantType: 'client_credentials',
accessTokenUrl: findValueUsingKey('accessTokenUrl'),
refreshTokenUrl: findValueUsingKey('refreshTokenUrl'),
clientId: findValueUsingKey('clientId'),
clientSecret: findValueUsingKey('clientSecret'),
scope: findValueUsingKey('scope'),
state: findValueUsingKey('state'),
tokenPlacement: findValueUsingKey('addTokenTo') == 'header' ? 'header' : 'url',
credentialsPlacement: findValueUsingKey('client_authentication') == 'body' ? 'body' : 'basic_auth_header'
};
break;
}
}
}
}
processAuth(auth, brunoRequestItem.request);
each(get(i, 'request.url.query'), (param) => {
brunoRequestItem.request.params.push({
@@ -519,7 +547,10 @@ const importPostmanV2Collection = (collection) => {
mode: 'none',
basic: null,
bearer: null,
awsv4: null
awsv4: null,
apikey: null,
oauth2: null,
digest: null
},
headers: [],
script: {},
@@ -533,10 +564,13 @@ const importPostmanV2Collection = (collection) => {
importScriptsFromEvents(collection.event, brunoCollection.root.request);
}
if (collection?.variable){
if (collection?.variable) {
importCollectionLevelVariables(collection.variable, brunoCollection.root.request);
}
// Collection level auth
processAuth(collection.auth, brunoCollection.root.request);
importPostmanV2CollectionItem(brunoCollection, collection.item, collection.auth);
return brunoCollection;
@@ -557,28 +591,28 @@ const parsePostmanCollection = (collection) => {
return importPostmanV2Collection(collection);
}
throw new Error('Unknown postman schema');
throw new Error('Unsupported Postman schema version. Only Postman Collection v2.0 and v2.1 are supported.');
} catch (err) {
console.log(err);
if (err instanceof Error) {
throw err;
}
throw new Error('Unable to parse the postman collection json file');
throw new Error('Invalid Postman collection format. Please check your JSON file.');
}
};
const postmanToBruno = (postmanCollection) => {
try {
const parsedPostmanCollection = parsePostmanCollection(postmanCollection);
const transformedCollection = transformItemsInCollection(parsedPostmanCollection);
const hydratedCollection = hydrateSeqInCollection(transformedCollection);
const validatedCollection = validateSchema(hydratedCollection);
return validatedCollection;
} catch(err) {
console.log(err);
throw new Error('Import collection failed');
}
try {
const parsedPostmanCollection = parsePostmanCollection(postmanCollection);
const transformedCollection = transformItemsInCollection(parsedPostmanCollection);
const hydratedCollection = hydrateSeqInCollection(transformedCollection);
const validatedCollection = validateSchema(hydratedCollection);
return validatedCollection;
} catch (err) {
console.log(err);
throw new Error(`Import collection failed: ${err.message}`);
}
};
export default postmanToBruno;

View File

@@ -1,734 +0,0 @@
import { describe, it, expect } from '@jest/globals';
import postmanToBruno from '../../src/postman/postman-to-bruno';
describe('postman-collection', () => {
it('should correctly import a valid Postman collection file', async () => {
const brunoCollection = postmanToBruno(postmanCollection);
expect(brunoCollection).toMatchObject(expectedOutput);
});
});
const postmanCollection = {
"info": {
"_postman_id": "0596d399-cfd2-4f8f-9869-65238eb40a45",
"name": "CRUD",
"schema": "https://schema.getpostman.com/json/collection/v2.0.0/collection.json",
"_exporter_id": "32111649",
"_collection_link": "https://www.postman.com/fudzi9/workspace/nodejs/collection/16541095-0596d399-cfd2-4f8f-9869-65238eb40a45?action=share&source=collection_link&creator=32111649"
},
"item": [
{
"name": "GET",
"request": {
"method": "GET",
"header": [],
"url": "https://node-task2.herokuapp.com/api/notes/"
},
"response": [
{
"name": "1.GET",
"originalRequest": {
"method": "GET",
"header": [],
"url": "https://node-task2.herokuapp.com/api/notes/"
},
"status": "OK",
"code": 200,
"_postman_previewlanguage": "json",
"header": [
{
"key": "Server",
"value": "Cowboy"
},
{
"key": "Connection",
"value": "keep-alive"
},
{
"key": "X-Powered-By",
"value": "Express"
},
{
"key": "Content-Type",
"value": "application/json; charset=utf-8"
},
{
"key": "Content-Length",
"value": "2"
},
{
"key": "Etag",
"value": "W/\"2-l9Fw4VUO7kr8CvBlt4zaMCqXZ0w\""
},
{
"key": "Date",
"value": "Tue, 06 Jul 2021 21:30:45 GMT"
},
{
"key": "Via",
"value": "1.1 vegur"
}
],
"cookie": [],
"body": "[]"
},
{
"name": "3.GET",
"originalRequest": {
"method": "GET",
"header": [],
"url": "https://node-task2.herokuapp.com/api/notes/"
},
"status": "OK",
"code": 200,
"_postman_previewlanguage": "json",
"header": [
{
"key": "Server",
"value": "Cowboy"
},
{
"key": "Connection",
"value": "keep-alive"
},
{
"key": "X-Powered-By",
"value": "Express"
},
{
"key": "Content-Type",
"value": "application/json; charset=utf-8"
},
{
"key": "Content-Length",
"value": "96"
},
{
"key": "Etag",
"value": "W/\"60-ixboSJswZpL0hV7rJrY1IE5nQlM\""
},
{
"key": "Date",
"value": "Tue, 06 Jul 2021 21:58:32 GMT"
},
{
"key": "Via",
"value": "1.1 vegur"
}
],
"cookie": [],
"body": "[\n {\n \"id\": 1,\n \"title\": \"first\",\n \"content\": \"some text\",\n \"createdAt\": \"some date\",\n \"updatedAt\": \"some date\"\n }\n]"
},
{
"name": "5.GET",
"originalRequest": {
"method": "GET",
"header": [],
"url": "https://node-task2.herokuapp.com/api/notes/"
},
"status": "OK",
"code": 200,
"_postman_previewlanguage": "json",
"header": [
{
"key": "Server",
"value": "Cowboy"
},
{
"key": "Connection",
"value": "keep-alive"
},
{
"key": "X-Powered-By",
"value": "Express"
},
{
"key": "Content-Type",
"value": "application/json; charset=utf-8"
},
{
"key": "Content-Length",
"value": "192"
},
{
"key": "Etag",
"value": "W/\"c0-rg+VAYKuV+nAzdAnddMXRNSM3tg\""
},
{
"key": "Date",
"value": "Tue, 06 Jul 2021 22:01:36 GMT"
},
{
"key": "Via",
"value": "1.1 vegur"
}
],
"cookie": [],
"body": "[\n {\n \"id\": 1,\n \"title\": \"first\",\n \"content\": \"some text\",\n \"createdAt\": \"some date\",\n \"updatedAt\": \"some date\"\n },\n {\n \"id\": 2,\n \"title\": \"second\",\n \"content\": \"some text\",\n \"createdAt\": \"some date\",\n \"updatedAt\": \"some date\"\n }\n]"
},
{
"name": "7.GET",
"originalRequest": {
"method": "GET",
"header": [],
"url": "https://node-task2.herokuapp.com/api/notes/"
},
"status": "OK",
"code": 200,
"_postman_previewlanguage": "json",
"header": [
{
"key": "Server",
"value": "Cowboy"
},
{
"key": "Connection",
"value": "keep-alive"
},
{
"key": "X-Powered-By",
"value": "Express"
},
{
"key": "Content-Type",
"value": "application/json; charset=utf-8"
},
{
"key": "Content-Length",
"value": "199"
},
{
"key": "Etag",
"value": "W/\"c7-SBFGBh+BSdmKqSUIW4VDODIOnaI\""
},
{
"key": "Date",
"value": "Tue, 06 Jul 2021 22:38:51 GMT"
},
{
"key": "Via",
"value": "1.1 vegur"
}
],
"cookie": [],
"body": "[\n {\n \"id\": 2,\n \"title\": \"second\",\n \"content\": \"some text\",\n \"createdAt\": \"some date\",\n \"updatedAt\": \"some date\"\n },\n {\n \"id\": 1,\n \"title\": \"first changed\",\n \"content\": \"new text\",\n \"createdAt\": \"some date\",\n \"updatedAt\": \"some date\"\n }\n]"
},
{
"name": "9.GET",
"originalRequest": {
"method": "GET",
"header": [],
"url": "https://node-task2.herokuapp.com/api/notes/"
},
"status": "OK",
"code": 200,
"_postman_previewlanguage": "json",
"header": [
{
"key": "Server",
"value": "Cowboy"
},
{
"key": "Connection",
"value": "keep-alive"
},
{
"key": "X-Powered-By",
"value": "Express"
},
{
"key": "Content-Type",
"value": "application/json; charset=utf-8"
},
{
"key": "Content-Length",
"value": "103"
},
{
"key": "Etag",
"value": "W/\"67-aR9NxSbB5ab73lSksdIWZNuQyq8\""
},
{
"key": "Date",
"value": "Tue, 06 Jul 2021 22:40:55 GMT"
},
{
"key": "Via",
"value": "1.1 vegur"
}
],
"cookie": [],
"body": "[\n {\n \"id\": 1,\n \"title\": \"first changed\",\n \"content\": \"new text\",\n \"createdAt\": \"some date\",\n \"updatedAt\": \"some date\"\n }\n]"
}
]
},
{
"name": "POST",
"event": [
{
"listen": "prerequest",
"script": {
"exec": [
""
],
"type": "text/javascript"
}
},
{
"listen": "test",
"script": {
"exec": [
""
],
"type": "text/javascript"
}
}
],
"request": {
"method": "POST",
"header": [],
"body": {
"mode": "raw",
"raw": "{\"id\": 1, \"title\": \"first\", \"content\": \"some text\", \"createdAt\": \"some date\", \"updatedAt\": \"some date\"}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": "https://node-task2.herokuapp.com/api/notes/"
},
"response": [
{
"name": "2.POST",
"originalRequest": {
"method": "POST",
"header": [],
"body": {
"mode": "raw",
"raw": "{\"id\": 1, \"title\": \"first\", \"content\": \"some text\", \"createdAt\": \"some date\", \"updatedAt\": \"some date\"}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": "https://node-task2.herokuapp.com/api/notes/"
},
"status": "OK",
"code": 200,
"_postman_previewlanguage": "json",
"header": [
{
"key": "Server",
"value": "Cowboy"
},
{
"key": "Connection",
"value": "keep-alive"
},
{
"key": "X-Powered-By",
"value": "Express"
},
{
"key": "Content-Type",
"value": "application/json; charset=utf-8"
},
{
"key": "Content-Length",
"value": "123"
},
{
"key": "Etag",
"value": "W/\"7b-Zs+ZSZvDSG55ZK90aBqfAjoxdAg\""
},
{
"key": "Date",
"value": "Tue, 06 Jul 2021 21:58:17 GMT"
},
{
"key": "Via",
"value": "1.1 vegur"
}
],
"cookie": [],
"body": "\"{\\\"id\\\": 1, \\\"title\\\": \\\"first\\\", \\\"content\\\": \\\"some text\\\", \\\"createdAt\\\": \\\"some date\\\", \\\"updatedAt\\\": \\\"some date\\\"}\""
}
]
},
{
"name": "POST",
"request": {
"method": "POST",
"header": [],
"body": {
"mode": "raw",
"raw": "{\"id\": 2, \"title\": \"second\", \"content\": \"some text\", \"createdAt\": \"some date\", \"updatedAt\": \"some date\"}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": "https://node-task2.herokuapp.com/api/notes/"
},
"response": [
{
"name": "4.POST",
"originalRequest": {
"method": "POST",
"header": [],
"body": {
"mode": "raw",
"raw": "{\"id\": 2, \"title\": \"second\", \"content\": \"some text\", \"createdAt\": \"some date\", \"updatedAt\": \"some date\"}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": "https://node-task2.herokuapp.com/api/notes/"
},
"status": "OK",
"code": 200,
"_postman_previewlanguage": "json",
"header": [
{
"key": "Server",
"value": "Cowboy"
},
{
"key": "Connection",
"value": "keep-alive"
},
{
"key": "X-Powered-By",
"value": "Express"
},
{
"key": "Content-Type",
"value": "application/json; charset=utf-8"
},
{
"key": "Content-Length",
"value": "124"
},
{
"key": "Etag",
"value": "W/\"7c-vtAEN2HlKwhD6OkasvICg9Ni+g0\""
},
{
"key": "Date",
"value": "Tue, 06 Jul 2021 22:00:49 GMT"
},
{
"key": "Via",
"value": "1.1 vegur"
}
],
"cookie": [],
"body": "\"{\\\"id\\\": 2, \\\"title\\\": \\\"second\\\", \\\"content\\\": \\\"some text\\\", \\\"createdAt\\\": \\\"some date\\\", \\\"updatedAt\\\": \\\"some date\\\"}\""
}
]
},
{
"name": "PUT",
"request": {
"method": "PUT",
"header": [],
"body": {
"mode": "raw",
"raw": "{\"id\": 1, \"title\": \"first changed\", \"content\": \"new text\", \"createdAt\": \"some date\", \"updatedAt\": \"some date\"}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": "https://node-task2.herokuapp.com/api/notes/1"
},
"response": [
{
"name": "6.PUT",
"originalRequest": {
"method": "PUT",
"header": [],
"body": {
"mode": "raw",
"raw": "{\"id\": 1, \"title\": \"first changed\", \"content\": \"new text\", \"createdAt\": \"some date\", \"updatedAt\": \"some date\"}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": "https://node-task2.herokuapp.com/api/notes/1"
},
"status": "OK",
"code": 200,
"_postman_previewlanguage": "json",
"header": [
{
"key": "Server",
"value": "Cowboy"
},
{
"key": "Connection",
"value": "keep-alive"
},
{
"key": "X-Powered-By",
"value": "Express"
},
{
"key": "Content-Type",
"value": "application/json; charset=utf-8"
},
{
"key": "Content-Length",
"value": "130"
},
{
"key": "Etag",
"value": "W/\"82-QdzTirfdP1+K+iNOkslStk0OPpg\""
},
{
"key": "Date",
"value": "Tue, 06 Jul 2021 22:03:36 GMT"
},
{
"key": "Via",
"value": "1.1 vegur"
}
],
"cookie": [],
"body": "\"{\\\"id\\\": 1, \\\"title\\\": \\\"first changed\\\", \\\"content\\\": \\\"new text\\\", \\\"createdAt\\\": \\\"some date\\\", \\\"updatedAt\\\": \\\"some date\\\"}\""
}
]
},
{
"name": "DELETE",
"request": {
"method": "DELETE",
"header": [],
"url": "https://node-task2.herokuapp.com/api/notes/2"
},
"response": [
{
"name": "8.DELETE",
"originalRequest": {
"method": "DELETE",
"header": [],
"url": "https://node-task2.herokuapp.com/api/notes/2"
},
"status": "OK",
"code": 200,
"_postman_previewlanguage": "json",
"header": [
{
"key": "Server",
"value": "Cowboy"
},
{
"key": "Connection",
"value": "keep-alive"
},
{
"key": "X-Powered-By",
"value": "Express"
},
{
"key": "Content-Type",
"value": "application/json; charset=utf-8"
},
{
"key": "Content-Length",
"value": "23"
},
{
"key": "Etag",
"value": "W/\"17-bCXlhEBJSVIeQ+m1i+6p7+rrNak\""
},
{
"key": "Date",
"value": "Tue, 06 Jul 2021 22:40:08 GMT"
},
{
"key": "Via",
"value": "1.1 vegur"
}
],
"cookie": [],
"body": "{\n \"success\": true,\n \"id\": 2\n}"
}
]
}
]
};
const expectedOutput = {
"name": "CRUD",
"uid": "mockeduuidvalue123456",
"version": "1",
"items": [
{
"uid": "mockeduuidvalue123456",
"name": "GET",
"type": "http-request",
"request": {
"url": "https://node-task2.herokuapp.com/api/notes/",
"method": "GET",
"auth": {
"mode": "none",
"basic": null,
"bearer": null,
"awsv4": null
},
"headers": [],
"params": [],
"body": {
"mode": "none",
"json": null,
"text": null,
"xml": null,
"formUrlEncoded": [],
"multipartForm": []
},
"docs": ""
},
"seq": 1
},
{
"uid": "mockeduuidvalue123456",
"name": "POST",
"type": "http-request",
"request": {
"url": "https://node-task2.herokuapp.com/api/notes/",
"method": "POST",
"auth": {
"mode": "none",
"basic": null,
"bearer": null,
"awsv4": null
},
"headers": [],
"params": [],
"body": {
"mode": "json",
"json": "{\"id\": 1, \"title\": \"first\", \"content\": \"some text\", \"createdAt\": \"some date\", \"updatedAt\": \"some date\"}",
"text": null,
"xml": null,
"formUrlEncoded": [],
"multipartForm": []
},
"docs": "",
"script": {
"req": ""
},
"tests": ""
},
"seq": 2
},
{
"uid": "mockeduuidvalue123456",
"name": "POST_1",
"type": "http-request",
"request": {
"url": "https://node-task2.herokuapp.com/api/notes/",
"method": "POST",
"auth": {
"mode": "none",
"basic": null,
"bearer": null,
"awsv4": null
},
"headers": [],
"params": [],
"body": {
"mode": "json",
"json": "{\"id\": 2, \"title\": \"second\", \"content\": \"some text\", \"createdAt\": \"some date\", \"updatedAt\": \"some date\"}",
"text": null,
"xml": null,
"formUrlEncoded": [],
"multipartForm": []
},
"docs": ""
},
"seq": 3
},
{
"uid": "mockeduuidvalue123456",
"name": "PUT",
"type": "http-request",
"request": {
"url": "https://node-task2.herokuapp.com/api/notes/1",
"method": "PUT",
"auth": {
"mode": "none",
"basic": null,
"bearer": null,
"awsv4": null
},
"headers": [],
"params": [],
"body": {
"mode": "json",
"json": "{\"id\": 1, \"title\": \"first changed\", \"content\": \"new text\", \"createdAt\": \"some date\", \"updatedAt\": \"some date\"}",
"text": null,
"xml": null,
"formUrlEncoded": [],
"multipartForm": []
},
"docs": ""
},
"seq": 4
},
{
"uid": "mockeduuidvalue123456",
"name": "DELETE",
"type": "http-request",
"request": {
"url": "https://node-task2.herokuapp.com/api/notes/2",
"method": "DELETE",
"auth": {
"mode": "none",
"basic": null,
"bearer": null,
"awsv4": null
},
"headers": [],
"params": [],
"body": {
"mode": "none",
"json": null,
"text": null,
"xml": null,
"formUrlEncoded": [],
"multipartForm": []
},
"docs": ""
},
"seq": 5
}
],
"environments": [],
"root": {
"docs": "",
"meta": {
"name": "CRUD"
},
"request": {
"auth": {
"mode": "none",
"basic": null,
"bearer": null,
"awsv4": null
},
"headers": [],
"script": {},
"tests": "",
"vars": {}
}
}
};

View File

@@ -0,0 +1,238 @@
import { describe, it, expect } from '@jest/globals';
import postmanToBruno from '../../../src/postman/postman-to-bruno';
describe('Collection Authentication', () => {
it('should handle basic auth at collection level', () => {
const postmanCollection = {
info: {
name: 'Collection level basic auth',
schema: 'https://schema.getpostman.com/json/collection/v2.1.0/collection.json'
},
item: [],
auth: {
type: 'basic',
basic: [
{
key: 'password',
value: 'testpass',
type: 'string'
},
{
key: 'username',
value: 'testuser',
type: 'string'
}
]
},
event: [
{
listen: 'prerequest',
script: {
type: 'text/javascript',
packages: {},
exec: ['']
}
},
{
listen: 'test',
script: {
type: 'text/javascript',
packages: {},
exec: ['']
}
}
]
};
const result = postmanToBruno(postmanCollection);
// console.log('result', JSON.stringify(result, null, 2));
expect(result.root.request.auth).toEqual({
mode: 'basic',
basic: {
username: 'testuser',
password: 'testpass'
},
bearer: null,
awsv4: null,
apikey: null,
oauth2: null,
digest: null
});
});
it('should handle bearer token auth at collection level', () => {
const postmanCollection = {
info: {
name: 'Collection level bearer token',
schema: 'https://schema.getpostman.com/json/collection/v2.1.0/collection.json'
},
item: [],
auth: {
type: 'bearer',
bearer: [
{
key: 'token',
value: 'token',
type: 'string'
}
]
},
event: [
{
listen: 'prerequest',
script: {
type: 'text/javascript',
packages: {},
exec: ['']
}
},
{
listen: 'test',
script: {
type: 'text/javascript',
packages: {},
exec: ['']
}
}
]
};
const result = postmanToBruno(postmanCollection);
// console.log('result', JSON.stringify(result, null, 2));
expect(result.root.request.auth).toEqual({
mode: 'bearer',
basic: null,
bearer: {
token: 'token'
},
awsv4: null,
apikey: null,
oauth2: null,
digest: null
});
});
it('should handle API key auth at collection level', () => {
const postmanCollection = {
info: {
name: 'Collection level api key',
schema: 'https://schema.getpostman.com/json/collection/v2.1.0/collection.json'
},
item: [],
auth: {
type: 'apikey',
apikey: [
{
key: 'value',
value: 'apikey',
type: 'string'
},
{
key: 'key',
value: 'apikey',
type: 'string'
}
]
},
event: [
{
listen: 'prerequest',
script: {
type: 'text/javascript',
packages: {},
exec: ['']
}
},
{
listen: 'test',
script: {
type: 'text/javascript',
packages: {},
exec: ['']
}
}
]
};
const result = postmanToBruno(postmanCollection);
expect(result.root.request.auth).toEqual({
mode: 'apikey',
basic: null,
bearer: null,
awsv4: null,
apikey: {
key: 'apikey',
value: 'apikey',
placement: 'header'
},
oauth2: null,
digest: null
});
});
it('should handle digest auth at collection level', () => {
const postmanCollection = {
info: {
name: 'Collection level digest auth',
schema: 'https://schema.getpostman.com/json/collection/v2.1.0/collection.json'
},
item: [],
auth: {
type: 'digest',
digest: [
{
key: 'password',
value: 'digest auth',
type: 'string'
},
{
key: 'username',
value: 'digest auth',
type: 'string'
},
{
key: 'algorithm',
value: 'MD5',
type: 'string'
}
]
},
event: [
{
listen: 'prerequest',
script: {
type: 'text/javascript',
packages: {},
exec: ['']
}
},
{
listen: 'test',
script: {
type: 'text/javascript',
packages: {},
exec: ['']
}
}
]
};
const result = postmanToBruno(postmanCollection);
expect(result.root.request.auth).toEqual({
mode: 'digest',
basic: null,
bearer: null,
awsv4: null,
apikey: null,
oauth2: null,
digest: {
username: 'digest auth',
password: 'digest auth'
}
});
});
});

View File

@@ -0,0 +1,247 @@
import { describe, it, expect } from '@jest/globals';
import postmanToBruno from '../../../src/postman/postman-to-bruno';
describe('Folder Authentication', () => {
it('should handle basic auth at folder level', () => {
const postmanCollection = {
info: {
name: 'Folder level basic auth',
schema: 'https://schema.getpostman.com/json/collection/v2.1.0/collection.json'
},
item: [
{
name: 'folder',
item: [],
auth: {
type: 'basic',
basic: [
{
key: 'password',
value: 'testpass',
type: 'string'
},
{
key: 'username',
value: 'testuser',
type: 'string'
}
]
},
event: [
{
listen: 'prerequest',
script: {
type: 'text/javascript',
packages: {},
exec: ['']
}
},
{
listen: 'test',
script: {
type: 'text/javascript',
packages: {},
exec: ['']
}
}
]
}
]
};
const result = postmanToBruno(postmanCollection);
expect(result.items[0].root.request.auth).toEqual({
mode: 'basic',
basic: {
username: 'testuser',
password: 'testpass'
},
bearer: null,
awsv4: null,
apikey: null,
oauth2: null,
digest: null
});
});
it('should handle bearer token auth at folder level', () => {
const postmanCollection = {
info: {
name: 'Folder level bearer token',
schema: 'https://schema.getpostman.com/json/collection/v2.1.0/collection.json'
},
item: [
{
name: 'folder',
item: [],
auth: {
type: 'bearer',
bearer: [
{
key: 'token',
value: 'token',
type: 'string'
}
]
},
event: [
{
listen: 'prerequest',
script: {
type: 'text/javascript',
packages: {},
exec: ['']
}
},
{
listen: 'test',
script: {
type: 'text/javascript',
packages: {},
exec: ['']
}
}
]
}
]
};
const result = postmanToBruno(postmanCollection);
expect(result.items[0].root.request.auth).toEqual({
mode: 'bearer',
basic: null,
bearer: { token: 'token' },
awsv4: null,
apikey: null,
oauth2: null,
digest: null
});
});
it('should handle API key auth at folder level', () => {
const postmanCollection = {
info: {
name: 'Folder level API key',
schema: 'https://schema.getpostman.com/json/collection/v2.1.0/collection.json'
},
item: [
{
name: 'folder',
item: [],
auth: {
type: 'apikey',
apikey: [
{
key: 'value',
value: 'apikey',
type: 'string'
},
{
key: 'key',
value: 'apikey',
type: 'string'
}
]
},
event: [
{
listen: 'prerequest',
script: {
type: 'text/javascript',
packages: {},
exec: ['']
}
},
{
listen: 'test',
script: {
type: 'text/javascript',
packages: {},
exec: ['']
}
}
]
}
]
};
const result = postmanToBruno(postmanCollection);
expect(result.items[0].root.request.auth).toEqual({
mode: 'apikey',
basic: null,
bearer: null,
awsv4: null,
apikey: { key: 'apikey', value: 'apikey', placement: 'header' },
oauth2: null,
digest: null
});
});
it('should handle digest auth at folder level', () => {
const postmanCollection = {
info: {
name: 'Folder level digest auth',
schema: 'https://schema.getpostman.com/json/collection/v2.1.0/collection.json'
},
item: [
{
name: 'folder',
item: [],
auth: {
type: 'digest',
digest: [
{
key: 'password',
value: 'digest pass',
type: 'string'
},
{
key: 'username',
value: 'digest user',
type: 'string'
},
{
key: 'algorithm',
value: 'MD5',
type: 'string'
}
]
},
event: [
{
listen: 'prerequest',
script: {
type: 'text/javascript',
packages: {},
exec: ['']
}
},
{
listen: 'test',
script: {
type: 'text/javascript',
packages: {},
exec: ['']
}
}
]
}
]
};
const result = postmanToBruno(postmanCollection);
expect(result.items[0].root.request.auth).toEqual({
mode: 'digest',
basic: null,
bearer: null,
awsv4: null,
apikey: null,
oauth2: null,
digest: { username: 'digest user', password: 'digest pass' }
});
});
});

View File

@@ -0,0 +1,186 @@
import { describe, it, expect } from '@jest/globals';
import postmanToBruno from '../../../src/postman/postman-to-bruno';
describe('postman-collection', () => {
it('should correctly import a valid Postman collection file', async () => {
const brunoCollection = postmanToBruno(postmanCollection);
expect(brunoCollection).toMatchObject(expectedOutput);
});
});
// Simple Collection (postman)
// ├── folder
// │ └── request (GET)
// └── request (GET)
const postmanCollection = {
"info": {
"_postman_id": "7f91bbd8-cb97-41ac-8d0b-e1fcd8bb4ce9",
"name": "simple collection",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
"_exporter_id": "21992467",
"_collection_link": "https://random-user-007.postman.co/workspace/testing~7523f559-3d5f-4c30-8315-3cb3c3ff98b7/collection/21992467-7f91bbd8-cb97-41ac-8d0b-e1fcd8bb4ce9?action=share&source=collection_link&creator=007"
},
"item": [
{
"name": "folder",
"item": [
{
"name": "request",
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "https://usebruno.com",
"protocol": "https",
"host": [
"usebruno",
"com"
]
}
},
"response": []
}
]
},
{
"name": "request",
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "https://usebruno.com",
"protocol": "https",
"host": [
"usebruno",
"com"
]
}
},
"response": []
}
]
};
// Simple Collection (bruno)
// ├── folder
// │ └── request (GET)
// └── request (GET)
const expectedOutput = {
"name": "simple collection",
"uid": "mockeduuidvalue123456",
"version": "1",
"items": [
{
"uid": "mockeduuidvalue123456",
"name": "folder",
"type": "folder",
"items": [
{
"uid": "mockeduuidvalue123456",
"name": "request",
"type": "http-request",
"request": {
"url": "https://usebruno.com",
"method": "GET",
"auth": {
"mode": "none",
"basic": null,
"bearer": null,
"awsv4": null,
"apikey": null,
"oauth2": null,
"digest": null
},
"headers": [],
"params": [],
"body": {
"mode": "none",
"json": null,
"text": null,
"xml": null,
"formUrlEncoded": [],
"multipartForm": []
},
"docs": ""
},
"seq": 1
}
],
"root": {
"docs": "",
"meta": {
"name": "folder"
},
"request": {
"auth": {
"mode": "none",
"basic": null,
"bearer": null,
"awsv4": null,
"apikey": null,
"oauth2": null,
"digest": null
},
"headers": [],
"script": {},
"tests": "",
"vars": {}
}
}
},
{
"uid": "mockeduuidvalue123456",
"name": "request",
"type": "http-request",
"request": {
"url": "https://usebruno.com",
"method": "GET",
"auth": {
"mode": "none",
"basic": null,
"bearer": null,
"awsv4": null,
"apikey": null,
"oauth2": null,
"digest": null
},
"headers": [],
"params": [],
"body": {
"mode": "none",
"json": null,
"text": null,
"xml": null,
"formUrlEncoded": [],
"multipartForm": []
},
"docs": ""
},
"seq": 1
}
],
"environments": [],
"root": {
"docs": "",
"meta": {
"name": "simple collection"
},
"request": {
"auth": {
"mode": "none",
"basic": null,
"bearer": null,
"awsv4": null,
"apikey": null,
"oauth2": null,
"digest": null
},
"headers": [],
"script": {},
"tests": "",
"vars": {}
}
}
};

View File

@@ -1,4 +1,4 @@
const { default: postmanTranslation } = require("../../../src/postman/postman-translations");
const { default: postmanTranslation } = require("../../../../src/postman/postman-translations");
describe('postmanTranslations - request commands', () => {
test('should handle request commands', () => {

View File

@@ -1,4 +1,4 @@
const { default: postmanTranslation } = require("../../../src/postman/postman-translations");
const { default: postmanTranslation } = require("../../../../src/postman/postman-translations");
describe('postmanTranslations - response commands', () => {
test('should handle response commands', () => {

View File

@@ -0,0 +1,134 @@
import { describe, it, expect } from '@jest/globals';
import postmanToBruno from '../../../src/postman/postman-to-bruno';
describe('Request Authentication', () => {
it('should handle basic auth at request level', () => {
const postmanCollection = {
info: {
name: 'Request Auth Collection',
schema: 'https://schema.getpostman.com/json/collection/v2.1.0/collection.json'
},
item: [
{
name: 'Basic Auth Request',
request: {
method: 'GET',
url: 'https://api.example.com/test',
auth: {
type: 'basic',
basic: [
{ key: 'username', value: 'requestuser' },
{ key: 'password', value: 'requestpass' }
]
}
}
}
]
};
const result = postmanToBruno(postmanCollection);
expect(result.items[0].request.auth).toEqual({
mode: 'basic',
basic: {
username: 'requestuser',
password: 'requestpass'
},
bearer: null,
awsv4: null,
apikey: null,
oauth2: null,
digest: null
});
});
it('should inherit folder auth when request has no auth', () => {
const postmanCollection = {
info: {
name: 'Inherit Request Auth Collection',
schema: 'https://schema.getpostman.com/json/collection/v2.1.0/collection.json'
},
item: [
{
name: 'Auth Folder',
auth: {
type: 'bearer',
bearer: [{ key: 'token', value: 'foldertoken' }]
},
item: [
{
name: 'No Auth Request',
request: {
method: 'GET',
url: 'https://api.example.com/test'
}
}
]
}
]
};
const result = postmanToBruno(postmanCollection);
expect(result.items[0].items[0].request.auth).toEqual({
mode: 'bearer',
basic: null,
bearer: {
token: 'foldertoken'
},
awsv4: null,
apikey: null,
oauth2: null,
digest: null
});
});
it('should override folder auth with request auth', () => {
const postmanCollection = {
info: {
name: 'Override Request Auth Collection',
schema: 'https://schema.getpostman.com/json/collection/v2.1.0/collection.json'
},
item: [
{
name: 'Auth Folder',
auth: {
type: 'basic',
basic: [
{ key: 'username', value: 'folderuser' },
{ key: 'password', value: 'folderpass' }
]
},
item: [
{
name: 'Override Auth Request',
request: {
method: 'GET',
url: 'https://api.example.com/test',
auth: {
type: 'bearer',
bearer: [{ key: 'token', value: 'requesttoken' }]
}
}
}
]
}
]
};
const result = postmanToBruno(postmanCollection);
expect(result.items[0].items[0].request.auth).toEqual({
mode: 'bearer',
basic: null,
bearer: {
token: 'requesttoken'
},
awsv4: null,
apikey: null,
oauth2: null,
digest: null
});
});
});