Skip to content

Commit f70c06a

Browse files
Add unit and functional tests
Issue: CLDSRV-669
1 parent 6d352b5 commit f70c06a

File tree

2 files changed

+611
-92
lines changed

2 files changed

+611
-92
lines changed

tests/functional/aws-node-sdk/test/object/abortMPU.js

Lines changed: 240 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -25,18 +25,20 @@ describe('Abort MPU', () => {
2525
bucketUtil = new BucketUtility('default', sigCfg);
2626
s3 = bucketUtil.s3;
2727
return s3.createBucket({ Bucket: bucket }).promise()
28-
.then(() => s3.createMultipartUpload({
29-
Bucket: bucket, Key: key }).promise())
30-
.then(res => {
31-
uploadId = res.UploadId;
32-
return s3.uploadPart({ Bucket: bucket, Key: key,
33-
PartNumber: 1, UploadId: uploadId, Body: bodyFirstPart,
34-
}).promise();
35-
})
36-
.catch(err => {
37-
process.stdout.write(`Error in beforeEach: ${err}\n`);
38-
throw err;
39-
});
28+
.then(() => s3.createMultipartUpload({
29+
Bucket: bucket, Key: key
30+
}).promise())
31+
.then(res => {
32+
uploadId = res.UploadId;
33+
return s3.uploadPart({
34+
Bucket: bucket, Key: key,
35+
PartNumber: 1, UploadId: uploadId, Body: bodyFirstPart,
36+
}).promise();
37+
})
38+
.catch(err => {
39+
process.stdout.write(`Error in beforeEach: ${err}\n`);
40+
throw err;
41+
});
4042
});
4143

4244
afterEach(() =>
@@ -45,23 +47,24 @@ describe('Abort MPU', () => {
4547
Key: key,
4648
UploadId: uploadId,
4749
}).promise()
48-
.then(() => bucketUtil.empty(bucket))
49-
.then(() => bucketUtil.deleteOne(bucket))
50+
.then(() => bucketUtil.empty(bucket))
51+
.then(() => bucketUtil.deleteOne(bucket))
5052
);
5153

5254
// aws-sdk now (v2.363.0) returns 'UriParameterError' error
5355
// this test was not replaced in any other suite
5456
it.skip('should return InvalidRequest error if aborting without key',
55-
done => {
56-
s3.abortMultipartUpload({
57-
Bucket: bucket,
58-
Key: '',
59-
UploadId: uploadId },
60-
err => {
61-
checkError(err, 'InvalidRequest', 'A key must be specified');
62-
done();
57+
done => {
58+
s3.abortMultipartUpload({
59+
Bucket: bucket,
60+
Key: '',
61+
UploadId: uploadId
62+
},
63+
err => {
64+
checkError(err, 'InvalidRequest', 'A key must be specified');
65+
done();
66+
});
6367
});
64-
});
6568
});
6669
});
6770

