refactor(server): make storage core singleton (#4608)

This commit is contained in:
Daniel Dietzler 2023-10-23 17:52:21 +02:00 committed by GitHub
parent 2288b022bc
commit 6b25435b4f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 80 additions and 90 deletions

View File

@ -10,8 +10,6 @@ import {
newCommunicationRepositoryMock, newCommunicationRepositoryMock,
newCryptoRepositoryMock, newCryptoRepositoryMock,
newJobRepositoryMock, newJobRepositoryMock,
newMoveRepositoryMock,
newPersonRepositoryMock,
newStorageRepositoryMock, newStorageRepositoryMock,
newSystemConfigRepositoryMock, newSystemConfigRepositoryMock,
} from '@test'; } from '@test';
@ -25,8 +23,6 @@ import {
ICommunicationRepository, ICommunicationRepository,
ICryptoRepository, ICryptoRepository,
IJobRepository, IJobRepository,
IMoveRepository,
IPersonRepository,
IStorageRepository, IStorageRepository,
ISystemConfigRepository, ISystemConfigRepository,
JobItem, JobItem,
@ -165,8 +161,6 @@ describe(AssetService.name, () => {
let assetMock: jest.Mocked<IAssetRepository>; let assetMock: jest.Mocked<IAssetRepository>;
let cryptoMock: jest.Mocked<ICryptoRepository>; let cryptoMock: jest.Mocked<ICryptoRepository>;
let jobMock: jest.Mocked<IJobRepository>; let jobMock: jest.Mocked<IJobRepository>;
let moveMock: jest.Mocked<IMoveRepository>;
let personMock: jest.Mocked<IPersonRepository>;
let storageMock: jest.Mocked<IStorageRepository>; let storageMock: jest.Mocked<IStorageRepository>;
let communicationMock: jest.Mocked<ICommunicationRepository>; let communicationMock: jest.Mocked<ICommunicationRepository>;
let configMock: jest.Mocked<ISystemConfigRepository>; let configMock: jest.Mocked<ISystemConfigRepository>;
@ -181,21 +175,9 @@ describe(AssetService.name, () => {
communicationMock = newCommunicationRepositoryMock(); communicationMock = newCommunicationRepositoryMock();
cryptoMock = newCryptoRepositoryMock(); cryptoMock = newCryptoRepositoryMock();
jobMock = newJobRepositoryMock(); jobMock = newJobRepositoryMock();
moveMock = newMoveRepositoryMock();
personMock = newPersonRepositoryMock();
storageMock = newStorageRepositoryMock(); storageMock = newStorageRepositoryMock();
configMock = newSystemConfigRepositoryMock(); configMock = newSystemConfigRepositoryMock();
sut = new AssetService( sut = new AssetService(accessMock, assetMock, cryptoMock, jobMock, configMock, storageMock, communicationMock);
accessMock,
assetMock,
cryptoMock,
jobMock,
configMock,
moveMock,
personMock,
storageMock,
communicationMock,
);
when(assetMock.getById) when(assetMock.getById)
.calledWith(assetStub.livePhotoStillAsset.id) .calledWith(assetStub.livePhotoStillAsset.id)

View File

@ -16,8 +16,6 @@ import {
ICommunicationRepository, ICommunicationRepository,
ICryptoRepository, ICryptoRepository,
IJobRepository, IJobRepository,
IMoveRepository,
IPersonRepository,
IStorageRepository, IStorageRepository,
ISystemConfigRepository, ISystemConfigRepository,
ImmichReadStream, ImmichReadStream,
@ -76,7 +74,6 @@ export class AssetService {
private logger = new Logger(AssetService.name); private logger = new Logger(AssetService.name);
private access: AccessCore; private access: AccessCore;
private configCore: SystemConfigCore; private configCore: SystemConfigCore;
private storageCore: StorageCore;
constructor( constructor(
@Inject(IAccessRepository) accessRepository: IAccessRepository, @Inject(IAccessRepository) accessRepository: IAccessRepository,
@ -84,14 +81,11 @@ export class AssetService {
@Inject(ICryptoRepository) private cryptoRepository: ICryptoRepository, @Inject(ICryptoRepository) private cryptoRepository: ICryptoRepository,
@Inject(IJobRepository) private jobRepository: IJobRepository, @Inject(IJobRepository) private jobRepository: IJobRepository,
@Inject(ISystemConfigRepository) configRepository: ISystemConfigRepository, @Inject(ISystemConfigRepository) configRepository: ISystemConfigRepository,
@Inject(IMoveRepository) moveRepository: IMoveRepository,
@Inject(IPersonRepository) personRepository: IPersonRepository,
@Inject(IStorageRepository) private storageRepository: IStorageRepository, @Inject(IStorageRepository) private storageRepository: IStorageRepository,
@Inject(ICommunicationRepository) private communicationRepository: ICommunicationRepository, @Inject(ICommunicationRepository) private communicationRepository: ICommunicationRepository,
) { ) {
this.access = AccessCore.create(accessRepository); this.access = AccessCore.create(accessRepository);
this.configCore = SystemConfigCore.create(configRepository); this.configCore = SystemConfigCore.create(configRepository);
this.storageCore = new StorageCore(storageRepository, assetRepository, moveRepository, personRepository);
} }
canUploadFile({ authUser, fieldName, file }: UploadRequest): true { canUploadFile({ authUser, fieldName, file }: UploadRequest): true {
@ -147,9 +141,9 @@ export class AssetService {
getUploadFolder({ authUser, fieldName }: UploadRequest): string { getUploadFolder({ authUser, fieldName }: UploadRequest): string {
authUser = this.access.requireUploadAccess(authUser); authUser = this.access.requireUploadAccess(authUser);
let folder = this.storageCore.getFolderLocation(StorageFolder.UPLOAD, authUser.id); let folder = StorageCore.getFolderLocation(StorageFolder.UPLOAD, authUser.id);
if (fieldName === UploadFieldName.PROFILE_DATA) { if (fieldName === UploadFieldName.PROFILE_DATA) {
folder = this.storageCore.getFolderLocation(StorageFolder.PROFILE, authUser.id); folder = StorageCore.getFolderLocation(StorageFolder.PROFILE, authUser.id);
} }
this.storageRepository.mkdirSync(folder); this.storageRepository.mkdirSync(folder);

View File

@ -44,7 +44,7 @@ export class MediaService {
@Inject(IMoveRepository) moveRepository: IMoveRepository, @Inject(IMoveRepository) moveRepository: IMoveRepository,
) { ) {
this.configCore = SystemConfigCore.create(configRepository); this.configCore = SystemConfigCore.create(configRepository);
this.storageCore = new StorageCore(this.storageRepository, assetRepository, moveRepository, personRepository); this.storageCore = StorageCore.create(assetRepository, moveRepository, personRepository, storageRepository);
} }
async handleQueueGenerateThumbnails({ force }: IBaseJob) { async handleQueueGenerateThumbnails({ force }: IBaseJob) {
@ -140,7 +140,7 @@ export class MediaService {
const { thumbnail, ffmpeg } = await this.configCore.getConfig(); const { thumbnail, ffmpeg } = await this.configCore.getConfig();
const size = format === 'jpeg' ? thumbnail.jpegSize : thumbnail.webpSize; const size = format === 'jpeg' ? thumbnail.jpegSize : thumbnail.webpSize;
const path = const path =
format === 'jpeg' ? this.storageCore.getLargeThumbnailPath(asset) : this.storageCore.getSmallThumbnailPath(asset); format === 'jpeg' ? StorageCore.getLargeThumbnailPath(asset) : StorageCore.getSmallThumbnailPath(asset);
this.storageCore.ensureFolders(path); this.storageCore.ensureFolders(path);
switch (asset.type) { switch (asset.type) {
@ -220,7 +220,7 @@ export class MediaService {
} }
const input = asset.originalPath; const input = asset.originalPath;
const output = this.storageCore.getEncodedVideoPath(asset); const output = StorageCore.getEncodedVideoPath(asset);
this.storageCore.ensureFolders(output); this.storageCore.ensureFolders(output);
const { videoStreams, audioStreams, format } = await this.mediaRepository.probe(input); const { videoStreams, audioStreams, format } = await this.mediaRepository.probe(input);

View File

@ -80,7 +80,7 @@ export class MetadataService {
@Inject(IPersonRepository) personRepository: IPersonRepository, @Inject(IPersonRepository) personRepository: IPersonRepository,
) { ) {
this.configCore = SystemConfigCore.create(configRepository); this.configCore = SystemConfigCore.create(configRepository);
this.storageCore = new StorageCore(storageRepository, assetRepository, moveRepository, personRepository); this.storageCore = StorageCore.create(assetRepository, moveRepository, personRepository, storageRepository);
this.configCore.config$.subscribe(() => this.init()); this.configCore.config$.subscribe(() => this.init());
} }
@ -294,7 +294,7 @@ export class MetadataService {
}); });
const checksum = this.cryptoRepository.hashSha1(video); const checksum = this.cryptoRepository.hashSha1(video);
const motionPath = this.storageCore.getAndroidMotionPath(asset); const motionPath = StorageCore.getAndroidMotionPath(asset);
this.storageCore.ensureFolders(motionPath); this.storageCore.ensureFolders(motionPath);
let motionAsset = await this.assetRepository.getByChecksum(asset.ownerId, checksum); let motionAsset = await this.assetRepository.getByChecksum(asset.ownerId, checksum);

View File

@ -58,7 +58,7 @@ export class PersonService {
) { ) {
this.access = AccessCore.create(accessRepository); this.access = AccessCore.create(accessRepository);
this.configCore = SystemConfigCore.create(configRepository); this.configCore = SystemConfigCore.create(configRepository);
this.storageCore = new StorageCore(storageRepository, assetRepository, moveRepository, repository); this.storageCore = StorageCore.create(assetRepository, moveRepository, repository, storageRepository);
} }
async getAll(authUser: AuthUserDto, dto: PersonSearchDto): Promise<PeopleResponseDto> { async getAll(authUser: AuthUserDto, dto: PersonSearchDto): Promise<PeopleResponseDto> {
@ -309,7 +309,7 @@ export class PersonService {
} }
this.logger.verbose(`Cropping face for person: ${personId}`); this.logger.verbose(`Cropping face for person: ${personId}`);
const thumbnailPath = this.storageCore.getPersonThumbnailPath(person); const thumbnailPath = StorageCore.getPersonThumbnailPath(person);
this.storageCore.ensureFolders(thumbnailPath); this.storageCore.ensureFolders(thumbnailPath);
const halfWidth = (x2 - x1) / 2; const halfWidth = (x2 - x1) / 2;

View File

@ -52,7 +52,7 @@ export class StorageTemplateService {
this.configCore = SystemConfigCore.create(configRepository); this.configCore = SystemConfigCore.create(configRepository);
this.configCore.addValidator((config) => this.validate(config)); this.configCore.addValidator((config) => this.validate(config));
this.configCore.config$.subscribe((config) => this.onConfig(config)); this.configCore.config$.subscribe((config) => this.onConfig(config));
this.storageCore = new StorageCore(storageRepository, assetRepository, moveRepository, personRepository); this.storageCore = StorageCore.create(assetRepository, moveRepository, personRepository, storageRepository);
} }
async handleMigrationSingle({ id }: IEntityJob) { async handleMigrationSingle({ id }: IEntityJob) {
@ -99,7 +99,7 @@ export class StorageTemplateService {
} }
async moveAsset(asset: AssetEntity, metadata: MoveAssetMetadata) { async moveAsset(asset: AssetEntity, metadata: MoveAssetMetadata) {
if (asset.isReadOnly || asset.isExternal || this.storageCore.isAndroidMotionPath(asset.originalPath)) { if (asset.isReadOnly || asset.isExternal || StorageCore.isAndroidMotionPath(asset.originalPath)) {
// External assets are not affected by storage template // External assets are not affected by storage template
// TODO: shouldn't this only apply to external assets? // TODO: shouldn't this only apply to external assets?
return; return;
@ -131,7 +131,7 @@ export class StorageTemplateService {
const source = asset.originalPath; const source = asset.originalPath;
const ext = path.extname(source).split('.').pop() as string; const ext = path.extname(source).split('.').pop() as string;
const sanitized = sanitize(path.basename(filename, `.${ext}`)); const sanitized = sanitize(path.basename(filename, `.${ext}`));
const rootPath = this.storageCore.getLibraryFolder({ id: asset.ownerId, storageLabel }); const rootPath = StorageCore.getLibraryFolder({ id: asset.ownerId, storageLabel });
const storagePath = this.render(this.storageTemplate, asset, sanitized, ext); const storagePath = this.render(this.storageTemplate, asset, sanitized, ext);
const fullPath = path.normalize(path.join(rootPath, storagePath)); const fullPath = path.normalize(path.join(rootPath, storagePath));
let destination = `${fullPath}.${ext}`; let destination = `${fullPath}.${ext}`;

View File

@ -21,21 +21,40 @@ export interface MoveRequest {
type GeneratedAssetPath = AssetPathType.JPEG_THUMBNAIL | AssetPathType.WEBP_THUMBNAIL | AssetPathType.ENCODED_VIDEO; type GeneratedAssetPath = AssetPathType.JPEG_THUMBNAIL | AssetPathType.WEBP_THUMBNAIL | AssetPathType.ENCODED_VIDEO;
let instance: StorageCore | null;
export class StorageCore { export class StorageCore {
private logger = new Logger(StorageCore.name); private logger = new Logger(StorageCore.name);
constructor( private constructor(
private repository: IStorageRepository,
private assetRepository: IAssetRepository, private assetRepository: IAssetRepository,
private moveRepository: IMoveRepository, private moveRepository: IMoveRepository,
private personRepository: IPersonRepository, private personRepository: IPersonRepository,
private repository: IStorageRepository,
) {} ) {}
getFolderLocation(folder: StorageFolder, userId: string) { static create(
assetRepository: IAssetRepository,
moveRepository: IMoveRepository,
personRepository: IPersonRepository,
repository: IStorageRepository,
) {
if (!instance) {
instance = new StorageCore(assetRepository, moveRepository, personRepository, repository);
}
return instance;
}
static reset() {
instance = null;
}
static getFolderLocation(folder: StorageFolder, userId: string) {
return join(StorageCore.getBaseFolder(folder), userId); return join(StorageCore.getBaseFolder(folder), userId);
} }
getLibraryFolder(user: { storageLabel: string | null; id: string }) { static getLibraryFolder(user: { storageLabel: string | null; id: string }) {
return join(StorageCore.getBaseFolder(StorageFolder.LIBRARY), user.storageLabel || user.id); return join(StorageCore.getBaseFolder(StorageFolder.LIBRARY), user.storageLabel || user.id);
} }
@ -43,27 +62,27 @@ export class StorageCore {
return join(APP_MEDIA_LOCATION, folder); return join(APP_MEDIA_LOCATION, folder);
} }
getPersonThumbnailPath(person: PersonEntity) { static getPersonThumbnailPath(person: PersonEntity) {
return this.getNestedPath(StorageFolder.THUMBNAILS, person.ownerId, `${person.id}.jpeg`); return StorageCore.getNestedPath(StorageFolder.THUMBNAILS, person.ownerId, `${person.id}.jpeg`);
} }
getLargeThumbnailPath(asset: AssetEntity) { static getLargeThumbnailPath(asset: AssetEntity) {
return this.getNestedPath(StorageFolder.THUMBNAILS, asset.ownerId, `${asset.id}.jpeg`); return StorageCore.getNestedPath(StorageFolder.THUMBNAILS, asset.ownerId, `${asset.id}.jpeg`);
} }
getSmallThumbnailPath(asset: AssetEntity) { static getSmallThumbnailPath(asset: AssetEntity) {
return this.getNestedPath(StorageFolder.THUMBNAILS, asset.ownerId, `${asset.id}.webp`); return StorageCore.getNestedPath(StorageFolder.THUMBNAILS, asset.ownerId, `${asset.id}.webp`);
} }
getEncodedVideoPath(asset: AssetEntity) { static getEncodedVideoPath(asset: AssetEntity) {
return this.getNestedPath(StorageFolder.ENCODED_VIDEO, asset.ownerId, `${asset.id}.mp4`); return StorageCore.getNestedPath(StorageFolder.ENCODED_VIDEO, asset.ownerId, `${asset.id}.mp4`);
} }
getAndroidMotionPath(asset: AssetEntity) { static getAndroidMotionPath(asset: AssetEntity) {
return this.getNestedPath(StorageFolder.ENCODED_VIDEO, asset.ownerId, `${asset.id}-MP.mp4`); return StorageCore.getNestedPath(StorageFolder.ENCODED_VIDEO, asset.ownerId, `${asset.id}-MP.mp4`);
} }
isAndroidMotionPath(originalPath: string) { static isAndroidMotionPath(originalPath: string) {
return originalPath.startsWith(StorageCore.getBaseFolder(StorageFolder.ENCODED_VIDEO)); return originalPath.startsWith(StorageCore.getBaseFolder(StorageFolder.ENCODED_VIDEO));
} }
@ -75,15 +94,25 @@ export class StorageCore {
const { id: entityId, resizePath, webpPath, encodedVideoPath } = asset; const { id: entityId, resizePath, webpPath, encodedVideoPath } = asset;
switch (pathType) { switch (pathType) {
case AssetPathType.JPEG_THUMBNAIL: case AssetPathType.JPEG_THUMBNAIL:
return this.moveFile({ entityId, pathType, oldPath: resizePath, newPath: this.getLargeThumbnailPath(asset) }); return this.moveFile({
entityId,
pathType,
oldPath: resizePath,
newPath: StorageCore.getLargeThumbnailPath(asset),
});
case AssetPathType.WEBP_THUMBNAIL: case AssetPathType.WEBP_THUMBNAIL:
return this.moveFile({ entityId, pathType, oldPath: webpPath, newPath: this.getSmallThumbnailPath(asset) }); return this.moveFile({
entityId,
pathType,
oldPath: webpPath,
newPath: StorageCore.getSmallThumbnailPath(asset),
});
case AssetPathType.ENCODED_VIDEO: case AssetPathType.ENCODED_VIDEO:
return this.moveFile({ return this.moveFile({
entityId, entityId,
pathType, pathType,
oldPath: encodedVideoPath, oldPath: encodedVideoPath,
newPath: this.getEncodedVideoPath(asset), newPath: StorageCore.getEncodedVideoPath(asset),
}); });
} }
} }
@ -96,7 +125,7 @@ export class StorageCore {
entityId, entityId,
pathType, pathType,
oldPath: thumbnailPath, oldPath: thumbnailPath,
newPath: this.getPersonThumbnailPath(person), newPath: StorageCore.getPersonThumbnailPath(person),
}); });
} }
} }
@ -159,7 +188,12 @@ export class StorageCore {
} }
} }
private getNestedPath(folder: StorageFolder, ownerId: string, filename: string): string { private static getNestedPath(folder: StorageFolder, ownerId: string, filename: string): string {
return join(this.getFolderLocation(folder, ownerId), filename.substring(0, 2), filename.substring(2, 4), filename); return join(
StorageCore.getFolderLocation(folder, ownerId),
filename.substring(0, 2),
filename.substring(2, 4),
filename,
);
} }
} }

View File

@ -11,8 +11,6 @@ import {
newCryptoRepositoryMock, newCryptoRepositoryMock,
newJobRepositoryMock, newJobRepositoryMock,
newLibraryRepositoryMock, newLibraryRepositoryMock,
newMoveRepositoryMock,
newPersonRepositoryMock,
newStorageRepositoryMock, newStorageRepositoryMock,
newUserRepositoryMock, newUserRepositoryMock,
userStub, userStub,
@ -26,8 +24,6 @@ import {
ICryptoRepository, ICryptoRepository,
IJobRepository, IJobRepository,
ILibraryRepository, ILibraryRepository,
IMoveRepository,
IPersonRepository,
IStorageRepository, IStorageRepository,
IUserRepository, IUserRepository,
} from '../repositories'; } from '../repositories';
@ -139,8 +135,6 @@ describe(UserService.name, () => {
let assetMock: jest.Mocked<IAssetRepository>; let assetMock: jest.Mocked<IAssetRepository>;
let jobMock: jest.Mocked<IJobRepository>; let jobMock: jest.Mocked<IJobRepository>;
let libraryMock: jest.Mocked<ILibraryRepository>; let libraryMock: jest.Mocked<ILibraryRepository>;
let moveMock: jest.Mocked<IMoveRepository>;
let personMock: jest.Mocked<IPersonRepository>;
let storageMock: jest.Mocked<IStorageRepository>; let storageMock: jest.Mocked<IStorageRepository>;
beforeEach(async () => { beforeEach(async () => {
@ -149,22 +143,10 @@ describe(UserService.name, () => {
cryptoRepositoryMock = newCryptoRepositoryMock(); cryptoRepositoryMock = newCryptoRepositoryMock();
jobMock = newJobRepositoryMock(); jobMock = newJobRepositoryMock();
libraryMock = newLibraryRepositoryMock(); libraryMock = newLibraryRepositoryMock();
moveMock = newMoveRepositoryMock();
personMock = newPersonRepositoryMock();
storageMock = newStorageRepositoryMock(); storageMock = newStorageRepositoryMock();
userMock = newUserRepositoryMock(); userMock = newUserRepositoryMock();
sut = new UserService( sut = new UserService(albumMock, assetMock, cryptoRepositoryMock, jobMock, libraryMock, storageMock, userMock);
albumMock,
assetMock,
cryptoRepositoryMock,
jobMock,
libraryMock,
moveMock,
personMock,
storageMock,
userMock,
);
when(userMock.get).calledWith(adminUser.id).mockResolvedValue(adminUser); when(userMock.get).calledWith(adminUser.id).mockResolvedValue(adminUser);
when(userMock.get).calledWith(adminUser.id, undefined).mockResolvedValue(adminUser); when(userMock.get).calledWith(adminUser.id, undefined).mockResolvedValue(adminUser);

View File

@ -10,8 +10,6 @@ import {
ICryptoRepository, ICryptoRepository,
IJobRepository, IJobRepository,
ILibraryRepository, ILibraryRepository,
IMoveRepository,
IPersonRepository,
IStorageRepository, IStorageRepository,
IUserRepository, IUserRepository,
} from '../repositories'; } from '../repositories';
@ -30,7 +28,6 @@ import { UserCore } from './user.core';
@Injectable() @Injectable()
export class UserService { export class UserService {
private logger = new Logger(UserService.name); private logger = new Logger(UserService.name);
private storageCore: StorageCore;
private userCore: UserCore; private userCore: UserCore;
constructor( constructor(
@ -39,12 +36,9 @@ export class UserService {
@Inject(ICryptoRepository) cryptoRepository: ICryptoRepository, @Inject(ICryptoRepository) cryptoRepository: ICryptoRepository,
@Inject(IJobRepository) private jobRepository: IJobRepository, @Inject(IJobRepository) private jobRepository: IJobRepository,
@Inject(ILibraryRepository) libraryRepository: ILibraryRepository, @Inject(ILibraryRepository) libraryRepository: ILibraryRepository,
@Inject(IMoveRepository) moveRepository: IMoveRepository,
@Inject(IPersonRepository) personRepository: IPersonRepository,
@Inject(IStorageRepository) private storageRepository: IStorageRepository, @Inject(IStorageRepository) private storageRepository: IStorageRepository,
@Inject(IUserRepository) private userRepository: IUserRepository, @Inject(IUserRepository) private userRepository: IUserRepository,
) { ) {
this.storageCore = new StorageCore(storageRepository, assetRepository, moveRepository, personRepository);
this.userCore = UserCore.create(cryptoRepository, libraryRepository, userRepository); this.userCore = UserCore.create(cryptoRepository, libraryRepository, userRepository);
} }
@ -171,11 +165,11 @@ export class UserService {
this.logger.log(`Deleting user: ${user.id}`); this.logger.log(`Deleting user: ${user.id}`);
const folders = [ const folders = [
this.storageCore.getLibraryFolder(user), StorageCore.getLibraryFolder(user),
this.storageCore.getFolderLocation(StorageFolder.UPLOAD, user.id), StorageCore.getFolderLocation(StorageFolder.UPLOAD, user.id),
this.storageCore.getFolderLocation(StorageFolder.PROFILE, user.id), StorageCore.getFolderLocation(StorageFolder.PROFILE, user.id),
this.storageCore.getFolderLocation(StorageFolder.THUMBNAILS, user.id), StorageCore.getFolderLocation(StorageFolder.THUMBNAILS, user.id),
this.storageCore.getFolderLocation(StorageFolder.ENCODED_VIDEO, user.id), StorageCore.getFolderLocation(StorageFolder.ENCODED_VIDEO, user.id),
]; ];
for (const folder of folders) { for (const folder of folders) {

View File

@ -1,6 +1,10 @@
import { IStorageRepository } from '@app/domain'; import { IStorageRepository, StorageCore } from '@app/domain';
export const newStorageRepositoryMock = (reset = true): jest.Mocked<IStorageRepository> => {
if (reset) {
StorageCore.reset();
}
export const newStorageRepositoryMock = (): jest.Mocked<IStorageRepository> => {
return { return {
createZipStream: jest.fn(), createZipStream: jest.fn(),
createReadStream: jest.fn(), createReadStream: jest.fn(),