diff --git a/src/formatters/NetworkFormatter.ts b/src/formatters/NetworkFormatter.ts index 7302cd4e2..313e091cb 100644 --- a/src/formatters/NetworkFormatter.ts +++ b/src/formatters/NetworkFormatter.ts @@ -50,45 +50,36 @@ export class NetworkFormatter { async #loadDetailedData(): Promise { // Load Request Body if (this.#request.hasPostData()) { - const data = this.#request.postData(); - if (data) { - if (this.#options.requestFilePath) { - if (!this.#options.saveFile) { - throw new Error('saveFile is not provided'); - } + let data; + try { + data = + this.#request.postData() ?? (await this.#request.fetchPostData()); + } catch { + // Ignore parsing errors + } + const requestBodyNotAvailableMessage = + ''; + if (this.#options.requestFilePath) { + if (!this.#options.saveFile) { + throw new Error('saveFile is not provided'); + } + if (data) { await this.#options.saveFile( Buffer.from(data), this.#options.requestFilePath, ); this.#requestBodyFilePath = this.#options.requestFilePath; } else { + this.#requestBody = requestBodyNotAvailableMessage; + } + } else { + if (data) { this.#requestBody = getSizeLimitedString( data, BODY_CONTEXT_SIZE_LIMIT, ); - } - } else { - try { - const fetchData = await this.#request.fetchPostData(); - if (fetchData) { - if (this.#options.requestFilePath) { - if (!this.#options.saveFile) { - throw new Error('saveFile is not provided'); - } - await this.#options.saveFile( - Buffer.from(fetchData), - this.#options.requestFilePath, - ); - this.#requestBodyFilePath = this.#options.requestFilePath; - } else { - this.#requestBody = getSizeLimitedString( - fetchData, - BODY_CONTEXT_SIZE_LIMIT, - ); - } - } - } catch { - this.#requestBody = ''; + } else { + this.#requestBody = requestBodyNotAvailableMessage; } } } @@ -96,11 +87,23 @@ export class NetworkFormatter { // Load Response Body const response = this.#request.response(); if (response) { + const responseBodyNotAvailableMessage = + ''; if (this.#options.responseFilePath) { - this.#responseBodyFilePath = await this.#saveResponseBodyToFile( - response, - this.#options.responseFilePath, - ); + try { + const buffer = await response.buffer(); + if (!this.#options.saveFile) { + throw new Error('saveFile is not provided'); + } + await this.#options.saveFile(buffer, this.#options.responseFilePath); + this.#responseBodyFilePath = this.#options.responseFilePath; + } catch { + // Flatten error handling for buffer() failure and save failure + } + + if (!this.#responseBodyFilePath) { + this.#responseBody = responseBodyNotAvailableMessage; + } } else { this.#responseBody = await this.#getFormattedResponseBody( response, @@ -262,22 +265,6 @@ export class NetworkFormatter { return ''; } } - - async #saveResponseBodyToFile( - httpResponse: HTTPResponse, - filePath: string, - ): Promise { - try { - const responseBuffer = await httpResponse.buffer(); - if (!this.#options.saveFile) { - throw new Error('saveFile is not provided'); - } - await this.#options.saveFile(responseBuffer, filePath); - return filePath; - } catch { - return ''; - } - } } function getSizeLimitedString(text: string, sizeLimit: number) { diff --git a/tests/formatters/NetworkFormatter.test.ts b/tests/formatters/NetworkFormatter.test.ts index 94fa79f05..a1f1fd3c2 100644 --- a/tests/formatters/NetworkFormatter.test.ts +++ b/tests/formatters/NetworkFormatter.test.ts @@ -328,6 +328,48 @@ describe('NetworkFormatter', () => { assert.ok(result.includes(`Saved to ${reqPath}.`)); assert.ok(result.includes(`Saved to ${resPath}.`)); }); + + it('handles missing bodies with filepath', async () => { + const request = { + method: () => 'POST', + url: () => 'http://example.com', + headers: () => ({}), + hasPostData: () => true, // Claim we have data + postData: () => null, // But returns null + response: () => ({ + status: () => 200, + headers: () => ({}), + buffer: async () => { + throw new Error('Body not available'); + }, + }), + failure: () => null, + redirectChain: () => [], + fetchPostData: async () => { + throw new Error('Body not available'); + }, + } as unknown as HTTPRequest; + + const reqPath = join(tmpDir, 'req_missing.txt'); + const resPath = join(tmpDir, 'res_missing.txt'); + + const formatter = await NetworkFormatter.from(request, { + fetchData: true, + requestFilePath: reqPath, + responseFilePath: resPath, + saveFile: async (data, filename) => { + await writeFile(filename, data); + return {filename}; + }, + }); + + const result = formatter.toStringDetailed(); + assert.ok( + result.includes( + `### Response Body\n`, + ), + ); + }); }); describe('toJSON', () => {