mirror of
https://github.com/KevinMidboe/immich.git
synced 2025-12-07 19:59:07 +00:00
355 lines
12 KiB
TypeScript
355 lines
12 KiB
TypeScript
import { AlbumService } from './album.service';
|
|
import { AuthUserDto } from '../../decorators/auth-user.decorator';
|
|
import { BadRequestException, NotFoundException, ForbiddenException } from '@nestjs/common';
|
|
import { AlbumEntity, UserEntity } from '@app/infra/entities';
|
|
import { AlbumResponseDto, ICryptoRepository, IJobRepository, mapUser } from '@app/domain';
|
|
import { AddAssetsResponseDto } from './response-dto/add-assets-response.dto';
|
|
import { IAlbumRepository } from './album-repository';
|
|
import { DownloadService } from '../../modules/download/download.service';
|
|
import { ISharedLinkRepository } from '@app/domain';
|
|
import {
|
|
newCryptoRepositoryMock,
|
|
newJobRepositoryMock,
|
|
newSharedLinkRepositoryMock,
|
|
userEntityStub,
|
|
} from '@app/domain/../test';
|
|
|
|
describe('Album service', () => {
|
|
let sut: AlbumService;
|
|
let albumRepositoryMock: jest.Mocked<IAlbumRepository>;
|
|
let sharedLinkRepositoryMock: jest.Mocked<ISharedLinkRepository>;
|
|
let downloadServiceMock: jest.Mocked<Partial<DownloadService>>;
|
|
let cryptoMock: jest.Mocked<ICryptoRepository>;
|
|
let jobMock: jest.Mocked<IJobRepository>;
|
|
|
|
const authUser: AuthUserDto = Object.freeze({
|
|
id: '1111',
|
|
email: 'auth@test.com',
|
|
isAdmin: false,
|
|
});
|
|
|
|
const albumOwner: UserEntity = Object.freeze({
|
|
...authUser,
|
|
firstName: 'auth',
|
|
lastName: 'user',
|
|
createdAt: 'date',
|
|
updatedAt: 'date',
|
|
profileImagePath: '',
|
|
shouldChangePassword: false,
|
|
oauthId: '',
|
|
tags: [],
|
|
assets: [],
|
|
storageLabel: null,
|
|
});
|
|
const albumId = 'f19ab956-4761-41ea-a5d6-bae948308d58';
|
|
const sharedAlbumOwnerId = '2222';
|
|
const sharedAlbumSharedAlsoWithId = '3333';
|
|
const ownedAlbumSharedWithId = '4444';
|
|
|
|
const _getOwnedAlbum = () => {
|
|
const albumEntity = new AlbumEntity();
|
|
albumEntity.ownerId = albumOwner.id;
|
|
albumEntity.owner = albumOwner;
|
|
albumEntity.id = albumId;
|
|
albumEntity.albumName = 'name';
|
|
albumEntity.createdAt = 'date';
|
|
albumEntity.updatedAt = 'date';
|
|
albumEntity.sharedUsers = [];
|
|
albumEntity.assets = [];
|
|
albumEntity.albumThumbnailAssetId = null;
|
|
albumEntity.sharedLinks = [];
|
|
return albumEntity;
|
|
};
|
|
|
|
const _getOwnedSharedAlbum = () => {
|
|
const albumEntity = new AlbumEntity();
|
|
albumEntity.ownerId = albumOwner.id;
|
|
albumEntity.owner = albumOwner;
|
|
albumEntity.id = albumId;
|
|
albumEntity.albumName = 'name';
|
|
albumEntity.createdAt = 'date';
|
|
albumEntity.assets = [];
|
|
albumEntity.albumThumbnailAssetId = null;
|
|
albumEntity.sharedUsers = [
|
|
{
|
|
...userEntityStub.user1,
|
|
id: ownedAlbumSharedWithId,
|
|
},
|
|
];
|
|
|
|
return albumEntity;
|
|
};
|
|
|
|
const _getSharedWithAuthUserAlbum = () => {
|
|
const albumEntity = new AlbumEntity();
|
|
albumEntity.ownerId = sharedAlbumOwnerId;
|
|
albumEntity.owner = albumOwner;
|
|
albumEntity.id = albumId;
|
|
albumEntity.albumName = 'name';
|
|
albumEntity.createdAt = 'date';
|
|
albumEntity.assets = [];
|
|
albumEntity.albumThumbnailAssetId = null;
|
|
albumEntity.sharedUsers = [
|
|
{
|
|
...userEntityStub.user1,
|
|
id: authUser.id,
|
|
},
|
|
{
|
|
...userEntityStub.user1,
|
|
id: sharedAlbumSharedAlsoWithId,
|
|
},
|
|
];
|
|
albumEntity.sharedLinks = [];
|
|
|
|
return albumEntity;
|
|
};
|
|
|
|
const _getNotOwnedNotSharedAlbum = () => {
|
|
const albumEntity = new AlbumEntity();
|
|
albumEntity.ownerId = '5555';
|
|
albumEntity.id = albumId;
|
|
albumEntity.albumName = 'name';
|
|
albumEntity.createdAt = 'date';
|
|
albumEntity.sharedUsers = [];
|
|
albumEntity.assets = [];
|
|
albumEntity.albumThumbnailAssetId = null;
|
|
|
|
return albumEntity;
|
|
};
|
|
|
|
beforeAll(() => {
|
|
albumRepositoryMock = {
|
|
addAssets: jest.fn(),
|
|
addSharedUsers: jest.fn(),
|
|
get: jest.fn(),
|
|
removeAssets: jest.fn(),
|
|
removeUser: jest.fn(),
|
|
updateThumbnails: jest.fn(),
|
|
getCountByUserId: jest.fn(),
|
|
getSharedWithUserAlbumCount: jest.fn(),
|
|
};
|
|
|
|
sharedLinkRepositoryMock = newSharedLinkRepositoryMock();
|
|
|
|
downloadServiceMock = {
|
|
downloadArchive: jest.fn(),
|
|
};
|
|
|
|
cryptoMock = newCryptoRepositoryMock();
|
|
jobMock = newJobRepositoryMock();
|
|
|
|
sut = new AlbumService(
|
|
albumRepositoryMock,
|
|
sharedLinkRepositoryMock,
|
|
downloadServiceMock as DownloadService,
|
|
cryptoMock,
|
|
jobMock,
|
|
);
|
|
});
|
|
|
|
it('gets an owned album', async () => {
|
|
const albumId = 'f19ab956-4761-41ea-a5d6-bae948308d58';
|
|
|
|
const albumEntity = _getOwnedAlbum();
|
|
albumRepositoryMock.get.mockImplementation(() => Promise.resolve<AlbumEntity>(albumEntity));
|
|
|
|
const expectedResult: AlbumResponseDto = {
|
|
ownerId: albumOwner.id,
|
|
owner: mapUser(albumOwner),
|
|
id: albumId,
|
|
albumName: 'name',
|
|
createdAt: 'date',
|
|
updatedAt: 'date',
|
|
sharedUsers: [],
|
|
assets: [],
|
|
albumThumbnailAssetId: null,
|
|
shared: false,
|
|
assetCount: 0,
|
|
};
|
|
await expect(sut.get(authUser, albumId)).resolves.toEqual(expectedResult);
|
|
});
|
|
|
|
it('gets a shared album', async () => {
|
|
const albumEntity = _getSharedWithAuthUserAlbum();
|
|
albumRepositoryMock.get.mockImplementation(() => Promise.resolve<AlbumEntity>(albumEntity));
|
|
|
|
const result = await sut.get(authUser, albumId);
|
|
expect(result.id).toEqual(albumId);
|
|
expect(result.ownerId).toEqual(sharedAlbumOwnerId);
|
|
expect(result.shared).toEqual(true);
|
|
expect(result.sharedUsers).toHaveLength(2);
|
|
expect(result.sharedUsers[0].id).toEqual(authUser.id);
|
|
expect(result.sharedUsers[1].id).toEqual(sharedAlbumSharedAlsoWithId);
|
|
});
|
|
|
|
it('prevents retrieving an album that is not owned or shared', async () => {
|
|
const albumEntity = _getNotOwnedNotSharedAlbum();
|
|
const albumId = albumEntity.id;
|
|
|
|
albumRepositoryMock.get.mockImplementation(() => Promise.resolve<AlbumEntity>(albumEntity));
|
|
await expect(sut.get(authUser, albumId)).rejects.toBeInstanceOf(ForbiddenException);
|
|
});
|
|
|
|
it('throws a not found exception if the album is not found', async () => {
|
|
albumRepositoryMock.get.mockImplementation(() => Promise.resolve(null));
|
|
await expect(sut.get(authUser, '0002')).rejects.toBeInstanceOf(NotFoundException);
|
|
});
|
|
|
|
it('removes a shared user from an owned album', async () => {
|
|
const albumEntity = _getOwnedSharedAlbum();
|
|
albumRepositoryMock.get.mockImplementation(() => Promise.resolve<AlbumEntity>(albumEntity));
|
|
albumRepositoryMock.removeUser.mockImplementation(() => Promise.resolve());
|
|
await expect(sut.removeUser(authUser, albumEntity.id, ownedAlbumSharedWithId)).resolves.toBeUndefined();
|
|
expect(albumRepositoryMock.removeUser).toHaveBeenCalledTimes(1);
|
|
expect(albumRepositoryMock.removeUser).toHaveBeenCalledWith(albumEntity, ownedAlbumSharedWithId);
|
|
});
|
|
|
|
it('prevents removing a shared user from a not owned album (shared with auth user)', async () => {
|
|
const albumEntity = _getSharedWithAuthUserAlbum();
|
|
const albumId = albumEntity.id;
|
|
const userIdToRemove = sharedAlbumSharedAlsoWithId;
|
|
|
|
albumRepositoryMock.get.mockImplementation(() => Promise.resolve<AlbumEntity>(albumEntity));
|
|
|
|
await expect(sut.removeUser(authUser, albumId, userIdToRemove)).rejects.toBeInstanceOf(ForbiddenException);
|
|
expect(albumRepositoryMock.removeUser).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('removes itself from a shared album', async () => {
|
|
const albumEntity = _getSharedWithAuthUserAlbum();
|
|
albumRepositoryMock.get.mockImplementation(() => Promise.resolve<AlbumEntity>(albumEntity));
|
|
albumRepositoryMock.removeUser.mockImplementation(() => Promise.resolve());
|
|
|
|
await sut.removeUser(authUser, albumEntity.id, authUser.id);
|
|
expect(albumRepositoryMock.removeUser).toHaveReturnedTimes(1);
|
|
expect(albumRepositoryMock.removeUser).toHaveBeenCalledWith(albumEntity, authUser.id);
|
|
});
|
|
|
|
it('removes itself from a shared album using "me" as id', async () => {
|
|
const albumEntity = _getSharedWithAuthUserAlbum();
|
|
albumRepositoryMock.get.mockImplementation(() => Promise.resolve<AlbumEntity>(albumEntity));
|
|
albumRepositoryMock.removeUser.mockImplementation(() => Promise.resolve());
|
|
|
|
await sut.removeUser(authUser, albumEntity.id, 'me');
|
|
expect(albumRepositoryMock.removeUser).toHaveReturnedTimes(1);
|
|
expect(albumRepositoryMock.removeUser).toHaveBeenCalledWith(albumEntity, authUser.id);
|
|
});
|
|
|
|
it('prevents removing itself from a owned album', async () => {
|
|
const albumEntity = _getOwnedAlbum();
|
|
albumRepositoryMock.get.mockImplementation(() => Promise.resolve<AlbumEntity>(albumEntity));
|
|
|
|
await expect(sut.removeUser(authUser, albumEntity.id, authUser.id)).rejects.toBeInstanceOf(BadRequestException);
|
|
});
|
|
|
|
it('adds assets to owned album', async () => {
|
|
const albumEntity = _getOwnedAlbum();
|
|
|
|
const albumResponse: AddAssetsResponseDto = {
|
|
alreadyInAlbum: [],
|
|
successfullyAdded: 1,
|
|
};
|
|
|
|
const albumId = albumEntity.id;
|
|
|
|
albumRepositoryMock.get.mockImplementation(() => Promise.resolve<AlbumEntity>(albumEntity));
|
|
albumRepositoryMock.addAssets.mockImplementation(() => Promise.resolve<AddAssetsResponseDto>(albumResponse));
|
|
|
|
const result = (await sut.addAssets(authUser, albumId, { assetIds: ['1'] })) as AddAssetsResponseDto;
|
|
|
|
// TODO: stub and expect album rendered
|
|
expect(result.album?.id).toEqual(albumId);
|
|
});
|
|
|
|
it('adds assets to shared album (shared with auth user)', async () => {
|
|
const albumEntity = _getSharedWithAuthUserAlbum();
|
|
|
|
const albumResponse: AddAssetsResponseDto = {
|
|
alreadyInAlbum: [],
|
|
successfullyAdded: 1,
|
|
};
|
|
|
|
const albumId = albumEntity.id;
|
|
|
|
albumRepositoryMock.get.mockImplementation(() => Promise.resolve<AlbumEntity>(albumEntity));
|
|
albumRepositoryMock.addAssets.mockImplementation(() => Promise.resolve<AddAssetsResponseDto>(albumResponse));
|
|
|
|
const result = (await sut.addAssets(authUser, albumId, { assetIds: ['1'] })) as AddAssetsResponseDto;
|
|
|
|
// TODO: stub and expect album rendered
|
|
expect(result.album?.id).toEqual(albumId);
|
|
});
|
|
|
|
it('prevents adding assets to a not owned / shared album', async () => {
|
|
const albumEntity = _getNotOwnedNotSharedAlbum();
|
|
|
|
const albumResponse: AddAssetsResponseDto = {
|
|
alreadyInAlbum: [],
|
|
successfullyAdded: 1,
|
|
};
|
|
|
|
const albumId = albumEntity.id;
|
|
|
|
albumRepositoryMock.get.mockImplementation(() => Promise.resolve<AlbumEntity>(albumEntity));
|
|
albumRepositoryMock.addAssets.mockImplementation(() => Promise.resolve<AddAssetsResponseDto>(albumResponse));
|
|
|
|
await expect(sut.addAssets(authUser, albumId, { assetIds: ['1'] })).rejects.toBeInstanceOf(ForbiddenException);
|
|
});
|
|
|
|
// it('removes assets from owned album', async () => {
|
|
// const albumEntity = _getOwnedAlbum();
|
|
// albumRepositoryMock.get.mockImplementation(() => Promise.resolve<AlbumEntity>(albumEntity));
|
|
// albumRepositoryMock.removeAssets.mockImplementation(() => Promise.resolve<AlbumEntity>(albumEntity));
|
|
|
|
// await expect(
|
|
// sut.removeAssetsFromAlbum(
|
|
// authUser,
|
|
// {
|
|
// assetIds: ['f19ab956-4761-41ea-a5d6-bae948308d60'],
|
|
// },
|
|
// albumEntity.id,
|
|
// ),
|
|
// ).resolves.toBeUndefined();
|
|
// expect(albumRepositoryMock.removeAssets).toHaveBeenCalledTimes(1);
|
|
// expect(albumRepositoryMock.removeAssets).toHaveBeenCalledWith(albumEntity, {
|
|
// assetIds: ['f19ab956-4761-41ea-a5d6-bae948308d60'],
|
|
// });
|
|
// });
|
|
|
|
// it('removes assets from shared album (shared with auth user)', async () => {
|
|
// const albumEntity = _getOwnedSharedAlbum();
|
|
// albumRepositoryMock.get.mockImplementation(() => Promise.resolve<AlbumEntity>(albumEntity));
|
|
// albumRepositoryMock.removeAssets.mockImplementation(() => Promise.resolve<AlbumEntity>(albumEntity));
|
|
|
|
// await expect(
|
|
// sut.removeAssetsFromAlbum(
|
|
// authUser,
|
|
// {
|
|
// assetIds: ['1'],
|
|
// },
|
|
// albumEntity.id,
|
|
// ),
|
|
// ).resolves.toBeUndefined();
|
|
// expect(albumRepositoryMock.removeAssets).toHaveBeenCalledTimes(1);
|
|
// expect(albumRepositoryMock.removeAssets).toHaveBeenCalledWith(albumEntity, {
|
|
// assetIds: ['1'],
|
|
// });
|
|
// });
|
|
|
|
it('prevents removing assets from a not owned / shared album', async () => {
|
|
const albumEntity = _getNotOwnedNotSharedAlbum();
|
|
|
|
const albumResponse: AddAssetsResponseDto = {
|
|
alreadyInAlbum: [],
|
|
successfullyAdded: 1,
|
|
};
|
|
|
|
const albumId = albumEntity.id;
|
|
|
|
albumRepositoryMock.get.mockImplementation(() => Promise.resolve<AlbumEntity>(albumEntity));
|
|
albumRepositoryMock.addAssets.mockImplementation(() => Promise.resolve<AddAssetsResponseDto>(albumResponse));
|
|
|
|
await expect(sut.removeAssets(authUser, albumId, { assetIds: ['1'] })).rejects.toBeInstanceOf(ForbiddenException);
|
|
});
|
|
});
|