fix: multipart form-data file param export/import for Postman (#7111)

This commit is contained in:
Chirag Chandrashekhar
2026-02-12 16:41:25 +05:30
committed by GitHub
parent d30ab4d984
commit 7f047a4412
4 changed files with 471 additions and 32 deletions

View File

@@ -267,11 +267,15 @@ export const brunoToPostman = (collection) => {
return {
mode: 'formdata',
formdata: map(body.multipartForm || [], (bodyItem) => {
const isFile = bodyItem.type === 'file';
return {
key: bodyItem.name || '',
value: bodyItem.value || '',
disabled: !bodyItem.enabled,
type: 'default'
type: isFile ? 'file' : 'text',
...(isFile
? { src: Array.isArray(bodyItem.value) ? bodyItem.value : bodyItem.value ? [bodyItem.value] : [] }
: { value: bodyItem.value || '' }),
...(bodyItem.contentType && { contentType: bodyItem.contentType })
};
})
};

View File

@@ -465,27 +465,19 @@ const importPostmanV2CollectionItem = (brunoParent, item, { useWorkers = false }
brunoRequestItem.request.body.mode = 'multipartForm';
each(i.request.body.formdata, (param) => {
const isFile = param.type === 'file';
let value;
let type;
if (isFile) {
// If param.src is an array, keep it as it is.
// If param.src is a string, convert it into an array with a single element.
value = Array.isArray(param.src) ? param.src : typeof param.src === 'string' ? [param.src] : null;
type = 'file';
} else {
value = param.value;
type = 'text';
}
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);
brunoRequestItem.request.body.multipartForm.push({
uid: uuid(),
type: type,
type: isFile ? 'file' : 'text',
name: param.key,
value: value,
value,
description: transformDescription(param.description),
enabled: !param.disabled
enabled: !param.disabled,
...(param.contentType && { contentType: param.contentType })
});
});
}
@@ -658,25 +650,19 @@ 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) => {
const isFile = param.type === 'file';
let value;
let type;
if (isFile) {
value = Array.isArray(param.src) ? param.src : typeof param.src === 'string' ? [param.src] : null;
type = 'file';
} else {
value = param.value;
type = 'text';
}
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);
example.request.body.multipartForm.push({
uid: uuid(),
type: type,
type: isFile ? 'file' : 'text',
name: param.key,
value: value,
value,
description: transformDescription(param.description),
enabled: !param.disabled
enabled: !param.disabled,
...(param.contentType && { contentType: param.contentType })
});
});
}

View File

