chore: upgrade to kysely 0.28.11 (#26744)

This commit is contained in:
Daniel Dietzler
2026-03-11 16:17:31 +01:00
committed by GitHub
parent 8764a1894b
commit 34ce68095d
50 changed files with 941 additions and 857 deletions
+76 -72
View File
@@ -19,6 +19,7 @@ import { AssetFactory } from 'test/factories/asset.factory';
import { PersonFactory } from 'test/factories/person.factory';
import { probeStub } from 'test/fixtures/media.stub';
import { tagStub } from 'test/fixtures/tag.stub';
import { getForMetadataExtraction, getForSidecarWrite } from 'test/mappers';
import { factory } from 'test/small.factory';
import { makeStream, newTestService, ServiceMocks } from 'test/utils';
@@ -176,7 +177,7 @@ describe(MetadataService.name, () => {
const originalDate = new Date('2023-11-21T16:13:17.517Z');
const sidecarDate = new Date('2022-01-01T00:00:00.000Z');
const asset = AssetFactory.from().file({ type: AssetFileType.Sidecar }).build();
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(asset);
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(getForMetadataExtraction(asset));
mockReadTags({ CreationDate: originalDate.toISOString() }, { CreationDate: sidecarDate.toISOString() });
await sut.handleMetadataExtraction({ id: asset.id });
@@ -198,7 +199,7 @@ describe(MetadataService.name, () => {
const fileCreatedAt = new Date('2022-01-01T00:00:00.000Z');
const fileModifiedAt = new Date('2021-01-01T00:00:00.000Z');
const asset = AssetFactory.create();
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(asset);
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(getForMetadataExtraction(asset));
mocks.storage.stat.mockResolvedValue({
size: 123_456,
mtime: fileModifiedAt,
@@ -228,7 +229,7 @@ describe(MetadataService.name, () => {
const fileCreatedAt = new Date('2021-01-01T00:00:00.000Z');
const fileModifiedAt = new Date('2022-01-01T00:00:00.000Z');
const asset = AssetFactory.create();
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(asset);
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(getForMetadataExtraction(asset));
mocks.storage.stat.mockResolvedValue({
size: 123_456,
mtime: fileModifiedAt,
@@ -257,7 +258,7 @@ describe(MetadataService.name, () => {
it('should determine dateTimeOriginal regardless of the server time zone', async () => {
process.env.TZ = 'America/Los_Angeles';
const asset = AssetFactory.create();
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(asset);
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(getForMetadataExtraction(asset));
mockReadTags({ DateTimeOriginal: '2022:01:01 00:00:00' });
await sut.handleMetadataExtraction({ id: asset.id });
@@ -277,7 +278,7 @@ describe(MetadataService.name, () => {
it('should handle lists of numbers', async () => {
const asset = AssetFactory.create();
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(asset);
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(getForMetadataExtraction(asset));
mocks.storage.stat.mockResolvedValue({
size: 123_456,
mtime: asset.fileModifiedAt,
@@ -305,7 +306,7 @@ describe(MetadataService.name, () => {
it('should not delete latituide and longitude without reverse geocode', async () => {
// regression test for issue 17511
const asset = AssetFactory.from().exif().build();
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(asset);
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(getForMetadataExtraction(asset));
mocks.systemMetadata.get.mockResolvedValue({ reverseGeocoding: { enabled: false } });
mocks.storage.stat.mockResolvedValue({
size: 123_456,
@@ -337,7 +338,7 @@ describe(MetadataService.name, () => {
it('should apply reverse geocoding', async () => {
const asset = AssetFactory.from().exif({ latitude: 10, longitude: 20 }).build();
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(asset);
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(getForMetadataExtraction(asset));
mocks.systemMetadata.get.mockResolvedValue({ reverseGeocoding: { enabled: true } });
mocks.map.reverseGeocode.mockResolvedValue({ city: 'City', state: 'State', country: 'Country' });
mocks.storage.stat.mockResolvedValue({
@@ -367,7 +368,7 @@ describe(MetadataService.name, () => {
it('should discard latitude and longitude on null island', async () => {
const asset = AssetFactory.create();
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(asset);
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(getForMetadataExtraction(asset));
mockReadTags({
GPSLatitude: 0,
GPSLongitude: 0,
@@ -383,7 +384,7 @@ describe(MetadataService.name, () => {
it('should extract tags from TagsList', async () => {
const asset = AssetFactory.create();
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(asset);
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(getForMetadataExtraction(asset));
mocks.asset.getForMetadataExtractionTags.mockResolvedValue({ tags: ['Parent'] });
mockReadTags({ TagsList: ['Parent'] });
mocks.tag.upsertValue.mockResolvedValue(tagStub.parentUpsert);
@@ -395,7 +396,7 @@ describe(MetadataService.name, () => {
it('should extract hierarchy from TagsList', async () => {
const asset = AssetFactory.create();
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(asset);
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(getForMetadataExtraction(asset));
mocks.asset.getForMetadataExtractionTags.mockResolvedValue({ tags: ['Parent/Child'] });
mockReadTags({ TagsList: ['Parent/Child'] });
mocks.tag.upsertValue.mockResolvedValueOnce(tagStub.parentUpsert);
@@ -417,7 +418,7 @@ describe(MetadataService.name, () => {
it('should extract tags from Keywords as a string', async () => {
const asset = AssetFactory.create();
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(asset);
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(getForMetadataExtraction(asset));
mocks.asset.getForMetadataExtractionTags.mockResolvedValue({ tags: ['Parent'] });
mockReadTags({ Keywords: 'Parent' });
mocks.tag.upsertValue.mockResolvedValue(tagStub.parentUpsert);
@@ -429,7 +430,7 @@ describe(MetadataService.name, () => {
it('should extract tags from Keywords as a list', async () => {
const asset = AssetFactory.create();
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(asset);
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(getForMetadataExtraction(asset));
mocks.asset.getForMetadataExtractionTags.mockResolvedValue({ tags: ['Parent'] });
mockReadTags({ Keywords: ['Parent'] });
mocks.tag.upsertValue.mockResolvedValue(tagStub.parentUpsert);
@@ -441,7 +442,7 @@ describe(MetadataService.name, () => {
it('should extract tags from Keywords as a list with a number', async () => {
const asset = AssetFactory.create();
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(asset);
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(getForMetadataExtraction(asset));
mocks.asset.getForMetadataExtractionTags.mockResolvedValue({ tags: ['Parent', '2024'] });
mockReadTags({ Keywords: ['Parent', 2024] });
mocks.tag.upsertValue.mockResolvedValue(tagStub.parentUpsert);
@@ -454,7 +455,7 @@ describe(MetadataService.name, () => {
it('should extract hierarchal tags from Keywords', async () => {
const asset = AssetFactory.create();
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(asset);
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(getForMetadataExtraction(asset));
mocks.asset.getForMetadataExtractionTags.mockResolvedValue({ tags: ['Parent/Child'] });
mockReadTags({ Keywords: 'Parent/Child' });
mocks.tag.upsertValue.mockResolvedValue(tagStub.parentUpsert);
@@ -474,7 +475,7 @@ describe(MetadataService.name, () => {
it('should ignore Keywords when TagsList is present', async () => {
const asset = AssetFactory.create();
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(asset);
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(getForMetadataExtraction(asset));
mocks.asset.getForMetadataExtractionTags.mockResolvedValue({ tags: ['Parent/Child', 'Child'] });
mockReadTags({ Keywords: 'Child', TagsList: ['Parent/Child'] });
mocks.tag.upsertValue.mockResolvedValue(tagStub.parentUpsert);
@@ -495,7 +496,7 @@ describe(MetadataService.name, () => {
it('should extract hierarchy from HierarchicalSubject', async () => {
const asset = AssetFactory.create();
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(asset);
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(getForMetadataExtraction(asset));
mocks.asset.getForMetadataExtractionTags.mockResolvedValue({ tags: ['Parent/Child', 'TagA'] });
mockReadTags({ HierarchicalSubject: ['Parent|Child', 'TagA'] });
mocks.tag.upsertValue.mockResolvedValueOnce(tagStub.parentUpsert);
@@ -522,7 +523,7 @@ describe(MetadataService.name, () => {
it('should extract tags from HierarchicalSubject as a list with a number', async () => {
const asset = AssetFactory.create();
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(asset);
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(getForMetadataExtraction(asset));
mocks.asset.getForMetadataExtractionTags.mockResolvedValue({ tags: ['Parent', '2024'] });
mockReadTags({ HierarchicalSubject: ['Parent', 2024] });
mocks.tag.upsertValue.mockResolvedValue(tagStub.parentUpsert);
@@ -535,7 +536,7 @@ describe(MetadataService.name, () => {
it('should extract ignore / characters in a HierarchicalSubject tag', async () => {
const asset = AssetFactory.create();
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(asset);
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(getForMetadataExtraction(asset));
mocks.asset.getForMetadataExtractionTags.mockResolvedValue({ tags: ['Mom|Dad'] });
mockReadTags({ HierarchicalSubject: ['Mom/Dad'] });
mocks.tag.upsertValue.mockResolvedValueOnce(tagStub.parentUpsert);
@@ -551,7 +552,7 @@ describe(MetadataService.name, () => {
it('should ignore HierarchicalSubject when TagsList is present', async () => {
const asset = AssetFactory.create();
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(asset);
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(getForMetadataExtraction(asset));
mocks.asset.getForMetadataExtractionTags.mockResolvedValue({ tags: ['Parent/Child', 'Parent2/Child2'] });
mockReadTags({ HierarchicalSubject: ['Parent2|Child2'], TagsList: ['Parent/Child'] });
mocks.tag.upsertValue.mockResolvedValue(tagStub.parentUpsert);
@@ -572,7 +573,7 @@ describe(MetadataService.name, () => {
it('should remove existing tags', async () => {
const asset = AssetFactory.create();
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(asset);
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(getForMetadataExtraction(asset));
mockReadTags({});
await sut.handleMetadataExtraction({ id: asset.id });
@@ -582,7 +583,7 @@ describe(MetadataService.name, () => {
it('should not apply motion photos if asset is video', async () => {
const asset = AssetFactory.create({ type: AssetType.Video });
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(asset);
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(getForMetadataExtraction(asset));
mocks.media.probe.mockResolvedValue(probeStub.matroskaContainer);
await sut.handleMetadataExtraction({ id: asset.id });
@@ -597,7 +598,7 @@ describe(MetadataService.name, () => {
it('should handle an invalid Directory Item', async () => {
const asset = AssetFactory.create();
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(asset);
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(getForMetadataExtraction(asset));
mockReadTags({
MotionPhoto: 1,
ContainerDirectory: [{ Foo: 100 }],
@@ -608,7 +609,7 @@ describe(MetadataService.name, () => {
it('should extract the correct video orientation', async () => {
const asset = AssetFactory.create({ type: AssetType.Video });
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(asset);
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(getForMetadataExtraction(asset));
mocks.media.probe.mockResolvedValue(probeStub.videoStreamVertical2160p);
mockReadTags({});
@@ -624,7 +625,7 @@ describe(MetadataService.name, () => {
it('should extract the MotionPhotoVideo tag from Samsung HEIC motion photos', async () => {
const asset = AssetFactory.create();
const motionAsset = AssetFactory.create({ type: AssetType.Video, visibility: AssetVisibility.Hidden });
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(asset);
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(getForMetadataExtraction(asset));
mocks.storage.stat.mockResolvedValue({
size: 123_456,
mtime: asset.fileModifiedAt,
@@ -686,7 +687,7 @@ describe(MetadataService.name, () => {
mtimeMs: asset.fileModifiedAt.valueOf(),
birthtimeMs: asset.fileCreatedAt.valueOf(),
} as Stats);
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(asset);
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(getForMetadataExtraction(asset));
mockReadTags({
Directory: 'foo/bar/',
EmbeddedVideoFile: new BinaryField(0, ''),
@@ -733,7 +734,7 @@ describe(MetadataService.name, () => {
it('should extract the motion photo video from the XMP directory entry ', async () => {
const asset = AssetFactory.create();
const motionAsset = AssetFactory.create({ type: AssetType.Video, visibility: AssetVisibility.Hidden });
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(asset);
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(getForMetadataExtraction(asset));
mocks.storage.stat.mockResolvedValue({
size: 123_456,
mtime: asset.fileModifiedAt,
@@ -786,7 +787,7 @@ describe(MetadataService.name, () => {
it('should delete old motion photo video assets if they do not match what is extracted', async () => {
const motionAsset = AssetFactory.create({ type: AssetType.Video, visibility: AssetVisibility.Hidden });
const asset = AssetFactory.create({ livePhotoVideoId: motionAsset.id });
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(asset);
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(getForMetadataExtraction(asset));
mockReadTags({
Directory: 'foo/bar/',
MotionPhoto: 1,
@@ -808,7 +809,7 @@ describe(MetadataService.name, () => {
it('should not create a new motion photo video asset if the hash of the extracted video matches an existing asset', async () => {
const motionAsset = AssetFactory.create({ type: AssetType.Video, visibility: AssetVisibility.Hidden });
const asset = AssetFactory.create({ livePhotoVideoId: motionAsset.id });
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(asset);
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(getForMetadataExtraction(asset));
mockReadTags({
Directory: 'foo/bar/',
MotionPhoto: 1,
@@ -832,7 +833,7 @@ describe(MetadataService.name, () => {
it('should link and hide motion video asset to still asset if the hash of the extracted video matches an existing asset', async () => {
const motionAsset = AssetFactory.create({ type: AssetType.Video });
const asset = AssetFactory.create();
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(asset);
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(getForMetadataExtraction(asset));
mockReadTags({
Directory: 'foo/bar/',
MotionPhoto: 1,
@@ -859,7 +860,7 @@ describe(MetadataService.name, () => {
it('should not update storage usage if motion photo is external', async () => {
const motionAsset = AssetFactory.create({ type: AssetType.Video, visibility: AssetVisibility.Hidden });
const asset = AssetFactory.create({ isExternal: true });
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(asset);
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(getForMetadataExtraction(asset));
mockReadTags({
Directory: 'foo/bar/',
MotionPhoto: 1,
@@ -904,7 +905,7 @@ describe(MetadataService.name, () => {
Rating: 3,
};
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(asset);
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(getForMetadataExtraction(asset));
mockReadTags(tags);
await sut.handleMetadataExtraction({ id: asset.id });
@@ -969,7 +970,7 @@ describe(MetadataService.name, () => {
DateTimeOriginal: ExifDateTime.fromISO(someDate + '+00:00'),
zone: undefined,
};
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(asset);
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(getForMetadataExtraction(asset));
mockReadTags(tags);
await sut.handleMetadataExtraction({ id: asset.id });
@@ -984,7 +985,7 @@ describe(MetadataService.name, () => {
it('should extract duration', async () => {
const asset = AssetFactory.create({ type: AssetType.Video });
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(asset);
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(getForMetadataExtraction(asset));
mocks.media.probe.mockResolvedValue({
...probeStub.videoStreamH264,
format: {
@@ -1007,7 +1008,7 @@ describe(MetadataService.name, () => {
it('should only extract duration for videos', async () => {
const asset = AssetFactory.create();
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(asset);
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(getForMetadataExtraction(asset));
mocks.media.probe.mockResolvedValue({
...probeStub.videoStreamH264,
format: {
@@ -1029,7 +1030,7 @@ describe(MetadataService.name, () => {
it('should omit duration of zero', async () => {
const asset = AssetFactory.create({ type: AssetType.Video });
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(asset);
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(getForMetadataExtraction(asset));
mocks.media.probe.mockResolvedValue({
...probeStub.videoStreamH264,
format: {
@@ -1052,7 +1053,7 @@ describe(MetadataService.name, () => {
it('should a handle duration of 1 week', async () => {
const asset = AssetFactory.create({ type: AssetType.Video });
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(asset);
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(getForMetadataExtraction(asset));
mocks.media.probe.mockResolvedValue({
...probeStub.videoStreamH264,
format: {
@@ -1075,7 +1076,7 @@ describe(MetadataService.name, () => {
it('should use Duration from exif', async () => {
const asset = AssetFactory.create({ originalFileName: 'file.webp' });
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(asset);
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(getForMetadataExtraction(asset));
mockReadTags({ Duration: 123 }, {});
await sut.handleMetadataExtraction({ id: asset.id });
@@ -1086,7 +1087,7 @@ describe(MetadataService.name, () => {
it('should prefer Duration from exif over sidecar', async () => {
const asset = AssetFactory.from({ originalFileName: 'file.webp' }).file({ type: AssetFileType.Sidecar }).build();
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(asset);
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(getForMetadataExtraction(asset));
mockReadTags({ Duration: 123 }, { Duration: 456 });
@@ -1098,7 +1099,7 @@ describe(MetadataService.name, () => {
it('should ignore all Duration tags for definitely static images', async () => {
const asset = AssetFactory.from({ originalFileName: 'file.dng' }).build();
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(asset);
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(getForMetadataExtraction(asset));
mockReadTags({ Duration: 123 }, { Duration: 456 });
await sut.handleMetadataExtraction({ id: asset.id });
@@ -1109,7 +1110,7 @@ describe(MetadataService.name, () => {
it('should ignore Duration from exif for videos', async () => {
const asset = AssetFactory.create({ type: AssetType.Video });
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(asset);
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(getForMetadataExtraction(asset));
mockReadTags({ Duration: 123 }, {});
mocks.media.probe.mockResolvedValue({
...probeStub.videoStreamH264,
@@ -1127,7 +1128,7 @@ describe(MetadataService.name, () => {
it('should trim whitespace from description', async () => {
const asset = AssetFactory.create();
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(asset);
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(getForMetadataExtraction(asset));
mockReadTags({ Description: '\t \v \f \n \r' });
await sut.handleMetadataExtraction({ id: asset.id });
@@ -1150,7 +1151,7 @@ describe(MetadataService.name, () => {
it('should handle a numeric description', async () => {
const asset = AssetFactory.create();
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(asset);
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(getForMetadataExtraction(asset));
mockReadTags({ Description: 1000 });
await sut.handleMetadataExtraction({ id: asset.id });
@@ -1164,7 +1165,7 @@ describe(MetadataService.name, () => {
it('should skip importing metadata when the feature is disabled', async () => {
const asset = AssetFactory.create();
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(asset);
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(getForMetadataExtraction(asset));
mocks.systemMetadata.get.mockResolvedValue({ metadata: { faces: { import: false } } });
mockReadTags(makeFaceTags({ Name: 'Person 1' }));
await sut.handleMetadataExtraction({ id: asset.id });
@@ -1173,7 +1174,7 @@ describe(MetadataService.name, () => {
it('should skip importing metadata face for assets without tags.RegionInfo', async () => {
const asset = AssetFactory.create();
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(asset);
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(getForMetadataExtraction(asset));
mocks.systemMetadata.get.mockResolvedValue({ metadata: { faces: { import: true } } });
mockReadTags();
await sut.handleMetadataExtraction({ id: asset.id });
@@ -1182,7 +1183,7 @@ describe(MetadataService.name, () => {
it('should skip importing faces without name', async () => {
const asset = AssetFactory.create();
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(asset);
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(getForMetadataExtraction(asset));
mocks.systemMetadata.get.mockResolvedValue({ metadata: { faces: { import: true } } });
mockReadTags(makeFaceTags());
mocks.person.getDistinctNames.mockResolvedValue([]);
@@ -1195,7 +1196,7 @@ describe(MetadataService.name, () => {
it('should skip importing faces with empty name', async () => {
const asset = AssetFactory.create();
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(asset);
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(getForMetadataExtraction(asset));
mocks.systemMetadata.get.mockResolvedValue({ metadata: { faces: { import: true } } });
mockReadTags(makeFaceTags({ Name: '' }));
mocks.person.getDistinctNames.mockResolvedValue([]);
@@ -1210,7 +1211,7 @@ describe(MetadataService.name, () => {
const asset = AssetFactory.create();
const person = PersonFactory.create();
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(asset);
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(getForMetadataExtraction(asset));
mocks.systemMetadata.get.mockResolvedValue({ metadata: { faces: { import: true } } });
mockReadTags(makeFaceTags({ Name: person.name }));
mocks.person.getDistinctNames.mockResolvedValue([]);
@@ -1252,7 +1253,7 @@ describe(MetadataService.name, () => {
const asset = AssetFactory.create();
const person = PersonFactory.create();
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(asset);
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(getForMetadataExtraction(asset));
mocks.systemMetadata.get.mockResolvedValue({ metadata: { faces: { import: true } } });
mockReadTags(makeFaceTags({ Name: person.name }));
mocks.person.getDistinctNames.mockResolvedValue([{ id: person.id, name: person.name }]);
@@ -1339,7 +1340,7 @@ describe(MetadataService.name, () => {
const asset = AssetFactory.create();
const person = PersonFactory.create();
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(asset);
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(getForMetadataExtraction(asset));
mocks.systemMetadata.get.mockResolvedValue({ metadata: { faces: { import: true } } });
mockReadTags(makeFaceTags({ Name: person.name }, orientation));
mocks.person.getDistinctNames.mockResolvedValue([]);
@@ -1383,7 +1384,7 @@ describe(MetadataService.name, () => {
it('should handle invalid modify date', async () => {
const asset = AssetFactory.create();
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(asset);
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(getForMetadataExtraction(asset));
mockReadTags({ ModifyDate: '00:00:00.000' });
await sut.handleMetadataExtraction({ id: asset.id });
@@ -1397,7 +1398,7 @@ describe(MetadataService.name, () => {
it('should handle invalid rating value', async () => {
const asset = AssetFactory.create();
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(asset);
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(getForMetadataExtraction(asset));
mockReadTags({ Rating: 6 });
await sut.handleMetadataExtraction({ id: asset.id });
@@ -1411,7 +1412,7 @@ describe(MetadataService.name, () => {
it('should handle valid rating value', async () => {
const asset = AssetFactory.create();
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(asset);
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(getForMetadataExtraction(asset));
mockReadTags({ Rating: 5 });
await sut.handleMetadataExtraction({ id: asset.id });
@@ -1425,7 +1426,7 @@ describe(MetadataService.name, () => {
it('should handle 0 as unrated -> null', async () => {
const asset = AssetFactory.create();
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(asset);
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(getForMetadataExtraction(asset));
mockReadTags({ Rating: 0 });
await sut.handleMetadataExtraction({ id: asset.id });
@@ -1439,7 +1440,7 @@ describe(MetadataService.name, () => {
it('should handle valid negative rating value', async () => {
const asset = AssetFactory.create();
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(asset);
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(getForMetadataExtraction(asset));
mockReadTags({ Rating: -1 });
await sut.handleMetadataExtraction({ id: asset.id });
@@ -1453,7 +1454,7 @@ describe(MetadataService.name, () => {
it('should handle livePhotoCID not set', async () => {
const asset = AssetFactory.create();
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(asset);
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(getForMetadataExtraction(asset));
await sut.handleMetadataExtraction({ id: asset.id });
@@ -1468,7 +1469,7 @@ describe(MetadataService.name, () => {
it('should handle not finding a match', async () => {
const asset = AssetFactory.create({ type: AssetType.Video });
mocks.media.probe.mockResolvedValue(probeStub.videoStreamVertical2160p);
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(asset);
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(getForMetadataExtraction(asset));
mockReadTags({ ContentIdentifier: 'CID' });
await sut.handleMetadataExtraction({ id: asset.id });
@@ -1490,7 +1491,7 @@ describe(MetadataService.name, () => {
it('should link photo and video', async () => {
const motionAsset = AssetFactory.create({ type: AssetType.Video });
const asset = AssetFactory.create();
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(asset);
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(getForMetadataExtraction(asset));
mocks.asset.findLivePhotoMatch.mockResolvedValue(motionAsset);
mockReadTags({ ContentIdentifier: 'CID' });
@@ -1518,7 +1519,7 @@ describe(MetadataService.name, () => {
it('should notify clients on live photo link', async () => {
const motionAsset = AssetFactory.create({ type: AssetType.Video });
const asset = AssetFactory.create();
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(asset);
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(getForMetadataExtraction(asset));
mocks.asset.findLivePhotoMatch.mockResolvedValue(motionAsset);
mockReadTags({ ContentIdentifier: 'CID' });
@@ -1533,7 +1534,7 @@ describe(MetadataService.name, () => {
it('should search by libraryId', async () => {
const motionAsset = AssetFactory.create({ type: AssetType.Video, libraryId: 'library-id' });
const asset = AssetFactory.create({ libraryId: 'library-id' });
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(asset);
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(getForMetadataExtraction(asset));
mocks.asset.findLivePhotoMatch.mockResolvedValue(motionAsset);
mockReadTags({ ContentIdentifier: 'CID' });
@@ -1570,7 +1571,7 @@ describe(MetadataService.name, () => {
{ exif: { AndroidMake: '1', AndroidModel: '2' }, expected: { make: '1', model: '2' } },
])('should read camera make and model $exif -> $expected', async ({ exif, expected }) => {
const asset = AssetFactory.create();
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(asset);
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(getForMetadataExtraction(asset));
mockReadTags(exif);
await sut.handleMetadataExtraction({ id: asset.id });
@@ -1595,7 +1596,7 @@ describe(MetadataService.name, () => {
{ exif: { LensID: '' }, expected: null },
])('should read camera lens information $exif -> $expected', async ({ exif, expected }) => {
const asset = AssetFactory.create();
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(asset);
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(getForMetadataExtraction(asset));
mockReadTags(exif);
await sut.handleMetadataExtraction({ id: asset.id });
@@ -1609,7 +1610,7 @@ describe(MetadataService.name, () => {
it('should properly set width/height for normal images', async () => {
const asset = AssetFactory.create();
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(asset);
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(getForMetadataExtraction(asset));
mockReadTags({ ImageWidth: 1000, ImageHeight: 2000 });
await sut.handleMetadataExtraction({ id: asset.id });
@@ -1623,7 +1624,7 @@ describe(MetadataService.name, () => {
it('should properly swap asset width/height for rotated images', async () => {
const asset = AssetFactory.create();
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(asset);
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(getForMetadataExtraction(asset));
mockReadTags({ ImageWidth: 1000, ImageHeight: 2000, Orientation: 6 });
await sut.handleMetadataExtraction({ id: asset.id });
@@ -1637,7 +1638,7 @@ describe(MetadataService.name, () => {
it('should not overwrite existing width/height if they already exist', async () => {
const asset = AssetFactory.create({ width: 1920, height: 1080 });
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(asset);
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(getForMetadataExtraction(asset));
mockReadTags({ ImageWidth: 1280, ImageHeight: 720 });
await sut.handleMetadataExtraction({ id: asset.id });
@@ -1754,17 +1755,20 @@ describe(MetadataService.name, () => {
it('should skip jobs with no metadata', async () => {
mocks.assetJob.getLockedPropertiesForMetadataExtraction.mockResolvedValue([]);
const asset = factory.jobAssets.sidecarWrite();
mocks.assetJob.getForSidecarWriteJob.mockResolvedValue(asset);
const asset = AssetFactory.from().file({ type: AssetFileType.Sidecar }).exif().build();
mocks.assetJob.getForSidecarWriteJob.mockResolvedValue(getForSidecarWrite(asset));
await expect(sut.handleSidecarWrite({ id: asset.id })).resolves.toBe(JobStatus.Skipped);
expect(mocks.metadata.writeTags).not.toHaveBeenCalled();
});
it('should write tags', async () => {
const asset = factory.jobAssets.sidecarWrite();
const description = 'this is a description';
const gps = 12;
const date = '2023-11-21T22:56:12.196-06:00';
const asset = AssetFactory.from()
.file({ type: AssetFileType.Sidecar })
.exif({ description, dateTimeOriginal: new Date(date), latitude: gps, longitude: gps })
.build();
mocks.assetJob.getLockedPropertiesForMetadataExtraction.mockResolvedValue([
'description',
@@ -1773,7 +1777,7 @@ describe(MetadataService.name, () => {
'dateTimeOriginal',
'timeZone',
]);
mocks.assetJob.getForSidecarWriteJob.mockResolvedValue(asset);
mocks.assetJob.getForSidecarWriteJob.mockResolvedValue(getForSidecarWrite(asset));
await expect(
sut.handleSidecarWrite({
id: asset.id,
@@ -1796,22 +1800,22 @@ describe(MetadataService.name, () => {
});
it('should write rating', async () => {
const asset = factory.jobAssets.sidecarWrite();
const asset = AssetFactory.from().file({ type: AssetFileType.Sidecar }).exif().build();
asset.exifInfo.rating = 4;
mocks.assetJob.getLockedPropertiesForMetadataExtraction.mockResolvedValue(['rating']);
mocks.assetJob.getForSidecarWriteJob.mockResolvedValue(asset);
mocks.assetJob.getForSidecarWriteJob.mockResolvedValue(getForSidecarWrite(asset));
await expect(sut.handleSidecarWrite({ id: asset.id })).resolves.toBe(JobStatus.Success);
expect(mocks.metadata.writeTags).toHaveBeenCalledWith(asset.files[0].path, { Rating: 4 });
expect(mocks.asset.unlockProperties).toHaveBeenCalledWith(asset.id, ['rating']);
});
it('should write null rating as 0', async () => {
const asset = factory.jobAssets.sidecarWrite();
const asset = AssetFactory.from().file({ type: AssetFileType.Sidecar }).exif().build();
asset.exifInfo.rating = null;
mocks.assetJob.getLockedPropertiesForMetadataExtraction.mockResolvedValue(['rating']);
mocks.assetJob.getForSidecarWriteJob.mockResolvedValue(asset);
mocks.assetJob.getForSidecarWriteJob.mockResolvedValue(getForSidecarWrite(asset));
await expect(sut.handleSidecarWrite({ id: asset.id })).resolves.toBe(JobStatus.Success);
expect(mocks.metadata.writeTags).toHaveBeenCalledWith(asset.files[0].path, { Rating: 0 });
expect(mocks.asset.unlockProperties).toHaveBeenCalledWith(asset.id, ['rating']);