From 0e054259e9d230462dbd49d3fd4eaf201e3b471f Mon Sep 17 00:00:00 2001 From: Leonard Phillips Date: Sun, 25 May 2025 13:06:13 +0200 Subject: [PATCH] Expand postman import to handle "inherit" auth type Allow child items to inherit "No Auth" auths from parent Simplify processAuth checks by setting mode="inherit" in bruno request object Allow folders to inherit "No Auth" from parent folder Reduce inherit fix scope Revert standard jest test config Reduce inherit fix scope even more Reduce inherit fix scope final Minor format change --- .../components/FolderSettings/Auth/index.js | 2 +- .../src/components/RequestPane/Auth/index.js | 2 +- .../src/postman/postman-to-bruno.js | 210 ++++++----- .../postman-to-bruno/collection-auth.spec.js | 56 ++- .../postman-to-bruno/folder-auth.spec.js | 133 ++++++- .../postman-to-bruno/postman-to-bruno.spec.js | 6 +- .../postman-to-bruno/process-auth.spec.js | 10 +- .../postman-to-bruno/request-auth.spec.js | 342 +++++++++++++++--- 8 files changed, 577 insertions(+), 184 deletions(-) diff --git a/packages/bruno-app/src/components/FolderSettings/Auth/index.js b/packages/bruno-app/src/components/FolderSettings/Auth/index.js index cbdbba57d..2c852af4b 100644 --- a/packages/bruno-app/src/components/FolderSettings/Auth/index.js +++ b/packages/bruno-app/src/components/FolderSettings/Auth/index.js @@ -77,7 +77,7 @@ const Auth = ({ collection, folder }) => { const parentFolder = folderTreePath[i]; if (parentFolder.type === 'folder') { const folderAuth = get(parentFolder, 'root.request.auth'); - if (folderAuth && folderAuth.mode && folderAuth.mode !== 'none' && folderAuth.mode !== 'inherit') { + if (folderAuth && folderAuth.mode && folderAuth.mode !== 'inherit') { effectiveSource = { type: 'folder', name: parentFolder.name, diff --git a/packages/bruno-app/src/components/RequestPane/Auth/index.js b/packages/bruno-app/src/components/RequestPane/Auth/index.js index 8ca23ab8d..c16f7bb68 100644 --- a/packages/bruno-app/src/components/RequestPane/Auth/index.js +++ b/packages/bruno-app/src/components/RequestPane/Auth/index.js @@ -54,7 +54,7 @@ const Auth = ({ item, collection }) => { for (let i of [...requestTreePath].reverse()) { if (i.type === 'folder') { const folderAuth = get(i, 'root.request.auth'); - if (folderAuth && folderAuth.mode && folderAuth.mode !== 'none' && folderAuth.mode !== 'inherit') { + if (folderAuth && folderAuth.mode && folderAuth.mode !== 'inherit') { effectiveSource = { type: 'folder', name: i.name, diff --git a/packages/bruno-converters/src/postman/postman-to-bruno.js b/packages/bruno-converters/src/postman/postman-to-bruno.js index 9fa85c625..ca07572f5 100644 --- a/packages/bruno-converters/src/postman/postman-to-bruno.js +++ b/packages/bruno-converters/src/postman/postman-to-bruno.js @@ -2,7 +2,7 @@ import get from 'lodash/get'; import { validateSchema, transformItemsInCollection, hydrateSeqInCollection, uuid } from '../common'; import each from 'lodash/each'; import postmanTranslation from './postman-translations'; -import { invalidVariableCharacterRegex } from '../constants/index'; +import { invalidVariableCharacterRegex } from '../constants/index'; const AUTH_TYPES = Object.freeze({ BASIC: 'basic', @@ -56,7 +56,7 @@ 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 || ''; @@ -64,9 +64,9 @@ const constructUrlFromParts = (url) => { const queryStr = query && Array.isArray(query) && query.length > 0 ? `?${query - .filter((q) => q && q.key) - .map((q) => `${q.key}=${q.value || ''}`) - .join('&')}` + .filter((q) => q && q.key) + .map((q) => `${q.key}=${q.value || ''}`) + .join('&')}` : ''; const urlStr = `${protocol}://${hostStr}${portStr}${pathStr ? `/${pathStr}` : ''}${queryStr}`; return urlStr; @@ -140,39 +140,47 @@ const importCollectionLevelVariables = (variables, requestObject) => { requestObject.vars.req = vars; }; -export const processAuth = (auth, requestObject) => { - if (!auth || !auth.type || auth.type === AUTH_TYPES.NOAUTH) { +export const processAuth = (auth, requestObject, collection = false) => { + // As of 14/05/2025 + // When collections are set to "No Auth" in Postman, the auth object is null. + // When folders and requests are set to "Inherit" in Postman, the auth object is null. + // When folders and requests are set to "No Auth" in Postman, the auth object is present. + + // Handle collection-specific "No Auth" + if (collection && !auth) return; // Return as requestObject is a collection and has a default mode = none + + // Handle "Inherit Auth" (typically for non-collections when postmanAuth is null) + if (!auth) { + requestObject.auth.mode = AUTH_TYPES.INHERIT; return; } - let authValues = auth[auth.type]; - - if(!authValues) { - console.warn('Unexpected auth.type, auth object doesn\'t have the key', auth.type); - requestObject.auth.mode = auth.type; - authValues = {}; + // Handle explicit "No Auth" + if (auth.type === AUTH_TYPES.NOAUTH) { + requestObject.auth.mode = 'none'; + return; } - + + let authValues = auth[auth.type] ?? []; if (Array.isArray(authValues)) { authValues = convertV21Auth(authValues); } + requestObject.auth.mode = auth.type; // Set the mode based on Postman's auth type + switch (auth.type) { case AUTH_TYPES.BASIC: - requestObject.auth.mode = AUTH_TYPES.BASIC; requestObject.auth.basic = { username: authValues.username || '', password: authValues.password || '' }; break; case AUTH_TYPES.BEARER: - requestObject.auth.mode = AUTH_TYPES.BEARER; requestObject.auth.bearer = { token: authValues.token || '' }; break; case AUTH_TYPES.AWSV4: - requestObject.auth.mode = AUTH_TYPES.AWSV4; requestObject.auth.awsv4 = { accessKeyId: authValues.accessKey || '', secretAccessKey: authValues.secretKey || '', @@ -183,7 +191,6 @@ export const processAuth = (auth, requestObject) => { }; break; case AUTH_TYPES.APIKEY: - requestObject.auth.mode = AUTH_TYPES.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, @@ -191,75 +198,78 @@ export const processAuth = (auth, requestObject) => { }; break; case AUTH_TYPES.DIGEST: - requestObject.auth.mode = AUTH_TYPES.DIGEST; requestObject.auth.digest = { username: authValues.username || '', password: authValues.password || '' }; break; case AUTH_TYPES.OAUTH2: - const findValueUsingKey = (key) => { - return authValues[key] || ''; - }; + const findValueUsingKey = (key) => authValues[key] || ''; + + // Maps Postman's grant_type to the Bruno's grantType string expected in the target object const oauth2GrantTypeMaps = { authorization_code_with_pkce: 'authorization_code', authorization_code: 'authorization_code', client_credentials: 'client_credentials', - password_credentials: 'password_credentials' + password_credentials: 'password' }; - const grantType = oauth2GrantTypeMaps[findValueUsingKey('grant_type')] || 'authorization_code'; - requestObject.auth.mode = AUTH_TYPES.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' - }; + const postmanGrantType = findValueUsingKey('grant_type'); + const targetGrantType = oauth2GrantTypeMaps[postmanGrantType] ?? 'client_credentials'; // Default + + // Common properties for all OAuth2 grant types + const baseOAuth2Config = { + grantType: targetGrantType, + 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' + }; + + switch (postmanGrantType) { + case 'authorization_code': + requestObject.auth.oauth2 = { + ...baseOAuth2Config, + authorizationUrl: findValueUsingKey('authUrl'), + callbackUrl: findValueUsingKey('redirect_uri'), + pkce: false // PKCE is not used for standard authorization_code + }; + break; + case 'authorization_code_with_pkce': + requestObject.auth.oauth2 = { + ...baseOAuth2Config, + authorizationUrl: findValueUsingKey('authUrl'), + callbackUrl: findValueUsingKey('redirect_uri'), + pkce: true, // Explicitly set pkce to true for this grant type + }; + break; + case 'password_credentials': + requestObject.auth.oauth2 = { + ...baseOAuth2Config, + username: findValueUsingKey('username'), + password: findValueUsingKey('password'), + }; + break; + case 'client_credentials': + requestObject.auth.oauth2 = baseOAuth2Config; + break; + default: + console.warn('Unexpected OAuth2 grant type after mapping:', targetGrantType); + requestObject.auth.oauth2 = baseOAuth2Config; // Fallback to default which is Client Credentials + break; } break; default: - requestObject.auth.mode = AUTH_TYPES.NONE; - console.warn('Unexpected auth.type', auth.type); + console.warn('Unexpected auth.type:', auth.type, '- Mode set, but no specific config generated.'); + break; } }; -const importPostmanV2CollectionItem = (brunoParent, item, parentAuth, { useWorkers = false } = {}, scriptMap)=> { +const importPostmanV2CollectionItem = (brunoParent, item, { useWorkers = false } = {}, scriptMap) => { brunoParent.items = brunoParent.items || []; const folderMap = {}; const requestMap = {}; @@ -289,7 +299,7 @@ const importPostmanV2CollectionItem = (brunoParent, item, parentAuth, { useWorke }, request: { auth: { - mode: 'none', + mode: 'inherit', basic: null, bearer: null, awsv4: null, @@ -308,19 +318,14 @@ const importPostmanV2CollectionItem = (brunoParent, item, parentAuth, { useWorke brunoParent.items.push(brunoFolderItem); // 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); - } + processAuth(i.auth, brunoFolderItem.root.request); if (i.item && i.item.length) { - importPostmanV2CollectionItem(brunoFolderItem, i.item, i.auth ?? parentAuth, { useWorkers }, scriptMap); + importPostmanV2CollectionItem(brunoFolderItem, i.item, { useWorkers }, scriptMap); } if (i.event) { - if(useWorkers) { + if (useWorkers) { scriptMap.set(brunoFolderItem.uid, { events: i.event, request: brunoFolderItem.root.request @@ -353,12 +358,12 @@ const importPostmanV2CollectionItem = (brunoParent, item, parentAuth, { useWorke uid: uuid(), name: requestName, type: 'http-request', - seq: index + 1, + seq: index + 1, request: { url: url, method: i?.request?.method?.toUpperCase(), auth: { - mode: 'none', + mode: 'inherit', basic: null, bearer: null, awsv4: null, @@ -389,8 +394,8 @@ const importPostmanV2CollectionItem = (brunoParent, item, parentAuth, { useWorke brunoParent.items.push(brunoRequestItem); if (i.event) { - if(useWorkers) { - scriptMap.set(brunoRequestItem.uid, { + if (useWorkers) { + scriptMap.set(brunoRequestItem.uid, { events: i.event, request: brunoRequestItem.request }); @@ -501,9 +506,8 @@ const importPostmanV2CollectionItem = (brunoParent, item, parentAuth, { useWorke }); }); - // Handle request-level auth or inherit from parent - const auth = i.request.auth ?? parentAuth; - processAuth(auth, brunoRequestItem.request); + // Request-level auth + processAuth(i.request.auth, brunoRequestItem.request); each(get(i, 'request.url.query'), (param) => { brunoRequestItem.request.params.push({ @@ -537,7 +541,7 @@ const importPostmanV2CollectionItem = (brunoParent, item, parentAuth, { useWorke }); }; - + const searchLanguageByHeader = (headers) => { let contentType; each(headers, (header) => { @@ -592,19 +596,19 @@ const importPostmanV2Collection = async (collection, { useWorkers = false }) => } // Collection level auth - processAuth(collection.auth, brunoCollection.root.request); + processAuth(collection.auth, brunoCollection.root.request, true); // Create a single scriptMap for all items const scriptMap = useWorkers ? new Map() : null; - - importPostmanV2CollectionItem(brunoCollection, collection.item, collection.auth, { useWorkers }, scriptMap); - + + importPostmanV2CollectionItem(brunoCollection, collection.item, { useWorkers }, scriptMap); + // Process all scripts in a single call at the top level if (useWorkers && scriptMap && scriptMap.size > 0) { try { - const { default: scriptTranslationWorker } = await import('../workers/postman-translator-worker'); + const { default: scriptTranslationWorker } = await import('../workers/postman-translator-worker'); const translatedScripts = await scriptTranslationWorker(scriptMap); - + // Apply translated scripts to all items in the collection const applyScriptsToItems = (items) => { items.forEach(item => { @@ -614,14 +618,17 @@ const importPostmanV2Collection = async (collection, { useWorkers = false }) => if (!item.root.request.script) { item.root.request.script = {}; } - + if (!item.root.request.tests) { + item.root.request.tests = ''; + } + const script = translatedScripts.get(item.uid).request?.script?.req; const tests = translatedScripts.get(item.uid).request?.script?.res; - + item.root.request.script.req = script && script.length > 0 ? script : ''; item.root.request.script.res = tests && tests.length > 0 ? tests : ''; } - + // Recursively apply to nested items if (item.items && item.items.length > 0) { applyScriptsToItems(item.items); @@ -631,26 +638,29 @@ const importPostmanV2Collection = async (collection, { useWorkers = false }) => if (!item.request.script) { item.request.script = {}; } - + if (!item.request.tests) { + item.request.tests = ''; + } + const script = translatedScripts.get(item.uid).request?.script?.req; const tests = translatedScripts.get(item.uid).request?.script?.res; - + item.request.script.req = script && script.length > 0 ? script : ''; item.request.script.res = tests && tests.length > 0 ? tests : ''; } } }); }; - + applyScriptsToItems(brunoCollection.items); - + } catch (error) { console.error('Error in script translation worker:', error); } finally { scriptMap.clear(); } } - + return brunoCollection; }; 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 index 77b2ea7d4..67cd32cd8 100644 --- 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 @@ -2,7 +2,48 @@ 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', async() => { + it('should handle no auth at collection level (when auth property is absent)', async () => { + const postmanCollection = { + info: { + name: 'Collection level no auth', + schema: 'https://schema.getpostman.com/json/collection/v2.1.0/collection.json' + }, + item: [], + event: [ + { + listen: 'prerequest', + script: { + type: 'text/javascript', + packages: {}, + exec: [''] + } + }, + { + listen: 'test', + script: { + type: 'text/javascript', + packages: {}, + exec: [''] + } + } + ] + }; + + const result = await postmanToBruno(postmanCollection); + // console.log('result', JSON.stringify(result, null, 2)); + + expect(result.root.request.auth).toEqual({ + mode: 'none', + basic: null, + bearer: null, + awsv4: null, + apikey: null, + oauth2: null, + digest: null + }); + }); + + it('should handle basic auth at collection level', async () => { const postmanCollection = { info: { name: 'Collection level basic auth', @@ -61,7 +102,7 @@ describe('Collection Authentication', () => { }); }); - it('should handle bearer token auth at collection level', async() => { + it('should handle bearer token auth at collection level', async () => { const postmanCollection = { info: { name: 'Collection level bearer token', @@ -112,9 +153,9 @@ describe('Collection Authentication', () => { oauth2: null, digest: null }); - }); + }); - it('should handle API key auth at collection level', async() => { + it('should handle API key auth at collection level', async () => { const postmanCollection = { info: { name: 'Collection level api key', @@ -173,7 +214,7 @@ describe('Collection Authentication', () => { }); }); - it('should handle digest auth at collection level', async() => { + it('should handle digest auth at collection level', async () => { const postmanCollection = { info: { name: 'Collection level digest auth', @@ -235,8 +276,7 @@ describe('Collection Authentication', () => { } }); }); - - it('should handle missing auth values when auth.type exists', async() => { + it('should handle missing auth values when auth.type exists', async () => { const postmanCollection = { info: { name: 'Collection with missing auth values', @@ -283,7 +323,7 @@ describe('Collection Authentication', () => { }); }); - it('should handle missing auth values for different auth types', async() => { + it('should handle missing auth values for different auth types', async () => { const postmanCollection = { info: { name: 'Collection with missing auth values for different types', 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 index 7e273826e..9ab0534eb 100644 --- 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 @@ -2,7 +2,130 @@ 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', async() => { + it('should handle "Inherit Auth" at folder level (auth property absent)', async () => { + const postmanCollection = { + info: { + name: 'Folder Inherit Auth', + schema: 'https://schema.getpostman.com/json/collection/v2.1.0/collection.json' + }, + item: [ + { + name: 'Inheriting Folder', + items: [] + } + ], + 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 = await postmanToBruno(postmanCollection); + + expect(result.items[0].root.request.auth).toEqual({ + mode: 'inherit', + basic: null, + bearer: null, + awsv4: null, + apikey: null, + oauth2: null, + digest: null + }); + }); + + it('should handle explicit "No Auth" at folder level', async () => { + const postmanCollection = { + info: { + name: 'Folder No Auth', + schema: 'https://schema.getpostman.com/json/collection/v2.1.0/collection.json' + }, + item: [ + { + name: 'No Auth Folder', + item: [], + auth: { + type: 'noauth' + } + } + ], + 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 = await postmanToBruno(postmanCollection); + + expect(result.items[0].root.request.auth).toEqual({ + mode: 'none', + basic: null, + bearer: null, + awsv4: null, + apikey: null, + oauth2: null, + digest: null + }); + }); + + it('should handle basic auth at folder level', async () => { const postmanCollection = { info: { name: 'Folder level basic auth', @@ -65,7 +188,7 @@ describe('Folder Authentication', () => { }); }); - it('should handle bearer token auth at folder level', async() => { + it('should handle bearer token auth at folder level', async () => { const postmanCollection = { info: { name: 'Folder level bearer token', @@ -120,7 +243,7 @@ describe('Folder Authentication', () => { }); }); - it('should handle API key auth at folder level', async() => { + it('should handle API key auth at folder level', async () => { const postmanCollection = { info: { name: 'Folder level API key', @@ -180,7 +303,7 @@ describe('Folder Authentication', () => { }); }); - it('should handle digest auth at folder level', async() => { + it('should handle digest auth at folder level', async () => { const postmanCollection = { info: { name: 'Folder level digest auth', @@ -245,7 +368,7 @@ describe('Folder Authentication', () => { }); }); - it('should handle missing auth values in folder level auth', async() => { + it('should handle missing auth values in folder level auth', async () => { const postmanCollection = { info: { name: 'Folder with missing auth values', 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 index e303ab3d3..84a3ccba9 100644 --- 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 @@ -154,7 +154,7 @@ const expectedOutput = { "url": "https://usebruno.com", "method": "GET", "auth": { - "mode": "none", + "mode": "inherit", "basic": null, "bearer": null, "awsv4": null, @@ -183,7 +183,7 @@ const expectedOutput = { }, "request": { "auth": { - "mode": "none", + "mode": "inherit", "basic": null, "bearer": null, "awsv4": null, @@ -207,7 +207,7 @@ const expectedOutput = { "url": "https://usebruno.com", "method": "GET", "auth": { - "mode": "none", + "mode": "inherit", "basic": null, "bearer": null, "awsv4": null, diff --git a/packages/bruno-converters/tests/postman/postman-to-bruno/process-auth.spec.js b/packages/bruno-converters/tests/postman/postman-to-bruno/process-auth.spec.js index 8bbf697a5..2b4d1c7b4 100644 --- a/packages/bruno-converters/tests/postman/postman-to-bruno/process-auth.spec.js +++ b/packages/bruno-converters/tests/postman/postman-to-bruno/process-auth.spec.js @@ -354,16 +354,13 @@ describe('processAuth', () => { processAuth(auth, requestObject); expect(requestObject.auth.mode).toBe('oauth2'); expect(requestObject.auth.oauth2).toEqual({ - grantType: 'authorization_code', - authorizationUrl: '', - callbackUrl: '', + grantType: 'client_credentials', accessTokenUrl: '', refreshTokenUrl: '', clientId: '', clientSecret: '', scope: '', state: '', - pkce: false, tokenPlacement: 'url', credentialsPlacement: 'basic_auth_header' }); @@ -376,16 +373,13 @@ describe('processAuth', () => { processAuth(auth, requestObject); expect(requestObject.auth.mode).toBe('oauth2'); expect(requestObject.auth.oauth2).toEqual({ - grantType: 'authorization_code', - authorizationUrl: '', - callbackUrl: '', + grantType: 'client_credentials', accessTokenUrl: '', refreshTokenUrl: '', clientId: '', clientSecret: '', scope: '', state: '', - pkce: false, tokenPlacement: 'url', credentialsPlacement: 'basic_auth_header' }); 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 index 8a1c5fa27..09cc712c9 100644 --- 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 @@ -2,7 +2,9 @@ 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', async() => { + + + it('should handle basic auth at request level', async () => { const postmanCollection = { info: { name: 'Request Auth Collection', @@ -42,7 +44,7 @@ describe('Request Authentication', () => { }); }); - it('should inherit folder auth when request has no auth', async() => { + it('should inherit folder auth when request has no auth', async () => { const postmanCollection = { info: { name: 'Inherit Request Auth Collection', @@ -57,7 +59,7 @@ describe('Request Authentication', () => { }, item: [ { - name: 'No Auth Request', + name: 'Inherit Auth Request', request: { method: 'GET', url: 'https://api.example.com/test' @@ -71,11 +73,9 @@ describe('Request Authentication', () => { const result = await postmanToBruno(postmanCollection); expect(result.items[0].items[0].request.auth).toEqual({ - mode: 'bearer', + mode: 'inherit', basic: null, - bearer: { - token: 'foldertoken' - }, + bearer: null, awsv4: null, apikey: null, oauth2: null, @@ -83,31 +83,113 @@ describe('Request Authentication', () => { }); }); - it('should override folder auth with request auth', async() => { + it('should handle "Inherit Auth" for request (auth property absent, inherits from folder)', async () => { const postmanCollection = { info: { - name: 'Override Request Auth Collection', + name: 'Request Inherit Auth from Folder', 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' } - ] + auth: { // Folder has auth + type: 'bearer', + bearer: [{ key: 'token', value: 'foldertoken' }] }, item: [ { - name: 'Override Auth Request', + name: 'Inheriting Request', + request: { + method: 'GET', + url: 'https://api.example.com/test' + // auth property is ABSENT for this request, meaning "Inherit auth from parent" + } + } + ] + } + ] + }; + + const result = await postmanToBruno(postmanCollection); + + expect(result.items[0].items[0].request.auth).toEqual({ + mode: 'inherit', + basic: null, + bearer: null, // It should NOT have the folder's token directly here after import + awsv4: null, + apikey: null, + oauth2: null, + digest: null + }); + }); + + it('should handle "Inherit Auth" for request (auth property absent, inherits from collection if folder also inherits)', async () => { + const postmanCollection = { + info: { + name: 'Request Inherit Auth from Collection', + schema: 'https://schema.getpostman.com/json/collection/v2.1.0/collection.json' + }, + auth: { // Collection has auth + type: 'basic', + basic: [ + + { key: 'username', value: 'requestuser' }, + { key: 'password', value: 'requestpass' } + ] + }, + item: [ + { + name: 'Inheriting Folder', + // auth property is ABSENT for this folder + item: [ + { + name: 'Inheriting Request', + request: { + method: 'GET', + url: 'https://api.example.com/test' + // auth property is ABSENT for this request + } + } + ] + } + ] + }; + + const result = await postmanToBruno(postmanCollection); + + // Check folder first + expect(result.items[0].root.request.auth).toEqual({ + mode: 'inherit', + basic: null, bearer: null, awsv4: null, apikey: null, oauth2: null, digest: null + }); + // Then check request + expect(result.items[0].items[0].request.auth).toEqual({ + mode: 'inherit', + basic: null, bearer: null, awsv4: null, apikey: null, oauth2: null, digest: null + }); + }); + + it('should handle explicit "No Auth" at request level', async () => { + const postmanCollection = { + info: { + name: 'Request No Auth', + schema: 'https://schema.getpostman.com/json/collection/v2.1.0/collection.json' + }, + item: [ + { + name: 'Auth Folder', // Parent folder might have auth + auth: { + type: 'bearer', + bearer: [{ key: 'token', value: 'foldertoken' }] + }, + item: [ + { + name: 'Explicit No Auth Request', request: { method: 'GET', url: 'https://api.example.com/test', - auth: { - type: 'bearer', - bearer: [{ key: 'token', value: 'requesttoken' }] + auth: { // Request explicitly set to "No Auth" + type: 'noauth' } } } @@ -119,46 +201,8 @@ describe('Request Authentication', () => { const result = await postmanToBruno(postmanCollection); expect(result.items[0].items[0].request.auth).toEqual({ - mode: 'bearer', + mode: 'none', // <<<< KEY CHECK basic: null, - bearer: { - token: 'requesttoken' - }, - awsv4: null, - apikey: null, - oauth2: null, - digest: null - }); - }); - - it('should handle missing basic auth values in request level', async() => { - const postmanCollection = { - info: { - name: 'Missing Auth Request Collection', - schema: 'https://schema.getpostman.com/json/collection/v2.1.0/collection.json' - }, - item: [ - { - name: 'Missing Auth Request', - request: { - method: 'GET', - url: 'https://api.example.com/test', - auth: { - type: 'basic' - } - } - } - ] - }; - - const result = await postmanToBruno(postmanCollection); - - expect(result.items[0].request.auth).toEqual({ - mode: 'basic', - basic: { - username: '', - password: '' - }, bearer: null, awsv4: null, apikey: null, @@ -166,4 +210,186 @@ describe('Request Authentication', () => { digest: null }); }); + + it('should handle "Inherit Auth" for a request nested under multiple inheriting folders', async () => { + const postmanCollection = { + info: { + name: 'Multi-Level Inherit Auth', + schema: 'https://schema.getpostman.com/json/collection/v2.1.0/collection.json' + }, + auth: { // Collection level auth + type: 'basic', + basic: [ + { key: 'username', value: 'collectionUser' }, + { key: 'password', value: 'collectionPass' } + ] + }, + item: [ + { + name: 'Folder Level 1 (Inherit)', + // auth property is ABSENT for this folder, meaning "Inherit" + item: [ + { + name: 'Folder Level 2 (Inherit)', + // auth property is ABSENT for this folder, meaning "Inherit" + item: [ + { + name: 'Deeply Nested Request (Inherit)', + request: { + method: 'GET', + url: 'https://api.example.com/deep' + // auth property is ABSENT for this request, meaning "Inherit" + } + } + ] + } + ] + } + ] + }; + + const result = await postmanToBruno(postmanCollection); + + // Check Folder Level 1 + expect(result.items[0].root.request.auth).toEqual({ + mode: 'inherit', + basic: null, bearer: null, awsv4: null, apikey: null, oauth2: null, digest: null + }); + + // Check Folder Level 2 + expect(result.items[0].items[0].root.request.auth).toEqual({ + mode: 'inherit', + basic: null, bearer: null, awsv4: null, apikey: null, oauth2: null, digest: null + }); + + // Check the Request + expect(result.items[0].items[0].items[0].request.auth).toEqual({ + mode: 'inherit', + basic: null, bearer: null, awsv4: null, apikey: null, oauth2: null, digest: null + }); + }); + + it('should handle "Inherit Auth" where an intermediate folder has explicit auth', async () => { + const postmanCollection = { + info: { + name: 'Multi-Level Inherit with Override', + schema: 'https://schema.getpostman.com/json/collection/v2.1.0/collection.json' + }, + auth: { // Collection level auth + type: 'basic', + basic: [ + { key: 'username', value: 'collectionUser' }, + { key: 'password', value: 'collectionPass' } + ] + }, + item: [ + { + name: 'Folder Level 1 (Explicit Bearer)', + auth: { // This folder has its own auth + type: 'bearer', + bearer: [{ key: 'token', value: 'folder1Token' }] + }, + item: [ + { + name: 'Folder Level 2 (Inherit from Folder 1)', + // auth property is ABSENT for this folder, meaning "Inherit" + item: [ + { + name: 'Deeply Nested Request (Inherit from Folder 1 via Folder 2)', + request: { + method: 'GET', + url: 'https://api.example.com/deep_override' + // auth property is ABSENT for this request, meaning "Inherit" + } + } + ] + } + ] + } + ] + }; + + const result = await postmanToBruno(postmanCollection); + + // Check Folder Level 1 + expect(result.items[0].root.request.auth).toEqual({ + mode: 'bearer', + basic: null, + bearer: { token: 'folder1Token' }, // Explicitly set + awsv4: null, apikey: null, oauth2: null, digest: null + }); + + // Check Folder Level 2 + expect(result.items[0].items[0].root.request.auth).toEqual({ + mode: 'inherit', // Inherits from Folder 1 + basic: null, bearer: null, awsv4: null, apikey: null, oauth2: null, digest: null + }); + + // Check the Request + expect(result.items[0].items[0].items[0].request.auth).toEqual({ + mode: 'inherit', // Inherits from Folder 1 + basic: null, bearer: null, awsv4: null, apikey: null, oauth2: null, digest: null + }); + }); + + it('should handle "Inherit Auth" where an intermediate folder has explicit "No Auth"', async () => { + const postmanCollection = { + info: { + name: 'Multi-Level Inherit with No Auth Stop', + schema: 'https://schema.getpostman.com/json/collection/v2.1.0/collection.json' + }, + auth: { // Collection level auth + type: 'basic', + basic: [ + { key: 'username', value: 'collectionUser' }, + { key: 'password', value: 'collectionPass' } + ] + }, + item: [ + { + name: 'Folder Level 1 (Explicit No Auth)', + auth: { // This folder is explicitly "No Auth" + type: 'noauth' + }, + item: [ + { + name: 'Folder Level 2 (Inherit from Folder 1 - so No Auth)', + // auth property is ABSENT for this folder, meaning "Inherit" + item: [ + { + name: 'Deeply Nested Request (Inherit from Folder 1 via Folder 2 - so No Auth)', + request: { + method: 'GET', + url: 'https://api.example.com/deep_no_auth_stop' + // auth property is ABSENT for this request, meaning "Inherit" + } + } + ] + } + ] + } + ] + }; + + const result = await postmanToBruno(postmanCollection); + + // Check Folder Level 1 + expect(result.items[0].root.request.auth).toEqual({ + mode: 'none', // Explicitly "No Auth" + basic: null, bearer: null, awsv4: null, apikey: null, oauth2: null, digest: null + }); + + // Check Folder Level 2 + expect(result.items[0].items[0].root.request.auth).toEqual({ + mode: 'inherit', // Inherits from Folder 1 + basic: null, bearer: null, awsv4: null, apikey: null, oauth2: null, digest: null + }); + + // Check the Request + expect(result.items[0].items[0].items[0].request.auth).toEqual({ + mode: 'inherit', // Inherits from Folder 1 + basic: null, bearer: null, awsv4: null, apikey: null, oauth2: null, digest: null + }); + }); + });