feat: introduce res.getSize() helper (header/body/total) (#5018)

* feat: introduce `res.getSize()` helper (header/body/total)

* fix: unit test

* rm: request-duration from collection runner header

* change: api for getSize

* fix

* improve: getSize method

* add: todo comment

---------

Co-authored-by: lohit <lohit@usebruno.com>
This commit is contained in:
Pooja
2025-07-08 21:00:05 +05:30
committed by GitHub
parent 82f5f9ee88
commit fbc77fc725
10 changed files with 149 additions and 10 deletions

View File

@@ -43,7 +43,11 @@ const STATIC_API_HINTS = {
'res.getHeaders()',
'res.getBody()',
'res.setBody(data)',
'res.getResponseTime()'
'res.getResponseTime()',
'res.getSize()',
'res.getSize().header',
'res.getSize().body',
'res.getSize().total',
],
bru: [
'bru',

View File

@@ -415,8 +415,9 @@ const runSingleRequest = async function (
/** @type {import('axios').AxiosResponse} */
response = await axiosInstance(request);
const { data } = parseDataFromResponse(response, request.__brunoDisableParsingResponseJson);
const { data, dataBuffer } = parseDataFromResponse(response, request.__brunoDisableParsingResponseJson);
response.data = data;
response.dataBuffer = dataBuffer;
// Prevents the duration on leaking to the actual result
responseTime = response.headers.get('request-duration');
@@ -428,8 +429,9 @@ const runSingleRequest = async function (
}
} catch (err) {
if (err?.response) {
const { data } = parseDataFromResponse(err?.response);
const { data, dataBuffer } = parseDataFromResponse(err?.response);
err.response.data = data;
err.response.dataBuffer = dataBuffer;
response = err.response;
// Prevents the duration on leaking to the actual result

View File

@@ -26,6 +26,11 @@ const replacements = {
'pm\\.response\\.headers\\.get\\(': 'res.getHeader(',
'pm\\.response\\.to\\.have\\.body\\(': 'expect(res.getBody()).to.equal(',
'pm\\.response\\.to\\.have\\.header\\(': 'expect(res.getHeaders()).to.have.property(',
'pm\\.response\\.size\\(\\)': 'res.getSize()',
'pm\\.response\\.size\\(\\)\\.body': 'res.getSize().body',
'pm\\.response\\.responseSize': 'res.getSize().body',
'pm\\.response\\.size\\(\\)\\.header': 'res.getSize().header',
'pm\\.response\\.size\\(\\)\\.total': 'res.getSize().total',
'pm\\.environment\\.name': 'bru.getEnvName()',
'pm\\.response\\.status': 'res.statusText',
'pm\\.response\\.headers': 'res.getHeaders()',

View File

@@ -84,6 +84,11 @@ const simpleTranslations = {
'pm.response.responseTime': 'res.getResponseTime()',
'pm.response.statusText': 'res.statusText',
'pm.response.headers': 'res.getHeaders()',
'pm.response.size': 'res.getSize',
'pm.response.responseSize': 'res.getSize().body',
'pm.response.size().body': 'res.getSize().body',
'pm.response.size().header': 'res.getSize().header',
'pm.response.size().total': 'res.getSize().total',
// Execution control
'pm.execution.skipRequest': 'bru.runner.skipRequest',

View File

@@ -9,6 +9,11 @@ describe('postmanTranslations - response commands', () => {
const responseJson = pm.response.json();
const responseStatus = pm.response.status;
const responseHeaders = pm.response.headers;
const responseSize = pm.response.size();
const responseSizeBody = pm.response.size().body;
const responseSizeHeader = pm.response.size().header;
const responseSizeTotal = pm.response.size().total;
const responseSizeBody2 = pm.response.responseSize;
pm.test('Status code is 200', function() {
pm.response.to.have.status(200);
@@ -21,6 +26,11 @@ describe('postmanTranslations - response commands', () => {
const responseJson = res.getBody();
const responseStatus = res.statusText;
const responseHeaders = res.getHeaders();
const responseSize = res.getSize();
const responseSizeBody = res.getSize().body;
const responseSizeHeader = res.getSize().header;
const responseSizeTotal = res.getSize().total;
const responseSizeBody2 = res.getSize().body;
test('Status code is 200', function() {
expect(res.getStatus()).to.equal(200);
@@ -29,6 +39,3 @@ describe('postmanTranslations - response commands', () => {
expect(postmanTranslation(inputScript)).toBe(expectedOutput);
});
});

View File

@@ -553,4 +553,35 @@ describe('Response Translation', () => {
expect(res.getBody()).to.equal({"status": "ok"});
`);
});
// --- getSize translations ---------------------------
it('should translate pm.response.size()', () => {
const code = 'const size = pm.response.size();';
const translatedCode = translateCode(code);
expect(translatedCode).toBe('const size = res.getSize();');
});
it('should translate pm.response.size().body', () => {
const code = 'const bodySize = pm.response.size().body;';
const translatedCode = translateCode(code);
expect(translatedCode).toBe('const bodySize = res.getSize().body;');
});
it('should translate pm.response.size().header', () => {
const code = 'const headerSize = pm.response.size().header;';
const translatedCode = translateCode(code);
expect(translatedCode).toBe('const headerSize = res.getSize().header;');
});
it('should translate pm.response.size().total', () => {
const code = 'const totalSize = pm.response.size().total;';
const translatedCode = translateCode(code);
expect(translatedCode).toBe('const totalSize = res.getSize().total;');
});
it('should translate pm.response.responseSize alias', () => {
const code = 'const responseSize = pm.response.responseSize;';
const translatedCode = translateCode(code);
expect(translatedCode).toBe('const responseSize = res.getSize().body;');
});
});

View File

@@ -876,8 +876,8 @@ const registerNetworkIpc = (mainWindow) => {
statusText: response.statusText,
headers: response.headers,
data: response.data,
dataBuffer: dataBuffer.toString('base64'),
size: Buffer.byteLength(dataBuffer),
dataBuffer: response.dataBuffer.toString('base64'),
size: Buffer.byteLength(response.dataBuffer),
duration: responseTime ?? 0,
timeline: response.timeline
};
@@ -1153,6 +1153,7 @@ const registerNetworkIpc = (mainWindow) => {
response.data = data;
response.dataBuffer = dataBuffer;
response.responseTime = response.headers.get('request-duration');
response.headers.delete('request-duration');
// save cookies
if (preferencesUtil.shouldStoreCookies()) {
@@ -1174,8 +1175,8 @@ const registerNetworkIpc = (mainWindow) => {
dataBuffer: dataBuffer.toString('base64'),
size: Buffer.byteLength(dataBuffer),
data: response.data,
responseTime: response.responseTime,
timeline: response.timeline,
responseTime: response.headers.get('request-duration')
},
...eventData
});
@@ -1187,6 +1188,8 @@ const registerNetworkIpc = (mainWindow) => {
if (error?.response) {
const { data, dataBuffer } = parseDataFromResponse(error.response);
error.response.responseTime = error.response.headers.get('request-duration');
error.response.headers.delete('request-duration');
error.response.data = data;
error.response.dataBuffer = dataBuffer;
@@ -1199,8 +1202,8 @@ const registerNetworkIpc = (mainWindow) => {
dataBuffer: dataBuffer.toString('base64'),
size: Buffer.byteLength(dataBuffer),
data: error.response.data,
responseTime: error.response.responseTime,
timeline: error.response.timeline,
responseTime: error.response.headers.get('request-duration')
};
// if we get a response from the server, we consider it as a success

View File

@@ -52,6 +52,46 @@ class BrunoResponse {
this.body = clonedData;
}
// TODO: Refactor: dataBuffer size calculation should be handled in a shared utility so it can be passed and reused across the application
getSize() {
if (!this.res) {
return { header: 0, body: 0, total: 0 };
}
const { data, dataBuffer, headers } = this.res;
let bodySize = 0;
// Use raw received bytes
if (Buffer.isBuffer(dataBuffer)) {
bodySize = dataBuffer.length;
} else {
// Use server-reported Content-Length
const contentLength = headers && (headers['content-length'] || headers['Content-Length']);
if (contentLength && !isNaN(contentLength)) {
bodySize = parseInt(contentLength, 10);
} else if (data != null) {
// Manual calculation
const raw = typeof data === 'string' ? data : JSON.stringify(data);
bodySize = Buffer.byteLength(raw);
}
}
const headerLines = [
`HTTP/1.1 ${this.res.status} ${this.res.statusText}`,
...Object.entries(this.res.headers || {}).flatMap(([key, value]) =>
Array.isArray(value)
? value.map((v) => `${key}: ${v}`)
: [`${key}: ${value}`]
),
'',
''
];
const headerSize = Buffer.byteLength(headerLines.join('\r\n'));
return { header: headerSize, body: bodySize, total: headerSize + bodySize };
}
getDataBuffer() {
return this.res ? this.res.dataBuffer : null;
}

View File

@@ -65,6 +65,12 @@ const addBrunoResponseShimToContext = (vm, res) => {
vm.setProp(resFn, 'setBody', setBody);
setBody.dispose();
let getSize = vm.newFunction('getSize', function () {
return marshallToVm(res.getSize(), vm);
});
vm.setProp(resFn, 'getSize', getSize);
getSize.dispose();
vm.setProp(vm.global, 'res', resFn);
resFn.dispose();
};

View File

@@ -0,0 +1,36 @@
meta {
name: getSize
type: http
seq: 8
}
get {
url: https://www.httpfaker.org/api/random/json?size=1mb
body: none
auth: inherit
}
params:query {
size: 1mb
}
script:post-response {
console.log(res.getSize())
}
tests {
test("test body size", function() {
const bodySize = res.getSize().body;
expect(bodySize === 1048934).to.be.true;
});
test("test header size", function() {
const bodySize = res.getSize().header;
expect(bodySize === 305).to.be.true;
});
test("test total size", function() {
const sizes = res.getSize();
expect(sizes.total).to.equal(sizes.header + sizes.body);
});
}