mirror of
https://github.com/usebruno/bruno.git
synced 2026-06-25 21:55:49 +00:00
fix: multipart form-data file param export/import for Postman (#7111)
This commit is contained in:
committed by
GitHub
parent
d30ab4d984
commit
7f047a4412
@@ -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 })
|
||||
};
|
||||
})
|
||||
};
|
||||
|
||||
@@ -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 })
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user