diff --git a/CHANGELOG.yaml b/CHANGELOG.yaml index f265db41f..b7181b012 100644 --- a/CHANGELOG.yaml +++ b/CHANGELOG.yaml @@ -1,6 +1,8 @@ unreleased: fixed bugs: - GH-1366 Fixed a bug where $randomAvatarImage was returning an invalid URL + chores: + - GH-1362 Updated `Request~size` to handle `downloadedBytes` property 4.4.0: date: 2024-02-28 diff --git a/lib/collection/response.js b/lib/collection/response.js index 9b9787e7d..ff61e9a7d 100644 --- a/lib/collection/response.js +++ b/lib/collection/response.js @@ -198,7 +198,7 @@ _.assign(Response.prototype, /** @lends Response.prototype */ { update (options) { // options.stream accepts Buffer, Buffer.toJSON() or base64 string // @todo this temporarily doubles the memory footprint (options.stream + generated buffer). - var stream = normalizeStream(options.stream); + const stream = normalizeStream(options.stream); _.mergeDefined((this._details = _.clone(httpReasons.lookup(options.code))), { name: _.choose(options.reason, options.status), @@ -255,7 +255,13 @@ _.assign(Response.prototype, /** @lends Response.prototype */ { * @private * @type {Number} */ - responseSize: stream && stream.byteLength + responseSize: stream && stream.byteLength, + + /** + * @private + * @type {Number} + */ + downloadedBytes: options.downloadedBytes }); } }); @@ -397,61 +403,57 @@ _.assign(Response.prototype, /** @lends Response.prototype */ { }, /** - * Get the response size by computing the same from content length header or using the actual response body. + * @typedef Response.sizeInfo + * @property {Number} body - size of the response body in bytes + * @property {Number} header - size of the response header in bytes + * @property {Number} total - total size of the response body and header in bytes + */ + /** + * Get the response size by computing the same from content length header or + * using the actual response body. * - * @returns {Number} - * @todo write unit tests + * @returns {Response.sizeInfo} - Response size object */ size: function () { - var sizeInfo = { + const sizeInfo = { body: 0, header: 0, total: 0 }, + contentLength = this.headers.get(CONTENT_LENGTH); - contentEncoding = this.headers.get(CONTENT_ENCODING), - contentLength = this.headers.get(CONTENT_LENGTH), - isCompressed = false, - byteLength; - - // if server sent encoded data, we should first try deriving length from headers - if (_.isString(contentEncoding)) { - // desensitise case of content encoding - contentEncoding = contentEncoding.toLowerCase(); - // eslint-disable-next-line lodash/prefer-includes - isCompressed = (contentEncoding.indexOf('gzip') > -1) || (contentEncoding.indexOf('deflate') > -1); + // Set body size from downloadedBytes if available + if (util.isNumeric(this.downloadedBytes)) { + sizeInfo.body = this.downloadedBytes; } - - // if 'Content-Length' header is present and encoding is of type gzip/deflate, we take body as declared by - // server. else we need to compute the same. - if (contentLength && isCompressed && util.isNumeric(contentLength)) { + // Rely on content-length header + else if (contentLength && util.isNumeric(contentLength)) { sizeInfo.body = _.parseInt(contentLength, 10); } - // if there is a stream defined which looks like buffer, use it's data and move on - else if (this.stream) { - byteLength = this.stream.byteLength; - sizeInfo.body = util.isNumeric(byteLength) ? byteLength : - /* istanbul ignore next */ - 0; + // Fall back to stream if available + else if (this.stream && util.isNumeric(this.stream.byteLength)) { + sizeInfo.body = this.stream.byteLength; } - // otherwise, if body is defined, we try get the true length of the body + // Or, calculate size from body string else if (!_.isNil(this.body)) { - sizeInfo.body = supportsBuffer ? Buffer.byteLength(this.body.toString()) : + sizeInfo.body = supportsBuffer ? + Buffer.byteLength(this.body.toString()) : /* istanbul ignore next */ this.body.toString().length; } - // size of header is added + // Header size is calculated as per the HTTP message format // https://tools.ietf.org/html/rfc7230#section-3 // HTTP-message = start-line (request-line / status-line) // *( header-field CRLF ) // CRLF // [ message-body ] // status-line = HTTP-version SP status-code SP reason-phrase CRLF - sizeInfo.header = (HTTP_X_X + SP + this.code + SP + this.reason() + CRLF + CRLF).length + - this.headers.contentSize(); + sizeInfo.header = ( + HTTP_X_X + SP + this.code + SP + this.reason() + CRLF + CRLF + ).length + this.headers.contentSize(); - // compute the approximate total body size by adding size of header and body + // Compute the approximate total body size by adding size of header and body sizeInfo.total = (sizeInfo.body || 0) + (sizeInfo.header); return sizeInfo; diff --git a/test/unit/response.test.js b/test/unit/response.test.js index a6a71ce81..beb485134 100644 --- a/test/unit/response.test.js +++ b/test/unit/response.test.js @@ -271,8 +271,20 @@ describe('Response', function () { it('should handle blank responses correctly', function () { var response = new Response(); + expect(response.size()).to.eql({ body: 0, header: 32, total: 32 }); + }); + + it('should handle string responses correctly', function () { + var response = new Response({ body: 'random' }); + + expect(response.size()).to.eql({ body: 6, header: 32, total: 38 }); + }); + + it('should report downloaded size correctly', function () { + var response = new Response({ body: 'random', downloadedBytes: 6 }); + expect(response.size()).to.eql({ - body: 0, header: 32, total: 32 + body: 6, header: 32, total: 38 }); }); }); @@ -477,6 +489,17 @@ describe('Response', function () { expect(response.size().body).to.equal(20); }); + it('must match the content-length of the response if brotli encoded', function () { + var rawResponse = { + code: 200, + body: 'gzipped content xyzxyzxyzxyzxyzxyz', + header: 'Content-Encoding: br\nContent-Length: 20' + }, + response = new Response(rawResponse); + + expect(response.size().body).to.equal(20); + }); + it('must use byteLength from buffer if provided', function () { var rawResponse = { code: 200, @@ -487,6 +510,29 @@ describe('Response', function () { expect(response.size().body).to.equal(14); }); + + it('must use downloaded size if provided', function () { + var rawResponse = { + code: 200, + body: 'something nice', + header: 'Transfer-Encoding: chunked\nContent-Length: 20', + downloadedBytes: 10 + }, + response = new Response(rawResponse); + + expect(response.size().body).to.equal(10); + }); + + it('must use content length of stream if header is provided and downloaded bytes is not present', function () { + var rawResponse = { + code: 200, + stream: Buffer.from('something nice'), + header: 'Transfer-Encoding: chunked\nContent-Length: 20\nContent-Encoding: gzip' + }, + response = new Response(rawResponse); + + expect(response.size().body).to.equal(20); + }); }); describe('toJSON', function () { diff --git a/types/index.d.ts b/types/index.d.ts index 7f799c0da..9a881d4f3 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -1,4 +1,4 @@ -// Type definitions for postman-collection 4.3.0 +// Type definitions for postman-collection 4.4.0 // Project: https://github.com/postmanlabs/postman-collection // Definitions by: PostmanLabs // Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped @@ -2005,6 +2005,16 @@ declare module "postman-collection" { stream?: Buffer | ArrayBuffer; responseTime: number; }; + /** + * @property body - size of the response body in bytes + * @property header - size of the response header in bytes + * @property total - total size of the response body and header in bytes + */ + type sizeInfo = { + body: number; + header: number; + total: number; + }; /** * Returns the durations of each request phase in milliseconds * @example @@ -2129,9 +2139,11 @@ declare module "postman-collection" { */ dataURI(): string; /** - * Get the response size by computing the same from content length header or using the actual response body. + * Get the response size by computing the same from content length header or + * using the actual response body. + * @returns - Response size object */ - size(): number; + size(): Response.sizeInfo; /** * Check whether an object is an instance of ItemGroup. * @param obj - -