diff --git a/core/packages/gaxios/src/gaxios.ts b/core/packages/gaxios/src/gaxios.ts index 2df23b4c07b6..c2854de06661 100644 --- a/core/packages/gaxios/src/gaxios.ts +++ b/core/packages/gaxios/src/gaxios.ts @@ -202,7 +202,9 @@ export class Gaxios implements FetchCompliance { response.push(chunk); } - translatedResponse.data = response.toString() as T; + translatedResponse.data = Buffer.concat( + response.map(c => (typeof c === 'string' ? Buffer.from(c) : c)), + ).toString('utf8') as T; } const errorInfo = GaxiosError.extractAPIErrorFromResponse( diff --git a/core/packages/gaxios/test/test.getch.ts b/core/packages/gaxios/test/test.getch.ts index 3db1a3d46fce..62072b7897c8 100644 --- a/core/packages/gaxios/test/test.getch.ts +++ b/core/packages/gaxios/test/test.getch.ts @@ -143,6 +143,100 @@ describe('🚙 error handling', () => { ); }); + describe('should handle stream error responses', () => { + it('split across multiple string chunks', async () => { + const chunks = [ + '{"error": {"code": 400, ', + '"message": "Invalid ', + 'argument", "status": "INVALID_ARGUMENT"}}', + ]; + const readableStream = Readable.from(chunks); + const scope = nock(url).get('/').reply(400, readableStream); + + await assert.rejects( + request({url, responseType: 'stream'}), + (err: GaxiosError) => { + scope.done(); + const apiError = JSON.parse(err.message); + return ( + apiError.error.code === 400 && + apiError.error.message === 'Invalid argument' && + apiError.error.status === 'INVALID_ARGUMENT' + ); + }, + ); + }); + + it('split across multiple Buffer chunks', async () => { + const chunks = [ + Buffer.from('{"error": {"code": 400, '), + Buffer.from('"message": "Invalid '), + Buffer.from('argument", "status": "INVALID_ARGUMENT"}}'), + ]; + const readableStream = Readable.from(chunks); + const scope = nock(url).get('/').reply(400, readableStream); + + await assert.rejects( + request({url, responseType: 'stream'}), + (err: GaxiosError) => { + scope.done(); + const apiError = JSON.parse(err.message); + return ( + apiError.error.code === 400 && + apiError.error.message === 'Invalid argument' && + apiError.error.status === 'INVALID_ARGUMENT' + ); + }, + ); + }); + + it('split across multiple Uint8Array chunks', async () => { + const chunks = [ + new Uint8Array(Buffer.from('{"error": {"code": 400, ')), + new Uint8Array(Buffer.from('"message": "Invalid ')), + new Uint8Array(Buffer.from('argument", "status": "INVALID_ARGUMENT"}}')), + ]; + const readableStream = Readable.from(chunks); + const scope = nock(url).get('/').reply(400, readableStream); + + await assert.rejects( + request({url, responseType: 'stream'}), + (err: GaxiosError) => { + scope.done(); + const apiError = JSON.parse(err.message); + return ( + apiError.error.code === 400 && + apiError.error.message === 'Invalid argument' && + apiError.error.status === 'INVALID_ARGUMENT' + ); + }, + ); + }); + + it('split across mixed type chunks (string, Buffer, Uint8Array)', async () => { + const chunks = [ + '{"error": {"code": 400, ', + Buffer.from('"message": "Invalid '), + new Uint8Array(Buffer.from('argument", "status": "INVALID_ARGUMENT"}}')), + ]; + const readableStream = Readable.from(chunks); + const scope = nock(url).get('/').reply(400, readableStream); + + await assert.rejects( + request({url, responseType: 'stream'}), + (err: GaxiosError) => { + scope.done(); + const apiError = JSON.parse(err.message); + return ( + apiError.error.code === 400 && + apiError.error.message === 'Invalid argument' && + apiError.error.status === 'INVALID_ARGUMENT' + ); + }, + ); + }); + }); + it('should not throw an error during a translation error', () => { const notJSON = '.'; const response = {