refactor: repository mocks (#16785)

This commit is contained in:
Jason Rasmussen
2025-03-10 16:52:44 -04:00
committed by GitHub
parent 1b35400043
commit 1382b27349
59 changed files with 320 additions and 550 deletions

View File

@@ -1,11 +1,12 @@
import { LoggingRepository } from 'src/repositories/logging.repository';
import { EmailRenderRequest, EmailTemplate, NotificationRepository } from 'src/repositories/notification.repository';
import { newFakeLoggingRepository } from 'test/repositories/logger.repository.mock';
import { automock } from 'test/utils';
describe(NotificationRepository.name, () => {
let sut: NotificationRepository;
beforeEach(() => {
sut = new NotificationRepository(newFakeLoggingRepository());
sut = new NotificationRepository(automock(LoggingRepository, { args: [, { getEnv: () => ({}) }], strict: false }));
});
describe('renderEmail', () => {

View File

@@ -1,7 +1,8 @@
import mockfs from 'mock-fs';
import { CrawlOptionsDto } from 'src/dtos/library.dto';
import { LoggingRepository } from 'src/repositories/logging.repository';
import { StorageRepository } from 'src/repositories/storage.repository';
import { newFakeLoggingRepository } from 'test/repositories/logger.repository.mock';
import { automock } from 'test/utils';
interface Test {
test: string;
@@ -182,7 +183,7 @@ describe(StorageRepository.name, () => {
let sut: StorageRepository;
beforeEach(() => {
sut = new StorageRepository(newFakeLoggingRepository());
sut = new StorageRepository(automock(LoggingRepository, { args: [, { getEnv: () => ({}) }], strict: false }));
});
afterEach(() => {

View File

@@ -146,6 +146,7 @@ describe(ActivityService.name, () => {
const activity = factory.activity();
mocks.access.activity.checkOwnerAccess.mockResolvedValue(new Set([activity.id]));
mocks.activity.delete.mockResolvedValue();
await sut.delete(factory.auth(), activity.id);
@@ -156,6 +157,7 @@ describe(ActivityService.name, () => {
const activity = factory.activity();
mocks.access.activity.checkAlbumOwnerAccess.mockResolvedValue(new Set([activity.id]));
mocks.activity.delete.mockResolvedValue();
await sut.delete(factory.auth(), activity.id);

View File

@@ -347,6 +347,7 @@ describe(AlbumService.name, () => {
it('should remove a shared user from an owned album', async () => {
mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([albumStub.sharedWithUser.id]));
mocks.album.getById.mockResolvedValue(albumStub.sharedWithUser);
mocks.albumUser.delete.mockResolvedValue();
await expect(
sut.removeUser(authStub.admin, albumStub.sharedWithUser.id, userStub.user1.id),
@@ -376,6 +377,7 @@ describe(AlbumService.name, () => {
it('should allow a shared user to remove themselves', async () => {
mocks.album.getById.mockResolvedValue(albumStub.sharedWithUser);
mocks.albumUser.delete.mockResolvedValue();
await sut.removeUser(authStub.user1, albumStub.sharedWithUser.id, authStub.user1.user.id);
@@ -388,6 +390,7 @@ describe(AlbumService.name, () => {
it('should allow a shared user to remove themselves using "me"', async () => {
mocks.album.getById.mockResolvedValue(albumStub.sharedWithUser);
mocks.albumUser.delete.mockResolvedValue();
await sut.removeUser(authStub.user1, albumStub.sharedWithUser.id, 'me');
@@ -422,6 +425,8 @@ describe(AlbumService.name, () => {
describe('updateUser', () => {
it('should update user role', async () => {
mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([albumStub.sharedWithAdmin.id]));
mocks.albumUser.update.mockResolvedValue(null as any);
await sut.updateUser(authStub.user1, albumStub.sharedWithAdmin.id, userStub.admin.id, {
role: AlbumUserRole.EDITOR,
});

View File

@@ -67,6 +67,8 @@ describe(ApiKeyService.name, () => {
const id = newUuid();
const auth = factory.auth();
mocks.apiKey.getById.mockResolvedValue(void 0);
await expect(sut.update(auth, id, { name: 'New Name' })).rejects.toBeInstanceOf(BadRequestException);
expect(mocks.apiKey.update).not.toHaveBeenCalledWith(id);
@@ -91,6 +93,8 @@ describe(ApiKeyService.name, () => {
const auth = factory.auth();
const id = newUuid();
mocks.apiKey.getById.mockResolvedValue(void 0);
await expect(sut.delete(auth, id)).rejects.toBeInstanceOf(BadRequestException);
expect(mocks.apiKey.delete).not.toHaveBeenCalledWith(id);
@@ -101,6 +105,7 @@ describe(ApiKeyService.name, () => {
const apiKey = factory.apiKey({ userId: auth.user.id });
mocks.apiKey.getById.mockResolvedValue(apiKey);
mocks.apiKey.delete.mockResolvedValue();
await sut.delete(auth, apiKey.id);
@@ -113,6 +118,8 @@ describe(ApiKeyService.name, () => {
const auth = factory.auth();
const id = newUuid();
mocks.apiKey.getById.mockResolvedValue(void 0);
await expect(sut.getById(auth, id)).rejects.toBeInstanceOf(BadRequestException);
expect(mocks.apiKey.getById).toHaveBeenCalledWith(auth.user.id, id);

View File

@@ -127,8 +127,11 @@ describe(AssetService.name, () => {
describe('getRandom', () => {
it('should get own random assets', async () => {
mocks.partner.getAll.mockResolvedValue([]);
mocks.asset.getRandom.mockResolvedValue([assetStub.image]);
await sut.getRandom(authStub.admin, 1);
expect(mocks.asset.getRandom).toHaveBeenCalledWith([authStub.admin.user.id], 1);
});
@@ -531,6 +534,7 @@ describe(AssetService.name, () => {
});
it('should update stack primary asset if deleted asset was primary asset in a stack', async () => {
mocks.stack.update.mockResolvedValue(factory.stack() as unknown as any);
mocks.asset.getById.mockResolvedValue(assetStub.primaryImage as AssetEntity);
await sut.handleAssetDeletion({ id: assetStub.primaryImage.id, deleteOnDisk: true });
@@ -542,6 +546,7 @@ describe(AssetService.name, () => {
});
it('should delete the entire stack if deleted asset was the primary asset and the stack would only contain one asset afterwards', async () => {
mocks.stack.delete.mockResolvedValue();
mocks.asset.getById.mockResolvedValue({
...assetStub.primaryImage,
stack: { ...assetStub.primaryImage.stack, assets: assetStub.primaryImage.stack!.assets.slice(0, 2) },

View File

@@ -18,7 +18,10 @@ describe(AuditService.name, () => {
describe('handleCleanup', () => {
it('should delete old audit entries', async () => {
mocks.audit.removeBefore.mockResolvedValue();
await expect(sut.handleCleanup()).resolves.toBe(JobStatus.SUCCESS);
expect(mocks.audit.removeBefore).toHaveBeenCalledWith(expect.any(Date));
});
});

View File

@@ -65,7 +65,10 @@ describe('AuthService', () => {
describe('onBootstrap', () => {
it('should init the repo', () => {
mocks.oauth.init.mockResolvedValue();
sut.onBootstrap();
expect(mocks.oauth.init).toHaveBeenCalled();
});
});
@@ -73,24 +76,30 @@ describe('AuthService', () => {
describe('login', () => {
it('should throw an error if password login is disabled', async () => {
mocks.systemMetadata.get.mockResolvedValue(systemConfigStub.disabled);
await expect(sut.login(fixtures.login, loginDetails)).rejects.toBeInstanceOf(UnauthorizedException);
});
it('should check the user exists', async () => {
mocks.user.getByEmail.mockResolvedValue(void 0);
await expect(sut.login(fixtures.login, loginDetails)).rejects.toBeInstanceOf(UnauthorizedException);
expect(mocks.user.getByEmail).toHaveBeenCalledTimes(1);
});
it('should check the user has a password', async () => {
mocks.user.getByEmail.mockResolvedValue({} as UserEntity);
await expect(sut.login(fixtures.login, loginDetails)).rejects.toBeInstanceOf(UnauthorizedException);
expect(mocks.user.getByEmail).toHaveBeenCalledTimes(1);
});
it('should successfully log the user in', async () => {
mocks.user.getByEmail.mockResolvedValue(userStub.user1);
mocks.session.create.mockResolvedValue(sessionStub.valid);
await expect(sut.login(fixtures.login, loginDetails)).resolves.toEqual({
accessToken: 'cmFuZG9tLWJ5dGVz',
userId: 'user-id',
@@ -100,6 +109,7 @@ describe('AuthService', () => {
isAdmin: false,
shouldChangePassword: false,
});
expect(mocks.user.getByEmail).toHaveBeenCalledTimes(1);
});
});
@@ -159,8 +169,10 @@ describe('AuthService', () => {
describe('logout', () => {
it('should return the end session endpoint', async () => {
const auth = factory.auth();
mocks.systemMetadata.get.mockResolvedValue(systemConfigStub.enabled);
const auth = { user: { id: '123' } } as AuthDto;
await expect(sut.logout(auth, AuthType.OAUTH)).resolves.toEqual({
successful: true,
redirectUri: 'http://end-session-endpoint',
@@ -168,7 +180,7 @@ describe('AuthService', () => {
});
it('should return the default redirect', async () => {
const auth = { user: { id: '123' } } as AuthDto;
const auth = factory.auth();
await expect(sut.logout(auth, AuthType.PASSWORD)).resolves.toEqual({
successful: true,
@@ -178,6 +190,7 @@ describe('AuthService', () => {
it('should delete the access token', async () => {
const auth = { user: { id: '123' }, session: { id: 'token123' } } as AuthDto;
mocks.session.delete.mockResolvedValue();
await expect(sut.logout(auth, AuthType.PASSWORD)).resolves.toEqual({
successful: true,
@@ -203,7 +216,9 @@ describe('AuthService', () => {
it('should only allow one admin', async () => {
mocks.user.getAdmin.mockResolvedValue({} as UserEntity);
await expect(sut.adminSignUp(dto)).rejects.toBeInstanceOf(BadRequestException);
expect(mocks.user.getAdmin).toHaveBeenCalled();
});
@@ -215,6 +230,7 @@ describe('AuthService', () => {
createdAt: new Date('2021-01-01'),
metadata: [] as UserMetadataEntity[],
} as UserEntity);
await expect(sut.adminSignUp(dto)).resolves.toMatchObject({
avatarColor: expect.any(String),
id: 'admin',
@@ -222,6 +238,7 @@ describe('AuthService', () => {
email: 'test@immich.com',
name: 'immich admin',
});
expect(mocks.user.getAdmin).toHaveBeenCalled();
expect(mocks.user.create).toHaveBeenCalled();
});
@@ -241,6 +258,7 @@ describe('AuthService', () => {
it('should validate using authorization header', async () => {
mocks.user.get.mockResolvedValue(userStub.user1);
mocks.session.getByToken.mockResolvedValue(sessionStub.valid as any);
await expect(
sut.authenticate({
headers: { authorization: 'Bearer auth_token' },
@@ -256,6 +274,8 @@ describe('AuthService', () => {
describe('validate - shared key', () => {
it('should not accept a non-existent key', async () => {
mocks.sharedLink.getByKey.mockResolvedValue(void 0);
await expect(
sut.authenticate({
headers: { 'x-immich-share-key': 'key' },
@@ -267,6 +287,7 @@ describe('AuthService', () => {
it('should not accept an expired key', async () => {
mocks.sharedLink.getByKey.mockResolvedValue(sharedLinkStub.expired);
await expect(
sut.authenticate({
headers: { 'x-immich-share-key': 'key' },
@@ -278,6 +299,7 @@ describe('AuthService', () => {
it('should not accept a key on a non-shared route', async () => {
mocks.sharedLink.getByKey.mockResolvedValue(sharedLinkStub.valid);
await expect(
sut.authenticate({
headers: { 'x-immich-share-key': 'key' },
@@ -290,6 +312,7 @@ describe('AuthService', () => {
it('should not accept a key without a user', async () => {
mocks.sharedLink.getByKey.mockResolvedValue(sharedLinkStub.expired);
mocks.user.get.mockResolvedValue(void 0);
await expect(
sut.authenticate({
headers: { 'x-immich-share-key': 'key' },
@@ -302,6 +325,7 @@ describe('AuthService', () => {
it('should accept a base64url key', async () => {
mocks.sharedLink.getByKey.mockResolvedValue(sharedLinkStub.valid);
mocks.user.get.mockResolvedValue(userStub.admin);
await expect(
sut.authenticate({
headers: { 'x-immich-share-key': sharedLinkStub.valid.key.toString('base64url') },
@@ -318,6 +342,7 @@ describe('AuthService', () => {
it('should accept a hex key', async () => {
mocks.sharedLink.getByKey.mockResolvedValue(sharedLinkStub.valid);
mocks.user.get.mockResolvedValue(userStub.admin);
await expect(
sut.authenticate({
headers: { 'x-immich-share-key': sharedLinkStub.valid.key.toString('hex') },
@@ -335,6 +360,7 @@ describe('AuthService', () => {
describe('validate - user token', () => {
it('should throw if no token is found', async () => {
mocks.session.getByToken.mockResolvedValue(void 0);
await expect(
sut.authenticate({
headers: { 'x-immich-user-token': 'auth_token' },
@@ -346,6 +372,7 @@ describe('AuthService', () => {
it('should return an auth dto', async () => {
mocks.session.getByToken.mockResolvedValue(sessionStub.valid as any);
await expect(
sut.authenticate({
headers: { cookie: 'immich_access_token=auth_token' },
@@ -360,6 +387,7 @@ describe('AuthService', () => {
it('should throw if admin route and not an admin', async () => {
mocks.session.getByToken.mockResolvedValue(sessionStub.valid as any);
await expect(
sut.authenticate({
headers: { cookie: 'immich_access_token=auth_token' },
@@ -372,6 +400,7 @@ describe('AuthService', () => {
it('should update when access time exceeds an hour', async () => {
mocks.session.getByToken.mockResolvedValue(sessionStub.inactive as any);
mocks.session.update.mockResolvedValue(sessionStub.valid);
await expect(
sut.authenticate({
headers: { cookie: 'immich_access_token=auth_token' },
@@ -386,6 +415,7 @@ describe('AuthService', () => {
describe('validate - api key', () => {
it('should throw an error if no api key is found', async () => {
mocks.apiKey.getKey.mockResolvedValue(void 0);
await expect(
sut.authenticate({
headers: { 'x-api-key': 'auth_token' },
@@ -401,6 +431,7 @@ describe('AuthService', () => {
const authApiKey = factory.authApiKey({ permissions: [] });
mocks.apiKey.getKey.mockResolvedValue({ ...authApiKey, user: authUser });
await expect(
sut.authenticate({
headers: { 'x-api-key': 'auth_token' },
@@ -442,6 +473,7 @@ describe('AuthService', () => {
describe('authorize', () => {
it('should fail if oauth is disabled', async () => {
mocks.systemMetadata.get.mockResolvedValue({ oauth: { enabled: false } });
await expect(sut.authorize({ redirectUri: 'https://demo.immich.app' })).rejects.toBeInstanceOf(
BadRequestException,
);
@@ -449,6 +481,7 @@ describe('AuthService', () => {
it('should authorize the user', async () => {
mocks.systemMetadata.get.mockResolvedValue(systemConfigStub.oauthWithMobileOverride);
await sut.authorize({ redirectUri: 'https://demo.immich.app' });
});
});
@@ -461,9 +494,11 @@ describe('AuthService', () => {
it('should not allow auto registering', async () => {
mocks.systemMetadata.get.mockResolvedValue(systemConfigStub.oauthEnabled);
mocks.user.getByEmail.mockResolvedValue(void 0);
await expect(sut.callback({ url: 'http://immich/auth/login?code=abc123' }, loginDetails)).rejects.toBeInstanceOf(
BadRequestException,
);
expect(mocks.user.getByEmail).toHaveBeenCalledTimes(1);
});
@@ -540,6 +575,7 @@ describe('AuthService', () => {
mocks.session.create.mockResolvedValue(sessionStub.valid);
await sut.callback({ url }, loginDetails);
expect(mocks.oauth.getProfile).toHaveBeenCalledWith(expect.objectContaining({}), url, 'http://mobile-redirect');
});
}
@@ -549,6 +585,7 @@ describe('AuthService', () => {
mocks.user.getByEmail.mockResolvedValue(void 0);
mocks.user.getAdmin.mockResolvedValue(userStub.user1);
mocks.user.create.mockResolvedValue(userStub.user1);
mocks.session.create.mockResolvedValue(factory.session());
await expect(sut.callback({ url: 'http://immich/auth/login?code=abc123' }, loginDetails)).resolves.toEqual(
oauthResponse,
@@ -563,6 +600,7 @@ describe('AuthService', () => {
mocks.user.getAdmin.mockResolvedValue(userStub.user1);
mocks.user.create.mockResolvedValue(userStub.user1);
mocks.oauth.getProfile.mockResolvedValue({ sub, email, immich_quota: 'abc' });
mocks.session.create.mockResolvedValue(factory.session());
await expect(sut.callback({ url: 'http://immich/auth/login?code=abc123' }, loginDetails)).resolves.toEqual(
oauthResponse,
@@ -577,6 +615,7 @@ describe('AuthService', () => {
mocks.user.getAdmin.mockResolvedValue(userStub.user1);
mocks.user.create.mockResolvedValue(userStub.user1);
mocks.oauth.getProfile.mockResolvedValue({ sub, email, immich_quota: -5 });
mocks.session.create.mockResolvedValue(factory.session());
await expect(sut.callback({ url: 'http://immich/auth/login?code=abc123' }, loginDetails)).resolves.toEqual(
oauthResponse,
@@ -591,6 +630,7 @@ describe('AuthService', () => {
mocks.user.getAdmin.mockResolvedValue(userStub.user1);
mocks.user.create.mockResolvedValue(userStub.user1);
mocks.oauth.getProfile.mockResolvedValue({ sub, email, immich_quota: 0 });
mocks.session.create.mockResolvedValue(factory.session());
await expect(sut.callback({ url: 'http://immich/auth/login?code=abc123' }, loginDetails)).resolves.toEqual(
oauthResponse,
@@ -611,6 +651,7 @@ describe('AuthService', () => {
mocks.user.getAdmin.mockResolvedValue(userStub.user1);
mocks.user.create.mockResolvedValue(userStub.user1);
mocks.oauth.getProfile.mockResolvedValue({ sub, email, immich_quota: 5 });
mocks.session.create.mockResolvedValue(factory.session());
await expect(sut.callback({ url: 'http://immich/auth/login?code=abc123' }, loginDetails)).resolves.toEqual(
oauthResponse,

View File

@@ -22,6 +22,7 @@ describe(BackupService.name, () => {
describe('onBootstrapEvent', () => {
it('should init cron job and handle config changes', async () => {
mocks.database.tryLock.mockResolvedValue(true);
mocks.cron.create.mockResolvedValue();
await sut.onConfigInit({ newConfig: systemConfigStub.backupEnabled as SystemConfig });
@@ -47,10 +48,14 @@ describe(BackupService.name, () => {
describe('onConfigUpdateEvent', () => {
beforeEach(async () => {
mocks.database.tryLock.mockResolvedValue(true);
mocks.cron.create.mockResolvedValue();
await sut.onConfigInit({ newConfig: defaults });
});
it('should update cron job if backup is enabled', () => {
mocks.cron.update.mockResolvedValue();
sut.onConfigUpdate({
oldConfig: defaults,
newConfig: {

View File

@@ -31,6 +31,8 @@ describe(CliService.name, () => {
it('should default to a random password', async () => {
mocks.user.getAdmin.mockResolvedValue(userStub.admin);
mocks.user.update.mockResolvedValue(userStub.admin);
const ask = vitest.fn().mockImplementation(() => {});
const response = await sut.resetAdminPassword(ask);
@@ -45,6 +47,8 @@ describe(CliService.name, () => {
it('should use the supplied password', async () => {
mocks.user.getAdmin.mockResolvedValue(userStub.admin);
mocks.user.update.mockResolvedValue(userStub.admin);
const ask = vitest.fn().mockResolvedValue('new-password');
const response = await sut.resetAdminPassword(ask);

View File

@@ -173,6 +173,7 @@ describe(DownloadService.name, () => {
it('should return a list of archives (assetIds)', async () => {
const assetIds = ['asset-1', 'asset-2'];
mocks.user.getMetadata.mockResolvedValue([]);
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(assetIds));
mocks.downloadRepository.downloadAssetIds.mockReturnValue(
makeStream([
@@ -187,6 +188,7 @@ describe(DownloadService.name, () => {
});
it('should return a list of archives (albumId)', async () => {
mocks.user.getMetadata.mockResolvedValue([]);
mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set(['album-1']));
mocks.downloadRepository.downloadAlbumId.mockReturnValue(
makeStream([
@@ -202,6 +204,7 @@ describe(DownloadService.name, () => {
});
it('should return a list of archives (userId)', async () => {
mocks.user.getMetadata.mockResolvedValue([]);
mocks.downloadRepository.downloadUserId.mockReturnValue(
makeStream([
{ id: 'asset-1', livePhotoVideoId: null, size: 100_000 },
@@ -217,6 +220,7 @@ describe(DownloadService.name, () => {
});
it('should split archives by size', async () => {
mocks.user.getMetadata.mockResolvedValue([]);
mocks.downloadRepository.downloadUserId.mockReturnValue(
makeStream([
{ id: 'asset-1', livePhotoVideoId: null, size: 5000 },
@@ -244,13 +248,13 @@ describe(DownloadService.name, () => {
const assetIds = ['asset-1', 'asset-2'];
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(assetIds));
mocks.user.getMetadata.mockResolvedValue([]);
mocks.downloadRepository.downloadAssetIds.mockReturnValue(
makeStream([
{ id: 'asset-1', livePhotoVideoId: 'asset-3', size: 5000 },
{ id: 'asset-2', livePhotoVideoId: 'asset-4', size: 100_000 },
]),
);
mocks.downloadRepository.downloadMotionAssetIds.mockReturnValue(
makeStream([
{ id: 'asset-3', livePhotoVideoId: null, size: 23_456, originalPath: '/path/to/file.mp4' },
@@ -271,11 +275,10 @@ describe(DownloadService.name, () => {
const assetIds = ['asset-1'];
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(assetIds));
mocks.user.getMetadata.mockResolvedValue([]);
mocks.downloadRepository.downloadAssetIds.mockReturnValue(
makeStream([{ id: 'asset-1', livePhotoVideoId: 'asset-3', size: 5000 }]),
);
mocks.downloadRepository.downloadMotionAssetIds.mockReturnValue(
makeStream([
{ id: 'asset-2', livePhotoVideoId: null, size: 23_456, originalPath: 'upload/encoded-video/uuid-MP.mp4' },

View File

@@ -36,6 +36,9 @@ describe(LibraryService.name, () => {
describe('onConfigInit', () => {
it('should init cron job and handle config changes', async () => {
mocks.cron.create.mockResolvedValue();
mocks.cron.update.mockResolvedValue();
await sut.onConfigInit({ newConfig: defaults });
expect(mocks.cron.create).toHaveBeenCalled();
@@ -65,6 +68,7 @@ describe(LibraryService.name, () => {
mocks.library.get.mockImplementation((id) =>
Promise.resolve([library1, library2].find((library) => library.id === id)),
);
mocks.cron.create.mockResolvedValue();
await sut.onConfigInit({ newConfig: systemConfigStub.libraryWatchEnabled as SystemConfig });
@@ -74,6 +78,8 @@ describe(LibraryService.name, () => {
});
it('should not initialize watcher when watching is disabled', async () => {
mocks.cron.create.mockResolvedValue();
await sut.onConfigInit({ newConfig: systemConfigStub.libraryWatchDisabled as SystemConfig });
expect(mocks.storage.watch).not.toHaveBeenCalled();
@@ -99,6 +105,8 @@ describe(LibraryService.name, () => {
describe('onConfigUpdateEvent', () => {
beforeEach(async () => {
mocks.database.tryLock.mockResolvedValue(true);
mocks.cron.create.mockResolvedValue();
await sut.onConfigInit({ newConfig: defaults });
});
@@ -111,6 +119,9 @@ describe(LibraryService.name, () => {
it('should update cron job and enable watching', async () => {
mocks.library.getAll.mockResolvedValue([]);
mocks.cron.create.mockResolvedValue();
mocks.cron.update.mockResolvedValue();
await sut.onConfigUpdate({
newConfig: systemConfigStub.libraryScanAndWatch as SystemConfig,
oldConfig: defaults,
@@ -125,6 +136,9 @@ describe(LibraryService.name, () => {
it('should update cron job and disable watching', async () => {
mocks.library.getAll.mockResolvedValue([]);
mocks.cron.create.mockResolvedValue();
mocks.cron.update.mockResolvedValue();
await sut.onConfigUpdate({
newConfig: systemConfigStub.libraryScanAndWatch as SystemConfig,
oldConfig: defaults,
@@ -620,6 +634,7 @@ describe(LibraryService.name, () => {
const mockClose = vitest.fn();
mocks.storage.watch.mockImplementation(makeMockWatcher({ close: mockClose }));
mocks.cron.create.mockResolvedValue();
await sut.onConfigInit({ newConfig: systemConfigStub.libraryWatchEnabled as SystemConfig });
await sut.delete(library.id);
@@ -765,6 +780,7 @@ describe(LibraryService.name, () => {
mocks.library.create.mockResolvedValue(library);
mocks.library.get.mockResolvedValue(library);
mocks.library.getAll.mockResolvedValue([]);
mocks.cron.create.mockResolvedValue();
await sut.onConfigInit({ newConfig: systemConfigStub.libraryWatchEnabled as SystemConfig });
await sut.create({ ownerId: authStub.admin.user.id, importPaths: library.importPaths });
@@ -832,6 +848,7 @@ describe(LibraryService.name, () => {
describe('update', () => {
beforeEach(async () => {
mocks.library.getAll.mockResolvedValue([]);
mocks.cron.create.mockResolvedValue();
await sut.onConfigInit({ newConfig: systemConfigStub.libraryWatchEnabled as SystemConfig });
});
@@ -878,6 +895,8 @@ describe(LibraryService.name, () => {
describe('watching disabled', () => {
beforeEach(async () => {
mocks.cron.create.mockResolvedValue();
await sut.onConfigInit({ newConfig: systemConfigStub.libraryWatchDisabled as SystemConfig });
});
@@ -895,6 +914,8 @@ describe(LibraryService.name, () => {
describe('watching enabled', () => {
beforeEach(async () => {
mocks.library.getAll.mockResolvedValue([]);
mocks.cron.create.mockResolvedValue();
await sut.onConfigInit({ newConfig: systemConfigStub.libraryWatchEnabled as SystemConfig });
});
@@ -1067,6 +1088,7 @@ describe(LibraryService.name, () => {
const mockClose = vitest.fn();
mocks.storage.watch.mockImplementation(makeMockWatcher({ close: mockClose }));
mocks.cron.create.mockResolvedValue();
await sut.onConfigInit({ newConfig: systemConfigStub.libraryWatchEnabled as SystemConfig });
await sut.onShutdown();

View File

@@ -33,6 +33,8 @@ describe(MemoryService.name, () => {
});
it('should map ', async () => {
mocks.memory.search.mockResolvedValue([]);
await expect(sut.search(factory.auth(), {})).resolves.toEqual([]);
});
});
@@ -46,6 +48,7 @@ describe(MemoryService.name, () => {
const [memoryId] = newUuids();
mocks.access.memory.checkOwnerAccess.mockResolvedValue(new Set([memoryId]));
mocks.memory.get.mockResolvedValue(void 0);
await expect(sut.get(factory.auth(), memoryId)).rejects.toBeInstanceOf(BadRequestException);
});
@@ -159,6 +162,7 @@ describe(MemoryService.name, () => {
const memoryId = newUuid();
mocks.access.memory.checkOwnerAccess.mockResolvedValue(new Set([memoryId]));
mocks.memory.delete.mockResolvedValue();
await expect(sut.remove(factory.auth(), memoryId)).resolves.toBeUndefined();
@@ -183,6 +187,7 @@ describe(MemoryService.name, () => {
mocks.access.memory.checkOwnerAccess.mockResolvedValue(new Set([memory.id]));
mocks.memory.get.mockResolvedValue(memory);
mocks.memory.getAssetIds.mockResolvedValue(new Set());
await expect(sut.addAssets(factory.auth(), memory.id, { ids: [assetId] })).resolves.toEqual([
{ error: 'no_permission', id: assetId, success: false },
@@ -213,6 +218,9 @@ describe(MemoryService.name, () => {
mocks.access.memory.checkOwnerAccess.mockResolvedValue(new Set([memory.id]));
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([assetId]));
mocks.memory.get.mockResolvedValue(memory);
mocks.memory.update.mockResolvedValue(memory);
mocks.memory.getAssetIds.mockResolvedValue(new Set());
mocks.memory.addAssetIds.mockResolvedValue();
await expect(sut.addAssets(factory.auth(), memory.id, { ids: [assetId] })).resolves.toEqual([
{ id: assetId, success: true },
@@ -233,6 +241,7 @@ describe(MemoryService.name, () => {
it('should skip assets not in the memory', async () => {
mocks.access.memory.checkOwnerAccess.mockResolvedValue(new Set(['memory1']));
mocks.memory.getAssetIds.mockResolvedValue(new Set());
await expect(sut.removeAssets(factory.auth(), 'memory1', { ids: ['not-found'] })).resolves.toEqual([
{ error: 'not_found', id: 'not-found', success: false },
@@ -242,15 +251,20 @@ describe(MemoryService.name, () => {
});
it('should remove assets', async () => {
mocks.access.memory.checkOwnerAccess.mockResolvedValue(new Set(['memory1']));
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset1']));
mocks.memory.getAssetIds.mockResolvedValue(new Set(['asset1']));
const memory = factory.memory();
const asset = factory.asset();
await expect(sut.removeAssets(factory.auth(), 'memory1', { ids: ['asset1'] })).resolves.toEqual([
{ id: 'asset1', success: true },
mocks.access.memory.checkOwnerAccess.mockResolvedValue(new Set([memory.id]));
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([asset.id]));
mocks.memory.getAssetIds.mockResolvedValue(new Set([asset.id]));
mocks.memory.removeAssetIds.mockResolvedValue();
mocks.memory.update.mockResolvedValue(memory);
await expect(sut.removeAssets(factory.auth(), memory.id, { ids: [asset.id] })).resolves.toEqual([
{ id: asset.id, success: true },
]);
expect(mocks.memory.removeAssetIds).toHaveBeenCalledWith('memory1', ['asset1']);
expect(mocks.memory.removeAssetIds).toHaveBeenCalledWith(memory.id, [asset.id]);
});
});
});

View File

@@ -52,6 +52,10 @@ describe(MetadataService.name, () => {
describe('onBootstrapEvent', () => {
it('should pause and resume queue during init', async () => {
mocks.job.pause.mockResolvedValue();
mocks.map.init.mockResolvedValue();
mocks.job.resume.mockResolvedValue();
await sut.onBootstrap();
expect(mocks.job.pause).toHaveBeenCalledTimes(1);

View File

@@ -260,6 +260,7 @@ describe(NotificationService.name, () => {
mocks.user.get.mockResolvedValue(userStub.admin);
mocks.notification.verifySmtp.mockResolvedValue(true);
mocks.notification.renderEmail.mockResolvedValue({ html: '', text: '' });
mocks.notification.sendEmail.mockResolvedValue({ messageId: 'message-1', response: '' });
await expect(sut.sendTestEmail('', configs.smtpTransport.notifications.smtp)).resolves.not.toThrow();
expect(mocks.notification.renderEmail).toHaveBeenCalledWith({
@@ -279,6 +280,7 @@ describe(NotificationService.name, () => {
mocks.notification.verifySmtp.mockResolvedValue(true);
mocks.notification.renderEmail.mockResolvedValue({ html: '', text: '' });
mocks.systemMetadata.get.mockResolvedValue({ server: { externalDomain: 'https://demo.immich.app' } });
mocks.notification.sendEmail.mockResolvedValue({ messageId: 'message-1', response: '' });
await expect(sut.sendTestEmail('', configs.smtpTransport.notifications.smtp)).resolves.not.toThrow();
expect(mocks.notification.renderEmail).toHaveBeenCalledWith({
@@ -297,6 +299,7 @@ describe(NotificationService.name, () => {
mocks.user.get.mockResolvedValue(userStub.admin);
mocks.notification.verifySmtp.mockResolvedValue(true);
mocks.notification.renderEmail.mockResolvedValue({ html: '', text: '' });
mocks.notification.sendEmail.mockResolvedValue({ messageId: 'message-1', response: '' });
await expect(
sut.sendTestEmail('', { ...configs.smtpTransport.notifications.smtp, replyTo: 'demo@immich.app' }),

View File

@@ -324,6 +324,10 @@ describe(PersonService.name, () => {
mocks.person.getFacesByIds.mockResolvedValue([faceStub.face1]);
mocks.person.reassignFace.mockResolvedValue(1);
mocks.person.getRandomFace.mockResolvedValue(faceStub.primaryFace1);
mocks.person.refreshFaces.mockResolvedValue();
mocks.person.reassignFace.mockResolvedValue(5);
mocks.person.update.mockResolvedValue(personStub.noName);
await expect(
sut.reassignFaces(authStub.admin, personStub.noName.id, {
data: [{ personId: personStub.withName.id, assetId: assetStub.image.id }],
@@ -515,6 +519,7 @@ describe(PersonService.name, () => {
hasNextPage: false,
});
mocks.person.getAllWithoutFaces.mockResolvedValue([personStub.randomPerson]);
mocks.person.deleteFaces.mockResolvedValue();
await sut.handleQueueDetectFaces({ force: true });
@@ -633,6 +638,7 @@ describe(PersonService.name, () => {
mocks.person.getAll.mockReturnValue(makeStream());
mocks.person.getAllFaces.mockReturnValue(makeStream([faceStub.face1]));
mocks.person.getAllWithoutFaces.mockResolvedValue([]);
mocks.person.unassignFaces.mockResolvedValue();
await sut.handleQueueRecognizeFaces({ force: true, nightly: true });
@@ -679,6 +685,7 @@ describe(PersonService.name, () => {
mocks.person.getAll.mockReturnValue(makeStream([faceStub.face1.person, personStub.randomPerson]));
mocks.person.getAllFaces.mockReturnValue(makeStream([faceStub.face1]));
mocks.person.getAllWithoutFaces.mockResolvedValue([personStub.randomPerson]);
mocks.person.unassignFaces.mockResolvedValue();
await sut.handleQueueRecognizeFaces({ force: true });
@@ -757,6 +764,7 @@ describe(PersonService.name, () => {
mocks.machineLearning.detectFaces.mockResolvedValue(detectFaceMock);
mocks.search.searchFaces.mockResolvedValue([{ ...faceStub.face1, distance: 0.7 }]);
mocks.asset.getByIds.mockResolvedValue([assetStub.image]);
mocks.person.refreshFaces.mockResolvedValue();
await sut.handleDetectFaces({ id: assetStub.image.id });
@@ -784,6 +792,7 @@ describe(PersonService.name, () => {
it('should add new face and delete an existing face not among the new detected faces', async () => {
mocks.machineLearning.detectFaces.mockResolvedValue(detectFaceMock);
mocks.asset.getByIds.mockResolvedValue([{ ...assetStub.image, faces: [faceStub.primaryFace1] }]);
mocks.person.refreshFaces.mockResolvedValue();
await sut.handleDetectFaces({ id: assetStub.image.id });
@@ -799,6 +808,7 @@ describe(PersonService.name, () => {
it('should add embedding to matching metadata face', async () => {
mocks.machineLearning.detectFaces.mockResolvedValue(detectFaceMock);
mocks.asset.getByIds.mockResolvedValue([{ ...assetStub.image, faces: [faceStub.fromExif1] }]);
mocks.person.refreshFaces.mockResolvedValue();
await sut.handleDetectFaces({ id: assetStub.image.id });
@@ -1006,6 +1016,7 @@ describe(PersonService.name, () => {
mocks.person.getById.mockResolvedValue({ ...personStub.primaryPerson, faceAssetId: faceStub.middle.assetId });
mocks.person.getFaceByIdWithAssets.mockResolvedValue(faceStub.middle);
mocks.asset.getById.mockResolvedValue(assetStub.primaryImage);
mocks.media.generateThumbnail.mockResolvedValue();
await sut.handleGeneratePersonThumbnail({ id: personStub.primaryPerson.id });
@@ -1038,6 +1049,7 @@ describe(PersonService.name, () => {
mocks.person.getById.mockResolvedValue({ ...personStub.primaryPerson, faceAssetId: faceStub.start.assetId });
mocks.person.getFaceByIdWithAssets.mockResolvedValue(faceStub.start);
mocks.asset.getById.mockResolvedValue(assetStub.image);
mocks.media.generateThumbnail.mockResolvedValue();
await sut.handleGeneratePersonThumbnail({ id: personStub.primaryPerson.id });
@@ -1063,7 +1075,9 @@ describe(PersonService.name, () => {
it('should generate a thumbnail without overflowing', async () => {
mocks.person.getById.mockResolvedValue({ ...personStub.primaryPerson, faceAssetId: faceStub.end.assetId });
mocks.person.getFaceByIdWithAssets.mockResolvedValue(faceStub.end);
mocks.person.update.mockResolvedValue(personStub.primaryPerson);
mocks.asset.getById.mockResolvedValue(assetStub.primaryImage);
mocks.media.generateThumbnail.mockResolvedValue();
await sut.handleGeneratePersonThumbnail({ id: personStub.primaryPerson.id });

View File

@@ -57,6 +57,8 @@ describe(SearchService.name, () => {
describe('getSearchSuggestions', () => {
it('should return search suggestions for country', async () => {
mocks.search.getCountries.mockResolvedValue(['USA']);
mocks.partner.getAll.mockResolvedValue([]);
await expect(
sut.getSearchSuggestions(authStub.user1, { includeNull: false, type: SearchSuggestionType.COUNTRY }),
).resolves.toEqual(['USA']);
@@ -65,6 +67,8 @@ describe(SearchService.name, () => {
it('should return search suggestions for country (including null)', async () => {
mocks.search.getCountries.mockResolvedValue(['USA']);
mocks.partner.getAll.mockResolvedValue([]);
await expect(
sut.getSearchSuggestions(authStub.user1, { includeNull: true, type: SearchSuggestionType.COUNTRY }),
).resolves.toEqual(['USA', null]);
@@ -73,6 +77,8 @@ describe(SearchService.name, () => {
it('should return search suggestions for state', async () => {
mocks.search.getStates.mockResolvedValue(['California']);
mocks.partner.getAll.mockResolvedValue([]);
await expect(
sut.getSearchSuggestions(authStub.user1, { includeNull: false, type: SearchSuggestionType.STATE }),
).resolves.toEqual(['California']);
@@ -81,6 +87,8 @@ describe(SearchService.name, () => {
it('should return search suggestions for state (including null)', async () => {
mocks.search.getStates.mockResolvedValue(['California']);
mocks.partner.getAll.mockResolvedValue([]);
await expect(
sut.getSearchSuggestions(authStub.user1, { includeNull: true, type: SearchSuggestionType.STATE }),
).resolves.toEqual(['California', null]);
@@ -89,6 +97,8 @@ describe(SearchService.name, () => {
it('should return search suggestions for city', async () => {
mocks.search.getCities.mockResolvedValue(['Denver']);
mocks.partner.getAll.mockResolvedValue([]);
await expect(
sut.getSearchSuggestions(authStub.user1, { includeNull: false, type: SearchSuggestionType.CITY }),
).resolves.toEqual(['Denver']);
@@ -97,6 +107,8 @@ describe(SearchService.name, () => {
it('should return search suggestions for city (including null)', async () => {
mocks.search.getCities.mockResolvedValue(['Denver']);
mocks.partner.getAll.mockResolvedValue([]);
await expect(
sut.getSearchSuggestions(authStub.user1, { includeNull: true, type: SearchSuggestionType.CITY }),
).resolves.toEqual(['Denver', null]);
@@ -105,6 +117,8 @@ describe(SearchService.name, () => {
it('should return search suggestions for camera make', async () => {
mocks.search.getCameraMakes.mockResolvedValue(['Nikon']);
mocks.partner.getAll.mockResolvedValue([]);
await expect(
sut.getSearchSuggestions(authStub.user1, { includeNull: false, type: SearchSuggestionType.CAMERA_MAKE }),
).resolves.toEqual(['Nikon']);
@@ -113,6 +127,8 @@ describe(SearchService.name, () => {
it('should return search suggestions for camera make (including null)', async () => {
mocks.search.getCameraMakes.mockResolvedValue(['Nikon']);
mocks.partner.getAll.mockResolvedValue([]);
await expect(
sut.getSearchSuggestions(authStub.user1, { includeNull: true, type: SearchSuggestionType.CAMERA_MAKE }),
).resolves.toEqual(['Nikon', null]);
@@ -121,6 +137,8 @@ describe(SearchService.name, () => {
it('should return search suggestions for camera model', async () => {
mocks.search.getCameraModels.mockResolvedValue(['Fujifilm X100VI']);
mocks.partner.getAll.mockResolvedValue([]);
await expect(
sut.getSearchSuggestions(authStub.user1, { includeNull: false, type: SearchSuggestionType.CAMERA_MODEL }),
).resolves.toEqual(['Fujifilm X100VI']);
@@ -129,6 +147,8 @@ describe(SearchService.name, () => {
it('should return search suggestions for camera model (including null)', async () => {
mocks.search.getCameraModels.mockResolvedValue(['Fujifilm X100VI']);
mocks.partner.getAll.mockResolvedValue([]);
await expect(
sut.getSearchSuggestions(authStub.user1, { includeNull: true, type: SearchSuggestionType.CAMERA_MODEL }),
).resolves.toEqual(['Fujifilm X100VI', null]);

View File

@@ -36,6 +36,7 @@ describe('SessionService', () => {
updateId: 'uuid-v7',
},
]);
mocks.session.delete.mockResolvedValue();
await expect(sut.handleCleanup()).resolves.toEqual(JobStatus.SUCCESS);
expect(mocks.session.delete).toHaveBeenCalledWith('123');
@@ -71,6 +72,7 @@ describe('SessionService', () => {
describe('logoutDevices', () => {
it('should logout all devices', async () => {
mocks.session.getByUserId.mockResolvedValue([sessionStub.inactive, sessionStub.valid] as any[]);
mocks.session.delete.mockResolvedValue();
await sut.deleteAll(authStub.user1);
@@ -83,6 +85,7 @@ describe('SessionService', () => {
describe('logoutDevice', () => {
it('should logout the device', async () => {
mocks.access.authDevice.checkOwnerAccess.mockResolvedValue(new Set(['token-1']));
mocks.session.delete.mockResolvedValue();
await sut.delete(authStub.user1, 'token-1');

View File

@@ -71,7 +71,10 @@ describe(SharedLinkService.name, () => {
describe('get', () => {
it('should throw an error for an invalid shared link', async () => {
mocks.sharedLink.get.mockResolvedValue(void 0);
await expect(sut.get(authStub.user1, 'missing-id')).rejects.toBeInstanceOf(BadRequestException);
expect(mocks.sharedLink.get).toHaveBeenCalledWith(authStub.user1.user.id, 'missing-id');
expect(mocks.sharedLink.update).not.toHaveBeenCalled();
});
@@ -194,7 +197,10 @@ describe(SharedLinkService.name, () => {
describe('update', () => {
it('should throw an error for an invalid shared link', async () => {
mocks.sharedLink.get.mockResolvedValue(void 0);
await expect(sut.update(authStub.user1, 'missing-id', {})).rejects.toBeInstanceOf(BadRequestException);
expect(mocks.sharedLink.get).toHaveBeenCalledWith(authStub.user1.user.id, 'missing-id');
expect(mocks.sharedLink.update).not.toHaveBeenCalled();
});
@@ -214,14 +220,20 @@ describe(SharedLinkService.name, () => {
describe('remove', () => {
it('should throw an error for an invalid shared link', async () => {
mocks.sharedLink.get.mockResolvedValue(void 0);
await expect(sut.remove(authStub.user1, 'missing-id')).rejects.toBeInstanceOf(BadRequestException);
expect(mocks.sharedLink.get).toHaveBeenCalledWith(authStub.user1.user.id, 'missing-id');
expect(mocks.sharedLink.update).not.toHaveBeenCalled();
});
it('should remove a key', async () => {
mocks.sharedLink.get.mockResolvedValue(sharedLinkStub.valid);
mocks.sharedLink.remove.mockResolvedValue();
await sut.remove(authStub.user1, sharedLinkStub.valid.id);
expect(mocks.sharedLink.get).toHaveBeenCalledWith(authStub.user1.user.id, sharedLinkStub.valid.id);
expect(mocks.sharedLink.remove).toHaveBeenCalledWith(sharedLinkStub.valid);
});
@@ -238,6 +250,7 @@ describe(SharedLinkService.name, () => {
it('should add assets to a shared link', async () => {
mocks.sharedLink.get.mockResolvedValue(_.cloneDeep(sharedLinkStub.individual));
mocks.sharedLink.create.mockResolvedValue(sharedLinkStub.individual);
mocks.sharedLink.update.mockResolvedValue(sharedLinkStub.individual);
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-3']));
await expect(
@@ -268,6 +281,7 @@ describe(SharedLinkService.name, () => {
it('should remove assets from a shared link', async () => {
mocks.sharedLink.get.mockResolvedValue(_.cloneDeep(sharedLinkStub.individual));
mocks.sharedLink.create.mockResolvedValue(sharedLinkStub.individual);
mocks.sharedLink.update.mockResolvedValue(sharedLinkStub.individual);
await expect(
sut.removeAssets(authStub.admin, 'link-1', { assetIds: [assetStub.image.id, 'asset-2'] }),

View File

@@ -155,6 +155,7 @@ describe(StackService.name, () => {
it('should delete stack', async () => {
mocks.access.stack.checkOwnerAccess.mockResolvedValue(new Set(['stack-id']));
mocks.stack.delete.mockResolvedValue();
await sut.delete(authStub.admin, 'stack-id');
@@ -176,6 +177,7 @@ describe(StackService.name, () => {
it('should delete all stacks', async () => {
mocks.access.stack.checkOwnerAccess.mockResolvedValue(new Set(['stack-id']));
mocks.stack.deleteAll.mockResolvedValue();
await sut.deleteAll(authStub.admin, { ids: ['stack-id'] });

View File

@@ -93,7 +93,9 @@ describe(StorageTemplateService.name, () => {
describe('handleMigrationSingle', () => {
it('should skip when storage template is disabled', async () => {
mocks.systemMetadata.get.mockResolvedValue({ storageTemplate: { enabled: false } });
await expect(sut.handleMigrationSingle({ id: testAsset.id })).resolves.toBe(JobStatus.SKIPPED);
expect(mocks.asset.getByIds).not.toHaveBeenCalled();
expect(mocks.storage.checkFileExists).not.toHaveBeenCalled();
expect(mocks.storage.rename).not.toHaveBeenCalled();

View File

@@ -87,9 +87,12 @@ describe(TagService.name, () => {
it('should create a new tag with optional color', async () => {
mocks.tag.create.mockResolvedValue(tagStub.colorCreate);
mocks.tag.getByValue.mockResolvedValue(void 0);
await expect(sut.create(authStub.admin, { name: 'tag-1', color: '#000000' })).resolves.toEqual(
tagResponseStub.color1,
);
expect(mocks.tag.create).toHaveBeenCalledWith({
userId: authStub.admin.user.id,
value: 'tag-1',
@@ -168,6 +171,8 @@ describe(TagService.name, () => {
it('should remove a tag', async () => {
mocks.tag.get.mockResolvedValue(tagStub.tag);
mocks.tag.delete.mockResolvedValue();
await sut.remove(authStub.admin, 'tag-1');
expect(mocks.tag.delete).toHaveBeenCalledWith('tag-1');
});
@@ -223,6 +228,7 @@ describe(TagService.name, () => {
it('should accept accept ids that are new and reject the rest', async () => {
mocks.tag.get.mockResolvedValue(tagStub.tag);
mocks.tag.getAssetIds.mockResolvedValue(new Set(['asset-1']));
mocks.tag.addAssetIds.mockResolvedValue();
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-2']));
await expect(
@@ -242,6 +248,8 @@ describe(TagService.name, () => {
describe('removeAssets', () => {
it('should throw an error for an invalid id', async () => {
mocks.tag.getAssetIds.mockResolvedValue(new Set());
mocks.tag.removeAssetIds.mockResolvedValue();
await expect(sut.removeAssets(authStub.admin, 'tag-1', { ids: ['asset-1'] })).resolves.toEqual([
{ id: 'asset-1', success: false, error: 'not_found' },
]);
@@ -250,6 +258,7 @@ describe(TagService.name, () => {
it('should accept accept ids that are tagged and reject the rest', async () => {
mocks.tag.get.mockResolvedValue(tagStub.tag);
mocks.tag.getAssetIds.mockResolvedValue(new Set(['asset-1']));
mocks.tag.removeAssetIds.mockResolvedValue();
await expect(
sut.removeAssets(authStub.admin, 'tag-1', {
@@ -267,7 +276,10 @@ describe(TagService.name, () => {
describe('handleTagCleanup', () => {
it('should delete empty tags', async () => {
mocks.tag.deleteEmptyTags.mockResolvedValue();
await expect(sut.handleTagCleanup()).resolves.toBe(JobStatus.SUCCESS);
expect(mocks.tag.deleteEmptyTags).toHaveBeenCalled();
});
});

View File

@@ -70,6 +70,7 @@ describe(TimelineService.name, () => {
it('should include partner shared assets', async () => {
mocks.asset.getTimeBucket.mockResolvedValue([assetStub.image]);
mocks.partner.getAll.mockResolvedValue([]);
await expect(
sut.getTimeBucket(authStub.admin, {

View File

@@ -39,6 +39,7 @@ describe(TrashService.name, () => {
it('should restore a batch of assets', async () => {
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset1', 'asset2']));
mocks.trash.restoreAll.mockResolvedValue(0);
await sut.restoreAssets(authStub.user1, { ids: ['asset1', 'asset2'] });

View File

@@ -4,6 +4,7 @@ import { serverVersion } from 'src/constants';
import { ImmichEnvironment, JobName, JobStatus, SystemMetadataKey } from 'src/enum';
import { VersionService } from 'src/services/version.service';
import { mockEnvData } from 'test/repositories/config.repository.mock';
import { factory } from 'test/small.factory';
import { newTestService, ServiceMocks } from 'test/utils';
const mockRelease = (version: string) => ({
@@ -30,7 +31,12 @@ describe(VersionService.name, () => {
describe('onBootstrap', () => {
it('should record a new version', async () => {
mocks.versionHistory.getAll.mockResolvedValue([]);
mocks.versionHistory.getLatest.mockResolvedValue(void 0);
mocks.versionHistory.create.mockResolvedValue(factory.versionHistory());
await expect(sut.onBootstrap()).resolves.toBeUndefined();
expect(mocks.versionHistory.create).toHaveBeenCalledWith({ version: expect.any(String) });
});