diff --git a/server/src/domain/album/album.service.spec.ts b/server/src/domain/album/album.service.spec.ts index 7d2304d443..d4eeca7fc4 100644 --- a/server/src/domain/album/album.service.spec.ts +++ b/server/src/domain/album/album.service.spec.ts @@ -8,7 +8,7 @@ import { newAssetRepositoryMock, newJobRepositoryMock, newUserRepositoryMock, - userEntityStub, + userStub, } from '@test'; import _ from 'lodash'; import { IAssetRepository } from '../asset'; @@ -326,12 +326,12 @@ describe(AlbumService.name, () => { accessMock.album.hasOwnerAccess.mockResolvedValue(true); albumMock.getByIds.mockResolvedValue([_.cloneDeep(albumStub.sharedWithAdmin)]); albumMock.update.mockResolvedValue(albumStub.sharedWithAdmin); - userMock.get.mockResolvedValue(userEntityStub.user2); + userMock.get.mockResolvedValue(userStub.user2); await sut.addUsers(authStub.user1, albumStub.sharedWithAdmin.id, { sharedUserIds: [authStub.user2.id] }); expect(albumMock.update).toHaveBeenCalledWith({ id: albumStub.sharedWithAdmin.id, updatedAt: expect.any(Date), - sharedUsers: [userEntityStub.admin, { id: authStub.user2.id }], + sharedUsers: [userStub.admin, { id: authStub.user2.id }], }); }); }); @@ -349,7 +349,7 @@ describe(AlbumService.name, () => { albumMock.getByIds.mockResolvedValue([albumStub.sharedWithUser]); await expect( - sut.removeUser(authStub.admin, albumStub.sharedWithUser.id, userEntityStub.user1.id), + sut.removeUser(authStub.admin, albumStub.sharedWithUser.id, userStub.user1.id), ).resolves.toBeUndefined(); expect(albumMock.update).toHaveBeenCalledTimes(1); diff --git a/server/src/domain/asset/asset.service.spec.ts b/server/src/domain/asset/asset.service.spec.ts index 4868919c97..4c91ce3425 100644 --- a/server/src/domain/asset/asset.service.spec.ts +++ b/server/src/domain/asset/asset.service.spec.ts @@ -1,7 +1,7 @@ import { AssetType } from '@app/infra/entities'; import { BadRequestException, UnauthorizedException } from '@nestjs/common'; import { - assetEntityStub, + assetStub, authStub, IAccessRepositoryMock, newAccessRepositoryMock, @@ -246,7 +246,7 @@ describe(AssetService.name, () => { describe('getMapMarkers', () => { it('should get geo information of assets', async () => { assetMock.getMapMarkers.mockResolvedValue( - [assetEntityStub.withLocation].map((asset) => ({ + [assetStub.withLocation].map((asset) => ({ id: asset.id, /* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */ @@ -261,7 +261,7 @@ describe(AssetService.name, () => { expect(markers).toHaveLength(1); expect(markers[0]).toEqual({ - id: assetEntityStub.withLocation.id, + id: assetStub.withLocation.id, lat: 100, lon: 100, }); @@ -308,14 +308,14 @@ describe(AssetService.name, () => { it('should set the title correctly', async () => { when(assetMock.getByDate) .calledWith(authStub.admin.id, new Date('2022-06-15T00:00:00.000Z')) - .mockResolvedValue([assetEntityStub.image]); + .mockResolvedValue([assetStub.image]); when(assetMock.getByDate) .calledWith(authStub.admin.id, new Date('2021-06-15T00:00:00.000Z')) - .mockResolvedValue([assetEntityStub.video]); + .mockResolvedValue([assetStub.video]); await expect(sut.getMemoryLane(authStub.admin, { timestamp: new Date(2023, 5, 15), years: 2 })).resolves.toEqual([ - { title: '1 year since...', assets: [mapAsset(assetEntityStub.image)] }, - { title: '2 years since...', assets: [mapAsset(assetEntityStub.video)] }, + { title: '1 year since...', assets: [mapAsset(assetStub.image)] }, + { title: '2 years since...', assets: [mapAsset(assetStub.video)] }, ]); expect(assetMock.getByDate).toHaveBeenCalledTimes(2); @@ -352,12 +352,12 @@ describe(AssetService.name, () => { const stream = new Readable(); accessMock.asset.hasOwnerAccess.mockResolvedValue(true); - assetMock.getByIds.mockResolvedValue([assetEntityStub.image]); + assetMock.getByIds.mockResolvedValue([assetStub.image]); storageMock.createReadStream.mockResolvedValue({ stream }); await expect(sut.downloadFile(authStub.admin, 'asset-1')).resolves.toEqual({ stream }); - expect(storageMock.createReadStream).toHaveBeenCalledWith(assetEntityStub.image.originalPath, 'image/jpeg'); + expect(storageMock.createReadStream).toHaveBeenCalledWith(assetStub.image.originalPath, 'image/jpeg'); }); it('should download an archive', async () => { @@ -368,7 +368,7 @@ describe(AssetService.name, () => { }; accessMock.asset.hasOwnerAccess.mockResolvedValue(true); - assetMock.getByIds.mockResolvedValue([assetEntityStub.noResizePath, assetEntityStub.noWebpPath]); + assetMock.getByIds.mockResolvedValue([assetStub.noResizePath, assetStub.noWebpPath]); storageMock.createZipStream.mockReturnValue(archiveMock); await expect(sut.downloadArchive(authStub.admin, { assetIds: ['asset-1', 'asset-2'] })).resolves.toEqual({ @@ -388,7 +388,7 @@ describe(AssetService.name, () => { }; accessMock.asset.hasOwnerAccess.mockResolvedValue(true); - assetMock.getByIds.mockResolvedValue([assetEntityStub.noResizePath, assetEntityStub.noResizePath]); + assetMock.getByIds.mockResolvedValue([assetStub.noResizePath, assetStub.noResizePath]); storageMock.createZipStream.mockReturnValue(archiveMock); await expect(sut.downloadArchive(authStub.admin, { assetIds: ['asset-1', 'asset-2'] })).resolves.toEqual({ @@ -408,7 +408,7 @@ describe(AssetService.name, () => { it('should return a list of archives (assetIds)', async () => { accessMock.asset.hasOwnerAccess.mockResolvedValue(true); - assetMock.getByIds.mockResolvedValue([assetEntityStub.image, assetEntityStub.video]); + assetMock.getByIds.mockResolvedValue([assetStub.image, assetStub.video]); const assetIds = ['asset-1', 'asset-2']; await expect(sut.getDownloadInfo(authStub.admin, { assetIds })).resolves.toEqual(downloadResponse); @@ -419,7 +419,7 @@ describe(AssetService.name, () => { it('should return a list of archives (albumId)', async () => { accessMock.album.hasOwnerAccess.mockResolvedValue(true); assetMock.getByAlbumId.mockResolvedValue({ - items: [assetEntityStub.image, assetEntityStub.video], + items: [assetStub.image, assetStub.video], hasNextPage: false, }); @@ -431,7 +431,7 @@ describe(AssetService.name, () => { it('should return a list of archives (userId)', async () => { assetMock.getByUserId.mockResolvedValue({ - items: [assetEntityStub.image, assetEntityStub.video], + items: [assetStub.image, assetStub.video], hasNextPage: false, }); @@ -445,10 +445,10 @@ describe(AssetService.name, () => { it('should split archives by size', async () => { assetMock.getByUserId.mockResolvedValue({ items: [ - { ...assetEntityStub.image, id: 'asset-1' }, - { ...assetEntityStub.video, id: 'asset-2' }, - { ...assetEntityStub.withLocation, id: 'asset-3' }, - { ...assetEntityStub.noWebpPath, id: 'asset-4' }, + { ...assetStub.image, id: 'asset-1' }, + { ...assetStub.video, id: 'asset-2' }, + { ...assetStub.withLocation, id: 'asset-3' }, + { ...assetStub.noWebpPath, id: 'asset-4' }, ], hasNextPage: false, }); @@ -470,18 +470,18 @@ describe(AssetService.name, () => { it('should include the video portion of a live photo', async () => { accessMock.asset.hasOwnerAccess.mockResolvedValue(true); when(assetMock.getByIds) - .calledWith([assetEntityStub.livePhotoStillAsset.id]) - .mockResolvedValue([assetEntityStub.livePhotoStillAsset]); + .calledWith([assetStub.livePhotoStillAsset.id]) + .mockResolvedValue([assetStub.livePhotoStillAsset]); when(assetMock.getByIds) - .calledWith([assetEntityStub.livePhotoMotionAsset.id]) - .mockResolvedValue([assetEntityStub.livePhotoMotionAsset]); + .calledWith([assetStub.livePhotoMotionAsset.id]) + .mockResolvedValue([assetStub.livePhotoMotionAsset]); - const assetIds = [assetEntityStub.livePhotoStillAsset.id]; + const assetIds = [assetStub.livePhotoStillAsset.id]; await expect(sut.getDownloadInfo(authStub.admin, { assetIds })).resolves.toEqual({ totalSize: 125_000, archives: [ { - assetIds: [assetEntityStub.livePhotoStillAsset.id, assetEntityStub.livePhotoMotionAsset.id], + assetIds: [assetStub.livePhotoStillAsset.id, assetStub.livePhotoMotionAsset.id], size: 125_000, }, ], diff --git a/server/src/domain/auth/auth.service.spec.ts b/server/src/domain/auth/auth.service.spec.ts index c191807f1d..6aba20a9ec 100644 --- a/server/src/domain/auth/auth.service.spec.ts +++ b/server/src/domain/auth/auth.service.spec.ts @@ -12,8 +12,8 @@ import { newUserTokenRepositoryMock, sharedLinkStub, systemConfigStub, - userEntityStub, - userTokenEntityStub, + userStub, + userTokenStub, } from '@test'; import { IncomingHttpHeaders } from 'http'; import { generators, Issuer } from 'openid-client'; @@ -112,15 +112,15 @@ describe('AuthService', () => { }); it('should successfully log the user in', async () => { - userMock.getByEmail.mockResolvedValue(userEntityStub.user1); - userTokenMock.create.mockResolvedValue(userTokenEntityStub.userToken); + userMock.getByEmail.mockResolvedValue(userStub.user1); + userTokenMock.create.mockResolvedValue(userTokenStub.userToken); await expect(sut.login(fixtures.login, loginDetails)).resolves.toEqual(loginResponseStub.user1password); expect(userMock.getByEmail).toHaveBeenCalledTimes(1); }); it('should generate the cookie headers (insecure)', async () => { - userMock.getByEmail.mockResolvedValue(userEntityStub.user1); - userTokenMock.create.mockResolvedValue(userTokenEntityStub.userToken); + userMock.getByEmail.mockResolvedValue(userStub.user1); + userTokenMock.create.mockResolvedValue(userTokenStub.userToken); await expect( sut.login(fixtures.login, { clientIp: '127.0.0.1', @@ -246,10 +246,10 @@ describe('AuthService', () => { }); it('should validate using authorization header', async () => { - userMock.get.mockResolvedValue(userEntityStub.user1); - userTokenMock.getByToken.mockResolvedValue(userTokenEntityStub.userToken); + userMock.get.mockResolvedValue(userStub.user1); + userTokenMock.getByToken.mockResolvedValue(userTokenStub.userToken); const client = { request: { headers: { authorization: 'Bearer auth_token' } } }; - await expect(sut.validate((client as Socket).request.headers, {})).resolves.toEqual(userEntityStub.user1); + await expect(sut.validate((client as Socket).request.headers, {})).resolves.toEqual(userStub.user1); }); }); @@ -275,7 +275,7 @@ describe('AuthService', () => { it('should accept a base64url key', async () => { shareMock.getByKey.mockResolvedValue(sharedLinkStub.valid); - userMock.get.mockResolvedValue(userEntityStub.admin); + userMock.get.mockResolvedValue(userStub.admin); const headers: IncomingHttpHeaders = { 'x-immich-share-key': sharedLinkStub.valid.key.toString('base64url') }; await expect(sut.validate(headers, {})).resolves.toEqual(authStub.adminSharedLink); expect(shareMock.getByKey).toHaveBeenCalledWith(sharedLinkStub.valid.key); @@ -283,7 +283,7 @@ describe('AuthService', () => { it('should accept a hex key', async () => { shareMock.getByKey.mockResolvedValue(sharedLinkStub.valid); - userMock.get.mockResolvedValue(userEntityStub.admin); + userMock.get.mockResolvedValue(userStub.admin); const headers: IncomingHttpHeaders = { 'x-immich-share-key': sharedLinkStub.valid.key.toString('hex') }; await expect(sut.validate(headers, {})).resolves.toEqual(authStub.adminSharedLink); expect(shareMock.getByKey).toHaveBeenCalledWith(sharedLinkStub.valid.key); @@ -298,16 +298,16 @@ describe('AuthService', () => { }); it('should return an auth dto', async () => { - userTokenMock.getByToken.mockResolvedValue(userTokenEntityStub.userToken); + userTokenMock.getByToken.mockResolvedValue(userTokenStub.userToken); const headers: IncomingHttpHeaders = { cookie: 'immich_access_token=auth_token' }; - await expect(sut.validate(headers, {})).resolves.toEqual(userEntityStub.user1); + await expect(sut.validate(headers, {})).resolves.toEqual(userStub.user1); }); it('should update when access time exceeds an hour', async () => { - userTokenMock.getByToken.mockResolvedValue(userTokenEntityStub.inactiveToken); - userTokenMock.save.mockResolvedValue(userTokenEntityStub.userToken); + userTokenMock.getByToken.mockResolvedValue(userTokenStub.inactiveToken); + userTokenMock.save.mockResolvedValue(userTokenStub.userToken); const headers: IncomingHttpHeaders = { cookie: 'immich_access_token=auth_token' }; - await expect(sut.validate(headers, {})).resolves.toEqual(userEntityStub.user1); + await expect(sut.validate(headers, {})).resolves.toEqual(userStub.user1); expect(userTokenMock.save.mock.calls[0][0]).toMatchObject({ id: 'not_active', token: 'auth_token', @@ -338,7 +338,7 @@ describe('AuthService', () => { describe('getDevices', () => { it('should get the devices', async () => { - userTokenMock.getAll.mockResolvedValue([userTokenEntityStub.userToken, userTokenEntityStub.inactiveToken]); + userTokenMock.getAll.mockResolvedValue([userTokenStub.userToken, userTokenStub.inactiveToken]); await expect(sut.getDevices(authStub.user1)).resolves.toEqual([ { createdAt: '2021-01-01T00:00:00.000Z', @@ -364,7 +364,7 @@ describe('AuthService', () => { describe('logoutDevices', () => { it('should logout all devices', async () => { - userTokenMock.getAll.mockResolvedValue([userTokenEntityStub.inactiveToken, userTokenEntityStub.userToken]); + userTokenMock.getAll.mockResolvedValue([userTokenStub.inactiveToken, userTokenStub.userToken]); await sut.logoutDevices(authStub.user1); @@ -429,24 +429,24 @@ describe('AuthService', () => { it('should link an existing user', async () => { configMock.load.mockResolvedValue(systemConfigStub.noAutoRegister); - userMock.getByEmail.mockResolvedValue(userEntityStub.user1); - userMock.update.mockResolvedValue(userEntityStub.user1); - userTokenMock.create.mockResolvedValue(userTokenEntityStub.userToken); + userMock.getByEmail.mockResolvedValue(userStub.user1); + userMock.update.mockResolvedValue(userStub.user1); + userTokenMock.create.mockResolvedValue(userTokenStub.userToken); await expect(sut.callback({ url: 'http://immich/auth/login?code=abc123' }, loginDetails)).resolves.toEqual( loginResponseStub.user1oauth, ); expect(userMock.getByEmail).toHaveBeenCalledTimes(1); - expect(userMock.update).toHaveBeenCalledWith(userEntityStub.user1.id, { oauthId: sub }); + expect(userMock.update).toHaveBeenCalledWith(userStub.user1.id, { oauthId: sub }); }); it('should allow auto registering by default', async () => { configMock.load.mockResolvedValue(systemConfigStub.enabled); userMock.getByEmail.mockResolvedValue(null); - userMock.getAdmin.mockResolvedValue(userEntityStub.user1); - userMock.create.mockResolvedValue(userEntityStub.user1); - userTokenMock.create.mockResolvedValue(userTokenEntityStub.userToken); + userMock.getAdmin.mockResolvedValue(userStub.user1); + userMock.create.mockResolvedValue(userStub.user1); + userTokenMock.create.mockResolvedValue(userTokenStub.userToken); await expect(sut.callback({ url: 'http://immich/auth/login?code=abc123' }, loginDetails)).resolves.toEqual( loginResponseStub.user1oauth, @@ -458,8 +458,8 @@ describe('AuthService', () => { it('should use the mobile redirect override', async () => { configMock.load.mockResolvedValue(systemConfigStub.override); - userMock.getByOAuthId.mockResolvedValue(userEntityStub.user1); - userTokenMock.create.mockResolvedValue(userTokenEntityStub.userToken); + userMock.getByOAuthId.mockResolvedValue(userStub.user1); + userTokenMock.create.mockResolvedValue(userTokenStub.userToken); await sut.callback({ url: `app.immich:/?code=abc123` }, loginDetails); @@ -468,8 +468,8 @@ describe('AuthService', () => { it('should use the mobile redirect override for ios urls with multiple slashes', async () => { configMock.load.mockResolvedValue(systemConfigStub.override); - userMock.getByOAuthId.mockResolvedValue(userEntityStub.user1); - userTokenMock.create.mockResolvedValue(userTokenEntityStub.userToken); + userMock.getByOAuthId.mockResolvedValue(userStub.user1); + userTokenMock.create.mockResolvedValue(userTokenStub.userToken); await sut.callback({ url: `app.immich:///?code=abc123` }, loginDetails); @@ -480,7 +480,7 @@ describe('AuthService', () => { describe('link', () => { it('should link an account', async () => { configMock.load.mockResolvedValue(systemConfigStub.enabled); - userMock.update.mockResolvedValue(userEntityStub.user1); + userMock.update.mockResolvedValue(userStub.user1); await sut.link(authStub.user1, { url: 'http://immich/user-settings?code=abc123' }); @@ -502,7 +502,7 @@ describe('AuthService', () => { describe('unlink', () => { it('should unlink an account', async () => { configMock.load.mockResolvedValue(systemConfigStub.enabled); - userMock.update.mockResolvedValue(userEntityStub.user1); + userMock.update.mockResolvedValue(userStub.user1); await sut.unlink(authStub.user1); diff --git a/server/src/domain/facial-recognition/facial-recognition.service.spec.ts b/server/src/domain/facial-recognition/facial-recognition.service.spec.ts index 2c737bf620..537d5e5fe6 100644 --- a/server/src/domain/facial-recognition/facial-recognition.service.spec.ts +++ b/server/src/domain/facial-recognition/facial-recognition.service.spec.ts @@ -1,5 +1,5 @@ import { - assetEntityStub, + assetStub, faceStub, newAssetRepositoryMock, newFaceRepositoryMock, @@ -133,7 +133,7 @@ describe(FacialRecognitionService.name, () => { describe('handleQueueRecognizeFaces', () => { it('should queue missing assets', async () => { assetMock.getWithout.mockResolvedValue({ - items: [assetEntityStub.image], + items: [assetStub.image], hasNextPage: false, }); await sut.handleQueueRecognizeFaces({}); @@ -141,13 +141,13 @@ describe(FacialRecognitionService.name, () => { expect(assetMock.getWithout).toHaveBeenCalledWith({ skip: 0, take: 1000 }, WithoutProperty.FACES); expect(jobMock.queue).toHaveBeenCalledWith({ name: JobName.RECOGNIZE_FACES, - data: { id: assetEntityStub.image.id }, + data: { id: assetStub.image.id }, }); }); it('should queue all assets', async () => { assetMock.getAll.mockResolvedValue({ - items: [assetEntityStub.image], + items: [assetStub.image], hasNextPage: false, }); personMock.deleteAll.mockResolvedValue(5); @@ -158,24 +158,24 @@ describe(FacialRecognitionService.name, () => { expect(assetMock.getAll).toHaveBeenCalled(); expect(jobMock.queue).toHaveBeenCalledWith({ name: JobName.RECOGNIZE_FACES, - data: { id: assetEntityStub.image.id }, + data: { id: assetStub.image.id }, }); }); }); describe('handleRecognizeFaces', () => { it('should skip when no resize path', async () => { - assetMock.getByIds.mockResolvedValue([assetEntityStub.noResizePath]); - await sut.handleRecognizeFaces({ id: assetEntityStub.noResizePath.id }); + assetMock.getByIds.mockResolvedValue([assetStub.noResizePath]); + await sut.handleRecognizeFaces({ id: assetStub.noResizePath.id }); expect(machineLearningMock.detectFaces).not.toHaveBeenCalled(); }); it('should handle no results', async () => { machineLearningMock.detectFaces.mockResolvedValue([]); - assetMock.getByIds.mockResolvedValue([assetEntityStub.image]); - await sut.handleRecognizeFaces({ id: assetEntityStub.image.id }); + assetMock.getByIds.mockResolvedValue([assetStub.image]); + await sut.handleRecognizeFaces({ id: assetStub.image.id }); expect(machineLearningMock.detectFaces).toHaveBeenCalledWith({ - imagePath: assetEntityStub.image.resizePath, + imagePath: assetStub.image.resizePath, }); expect(faceMock.create).not.toHaveBeenCalled(); expect(jobMock.queue).not.toHaveBeenCalled(); @@ -184,8 +184,8 @@ describe(FacialRecognitionService.name, () => { it('should match existing people', async () => { machineLearningMock.detectFaces.mockResolvedValue([face.middle]); searchMock.searchFaces.mockResolvedValue(faceSearch.oneMatch); - assetMock.getByIds.mockResolvedValue([assetEntityStub.image]); - await sut.handleRecognizeFaces({ id: assetEntityStub.image.id }); + assetMock.getByIds.mockResolvedValue([assetStub.image]); + await sut.handleRecognizeFaces({ id: assetStub.image.id }); expect(faceMock.create).toHaveBeenCalledWith({ personId: 'person-1', @@ -204,11 +204,11 @@ describe(FacialRecognitionService.name, () => { machineLearningMock.detectFaces.mockResolvedValue([face.middle]); searchMock.searchFaces.mockResolvedValue(faceSearch.oneRemoteMatch); personMock.create.mockResolvedValue(personStub.noName); - assetMock.getByIds.mockResolvedValue([assetEntityStub.image]); + assetMock.getByIds.mockResolvedValue([assetStub.image]); - await sut.handleRecognizeFaces({ id: assetEntityStub.image.id }); + await sut.handleRecognizeFaces({ id: assetStub.image.id }); - expect(personMock.create).toHaveBeenCalledWith({ ownerId: assetEntityStub.image.ownerId }); + expect(personMock.create).toHaveBeenCalledWith({ ownerId: assetStub.image.ownerId }); expect(faceMock.create).toHaveBeenCalledWith({ personId: 'person-1', assetId: 'asset-id', @@ -254,7 +254,7 @@ describe(FacialRecognitionService.name, () => { }); it('should skip an asset without a thumbnail', async () => { - assetMock.getByIds.mockResolvedValue([assetEntityStub.noResizePath]); + assetMock.getByIds.mockResolvedValue([assetStub.noResizePath]); await sut.handleGenerateFaceThumbnail(face.middle); @@ -262,7 +262,7 @@ describe(FacialRecognitionService.name, () => { }); it('should generate a thumbnail', async () => { - assetMock.getByIds.mockResolvedValue([assetEntityStub.image]); + assetMock.getByIds.mockResolvedValue([assetStub.image]); await sut.handleGenerateFaceThumbnail(face.middle); @@ -285,7 +285,7 @@ describe(FacialRecognitionService.name, () => { }); it('should generate a thumbnail without going negative', async () => { - assetMock.getByIds.mockResolvedValue([assetEntityStub.image]); + assetMock.getByIds.mockResolvedValue([assetStub.image]); await sut.handleGenerateFaceThumbnail(face.start); @@ -302,7 +302,7 @@ describe(FacialRecognitionService.name, () => { }); it('should generate a thumbnail without overflowing', async () => { - assetMock.getByIds.mockResolvedValue([assetEntityStub.image]); + assetMock.getByIds.mockResolvedValue([assetStub.image]); await sut.handleGenerateFaceThumbnail(face.end); diff --git a/server/src/domain/job/job.service.spec.ts b/server/src/domain/job/job.service.spec.ts index 1b424678b9..a8c6ec9dc6 100644 --- a/server/src/domain/job/job.service.spec.ts +++ b/server/src/domain/job/job.service.spec.ts @@ -1,7 +1,7 @@ import { SystemConfig } from '@app/infra/entities'; import { BadRequestException } from '@nestjs/common'; import { - assetEntityStub, + assetStub, asyncTick, newAssetRepositoryMock, newCommunicationRepositoryMock, @@ -300,7 +300,7 @@ describe(JobService.name, () => { for (const { item, jobs } of tests) { it(`should queue ${jobs.length} jobs when a ${item.name} job finishes successfully`, async () => { if (item.name === JobName.GENERATE_JPEG_THUMBNAIL && item.data.source === 'upload') { - assetMock.getByIds.mockResolvedValue([assetEntityStub.livePhotoMotionAsset]); + assetMock.getByIds.mockResolvedValue([assetStub.livePhotoMotionAsset]); } else { assetMock.getByIds.mockResolvedValue([]); } diff --git a/server/src/domain/media/media.service.spec.ts b/server/src/domain/media/media.service.spec.ts index f67bc40c90..4ab136a13e 100644 --- a/server/src/domain/media/media.service.spec.ts +++ b/server/src/domain/media/media.service.spec.ts @@ -1,6 +1,6 @@ import { AssetType, SystemConfigKey, TranscodePolicy, VideoCodec } from '@app/infra/entities'; import { - assetEntityStub, + assetStub, newAssetRepositoryMock, newJobRepositoryMock, newMediaRepositoryMock, @@ -40,7 +40,7 @@ describe(MediaService.name, () => { describe('handleQueueGenerateThumbnails', () => { it('should queue all assets', async () => { assetMock.getAll.mockResolvedValue({ - items: [assetEntityStub.image], + items: [assetStub.image], hasNextPage: false, }); @@ -50,13 +50,13 @@ describe(MediaService.name, () => { expect(assetMock.getWithout).not.toHaveBeenCalled(); expect(jobMock.queue).toHaveBeenCalledWith({ name: JobName.GENERATE_JPEG_THUMBNAIL, - data: { id: assetEntityStub.image.id }, + data: { id: assetStub.image.id }, }); }); it('should queue all assets with missing resize path', async () => { assetMock.getWithout.mockResolvedValue({ - items: [assetEntityStub.noResizePath], + items: [assetStub.noResizePath], hasNextPage: false, }); @@ -66,13 +66,13 @@ describe(MediaService.name, () => { expect(assetMock.getWithout).toHaveBeenCalledWith({ skip: 0, take: 1000 }, WithoutProperty.THUMBNAIL); expect(jobMock.queue).toHaveBeenCalledWith({ name: JobName.GENERATE_JPEG_THUMBNAIL, - data: { id: assetEntityStub.image.id }, + data: { id: assetStub.image.id }, }); }); it('should queue all assets with missing webp path', async () => { assetMock.getWithout.mockResolvedValue({ - items: [assetEntityStub.noWebpPath], + items: [assetStub.noWebpPath], hasNextPage: false, }); @@ -82,13 +82,13 @@ describe(MediaService.name, () => { expect(assetMock.getWithout).toHaveBeenCalledWith({ skip: 0, take: 1000 }, WithoutProperty.THUMBNAIL); expect(jobMock.queue).toHaveBeenCalledWith({ name: JobName.GENERATE_WEBP_THUMBNAIL, - data: { id: assetEntityStub.image.id }, + data: { id: assetStub.image.id }, }); }); it('should queue all assets with missing thumbhash', async () => { assetMock.getWithout.mockResolvedValue({ - items: [assetEntityStub.noThumbhash], + items: [assetStub.noThumbhash], hasNextPage: false, }); @@ -98,7 +98,7 @@ describe(MediaService.name, () => { expect(assetMock.getWithout).toHaveBeenCalledWith({ skip: 0, take: 1000 }, WithoutProperty.THUMBNAIL); expect(jobMock.queue).toHaveBeenCalledWith({ name: JobName.GENERATE_THUMBHASH_THUMBNAIL, - data: { id: assetEntityStub.image.id }, + data: { id: assetStub.image.id }, }); }); }); @@ -106,14 +106,14 @@ describe(MediaService.name, () => { describe('handleGenerateJpegThumbnail', () => { it('should skip thumbnail generation if asset not found', async () => { assetMock.getByIds.mockResolvedValue([]); - await sut.handleGenerateJpegThumbnail({ id: assetEntityStub.image.id }); + await sut.handleGenerateJpegThumbnail({ id: assetStub.image.id }); expect(mediaMock.resize).not.toHaveBeenCalled(); expect(assetMock.save).not.toHaveBeenCalledWith(); }); it('should generate a thumbnail for an image', async () => { - assetMock.getByIds.mockResolvedValue([assetEntityStub.image]); - await sut.handleGenerateJpegThumbnail({ id: assetEntityStub.image.id }); + assetMock.getByIds.mockResolvedValue([assetStub.image]); + await sut.handleGenerateJpegThumbnail({ id: assetStub.image.id }); expect(storageMock.mkdirSync).toHaveBeenCalledWith('upload/thumbs/user-id'); expect(mediaMock.resize).toHaveBeenCalledWith('/original/path.jpg', 'upload/thumbs/user-id/asset-id.jpeg', { @@ -127,8 +127,8 @@ describe(MediaService.name, () => { }); it('should generate a thumbnail for a video', async () => { - assetMock.getByIds.mockResolvedValue([assetEntityStub.video]); - await sut.handleGenerateJpegThumbnail({ id: assetEntityStub.video.id }); + assetMock.getByIds.mockResolvedValue([assetStub.video]); + await sut.handleGenerateJpegThumbnail({ id: assetStub.video.id }); expect(storageMock.mkdirSync).toHaveBeenCalledWith('upload/thumbs/user-id'); expect(mediaMock.extractVideoThumbnail).toHaveBeenCalledWith( @@ -143,28 +143,28 @@ describe(MediaService.name, () => { }); it('should run successfully', async () => { - assetMock.getByIds.mockResolvedValue([assetEntityStub.image]); - await sut.handleGenerateJpegThumbnail({ id: assetEntityStub.image.id }); + assetMock.getByIds.mockResolvedValue([assetStub.image]); + await sut.handleGenerateJpegThumbnail({ id: assetStub.image.id }); }); }); describe('handleGenerateWebpThumbnail', () => { it('should skip thumbnail generation if asset not found', async () => { assetMock.getByIds.mockResolvedValue([]); - await sut.handleGenerateWebpThumbnail({ id: assetEntityStub.image.id }); + await sut.handleGenerateWebpThumbnail({ id: assetStub.image.id }); expect(mediaMock.resize).not.toHaveBeenCalled(); expect(assetMock.save).not.toHaveBeenCalledWith(); }); it('should skip thumbnail generate if resize path is missing', async () => { - assetMock.getByIds.mockResolvedValue([assetEntityStub.noResizePath]); - await sut.handleGenerateWebpThumbnail({ id: assetEntityStub.noResizePath.id }); + assetMock.getByIds.mockResolvedValue([assetStub.noResizePath]); + await sut.handleGenerateWebpThumbnail({ id: assetStub.noResizePath.id }); expect(mediaMock.resize).not.toHaveBeenCalled(); }); it('should generate a thumbnail', async () => { - assetMock.getByIds.mockResolvedValue([assetEntityStub.image]); - await sut.handleGenerateWebpThumbnail({ id: assetEntityStub.image.id }); + assetMock.getByIds.mockResolvedValue([assetStub.image]); + await sut.handleGenerateWebpThumbnail({ id: assetStub.image.id }); expect(mediaMock.resize).toHaveBeenCalledWith( '/uploads/user-id/thumbs/path.jpg', @@ -178,22 +178,22 @@ describe(MediaService.name, () => { describe('handleGenerateThumbhashThumbnail', () => { it('should skip thumbhash generation if asset not found', async () => { assetMock.getByIds.mockResolvedValue([]); - await sut.handleGenerateThumbhashThumbnail({ id: assetEntityStub.image.id }); + await sut.handleGenerateThumbhashThumbnail({ id: assetStub.image.id }); expect(mediaMock.generateThumbhash).not.toHaveBeenCalled(); }); it('should skip thumbhash generation if resize path is missing', async () => { - assetMock.getByIds.mockResolvedValue([assetEntityStub.noResizePath]); - await sut.handleGenerateThumbhashThumbnail({ id: assetEntityStub.noResizePath.id }); + assetMock.getByIds.mockResolvedValue([assetStub.noResizePath]); + await sut.handleGenerateThumbhashThumbnail({ id: assetStub.noResizePath.id }); expect(mediaMock.generateThumbhash).not.toHaveBeenCalled(); }); it('should generate a thumbhash', async () => { const thumbhashBuffer = Buffer.from('a thumbhash', 'utf8'); - assetMock.getByIds.mockResolvedValue([assetEntityStub.image]); + assetMock.getByIds.mockResolvedValue([assetStub.image]); mediaMock.generateThumbhash.mockResolvedValue(thumbhashBuffer); - await sut.handleGenerateThumbhashThumbnail({ id: assetEntityStub.image.id }); + await sut.handleGenerateThumbhashThumbnail({ id: assetStub.image.id }); expect(mediaMock.generateThumbhash).toHaveBeenCalledWith('/uploads/user-id/thumbs/path.jpg'); expect(assetMock.save).toHaveBeenCalledWith({ id: 'asset-id', thumbhash: thumbhashBuffer }); @@ -203,7 +203,7 @@ describe(MediaService.name, () => { describe('handleQueueVideoConversion', () => { it('should queue all video assets', async () => { assetMock.getAll.mockResolvedValue({ - items: [assetEntityStub.video], + items: [assetStub.video], hasNextPage: false, }); @@ -213,13 +213,13 @@ describe(MediaService.name, () => { expect(assetMock.getWithout).not.toHaveBeenCalled(); expect(jobMock.queue).toHaveBeenCalledWith({ name: JobName.VIDEO_CONVERSION, - data: { id: assetEntityStub.video.id }, + data: { id: assetStub.video.id }, }); }); it('should queue all video assets without encoded videos', async () => { assetMock.getWithout.mockResolvedValue({ - items: [assetEntityStub.video], + items: [assetStub.video], hasNextPage: false, }); @@ -229,35 +229,35 @@ describe(MediaService.name, () => { expect(assetMock.getWithout).toHaveBeenCalledWith({ skip: 0, take: 1000 }, WithoutProperty.ENCODED_VIDEO); expect(jobMock.queue).toHaveBeenCalledWith({ name: JobName.VIDEO_CONVERSION, - data: { id: assetEntityStub.video.id }, + data: { id: assetStub.video.id }, }); }); }); describe('handleVideoConversion', () => { beforeEach(() => { - assetMock.getByIds.mockResolvedValue([assetEntityStub.video]); + assetMock.getByIds.mockResolvedValue([assetStub.video]); }); it('should skip transcoding if asset not found', async () => { assetMock.getByIds.mockResolvedValue([]); - await sut.handleVideoConversion({ id: assetEntityStub.video.id }); + await sut.handleVideoConversion({ id: assetStub.video.id }); expect(mediaMock.probe).not.toHaveBeenCalled(); expect(mediaMock.transcode).not.toHaveBeenCalled(); }); it('should skip transcoding if non-video asset', async () => { - assetMock.getByIds.mockResolvedValue([assetEntityStub.image]); - await sut.handleVideoConversion({ id: assetEntityStub.image.id }); + assetMock.getByIds.mockResolvedValue([assetStub.image]); + await sut.handleVideoConversion({ id: assetStub.image.id }); expect(mediaMock.probe).not.toHaveBeenCalled(); expect(mediaMock.transcode).not.toHaveBeenCalled(); }); it('should transcode the longest stream', async () => { - assetMock.getByIds.mockResolvedValue([assetEntityStub.video]); + assetMock.getByIds.mockResolvedValue([assetStub.video]); mediaMock.probe.mockResolvedValue(probeStub.multipleVideoStreams); - await sut.handleVideoConversion({ id: assetEntityStub.video.id }); + await sut.handleVideoConversion({ id: assetStub.video.id }); expect(mediaMock.probe).toHaveBeenCalledWith('/original/path.ext'); expect(configMock.load).toHaveBeenCalled(); @@ -282,23 +282,23 @@ describe(MediaService.name, () => { it('should skip a video without any streams', async () => { mediaMock.probe.mockResolvedValue(probeStub.noVideoStreams); - assetMock.getByIds.mockResolvedValue([assetEntityStub.video]); - await sut.handleVideoConversion({ id: assetEntityStub.video.id }); + assetMock.getByIds.mockResolvedValue([assetStub.video]); + await sut.handleVideoConversion({ id: assetStub.video.id }); expect(mediaMock.transcode).not.toHaveBeenCalled(); }); it('should skip a video without any height', async () => { mediaMock.probe.mockResolvedValue(probeStub.noHeight); - assetMock.getByIds.mockResolvedValue([assetEntityStub.video]); - await sut.handleVideoConversion({ id: assetEntityStub.video.id }); + assetMock.getByIds.mockResolvedValue([assetStub.video]); + await sut.handleVideoConversion({ id: assetStub.video.id }); expect(mediaMock.transcode).not.toHaveBeenCalled(); }); it('should transcode when set to all', async () => { mediaMock.probe.mockResolvedValue(probeStub.multipleVideoStreams); configMock.load.mockResolvedValue([{ key: SystemConfigKey.FFMPEG_TRANSCODE, value: TranscodePolicy.ALL }]); - assetMock.getByIds.mockResolvedValue([assetEntityStub.video]); - await sut.handleVideoConversion({ id: assetEntityStub.video.id }); + assetMock.getByIds.mockResolvedValue([assetStub.video]); + await sut.handleVideoConversion({ id: assetStub.video.id }); expect(mediaMock.transcode).toHaveBeenCalledWith( '/original/path.ext', 'upload/encoded-video/user-id/asset-id.mp4', @@ -320,7 +320,7 @@ describe(MediaService.name, () => { it('should transcode when optimal and too big', async () => { mediaMock.probe.mockResolvedValue(probeStub.videoStream2160p); configMock.load.mockResolvedValue([{ key: SystemConfigKey.FFMPEG_TRANSCODE, value: TranscodePolicy.OPTIMAL }]); - await sut.handleVideoConversion({ id: assetEntityStub.video.id }); + await sut.handleVideoConversion({ id: assetStub.video.id }); expect(mediaMock.transcode).toHaveBeenCalledWith( '/original/path.ext', 'upload/encoded-video/user-id/asset-id.mp4', @@ -346,7 +346,7 @@ describe(MediaService.name, () => { { key: SystemConfigKey.FFMPEG_TRANSCODE, value: TranscodePolicy.ALL }, { key: SystemConfigKey.FFMPEG_TARGET_RESOLUTION, value: 'original' }, ]); - await sut.handleVideoConversion({ id: assetEntityStub.video.id }); + await sut.handleVideoConversion({ id: assetStub.video.id }); expect(mediaMock.transcode).toHaveBeenCalledWith( '/original/path.ext', 'upload/encoded-video/user-id/asset-id.mp4', @@ -368,8 +368,8 @@ describe(MediaService.name, () => { it('should transcode with alternate scaling video is vertical', async () => { mediaMock.probe.mockResolvedValue(probeStub.videoStreamVertical2160p); configMock.load.mockResolvedValue([{ key: SystemConfigKey.FFMPEG_TRANSCODE, value: TranscodePolicy.OPTIMAL }]); - assetMock.getByIds.mockResolvedValue([assetEntityStub.video]); - await sut.handleVideoConversion({ id: assetEntityStub.video.id }); + assetMock.getByIds.mockResolvedValue([assetStub.video]); + await sut.handleVideoConversion({ id: assetStub.video.id }); expect(mediaMock.transcode).toHaveBeenCalledWith( '/original/path.ext', 'upload/encoded-video/user-id/asset-id.mp4', @@ -392,8 +392,8 @@ describe(MediaService.name, () => { it('should transcode when audio doesnt match target', async () => { mediaMock.probe.mockResolvedValue(probeStub.audioStreamMp3); configMock.load.mockResolvedValue([{ key: SystemConfigKey.FFMPEG_TRANSCODE, value: TranscodePolicy.OPTIMAL }]); - assetMock.getByIds.mockResolvedValue([assetEntityStub.video]); - await sut.handleVideoConversion({ id: assetEntityStub.video.id }); + assetMock.getByIds.mockResolvedValue([assetStub.video]); + await sut.handleVideoConversion({ id: assetStub.video.id }); expect(mediaMock.transcode).toHaveBeenCalledWith( '/original/path.ext', 'upload/encoded-video/user-id/asset-id.mp4', @@ -416,8 +416,8 @@ describe(MediaService.name, () => { it('should transcode when container doesnt match target', async () => { mediaMock.probe.mockResolvedValue(probeStub.matroskaContainer); configMock.load.mockResolvedValue([{ key: SystemConfigKey.FFMPEG_TRANSCODE, value: TranscodePolicy.OPTIMAL }]); - assetMock.getByIds.mockResolvedValue([assetEntityStub.video]); - await sut.handleVideoConversion({ id: assetEntityStub.video.id }); + assetMock.getByIds.mockResolvedValue([assetStub.video]); + await sut.handleVideoConversion({ id: assetStub.video.id }); expect(mediaMock.transcode).toHaveBeenCalledWith( '/original/path.ext', 'upload/encoded-video/user-id/asset-id.mp4', @@ -440,32 +440,32 @@ describe(MediaService.name, () => { it('should not transcode an invalid transcode value', async () => { mediaMock.probe.mockResolvedValue(probeStub.videoStream2160p); configMock.load.mockResolvedValue([{ key: SystemConfigKey.FFMPEG_TRANSCODE, value: 'invalid' }]); - assetMock.getByIds.mockResolvedValue([assetEntityStub.video]); - await sut.handleVideoConversion({ id: assetEntityStub.video.id }); + assetMock.getByIds.mockResolvedValue([assetStub.video]); + await sut.handleVideoConversion({ id: assetStub.video.id }); expect(mediaMock.transcode).not.toHaveBeenCalled(); }); it('should not transcode if transcoding is disabled', async () => { mediaMock.probe.mockResolvedValue(probeStub.videoStream2160p); configMock.load.mockResolvedValue([{ key: SystemConfigKey.FFMPEG_TRANSCODE, value: TranscodePolicy.DISABLED }]); - assetMock.getByIds.mockResolvedValue([assetEntityStub.video]); - await sut.handleVideoConversion({ id: assetEntityStub.video.id }); + assetMock.getByIds.mockResolvedValue([assetStub.video]); + await sut.handleVideoConversion({ id: assetStub.video.id }); expect(mediaMock.transcode).not.toHaveBeenCalled(); }); it('should not transcode if target codec is invalid', async () => { mediaMock.probe.mockResolvedValue(probeStub.videoStream2160p); configMock.load.mockResolvedValue([{ key: SystemConfigKey.FFMPEG_TARGET_VIDEO_CODEC, value: 'invalid' }]); - assetMock.getByIds.mockResolvedValue([assetEntityStub.video]); - await sut.handleVideoConversion({ id: assetEntityStub.video.id }); + assetMock.getByIds.mockResolvedValue([assetStub.video]); + await sut.handleVideoConversion({ id: assetStub.video.id }); expect(mediaMock.transcode).not.toHaveBeenCalled(); }); it('should set max bitrate if above 0', async () => { mediaMock.probe.mockResolvedValue(probeStub.matroskaContainer); configMock.load.mockResolvedValue([{ key: SystemConfigKey.FFMPEG_MAX_BITRATE, value: '4500k' }]); - assetMock.getByIds.mockResolvedValue([assetEntityStub.video]); - await sut.handleVideoConversion({ id: assetEntityStub.video.id }); + assetMock.getByIds.mockResolvedValue([assetStub.video]); + await sut.handleVideoConversion({ id: assetStub.video.id }); expect(mediaMock.transcode).toHaveBeenCalledWith( '/original/path.ext', 'upload/encoded-video/user-id/asset-id.mp4', @@ -493,8 +493,8 @@ describe(MediaService.name, () => { { key: SystemConfigKey.FFMPEG_MAX_BITRATE, value: '4500k' }, { key: SystemConfigKey.FFMPEG_TWO_PASS, value: true }, ]); - assetMock.getByIds.mockResolvedValue([assetEntityStub.video]); - await sut.handleVideoConversion({ id: assetEntityStub.video.id }); + assetMock.getByIds.mockResolvedValue([assetStub.video]); + await sut.handleVideoConversion({ id: assetStub.video.id }); expect(mediaMock.transcode).toHaveBeenCalledWith( '/original/path.ext', 'upload/encoded-video/user-id/asset-id.mp4', @@ -519,8 +519,8 @@ describe(MediaService.name, () => { it('should fallback to one pass for h264/h265 if two-pass is enabled but no max bitrate is set', async () => { mediaMock.probe.mockResolvedValue(probeStub.matroskaContainer); configMock.load.mockResolvedValue([{ key: SystemConfigKey.FFMPEG_TWO_PASS, value: true }]); - assetMock.getByIds.mockResolvedValue([assetEntityStub.video]); - await sut.handleVideoConversion({ id: assetEntityStub.video.id }); + assetMock.getByIds.mockResolvedValue([assetStub.video]); + await sut.handleVideoConversion({ id: assetStub.video.id }); expect(mediaMock.transcode).toHaveBeenCalledWith( '/original/path.ext', 'upload/encoded-video/user-id/asset-id.mp4', @@ -547,8 +547,8 @@ describe(MediaService.name, () => { { key: SystemConfigKey.FFMPEG_TWO_PASS, value: true }, { key: SystemConfigKey.FFMPEG_TARGET_VIDEO_CODEC, value: VideoCodec.VP9 }, ]); - assetMock.getByIds.mockResolvedValue([assetEntityStub.video]); - await sut.handleVideoConversion({ id: assetEntityStub.video.id }); + assetMock.getByIds.mockResolvedValue([assetStub.video]); + await sut.handleVideoConversion({ id: assetStub.video.id }); expect(mediaMock.transcode).toHaveBeenCalledWith( '/original/path.ext', 'upload/encoded-video/user-id/asset-id.mp4', @@ -577,8 +577,8 @@ describe(MediaService.name, () => { { key: SystemConfigKey.FFMPEG_TARGET_VIDEO_CODEC, value: VideoCodec.VP9 }, { key: SystemConfigKey.FFMPEG_PRESET, value: 'slow' }, ]); - assetMock.getByIds.mockResolvedValue([assetEntityStub.video]); - await sut.handleVideoConversion({ id: assetEntityStub.video.id }); + assetMock.getByIds.mockResolvedValue([assetStub.video]); + await sut.handleVideoConversion({ id: assetStub.video.id }); expect(mediaMock.transcode).toHaveBeenCalledWith( '/original/path.ext', 'upload/encoded-video/user-id/asset-id.mp4', @@ -606,8 +606,8 @@ describe(MediaService.name, () => { { key: SystemConfigKey.FFMPEG_TARGET_VIDEO_CODEC, value: VideoCodec.VP9 }, { key: SystemConfigKey.FFMPEG_PRESET, value: 'invalid' }, ]); - assetMock.getByIds.mockResolvedValue([assetEntityStub.video]); - await sut.handleVideoConversion({ id: assetEntityStub.video.id }); + assetMock.getByIds.mockResolvedValue([assetStub.video]); + await sut.handleVideoConversion({ id: assetStub.video.id }); expect(mediaMock.transcode).toHaveBeenCalledWith( '/original/path.ext', 'upload/encoded-video/user-id/asset-id.mp4', @@ -634,8 +634,8 @@ describe(MediaService.name, () => { { key: SystemConfigKey.FFMPEG_TARGET_VIDEO_CODEC, value: VideoCodec.VP9 }, { key: SystemConfigKey.FFMPEG_THREADS, value: 2 }, ]); - assetMock.getByIds.mockResolvedValue([assetEntityStub.video]); - await sut.handleVideoConversion({ id: assetEntityStub.video.id }); + assetMock.getByIds.mockResolvedValue([assetStub.video]); + await sut.handleVideoConversion({ id: assetStub.video.id }); expect(mediaMock.transcode).toHaveBeenCalledWith( '/original/path.ext', 'upload/encoded-video/user-id/asset-id.mp4', @@ -661,8 +661,8 @@ describe(MediaService.name, () => { it('should disable thread pooling for h264 if thread limit is above 0', async () => { mediaMock.probe.mockResolvedValue(probeStub.matroskaContainer); configMock.load.mockResolvedValue([{ key: SystemConfigKey.FFMPEG_THREADS, value: 2 }]); - assetMock.getByIds.mockResolvedValue([assetEntityStub.video]); - await sut.handleVideoConversion({ id: assetEntityStub.video.id }); + assetMock.getByIds.mockResolvedValue([assetStub.video]); + await sut.handleVideoConversion({ id: assetStub.video.id }); expect(mediaMock.transcode).toHaveBeenCalledWith( '/original/path.ext', 'upload/encoded-video/user-id/asset-id.mp4', @@ -688,8 +688,8 @@ describe(MediaService.name, () => { it('should omit thread flags for h264 if thread limit is at or below 0', async () => { mediaMock.probe.mockResolvedValue(probeStub.matroskaContainer); configMock.load.mockResolvedValue([{ key: SystemConfigKey.FFMPEG_THREADS, value: 0 }]); - assetMock.getByIds.mockResolvedValue([assetEntityStub.video]); - await sut.handleVideoConversion({ id: assetEntityStub.video.id }); + assetMock.getByIds.mockResolvedValue([assetStub.video]); + await sut.handleVideoConversion({ id: assetStub.video.id }); expect(mediaMock.transcode).toHaveBeenCalledWith( '/original/path.ext', 'upload/encoded-video/user-id/asset-id.mp4', @@ -715,8 +715,8 @@ describe(MediaService.name, () => { { key: SystemConfigKey.FFMPEG_THREADS, value: 2 }, { key: SystemConfigKey.FFMPEG_TARGET_VIDEO_CODEC, value: VideoCodec.HEVC }, ]); - assetMock.getByIds.mockResolvedValue([assetEntityStub.video]); - await sut.handleVideoConversion({ id: assetEntityStub.video.id }); + assetMock.getByIds.mockResolvedValue([assetStub.video]); + await sut.handleVideoConversion({ id: assetStub.video.id }); expect(mediaMock.transcode).toHaveBeenCalledWith( '/original/path.ext', 'upload/encoded-video/user-id/asset-id.mp4', @@ -745,8 +745,8 @@ describe(MediaService.name, () => { { key: SystemConfigKey.FFMPEG_THREADS, value: 0 }, { key: SystemConfigKey.FFMPEG_TARGET_VIDEO_CODEC, value: VideoCodec.HEVC }, ]); - assetMock.getByIds.mockResolvedValue([assetEntityStub.video]); - await sut.handleVideoConversion({ id: assetEntityStub.video.id }); + assetMock.getByIds.mockResolvedValue([assetStub.video]); + await sut.handleVideoConversion({ id: assetStub.video.id }); expect(mediaMock.transcode).toHaveBeenCalledWith( '/original/path.ext', 'upload/encoded-video/user-id/asset-id.mp4', diff --git a/server/src/domain/metadata/metadata.service.spec.ts b/server/src/domain/metadata/metadata.service.spec.ts index 19cae7cfb1..04fecbdfaa 100644 --- a/server/src/domain/metadata/metadata.service.spec.ts +++ b/server/src/domain/metadata/metadata.service.spec.ts @@ -1,4 +1,4 @@ -import { assetEntityStub, newAssetRepositoryMock, newJobRepositoryMock, newStorageRepositoryMock } from '@test'; +import { assetStub, newAssetRepositoryMock, newJobRepositoryMock, newStorageRepositoryMock } from '@test'; import { constants } from 'fs/promises'; import { IAssetRepository, WithoutProperty, WithProperty } from '../asset'; import { IJobRepository, JobName } from '../job'; @@ -25,7 +25,7 @@ describe(MetadataService.name, () => { describe('handleQueueSidecar', () => { it('should queue assets with sidecar files', async () => { - assetMock.getWith.mockResolvedValue({ items: [assetEntityStub.sidecar], hasNextPage: false }); + assetMock.getWith.mockResolvedValue({ items: [assetStub.sidecar], hasNextPage: false }); await sut.handleQueueSidecar({ force: true }); @@ -33,12 +33,12 @@ describe(MetadataService.name, () => { expect(assetMock.getWithout).not.toHaveBeenCalled(); expect(jobMock.queue).toHaveBeenCalledWith({ name: JobName.SIDECAR_SYNC, - data: { id: assetEntityStub.sidecar.id }, + data: { id: assetStub.sidecar.id }, }); }); it('should queue assets without sidecar files', async () => { - assetMock.getWithout.mockResolvedValue({ items: [assetEntityStub.image], hasNextPage: false }); + assetMock.getWithout.mockResolvedValue({ items: [assetStub.image], hasNextPage: false }); await sut.handleQueueSidecar({ force: false }); @@ -46,7 +46,7 @@ describe(MetadataService.name, () => { expect(assetMock.getWith).not.toHaveBeenCalled(); expect(jobMock.queue).toHaveBeenCalledWith({ name: JobName.SIDECAR_DISCOVERY, - data: { id: assetEntityStub.image.id }, + data: { id: assetStub.image.id }, }); }); }); @@ -59,44 +59,44 @@ describe(MetadataService.name, () => { describe('handleSidecarDiscovery', () => { it('should skip hidden assets', async () => { - assetMock.getByIds.mockResolvedValue([assetEntityStub.livePhotoMotionAsset]); - await sut.handleSidecarDiscovery({ id: assetEntityStub.livePhotoMotionAsset.id }); + assetMock.getByIds.mockResolvedValue([assetStub.livePhotoMotionAsset]); + await sut.handleSidecarDiscovery({ id: assetStub.livePhotoMotionAsset.id }); expect(storageMock.checkFileExists).not.toHaveBeenCalled(); }); it('should skip assets with a sidecar path', async () => { - assetMock.getByIds.mockResolvedValue([assetEntityStub.sidecar]); - await sut.handleSidecarDiscovery({ id: assetEntityStub.sidecar.id }); + assetMock.getByIds.mockResolvedValue([assetStub.sidecar]); + await sut.handleSidecarDiscovery({ id: assetStub.sidecar.id }); expect(storageMock.checkFileExists).not.toHaveBeenCalled(); }); it('should do nothing when a sidecar is not found ', async () => { - assetMock.getByIds.mockResolvedValue([assetEntityStub.image]); + assetMock.getByIds.mockResolvedValue([assetStub.image]); storageMock.checkFileExists.mockResolvedValue(false); - await sut.handleSidecarDiscovery({ id: assetEntityStub.image.id }); + await sut.handleSidecarDiscovery({ id: assetStub.image.id }); expect(assetMock.save).not.toHaveBeenCalled(); }); it('should update a image asset when a sidecar is found', async () => { - assetMock.getByIds.mockResolvedValue([assetEntityStub.image]); - assetMock.save.mockResolvedValue(assetEntityStub.image); + assetMock.getByIds.mockResolvedValue([assetStub.image]); + assetMock.save.mockResolvedValue(assetStub.image); storageMock.checkFileExists.mockResolvedValue(true); - await sut.handleSidecarDiscovery({ id: assetEntityStub.image.id }); + await sut.handleSidecarDiscovery({ id: assetStub.image.id }); expect(storageMock.checkFileExists).toHaveBeenCalledWith('/original/path.jpg.xmp', constants.R_OK); expect(assetMock.save).toHaveBeenCalledWith({ - id: assetEntityStub.image.id, + id: assetStub.image.id, sidecarPath: '/original/path.jpg.xmp', }); }); it('should update a video asset when a sidecar is found', async () => { - assetMock.getByIds.mockResolvedValue([assetEntityStub.video]); - assetMock.save.mockResolvedValue(assetEntityStub.video); + assetMock.getByIds.mockResolvedValue([assetStub.video]); + assetMock.save.mockResolvedValue(assetStub.video); storageMock.checkFileExists.mockResolvedValue(true); - await sut.handleSidecarDiscovery({ id: assetEntityStub.video.id }); + await sut.handleSidecarDiscovery({ id: assetStub.video.id }); expect(storageMock.checkFileExists).toHaveBeenCalledWith('/original/path.ext.xmp', constants.R_OK); expect(assetMock.save).toHaveBeenCalledWith({ - id: assetEntityStub.image.id, + id: assetStub.image.id, sidecarPath: '/original/path.ext.xmp', }); }); diff --git a/server/src/domain/person/person.service.spec.ts b/server/src/domain/person/person.service.spec.ts index 10d5763f91..7aaa875f27 100644 --- a/server/src/domain/person/person.service.spec.ts +++ b/server/src/domain/person/person.service.spec.ts @@ -1,6 +1,6 @@ import { BadRequestException, NotFoundException } from '@nestjs/common'; import { - assetEntityStub, + assetStub, authStub, faceStub, newJobRepositoryMock, @@ -112,7 +112,7 @@ describe(PersonService.name, () => { describe('getAssets', () => { it("should return a person's assets", async () => { - personMock.getAssets.mockResolvedValue([assetEntityStub.image, assetEntityStub.video]); + personMock.getAssets.mockResolvedValue([assetStub.image, assetStub.video]); await sut.getAssets(authStub.admin, 'person-1'); expect(personMock.getAssets).toHaveBeenCalledWith('admin_id', 'person-1'); }); @@ -130,7 +130,7 @@ describe(PersonService.name, () => { it("should update a person's name", async () => { personMock.getById.mockResolvedValue(personStub.noName); personMock.update.mockResolvedValue(personStub.withName); - personMock.getAssets.mockResolvedValue([assetEntityStub.image]); + personMock.getAssets.mockResolvedValue([assetStub.image]); await expect(sut.update(authStub.admin, 'person-1', { name: 'Person 1' })).resolves.toEqual(responseDto); @@ -138,14 +138,14 @@ describe(PersonService.name, () => { expect(personMock.update).toHaveBeenCalledWith({ id: 'person-1', name: 'Person 1' }); expect(jobMock.queue).toHaveBeenCalledWith({ name: JobName.SEARCH_INDEX_ASSET, - data: { ids: [assetEntityStub.image.id] }, + data: { ids: [assetStub.image.id] }, }); }); it('should update a person visibility', async () => { personMock.getById.mockResolvedValue(personStub.hidden); personMock.update.mockResolvedValue(personStub.withName); - personMock.getAssets.mockResolvedValue([assetEntityStub.image]); + personMock.getAssets.mockResolvedValue([assetStub.image]); await expect(sut.update(authStub.admin, 'person-1', { isHidden: false })).resolves.toEqual(responseDto); @@ -153,7 +153,7 @@ describe(PersonService.name, () => { expect(personMock.update).toHaveBeenCalledWith({ id: 'person-1', isHidden: false }); expect(jobMock.queue).toHaveBeenCalledWith({ name: JobName.SEARCH_INDEX_ASSET, - data: { ids: [assetEntityStub.image.id] }, + data: { ids: [assetStub.image.id] }, }); }); @@ -239,7 +239,7 @@ describe(PersonService.name, () => { it('should delete conflicting faces before merging', async () => { personMock.getById.mockResolvedValue(personStub.primaryPerson); personMock.getById.mockResolvedValue(personStub.mergePerson); - personMock.prepareReassignFaces.mockResolvedValue([assetEntityStub.image.id]); + personMock.prepareReassignFaces.mockResolvedValue([assetStub.image.id]); await expect(sut.mergePerson(authStub.admin, 'person-1', { ids: ['person-2'] })).resolves.toEqual([ { id: 'person-2', success: true }, @@ -252,7 +252,7 @@ describe(PersonService.name, () => { expect(jobMock.queue).toHaveBeenCalledWith({ name: JobName.SEARCH_REMOVE_FACE, - data: { assetId: assetEntityStub.image.id, personId: personStub.mergePerson.id }, + data: { assetId: assetStub.image.id, personId: personStub.mergePerson.id }, }); }); @@ -282,7 +282,7 @@ describe(PersonService.name, () => { it('should handle an error reassigning faces', async () => { personMock.getById.mockResolvedValue(personStub.primaryPerson); personMock.getById.mockResolvedValue(personStub.mergePerson); - personMock.prepareReassignFaces.mockResolvedValue([assetEntityStub.image.id]); + personMock.prepareReassignFaces.mockResolvedValue([assetStub.image.id]); personMock.reassignFaces.mockRejectedValue(new Error('update failed')); await expect(sut.mergePerson(authStub.admin, 'person-1', { ids: ['person-2'] })).resolves.toEqual([ diff --git a/server/src/domain/search/search.service.spec.ts b/server/src/domain/search/search.service.spec.ts index 966e2de8d3..4ffec5832c 100644 --- a/server/src/domain/search/search.service.spec.ts +++ b/server/src/domain/search/search.service.spec.ts @@ -2,7 +2,7 @@ import { BadRequestException } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { albumStub, - assetEntityStub, + assetStub, asyncTick, authStub, faceStub, @@ -192,14 +192,14 @@ describe(SearchService.name, () => { it('should index all the assets', async () => { assetMock.getAll.mockResolvedValue({ - items: [assetEntityStub.image], + items: [assetStub.image], hasNextPage: false, }); await sut.handleIndexAssets(); expect(searchMock.importAssets.mock.calls).toEqual([ - [[assetEntityStub.image], false], + [[assetStub.image], false], [[], true], ]); }); @@ -217,11 +217,11 @@ describe(SearchService.name, () => { describe('handleIndexAsset', () => { it('should skip if search is disabled', () => { const sut = makeSut('false'); - sut.handleIndexAsset({ ids: [assetEntityStub.image.id] }); + sut.handleIndexAsset({ ids: [assetStub.image.id] }); }); it('should index the asset', () => { - sut.handleIndexAsset({ ids: [assetEntityStub.image.id] }); + sut.handleIndexAsset({ ids: [assetStub.image.id] }); }); }); @@ -367,7 +367,7 @@ describe(SearchService.name, () => { }); it('should flush queued asset updates', async () => { - assetMock.getByIds.mockResolvedValue([assetEntityStub.image]); + assetMock.getByIds.mockResolvedValue([assetStub.image]); sut.handleIndexAsset({ ids: ['asset1'] }); @@ -376,7 +376,7 @@ describe(SearchService.name, () => { await asyncTick(4); expect(assetMock.getByIds).toHaveBeenCalledWith(['asset1']); - expect(searchMock.importAssets).toHaveBeenCalledWith([assetEntityStub.image], false); + expect(searchMock.importAssets).toHaveBeenCalledWith([assetStub.image], false); }); it('should flush queued asset deletes', async () => { diff --git a/server/src/domain/shared-link/shared-link.service.spec.ts b/server/src/domain/shared-link/shared-link.service.spec.ts index 507305e8d7..b244c559b3 100644 --- a/server/src/domain/shared-link/shared-link.service.spec.ts +++ b/server/src/domain/shared-link/shared-link.service.spec.ts @@ -1,7 +1,7 @@ import { BadRequestException, ForbiddenException } from '@nestjs/common'; import { albumStub, - assetEntityStub, + assetStub, authStub, IAccessRepositoryMock, newAccessRepositoryMock, @@ -136,20 +136,20 @@ describe(SharedLinkService.name, () => { await sut.create(authStub.admin, { type: SharedLinkType.INDIVIDUAL, - assetIds: [assetEntityStub.image.id], + assetIds: [assetStub.image.id], showExif: true, allowDownload: true, allowUpload: true, }); - expect(accessMock.asset.hasOwnerAccess).toHaveBeenCalledWith(authStub.admin.id, assetEntityStub.image.id); + expect(accessMock.asset.hasOwnerAccess).toHaveBeenCalledWith(authStub.admin.id, assetStub.image.id); expect(shareMock.create).toHaveBeenCalledWith({ type: SharedLinkType.INDIVIDUAL, userId: authStub.admin.id, albumId: null, allowDownload: true, allowUpload: true, - assets: [{ id: assetEntityStub.image.id }], + assets: [{ id: assetStub.image.id }], description: null, expiresAt: null, showExif: true, @@ -211,9 +211,9 @@ describe(SharedLinkService.name, () => { when(accessMock.asset.hasOwnerAccess).calledWith(authStub.admin.id, 'asset-3').mockResolvedValue(true); await expect( - sut.addAssets(authStub.admin, 'link-1', { assetIds: [assetEntityStub.image.id, 'asset-2', 'asset-3'] }), + sut.addAssets(authStub.admin, 'link-1', { assetIds: [assetStub.image.id, 'asset-2', 'asset-3'] }), ).resolves.toEqual([ - { assetId: assetEntityStub.image.id, success: false, error: AssetIdErrorReason.DUPLICATE }, + { assetId: assetStub.image.id, success: false, error: AssetIdErrorReason.DUPLICATE }, { assetId: 'asset-2', success: false, error: AssetIdErrorReason.NO_PERMISSION }, { assetId: 'asset-3', success: true }, ]); @@ -221,7 +221,7 @@ describe(SharedLinkService.name, () => { expect(accessMock.asset.hasOwnerAccess).toHaveBeenCalledTimes(2); expect(shareMock.update).toHaveBeenCalledWith({ ...sharedLinkStub.individual, - assets: [assetEntityStub.image, { id: 'asset-3' }], + assets: [assetStub.image, { id: 'asset-3' }], }); }); }); @@ -239,9 +239,9 @@ describe(SharedLinkService.name, () => { shareMock.create.mockResolvedValue(sharedLinkStub.individual); await expect( - sut.removeAssets(authStub.admin, 'link-1', { assetIds: [assetEntityStub.image.id, 'asset-2'] }), + sut.removeAssets(authStub.admin, 'link-1', { assetIds: [assetStub.image.id, 'asset-2'] }), ).resolves.toEqual([ - { assetId: assetEntityStub.image.id, success: true }, + { assetId: assetStub.image.id, success: true }, { assetId: 'asset-2', success: false, error: AssetIdErrorReason.NOT_FOUND }, ]); diff --git a/server/src/domain/smart-info/smart-info.service.spec.ts b/server/src/domain/smart-info/smart-info.service.spec.ts index 2250d1796a..f6464cb021 100644 --- a/server/src/domain/smart-info/smart-info.service.spec.ts +++ b/server/src/domain/smart-info/smart-info.service.spec.ts @@ -1,6 +1,6 @@ import { AssetEntity } from '@app/infra/entities'; import { - assetEntityStub, + assetStub, newAssetRepositoryMock, newJobRepositoryMock, newMachineLearningRepositoryMock, @@ -41,29 +41,25 @@ describe(SmartInfoService.name, () => { describe('handleQueueObjectTagging', () => { it('should queue the assets without tags', async () => { assetMock.getWithout.mockResolvedValue({ - items: [assetEntityStub.image], + items: [assetStub.image], hasNextPage: false, }); await sut.handleQueueObjectTagging({ force: false }); - expect(jobMock.queue.mock.calls).toEqual([ - [{ name: JobName.CLASSIFY_IMAGE, data: { id: assetEntityStub.image.id } }], - ]); + expect(jobMock.queue.mock.calls).toEqual([[{ name: JobName.CLASSIFY_IMAGE, data: { id: assetStub.image.id } }]]); expect(assetMock.getWithout).toHaveBeenCalledWith({ skip: 0, take: 1000 }, WithoutProperty.OBJECT_TAGS); }); it('should queue all the assets', async () => { assetMock.getAll.mockResolvedValue({ - items: [assetEntityStub.image], + items: [assetStub.image], hasNextPage: false, }); await sut.handleQueueObjectTagging({ force: true }); - expect(jobMock.queue.mock.calls).toEqual([ - [{ name: JobName.CLASSIFY_IMAGE, data: { id: assetEntityStub.image.id } }], - ]); + expect(jobMock.queue.mock.calls).toEqual([[{ name: JobName.CLASSIFY_IMAGE, data: { id: assetStub.image.id } }]]); expect(assetMock.getAll).toHaveBeenCalled(); }); }); @@ -104,25 +100,25 @@ describe(SmartInfoService.name, () => { describe('handleQueueEncodeClip', () => { it('should queue the assets without clip embeddings', async () => { assetMock.getWithout.mockResolvedValue({ - items: [assetEntityStub.image], + items: [assetStub.image], hasNextPage: false, }); await sut.handleQueueEncodeClip({ force: false }); - expect(jobMock.queue).toHaveBeenCalledWith({ name: JobName.ENCODE_CLIP, data: { id: assetEntityStub.image.id } }); + expect(jobMock.queue).toHaveBeenCalledWith({ name: JobName.ENCODE_CLIP, data: { id: assetStub.image.id } }); expect(assetMock.getWithout).toHaveBeenCalledWith({ skip: 0, take: 1000 }, WithoutProperty.CLIP_ENCODING); }); it('should queue all the assets', async () => { assetMock.getAll.mockResolvedValue({ - items: [assetEntityStub.image], + items: [assetStub.image], hasNextPage: false, }); await sut.handleQueueEncodeClip({ force: true }); - expect(jobMock.queue).toHaveBeenCalledWith({ name: JobName.ENCODE_CLIP, data: { id: assetEntityStub.image.id } }); + expect(jobMock.queue).toHaveBeenCalledWith({ name: JobName.ENCODE_CLIP, data: { id: assetStub.image.id } }); expect(assetMock.getAll).toHaveBeenCalled(); }); }); diff --git a/server/src/domain/storage-template/storage-template.service.spec.ts b/server/src/domain/storage-template/storage-template.service.spec.ts index bef63d85ac..a7110dc8e7 100644 --- a/server/src/domain/storage-template/storage-template.service.spec.ts +++ b/server/src/domain/storage-template/storage-template.service.spec.ts @@ -1,10 +1,10 @@ import { - assetEntityStub, + assetStub, newAssetRepositoryMock, newStorageRepositoryMock, newSystemConfigRepositoryMock, newUserRepositoryMock, - userEntityStub, + userStub, } from '@test'; import { when } from 'jest-when'; import { StorageTemplateService } from '.'; @@ -49,11 +49,11 @@ describe(StorageTemplateService.name, () => { it('should handle an asset with a duplicate destination', async () => { assetMock.getAll.mockResolvedValue({ - items: [assetEntityStub.image], + items: [assetStub.image], hasNextPage: false, }); - assetMock.save.mockResolvedValue(assetEntityStub.image); - userMock.getList.mockResolvedValue([userEntityStub.user1]); + assetMock.save.mockResolvedValue(assetStub.image); + userMock.getList.mockResolvedValue([userStub.user1]); when(storageMock.checkFileExists) .calledWith('upload/library/user-id/2023/2023-02-23/asset-id.jpg') @@ -68,7 +68,7 @@ describe(StorageTemplateService.name, () => { expect(assetMock.getAll).toHaveBeenCalled(); expect(storageMock.checkFileExists).toHaveBeenCalledTimes(2); expect(assetMock.save).toHaveBeenCalledWith({ - id: assetEntityStub.image.id, + id: assetStub.image.id, originalPath: 'upload/library/user-id/2023/2023-02-23/asset-id+1.jpg', }); expect(userMock.getList).toHaveBeenCalled(); @@ -78,13 +78,13 @@ describe(StorageTemplateService.name, () => { assetMock.getAll.mockResolvedValue({ items: [ { - ...assetEntityStub.image, + ...assetStub.image, originalPath: 'upload/library/user-id/2023/2023-02-23/asset-id.jpg', }, ], hasNextPage: false, }); - userMock.getList.mockResolvedValue([userEntityStub.user1]); + userMock.getList.mockResolvedValue([userStub.user1]); await sut.handleMigration(); @@ -98,13 +98,13 @@ describe(StorageTemplateService.name, () => { assetMock.getAll.mockResolvedValue({ items: [ { - ...assetEntityStub.image, + ...assetStub.image, originalPath: 'upload/library/user-id/2023/2023-02-23/asset-id+1.jpg', }, ], hasNextPage: false, }); - userMock.getList.mockResolvedValue([userEntityStub.user1]); + userMock.getList.mockResolvedValue([userStub.user1]); await sut.handleMigration(); @@ -116,11 +116,11 @@ describe(StorageTemplateService.name, () => { it('should move an asset', async () => { assetMock.getAll.mockResolvedValue({ - items: [assetEntityStub.image], + items: [assetStub.image], hasNextPage: false, }); - assetMock.save.mockResolvedValue(assetEntityStub.image); - userMock.getList.mockResolvedValue([userEntityStub.user1]); + assetMock.save.mockResolvedValue(assetStub.image); + userMock.getList.mockResolvedValue([userStub.user1]); await sut.handleMigration(); @@ -130,18 +130,18 @@ describe(StorageTemplateService.name, () => { 'upload/library/user-id/2023/2023-02-23/asset-id.jpg', ); expect(assetMock.save).toHaveBeenCalledWith({ - id: assetEntityStub.image.id, + id: assetStub.image.id, originalPath: 'upload/library/user-id/2023/2023-02-23/asset-id.jpg', }); }); it('should use the user storage label', async () => { assetMock.getAll.mockResolvedValue({ - items: [assetEntityStub.image], + items: [assetStub.image], hasNextPage: false, }); - assetMock.save.mockResolvedValue(assetEntityStub.image); - userMock.getList.mockResolvedValue([userEntityStub.storageLabel]); + assetMock.save.mockResolvedValue(assetStub.image); + userMock.getList.mockResolvedValue([userStub.storageLabel]); await sut.handleMigration(); @@ -151,18 +151,18 @@ describe(StorageTemplateService.name, () => { 'upload/library/label-1/2023/2023-02-23/asset-id.jpg', ); expect(assetMock.save).toHaveBeenCalledWith({ - id: assetEntityStub.image.id, + id: assetStub.image.id, originalPath: 'upload/library/label-1/2023/2023-02-23/asset-id.jpg', }); }); it('should not update the database if the move fails', async () => { assetMock.getAll.mockResolvedValue({ - items: [assetEntityStub.image], + items: [assetStub.image], hasNextPage: false, }); storageMock.moveFile.mockRejectedValue(new Error('Read only system')); - userMock.getList.mockResolvedValue([userEntityStub.user1]); + userMock.getList.mockResolvedValue([userStub.user1]); await sut.handleMigration(); @@ -176,17 +176,17 @@ describe(StorageTemplateService.name, () => { it('should move the asset back if the database fails', async () => { assetMock.getAll.mockResolvedValue({ - items: [assetEntityStub.image], + items: [assetStub.image], hasNextPage: false, }); assetMock.save.mockRejectedValue('Connection Error!'); - userMock.getList.mockResolvedValue([userEntityStub.user1]); + userMock.getList.mockResolvedValue([userStub.user1]); await sut.handleMigration(); expect(assetMock.getAll).toHaveBeenCalled(); expect(assetMock.save).toHaveBeenCalledWith({ - id: assetEntityStub.image.id, + id: assetStub.image.id, originalPath: 'upload/library/user-id/2023/2023-02-23/asset-id.jpg', }); expect(storageMock.moveFile.mock.calls).toEqual([ @@ -199,15 +199,15 @@ describe(StorageTemplateService.name, () => { assetMock.getAll.mockResolvedValue({ items: [ { - ...assetEntityStub.image, + ...assetStub.image, originalPath: 'upload/library/user-id/2023/2023-02-23/asset-id+1.jpg', isReadOnly: true, }, ], hasNextPage: false, }); - assetMock.save.mockResolvedValue(assetEntityStub.image); - userMock.getList.mockResolvedValue([userEntityStub.user1]); + assetMock.save.mockResolvedValue(assetStub.image); + userMock.getList.mockResolvedValue([userStub.user1]); await sut.handleMigration(); diff --git a/server/src/domain/tag/tag.service.spec.ts b/server/src/domain/tag/tag.service.spec.ts index 7b6b4f8638..cc37b9c687 100644 --- a/server/src/domain/tag/tag.service.spec.ts +++ b/server/src/domain/tag/tag.service.spec.ts @@ -1,6 +1,6 @@ import { TagType } from '@app/infra/entities'; import { BadRequestException } from '@nestjs/common'; -import { assetEntityStub, authStub, newTagRepositoryMock, tagResponseStub, tagStub } from '@test'; +import { assetStub, authStub, newTagRepositoryMock, tagResponseStub, tagStub } from '@test'; import { when } from 'jest-when'; import { AssetIdErrorReason } from '../asset'; import { ITagRepository } from './tag.repository'; @@ -107,7 +107,7 @@ describe(TagService.name, () => { it('should get the assets for a tag', async () => { tagMock.getById.mockResolvedValue(tagStub.tag1); - tagMock.getAssets.mockResolvedValue([assetEntityStub.image]); + tagMock.getAssets.mockResolvedValue([assetStub.image]); await sut.getAssets(authStub.admin, 'tag-1'); expect(tagMock.getById).toHaveBeenCalledWith(authStub.admin.id, 'tag-1'); expect(tagMock.getAssets).toHaveBeenCalledWith(authStub.admin.id, 'tag-1'); diff --git a/server/src/immich/api-v1/album/album.service.spec.ts b/server/src/immich/api-v1/album/album.service.spec.ts index 89275b9687..98edf6c8f2 100644 --- a/server/src/immich/api-v1/album/album.service.spec.ts +++ b/server/src/immich/api-v1/album/album.service.spec.ts @@ -1,7 +1,7 @@ import { AlbumResponseDto, AuthUserDto, mapUser } from '@app/domain'; import { AlbumEntity, UserEntity } from '@app/infra/entities'; import { ForbiddenException, NotFoundException } from '@nestjs/common'; -import { userEntityStub } from '@test'; +import { userStub } from '@test'; import { IAlbumRepository } from './album-repository'; import { AlbumService } from './album.service'; import { AddAssetsResponseDto } from './response-dto/add-assets-response.dto'; @@ -61,11 +61,11 @@ describe('Album service', () => { albumEntity.albumThumbnailAssetId = null; albumEntity.sharedUsers = [ { - ...userEntityStub.user1, + ...userStub.user1, id: authUser.id, }, { - ...userEntityStub.user1, + ...userStub.user1, id: sharedAlbumSharedAlsoWithId, }, ]; diff --git a/server/src/immich/api-v1/asset/asset.service.spec.ts b/server/src/immich/api-v1/asset/asset.service.spec.ts index 913de5a87f..bf7441b4ad 100644 --- a/server/src/immich/api-v1/asset/asset.service.spec.ts +++ b/server/src/immich/api-v1/asset/asset.service.spec.ts @@ -2,7 +2,7 @@ import { ICryptoRepository, IJobRepository, IStorageRepository, JobName } from ' import { AssetEntity, AssetType, ExifEntity } from '@app/infra/entities'; import { BadRequestException } from '@nestjs/common'; import { - assetEntityStub, + assetStub, authStub, fileStub, IAccessRepositoryMock, @@ -132,11 +132,11 @@ describe('AssetService', () => { sut = new AssetService(accessMock, assetRepositoryMock, a, cryptoMock, jobMock, storageMock); when(assetRepositoryMock.get) - .calledWith(assetEntityStub.livePhotoStillAsset.id) - .mockResolvedValue(assetEntityStub.livePhotoStillAsset); + .calledWith(assetStub.livePhotoStillAsset.id) + .mockResolvedValue(assetStub.livePhotoStillAsset); when(assetRepositoryMock.get) - .calledWith(assetEntityStub.livePhotoMotionAsset.id) - .mockResolvedValue(assetEntityStub.livePhotoMotionAsset); + .calledWith(assetStub.livePhotoMotionAsset.id) + .mockResolvedValue(assetStub.livePhotoMotionAsset); }); describe('uploadFile', () => { @@ -185,8 +185,8 @@ describe('AssetService', () => { const error = new QueryFailedError('', [], ''); (error as any).constraint = 'UQ_userid_checksum'; - assetRepositoryMock.create.mockResolvedValueOnce(assetEntityStub.livePhotoMotionAsset); - assetRepositoryMock.create.mockResolvedValueOnce(assetEntityStub.livePhotoStillAsset); + assetRepositoryMock.create.mockResolvedValueOnce(assetStub.livePhotoMotionAsset); + assetRepositoryMock.create.mockResolvedValueOnce(assetStub.livePhotoStillAsset); await expect( sut.uploadFile(authStub.user1, dto, fileStub.livePhotoStill, fileStub.livePhotoMotion), @@ -199,10 +199,10 @@ describe('AssetService', () => { [ { name: JobName.METADATA_EXTRACTION, - data: { id: assetEntityStub.livePhotoMotionAsset.id, source: 'upload' }, + data: { id: assetStub.livePhotoMotionAsset.id, source: 'upload' }, }, ], - [{ name: JobName.METADATA_EXTRACTION, data: { id: assetEntityStub.livePhotoStillAsset.id, source: 'upload' } }], + [{ name: JobName.METADATA_EXTRACTION, data: { id: assetStub.livePhotoStillAsset.id, source: 'upload' } }], ]); }); }); @@ -263,9 +263,9 @@ describe('AssetService', () => { it('should delete a live photo', async () => { accessMock.asset.hasOwnerAccess.mockResolvedValue(true); - await expect(sut.deleteAll(authStub.user1, { ids: [assetEntityStub.livePhotoStillAsset.id] })).resolves.toEqual([ - { id: assetEntityStub.livePhotoStillAsset.id, status: 'SUCCESS' }, - { id: assetEntityStub.livePhotoMotionAsset.id, status: 'SUCCESS' }, + await expect(sut.deleteAll(authStub.user1, { ids: [assetStub.livePhotoStillAsset.id] })).resolves.toEqual([ + { id: assetStub.livePhotoStillAsset.id, status: 'SUCCESS' }, + { id: assetStub.livePhotoMotionAsset.id, status: 'SUCCESS' }, ]); expect(jobMock.queue).toHaveBeenCalledWith({ @@ -373,7 +373,7 @@ describe('AssetService', () => { describe('importFile', () => { it('should handle a file import', async () => { - assetRepositoryMock.create.mockResolvedValue(assetEntityStub.image); + assetRepositoryMock.create.mockResolvedValue(assetStub.image); storageMock.checkFileExists.mockResolvedValue(true); await expect( @@ -392,7 +392,7 @@ describe('AssetService', () => { (error as any).constraint = 'UQ_userid_checksum'; assetRepositoryMock.create.mockRejectedValue(error); - assetRepositoryMock.getAssetsByChecksums.mockResolvedValue([assetEntityStub.image]); + assetRepositoryMock.getAssetsByChecksums.mockResolvedValue([assetStub.image]); storageMock.checkFileExists.mockResolvedValue(true); cryptoMock.hashFile.mockResolvedValue(Buffer.from('file hash', 'utf8')); @@ -411,36 +411,36 @@ describe('AssetService', () => { describe('getAssetById', () => { it('should allow owner access', async () => { accessMock.asset.hasOwnerAccess.mockResolvedValue(true); - assetRepositoryMock.getById.mockResolvedValue(assetEntityStub.image); - await sut.getAssetById(authStub.admin, assetEntityStub.image.id); - expect(accessMock.asset.hasOwnerAccess).toHaveBeenCalledWith(authStub.admin.id, assetEntityStub.image.id); + assetRepositoryMock.getById.mockResolvedValue(assetStub.image); + await sut.getAssetById(authStub.admin, assetStub.image.id); + expect(accessMock.asset.hasOwnerAccess).toHaveBeenCalledWith(authStub.admin.id, assetStub.image.id); }); it('should allow shared link access', async () => { accessMock.asset.hasSharedLinkAccess.mockResolvedValue(true); - assetRepositoryMock.getById.mockResolvedValue(assetEntityStub.image); - await sut.getAssetById(authStub.adminSharedLink, assetEntityStub.image.id); + assetRepositoryMock.getById.mockResolvedValue(assetStub.image); + await sut.getAssetById(authStub.adminSharedLink, assetStub.image.id); expect(accessMock.asset.hasSharedLinkAccess).toHaveBeenCalledWith( authStub.adminSharedLink.sharedLinkId, - assetEntityStub.image.id, + assetStub.image.id, ); }); it('should allow partner sharing access', async () => { accessMock.asset.hasOwnerAccess.mockResolvedValue(false); accessMock.asset.hasPartnerAccess.mockResolvedValue(true); - assetRepositoryMock.getById.mockResolvedValue(assetEntityStub.image); - await sut.getAssetById(authStub.admin, assetEntityStub.image.id); - expect(accessMock.asset.hasPartnerAccess).toHaveBeenCalledWith(authStub.admin.id, assetEntityStub.image.id); + assetRepositoryMock.getById.mockResolvedValue(assetStub.image); + await sut.getAssetById(authStub.admin, assetStub.image.id); + expect(accessMock.asset.hasPartnerAccess).toHaveBeenCalledWith(authStub.admin.id, assetStub.image.id); }); it('should allow shared album access', async () => { accessMock.asset.hasOwnerAccess.mockResolvedValue(false); accessMock.asset.hasPartnerAccess.mockResolvedValue(false); accessMock.asset.hasAlbumAccess.mockResolvedValue(true); - assetRepositoryMock.getById.mockResolvedValue(assetEntityStub.image); - await sut.getAssetById(authStub.admin, assetEntityStub.image.id); - expect(accessMock.asset.hasAlbumAccess).toHaveBeenCalledWith(authStub.admin.id, assetEntityStub.image.id); + assetRepositoryMock.getById.mockResolvedValue(assetStub.image); + await sut.getAssetById(authStub.admin, assetStub.image.id); + expect(accessMock.asset.hasAlbumAccess).toHaveBeenCalledWith(authStub.admin.id, assetStub.image.id); }); it('should throw an error for no access', async () => { @@ -448,15 +448,13 @@ describe('AssetService', () => { accessMock.asset.hasPartnerAccess.mockResolvedValue(false); accessMock.asset.hasSharedLinkAccess.mockResolvedValue(false); accessMock.asset.hasAlbumAccess.mockResolvedValue(false); - await expect(sut.getAssetById(authStub.admin, assetEntityStub.image.id)).rejects.toBeInstanceOf( - BadRequestException, - ); + await expect(sut.getAssetById(authStub.admin, assetStub.image.id)).rejects.toBeInstanceOf(BadRequestException); expect(assetRepositoryMock.getById).not.toHaveBeenCalled(); }); it('should throw an error for an invalid shared link', async () => { accessMock.asset.hasSharedLinkAccess.mockResolvedValue(false); - await expect(sut.getAssetById(authStub.adminSharedLink, assetEntityStub.image.id)).rejects.toBeInstanceOf( + await expect(sut.getAssetById(authStub.adminSharedLink, assetStub.image.id)).rejects.toBeInstanceOf( BadRequestException, ); expect(accessMock.asset.hasOwnerAccess).not.toHaveBeenCalled(); diff --git a/server/test/fixtures.ts b/server/test/fixtures.ts deleted file mode 100644 index 54da56961b..0000000000 --- a/server/test/fixtures.ts +++ /dev/null @@ -1,1262 +0,0 @@ -import { - AlbumResponseDto, - AssetResponseDto, - AudioStreamInfo, - AuthUserDto, - ExifResponseDto, - mapUser, - SearchResult, - SharedLinkResponseDto, - TagResponseDto, - VideoFormat, - VideoInfo, - VideoStreamInfo, -} from '@app/domain'; -import { - AlbumEntity, - APIKeyEntity, - AssetEntity, - AssetFaceEntity, - AssetType, - ExifEntity, - PartnerEntity, - PersonEntity, - SharedLinkEntity, - SharedLinkType, - SystemConfigEntity, - SystemConfigKey, - TagEntity, - TagType, - UserEntity, - UserTokenEntity, -} from '@app/infra/entities'; - -const today = new Date(); -const tomorrow = new Date(); -const yesterday = new Date(); -tomorrow.setDate(today.getDate() + 1); -yesterday.setDate(yesterday.getDate() - 1); - -const sharedLinkBytes = Buffer.from( - '2c2b646895f84753bff43fb696ad124f3b0faf2a0bd547406f26fa4a76b5c71990092baa536275654b2ab7a191fb21a6d6cd', - 'hex', -); - -export const authStub = { - admin: Object.freeze({ - id: 'admin_id', - email: 'admin@test.com', - isAdmin: true, - isPublicUser: false, - isAllowUpload: true, - externalPath: null, - }), - user1: Object.freeze({ - id: 'user-id', - email: 'immich@test.com', - isAdmin: false, - isPublicUser: false, - isAllowUpload: true, - isAllowDownload: true, - isShowExif: true, - accessTokenId: 'token-id', - externalPath: null, - }), - user2: Object.freeze({ - id: 'user-2', - email: 'user2@immich.app', - isAdmin: false, - isPublicUser: false, - isAllowUpload: true, - isAllowDownload: true, - isShowExif: true, - accessTokenId: 'token-id', - externalPath: null, - }), - external1: Object.freeze({ - id: 'user-id', - email: 'immich@test.com', - isAdmin: false, - isPublicUser: false, - isAllowUpload: true, - isAllowDownload: true, - isShowExif: true, - accessTokenId: 'token-id', - externalPath: '/data/user1', - }), - adminSharedLink: Object.freeze({ - id: 'admin_id', - email: 'admin@test.com', - isAdmin: true, - isAllowUpload: true, - isAllowDownload: true, - isPublicUser: true, - isShowExif: true, - sharedLinkId: '123', - }), - adminSharedLinkNoExif: Object.freeze({ - id: 'admin_id', - email: 'admin@test.com', - isAdmin: true, - isAllowUpload: true, - isAllowDownload: true, - isPublicUser: true, - isShowExif: false, - sharedLinkId: '123', - }), - readonlySharedLink: Object.freeze({ - id: 'admin_id', - email: 'admin@test.com', - isAdmin: true, - isAllowUpload: false, - isAllowDownload: false, - isPublicUser: true, - isShowExif: true, - sharedLinkId: '123', - accessTokenId: 'token-id', - }), -}; - -export const userEntityStub = { - admin: Object.freeze({ - ...authStub.admin, - password: 'admin_password', - firstName: 'admin_first_name', - lastName: 'admin_last_name', - storageLabel: 'admin', - externalPath: null, - oauthId: '', - shouldChangePassword: false, - profileImagePath: '', - createdAt: new Date('2021-01-01'), - deletedAt: null, - updatedAt: new Date('2021-01-01'), - tags: [], - assets: [], - }), - user1: Object.freeze({ - ...authStub.user1, - password: 'immich_password', - firstName: 'immich_first_name', - lastName: 'immich_last_name', - storageLabel: null, - externalPath: null, - oauthId: '', - shouldChangePassword: false, - profileImagePath: '', - createdAt: new Date('2021-01-01'), - deletedAt: null, - updatedAt: new Date('2021-01-01'), - tags: [], - assets: [], - }), - user2: Object.freeze({ - ...authStub.user2, - password: 'immich_password', - firstName: 'immich_first_name', - lastName: 'immich_last_name', - storageLabel: null, - externalPath: null, - oauthId: '', - shouldChangePassword: false, - profileImagePath: '', - createdAt: new Date('2021-01-01'), - deletedAt: null, - updatedAt: new Date('2021-01-01'), - tags: [], - assets: [], - }), - storageLabel: Object.freeze({ - ...authStub.user1, - password: 'immich_password', - firstName: 'immich_first_name', - lastName: 'immich_last_name', - storageLabel: 'label-1', - externalPath: null, - oauthId: '', - shouldChangePassword: false, - profileImagePath: '', - createdAt: new Date('2021-01-01'), - deletedAt: null, - updatedAt: new Date('2021-01-01'), - tags: [], - assets: [], - }), -}; - -export const fileStub = { - livePhotoStill: Object.freeze({ - originalPath: 'fake_path/asset_1.jpeg', - checksum: Buffer.from('file hash', 'utf8'), - originalName: 'asset_1.jpeg', - }), - livePhotoMotion: Object.freeze({ - originalPath: 'fake_path/asset_1.mp4', - checksum: Buffer.from('live photo file hash', 'utf8'), - originalName: 'asset_1.mp4', - }), -}; - -export const assetEntityStub = { - noResizePath: Object.freeze({ - id: 'asset-id', - originalFileName: 'IMG_123', - deviceAssetId: 'device-asset-id', - fileModifiedAt: new Date('2023-02-23T05:06:29.716Z'), - fileCreatedAt: new Date('2023-02-23T05:06:29.716Z'), - owner: userEntityStub.user1, - ownerId: 'user-id', - deviceId: 'device-id', - originalPath: 'upload/library/IMG_123.jpg', - resizePath: null, - checksum: Buffer.from('file hash', 'utf8'), - type: AssetType.IMAGE, - webpPath: '/uploads/user-id/webp/path.ext', - thumbhash: Buffer.from('blablabla', 'base64'), - encodedVideoPath: null, - createdAt: new Date('2023-02-23T05:06:29.716Z'), - updatedAt: new Date('2023-02-23T05:06:29.716Z'), - isFavorite: true, - isArchived: false, - duration: null, - isVisible: true, - livePhotoVideo: null, - livePhotoVideoId: null, - tags: [], - sharedLinks: [], - faces: [], - sidecarPath: null, - isReadOnly: false, - }), - noWebpPath: Object.freeze({ - id: 'asset-id', - deviceAssetId: 'device-asset-id', - fileModifiedAt: new Date('2023-02-23T05:06:29.716Z'), - fileCreatedAt: new Date('2023-02-23T05:06:29.716Z'), - owner: userEntityStub.user1, - ownerId: 'user-id', - deviceId: 'device-id', - originalPath: 'upload/library/IMG_456.jpg', - resizePath: '/uploads/user-id/thumbs/path.ext', - checksum: Buffer.from('file hash', 'utf8'), - type: AssetType.IMAGE, - webpPath: null, - thumbhash: Buffer.from('blablabla', 'base64'), - encodedVideoPath: null, - createdAt: new Date('2023-02-23T05:06:29.716Z'), - updatedAt: new Date('2023-02-23T05:06:29.716Z'), - isFavorite: true, - isArchived: false, - duration: null, - isVisible: true, - livePhotoVideo: null, - livePhotoVideoId: null, - tags: [], - sharedLinks: [], - originalFileName: 'IMG_456', - faces: [], - sidecarPath: null, - isReadOnly: false, - exifInfo: { - fileSizeInByte: 123_000, - } as ExifEntity, - }), - noThumbhash: Object.freeze({ - id: 'asset-id', - deviceAssetId: 'device-asset-id', - fileModifiedAt: new Date('2023-02-23T05:06:29.716Z'), - fileCreatedAt: new Date('2023-02-23T05:06:29.716Z'), - owner: userEntityStub.user1, - ownerId: 'user-id', - deviceId: 'device-id', - originalPath: '/original/path.ext', - resizePath: '/uploads/user-id/thumbs/path.ext', - checksum: Buffer.from('file hash', 'utf8'), - type: AssetType.IMAGE, - webpPath: '/uploads/user-id/webp/path.ext', - thumbhash: null, - encodedVideoPath: null, - createdAt: new Date('2023-02-23T05:06:29.716Z'), - updatedAt: new Date('2023-02-23T05:06:29.716Z'), - isFavorite: true, - isArchived: false, - isReadOnly: false, - duration: null, - isVisible: true, - livePhotoVideo: null, - livePhotoVideoId: null, - tags: [], - sharedLinks: [], - originalFileName: 'asset-id.ext', - faces: [], - sidecarPath: null, - }), - image: Object.freeze({ - id: 'asset-id', - deviceAssetId: 'device-asset-id', - fileModifiedAt: new Date('2023-02-23T05:06:29.716Z'), - fileCreatedAt: new Date('2023-02-23T05:06:29.716Z'), - owner: userEntityStub.user1, - ownerId: 'user-id', - deviceId: 'device-id', - originalPath: '/original/path.jpg', - resizePath: '/uploads/user-id/thumbs/path.jpg', - checksum: Buffer.from('file hash', 'utf8'), - type: AssetType.IMAGE, - webpPath: '/uploads/user-id/webp/path.ext', - thumbhash: Buffer.from('blablabla', 'base64'), - encodedVideoPath: null, - createdAt: new Date('2023-02-23T05:06:29.716Z'), - updatedAt: new Date('2023-02-23T05:06:29.716Z'), - isFavorite: true, - isArchived: false, - isReadOnly: false, - duration: null, - isVisible: true, - livePhotoVideo: null, - livePhotoVideoId: null, - tags: [], - sharedLinks: [], - originalFileName: 'asset-id.jpg', - faces: [], - sidecarPath: null, - exifInfo: { - fileSizeInByte: 5_000, - } as ExifEntity, - }), - image1: Object.freeze({ - id: 'asset-id-1', - deviceAssetId: 'device-asset-id', - fileModifiedAt: new Date('2023-02-23T05:06:29.716Z'), - fileCreatedAt: new Date('2023-02-23T05:06:29.716Z'), - owner: userEntityStub.user1, - ownerId: 'user-id', - deviceId: 'device-id', - originalPath: '/original/path.ext', - resizePath: '/uploads/user-id/thumbs/path.ext', - checksum: Buffer.from('file hash', 'utf8'), - type: AssetType.IMAGE, - webpPath: '/uploads/user-id/webp/path.ext', - thumbhash: Buffer.from('blablabla', 'base64'), - encodedVideoPath: null, - createdAt: new Date('2023-02-23T05:06:29.716Z'), - updatedAt: new Date('2023-02-23T05:06:29.716Z'), - isFavorite: true, - isArchived: false, - isReadOnly: false, - duration: null, - isVisible: true, - livePhotoVideo: null, - livePhotoVideoId: null, - tags: [], - sharedLinks: [], - originalFileName: 'asset-id.ext', - faces: [], - sidecarPath: null, - exifInfo: { - fileSizeInByte: 5_000, - } as ExifEntity, - }), - video: Object.freeze({ - id: 'asset-id', - originalFileName: 'asset-id.ext', - deviceAssetId: 'device-asset-id', - fileModifiedAt: new Date('2023-02-23T05:06:29.716Z'), - fileCreatedAt: new Date('2023-02-23T05:06:29.716Z'), - owner: userEntityStub.user1, - ownerId: 'user-id', - deviceId: 'device-id', - originalPath: '/original/path.ext', - resizePath: '/uploads/user-id/thumbs/path.ext', - checksum: Buffer.from('file hash', 'utf8'), - type: AssetType.VIDEO, - webpPath: null, - thumbhash: null, - encodedVideoPath: null, - createdAt: new Date('2023-02-23T05:06:29.716Z'), - updatedAt: new Date('2023-02-23T05:06:29.716Z'), - isFavorite: true, - isArchived: false, - isReadOnly: false, - duration: null, - isVisible: true, - livePhotoVideo: null, - livePhotoVideoId: null, - tags: [], - sharedLinks: [], - faces: [], - sidecarPath: null, - exifInfo: { - fileSizeInByte: 100_000, - } as ExifEntity, - }), - livePhotoMotionAsset: Object.freeze({ - id: 'live-photo-motion-asset', - originalPath: fileStub.livePhotoMotion.originalPath, - ownerId: authStub.user1.id, - type: AssetType.VIDEO, - isVisible: false, - fileModifiedAt: new Date('2022-06-19T23:41:36.910Z'), - fileCreatedAt: new Date('2022-06-19T23:41:36.910Z'), - exifInfo: { - fileSizeInByte: 100_000, - }, - } as AssetEntity), - - livePhotoStillAsset: Object.freeze({ - id: 'live-photo-still-asset', - originalPath: fileStub.livePhotoStill.originalPath, - ownerId: authStub.user1.id, - type: AssetType.IMAGE, - livePhotoVideoId: 'live-photo-motion-asset', - isVisible: true, - fileModifiedAt: new Date('2022-06-19T23:41:36.910Z'), - fileCreatedAt: new Date('2022-06-19T23:41:36.910Z'), - exifInfo: { - fileSizeInByte: 25_000, - }, - } as AssetEntity), - - withLocation: Object.freeze({ - id: 'asset-with-favorite-id', - deviceAssetId: 'device-asset-id', - fileModifiedAt: new Date('2023-02-23T05:06:29.716Z'), - fileCreatedAt: new Date('2023-02-23T05:06:29.716Z'), - owner: userEntityStub.user1, - ownerId: 'user-id', - deviceId: 'device-id', - checksum: Buffer.from('file hash', 'utf8'), - originalPath: '/original/path.ext', - resizePath: '/uploads/user-id/thumbs/path.ext', - sidecarPath: null, - type: AssetType.IMAGE, - webpPath: null, - thumbhash: null, - encodedVideoPath: null, - createdAt: new Date('2023-02-23T05:06:29.716Z'), - updatedAt: new Date('2023-02-23T05:06:29.716Z'), - isFavorite: false, - isArchived: false, - isReadOnly: false, - duration: null, - isVisible: true, - livePhotoVideo: null, - livePhotoVideoId: null, - tags: [], - sharedLinks: [], - originalFileName: 'asset-id.ext', - faces: [], - exifInfo: { - latitude: 100, - longitude: 100, - fileSizeInByte: 23_456, - } as ExifEntity, - }), - sidecar: Object.freeze({ - id: 'asset-id', - deviceAssetId: 'device-asset-id', - fileModifiedAt: new Date('2023-02-23T05:06:29.716Z'), - fileCreatedAt: new Date('2023-02-23T05:06:29.716Z'), - owner: userEntityStub.user1, - ownerId: 'user-id', - deviceId: 'device-id', - originalPath: '/original/path.ext', - resizePath: '/uploads/user-id/thumbs/path.ext', - thumbhash: null, - checksum: Buffer.from('file hash', 'utf8'), - type: AssetType.IMAGE, - webpPath: null, - encodedVideoPath: null, - createdAt: new Date('2023-02-23T05:06:29.716Z'), - updatedAt: new Date('2023-02-23T05:06:29.716Z'), - isFavorite: true, - isArchived: false, - isReadOnly: false, - duration: null, - isVisible: true, - livePhotoVideo: null, - livePhotoVideoId: null, - tags: [], - sharedLinks: [], - originalFileName: 'asset-id.ext', - faces: [], - sidecarPath: '/original/path.ext.xmp', - }), -}; - -export const albumStub = { - empty: Object.freeze({ - id: 'album-1', - albumName: 'Empty album', - ownerId: authStub.admin.id, - owner: userEntityStub.admin, - assets: [], - albumThumbnailAsset: null, - albumThumbnailAssetId: null, - createdAt: new Date(), - updatedAt: new Date(), - sharedLinks: [], - sharedUsers: [], - }), - sharedWithUser: Object.freeze({ - id: 'album-2', - albumName: 'Empty album shared with user', - ownerId: authStub.admin.id, - owner: userEntityStub.admin, - assets: [], - albumThumbnailAsset: null, - albumThumbnailAssetId: null, - createdAt: new Date(), - updatedAt: new Date(), - sharedLinks: [], - sharedUsers: [userEntityStub.user1], - }), - sharedWithMultiple: Object.freeze({ - id: 'album-3', - albumName: 'Empty album shared with users', - ownerId: authStub.admin.id, - owner: userEntityStub.admin, - assets: [], - albumThumbnailAsset: null, - albumThumbnailAssetId: null, - createdAt: new Date(), - updatedAt: new Date(), - sharedLinks: [], - sharedUsers: [userEntityStub.user1, userEntityStub.user2], - }), - sharedWithAdmin: Object.freeze({ - id: 'album-3', - albumName: 'Empty album shared with admin', - ownerId: authStub.user1.id, - owner: userEntityStub.user1, - assets: [], - albumThumbnailAsset: null, - albumThumbnailAssetId: null, - createdAt: new Date(), - updatedAt: new Date(), - sharedLinks: [], - sharedUsers: [userEntityStub.admin], - }), - oneAsset: Object.freeze({ - id: 'album-4', - albumName: 'Album with one asset', - ownerId: authStub.admin.id, - owner: userEntityStub.admin, - assets: [assetEntityStub.image], - albumThumbnailAsset: null, - albumThumbnailAssetId: null, - createdAt: new Date(), - updatedAt: new Date(), - sharedLinks: [], - sharedUsers: [], - }), - emptyWithInvalidThumbnail: Object.freeze({ - id: 'album-5', - albumName: 'Empty album with invalid thumbnail', - ownerId: authStub.admin.id, - owner: userEntityStub.admin, - assets: [], - albumThumbnailAsset: assetEntityStub.image, - albumThumbnailAssetId: assetEntityStub.image.id, - createdAt: new Date(), - updatedAt: new Date(), - sharedLinks: [], - sharedUsers: [], - }), - emptyWithValidThumbnail: Object.freeze({ - id: 'album-5', - albumName: 'Empty album with invalid thumbnail', - ownerId: authStub.admin.id, - owner: userEntityStub.admin, - assets: [], - albumThumbnailAsset: null, - albumThumbnailAssetId: null, - createdAt: new Date(), - updatedAt: new Date(), - sharedLinks: [], - sharedUsers: [], - }), - oneAssetInvalidThumbnail: Object.freeze({ - id: 'album-6', - albumName: 'Album with one asset and invalid thumbnail', - ownerId: authStub.admin.id, - owner: userEntityStub.admin, - assets: [assetEntityStub.image], - albumThumbnailAsset: assetEntityStub.livePhotoMotionAsset, - albumThumbnailAssetId: assetEntityStub.livePhotoMotionAsset.id, - createdAt: new Date(), - updatedAt: new Date(), - sharedLinks: [], - sharedUsers: [], - }), - oneAssetValidThumbnail: Object.freeze({ - id: 'album-6', - albumName: 'Album with one asset and invalid thumbnail', - ownerId: authStub.admin.id, - owner: userEntityStub.admin, - assets: [assetEntityStub.image], - albumThumbnailAsset: assetEntityStub.image, - albumThumbnailAssetId: assetEntityStub.image.id, - createdAt: new Date(), - updatedAt: new Date(), - sharedLinks: [], - sharedUsers: [], - }), -}; - -const assetInfo: ExifResponseDto = { - make: 'camera-make', - model: 'camera-model', - exifImageWidth: 500, - exifImageHeight: 500, - fileSizeInByte: 100, - orientation: 'orientation', - dateTimeOriginal: today, - modifyDate: today, - timeZone: 'America/Los_Angeles', - lensModel: 'fancy', - fNumber: 100, - focalLength: 100, - iso: 100, - exposureTime: '1/16', - latitude: 100, - longitude: 100, - city: 'city', - state: 'state', - country: 'country', - description: 'description', - projectionType: null, -}; - -const assetResponse: AssetResponseDto = { - id: 'id_1', - deviceAssetId: 'device_asset_id_1', - ownerId: 'user_id_1', - deviceId: 'device_id_1', - type: AssetType.VIDEO, - originalPath: 'fake_path/jpeg', - originalFileName: 'asset_1.jpeg', - resized: false, - thumbhash: null, - fileModifiedAt: today, - fileCreatedAt: today, - updatedAt: today, - isFavorite: false, - isArchived: false, - smartInfo: { - tags: [], - objects: ['a', 'b', 'c'], - }, - duration: '0:00:00.00000', - exifInfo: assetInfo, - livePhotoVideoId: null, - tags: [], - people: [], - checksum: 'ZmlsZSBoYXNo', -}; - -const albumResponse: AlbumResponseDto = { - albumName: 'Test Album', - albumThumbnailAssetId: null, - createdAt: today, - updatedAt: today, - id: 'album-123', - ownerId: 'admin_id', - owner: mapUser(userEntityStub.admin), - sharedUsers: [], - shared: false, - assets: [], - assetCount: 1, -}; - -export const userTokenEntityStub = { - userToken: Object.freeze({ - id: 'token-id', - token: 'auth_token', - userId: userEntityStub.user1.id, - user: userEntityStub.user1, - createdAt: new Date('2021-01-01'), - updatedAt: new Date(), - deviceType: '', - deviceOS: '', - }), - inactiveToken: Object.freeze({ - id: 'not_active', - token: 'auth_token', - userId: userEntityStub.user1.id, - user: userEntityStub.user1, - createdAt: new Date('2021-01-01'), - updatedAt: new Date('2021-01-01'), - deviceType: 'Mobile', - deviceOS: 'Android', - }), -}; - -export const keyStub = { - admin: Object.freeze({ - id: 'my-random-guid', - name: 'My Key', - key: 'my-api-key (hashed)', - userId: authStub.admin.id, - user: userEntityStub.admin, - } as APIKeyEntity), -}; - -export const systemConfigStub: Record = { - defaults: [], - enabled: [ - { key: SystemConfigKey.OAUTH_ENABLED, value: true }, - { key: SystemConfigKey.OAUTH_AUTO_REGISTER, value: true }, - { key: SystemConfigKey.OAUTH_AUTO_LAUNCH, value: false }, - { key: SystemConfigKey.OAUTH_BUTTON_TEXT, value: 'OAuth' }, - ], - disabled: [{ key: SystemConfigKey.PASSWORD_LOGIN_ENABLED, value: false }], - noAutoRegister: [ - { key: SystemConfigKey.OAUTH_ENABLED, value: true }, - { key: SystemConfigKey.OAUTH_AUTO_LAUNCH, value: false }, - { key: SystemConfigKey.OAUTH_AUTO_REGISTER, value: false }, - { key: SystemConfigKey.OAUTH_BUTTON_TEXT, value: 'OAuth' }, - ], - override: [ - { key: SystemConfigKey.OAUTH_ENABLED, value: true }, - { key: SystemConfigKey.OAUTH_AUTO_REGISTER, value: true }, - { key: SystemConfigKey.OAUTH_MOBILE_OVERRIDE_ENABLED, value: true }, - { key: SystemConfigKey.OAUTH_MOBILE_REDIRECT_URI, value: 'http://mobile-redirect' }, - { key: SystemConfigKey.OAUTH_BUTTON_TEXT, value: 'OAuth' }, - ], -}; - -export const loginResponseStub = { - user1oauth: { - response: { - accessToken: 'cmFuZG9tLWJ5dGVz', - userId: 'user-id', - userEmail: 'immich@test.com', - firstName: 'immich_first_name', - lastName: 'immich_last_name', - profileImagePath: '', - isAdmin: false, - shouldChangePassword: false, - }, - cookie: [ - 'immich_access_token=cmFuZG9tLWJ5dGVz; HttpOnly; Secure; Path=/; Max-Age=34560000; SameSite=Lax;', - 'immich_auth_type=oauth; HttpOnly; Secure; Path=/; Max-Age=34560000; SameSite=Lax;', - ], - }, - user1password: { - response: { - accessToken: 'cmFuZG9tLWJ5dGVz', - userId: 'user-id', - userEmail: 'immich@test.com', - firstName: 'immich_first_name', - lastName: 'immich_last_name', - profileImagePath: '', - isAdmin: false, - shouldChangePassword: false, - }, - cookie: [ - 'immich_access_token=cmFuZG9tLWJ5dGVz; HttpOnly; Secure; Path=/; Max-Age=34560000; SameSite=Lax;', - 'immich_auth_type=password; HttpOnly; Secure; Path=/; Max-Age=34560000; SameSite=Lax;', - ], - }, - user1insecure: { - response: { - accessToken: 'cmFuZG9tLWJ5dGVz', - userId: 'user-id', - userEmail: 'immich@test.com', - firstName: 'immich_first_name', - lastName: 'immich_last_name', - profileImagePath: '', - isAdmin: false, - shouldChangePassword: false, - }, - cookie: [ - 'immich_access_token=cmFuZG9tLWJ5dGVz; HttpOnly; Path=/; Max-Age=34560000; SameSite=Lax;', - 'immich_auth_type=password; HttpOnly; Path=/; Max-Age=34560000; SameSite=Lax;', - ], - }, -}; - -export const sharedLinkStub = { - individual: Object.freeze({ - id: '123', - userId: authStub.admin.id, - user: userEntityStub.admin, - key: sharedLinkBytes, - type: SharedLinkType.INDIVIDUAL, - createdAt: today, - expiresAt: tomorrow, - allowUpload: true, - allowDownload: true, - showExif: true, - album: undefined, - description: null, - assets: [assetEntityStub.image], - } as SharedLinkEntity), - valid: Object.freeze({ - id: '123', - userId: authStub.admin.id, - user: userEntityStub.admin, - key: sharedLinkBytes, - type: SharedLinkType.ALBUM, - createdAt: today, - expiresAt: tomorrow, - allowUpload: true, - allowDownload: true, - showExif: true, - album: undefined, - albumId: null, - description: null, - assets: [], - } as SharedLinkEntity), - expired: Object.freeze({ - id: '123', - userId: authStub.admin.id, - user: userEntityStub.admin, - key: sharedLinkBytes, - type: SharedLinkType.ALBUM, - createdAt: today, - expiresAt: yesterday, - allowUpload: true, - allowDownload: true, - showExif: true, - description: null, - albumId: null, - assets: [], - } as SharedLinkEntity), - readonlyNoExif: Object.freeze({ - id: '123', - userId: authStub.admin.id, - user: userEntityStub.admin, - key: sharedLinkBytes, - type: SharedLinkType.ALBUM, - createdAt: today, - expiresAt: tomorrow, - allowUpload: false, - allowDownload: false, - showExif: false, - description: null, - assets: [], - albumId: 'album-123', - album: { - id: 'album-123', - ownerId: authStub.admin.id, - owner: userEntityStub.admin, - albumName: 'Test Album', - createdAt: today, - updatedAt: today, - albumThumbnailAsset: null, - albumThumbnailAssetId: null, - sharedUsers: [], - sharedLinks: [], - assets: [ - { - id: 'id_1', - owner: userEntityStub.user1, - ownerId: 'user_id_1', - deviceAssetId: 'device_asset_id_1', - deviceId: 'device_id_1', - type: AssetType.VIDEO, - originalPath: 'fake_path/jpeg', - resizePath: '', - checksum: Buffer.from('file hash', 'utf8'), - fileModifiedAt: today, - fileCreatedAt: today, - createdAt: today, - updatedAt: today, - isFavorite: false, - isArchived: false, - isReadOnly: false, - smartInfo: { - assetId: 'id_1', - tags: [], - objects: ['a', 'b', 'c'], - asset: null as any, - clipEmbedding: [0.12, 0.13, 0.14], - }, - webpPath: '', - thumbhash: null, - encodedVideoPath: '', - duration: null, - isVisible: true, - livePhotoVideo: null, - livePhotoVideoId: null, - originalFileName: 'asset_1.jpeg', - exifInfo: { - projectionType: null, - livePhotoCID: null, - assetId: 'id_1', - description: 'description', - exifImageWidth: 500, - exifImageHeight: 500, - fileSizeInByte: 100, - orientation: 'orientation', - dateTimeOriginal: today, - modifyDate: today, - timeZone: 'America/Los_Angeles', - latitude: 100, - longitude: 100, - city: 'city', - state: 'state', - country: 'country', - make: 'camera-make', - model: 'camera-model', - lensModel: 'fancy', - fNumber: 100, - focalLength: 100, - iso: 100, - exposureTime: '1/16', - fps: 100, - asset: null as any, - exifTextSearchableColumn: '', - }, - tags: [], - sharedLinks: [], - faces: [], - sidecarPath: null, - }, - ], - }, - }), -}; - -export const sharedLinkResponseStub = { - valid: Object.freeze({ - allowDownload: true, - allowUpload: true, - assets: [], - createdAt: today, - description: null, - expiresAt: tomorrow, - id: '123', - key: sharedLinkBytes.toString('base64url'), - showExif: true, - type: SharedLinkType.ALBUM, - userId: 'admin_id', - }), - expired: Object.freeze({ - album: undefined, - allowDownload: true, - allowUpload: true, - assets: [], - createdAt: today, - description: null, - expiresAt: yesterday, - id: '123', - key: sharedLinkBytes.toString('base64url'), - showExif: true, - type: SharedLinkType.ALBUM, - userId: 'admin_id', - }), - readonly: Object.freeze({ - id: '123', - userId: 'admin_id', - key: sharedLinkBytes.toString('base64url'), - type: SharedLinkType.ALBUM, - createdAt: today, - expiresAt: tomorrow, - description: null, - allowUpload: false, - allowDownload: false, - showExif: true, - album: albumResponse, - assets: [assetResponse], - }), - readonlyNoExif: Object.freeze({ - id: '123', - userId: 'admin_id', - key: sharedLinkBytes.toString('base64url'), - type: SharedLinkType.ALBUM, - createdAt: today, - expiresAt: tomorrow, - description: null, - allowUpload: false, - allowDownload: false, - showExif: false, - album: albumResponse, - assets: [{ ...assetResponse, exifInfo: undefined }], - }), -}; - -// TODO - the constructor isn't used anywhere, so not test coverage -new ExifResponseDto(); - -export const searchStub = { - emptyResults: Object.freeze>({ - total: 0, - count: 0, - page: 1, - items: [], - facets: [], - distances: [], - }), -}; - -const probeStubDefaultFormat: VideoFormat = { - formatName: 'mov,mp4,m4a,3gp,3g2,mj2', - formatLongName: 'QuickTime / MOV', - duration: 0, -}; - -const probeStubDefaultVideoStream: VideoStreamInfo[] = [ - { height: 1080, width: 1920, codecName: 'h265', codecType: 'video', frameCount: 100, rotation: 0 }, -]; - -const probeStubDefaultAudioStream: AudioStreamInfo[] = [{ codecName: 'aac', codecType: 'audio' }]; - -const probeStubDefault: VideoInfo = { - format: probeStubDefaultFormat, - videoStreams: probeStubDefaultVideoStream, - audioStreams: probeStubDefaultAudioStream, -}; - -export const probeStub = { - noVideoStreams: Object.freeze({ ...probeStubDefault, videoStreams: [] }), - multipleVideoStreams: Object.freeze({ - ...probeStubDefault, - videoStreams: [ - { - height: 1080, - width: 400, - codecName: 'h265', - codecType: 'video', - frameCount: 100, - rotation: 0, - }, - { - height: 1080, - width: 400, - codecName: 'h7000', - codecType: 'video', - frameCount: 99, - rotation: 0, - }, - ], - }), - noHeight: Object.freeze({ - ...probeStubDefault, - videoStreams: [ - { - height: 0, - width: 400, - codecName: 'h265', - codecType: 'video', - frameCount: 100, - rotation: 0, - }, - ], - }), - videoStream2160p: Object.freeze({ - ...probeStubDefault, - videoStreams: [ - { - height: 2160, - width: 3840, - codecName: 'h264', - codecType: 'video', - frameCount: 100, - rotation: 0, - }, - ], - }), - videoStreamVertical2160p: Object.freeze({ - ...probeStubDefault, - videoStreams: [ - { - height: 2160, - width: 3840, - codecName: 'h264', - codecType: 'video', - frameCount: 100, - rotation: 90, - }, - ], - }), - audioStreamMp3: Object.freeze({ - ...probeStubDefault, - audioStreams: [{ codecType: 'audio', codecName: 'aac' }], - }), - matroskaContainer: Object.freeze({ - ...probeStubDefault, - format: { - formatName: 'matroska,webm', - formatLongName: 'Matroska / WebM', - duration: 0, - }, - }), -}; - -export const personStub = { - noName: Object.freeze({ - id: 'person-1', - createdAt: new Date('2021-01-01'), - updatedAt: new Date('2021-01-01'), - ownerId: userEntityStub.admin.id, - owner: userEntityStub.admin, - name: '', - thumbnailPath: '/path/to/thumbnail.jpg', - faces: [], - isHidden: false, - }), - hidden: Object.freeze({ - id: 'person-1', - createdAt: new Date('2021-01-01'), - updatedAt: new Date('2021-01-01'), - ownerId: userEntityStub.admin.id, - owner: userEntityStub.admin, - name: '', - thumbnailPath: '/path/to/thumbnail.jpg', - faces: [], - isHidden: true, - }), - withName: Object.freeze({ - id: 'person-1', - createdAt: new Date('2021-01-01'), - updatedAt: new Date('2021-01-01'), - ownerId: userEntityStub.admin.id, - owner: userEntityStub.admin, - name: 'Person 1', - thumbnailPath: '/path/to/thumbnail.jpg', - faces: [], - isHidden: false, - }), - noThumbnail: Object.freeze({ - id: 'person-1', - createdAt: new Date('2021-01-01'), - updatedAt: new Date('2021-01-01'), - ownerId: userEntityStub.admin.id, - owner: userEntityStub.admin, - name: '', - thumbnailPath: '', - faces: [], - isHidden: false, - }), - newThumbnail: Object.freeze({ - id: 'person-1', - createdAt: new Date('2021-01-01'), - updatedAt: new Date('2021-01-01'), - ownerId: userEntityStub.admin.id, - owner: userEntityStub.admin, - name: '', - thumbnailPath: '/new/path/to/thumbnail.jpg', - faces: [], - isHidden: false, - }), - primaryPerson: Object.freeze({ - id: 'person-1', - createdAt: new Date('2021-01-01'), - updatedAt: new Date('2021-01-01'), - ownerId: userEntityStub.admin.id, - owner: userEntityStub.admin, - name: 'Person 1', - thumbnailPath: '/path/to/thumbnail', - faces: [], - isHidden: false, - }), - mergePerson: Object.freeze({ - id: 'person-2', - createdAt: new Date('2021-01-01'), - updatedAt: new Date('2021-01-01'), - ownerId: userEntityStub.admin.id, - owner: userEntityStub.admin, - name: 'Person 2', - thumbnailPath: '/path/to/thumbnail', - faces: [], - isHidden: false, - }), -}; - -export const partnerStub = { - adminToUser1: Object.freeze({ - createdAt: new Date('2023-02-23T05:06:29.716Z'), - updatedAt: new Date('2023-02-23T05:06:29.716Z'), - sharedById: userEntityStub.admin.id, - sharedBy: userEntityStub.admin, - sharedWith: userEntityStub.user1, - sharedWithId: userEntityStub.user1.id, - }), - user1ToAdmin1: Object.freeze({ - createdAt: new Date('2023-02-23T05:06:29.716Z'), - updatedAt: new Date('2023-02-23T05:06:29.716Z'), - sharedBy: userEntityStub.user1, - sharedById: userEntityStub.user1.id, - sharedWithId: userEntityStub.admin.id, - sharedWith: userEntityStub.admin, - }), -}; - -export const faceStub = { - face1: Object.freeze({ - assetId: assetEntityStub.image.id, - asset: assetEntityStub.image, - personId: personStub.withName.id, - person: personStub.withName, - embedding: [1, 2, 3, 4], - boundingBoxX1: 0, - boundingBoxY1: 0, - boundingBoxX2: 1, - boundingBoxY2: 1, - imageHeight: 1024, - imageWidth: 1024, - }), - primaryFace1: Object.freeze({ - assetId: assetEntityStub.image.id, - asset: assetEntityStub.image, - personId: personStub.primaryPerson.id, - person: personStub.primaryPerson, - embedding: [1, 2, 3, 4], - boundingBoxX1: 0, - boundingBoxY1: 0, - boundingBoxX2: 1, - boundingBoxY2: 1, - imageHeight: 1024, - imageWidth: 1024, - }), - mergeFace1: Object.freeze({ - assetId: assetEntityStub.image.id, - asset: assetEntityStub.image, - personId: personStub.mergePerson.id, - person: personStub.mergePerson, - embedding: [1, 2, 3, 4], - boundingBoxX1: 0, - boundingBoxY1: 0, - boundingBoxX2: 1, - boundingBoxY2: 1, - imageHeight: 1024, - imageWidth: 1024, - }), - mergeFace2: Object.freeze({ - assetId: assetEntityStub.image1.id, - asset: assetEntityStub.image1, - personId: personStub.mergePerson.id, - person: personStub.mergePerson, - embedding: [1, 2, 3, 4], - boundingBoxX1: 0, - boundingBoxY1: 0, - boundingBoxX2: 1, - boundingBoxY2: 1, - imageHeight: 1024, - imageWidth: 1024, - }), -}; - -export const tagStub = { - tag1: Object.freeze({ - id: 'tag-1', - name: 'Tag1', - type: TagType.CUSTOM, - userId: userEntityStub.admin.id, - user: userEntityStub.admin, - renameTagId: null, - assets: [], - }), -}; - -export const tagResponseStub = { - tag1: Object.freeze({ - id: 'tag-1', - name: 'Tag1', - type: 'CUSTOM', - userId: 'admin_id', - }), -}; diff --git a/server/test/fixtures/album.stub.ts b/server/test/fixtures/album.stub.ts new file mode 100644 index 0000000000..6762950d31 --- /dev/null +++ b/server/test/fixtures/album.stub.ts @@ -0,0 +1,124 @@ +import { AlbumEntity } from '@app/infra/entities'; +import { assetStub } from './asset.stub'; +import { authStub } from './auth.stub'; +import { userStub } from './user.stub'; + +export const albumStub = { + empty: Object.freeze({ + id: 'album-1', + albumName: 'Empty album', + ownerId: authStub.admin.id, + owner: userStub.admin, + assets: [], + albumThumbnailAsset: null, + albumThumbnailAssetId: null, + createdAt: new Date(), + updatedAt: new Date(), + sharedLinks: [], + sharedUsers: [], + }), + sharedWithUser: Object.freeze({ + id: 'album-2', + albumName: 'Empty album shared with user', + ownerId: authStub.admin.id, + owner: userStub.admin, + assets: [], + albumThumbnailAsset: null, + albumThumbnailAssetId: null, + createdAt: new Date(), + updatedAt: new Date(), + sharedLinks: [], + sharedUsers: [userStub.user1], + }), + sharedWithMultiple: Object.freeze({ + id: 'album-3', + albumName: 'Empty album shared with users', + ownerId: authStub.admin.id, + owner: userStub.admin, + assets: [], + albumThumbnailAsset: null, + albumThumbnailAssetId: null, + createdAt: new Date(), + updatedAt: new Date(), + sharedLinks: [], + sharedUsers: [userStub.user1, userStub.user2], + }), + sharedWithAdmin: Object.freeze({ + id: 'album-3', + albumName: 'Empty album shared with admin', + ownerId: authStub.user1.id, + owner: userStub.user1, + assets: [], + albumThumbnailAsset: null, + albumThumbnailAssetId: null, + createdAt: new Date(), + updatedAt: new Date(), + sharedLinks: [], + sharedUsers: [userStub.admin], + }), + oneAsset: Object.freeze({ + id: 'album-4', + albumName: 'Album with one asset', + ownerId: authStub.admin.id, + owner: userStub.admin, + assets: [assetStub.image], + albumThumbnailAsset: null, + albumThumbnailAssetId: null, + createdAt: new Date(), + updatedAt: new Date(), + sharedLinks: [], + sharedUsers: [], + }), + emptyWithInvalidThumbnail: Object.freeze({ + id: 'album-5', + albumName: 'Empty album with invalid thumbnail', + ownerId: authStub.admin.id, + owner: userStub.admin, + assets: [], + albumThumbnailAsset: assetStub.image, + albumThumbnailAssetId: assetStub.image.id, + createdAt: new Date(), + updatedAt: new Date(), + sharedLinks: [], + sharedUsers: [], + }), + emptyWithValidThumbnail: Object.freeze({ + id: 'album-5', + albumName: 'Empty album with invalid thumbnail', + ownerId: authStub.admin.id, + owner: userStub.admin, + assets: [], + albumThumbnailAsset: null, + albumThumbnailAssetId: null, + createdAt: new Date(), + updatedAt: new Date(), + sharedLinks: [], + sharedUsers: [], + }), + oneAssetInvalidThumbnail: Object.freeze({ + id: 'album-6', + albumName: 'Album with one asset and invalid thumbnail', + ownerId: authStub.admin.id, + owner: userStub.admin, + assets: [assetStub.image], + albumThumbnailAsset: assetStub.livePhotoMotionAsset, + albumThumbnailAssetId: assetStub.livePhotoMotionAsset.id, + createdAt: new Date(), + updatedAt: new Date(), + sharedLinks: [], + sharedUsers: [], + }), + oneAssetValidThumbnail: Object.freeze({ + id: 'album-6', + albumName: 'Album with one asset and invalid thumbnail', + ownerId: authStub.admin.id, + owner: userStub.admin, + assets: [assetStub.image], + albumThumbnailAsset: assetStub.image, + albumThumbnailAssetId: assetStub.image.id, + createdAt: new Date(), + updatedAt: new Date(), + sharedLinks: [], + sharedUsers: [], + }), +}; diff --git a/server/test/fixtures/api-key.stub.ts b/server/test/fixtures/api-key.stub.ts new file mode 100644 index 0000000000..36554ef688 --- /dev/null +++ b/server/test/fixtures/api-key.stub.ts @@ -0,0 +1,13 @@ +import { APIKeyEntity } from '@app/infra/entities'; +import { authStub } from './auth.stub'; +import { userStub } from './user.stub'; + +export const keyStub = { + admin: Object.freeze({ + id: 'my-random-guid', + name: 'My Key', + key: 'my-api-key (hashed)', + userId: authStub.admin.id, + user: userStub.admin, + } as APIKeyEntity), +}; diff --git a/server/test/fixtures/asset.stub.ts b/server/test/fixtures/asset.stub.ts new file mode 100644 index 0000000000..fa7df9f4ad --- /dev/null +++ b/server/test/fixtures/asset.stub.ts @@ -0,0 +1,291 @@ +import { AssetEntity, AssetType, ExifEntity } from '@app/infra/entities'; +import { authStub } from './auth.stub'; +import { fileStub } from './file.stub'; +import { userStub } from './user.stub'; + +export const assetStub = { + noResizePath: Object.freeze({ + id: 'asset-id', + originalFileName: 'IMG_123', + deviceAssetId: 'device-asset-id', + fileModifiedAt: new Date('2023-02-23T05:06:29.716Z'), + fileCreatedAt: new Date('2023-02-23T05:06:29.716Z'), + owner: userStub.user1, + ownerId: 'user-id', + deviceId: 'device-id', + originalPath: 'upload/library/IMG_123.jpg', + resizePath: null, + checksum: Buffer.from('file hash', 'utf8'), + type: AssetType.IMAGE, + webpPath: '/uploads/user-id/webp/path.ext', + thumbhash: Buffer.from('blablabla', 'base64'), + encodedVideoPath: null, + createdAt: new Date('2023-02-23T05:06:29.716Z'), + updatedAt: new Date('2023-02-23T05:06:29.716Z'), + isFavorite: true, + isArchived: false, + duration: null, + isVisible: true, + livePhotoVideo: null, + livePhotoVideoId: null, + tags: [], + sharedLinks: [], + faces: [], + sidecarPath: null, + isReadOnly: false, + }), + noWebpPath: Object.freeze({ + id: 'asset-id', + deviceAssetId: 'device-asset-id', + fileModifiedAt: new Date('2023-02-23T05:06:29.716Z'), + fileCreatedAt: new Date('2023-02-23T05:06:29.716Z'), + owner: userStub.user1, + ownerId: 'user-id', + deviceId: 'device-id', + originalPath: 'upload/library/IMG_456.jpg', + resizePath: '/uploads/user-id/thumbs/path.ext', + checksum: Buffer.from('file hash', 'utf8'), + type: AssetType.IMAGE, + webpPath: null, + thumbhash: Buffer.from('blablabla', 'base64'), + encodedVideoPath: null, + createdAt: new Date('2023-02-23T05:06:29.716Z'), + updatedAt: new Date('2023-02-23T05:06:29.716Z'), + isFavorite: true, + isArchived: false, + duration: null, + isVisible: true, + livePhotoVideo: null, + livePhotoVideoId: null, + tags: [], + sharedLinks: [], + originalFileName: 'IMG_456', + faces: [], + sidecarPath: null, + isReadOnly: false, + exifInfo: { + fileSizeInByte: 123_000, + } as ExifEntity, + }), + noThumbhash: Object.freeze({ + id: 'asset-id', + deviceAssetId: 'device-asset-id', + fileModifiedAt: new Date('2023-02-23T05:06:29.716Z'), + fileCreatedAt: new Date('2023-02-23T05:06:29.716Z'), + owner: userStub.user1, + ownerId: 'user-id', + deviceId: 'device-id', + originalPath: '/original/path.ext', + resizePath: '/uploads/user-id/thumbs/path.ext', + checksum: Buffer.from('file hash', 'utf8'), + type: AssetType.IMAGE, + webpPath: '/uploads/user-id/webp/path.ext', + thumbhash: null, + encodedVideoPath: null, + createdAt: new Date('2023-02-23T05:06:29.716Z'), + updatedAt: new Date('2023-02-23T05:06:29.716Z'), + isFavorite: true, + isArchived: false, + isReadOnly: false, + duration: null, + isVisible: true, + livePhotoVideo: null, + livePhotoVideoId: null, + tags: [], + sharedLinks: [], + originalFileName: 'asset-id.ext', + faces: [], + sidecarPath: null, + }), + image: Object.freeze({ + id: 'asset-id', + deviceAssetId: 'device-asset-id', + fileModifiedAt: new Date('2023-02-23T05:06:29.716Z'), + fileCreatedAt: new Date('2023-02-23T05:06:29.716Z'), + owner: userStub.user1, + ownerId: 'user-id', + deviceId: 'device-id', + originalPath: '/original/path.jpg', + resizePath: '/uploads/user-id/thumbs/path.jpg', + checksum: Buffer.from('file hash', 'utf8'), + type: AssetType.IMAGE, + webpPath: '/uploads/user-id/webp/path.ext', + thumbhash: Buffer.from('blablabla', 'base64'), + encodedVideoPath: null, + createdAt: new Date('2023-02-23T05:06:29.716Z'), + updatedAt: new Date('2023-02-23T05:06:29.716Z'), + isFavorite: true, + isArchived: false, + isReadOnly: false, + duration: null, + isVisible: true, + livePhotoVideo: null, + livePhotoVideoId: null, + tags: [], + sharedLinks: [], + originalFileName: 'asset-id.jpg', + faces: [], + sidecarPath: null, + exifInfo: { + fileSizeInByte: 5_000, + } as ExifEntity, + }), + image1: Object.freeze({ + id: 'asset-id-1', + deviceAssetId: 'device-asset-id', + fileModifiedAt: new Date('2023-02-23T05:06:29.716Z'), + fileCreatedAt: new Date('2023-02-23T05:06:29.716Z'), + owner: userStub.user1, + ownerId: 'user-id', + deviceId: 'device-id', + originalPath: '/original/path.ext', + resizePath: '/uploads/user-id/thumbs/path.ext', + checksum: Buffer.from('file hash', 'utf8'), + type: AssetType.IMAGE, + webpPath: '/uploads/user-id/webp/path.ext', + thumbhash: Buffer.from('blablabla', 'base64'), + encodedVideoPath: null, + createdAt: new Date('2023-02-23T05:06:29.716Z'), + updatedAt: new Date('2023-02-23T05:06:29.716Z'), + isFavorite: true, + isArchived: false, + isReadOnly: false, + duration: null, + isVisible: true, + livePhotoVideo: null, + livePhotoVideoId: null, + tags: [], + sharedLinks: [], + originalFileName: 'asset-id.ext', + faces: [], + sidecarPath: null, + exifInfo: { + fileSizeInByte: 5_000, + } as ExifEntity, + }), + video: Object.freeze({ + id: 'asset-id', + originalFileName: 'asset-id.ext', + deviceAssetId: 'device-asset-id', + fileModifiedAt: new Date('2023-02-23T05:06:29.716Z'), + fileCreatedAt: new Date('2023-02-23T05:06:29.716Z'), + owner: userStub.user1, + ownerId: 'user-id', + deviceId: 'device-id', + originalPath: '/original/path.ext', + resizePath: '/uploads/user-id/thumbs/path.ext', + checksum: Buffer.from('file hash', 'utf8'), + type: AssetType.VIDEO, + webpPath: null, + thumbhash: null, + encodedVideoPath: null, + createdAt: new Date('2023-02-23T05:06:29.716Z'), + updatedAt: new Date('2023-02-23T05:06:29.716Z'), + isFavorite: true, + isArchived: false, + isReadOnly: false, + duration: null, + isVisible: true, + livePhotoVideo: null, + livePhotoVideoId: null, + tags: [], + sharedLinks: [], + faces: [], + sidecarPath: null, + exifInfo: { + fileSizeInByte: 100_000, + } as ExifEntity, + }), + livePhotoMotionAsset: Object.freeze({ + id: 'live-photo-motion-asset', + originalPath: fileStub.livePhotoMotion.originalPath, + ownerId: authStub.user1.id, + type: AssetType.VIDEO, + isVisible: false, + fileModifiedAt: new Date('2022-06-19T23:41:36.910Z'), + fileCreatedAt: new Date('2022-06-19T23:41:36.910Z'), + exifInfo: { + fileSizeInByte: 100_000, + }, + } as AssetEntity), + + livePhotoStillAsset: Object.freeze({ + id: 'live-photo-still-asset', + originalPath: fileStub.livePhotoStill.originalPath, + ownerId: authStub.user1.id, + type: AssetType.IMAGE, + livePhotoVideoId: 'live-photo-motion-asset', + isVisible: true, + fileModifiedAt: new Date('2022-06-19T23:41:36.910Z'), + fileCreatedAt: new Date('2022-06-19T23:41:36.910Z'), + exifInfo: { + fileSizeInByte: 25_000, + }, + } as AssetEntity), + + withLocation: Object.freeze({ + id: 'asset-with-favorite-id', + deviceAssetId: 'device-asset-id', + fileModifiedAt: new Date('2023-02-23T05:06:29.716Z'), + fileCreatedAt: new Date('2023-02-23T05:06:29.716Z'), + owner: userStub.user1, + ownerId: 'user-id', + deviceId: 'device-id', + checksum: Buffer.from('file hash', 'utf8'), + originalPath: '/original/path.ext', + resizePath: '/uploads/user-id/thumbs/path.ext', + sidecarPath: null, + type: AssetType.IMAGE, + webpPath: null, + thumbhash: null, + encodedVideoPath: null, + createdAt: new Date('2023-02-23T05:06:29.716Z'), + updatedAt: new Date('2023-02-23T05:06:29.716Z'), + isFavorite: false, + isArchived: false, + isReadOnly: false, + duration: null, + isVisible: true, + livePhotoVideo: null, + livePhotoVideoId: null, + tags: [], + sharedLinks: [], + originalFileName: 'asset-id.ext', + faces: [], + exifInfo: { + latitude: 100, + longitude: 100, + fileSizeInByte: 23_456, + } as ExifEntity, + }), + sidecar: Object.freeze({ + id: 'asset-id', + deviceAssetId: 'device-asset-id', + fileModifiedAt: new Date('2023-02-23T05:06:29.716Z'), + fileCreatedAt: new Date('2023-02-23T05:06:29.716Z'), + owner: userStub.user1, + ownerId: 'user-id', + deviceId: 'device-id', + originalPath: '/original/path.ext', + resizePath: '/uploads/user-id/thumbs/path.ext', + thumbhash: null, + checksum: Buffer.from('file hash', 'utf8'), + type: AssetType.IMAGE, + webpPath: null, + encodedVideoPath: null, + createdAt: new Date('2023-02-23T05:06:29.716Z'), + updatedAt: new Date('2023-02-23T05:06:29.716Z'), + isFavorite: true, + isArchived: false, + isReadOnly: false, + duration: null, + isVisible: true, + livePhotoVideo: null, + livePhotoVideoId: null, + tags: [], + sharedLinks: [], + originalFileName: 'asset-id.ext', + faces: [], + sidecarPath: '/original/path.ext.xmp', + }), +}; diff --git a/server/test/fixtures/auth.stub.ts b/server/test/fixtures/auth.stub.ts new file mode 100644 index 0000000000..a8c8b37a68 --- /dev/null +++ b/server/test/fixtures/auth.stub.ts @@ -0,0 +1,127 @@ +import { AuthUserDto } from '@app/domain'; + +export const authStub = { + admin: Object.freeze({ + id: 'admin_id', + email: 'admin@test.com', + isAdmin: true, + isPublicUser: false, + isAllowUpload: true, + externalPath: null, + }), + user1: Object.freeze({ + id: 'user-id', + email: 'immich@test.com', + isAdmin: false, + isPublicUser: false, + isAllowUpload: true, + isAllowDownload: true, + isShowExif: true, + accessTokenId: 'token-id', + externalPath: null, + }), + user2: Object.freeze({ + id: 'user-2', + email: 'user2@immich.app', + isAdmin: false, + isPublicUser: false, + isAllowUpload: true, + isAllowDownload: true, + isShowExif: true, + accessTokenId: 'token-id', + externalPath: null, + }), + external1: Object.freeze({ + id: 'user-id', + email: 'immich@test.com', + isAdmin: false, + isPublicUser: false, + isAllowUpload: true, + isAllowDownload: true, + isShowExif: true, + accessTokenId: 'token-id', + externalPath: '/data/user1', + }), + adminSharedLink: Object.freeze({ + id: 'admin_id', + email: 'admin@test.com', + isAdmin: true, + isAllowUpload: true, + isAllowDownload: true, + isPublicUser: true, + isShowExif: true, + sharedLinkId: '123', + }), + adminSharedLinkNoExif: Object.freeze({ + id: 'admin_id', + email: 'admin@test.com', + isAdmin: true, + isAllowUpload: true, + isAllowDownload: true, + isPublicUser: true, + isShowExif: false, + sharedLinkId: '123', + }), + readonlySharedLink: Object.freeze({ + id: 'admin_id', + email: 'admin@test.com', + isAdmin: true, + isAllowUpload: false, + isAllowDownload: false, + isPublicUser: true, + isShowExif: true, + sharedLinkId: '123', + accessTokenId: 'token-id', + }), +}; + +export const loginResponseStub = { + user1oauth: { + response: { + accessToken: 'cmFuZG9tLWJ5dGVz', + userId: 'user-id', + userEmail: 'immich@test.com', + firstName: 'immich_first_name', + lastName: 'immich_last_name', + profileImagePath: '', + isAdmin: false, + shouldChangePassword: false, + }, + cookie: [ + 'immich_access_token=cmFuZG9tLWJ5dGVz; HttpOnly; Secure; Path=/; Max-Age=34560000; SameSite=Lax;', + 'immich_auth_type=oauth; HttpOnly; Secure; Path=/; Max-Age=34560000; SameSite=Lax;', + ], + }, + user1password: { + response: { + accessToken: 'cmFuZG9tLWJ5dGVz', + userId: 'user-id', + userEmail: 'immich@test.com', + firstName: 'immich_first_name', + lastName: 'immich_last_name', + profileImagePath: '', + isAdmin: false, + shouldChangePassword: false, + }, + cookie: [ + 'immich_access_token=cmFuZG9tLWJ5dGVz; HttpOnly; Secure; Path=/; Max-Age=34560000; SameSite=Lax;', + 'immich_auth_type=password; HttpOnly; Secure; Path=/; Max-Age=34560000; SameSite=Lax;', + ], + }, + user1insecure: { + response: { + accessToken: 'cmFuZG9tLWJ5dGVz', + userId: 'user-id', + userEmail: 'immich@test.com', + firstName: 'immich_first_name', + lastName: 'immich_last_name', + profileImagePath: '', + isAdmin: false, + shouldChangePassword: false, + }, + cookie: [ + 'immich_access_token=cmFuZG9tLWJ5dGVz; HttpOnly; Path=/; Max-Age=34560000; SameSite=Lax;', + 'immich_auth_type=password; HttpOnly; Path=/; Max-Age=34560000; SameSite=Lax;', + ], + }, +}; diff --git a/server/test/fixtures/face.stub.ts b/server/test/fixtures/face.stub.ts new file mode 100644 index 0000000000..74461e845d --- /dev/null +++ b/server/test/fixtures/face.stub.ts @@ -0,0 +1,58 @@ +import { AssetFaceEntity } from '@app/infra/entities'; +import { assetStub } from './asset.stub'; +import { personStub } from './person.stub'; + +export const faceStub = { + face1: Object.freeze({ + assetId: assetStub.image.id, + asset: assetStub.image, + personId: personStub.withName.id, + person: personStub.withName, + embedding: [1, 2, 3, 4], + boundingBoxX1: 0, + boundingBoxY1: 0, + boundingBoxX2: 1, + boundingBoxY2: 1, + imageHeight: 1024, + imageWidth: 1024, + }), + primaryFace1: Object.freeze({ + assetId: assetStub.image.id, + asset: assetStub.image, + personId: personStub.primaryPerson.id, + person: personStub.primaryPerson, + embedding: [1, 2, 3, 4], + boundingBoxX1: 0, + boundingBoxY1: 0, + boundingBoxX2: 1, + boundingBoxY2: 1, + imageHeight: 1024, + imageWidth: 1024, + }), + mergeFace1: Object.freeze({ + assetId: assetStub.image.id, + asset: assetStub.image, + personId: personStub.mergePerson.id, + person: personStub.mergePerson, + embedding: [1, 2, 3, 4], + boundingBoxX1: 0, + boundingBoxY1: 0, + boundingBoxX2: 1, + boundingBoxY2: 1, + imageHeight: 1024, + imageWidth: 1024, + }), + mergeFace2: Object.freeze({ + assetId: assetStub.image1.id, + asset: assetStub.image1, + personId: personStub.mergePerson.id, + person: personStub.mergePerson, + embedding: [1, 2, 3, 4], + boundingBoxX1: 0, + boundingBoxY1: 0, + boundingBoxX2: 1, + boundingBoxY2: 1, + imageHeight: 1024, + imageWidth: 1024, + }), +}; diff --git a/server/test/fixtures/file.stub.ts b/server/test/fixtures/file.stub.ts new file mode 100644 index 0000000000..938213c96c --- /dev/null +++ b/server/test/fixtures/file.stub.ts @@ -0,0 +1,12 @@ +export const fileStub = { + livePhotoStill: Object.freeze({ + originalPath: 'fake_path/asset_1.jpeg', + checksum: Buffer.from('file hash', 'utf8'), + originalName: 'asset_1.jpeg', + }), + livePhotoMotion: Object.freeze({ + originalPath: 'fake_path/asset_1.mp4', + checksum: Buffer.from('live photo file hash', 'utf8'), + originalName: 'asset_1.mp4', + }), +}; diff --git a/server/test/fixtures/index.ts b/server/test/fixtures/index.ts new file mode 100644 index 0000000000..19f62e8614 --- /dev/null +++ b/server/test/fixtures/index.ts @@ -0,0 +1,15 @@ +export * from './album.stub'; +export * from './api-key.stub'; +export * from './asset.stub'; +export * from './auth.stub'; +export * from './face.stub'; +export * from './file.stub'; +export * from './media.stub'; +export * from './partner.stub'; +export * from './person.stub'; +export * from './search.stub'; +export * from './shared-link.stub'; +export * from './system-config.stub'; +export * from './tag.stub'; +export * from './user-token.stub'; +export * from './user.stub'; diff --git a/server/test/fixtures/media.stub.ts b/server/test/fixtures/media.stub.ts new file mode 100644 index 0000000000..8d885a3a44 --- /dev/null +++ b/server/test/fixtures/media.stub.ts @@ -0,0 +1,95 @@ +import { AudioStreamInfo, VideoFormat, VideoInfo, VideoStreamInfo } from '@app/domain'; + +const probeStubDefaultFormat: VideoFormat = { + formatName: 'mov,mp4,m4a,3gp,3g2,mj2', + formatLongName: 'QuickTime / MOV', + duration: 0, +}; + +const probeStubDefaultVideoStream: VideoStreamInfo[] = [ + { height: 1080, width: 1920, codecName: 'h265', codecType: 'video', frameCount: 100, rotation: 0 }, +]; + +const probeStubDefaultAudioStream: AudioStreamInfo[] = [{ codecName: 'aac', codecType: 'audio' }]; + +const probeStubDefault: VideoInfo = { + format: probeStubDefaultFormat, + videoStreams: probeStubDefaultVideoStream, + audioStreams: probeStubDefaultAudioStream, +}; + +export const probeStub = { + noVideoStreams: Object.freeze({ ...probeStubDefault, videoStreams: [] }), + multipleVideoStreams: Object.freeze({ + ...probeStubDefault, + videoStreams: [ + { + height: 1080, + width: 400, + codecName: 'h265', + codecType: 'video', + frameCount: 100, + rotation: 0, + }, + { + height: 1080, + width: 400, + codecName: 'h7000', + codecType: 'video', + frameCount: 99, + rotation: 0, + }, + ], + }), + noHeight: Object.freeze({ + ...probeStubDefault, + videoStreams: [ + { + height: 0, + width: 400, + codecName: 'h265', + codecType: 'video', + frameCount: 100, + rotation: 0, + }, + ], + }), + videoStream2160p: Object.freeze({ + ...probeStubDefault, + videoStreams: [ + { + height: 2160, + width: 3840, + codecName: 'h264', + codecType: 'video', + frameCount: 100, + rotation: 0, + }, + ], + }), + videoStreamVertical2160p: Object.freeze({ + ...probeStubDefault, + videoStreams: [ + { + height: 2160, + width: 3840, + codecName: 'h264', + codecType: 'video', + frameCount: 100, + rotation: 90, + }, + ], + }), + audioStreamMp3: Object.freeze({ + ...probeStubDefault, + audioStreams: [{ codecType: 'audio', codecName: 'aac' }], + }), + matroskaContainer: Object.freeze({ + ...probeStubDefault, + format: { + formatName: 'matroska,webm', + formatLongName: 'Matroska / WebM', + duration: 0, + }, + }), +}; diff --git a/server/test/fixtures/partner.stub.ts b/server/test/fixtures/partner.stub.ts new file mode 100644 index 0000000000..dc54332ead --- /dev/null +++ b/server/test/fixtures/partner.stub.ts @@ -0,0 +1,21 @@ +import { PartnerEntity } from '@app/infra/entities'; +import { userStub } from './user.stub'; + +export const partnerStub = { + adminToUser1: Object.freeze({ + createdAt: new Date('2023-02-23T05:06:29.716Z'), + updatedAt: new Date('2023-02-23T05:06:29.716Z'), + sharedById: userStub.admin.id, + sharedBy: userStub.admin, + sharedWith: userStub.user1, + sharedWithId: userStub.user1.id, + }), + user1ToAdmin1: Object.freeze({ + createdAt: new Date('2023-02-23T05:06:29.716Z'), + updatedAt: new Date('2023-02-23T05:06:29.716Z'), + sharedBy: userStub.user1, + sharedById: userStub.user1.id, + sharedWithId: userStub.admin.id, + sharedWith: userStub.admin, + }), +}; diff --git a/server/test/fixtures/person.stub.ts b/server/test/fixtures/person.stub.ts new file mode 100644 index 0000000000..f2b512b88f --- /dev/null +++ b/server/test/fixtures/person.stub.ts @@ -0,0 +1,82 @@ +import { PersonEntity } from '@app/infra/entities'; +import { userStub } from './user.stub'; + +export const personStub = { + noName: Object.freeze({ + id: 'person-1', + createdAt: new Date('2021-01-01'), + updatedAt: new Date('2021-01-01'), + ownerId: userStub.admin.id, + owner: userStub.admin, + name: '', + thumbnailPath: '/path/to/thumbnail.jpg', + faces: [], + isHidden: false, + }), + hidden: Object.freeze({ + id: 'person-1', + createdAt: new Date('2021-01-01'), + updatedAt: new Date('2021-01-01'), + ownerId: userStub.admin.id, + owner: userStub.admin, + name: '', + thumbnailPath: '/path/to/thumbnail.jpg', + faces: [], + isHidden: true, + }), + withName: Object.freeze({ + id: 'person-1', + createdAt: new Date('2021-01-01'), + updatedAt: new Date('2021-01-01'), + ownerId: userStub.admin.id, + owner: userStub.admin, + name: 'Person 1', + thumbnailPath: '/path/to/thumbnail.jpg', + faces: [], + isHidden: false, + }), + noThumbnail: Object.freeze({ + id: 'person-1', + createdAt: new Date('2021-01-01'), + updatedAt: new Date('2021-01-01'), + ownerId: userStub.admin.id, + owner: userStub.admin, + name: '', + thumbnailPath: '', + faces: [], + isHidden: false, + }), + newThumbnail: Object.freeze({ + id: 'person-1', + createdAt: new Date('2021-01-01'), + updatedAt: new Date('2021-01-01'), + ownerId: userStub.admin.id, + owner: userStub.admin, + name: '', + thumbnailPath: '/new/path/to/thumbnail.jpg', + faces: [], + isHidden: false, + }), + primaryPerson: Object.freeze({ + id: 'person-1', + createdAt: new Date('2021-01-01'), + updatedAt: new Date('2021-01-01'), + ownerId: userStub.admin.id, + owner: userStub.admin, + name: 'Person 1', + thumbnailPath: '/path/to/thumbnail', + faces: [], + isHidden: false, + }), + mergePerson: Object.freeze({ + id: 'person-2', + createdAt: new Date('2021-01-01'), + updatedAt: new Date('2021-01-01'), + ownerId: userStub.admin.id, + owner: userStub.admin, + name: 'Person 2', + thumbnailPath: '/path/to/thumbnail', + faces: [], + isHidden: false, + }), +}; diff --git a/server/test/fixtures/search.stub.ts b/server/test/fixtures/search.stub.ts new file mode 100644 index 0000000000..f96bddafe8 --- /dev/null +++ b/server/test/fixtures/search.stub.ts @@ -0,0 +1,12 @@ +import { SearchResult } from '@app/domain'; + +export const searchStub = { + emptyResults: Object.freeze>({ + total: 0, + count: 0, + page: 1, + items: [], + facets: [], + distances: [], + }), +}; diff --git a/server/test/fixtures/shared-link.stub.ts b/server/test/fixtures/shared-link.stub.ts new file mode 100644 index 0000000000..777e136cea --- /dev/null +++ b/server/test/fixtures/shared-link.stub.ts @@ -0,0 +1,282 @@ +import { AlbumResponseDto, AssetResponseDto, ExifResponseDto, mapUser, SharedLinkResponseDto } from '@app/domain'; +import { AssetType, SharedLinkEntity, SharedLinkType } from '@app/infra/entities'; +import { assetStub } from './asset.stub'; +import { authStub } from './auth.stub'; +import { userStub } from './user.stub'; + +const today = new Date(); +const tomorrow = new Date(); +const yesterday = new Date(); +tomorrow.setDate(today.getDate() + 1); +yesterday.setDate(yesterday.getDate() - 1); + +const sharedLinkBytes = Buffer.from( + '2c2b646895f84753bff43fb696ad124f3b0faf2a0bd547406f26fa4a76b5c71990092baa536275654b2ab7a191fb21a6d6cd', + 'hex', +); + +const assetInfo: ExifResponseDto = { + make: 'camera-make', + model: 'camera-model', + exifImageWidth: 500, + exifImageHeight: 500, + fileSizeInByte: 100, + orientation: 'orientation', + dateTimeOriginal: today, + modifyDate: today, + timeZone: 'America/Los_Angeles', + lensModel: 'fancy', + fNumber: 100, + focalLength: 100, + iso: 100, + exposureTime: '1/16', + latitude: 100, + longitude: 100, + city: 'city', + state: 'state', + country: 'country', + description: 'description', + projectionType: null, +}; + +const assetResponse: AssetResponseDto = { + id: 'id_1', + deviceAssetId: 'device_asset_id_1', + ownerId: 'user_id_1', + deviceId: 'device_id_1', + type: AssetType.VIDEO, + originalPath: 'fake_path/jpeg', + originalFileName: 'asset_1.jpeg', + resized: false, + thumbhash: null, + fileModifiedAt: today, + fileCreatedAt: today, + updatedAt: today, + isFavorite: false, + isArchived: false, + smartInfo: { + tags: [], + objects: ['a', 'b', 'c'], + }, + duration: '0:00:00.00000', + exifInfo: assetInfo, + livePhotoVideoId: null, + tags: [], + people: [], + checksum: 'ZmlsZSBoYXNo', +}; + +const albumResponse: AlbumResponseDto = { + albumName: 'Test Album', + albumThumbnailAssetId: null, + createdAt: today, + updatedAt: today, + id: 'album-123', + ownerId: 'admin_id', + owner: mapUser(userStub.admin), + sharedUsers: [], + shared: false, + assets: [], + assetCount: 1, +}; + +export const sharedLinkStub = { + individual: Object.freeze({ + id: '123', + userId: authStub.admin.id, + user: userStub.admin, + key: sharedLinkBytes, + type: SharedLinkType.INDIVIDUAL, + createdAt: today, + expiresAt: tomorrow, + allowUpload: true, + allowDownload: true, + showExif: true, + album: undefined, + description: null, + assets: [assetStub.image], + } as SharedLinkEntity), + valid: Object.freeze({ + id: '123', + userId: authStub.admin.id, + user: userStub.admin, + key: sharedLinkBytes, + type: SharedLinkType.ALBUM, + createdAt: today, + expiresAt: tomorrow, + allowUpload: true, + allowDownload: true, + showExif: true, + album: undefined, + albumId: null, + description: null, + assets: [], + } as SharedLinkEntity), + expired: Object.freeze({ + id: '123', + userId: authStub.admin.id, + user: userStub.admin, + key: sharedLinkBytes, + type: SharedLinkType.ALBUM, + createdAt: today, + expiresAt: yesterday, + allowUpload: true, + allowDownload: true, + showExif: true, + description: null, + albumId: null, + assets: [], + } as SharedLinkEntity), + readonlyNoExif: Object.freeze({ + id: '123', + userId: authStub.admin.id, + user: userStub.admin, + key: sharedLinkBytes, + type: SharedLinkType.ALBUM, + createdAt: today, + expiresAt: tomorrow, + allowUpload: false, + allowDownload: false, + showExif: false, + description: null, + assets: [], + albumId: 'album-123', + album: { + id: 'album-123', + ownerId: authStub.admin.id, + owner: userStub.admin, + albumName: 'Test Album', + createdAt: today, + updatedAt: today, + albumThumbnailAsset: null, + albumThumbnailAssetId: null, + sharedUsers: [], + sharedLinks: [], + assets: [ + { + id: 'id_1', + owner: userStub.user1, + ownerId: 'user_id_1', + deviceAssetId: 'device_asset_id_1', + deviceId: 'device_id_1', + type: AssetType.VIDEO, + originalPath: 'fake_path/jpeg', + resizePath: '', + checksum: Buffer.from('file hash', 'utf8'), + fileModifiedAt: today, + fileCreatedAt: today, + createdAt: today, + updatedAt: today, + isFavorite: false, + isArchived: false, + isReadOnly: false, + smartInfo: { + assetId: 'id_1', + tags: [], + objects: ['a', 'b', 'c'], + asset: null as any, + clipEmbedding: [0.12, 0.13, 0.14], + }, + webpPath: '', + thumbhash: null, + encodedVideoPath: '', + duration: null, + isVisible: true, + livePhotoVideo: null, + livePhotoVideoId: null, + originalFileName: 'asset_1.jpeg', + exifInfo: { + projectionType: null, + livePhotoCID: null, + assetId: 'id_1', + description: 'description', + exifImageWidth: 500, + exifImageHeight: 500, + fileSizeInByte: 100, + orientation: 'orientation', + dateTimeOriginal: today, + modifyDate: today, + timeZone: 'America/Los_Angeles', + latitude: 100, + longitude: 100, + city: 'city', + state: 'state', + country: 'country', + make: 'camera-make', + model: 'camera-model', + lensModel: 'fancy', + fNumber: 100, + focalLength: 100, + iso: 100, + exposureTime: '1/16', + fps: 100, + asset: null as any, + exifTextSearchableColumn: '', + }, + tags: [], + sharedLinks: [], + faces: [], + sidecarPath: null, + }, + ], + }, + }), +}; + +export const sharedLinkResponseStub = { + valid: Object.freeze({ + allowDownload: true, + allowUpload: true, + assets: [], + createdAt: today, + description: null, + expiresAt: tomorrow, + id: '123', + key: sharedLinkBytes.toString('base64url'), + showExif: true, + type: SharedLinkType.ALBUM, + userId: 'admin_id', + }), + expired: Object.freeze({ + album: undefined, + allowDownload: true, + allowUpload: true, + assets: [], + createdAt: today, + description: null, + expiresAt: yesterday, + id: '123', + key: sharedLinkBytes.toString('base64url'), + showExif: true, + type: SharedLinkType.ALBUM, + userId: 'admin_id', + }), + readonly: Object.freeze({ + id: '123', + userId: 'admin_id', + key: sharedLinkBytes.toString('base64url'), + type: SharedLinkType.ALBUM, + createdAt: today, + expiresAt: tomorrow, + description: null, + allowUpload: false, + allowDownload: false, + showExif: true, + album: albumResponse, + assets: [assetResponse], + }), + readonlyNoExif: Object.freeze({ + id: '123', + userId: 'admin_id', + key: sharedLinkBytes.toString('base64url'), + type: SharedLinkType.ALBUM, + createdAt: today, + expiresAt: tomorrow, + description: null, + allowUpload: false, + allowDownload: false, + showExif: false, + album: albumResponse, + assets: [{ ...assetResponse, exifInfo: undefined }], + }), +}; diff --git a/server/test/fixtures/system-config.stub.ts b/server/test/fixtures/system-config.stub.ts new file mode 100644 index 0000000000..dd34910717 --- /dev/null +++ b/server/test/fixtures/system-config.stub.ts @@ -0,0 +1,25 @@ +import { SystemConfigEntity, SystemConfigKey } from '@app/infra/entities'; + +export const systemConfigStub: Record = { + defaults: [], + enabled: [ + { key: SystemConfigKey.OAUTH_ENABLED, value: true }, + { key: SystemConfigKey.OAUTH_AUTO_REGISTER, value: true }, + { key: SystemConfigKey.OAUTH_AUTO_LAUNCH, value: false }, + { key: SystemConfigKey.OAUTH_BUTTON_TEXT, value: 'OAuth' }, + ], + disabled: [{ key: SystemConfigKey.PASSWORD_LOGIN_ENABLED, value: false }], + noAutoRegister: [ + { key: SystemConfigKey.OAUTH_ENABLED, value: true }, + { key: SystemConfigKey.OAUTH_AUTO_LAUNCH, value: false }, + { key: SystemConfigKey.OAUTH_AUTO_REGISTER, value: false }, + { key: SystemConfigKey.OAUTH_BUTTON_TEXT, value: 'OAuth' }, + ], + override: [ + { key: SystemConfigKey.OAUTH_ENABLED, value: true }, + { key: SystemConfigKey.OAUTH_AUTO_REGISTER, value: true }, + { key: SystemConfigKey.OAUTH_MOBILE_OVERRIDE_ENABLED, value: true }, + { key: SystemConfigKey.OAUTH_MOBILE_REDIRECT_URI, value: 'http://mobile-redirect' }, + { key: SystemConfigKey.OAUTH_BUTTON_TEXT, value: 'OAuth' }, + ], +}; diff --git a/server/test/fixtures/tag.stub.ts b/server/test/fixtures/tag.stub.ts new file mode 100644 index 0000000000..cffae00325 --- /dev/null +++ b/server/test/fixtures/tag.stub.ts @@ -0,0 +1,24 @@ +import { TagResponseDto } from '@app/domain'; +import { TagEntity, TagType } from '@app/infra/entities'; +import { userStub } from './user.stub'; + +export const tagStub = { + tag1: Object.freeze({ + id: 'tag-1', + name: 'Tag1', + type: TagType.CUSTOM, + userId: userStub.admin.id, + user: userStub.admin, + renameTagId: null, + assets: [], + }), +}; + +export const tagResponseStub = { + tag1: Object.freeze({ + id: 'tag-1', + name: 'Tag1', + type: 'CUSTOM', + userId: 'admin_id', + }), +}; diff --git a/server/test/fixtures/user-token.stub.ts b/server/test/fixtures/user-token.stub.ts new file mode 100644 index 0000000000..975318e215 --- /dev/null +++ b/server/test/fixtures/user-token.stub.ts @@ -0,0 +1,25 @@ +import { UserTokenEntity } from '@app/infra/entities'; +import { userStub } from './user.stub'; + +export const userTokenStub = { + userToken: Object.freeze({ + id: 'token-id', + token: 'auth_token', + userId: userStub.user1.id, + user: userStub.user1, + createdAt: new Date('2021-01-01'), + updatedAt: new Date(), + deviceType: '', + deviceOS: '', + }), + inactiveToken: Object.freeze({ + id: 'not_active', + token: 'auth_token', + userId: userStub.user1.id, + user: userStub.user1, + createdAt: new Date('2021-01-01'), + updatedAt: new Date('2021-01-01'), + deviceType: 'Mobile', + deviceOS: 'Android', + }), +}; diff --git a/server/test/fixtures/user.stub.ts b/server/test/fixtures/user.stub.ts new file mode 100644 index 0000000000..4367bf60ef --- /dev/null +++ b/server/test/fixtures/user.stub.ts @@ -0,0 +1,69 @@ +import { UserEntity } from '@app/infra/entities'; +import { authStub } from './auth.stub'; + +export const userStub = { + admin: Object.freeze({ + ...authStub.admin, + password: 'admin_password', + firstName: 'admin_first_name', + lastName: 'admin_last_name', + storageLabel: 'admin', + externalPath: null, + oauthId: '', + shouldChangePassword: false, + profileImagePath: '', + createdAt: new Date('2021-01-01'), + deletedAt: null, + updatedAt: new Date('2021-01-01'), + tags: [], + assets: [], + }), + user1: Object.freeze({ + ...authStub.user1, + password: 'immich_password', + firstName: 'immich_first_name', + lastName: 'immich_last_name', + storageLabel: null, + externalPath: null, + oauthId: '', + shouldChangePassword: false, + profileImagePath: '', + createdAt: new Date('2021-01-01'), + deletedAt: null, + updatedAt: new Date('2021-01-01'), + tags: [], + assets: [], + }), + user2: Object.freeze({ + ...authStub.user2, + password: 'immich_password', + firstName: 'immich_first_name', + lastName: 'immich_last_name', + storageLabel: null, + externalPath: null, + oauthId: '', + shouldChangePassword: false, + profileImagePath: '', + createdAt: new Date('2021-01-01'), + deletedAt: null, + updatedAt: new Date('2021-01-01'), + tags: [], + assets: [], + }), + storageLabel: Object.freeze({ + ...authStub.user1, + password: 'immich_password', + firstName: 'immich_first_name', + lastName: 'immich_last_name', + storageLabel: 'label-1', + externalPath: null, + oauthId: '', + shouldChangePassword: false, + profileImagePath: '', + createdAt: new Date('2021-01-01'), + deletedAt: null, + updatedAt: new Date('2021-01-01'), + tags: [], + assets: [], + }), +};