feat: more factories for small tests (#25396)

This commit is contained in:
Daniel Dietzler
2026-01-23 09:54:04 -06:00
committed by GitHub
parent 574d9c34ff
commit 2792d97027
5 changed files with 210 additions and 65 deletions

View File

@@ -15,7 +15,7 @@ import {
} from 'src/enum'; } from 'src/enum';
import { ConcurrentQueueName, FullsizeImageOptions, ImageOptions } from 'src/types'; import { ConcurrentQueueName, FullsizeImageOptions, ImageOptions } from 'src/types';
export interface SystemConfig { export type SystemConfig = {
backup: { backup: {
database: { database: {
enabled: boolean; enabled: boolean;
@@ -187,7 +187,7 @@ export interface SystemConfig {
user: { user: {
deleteDelay: number; deleteDelay: number;
}; };
} };
export type MachineLearningConfig = SystemConfig['machineLearning']; export type MachineLearningConfig = SystemConfig['machineLearning'];

View File

@@ -387,7 +387,7 @@ describe(MetadataService.name, () => {
it('should extract tags from TagsList', async () => { it('should extract tags from TagsList', async () => {
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(removeNonSidecarFiles(assetStub.image)); mocks.assetJob.getForMetadataExtraction.mockResolvedValue(removeNonSidecarFiles(assetStub.image));
mocks.asset.getById.mockResolvedValue({ exifInfo: { tags: ['Parent'] } } as any); mocks.asset.getById.mockResolvedValue({ ...factory.asset(), exifInfo: factory.exif({ tags: ['Parent'] }) });
mockReadTags({ TagsList: ['Parent'] }); mockReadTags({ TagsList: ['Parent'] });
mocks.tag.upsertValue.mockResolvedValue(tagStub.parentUpsert); mocks.tag.upsertValue.mockResolvedValue(tagStub.parentUpsert);
@@ -398,7 +398,7 @@ describe(MetadataService.name, () => {
it('should extract hierarchy from TagsList', async () => { it('should extract hierarchy from TagsList', async () => {
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(removeNonSidecarFiles(assetStub.image)); mocks.assetJob.getForMetadataExtraction.mockResolvedValue(removeNonSidecarFiles(assetStub.image));
mocks.asset.getById.mockResolvedValue({ exifInfo: { tags: ['Parent/Child'] } } as any); mocks.asset.getById.mockResolvedValue({ ...factory.asset(), exifInfo: factory.exif({ tags: ['Parent/Child'] }) });
mockReadTags({ TagsList: ['Parent/Child'] }); mockReadTags({ TagsList: ['Parent/Child'] });
mocks.tag.upsertValue.mockResolvedValueOnce(tagStub.parentUpsert); mocks.tag.upsertValue.mockResolvedValueOnce(tagStub.parentUpsert);
mocks.tag.upsertValue.mockResolvedValueOnce(tagStub.childUpsert); mocks.tag.upsertValue.mockResolvedValueOnce(tagStub.childUpsert);
@@ -419,7 +419,7 @@ describe(MetadataService.name, () => {
it('should extract tags from Keywords as a string', async () => { it('should extract tags from Keywords as a string', async () => {
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(removeNonSidecarFiles(assetStub.image)); mocks.assetJob.getForMetadataExtraction.mockResolvedValue(removeNonSidecarFiles(assetStub.image));
mocks.asset.getById.mockResolvedValue({ exifInfo: { tags: ['Parent'] } } as any); mocks.asset.getById.mockResolvedValue({ ...factory.asset(), exifInfo: factory.exif({ tags: ['Parent'] }) });
mockReadTags({ Keywords: 'Parent' }); mockReadTags({ Keywords: 'Parent' });
mocks.tag.upsertValue.mockResolvedValue(tagStub.parentUpsert); mocks.tag.upsertValue.mockResolvedValue(tagStub.parentUpsert);
@@ -430,7 +430,7 @@ describe(MetadataService.name, () => {
it('should extract tags from Keywords as a list', async () => { it('should extract tags from Keywords as a list', async () => {
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(removeNonSidecarFiles(assetStub.image)); mocks.assetJob.getForMetadataExtraction.mockResolvedValue(removeNonSidecarFiles(assetStub.image));
mocks.asset.getById.mockResolvedValue({ exifInfo: { tags: ['Parent'] } } as any); mocks.asset.getById.mockResolvedValue({ ...factory.asset(), exifInfo: factory.exif({ tags: ['Parent'] }) });
mockReadTags({ Keywords: ['Parent'] }); mockReadTags({ Keywords: ['Parent'] });
mocks.tag.upsertValue.mockResolvedValue(tagStub.parentUpsert); mocks.tag.upsertValue.mockResolvedValue(tagStub.parentUpsert);
@@ -441,7 +441,10 @@ describe(MetadataService.name, () => {
it('should extract tags from Keywords as a list with a number', async () => { it('should extract tags from Keywords as a list with a number', async () => {
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(removeNonSidecarFiles(assetStub.image)); mocks.assetJob.getForMetadataExtraction.mockResolvedValue(removeNonSidecarFiles(assetStub.image));
mocks.asset.getById.mockResolvedValue({ exifInfo: { tags: ['Parent', '2024'] } } as any); mocks.asset.getById.mockResolvedValue({
...factory.asset(),
exifInfo: factory.exif({ tags: ['Parent', '2024'] }),
});
mockReadTags({ Keywords: ['Parent', 2024] }); mockReadTags({ Keywords: ['Parent', 2024] });
mocks.tag.upsertValue.mockResolvedValue(tagStub.parentUpsert); mocks.tag.upsertValue.mockResolvedValue(tagStub.parentUpsert);
@@ -453,7 +456,7 @@ describe(MetadataService.name, () => {
it('should extract hierarchal tags from Keywords', async () => { it('should extract hierarchal tags from Keywords', async () => {
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(removeNonSidecarFiles(assetStub.image)); mocks.assetJob.getForMetadataExtraction.mockResolvedValue(removeNonSidecarFiles(assetStub.image));
mocks.asset.getById.mockResolvedValue({ exifInfo: { tags: ['Parent/Child'] } } as any); mocks.asset.getById.mockResolvedValue({ ...factory.asset(), exifInfo: factory.exif({ tags: ['Parent/Child'] }) });
mockReadTags({ Keywords: 'Parent/Child' }); mockReadTags({ Keywords: 'Parent/Child' });
mocks.tag.upsertValue.mockResolvedValue(tagStub.parentUpsert); mocks.tag.upsertValue.mockResolvedValue(tagStub.parentUpsert);
@@ -473,7 +476,10 @@ describe(MetadataService.name, () => {
it('should ignore Keywords when TagsList is present', async () => { it('should ignore Keywords when TagsList is present', async () => {
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(removeNonSidecarFiles(assetStub.image)); mocks.assetJob.getForMetadataExtraction.mockResolvedValue(removeNonSidecarFiles(assetStub.image));
mocks.asset.getById.mockResolvedValue({ exifInfo: { tags: ['Parent/Child', 'Child'] } } as any); mocks.asset.getById.mockResolvedValue({
...factory.asset(),
exifInfo: factory.exif({ tags: ['Parent/Child', 'Child'] }),
});
mockReadTags({ Keywords: 'Child', TagsList: ['Parent/Child'] }); mockReadTags({ Keywords: 'Child', TagsList: ['Parent/Child'] });
mocks.tag.upsertValue.mockResolvedValue(tagStub.parentUpsert); mocks.tag.upsertValue.mockResolvedValue(tagStub.parentUpsert);
@@ -493,7 +499,10 @@ describe(MetadataService.name, () => {
it('should extract hierarchy from HierarchicalSubject', async () => { it('should extract hierarchy from HierarchicalSubject', async () => {
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(removeNonSidecarFiles(assetStub.image)); mocks.assetJob.getForMetadataExtraction.mockResolvedValue(removeNonSidecarFiles(assetStub.image));
mocks.asset.getById.mockResolvedValue({ exifInfo: { tags: ['Parent/Child', 'TagA'] } } as any); mocks.asset.getById.mockResolvedValue({
...factory.asset(),
exifInfo: factory.exif({ tags: ['Parent/Child', 'TagA'] }),
});
mockReadTags({ HierarchicalSubject: ['Parent|Child', 'TagA'] }); mockReadTags({ HierarchicalSubject: ['Parent|Child', 'TagA'] });
mocks.tag.upsertValue.mockResolvedValueOnce(tagStub.parentUpsert); mocks.tag.upsertValue.mockResolvedValueOnce(tagStub.parentUpsert);
mocks.tag.upsertValue.mockResolvedValueOnce(tagStub.childUpsert); mocks.tag.upsertValue.mockResolvedValueOnce(tagStub.childUpsert);
@@ -515,7 +524,10 @@ describe(MetadataService.name, () => {
it('should extract tags from HierarchicalSubject as a list with a number', async () => { it('should extract tags from HierarchicalSubject as a list with a number', async () => {
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(removeNonSidecarFiles(assetStub.image)); mocks.assetJob.getForMetadataExtraction.mockResolvedValue(removeNonSidecarFiles(assetStub.image));
mocks.asset.getById.mockResolvedValue({ exifInfo: { tags: ['Parent', '2024'] } } as any); mocks.asset.getById.mockResolvedValue({
...factory.asset(),
exifInfo: factory.exif({ tags: ['Parent', '2024'] }),
});
mockReadTags({ HierarchicalSubject: ['Parent', 2024] }); mockReadTags({ HierarchicalSubject: ['Parent', 2024] });
mocks.tag.upsertValue.mockResolvedValue(tagStub.parentUpsert); mocks.tag.upsertValue.mockResolvedValue(tagStub.parentUpsert);
@@ -527,7 +539,7 @@ describe(MetadataService.name, () => {
it('should extract ignore / characters in a HierarchicalSubject tag', async () => { it('should extract ignore / characters in a HierarchicalSubject tag', async () => {
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(assetStub.image); mocks.assetJob.getForMetadataExtraction.mockResolvedValue(assetStub.image);
mocks.asset.getById.mockResolvedValue({ exifInfo: { tags: ['Mom|Dad'] } } as any); mocks.asset.getById.mockResolvedValue({ ...factory.asset(), exifInfo: factory.exif({ tags: ['Mom|Dad'] }) });
mockReadTags({ HierarchicalSubject: ['Mom/Dad'] }); mockReadTags({ HierarchicalSubject: ['Mom/Dad'] });
mocks.tag.upsertValue.mockResolvedValueOnce(tagStub.parentUpsert); mocks.tag.upsertValue.mockResolvedValueOnce(tagStub.parentUpsert);
@@ -542,7 +554,10 @@ describe(MetadataService.name, () => {
it('should ignore HierarchicalSubject when TagsList is present', async () => { it('should ignore HierarchicalSubject when TagsList is present', async () => {
mocks.assetJob.getForMetadataExtraction.mockResolvedValue(assetStub.image); mocks.assetJob.getForMetadataExtraction.mockResolvedValue(assetStub.image);
mocks.asset.getById.mockResolvedValue({ exifInfo: { tags: ['Parent/Child', 'Parent2/Child2'] } } as any); mocks.asset.getById.mockResolvedValue({
...factory.asset(),
exifInfo: factory.exif({ tags: ['Parent/Child', 'Parent2/Child2'] }),
});
mockReadTags({ HierarchicalSubject: ['Parent2|Child2'], TagsList: ['Parent/Child'] }); mockReadTags({ HierarchicalSubject: ['Parent2|Child2'], TagsList: ['Parent/Child'] });
mocks.tag.upsertValue.mockResolvedValue(tagStub.parentUpsert); mocks.tag.upsertValue.mockResolvedValue(tagStub.parentUpsert);

View File

@@ -4,6 +4,7 @@ import { JobStatus } from 'src/enum';
import { TagService } from 'src/services/tag.service'; import { TagService } from 'src/services/tag.service';
import { authStub } from 'test/fixtures/auth.stub'; import { authStub } from 'test/fixtures/auth.stub';
import { tagResponseStub, tagStub } from 'test/fixtures/tag.stub'; import { tagResponseStub, tagStub } from 'test/fixtures/tag.stub';
import { factory } from 'test/small.factory';
import { newTestService, ServiceMocks } from 'test/utils'; import { newTestService, ServiceMocks } from 'test/utils';
describe(TagService.name, () => { describe(TagService.name, () => {
@@ -191,7 +192,10 @@ describe(TagService.name, () => {
it('should upsert records', async () => { it('should upsert records', async () => {
mocks.access.tag.checkOwnerAccess.mockResolvedValue(new Set(['tag-1', 'tag-2'])); mocks.access.tag.checkOwnerAccess.mockResolvedValue(new Set(['tag-1', 'tag-2']));
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1', 'asset-2', 'asset-3'])); mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1', 'asset-2', 'asset-3']));
mocks.asset.getById.mockResolvedValue({ tags: [{ value: 'tag-1' }, { value: 'tag-2' }] } as any); mocks.asset.getById.mockResolvedValue({
...factory.asset(),
tags: [factory.tag({ value: 'tag-1' }), factory.tag({ value: 'tag-2' })],
});
mocks.tag.upsertAssetIds.mockResolvedValue([ mocks.tag.upsertAssetIds.mockResolvedValue([
{ tagId: 'tag-1', assetId: 'asset-1' }, { tagId: 'tag-1', assetId: 'asset-1' },
{ tagId: 'tag-1', assetId: 'asset-2' }, { tagId: 'tag-1', assetId: 'asset-2' },
@@ -242,7 +246,10 @@ describe(TagService.name, () => {
mocks.tag.get.mockResolvedValue(tagStub.tag); mocks.tag.get.mockResolvedValue(tagStub.tag);
mocks.tag.getAssetIds.mockResolvedValue(new Set(['asset-1'])); mocks.tag.getAssetIds.mockResolvedValue(new Set(['asset-1']));
mocks.tag.addAssetIds.mockResolvedValue(); mocks.tag.addAssetIds.mockResolvedValue();
mocks.asset.getById.mockResolvedValue({ tags: [{ value: 'tag-1' }] } as any); mocks.asset.getById.mockResolvedValue({
...factory.asset(),
tags: [factory.tag({ value: 'tag-1' })],
});
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-2'])); mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-2']));
await expect( await expect(

View File

@@ -23,34 +23,39 @@ import {
VideoCodec, VideoCodec,
} from 'src/enum'; } from 'src/enum';
export type DeepPartial<T> = T extends object ? { [K in keyof T]?: DeepPartial<T[K]> } : T; export type DeepPartial<T> =
T extends Record<string, unknown>
? { [K in keyof T]?: DeepPartial<T[K]> }
: T extends Array<infer R>
? DeepPartial<R>[]
: T;
export type RepositoryInterface<T extends object> = Pick<T, keyof T>; export type RepositoryInterface<T extends object> = Pick<T, keyof T>;
export interface FullsizeImageOptions { export type FullsizeImageOptions = {
format: ImageFormat; format: ImageFormat;
quality: number; quality: number;
enabled: boolean; enabled: boolean;
} };
export interface ImageOptions { export type ImageOptions = {
format: ImageFormat; format: ImageFormat;
quality: number; quality: number;
size: number; size: number;
} };
export interface RawImageInfo { export type RawImageInfo = {
width: number; width: number;
height: number; height: number;
channels: 1 | 2 | 3 | 4; channels: 1 | 2 | 3 | 4;
} };
interface DecodeImageOptions { type DecodeImageOptions = {
colorspace: string; colorspace: string;
processInvalidImages: boolean; processInvalidImages: boolean;
raw?: RawImageInfo; raw?: RawImageInfo;
edits?: AssetEditActionItem[]; edits?: AssetEditActionItem[];
} };
export interface DecodeToBufferOptions extends DecodeImageOptions { export interface DecodeToBufferOptions extends DecodeImageOptions {
size?: number; size?: number;
@@ -504,7 +509,7 @@ export interface SystemMetadata extends Record<SystemMetadataKey, Record<string,
[SystemMetadataKey.MemoriesState]: MemoriesState; [SystemMetadataKey.MemoriesState]: MemoriesState;
} }
export interface UserPreferences { export type UserPreferences = {
albums: { albums: {
defaultAssetOrder: AssetOrder; defaultAssetOrder: AssetOrder;
}; };
@@ -547,7 +552,7 @@ export interface UserPreferences {
cast: { cast: {
gCastEnabled: boolean; gCastEnabled: boolean;
}; };
} };
export type UserMetadataItem<T extends keyof UserMetadata = UserMetadataKey> = { export type UserMetadataItem<T extends keyof UserMetadata = UserMetadataKey> = {
key: T; key: T;

View File

@@ -1,6 +1,7 @@
import { import {
Activity, Activity,
ApiKey, ApiKey,
AssetFace,
AssetFile, AssetFile,
AuthApiKey, AuthApiKey,
AuthSharedLink, AuthSharedLink,
@@ -9,12 +10,16 @@ import {
Library, Library,
Memory, Memory,
Partner, Partner,
Person,
Session, Session,
Stack,
Tag,
User, User,
UserAdmin, UserAdmin,
} from 'src/database'; } from 'src/database';
import { MapAsset } from 'src/dtos/asset-response.dto'; import { MapAsset } from 'src/dtos/asset-response.dto';
import { AuthDto } from 'src/dtos/auth.dto'; import { AuthDto } from 'src/dtos/auth.dto';
import { AssetEditAction, AssetEditActionItem, MirrorAxis } from 'src/dtos/editing.dto';
import { QueueStatisticsDto } from 'src/dtos/queue.dto'; import { QueueStatisticsDto } from 'src/dtos/queue.dto';
import { import {
AssetFileType, AssetFileType,
@@ -23,10 +28,11 @@ import {
AssetVisibility, AssetVisibility,
MemoryType, MemoryType,
Permission, Permission,
SourceType,
UserMetadataKey, UserMetadataKey,
UserStatus, UserStatus,
} from 'src/enum'; } from 'src/enum';
import { OnThisDayData, UserMetadataItem } from 'src/types'; import { DeepPartial, OnThisDayData, UserMetadataItem } from 'src/types';
import { v4, v7 } from 'uuid'; import { v4, v7 } from 'uuid';
export const newUuid = () => v4(); export const newUuid = () => v4();
@@ -160,11 +166,18 @@ const queueStatisticsFactory = (dto?: Partial<QueueStatisticsDto>) => ({
...dto, ...dto,
}); });
const stackFactory = () => ({ const stackFactory = ({ owner, assets, ...stack }: DeepPartial<Stack> = {}): Stack => {
id: newUuid(), const ownerId = newUuid();
ownerId: newUuid(),
primaryAssetId: newUuid(), return {
}); id: newUuid(),
primaryAssetId: assets?.[0].id ?? newUuid(),
ownerId,
owner: userFactory(owner ?? { id: ownerId }),
assets: assets?.map((asset) => assetFactory(asset)) ?? [],
...stack,
};
};
const userFactory = (user: Partial<User> = {}) => ({ const userFactory = (user: Partial<User> = {}) => ({
id: newUuid(), id: newUuid(),
@@ -223,39 +236,43 @@ const userAdminFactory = (user: Partial<UserAdmin> = {}) => {
}; };
}; };
const assetFactory = (asset: Partial<MapAsset> = {}) => ({ const assetFactory = (
id: newUuid(), asset: Omit<DeepPartial<MapAsset>, 'exifInfo' | 'owner' | 'stack' | 'tags' | 'faces' | 'files' | 'edits'> = {},
createdAt: newDate(), ) => {
updatedAt: newDate(), return {
deletedAt: null, id: newUuid(),
updateId: newUuidV7(), createdAt: newDate(),
status: AssetStatus.Active, updatedAt: newDate(),
checksum: newSha1(), deletedAt: null,
deviceAssetId: '', updateId: newUuidV7(),
deviceId: '', status: AssetStatus.Active,
duplicateId: null, checksum: newSha1(),
duration: null, deviceAssetId: '',
encodedVideoPath: null, deviceId: '',
fileCreatedAt: newDate(), duplicateId: null,
fileModifiedAt: newDate(), duration: null,
isExternal: false, encodedVideoPath: null,
isFavorite: false, fileCreatedAt: newDate(),
isOffline: false, fileModifiedAt: newDate(),
libraryId: null, isExternal: false,
livePhotoVideoId: null, isFavorite: false,
localDateTime: newDate(), isOffline: false,
originalFileName: 'IMG_123.jpg', libraryId: null,
originalPath: `/data/12/34/IMG_123.jpg`, livePhotoVideoId: null,
ownerId: newUuid(), localDateTime: newDate(),
stackId: null, originalFileName: 'IMG_123.jpg',
thumbhash: null, originalPath: `/data/12/34/IMG_123.jpg`,
type: AssetType.Image, ownerId: newUuid(),
visibility: AssetVisibility.Timeline, stackId: null,
width: null, thumbhash: null,
height: null, type: AssetType.Image,
isEdited: false, visibility: AssetVisibility.Timeline,
...asset, width: null,
}); height: null,
isEdited: false,
...asset,
};
};
const activityFactory = (activity: Partial<Activity> = {}) => { const activityFactory = (activity: Partial<Activity> = {}) => {
const userId = activity.userId || newUuid(); const userId = activity.userId || newUuid();
@@ -391,6 +408,102 @@ const assetFileFactory = (file: Partial<AssetFile> = {}): AssetFile => ({
...file, ...file,
}); });
const exifFactory = (exif: Partial<Exif> = {}) => ({
assetId: newUuid(),
autoStackId: null,
bitsPerSample: null,
city: 'Austin',
colorspace: null,
country: 'United States of America',
dateTimeOriginal: newDate(),
description: '',
exifImageHeight: 420,
exifImageWidth: 42,
exposureTime: null,
fileSizeInByte: 69,
fNumber: 1.7,
focalLength: 4.38,
fps: null,
iso: 947,
latitude: 30.267_334_570_570_195,
longitude: -97.789_833_534_282_07,
lensModel: null,
livePhotoCID: null,
make: 'Google',
model: 'Pixel 7',
modifyDate: newDate(),
orientation: '1',
profileDescription: null,
projectionType: null,
rating: 4,
state: 'Texas',
tags: ['parent/child'],
timeZone: 'UTC-6',
...exif,
});
const tagFactory = (tag: Partial<Tag>): Tag => ({
id: newUuid(),
color: null,
createdAt: newDate(),
parentId: null,
updatedAt: newDate(),
value: `tag-${newUuid()}`,
...tag,
});
const faceFactory = ({ person, ...face }: DeepPartial<AssetFace> = {}): AssetFace => ({
assetId: newUuid(),
boundingBoxX1: 1,
boundingBoxX2: 2,
boundingBoxY1: 1,
boundingBoxY2: 2,
deletedAt: null,
id: newUuid(),
imageHeight: 420,
imageWidth: 42,
isVisible: true,
personId: null,
sourceType: SourceType.MachineLearning,
updatedAt: newDate(),
updateId: newUuidV7(),
person: person === null ? null : personFactory(person),
...face,
});
const assetEditFactory = (edit?: Partial<AssetEditActionItem>): AssetEditActionItem => {
switch (edit?.action) {
case AssetEditAction.Crop: {
return { action: AssetEditAction.Crop, parameters: { height: 42, width: 42, x: 0, y: 10 }, ...edit };
}
case AssetEditAction.Mirror: {
return { action: AssetEditAction.Mirror, parameters: { axis: MirrorAxis.Horizontal }, ...edit };
}
case AssetEditAction.Rotate: {
return { action: AssetEditAction.Rotate, parameters: { angle: 90 }, ...edit };
}
default: {
return { action: AssetEditAction.Mirror, parameters: { axis: MirrorAxis.Vertical } };
}
}
};
const personFactory = (person?: Partial<Person>): Person => ({
birthDate: newDate(),
color: null,
createdAt: newDate(),
faceAssetId: null,
id: newUuid(),
isFavorite: false,
isHidden: false,
name: 'person',
ownerId: newUuid(),
thumbnailPath: '/path/to/person/thumbnail.jpg',
updatedAt: newDate(),
updateId: newUuidV7(),
...person,
});
export const factory = { export const factory = {
activity: activityFactory, activity: activityFactory,
apiKey: apiKeyFactory, apiKey: apiKeyFactory,
@@ -412,6 +525,11 @@ export const factory = {
jobAssets: { jobAssets: {
sidecarWrite: assetSidecarWriteFactory, sidecarWrite: assetSidecarWriteFactory,
}, },
exif: exifFactory,
face: faceFactory,
person: personFactory,
assetEdit: assetEditFactory,
tag: tagFactory,
uuid: newUuid, uuid: newUuid,
date: newDate, date: newDate,
responses: { responses: {