@@ -493,6 +493,246 @@ describe('brunoToPostman null checks and fallbacks', () => {
});
});
describe('brunoToPostman multipartForm handling', () => {
it('should export file type with type: file and src field', () => {
const simpleCollection = {
items: [
{
name: 'Test Request',
type: 'http-request',
request: {
method: 'POST',
url: 'https://example.com',
body: {
mode: 'multipartForm',
multipartForm: [
{
name: 'myFile',
value: ['/path/to/file1.txt', '/path/to/file2.txt'],
type: 'file',
enabled: true
}
]
}
}
}
]
};
const result = brunoToPostman(simpleCollection);
expect(result.item[0].request.body).toEqual({
mode: 'formdata',
formdata: [
{
key: 'myFile',
src: ['/path/to/file1.txt', '/path/to/file2.txt'],
disabled: false,
type: 'file'
}
]
});
});
it('should export text type with type: text and value field', () => {
const simpleCollection = {
items: [
{
name: 'Test Request',
type: 'http-request',
request: {
method: 'POST',
url: 'https://example.com',
body: {
mode: 'multipartForm',
multipartForm: [
{
name: 'myField',
value: 'some text value',
type: 'text',
enabled: true
}
]
}
}
}
]
};
const result = brunoToPostman(simpleCollection);
expect(result.item[0].request.body).toEqual({
mode: 'formdata',
formdata: [
{
key: 'myField',
value: 'some text value',
disabled: false,
type: 'text'
}
]
});
});
it('should export contentType when specified', () => {
const simpleCollection = {
items: [
{
name: 'Test Request',
type: 'http-request',
request: {
method: 'POST',
url: 'https://example.com',
body: {
mode: 'multipartForm',
multipartForm: [
{
name: 'myFile',
value: ['/path/to/file.json'],
type: 'file',
contentType: 'application/json',
enabled: true
}
]
}
}
}
]
};
const result = brunoToPostman(simpleCollection);
expect(result.item[0].request.body).toEqual({
mode: 'formdata',
formdata: [
{
key: 'myFile',
src: ['/path/to/file.json'],
disabled: false,
type: 'file',
contentType: 'application/json'
}
]
});
});
it('should handle mixed file and text fields', () => {
const simpleCollection = {
items: [
{
name: 'Test Request',
type: 'http-request',
request: {
method: 'POST',
url: 'https://example.com',
body: {
mode: 'multipartForm',
multipartForm: [
{
name: 'textField',
value: 'hello',
type: 'text',
enabled: true
},
{
name: 'fileField',
value: ['/path/to/file.txt'],
type: 'file',
enabled: false
}
]
}
}
}
]
};
const result = brunoToPostman(simpleCollection);
expect(result.item[0].request.body).toEqual({
mode: 'formdata',
formdata: [
{
key: 'textField',
value: 'hello',
disabled: false,
type: 'text'
},
{
key: 'fileField',
src: ['/path/to/file.txt'],
disabled: true,
type: 'file'
}
]
});
});
it('should handle file type with string value (not array)', () => {
const simpleCollection = {
items: [
{
name: 'Test Request',
type: 'http-request',
request: {
method: 'POST',
url: 'https://example.com',
body: {
mode: 'multipartForm',
multipartForm: [
{
name: 'myFile',
value: '/single/file/path.txt',
type: 'file',
enabled: true
}
]
}
}
}
]
};
const result = brunoToPostman(simpleCollection);
expect(result.item[0].request.body.formdata[0]).toEqual({
key: 'myFile',
src: ['/single/file/path.txt'],
disabled: false,
type: 'file'
});
});
it('should handle file type with empty value', () => {
const simpleCollection = {
items: [
{
name: 'Test Request',
type: 'http-request',
request: {
method: 'POST',
url: 'https://example.com',
body: {
mode: 'multipartForm',
multipartForm: [
{
name: 'myFile',
value: '',
type: 'file',
enabled: true
}
]
}
}
}
]
};
const result = brunoToPostman(simpleCollection);
expect(result.item[0].request.body.formdata[0]).toEqual({
key: 'myFile',
src: [],
disabled: false,
type: 'file'
});
});
});
describe('brunoToPostman event handling', () => {
it('should generate events for request scripts (req/res)', () => {
const simpleCollection = {

View File

@@ -665,6 +665,215 @@ const postmanCollection = {
// │ └── request (GET)
// └── request (GET)
describe('postman-collection formdata import', () => {
it('should import formdata with type: file correctly', async () => {
const collectionWithFileFormdata = {
info: {
_postman_id: 'test-id',
name: 'collection with file formdata',
schema: 'https://schema.getpostman.com/json/collection/v2.1.0/collection.json'
},
item: [
{
name: 'request with file',
request: {
method: 'POST',
header: [],
url: { raw: 'https://example.com/upload' },
body: {
mode: 'formdata',
formdata: [
{
key: 'myFile',
type: 'file',
src: ['/path/to/file1.txt', '/path/to/file2.txt'],
disabled: false
}
]
}
}
}
]
};
const brunoCollection = await postmanToBruno(collectionWithFileFormdata);
const multipartForm = brunoCollection.items[0].request.body.multipartForm;
expect(multipartForm).toHaveLength(1);
expect(multipartForm[0].type).toBe('file');
expect(multipartForm[0].name).toBe('myFile');
expect(multipartForm[0].value).toEqual(['/path/to/file1.txt', '/path/to/file2.txt']);
expect(multipartForm[0].enabled).toBe(true);
});
it('should import formdata with type: default and src field as file', async () => {
const collectionWithDefaultTypeAndSrc = {
info: {
_postman_id: 'test-id',
name: 'collection with default type formdata',
schema: 'https://schema.getpostman.com/json/collection/v2.1.0/collection.json'
},
item: [
{
name: 'request with default type',
request: {
method: 'POST',
header: [],
url: { raw: 'https://example.com/upload' },
body: {
mode: 'formdata',
formdata: [
{
key: 'myFile',
type: 'default',
src: '/path/to/file.txt',
disabled: false
}
]
}
}
}
]
};
const brunoCollection = await postmanToBruno(collectionWithDefaultTypeAndSrc);
const multipartForm = brunoCollection.items[0].request.body.multipartForm;
expect(multipartForm).toHaveLength(1);
expect(multipartForm[0].type).toBe('file');
expect(multipartForm[0].name).toBe('myFile');
expect(multipartForm[0].value).toEqual(['/path/to/file.txt']);
expect(multipartForm[0].enabled).toBe(true);
});
it('should import formdata with type: default and value array as text', async () => {
const collectionWithDefaultTypeAndValueArray = {
info: {
_postman_id: 'test-id',
name: 'collection with default type and value array',
schema: 'https://schema.getpostman.com/json/collection/v2.1.0/collection.json'
},
item: [
{
name: 'request with default type',
request: {
method: 'POST',
header: [],
url: { raw: 'https://example.com/upload' },
body: {
mode: 'formdata',
formdata: [
{
key: 'myField',
type: 'default',
value: ['some', 'text'],
disabled: false
}
]
}
}
}
]
};
const brunoCollection = await postmanToBruno(collectionWithDefaultTypeAndValueArray);
const multipartForm = brunoCollection.items[0].request.body.multipartForm;
expect(multipartForm).toHaveLength(1);
expect(multipartForm[0].type).toBe('text');
expect(multipartForm[0].name).toBe('myField');
expect(multipartForm[0].value).toBe('sometext');
expect(multipartForm[0].enabled).toBe(true);
});
it('should preserve contentType when importing formdata', async () => {
const collectionWithContentType = {
info: {
_postman_id: 'test-id',
name: 'collection with contentType',
schema: 'https://schema.getpostman.com/json/collection/v2.1.0/collection.json'
},
item: [
{
name: 'request with contentType',
request: {
method: 'POST',
header: [],
url: { raw: 'https://example.com/upload' },
body: {
mode: 'formdata',
formdata: [
{
key: 'myFile',
type: 'file',
src: '/path/to/file.json',
contentType: 'application/json',
disabled: false
}
]
}
}
}
]
};
const brunoCollection = await postmanToBruno(collectionWithContentType);
const multipartForm = brunoCollection.items[0].request.body.multipartForm;
expect(multipartForm).toHaveLength(1);
expect(multipartForm[0].type).toBe('file');
expect(multipartForm[0].contentType).toBe('application/json');
});
it('should handle mixed file and text fields in formdata', async () => {
const collectionWithMixedFormdata = {
info: {
_postman_id: 'test-id',
name: 'collection with mixed formdata',
schema: 'https://schema.getpostman.com/json/collection/v2.1.0/collection.json'
},
item: [
{
name: 'request with mixed fields',
request: {
method: 'POST',
header: [],
url: { raw: 'https://example.com/upload' },
body: {
mode: 'formdata',
formdata: [
{
key: 'textField',
type: 'text',
value: 'hello world',
disabled: false
},
{
key: 'fileField',
type: 'file',
src: '/path/to/file.txt',
disabled: true
}
]
}
}
}
]
};
const brunoCollection = await postmanToBruno(collectionWithMixedFormdata);
const multipartForm = brunoCollection.items[0].request.body.multipartForm;
expect(multipartForm).toHaveLength(2);
expect(multipartForm[0].type).toBe('text');
expect(multipartForm[0].value).toBe('hello world');
expect(multipartForm[0].enabled).toBe(true);
expect(multipartForm[1].type).toBe('file');
expect(multipartForm[1].value).toEqual(['/path/to/file.txt']);
expect(multipartForm[1].enabled).toBe(false);
});
});
const expectedOutput = {
name: 'simple collection',
uid: 'mockeduuidvalue123456',