fix: Postman import compatibility for multipart form-data file params (#7325)

* fix: multipart form-data file param export/import for Postman

* fix: Postman import compatibility for multipart form-data file params

This commit fixes two issues that caused Postman to fail importing
Bruno-exported collections with multipart form-data file parameters:

1. Changed `src` field format to match Postman's export format:
   - Single file: export as string (e.g., "/path/to/file")
   - Multiple files: export as array (e.g., ["/path/a", "/path/b"])
   - Empty/null: export as null

   Previously, Bruno always exported `src` as an array, but Postman's
   importer expects a string for single files and fails to recognize
   the file type and path when given an array.

2. Added `protocolProfileBehavior.disableBodyPruning` for GET/HEAD/OPTIONS
   requests that have a body:

   By default, Postman discards request bodies for HTTP methods that
   typically don't have bodies (GET, HEAD, OPTIONS). Without this flag,
   importing a GET request with a form-data body would result in the
   body being silently dropped, making Postman unable to identify that
   the request has a formdata body at all.

Both changes align Bruno's Postman export format with Postman's own
export format, ensuring full import compatibility.

---------

Co-authored-by: Chirag Chandrashekhar <cchirag85@gmail.com>
This commit is contained in:
Chirag Chandrashekhar
2026-02-27 17:30:20 +05:30
committed by GitHub
parent 5e75bc5fcb
commit a9709fb82a
2 changed files with 126 additions and 7 deletions

View File

@@ -268,13 +268,21 @@ export const brunoToPostman = (collection) => {
mode: 'formdata',
formdata: map(body.multipartForm || [], (bodyItem) => {
const isFile = bodyItem.type === 'file';
const getSrc = () => {
if (!bodyItem.value) return null;
if (Array.isArray(bodyItem.value)) {
if (bodyItem.value.length === 0) return null;
if (bodyItem.value.length === 1) return bodyItem.value[0];
return bodyItem.value;
}
return bodyItem.value;
};
return {
key: bodyItem.name || '',
disabled: !bodyItem.enabled,
type: isFile ? 'file' : 'text',
...(isFile
? { src: Array.isArray(bodyItem.value) ? bodyItem.value : bodyItem.value ? [bodyItem.value] : [] }
: { value: bodyItem.value || '' }),
...(isFile ? { src: getSrc() } : { value: bodyItem.value || '' }),
...(bodyItem.contentType && { contentType: bodyItem.contentType })
};
})
@@ -507,8 +515,15 @@ export const brunoToPostman = (collection) => {
};
} else if (isItemARequest(item)) {
const requestEvents = generateEventSection(item.request);
const method = (item.request?.method || 'GET').toUpperCase();
const hasBody = item.request?.body && item.request.body.mode !== 'none';
const methodsWithoutBody = ['GET', 'HEAD', 'OPTIONS'];
const needsBodyPruningDisabled = hasBody && methodsWithoutBody.includes(method);
const postmanItem = {
name: item.name || 'Untitled Request',
...(needsBodyPruningDisabled ? { protocolProfileBehavior: { disableBodyPruning: true } } : {}),
request: generateRequestSection(item.request),
...(requestEvents.length ? { event: requestEvents } : {})
};

View File

@@ -604,7 +604,7 @@ describe('brunoToPostman multipartForm handling', () => {
formdata: [
{
key: 'myFile',
src: ['/path/to/file.json'],
src: '/path/to/file.json',
disabled: false,
type: 'file',
contentType: 'application/json'
@@ -656,7 +656,7 @@ describe('brunoToPostman multipartForm handling', () => {
},
{
key: 'fileField',
src: ['/path/to/file.txt'],
src: '/path/to/file.txt',
disabled: true,
type: 'file'
}
@@ -692,7 +692,7 @@ describe('brunoToPostman multipartForm handling', () => {
const result = brunoToPostman(simpleCollection);
expect(result.item[0].request.body.formdata[0]).toEqual({
key: 'myFile',
src: ['/single/file/path.txt'],
src: '/single/file/path.txt',
disabled: false,
type: 'file'
});
@@ -726,13 +726,117 @@ describe('brunoToPostman multipartForm handling', () => {
const result = brunoToPostman(simpleCollection);
expect(result.item[0].request.body.formdata[0]).toEqual({
key: 'myFile',
src: [],
src: null,
disabled: false,
type: 'file'
});
});
});
describe('brunoToPostman protocolProfileBehavior handling', () => {
it('should add disableBodyPruning for GET requests with body', () => {
const simpleCollection = {
items: [
{
name: 'GET with body',
type: 'http-request',
request: {
method: 'GET',
url: 'https://example.com',
body: {
mode: 'multipartForm',
multipartForm: [
{
name: 'file',
value: '/path/to/file.txt',
type: 'file',
enabled: true
}
]
}
}
}
]
};
const result = brunoToPostman(simpleCollection);
expect(result.item[0].protocolProfileBehavior).toEqual({
disableBodyPruning: true
});
});
it('should not add protocolProfileBehavior for POST requests with body', () => {
const simpleCollection = {
items: [
{
name: 'POST with body',
type: 'http-request',
request: {
method: 'POST',
url: 'https://example.com',
body: {
mode: 'multipartForm',
multipartForm: [
{
name: 'file',
value: '/path/to/file.txt',
type: 'file',
enabled: true
}
]
}
}
}
]
};
const result = brunoToPostman(simpleCollection);
expect(result.item[0].protocolProfileBehavior).toBeUndefined();
});
it('should not add protocolProfileBehavior for GET requests without body', () => {
const simpleCollection = {
items: [
{
name: 'GET without body',
type: 'http-request',
request: {
method: 'GET',
url: 'https://example.com'
}
}
]
};
const result = brunoToPostman(simpleCollection);
expect(result.item[0].protocolProfileBehavior).toBeUndefined();
});
it('should add disableBodyPruning for HEAD requests with body', () => {
const simpleCollection = {
items: [
{
name: 'HEAD with body',
type: 'http-request',
request: {
method: 'HEAD',
url: 'https://example.com',
body: {
mode: 'json',
json: '{"test": true}'
}
}
}
]
};
const result = brunoToPostman(simpleCollection);
expect(result.item[0].protocolProfileBehavior).toEqual({
disableBodyPruning: true
});
});
});
describe('brunoToPostman event handling', () => {
it('should generate events for request scripts (req/res)', () => {
const simpleCollection = {