diff --git a/packages/bruno-converters/src/postman/postman-to-bruno.js b/packages/bruno-converters/src/postman/postman-to-bruno.js index 7cf6f4c0a..7de6e0449 100644 --- a/packages/bruno-converters/src/postman/postman-to-bruno.js +++ b/packages/bruno-converters/src/postman/postman-to-bruno.js @@ -465,15 +465,16 @@ const importPostmanV2CollectionItem = (brunoParent, item, { useWorkers = false } brunoRequestItem.request.body.mode = 'multipartForm'; each(i.request.body.formdata, (param) => { + if (param.key == null && param.value == null) return; const isFile = param.type === 'file' || (param.type === 'default' && param.src); const value = isFile ? (Array.isArray(param.src) ? param.src : param.src ? [param.src] : []) - : (Array.isArray(param.value) ? param.value.join('') : param.value); + : (Array.isArray(param.value) ? param.value.join('') : param.value ?? ''); brunoRequestItem.request.body.multipartForm.push({ uid: uuid(), type: isFile ? 'file' : 'text', - name: param.key, + name: param.key ?? '', value, description: transformDescription(param.description), enabled: !param.disabled, @@ -485,10 +486,11 @@ const importPostmanV2CollectionItem = (brunoParent, item, { useWorkers = false } if (bodyMode === 'urlencoded') { brunoRequestItem.request.body.mode = 'formUrlEncoded'; each(i.request.body.urlencoded, (param) => { + if (param.key == null && param.value == null) return; brunoRequestItem.request.body.formUrlEncoded.push({ uid: uuid(), - name: param.key, - value: param.value, + name: param.key ?? '', + value: param.value ?? '', description: transformDescription(param.description), enabled: !param.disabled }); @@ -520,10 +522,11 @@ const importPostmanV2CollectionItem = (brunoParent, item, { useWorkers = false } } each(i.request.header, (header) => { + if (header.key == null && header.value == null) return; brunoRequestItem.request.headers.push({ uid: uuid(), - name: header.key, - value: header.value, + name: header.key ?? '', + value: header.value ?? '', description: transformDescription(header.description), enabled: !header.disabled }); @@ -533,10 +536,13 @@ const importPostmanV2CollectionItem = (brunoParent, item, { useWorkers = false } processAuth(i.request.auth, brunoRequestItem.request); each(get(i, 'request.url.query'), (param) => { + if (param.key == null && param.value == null) { + return; + } brunoRequestItem.request.params.push({ uid: uuid(), - name: param.key, - value: param.value, + name: param.key ?? '', + value: param.value ?? '', description: transformDescription(param.description), type: 'query', enabled: !param.disabled @@ -606,10 +612,11 @@ const importPostmanV2CollectionItem = (brunoParent, item, { useWorkers = false } // Convert original request headers if (originalRequest.header && Array.isArray(originalRequest.header)) { originalRequest.header.forEach((header) => { + if (header.key == null && header.value == null) return; example.request.headers.push({ uid: uuid(), - name: header.key, - value: header.value, + name: header.key ?? '', + value: header.value ?? '', description: transformDescription(header.description), enabled: !header.disabled }); @@ -619,10 +626,13 @@ const importPostmanV2CollectionItem = (brunoParent, item, { useWorkers = false } // Convert original request query parameters if (originalRequest.url && originalRequest.url.query && Array.isArray(originalRequest.url.query)) { originalRequest.url.query.forEach((param) => { + if (param.key == null && param.value == null) { + return; + } example.request.params.push({ uid: uuid(), - name: param.key, - value: param.value, + name: param.key ?? '', + value: param.value ?? '', description: transformDescription(param.description), type: 'query', enabled: !param.disabled @@ -632,6 +642,7 @@ const importPostmanV2CollectionItem = (brunoParent, item, { useWorkers = false } if (originalRequest.url && originalRequest.url.variable && Array.isArray(originalRequest.url.variable)) { originalRequest.url.variable.forEach((param) => { + if (!param.key) return; example.request.params.push({ uid: uuid(), name: param.key, @@ -650,15 +661,16 @@ const importPostmanV2CollectionItem = (brunoParent, item, { useWorkers = false } example.request.body.mode = 'multipartForm'; if (originalRequest.body.formdata && Array.isArray(originalRequest.body.formdata)) { originalRequest.body.formdata.forEach((param) => { + if (param.key == null && param.value == null) return; const isFile = param.type === 'file' || (param.type === 'default' && param.src); const value = isFile ? (Array.isArray(param.src) ? param.src : param.src ? [param.src] : []) - : (Array.isArray(param.value) ? param.value.join('') : param.value); + : (Array.isArray(param.value) ? param.value.join('') : param.value ?? ''); example.request.body.multipartForm.push({ uid: uuid(), type: isFile ? 'file' : 'text', - name: param.key, + name: param.key ?? '', value, description: transformDescription(param.description), enabled: !param.disabled, @@ -670,10 +682,11 @@ const importPostmanV2CollectionItem = (brunoParent, item, { useWorkers = false } example.request.body.mode = 'formUrlEncoded'; if (originalRequest.body.urlencoded && Array.isArray(originalRequest.body.urlencoded)) { originalRequest.body.urlencoded.forEach((param) => { + if (param.key == null && param.value == null) return; example.request.body.formUrlEncoded.push({ uid: uuid(), - name: param.key, - value: param.value, + name: param.key ?? '', + value: param.value ?? '', description: transformDescription(param.description), enabled: !param.disabled }); @@ -700,10 +713,11 @@ const importPostmanV2CollectionItem = (brunoParent, item, { useWorkers = false } // Convert response headers if (response.header && Array.isArray(response.header)) { response.header.forEach((header) => { + if (header.key == null && header.value == null) return; example.response.headers.push({ uid: uuid(), - name: header.key, - value: header.value, + name: header.key ?? '', + value: header.value ?? '', description: transformDescription(header.description), enabled: true }); 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 3e93a96c6..d55a8115b 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 @@ -604,6 +604,171 @@ describe('postman-collection', () => { digest: null }); }); + + it('should skip headers where both key and value are null, and coalesce partial nulls', async () => { + const collectionWithNullHeaders = { + info: { + _postman_id: 'test-null-headers', + name: 'collection with null headers', + schema: 'https://schema.getpostman.com/json/collection/v2.1.0/collection.json' + }, + item: [ + { + name: 'request with null headers', + request: { + method: 'GET', + header: [ + { key: 'Content-Type', value: 'application/json' }, + { key: null, value: null }, + { key: null, value: 'somevalue' }, + { key: 'X-Custom', value: null } + ], + url: { raw: 'https://example.com/api' } + } + } + ] + }; + + const brunoCollection = await postmanToBruno(collectionWithNullHeaders); + const headers = brunoCollection.items[0].request.headers; + + expect(headers).toHaveLength(3); + expect(headers[0].name).toBe('Content-Type'); + expect(headers[0].value).toBe('application/json'); + expect(headers[1].name).toBe(''); + expect(headers[1].value).toBe('somevalue'); + expect(headers[2].name).toBe('X-Custom'); + expect(headers[2].value).toBe(''); + }); + + it('should skip urlencoded params where both key and value are null', async () => { + const collectionWithNullUrlencoded = { + info: { + _postman_id: 'test-null-urlencoded', + name: 'collection with null urlencoded', + schema: 'https://schema.getpostman.com/json/collection/v2.1.0/collection.json' + }, + item: [ + { + name: 'request with null urlencoded', + request: { + method: 'POST', + header: [], + url: { raw: 'https://example.com/api' }, + body: { + mode: 'urlencoded', + urlencoded: [ + { key: 'field1', value: 'value1' }, + { key: null, value: null }, + { key: null, value: 'partialvalue' }, + { key: 'field2', value: null } + ] + } + } + } + ] + }; + + const brunoCollection = await postmanToBruno(collectionWithNullUrlencoded); + const formUrlEncoded = brunoCollection.items[0].request.body.formUrlEncoded; + + expect(formUrlEncoded).toHaveLength(3); + expect(formUrlEncoded[0].name).toBe('field1'); + expect(formUrlEncoded[0].value).toBe('value1'); + expect(formUrlEncoded[1].name).toBe(''); + expect(formUrlEncoded[1].value).toBe('partialvalue'); + expect(formUrlEncoded[2].name).toBe('field2'); + expect(formUrlEncoded[2].value).toBe(''); + }); + + it('should skip formdata params where both key and value are null', async () => { + const collectionWithNullFormdata = { + info: { + _postman_id: 'test-null-formdata', + name: 'collection with null formdata', + schema: 'https://schema.getpostman.com/json/collection/v2.1.0/collection.json' + }, + item: [ + { + name: 'request with null formdata', + request: { + method: 'POST', + header: [], + url: { raw: 'https://example.com/api' }, + body: { + mode: 'formdata', + formdata: [ + { key: 'field1', value: 'value1', type: 'text' }, + { key: null, value: null, type: 'text' }, + { key: 'field2', value: null, type: 'text' } + ] + } + } + } + ] + }; + + const brunoCollection = await postmanToBruno(collectionWithNullFormdata); + const multipartForm = brunoCollection.items[0].request.body.multipartForm; + + expect(multipartForm).toHaveLength(2); + expect(multipartForm[0].name).toBe('field1'); + expect(multipartForm[0].value).toBe('value1'); + expect(multipartForm[1].name).toBe('field2'); + expect(multipartForm[1].value).toBe(''); + }); + + it('should skip query params where both key and value are null', async () => { + const collectionWithNullQueryParams = { + info: { + _postman_id: 'test-null-query-params', + name: 'collection with null query params', + schema: 'https://schema.getpostman.com/json/collection/v2.1.0/collection.json' + }, + item: [ + { + name: 'request with null query params', + request: { + method: 'GET', + header: [], + url: { + raw: 'https://example.com/api?search=test', + protocol: 'https', + host: ['example', 'com'], + path: ['api'], + query: [ + { key: 'search', value: 'test' }, + { key: null, value: null }, + { key: null, value: 'somevalue' }, + { key: 'emptyval', value: null } + ] + } + } + } + ] + }; + + const brunoCollection = await postmanToBruno(collectionWithNullQueryParams); + const params = brunoCollection.items[0].request.params; + + // Fully-null entry should be skipped + expect(params).toHaveLength(3); + + // Normal param preserved as-is + expect(params[0].name).toBe('search'); + expect(params[0].value).toBe('test'); + expect(params[0].type).toBe('query'); + + // Null key normalized to empty string, value preserved + expect(params[1].name).toBe(''); + expect(params[1].value).toBe('somevalue'); + expect(params[1].type).toBe('query'); + + // Key preserved, null value normalized to empty string + expect(params[2].name).toBe('emptyval'); + expect(params[2].value).toBe(''); + expect(params[2].type).toBe('query'); + }); }); // Simple Collection (postman)