diff --git a/package.json b/package.json index b8690d75c..7508cb904 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/packages/bruno-converters/src/postman/postman-to-bruno.js b/packages/bruno-converters/src/postman/postman-to-bruno.js index c22898d6f..31b2f3929 100644 --- a/packages/bruno-converters/src/postman/postman-to-bruno.js +++ b/packages/bruno-converters/src/postman/postman-to-bruno.js @@ -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; \ No newline at end of file diff --git a/packages/bruno-converters/tests/postman/postman-to-bruno.spec.js b/packages/bruno-converters/tests/postman/postman-to-bruno.spec.js deleted file mode 100644 index 2ad195eda..000000000 --- a/packages/bruno-converters/tests/postman/postman-to-bruno.spec.js +++ /dev/null @@ -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": {} - } - } -}; \ No newline at end of file diff --git a/packages/bruno-converters/tests/postman/postman-to-bruno/collection-auth.spec.js b/packages/bruno-converters/tests/postman/postman-to-bruno/collection-auth.spec.js new file mode 100644 index 000000000..ef98774de --- /dev/null +++ b/packages/bruno-converters/tests/postman/postman-to-bruno/collection-auth.spec.js @@ -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' + } + }); + }); +}); diff --git a/packages/bruno-converters/tests/postman/postman-to-bruno/folder-auth.spec.js b/packages/bruno-converters/tests/postman/postman-to-bruno/folder-auth.spec.js new file mode 100644 index 000000000..b403d22d8 --- /dev/null +++ b/packages/bruno-converters/tests/postman/postman-to-bruno/folder-auth.spec.js @@ -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' } + }); + }); +}); diff --git a/packages/bruno-converters/tests/postman/postman-to-bruno/postman-to-bruno.spec.js b/packages/bruno-converters/tests/postman/postman-to-bruno/postman-to-bruno.spec.js new file mode 100644 index 000000000..7eac3906c --- /dev/null +++ b/packages/bruno-converters/tests/postman/postman-to-bruno/postman-to-bruno.spec.js @@ -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": {} + } + } + }; \ No newline at end of file diff --git a/packages/bruno-converters/tests/postman/postman-translations/postman-request.spec.js b/packages/bruno-converters/tests/postman/postman-to-bruno/postman-translations/postman-request.spec.js similarity index 89% rename from packages/bruno-converters/tests/postman/postman-translations/postman-request.spec.js rename to packages/bruno-converters/tests/postman/postman-to-bruno/postman-translations/postman-request.spec.js index 5b1305f73..3ee071640 100644 --- a/packages/bruno-converters/tests/postman/postman-translations/postman-request.spec.js +++ b/packages/bruno-converters/tests/postman/postman-to-bruno/postman-translations/postman-request.spec.js @@ -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', () => { diff --git a/packages/bruno-converters/tests/postman/postman-translations/postman-response.spec.js b/packages/bruno-converters/tests/postman/postman-to-bruno/postman-translations/postman-response.spec.js similarity index 91% rename from packages/bruno-converters/tests/postman/postman-translations/postman-response.spec.js rename to packages/bruno-converters/tests/postman/postman-to-bruno/postman-translations/postman-response.spec.js index 6e54af13b..4f365589c 100644 --- a/packages/bruno-converters/tests/postman/postman-translations/postman-response.spec.js +++ b/packages/bruno-converters/tests/postman/postman-to-bruno/postman-translations/postman-response.spec.js @@ -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', () => { diff --git a/packages/bruno-converters/tests/postman/postman-to-bruno/request-auth.spec.js b/packages/bruno-converters/tests/postman/postman-to-bruno/request-auth.spec.js new file mode 100644 index 000000000..a542a6b61 --- /dev/null +++ b/packages/bruno-converters/tests/postman/postman-to-bruno/request-auth.spec.js @@ -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 + }); + }); + +});