diff --git a/package.json b/package.json index 7e89650e..58ea2de1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "s3utils", - "version": "1.17.14", + "version": "1.17.15", "engines": { "node": ">= 22" }, diff --git a/tests/unit/utils/S3UtilsMongoClient.js b/tests/unit/utils/S3UtilsMongoClient.js index 01b5fa77..f16485d9 100644 --- a/tests/unit/utils/S3UtilsMongoClient.js +++ b/tests/unit/utils/S3UtilsMongoClient.js @@ -891,6 +891,39 @@ describe('S3UtilsMongoClient::_processEntryData', () => { }, }, ], + [ + 'should add content-length to cold storage location and current dataStore ' + + 'when object is restoring with string typed timestamps', + testBucketName, + true, + { + _id: 'testkey6b', + value: { + ...objectMdTemp, + dataStoreName: 'cold-location', + replicationInfo: { + backends: [], + }, + archive: { + archiveInfo: {}, + restoreRequestedAt: new Date(Date.now() - 1000).toISOString(), + restoreCompletedAt: null, + restoreWillExpireAt: null, + }, + }, + }, + locationConfig, + { + data: { + account: { [testAccountCanonicalId]: 42 }, + bucket: { [`${testBucketName}_${testBucketCreationDate}`]: 42 }, + location: { + 'us-east-1': 42, + 'cold-location': 42, + }, + }, + }, + ], [ 'should add content-length to cold storage location and current dataStore ' + 'when object is restored', @@ -923,6 +956,38 @@ describe('S3UtilsMongoClient::_processEntryData', () => { }, }, ], + [ + 'should add content-length to cold storage location and current dataStore ' + + 'when object is restored with string typed timestamps', + testBucketName, + true, + { + _id: 'testkey6c', + value: { + ...objectMdTemp, + 'replicationInfo': { + backends: [], + }, + 'archive': { + archiveInfo: {}, + restoreCompletedAt: new Date(Date.now() - 1000).toISOString(), + restoreWillExpireAt: new Date(Date.now() + 1000).toISOString(), + }, + 'x-amz-storage-class': 'cold-location', + }, + }, + locationConfig, + { + data: { + account: { [testAccountCanonicalId]: 42 }, + bucket: { [`${testBucketName}_${testBucketCreationDate}`]: 42 }, + location: { + 'us-east-1': 42, + 'cold-location': 42, + }, + }, + }, + ], [ 'should return error if content-length is invalid', testBucketName, @@ -3174,31 +3239,55 @@ describe('S3UtilsMongoClient, cold object helpers', () => { assert.strictEqual(mongoTestClient._isObjectCold(coldObject), true); }); - it('should detect a restoring object', () => { - const restoringObject = { - value: { - ...coldObjectMdTemp, - archive: new ObjectMDArchive({}, new Date(Date.now() - 5000), 10), - dataStoreName: 'cold-location', - }, - }; - assert.strictEqual(mongoTestClient._isObjectRestoring(restoringObject), true); - }); + // Arsenal types the archive timestamps `Date | string` and stores them as + // given, so both forms reach these helpers. + describe.each([ + ['Date', date => date], + ['string', date => date.toISOString()], + ])('with %s timestamps', (label, stored) => { + it('should detect a restoring object', () => { + const restoringObject = { + value: { + ...coldObjectMdTemp, + archive: new ObjectMDArchive({}, stored(new Date(Date.now() - 5000)), 10), + dataStoreName: 'cold-location', + }, + }; + assert.strictEqual(mongoTestClient._isObjectRestoring(restoringObject), true); + }); - it('should detect a restored object', () => { - const restoredObject = { - value: { - ...coldObjectMdTemp, - archive: new ObjectMDArchive( - {}, - new Date(Date.now() - 5000), - 10, - new Date(Date.now() - 1000), - new Date(Date.now() + 10000), - ), - dataStoreName: 'us-east-1', - }, - }; - assert.strictEqual(mongoTestClient._isObjectRestored(restoredObject), true); + it('should detect a restored object', () => { + const restoredObject = { + value: { + ...coldObjectMdTemp, + archive: new ObjectMDArchive( + {}, + stored(new Date(Date.now() - 5000)), + 10, + stored(new Date(Date.now() - 1000)), + stored(new Date(Date.now() + 10000)), + ), + dataStoreName: 'us-east-1', + }, + }; + assert.strictEqual(mongoTestClient._isObjectRestored(restoredObject), true); + }); + + it('should detect an object with an expired restore as cold', () => { + const expiredObject = { + value: { + ...coldObjectMdTemp, + archive: new ObjectMDArchive( + {}, + stored(new Date(Date.now() - 90000)), + 10, + stored(new Date(Date.now() - 80000)), + stored(new Date(Date.now() - 1000)), + ), + dataStoreName: 'cold-location', + }, + }; + assert.strictEqual(mongoTestClient._isObjectCold(expiredObject), true); + }); }); }); diff --git a/utils/S3UtilsMongoClient.js b/utils/S3UtilsMongoClient.js index 3934a0b9..e2b19336 100644 --- a/utils/S3UtilsMongoClient.js +++ b/utils/S3UtilsMongoClient.js @@ -1094,7 +1094,8 @@ class S3UtilsMongoClient extends MongoClientInterface { */ _isObjectCold(entry) { return entry.value.archive - && (!entry.value.archive.restoreRequestedAt || entry.value.archive.restoreWillExpireAt <= Date.now()); + && (!entry.value.archive.restoreRequestedAt + || new Date(entry.value.archive.restoreWillExpireAt) <= Date.now()); } /** @@ -1104,8 +1105,9 @@ class S3UtilsMongoClient extends MongoClientInterface { */ _isObjectRestoring(entry) { return entry.value.archive - && entry.value.archive.restoreRequestedAt <= Date.now() - && (!entry.value.archive.restoreCompletedAt || entry.value.archive.restoreCompletedAt > Date.now()); + && new Date(entry.value.archive.restoreRequestedAt) <= Date.now() + && (!entry.value.archive.restoreCompletedAt + || new Date(entry.value.archive.restoreCompletedAt) > Date.now()); } /** @@ -1115,8 +1117,9 @@ class S3UtilsMongoClient extends MongoClientInterface { */ _isObjectRestored(entry) { return entry.value.archive - && entry.value.archive.restoreCompletedAt && (entry.value.archive.restoreCompletedAt <= Date.now()) - && entry.value.archive.restoreWillExpireAt > Date.now(); + && entry.value.archive.restoreCompletedAt + && (new Date(entry.value.archive.restoreCompletedAt) <= Date.now()) + && new Date(entry.value.archive.restoreWillExpireAt) > Date.now(); } }