chore: remove asset stubs (#26187)

This commit is contained in:
Daniel Dietzler
2026-02-13 17:00:31 +01:00
committed by GitHub
parent ecb09501a5
commit 7cb355279e
18 changed files with 624 additions and 655 deletions
@@ -1,16 +1,14 @@
import { Stats } from 'node:fs';
import { defaults, SystemConfig } from 'src/config';
import { AssetPathType, JobStatus } from 'src/enum';
import { AssetPathType, AssetType, JobStatus } from 'src/enum';
import { StorageTemplateService } from 'src/services/storage-template.service';
import { AlbumFactory } from 'test/factories/album.factory';
import { assetStub } from 'test/fixtures/asset.stub';
import { AssetFactory } from 'test/factories/asset.factory';
import { UserFactory } from 'test/factories/user.factory';
import { userStub } from 'test/fixtures/user.stub';
import { factory } from 'test/small.factory';
import { getForStorageTemplate } from 'test/mappers';
import { makeStream, newTestService, ServiceMocks } from 'test/utils';
const motionAsset = assetStub.storageAsset({});
const stillAsset = assetStub.storageAsset({ livePhotoVideoId: motionAsset.id });
describe(StorageTemplateService.name, () => {
let sut: StorageTemplateService;
let mocks: ServiceMocks;
@@ -110,12 +108,27 @@ describe(StorageTemplateService.name, () => {
});
it('should migrate single moving picture', async () => {
const motionAsset = AssetFactory.from({
type: AssetType.Video,
fileCreatedAt: new Date('2022-06-19T23:41:36.910Z'),
})
.exif()
.build();
const stillAsset = AssetFactory.from({
livePhotoVideoId: motionAsset.id,
fileCreatedAt: new Date('2022-06-19T23:41:36.910Z'),
})
.exif()
.build();
mocks.user.get.mockResolvedValue(userStub.user1);
const newMotionPicturePath = `/data/library/${motionAsset.ownerId}/2022/2022-06-19/${motionAsset.originalFileName}`;
const newMotionPicturePath = `/data/library/${motionAsset.ownerId}/2022/2022-06-19/${stillAsset.originalFileName.slice(0, -4)}.mp4`;
const newStillPicturePath = `/data/library/${stillAsset.ownerId}/2022/2022-06-19/${stillAsset.originalFileName}`;
mocks.assetJob.getForStorageTemplateJob.mockResolvedValueOnce(stillAsset);
mocks.assetJob.getForStorageTemplateJob.mockResolvedValueOnce(motionAsset);
mocks.assetJob.getForStorageTemplateJob.mockResolvedValueOnce(getForStorageTemplate(stillAsset));
mocks.assetJob.getForStorageTemplateJob.mockResolvedValueOnce(getForStorageTemplate(motionAsset));
mocks.move.create.mockResolvedValueOnce({
id: '123',
@@ -141,8 +154,8 @@ describe(StorageTemplateService.name, () => {
});
it('should use handlebar if condition for album', async () => {
const asset = assetStub.storageAsset();
const user = userStub.user1;
const user = UserFactory.create();
const asset = AssetFactory.from().owner(user).exif().build();
const album = AlbumFactory.from().asset().build();
const config = structuredClone(defaults);
config.storageTemplate.template = '{{y}}/{{#if album}}{{album}}{{else}}other/{{MM}}{{/if}}/{{filename}}';
@@ -150,7 +163,7 @@ describe(StorageTemplateService.name, () => {
sut.onConfigInit({ newConfig: config });
mocks.user.get.mockResolvedValue(user);
mocks.assetJob.getForStorageTemplateJob.mockResolvedValueOnce(asset);
mocks.assetJob.getForStorageTemplateJob.mockResolvedValueOnce(getForStorageTemplate(asset));
mocks.album.getByAssetId.mockResolvedValueOnce([album]);
expect(await sut.handleMigrationSingle({ id: asset.id })).toBe(JobStatus.Success);
@@ -166,14 +179,14 @@ describe(StorageTemplateService.name, () => {
});
it('should use handlebar else condition for album', async () => {
const asset = assetStub.storageAsset();
const user = userStub.user1;
const user = UserFactory.create();
const asset = AssetFactory.from().owner(user).exif().build();
const config = structuredClone(defaults);
config.storageTemplate.template = '{{y}}/{{#if album}}{{album}}{{else}}other//{{MM}}{{/if}}/{{filename}}';
sut.onConfigInit({ newConfig: config });
mocks.user.get.mockResolvedValue(user);
mocks.assetJob.getForStorageTemplateJob.mockResolvedValueOnce(asset);
mocks.assetJob.getForStorageTemplateJob.mockResolvedValueOnce(getForStorageTemplate(asset));
expect(await sut.handleMigrationSingle({ id: asset.id })).toBe(JobStatus.Success);
@@ -189,8 +202,8 @@ describe(StorageTemplateService.name, () => {
});
it('should handle album startDate', async () => {
const asset = assetStub.storageAsset();
const user = userStub.user1;
const user = UserFactory.create();
const asset = AssetFactory.from().owner(user).exif().build();
const album = AlbumFactory.from().asset().build();
const config = structuredClone(defaults);
config.storageTemplate.template =
@@ -199,7 +212,7 @@ describe(StorageTemplateService.name, () => {
sut.onConfigInit({ newConfig: config });
mocks.user.get.mockResolvedValue(user);
mocks.assetJob.getForStorageTemplateJob.mockResolvedValueOnce(asset);
mocks.assetJob.getForStorageTemplateJob.mockResolvedValueOnce(getForStorageTemplate(asset));
mocks.album.getByAssetId.mockResolvedValueOnce([album]);
mocks.album.getMetadataForIds.mockResolvedValueOnce([
{
@@ -225,8 +238,8 @@ describe(StorageTemplateService.name, () => {
});
it('should handle else condition from album startDate', async () => {
const asset = assetStub.storageAsset();
const user = userStub.user1;
const user = UserFactory.create();
const asset = AssetFactory.from().owner(user).exif().build();
const config = structuredClone(defaults);
config.storageTemplate.template =
'{{#if album}}{{album-startDate-y}}/{{album-startDate-MM}} - {{album}}{{else}}{{y}}/{{MM}}/{{/if}}/{{filename}}';
@@ -234,7 +247,7 @@ describe(StorageTemplateService.name, () => {
sut.onConfigInit({ newConfig: config });
mocks.user.get.mockResolvedValue(user);
mocks.assetJob.getForStorageTemplateJob.mockResolvedValueOnce(asset);
mocks.assetJob.getForStorageTemplateJob.mockResolvedValueOnce(getForStorageTemplate(asset));
expect(await sut.handleMigrationSingle({ id: asset.id })).toBe(JobStatus.Success);
@@ -248,11 +261,18 @@ describe(StorageTemplateService.name, () => {
});
it('should migrate previously failed move from original path when it still exists', async () => {
mocks.user.get.mockResolvedValue(userStub.user1);
const user = UserFactory.create();
const asset = AssetFactory.from({
fileCreatedAt: new Date('2022-06-19T23:41:36.910Z'),
})
.owner(user)
.exif()
.build();
const asset = assetStub.storageAsset();
const previousFailedNewPath = `/data/library/${userStub.user1.id}/2023/Feb/${asset.originalFileName}`;
const newPath = `/data/library/${userStub.user1.id}/2022/2022-06-19/${asset.originalFileName}`;
mocks.user.get.mockResolvedValue(user);
const previousFailedNewPath = `/data/library/${user.id}/2023/Feb/${asset.originalFileName}`;
const newPath = `/data/library/${user.id}/2022/2022-06-19/${asset.originalFileName}`;
mocks.storage.checkFileExists.mockImplementation((path) => Promise.resolve(path === asset.originalPath));
mocks.move.getByEntity.mockResolvedValue({
@@ -262,7 +282,7 @@ describe(StorageTemplateService.name, () => {
oldPath: asset.originalPath,
newPath: previousFailedNewPath,
});
mocks.assetJob.getForStorageTemplateJob.mockResolvedValue(asset);
mocks.assetJob.getForStorageTemplateJob.mockResolvedValue(getForStorageTemplate(asset));
mocks.move.update.mockResolvedValue({
id: '123',
entityId: asset.id,
@@ -288,9 +308,16 @@ describe(StorageTemplateService.name, () => {
});
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 () => {
mocks.user.get.mockResolvedValue(userStub.user1);
const user = UserFactory.create();
const asset = AssetFactory.from({
fileCreatedAt: new Date('2022-06-19T23:41:36.910Z'),
})
.owner(user)
.exif({ fileSizeInByte: 5000 })
.build();
mocks.user.get.mockResolvedValue(user);
const asset = assetStub.storageAsset({ fileSizeInByte: 5000 });
const previousFailedNewPath = `/data/library/${asset.ownerId}/2022/June/${asset.originalFileName}`;
const newPath = `/data/library/${asset.ownerId}/2022/2022-06-19/${asset.originalFileName}`;
@@ -304,7 +331,7 @@ describe(StorageTemplateService.name, () => {
oldPath: asset.originalPath,
newPath: previousFailedNewPath,
});
mocks.assetJob.getForStorageTemplateJob.mockResolvedValue(asset);
mocks.assetJob.getForStorageTemplateJob.mockResolvedValue(getForStorageTemplate(asset));
mocks.move.update.mockResolvedValue({
id: '123',
entityId: asset.id,
@@ -325,45 +352,53 @@ describe(StorageTemplateService.name, () => {
});
it('should fail move if copying and hash of asset and the new file do not match', async () => {
mocks.user.get.mockResolvedValue(userStub.user1);
const newPath = `/data/library/${userStub.user1.id}/2022/2022-06-19/${testAsset.originalFileName}`;
const user = UserFactory.create();
const asset = AssetFactory.from({
fileCreatedAt: new Date('2022-06-19T23:41:36.910Z'),
})
.owner(user)
.exif()
.build();
mocks.user.get.mockResolvedValue(user);
const newPath = `/data/library/${user.id}/2022/2022-06-19/${asset.originalFileName}`;
mocks.storage.rename.mockRejectedValue({ code: 'EXDEV' });
mocks.storage.stat.mockResolvedValue({ size: 5000 } as Stats);
mocks.crypto.hashFile.mockResolvedValue(Buffer.from('different-hash', 'utf8'));
mocks.assetJob.getForStorageTemplateJob.mockResolvedValue(testAsset);
mocks.assetJob.getForStorageTemplateJob.mockResolvedValue(getForStorageTemplate(asset));
mocks.move.create.mockResolvedValue({
id: '123',
entityId: testAsset.id,
entityId: asset.id,
pathType: AssetPathType.Original,
oldPath: testAsset.originalPath,
oldPath: asset.originalPath,
newPath,
});
await expect(sut.handleMigrationSingle({ id: testAsset.id })).resolves.toBe(JobStatus.Success);
await expect(sut.handleMigrationSingle({ id: asset.id })).resolves.toBe(JobStatus.Success);
expect(mocks.assetJob.getForStorageTemplateJob).toHaveBeenCalledWith(testAsset.id);
expect(mocks.assetJob.getForStorageTemplateJob).toHaveBeenCalledWith(asset.id);
expect(mocks.storage.checkFileExists).toHaveBeenCalledTimes(1);
expect(mocks.storage.stat).toHaveBeenCalledWith(newPath);
expect(mocks.move.create).toHaveBeenCalledWith({
entityId: testAsset.id,
entityId: asset.id,
pathType: AssetPathType.Original,
oldPath: testAsset.originalPath,
oldPath: asset.originalPath,
newPath,
});
expect(mocks.storage.rename).toHaveBeenCalledWith(testAsset.originalPath, newPath);
expect(mocks.storage.copyFile).toHaveBeenCalledWith(testAsset.originalPath, newPath);
expect(mocks.storage.rename).toHaveBeenCalledWith(asset.originalPath, newPath);
expect(mocks.storage.copyFile).toHaveBeenCalledWith(asset.originalPath, newPath);
expect(mocks.storage.unlink).toHaveBeenCalledWith(newPath);
expect(mocks.storage.unlink).toHaveBeenCalledTimes(1);
expect(mocks.asset.update).not.toHaveBeenCalled();
});
const testAsset = assetStub.storageAsset();
const testAsset = AssetFactory.from().exif({ fileSizeInByte: 12_345 }).build();
it.each`
failedPathChecksum | failedPathSize | reason
${testAsset.checksum} | ${500} | ${'file size'}
${Buffer.from('bad checksum', 'utf8')} | ${testAsset.fileSizeInByte} | ${'checksum'}
failedPathChecksum | failedPathSize | reason
${testAsset.checksum} | ${500} | ${'file size'}
${Buffer.from('bad checksum', 'utf8')} | ${testAsset.exifInfo.fileSizeInByte} | ${'checksum'}
`(
'should fail to migrate previously failed move from previous new path when old path no longer exists if $reason validation fails',
async ({ failedPathChecksum, failedPathSize }) => {
@@ -381,7 +416,7 @@ describe(StorageTemplateService.name, () => {
oldPath: testAsset.originalPath,
newPath: previousFailedNewPath,
});
mocks.assetJob.getForStorageTemplateJob.mockResolvedValue(testAsset);
mocks.assetJob.getForStorageTemplateJob.mockResolvedValue(getForStorageTemplate(testAsset));
mocks.move.update.mockResolvedValue({
id: '123',
entityId: testAsset.id,
@@ -414,12 +449,17 @@ describe(StorageTemplateService.name, () => {
});
it('should handle an asset with a duplicate destination', async () => {
const asset = assetStub.storageAsset();
const asset = AssetFactory.from({
fileCreatedAt: new Date('2022-06-19T23:41:36.910Z'),
})
.exif()
.build();
const oldPath = asset.originalPath;
const newPath = `/data/library/user-id/2022/2022-06-19/${asset.originalFileName}`;
const newPath = `/data/library/${asset.ownerId}/2022/2022-06-19/${asset.originalFileName}`;
const newPath2 = newPath.replace('.jpg', '+1.jpg');
mocks.assetJob.streamForStorageTemplateJob.mockReturnValue(makeStream([asset]));
mocks.assetJob.streamForStorageTemplateJob.mockReturnValue(makeStream([getForStorageTemplate(asset)]));
mocks.user.getList.mockResolvedValue([userStub.user1]);
mocks.move.create.mockResolvedValue({
id: '123',
@@ -441,9 +481,13 @@ describe(StorageTemplateService.name, () => {
});
it('should skip when an asset already matches the template', async () => {
const asset = assetStub.storageAsset({ originalPath: '/data/library/user-id/2023/2023-02-23/asset-id.jpg' });
const asset = AssetFactory.from({
originalPath: '/data/library/user-id/2023/2023-02-23/asset-id.jpg',
})
.exif()
.build();
mocks.assetJob.streamForStorageTemplateJob.mockReturnValue(makeStream([asset]));
mocks.assetJob.streamForStorageTemplateJob.mockReturnValue(makeStream([getForStorageTemplate(asset)]));
mocks.user.getList.mockResolvedValue([userStub.user1]);
await sut.handleMigration();
@@ -456,9 +500,13 @@ describe(StorageTemplateService.name, () => {
});
it('should skip when an asset is probably a duplicate', async () => {
const asset = assetStub.storageAsset({ originalPath: '/data/library/user-id/2023/2023-02-23/asset-id+1.jpg' });
const asset = AssetFactory.from({
originalPath: '/data/library/user-id/2023/2023-02-23/asset-id+1.jpg',
})
.exif()
.build();
mocks.assetJob.streamForStorageTemplateJob.mockReturnValue(makeStream([asset]));
mocks.assetJob.streamForStorageTemplateJob.mockReturnValue(makeStream([getForStorageTemplate(asset)]));
mocks.user.getList.mockResolvedValue([userStub.user1]);
await sut.handleMigration();
@@ -471,10 +519,15 @@ describe(StorageTemplateService.name, () => {
});
it('should move an asset', async () => {
const asset = assetStub.storageAsset();
const asset = AssetFactory.from({
fileCreatedAt: new Date('2022-06-19T23:41:36.910Z'),
})
.exif()
.build();
const oldPath = asset.originalPath;
const newPath = `/data/library/user-id/2022/2022-06-19/${asset.originalFileName}`;
mocks.assetJob.streamForStorageTemplateJob.mockReturnValue(makeStream([asset]));
const newPath = `/data/library/${asset.ownerId}/2022/2022-06-19/${asset.originalFileName}`;
mocks.assetJob.streamForStorageTemplateJob.mockReturnValue(makeStream([getForStorageTemplate(asset)]));
mocks.user.getList.mockResolvedValue([userStub.user1]);
mocks.move.create.mockResolvedValue({
id: '123',
@@ -492,9 +545,15 @@ describe(StorageTemplateService.name, () => {
});
it('should use the user storage label', async () => {
const user = factory.userAdmin({ storageLabel: 'label-1' });
const asset = assetStub.storageAsset({ ownerId: user.id });
mocks.assetJob.streamForStorageTemplateJob.mockReturnValue(makeStream([asset]));
const user = UserFactory.create({ storageLabel: 'label-1' });
const asset = AssetFactory.from({
fileCreatedAt: new Date('2022-06-19T23:41:36.910Z'),
})
.owner(user)
.exif()
.build();
mocks.assetJob.streamForStorageTemplateJob.mockReturnValue(makeStream([getForStorageTemplate(asset)]));
mocks.user.getList.mockResolvedValue([user]);
mocks.move.create.mockResolvedValue({
id: '123',
@@ -508,7 +567,7 @@ describe(StorageTemplateService.name, () => {
expect(mocks.assetJob.streamForStorageTemplateJob).toHaveBeenCalled();
expect(mocks.storage.rename).toHaveBeenCalledWith(
'/original/path.jpg',
asset.originalPath,
expect.stringContaining(`/data/library/${user.storageLabel}/2022/2022-06-19/${asset.originalFileName}`),
);
expect(mocks.asset.update).toHaveBeenCalledWith({
@@ -520,10 +579,16 @@ describe(StorageTemplateService.name, () => {
});
it('should copy the file if rename fails due to EXDEV (rename across filesystems)', async () => {
const asset = assetStub.storageAsset({ originalPath: '/path/to/original.jpg', fileSizeInByte: 5000 });
const asset = AssetFactory.from({
fileCreatedAt: new Date('2022-06-19T23:41:36.910Z'),
originalPath: '/path/to/original.jpg',
})
.exif({ fileSizeInByte: 5000 })
.build();
const oldPath = asset.originalPath;
const newPath = `/data/library/user-id/2022/2022-06-19/${asset.originalFileName}`;
mocks.assetJob.streamForStorageTemplateJob.mockReturnValue(makeStream([asset]));
const newPath = `/data/library/${asset.ownerId}/2022/2022-06-19/${asset.originalFileName}`;
mocks.assetJob.streamForStorageTemplateJob.mockReturnValue(makeStream([getForStorageTemplate(asset)]));
mocks.storage.rename.mockRejectedValue({ code: 'EXDEV' });
mocks.user.getList.mockResolvedValue([userStub.user1]);
mocks.move.create.mockResolvedValue({
@@ -561,10 +626,17 @@ describe(StorageTemplateService.name, () => {
});
it('should not update the database if the move fails due to incorrect newPath filesize', async () => {
const asset = assetStub.storageAsset();
mocks.assetJob.streamForStorageTemplateJob.mockReturnValue(makeStream([asset]));
const user = UserFactory.create();
const asset = AssetFactory.from({
fileCreatedAt: new Date('2022-06-19T23:41:36.910Z'),
})
.owner(user)
.exif()
.build();
mocks.assetJob.streamForStorageTemplateJob.mockReturnValue(makeStream([getForStorageTemplate(asset)]));
mocks.storage.rename.mockRejectedValue({ code: 'EXDEV' });
mocks.user.getList.mockResolvedValue([userStub.user1]);
mocks.user.getList.mockResolvedValue([user]);
mocks.move.create.mockResolvedValue({
id: '123',
entityId: asset.id,
@@ -580,22 +652,29 @@ describe(StorageTemplateService.name, () => {
expect(mocks.assetJob.streamForStorageTemplateJob).toHaveBeenCalled();
expect(mocks.storage.rename).toHaveBeenCalledWith(
'/original/path.jpg',
expect.stringContaining(`/data/library/user-id/2022/2022-06-19/${asset.originalFileName}`),
asset.originalPath,
expect.stringContaining(`/data/library/${user.id}/2022/2022-06-19/${asset.originalFileName}`),
);
expect(mocks.storage.copyFile).toHaveBeenCalledWith(
'/original/path.jpg',
expect.stringContaining(`/data/library/user-id/2022/2022-06-19/${asset.originalFileName}`),
asset.originalPath,
expect.stringContaining(`/data/library/${user.id}/2022/2022-06-19/${asset.originalFileName}`),
);
expect(mocks.storage.stat).toHaveBeenCalledWith(
expect.stringContaining(`/data/library/user-id/2022/2022-06-19/${asset.originalFileName}`),
expect.stringContaining(`/data/library/${user.id}/2022/2022-06-19/${asset.originalFileName}`),
);
expect(mocks.asset.update).not.toHaveBeenCalled();
});
it('should not update the database if the move fails', async () => {
const asset = assetStub.storageAsset();
mocks.assetJob.streamForStorageTemplateJob.mockReturnValue(makeStream([asset]));
const user = UserFactory.create();
const asset = AssetFactory.from({
fileCreatedAt: new Date('2022-06-19T23:41:36.910Z'),
})
.owner(user)
.exif()
.build();
mocks.assetJob.streamForStorageTemplateJob.mockReturnValue(makeStream([getForStorageTemplate(asset)]));
mocks.storage.rename.mockRejectedValue(new Error('Read only system'));
mocks.storage.copyFile.mockRejectedValue(new Error('Read only system'));
mocks.move.create.mockResolvedValue({
@@ -605,25 +684,37 @@ describe(StorageTemplateService.name, () => {
oldPath: asset.originalPath,
newPath: '',
});
mocks.user.getList.mockResolvedValue([userStub.user1]);
mocks.user.getList.mockResolvedValue([user]);
await sut.handleMigration();
expect(mocks.assetJob.streamForStorageTemplateJob).toHaveBeenCalled();
expect(mocks.storage.rename).toHaveBeenCalledWith(
'/original/path.jpg',
expect.stringContaining(`/data/library/user-id/2022/2022-06-19/${asset.originalFileName}`),
asset.originalPath,
expect.stringContaining(`/data/library/${user.id}/2022/2022-06-19/${asset.originalFileName}`),
);
expect(mocks.asset.update).not.toHaveBeenCalled();
});
it('should migrate live photo motion video alongside the still image', async () => {
const newMotionPicturePath = `/data/library/${motionAsset.ownerId}/2022/2022-06-19/${motionAsset.originalFileName}`;
const motionAsset = AssetFactory.from({
type: AssetType.Video,
fileCreatedAt: new Date('2022-06-19T23:41:36.910Z'),
})
.exif()
.build();
const stillAsset = AssetFactory.from({
livePhotoVideoId: motionAsset.id,
fileCreatedAt: new Date('2022-06-19T23:41:36.910Z'),
})
.exif()
.build();
const newMotionPicturePath = `/data/library/${motionAsset.ownerId}/2022/2022-06-19/${stillAsset.originalFileName.slice(0, -4)}.mp4`;
const newStillPicturePath = `/data/library/${stillAsset.ownerId}/2022/2022-06-19/${stillAsset.originalFileName}`;
mocks.assetJob.streamForStorageTemplateJob.mockReturnValue(makeStream([stillAsset]));
mocks.assetJob.streamForStorageTemplateJob.mockReturnValue(makeStream([getForStorageTemplate(stillAsset)]));
mocks.user.getList.mockResolvedValue([userStub.user1]);
mocks.assetJob.getForStorageTemplateJob.mockResolvedValueOnce(motionAsset);
mocks.assetJob.getForStorageTemplateJob.mockResolvedValueOnce(getForStorageTemplate(motionAsset));
mocks.move.create.mockResolvedValueOnce({
id: '123',
@@ -653,13 +744,17 @@ describe(StorageTemplateService.name, () => {
describe('file rename correctness', () => {
it('should not create double extensions when filename has lower extension', async () => {
const user = factory.userAdmin({ storageLabel: 'label-1' });
const asset = assetStub.storageAsset({
ownerId: user.id,
const user = UserFactory.create({ storageLabel: 'label-1' });
const asset = AssetFactory.from({
fileCreatedAt: new Date('2022-06-19T23:41:36.910Z'),
originalPath: `/data/library/${user.id}/2022/2022-06-19/IMG_7065.heic`,
originalFileName: 'IMG_7065.HEIC',
});
mocks.assetJob.streamForStorageTemplateJob.mockReturnValue(makeStream([asset]));
})
.owner(user)
.exif()
.build();
mocks.assetJob.streamForStorageTemplateJob.mockReturnValue(makeStream([getForStorageTemplate(asset)]));
mocks.user.getList.mockResolvedValue([user]);
mocks.move.create.mockResolvedValue({
id: '123',
@@ -679,13 +774,17 @@ describe(StorageTemplateService.name, () => {
});
it('should not create double extensions when filename has uppercase extension', async () => {
const user = factory.userAdmin();
const asset = assetStub.storageAsset({
ownerId: user.id,
const user = UserFactory.create();
const asset = AssetFactory.from({
fileCreatedAt: new Date('2022-06-19T23:41:36.910Z'),
originalPath: `/data/library/${user.id}/2022/2022-06-19/IMG_7065.HEIC`,
originalFileName: 'IMG_7065.HEIC',
});
mocks.assetJob.streamForStorageTemplateJob.mockReturnValue(makeStream([asset]));
})
.owner(user)
.exif({ fileSizeInByte: 12_345 })
.build();
mocks.assetJob.streamForStorageTemplateJob.mockReturnValue(makeStream([getForStorageTemplate(asset)]));
mocks.user.getList.mockResolvedValue([user]);
mocks.move.create.mockResolvedValue({
id: '123',
@@ -705,13 +804,17 @@ describe(StorageTemplateService.name, () => {
});
it('should normalize the filename to lowercase (JPEG > jpg)', async () => {
const user = factory.userAdmin();
const asset = assetStub.storageAsset({
ownerId: user.id,
const user = UserFactory.create();
const asset = AssetFactory.from({
fileCreatedAt: new Date('2022-06-19T23:41:36.910Z'),
originalPath: `/data/library/${user.id}/2022/2022-06-19/IMG_7065.JPEG`,
originalFileName: 'IMG_7065.JPEG',
});
mocks.assetJob.streamForStorageTemplateJob.mockReturnValue(makeStream([asset]));
})
.owner(user)
.exif()
.build();
mocks.assetJob.streamForStorageTemplateJob.mockReturnValue(makeStream([getForStorageTemplate(asset)]));
mocks.user.getList.mockResolvedValue([user]);
mocks.move.create.mockResolvedValue({
id: '123',
@@ -731,13 +834,17 @@ describe(StorageTemplateService.name, () => {
});
it('should normalize the filename to lowercase (JPG > jpg)', async () => {
const user = factory.userAdmin();
const asset = assetStub.storageAsset({
ownerId: user.id,
const user = UserFactory.create();
const asset = AssetFactory.from({
fileCreatedAt: new Date('2022-06-19T23:41:36.910Z'),
originalPath: '/data/library/user-id/2022/2022-06-19/IMG_7065.JPG',
originalFileName: 'IMG_7065.JPG',
});
mocks.assetJob.streamForStorageTemplateJob.mockReturnValue(makeStream([asset]));
})
.owner(user)
.exif()
.build();
mocks.assetJob.streamForStorageTemplateJob.mockReturnValue(makeStream([getForStorageTemplate(asset)]));
mocks.user.getList.mockResolvedValue([user]);
mocks.move.create.mockResolvedValue({
id: '123',