mirror of
https://github.com/immich-app/immich
synced 2026-08-15 13:03:57 +00:00
fix: medium tests dependencies (#30612)
This commit is contained in:
parent
a9a99cffbf
commit
5ad1e4e0f7
4 changed files with 45 additions and 35 deletions
|
|
@ -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<T extends BaseService>(Service: ClassConstructor<T>, ctx: BaseService) {
|
||||
static create<T extends ClassConstructor<typeof BaseService>>(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<T>;
|
||||
}
|
||||
|
||||
get worker() {
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ import {
|
|||
UserMetadataKey,
|
||||
WorkflowType,
|
||||
} from 'src/enum';
|
||||
import { Mocked } from 'vitest';
|
||||
|
||||
export type DeepPartial<T> = 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<T = any> extends Function {
|
||||
new (...args: any[]): T;
|
||||
}
|
||||
export type ClassConstructor<T> = T extends new (...args: infer R) => infer L
|
||||
? new (...args: R) => L
|
||||
: new (...args: any[]) => unknown;
|
||||
|
||||
export type ClassConstructorsToInstances<T extends readonly ClassConstructor<unknown>[]> = {
|
||||
[K in keyof T]: InstanceType<T[K]> | Mocked<InstanceType<T[K]>>;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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<any>[];
|
||||
real: ClassConstructor<any>[];
|
||||
mock: Array<(typeof BASE_SERVICE_DEPENDENCIES)[number]>;
|
||||
real: Array<(typeof BASE_SERVICE_DEPENDENCIES)[number]>;
|
||||
database: Kysely<DB>;
|
||||
};
|
||||
|
||||
export const newMediumService = <S extends BaseService>(Service: ClassConstructor<S>, options: MediumTestOptions) => {
|
||||
type BaseServiceDeps = typeof BASE_SERVICE_DEPENDENCIES;
|
||||
|
||||
export const newMediumService = <S extends ClassConstructor<typeof BaseService>>(
|
||||
Service: S,
|
||||
options: MediumTestOptions,
|
||||
) => {
|
||||
const ctx = new MediumTestContext(Service, options);
|
||||
return { sut: ctx.sut, ctx };
|
||||
};
|
||||
|
||||
export class MediumTestContext<S extends BaseService = BaseService> {
|
||||
export class MediumTestContext<S extends ClassConstructor<typeof BaseService> = ClassConstructor<typeof BaseService>> {
|
||||
private repoCache: Record<string, any> = {};
|
||||
private sutDeps: any[];
|
||||
private sutDeps: ClassConstructorsToInstances<BaseServiceDeps>;
|
||||
|
||||
sut: S;
|
||||
sut: InstanceType<S>;
|
||||
database: Kysely<DB>;
|
||||
|
||||
constructor(
|
||||
Service: ClassConstructor<S>,
|
||||
Service: S,
|
||||
private options: MediumTestOptions,
|
||||
) {
|
||||
this.sutDeps = this.makeDeps(options);
|
||||
this.sut = new Service(...this.sutDeps);
|
||||
this.sut = new Service(...this.sutDeps) as InstanceType<S>;
|
||||
this.database = options.database;
|
||||
}
|
||||
|
||||
|
|
@ -128,7 +133,7 @@ export class MediumTestContext<S extends BaseService = BaseService> {
|
|||
throw new Error(`Real repository ${dep.name} is not a valid dependency`);
|
||||
}
|
||||
}
|
||||
return (deps as ClassConstructor<any>[]).map((dep) => {
|
||||
return deps.map((dep) => {
|
||||
if (options.real.includes(dep)) {
|
||||
return this.get(dep);
|
||||
}
|
||||
|
|
@ -136,10 +141,10 @@ export class MediumTestContext<S extends BaseService = BaseService> {
|
|||
if (options.mock.includes(dep)) {
|
||||
return newMockRepository(dep);
|
||||
}
|
||||
});
|
||||
}) as unknown as ClassConstructorsToInstances<BaseServiceDeps>;
|
||||
}
|
||||
|
||||
get<T>(key: ClassConstructor<T>): T {
|
||||
get<T extends BaseServiceDeps[number]>(key: T): InstanceType<T> {
|
||||
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<S extends BaseService = BaseService> {
|
|||
return this.repoCache[key.name];
|
||||
}
|
||||
|
||||
getMock<T, R = Mocked<T>>(key: ClassConstructor<T>): R {
|
||||
const index = BASE_SERVICE_DEPENDENCIES.indexOf(key as any);
|
||||
getMock<T extends BaseServiceDeps[number], R = Mocked<InstanceType<T>>>(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<S extends BaseService = BaseService> {
|
|||
}
|
||||
}
|
||||
|
||||
export class SyncTestContext extends MediumTestContext<SyncService> {
|
||||
export class SyncTestContext extends MediumTestContext<typeof SyncService> {
|
||||
constructor(database: Kysely<DB>) {
|
||||
super(SyncService, {
|
||||
database,
|
||||
|
|
@ -379,7 +384,7 @@ const mockStats = {
|
|||
birthtimeMs: 0,
|
||||
};
|
||||
|
||||
export class ExifTestContext extends MediumTestContext<MetadataService> {
|
||||
export class ExifTestContext extends MediumTestContext<typeof MetadataService> {
|
||||
constructor(database: Kysely<DB>) {
|
||||
super(MetadataService, {
|
||||
database,
|
||||
|
|
@ -431,7 +436,7 @@ export class ExifTestContext extends MediumTestContext<MetadataService> {
|
|||
}
|
||||
}
|
||||
|
||||
const newRealRepository = <T>(key: ClassConstructor<T>, db: Kysely<DB>): T => {
|
||||
const newRealRepository = <T extends BaseServiceDeps[number]>(key: T, db: Kysely<DB>): InstanceType<T> => {
|
||||
switch (key) {
|
||||
case AccessRepository:
|
||||
case AlbumRepository:
|
||||
|
|
@ -457,41 +462,41 @@ const newRealRepository = <T>(key: ClassConstructor<T>, db: Kysely<DB>): T => {
|
|||
case UserRepository:
|
||||
case VersionHistoryRepository:
|
||||
case WorkflowRepository: {
|
||||
return new key(db);
|
||||
return new key(db) as InstanceType<T>;
|
||||
}
|
||||
|
||||
case ConfigRepository:
|
||||
case CryptoRepository: {
|
||||
return new key();
|
||||
return new key() as InstanceType<T>;
|
||||
}
|
||||
|
||||
case DatabaseRepository: {
|
||||
return new key(db, LoggingRepository.create(), new ConfigRepository());
|
||||
return new key(db, LoggingRepository.create(), new ConfigRepository()) as InstanceType<T>;
|
||||
}
|
||||
|
||||
case EmailRepository: {
|
||||
return new key(LoggingRepository.create());
|
||||
return new key(LoggingRepository.create()) as InstanceType<T>;
|
||||
}
|
||||
|
||||
case MediaRepository:
|
||||
case MetadataRepository: {
|
||||
return new key(LoggingRepository.create());
|
||||
return new key(LoggingRepository.create()) as InstanceType<T>;
|
||||
}
|
||||
|
||||
case PluginRepository: {
|
||||
return new key(db, LoggingRepository.create());
|
||||
return new key(db, LoggingRepository.create()) as InstanceType<T>;
|
||||
}
|
||||
|
||||
case StorageRepository: {
|
||||
return new key(LoggingRepository.create());
|
||||
return new key(LoggingRepository.create()) as InstanceType<T>;
|
||||
}
|
||||
|
||||
case TagRepository: {
|
||||
return new key(db, LoggingRepository.create());
|
||||
return new key(db, LoggingRepository.create()) as InstanceType<T>;
|
||||
}
|
||||
|
||||
case LoggingRepository as unknown as ClassConstructor<LoggingRepository>: {
|
||||
return new key() as unknown as T;
|
||||
case LoggingRepository: {
|
||||
return new key(undefined, undefined) as InstanceType<T>;
|
||||
}
|
||||
|
||||
default: {
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ import { getKyselyDB } from 'test/utils';
|
|||
|
||||
let isInitialized = false;
|
||||
|
||||
class WorkflowTestContext extends MediumTestContext<WorkflowExecutionService> {
|
||||
class WorkflowTestContext extends MediumTestContext<typeof WorkflowExecutionService> {
|
||||
constructor(database: Kysely<DB>) {
|
||||
super(WorkflowExecutionService, {
|
||||
database,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue