refactor: test mocks (#16008)

This commit is contained in:
Jason Rasmussen
2025-02-10 18:47:42 -05:00
committed by GitHub
parent 8794c84e9d
commit 735f8d661e
74 changed files with 3820 additions and 4043 deletions
@@ -1,42 +1,26 @@
import { Stats } from 'node:fs';
import { SystemConfig, defaults } from 'src/config';
import { defaults, SystemConfig } from 'src/config';
import { AssetEntity } from 'src/entities/asset.entity';
import { AssetPathType } from 'src/enum';
import { IAlbumRepository } from 'src/interfaces/album.interface';
import { IAssetRepository } from 'src/interfaces/asset.interface';
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
import { JobStatus } from 'src/interfaces/job.interface';
import { IMoveRepository } from 'src/interfaces/move.interface';
import { IStorageRepository } from 'src/interfaces/storage.interface';
import { IUserRepository } from 'src/interfaces/user.interface';
import { StorageTemplateService } from 'src/services/storage-template.service';
import { ISystemMetadataRepository } from 'src/types';
import { albumStub } from 'test/fixtures/album.stub';
import { assetStub } from 'test/fixtures/asset.stub';
import { userStub } from 'test/fixtures/user.stub';
import { newTestService } from 'test/utils';
import { Mocked } from 'vitest';
import { newTestService, ServiceMocks } from 'test/utils';
describe(StorageTemplateService.name, () => {
let sut: StorageTemplateService;
let albumMock: Mocked<IAlbumRepository>;
let assetMock: Mocked<IAssetRepository>;
let cryptoMock: Mocked<ICryptoRepository>;
let moveMock: Mocked<IMoveRepository>;
let storageMock: Mocked<IStorageRepository>;
let systemMock: Mocked<ISystemMetadataRepository>;
let userMock: Mocked<IUserRepository>;
let mocks: ServiceMocks;
it('should work', () => {
expect(sut).toBeDefined();
});
beforeEach(() => {
({ sut, albumMock, assetMock, cryptoMock, moveMock, storageMock, systemMock, userMock } =
newTestService(StorageTemplateService));
({ sut, mocks } = newTestService(StorageTemplateService));
systemMock.get.mockResolvedValue({ storageTemplate: { enabled: true } });
mocks.systemMetadata.get.mockResolvedValue({ storageTemplate: { enabled: true } });
sut.onConfigInit({ newConfig: defaults });
});
@@ -107,31 +91,31 @@ describe(StorageTemplateService.name, () => {
describe('handleMigrationSingle', () => {
it('should skip when storage template is disabled', async () => {
systemMock.get.mockResolvedValue({ storageTemplate: { enabled: false } });
mocks.systemMetadata.get.mockResolvedValue({ storageTemplate: { enabled: false } });
await expect(sut.handleMigrationSingle({ id: assetStub.image.id })).resolves.toBe(JobStatus.SKIPPED);
expect(assetMock.getByIds).not.toHaveBeenCalled();
expect(storageMock.checkFileExists).not.toHaveBeenCalled();
expect(storageMock.rename).not.toHaveBeenCalled();
expect(storageMock.copyFile).not.toHaveBeenCalled();
expect(assetMock.update).not.toHaveBeenCalled();
expect(moveMock.create).not.toHaveBeenCalled();
expect(moveMock.update).not.toHaveBeenCalled();
expect(storageMock.stat).not.toHaveBeenCalled();
expect(mocks.asset.getByIds).not.toHaveBeenCalled();
expect(mocks.storage.checkFileExists).not.toHaveBeenCalled();
expect(mocks.storage.rename).not.toHaveBeenCalled();
expect(mocks.storage.copyFile).not.toHaveBeenCalled();
expect(mocks.asset.update).not.toHaveBeenCalled();
expect(mocks.move.create).not.toHaveBeenCalled();
expect(mocks.move.update).not.toHaveBeenCalled();
expect(mocks.storage.stat).not.toHaveBeenCalled();
});
it('should migrate single moving picture', async () => {
userMock.get.mockResolvedValue(userStub.user1);
mocks.user.get.mockResolvedValue(userStub.user1);
const newMotionPicturePath = `upload/library/${userStub.user1.id}/2022/2022-06-19/${assetStub.livePhotoStillAsset.id}.mp4`;
const newStillPicturePath = `upload/library/${userStub.user1.id}/2022/2022-06-19/${assetStub.livePhotoStillAsset.id}.jpeg`;
assetMock.getByIds.mockImplementation((ids) => {
mocks.asset.getByIds.mockImplementation((ids) => {
const assets = [assetStub.livePhotoStillAsset, assetStub.livePhotoMotionAsset];
return Promise.resolve(
ids.map((id) => assets.find((asset) => asset.id === id)).filter((asset) => !!asset),
) as Promise<AssetEntity[]>;
});
moveMock.create.mockResolvedValueOnce({
mocks.move.create.mockResolvedValueOnce({
id: '123',
entityId: assetStub.livePhotoStillAsset.id,
pathType: AssetPathType.ORIGINAL,
@@ -139,7 +123,7 @@ describe(StorageTemplateService.name, () => {
newPath: newStillPicturePath,
});
moveMock.create.mockResolvedValueOnce({
mocks.move.create.mockResolvedValueOnce({
id: '124',
entityId: assetStub.livePhotoMotionAsset.id,
pathType: AssetPathType.ORIGINAL,
@@ -151,14 +135,14 @@ describe(StorageTemplateService.name, () => {
JobStatus.SUCCESS,
);
expect(assetMock.getByIds).toHaveBeenCalledWith([assetStub.livePhotoStillAsset.id], { exifInfo: true });
expect(assetMock.getByIds).toHaveBeenCalledWith([assetStub.livePhotoMotionAsset.id], { exifInfo: true });
expect(storageMock.checkFileExists).toHaveBeenCalledTimes(2);
expect(assetMock.update).toHaveBeenCalledWith({
expect(mocks.asset.getByIds).toHaveBeenCalledWith([assetStub.livePhotoStillAsset.id], { exifInfo: true });
expect(mocks.asset.getByIds).toHaveBeenCalledWith([assetStub.livePhotoMotionAsset.id], { exifInfo: true });
expect(mocks.storage.checkFileExists).toHaveBeenCalledTimes(2);
expect(mocks.asset.update).toHaveBeenCalledWith({
id: assetStub.livePhotoStillAsset.id,
originalPath: newStillPicturePath,
});
expect(assetMock.update).toHaveBeenCalledWith({
expect(mocks.asset.update).toHaveBeenCalledWith({
id: assetStub.livePhotoMotionAsset.id,
originalPath: newMotionPicturePath,
});
@@ -173,13 +157,13 @@ describe(StorageTemplateService.name, () => {
sut.onConfigInit({ newConfig: config });
userMock.get.mockResolvedValue(user);
assetMock.getByIds.mockResolvedValueOnce([asset]);
albumMock.getByAssetId.mockResolvedValueOnce([album]);
mocks.user.get.mockResolvedValue(user);
mocks.asset.getByIds.mockResolvedValueOnce([asset]);
mocks.album.getByAssetId.mockResolvedValueOnce([album]);
expect(await sut.handleMigrationSingle({ id: asset.id })).toBe(JobStatus.SUCCESS);
expect(moveMock.create).toHaveBeenCalledWith({
expect(mocks.move.create).toHaveBeenCalledWith({
entityId: asset.id,
newPath: `upload/library/${user.id}/${asset.fileCreatedAt.getFullYear()}/${album.albumName}/${asset.originalFileName}`,
oldPath: asset.originalPath,
@@ -194,13 +178,13 @@ describe(StorageTemplateService.name, () => {
config.storageTemplate.template = '{{y}}/{{#if album}}{{album}}{{else}}other//{{MM}}{{/if}}/{{filename}}';
sut.onConfigInit({ newConfig: config });
userMock.get.mockResolvedValue(user);
assetMock.getByIds.mockResolvedValueOnce([asset]);
mocks.user.get.mockResolvedValue(user);
mocks.asset.getByIds.mockResolvedValueOnce([asset]);
expect(await sut.handleMigrationSingle({ id: asset.id })).toBe(JobStatus.SUCCESS);
const month = (asset.fileCreatedAt.getMonth() + 1).toString().padStart(2, '0');
expect(moveMock.create).toHaveBeenCalledWith({
expect(mocks.move.create).toHaveBeenCalledWith({
entityId: asset.id,
newPath: `upload/library/${user.id}/${asset.fileCreatedAt.getFullYear()}/other/${month}/${asset.originalFileName}`,
oldPath: asset.originalPath,
@@ -209,20 +193,22 @@ describe(StorageTemplateService.name, () => {
});
it('should migrate previously failed move from original path when it still exists', async () => {
userMock.get.mockResolvedValue(userStub.user1);
mocks.user.get.mockResolvedValue(userStub.user1);
const previousFailedNewPath = `upload/library/${userStub.user1.id}/2023/Feb/${assetStub.image.id}.jpg`;
const newPath = `upload/library/${userStub.user1.id}/2023/2023-02-23/${assetStub.image.id}.jpg`;
storageMock.checkFileExists.mockImplementation((path) => Promise.resolve(path === assetStub.image.originalPath));
moveMock.getByEntity.mockResolvedValue({
mocks.storage.checkFileExists.mockImplementation((path) =>
Promise.resolve(path === assetStub.image.originalPath),
);
mocks.move.getByEntity.mockResolvedValue({
id: '123',
entityId: assetStub.image.id,
pathType: AssetPathType.ORIGINAL,
oldPath: assetStub.image.originalPath,
newPath: previousFailedNewPath,
});
assetMock.getByIds.mockResolvedValue([assetStub.image]);
moveMock.update.mockResolvedValue({
mocks.asset.getByIds.mockResolvedValue([assetStub.image]);
mocks.move.update.mockResolvedValue({
id: '123',
entityId: assetStub.image.id,
pathType: AssetPathType.ORIGINAL,
@@ -232,37 +218,37 @@ describe(StorageTemplateService.name, () => {
await expect(sut.handleMigrationSingle({ id: assetStub.image.id })).resolves.toBe(JobStatus.SUCCESS);
expect(assetMock.getByIds).toHaveBeenCalledWith([assetStub.image.id], { exifInfo: true });
expect(storageMock.checkFileExists).toHaveBeenCalledTimes(3);
expect(storageMock.rename).toHaveBeenCalledWith(assetStub.image.originalPath, newPath);
expect(moveMock.update).toHaveBeenCalledWith('123', {
expect(mocks.asset.getByIds).toHaveBeenCalledWith([assetStub.image.id], { exifInfo: true });
expect(mocks.storage.checkFileExists).toHaveBeenCalledTimes(3);
expect(mocks.storage.rename).toHaveBeenCalledWith(assetStub.image.originalPath, newPath);
expect(mocks.move.update).toHaveBeenCalledWith('123', {
id: '123',
oldPath: assetStub.image.originalPath,
newPath,
});
expect(assetMock.update).toHaveBeenCalledWith({
expect(mocks.asset.update).toHaveBeenCalledWith({
id: assetStub.image.id,
originalPath: newPath,
});
});
it('should migrate previously failed move from previous new path when old path no longer exists, should validate file size still matches before moving', async () => {
userMock.get.mockResolvedValue(userStub.user1);
mocks.user.get.mockResolvedValue(userStub.user1);
const previousFailedNewPath = `upload/library/${userStub.user1.id}/2023/Feb/${assetStub.image.id}.jpg`;
const newPath = `upload/library/${userStub.user1.id}/2023/2023-02-23/${assetStub.image.id}.jpg`;
storageMock.checkFileExists.mockImplementation((path) => Promise.resolve(path === previousFailedNewPath));
storageMock.stat.mockResolvedValue({ size: 5000 } as Stats);
cryptoMock.hashFile.mockResolvedValue(assetStub.image.checksum);
moveMock.getByEntity.mockResolvedValue({
mocks.storage.checkFileExists.mockImplementation((path) => Promise.resolve(path === previousFailedNewPath));
mocks.storage.stat.mockResolvedValue({ size: 5000 } as Stats);
mocks.crypto.hashFile.mockResolvedValue(assetStub.image.checksum);
mocks.move.getByEntity.mockResolvedValue({
id: '123',
entityId: assetStub.image.id,
pathType: AssetPathType.ORIGINAL,
oldPath: assetStub.image.originalPath,
newPath: previousFailedNewPath,
});
assetMock.getByIds.mockResolvedValue([assetStub.image]);
moveMock.update.mockResolvedValue({
mocks.asset.getByIds.mockResolvedValue([assetStub.image]);
mocks.move.update.mockResolvedValue({
id: '123',
entityId: assetStub.image.id,
pathType: AssetPathType.ORIGINAL,
@@ -272,31 +258,31 @@ describe(StorageTemplateService.name, () => {
await expect(sut.handleMigrationSingle({ id: assetStub.image.id })).resolves.toBe(JobStatus.SUCCESS);
expect(assetMock.getByIds).toHaveBeenCalledWith([assetStub.image.id], { exifInfo: true });
expect(storageMock.checkFileExists).toHaveBeenCalledTimes(3);
expect(storageMock.stat).toHaveBeenCalledWith(previousFailedNewPath);
expect(storageMock.rename).toHaveBeenCalledWith(previousFailedNewPath, newPath);
expect(storageMock.copyFile).not.toHaveBeenCalled();
expect(moveMock.update).toHaveBeenCalledWith('123', {
expect(mocks.asset.getByIds).toHaveBeenCalledWith([assetStub.image.id], { exifInfo: true });
expect(mocks.storage.checkFileExists).toHaveBeenCalledTimes(3);
expect(mocks.storage.stat).toHaveBeenCalledWith(previousFailedNewPath);
expect(mocks.storage.rename).toHaveBeenCalledWith(previousFailedNewPath, newPath);
expect(mocks.storage.copyFile).not.toHaveBeenCalled();
expect(mocks.move.update).toHaveBeenCalledWith('123', {
id: '123',
oldPath: previousFailedNewPath,
newPath,
});
expect(assetMock.update).toHaveBeenCalledWith({
expect(mocks.asset.update).toHaveBeenCalledWith({
id: assetStub.image.id,
originalPath: newPath,
});
});
it('should fail move if copying and hash of asset and the new file do not match', async () => {
userMock.get.mockResolvedValue(userStub.user1);
mocks.user.get.mockResolvedValue(userStub.user1);
const newPath = `upload/library/${userStub.user1.id}/2023/2023-02-23/${assetStub.image.id}.jpg`;
storageMock.rename.mockRejectedValue({ code: 'EXDEV' });
storageMock.stat.mockResolvedValue({ size: 5000 } as Stats);
cryptoMock.hashFile.mockResolvedValue(Buffer.from('different-hash', 'utf8'));
assetMock.getByIds.mockResolvedValue([assetStub.image]);
moveMock.create.mockResolvedValue({
mocks.storage.rename.mockRejectedValue({ code: 'EXDEV' });
mocks.storage.stat.mockResolvedValue({ size: 5000 } as Stats);
mocks.crypto.hashFile.mockResolvedValue(Buffer.from('different-hash', 'utf8'));
mocks.asset.getByIds.mockResolvedValue([assetStub.image]);
mocks.move.create.mockResolvedValue({
id: '123',
entityId: assetStub.image.id,
pathType: AssetPathType.ORIGINAL,
@@ -306,20 +292,20 @@ describe(StorageTemplateService.name, () => {
await expect(sut.handleMigrationSingle({ id: assetStub.image.id })).resolves.toBe(JobStatus.SUCCESS);
expect(assetMock.getByIds).toHaveBeenCalledWith([assetStub.image.id], { exifInfo: true });
expect(storageMock.checkFileExists).toHaveBeenCalledTimes(1);
expect(storageMock.stat).toHaveBeenCalledWith(newPath);
expect(moveMock.create).toHaveBeenCalledWith({
expect(mocks.asset.getByIds).toHaveBeenCalledWith([assetStub.image.id], { exifInfo: true });
expect(mocks.storage.checkFileExists).toHaveBeenCalledTimes(1);
expect(mocks.storage.stat).toHaveBeenCalledWith(newPath);
expect(mocks.move.create).toHaveBeenCalledWith({
entityId: assetStub.image.id,
pathType: AssetPathType.ORIGINAL,
oldPath: assetStub.image.originalPath,
newPath,
});
expect(storageMock.rename).toHaveBeenCalledWith(assetStub.image.originalPath, newPath);
expect(storageMock.copyFile).toHaveBeenCalledWith(assetStub.image.originalPath, newPath);
expect(storageMock.unlink).toHaveBeenCalledWith(newPath);
expect(storageMock.unlink).toHaveBeenCalledTimes(1);
expect(assetMock.update).not.toHaveBeenCalled();
expect(mocks.storage.rename).toHaveBeenCalledWith(assetStub.image.originalPath, newPath);
expect(mocks.storage.copyFile).toHaveBeenCalledWith(assetStub.image.originalPath, newPath);
expect(mocks.storage.unlink).toHaveBeenCalledWith(newPath);
expect(mocks.storage.unlink).toHaveBeenCalledTimes(1);
expect(mocks.asset.update).not.toHaveBeenCalled();
});
it.each`
@@ -329,22 +315,22 @@ describe(StorageTemplateService.name, () => {
`(
'should fail to migrate previously failed move from previous new path when old path no longer exists if $reason validation fails',
async ({ failedPathChecksum, failedPathSize }) => {
userMock.get.mockResolvedValue(userStub.user1);
mocks.user.get.mockResolvedValue(userStub.user1);
const previousFailedNewPath = `upload/library/${userStub.user1.id}/2023/Feb/${assetStub.image.id}.jpg`;
const newPath = `upload/library/${userStub.user1.id}/2023/2023-02-23/${assetStub.image.id}.jpg`;
storageMock.checkFileExists.mockImplementation((path) => Promise.resolve(previousFailedNewPath === path));
storageMock.stat.mockResolvedValue({ size: failedPathSize } as Stats);
cryptoMock.hashFile.mockResolvedValue(failedPathChecksum);
moveMock.getByEntity.mockResolvedValue({
mocks.storage.checkFileExists.mockImplementation((path) => Promise.resolve(previousFailedNewPath === path));
mocks.storage.stat.mockResolvedValue({ size: failedPathSize } as Stats);
mocks.crypto.hashFile.mockResolvedValue(failedPathChecksum);
mocks.move.getByEntity.mockResolvedValue({
id: '123',
entityId: assetStub.image.id,
pathType: AssetPathType.ORIGINAL,
oldPath: assetStub.image.originalPath,
newPath: previousFailedNewPath,
});
assetMock.getByIds.mockResolvedValue([assetStub.image]);
moveMock.update.mockResolvedValue({
mocks.asset.getByIds.mockResolvedValue([assetStub.image]);
mocks.move.update.mockResolvedValue({
id: '123',
entityId: assetStub.image.id,
pathType: AssetPathType.ORIGINAL,
@@ -354,37 +340,37 @@ describe(StorageTemplateService.name, () => {
await expect(sut.handleMigrationSingle({ id: assetStub.image.id })).resolves.toBe(JobStatus.SUCCESS);
expect(assetMock.getByIds).toHaveBeenCalledWith([assetStub.image.id], { exifInfo: true });
expect(storageMock.checkFileExists).toHaveBeenCalledTimes(3);
expect(storageMock.stat).toHaveBeenCalledWith(previousFailedNewPath);
expect(storageMock.rename).not.toHaveBeenCalled();
expect(storageMock.copyFile).not.toHaveBeenCalled();
expect(moveMock.update).not.toHaveBeenCalled();
expect(assetMock.update).not.toHaveBeenCalled();
expect(mocks.asset.getByIds).toHaveBeenCalledWith([assetStub.image.id], { exifInfo: true });
expect(mocks.storage.checkFileExists).toHaveBeenCalledTimes(3);
expect(mocks.storage.stat).toHaveBeenCalledWith(previousFailedNewPath);
expect(mocks.storage.rename).not.toHaveBeenCalled();
expect(mocks.storage.copyFile).not.toHaveBeenCalled();
expect(mocks.move.update).not.toHaveBeenCalled();
expect(mocks.asset.update).not.toHaveBeenCalled();
},
);
});
describe('handle template migration', () => {
it('should handle no assets', async () => {
assetMock.getAll.mockResolvedValue({
mocks.asset.getAll.mockResolvedValue({
items: [],
hasNextPage: false,
});
userMock.getList.mockResolvedValue([]);
mocks.user.getList.mockResolvedValue([]);
await sut.handleMigration();
expect(assetMock.getAll).toHaveBeenCalled();
expect(mocks.asset.getAll).toHaveBeenCalled();
});
it('should handle an asset with a duplicate destination', async () => {
assetMock.getAll.mockResolvedValue({
mocks.asset.getAll.mockResolvedValue({
items: [assetStub.image],
hasNextPage: false,
});
userMock.getList.mockResolvedValue([userStub.user1]);
moveMock.create.mockResolvedValue({
mocks.user.getList.mockResolvedValue([userStub.user1]);
mocks.move.create.mockResolvedValue({
id: '123',
entityId: assetStub.image.id,
pathType: AssetPathType.ORIGINAL,
@@ -392,22 +378,22 @@ describe(StorageTemplateService.name, () => {
newPath: 'upload/library/user-id/2023/2023-02-23/asset-id+1.jpg',
});
storageMock.checkFileExists.mockResolvedValueOnce(true);
storageMock.checkFileExists.mockResolvedValueOnce(false);
mocks.storage.checkFileExists.mockResolvedValueOnce(true);
mocks.storage.checkFileExists.mockResolvedValueOnce(false);
await sut.handleMigration();
expect(assetMock.getAll).toHaveBeenCalled();
expect(storageMock.checkFileExists).toHaveBeenCalledTimes(2);
expect(assetMock.update).toHaveBeenCalledWith({
expect(mocks.asset.getAll).toHaveBeenCalled();
expect(mocks.storage.checkFileExists).toHaveBeenCalledTimes(2);
expect(mocks.asset.update).toHaveBeenCalledWith({
id: assetStub.image.id,
originalPath: 'upload/library/user-id/2023/2023-02-23/asset-id+1.jpg',
});
expect(userMock.getList).toHaveBeenCalled();
expect(mocks.user.getList).toHaveBeenCalled();
});
it('should skip when an asset already matches the template', async () => {
assetMock.getAll.mockResolvedValue({
mocks.asset.getAll.mockResolvedValue({
items: [
{
...assetStub.image,
@@ -416,19 +402,19 @@ describe(StorageTemplateService.name, () => {
],
hasNextPage: false,
});
userMock.getList.mockResolvedValue([userStub.user1]);
mocks.user.getList.mockResolvedValue([userStub.user1]);
await sut.handleMigration();
expect(assetMock.getAll).toHaveBeenCalled();
expect(storageMock.rename).not.toHaveBeenCalled();
expect(storageMock.copyFile).not.toHaveBeenCalled();
expect(storageMock.checkFileExists).not.toHaveBeenCalledTimes(2);
expect(assetMock.update).not.toHaveBeenCalled();
expect(mocks.asset.getAll).toHaveBeenCalled();
expect(mocks.storage.rename).not.toHaveBeenCalled();
expect(mocks.storage.copyFile).not.toHaveBeenCalled();
expect(mocks.storage.checkFileExists).not.toHaveBeenCalledTimes(2);
expect(mocks.asset.update).not.toHaveBeenCalled();
});
it('should skip when an asset is probably a duplicate', async () => {
assetMock.getAll.mockResolvedValue({
mocks.asset.getAll.mockResolvedValue({
items: [
{
...assetStub.image,
@@ -437,24 +423,24 @@ describe(StorageTemplateService.name, () => {
],
hasNextPage: false,
});
userMock.getList.mockResolvedValue([userStub.user1]);
mocks.user.getList.mockResolvedValue([userStub.user1]);
await sut.handleMigration();
expect(assetMock.getAll).toHaveBeenCalled();
expect(storageMock.rename).not.toHaveBeenCalled();
expect(storageMock.copyFile).not.toHaveBeenCalled();
expect(storageMock.checkFileExists).not.toHaveBeenCalledTimes(2);
expect(assetMock.update).not.toHaveBeenCalled();
expect(mocks.asset.getAll).toHaveBeenCalled();
expect(mocks.storage.rename).not.toHaveBeenCalled();
expect(mocks.storage.copyFile).not.toHaveBeenCalled();
expect(mocks.storage.checkFileExists).not.toHaveBeenCalledTimes(2);
expect(mocks.asset.update).not.toHaveBeenCalled();
});
it('should move an asset', async () => {
assetMock.getAll.mockResolvedValue({
mocks.asset.getAll.mockResolvedValue({
items: [assetStub.image],
hasNextPage: false,
});
userMock.getList.mockResolvedValue([userStub.user1]);
moveMock.create.mockResolvedValue({
mocks.user.getList.mockResolvedValue([userStub.user1]);
mocks.move.create.mockResolvedValue({
id: '123',
entityId: assetStub.image.id,
pathType: AssetPathType.ORIGINAL,
@@ -464,24 +450,24 @@ describe(StorageTemplateService.name, () => {
await sut.handleMigration();
expect(assetMock.getAll).toHaveBeenCalled();
expect(storageMock.rename).toHaveBeenCalledWith(
expect(mocks.asset.getAll).toHaveBeenCalled();
expect(mocks.storage.rename).toHaveBeenCalledWith(
'/original/path.jpg',
'upload/library/user-id/2023/2023-02-23/asset-id.jpg',
);
expect(assetMock.update).toHaveBeenCalledWith({
expect(mocks.asset.update).toHaveBeenCalledWith({
id: assetStub.image.id,
originalPath: 'upload/library/user-id/2023/2023-02-23/asset-id.jpg',
});
});
it('should use the user storage label', async () => {
assetMock.getAll.mockResolvedValue({
mocks.asset.getAll.mockResolvedValue({
items: [assetStub.image],
hasNextPage: false,
});
userMock.getList.mockResolvedValue([userStub.storageLabel]);
moveMock.create.mockResolvedValue({
mocks.user.getList.mockResolvedValue([userStub.storageLabel]);
mocks.move.create.mockResolvedValue({
id: '123',
entityId: assetStub.image.id,
pathType: AssetPathType.ORIGINAL,
@@ -491,12 +477,12 @@ describe(StorageTemplateService.name, () => {
await sut.handleMigration();
expect(assetMock.getAll).toHaveBeenCalled();
expect(storageMock.rename).toHaveBeenCalledWith(
expect(mocks.asset.getAll).toHaveBeenCalled();
expect(mocks.storage.rename).toHaveBeenCalledWith(
'/original/path.jpg',
'upload/library/label-1/2023/2023-02-23/asset-id.jpg',
);
expect(assetMock.update).toHaveBeenCalledWith({
expect(mocks.asset.update).toHaveBeenCalledWith({
id: assetStub.image.id,
originalPath: 'upload/library/label-1/2023/2023-02-23/asset-id.jpg',
});
@@ -504,105 +490,105 @@ describe(StorageTemplateService.name, () => {
it('should copy the file if rename fails due to EXDEV (rename across filesystems)', async () => {
const newPath = 'upload/library/user-id/2023/2023-02-23/asset-id.jpg';
assetMock.getAll.mockResolvedValue({
mocks.asset.getAll.mockResolvedValue({
items: [assetStub.image],
hasNextPage: false,
});
storageMock.rename.mockRejectedValue({ code: 'EXDEV' });
userMock.getList.mockResolvedValue([userStub.user1]);
moveMock.create.mockResolvedValue({
mocks.storage.rename.mockRejectedValue({ code: 'EXDEV' });
mocks.user.getList.mockResolvedValue([userStub.user1]);
mocks.move.create.mockResolvedValue({
id: '123',
entityId: assetStub.image.id,
pathType: AssetPathType.ORIGINAL,
oldPath: assetStub.image.originalPath,
newPath,
});
storageMock.stat.mockResolvedValueOnce({
mocks.storage.stat.mockResolvedValueOnce({
atime: new Date(),
mtime: new Date(),
} as Stats);
storageMock.stat.mockResolvedValueOnce({
mocks.storage.stat.mockResolvedValueOnce({
size: 5000,
} as Stats);
storageMock.stat.mockResolvedValueOnce({
mocks.storage.stat.mockResolvedValueOnce({
atime: new Date(),
mtime: new Date(),
} as Stats);
cryptoMock.hashFile.mockResolvedValue(assetStub.image.checksum);
mocks.crypto.hashFile.mockResolvedValue(assetStub.image.checksum);
await sut.handleMigration();
expect(assetMock.getAll).toHaveBeenCalled();
expect(storageMock.rename).toHaveBeenCalledWith('/original/path.jpg', newPath);
expect(storageMock.copyFile).toHaveBeenCalledWith('/original/path.jpg', newPath);
expect(storageMock.stat).toHaveBeenCalledWith(newPath);
expect(storageMock.stat).toHaveBeenCalledWith(assetStub.image.originalPath);
expect(storageMock.utimes).toHaveBeenCalledWith(newPath, expect.any(Date), expect.any(Date));
expect(storageMock.unlink).toHaveBeenCalledWith(assetStub.image.originalPath);
expect(storageMock.unlink).toHaveBeenCalledTimes(1);
expect(assetMock.update).toHaveBeenCalledWith({
expect(mocks.asset.getAll).toHaveBeenCalled();
expect(mocks.storage.rename).toHaveBeenCalledWith('/original/path.jpg', newPath);
expect(mocks.storage.copyFile).toHaveBeenCalledWith('/original/path.jpg', newPath);
expect(mocks.storage.stat).toHaveBeenCalledWith(newPath);
expect(mocks.storage.stat).toHaveBeenCalledWith(assetStub.image.originalPath);
expect(mocks.storage.utimes).toHaveBeenCalledWith(newPath, expect.any(Date), expect.any(Date));
expect(mocks.storage.unlink).toHaveBeenCalledWith(assetStub.image.originalPath);
expect(mocks.storage.unlink).toHaveBeenCalledTimes(1);
expect(mocks.asset.update).toHaveBeenCalledWith({
id: assetStub.image.id,
originalPath: newPath,
});
});
it('should not update the database if the move fails due to incorrect newPath filesize', async () => {
assetMock.getAll.mockResolvedValue({
mocks.asset.getAll.mockResolvedValue({
items: [assetStub.image],
hasNextPage: false,
});
storageMock.rename.mockRejectedValue({ code: 'EXDEV' });
userMock.getList.mockResolvedValue([userStub.user1]);
moveMock.create.mockResolvedValue({
mocks.storage.rename.mockRejectedValue({ code: 'EXDEV' });
mocks.user.getList.mockResolvedValue([userStub.user1]);
mocks.move.create.mockResolvedValue({
id: '123',
entityId: assetStub.image.id,
pathType: AssetPathType.ORIGINAL,
oldPath: assetStub.image.originalPath,
newPath: 'upload/library/user-id/2023/2023-02-23/asset-id.jpg',
});
storageMock.stat.mockResolvedValue({
mocks.storage.stat.mockResolvedValue({
size: 100,
} as Stats);
await sut.handleMigration();
expect(assetMock.getAll).toHaveBeenCalled();
expect(storageMock.rename).toHaveBeenCalledWith(
expect(mocks.asset.getAll).toHaveBeenCalled();
expect(mocks.storage.rename).toHaveBeenCalledWith(
'/original/path.jpg',
'upload/library/user-id/2023/2023-02-23/asset-id.jpg',
);
expect(storageMock.copyFile).toHaveBeenCalledWith(
expect(mocks.storage.copyFile).toHaveBeenCalledWith(
'/original/path.jpg',
'upload/library/user-id/2023/2023-02-23/asset-id.jpg',
);
expect(storageMock.stat).toHaveBeenCalledWith('upload/library/user-id/2023/2023-02-23/asset-id.jpg');
expect(assetMock.update).not.toHaveBeenCalled();
expect(mocks.storage.stat).toHaveBeenCalledWith('upload/library/user-id/2023/2023-02-23/asset-id.jpg');
expect(mocks.asset.update).not.toHaveBeenCalled();
});
it('should not update the database if the move fails', async () => {
assetMock.getAll.mockResolvedValue({
mocks.asset.getAll.mockResolvedValue({
items: [assetStub.image],
hasNextPage: false,
});
storageMock.rename.mockRejectedValue(new Error('Read only system'));
storageMock.copyFile.mockRejectedValue(new Error('Read only system'));
moveMock.create.mockResolvedValue({
mocks.storage.rename.mockRejectedValue(new Error('Read only system'));
mocks.storage.copyFile.mockRejectedValue(new Error('Read only system'));
mocks.move.create.mockResolvedValue({
id: 'move-123',
entityId: '123',
pathType: AssetPathType.ORIGINAL,
oldPath: assetStub.image.originalPath,
newPath: '',
});
userMock.getList.mockResolvedValue([userStub.user1]);
mocks.user.getList.mockResolvedValue([userStub.user1]);
await sut.handleMigration();
expect(assetMock.getAll).toHaveBeenCalled();
expect(storageMock.rename).toHaveBeenCalledWith(
expect(mocks.asset.getAll).toHaveBeenCalled();
expect(mocks.storage.rename).toHaveBeenCalledWith(
'/original/path.jpg',
'upload/library/user-id/2023/2023-02-23/asset-id.jpg',
);
expect(assetMock.update).not.toHaveBeenCalled();
expect(mocks.asset.update).not.toHaveBeenCalled();
});
});
});