diff --git a/server/src/services/asset.service.spec.ts b/server/src/services/asset.service.spec.ts index cc8603cc5a..f942754d05 100755 --- a/server/src/services/asset.service.spec.ts +++ b/server/src/services/asset.service.spec.ts @@ -532,7 +532,7 @@ describe(AssetService.name, () => { }); it('should immediately queue assets for deletion if trash is disabled', async () => { - const asset = factory.asset({ isOffline: false }); + const asset = AssetFactory.create(); mocks.assetJob.streamForDeletedJob.mockReturnValue(makeStream([asset])); mocks.systemMetadata.get.mockResolvedValue({ trash: { enabled: false } }); @@ -546,7 +546,7 @@ describe(AssetService.name, () => { }); it('should queue assets for deletion after trash duration', async () => { - const asset = factory.asset({ isOffline: false }); + const asset = AssetFactory.create(); mocks.assetJob.streamForDeletedJob.mockReturnValue(makeStream([asset])); mocks.systemMetadata.get.mockResolvedValue({ trash: { enabled: true, days: 7 } }); @@ -739,7 +739,7 @@ describe(AssetService.name, () => { describe('upsertMetadata', () => { it('should throw a bad request exception if duplicate keys are sent', async () => { - const asset = factory.asset(); + const asset = AssetFactory.create(); const items = [ { key: AssetMetadataKey.MobileApp, value: { iCloudId: 'id1' } }, { key: AssetMetadataKey.MobileApp, value: { iCloudId: 'id1' } }, @@ -757,7 +757,7 @@ describe(AssetService.name, () => { describe('upsertBulkMetadata', () => { it('should throw a bad request exception if duplicate keys are sent', async () => { - const asset = factory.asset(); + const asset = AssetFactory.create(); const items = [ { assetId: asset.id, key: AssetMetadataKey.MobileApp, value: { iCloudId: 'id1' } }, { assetId: asset.id, key: AssetMetadataKey.MobileApp, value: { iCloudId: 'id1' } }, diff --git a/server/src/services/memory.service.spec.ts b/server/src/services/memory.service.spec.ts index 44929f2bbf..738f7bb6d5 100644 --- a/server/src/services/memory.service.spec.ts +++ b/server/src/services/memory.service.spec.ts @@ -1,6 +1,8 @@ import { BadRequestException } from '@nestjs/common'; import { MemoryService } from 'src/services/memory.service'; import { OnThisDayData } from 'src/types'; +import { AssetFactory } from 'test/factories/asset.factory'; +import { MemoryFactory } from 'test/factories/memory.factory'; import { factory, newUuid, newUuids } from 'test/small.factory'; import { newTestService, ServiceMocks } from 'test/utils'; @@ -27,9 +29,9 @@ describe(MemoryService.name, () => { describe('search', () => { it('should search memories', async () => { const [userId] = newUuids(); - const asset = factory.asset(); - const memory1 = factory.memory({ ownerId: userId, assets: [asset] }); - const memory2 = factory.memory({ ownerId: userId }); + const asset = AssetFactory.create(); + const memory1 = MemoryFactory.from({ ownerId: userId }).asset(asset).build(); + const memory2 = MemoryFactory.create({ ownerId: userId }); mocks.memory.search.mockResolvedValue([memory1, memory2]); @@ -64,7 +66,7 @@ describe(MemoryService.name, () => { it('should get a memory by id', async () => { const userId = newUuid(); - const memory = factory.memory({ ownerId: userId }); + const memory = MemoryFactory.create({ ownerId: userId }); mocks.memory.get.mockResolvedValue(memory); mocks.access.memory.checkOwnerAccess.mockResolvedValue(new Set([memory.id])); @@ -81,7 +83,7 @@ describe(MemoryService.name, () => { describe('create', () => { it('should skip assets the user does not have access to', async () => { const [assetId, userId] = newUuids(); - const memory = factory.memory({ ownerId: userId }); + const memory = MemoryFactory.create({ ownerId: userId }); mocks.memory.create.mockResolvedValue(memory); @@ -109,8 +111,8 @@ describe(MemoryService.name, () => { it('should create a memory', async () => { const [assetId, userId] = newUuids(); - const asset = factory.asset({ id: assetId, ownerId: userId }); - const memory = factory.memory({ assets: [asset] }); + const asset = AssetFactory.create({ id: assetId, ownerId: userId }); + const memory = MemoryFactory.from().asset(asset).build(); mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([asset.id])); mocks.memory.create.mockResolvedValue(memory); @@ -131,7 +133,7 @@ describe(MemoryService.name, () => { }); it('should create a memory without assets', async () => { - const memory = factory.memory(); + const memory = MemoryFactory.create(); mocks.memory.create.mockResolvedValue(memory); @@ -155,7 +157,7 @@ describe(MemoryService.name, () => { }); it('should update a memory', async () => { - const memory = factory.memory(); + const memory = MemoryFactory.create(); mocks.access.memory.checkOwnerAccess.mockResolvedValue(new Set([memory.id])); mocks.memory.update.mockResolvedValue(memory); @@ -198,7 +200,7 @@ describe(MemoryService.name, () => { it('should require asset access', async () => { const assetId = newUuid(); - const memory = factory.memory(); + const memory = MemoryFactory.create(); mocks.access.memory.checkOwnerAccess.mockResolvedValue(new Set([memory.id])); mocks.memory.get.mockResolvedValue(memory); @@ -212,8 +214,8 @@ describe(MemoryService.name, () => { }); it('should skip assets already in the memory', async () => { - const asset = factory.asset(); - const memory = factory.memory({ assets: [asset] }); + const asset = AssetFactory.create(); + const memory = MemoryFactory.from().asset(asset).build(); mocks.access.memory.checkOwnerAccess.mockResolvedValue(new Set([memory.id])); mocks.memory.get.mockResolvedValue(memory); @@ -228,7 +230,7 @@ describe(MemoryService.name, () => { it('should add assets', async () => { const assetId = newUuid(); - const memory = factory.memory(); + const memory = MemoryFactory.create(); mocks.access.memory.checkOwnerAccess.mockResolvedValue(new Set([memory.id])); mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([assetId])); @@ -266,8 +268,8 @@ describe(MemoryService.name, () => { }); it('should remove assets', async () => { - const memory = factory.memory(); - const asset = factory.asset(); + const memory = MemoryFactory.create(); + const asset = AssetFactory.create(); mocks.access.memory.checkOwnerAccess.mockResolvedValue(new Set([memory.id])); mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([asset.id])); diff --git a/server/test/factories/memory.factory.ts b/server/test/factories/memory.factory.ts new file mode 100644 index 0000000000..bda1d15c25 --- /dev/null +++ b/server/test/factories/memory.factory.ts @@ -0,0 +1,45 @@ +import { Selectable } from 'kysely'; +import { MemoryType } from 'src/enum'; +import { MemoryTable } from 'src/schema/tables/memory.table'; +import { AssetFactory } from 'test/factories/asset.factory'; +import { build } from 'test/factories/builder.factory'; +import { AssetLike, FactoryBuilder, MemoryLike } from 'test/factories/types'; +import { newDate, newUuid, newUuidV7 } from 'test/small.factory'; + +export class MemoryFactory { + #assets: AssetFactory[] = []; + + private constructor(private readonly value: Selectable) {} + + static create(dto: MemoryLike = {}) { + return MemoryFactory.from(dto).build(); + } + + static from(dto: MemoryLike = {}) { + return new MemoryFactory({ + id: newUuid(), + createdAt: newDate(), + updatedAt: newDate(), + updateId: newUuidV7(), + deletedAt: null, + ownerId: newUuid(), + type: MemoryType.OnThisDay, + data: { year: 2024 }, + isSaved: false, + memoryAt: newDate(), + seenAt: null, + showAt: newDate(), + hideAt: newDate(), + ...dto, + }); + } + + asset(asset: AssetLike, builder?: FactoryBuilder) { + this.#assets.push(build(AssetFactory.from(asset), builder)); + return this; + } + + build() { + return { ...this.value, assets: this.#assets.map((asset) => asset.build()) }; + } +} diff --git a/server/test/factories/types.ts b/server/test/factories/types.ts index c5a327a624..0e070c1bcc 100644 --- a/server/test/factories/types.ts +++ b/server/test/factories/types.ts @@ -6,6 +6,7 @@ import { AssetExifTable } from 'src/schema/tables/asset-exif.table'; import { AssetFaceTable } from 'src/schema/tables/asset-face.table'; import { AssetFileTable } from 'src/schema/tables/asset-file.table'; import { AssetTable } from 'src/schema/tables/asset.table'; +import { MemoryTable } from 'src/schema/tables/memory.table'; import { PersonTable } from 'src/schema/tables/person.table'; import { SharedLinkTable } from 'src/schema/tables/shared-link.table'; import { StackTable } from 'src/schema/tables/stack.table'; @@ -24,3 +25,4 @@ export type UserLike = Partial>; export type AssetFaceLike = Partial>; export type PersonLike = Partial>; export type StackLike = Partial>; +export type MemoryLike = Partial>; diff --git a/server/test/small.factory.ts b/server/test/small.factory.ts index 06a5798405..cd34c056a7 100644 --- a/server/test/small.factory.ts +++ b/server/test/small.factory.ts @@ -2,39 +2,24 @@ import { Activity, Album, ApiKey, - AssetFace, - AssetFile, AuthApiKey, AuthSharedLink, AuthUser, Exif, Library, - Memory, Partner, Person, Session, - Stack, Tag, User, UserAdmin, } from 'src/database'; -import { MapAsset } from 'src/dtos/asset-response.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 { - AssetFileType, - AssetOrder, - AssetStatus, - AssetType, - AssetVisibility, - MemoryType, - Permission, - SourceType, - UserMetadataKey, - UserStatus, -} from 'src/enum'; -import { DeepPartial, OnThisDayData, UserMetadataItem } from 'src/types'; +import { AssetFileType, AssetOrder, Permission, UserMetadataKey, UserStatus } from 'src/enum'; +import { UserMetadataItem } from 'src/types'; +import { UserFactory } from 'test/factories/user.factory'; import { v4, v7 } from 'uuid'; export const newUuid = () => v4(); @@ -123,9 +108,13 @@ const authUserFactory = (authUser: Partial = {}) => { return { id, isAdmin, name, email, quotaUsageInBytes, quotaSizeInBytes }; }; -const partnerFactory = (partner: Partial = {}) => { - const sharedBy = userFactory(partner.sharedBy || {}); - const sharedWith = userFactory(partner.sharedWith || {}); +const partnerFactory = ({ + sharedBy: sharedByProvided, + sharedWith: sharedWithProvided, + ...partner +}: Partial = {}) => { + const sharedBy = UserFactory.create(sharedByProvided ?? {}); + const sharedWith = UserFactory.create(sharedWithProvided ?? {}); return { sharedById: sharedBy.id, @@ -168,19 +157,6 @@ const queueStatisticsFactory = (dto?: Partial) => ({ ...dto, }); -const stackFactory = ({ owner, assets, ...stack }: DeepPartial = {}): Stack => { - const ownerId = 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 = {}) => ({ id: newUuid(), name: 'Test User', @@ -238,44 +214,6 @@ const userAdminFactory = (user: Partial = {}) => { }; }; -const assetFactory = ( - asset: Omit, 'exifInfo' | 'owner' | 'stack' | 'tags' | 'faces' | 'files' | 'edits'> = {}, -) => { - return { - id: newUuid(), - createdAt: newDate(), - updatedAt: newDate(), - deletedAt: null, - updateId: newUuidV7(), - status: AssetStatus.Active, - checksum: newSha1(), - deviceAssetId: '', - deviceId: '', - duplicateId: null, - duration: null, - encodedVideoPath: null, - fileCreatedAt: newDate(), - fileModifiedAt: newDate(), - isExternal: false, - isFavorite: false, - isOffline: false, - libraryId: null, - livePhotoVideoId: null, - localDateTime: newDate(), - originalFileName: 'IMG_123.jpg', - originalPath: `/data/12/34/IMG_123.jpg`, - ownerId: newUuid(), - stackId: null, - thumbhash: null, - type: AssetType.Image, - visibility: AssetVisibility.Timeline, - width: null, - height: null, - isEdited: false, - ...asset, - }; -}; - const activityFactory = (activity: Partial = {}) => { const userId = activity.userId || newUuid(); return { @@ -283,7 +221,7 @@ const activityFactory = (activity: Partial = {}) => { comment: null, isLiked: false, userId, - user: userFactory({ id: userId }), + user: UserFactory.create({ id: userId }), assetId: newUuid(), albumId: newUuid(), createdAt: newDate(), @@ -319,24 +257,6 @@ const libraryFactory = (library: Partial = {}) => ({ ...library, }); -const memoryFactory = (memory: Partial = {}) => ({ - id: newUuid(), - createdAt: newDate(), - updatedAt: newDate(), - updateId: newUuidV7(), - deletedAt: null, - ownerId: newUuid(), - type: MemoryType.OnThisDay, - data: { year: 2024 } as OnThisDayData, - isSaved: false, - memoryAt: newDate(), - seenAt: null, - showAt: newDate(), - hideAt: newDate(), - assets: [], - ...memory, -}); - const versionHistoryFactory = () => ({ id: newUuid(), createdAt: newDate(), @@ -403,49 +323,6 @@ const assetOcrFactory = ( ...ocr, }); -const assetFileFactory = (file: Partial = {}) => ({ - id: newUuid(), - type: AssetFileType.Preview, - path: '/uploads/user-id/thumbs/path.jpg', - isEdited: false, - isProgressive: false, - ...file, -}); - -const exifFactory = (exif: Partial = {}) => ({ - 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 => ({ id: newUuid(), color: null, @@ -456,25 +333,6 @@ const tagFactory = (tag: Partial): Tag => ({ ...tag, }); -const faceFactory = ({ person, ...face }: DeepPartial = {}): 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 => { switch (edit?.action) { case AssetEditAction.Crop: { @@ -529,26 +387,20 @@ const albumFactory = (album?: Partial>) => ({ export const factory = { activity: activityFactory, apiKey: apiKeyFactory, - asset: assetFactory, - assetFile: assetFileFactory, assetOcr: assetOcrFactory, auth: authFactory, authApiKey: authApiKeyFactory, authUser: authUserFactory, library: libraryFactory, - memory: memoryFactory, partner: partnerFactory, queueStatistics: queueStatisticsFactory, session: sessionFactory, - stack: stackFactory, user: userFactory, userAdmin: userAdminFactory, versionHistory: versionHistoryFactory, jobAssets: { sidecarWrite: assetSidecarWriteFactory, }, - exif: exifFactory, - face: faceFactory, person: personFactory, assetEdit: assetEditFactory, tag: tagFactory,