2023-09-27 20:44:51 +02:00
|
|
|
import {
|
|
|
|
assetStub,
|
|
|
|
newAlbumRepositoryMock,
|
|
|
|
newAssetRepositoryMock,
|
|
|
|
newCryptoRepositoryMock,
|
|
|
|
newJobRepositoryMock,
|
|
|
|
newMetadataRepositoryMock,
|
|
|
|
newStorageRepositoryMock,
|
|
|
|
newSystemConfigRepositoryMock,
|
|
|
|
} from '@test';
|
2023-06-16 21:54:17 +02:00
|
|
|
import { constants } from 'fs/promises';
|
2023-09-27 20:44:51 +02:00
|
|
|
import { IAlbumRepository } from '../album';
|
2023-09-04 21:45:59 +02:00
|
|
|
import { IAssetRepository, WithProperty, WithoutProperty } from '../asset';
|
2023-09-27 20:44:51 +02:00
|
|
|
import { ICryptoRepository } from '../crypto';
|
2023-05-26 14:52:52 +02:00
|
|
|
import { IJobRepository, JobName } from '../job';
|
|
|
|
import { IStorageRepository } from '../storage';
|
2023-09-27 20:44:51 +02:00
|
|
|
import { ISystemConfigRepository } from '../system-config';
|
|
|
|
import { IMetadataRepository } from './metadata.repository';
|
2023-05-26 14:52:52 +02:00
|
|
|
import { MetadataService } from './metadata.service';
|
|
|
|
|
|
|
|
describe(MetadataService.name, () => {
|
2023-09-27 20:44:51 +02:00
|
|
|
let albumMock: jest.Mocked<IAlbumRepository>;
|
2023-05-26 14:52:52 +02:00
|
|
|
let assetMock: jest.Mocked<IAssetRepository>;
|
2023-09-27 20:44:51 +02:00
|
|
|
let configMock: jest.Mocked<ISystemConfigRepository>;
|
|
|
|
let cryptoRepository: jest.Mocked<ICryptoRepository>;
|
2023-05-26 14:52:52 +02:00
|
|
|
let jobMock: jest.Mocked<IJobRepository>;
|
2023-09-27 20:44:51 +02:00
|
|
|
let metadataMock: jest.Mocked<IMetadataRepository>;
|
2023-05-26 14:52:52 +02:00
|
|
|
let storageMock: jest.Mocked<IStorageRepository>;
|
2023-09-27 20:44:51 +02:00
|
|
|
let sut: MetadataService;
|
2023-05-26 14:52:52 +02:00
|
|
|
|
|
|
|
beforeEach(async () => {
|
2023-09-27 20:44:51 +02:00
|
|
|
albumMock = newAlbumRepositoryMock();
|
2023-05-26 14:52:52 +02:00
|
|
|
assetMock = newAssetRepositoryMock();
|
2023-09-27 20:44:51 +02:00
|
|
|
configMock = newSystemConfigRepositoryMock();
|
|
|
|
cryptoRepository = newCryptoRepositoryMock();
|
2023-05-26 14:52:52 +02:00
|
|
|
jobMock = newJobRepositoryMock();
|
2023-09-27 20:44:51 +02:00
|
|
|
metadataMock = newMetadataRepositoryMock();
|
2023-05-26 14:52:52 +02:00
|
|
|
storageMock = newStorageRepositoryMock();
|
|
|
|
|
2023-09-27 20:44:51 +02:00
|
|
|
sut = new MetadataService(albumMock, assetMock, cryptoRepository, jobMock, metadataMock, storageMock, configMock);
|
2023-05-26 14:52:52 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
it('should be defined', () => {
|
|
|
|
expect(sut).toBeDefined();
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('handleQueueSidecar', () => {
|
|
|
|
it('should queue assets with sidecar files', async () => {
|
2023-08-01 03:28:07 +02:00
|
|
|
assetMock.getWith.mockResolvedValue({ items: [assetStub.sidecar], hasNextPage: false });
|
2023-05-26 14:52:52 +02:00
|
|
|
|
|
|
|
await sut.handleQueueSidecar({ force: true });
|
|
|
|
|
|
|
|
expect(assetMock.getWith).toHaveBeenCalledWith({ take: 1000, skip: 0 }, WithProperty.SIDECAR);
|
|
|
|
expect(assetMock.getWithout).not.toHaveBeenCalled();
|
|
|
|
expect(jobMock.queue).toHaveBeenCalledWith({
|
|
|
|
name: JobName.SIDECAR_SYNC,
|
2023-08-01 03:28:07 +02:00
|
|
|
data: { id: assetStub.sidecar.id },
|
2023-05-26 14:52:52 +02:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should queue assets without sidecar files', async () => {
|
2023-08-01 03:28:07 +02:00
|
|
|
assetMock.getWithout.mockResolvedValue({ items: [assetStub.image], hasNextPage: false });
|
2023-05-26 14:52:52 +02:00
|
|
|
|
|
|
|
await sut.handleQueueSidecar({ force: false });
|
|
|
|
|
|
|
|
expect(assetMock.getWithout).toHaveBeenCalledWith({ take: 1000, skip: 0 }, WithoutProperty.SIDECAR);
|
|
|
|
expect(assetMock.getWith).not.toHaveBeenCalled();
|
|
|
|
expect(jobMock.queue).toHaveBeenCalledWith({
|
|
|
|
name: JobName.SIDECAR_DISCOVERY,
|
2023-08-01 03:28:07 +02:00
|
|
|
data: { id: assetStub.image.id },
|
2023-05-26 14:52:52 +02:00
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('handleSidecarSync', () => {
|
2023-05-26 21:43:24 +02:00
|
|
|
it('should not error', async () => {
|
|
|
|
await sut.handleSidecarSync();
|
2023-05-26 14:52:52 +02:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('handleSidecarDiscovery', () => {
|
|
|
|
it('should skip hidden assets', async () => {
|
2023-08-01 03:28:07 +02:00
|
|
|
assetMock.getByIds.mockResolvedValue([assetStub.livePhotoMotionAsset]);
|
|
|
|
await sut.handleSidecarDiscovery({ id: assetStub.livePhotoMotionAsset.id });
|
2023-05-26 14:52:52 +02:00
|
|
|
expect(storageMock.checkFileExists).not.toHaveBeenCalled();
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should skip assets with a sidecar path', async () => {
|
2023-08-01 03:28:07 +02:00
|
|
|
assetMock.getByIds.mockResolvedValue([assetStub.sidecar]);
|
|
|
|
await sut.handleSidecarDiscovery({ id: assetStub.sidecar.id });
|
2023-05-26 14:52:52 +02:00
|
|
|
expect(storageMock.checkFileExists).not.toHaveBeenCalled();
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should do nothing when a sidecar is not found ', async () => {
|
2023-08-01 03:28:07 +02:00
|
|
|
assetMock.getByIds.mockResolvedValue([assetStub.image]);
|
2023-05-26 14:52:52 +02:00
|
|
|
storageMock.checkFileExists.mockResolvedValue(false);
|
2023-08-01 03:28:07 +02:00
|
|
|
await sut.handleSidecarDiscovery({ id: assetStub.image.id });
|
2023-05-26 14:52:52 +02:00
|
|
|
expect(assetMock.save).not.toHaveBeenCalled();
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should update a image asset when a sidecar is found', async () => {
|
2023-08-01 03:28:07 +02:00
|
|
|
assetMock.getByIds.mockResolvedValue([assetStub.image]);
|
|
|
|
assetMock.save.mockResolvedValue(assetStub.image);
|
2023-05-26 14:52:52 +02:00
|
|
|
storageMock.checkFileExists.mockResolvedValue(true);
|
2023-08-01 03:28:07 +02:00
|
|
|
await sut.handleSidecarDiscovery({ id: assetStub.image.id });
|
2023-07-10 19:56:45 +02:00
|
|
|
expect(storageMock.checkFileExists).toHaveBeenCalledWith('/original/path.jpg.xmp', constants.R_OK);
|
2023-05-26 14:52:52 +02:00
|
|
|
expect(assetMock.save).toHaveBeenCalledWith({
|
2023-08-01 03:28:07 +02:00
|
|
|
id: assetStub.image.id,
|
2023-07-10 19:56:45 +02:00
|
|
|
sidecarPath: '/original/path.jpg.xmp',
|
2023-05-26 14:52:52 +02:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should update a video asset when a sidecar is found', async () => {
|
2023-08-01 03:28:07 +02:00
|
|
|
assetMock.getByIds.mockResolvedValue([assetStub.video]);
|
|
|
|
assetMock.save.mockResolvedValue(assetStub.video);
|
2023-05-26 14:52:52 +02:00
|
|
|
storageMock.checkFileExists.mockResolvedValue(true);
|
2023-08-01 03:28:07 +02:00
|
|
|
await sut.handleSidecarDiscovery({ id: assetStub.video.id });
|
2023-06-30 18:25:08 +02:00
|
|
|
expect(storageMock.checkFileExists).toHaveBeenCalledWith('/original/path.ext.xmp', constants.R_OK);
|
2023-05-26 14:52:52 +02:00
|
|
|
expect(assetMock.save).toHaveBeenCalledWith({
|
2023-08-01 03:28:07 +02:00
|
|
|
id: assetStub.image.id,
|
2023-05-26 14:52:52 +02:00
|
|
|
sidecarPath: '/original/path.ext.xmp',
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
2023-09-29 17:42:33 +02:00
|
|
|
|
|
|
|
describe('handleMetadataExtraction', () => {
|
|
|
|
it('should handle lists of numbers', async () => {
|
|
|
|
assetMock.getByIds.mockResolvedValue([assetStub.image1]);
|
|
|
|
storageMock.stat.mockResolvedValue({ size: 123456 } as any);
|
|
|
|
metadataMock.getExifTags.mockResolvedValue({ ISO: [160] as any });
|
|
|
|
await sut.handleMetadataExtraction({ id: assetStub.image1.id });
|
|
|
|
expect(assetMock.upsertExif).toHaveBeenCalledWith(expect.objectContaining({ iso: 160 }));
|
|
|
|
});
|
|
|
|
});
|
2023-05-26 14:52:52 +02:00
|
|
|
});
|