From 5ad1e4e0f7ecefdb6f43ba74a01dc92b9ea6b7cb Mon Sep 17 00:00:00 2001 From: Daniel Dietzler <36593685+danieldietzler@users.noreply.github.com> Date: Fri, 7 Aug 2026 11:36:20 +0200 Subject: [PATCH] fix: medium tests dependencies (#30612) --- server/src/services/base.service.ts | 7 ++- server/src/types.ts | 12 ++-- server/test/medium.factory.ts | 59 ++++++++++--------- .../workflow/workflow-core-plugin.spec.ts | 2 +- 4 files changed, 45 insertions(+), 35 deletions(-) diff --git a/server/src/services/base.service.ts b/server/src/services/base.service.ts index 2195c66a22..6ed8ce925a 100644 --- a/server/src/services/base.service.ts +++ b/server/src/services/base.service.ts @@ -117,7 +117,7 @@ export const BASE_SERVICE_DEPENDENCIES = [ ViewRepository, WebsocketRepository, WorkflowRepository, -]; +] as const; @Injectable() export class BaseService { @@ -191,7 +191,7 @@ export class BaseService { ); } - static create(Service: ClassConstructor, ctx: BaseService) { + static create>(Service: T, ctx: BaseService) { const service = new Service( LoggingRepository.create(), ctx.accessRepository, @@ -242,6 +242,7 @@ export class BaseService { ctx.trashRepository, ctx.userRepository, ctx.versionRepository, + ctx.videoStreamRepository, ctx.viewRepository, ctx.websocketRepository, ctx.workflowRepository, @@ -249,7 +250,7 @@ export class BaseService { service.logger.setContext(BaseService.name); - return service as T; + return service as InstanceType; } get worker() { diff --git a/server/src/types.ts b/server/src/types.ts index dca3119c76..27995e841f 100644 --- a/server/src/types.ts +++ b/server/src/types.ts @@ -32,6 +32,7 @@ import { UserMetadataKey, WorkflowType, } from 'src/enum'; +import { Mocked } from 'vitest'; export type DeepPartial = T extends Date ? T @@ -647,7 +648,10 @@ export type JSONSchemaProperty = { required?: string[]; }; -// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type -export interface ClassConstructor extends Function { - new (...args: any[]): T; -} +export type ClassConstructor = T extends new (...args: infer R) => infer L + ? new (...args: R) => L + : new (...args: any[]) => unknown; + +export type ClassConstructorsToInstances[]> = { + [K in keyof T]: InstanceType | Mocked>; +}; diff --git a/server/test/medium.factory.ts b/server/test/medium.factory.ts index b335a0fba5..f0f14757b2 100644 --- a/server/test/medium.factory.ts +++ b/server/test/medium.factory.ts @@ -76,7 +76,7 @@ import { UserTable } from 'src/schema/tables/user.table'; import { BASE_SERVICE_DEPENDENCIES, BaseService } from 'src/services/base.service'; import { MetadataService } from 'src/services/metadata.service'; import { SyncService } from 'src/services/sync.service'; -import { ClassConstructor, UploadFile } from 'src/types'; +import { ClassConstructor, ClassConstructorsToInstances, UploadFile } from 'src/types'; import { getConfig, updateConfig } from 'src/utils/config'; import { mockEnvData } from 'test/repositories/config.repository.mock'; import { newTelemetryRepositoryMock } from 'test/repositories/telemetry.repository.mock'; @@ -88,29 +88,34 @@ import { Mocked } from 'vitest'; export const testAssetsDir = resolve(__dirname, '../../e2e/test-assets'); type MediumTestOptions = { - mock: ClassConstructor[]; - real: ClassConstructor[]; + mock: Array<(typeof BASE_SERVICE_DEPENDENCIES)[number]>; + real: Array<(typeof BASE_SERVICE_DEPENDENCIES)[number]>; database: Kysely; }; -export const newMediumService = (Service: ClassConstructor, options: MediumTestOptions) => { +type BaseServiceDeps = typeof BASE_SERVICE_DEPENDENCIES; + +export const newMediumService = >( + Service: S, + options: MediumTestOptions, +) => { const ctx = new MediumTestContext(Service, options); return { sut: ctx.sut, ctx }; }; -export class MediumTestContext { +export class MediumTestContext = ClassConstructor> { private repoCache: Record = {}; - private sutDeps: any[]; + private sutDeps: ClassConstructorsToInstances; - sut: S; + sut: InstanceType; database: Kysely; constructor( - Service: ClassConstructor, + Service: S, private options: MediumTestOptions, ) { this.sutDeps = this.makeDeps(options); - this.sut = new Service(...this.sutDeps); + this.sut = new Service(...this.sutDeps) as InstanceType; this.database = options.database; } @@ -128,7 +133,7 @@ export class MediumTestContext { throw new Error(`Real repository ${dep.name} is not a valid dependency`); } } - return (deps as ClassConstructor[]).map((dep) => { + return deps.map((dep) => { if (options.real.includes(dep)) { return this.get(dep); } @@ -136,10 +141,10 @@ export class MediumTestContext { if (options.mock.includes(dep)) { return newMockRepository(dep); } - }); + }) as unknown as ClassConstructorsToInstances; } - get(key: ClassConstructor): T { + get(key: T): InstanceType { if (!Object.hasOwn(this.repoCache, key.name)) { const real = newRealRepository(key, this.options.database); this.repoCache[key.name] = real; @@ -148,8 +153,8 @@ export class MediumTestContext { return this.repoCache[key.name]; } - getMock>(key: ClassConstructor): R { - const index = BASE_SERVICE_DEPENDENCIES.indexOf(key as any); + getMock>>(key: T): R { + const index = BASE_SERVICE_DEPENDENCIES.indexOf(key); if (index === -1 || !this.options.mock.includes(key)) { throw new Error(`getMock called with a key that is not a mock: ${key.name}`); } @@ -328,7 +333,7 @@ export class MediumTestContext { } } -export class SyncTestContext extends MediumTestContext { +export class SyncTestContext extends MediumTestContext { constructor(database: Kysely) { super(SyncService, { database, @@ -379,7 +384,7 @@ const mockStats = { birthtimeMs: 0, }; -export class ExifTestContext extends MediumTestContext { +export class ExifTestContext extends MediumTestContext { constructor(database: Kysely) { super(MetadataService, { database, @@ -431,7 +436,7 @@ export class ExifTestContext extends MediumTestContext { } } -const newRealRepository = (key: ClassConstructor, db: Kysely): T => { +const newRealRepository = (key: T, db: Kysely): InstanceType => { switch (key) { case AccessRepository: case AlbumRepository: @@ -457,41 +462,41 @@ const newRealRepository = (key: ClassConstructor, db: Kysely): T => { case UserRepository: case VersionHistoryRepository: case WorkflowRepository: { - return new key(db); + return new key(db) as InstanceType; } case ConfigRepository: case CryptoRepository: { - return new key(); + return new key() as InstanceType; } case DatabaseRepository: { - return new key(db, LoggingRepository.create(), new ConfigRepository()); + return new key(db, LoggingRepository.create(), new ConfigRepository()) as InstanceType; } case EmailRepository: { - return new key(LoggingRepository.create()); + return new key(LoggingRepository.create()) as InstanceType; } case MediaRepository: case MetadataRepository: { - return new key(LoggingRepository.create()); + return new key(LoggingRepository.create()) as InstanceType; } case PluginRepository: { - return new key(db, LoggingRepository.create()); + return new key(db, LoggingRepository.create()) as InstanceType; } case StorageRepository: { - return new key(LoggingRepository.create()); + return new key(LoggingRepository.create()) as InstanceType; } case TagRepository: { - return new key(db, LoggingRepository.create()); + return new key(db, LoggingRepository.create()) as InstanceType; } - case LoggingRepository as unknown as ClassConstructor: { - return new key() as unknown as T; + case LoggingRepository: { + return new key(undefined, undefined) as InstanceType; } default: { diff --git a/server/test/medium/specs/workflow/workflow-core-plugin.spec.ts b/server/test/medium/specs/workflow/workflow-core-plugin.spec.ts index 8bd65af73f..dfb4868c0d 100644 --- a/server/test/medium/specs/workflow/workflow-core-plugin.spec.ts +++ b/server/test/medium/specs/workflow/workflow-core-plugin.spec.ts @@ -24,7 +24,7 @@ import { getKyselyDB } from 'test/utils'; let isInitialized = false; -class WorkflowTestContext extends MediumTestContext { +class WorkflowTestContext extends MediumTestContext { constructor(database: Kysely) { super(WorkflowExecutionService, { database,