refactor: service dependencies (#13108)

refactor(server): simplify service dependency management
This commit is contained in:
Jason Rasmussen
2024-10-02 10:54:35 -04:00
committed by GitHub
parent 1b7e4b4e52
commit 4ea281f854
77 changed files with 802 additions and 1862 deletions

View File

@@ -3,42 +3,24 @@ import _ from 'lodash';
import { DEFAULT_EXTERNAL_DOMAIN } from 'src/constants';
import { AssetIdErrorReason } from 'src/dtos/asset-ids.response.dto';
import { SharedLinkType } from 'src/enum';
import { IConfigRepository } from 'src/interfaces/config.interface';
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
import { ILoggerRepository } from 'src/interfaces/logger.interface';
import { ISharedLinkRepository } from 'src/interfaces/shared-link.interface';
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
import { SharedLinkService } from 'src/services/shared-link.service';
import { albumStub } from 'test/fixtures/album.stub';
import { assetStub } from 'test/fixtures/asset.stub';
import { authStub } from 'test/fixtures/auth.stub';
import { sharedLinkResponseStub, sharedLinkStub } from 'test/fixtures/shared-link.stub';
import { IAccessRepositoryMock, newAccessRepositoryMock } from 'test/repositories/access.repository.mock';
import { newConfigRepositoryMock } from 'test/repositories/config.repository.mock';
import { newCryptoRepositoryMock } from 'test/repositories/crypto.repository.mock';
import { newLoggerRepositoryMock } from 'test/repositories/logger.repository.mock';
import { newSharedLinkRepositoryMock } from 'test/repositories/shared-link.repository.mock';
import { newSystemMetadataRepositoryMock } from 'test/repositories/system-metadata.repository.mock';
import { IAccessRepositoryMock } from 'test/repositories/access.repository.mock';
import { newTestService } from 'test/utils';
import { Mocked } from 'vitest';
describe(SharedLinkService.name, () => {
let sut: SharedLinkService;
let accessMock: IAccessRepositoryMock;
let configMock: Mocked<IConfigRepository>;
let cryptoMock: Mocked<ICryptoRepository>;
let shareMock: Mocked<ISharedLinkRepository>;
let systemMock: Mocked<ISystemMetadataRepository>;
let logMock: Mocked<ILoggerRepository>;
let sharedLinkMock: Mocked<ISharedLinkRepository>;
beforeEach(() => {
accessMock = newAccessRepositoryMock();
configMock = newConfigRepositoryMock();
cryptoMock = newCryptoRepositoryMock();
shareMock = newSharedLinkRepositoryMock();
systemMock = newSystemMetadataRepositoryMock();
logMock = newLoggerRepositoryMock();
sut = new SharedLinkService(accessMock, configMock, cryptoMock, logMock, shareMock, systemMock);
({ sut, accessMock, sharedLinkMock } = newTestService(SharedLinkService));
});
it('should work', () => {
@@ -47,55 +29,55 @@ describe(SharedLinkService.name, () => {
describe('getAll', () => {
it('should return all shared links for a user', async () => {
shareMock.getAll.mockResolvedValue([sharedLinkStub.expired, sharedLinkStub.valid]);
sharedLinkMock.getAll.mockResolvedValue([sharedLinkStub.expired, sharedLinkStub.valid]);
await expect(sut.getAll(authStub.user1)).resolves.toEqual([
sharedLinkResponseStub.expired,
sharedLinkResponseStub.valid,
]);
expect(shareMock.getAll).toHaveBeenCalledWith(authStub.user1.user.id);
expect(sharedLinkMock.getAll).toHaveBeenCalledWith(authStub.user1.user.id);
});
});
describe('getMine', () => {
it('should only work for a public user', async () => {
await expect(sut.getMine(authStub.admin, {})).rejects.toBeInstanceOf(ForbiddenException);
expect(shareMock.get).not.toHaveBeenCalled();
expect(sharedLinkMock.get).not.toHaveBeenCalled();
});
it('should return the shared link for the public user', async () => {
const authDto = authStub.adminSharedLink;
shareMock.get.mockResolvedValue(sharedLinkStub.valid);
sharedLinkMock.get.mockResolvedValue(sharedLinkStub.valid);
await expect(sut.getMine(authDto, {})).resolves.toEqual(sharedLinkResponseStub.valid);
expect(shareMock.get).toHaveBeenCalledWith(authDto.user.id, authDto.sharedLink?.id);
expect(sharedLinkMock.get).toHaveBeenCalledWith(authDto.user.id, authDto.sharedLink?.id);
});
it('should not return metadata', async () => {
const authDto = authStub.adminSharedLinkNoExif;
shareMock.get.mockResolvedValue(sharedLinkStub.readonlyNoExif);
sharedLinkMock.get.mockResolvedValue(sharedLinkStub.readonlyNoExif);
await expect(sut.getMine(authDto, {})).resolves.toEqual(sharedLinkResponseStub.readonlyNoMetadata);
expect(shareMock.get).toHaveBeenCalledWith(authDto.user.id, authDto.sharedLink?.id);
expect(sharedLinkMock.get).toHaveBeenCalledWith(authDto.user.id, authDto.sharedLink?.id);
});
it('should throw an error for an password protected shared link', async () => {
const authDto = authStub.adminSharedLink;
shareMock.get.mockResolvedValue(sharedLinkStub.passwordRequired);
sharedLinkMock.get.mockResolvedValue(sharedLinkStub.passwordRequired);
await expect(sut.getMine(authDto, {})).rejects.toBeInstanceOf(UnauthorizedException);
expect(shareMock.get).toHaveBeenCalledWith(authDto.user.id, authDto.sharedLink?.id);
expect(sharedLinkMock.get).toHaveBeenCalledWith(authDto.user.id, authDto.sharedLink?.id);
});
});
describe('get', () => {
it('should throw an error for an invalid shared link', async () => {
shareMock.get.mockResolvedValue(null);
sharedLinkMock.get.mockResolvedValue(null);
await expect(sut.get(authStub.user1, 'missing-id')).rejects.toBeInstanceOf(BadRequestException);
expect(shareMock.get).toHaveBeenCalledWith(authStub.user1.user.id, 'missing-id');
expect(shareMock.update).not.toHaveBeenCalled();
expect(sharedLinkMock.get).toHaveBeenCalledWith(authStub.user1.user.id, 'missing-id');
expect(sharedLinkMock.update).not.toHaveBeenCalled();
});
it('should get a shared link by id', async () => {
shareMock.get.mockResolvedValue(sharedLinkStub.valid);
sharedLinkMock.get.mockResolvedValue(sharedLinkStub.valid);
await expect(sut.get(authStub.user1, sharedLinkStub.valid.id)).resolves.toEqual(sharedLinkResponseStub.valid);
expect(shareMock.get).toHaveBeenCalledWith(authStub.user1.user.id, sharedLinkStub.valid.id);
expect(sharedLinkMock.get).toHaveBeenCalledWith(authStub.user1.user.id, sharedLinkStub.valid.id);
});
});
@@ -126,7 +108,7 @@ describe(SharedLinkService.name, () => {
it('should create an album shared link', async () => {
accessMock.album.checkOwnerAccess.mockResolvedValue(new Set([albumStub.oneAsset.id]));
shareMock.create.mockResolvedValue(sharedLinkStub.valid);
sharedLinkMock.create.mockResolvedValue(sharedLinkStub.valid);
await sut.create(authStub.admin, { type: SharedLinkType.ALBUM, albumId: albumStub.oneAsset.id });
@@ -134,7 +116,7 @@ describe(SharedLinkService.name, () => {
authStub.admin.user.id,
new Set([albumStub.oneAsset.id]),
);
expect(shareMock.create).toHaveBeenCalledWith({
expect(sharedLinkMock.create).toHaveBeenCalledWith({
type: SharedLinkType.ALBUM,
userId: authStub.admin.user.id,
albumId: albumStub.oneAsset.id,
@@ -150,7 +132,7 @@ describe(SharedLinkService.name, () => {
it('should create an individual shared link', async () => {
accessMock.asset.checkOwnerAccess.mockResolvedValue(new Set([assetStub.image.id]));
shareMock.create.mockResolvedValue(sharedLinkStub.individual);
sharedLinkMock.create.mockResolvedValue(sharedLinkStub.individual);
await sut.create(authStub.admin, {
type: SharedLinkType.INDIVIDUAL,
@@ -164,7 +146,7 @@ describe(SharedLinkService.name, () => {
authStub.admin.user.id,
new Set([assetStub.image.id]),
);
expect(shareMock.create).toHaveBeenCalledWith({
expect(sharedLinkMock.create).toHaveBeenCalledWith({
type: SharedLinkType.INDIVIDUAL,
userId: authStub.admin.user.id,
albumId: null,
@@ -180,7 +162,7 @@ describe(SharedLinkService.name, () => {
it('should create a shared link with allowDownload set to false when showMetadata is false', async () => {
accessMock.asset.checkOwnerAccess.mockResolvedValue(new Set([assetStub.image.id]));
shareMock.create.mockResolvedValue(sharedLinkStub.individual);
sharedLinkMock.create.mockResolvedValue(sharedLinkStub.individual);
await sut.create(authStub.admin, {
type: SharedLinkType.INDIVIDUAL,
@@ -194,7 +176,7 @@ describe(SharedLinkService.name, () => {
authStub.admin.user.id,
new Set([assetStub.image.id]),
);
expect(shareMock.create).toHaveBeenCalledWith({
expect(sharedLinkMock.create).toHaveBeenCalledWith({
type: SharedLinkType.INDIVIDUAL,
userId: authStub.admin.user.id,
albumId: null,
@@ -211,18 +193,18 @@ describe(SharedLinkService.name, () => {
describe('update', () => {
it('should throw an error for an invalid shared link', async () => {
shareMock.get.mockResolvedValue(null);
sharedLinkMock.get.mockResolvedValue(null);
await expect(sut.update(authStub.user1, 'missing-id', {})).rejects.toBeInstanceOf(BadRequestException);
expect(shareMock.get).toHaveBeenCalledWith(authStub.user1.user.id, 'missing-id');
expect(shareMock.update).not.toHaveBeenCalled();
expect(sharedLinkMock.get).toHaveBeenCalledWith(authStub.user1.user.id, 'missing-id');
expect(sharedLinkMock.update).not.toHaveBeenCalled();
});
it('should update a shared link', async () => {
shareMock.get.mockResolvedValue(sharedLinkStub.valid);
shareMock.update.mockResolvedValue(sharedLinkStub.valid);
sharedLinkMock.get.mockResolvedValue(sharedLinkStub.valid);
sharedLinkMock.update.mockResolvedValue(sharedLinkStub.valid);
await sut.update(authStub.user1, sharedLinkStub.valid.id, { allowDownload: false });
expect(shareMock.get).toHaveBeenCalledWith(authStub.user1.user.id, sharedLinkStub.valid.id);
expect(shareMock.update).toHaveBeenCalledWith({
expect(sharedLinkMock.get).toHaveBeenCalledWith(authStub.user1.user.id, sharedLinkStub.valid.id);
expect(sharedLinkMock.update).toHaveBeenCalledWith({
id: sharedLinkStub.valid.id,
userId: authStub.user1.user.id,
allowDownload: false,
@@ -232,31 +214,31 @@ describe(SharedLinkService.name, () => {
describe('remove', () => {
it('should throw an error for an invalid shared link', async () => {
shareMock.get.mockResolvedValue(null);
sharedLinkMock.get.mockResolvedValue(null);
await expect(sut.remove(authStub.user1, 'missing-id')).rejects.toBeInstanceOf(BadRequestException);
expect(shareMock.get).toHaveBeenCalledWith(authStub.user1.user.id, 'missing-id');
expect(shareMock.update).not.toHaveBeenCalled();
expect(sharedLinkMock.get).toHaveBeenCalledWith(authStub.user1.user.id, 'missing-id');
expect(sharedLinkMock.update).not.toHaveBeenCalled();
});
it('should remove a key', async () => {
shareMock.get.mockResolvedValue(sharedLinkStub.valid);
sharedLinkMock.get.mockResolvedValue(sharedLinkStub.valid);
await sut.remove(authStub.user1, sharedLinkStub.valid.id);
expect(shareMock.get).toHaveBeenCalledWith(authStub.user1.user.id, sharedLinkStub.valid.id);
expect(shareMock.remove).toHaveBeenCalledWith(sharedLinkStub.valid);
expect(sharedLinkMock.get).toHaveBeenCalledWith(authStub.user1.user.id, sharedLinkStub.valid.id);
expect(sharedLinkMock.remove).toHaveBeenCalledWith(sharedLinkStub.valid);
});
});
describe('addAssets', () => {
it('should not work on album shared links', async () => {
shareMock.get.mockResolvedValue(sharedLinkStub.valid);
sharedLinkMock.get.mockResolvedValue(sharedLinkStub.valid);
await expect(sut.addAssets(authStub.admin, 'link-1', { assetIds: ['asset-1'] })).rejects.toBeInstanceOf(
BadRequestException,
);
});
it('should add assets to a shared link', async () => {
shareMock.get.mockResolvedValue(_.cloneDeep(sharedLinkStub.individual));
shareMock.create.mockResolvedValue(sharedLinkStub.individual);
sharedLinkMock.get.mockResolvedValue(_.cloneDeep(sharedLinkStub.individual));
sharedLinkMock.create.mockResolvedValue(sharedLinkStub.individual);
accessMock.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-3']));
await expect(
@@ -268,7 +250,7 @@ describe(SharedLinkService.name, () => {
]);
expect(accessMock.asset.checkOwnerAccess).toHaveBeenCalledTimes(1);
expect(shareMock.update).toHaveBeenCalledWith({
expect(sharedLinkMock.update).toHaveBeenCalledWith({
...sharedLinkStub.individual,
assets: [assetStub.image, { id: 'asset-3' }],
});
@@ -277,15 +259,15 @@ describe(SharedLinkService.name, () => {
describe('removeAssets', () => {
it('should not work on album shared links', async () => {
shareMock.get.mockResolvedValue(sharedLinkStub.valid);
sharedLinkMock.get.mockResolvedValue(sharedLinkStub.valid);
await expect(sut.removeAssets(authStub.admin, 'link-1', { assetIds: ['asset-1'] })).rejects.toBeInstanceOf(
BadRequestException,
);
});
it('should remove assets from a shared link', async () => {
shareMock.get.mockResolvedValue(_.cloneDeep(sharedLinkStub.individual));
shareMock.create.mockResolvedValue(sharedLinkStub.individual);
sharedLinkMock.get.mockResolvedValue(_.cloneDeep(sharedLinkStub.individual));
sharedLinkMock.create.mockResolvedValue(sharedLinkStub.individual);
await expect(
sut.removeAssets(authStub.admin, 'link-1', { assetIds: [assetStub.image.id, 'asset-2'] }),
@@ -294,29 +276,29 @@ describe(SharedLinkService.name, () => {
{ assetId: 'asset-2', success: false, error: AssetIdErrorReason.NOT_FOUND },
]);
expect(shareMock.update).toHaveBeenCalledWith({ ...sharedLinkStub.individual, assets: [] });
expect(sharedLinkMock.update).toHaveBeenCalledWith({ ...sharedLinkStub.individual, assets: [] });
});
});
describe('getMetadataTags', () => {
it('should return null when auth is not a shared link', async () => {
await expect(sut.getMetadataTags(authStub.admin)).resolves.toBe(null);
expect(shareMock.get).not.toHaveBeenCalled();
expect(sharedLinkMock.get).not.toHaveBeenCalled();
});
it('should return null when shared link has a password', async () => {
await expect(sut.getMetadataTags(authStub.passwordSharedLink)).resolves.toBe(null);
expect(shareMock.get).not.toHaveBeenCalled();
expect(sharedLinkMock.get).not.toHaveBeenCalled();
});
it('should return metadata tags', async () => {
shareMock.get.mockResolvedValue(sharedLinkStub.individual);
sharedLinkMock.get.mockResolvedValue(sharedLinkStub.individual);
await expect(sut.getMetadataTags(authStub.adminSharedLink)).resolves.toEqual({
description: '1 shared photos & videos',
imageUrl: `${DEFAULT_EXTERNAL_DOMAIN}/api/assets/asset-id/thumbnail?key=LCtkaJX4R1O_9D-2lq0STzsPryoL1UdAbyb6Sna1xxmQCSuqU2J1ZUsqt6GR-yGm1s0`,
title: 'Public Share',
});
expect(shareMock.get).toHaveBeenCalled();
expect(sharedLinkMock.get).toHaveBeenCalled();
});
});
});