@@ -273,16 +276,221 @@ describe('Abort MPU - No Such Upload', () => {
273276
afterEach(() => bucketUtil.deleteOne(bucket));
274277

275278
it('should return NoSuchUpload error when aborting non-existent mpu',
276-
done => {
277-
s3.abortMultipartUpload({
278-
Bucket: bucket,
279-
Key: key,
280-
UploadId: uuidv4().replace(/-/g, '') },
281-
err => {
282-
assert.notEqual(err, null, 'Expected failure but got success');
283-
assert.strictEqual(err.code, 'NoSuchUpload');
284-
done();
279+
done => {
280+
s3.abortMultipartUpload({
281+
Bucket: bucket,
282+
Key: key,
283+
UploadId: uuidv4().replace(/-/g, '')
284+
},
285+
err => {
286+
assert.notEqual(err, null, 'Expected failure but got success');
287+
assert.strictEqual(err.code, 'NoSuchUpload');
288+
done();
289+
});
285290
});
291+
});
292+
});
293+
294+
describe('Abort MPU - Versioned Bucket Cleanup', function testSuite() {
295+
this.timeout(120000);
296+
297+
withV4(sigCfg => {
298+
let bucketUtil;
299+
let s3;
300+
const bucketName = `abort-mpu-versioned-${Date.now()}`;
301+
const objectKey = 'test-object-with-versions';
302+
303+
beforeEach(done => {
304+
bucketUtil = new BucketUtility('default', sigCfg);
305+
s3 = bucketUtil.s3;
306+
307+
async.series([
308+
next => s3.createBucket({ Bucket: bucketName }, next),
309+
next => s3.putBucketVersioning({
310+
Bucket: bucketName,
311+
VersioningConfiguration: { Status: 'Enabled' },
312+
}, next),
313+
], done);
314+
});
315+
316+
afterEach(async () => {
317+
// Clean up all multipart uploads
318+
const listMPUResponse = await s3.listMultipartUploads({ Bucket: bucketName }).promise();
319+
await Promise.all(listMPUResponse.Uploads.map(upload =>
320+
s3.abortMultipartUpload({
321+
Bucket: bucketName,
322+
Key: upload.Key,
323+
UploadId: upload.UploadId,
324+
}).promise().catch(err => {
325+
if (err.code !== 'NoSuchUpload') throw err;
326+
})
327+
));
328+
329+
// Clean up all object versions
330+
const listVersionsResponse = await s3.listObjectVersions({ Bucket: bucketName }).promise();
331+
await Promise.all([
332+
...listVersionsResponse.Versions.map(version =>
333+
s3.deleteObject({
334+
Bucket: bucketName,
335+
Key: version.Key,
336+
VersionId: version.VersionId,
337+
}).promise()
338+
),
339+
...listVersionsResponse.DeleteMarkers.map(marker =>
340+
s3.deleteObject({
341+
Bucket: bucketName,
342+
Key: marker.Key,
343+
VersionId: marker.VersionId,
344+
}).promise()
345+
),
346+
]);
347+
348+
await bucketUtil.deleteOne(bucketName);
349+
});
350+
351+
it('should handle aborting MPU with many versions of same object', done => {
352+
const numberOfVersions = 5;
353+
let currentVersion = 0;
354+
let finalUploadId;
355+
356+
// Create multiple versions of the same object
357+
async.whilst(
358+
() => currentVersion < numberOfVersions,
359+
callback => {
360+
currentVersion++;
361+
const data = Buffer.from(`Version ${currentVersion} data`);
362+
363+
async.waterfall([
364+
next => {
365+
s3.createMultipartUpload({
366+
Bucket: bucketName,
367+
Key: objectKey,
368+
}, (err, result) => {
369+
assert.ifError(err);
370+
if (currentVersion === numberOfVersions) {
371+
finalUploadId = result.UploadId; // Save the last one for aborting
372+
}
373+
next(null, result.UploadId);
374+
});
375+
},
376+
(uploadId, next) => {
377+
s3.uploadPart({
378+
Bucket: bucketName,
379+
Key: objectKey,
380+
PartNumber: 1,
381+
UploadId: uploadId,
382+
Body: data,
383+
}, (err, result) => {
384+
assert.ifError(err);
385+
next(null, uploadId, result.ETag);
386+
});
387+
},
388+
(uploadId, etag, next) => {
389+
if (currentVersion === numberOfVersions) {
390+
// Don't complete the last one - we'll abort it
391+
return next();
392+
}
393+
394+
return s3.completeMultipartUpload({
395+
Bucket: bucketName,
396+
Key: objectKey,
397+
UploadId: uploadId,
398+
MultipartUpload: {
399+
Parts: [{ ETag: etag, PartNumber: 1 }],
400+
},
401+
}, next);
402+
},
403+
], callback);
404+
},
405+
err => {
406+
assert.ifError(err);
407+
408+
// Now abort the final MPU
409+
s3.abortMultipartUpload({
410+
Bucket: bucketName,
411+
Key: objectKey,
412+
UploadId: finalUploadId,
413+
}, err => {
414+
assert.ifError(err);
415+
416+
// Verify we still have the correct number of completed versions
417+
s3.listObjectVersions({ Bucket: bucketName }, (err, data) => {
418+
assert.ifError(err);
419+
420+
const objectVersions = data.Versions.filter(v => v.Key === objectKey);
421+
assert.strictEqual(objectVersions.length, numberOfVersions - 1,
422+
`Expected ${numberOfVersions - 1} versions after abort, got ${objectVersions.length}`);
423+
424+
done();
425+
});
426+
});
427+
}
428+
);
429+
});
430+
431+
it('should handle abort MPU when object has no versions', done => {
432+
let uploadId;
433+
const data = Buffer.from('test data for single MPU abort');
434+
435+
async.waterfall([
436+
// Create and upload part for MPU
437+
next => {
438+
s3.createMultipartUpload({
439+
Bucket: bucketName,
440+
Key: objectKey,
441+
}, (err, result) => {
442+
assert.ifError(err);
443+
uploadId = result.UploadId;
444+
next();
445+
});
446+
},
447+
next => {
448+
s3.uploadPart({
449+
Bucket: bucketName,
450+
Key: objectKey,
451+
PartNumber: 1,
452+
UploadId: uploadId,
453+
Body: data,
454+
}, err => {
455+
assert.ifError(err);
456+
next();
457+
});
458+
},
459+
460+
// Abort the MPU
461+
next => {
462+
s3.abortMultipartUpload({
463+
Bucket: bucketName,
464+
Key: objectKey,
465+
UploadId: uploadId,
466+
}, err => {
467+
assert.ifError(err);
468+
next();
469+
});
470+
},
471+
472+
// Verify no object exists
473+
next => {
474+
s3.getObject({ Bucket: bucketName, Key: objectKey }, err => {
475+
assert.notEqual(err, null, 'Expected NoSuchKey error');
476+
assert.strictEqual(err.code, 'NoSuchKey');
477+
next();
478+
});
479+
},
480+
481+
// Verify no versions exist
482+
next => {
483+
s3.listObjectVersions({ Bucket: bucketName }, (err, data) => {
484+
assert.ifError(err);
485+
486+
const objectVersions = data.Versions.filter(v => v.Key === objectKey);
487+
assert.strictEqual(objectVersions.length, 0,
488+
`Expected 0 versions after abort, got ${objectVersions.length}`);
489+
490+
next();
491+
});
492+
},
493+
], done);
286494
});
287495
});
288496
});

0 commit comments

Comments
 (0)