mirror of
https://github.com/immich-app/yucca.git
synced 2026-09-30 13:33:00 +08:00
148 lines
4.1 KiB
TypeScript
148 lines
4.1 KiB
TypeScript
import { INestApplication } from '@nestjs/common';
|
|
import { EventEmitterModule } from '@nestjs/event-emitter';
|
|
import { JwtModule } from '@nestjs/jwt';
|
|
import { ScheduleModule } from '@nestjs/schedule';
|
|
import { Test, TestingModule } from '@nestjs/testing';
|
|
import Database from 'better-sqlite3';
|
|
import { SqliteDialect } from 'kysely';
|
|
import { KyselyModule } from 'nestjs-kysely';
|
|
import { randomUUID } from 'node:crypto';
|
|
import { mkdir, mkdtemp } from 'node:fs/promises';
|
|
import { tmpdir } from 'node:os';
|
|
import { join } from 'node:path';
|
|
import { BackendType } from 'src/enum';
|
|
import { EventsGateway, GatewayEvent } from 'src/events/events.gateway';
|
|
import { ModuleConfigProvider } from 'src/moduleConfig';
|
|
import { controllers, repositories, services } from 'src/orchestrationApi.module';
|
|
import { BackendRepository } from 'src/repositories/backend.repository';
|
|
import { ResticRepository } from 'src/repositories/restic.repository';
|
|
import { newResticRepositoryMock, RepositoryInterface } from './mocks';
|
|
|
|
type GatewayListener = (event: GatewayEvent) => void;
|
|
|
|
export interface TestEventBus {
|
|
emit: GatewayListener;
|
|
on: (listener: GatewayListener) => void;
|
|
off: (listener: GatewayListener) => void;
|
|
}
|
|
|
|
function createEventBus(): TestEventBus {
|
|
const listeners = new Set<GatewayListener>();
|
|
return {
|
|
emit: (event) => {
|
|
for (const listener of listeners) {
|
|
listener(event);
|
|
}
|
|
},
|
|
on: (listener) => {
|
|
listeners.add(listener);
|
|
},
|
|
off: (listener) => {
|
|
listeners.delete(listener);
|
|
},
|
|
};
|
|
}
|
|
|
|
export interface TestContext {
|
|
app: INestApplication;
|
|
module: TestingModule;
|
|
gateway: EventsGateway;
|
|
events: TestEventBus;
|
|
database: InstanceType<typeof Database>;
|
|
resticMock: jest.Mocked<RepositoryInterface<ResticRepository>>;
|
|
backendId: string;
|
|
statePath: string;
|
|
backendPath: string;
|
|
}
|
|
|
|
export async function createTestingModule(): Promise<TestContext> {
|
|
const statePath = await mkdtemp(join(tmpdir(), 'yucca-test-'));
|
|
const backendPath = await mkdtemp(join(tmpdir(), 'yucca-backend-'));
|
|
await mkdir(join(statePath, 'logs'), { recursive: true });
|
|
|
|
const database = new Database(':memory:');
|
|
database.pragma('journal_mode = WAL');
|
|
|
|
const resticMock = newResticRepositoryMock();
|
|
const events = createEventBus();
|
|
|
|
const moduleFixture: TestingModule = await Test.createTestingModule({
|
|
imports: [
|
|
KyselyModule.forRoot([
|
|
{
|
|
namespace: 'orchestrator',
|
|
dialect: new SqliteDialect({ database }),
|
|
},
|
|
]),
|
|
EventEmitterModule.forRoot(),
|
|
JwtModule.register({}),
|
|
ScheduleModule.forRoot(),
|
|
],
|
|
controllers,
|
|
providers: [
|
|
{
|
|
provide: ModuleConfigProvider,
|
|
useValue: {
|
|
statePath,
|
|
requireLock: false,
|
|
requireWsAuth: false,
|
|
onInternalEvent: events.emit,
|
|
},
|
|
},
|
|
EventsGateway,
|
|
...repositories,
|
|
...services,
|
|
],
|
|
})
|
|
.overrideProvider(ResticRepository)
|
|
.useValue(resticMock)
|
|
.compile();
|
|
|
|
const app = moduleFixture.createNestApplication();
|
|
await app.init();
|
|
|
|
const backendId = 'test-backend';
|
|
await moduleFixture.get(BackendRepository).updateBackend(backendId, {
|
|
type: BackendType.Local,
|
|
path: backendPath,
|
|
});
|
|
|
|
return {
|
|
app,
|
|
module: moduleFixture,
|
|
gateway: moduleFixture.get(EventsGateway),
|
|
events,
|
|
database,
|
|
resticMock,
|
|
backendId,
|
|
statePath,
|
|
backendPath,
|
|
};
|
|
}
|
|
|
|
export function waitForEvent(
|
|
events: TestEventBus,
|
|
type: GatewayEvent['type'],
|
|
predicate: (event: GatewayEvent) => boolean = () => true,
|
|
): Promise<GatewayEvent> {
|
|
return new Promise((resolve) => {
|
|
const onEvent = (event: GatewayEvent) => {
|
|
if (event.type === type && predicate(event)) {
|
|
events.off(onEvent);
|
|
resolve(event);
|
|
}
|
|
};
|
|
events.on(onEvent);
|
|
});
|
|
}
|
|
|
|
export async function waitFor(predicate: () => boolean, timeoutMs = 2000): Promise<void> {
|
|
const deadline = Date.now() + timeoutMs;
|
|
while (!predicate()) {
|
|
if (Date.now() > deadline) {
|
|
throw new Error('Timed out waiting for condition');
|
|
}
|
|
await new Promise((resolve) => setImmediate(resolve));
|
|
}
|
|
}
|