chore(server): Improve test coverage! (#3889)

* tests for person service

* tests for auth service

* tests for access core

* improve tests for album service

* fix missing brackets and remove comments

* tests for asset service

* tests for face recognition

* tests for job service

* feedback

* tests for search service (broken)

* fix: disabled search test

* tests for smart-info service

* tests for storage template service

* tests for user service

* fix formatting of untouched files LOL

* attempt to fix formatting

* streamline api utils, add asset api for uploading files

* test upload of assets

* fix formatting

* move test-utils to correct folder

* test add assets to album

* use random bytes instead of test image

* (e2e) test albums with assets

* (e2e) complete tests for album endpoints

* (e2e) tests for asset endpoint

* fix: asset upload/import dto validation

* (e2e) tests for statistics asset endpoint

* fix wrong describe text

* (e2e) tests for people with faces

* (e2e) clean up person tests

* (e2e) tests for partner sharing endpoints

* (e2e) tests for link sharing

* (e2e) tests for the asset time bucket endpoint

* fix minor issues

* remove access.core.spec.ts

* chore: wording

* chore: organize test api files

* chore: fix test describe

* implement feedback

* fix race condition in album tests

---------

Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
This commit is contained in:
Daniel Dietzler
2023-09-11 17:56:38 +02:00
committed by GitHub
parent afccb37a3b
commit 7173af60e4
32 changed files with 1635 additions and 291 deletions

View File

@@ -1,5 +1,10 @@
import { UserEntity } from '@app/infra/entities';
import { BadRequestException, ForbiddenException, NotFoundException } from '@nestjs/common';
import {
BadRequestException,
ForbiddenException,
InternalServerErrorException,
NotFoundException,
} from '@nestjs/common';
import {
newAlbumRepositoryMock,
newAssetRepositoryMock,
@@ -7,6 +12,7 @@ import {
newJobRepositoryMock,
newStorageRepositoryMock,
newUserRepositoryMock,
userStub,
} from '@test';
import { when } from 'jest-when';
import { IAlbumRepository } from '../album';
@@ -16,7 +22,7 @@ import { ICryptoRepository } from '../crypto';
import { IJobRepository, JobName } from '../job';
import { IStorageRepository } from '../storage';
import { UpdateUserDto } from './dto/update-user.dto';
import { UserResponseDto } from './response-dto';
import { UserResponseDto, mapUser } from './response-dto';
import { IUserRepository } from './user.repository';
import { UserService } from './user.service';
@@ -216,6 +222,13 @@ describe(UserService.name, () => {
expect(userMock.getList).toHaveBeenCalled();
expect(response).toEqual({ userCount: 1 });
});
it('should get the user count of all admin users', async () => {
userMock.getList.mockResolvedValue([adminUser, immichUser]);
await expect(sut.getCount({ admin: true })).resolves.toEqual({ userCount: 1 });
expect(userMock.getList).toHaveBeenCalled();
});
});
describe('update', () => {
@@ -223,12 +236,17 @@ describe(UserService.name, () => {
const update: UpdateUserDto = {
id: immichUser.id,
shouldChangePassword: true,
email: 'immich@test.com',
storageLabel: 'storage_label',
};
userMock.getByEmail.mockResolvedValue(null);
userMock.getByStorageLabel.mockResolvedValue(null);
userMock.update.mockResolvedValue({ ...updatedImmichUser, isAdmin: true, storageLabel: 'storage_label' });
when(userMock.update).calledWith(update.id, update).mockResolvedValueOnce(updatedImmichUser);
const updatedUser = await sut.update(immichUserAuth, update);
const updatedUser = await sut.update({ ...immichUserAuth, isAdmin: true }, update);
expect(updatedUser.shouldChangePassword).toEqual(true);
expect(userMock.getByEmail).toHaveBeenCalledWith(update.email);
expect(userMock.getByStorageLabel).toHaveBeenCalledWith(update.storageLabel);
});
it('should not set an empty string for storage label', async () => {
@@ -345,20 +363,37 @@ describe(UserService.name, () => {
});
describe('restore', () => {
it('should throw error if user could not be found', async () => {
userMock.get.mockResolvedValue(null);
await expect(sut.restore(immichUserAuth, adminUser.id)).rejects.toThrowError(BadRequestException);
expect(userMock.restore).not.toHaveBeenCalled();
});
it('should require an admin', async () => {
when(userMock.get).calledWith(adminUser.id, true).mockResolvedValue(adminUser);
await expect(sut.restore(immichUserAuth, adminUser.id)).rejects.toBeInstanceOf(ForbiddenException);
expect(userMock.get).toHaveBeenCalledWith(adminUser.id, true);
});
it('should require the auth user be an admin', async () => {
await expect(sut.delete(immichUserAuth, adminUserAuth.id)).rejects.toBeInstanceOf(ForbiddenException);
it('should restore an user', async () => {
userMock.get.mockResolvedValue(immichUser);
userMock.restore.mockResolvedValue(immichUser);
expect(userMock.delete).not.toHaveBeenCalled();
await expect(sut.restore(adminUserAuth, immichUser.id)).resolves.toEqual(mapUser(immichUser));
expect(userMock.get).toHaveBeenCalledWith(immichUser.id, true);
expect(userMock.restore).toHaveBeenCalledWith(immichUser);
});
});
describe('delete', () => {
it('should throw error if user could not be found', async () => {
userMock.get.mockResolvedValue(null);
await expect(sut.delete(immichUserAuth, adminUser.id)).rejects.toThrowError(BadRequestException);
expect(userMock.delete).not.toHaveBeenCalled();
});
it('cannot delete admin user', async () => {
await expect(sut.delete(adminUserAuth, adminUserAuth.id)).rejects.toBeInstanceOf(ForbiddenException);
});
@@ -368,9 +403,18 @@ describe(UserService.name, () => {
expect(userMock.delete).not.toHaveBeenCalled();
});
it('should delete user', async () => {
userMock.get.mockResolvedValue(immichUser);
userMock.delete.mockResolvedValue(immichUser);
await expect(sut.delete(adminUserAuth, immichUser.id)).resolves.toEqual(mapUser(immichUser));
expect(userMock.get).toHaveBeenCalledWith(immichUser.id, undefined);
expect(userMock.delete).toHaveBeenCalledWith(immichUser);
});
});
describe('update', () => {
describe('create', () => {
it('should not create a user if there is no local admin account', async () => {
when(userMock.getAdmin).calledWith().mockResolvedValueOnce(null);
@@ -383,6 +427,30 @@ describe(UserService.name, () => {
}),
).rejects.toBeInstanceOf(BadRequestException);
});
it('should create user', async () => {
userMock.getAdmin.mockResolvedValue(userStub.admin);
userMock.create.mockResolvedValue(userStub.user1);
await expect(
sut.create({
email: userStub.user1.email,
firstName: userStub.user1.firstName,
lastName: userStub.user1.lastName,
password: 'password',
storageLabel: 'label',
}),
).resolves.toEqual(mapUser(userStub.user1));
expect(userMock.getAdmin).toBeCalled();
expect(userMock.create).toBeCalledWith({
email: userStub.user1.email,
firstName: userStub.user1.firstName,
lastName: userStub.user1.lastName,
storageLabel: 'label',
password: expect.anything(),
});
});
});
describe('createProfileImage', () => {
@@ -394,6 +462,13 @@ describe(UserService.name, () => {
expect(userMock.update).toHaveBeenCalledWith(adminUserAuth.id, { profileImagePath: file.path });
});
it('should throw an error if the user profile could not be updated with the new image', async () => {
const file = { path: '/profile/path' } as Express.Multer.File;
userMock.update.mockRejectedValue(new InternalServerErrorException('mocked error'));
await expect(sut.createProfileImage(adminUserAuth, file)).rejects.toThrowError(InternalServerErrorException);
});
});
describe('getUserProfileImage', () => {