diff --git a/packages/bruno-cli/src/runner/prepare-request.js b/packages/bruno-cli/src/runner/prepare-request.js index 1b5993852..abaed8bf7 100644 --- a/packages/bruno-cli/src/runner/prepare-request.js +++ b/packages/bruno-cli/src/runner/prepare-request.js @@ -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'; diff --git a/packages/bruno-cli/src/runner/run-single-request.js b/packages/bruno-cli/src/runner/run-single-request.js index fc7a7f6d6..40f34a910 100644 --- a/packages/bruno-cli/src/runner/run-single-request.js +++ b/packages/bruno-cli/src/runner/run-single-request.js @@ -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'; diff --git a/packages/bruno-cli/tests/runner/prepare-request.spec.js b/packages/bruno-cli/tests/runner/prepare-request.spec.js index d532dcff1..8056f17bb 100644 --- a/packages/bruno-cli/tests/runner/prepare-request.spec.js +++ b/packages/bruno-cli/tests/runner/prepare-request.spec.js @@ -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'); + }); + }); });