diff --git a/handwritten/storage/src/transfer-manager.ts b/handwritten/storage/src/transfer-manager.ts index ebf1ef4538f2..ac0c080bac41 100644 --- a/handwritten/storage/src/transfer-manager.ts +++ b/handwritten/storage/src/transfer-manager.ts @@ -964,23 +964,20 @@ export class TransferManager { await Promise.all(promises); return await mpuHelper.completeUpload(); } catch (e) { + let uploadMsg = (e as Error)?.message ?? String(e); if ( (options.autoAbortFailure === undefined || options.autoAbortFailure) && mpuHelper.uploadId ) { try { await mpuHelper.abortUpload(); - return; - } catch (e) { - throw new MultiPartUploadError( - (e as Error).message, - mpuHelper.uploadId!, - mpuHelper.partsMap!, - ); + } catch (abortErr) { + const abortMsg = (abortErr as Error)?.message ?? String(abortErr); + uploadMsg += `\n${abortMsg}`; } } throw new MultiPartUploadError( - (e as Error).message, + uploadMsg, mpuHelper.uploadId!, mpuHelper.partsMap!, ); diff --git a/handwritten/storage/test/transfer-manager.ts b/handwritten/storage/test/transfer-manager.ts index e5676b0ebe4c..13b25cfc4dbc 100644 --- a/handwritten/storage/test/transfer-manager.ts +++ b/handwritten/storage/test/transfer-manager.ts @@ -846,13 +846,14 @@ describe('Transfer Manager', () => { ); }); - it('should call abortUpload when a failure occurs after an uploadID is established', async () => { + it('should call abortUpload and reject with the original error when a failure occurs after an uploadID is established', async () => { + const fakeId = '123'; + const fakePartsMap = new Map([[1, 'etag-1']]); const expectedErr = new MultiPartUploadError( 'Hello World', - '', - new Map(), + fakeId, + fakePartsMap, ); - const fakeId = '123'; mockGeneratorFunction = (bucket, fileName, uploadId, partsMap) => { fakeHelper = sandbox.createStubInstance(FakeXMLHelper); @@ -861,7 +862,8 @@ describe('Transfer Manager', () => { fakeHelper.initiateUpload.resolves(); fakeHelper.uploadPart.callsFake(() => { fakeHelper.uploadId = fakeId; - return Promise.reject(expectedErr); + fakeHelper.partsMap = fakePartsMap; + return Promise.reject(new Error(expectedErr.message)); }); fakeHelper.completeUpload.resolves(); fakeHelper.abortUpload.callsFake(() => { @@ -871,9 +873,82 @@ describe('Transfer Manager', () => { return fakeHelper; }; - assert.doesNotThrow(() => + await assert.rejects( transferManager.uploadFileInChunks(filePath, {}, mockGeneratorFunction), + expectedErr, + ); + assert.strictEqual(fakeHelper.abortUpload.calledOnce, true); + }); + + it('should not call abortUpload when autoAbortFailure is false and a failure occurs after an uploadID is established', async () => { + const fakeId = '123'; + const fakePartsMap = new Map([[1, 'etag-1']]); + const expectedErr = new MultiPartUploadError( + 'Hello World', + fakeId, + fakePartsMap, + ); + + mockGeneratorFunction = (bucket, fileName, uploadId, partsMap) => { + fakeHelper = sandbox.createStubInstance(FakeXMLHelper); + fakeHelper.uploadId = uploadId || ''; + fakeHelper.partsMap = partsMap || new Map(); + fakeHelper.initiateUpload.resolves(); + fakeHelper.uploadPart.callsFake(() => { + fakeHelper.uploadId = fakeId; + fakeHelper.partsMap = fakePartsMap; + return Promise.reject(new Error(expectedErr.message)); + }); + fakeHelper.completeUpload.resolves(); + fakeHelper.abortUpload.resolves(); + return fakeHelper; + }; + + await assert.rejects( + transferManager.uploadFileInChunks( + filePath, + {autoAbortFailure: false}, + mockGeneratorFunction, + ), + expectedErr, + ); + assert.strictEqual(fakeHelper.abortUpload.notCalled, true); + }); + + it('should reject with an error containing both messages when both uploadPart and abortUpload fail', async () => { + const fakeId = '123'; + const fakePartsMap = new Map([[1, 'etag-1']]); + const uploadErr = new Error('Chunk upload failed'); + const abortErr = new Error('Abort upload failed'); + const expectedErr = new MultiPartUploadError( + `${uploadErr.message}\n${abortErr.message}`, + fakeId, + fakePartsMap, + ); + + mockGeneratorFunction = (bucket, fileName, uploadId, partsMap) => { + fakeHelper = sandbox.createStubInstance(FakeXMLHelper); + fakeHelper.uploadId = uploadId || ''; + fakeHelper.partsMap = partsMap || new Map(); + fakeHelper.initiateUpload.resolves(); + fakeHelper.uploadPart.callsFake(() => { + fakeHelper.uploadId = fakeId; + fakeHelper.partsMap = fakePartsMap; + return Promise.reject(uploadErr); + }); + fakeHelper.completeUpload.resolves(); + fakeHelper.abortUpload.callsFake(() => { + assert.strictEqual(fakeHelper.uploadId, fakeId); + return Promise.reject(abortErr); + }); + return fakeHelper; + }; + + await assert.rejects( + transferManager.uploadFileInChunks(filePath, {}, mockGeneratorFunction), + expectedErr, ); + assert.strictEqual(fakeHelper.abortUpload.calledOnce, true); }); it('should set the appropriate `GCCL_GCS_CMD_KEY`', async () => {