Files
bruno/packages/bruno-converters/tests/postman/postman-to-bruno/postman-to-bruno.spec.js
sanish chirayath c7ebe25cd6 Fix: ensure string authvalues, string header processing (#7646)
* feat: add helper to ensure string conversion for non-string values in Postman to Bruno conversion

- Introduced `ensureString` function to convert numeric and non-string values to strings, defaulting null/undefined to an empty string.
- Updated request handling in `importPostmanV2CollectionItem` to utilize `ensureString` for headers, parameters, and body fields.
- Added tests to verify correct conversion of numeric values to strings in headers, query parameters, and body fields.

* test: add test for numeric value conversion in Postman to Bruno transformation

- Implemented a new test case to verify that numeric values in example request and response fields are correctly converted to strings during the Postman to Bruno conversion process.
- The test checks various components including request headers, query parameters, path parameters, and body fields to ensure proper string conversion.

* test: add multipart form value test for numeric conversion in Postman to Bruno transformation

- Added a new test case to verify that numeric values in multipart form data are correctly converted to strings during the Postman to Bruno conversion process.
- The test checks the conversion of numeric values in the request body to ensure proper handling in the transformation.

* feat: enhance header parsing in Postman to Bruno conversion

- Added `parseStringHeader` and `normalizeHeaders` functions to handle various header formats, including string headers and concatenated strings.
- Updated the request and response handling in `importPostmanV2CollectionItem` to utilize the new header normalization logic.
- Introduced tests to verify correct parsing of string headers, including cases with no values and concatenated headers.

* refactor: enhance ensureString function for flexible fallback values

- Updated the `ensureString` function to accept a fallback parameter, allowing for customizable default values instead of a fixed empty string for null/undefined inputs.
- Modified the usage of `ensureString` in the `processAuth` function to utilize the new fallback feature for various authentication fields, improving the handling of optional values.

* refactor: update ensureString function to handle empty values

- Modified the `ensureString` function to return the fallback for null, undefined, or empty string values, enhancing its flexibility in handling various input scenarios.

* chore: update ESLint configuration and enhance Postman to Bruno conversion tests

- Added 'no-case-declarations' rule to ESLint configuration to enforce stricter coding standards.
- Modified the `processAuth` function to ensure proper block scoping for OAuth2 case handling.
- Improved header parsing logic to check for string type in content-type header.
- Added new tests to verify conversion of numeric authentication values to strings in both array-backed and object-backed formats during Postman to Bruno transformation.

* chore: update ESLint configuration to enforce stricter rules

- Added 'no-case-declarations' rule to ESLint configuration to enhance code quality.
- Adjusted existing rules for consistency and clarity in the configuration.

---------

Co-authored-by: Bijin A B <bijin@usebruno.com>
2026-04-01 21:41:05 +05:30

1541 lines
44 KiB
JavaScript

import { describe, it, expect } from '@jest/globals';
import postmanToBruno from '../../../src/postman/postman-to-bruno';
import { invalidVariableCharacterRegex } from '../../../src/constants';
describe('postman-collection', () => {
it('should correctly import a valid Postman collection file', async () => {
const brunoCollection = await postmanToBruno(postmanCollection);
expect(brunoCollection).toMatchObject(expectedOutput);
});
it('should replace invalid variable characters with underscores', () => {
const variables = [
{ key: 'validKey', value: 'value1' },
{ key: 'invalid key', value: 'value2' },
{ key: 'another@invalid#key$', value: 'value3' }
];
const processedVariables = variables.map((v) => ({
name: v.key.replace(invalidVariableCharacterRegex, '_'),
value: v.value
}));
expect(processedVariables).toEqual([
{ name: 'validKey', value: 'value1' },
{ name: 'invalid_key', value: 'value2' },
{ name: 'another_invalid_key_', value: 'value3' }
]);
});
it('should handle falsy values in collection variables', async () => {
const collectionWithFalsyVars = {
info: {
_postman_id: '7f91bbd8-cb97-41ac-8d0b-e1fcd8bb4ce9',
name: 'collection with falsy vars',
schema: 'https://schema.getpostman.com/json/collection/v2.1.0/collection.json'
},
variable: [
{
type: 'string'
},
{
key: '',
type: 'string'
},
{
value: '',
type: 'string'
},
{
key: '',
value: '',
type: 'string'
}
],
item: []
};
const brunoCollection = await postmanToBruno(collectionWithFalsyVars);
expect(brunoCollection.root.request.vars.req).toEqual([
{
uid: 'mockeduuidvalue123456',
name: '',
value: '',
enabled: true
},
{
uid: 'mockeduuidvalue123456',
name: '',
value: '',
enabled: true
},
{
uid: 'mockeduuidvalue123456',
name: '',
value: '',
enabled: true
}
]);
});
it('should successfully translate a URL path array with no empty elements', async () => {
const collectionWithFalsyVars = {
info: {
_postman_id: '7f91bbd8-cb97-41ac-8d0b-e1fcd8bb4ce9',
name: 'collection with falsy vars',
schema: 'https://schema.getpostman.com/json/collection/v2.1.0/collection.json'
},
variable: [
{
type: 'string'
},
{
key: '',
type: 'string'
},
{
value: '',
type: 'string'
},
{
key: '',
value: '',
type: 'string'
}
],
item: [
{
name: 'Request with all settings',
protocolProfileBehavior: {
maxRedirects: 10,
followRedirects: false,
disableUrlEncoding: true
},
request: {
method: 'GET',
header: [],
url: {
protocol: 'https',
host: ['httpbin', 'org'],
path: ['api', 'v1', 'resource']
}
}
}
]
};
const brunoCollection = await postmanToBruno(collectionWithFalsyVars);
expect(brunoCollection.items.map((item) => item.request.url)).toEqual([
'https://httpbin.org/api/v1/resource'
]);
});
it('should not mutate a URL path with an empty element representing a trailing slash', async () => {
const collectionWithFalsyVars = {
info: {
_postman_id: '7f91bbd8-cb97-41ac-8d0b-e1fcd8bb4ce9',
name: 'collection with falsy vars',
schema: 'https://schema.getpostman.com/json/collection/v2.1.0/collection.json'
},
variable: [
{
type: 'string'
},
{
key: '',
type: 'string'
},
{
value: '',
type: 'string'
},
{
key: '',
value: '',
type: 'string'
}
],
item: [
{
name: 'Request with all settings',
protocolProfileBehavior: {
maxRedirects: 10,
followRedirects: false,
disableUrlEncoding: true
},
request: {
method: 'GET',
header: [],
url: {
protocol: 'https',
host: ['httpbin', 'org'],
path: ['api', 'v1', 'resource', '']
}
}
}
]
};
const brunoCollection = await postmanToBruno(collectionWithFalsyVars);
expect(brunoCollection.items.map((item) => item.request.url)).toEqual([
'https://httpbin.org/api/v1/resource/'
]);
});
it('should not mutate a URL path with an empty element representing a trailing slash', async () => {
const collectionWithFalsyVars = {
info: {
_postman_id: '7f91bbd8-cb97-41ac-8d0b-e1fcd8bb4ce9',
name: 'collection with falsy vars',
schema: 'https://schema.getpostman.com/json/collection/v2.1.0/collection.json'
},
variable: [
{
type: 'string'
},
{
key: '',
type: 'string'
},
{
value: '',
type: 'string'
},
{
key: '',
value: '',
type: 'string'
}
],
item: [
{
name: 'Request with all settings',
protocolProfileBehavior: {
maxRedirects: 10,
followRedirects: false,
disableUrlEncoding: true
},
request: {
method: 'GET',
header: [],
url: {
protocol: 'https',
host: ['httpbin', 'org'],
path: ['api', '', 'resource']
}
}
}
]
};
const brunoCollection = await postmanToBruno(collectionWithFalsyVars);
expect(brunoCollection.items.map((item) => item.request.url)).toEqual([
'https://httpbin.org/api//resource'
]);
});
it('should convert non-string variable values to strings', async () => {
const collectionWithNonStringVars = {
info: {
name: 'Non-String Variable Demo',
_postman_id: 'abcd1234-5678-90ef-ghij-1234567890ab',
schema: 'https://schema.getpostman.com/json/collection/v2.1.0/collection.json'
},
variable: [
{ key: 'timeout', value: 5000 },
{ key: 'enabled', value: true },
{ key: 'user', value: { id: 1, name: 'Alice' } }
],
item: [
{
name: 'Sample Request',
request: {
method: 'GET',
url: {
raw: 'https://postman-echo.com/get',
protocol: 'https',
host: ['postman-echo', 'com'],
path: ['get']
}
}
}
]
};
const brunoCollection = await postmanToBruno(collectionWithNonStringVars);
const vars = brunoCollection.root.request.vars.req;
expect(vars).toHaveLength(3);
expect(vars[0]).toMatchObject({ name: 'timeout', value: '5000' });
expect(vars[1]).toMatchObject({ name: 'enabled', value: 'true' });
expect(vars[2]).toMatchObject({ name: 'user', value: '{"id":1,"name":"Alice"}' });
});
it('should handle empty variables', async () => {
const collectionWithEmptyVars = {
info: {
_postman_id: '7f91bbd8-cb97-41ac-8d0b-e1fcd8bb4ce9',
name: 'collection with falsy vars',
schema: 'https://schema.getpostman.com/json/collection/v2.1.0/collection.json'
},
variable: [],
item: []
};
const brunoCollection = await postmanToBruno(collectionWithEmptyVars);
expect(brunoCollection.root.request.vars.req).toEqual([]);
});
it('should correctly import protocolProfileBehavior settings from Postman requests', async () => {
const collectionWithSettings = {
info: {
_postman_id: 'test-settings-id',
name: 'Collection with Settings',
schema: 'https://schema.getpostman.com/json/collection/v2.1.0/collection.json'
},
item: [
{
name: 'Request with all settings',
protocolProfileBehavior: {
maxRedirects: 10,
followRedirects: false,
disableUrlEncoding: true
},
request: {
method: 'GET',
header: [],
url: {
raw: 'https://echo.usebruno.com/get',
protocol: 'https',
host: ['echo', 'usebruno', 'com'],
path: ['get']
}
}
},
{
name: 'Request with partial settings',
protocolProfileBehavior: {
followRedirects: true
},
request: {
method: 'POST',
header: [],
url: {
raw: 'https://echo.usebruno.com/post',
protocol: 'https',
host: ['echo', 'usebruno', 'com'],
path: ['post']
}
}
},
{
name: 'Request without settings',
request: {
method: 'PUT',
header: [],
url: {
raw: 'https://echo.usebruno.com/put',
protocol: 'https',
host: ['echo', 'usebruno', 'com'],
path: ['put']
}
}
}
]
};
const brunoCollection = await postmanToBruno(collectionWithSettings);
// Test request with all settings
const requestWithAllSettings = brunoCollection.items[0];
expect(requestWithAllSettings.settings).toEqual({
encodeUrl: false,
followRedirects: false,
maxRedirects: 10
});
// Test request with partial settings
const requestWithPartialSettings = brunoCollection.items[1];
expect(requestWithPartialSettings.settings).toEqual({
encodeUrl: true,
followRedirects: true
});
// Test request without settings
const requestWithoutSettings = brunoCollection.items[2];
expect(requestWithoutSettings.settings).toEqual({
encodeUrl: true
});
});
it('should handle collection with auth object having undefined type', async () => {
const collectionWithUndefinedAuthType = {
info: {
_postman_id: '7f91bbd8-cb97-41ac-8d0b-e1fcd8bb4ce9',
name: 'collection with undefined auth type',
schema: 'https://schema.getpostman.com/json/collection/v2.1.0/collection.json'
},
auth: {
basic: [
{ key: 'username', value: 'testuser', type: 'string' },
{ key: 'password', value: 'testpass', type: 'string' }
]
},
item: [
{
name: 'request',
request: {
method: 'GET',
header: [],
url: {
raw: 'https://api.example.com/test',
protocol: 'https',
host: ['api', 'example', 'com'],
path: ['test']
}
}
}
]
};
const brunoCollection = await postmanToBruno(collectionWithUndefinedAuthType);
// Collection level auth should default to 'none'
expect(brunoCollection.root.request.auth).toEqual({
mode: 'none',
basic: null,
bearer: null,
awsv4: null,
apikey: null,
oauth1: null,
oauth2: null,
digest: null
});
// Request should inherit auth mode
expect(brunoCollection.items[0].request.auth).toEqual({
mode: 'inherit',
basic: null,
bearer: null,
awsv4: null,
apikey: null,
oauth1: null,
oauth2: null,
digest: null
});
});
it('should handle collection with auth object having null type', async () => {
const collectionWithNullAuthType = {
info: {
_postman_id: '7f91bbd8-cb97-41ac-8d0b-e1fcd8bb4ce9',
name: 'collection with null auth type',
schema: 'https://schema.getpostman.com/json/collection/v2.1.0/collection.json'
},
auth: {
type: null,
bearer: {
token: 'test-token'
}
},
item: [
{
name: 'request',
request: {
method: 'GET',
header: [],
url: {
raw: 'https://api.example.com/test',
protocol: 'https',
host: ['api', 'example', 'com'],
path: ['test']
}
}
}
]
};
const brunoCollection = await postmanToBruno(collectionWithNullAuthType);
// Collection level auth should default to 'none'
expect(brunoCollection.root.request.auth).toEqual({
mode: 'none',
basic: null,
bearer: null,
awsv4: null,
apikey: null,
oauth1: null,
oauth2: null,
digest: null
});
});
it('should handle collection with auth object having unexpected type value', async () => {
const collectionWithUnexpectedAuthType = {
info: {
_postman_id: '7f91bbd8-cb97-41ac-8d0b-e1fcd8bb4ce9',
name: 'collection with unexpected auth type',
schema: 'https://schema.getpostman.com/json/collection/v2.1.0/collection.json'
},
auth: {
type: 'unexpected_auth_type',
basic: [
{ key: 'username', value: 'testuser', type: 'string' },
{ key: 'password', value: 'testpass', type: 'string' }
]
},
item: [
{
name: 'request',
request: {
method: 'GET',
header: [],
url: {
raw: 'https://api.example.com/test',
protocol: 'https',
host: ['api', 'example', 'com'],
path: ['test']
}
}
}
]
};
const brunoCollection = await postmanToBruno(collectionWithUnexpectedAuthType);
// Collection level auth should default to 'none'
expect(brunoCollection.root.request.auth).toEqual({
mode: 'none',
basic: null,
bearer: null,
awsv4: null,
apikey: null,
oauth1: null,
oauth2: null,
digest: null
});
// Request should inherit auth mode
expect(brunoCollection.items[0].request.auth).toEqual({
mode: 'inherit',
basic: null,
bearer: null,
awsv4: null,
apikey: null,
oauth1: null,
oauth2: null,
digest: null
});
});
it('should handle request with auth object having undefined type', async () => {
const collectionWithRequestUndefinedAuthType = {
info: {
_postman_id: '7f91bbd8-cb97-41ac-8d0b-e1fcd8bb4ce9',
name: 'collection with request undefined auth type',
schema: 'https://schema.getpostman.com/json/collection/v2.1.0/collection.json'
},
item: [
{
name: 'request',
request: {
method: 'GET',
header: [],
url: {
raw: 'https://api.example.com/test',
protocol: 'https',
host: ['api', 'example', 'com'],
path: ['test']
},
auth: {
basic: [
{ key: 'username', value: 'testuser', type: 'string' },
{ key: 'password', value: 'testpass', type: 'string' }
]
}
}
}
]
};
const brunoCollection = await postmanToBruno(collectionWithRequestUndefinedAuthType);
// Collection level auth should default to 'none'
expect(brunoCollection.root.request.auth).toEqual({
mode: 'none',
basic: null,
bearer: null,
awsv4: null,
apikey: null,
oauth1: null,
oauth2: null,
digest: null
});
// Request auth should default to 'none'
expect(brunoCollection.items[0].request.auth).toEqual({
mode: 'none',
basic: null,
bearer: null,
awsv4: null,
apikey: null,
oauth1: null,
oauth2: null,
digest: null
});
});
it('should handle folder with auth object having unexpected type', async () => {
const collectionWithFolderUnexpectedAuthType = {
info: {
_postman_id: '7f91bbd8-cb97-41ac-8d0b-e1fcd8bb4ce9',
name: 'collection with folder unexpected auth type',
schema: 'https://schema.getpostman.com/json/collection/v2.1.0/collection.json'
},
item: [
{
name: 'folder',
auth: {
type: 'unexpected_folder_auth_type',
bearer: {
token: 'folder-token'
}
},
item: [
{
name: 'request',
request: {
method: 'GET',
header: [],
url: {
raw: 'https://api.example.com/test',
protocol: 'https',
host: ['api', 'example', 'com'],
path: ['test']
}
}
}
]
}
]
};
const brunoCollection = await postmanToBruno(collectionWithFolderUnexpectedAuthType);
// Folder auth should default to 'none'
expect(brunoCollection.items[0].root.request.auth).toEqual({
mode: 'none',
basic: null,
bearer: null,
awsv4: null,
apikey: null,
oauth1: null,
oauth2: null,
digest: null
});
// Request should inherit auth mode
expect(brunoCollection.items[0].items[0].request.auth).toEqual({
mode: 'inherit',
basic: null,
bearer: null,
awsv4: null,
apikey: null,
oauth1: null,
oauth2: null,
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');
});
it('should convert numeric values to strings in headers, params, and body fields', async () => {
const collectionWithNumericValues = {
info: {
_postman_id: 'test-numeric-values',
name: 'collection with numeric values',
schema: 'https://schema.getpostman.com/json/collection/v2.1.0/collection.json'
},
item: [
{
name: 'request with numeric values',
request: {
method: 'POST',
header: [
{ key: 'X-Account-Id', value: 0 },
{ key: 'X-Retry-Count', value: 3 }
],
url: {
raw: 'https://example.com/api/:accountId',
protocol: 'https',
host: ['example', 'com'],
path: ['api', ':accountId'],
query: [
{ key: 'limit', value: 100 },
{ key: 'offset', value: 0 }
],
variable: [
{ key: 'accountId', value: 0 }
]
},
body: {
mode: 'urlencoded',
urlencoded: [
{ key: 'timeout', value: 5000 }
]
}
}
},
{
name: 'request with numeric multipart form values',
request: {
method: 'POST',
header: [],
url: { raw: 'https://example.com/upload' },
body: {
mode: 'formdata',
formdata: [
{ key: 'retries', value: 3, type: 'text' },
{ key: 'priority', value: 0, type: 'text' }
]
}
}
}
]
};
const brunoCollection = await postmanToBruno(collectionWithNumericValues);
const item = brunoCollection.items[0];
// Headers should have string values
expect(item.request.headers[0].value).toBe('0');
expect(item.request.headers[1].value).toBe('3');
// Query params should have string values
const queryParams = item.request.params.filter((p) => p.type === 'query');
expect(queryParams[0].value).toBe('100');
expect(queryParams[1].value).toBe('0');
// Path params should have string values
const pathParams = item.request.params.filter((p) => p.type === 'path');
expect(pathParams[0].value).toBe('0');
// Form URL-encoded should have string values
expect(item.request.body.formUrlEncoded[0].value).toBe('5000');
// Multipart form should have string values
const multipartItem = brunoCollection.items[1];
expect(multipartItem.request.body.multipartForm[0].value).toBe('3');
expect(multipartItem.request.body.multipartForm[1].value).toBe('0');
});
it('should convert numeric values to strings in example request and response fields', async () => {
const collectionWithNumericExamples = {
info: {
_postman_id: 'test-numeric-examples',
name: 'collection with numeric example values',
schema: 'https://schema.getpostman.com/json/collection/v2.1.0/collection.json'
},
item: [
{
name: 'request with numeric example',
request: {
method: 'GET',
header: [],
url: { raw: 'https://example.com/api' }
},
response: [
{
name: 'Example with numerics',
originalRequest: {
method: 'GET',
header: [
{ key: 'X-Account-Id', value: 42 }
],
url: {
raw: 'https://example.com/api/:id?page=1',
protocol: 'https',
host: ['example', 'com'],
path: ['api', ':id'],
query: [
{ key: 'page', value: 1 }
],
variable: [
{ key: 'id', value: 99 }
]
},
body: {
mode: 'urlencoded',
urlencoded: [
{ key: 'retries', value: 3 }
]
}
},
status: 'OK',
code: 200,
header: [
{ key: 'X-RateLimit-Remaining', value: 0 }
],
body: '{"ok": true}'
}
]
}
]
};
const brunoCollection = await postmanToBruno(collectionWithNumericExamples);
const example = brunoCollection.items[0].examples[0];
// Example request headers
expect(example.request.headers[0].value).toBe('42');
// Example request query params
const queryParams = example.request.params.filter((p) => p.type === 'query');
expect(queryParams[0].value).toBe('1');
// Example request path params
const pathParams = example.request.params.filter((p) => p.type === 'path');
expect(pathParams[0].value).toBe('99');
// Example request form URL-encoded
expect(example.request.body.formUrlEncoded[0].value).toBe('3');
// Example response headers
expect(example.response.headers[0].value).toBe('0');
});
it('should convert numeric auth values to strings (array-backed v2.1 format)', async () => {
const collectionWithNumericAuth = {
info: {
_postman_id: 'test-numeric-auth',
name: 'collection with numeric auth values',
schema: 'https://schema.getpostman.com/json/collection/v2.1.0/collection.json'
},
item: [
{
name: 'request with numeric bearer token',
request: {
method: 'GET',
header: [],
url: { raw: 'https://example.com/api' },
auth: {
type: 'bearer',
bearer: [
{ key: 'token', value: 123 }
]
}
}
},
{
name: 'request with numeric apikey values',
request: {
method: 'GET',
header: [],
url: { raw: 'https://example.com/api' },
auth: {
type: 'apikey',
apikey: [
{ key: 'key', value: 456 },
{ key: 'value', value: 789 }
]
}
}
}
]
};
const brunoCollection = await postmanToBruno(collectionWithNumericAuth);
// Bearer token should be stringified
expect(brunoCollection.items[0].request.auth.mode).toBe('bearer');
expect(brunoCollection.items[0].request.auth.bearer.token).toBe('123');
// API key fields should be stringified
expect(brunoCollection.items[1].request.auth.mode).toBe('apikey');
expect(brunoCollection.items[1].request.auth.apikey.key).toBe('456');
expect(brunoCollection.items[1].request.auth.apikey.value).toBe('789');
});
it('should convert numeric auth values to strings (object-backed format)', async () => {
const collectionWithObjectAuth = {
info: {
_postman_id: 'test-object-auth',
name: 'collection with object-backed auth',
schema: 'https://schema.getpostman.com/json/collection/v2.1.0/collection.json'
},
item: [
{
name: 'request with object-backed basic auth',
request: {
method: 'GET',
header: [],
url: { raw: 'https://example.com/api' },
auth: {
type: 'basic',
basic: {
username: 12345,
password: 67890
}
}
}
}
]
};
const brunoCollection = await postmanToBruno(collectionWithObjectAuth);
expect(brunoCollection.items[0].request.auth.mode).toBe('basic');
expect(brunoCollection.items[0].request.auth.basic.username).toBe('12345');
expect(brunoCollection.items[0].request.auth.basic.password).toBe('67890');
});
it('should parse string headers in request header arrays', async () => {
const collectionWithStringHeaders = {
info: {
_postman_id: 'test-string-headers',
name: 'collection with string headers',
schema: 'https://schema.getpostman.com/json/collection/v2.1.0/collection.json'
},
item: [
{
name: 'request with string headers',
request: {
method: 'GET',
header: [
'Content-Type: application/json',
{ key: 'X-Custom', value: 'test' },
'Authorization: Bearer token123'
],
url: { raw: 'https://example.com/api' }
}
}
]
};
const brunoCollection = await postmanToBruno(collectionWithStringHeaders);
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('X-Custom');
expect(headers[1].value).toBe('test');
expect(headers[2].name).toBe('Authorization');
expect(headers[2].value).toBe('Bearer token123');
});
it('should parse a single concatenated string as the header field', async () => {
const collectionWithConcatenatedHeaders = {
info: {
_postman_id: 'test-concat-headers',
name: 'collection with concatenated header string',
schema: 'https://schema.getpostman.com/json/collection/v2.1.0/collection.json'
},
item: [
{
name: 'request with concatenated header',
request: {
method: 'GET',
header: 'Content-Type: application/json\r\nHost: example.com',
url: { raw: 'https://example.com/api' }
}
}
]
};
const brunoCollection = await postmanToBruno(collectionWithConcatenatedHeaders);
const headers = brunoCollection.items[0].request.headers;
expect(headers).toHaveLength(2);
expect(headers[0].name).toBe('Content-Type');
expect(headers[0].value).toBe('application/json');
expect(headers[1].name).toBe('Host');
expect(headers[1].value).toBe('example.com');
});
it('should handle string headers with no value', async () => {
const collectionWithNoValueHeader = {
info: {
_postman_id: 'test-no-value-header',
name: 'collection with no-value string header',
schema: 'https://schema.getpostman.com/json/collection/v2.1.0/collection.json'
},
item: [
{
name: 'request with no-value header',
request: {
method: 'GET',
header: ['X-No-Value'],
url: { raw: 'https://example.com/api' }
}
}
]
};
const brunoCollection = await postmanToBruno(collectionWithNoValueHeader);
const headers = brunoCollection.items[0].request.headers;
expect(headers).toHaveLength(1);
expect(headers[0].name).toBe('X-No-Value');
expect(headers[0].value).toBe('');
});
});
// Simple Collection (postman)
// ├── folder
// │ └── request (GET)
// └── request (GET)
const postmanCollection = {
info: {
_postman_id: '7f91bbd8-cb97-41ac-8d0b-e1fcd8bb4ce9',
name: 'simple collection',
schema: 'https://schema.getpostman.com/json/collection/v2.1.0/collection.json',
_exporter_id: '21992467',
_collection_link: 'https://random-user-007.postman.co/workspace/testing~7523f559-3d5f-4c30-8315-3cb3c3ff98b7/collection/21992467-7f91bbd8-cb97-41ac-8d0b-e1fcd8bb4ce9?action=share&source=collection_link&creator=007'
},
item: [
{
name: 'folder',
item: [
{
name: 'request',
request: {
method: 'GET',
header: [],
url: {
raw: 'https://usebruno.com',
protocol: 'https',
host: [
'usebruno',
'com'
]
}
},
response: []
}
]
},
{
name: 'request',
request: {
method: 'GET',
header: [],
url: {
raw: 'https://usebruno.com',
protocol: 'https',
host: [
'usebruno',
'com'
]
}
},
response: []
}
]
};
// Simple Collection (bruno)
// ├── folder
// │ └── 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',
version: '1',
items: [
{
uid: 'mockeduuidvalue123456',
name: 'folder',
type: 'folder',
seq: 1,
items: [
{
uid: 'mockeduuidvalue123456',
name: 'request',
type: 'http-request',
seq: 1,
request: {
url: 'https://usebruno.com',
method: 'GET',
auth: {
mode: 'inherit',
basic: null,
bearer: null,
awsv4: null,
apikey: null,
oauth1: null,
oauth2: null,
digest: null
},
headers: [],
params: [],
body: {
mode: 'none',
json: null,
text: null,
xml: null,
formUrlEncoded: [],
multipartForm: []
},
docs: ''
}
}
],
root: {
docs: '',
meta: {
name: 'folder'
},
request: {
auth: {
mode: 'inherit',
basic: null,
bearer: null,
awsv4: null,
apikey: null,
oauth1: null,
oauth2: null,
digest: null
},
headers: [],
script: {},
tests: '',
vars: {}
}
}
},
{
uid: 'mockeduuidvalue123456',
name: 'request',
type: 'http-request',
seq: 2,
request: {
url: 'https://usebruno.com',
method: 'GET',
auth: {
mode: 'inherit',
basic: null,
bearer: null,
awsv4: null,
apikey: null,
oauth1: null,
oauth2: null,
digest: null
},
headers: [],
params: [],
body: {
mode: 'none',
json: null,
text: null,
xml: null,
formUrlEncoded: [],
multipartForm: []
},
docs: ''
}
}
],
environments: [],
root: {
docs: '',
meta: {
name: 'simple collection'
},
request: {
auth: {
mode: 'none',
basic: null,
bearer: null,
awsv4: null,
apikey: null,
oauth1: null,
oauth2: null,
digest: null
},
headers: [],
script: {},
tests: '',
vars: {}
}
}
};