From 7586ab4b1566a61b18d198fe1bb8880d8eec395a Mon Sep 17 00:00:00 2001 From: Merlijn Vos Date: Wed, 15 Jan 2025 09:59:41 +0100 Subject: [PATCH] @uppy/aws-s3: allow uploads to fail/succeed independently (#5603) --- packages/@uppy/aws-s3/src/index.test.ts | 51 +++++++++++++++++++------ packages/@uppy/aws-s3/src/index.ts | 2 +- 2 files changed, 40 insertions(+), 13 deletions(-) diff --git a/packages/@uppy/aws-s3/src/index.test.ts b/packages/@uppy/aws-s3/src/index.test.ts index 34bc269ef7..a19825444b 100644 --- a/packages/@uppy/aws-s3/src/index.test.ts +++ b/packages/@uppy/aws-s3/src/index.test.ts @@ -239,7 +239,7 @@ describe('AwsS3Multipart', () => { const signPart = vi.fn(async (file, { partNumber }) => { return { - url: `https://bucket.s3.us-east-2.amazonaws.com/test/upload/multitest.dat?partNumber=${partNumber}&uploadId=6aeb1980f3fc7ce0b5454d25b71992&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIATEST%2F20210729%2Fus-east-2%2Fs3%2Faws4_request&X-Amz-Date=20210729T014044Z&X-Amz-Expires=600&X-Amz-SignedHeaders=host&X-Amz-Signature=test`, + url: `https://bucket.s3.us-east-2.amazonaws.com/test/upload/${file.name}?partNumber=${partNumber}&uploadId=6aeb1980f3fc7ce0b5454d25b71992&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIATEST%2F20210729%2Fus-east-2%2Fs3%2Faws4_request&X-Amz-Date=20210729T014044Z&X-Amz-Expires=600&X-Amz-SignedHeaders=host&X-Amz-Signature=test`, } }) @@ -286,34 +286,61 @@ describe('AwsS3Multipart', () => { const core = new Core().use(AwsS3Multipart, { shouldUseMultipart: true, retryDelays: [10], - createMultipartUpload, + createMultipartUpload: vi.fn((file) => ({ + uploadId: '6aeb1980f3fc7ce0b5454d25b71992', + key: `test/upload/${file.name}`, + })), completeMultipartUpload: vi.fn(async () => ({ location: 'test' })), abortMultipartUpload: vi.fn(), signPart, - uploadPartBytes: uploadPartBytes.mockImplementation(() => + uploadPartBytes: uploadPartBytes.mockImplementation((options) => { + if (options.signature.url.includes('succeed.dat')) { + return new Promise((resolve) => { + // delay until after multitest.dat has failed. + setTimeout(() => resolve({ status: 200 }), 100) + }) + } // eslint-disable-next-line prefer-promise-reject-errors - Promise.reject({ source: { status: 500 } }), - ), + return Promise.reject({ source: { status: 500 } }) + }), listParts: undefined as any, }) - const awsS3Multipart = core.getPlugin('AwsS3Multipart')! const fileSize = 5 * MB + 1 * MB - const mock = vi.fn() - core.on('upload-error', mock) + const awsS3Multipart = core.getPlugin('AwsS3Multipart')! + const uploadErrorMock = vi.fn() + const uploadSuccessMock = vi.fn() + core.on('upload-error', uploadErrorMock) + core.on('upload-success', uploadSuccessMock) core.addFile({ source: 'vi', - name: 'multitest.dat', + name: 'fail.dat', + type: 'application/octet-stream', + data: new File([new Uint8Array(fileSize)], '', { + type: 'application/octet-stream', + }), + }) + + core.addFile({ + source: 'vi', + name: 'succeed.dat', type: 'application/octet-stream', data: new File([new Uint8Array(fileSize)], '', { type: 'application/octet-stream', }), }) - await expect(core.upload()).rejects.toEqual({ source: { status: 500 } }) + try { + const results = await core.upload() + expect(results!.successful!.length).toEqual(1) + expect(results!.failed!.length).toEqual(1) + } catch { + // Catch Promise.all reject + } - expect(awsS3Multipart.opts.uploadPartBytes.mock.calls.length).toEqual(3) - expect(mock.mock.calls.length).toEqual(1) + expect(awsS3Multipart.opts.uploadPartBytes.mock.calls.length).toEqual(5) + expect(uploadErrorMock.mock.calls.length).toEqual(1) + expect(uploadSuccessMock.mock.calls.length).toEqual(1) // This fails for me becuase upload returned early. }) }) diff --git a/packages/@uppy/aws-s3/src/index.ts b/packages/@uppy/aws-s3/src/index.ts index 18b895b848..5f2509fe74 100644 --- a/packages/@uppy/aws-s3/src/index.ts +++ b/packages/@uppy/aws-s3/src/index.ts @@ -972,7 +972,7 @@ export default class AwsS3Multipart< return this.#uploadLocalFile(file) }) - const upload = await Promise.all(promises) + const upload = await Promise.allSettled(promises) // After the upload is done, another upload may happen with only local files. // We reset the capability so that the next upload can use resumable uploads. this.#setResumableUploadsCapability(true)