diff --git a/app/app.js b/app/app.js index 1ef3b7ab..4d273cea 100644 --- a/app/app.js +++ b/app/app.js @@ -92,9 +92,9 @@ apiRouter.get('/', (_req, res) => { authMode: state.authMode, gitRev: state.gitRev, name: appName, - nodeVersion: appVersion, + nodeVersion: process.version, privacyMask: config.has('server.privacyMask'), - version: process.env.npm_package_version + version: appVersion }, endpoints: ['/api/v1'], versions: [1] diff --git a/app/tests/unit/components/utils.spec.js b/app/tests/unit/components/utils.spec.js index fb07cd49..fabc6577 100644 --- a/app/tests/unit/components/utils.spec.js +++ b/app/tests/unit/components/utils.spec.js @@ -8,7 +8,8 @@ const Problem = require('api-problem'); // Mock config library - @see {@link https://stackoverflow.com/a/64819698} jest.mock('config'); -const DEFAULTREGION = 'us-east-1'; // Need to specify valid AWS region or it'll explode ('us-east-1' is default, 'ca-central-1' for Canada) +// Need to specify valid AWS region or it'll explode ('us-east-1' is default, 'ca-central-1' for Canada) +const DEFAULTREGION = 'us-east-1'; beforeEach(() => { jest.resetAllMocks(); @@ -246,7 +247,9 @@ describe('getCurrentSubject', () => { expect(getCurrentTokenClaimSpy).toHaveBeenCalledWith(currentUser, 'sub', undefined); }); - it.each([undefined, null, '', [], {}])('should call getCurrentTokenClaim correctly given %j and defaultValue \'default\'', (currentUser) => { + it.each( + [undefined, null, '', [], {}] + )('should call getCurrentTokenClaim correctly given %j and defaultValue \'default\'', (currentUser) => { const defaultValue = 'default'; utils.getCurrentSubject(currentUser, defaultValue); @@ -472,15 +475,21 @@ describe('mixedQueryToArray', () => { }); it('should return an array with the appropriate set when there are multiples', () => { - expect(utils.mixedQueryToArray('there,are,duplicates,here,yes,here,there,is,here')).toEqual(['there', 'are', 'duplicates', 'here', 'yes', 'is']); + expect(utils.mixedQueryToArray('there,are,duplicates,here,yes,here,there,is,here')).toEqual( + ['there', 'are', 'duplicates', 'here', 'yes', 'is'] + ); }); it('should return an array with the appropriate set when there are multiples and spaces', () => { - expect(utils.mixedQueryToArray('there, are, duplicates, here ,yes ,here ,there,is,here ')).toEqual(['there', 'are', 'duplicates', 'here', 'yes', 'is']); + expect(utils.mixedQueryToArray('there, are, duplicates, here ,yes ,here ,there,is,here ')).toEqual( + ['there', 'are', 'duplicates', 'here', 'yes', 'is'] + ); }); it('should return an array with the appropriate set when there are multiples and spaces', () => { - expect(utils.mixedQueryToArray(['there', ' are', ' duplicates', ' here ', 'yes ', 'here ', 'there', 'is', 'here '])).toEqual(['there', 'are', 'duplicates', 'here', 'yes', 'is']); + expect(utils.mixedQueryToArray( + ['there', ' are', ' duplicates', ' here ', 'yes ', 'here ', 'there', 'is', 'here '] + )).toEqual(['there', 'are', 'duplicates', 'here', 'yes', 'is']); }); }); @@ -575,3 +584,35 @@ describe('toLowerKeys', () => { expect(utils.toLowerKeys(value)).toEqual(expected); }); }); + +describe('getUniqueObjects', () => { + const testObj1 = {key1: 'test1', val1: 'val11', val2: 'val21'}; + const testObj2 = {key1: 'test2', val1: 'val12', val2: 'val22'}; + const testObj3 = {key1: 'test3', val1: 'val13', val2: 'val23'}; + const testObj4 = {key1: 'test4', val1: 'val14', val2: 'val24'}; + const testObj5 = {key1: 'test4', val1: 'val15', val2: 'val25'}; + + it('return all input objects', () => { + expect(utils.getUniqueObjects([ + testObj1, testObj2, testObj3, testObj4 + ], 'key1')).toMatchObject([ + testObj1, testObj2, testObj3, testObj4 + ]); + }); + + it('filter for unique keys', () => { + expect(utils.getUniqueObjects([ + testObj2, testObj3, testObj4, testObj5 + ], 'key1')).toMatchObject([ + testObj2, testObj3, testObj5 + ]); + }); + + it('should return the last object entered with duplicate key', () => { + expect(utils.getUniqueObjects([ + testObj5, testObj4, + ], 'key1')).toMatchObject([ + testObj4 + ]); + }); +}); diff --git a/app/tests/unit/controllers/object.spec.js b/app/tests/unit/controllers/object.spec.js index 49f06b06..2385bf4d 100644 --- a/app/tests/unit/controllers/object.spec.js +++ b/app/tests/unit/controllers/object.spec.js @@ -4,7 +4,14 @@ const { MAXCOPYOBJECTLENGTH, MetadataDirective, TaggingDirective } = require('.. const utils = require('../../../src/db/models/utils'); const controller = require('../../../src/controllers/object'); -const { storageService, objectService, metadataService, tagService, versionService, userService } = require('../../../src/services'); +const { + storageService, + objectService, + metadataService, + tagService, + versionService, + userService +} = require('../../../src/services'); const mockResponse = () => { const res = {}; diff --git a/app/tests/unit/controllers/objectPermission.spec.js b/app/tests/unit/controllers/objectPermission.spec.js index 40001770..2ec54966 100644 --- a/app/tests/unit/controllers/objectPermission.spec.js +++ b/app/tests/unit/controllers/objectPermission.spec.js @@ -34,7 +34,12 @@ describe('searchPermissions', () => { const res = mockResponse(); await controller.searchPermissions(req, res, next); expect(searchPermissionsSpy).toHaveBeenCalledTimes(1); - expect(searchPermissionsSpy).toHaveBeenCalledWith({ bucketId: [req.query.bucketId], objId: [req.query.objectId], userId: [req.query.userId], permCode: [req.query.permCode] }); + expect(searchPermissionsSpy).toHaveBeenCalledWith({ + bucketId: [req.query.bucketId], + objId: [req.query.objectId], + userId: [req.query.userId], + permCode: [req.query.permCode] + }); expect(res.status).toHaveBeenCalledWith(200); expect(res.json).toHaveBeenCalledWith([]); expect(next).toHaveBeenCalledTimes(0); @@ -70,7 +75,11 @@ describe('listPermissions', () => { const res = mockResponse(); await controller.listPermissions(req, res, next); expect(searchPermissionsSpy).toHaveBeenCalledTimes(1); - expect(searchPermissionsSpy).toHaveBeenCalledWith({ objId: req.params.objectId, userId: [req.query.userId], permCode: [req.query.permCode] }); + expect(searchPermissionsSpy).toHaveBeenCalledWith({ + objId: req.params.objectId, + userId: [req.query.userId], + permCode: [req.query.permCode] + }); expect(res.status).toHaveBeenCalledWith(200); expect(res.json).toHaveBeenCalledWith({ res: 123 }); expect(next).toHaveBeenCalledTimes(0); diff --git a/app/tests/unit/middleware/authorization.spec.js b/app/tests/unit/middleware/authorization.spec.js index 5ce5aafc..3a8f2b3e 100644 --- a/app/tests/unit/middleware/authorization.spec.js +++ b/app/tests/unit/middleware/authorization.spec.js @@ -1,7 +1,12 @@ const { NIL: SYSTEM_USER } = require('uuid'); const mw = require('../../../src/middleware/authorization'); -const { bucketPermissionService, objectService, objectPermissionService, userService } = require('../../../src/services'); +const { + bucketPermissionService, + objectService, + objectPermissionService, + userService +} = require('../../../src/services'); const { AuthMode, AuthType, Permissions } = require('../../../src/components/constants'); const utils = require('../../../src/components/utils'); diff --git a/app/tests/unit/services/storage.spec.js b/app/tests/unit/services/storage.spec.js index 9c8ed137..e6bf8f4f 100644 --- a/app/tests/unit/services/storage.spec.js +++ b/app/tests/unit/services/storage.spec.js @@ -25,7 +25,9 @@ const service = require('../../../src/services/storage'); const utils = require('../../../src/components/utils'); const { MetadataDirective, TaggingDirective } = require('../../../src/components/constants'); -const DEFAULTREGION = 'us-east-1'; // Need to specify valid AWS region or it'll explode ('us-east-1' is default, 'ca-central-1' for Canada) +// Need to specify valid AWS region or it'll explode ('us-east-1' is default, 'ca-central-1' for Canada) +const DEFAULTREGION = 'us-east-1'; + const bucket = 'bucket'; const key = 'filePath'; const defaultTempExpiresIn = parseInt(config.get('server.defaultTempExpiresIn'), 10); @@ -456,8 +458,14 @@ describe('listAllObjects', () => { it('should call listObjectsV2 multiple times and return an array of precise path objects', async () => { const continueToken = 'token'; - listObjectsV2Mock.mockResolvedValueOnce({ Contents: [{ Key: 'filePath/foo' }], IsTruncated: true, NextContinuationToken: continueToken }); - listObjectsV2Mock.mockResolvedValueOnce({ Contents: [{ Key: 'filePath/bar' }], IsTruncated: false }); + listObjectsV2Mock.mockResolvedValueOnce({ + Contents: [{ Key: 'filePath/foo' }], + IsTruncated: true, + NextContinuationToken: continueToken }); + listObjectsV2Mock.mockResolvedValueOnce({ + Contents: [{ Key: 'filePath/bar' }], + IsTruncated: false + }); const result = await service.listAllObjects(); @@ -482,8 +490,14 @@ describe('listAllObjects', () => { it('should call listObjectsV2 multiple times and return an array of all path objects', async () => { const continueToken = 'token'; - listObjectsV2Mock.mockResolvedValueOnce({ Contents: [{ Key: 'filePath/test/foo' }], IsTruncated: true, NextContinuationToken: continueToken }); - listObjectsV2Mock.mockResolvedValueOnce({ Contents: [{ Key: 'filePath/test/bar' }], IsTruncated: false }); + listObjectsV2Mock.mockResolvedValueOnce({ + Contents: [{ Key: 'filePath/test/foo' }], + IsTruncated: true, + NextContinuationToken: continueToken }); + listObjectsV2Mock.mockResolvedValueOnce({ + Contents: [{ Key: 'filePath/test/bar' }], + IsTruncated: false + }); const result = await service.listAllObjects({ precisePath: false }); @@ -506,34 +520,43 @@ describe('listAllObjects', () => { })); }); - it('should call listObjectsV2 multiple times with the right bucketId and filePath, returning an array of objects', async () => { - const continueToken = 'token'; - const customPath = 'filePath/test'; - listObjectsV2Mock.mockResolvedValueOnce({ Contents: [{ Key: 'filePath/test/foo' }], IsTruncated: true, NextContinuationToken: continueToken }); - listObjectsV2Mock.mockResolvedValueOnce({ Contents: [{ Key: 'filePath/test/bar' }], IsTruncated: false }); - - const result = await service.listAllObjects({ filePath: customPath, bucketId: bucket }); - - expect(result).toBeTruthy(); - expect(Array.isArray(result)).toBeTruthy(); - expect(result).toHaveLength(2); - expect(result).toEqual(expect.arrayContaining([ - { Key: 'filePath/test/foo' }, - { Key: 'filePath/test/bar' } - ])); - expect(utils.getBucket).toHaveBeenCalledTimes(0); - expect(utils.isAtPath).toHaveBeenCalledTimes(2); - expect(listObjectsV2Mock).toHaveBeenCalledTimes(2); - expect(listObjectsV2Mock).toHaveBeenNthCalledWith(1, expect.objectContaining({ - filePath: customPath, - bucketId: bucket - })); - expect(listObjectsV2Mock).toHaveBeenNthCalledWith(2, expect.objectContaining({ - filePath: customPath, - continuationToken: continueToken, - bucketId: bucket - })); - }); + it( + 'should call listObjectsV2 multiple times with the right bucketId and filePath, returning an array of objects', + async () => { + const continueToken = 'token'; + const customPath = 'filePath/test'; + listObjectsV2Mock.mockResolvedValueOnce({ + Contents: [{ Key: 'filePath/test/foo' }], + IsTruncated: true, + NextContinuationToken: continueToken }); + listObjectsV2Mock.mockResolvedValueOnce({ + Contents: [{ Key: 'filePath/test/bar' }], + IsTruncated: false + }); + + const result = await service.listAllObjects({ filePath: customPath, bucketId: bucket }); + + expect(result).toBeTruthy(); + expect(Array.isArray(result)).toBeTruthy(); + expect(result).toHaveLength(2); + expect(result).toEqual(expect.arrayContaining([ + { Key: 'filePath/test/foo' }, + { Key: 'filePath/test/bar' } + ])); + expect(utils.getBucket).toHaveBeenCalledTimes(0); + expect(utils.isAtPath).toHaveBeenCalledTimes(2); + expect(listObjectsV2Mock).toHaveBeenCalledTimes(2); + expect(listObjectsV2Mock).toHaveBeenNthCalledWith(1, expect.objectContaining({ + filePath: customPath, + bucketId: bucket + })); + expect(listObjectsV2Mock).toHaveBeenNthCalledWith(2, expect.objectContaining({ + filePath: customPath, + continuationToken: continueToken, + bucketId: bucket + })); + } + ); }); describe('listAllObjectVersions', () => { @@ -589,8 +612,14 @@ describe('listAllObjectVersions', () => { it('should call listObjectVersion multiple times and return precise path objects', async () => { const nextKeyMarker = 'token'; - listObjectVersionMock.mockResolvedValueOnce({ DeleteMarkers: [{ Key: 'filePath/foo' }], IsTruncated: true, NextKeyMarker: nextKeyMarker }); - listObjectVersionMock.mockResolvedValueOnce({ Versions: [{ Key: 'filePath/bar' }], IsTruncated: false }); + listObjectVersionMock.mockResolvedValueOnce({ + DeleteMarkers: [{ Key: 'filePath/foo' }], + IsTruncated: true, + NextKeyMarker: nextKeyMarker }); + listObjectVersionMock.mockResolvedValueOnce({ + Versions: [{ Key: 'filePath/bar' }], + IsTruncated: false + }); const result = await service.listAllObjectVersions({ filePath: 'filePath' }); @@ -619,8 +648,14 @@ describe('listAllObjectVersions', () => { it('should call listObjectVersion multiple times and return all path objects', async () => { const nextKeyMarker = 'token'; - listObjectVersionMock.mockResolvedValueOnce({ DeleteMarkers: [{ Key: 'filePath/test/foo' }], IsTruncated: true, NextKeyMarker: nextKeyMarker }); - listObjectVersionMock.mockResolvedValueOnce({ Versions: [{ Key: 'filePath/test/bar' }], IsTruncated: false }); + listObjectVersionMock.mockResolvedValueOnce({ + DeleteMarkers: [{ Key: 'filePath/test/foo' }], + IsTruncated: true, + NextKeyMarker: nextKeyMarker }); + listObjectVersionMock.mockResolvedValueOnce({ + Versions: [{ Key: 'filePath/test/bar' }], + IsTruncated: false + }); const result = await service.listAllObjectVersions({ filePath: 'filePath', precisePath: false }); @@ -649,10 +684,20 @@ describe('listAllObjectVersions', () => { it('should call listObjectVersion multiple times and return all latest path objects', async () => { const nextKeyMarker = 'token'; - listObjectVersionMock.mockResolvedValueOnce({ DeleteMarkers: [{ Key: 'filePath/test/foo', IsLatest: true }], IsTruncated: true, NextKeyMarker: nextKeyMarker }); - listObjectVersionMock.mockResolvedValueOnce({ Versions: [{ Key: 'filePath/test/bar', IsLatest: false }], IsTruncated: false }); + listObjectVersionMock.mockResolvedValueOnce({ + DeleteMarkers: [{ Key: 'filePath/test/foo', IsLatest: true }], + IsTruncated: true, + NextKeyMarker: nextKeyMarker }); + listObjectVersionMock.mockResolvedValueOnce({ + Versions: [{ Key: 'filePath/test/bar', IsLatest: false }], + IsTruncated: false + }); - const result = await service.listAllObjectVersions({ filePath: 'filePath', precisePath: false, filterLatest: true }); + const result = await service.listAllObjectVersions({ + filePath: 'filePath', + precisePath: false, + filterLatest: true + }); expect(result).toBeTruthy(); expect(Array.isArray(result.DeleteMarkers)).toBeTruthy(); @@ -941,21 +986,24 @@ describe('readSignedUrl', () => { presignUrlMock.mockRestore(); }); - it('should call presignUrl with a get object command for the latest object and default expiration and bucketId', async () => { - const filePath = 'filePath'; - const bucketId = 'abc'; - const result = await service.readSignedUrl({ filePath, bucketId: bucketId }); - - expect(result).toBeTruthy(); - expect(utils.getBucket).toHaveBeenCalledTimes(1); - expect(presignUrlMock).toHaveBeenCalledTimes(1); - expect(presignUrlMock).toHaveBeenCalledWith(expect.objectContaining({ - input: { - Bucket: bucket, - Key: filePath - } - }), defaultTempExpiresIn, bucketId); - }); + it( + 'should call presignUrl with a get object command for the latest object and default expiration and bucketId', + async () => { + const filePath = 'filePath'; + const bucketId = 'abc'; + const result = await service.readSignedUrl({ filePath, bucketId: bucketId }); + + expect(result).toBeTruthy(); + expect(utils.getBucket).toHaveBeenCalledTimes(1); + expect(presignUrlMock).toHaveBeenCalledTimes(1); + expect(presignUrlMock).toHaveBeenCalledWith(expect.objectContaining({ + input: { + Bucket: bucket, + Key: filePath + } + }), defaultTempExpiresIn, bucketId); + } + ); it('should call presignUrl with a get object command for a specific version and default expiration', async () => { const filePath = 'filePath'; diff --git a/app/tests/unit/services/sync.spec.js b/app/tests/unit/services/sync.spec.js index 736f9851..a5088f98 100644 --- a/app/tests/unit/services/sync.spec.js +++ b/app/tests/unit/services/sync.spec.js @@ -263,7 +263,9 @@ describe('syncJob', () => { expect(syncVersionsSpy).toHaveBeenCalledTimes(1); expect(syncVersionsSpy).toHaveBeenCalledWith(expect.any(Object), expect.any(String), expect.any(Object)); expect(syncTagsSpy).toHaveBeenCalledTimes(1); - expect(syncTagsSpy).toHaveBeenCalledWith(expect.any(Object), path, bucketId, expect.any(String), expect.any(Object)); + expect(syncTagsSpy).toHaveBeenCalledWith( + expect.any(Object), path, bucketId, expect.any(String), expect.any(Object) + ); expect(syncMetadataSpy).toHaveBeenCalledTimes(0); }); @@ -281,9 +283,13 @@ describe('syncJob', () => { expect(syncVersionsSpy).toHaveBeenCalledTimes(1); expect(syncVersionsSpy).toHaveBeenCalledWith(expect.any(Object), expect.any(String), expect.any(Object)); expect(syncTagsSpy).toHaveBeenCalledTimes(1); - expect(syncTagsSpy).toHaveBeenCalledWith(expect.any(Object), path, bucketId, expect.any(String), expect.any(Object)); + expect(syncTagsSpy).toHaveBeenCalledWith( + expect.any(Object), path, bucketId, expect.any(String), expect.any(Object) + ); expect(syncMetadataSpy).toHaveBeenCalledTimes(1); - expect(syncMetadataSpy).toHaveBeenCalledWith(expect.any(Object), path, bucketId, expect.any(String), expect.any(Object)); + expect(syncMetadataSpy).toHaveBeenCalledWith( + expect.any(Object), path, bucketId, expect.any(String), expect.any(Object) + ); }); it('Calls everything when full mode', async () => { @@ -300,9 +306,13 @@ describe('syncJob', () => { expect(syncVersionsSpy).toHaveBeenCalledTimes(1); expect(syncVersionsSpy).toHaveBeenCalledWith(expect.any(Object), expect.any(String), expect.any(Object)); expect(syncTagsSpy).toHaveBeenCalledTimes(1); - expect(syncTagsSpy).toHaveBeenCalledWith(expect.any(Object), path, bucketId, expect.any(String), expect.any(Object)); + expect(syncTagsSpy).toHaveBeenCalledWith( + expect.any(Object), path, bucketId, expect.any(String), expect.any(Object) + ); expect(syncMetadataSpy).toHaveBeenCalledTimes(1); - expect(syncMetadataSpy).toHaveBeenCalledWith(expect.any(Object), path, bucketId, expect.any(String), expect.any(Object)); + expect(syncMetadataSpy).toHaveBeenCalledWith( + expect.any(Object), path, bucketId, expect.any(String), expect.any(Object) + ); }); }); @@ -730,6 +740,16 @@ describe('syncVersions', () => { expect(Version.startTransaction).toHaveBeenCalledTimes(1); expect(createSpy).toHaveBeenCalledTimes(2); expect(Version.delete).toHaveBeenCalledTimes(1); + expect(Version.where).toHaveBeenCalledTimes(1); + expect(Version.where).toHaveBeenCalledWith('objectId', comsObject.id); + expect(Version.whereNotNull).toHaveBeenCalledTimes(1); + expect(Version.whereNotNull).toHaveBeenCalledWith('s3VersionId'); + expect(Version.whereIn).toHaveBeenCalledTimes(1); + expect(Version.whereIn).toHaveBeenCalledWith('id', [ + { s3VersionId: validUuidv4 }, + { s3VersionId: validUuidv4 }, + { s3VersionId: validUuidv4 } + ].map(cv => cv.id)); expect(headObjectSpy).toHaveBeenCalledTimes(1); expect(headObjectSpy).toHaveBeenCalledWith(expect.objectContaining({ @@ -885,7 +905,9 @@ describe('syncTags', () => { expect(Version.startTransaction).toHaveBeenCalledTimes(1); expect(associateTagsSpy).toHaveBeenCalledTimes(1); - expect(associateTagsSpy).toHaveBeenCalledWith(comsVersion.id, expect.any(Array), expect.any(String), expect.any(Object)); + expect(associateTagsSpy).toHaveBeenCalledWith( + comsVersion.id, expect.any(Array), expect.any(String), expect.any(Object) + ); expect(dissociateTagsSpy).toHaveBeenCalledTimes(0); expect(fetchTagsForVersionSpy).toHaveBeenCalledTimes(1); expect(fetchTagsForVersionSpy).toHaveBeenCalledWith(expect.objectContaining({ @@ -927,7 +949,9 @@ describe('syncTags', () => { expect(Version.startTransaction).toHaveBeenCalledTimes(1); expect(associateTagsSpy).toHaveBeenCalledTimes(1); - expect(associateTagsSpy).toHaveBeenCalledWith(comsVersion.id, expect.any(Array), expect.any(String), expect.any(Object)); + expect(associateTagsSpy).toHaveBeenCalledWith( + comsVersion.id, expect.any(Array), expect.any(String), expect.any(Object) + ); expect(dissociateTagsSpy).toHaveBeenCalledTimes(0); expect(fetchTagsForVersionSpy).toHaveBeenCalledTimes(1); expect(fetchTagsForVersionSpy).toHaveBeenCalledWith(expect.objectContaining({ @@ -1036,7 +1060,9 @@ describe('syncTags', () => { expect(Version.startTransaction).toHaveBeenCalledTimes(1); expect(associateTagsSpy).toHaveBeenCalledTimes(1); - expect(associateTagsSpy).toHaveBeenCalledWith(comsVersion.id, expect.any(Array), expect.any(String), expect.any(Object)); + expect(associateTagsSpy).toHaveBeenCalledWith( + comsVersion.id, expect.any(Array), expect.any(String), expect.any(Object) + ); expect(dissociateTagsSpy).toHaveBeenCalledTimes(0); expect(fetchTagsForVersionSpy).toHaveBeenCalledTimes(1); expect(fetchTagsForVersionSpy).toHaveBeenCalledWith(expect.objectContaining({ @@ -1074,7 +1100,9 @@ describe('syncTags', () => { expect(Version.startTransaction).toHaveBeenCalledTimes(1); expect(associateTagsSpy).toHaveBeenCalledTimes(1); - expect(associateTagsSpy).toHaveBeenCalledWith(comsVersion.id, expect.any(Array), expect.any(String), expect.any(Object)); + expect(associateTagsSpy).toHaveBeenCalledWith( + comsVersion.id, expect.any(Array), expect.any(String), expect.any(Object) + ); expect(dissociateTagsSpy).toHaveBeenCalledTimes(0); expect(fetchTagsForVersionSpy).toHaveBeenCalledTimes(1); expect(fetchTagsForVersionSpy).toHaveBeenCalledWith(expect.objectContaining({ diff --git a/app/tests/unit/services/user.spec.js b/app/tests/unit/services/user.spec.js index d7df451d..7cfd80db 100644 --- a/app/tests/unit/services/user.spec.js +++ b/app/tests/unit/services/user.spec.js @@ -255,7 +255,9 @@ describe('login', () => { expect(createUserSpy).toHaveBeenCalledTimes(0); expect(updateUserSpy).toHaveBeenCalledTimes(1); - expect(updateUserSpy).toHaveBeenCalledWith('a96f2809-d6f4-4cef-a02a-3f72edff06d7', expect.objectContaining(user), expect.any(Object)); + expect(updateUserSpy).toHaveBeenCalledWith( + 'a96f2809-d6f4-4cef-a02a-3f72edff06d7', expect.objectContaining(user), expect.any(Object) + ); }); }); diff --git a/app/tests/unit/services/version.spec.js b/app/tests/unit/services/version.spec.js index 5aef7069..5d2131db 100644 --- a/app/tests/unit/services/version.spec.js +++ b/app/tests/unit/services/version.spec.js @@ -18,15 +18,34 @@ jest.mock('../../../src/db/models/tables/version', () => ({ query: jest.fn(), returning: jest.fn(), throwIfNotFound: jest.fn(), - where: jest.fn() + update: jest.fn(), + updateAndFetchById: jest.fn(), + where: jest.fn(), + whereNot: jest.fn() })); +const validUuidv4 = '3f4da093-6399-4711-8765-36ec5f8017c2'; + const service = require('../../../src/services/version'); -// const { version } = require('winston'); +const objectService = require('../../../src/services/object'); +const storageService = require('../../../src/services/storage'); + +const listAllObjectVersionsSpy = jest.spyOn(storageService, 'listAllObjectVersions'); +const objectSpy = jest.spyOn(objectService, 'read'); + beforeEach(() => { jest.clearAllMocks(); resetModel(Version, versionTrx); + + listAllObjectVersionsSpy.mockReset(); + objectSpy.mockReset(); +}); + +afterAll(() => { + jest.clearAllMocks(); + listAllObjectVersionsSpy.mockRestore(); + objectSpy.mockRestore(); }); describe('copy', () => { @@ -80,7 +99,12 @@ describe('copy', () => { describe('create', () => { it('Saves a version of an object', async () => { - await service.create({ s3VersionId: S3_VERSION_ID, mimeType: 'mimeType', id: OBJECT_ID, deleteMarker: 'deleteMarker' }, SYSTEM_USER); + await service.create({ + s3VersionId: S3_VERSION_ID, + mimeType: 'mimeType', + id: OBJECT_ID, + deleteMarker: 'deleteMarker' + }, SYSTEM_USER); expect(Version.startTransaction).toHaveBeenCalledTimes(1); expect(Version.query).toHaveBeenCalledTimes(1); @@ -208,3 +232,121 @@ describe('update', () => { expect(versionTrx.commit).toHaveBeenCalledTimes(1); }); }); + +describe('updateIsLatest', () => { + it('Updates a version of an object if it is the latest', async () => { + const versionSpy = jest.spyOn(service, 'removeDuplicateLatest'); + versionSpy.mockResolvedValueOnce(true); + listAllObjectVersionsSpy.mockResolvedValue({ + DeleteMarkers: [{}], + Versions: [{ IsLatest: true, VersionId: validUuidv4 }] + }); + objectSpy.mockResolvedValue({ + path: '/test', + bucketId: '0000-0000' + }); + + await service.updateIsLatest(OBJECT_ID); + + expect(Version.startTransaction).toHaveBeenCalledTimes(1); + expect(Version.query).toHaveBeenCalledTimes(2); + expect(Version.where).toHaveBeenCalledTimes(1); + expect(Version.where).toHaveBeenCalledWith( + { + objectId: OBJECT_ID, + s3VersionId: validUuidv4 + } + ); + expect(Version.updateAndFetchById).toHaveBeenCalledTimes(1); + expect(Version.first).toHaveBeenCalledTimes(1); + expect(versionSpy).toHaveBeenCalledTimes(1); + expect(versionTrx.commit).toHaveBeenCalledTimes(1); + }); + + it('Does not update if file is not the latest', async () => { + const versionSpy = jest.spyOn(service, 'removeDuplicateLatest'); + versionSpy.mockResolvedValueOnce(true); + listAllObjectVersionsSpy.mockResolvedValue({ + DeleteMarkers: [{}], + Versions: [ + { IsLatest: true, VersionId: validUuidv4 }, + ] + }); + objectSpy.mockResolvedValue({ + path: '/test', + bucketId: '0000-0000' + }); + Version.throwIfNotFound.mockResolvedValue({ isLatest: true }); + + await service.updateIsLatest(OBJECT_ID); + + expect(Version.startTransaction).toHaveBeenCalledTimes(1); + expect(Version.query).toHaveBeenCalledTimes(1); + expect(Version.first).toHaveBeenCalledTimes(1); + expect(Version.where).toHaveBeenCalledTimes(1); + expect(Version.where).toHaveBeenCalledWith( + { + objectId: OBJECT_ID, + s3VersionId: validUuidv4 + } + ); + expect(Version.updateAndFetchById).toHaveBeenCalledTimes(0); + + expect(versionSpy).toHaveBeenCalledTimes(1); + expect(versionTrx.commit).toHaveBeenCalledTimes(1); + }); +}); + +describe('removeDuplicateLatest', () => { + it('sets all other versions to isLatest=false', async () => { + listAllObjectVersionsSpy.mockResolvedValue({ + DeleteMarkers: [{}], + Versions: [{ IsLatest: true, VersionId: validUuidv4 }] + }); + objectSpy.mockResolvedValue({ + path: '/test', + bucketId: '0000-0000' + }); + Version.where.mockResolvedValueOnce([{ isLatest: true }, { isLatest: true }]); + + await service.removeDuplicateLatest(validUuidv4, OBJECT_ID); + + expect(Version.startTransaction).toHaveBeenCalledTimes(1); + expect(Version.query).toHaveBeenCalledTimes(2); + expect(Version.update).toHaveBeenCalledTimes(1); + + expect(Version.where).toHaveBeenCalledTimes(1); + expect(Version.where).toHaveBeenCalledWith('objectId', OBJECT_ID); + + expect(Version.whereNot).toHaveBeenCalledTimes(1); + expect(Version.whereNot).toHaveBeenCalledWith({ 'id': validUuidv4 }); + + expect(Version.andWhere).toHaveBeenCalledTimes(2); + expect(Version.andWhere).toHaveBeenCalledWith('objectId', OBJECT_ID); + expect(Version.andWhere).toHaveBeenCalledWith({ 'isLatest': true }); + }); + + it('does not set other versions to false', async () => { + listAllObjectVersionsSpy.mockResolvedValue({ + DeleteMarkers: [{}], + Versions: [{ IsLatest: false, VersionId: validUuidv4 }] + }); + objectSpy.mockResolvedValue({ + path: '/test', + bucketId: '0000-0000' + }); + Version.where.mockResolvedValueOnce([{ isLatest: false }, { isLatest: false }]); + + await service.removeDuplicateLatest(validUuidv4, OBJECT_ID); + + expect(Version.startTransaction).toHaveBeenCalledTimes(1); + expect(Version.query).toHaveBeenCalledTimes(1); + + expect(Version.where).toHaveBeenCalledTimes(1); + expect(Version.where).toHaveBeenCalledWith('objectId', OBJECT_ID); + + expect(Version.update).toHaveBeenCalledTimes(0); + expect(Version.whereNot).toHaveBeenCalledTimes(0); + expect(Version.andWhere).toHaveBeenCalledTimes(0); + }); +});