diff --git a/packages/bruno-cli/src/runner/interpolate-vars.js b/packages/bruno-cli/src/runner/interpolate-vars.js index 0b19e0a79..7f76aab14 100644 --- a/packages/bruno-cli/src/runner/interpolate-vars.js +++ b/packages/bruno-cli/src/runner/interpolate-vars.js @@ -2,6 +2,8 @@ const { interpolate } = require('@usebruno/common'); const { each, forOwn, cloneDeep, find } = require('lodash'); const { isFormData } = require('@usebruno/common').utils; +const isBinaryRequestBody = (data) => Buffer.isBuffer(data) || typeof data?.pipe === 'function'; + const getContentType = (headers = {}) => { let contentType = ''; forOwn(headers, (value, key) => { @@ -80,7 +82,7 @@ const interpolateVars = (request, envVariables = {}, runtimeVariables = {}, proc // Skip body interpolation for GraphQL requests. if (!isGraphqlRequest) { - if (contentType.includes('json') && !Buffer.isBuffer(request.data)) { + if (contentType.includes('json') && !isBinaryRequestBody(request.data)) { if (typeof request.data === 'string') { if (request?.data?.length) { request.data = _interpolate(request.data, { escapeJSONStrings: true }); diff --git a/packages/bruno-cli/tests/runner/interpolate-vars.spec.js b/packages/bruno-cli/tests/runner/interpolate-vars.spec.js index 7349b5bdd..0ffa29eb7 100644 --- a/packages/bruno-cli/tests/runner/interpolate-vars.spec.js +++ b/packages/bruno-cli/tests/runner/interpolate-vars.spec.js @@ -1,6 +1,25 @@ const { describe, it, expect } = require('@jest/globals'); const interpolateVars = require('../../src/runner/interpolate-vars'); +describe('interpolate-vars: interpolateVars', () => { + it('keeps stream-backed JSON request bodies intact', () => { + const streamPayload = { + pipe: jest.fn(), + path: '/tmp/allocations.json' + }; + const request = { + method: 'POST', + mode: 'file', + url: 'http://api.example/upload', + headers: { 'content-type': 'application/json' }, + data: streamPayload + }; + + const result = interpolateVars(request, { shouldNotApply: 'value' }, null, null); + expect(result.data).toBe(streamPayload); + }); +}); + describe('interpolate-vars: api key header name sidecar', () => { it('interpolates apiKeyHeaderName in lockstep with interpolated header keys', () => { const request = { diff --git a/packages/bruno-electron/src/ipc/network/interpolate-vars.js b/packages/bruno-electron/src/ipc/network/interpolate-vars.js index a90cc74a5..81e170e5d 100644 --- a/packages/bruno-electron/src/ipc/network/interpolate-vars.js +++ b/packages/bruno-electron/src/ipc/network/interpolate-vars.js @@ -2,6 +2,8 @@ const { interpolate } = require('@usebruno/common'); const { each, forOwn, cloneDeep } = require('lodash'); const { isFormData } = require('@usebruno/common').utils; +const isBinaryRequestBody = (data) => Buffer.isBuffer(data) || typeof data?.pipe === 'function'; + const getContentType = (headers = {}) => { let contentType = ''; forOwn(headers, (value, key) => { @@ -110,10 +112,11 @@ const interpolateVars = (request, envVariables = {}, runtimeVariables = {}, proc if (typeof contentType === 'string' && !isGraphqlRequest) { /* - We explicitly avoid interpolating buffer values because the file content is read as a buffer object in raw body mode. - Even if the selected file's content type is JSON, this prevents the buffer object from being interpolated. + We explicitly avoid interpolating binary payloads because raw file bodies can be represented as + buffers or streams depending on size. Even if the selected file's content type is JSON, the + transport object itself must not be interpolated. */ - if (contentType.includes('json') && !Buffer.isBuffer(request.data)) { + if (contentType.includes('json') && !isBinaryRequestBody(request.data)) { if (typeof request.data === 'string') { if (request.data.length) { request.data = _interpolate(request.data, { diff --git a/packages/bruno-electron/tests/network/interpolate-vars.spec.js b/packages/bruno-electron/tests/network/interpolate-vars.spec.js index 2eea40a93..48e1a9f9d 100644 --- a/packages/bruno-electron/tests/network/interpolate-vars.spec.js +++ b/packages/bruno-electron/tests/network/interpolate-vars.spec.js @@ -426,4 +426,24 @@ describe('interpolate-vars: interpolateVars', () => { expect(result.data).toContain('--TestBoundary123--'); }); }); + + describe('File body streaming', () => { + it('keeps stream-backed JSON request bodies intact', () => { + const streamPayload = { + pipe: jest.fn(), + path: '/tmp/allocations.json' + }; + const request = { + method: 'POST', + mode: 'file', + url: 'http://api.example/upload', + headers: { 'content-type': 'application/json' }, + data: streamPayload + }; + + const result = interpolateVars(request, { shouldNotApply: 'value' }, null, null); + + expect(result.data).toBe(streamPayload); + }); + }); });