mirror of
https://github.com/usebruno/bruno.git
synced 2026-06-11 09:51:30 +00:00
feat: add support for file body mode in bruno-cli (#5427)
* feat: add support for `file` body mode in `bruno-cli` Fixes #4336 * fix: Correct await/async on file reading. * fix: update test and fix lint errors --------- Co-authored-by: William Floyd <william.floyd@modopayments.com> Co-authored-by: Bijin Bruno <bijin@usebruno.com>
This commit is contained in:
@@ -1,12 +1,18 @@
|
||||
const { get, each, filter } = require('lodash');
|
||||
const get = require('lodash/get');
|
||||
const each = require('lodash/each');
|
||||
const filter = require('lodash/filter');
|
||||
const find = require('lodash/find');
|
||||
const decomment = require('decomment');
|
||||
const crypto = require('node:crypto');
|
||||
const fs = require('node:fs/promises');
|
||||
const { mergeHeaders, mergeScripts, mergeVars, mergeAuth, getTreePathFromCollectionToItem } = require('../utils/collection');
|
||||
const { buildFormUrlEncodedPayload } = require('../utils/form-data');
|
||||
const path = require('node:path');
|
||||
|
||||
const prepareRequest = (item = {}, collection = {}) => {
|
||||
const prepareRequest = async (item = {}, collection = {}) => {
|
||||
const request = item?.request;
|
||||
const brunoConfig = get(collection, 'brunoConfig', {});
|
||||
const collectionPath = collection?.pathname;
|
||||
const headers = {};
|
||||
let contentTypeDefined = false;
|
||||
|
||||
@@ -288,6 +294,32 @@ const prepareRequest = (item = {}, collection = {}) => {
|
||||
axiosRequest.data = request.body.sparql;
|
||||
}
|
||||
|
||||
if (request.body.mode === 'file') {
|
||||
if (!contentTypeDefined) {
|
||||
axiosRequest.headers['content-type'] = 'application/octet-stream'; // Default headers for binary file uploads
|
||||
}
|
||||
|
||||
const bodyFile = find(request.body.file, param => param.selected);
|
||||
if (bodyFile) {
|
||||
let { filePath, contentType } = bodyFile;
|
||||
|
||||
axiosRequest.headers['content-type'] = contentType;
|
||||
|
||||
if (filePath) {
|
||||
if (!path.isAbsolute(filePath)) {
|
||||
filePath = path.join(collectionPath, filePath);
|
||||
}
|
||||
|
||||
try {
|
||||
const fileContent = await fs.readFile(filePath);
|
||||
axiosRequest.data = fileContent;
|
||||
} catch (error) {
|
||||
console.error('Error reading file:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (request.body.mode === 'formUrlEncoded') {
|
||||
if (!contentTypeDefined) {
|
||||
axiosRequest.headers['content-type'] = 'application/x-www-form-urlencoded';
|
||||
|
||||
@@ -72,7 +72,7 @@ const runSingleRequest = async function (
|
||||
let preRequestTestResults = [];
|
||||
let postResponseTestResults = [];
|
||||
|
||||
request = prepareRequest(item, collection);
|
||||
request = await prepareRequest(item, collection);
|
||||
|
||||
request.__bruno__executionMode = 'cli';
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ describe('prepare-request: prepareRequest', () => {
|
||||
const expected = `{
|
||||
\"test\": \"{{someVar}}\"
|
||||
}`;
|
||||
const result = prepareRequest({ request: { body } });
|
||||
const result = await prepareRequest({ request: { body } });
|
||||
expect(result.data).toEqual(expected);
|
||||
});
|
||||
|
||||
@@ -17,7 +17,7 @@ describe('prepare-request: prepareRequest', () => {
|
||||
const expected = `{
|
||||
\"test\": {{someVar}}
|
||||
}`;
|
||||
const result = prepareRequest({ request: { body } });
|
||||
const result = await prepareRequest({ request: { body } });
|
||||
expect(result.data).toEqual(expected);
|
||||
});
|
||||
});
|
||||
@@ -56,7 +56,7 @@ describe('prepare-request: prepareRequest', () => {
|
||||
});
|
||||
|
||||
describe('API Key Authentication', () => {
|
||||
it('If collection auth is apikey in header', () => {
|
||||
it('If collection auth is apikey in header', async () => {
|
||||
collection.root.request.auth = {
|
||||
mode: "apikey",
|
||||
apikey: {
|
||||
@@ -66,11 +66,11 @@ describe('prepare-request: prepareRequest', () => {
|
||||
}
|
||||
};
|
||||
|
||||
const result = prepareRequest(item, collection);
|
||||
const result = await prepareRequest(item, collection);
|
||||
expect(result.headers).toHaveProperty('x-api-key', '{{apiKey}}');
|
||||
});
|
||||
|
||||
it('If collection auth is apikey in header and request has existing headers', () => {
|
||||
it('If collection auth is apikey in header and request has existing headers', async () => {
|
||||
collection.root.request.auth = {
|
||||
mode: "apikey",
|
||||
apikey: {
|
||||
@@ -81,12 +81,12 @@ describe('prepare-request: prepareRequest', () => {
|
||||
};
|
||||
|
||||
item.request.headers.push({ name: 'Content-Type', value: 'application/json', enabled: true });
|
||||
const result = prepareRequest(item, collection);
|
||||
const result = await prepareRequest(item, collection);
|
||||
expect(result.headers).toHaveProperty('Content-Type', 'application/json');
|
||||
expect(result.headers).toHaveProperty('x-api-key', '{{apiKey}}');
|
||||
});
|
||||
|
||||
it('If collection auth is apikey in query parameters', () => {
|
||||
it('If collection auth is apikey in query parameters', async () => {
|
||||
collection.root.request.auth = {
|
||||
mode: "apikey",
|
||||
apikey: {
|
||||
@@ -100,13 +100,13 @@ describe('prepare-request: prepareRequest', () => {
|
||||
urlObj.searchParams.set(collection.root.request.auth.apikey.key, collection.root.request.auth.apikey.value);
|
||||
|
||||
const expected = urlObj.toString();
|
||||
const result = prepareRequest(item, collection);
|
||||
const result = await prepareRequest(item, collection);
|
||||
expect(result.url).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Basic Authentication', () => {
|
||||
it('If collection auth is basic auth', () => {
|
||||
it('If collection auth is basic auth', async () => {
|
||||
collection.root.request.auth = {
|
||||
mode: 'basic',
|
||||
basic: {
|
||||
@@ -115,14 +115,14 @@ describe('prepare-request: prepareRequest', () => {
|
||||
}
|
||||
};
|
||||
|
||||
const result = prepareRequest(item, collection);
|
||||
const result = await prepareRequest(item, collection);
|
||||
const expected = { username: 'testUser', password: 'testPass123' };
|
||||
expect(result.basicAuth).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Bearer Token Authentication', () => {
|
||||
it('If collection auth is bearer token', () => {
|
||||
it('If collection auth is bearer token', async () => {
|
||||
collection.root.request.auth = {
|
||||
mode: 'bearer',
|
||||
bearer: {
|
||||
@@ -130,11 +130,11 @@ describe('prepare-request: prepareRequest', () => {
|
||||
}
|
||||
};
|
||||
|
||||
const result = prepareRequest(item, collection);
|
||||
const result = await prepareRequest(item, collection);
|
||||
expect(result.headers).toHaveProperty('Authorization', 'Bearer token');
|
||||
});
|
||||
|
||||
it('If collection auth is bearer token and request has existing headers', () => {
|
||||
it('If collection auth is bearer token and request has existing headers', async () => {
|
||||
collection.root.request.auth = {
|
||||
mode: 'bearer',
|
||||
bearer: {
|
||||
@@ -144,14 +144,14 @@ describe('prepare-request: prepareRequest', () => {
|
||||
|
||||
item.request.headers.push({ name: 'Content-Type', value: 'application/json', enabled: true });
|
||||
|
||||
const result = prepareRequest(item, collection);
|
||||
const result = await prepareRequest(item, collection);
|
||||
expect(result.headers).toHaveProperty('Authorization', 'Bearer token');
|
||||
expect(result.headers).toHaveProperty('Content-Type', 'application/json');
|
||||
});
|
||||
});
|
||||
|
||||
describe('OAuth2 Authentication', () => {
|
||||
it('If collection auth is OAuth2 with client credentials grant type', () => {
|
||||
it('If collection auth is OAuth2 with client credentials grant type', async () => {
|
||||
collection.root.request.auth = {
|
||||
mode: 'oauth2',
|
||||
oauth2: {
|
||||
@@ -167,7 +167,7 @@ describe('prepare-request: prepareRequest', () => {
|
||||
}
|
||||
};
|
||||
|
||||
const result = prepareRequest(item, collection);
|
||||
const result = await prepareRequest(item, collection);
|
||||
|
||||
expect(result.oauth2).toBeDefined();
|
||||
expect(result.oauth2.grantType).toBe('client_credentials');
|
||||
@@ -181,7 +181,7 @@ describe('prepare-request: prepareRequest', () => {
|
||||
expect(result.oauth2.tokenQueryKey).toBe('access_token');
|
||||
});
|
||||
|
||||
it('If collection auth is OAuth2 with password grant type', () => {
|
||||
it('If collection auth is OAuth2 with password grant type', async () => {
|
||||
collection.root.request.auth = {
|
||||
mode: 'oauth2',
|
||||
oauth2: {
|
||||
@@ -199,7 +199,7 @@ describe('prepare-request: prepareRequest', () => {
|
||||
}
|
||||
};
|
||||
|
||||
const result = prepareRequest(item, collection);
|
||||
const result = await prepareRequest(item, collection);
|
||||
|
||||
expect(result.oauth2).toBeDefined();
|
||||
expect(result.oauth2.grantType).toBe('password');
|
||||
@@ -217,7 +217,7 @@ describe('prepare-request: prepareRequest', () => {
|
||||
});
|
||||
|
||||
describe('AWS v4 Authentication', () => {
|
||||
it('If collection auth is AWS v4', () => {
|
||||
it('If collection auth is AWS v4', async () => {
|
||||
collection.root.request.auth = {
|
||||
mode: 'awsv4',
|
||||
awsv4: {
|
||||
@@ -230,7 +230,7 @@ describe('prepare-request: prepareRequest', () => {
|
||||
}
|
||||
};
|
||||
|
||||
const result = prepareRequest(item, collection);
|
||||
const result = await prepareRequest(item, collection);
|
||||
const expected = {
|
||||
accessKeyId: 'AKIAIOSFODNN7EXAMPLE',
|
||||
secretAccessKey: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY',
|
||||
@@ -244,7 +244,7 @@ describe('prepare-request: prepareRequest', () => {
|
||||
});
|
||||
|
||||
describe('NTLM Authentication', () => {
|
||||
it('If collection auth is NTLM', () => {
|
||||
it('If collection auth is NTLM', async () => {
|
||||
collection.root.request.auth = {
|
||||
mode: 'ntlm',
|
||||
ntlm: {
|
||||
@@ -254,7 +254,7 @@ describe('prepare-request: prepareRequest', () => {
|
||||
}
|
||||
};
|
||||
|
||||
const result = prepareRequest(item, collection);
|
||||
const result = await prepareRequest(item, collection);
|
||||
const expected = {
|
||||
username: 'testUser',
|
||||
password: 'testPass123',
|
||||
@@ -265,7 +265,7 @@ describe('prepare-request: prepareRequest', () => {
|
||||
});
|
||||
|
||||
describe('WSSE Authentication', () => {
|
||||
it('If collection auth is WSSE', () => {
|
||||
it('If collection auth is WSSE', async () => {
|
||||
collection.root.request.auth = {
|
||||
mode: 'wsse',
|
||||
wsse: {
|
||||
@@ -274,7 +274,7 @@ describe('prepare-request: prepareRequest', () => {
|
||||
}
|
||||
};
|
||||
|
||||
const result = prepareRequest(item, collection);
|
||||
const result = await prepareRequest(item, collection);
|
||||
expect(result.headers).toHaveProperty('X-WSSE');
|
||||
expect(result.headers['X-WSSE']).toContain('UsernameToken Username="testUser"');
|
||||
expect(result.headers['X-WSSE']).toContain('PasswordDigest="');
|
||||
@@ -284,7 +284,7 @@ describe('prepare-request: prepareRequest', () => {
|
||||
});
|
||||
|
||||
describe('Digest Authentication', () => {
|
||||
it('If collection auth is digest auth', () => {
|
||||
it('If collection auth is digest auth', async () => {
|
||||
collection.root.request.auth = {
|
||||
mode: 'digest',
|
||||
digest: {
|
||||
@@ -293,7 +293,7 @@ describe('prepare-request: prepareRequest', () => {
|
||||
}
|
||||
};
|
||||
|
||||
const result = prepareRequest(item, collection);
|
||||
const result = await prepareRequest(item, collection);
|
||||
|
||||
const expected = {
|
||||
username: 'testUser',
|
||||
@@ -304,7 +304,7 @@ describe('prepare-request: prepareRequest', () => {
|
||||
});
|
||||
|
||||
describe('No Authentication', () => {
|
||||
it('If request does not have auth configured', () => {
|
||||
it('If request does not have auth configured', async () => {
|
||||
delete item.request.auth;
|
||||
let result;
|
||||
expect(() => {
|
||||
@@ -339,7 +339,7 @@ describe('prepare-request: prepareRequest', () => {
|
||||
});
|
||||
|
||||
describe('API Key Authentication', () => {
|
||||
it('If request auth is apikey in header', () => {
|
||||
it('If request auth is apikey in header', async () => {
|
||||
item.request.auth = {
|
||||
mode: "apikey",
|
||||
apikey: {
|
||||
@@ -349,11 +349,11 @@ describe('prepare-request: prepareRequest', () => {
|
||||
}
|
||||
};
|
||||
|
||||
const result = prepareRequest(item);
|
||||
const result = await prepareRequest(item);
|
||||
expect(result.headers).toHaveProperty('x-api-key', '{{apiKey}}');
|
||||
});
|
||||
|
||||
it('If request auth is apikey in header and request has existing headers', () => {
|
||||
it('If request auth is apikey in header and request has existing headers', async () => {
|
||||
item.request.auth = {
|
||||
mode: "apikey",
|
||||
apikey: {
|
||||
@@ -364,12 +364,12 @@ describe('prepare-request: prepareRequest', () => {
|
||||
};
|
||||
|
||||
item.request.headers.push({ name: 'Content-Type', value: 'application/json', enabled: true });
|
||||
const result = prepareRequest(item);
|
||||
const result = await prepareRequest(item);
|
||||
expect(result.headers).toHaveProperty('Content-Type', 'application/json');
|
||||
expect(result.headers).toHaveProperty('x-api-key', '{{apiKey}}');
|
||||
});
|
||||
|
||||
it('If request auth is apikey in query parameters', () => {
|
||||
it('If request auth is apikey in query parameters', async () => {
|
||||
item.request.auth = {
|
||||
mode: "apikey",
|
||||
apikey: {
|
||||
@@ -383,13 +383,13 @@ describe('prepare-request: prepareRequest', () => {
|
||||
urlObj.searchParams.set(item.request.auth.apikey.key, item.request.auth.apikey.value);
|
||||
|
||||
const expected = urlObj.toString();
|
||||
const result = prepareRequest(item);
|
||||
const result = await prepareRequest(item);
|
||||
expect(result.url).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Basic Authentication', () => {
|
||||
it('If request auth is basic auth', () => {
|
||||
it('If request auth is basic auth', async () => {
|
||||
item.request.auth = {
|
||||
mode: 'basic',
|
||||
basic: {
|
||||
@@ -398,14 +398,14 @@ describe('prepare-request: prepareRequest', () => {
|
||||
}
|
||||
};
|
||||
|
||||
const result = prepareRequest(item);
|
||||
const result = await prepareRequest(item);
|
||||
const expected = { username: 'testUser', password: 'testPass123' };
|
||||
expect(result.basicAuth).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Bearer Token Authentication', () => {
|
||||
it('If request auth is bearer token', () => {
|
||||
it('If request auth is bearer token', async () => {
|
||||
item.request.auth = {
|
||||
mode: 'bearer',
|
||||
bearer: {
|
||||
@@ -413,11 +413,11 @@ describe('prepare-request: prepareRequest', () => {
|
||||
}
|
||||
};
|
||||
|
||||
const result = prepareRequest(item);
|
||||
const result = await prepareRequest(item);
|
||||
expect(result.headers).toHaveProperty('Authorization', 'Bearer token123');
|
||||
});
|
||||
|
||||
it('If request auth is bearer token and request has existing headers', () => {
|
||||
it('If request auth is bearer token and request has existing headers', async () => {
|
||||
item.request.auth = {
|
||||
mode: 'bearer',
|
||||
bearer: {
|
||||
@@ -427,14 +427,14 @@ describe('prepare-request: prepareRequest', () => {
|
||||
|
||||
item.request.headers.push({ name: 'Content-Type', value: 'application/json', enabled: true });
|
||||
|
||||
const result = prepareRequest(item);
|
||||
const result = await prepareRequest(item);
|
||||
expect(result.headers).toHaveProperty('Authorization', 'Bearer token123');
|
||||
expect(result.headers).toHaveProperty('Content-Type', 'application/json');
|
||||
});
|
||||
});
|
||||
|
||||
describe('AWS v4 Authentication', () => {
|
||||
it('If request auth is AWS v4', () => {
|
||||
it('If request auth is AWS v4', async () => {
|
||||
item.request.auth = {
|
||||
mode: 'awsv4',
|
||||
awsv4: {
|
||||
@@ -447,7 +447,7 @@ describe('prepare-request: prepareRequest', () => {
|
||||
}
|
||||
};
|
||||
|
||||
const result = prepareRequest(item);
|
||||
const result = await prepareRequest(item);
|
||||
const expected = {
|
||||
accessKeyId: 'AKIAIOSFODNN7EXAMPLE',
|
||||
secretAccessKey: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY',
|
||||
@@ -461,7 +461,7 @@ describe('prepare-request: prepareRequest', () => {
|
||||
});
|
||||
|
||||
describe('NTLM Authentication', () => {
|
||||
it('If request auth is NTLM', () => {
|
||||
it('If request auth is NTLM', async () => {
|
||||
item.request.auth = {
|
||||
mode: 'ntlm',
|
||||
ntlm: {
|
||||
@@ -471,7 +471,7 @@ describe('prepare-request: prepareRequest', () => {
|
||||
}
|
||||
};
|
||||
|
||||
const result = prepareRequest(item);
|
||||
const result = await prepareRequest(item);
|
||||
const expected = {
|
||||
username: 'testUser',
|
||||
password: 'testPass123',
|
||||
@@ -482,7 +482,7 @@ describe('prepare-request: prepareRequest', () => {
|
||||
});
|
||||
|
||||
describe('WSSE Authentication', () => {
|
||||
it('If request auth is WSSE', () => {
|
||||
it('If request auth is WSSE', async () => {
|
||||
item.request.auth = {
|
||||
mode: 'wsse',
|
||||
wsse: {
|
||||
@@ -491,7 +491,7 @@ describe('prepare-request: prepareRequest', () => {
|
||||
}
|
||||
};
|
||||
|
||||
const result = prepareRequest(item);
|
||||
const result = await prepareRequest(item);
|
||||
expect(result.headers).toHaveProperty('X-WSSE');
|
||||
expect(result.headers['X-WSSE']).toContain('UsernameToken Username="requestUser"');
|
||||
expect(result.headers['X-WSSE']).toContain('PasswordDigest="');
|
||||
@@ -501,7 +501,7 @@ describe('prepare-request: prepareRequest', () => {
|
||||
});
|
||||
|
||||
describe('Digest Authentication', () => {
|
||||
it('If request auth is digest auth', () => {
|
||||
it('If request auth is digest auth', async () => {
|
||||
item.request.auth = {
|
||||
mode: 'digest',
|
||||
digest: {
|
||||
@@ -510,7 +510,7 @@ describe('prepare-request: prepareRequest', () => {
|
||||
}
|
||||
};
|
||||
|
||||
const result = prepareRequest(item);
|
||||
const result = await prepareRequest(item);
|
||||
const expected = {
|
||||
username: 'requestUser',
|
||||
password: 'requestPass123'
|
||||
@@ -519,4 +519,39 @@ describe('prepare-request: prepareRequest', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Request file body mode', () => {
|
||||
it('reads the uploaded file and applies correct headers', async () => {
|
||||
const fsPromises = require('node:fs/promises');
|
||||
// Mock fs.readFile to avoid actual file system dependency
|
||||
jest.spyOn(fsPromises, 'readFile').mockResolvedValue(Buffer.from('dummy file content'));
|
||||
|
||||
const body = {
|
||||
mode: 'file',
|
||||
file: [
|
||||
{
|
||||
contentType: 'text/plain',
|
||||
filePath: '/absolute/path/to/file.txt',
|
||||
selected: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const item = {
|
||||
name: 'File Request',
|
||||
type: 'http-request',
|
||||
request: {
|
||||
method: 'POST',
|
||||
headers: [],
|
||||
params: [],
|
||||
url: 'https://example.com/upload',
|
||||
body,
|
||||
},
|
||||
};
|
||||
|
||||
const result = await prepareRequest(item);
|
||||
expect(result.data).toBeInstanceOf(Buffer);
|
||||
expect(result.headers['content-type']).toBe('text/plain');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user