1
0
Fork 0
mirror of https://github.com/immich-app/immich.git synced 2025-03-01 15:11:21 +01:00

refactor(server): test fixtures (#3491)

This commit is contained in:
Jason Rasmussen 2023-07-31 21:28:07 -04:00 committed by GitHub
parent 5f9dfa9493
commit 9e085c1071
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
32 changed files with 1545 additions and 1538 deletions

View file

@ -8,7 +8,7 @@ import {
newAssetRepositoryMock, newAssetRepositoryMock,
newJobRepositoryMock, newJobRepositoryMock,
newUserRepositoryMock, newUserRepositoryMock,
userEntityStub, userStub,
} from '@test'; } from '@test';
import _ from 'lodash'; import _ from 'lodash';
import { IAssetRepository } from '../asset'; import { IAssetRepository } from '../asset';
@ -326,12 +326,12 @@ describe(AlbumService.name, () => {
accessMock.album.hasOwnerAccess.mockResolvedValue(true); accessMock.album.hasOwnerAccess.mockResolvedValue(true);
albumMock.getByIds.mockResolvedValue([_.cloneDeep(albumStub.sharedWithAdmin)]); albumMock.getByIds.mockResolvedValue([_.cloneDeep(albumStub.sharedWithAdmin)]);
albumMock.update.mockResolvedValue(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] }); await sut.addUsers(authStub.user1, albumStub.sharedWithAdmin.id, { sharedUserIds: [authStub.user2.id] });
expect(albumMock.update).toHaveBeenCalledWith({ expect(albumMock.update).toHaveBeenCalledWith({
id: albumStub.sharedWithAdmin.id, id: albumStub.sharedWithAdmin.id,
updatedAt: expect.any(Date), 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]); albumMock.getByIds.mockResolvedValue([albumStub.sharedWithUser]);
await expect( await expect(
sut.removeUser(authStub.admin, albumStub.sharedWithUser.id, userEntityStub.user1.id), sut.removeUser(authStub.admin, albumStub.sharedWithUser.id, userStub.user1.id),
).resolves.toBeUndefined(); ).resolves.toBeUndefined();
expect(albumMock.update).toHaveBeenCalledTimes(1); expect(albumMock.update).toHaveBeenCalledTimes(1);

View file

@ -1,7 +1,7 @@
import { AssetType } from '@app/infra/entities'; import { AssetType } from '@app/infra/entities';
import { BadRequestException, UnauthorizedException } from '@nestjs/common'; import { BadRequestException, UnauthorizedException } from '@nestjs/common';
import { import {
assetEntityStub, assetStub,
authStub, authStub,
IAccessRepositoryMock, IAccessRepositoryMock,
newAccessRepositoryMock, newAccessRepositoryMock,
@ -246,7 +246,7 @@ describe(AssetService.name, () => {
describe('getMapMarkers', () => { describe('getMapMarkers', () => {
it('should get geo information of assets', async () => { it('should get geo information of assets', async () => {
assetMock.getMapMarkers.mockResolvedValue( assetMock.getMapMarkers.mockResolvedValue(
[assetEntityStub.withLocation].map((asset) => ({ [assetStub.withLocation].map((asset) => ({
id: asset.id, id: asset.id,
/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */ /* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */
@ -261,7 +261,7 @@ describe(AssetService.name, () => {
expect(markers).toHaveLength(1); expect(markers).toHaveLength(1);
expect(markers[0]).toEqual({ expect(markers[0]).toEqual({
id: assetEntityStub.withLocation.id, id: assetStub.withLocation.id,
lat: 100, lat: 100,
lon: 100, lon: 100,
}); });
@ -308,14 +308,14 @@ describe(AssetService.name, () => {
it('should set the title correctly', async () => { it('should set the title correctly', async () => {
when(assetMock.getByDate) when(assetMock.getByDate)
.calledWith(authStub.admin.id, new Date('2022-06-15T00:00:00.000Z')) .calledWith(authStub.admin.id, new Date('2022-06-15T00:00:00.000Z'))
.mockResolvedValue([assetEntityStub.image]); .mockResolvedValue([assetStub.image]);
when(assetMock.getByDate) when(assetMock.getByDate)
.calledWith(authStub.admin.id, new Date('2021-06-15T00:00:00.000Z')) .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([ 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: '1 year since...', assets: [mapAsset(assetStub.image)] },
{ title: '2 years since...', assets: [mapAsset(assetEntityStub.video)] }, { title: '2 years since...', assets: [mapAsset(assetStub.video)] },
]); ]);
expect(assetMock.getByDate).toHaveBeenCalledTimes(2); expect(assetMock.getByDate).toHaveBeenCalledTimes(2);
@ -352,12 +352,12 @@ describe(AssetService.name, () => {
const stream = new Readable(); const stream = new Readable();
accessMock.asset.hasOwnerAccess.mockResolvedValue(true); accessMock.asset.hasOwnerAccess.mockResolvedValue(true);
assetMock.getByIds.mockResolvedValue([assetEntityStub.image]); assetMock.getByIds.mockResolvedValue([assetStub.image]);
storageMock.createReadStream.mockResolvedValue({ stream }); storageMock.createReadStream.mockResolvedValue({ stream });
await expect(sut.downloadFile(authStub.admin, 'asset-1')).resolves.toEqual({ 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 () => { it('should download an archive', async () => {
@ -368,7 +368,7 @@ describe(AssetService.name, () => {
}; };
accessMock.asset.hasOwnerAccess.mockResolvedValue(true); accessMock.asset.hasOwnerAccess.mockResolvedValue(true);
assetMock.getByIds.mockResolvedValue([assetEntityStub.noResizePath, assetEntityStub.noWebpPath]); assetMock.getByIds.mockResolvedValue([assetStub.noResizePath, assetStub.noWebpPath]);
storageMock.createZipStream.mockReturnValue(archiveMock); storageMock.createZipStream.mockReturnValue(archiveMock);
await expect(sut.downloadArchive(authStub.admin, { assetIds: ['asset-1', 'asset-2'] })).resolves.toEqual({ 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); accessMock.asset.hasOwnerAccess.mockResolvedValue(true);
assetMock.getByIds.mockResolvedValue([assetEntityStub.noResizePath, assetEntityStub.noResizePath]); assetMock.getByIds.mockResolvedValue([assetStub.noResizePath, assetStub.noResizePath]);
storageMock.createZipStream.mockReturnValue(archiveMock); storageMock.createZipStream.mockReturnValue(archiveMock);
await expect(sut.downloadArchive(authStub.admin, { assetIds: ['asset-1', 'asset-2'] })).resolves.toEqual({ 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 () => { it('should return a list of archives (assetIds)', async () => {
accessMock.asset.hasOwnerAccess.mockResolvedValue(true); 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']; const assetIds = ['asset-1', 'asset-2'];
await expect(sut.getDownloadInfo(authStub.admin, { assetIds })).resolves.toEqual(downloadResponse); 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 () => { it('should return a list of archives (albumId)', async () => {
accessMock.album.hasOwnerAccess.mockResolvedValue(true); accessMock.album.hasOwnerAccess.mockResolvedValue(true);
assetMock.getByAlbumId.mockResolvedValue({ assetMock.getByAlbumId.mockResolvedValue({
items: [assetEntityStub.image, assetEntityStub.video], items: [assetStub.image, assetStub.video],
hasNextPage: false, hasNextPage: false,
}); });
@ -431,7 +431,7 @@ describe(AssetService.name, () => {
it('should return a list of archives (userId)', async () => { it('should return a list of archives (userId)', async () => {
assetMock.getByUserId.mockResolvedValue({ assetMock.getByUserId.mockResolvedValue({
items: [assetEntityStub.image, assetEntityStub.video], items: [assetStub.image, assetStub.video],
hasNextPage: false, hasNextPage: false,
}); });
@ -445,10 +445,10 @@ describe(AssetService.name, () => {
it('should split archives by size', async () => { it('should split archives by size', async () => {
assetMock.getByUserId.mockResolvedValue({ assetMock.getByUserId.mockResolvedValue({
items: [ items: [
{ ...assetEntityStub.image, id: 'asset-1' }, { ...assetStub.image, id: 'asset-1' },
{ ...assetEntityStub.video, id: 'asset-2' }, { ...assetStub.video, id: 'asset-2' },
{ ...assetEntityStub.withLocation, id: 'asset-3' }, { ...assetStub.withLocation, id: 'asset-3' },
{ ...assetEntityStub.noWebpPath, id: 'asset-4' }, { ...assetStub.noWebpPath, id: 'asset-4' },
], ],
hasNextPage: false, hasNextPage: false,
}); });
@ -470,18 +470,18 @@ describe(AssetService.name, () => {
it('should include the video portion of a live photo', async () => { it('should include the video portion of a live photo', async () => {
accessMock.asset.hasOwnerAccess.mockResolvedValue(true); accessMock.asset.hasOwnerAccess.mockResolvedValue(true);
when(assetMock.getByIds) when(assetMock.getByIds)
.calledWith([assetEntityStub.livePhotoStillAsset.id]) .calledWith([assetStub.livePhotoStillAsset.id])
.mockResolvedValue([assetEntityStub.livePhotoStillAsset]); .mockResolvedValue([assetStub.livePhotoStillAsset]);
when(assetMock.getByIds) when(assetMock.getByIds)
.calledWith([assetEntityStub.livePhotoMotionAsset.id]) .calledWith([assetStub.livePhotoMotionAsset.id])
.mockResolvedValue([assetEntityStub.livePhotoMotionAsset]); .mockResolvedValue([assetStub.livePhotoMotionAsset]);
const assetIds = [assetEntityStub.livePhotoStillAsset.id]; const assetIds = [assetStub.livePhotoStillAsset.id];
await expect(sut.getDownloadInfo(authStub.admin, { assetIds })).resolves.toEqual({ await expect(sut.getDownloadInfo(authStub.admin, { assetIds })).resolves.toEqual({
totalSize: 125_000, totalSize: 125_000,
archives: [ archives: [
{ {
assetIds: [assetEntityStub.livePhotoStillAsset.id, assetEntityStub.livePhotoMotionAsset.id], assetIds: [assetStub.livePhotoStillAsset.id, assetStub.livePhotoMotionAsset.id],
size: 125_000, size: 125_000,
}, },
], ],

View file

@ -12,8 +12,8 @@ import {
newUserTokenRepositoryMock, newUserTokenRepositoryMock,
sharedLinkStub, sharedLinkStub,
systemConfigStub, systemConfigStub,
userEntityStub, userStub,
userTokenEntityStub, userTokenStub,
} from '@test'; } from '@test';
import { IncomingHttpHeaders } from 'http'; import { IncomingHttpHeaders } from 'http';
import { generators, Issuer } from 'openid-client'; import { generators, Issuer } from 'openid-client';
@ -112,15 +112,15 @@ describe('AuthService', () => {
}); });
it('should successfully log the user in', async () => { it('should successfully log the user in', async () => {
userMock.getByEmail.mockResolvedValue(userEntityStub.user1); userMock.getByEmail.mockResolvedValue(userStub.user1);
userTokenMock.create.mockResolvedValue(userTokenEntityStub.userToken); userTokenMock.create.mockResolvedValue(userTokenStub.userToken);
await expect(sut.login(fixtures.login, loginDetails)).resolves.toEqual(loginResponseStub.user1password); await expect(sut.login(fixtures.login, loginDetails)).resolves.toEqual(loginResponseStub.user1password);
expect(userMock.getByEmail).toHaveBeenCalledTimes(1); expect(userMock.getByEmail).toHaveBeenCalledTimes(1);
}); });
it('should generate the cookie headers (insecure)', async () => { it('should generate the cookie headers (insecure)', async () => {
userMock.getByEmail.mockResolvedValue(userEntityStub.user1); userMock.getByEmail.mockResolvedValue(userStub.user1);
userTokenMock.create.mockResolvedValue(userTokenEntityStub.userToken); userTokenMock.create.mockResolvedValue(userTokenStub.userToken);
await expect( await expect(
sut.login(fixtures.login, { sut.login(fixtures.login, {
clientIp: '127.0.0.1', clientIp: '127.0.0.1',
@ -246,10 +246,10 @@ describe('AuthService', () => {
}); });
it('should validate using authorization header', async () => { it('should validate using authorization header', async () => {
userMock.get.mockResolvedValue(userEntityStub.user1); userMock.get.mockResolvedValue(userStub.user1);
userTokenMock.getByToken.mockResolvedValue(userTokenEntityStub.userToken); userTokenMock.getByToken.mockResolvedValue(userTokenStub.userToken);
const client = { request: { headers: { authorization: 'Bearer auth_token' } } }; 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 () => { it('should accept a base64url key', async () => {
shareMock.getByKey.mockResolvedValue(sharedLinkStub.valid); 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') }; const headers: IncomingHttpHeaders = { 'x-immich-share-key': sharedLinkStub.valid.key.toString('base64url') };
await expect(sut.validate(headers, {})).resolves.toEqual(authStub.adminSharedLink); await expect(sut.validate(headers, {})).resolves.toEqual(authStub.adminSharedLink);
expect(shareMock.getByKey).toHaveBeenCalledWith(sharedLinkStub.valid.key); expect(shareMock.getByKey).toHaveBeenCalledWith(sharedLinkStub.valid.key);
@ -283,7 +283,7 @@ describe('AuthService', () => {
it('should accept a hex key', async () => { it('should accept a hex key', async () => {
shareMock.getByKey.mockResolvedValue(sharedLinkStub.valid); 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') }; const headers: IncomingHttpHeaders = { 'x-immich-share-key': sharedLinkStub.valid.key.toString('hex') };
await expect(sut.validate(headers, {})).resolves.toEqual(authStub.adminSharedLink); await expect(sut.validate(headers, {})).resolves.toEqual(authStub.adminSharedLink);
expect(shareMock.getByKey).toHaveBeenCalledWith(sharedLinkStub.valid.key); expect(shareMock.getByKey).toHaveBeenCalledWith(sharedLinkStub.valid.key);
@ -298,16 +298,16 @@ describe('AuthService', () => {
}); });
it('should return an auth dto', async () => { 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' }; 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 () => { it('should update when access time exceeds an hour', async () => {
userTokenMock.getByToken.mockResolvedValue(userTokenEntityStub.inactiveToken); userTokenMock.getByToken.mockResolvedValue(userTokenStub.inactiveToken);
userTokenMock.save.mockResolvedValue(userTokenEntityStub.userToken); userTokenMock.save.mockResolvedValue(userTokenStub.userToken);
const headers: IncomingHttpHeaders = { cookie: 'immich_access_token=auth_token' }; 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({ expect(userTokenMock.save.mock.calls[0][0]).toMatchObject({
id: 'not_active', id: 'not_active',
token: 'auth_token', token: 'auth_token',
@ -338,7 +338,7 @@ describe('AuthService', () => {
describe('getDevices', () => { describe('getDevices', () => {
it('should get the devices', async () => { 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([ await expect(sut.getDevices(authStub.user1)).resolves.toEqual([
{ {
createdAt: '2021-01-01T00:00:00.000Z', createdAt: '2021-01-01T00:00:00.000Z',
@ -364,7 +364,7 @@ describe('AuthService', () => {
describe('logoutDevices', () => { describe('logoutDevices', () => {
it('should logout all devices', async () => { 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); await sut.logoutDevices(authStub.user1);
@ -429,24 +429,24 @@ describe('AuthService', () => {
it('should link an existing user', async () => { it('should link an existing user', async () => {
configMock.load.mockResolvedValue(systemConfigStub.noAutoRegister); configMock.load.mockResolvedValue(systemConfigStub.noAutoRegister);
userMock.getByEmail.mockResolvedValue(userEntityStub.user1); userMock.getByEmail.mockResolvedValue(userStub.user1);
userMock.update.mockResolvedValue(userEntityStub.user1); userMock.update.mockResolvedValue(userStub.user1);
userTokenMock.create.mockResolvedValue(userTokenEntityStub.userToken); userTokenMock.create.mockResolvedValue(userTokenStub.userToken);
await expect(sut.callback({ url: 'http://immich/auth/login?code=abc123' }, loginDetails)).resolves.toEqual( await expect(sut.callback({ url: 'http://immich/auth/login?code=abc123' }, loginDetails)).resolves.toEqual(
loginResponseStub.user1oauth, loginResponseStub.user1oauth,
); );
expect(userMock.getByEmail).toHaveBeenCalledTimes(1); 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 () => { it('should allow auto registering by default', async () => {
configMock.load.mockResolvedValue(systemConfigStub.enabled); configMock.load.mockResolvedValue(systemConfigStub.enabled);
userMock.getByEmail.mockResolvedValue(null); userMock.getByEmail.mockResolvedValue(null);
userMock.getAdmin.mockResolvedValue(userEntityStub.user1); userMock.getAdmin.mockResolvedValue(userStub.user1);
userMock.create.mockResolvedValue(userEntityStub.user1); userMock.create.mockResolvedValue(userStub.user1);
userTokenMock.create.mockResolvedValue(userTokenEntityStub.userToken); userTokenMock.create.mockResolvedValue(userTokenStub.userToken);
await expect(sut.callback({ url: 'http://immich/auth/login?code=abc123' }, loginDetails)).resolves.toEqual( await expect(sut.callback({ url: 'http://immich/auth/login?code=abc123' }, loginDetails)).resolves.toEqual(
loginResponseStub.user1oauth, loginResponseStub.user1oauth,
@ -458,8 +458,8 @@ describe('AuthService', () => {
it('should use the mobile redirect override', async () => { it('should use the mobile redirect override', async () => {
configMock.load.mockResolvedValue(systemConfigStub.override); configMock.load.mockResolvedValue(systemConfigStub.override);
userMock.getByOAuthId.mockResolvedValue(userEntityStub.user1); userMock.getByOAuthId.mockResolvedValue(userStub.user1);
userTokenMock.create.mockResolvedValue(userTokenEntityStub.userToken); userTokenMock.create.mockResolvedValue(userTokenStub.userToken);
await sut.callback({ url: `app.immich:/?code=abc123` }, loginDetails); 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 () => { it('should use the mobile redirect override for ios urls with multiple slashes', async () => {
configMock.load.mockResolvedValue(systemConfigStub.override); configMock.load.mockResolvedValue(systemConfigStub.override);
userMock.getByOAuthId.mockResolvedValue(userEntityStub.user1); userMock.getByOAuthId.mockResolvedValue(userStub.user1);
userTokenMock.create.mockResolvedValue(userTokenEntityStub.userToken); userTokenMock.create.mockResolvedValue(userTokenStub.userToken);
await sut.callback({ url: `app.immich:///?code=abc123` }, loginDetails); await sut.callback({ url: `app.immich:///?code=abc123` }, loginDetails);
@ -480,7 +480,7 @@ describe('AuthService', () => {
describe('link', () => { describe('link', () => {
it('should link an account', async () => { it('should link an account', async () => {
configMock.load.mockResolvedValue(systemConfigStub.enabled); 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' }); await sut.link(authStub.user1, { url: 'http://immich/user-settings?code=abc123' });
@ -502,7 +502,7 @@ describe('AuthService', () => {
describe('unlink', () => { describe('unlink', () => {
it('should unlink an account', async () => { it('should unlink an account', async () => {
configMock.load.mockResolvedValue(systemConfigStub.enabled); configMock.load.mockResolvedValue(systemConfigStub.enabled);
userMock.update.mockResolvedValue(userEntityStub.user1); userMock.update.mockResolvedValue(userStub.user1);
await sut.unlink(authStub.user1); await sut.unlink(authStub.user1);

View file

@ -1,5 +1,5 @@
import { import {
assetEntityStub, assetStub,
faceStub, faceStub,
newAssetRepositoryMock, newAssetRepositoryMock,
newFaceRepositoryMock, newFaceRepositoryMock,
@ -133,7 +133,7 @@ describe(FacialRecognitionService.name, () => {
describe('handleQueueRecognizeFaces', () => { describe('handleQueueRecognizeFaces', () => {
it('should queue missing assets', async () => { it('should queue missing assets', async () => {
assetMock.getWithout.mockResolvedValue({ assetMock.getWithout.mockResolvedValue({
items: [assetEntityStub.image], items: [assetStub.image],
hasNextPage: false, hasNextPage: false,
}); });
await sut.handleQueueRecognizeFaces({}); await sut.handleQueueRecognizeFaces({});
@ -141,13 +141,13 @@ describe(FacialRecognitionService.name, () => {
expect(assetMock.getWithout).toHaveBeenCalledWith({ skip: 0, take: 1000 }, WithoutProperty.FACES); expect(assetMock.getWithout).toHaveBeenCalledWith({ skip: 0, take: 1000 }, WithoutProperty.FACES);
expect(jobMock.queue).toHaveBeenCalledWith({ expect(jobMock.queue).toHaveBeenCalledWith({
name: JobName.RECOGNIZE_FACES, name: JobName.RECOGNIZE_FACES,
data: { id: assetEntityStub.image.id }, data: { id: assetStub.image.id },
}); });
}); });
it('should queue all assets', async () => { it('should queue all assets', async () => {
assetMock.getAll.mockResolvedValue({ assetMock.getAll.mockResolvedValue({
items: [assetEntityStub.image], items: [assetStub.image],
hasNextPage: false, hasNextPage: false,
}); });
personMock.deleteAll.mockResolvedValue(5); personMock.deleteAll.mockResolvedValue(5);
@ -158,24 +158,24 @@ describe(FacialRecognitionService.name, () => {
expect(assetMock.getAll).toHaveBeenCalled(); expect(assetMock.getAll).toHaveBeenCalled();
expect(jobMock.queue).toHaveBeenCalledWith({ expect(jobMock.queue).toHaveBeenCalledWith({
name: JobName.RECOGNIZE_FACES, name: JobName.RECOGNIZE_FACES,
data: { id: assetEntityStub.image.id }, data: { id: assetStub.image.id },
}); });
}); });
}); });
describe('handleRecognizeFaces', () => { describe('handleRecognizeFaces', () => {
it('should skip when no resize path', async () => { it('should skip when no resize path', async () => {
assetMock.getByIds.mockResolvedValue([assetEntityStub.noResizePath]); assetMock.getByIds.mockResolvedValue([assetStub.noResizePath]);
await sut.handleRecognizeFaces({ id: assetEntityStub.noResizePath.id }); await sut.handleRecognizeFaces({ id: assetStub.noResizePath.id });
expect(machineLearningMock.detectFaces).not.toHaveBeenCalled(); expect(machineLearningMock.detectFaces).not.toHaveBeenCalled();
}); });
it('should handle no results', async () => { it('should handle no results', async () => {
machineLearningMock.detectFaces.mockResolvedValue([]); machineLearningMock.detectFaces.mockResolvedValue([]);
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(machineLearningMock.detectFaces).toHaveBeenCalledWith({ expect(machineLearningMock.detectFaces).toHaveBeenCalledWith({
imagePath: assetEntityStub.image.resizePath, imagePath: assetStub.image.resizePath,
}); });
expect(faceMock.create).not.toHaveBeenCalled(); expect(faceMock.create).not.toHaveBeenCalled();
expect(jobMock.queue).not.toHaveBeenCalled(); expect(jobMock.queue).not.toHaveBeenCalled();
@ -184,8 +184,8 @@ describe(FacialRecognitionService.name, () => {
it('should match existing people', async () => { it('should match existing people', async () => {
machineLearningMock.detectFaces.mockResolvedValue([face.middle]); machineLearningMock.detectFaces.mockResolvedValue([face.middle]);
searchMock.searchFaces.mockResolvedValue(faceSearch.oneMatch); searchMock.searchFaces.mockResolvedValue(faceSearch.oneMatch);
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(faceMock.create).toHaveBeenCalledWith({ expect(faceMock.create).toHaveBeenCalledWith({
personId: 'person-1', personId: 'person-1',
@ -204,11 +204,11 @@ describe(FacialRecognitionService.name, () => {
machineLearningMock.detectFaces.mockResolvedValue([face.middle]); machineLearningMock.detectFaces.mockResolvedValue([face.middle]);
searchMock.searchFaces.mockResolvedValue(faceSearch.oneRemoteMatch); searchMock.searchFaces.mockResolvedValue(faceSearch.oneRemoteMatch);
personMock.create.mockResolvedValue(personStub.noName); 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({ expect(faceMock.create).toHaveBeenCalledWith({
personId: 'person-1', personId: 'person-1',
assetId: 'asset-id', assetId: 'asset-id',
@ -254,7 +254,7 @@ describe(FacialRecognitionService.name, () => {
}); });
it('should skip an asset without a thumbnail', async () => { it('should skip an asset without a thumbnail', async () => {
assetMock.getByIds.mockResolvedValue([assetEntityStub.noResizePath]); assetMock.getByIds.mockResolvedValue([assetStub.noResizePath]);
await sut.handleGenerateFaceThumbnail(face.middle); await sut.handleGenerateFaceThumbnail(face.middle);
@ -262,7 +262,7 @@ describe(FacialRecognitionService.name, () => {
}); });
it('should generate a thumbnail', async () => { it('should generate a thumbnail', async () => {
assetMock.getByIds.mockResolvedValue([assetEntityStub.image]); assetMock.getByIds.mockResolvedValue([assetStub.image]);
await sut.handleGenerateFaceThumbnail(face.middle); await sut.handleGenerateFaceThumbnail(face.middle);
@ -285,7 +285,7 @@ describe(FacialRecognitionService.name, () => {
}); });
it('should generate a thumbnail without going negative', async () => { it('should generate a thumbnail without going negative', async () => {
assetMock.getByIds.mockResolvedValue([assetEntityStub.image]); assetMock.getByIds.mockResolvedValue([assetStub.image]);
await sut.handleGenerateFaceThumbnail(face.start); await sut.handleGenerateFaceThumbnail(face.start);
@ -302,7 +302,7 @@ describe(FacialRecognitionService.name, () => {
}); });
it('should generate a thumbnail without overflowing', async () => { it('should generate a thumbnail without overflowing', async () => {
assetMock.getByIds.mockResolvedValue([assetEntityStub.image]); assetMock.getByIds.mockResolvedValue([assetStub.image]);
await sut.handleGenerateFaceThumbnail(face.end); await sut.handleGenerateFaceThumbnail(face.end);

View file

@ -1,7 +1,7 @@
import { SystemConfig } from '@app/infra/entities'; import { SystemConfig } from '@app/infra/entities';
import { BadRequestException } from '@nestjs/common'; import { BadRequestException } from '@nestjs/common';
import { import {
assetEntityStub, assetStub,
asyncTick, asyncTick,
newAssetRepositoryMock, newAssetRepositoryMock,
newCommunicationRepositoryMock, newCommunicationRepositoryMock,
@ -300,7 +300,7 @@ describe(JobService.name, () => {
for (const { item, jobs } of tests) { for (const { item, jobs } of tests) {
it(`should queue ${jobs.length} jobs when a ${item.name} job finishes successfully`, async () => { 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') { if (item.name === JobName.GENERATE_JPEG_THUMBNAIL && item.data.source === 'upload') {
assetMock.getByIds.mockResolvedValue([assetEntityStub.livePhotoMotionAsset]); assetMock.getByIds.mockResolvedValue([assetStub.livePhotoMotionAsset]);
} else { } else {
assetMock.getByIds.mockResolvedValue([]); assetMock.getByIds.mockResolvedValue([]);
} }

View file

@ -1,6 +1,6 @@
import { AssetType, SystemConfigKey, TranscodePolicy, VideoCodec } from '@app/infra/entities'; import { AssetType, SystemConfigKey, TranscodePolicy, VideoCodec } from '@app/infra/entities';
import { import {
assetEntityStub, assetStub,
newAssetRepositoryMock, newAssetRepositoryMock,
newJobRepositoryMock, newJobRepositoryMock,
newMediaRepositoryMock, newMediaRepositoryMock,
@ -40,7 +40,7 @@ describe(MediaService.name, () => {
describe('handleQueueGenerateThumbnails', () => { describe('handleQueueGenerateThumbnails', () => {
it('should queue all assets', async () => { it('should queue all assets', async () => {
assetMock.getAll.mockResolvedValue({ assetMock.getAll.mockResolvedValue({
items: [assetEntityStub.image], items: [assetStub.image],
hasNextPage: false, hasNextPage: false,
}); });
@ -50,13 +50,13 @@ describe(MediaService.name, () => {
expect(assetMock.getWithout).not.toHaveBeenCalled(); expect(assetMock.getWithout).not.toHaveBeenCalled();
expect(jobMock.queue).toHaveBeenCalledWith({ expect(jobMock.queue).toHaveBeenCalledWith({
name: JobName.GENERATE_JPEG_THUMBNAIL, 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 () => { it('should queue all assets with missing resize path', async () => {
assetMock.getWithout.mockResolvedValue({ assetMock.getWithout.mockResolvedValue({
items: [assetEntityStub.noResizePath], items: [assetStub.noResizePath],
hasNextPage: false, hasNextPage: false,
}); });
@ -66,13 +66,13 @@ describe(MediaService.name, () => {
expect(assetMock.getWithout).toHaveBeenCalledWith({ skip: 0, take: 1000 }, WithoutProperty.THUMBNAIL); expect(assetMock.getWithout).toHaveBeenCalledWith({ skip: 0, take: 1000 }, WithoutProperty.THUMBNAIL);
expect(jobMock.queue).toHaveBeenCalledWith({ expect(jobMock.queue).toHaveBeenCalledWith({
name: JobName.GENERATE_JPEG_THUMBNAIL, 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 () => { it('should queue all assets with missing webp path', async () => {
assetMock.getWithout.mockResolvedValue({ assetMock.getWithout.mockResolvedValue({
items: [assetEntityStub.noWebpPath], items: [assetStub.noWebpPath],
hasNextPage: false, hasNextPage: false,
}); });
@ -82,13 +82,13 @@ describe(MediaService.name, () => {
expect(assetMock.getWithout).toHaveBeenCalledWith({ skip: 0, take: 1000 }, WithoutProperty.THUMBNAIL); expect(assetMock.getWithout).toHaveBeenCalledWith({ skip: 0, take: 1000 }, WithoutProperty.THUMBNAIL);
expect(jobMock.queue).toHaveBeenCalledWith({ expect(jobMock.queue).toHaveBeenCalledWith({
name: JobName.GENERATE_WEBP_THUMBNAIL, name: JobName.GENERATE_WEBP_THUMBNAIL,
data: { id: assetEntityStub.image.id }, data: { id: assetStub.image.id },
}); });
}); });
it('should queue all assets with missing thumbhash', async () => { it('should queue all assets with missing thumbhash', async () => {
assetMock.getWithout.mockResolvedValue({ assetMock.getWithout.mockResolvedValue({
items: [assetEntityStub.noThumbhash], items: [assetStub.noThumbhash],
hasNextPage: false, hasNextPage: false,
}); });
@ -98,7 +98,7 @@ describe(MediaService.name, () => {
expect(assetMock.getWithout).toHaveBeenCalledWith({ skip: 0, take: 1000 }, WithoutProperty.THUMBNAIL); expect(assetMock.getWithout).toHaveBeenCalledWith({ skip: 0, take: 1000 }, WithoutProperty.THUMBNAIL);
expect(jobMock.queue).toHaveBeenCalledWith({ expect(jobMock.queue).toHaveBeenCalledWith({
name: JobName.GENERATE_THUMBHASH_THUMBNAIL, name: JobName.GENERATE_THUMBHASH_THUMBNAIL,
data: { id: assetEntityStub.image.id }, data: { id: assetStub.image.id },
}); });
}); });
}); });
@ -106,14 +106,14 @@ describe(MediaService.name, () => {
describe('handleGenerateJpegThumbnail', () => { describe('handleGenerateJpegThumbnail', () => {
it('should skip thumbnail generation if asset not found', async () => { it('should skip thumbnail generation if asset not found', async () => {
assetMock.getByIds.mockResolvedValue([]); assetMock.getByIds.mockResolvedValue([]);
await sut.handleGenerateJpegThumbnail({ id: assetEntityStub.image.id }); await sut.handleGenerateJpegThumbnail({ id: assetStub.image.id });
expect(mediaMock.resize).not.toHaveBeenCalled(); expect(mediaMock.resize).not.toHaveBeenCalled();
expect(assetMock.save).not.toHaveBeenCalledWith(); expect(assetMock.save).not.toHaveBeenCalledWith();
}); });
it('should generate a thumbnail for an image', async () => { it('should generate a thumbnail for an image', async () => {
assetMock.getByIds.mockResolvedValue([assetEntityStub.image]); assetMock.getByIds.mockResolvedValue([assetStub.image]);
await sut.handleGenerateJpegThumbnail({ id: assetEntityStub.image.id }); await sut.handleGenerateJpegThumbnail({ id: assetStub.image.id });
expect(storageMock.mkdirSync).toHaveBeenCalledWith('upload/thumbs/user-id'); expect(storageMock.mkdirSync).toHaveBeenCalledWith('upload/thumbs/user-id');
expect(mediaMock.resize).toHaveBeenCalledWith('/original/path.jpg', 'upload/thumbs/user-id/asset-id.jpeg', { 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 () => { it('should generate a thumbnail for a video', async () => {
assetMock.getByIds.mockResolvedValue([assetEntityStub.video]); assetMock.getByIds.mockResolvedValue([assetStub.video]);
await sut.handleGenerateJpegThumbnail({ id: assetEntityStub.video.id }); await sut.handleGenerateJpegThumbnail({ id: assetStub.video.id });
expect(storageMock.mkdirSync).toHaveBeenCalledWith('upload/thumbs/user-id'); expect(storageMock.mkdirSync).toHaveBeenCalledWith('upload/thumbs/user-id');
expect(mediaMock.extractVideoThumbnail).toHaveBeenCalledWith( expect(mediaMock.extractVideoThumbnail).toHaveBeenCalledWith(
@ -143,28 +143,28 @@ describe(MediaService.name, () => {
}); });
it('should run successfully', async () => { it('should run successfully', async () => {
assetMock.getByIds.mockResolvedValue([assetEntityStub.image]); assetMock.getByIds.mockResolvedValue([assetStub.image]);
await sut.handleGenerateJpegThumbnail({ id: assetEntityStub.image.id }); await sut.handleGenerateJpegThumbnail({ id: assetStub.image.id });
}); });
}); });
describe('handleGenerateWebpThumbnail', () => { describe('handleGenerateWebpThumbnail', () => {
it('should skip thumbnail generation if asset not found', async () => { it('should skip thumbnail generation if asset not found', async () => {
assetMock.getByIds.mockResolvedValue([]); assetMock.getByIds.mockResolvedValue([]);
await sut.handleGenerateWebpThumbnail({ id: assetEntityStub.image.id }); await sut.handleGenerateWebpThumbnail({ id: assetStub.image.id });
expect(mediaMock.resize).not.toHaveBeenCalled(); expect(mediaMock.resize).not.toHaveBeenCalled();
expect(assetMock.save).not.toHaveBeenCalledWith(); expect(assetMock.save).not.toHaveBeenCalledWith();
}); });
it('should skip thumbnail generate if resize path is missing', async () => { it('should skip thumbnail generate if resize path is missing', async () => {
assetMock.getByIds.mockResolvedValue([assetEntityStub.noResizePath]); assetMock.getByIds.mockResolvedValue([assetStub.noResizePath]);
await sut.handleGenerateWebpThumbnail({ id: assetEntityStub.noResizePath.id }); await sut.handleGenerateWebpThumbnail({ id: assetStub.noResizePath.id });
expect(mediaMock.resize).not.toHaveBeenCalled(); expect(mediaMock.resize).not.toHaveBeenCalled();
}); });
it('should generate a thumbnail', async () => { it('should generate a thumbnail', async () => {
assetMock.getByIds.mockResolvedValue([assetEntityStub.image]); assetMock.getByIds.mockResolvedValue([assetStub.image]);
await sut.handleGenerateWebpThumbnail({ id: assetEntityStub.image.id }); await sut.handleGenerateWebpThumbnail({ id: assetStub.image.id });
expect(mediaMock.resize).toHaveBeenCalledWith( expect(mediaMock.resize).toHaveBeenCalledWith(
'/uploads/user-id/thumbs/path.jpg', '/uploads/user-id/thumbs/path.jpg',
@ -178,22 +178,22 @@ describe(MediaService.name, () => {
describe('handleGenerateThumbhashThumbnail', () => { describe('handleGenerateThumbhashThumbnail', () => {
it('should skip thumbhash generation if asset not found', async () => { it('should skip thumbhash generation if asset not found', async () => {
assetMock.getByIds.mockResolvedValue([]); assetMock.getByIds.mockResolvedValue([]);
await sut.handleGenerateThumbhashThumbnail({ id: assetEntityStub.image.id }); await sut.handleGenerateThumbhashThumbnail({ id: assetStub.image.id });
expect(mediaMock.generateThumbhash).not.toHaveBeenCalled(); expect(mediaMock.generateThumbhash).not.toHaveBeenCalled();
}); });
it('should skip thumbhash generation if resize path is missing', async () => { it('should skip thumbhash generation if resize path is missing', async () => {
assetMock.getByIds.mockResolvedValue([assetEntityStub.noResizePath]); assetMock.getByIds.mockResolvedValue([assetStub.noResizePath]);
await sut.handleGenerateThumbhashThumbnail({ id: assetEntityStub.noResizePath.id }); await sut.handleGenerateThumbhashThumbnail({ id: assetStub.noResizePath.id });
expect(mediaMock.generateThumbhash).not.toHaveBeenCalled(); expect(mediaMock.generateThumbhash).not.toHaveBeenCalled();
}); });
it('should generate a thumbhash', async () => { it('should generate a thumbhash', async () => {
const thumbhashBuffer = Buffer.from('a thumbhash', 'utf8'); const thumbhashBuffer = Buffer.from('a thumbhash', 'utf8');
assetMock.getByIds.mockResolvedValue([assetEntityStub.image]); assetMock.getByIds.mockResolvedValue([assetStub.image]);
mediaMock.generateThumbhash.mockResolvedValue(thumbhashBuffer); 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(mediaMock.generateThumbhash).toHaveBeenCalledWith('/uploads/user-id/thumbs/path.jpg');
expect(assetMock.save).toHaveBeenCalledWith({ id: 'asset-id', thumbhash: thumbhashBuffer }); expect(assetMock.save).toHaveBeenCalledWith({ id: 'asset-id', thumbhash: thumbhashBuffer });
@ -203,7 +203,7 @@ describe(MediaService.name, () => {
describe('handleQueueVideoConversion', () => { describe('handleQueueVideoConversion', () => {
it('should queue all video assets', async () => { it('should queue all video assets', async () => {
assetMock.getAll.mockResolvedValue({ assetMock.getAll.mockResolvedValue({
items: [assetEntityStub.video], items: [assetStub.video],
hasNextPage: false, hasNextPage: false,
}); });
@ -213,13 +213,13 @@ describe(MediaService.name, () => {
expect(assetMock.getWithout).not.toHaveBeenCalled(); expect(assetMock.getWithout).not.toHaveBeenCalled();
expect(jobMock.queue).toHaveBeenCalledWith({ expect(jobMock.queue).toHaveBeenCalledWith({
name: JobName.VIDEO_CONVERSION, name: JobName.VIDEO_CONVERSION,
data: { id: assetEntityStub.video.id }, data: { id: assetStub.video.id },
}); });
}); });
it('should queue all video assets without encoded videos', async () => { it('should queue all video assets without encoded videos', async () => {
assetMock.getWithout.mockResolvedValue({ assetMock.getWithout.mockResolvedValue({
items: [assetEntityStub.video], items: [assetStub.video],
hasNextPage: false, hasNextPage: false,
}); });
@ -229,35 +229,35 @@ describe(MediaService.name, () => {
expect(assetMock.getWithout).toHaveBeenCalledWith({ skip: 0, take: 1000 }, WithoutProperty.ENCODED_VIDEO); expect(assetMock.getWithout).toHaveBeenCalledWith({ skip: 0, take: 1000 }, WithoutProperty.ENCODED_VIDEO);
expect(jobMock.queue).toHaveBeenCalledWith({ expect(jobMock.queue).toHaveBeenCalledWith({
name: JobName.VIDEO_CONVERSION, name: JobName.VIDEO_CONVERSION,
data: { id: assetEntityStub.video.id }, data: { id: assetStub.video.id },
}); });
}); });
}); });
describe('handleVideoConversion', () => { describe('handleVideoConversion', () => {
beforeEach(() => { beforeEach(() => {
assetMock.getByIds.mockResolvedValue([assetEntityStub.video]); assetMock.getByIds.mockResolvedValue([assetStub.video]);
}); });
it('should skip transcoding if asset not found', async () => { it('should skip transcoding if asset not found', async () => {
assetMock.getByIds.mockResolvedValue([]); assetMock.getByIds.mockResolvedValue([]);
await sut.handleVideoConversion({ id: assetEntityStub.video.id }); await sut.handleVideoConversion({ id: assetStub.video.id });
expect(mediaMock.probe).not.toHaveBeenCalled(); expect(mediaMock.probe).not.toHaveBeenCalled();
expect(mediaMock.transcode).not.toHaveBeenCalled(); expect(mediaMock.transcode).not.toHaveBeenCalled();
}); });
it('should skip transcoding if non-video asset', async () => { it('should skip transcoding if non-video asset', async () => {
assetMock.getByIds.mockResolvedValue([assetEntityStub.image]); assetMock.getByIds.mockResolvedValue([assetStub.image]);
await sut.handleVideoConversion({ id: assetEntityStub.image.id }); await sut.handleVideoConversion({ id: assetStub.image.id });
expect(mediaMock.probe).not.toHaveBeenCalled(); expect(mediaMock.probe).not.toHaveBeenCalled();
expect(mediaMock.transcode).not.toHaveBeenCalled(); expect(mediaMock.transcode).not.toHaveBeenCalled();
}); });
it('should transcode the longest stream', async () => { it('should transcode the longest stream', async () => {
assetMock.getByIds.mockResolvedValue([assetEntityStub.video]); assetMock.getByIds.mockResolvedValue([assetStub.video]);
mediaMock.probe.mockResolvedValue(probeStub.multipleVideoStreams); 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(mediaMock.probe).toHaveBeenCalledWith('/original/path.ext');
expect(configMock.load).toHaveBeenCalled(); expect(configMock.load).toHaveBeenCalled();
@ -282,23 +282,23 @@ describe(MediaService.name, () => {
it('should skip a video without any streams', async () => { it('should skip a video without any streams', async () => {
mediaMock.probe.mockResolvedValue(probeStub.noVideoStreams); mediaMock.probe.mockResolvedValue(probeStub.noVideoStreams);
assetMock.getByIds.mockResolvedValue([assetEntityStub.video]); assetMock.getByIds.mockResolvedValue([assetStub.video]);
await sut.handleVideoConversion({ id: assetEntityStub.video.id }); await sut.handleVideoConversion({ id: assetStub.video.id });
expect(mediaMock.transcode).not.toHaveBeenCalled(); expect(mediaMock.transcode).not.toHaveBeenCalled();
}); });
it('should skip a video without any height', async () => { it('should skip a video without any height', async () => {
mediaMock.probe.mockResolvedValue(probeStub.noHeight); mediaMock.probe.mockResolvedValue(probeStub.noHeight);
assetMock.getByIds.mockResolvedValue([assetEntityStub.video]); assetMock.getByIds.mockResolvedValue([assetStub.video]);
await sut.handleVideoConversion({ id: assetEntityStub.video.id }); await sut.handleVideoConversion({ id: assetStub.video.id });
expect(mediaMock.transcode).not.toHaveBeenCalled(); expect(mediaMock.transcode).not.toHaveBeenCalled();
}); });
it('should transcode when set to all', async () => { it('should transcode when set to all', async () => {
mediaMock.probe.mockResolvedValue(probeStub.multipleVideoStreams); mediaMock.probe.mockResolvedValue(probeStub.multipleVideoStreams);
configMock.load.mockResolvedValue([{ key: SystemConfigKey.FFMPEG_TRANSCODE, value: TranscodePolicy.ALL }]); configMock.load.mockResolvedValue([{ key: SystemConfigKey.FFMPEG_TRANSCODE, value: TranscodePolicy.ALL }]);
assetMock.getByIds.mockResolvedValue([assetEntityStub.video]); assetMock.getByIds.mockResolvedValue([assetStub.video]);
await sut.handleVideoConversion({ id: assetEntityStub.video.id }); await sut.handleVideoConversion({ id: assetStub.video.id });
expect(mediaMock.transcode).toHaveBeenCalledWith( expect(mediaMock.transcode).toHaveBeenCalledWith(
'/original/path.ext', '/original/path.ext',
'upload/encoded-video/user-id/asset-id.mp4', 'upload/encoded-video/user-id/asset-id.mp4',
@ -320,7 +320,7 @@ describe(MediaService.name, () => {
it('should transcode when optimal and too big', async () => { it('should transcode when optimal and too big', async () => {
mediaMock.probe.mockResolvedValue(probeStub.videoStream2160p); mediaMock.probe.mockResolvedValue(probeStub.videoStream2160p);
configMock.load.mockResolvedValue([{ key: SystemConfigKey.FFMPEG_TRANSCODE, value: TranscodePolicy.OPTIMAL }]); 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( expect(mediaMock.transcode).toHaveBeenCalledWith(
'/original/path.ext', '/original/path.ext',
'upload/encoded-video/user-id/asset-id.mp4', '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_TRANSCODE, value: TranscodePolicy.ALL },
{ key: SystemConfigKey.FFMPEG_TARGET_RESOLUTION, value: 'original' }, { 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( expect(mediaMock.transcode).toHaveBeenCalledWith(
'/original/path.ext', '/original/path.ext',
'upload/encoded-video/user-id/asset-id.mp4', '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 () => { it('should transcode with alternate scaling video is vertical', async () => {
mediaMock.probe.mockResolvedValue(probeStub.videoStreamVertical2160p); mediaMock.probe.mockResolvedValue(probeStub.videoStreamVertical2160p);
configMock.load.mockResolvedValue([{ key: SystemConfigKey.FFMPEG_TRANSCODE, value: TranscodePolicy.OPTIMAL }]); configMock.load.mockResolvedValue([{ key: SystemConfigKey.FFMPEG_TRANSCODE, value: TranscodePolicy.OPTIMAL }]);
assetMock.getByIds.mockResolvedValue([assetEntityStub.video]); assetMock.getByIds.mockResolvedValue([assetStub.video]);
await sut.handleVideoConversion({ id: assetEntityStub.video.id }); await sut.handleVideoConversion({ id: assetStub.video.id });
expect(mediaMock.transcode).toHaveBeenCalledWith( expect(mediaMock.transcode).toHaveBeenCalledWith(
'/original/path.ext', '/original/path.ext',
'upload/encoded-video/user-id/asset-id.mp4', 'upload/encoded-video/user-id/asset-id.mp4',
@ -392,8 +392,8 @@ describe(MediaService.name, () => {
it('should transcode when audio doesnt match target', async () => { it('should transcode when audio doesnt match target', async () => {
mediaMock.probe.mockResolvedValue(probeStub.audioStreamMp3); mediaMock.probe.mockResolvedValue(probeStub.audioStreamMp3);
configMock.load.mockResolvedValue([{ key: SystemConfigKey.FFMPEG_TRANSCODE, value: TranscodePolicy.OPTIMAL }]); configMock.load.mockResolvedValue([{ key: SystemConfigKey.FFMPEG_TRANSCODE, value: TranscodePolicy.OPTIMAL }]);
assetMock.getByIds.mockResolvedValue([assetEntityStub.video]); assetMock.getByIds.mockResolvedValue([assetStub.video]);
await sut.handleVideoConversion({ id: assetEntityStub.video.id }); await sut.handleVideoConversion({ id: assetStub.video.id });
expect(mediaMock.transcode).toHaveBeenCalledWith( expect(mediaMock.transcode).toHaveBeenCalledWith(
'/original/path.ext', '/original/path.ext',
'upload/encoded-video/user-id/asset-id.mp4', 'upload/encoded-video/user-id/asset-id.mp4',
@ -416,8 +416,8 @@ describe(MediaService.name, () => {
it('should transcode when container doesnt match target', async () => { it('should transcode when container doesnt match target', async () => {
mediaMock.probe.mockResolvedValue(probeStub.matroskaContainer); mediaMock.probe.mockResolvedValue(probeStub.matroskaContainer);
configMock.load.mockResolvedValue([{ key: SystemConfigKey.FFMPEG_TRANSCODE, value: TranscodePolicy.OPTIMAL }]); configMock.load.mockResolvedValue([{ key: SystemConfigKey.FFMPEG_TRANSCODE, value: TranscodePolicy.OPTIMAL }]);
assetMock.getByIds.mockResolvedValue([assetEntityStub.video]); assetMock.getByIds.mockResolvedValue([assetStub.video]);
await sut.handleVideoConversion({ id: assetEntityStub.video.id }); await sut.handleVideoConversion({ id: assetStub.video.id });
expect(mediaMock.transcode).toHaveBeenCalledWith( expect(mediaMock.transcode).toHaveBeenCalledWith(
'/original/path.ext', '/original/path.ext',
'upload/encoded-video/user-id/asset-id.mp4', 'upload/encoded-video/user-id/asset-id.mp4',
@ -440,32 +440,32 @@ describe(MediaService.name, () => {
it('should not transcode an invalid transcode value', async () => { it('should not transcode an invalid transcode value', async () => {
mediaMock.probe.mockResolvedValue(probeStub.videoStream2160p); mediaMock.probe.mockResolvedValue(probeStub.videoStream2160p);
configMock.load.mockResolvedValue([{ key: SystemConfigKey.FFMPEG_TRANSCODE, value: 'invalid' }]); configMock.load.mockResolvedValue([{ key: SystemConfigKey.FFMPEG_TRANSCODE, value: 'invalid' }]);
assetMock.getByIds.mockResolvedValue([assetEntityStub.video]); assetMock.getByIds.mockResolvedValue([assetStub.video]);
await sut.handleVideoConversion({ id: assetEntityStub.video.id }); await sut.handleVideoConversion({ id: assetStub.video.id });
expect(mediaMock.transcode).not.toHaveBeenCalled(); expect(mediaMock.transcode).not.toHaveBeenCalled();
}); });
it('should not transcode if transcoding is disabled', async () => { it('should not transcode if transcoding is disabled', async () => {
mediaMock.probe.mockResolvedValue(probeStub.videoStream2160p); mediaMock.probe.mockResolvedValue(probeStub.videoStream2160p);
configMock.load.mockResolvedValue([{ key: SystemConfigKey.FFMPEG_TRANSCODE, value: TranscodePolicy.DISABLED }]); configMock.load.mockResolvedValue([{ key: SystemConfigKey.FFMPEG_TRANSCODE, value: TranscodePolicy.DISABLED }]);
assetMock.getByIds.mockResolvedValue([assetEntityStub.video]); assetMock.getByIds.mockResolvedValue([assetStub.video]);
await sut.handleVideoConversion({ id: assetEntityStub.video.id }); await sut.handleVideoConversion({ id: assetStub.video.id });
expect(mediaMock.transcode).not.toHaveBeenCalled(); expect(mediaMock.transcode).not.toHaveBeenCalled();
}); });
it('should not transcode if target codec is invalid', async () => { it('should not transcode if target codec is invalid', async () => {
mediaMock.probe.mockResolvedValue(probeStub.videoStream2160p); mediaMock.probe.mockResolvedValue(probeStub.videoStream2160p);
configMock.load.mockResolvedValue([{ key: SystemConfigKey.FFMPEG_TARGET_VIDEO_CODEC, value: 'invalid' }]); configMock.load.mockResolvedValue([{ key: SystemConfigKey.FFMPEG_TARGET_VIDEO_CODEC, value: 'invalid' }]);
assetMock.getByIds.mockResolvedValue([assetEntityStub.video]); assetMock.getByIds.mockResolvedValue([assetStub.video]);
await sut.handleVideoConversion({ id: assetEntityStub.video.id }); await sut.handleVideoConversion({ id: assetStub.video.id });
expect(mediaMock.transcode).not.toHaveBeenCalled(); expect(mediaMock.transcode).not.toHaveBeenCalled();
}); });
it('should set max bitrate if above 0', async () => { it('should set max bitrate if above 0', async () => {
mediaMock.probe.mockResolvedValue(probeStub.matroskaContainer); mediaMock.probe.mockResolvedValue(probeStub.matroskaContainer);
configMock.load.mockResolvedValue([{ key: SystemConfigKey.FFMPEG_MAX_BITRATE, value: '4500k' }]); configMock.load.mockResolvedValue([{ key: SystemConfigKey.FFMPEG_MAX_BITRATE, value: '4500k' }]);
assetMock.getByIds.mockResolvedValue([assetEntityStub.video]); assetMock.getByIds.mockResolvedValue([assetStub.video]);
await sut.handleVideoConversion({ id: assetEntityStub.video.id }); await sut.handleVideoConversion({ id: assetStub.video.id });
expect(mediaMock.transcode).toHaveBeenCalledWith( expect(mediaMock.transcode).toHaveBeenCalledWith(
'/original/path.ext', '/original/path.ext',
'upload/encoded-video/user-id/asset-id.mp4', '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_MAX_BITRATE, value: '4500k' },
{ key: SystemConfigKey.FFMPEG_TWO_PASS, value: true }, { key: SystemConfigKey.FFMPEG_TWO_PASS, value: true },
]); ]);
assetMock.getByIds.mockResolvedValue([assetEntityStub.video]); assetMock.getByIds.mockResolvedValue([assetStub.video]);
await sut.handleVideoConversion({ id: assetEntityStub.video.id }); await sut.handleVideoConversion({ id: assetStub.video.id });
expect(mediaMock.transcode).toHaveBeenCalledWith( expect(mediaMock.transcode).toHaveBeenCalledWith(
'/original/path.ext', '/original/path.ext',
'upload/encoded-video/user-id/asset-id.mp4', '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 () => { 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); mediaMock.probe.mockResolvedValue(probeStub.matroskaContainer);
configMock.load.mockResolvedValue([{ key: SystemConfigKey.FFMPEG_TWO_PASS, value: true }]); configMock.load.mockResolvedValue([{ key: SystemConfigKey.FFMPEG_TWO_PASS, value: true }]);
assetMock.getByIds.mockResolvedValue([assetEntityStub.video]); assetMock.getByIds.mockResolvedValue([assetStub.video]);
await sut.handleVideoConversion({ id: assetEntityStub.video.id }); await sut.handleVideoConversion({ id: assetStub.video.id });
expect(mediaMock.transcode).toHaveBeenCalledWith( expect(mediaMock.transcode).toHaveBeenCalledWith(
'/original/path.ext', '/original/path.ext',
'upload/encoded-video/user-id/asset-id.mp4', '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_TWO_PASS, value: true },
{ key: SystemConfigKey.FFMPEG_TARGET_VIDEO_CODEC, value: VideoCodec.VP9 }, { key: SystemConfigKey.FFMPEG_TARGET_VIDEO_CODEC, value: VideoCodec.VP9 },
]); ]);
assetMock.getByIds.mockResolvedValue([assetEntityStub.video]); assetMock.getByIds.mockResolvedValue([assetStub.video]);
await sut.handleVideoConversion({ id: assetEntityStub.video.id }); await sut.handleVideoConversion({ id: assetStub.video.id });
expect(mediaMock.transcode).toHaveBeenCalledWith( expect(mediaMock.transcode).toHaveBeenCalledWith(
'/original/path.ext', '/original/path.ext',
'upload/encoded-video/user-id/asset-id.mp4', '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_TARGET_VIDEO_CODEC, value: VideoCodec.VP9 },
{ key: SystemConfigKey.FFMPEG_PRESET, value: 'slow' }, { key: SystemConfigKey.FFMPEG_PRESET, value: 'slow' },
]); ]);
assetMock.getByIds.mockResolvedValue([assetEntityStub.video]); assetMock.getByIds.mockResolvedValue([assetStub.video]);
await sut.handleVideoConversion({ id: assetEntityStub.video.id }); await sut.handleVideoConversion({ id: assetStub.video.id });
expect(mediaMock.transcode).toHaveBeenCalledWith( expect(mediaMock.transcode).toHaveBeenCalledWith(
'/original/path.ext', '/original/path.ext',
'upload/encoded-video/user-id/asset-id.mp4', '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_TARGET_VIDEO_CODEC, value: VideoCodec.VP9 },
{ key: SystemConfigKey.FFMPEG_PRESET, value: 'invalid' }, { key: SystemConfigKey.FFMPEG_PRESET, value: 'invalid' },
]); ]);
assetMock.getByIds.mockResolvedValue([assetEntityStub.video]); assetMock.getByIds.mockResolvedValue([assetStub.video]);
await sut.handleVideoConversion({ id: assetEntityStub.video.id }); await sut.handleVideoConversion({ id: assetStub.video.id });
expect(mediaMock.transcode).toHaveBeenCalledWith( expect(mediaMock.transcode).toHaveBeenCalledWith(
'/original/path.ext', '/original/path.ext',
'upload/encoded-video/user-id/asset-id.mp4', '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_TARGET_VIDEO_CODEC, value: VideoCodec.VP9 },
{ key: SystemConfigKey.FFMPEG_THREADS, value: 2 }, { key: SystemConfigKey.FFMPEG_THREADS, value: 2 },
]); ]);
assetMock.getByIds.mockResolvedValue([assetEntityStub.video]); assetMock.getByIds.mockResolvedValue([assetStub.video]);
await sut.handleVideoConversion({ id: assetEntityStub.video.id }); await sut.handleVideoConversion({ id: assetStub.video.id });
expect(mediaMock.transcode).toHaveBeenCalledWith( expect(mediaMock.transcode).toHaveBeenCalledWith(
'/original/path.ext', '/original/path.ext',
'upload/encoded-video/user-id/asset-id.mp4', '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 () => { it('should disable thread pooling for h264 if thread limit is above 0', async () => {
mediaMock.probe.mockResolvedValue(probeStub.matroskaContainer); mediaMock.probe.mockResolvedValue(probeStub.matroskaContainer);
configMock.load.mockResolvedValue([{ key: SystemConfigKey.FFMPEG_THREADS, value: 2 }]); configMock.load.mockResolvedValue([{ key: SystemConfigKey.FFMPEG_THREADS, value: 2 }]);
assetMock.getByIds.mockResolvedValue([assetEntityStub.video]); assetMock.getByIds.mockResolvedValue([assetStub.video]);
await sut.handleVideoConversion({ id: assetEntityStub.video.id }); await sut.handleVideoConversion({ id: assetStub.video.id });
expect(mediaMock.transcode).toHaveBeenCalledWith( expect(mediaMock.transcode).toHaveBeenCalledWith(
'/original/path.ext', '/original/path.ext',
'upload/encoded-video/user-id/asset-id.mp4', '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 () => { it('should omit thread flags for h264 if thread limit is at or below 0', async () => {
mediaMock.probe.mockResolvedValue(probeStub.matroskaContainer); mediaMock.probe.mockResolvedValue(probeStub.matroskaContainer);
configMock.load.mockResolvedValue([{ key: SystemConfigKey.FFMPEG_THREADS, value: 0 }]); configMock.load.mockResolvedValue([{ key: SystemConfigKey.FFMPEG_THREADS, value: 0 }]);
assetMock.getByIds.mockResolvedValue([assetEntityStub.video]); assetMock.getByIds.mockResolvedValue([assetStub.video]);
await sut.handleVideoConversion({ id: assetEntityStub.video.id }); await sut.handleVideoConversion({ id: assetStub.video.id });
expect(mediaMock.transcode).toHaveBeenCalledWith( expect(mediaMock.transcode).toHaveBeenCalledWith(
'/original/path.ext', '/original/path.ext',
'upload/encoded-video/user-id/asset-id.mp4', 'upload/encoded-video/user-id/asset-id.mp4',
@ -715,8 +715,8 @@ describe(MediaService.name, () => {
{ key: SystemConfigKey.FFMPEG_THREADS, value: 2 }, { key: SystemConfigKey.FFMPEG_THREADS, value: 2 },
{ key: SystemConfigKey.FFMPEG_TARGET_VIDEO_CODEC, value: VideoCodec.HEVC }, { key: SystemConfigKey.FFMPEG_TARGET_VIDEO_CODEC, value: VideoCodec.HEVC },
]); ]);
assetMock.getByIds.mockResolvedValue([assetEntityStub.video]); assetMock.getByIds.mockResolvedValue([assetStub.video]);
await sut.handleVideoConversion({ id: assetEntityStub.video.id }); await sut.handleVideoConversion({ id: assetStub.video.id });
expect(mediaMock.transcode).toHaveBeenCalledWith( expect(mediaMock.transcode).toHaveBeenCalledWith(
'/original/path.ext', '/original/path.ext',
'upload/encoded-video/user-id/asset-id.mp4', 'upload/encoded-video/user-id/asset-id.mp4',
@ -745,8 +745,8 @@ describe(MediaService.name, () => {
{ key: SystemConfigKey.FFMPEG_THREADS, value: 0 }, { key: SystemConfigKey.FFMPEG_THREADS, value: 0 },
{ key: SystemConfigKey.FFMPEG_TARGET_VIDEO_CODEC, value: VideoCodec.HEVC }, { key: SystemConfigKey.FFMPEG_TARGET_VIDEO_CODEC, value: VideoCodec.HEVC },
]); ]);
assetMock.getByIds.mockResolvedValue([assetEntityStub.video]); assetMock.getByIds.mockResolvedValue([assetStub.video]);
await sut.handleVideoConversion({ id: assetEntityStub.video.id }); await sut.handleVideoConversion({ id: assetStub.video.id });
expect(mediaMock.transcode).toHaveBeenCalledWith( expect(mediaMock.transcode).toHaveBeenCalledWith(
'/original/path.ext', '/original/path.ext',
'upload/encoded-video/user-id/asset-id.mp4', 'upload/encoded-video/user-id/asset-id.mp4',

View file

@ -1,4 +1,4 @@
import { assetEntityStub, newAssetRepositoryMock, newJobRepositoryMock, newStorageRepositoryMock } from '@test'; import { assetStub, newAssetRepositoryMock, newJobRepositoryMock, newStorageRepositoryMock } from '@test';
import { constants } from 'fs/promises'; import { constants } from 'fs/promises';
import { IAssetRepository, WithoutProperty, WithProperty } from '../asset'; import { IAssetRepository, WithoutProperty, WithProperty } from '../asset';
import { IJobRepository, JobName } from '../job'; import { IJobRepository, JobName } from '../job';
@ -25,7 +25,7 @@ describe(MetadataService.name, () => {
describe('handleQueueSidecar', () => { describe('handleQueueSidecar', () => {
it('should queue assets with sidecar files', async () => { 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 }); await sut.handleQueueSidecar({ force: true });
@ -33,12 +33,12 @@ describe(MetadataService.name, () => {
expect(assetMock.getWithout).not.toHaveBeenCalled(); expect(assetMock.getWithout).not.toHaveBeenCalled();
expect(jobMock.queue).toHaveBeenCalledWith({ expect(jobMock.queue).toHaveBeenCalledWith({
name: JobName.SIDECAR_SYNC, name: JobName.SIDECAR_SYNC,
data: { id: assetEntityStub.sidecar.id }, data: { id: assetStub.sidecar.id },
}); });
}); });
it('should queue assets without sidecar files', async () => { 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 }); await sut.handleQueueSidecar({ force: false });
@ -46,7 +46,7 @@ describe(MetadataService.name, () => {
expect(assetMock.getWith).not.toHaveBeenCalled(); expect(assetMock.getWith).not.toHaveBeenCalled();
expect(jobMock.queue).toHaveBeenCalledWith({ expect(jobMock.queue).toHaveBeenCalledWith({
name: JobName.SIDECAR_DISCOVERY, name: JobName.SIDECAR_DISCOVERY,
data: { id: assetEntityStub.image.id }, data: { id: assetStub.image.id },
}); });
}); });
}); });
@ -59,44 +59,44 @@ describe(MetadataService.name, () => {
describe('handleSidecarDiscovery', () => { describe('handleSidecarDiscovery', () => {
it('should skip hidden assets', async () => { it('should skip hidden assets', async () => {
assetMock.getByIds.mockResolvedValue([assetEntityStub.livePhotoMotionAsset]); assetMock.getByIds.mockResolvedValue([assetStub.livePhotoMotionAsset]);
await sut.handleSidecarDiscovery({ id: assetEntityStub.livePhotoMotionAsset.id }); await sut.handleSidecarDiscovery({ id: assetStub.livePhotoMotionAsset.id });
expect(storageMock.checkFileExists).not.toHaveBeenCalled(); expect(storageMock.checkFileExists).not.toHaveBeenCalled();
}); });
it('should skip assets with a sidecar path', async () => { it('should skip assets with a sidecar path', async () => {
assetMock.getByIds.mockResolvedValue([assetEntityStub.sidecar]); assetMock.getByIds.mockResolvedValue([assetStub.sidecar]);
await sut.handleSidecarDiscovery({ id: assetEntityStub.sidecar.id }); await sut.handleSidecarDiscovery({ id: assetStub.sidecar.id });
expect(storageMock.checkFileExists).not.toHaveBeenCalled(); expect(storageMock.checkFileExists).not.toHaveBeenCalled();
}); });
it('should do nothing when a sidecar is not found ', async () => { 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); storageMock.checkFileExists.mockResolvedValue(false);
await sut.handleSidecarDiscovery({ id: assetEntityStub.image.id }); await sut.handleSidecarDiscovery({ id: assetStub.image.id });
expect(assetMock.save).not.toHaveBeenCalled(); expect(assetMock.save).not.toHaveBeenCalled();
}); });
it('should update a image asset when a sidecar is found', async () => { it('should update a image asset when a sidecar is found', async () => {
assetMock.getByIds.mockResolvedValue([assetEntityStub.image]); assetMock.getByIds.mockResolvedValue([assetStub.image]);
assetMock.save.mockResolvedValue(assetEntityStub.image); assetMock.save.mockResolvedValue(assetStub.image);
storageMock.checkFileExists.mockResolvedValue(true); 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(storageMock.checkFileExists).toHaveBeenCalledWith('/original/path.jpg.xmp', constants.R_OK);
expect(assetMock.save).toHaveBeenCalledWith({ expect(assetMock.save).toHaveBeenCalledWith({
id: assetEntityStub.image.id, id: assetStub.image.id,
sidecarPath: '/original/path.jpg.xmp', sidecarPath: '/original/path.jpg.xmp',
}); });
}); });
it('should update a video asset when a sidecar is found', async () => { it('should update a video asset when a sidecar is found', async () => {
assetMock.getByIds.mockResolvedValue([assetEntityStub.video]); assetMock.getByIds.mockResolvedValue([assetStub.video]);
assetMock.save.mockResolvedValue(assetEntityStub.video); assetMock.save.mockResolvedValue(assetStub.video);
storageMock.checkFileExists.mockResolvedValue(true); 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(storageMock.checkFileExists).toHaveBeenCalledWith('/original/path.ext.xmp', constants.R_OK);
expect(assetMock.save).toHaveBeenCalledWith({ expect(assetMock.save).toHaveBeenCalledWith({
id: assetEntityStub.image.id, id: assetStub.image.id,
sidecarPath: '/original/path.ext.xmp', sidecarPath: '/original/path.ext.xmp',
}); });
}); });

View file

@ -1,6 +1,6 @@
import { BadRequestException, NotFoundException } from '@nestjs/common'; import { BadRequestException, NotFoundException } from '@nestjs/common';
import { import {
assetEntityStub, assetStub,
authStub, authStub,
faceStub, faceStub,
newJobRepositoryMock, newJobRepositoryMock,
@ -112,7 +112,7 @@ describe(PersonService.name, () => {
describe('getAssets', () => { describe('getAssets', () => {
it("should return a person's assets", async () => { 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'); await sut.getAssets(authStub.admin, 'person-1');
expect(personMock.getAssets).toHaveBeenCalledWith('admin_id', '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 () => { it("should update a person's name", async () => {
personMock.getById.mockResolvedValue(personStub.noName); personMock.getById.mockResolvedValue(personStub.noName);
personMock.update.mockResolvedValue(personStub.withName); 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); 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(personMock.update).toHaveBeenCalledWith({ id: 'person-1', name: 'Person 1' });
expect(jobMock.queue).toHaveBeenCalledWith({ expect(jobMock.queue).toHaveBeenCalledWith({
name: JobName.SEARCH_INDEX_ASSET, name: JobName.SEARCH_INDEX_ASSET,
data: { ids: [assetEntityStub.image.id] }, data: { ids: [assetStub.image.id] },
}); });
}); });
it('should update a person visibility', async () => { it('should update a person visibility', async () => {
personMock.getById.mockResolvedValue(personStub.hidden); personMock.getById.mockResolvedValue(personStub.hidden);
personMock.update.mockResolvedValue(personStub.withName); 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); 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(personMock.update).toHaveBeenCalledWith({ id: 'person-1', isHidden: false });
expect(jobMock.queue).toHaveBeenCalledWith({ expect(jobMock.queue).toHaveBeenCalledWith({
name: JobName.SEARCH_INDEX_ASSET, 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 () => { it('should delete conflicting faces before merging', async () => {
personMock.getById.mockResolvedValue(personStub.primaryPerson); personMock.getById.mockResolvedValue(personStub.primaryPerson);
personMock.getById.mockResolvedValue(personStub.mergePerson); 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([ await expect(sut.mergePerson(authStub.admin, 'person-1', { ids: ['person-2'] })).resolves.toEqual([
{ id: 'person-2', success: true }, { id: 'person-2', success: true },
@ -252,7 +252,7 @@ describe(PersonService.name, () => {
expect(jobMock.queue).toHaveBeenCalledWith({ expect(jobMock.queue).toHaveBeenCalledWith({
name: JobName.SEARCH_REMOVE_FACE, 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 () => { it('should handle an error reassigning faces', async () => {
personMock.getById.mockResolvedValue(personStub.primaryPerson); personMock.getById.mockResolvedValue(personStub.primaryPerson);
personMock.getById.mockResolvedValue(personStub.mergePerson); personMock.getById.mockResolvedValue(personStub.mergePerson);
personMock.prepareReassignFaces.mockResolvedValue([assetEntityStub.image.id]); personMock.prepareReassignFaces.mockResolvedValue([assetStub.image.id]);
personMock.reassignFaces.mockRejectedValue(new Error('update failed')); personMock.reassignFaces.mockRejectedValue(new Error('update failed'));
await expect(sut.mergePerson(authStub.admin, 'person-1', { ids: ['person-2'] })).resolves.toEqual([ await expect(sut.mergePerson(authStub.admin, 'person-1', { ids: ['person-2'] })).resolves.toEqual([

View file

@ -2,7 +2,7 @@ import { BadRequestException } from '@nestjs/common';
import { ConfigService } from '@nestjs/config'; import { ConfigService } from '@nestjs/config';
import { import {
albumStub, albumStub,
assetEntityStub, assetStub,
asyncTick, asyncTick,
authStub, authStub,
faceStub, faceStub,
@ -192,14 +192,14 @@ describe(SearchService.name, () => {
it('should index all the assets', async () => { it('should index all the assets', async () => {
assetMock.getAll.mockResolvedValue({ assetMock.getAll.mockResolvedValue({
items: [assetEntityStub.image], items: [assetStub.image],
hasNextPage: false, hasNextPage: false,
}); });
await sut.handleIndexAssets(); await sut.handleIndexAssets();
expect(searchMock.importAssets.mock.calls).toEqual([ expect(searchMock.importAssets.mock.calls).toEqual([
[[assetEntityStub.image], false], [[assetStub.image], false],
[[], true], [[], true],
]); ]);
}); });
@ -217,11 +217,11 @@ describe(SearchService.name, () => {
describe('handleIndexAsset', () => { describe('handleIndexAsset', () => {
it('should skip if search is disabled', () => { it('should skip if search is disabled', () => {
const sut = makeSut('false'); const sut = makeSut('false');
sut.handleIndexAsset({ ids: [assetEntityStub.image.id] }); sut.handleIndexAsset({ ids: [assetStub.image.id] });
}); });
it('should index the asset', () => { 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 () => { it('should flush queued asset updates', async () => {
assetMock.getByIds.mockResolvedValue([assetEntityStub.image]); assetMock.getByIds.mockResolvedValue([assetStub.image]);
sut.handleIndexAsset({ ids: ['asset1'] }); sut.handleIndexAsset({ ids: ['asset1'] });
@ -376,7 +376,7 @@ describe(SearchService.name, () => {
await asyncTick(4); await asyncTick(4);
expect(assetMock.getByIds).toHaveBeenCalledWith(['asset1']); 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 () => { it('should flush queued asset deletes', async () => {

View file

@ -1,7 +1,7 @@
import { BadRequestException, ForbiddenException } from '@nestjs/common'; import { BadRequestException, ForbiddenException } from '@nestjs/common';
import { import {
albumStub, albumStub,
assetEntityStub, assetStub,
authStub, authStub,
IAccessRepositoryMock, IAccessRepositoryMock,
newAccessRepositoryMock, newAccessRepositoryMock,
@ -136,20 +136,20 @@ describe(SharedLinkService.name, () => {
await sut.create(authStub.admin, { await sut.create(authStub.admin, {
type: SharedLinkType.INDIVIDUAL, type: SharedLinkType.INDIVIDUAL,
assetIds: [assetEntityStub.image.id], assetIds: [assetStub.image.id],
showExif: true, showExif: true,
allowDownload: true, allowDownload: true,
allowUpload: 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({ expect(shareMock.create).toHaveBeenCalledWith({
type: SharedLinkType.INDIVIDUAL, type: SharedLinkType.INDIVIDUAL,
userId: authStub.admin.id, userId: authStub.admin.id,
albumId: null, albumId: null,
allowDownload: true, allowDownload: true,
allowUpload: true, allowUpload: true,
assets: [{ id: assetEntityStub.image.id }], assets: [{ id: assetStub.image.id }],
description: null, description: null,
expiresAt: null, expiresAt: null,
showExif: true, showExif: true,
@ -211,9 +211,9 @@ describe(SharedLinkService.name, () => {
when(accessMock.asset.hasOwnerAccess).calledWith(authStub.admin.id, 'asset-3').mockResolvedValue(true); when(accessMock.asset.hasOwnerAccess).calledWith(authStub.admin.id, 'asset-3').mockResolvedValue(true);
await expect( 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([ ).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-2', success: false, error: AssetIdErrorReason.NO_PERMISSION },
{ assetId: 'asset-3', success: true }, { assetId: 'asset-3', success: true },
]); ]);
@ -221,7 +221,7 @@ describe(SharedLinkService.name, () => {
expect(accessMock.asset.hasOwnerAccess).toHaveBeenCalledTimes(2); expect(accessMock.asset.hasOwnerAccess).toHaveBeenCalledTimes(2);
expect(shareMock.update).toHaveBeenCalledWith({ expect(shareMock.update).toHaveBeenCalledWith({
...sharedLinkStub.individual, ...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); shareMock.create.mockResolvedValue(sharedLinkStub.individual);
await expect( 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([ ).resolves.toEqual([
{ assetId: assetEntityStub.image.id, success: true }, { assetId: assetStub.image.id, success: true },
{ assetId: 'asset-2', success: false, error: AssetIdErrorReason.NOT_FOUND }, { assetId: 'asset-2', success: false, error: AssetIdErrorReason.NOT_FOUND },
]); ]);

View file

@ -1,6 +1,6 @@
import { AssetEntity } from '@app/infra/entities'; import { AssetEntity } from '@app/infra/entities';
import { import {
assetEntityStub, assetStub,
newAssetRepositoryMock, newAssetRepositoryMock,
newJobRepositoryMock, newJobRepositoryMock,
newMachineLearningRepositoryMock, newMachineLearningRepositoryMock,
@ -41,29 +41,25 @@ describe(SmartInfoService.name, () => {
describe('handleQueueObjectTagging', () => { describe('handleQueueObjectTagging', () => {
it('should queue the assets without tags', async () => { it('should queue the assets without tags', async () => {
assetMock.getWithout.mockResolvedValue({ assetMock.getWithout.mockResolvedValue({
items: [assetEntityStub.image], items: [assetStub.image],
hasNextPage: false, hasNextPage: false,
}); });
await sut.handleQueueObjectTagging({ force: false }); await sut.handleQueueObjectTagging({ force: false });
expect(jobMock.queue.mock.calls).toEqual([ expect(jobMock.queue.mock.calls).toEqual([[{ name: JobName.CLASSIFY_IMAGE, data: { id: assetStub.image.id } }]]);
[{ name: JobName.CLASSIFY_IMAGE, data: { id: assetEntityStub.image.id } }],
]);
expect(assetMock.getWithout).toHaveBeenCalledWith({ skip: 0, take: 1000 }, WithoutProperty.OBJECT_TAGS); expect(assetMock.getWithout).toHaveBeenCalledWith({ skip: 0, take: 1000 }, WithoutProperty.OBJECT_TAGS);
}); });
it('should queue all the assets', async () => { it('should queue all the assets', async () => {
assetMock.getAll.mockResolvedValue({ assetMock.getAll.mockResolvedValue({
items: [assetEntityStub.image], items: [assetStub.image],
hasNextPage: false, hasNextPage: false,
}); });
await sut.handleQueueObjectTagging({ force: true }); await sut.handleQueueObjectTagging({ force: true });
expect(jobMock.queue.mock.calls).toEqual([ expect(jobMock.queue.mock.calls).toEqual([[{ name: JobName.CLASSIFY_IMAGE, data: { id: assetStub.image.id } }]]);
[{ name: JobName.CLASSIFY_IMAGE, data: { id: assetEntityStub.image.id } }],
]);
expect(assetMock.getAll).toHaveBeenCalled(); expect(assetMock.getAll).toHaveBeenCalled();
}); });
}); });
@ -104,25 +100,25 @@ describe(SmartInfoService.name, () => {
describe('handleQueueEncodeClip', () => { describe('handleQueueEncodeClip', () => {
it('should queue the assets without clip embeddings', async () => { it('should queue the assets without clip embeddings', async () => {
assetMock.getWithout.mockResolvedValue({ assetMock.getWithout.mockResolvedValue({
items: [assetEntityStub.image], items: [assetStub.image],
hasNextPage: false, hasNextPage: false,
}); });
await sut.handleQueueEncodeClip({ force: 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); expect(assetMock.getWithout).toHaveBeenCalledWith({ skip: 0, take: 1000 }, WithoutProperty.CLIP_ENCODING);
}); });
it('should queue all the assets', async () => { it('should queue all the assets', async () => {
assetMock.getAll.mockResolvedValue({ assetMock.getAll.mockResolvedValue({
items: [assetEntityStub.image], items: [assetStub.image],
hasNextPage: false, hasNextPage: false,
}); });
await sut.handleQueueEncodeClip({ force: true }); 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(); expect(assetMock.getAll).toHaveBeenCalled();
}); });
}); });

View file

@ -1,10 +1,10 @@
import { import {
assetEntityStub, assetStub,
newAssetRepositoryMock, newAssetRepositoryMock,
newStorageRepositoryMock, newStorageRepositoryMock,
newSystemConfigRepositoryMock, newSystemConfigRepositoryMock,
newUserRepositoryMock, newUserRepositoryMock,
userEntityStub, userStub,
} from '@test'; } from '@test';
import { when } from 'jest-when'; import { when } from 'jest-when';
import { StorageTemplateService } from '.'; import { StorageTemplateService } from '.';
@ -49,11 +49,11 @@ describe(StorageTemplateService.name, () => {
it('should handle an asset with a duplicate destination', async () => { it('should handle an asset with a duplicate destination', async () => {
assetMock.getAll.mockResolvedValue({ assetMock.getAll.mockResolvedValue({
items: [assetEntityStub.image], items: [assetStub.image],
hasNextPage: false, hasNextPage: false,
}); });
assetMock.save.mockResolvedValue(assetEntityStub.image); assetMock.save.mockResolvedValue(assetStub.image);
userMock.getList.mockResolvedValue([userEntityStub.user1]); userMock.getList.mockResolvedValue([userStub.user1]);
when(storageMock.checkFileExists) when(storageMock.checkFileExists)
.calledWith('upload/library/user-id/2023/2023-02-23/asset-id.jpg') .calledWith('upload/library/user-id/2023/2023-02-23/asset-id.jpg')
@ -68,7 +68,7 @@ describe(StorageTemplateService.name, () => {
expect(assetMock.getAll).toHaveBeenCalled(); expect(assetMock.getAll).toHaveBeenCalled();
expect(storageMock.checkFileExists).toHaveBeenCalledTimes(2); expect(storageMock.checkFileExists).toHaveBeenCalledTimes(2);
expect(assetMock.save).toHaveBeenCalledWith({ 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', originalPath: 'upload/library/user-id/2023/2023-02-23/asset-id+1.jpg',
}); });
expect(userMock.getList).toHaveBeenCalled(); expect(userMock.getList).toHaveBeenCalled();
@ -78,13 +78,13 @@ describe(StorageTemplateService.name, () => {
assetMock.getAll.mockResolvedValue({ assetMock.getAll.mockResolvedValue({
items: [ items: [
{ {
...assetEntityStub.image, ...assetStub.image,
originalPath: 'upload/library/user-id/2023/2023-02-23/asset-id.jpg', originalPath: 'upload/library/user-id/2023/2023-02-23/asset-id.jpg',
}, },
], ],
hasNextPage: false, hasNextPage: false,
}); });
userMock.getList.mockResolvedValue([userEntityStub.user1]); userMock.getList.mockResolvedValue([userStub.user1]);
await sut.handleMigration(); await sut.handleMigration();
@ -98,13 +98,13 @@ describe(StorageTemplateService.name, () => {
assetMock.getAll.mockResolvedValue({ assetMock.getAll.mockResolvedValue({
items: [ items: [
{ {
...assetEntityStub.image, ...assetStub.image,
originalPath: 'upload/library/user-id/2023/2023-02-23/asset-id+1.jpg', originalPath: 'upload/library/user-id/2023/2023-02-23/asset-id+1.jpg',
}, },
], ],
hasNextPage: false, hasNextPage: false,
}); });
userMock.getList.mockResolvedValue([userEntityStub.user1]); userMock.getList.mockResolvedValue([userStub.user1]);
await sut.handleMigration(); await sut.handleMigration();
@ -116,11 +116,11 @@ describe(StorageTemplateService.name, () => {
it('should move an asset', async () => { it('should move an asset', async () => {
assetMock.getAll.mockResolvedValue({ assetMock.getAll.mockResolvedValue({
items: [assetEntityStub.image], items: [assetStub.image],
hasNextPage: false, hasNextPage: false,
}); });
assetMock.save.mockResolvedValue(assetEntityStub.image); assetMock.save.mockResolvedValue(assetStub.image);
userMock.getList.mockResolvedValue([userEntityStub.user1]); userMock.getList.mockResolvedValue([userStub.user1]);
await sut.handleMigration(); await sut.handleMigration();
@ -130,18 +130,18 @@ describe(StorageTemplateService.name, () => {
'upload/library/user-id/2023/2023-02-23/asset-id.jpg', 'upload/library/user-id/2023/2023-02-23/asset-id.jpg',
); );
expect(assetMock.save).toHaveBeenCalledWith({ expect(assetMock.save).toHaveBeenCalledWith({
id: assetEntityStub.image.id, id: assetStub.image.id,
originalPath: 'upload/library/user-id/2023/2023-02-23/asset-id.jpg', originalPath: 'upload/library/user-id/2023/2023-02-23/asset-id.jpg',
}); });
}); });
it('should use the user storage label', async () => { it('should use the user storage label', async () => {
assetMock.getAll.mockResolvedValue({ assetMock.getAll.mockResolvedValue({
items: [assetEntityStub.image], items: [assetStub.image],
hasNextPage: false, hasNextPage: false,
}); });
assetMock.save.mockResolvedValue(assetEntityStub.image); assetMock.save.mockResolvedValue(assetStub.image);
userMock.getList.mockResolvedValue([userEntityStub.storageLabel]); userMock.getList.mockResolvedValue([userStub.storageLabel]);
await sut.handleMigration(); await sut.handleMigration();
@ -151,18 +151,18 @@ describe(StorageTemplateService.name, () => {
'upload/library/label-1/2023/2023-02-23/asset-id.jpg', 'upload/library/label-1/2023/2023-02-23/asset-id.jpg',
); );
expect(assetMock.save).toHaveBeenCalledWith({ expect(assetMock.save).toHaveBeenCalledWith({
id: assetEntityStub.image.id, id: assetStub.image.id,
originalPath: 'upload/library/label-1/2023/2023-02-23/asset-id.jpg', originalPath: 'upload/library/label-1/2023/2023-02-23/asset-id.jpg',
}); });
}); });
it('should not update the database if the move fails', async () => { it('should not update the database if the move fails', async () => {
assetMock.getAll.mockResolvedValue({ assetMock.getAll.mockResolvedValue({
items: [assetEntityStub.image], items: [assetStub.image],
hasNextPage: false, hasNextPage: false,
}); });
storageMock.moveFile.mockRejectedValue(new Error('Read only system')); storageMock.moveFile.mockRejectedValue(new Error('Read only system'));
userMock.getList.mockResolvedValue([userEntityStub.user1]); userMock.getList.mockResolvedValue([userStub.user1]);
await sut.handleMigration(); await sut.handleMigration();
@ -176,17 +176,17 @@ describe(StorageTemplateService.name, () => {
it('should move the asset back if the database fails', async () => { it('should move the asset back if the database fails', async () => {
assetMock.getAll.mockResolvedValue({ assetMock.getAll.mockResolvedValue({
items: [assetEntityStub.image], items: [assetStub.image],
hasNextPage: false, hasNextPage: false,
}); });
assetMock.save.mockRejectedValue('Connection Error!'); assetMock.save.mockRejectedValue('Connection Error!');
userMock.getList.mockResolvedValue([userEntityStub.user1]); userMock.getList.mockResolvedValue([userStub.user1]);
await sut.handleMigration(); await sut.handleMigration();
expect(assetMock.getAll).toHaveBeenCalled(); expect(assetMock.getAll).toHaveBeenCalled();
expect(assetMock.save).toHaveBeenCalledWith({ expect(assetMock.save).toHaveBeenCalledWith({
id: assetEntityStub.image.id, id: assetStub.image.id,
originalPath: 'upload/library/user-id/2023/2023-02-23/asset-id.jpg', originalPath: 'upload/library/user-id/2023/2023-02-23/asset-id.jpg',
}); });
expect(storageMock.moveFile.mock.calls).toEqual([ expect(storageMock.moveFile.mock.calls).toEqual([
@ -199,15 +199,15 @@ describe(StorageTemplateService.name, () => {
assetMock.getAll.mockResolvedValue({ assetMock.getAll.mockResolvedValue({
items: [ items: [
{ {
...assetEntityStub.image, ...assetStub.image,
originalPath: 'upload/library/user-id/2023/2023-02-23/asset-id+1.jpg', originalPath: 'upload/library/user-id/2023/2023-02-23/asset-id+1.jpg',
isReadOnly: true, isReadOnly: true,
}, },
], ],
hasNextPage: false, hasNextPage: false,
}); });
assetMock.save.mockResolvedValue(assetEntityStub.image); assetMock.save.mockResolvedValue(assetStub.image);
userMock.getList.mockResolvedValue([userEntityStub.user1]); userMock.getList.mockResolvedValue([userStub.user1]);
await sut.handleMigration(); await sut.handleMigration();

View file

@ -1,6 +1,6 @@
import { TagType } from '@app/infra/entities'; import { TagType } from '@app/infra/entities';
import { BadRequestException } from '@nestjs/common'; 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 { when } from 'jest-when';
import { AssetIdErrorReason } from '../asset'; import { AssetIdErrorReason } from '../asset';
import { ITagRepository } from './tag.repository'; import { ITagRepository } from './tag.repository';
@ -107,7 +107,7 @@ describe(TagService.name, () => {
it('should get the assets for a tag', async () => { it('should get the assets for a tag', async () => {
tagMock.getById.mockResolvedValue(tagStub.tag1); tagMock.getById.mockResolvedValue(tagStub.tag1);
tagMock.getAssets.mockResolvedValue([assetEntityStub.image]); tagMock.getAssets.mockResolvedValue([assetStub.image]);
await sut.getAssets(authStub.admin, 'tag-1'); await sut.getAssets(authStub.admin, 'tag-1');
expect(tagMock.getById).toHaveBeenCalledWith(authStub.admin.id, 'tag-1'); expect(tagMock.getById).toHaveBeenCalledWith(authStub.admin.id, 'tag-1');
expect(tagMock.getAssets).toHaveBeenCalledWith(authStub.admin.id, 'tag-1'); expect(tagMock.getAssets).toHaveBeenCalledWith(authStub.admin.id, 'tag-1');

View file

@ -1,7 +1,7 @@
import { AlbumResponseDto, AuthUserDto, mapUser } from '@app/domain'; import { AlbumResponseDto, AuthUserDto, mapUser } from '@app/domain';
import { AlbumEntity, UserEntity } from '@app/infra/entities'; import { AlbumEntity, UserEntity } from '@app/infra/entities';
import { ForbiddenException, NotFoundException } from '@nestjs/common'; import { ForbiddenException, NotFoundException } from '@nestjs/common';
import { userEntityStub } from '@test'; import { userStub } from '@test';
import { IAlbumRepository } from './album-repository'; import { IAlbumRepository } from './album-repository';
import { AlbumService } from './album.service'; import { AlbumService } from './album.service';
import { AddAssetsResponseDto } from './response-dto/add-assets-response.dto'; import { AddAssetsResponseDto } from './response-dto/add-assets-response.dto';
@ -61,11 +61,11 @@ describe('Album service', () => {
albumEntity.albumThumbnailAssetId = null; albumEntity.albumThumbnailAssetId = null;
albumEntity.sharedUsers = [ albumEntity.sharedUsers = [
{ {
...userEntityStub.user1, ...userStub.user1,
id: authUser.id, id: authUser.id,
}, },
{ {
...userEntityStub.user1, ...userStub.user1,
id: sharedAlbumSharedAlsoWithId, id: sharedAlbumSharedAlsoWithId,
}, },
]; ];

View file

@ -2,7 +2,7 @@ import { ICryptoRepository, IJobRepository, IStorageRepository, JobName } from '
import { AssetEntity, AssetType, ExifEntity } from '@app/infra/entities'; import { AssetEntity, AssetType, ExifEntity } from '@app/infra/entities';
import { BadRequestException } from '@nestjs/common'; import { BadRequestException } from '@nestjs/common';
import { import {
assetEntityStub, assetStub,
authStub, authStub,
fileStub, fileStub,
IAccessRepositoryMock, IAccessRepositoryMock,
@ -132,11 +132,11 @@ describe('AssetService', () => {
sut = new AssetService(accessMock, assetRepositoryMock, a, cryptoMock, jobMock, storageMock); sut = new AssetService(accessMock, assetRepositoryMock, a, cryptoMock, jobMock, storageMock);
when(assetRepositoryMock.get) when(assetRepositoryMock.get)
.calledWith(assetEntityStub.livePhotoStillAsset.id) .calledWith(assetStub.livePhotoStillAsset.id)
.mockResolvedValue(assetEntityStub.livePhotoStillAsset); .mockResolvedValue(assetStub.livePhotoStillAsset);
when(assetRepositoryMock.get) when(assetRepositoryMock.get)
.calledWith(assetEntityStub.livePhotoMotionAsset.id) .calledWith(assetStub.livePhotoMotionAsset.id)
.mockResolvedValue(assetEntityStub.livePhotoMotionAsset); .mockResolvedValue(assetStub.livePhotoMotionAsset);
}); });
describe('uploadFile', () => { describe('uploadFile', () => {
@ -185,8 +185,8 @@ describe('AssetService', () => {
const error = new QueryFailedError('', [], ''); const error = new QueryFailedError('', [], '');
(error as any).constraint = 'UQ_userid_checksum'; (error as any).constraint = 'UQ_userid_checksum';
assetRepositoryMock.create.mockResolvedValueOnce(assetEntityStub.livePhotoMotionAsset); assetRepositoryMock.create.mockResolvedValueOnce(assetStub.livePhotoMotionAsset);
assetRepositoryMock.create.mockResolvedValueOnce(assetEntityStub.livePhotoStillAsset); assetRepositoryMock.create.mockResolvedValueOnce(assetStub.livePhotoStillAsset);
await expect( await expect(
sut.uploadFile(authStub.user1, dto, fileStub.livePhotoStill, fileStub.livePhotoMotion), sut.uploadFile(authStub.user1, dto, fileStub.livePhotoStill, fileStub.livePhotoMotion),
@ -199,10 +199,10 @@ describe('AssetService', () => {
[ [
{ {
name: JobName.METADATA_EXTRACTION, 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 () => { it('should delete a live photo', async () => {
accessMock.asset.hasOwnerAccess.mockResolvedValue(true); accessMock.asset.hasOwnerAccess.mockResolvedValue(true);
await expect(sut.deleteAll(authStub.user1, { ids: [assetEntityStub.livePhotoStillAsset.id] })).resolves.toEqual([ await expect(sut.deleteAll(authStub.user1, { ids: [assetStub.livePhotoStillAsset.id] })).resolves.toEqual([
{ id: assetEntityStub.livePhotoStillAsset.id, status: 'SUCCESS' }, { id: assetStub.livePhotoStillAsset.id, status: 'SUCCESS' },
{ id: assetEntityStub.livePhotoMotionAsset.id, status: 'SUCCESS' }, { id: assetStub.livePhotoMotionAsset.id, status: 'SUCCESS' },
]); ]);
expect(jobMock.queue).toHaveBeenCalledWith({ expect(jobMock.queue).toHaveBeenCalledWith({
@ -373,7 +373,7 @@ describe('AssetService', () => {
describe('importFile', () => { describe('importFile', () => {
it('should handle a file import', async () => { it('should handle a file import', async () => {
assetRepositoryMock.create.mockResolvedValue(assetEntityStub.image); assetRepositoryMock.create.mockResolvedValue(assetStub.image);
storageMock.checkFileExists.mockResolvedValue(true); storageMock.checkFileExists.mockResolvedValue(true);
await expect( await expect(
@ -392,7 +392,7 @@ describe('AssetService', () => {
(error as any).constraint = 'UQ_userid_checksum'; (error as any).constraint = 'UQ_userid_checksum';
assetRepositoryMock.create.mockRejectedValue(error); assetRepositoryMock.create.mockRejectedValue(error);
assetRepositoryMock.getAssetsByChecksums.mockResolvedValue([assetEntityStub.image]); assetRepositoryMock.getAssetsByChecksums.mockResolvedValue([assetStub.image]);
storageMock.checkFileExists.mockResolvedValue(true); storageMock.checkFileExists.mockResolvedValue(true);
cryptoMock.hashFile.mockResolvedValue(Buffer.from('file hash', 'utf8')); cryptoMock.hashFile.mockResolvedValue(Buffer.from('file hash', 'utf8'));
@ -411,36 +411,36 @@ describe('AssetService', () => {
describe('getAssetById', () => { describe('getAssetById', () => {
it('should allow owner access', async () => { it('should allow owner access', async () => {
accessMock.asset.hasOwnerAccess.mockResolvedValue(true); accessMock.asset.hasOwnerAccess.mockResolvedValue(true);
assetRepositoryMock.getById.mockResolvedValue(assetEntityStub.image); assetRepositoryMock.getById.mockResolvedValue(assetStub.image);
await sut.getAssetById(authStub.admin, assetEntityStub.image.id); await sut.getAssetById(authStub.admin, assetStub.image.id);
expect(accessMock.asset.hasOwnerAccess).toHaveBeenCalledWith(authStub.admin.id, assetEntityStub.image.id); expect(accessMock.asset.hasOwnerAccess).toHaveBeenCalledWith(authStub.admin.id, assetStub.image.id);
}); });
it('should allow shared link access', async () => { it('should allow shared link access', async () => {
accessMock.asset.hasSharedLinkAccess.mockResolvedValue(true); accessMock.asset.hasSharedLinkAccess.mockResolvedValue(true);
assetRepositoryMock.getById.mockResolvedValue(assetEntityStub.image); assetRepositoryMock.getById.mockResolvedValue(assetStub.image);
await sut.getAssetById(authStub.adminSharedLink, assetEntityStub.image.id); await sut.getAssetById(authStub.adminSharedLink, assetStub.image.id);
expect(accessMock.asset.hasSharedLinkAccess).toHaveBeenCalledWith( expect(accessMock.asset.hasSharedLinkAccess).toHaveBeenCalledWith(
authStub.adminSharedLink.sharedLinkId, authStub.adminSharedLink.sharedLinkId,
assetEntityStub.image.id, assetStub.image.id,
); );
}); });
it('should allow partner sharing access', async () => { it('should allow partner sharing access', async () => {
accessMock.asset.hasOwnerAccess.mockResolvedValue(false); accessMock.asset.hasOwnerAccess.mockResolvedValue(false);
accessMock.asset.hasPartnerAccess.mockResolvedValue(true); accessMock.asset.hasPartnerAccess.mockResolvedValue(true);
assetRepositoryMock.getById.mockResolvedValue(assetEntityStub.image); assetRepositoryMock.getById.mockResolvedValue(assetStub.image);
await sut.getAssetById(authStub.admin, assetEntityStub.image.id); await sut.getAssetById(authStub.admin, assetStub.image.id);
expect(accessMock.asset.hasPartnerAccess).toHaveBeenCalledWith(authStub.admin.id, assetEntityStub.image.id); expect(accessMock.asset.hasPartnerAccess).toHaveBeenCalledWith(authStub.admin.id, assetStub.image.id);
}); });
it('should allow shared album access', async () => { it('should allow shared album access', async () => {
accessMock.asset.hasOwnerAccess.mockResolvedValue(false); accessMock.asset.hasOwnerAccess.mockResolvedValue(false);
accessMock.asset.hasPartnerAccess.mockResolvedValue(false); accessMock.asset.hasPartnerAccess.mockResolvedValue(false);
accessMock.asset.hasAlbumAccess.mockResolvedValue(true); accessMock.asset.hasAlbumAccess.mockResolvedValue(true);
assetRepositoryMock.getById.mockResolvedValue(assetEntityStub.image); assetRepositoryMock.getById.mockResolvedValue(assetStub.image);
await sut.getAssetById(authStub.admin, assetEntityStub.image.id); await sut.getAssetById(authStub.admin, assetStub.image.id);
expect(accessMock.asset.hasAlbumAccess).toHaveBeenCalledWith(authStub.admin.id, assetEntityStub.image.id); expect(accessMock.asset.hasAlbumAccess).toHaveBeenCalledWith(authStub.admin.id, assetStub.image.id);
}); });
it('should throw an error for no access', async () => { it('should throw an error for no access', async () => {
@ -448,15 +448,13 @@ describe('AssetService', () => {
accessMock.asset.hasPartnerAccess.mockResolvedValue(false); accessMock.asset.hasPartnerAccess.mockResolvedValue(false);
accessMock.asset.hasSharedLinkAccess.mockResolvedValue(false); accessMock.asset.hasSharedLinkAccess.mockResolvedValue(false);
accessMock.asset.hasAlbumAccess.mockResolvedValue(false); accessMock.asset.hasAlbumAccess.mockResolvedValue(false);
await expect(sut.getAssetById(authStub.admin, assetEntityStub.image.id)).rejects.toBeInstanceOf( await expect(sut.getAssetById(authStub.admin, assetStub.image.id)).rejects.toBeInstanceOf(BadRequestException);
BadRequestException,
);
expect(assetRepositoryMock.getById).not.toHaveBeenCalled(); expect(assetRepositoryMock.getById).not.toHaveBeenCalled();
}); });
it('should throw an error for an invalid shared link', async () => { it('should throw an error for an invalid shared link', async () => {
accessMock.asset.hasSharedLinkAccess.mockResolvedValue(false); 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, BadRequestException,
); );
expect(accessMock.asset.hasOwnerAccess).not.toHaveBeenCalled(); expect(accessMock.asset.hasOwnerAccess).not.toHaveBeenCalled();

File diff suppressed because it is too large Load diff

124
server/test/fixtures/album.stub.ts vendored Normal file
View file

@ -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<AlbumEntity>({
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<AlbumEntity>({
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<AlbumEntity>({
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<AlbumEntity>({
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<AlbumEntity>({
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<AlbumEntity>({
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<AlbumEntity>({
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<AlbumEntity>({
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<AlbumEntity>({
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: [],
}),
};

13
server/test/fixtures/api-key.stub.ts vendored Normal file
View file

@ -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),
};

291
server/test/fixtures/asset.stub.ts vendored Normal file
View file

@ -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<AssetEntity>({
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<AssetEntity>({
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<AssetEntity>({
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<AssetEntity>({
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<AssetEntity>({
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<AssetEntity>({
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<AssetEntity>({
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<AssetEntity>({
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',
}),
};

127
server/test/fixtures/auth.stub.ts vendored Normal file
View file

@ -0,0 +1,127 @@
import { AuthUserDto } from '@app/domain';
export const authStub = {
admin: Object.freeze<AuthUserDto>({
id: 'admin_id',
email: 'admin@test.com',
isAdmin: true,
isPublicUser: false,
isAllowUpload: true,
externalPath: null,
}),
user1: Object.freeze<AuthUserDto>({
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<AuthUserDto>({
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<AuthUserDto>({
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<AuthUserDto>({
id: 'admin_id',
email: 'admin@test.com',
isAdmin: true,
isAllowUpload: true,
isAllowDownload: true,
isPublicUser: true,
isShowExif: true,
sharedLinkId: '123',
}),
adminSharedLinkNoExif: Object.freeze<AuthUserDto>({
id: 'admin_id',
email: 'admin@test.com',
isAdmin: true,
isAllowUpload: true,
isAllowDownload: true,
isPublicUser: true,
isShowExif: false,
sharedLinkId: '123',
}),
readonlySharedLink: Object.freeze<AuthUserDto>({
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;',
],
},
};

58
server/test/fixtures/face.stub.ts vendored Normal file
View file

@ -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<AssetFaceEntity>({
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<AssetFaceEntity>({
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<AssetFaceEntity>({
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<AssetFaceEntity>({
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,
}),
};

12
server/test/fixtures/file.stub.ts vendored Normal file
View file

@ -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',
}),
};

15
server/test/fixtures/index.ts vendored Normal file
View file

@ -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';

95
server/test/fixtures/media.stub.ts vendored Normal file
View file

@ -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<VideoInfo>({ ...probeStubDefault, videoStreams: [] }),
multipleVideoStreams: Object.freeze<VideoInfo>({
...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<VideoInfo>({
...probeStubDefault,
videoStreams: [
{
height: 0,
width: 400,
codecName: 'h265',
codecType: 'video',
frameCount: 100,
rotation: 0,
},
],
}),
videoStream2160p: Object.freeze<VideoInfo>({
...probeStubDefault,
videoStreams: [
{
height: 2160,
width: 3840,
codecName: 'h264',
codecType: 'video',
frameCount: 100,
rotation: 0,
},
],
}),
videoStreamVertical2160p: Object.freeze<VideoInfo>({
...probeStubDefault,
videoStreams: [
{
height: 2160,
width: 3840,
codecName: 'h264',
codecType: 'video',
frameCount: 100,
rotation: 90,
},
],
}),
audioStreamMp3: Object.freeze<VideoInfo>({
...probeStubDefault,
audioStreams: [{ codecType: 'audio', codecName: 'aac' }],
}),
matroskaContainer: Object.freeze<VideoInfo>({
...probeStubDefault,
format: {
formatName: 'matroska,webm',
formatLongName: 'Matroska / WebM',
duration: 0,
},
}),
};

21
server/test/fixtures/partner.stub.ts vendored Normal file
View file

@ -0,0 +1,21 @@
import { PartnerEntity } from '@app/infra/entities';
import { userStub } from './user.stub';
export const partnerStub = {
adminToUser1: Object.freeze<PartnerEntity>({
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<PartnerEntity>({
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,
}),
};

82
server/test/fixtures/person.stub.ts vendored Normal file
View file

@ -0,0 +1,82 @@
import { PersonEntity } from '@app/infra/entities';
import { userStub } from './user.stub';
export const personStub = {
noName: Object.freeze<PersonEntity>({
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<PersonEntity>({
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<PersonEntity>({
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<PersonEntity>({
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<PersonEntity>({
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<PersonEntity>({
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<PersonEntity>({
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,
}),
};

12
server/test/fixtures/search.stub.ts vendored Normal file
View file

@ -0,0 +1,12 @@
import { SearchResult } from '@app/domain';
export const searchStub = {
emptyResults: Object.freeze<SearchResult<any>>({
total: 0,
count: 0,
page: 1,
items: [],
facets: [],
distances: [],
}),
};

282
server/test/fixtures/shared-link.stub.ts vendored Normal file
View file

@ -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<SharedLinkEntity>({
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<SharedLinkResponseDto>({
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<SharedLinkResponseDto>({
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<SharedLinkResponseDto>({
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<SharedLinkResponseDto>({
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 }],
}),
};

View file

@ -0,0 +1,25 @@
import { SystemConfigEntity, SystemConfigKey } from '@app/infra/entities';
export const systemConfigStub: Record<string, SystemConfigEntity[]> = {
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' },
],
};

24
server/test/fixtures/tag.stub.ts vendored Normal file
View file

@ -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<TagEntity>({
id: 'tag-1',
name: 'Tag1',
type: TagType.CUSTOM,
userId: userStub.admin.id,
user: userStub.admin,
renameTagId: null,
assets: [],
}),
};
export const tagResponseStub = {
tag1: Object.freeze<TagResponseDto>({
id: 'tag-1',
name: 'Tag1',
type: 'CUSTOM',
userId: 'admin_id',
}),
};

25
server/test/fixtures/user-token.stub.ts vendored Normal file
View file

@ -0,0 +1,25 @@
import { UserTokenEntity } from '@app/infra/entities';
import { userStub } from './user.stub';
export const userTokenStub = {
userToken: Object.freeze<UserTokenEntity>({
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<UserTokenEntity>({
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',
}),
};

69
server/test/fixtures/user.stub.ts vendored Normal file
View file

@ -0,0 +1,69 @@
import { UserEntity } from '@app/infra/entities';
import { authStub } from './auth.stub';
export const userStub = {
admin: Object.freeze<UserEntity>({
...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<UserEntity>({
...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<UserEntity>({
...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<UserEntity>({
...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: [],
}),
};