@@ -156,3 +156,146 @@ describe('S3ProtocolHandler.getObject', () => {
156156 )
157157 } )
158158} )
159+
160+ describe ( 'S3ProtocolHandler.abortMultipartUpload' , ( ) => {
161+ it ( 'aborts multipart upload and deletes from database when backend succeeds' , async ( ) => {
162+ const uploadId = 'test-upload-id'
163+ const findMultipartUpload = vi . fn ( ) . mockResolvedValue ( {
164+ id : uploadId ,
165+ version : 'test-version' ,
166+ user_metadata : { key : 'value' } ,
167+ metadata : { mimetype : 'text/plain' } ,
168+ } )
169+ const deleteMultipartUpload = vi . fn ( ) . mockResolvedValue ( undefined )
170+ const testPermission = vi . fn ( ) . mockResolvedValue ( undefined )
171+ const abortMultipartUpload = vi . fn ( ) . mockResolvedValue ( undefined )
172+ const getKeyLocation = vi . fn ( ( ) => 'tenant-id/bucket/object.txt' )
173+
174+ const storage = {
175+ backend : {
176+ abortMultipartUpload,
177+ } ,
178+ db : {
179+ asSuperUser : vi . fn ( ( ) => ( {
180+ findMultipartUpload,
181+ deleteMultipartUpload,
182+ } ) ) ,
183+ testPermission,
184+ } ,
185+ location : {
186+ getKeyLocation,
187+ } ,
188+ }
189+ const handler = new S3ProtocolHandler ( storage as never , 'tenant-id' )
190+
191+ const response = await handler . abortMultipartUpload ( {
192+ Bucket : 'bucket' ,
193+ Key : 'object.txt' ,
194+ UploadId : uploadId ,
195+ } )
196+
197+ expect ( response ) . toEqual ( { } )
198+ expect ( findMultipartUpload ) . toHaveBeenCalledWith ( uploadId , 'id,version,user_metadata,metadata' )
199+ expect ( abortMultipartUpload ) . toHaveBeenCalled ( )
200+ expect ( deleteMultipartUpload ) . toHaveBeenCalledWith ( uploadId )
201+ } )
202+
203+ it ( 'deletes from database when backend throws NoSuchUpload error' , async ( ) => {
204+ const uploadId = 'test-upload-id'
205+ const findMultipartUpload = vi . fn ( ) . mockResolvedValue ( {
206+ id : uploadId ,
207+ version : 'test-version' ,
208+ user_metadata : null ,
209+ metadata : null ,
210+ } )
211+ const deleteMultipartUpload = vi . fn ( ) . mockResolvedValue ( undefined )
212+ const testPermission = vi . fn ( ) . mockResolvedValue ( undefined )
213+ const noSuchUploadError = {
214+ name : 'NoSuchUpload' ,
215+ message : 'The specified upload does not exist.' ,
216+ $metadata : {
217+ httpStatusCode : 404 ,
218+ } ,
219+ }
220+ const abortMultipartUpload = vi . fn ( ) . mockRejectedValue ( noSuchUploadError )
221+ const getKeyLocation = vi . fn ( ( ) => 'tenant-id/bucket/object.txt' )
222+
223+ const storage = {
224+ backend : {
225+ abortMultipartUpload,
226+ } ,
227+ db : {
228+ asSuperUser : vi . fn ( ( ) => ( {
229+ findMultipartUpload,
230+ deleteMultipartUpload,
231+ } ) ) ,
232+ testPermission,
233+ } ,
234+ location : {
235+ getKeyLocation,
236+ } ,
237+ }
238+ const handler = new S3ProtocolHandler ( storage as never , 'tenant-id' )
239+
240+ const response = await handler . abortMultipartUpload ( {
241+ Bucket : 'bucket' ,
242+ Key : 'object.txt' ,
243+ UploadId : uploadId ,
244+ } )
245+
246+ expect ( response ) . toEqual ( { } )
247+ expect ( findMultipartUpload ) . toHaveBeenCalledWith ( uploadId , 'id,version,user_metadata,metadata' )
248+ expect ( abortMultipartUpload ) . toHaveBeenCalled ( )
249+ expect ( deleteMultipartUpload ) . toHaveBeenCalledWith ( uploadId )
250+ } )
251+
252+ it ( 'throws error when backend throws non-NoSuchUpload error' , async ( ) => {
253+ const uploadId = 'test-upload-id'
254+ const findMultipartUpload = vi . fn ( ) . mockResolvedValue ( {
255+ id : uploadId ,
256+ version : 'test-version' ,
257+ user_metadata : null ,
258+ metadata : null ,
259+ } )
260+ const deleteMultipartUpload = vi . fn ( ) . mockResolvedValue ( undefined )
261+ const testPermission = vi . fn ( ) . mockResolvedValue ( undefined )
262+ const otherError = {
263+ name : 'AccessDenied' ,
264+ message : 'Access Denied' ,
265+ $metadata : {
266+ httpStatusCode : 403 ,
267+ } ,
268+ }
269+ const abortMultipartUpload = vi . fn ( ) . mockRejectedValue ( otherError )
270+ const getKeyLocation = vi . fn ( ( ) => 'tenant-id/bucket/object.txt' )
271+
272+ const storage = {
273+ backend : {
274+ abortMultipartUpload,
275+ } ,
276+ db : {
277+ asSuperUser : vi . fn ( ( ) => ( {
278+ findMultipartUpload,
279+ deleteMultipartUpload,
280+ } ) ) ,
281+ testPermission,
282+ } ,
283+ location : {
284+ getKeyLocation,
285+ } ,
286+ }
287+ const handler = new S3ProtocolHandler ( storage as never , 'tenant-id' )
288+
289+ await expect (
290+ handler . abortMultipartUpload ( {
291+ Bucket : 'bucket' ,
292+ Key : 'object.txt' ,
293+ UploadId : uploadId ,
294+ } )
295+ ) . rejects . toEqual ( otherError )
296+
297+ expect ( findMultipartUpload ) . toHaveBeenCalledWith ( uploadId , 'id,version,user_metadata,metadata' )
298+ expect ( abortMultipartUpload ) . toHaveBeenCalled ( )
299+ expect ( deleteMultipartUpload ) . not . toHaveBeenCalled ( )
300+ } )
301+ } )
0 commit comments