refactor: e2e tests (#4536)

This commit is contained in:
Jason Rasmussen 2023-10-18 18:02:42 -04:00 committed by GitHub
parent 31987bc043
commit 4b59f83288
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 189 additions and 201 deletions

View File

@ -21,7 +21,7 @@ jobs:
submodules: "recursive"
- name: Run e2e tests
run: docker-compose -f ./docker/docker-compose.test.yml -p immich-test-e2e up --renew-anon-volumes --abort-on-container-exit --exit-code-from immich-server-test --remove-orphans --build
run: docker compose -f ./docker/docker-compose.test.yml up --renew-anon-volumes --abort-on-container-exit --exit-code-from immich-server --remove-orphans --build
doc-tests:
name: Run documentation checks

View File

@ -20,7 +20,7 @@ pull-stage:
docker-compose -f ./docker/docker-compose.staging.yml pull
test-e2e:
docker-compose -f ./docker/docker-compose.test.yml -p immich-test-e2e up --renew-anon-volumes --abort-on-container-exit --exit-code-from immich-server-test --remove-orphans --build
docker compose -f ./docker/docker-compose.test.yml up --renew-anon-volumes --abort-on-container-exit --exit-code-from immich-server --remove-orphans --build
prod:
docker-compose -f ./docker/docker-compose.prod.yml up --build -V --remove-orphans
@ -32,4 +32,4 @@ api:
cd ./server && npm run api:generate
attach-server:
docker exec -it docker_immich-server_1 sh
docker exec -it docker_immich-server_1 sh

View File

@ -1,10 +1,10 @@
version: "3.8"
# Compose file for dockerized end-to-end testing of the backend
name: "immich-test-e2e"
services:
immich-server-test:
image: immich-server-test
immich-server:
image: immich-server-dev:latest
build:
context: ../server
dockerfile: Dockerfile
@ -14,27 +14,20 @@ services:
- ../server:/usr/src/app
- /usr/src/app/node_modules
environment:
- DB_HOSTNAME=immich-database-test
- DB_HOSTNAME=database
- DB_USERNAME=postgres
- DB_PASSWORD=postgres
- DB_DATABASE_NAME=e2e_test
- IMMICH_RUN_ALL_TESTS=true
depends_on:
- immich-database-test
networks:
- immich-test-network
- database
immich-database-test:
container_name: immich-database-test
database:
image: postgres:14-alpine@sha256:28407a9961e76f2d285dc6991e8e48893503cc3836a4755bbc2d40bcc272a441
command: -c fsync=off
environment:
POSTGRES_PASSWORD: postgres
POSTGRES_USER: postgres
POSTGRES_DB: e2e_test
networks:
- immich-test-network
logging:
driver: none
networks:
immich-test-network:

View File

@ -26,7 +26,7 @@
"test:watch": "jest --watch",
"test:cov": "jest --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:e2e": "NODE_OPTIONS='--experimental-vm-modules --max_old_space_size=4096' jest --config test/e2e/jest-e2e.json --runInBand --forceExit",
"test:e2e": "NODE_OPTIONS='--experimental-vm-modules --max_old_space_size=4096' jest --config test/e2e/jest-e2e.json --runInBand",
"typeorm": "typeorm",
"typeorm:migrations:create": "typeorm migration:create",
"typeorm:migrations:generate": "typeorm migration:generate -d ./dist/infra/database.config.js",

View File

@ -109,6 +109,10 @@ export class MetadataService {
}
}
async teardown() {
await this.repository.teardown();
}
async handleLivePhotoLinking(job: IEntityJob) {
const { id } = job;
const [asset] = await this.assetRepository.getByIds([id]);

View File

@ -26,6 +26,7 @@ export interface ImmichTags extends Omit<Tags, 'FocalLength'> {
export interface IMetadataRepository {
init(options: Partial<InitOptions>): Promise<void>;
teardown(): Promise<void>;
reverseGeocode(point: GeoPoint): Promise<ReverseGeocodeResult>;
deleteCache(): Promise<void>;
getExifTags(path: string): Promise<ImmichTags | null>;

View File

@ -45,6 +45,10 @@ export class MetadataRepository implements IMetadataRepository {
});
}
async teardown() {
await exiftool.end();
}
async deleteCache() {
const dumpDirectory = REVERSE_GEOCODING_DUMP_DIRECTORY;
if (dumpDirectory) {

View File

@ -103,4 +103,8 @@ export class AppService {
await this.metadataService.init();
await this.searchService.init();
}
async teardown() {
await this.metadataService.teardown();
}
}

View File

@ -2,11 +2,10 @@ import { AlbumResponseDto, LoginResponseDto } from '@app/domain';
import { AlbumController } from '@app/immich';
import { AssetFileUploadResponseDto } from '@app/immich/api-v1/asset/response-dto/asset-file-upload-response.dto';
import { SharedLinkType } from '@app/infra/entities';
import { INestApplication } from '@nestjs/common';
import { api } from '@test/api';
import { db } from '@test/db';
import { errorStub, uuidStub } from '@test/fixtures';
import { createTestApp } from '@test/test-utils';
import { testApp } from '@test/test-utils';
import request from 'supertest';
const user1SharedUser = 'user1SharedUser';
@ -17,7 +16,6 @@ const user2SharedLink = 'user2SharedLink';
const user2NotShared = 'user2NotShared';
describe(`${AlbumController.name} (e2e)`, () => {
let app: INestApplication;
let server: any;
let admin: LoginResponseDto;
let user1: LoginResponseDto;
@ -27,9 +25,11 @@ describe(`${AlbumController.name} (e2e)`, () => {
let user2Albums: AlbumResponseDto[];
beforeAll(async () => {
app = await createTestApp();
[server] = await testApp.create();
});
server = app.getHttpServer();
afterAll(async () => {
await testApp.teardown();
});
beforeEach(async () => {
@ -37,24 +37,30 @@ describe(`${AlbumController.name} (e2e)`, () => {
await api.authApi.adminSignUp(server);
admin = await api.authApi.adminLogin(server);
await api.userApi.create(server, admin.accessToken, {
email: 'user1@immich.app',
password: 'Password123',
firstName: 'User 1',
lastName: 'Test',
});
user1 = await api.authApi.login(server, { email: 'user1@immich.app', password: 'Password123' });
await Promise.all([
api.userApi.create(server, admin.accessToken, {
email: 'user1@immich.app',
password: 'Password123',
firstName: 'User 1',
lastName: 'Test',
}),
api.userApi.create(server, admin.accessToken, {
email: 'user2@immich.app',
password: 'Password123',
firstName: 'User 2',
lastName: 'Test',
}),
]);
await api.userApi.create(server, admin.accessToken, {
email: 'user2@immich.app',
password: 'Password123',
firstName: 'User 2',
lastName: 'Test',
});
user2 = await api.authApi.login(server, { email: 'user2@immich.app', password: 'Password123' });
[user1, user2] = await Promise.all([
api.authApi.login(server, { email: 'user1@immich.app', password: 'Password123' }),
api.authApi.login(server, { email: 'user2@immich.app', password: 'Password123' }),
]);
user1Asset = await api.assetApi.upload(server, user1.accessToken, 'example');
user1Albums = await Promise.all([
const albums = await Promise.all([
// user 1
api.albumApi.create(server, user1.accessToken, {
albumName: user1SharedUser,
sharedWithUserIds: [user2.userId],
@ -62,15 +68,8 @@ describe(`${AlbumController.name} (e2e)`, () => {
}),
api.albumApi.create(server, user1.accessToken, { albumName: user1SharedLink, assetIds: [user1Asset.id] }),
api.albumApi.create(server, user1.accessToken, { albumName: user1NotShared, assetIds: [user1Asset.id] }),
]);
// add shared link to user1SharedLink album
await api.sharedLinkApi.create(server, user1.accessToken, {
type: SharedLinkType.ALBUM,
albumId: user1Albums[1].id,
});
user2Albums = await Promise.all([
// user 2
api.albumApi.create(server, user2.accessToken, {
albumName: user2SharedUser,
sharedWithUserIds: [user1.userId],
@ -80,16 +79,22 @@ describe(`${AlbumController.name} (e2e)`, () => {
api.albumApi.create(server, user2.accessToken, { albumName: user2NotShared }),
]);
// add shared link to user2SharedLink album
await api.sharedLinkApi.create(server, user2.accessToken, {
type: SharedLinkType.ALBUM,
albumId: user2Albums[1].id,
});
});
user1Albums = albums.slice(0, 3);
user2Albums = albums.slice(3);
afterAll(async () => {
await db.disconnect();
await app.close();
await Promise.all([
// add shared link to user1SharedLink album
api.sharedLinkApi.create(server, user1.accessToken, {
type: SharedLinkType.ALBUM,
albumId: user1Albums[1].id,
}),
// add shared link to user2SharedLink album
api.sharedLinkApi.create(server, user2.accessToken, {
type: SharedLinkType.ALBUM,
albumId: user2Albums[1].id,
}),
]);
});
describe('GET /album', () => {

View File

@ -12,7 +12,7 @@ import { AssetEntity, AssetType, SharedLinkType } from '@app/infra/entities';
import { INestApplication } from '@nestjs/common';
import { api } from '@test/api';
import { errorStub, uuidStub } from '@test/fixtures';
import { createTestApp, db } from '@test/test-utils';
import { db, testApp } from '@test/test-utils';
import { randomBytes } from 'crypto';
import request from 'supertest';
@ -86,12 +86,14 @@ describe(`${AssetController.name} (e2e)`, () => {
let asset4: AssetEntity;
beforeAll(async () => {
app = await createTestApp();
server = app.getHttpServer();
[server, app] = await testApp.create();
assetRepository = app.get<IAssetRepository>(IAssetRepository);
});
afterAll(async () => {
await testApp.teardown();
});
beforeEach(async () => {
await db.reset();
await api.authApi.adminSignUp(server);
@ -123,11 +125,6 @@ describe(`${AssetController.name} (e2e)`, () => {
});
});
afterAll(async () => {
await db.disconnect();
await app.close();
});
describe('POST /asset/upload', () => {
it('should require authentication', async () => {
const { status, body } = await request(server)
@ -589,9 +586,11 @@ describe(`${AssetController.name} (e2e)`, () => {
describe('GET /asset/map-marker', () => {
beforeEach(async () => {
await assetRepository.save({ id: asset1.id, isArchived: true });
await assetRepository.upsertExif({ assetId: asset1.id, latitude: 0, longitude: 0 });
await assetRepository.upsertExif({ assetId: asset2.id, latitude: 0, longitude: 0 });
await Promise.all([
assetRepository.save({ id: asset1.id, isArchived: true }),
assetRepository.upsertExif({ assetId: asset1.id, latitude: 0, longitude: 0 }),
assetRepository.upsertExif({ assetId: asset2.id, latitude: 0, longitude: 0 }),
]);
});
it('should require authentication', async () => {

View File

@ -1,5 +1,4 @@
import { AuthController } from '@app/immich';
import { INestApplication } from '@nestjs/common';
import { api } from '@test/api';
import { db } from '@test/db';
import {
@ -12,7 +11,7 @@ import {
signupResponseStub,
uuidStub,
} from '@test/fixtures';
import { createTestApp } from '@test/test-utils';
import { testApp } from '@test/test-utils';
import request from 'supertest';
const firstName = 'Immich';
@ -21,13 +20,16 @@ const password = 'Password123';
const email = 'admin@immich.app';
describe(`${AuthController.name} (e2e)`, () => {
let app: INestApplication;
let server: any;
let accessToken: string;
beforeAll(async () => {
app = await createTestApp();
server = app.getHttpServer();
await testApp.reset();
[server] = await testApp.create();
});
afterAll(async () => {
await testApp.teardown();
});
beforeEach(async () => {
@ -37,11 +39,6 @@ describe(`${AuthController.name} (e2e)`, () => {
accessToken = response.accessToken;
});
afterAll(async () => {
await db.disconnect();
await app.close();
});
describe('POST /auth/admin-sign-up', () => {
beforeEach(async () => {
await db.reset();

View File

@ -1,11 +1,9 @@
import { LoginResponseDto } from '@app/domain';
import { AssetType, LibraryType } from '@app/infra/entities';
import { INestApplication } from '@nestjs/common';
import { api } from '@test/api';
import { IMMICH_TEST_ASSET_PATH, createTestApp, db, runAllTests } from '@test/test-utils';
import { IMMICH_TEST_ASSET_PATH, db, runAllTests, testApp } from '@test/test-utils';
describe(`Supported file formats (e2e)`, () => {
let app: INestApplication;
let server: any;
let admin: LoginResponseDto;
@ -170,8 +168,11 @@ describe(`Supported file formats (e2e)`, () => {
const testsToRun = formatTests.filter((formatTest) => formatTest.runTest);
beforeAll(async () => {
app = await createTestApp(true);
server = app.getHttpServer();
[server] = await testApp.create({ jobs: true });
});
afterAll(async () => {
await testApp.teardown();
});
beforeEach(async () => {
@ -181,11 +182,6 @@ describe(`Supported file formats (e2e)`, () => {
await api.userApi.setExternalPath(server, admin.accessToken, admin.userId, '/');
});
afterAll(async () => {
await db.disconnect();
await app.close();
});
it.each(testsToRun)('should import file of format $format', async (testedFormat) => {
const library = await api.libraryApi.create(server, admin.accessToken, {
type: LibraryType.EXTERNAL,

View File

@ -1,22 +1,14 @@
import { LibraryResponseDto, LoginResponseDto } from '@app/domain';
import { LibraryController } from '@app/immich';
import { AssetType, LibraryType } from '@app/infra/entities';
import { INestApplication } from '@nestjs/common';
import { api } from '@test/api';
import {
IMMICH_TEST_ASSET_PATH,
IMMICH_TEST_ASSET_TEMP_PATH,
createTestApp,
db,
restoreTempFolder,
} from '@test/test-utils';
import { IMMICH_TEST_ASSET_PATH, IMMICH_TEST_ASSET_TEMP_PATH, db, restoreTempFolder, testApp } from '@test/test-utils';
import * as fs from 'fs';
import request from 'supertest';
import { utimes } from 'utimes';
import { errorStub, uuidStub } from '../fixtures';
describe(`${LibraryController.name} (e2e)`, () => {
let app: INestApplication;
let server: any;
let admin: LoginResponseDto;
@ -35,8 +27,12 @@ describe(`${LibraryController.name} (e2e)`, () => {
};
beforeAll(async () => {
app = await createTestApp(true);
server = app.getHttpServer();
[server] = await testApp.create({ jobs: true });
});
afterAll(async () => {
await testApp.teardown();
await restoreTempFolder();
});
beforeEach(async () => {
@ -46,12 +42,6 @@ describe(`${LibraryController.name} (e2e)`, () => {
admin = await api.authApi.adminLogin(server);
});
afterAll(async () => {
await db.disconnect();
await app.close();
await restoreTempFolder();
});
describe('GET /library', () => {
it('should require authentication', async () => {
const { status, body } = await request(server).get('/library');

View File

@ -1,18 +1,19 @@
import { OAuthController } from '@app/immich';
import { INestApplication } from '@nestjs/common';
import { api } from '@test/api';
import { db } from '@test/db';
import { errorStub } from '@test/fixtures';
import { createTestApp } from '@test/test-utils';
import { testApp } from '@test/test-utils';
import request from 'supertest';
describe(`${OAuthController.name} (e2e)`, () => {
let app: INestApplication;
let server: any;
beforeAll(async () => {
app = await createTestApp();
server = app.getHttpServer();
[server] = await testApp.create();
});
afterAll(async () => {
await testApp.teardown();
});
beforeEach(async () => {
@ -20,11 +21,6 @@ describe(`${OAuthController.name} (e2e)`, () => {
await api.authApi.adminSignUp(server);
});
afterAll(async () => {
await db.disconnect();
await app.close();
});
describe('POST /oauth/authorize', () => {
beforeEach(async () => {
await db.reset();

View File

@ -4,7 +4,7 @@ import { INestApplication } from '@nestjs/common';
import { api } from '@test/api';
import { db } from '@test/db';
import { errorStub } from '@test/fixtures';
import { createTestApp } from '@test/test-utils';
import { testApp } from '@test/test-utils';
import request from 'supertest';
const user1Dto = {
@ -31,27 +31,29 @@ describe(`${PartnerController.name} (e2e)`, () => {
let user2: LoginResponseDto;
beforeAll(async () => {
app = await createTestApp();
server = app.getHttpServer();
[server, app] = await testApp.create();
repository = app.get<IPartnerRepository>(IPartnerRepository);
});
afterAll(async () => {
await testApp.teardown();
});
beforeEach(async () => {
await db.reset();
await api.authApi.adminSignUp(server);
loginResponse = await api.authApi.adminLogin(server);
accessToken = loginResponse.accessToken;
await api.userApi.create(server, accessToken, user1Dto);
user1 = await api.authApi.login(server, { email: user1Dto.email, password: user1Dto.password });
await Promise.all([
api.userApi.create(server, accessToken, user1Dto),
api.userApi.create(server, accessToken, user2Dto),
]);
await api.userApi.create(server, accessToken, user2Dto);
user2 = await api.authApi.login(server, { email: user2Dto.email, password: user2Dto.password });
});
afterAll(async () => {
await db.disconnect();
await app.close();
[user1, user2] = await Promise.all([
api.authApi.login(server, { email: user1Dto.email, password: user1Dto.password }),
api.authApi.login(server, { email: user2Dto.email, password: user2Dto.password }),
]);
});
describe('GET /partner', () => {

View File

@ -5,7 +5,7 @@ import { INestApplication } from '@nestjs/common';
import { api } from '@test/api';
import { db } from '@test/db';
import { errorStub, uuidStub } from '@test/fixtures';
import { createTestApp } from '@test/test-utils';
import { testApp } from '@test/test-utils';
import request from 'supertest';
describe(`${PersonController.name}`, () => {
@ -18,11 +18,14 @@ describe(`${PersonController.name}`, () => {
let hiddenPerson: PersonEntity;
beforeAll(async () => {
app = await createTestApp();
server = app.getHttpServer();
[server, app] = await testApp.create();
personRepository = app.get<IPersonRepository>(IPersonRepository);
});
afterAll(async () => {
await testApp.teardown();
});
beforeEach(async () => {
await db.reset();
await api.authApi.adminSignUp(server);
@ -46,11 +49,6 @@ describe(`${PersonController.name}`, () => {
await personRepository.createFace({ assetId: faceAsset.id, personId: hiddenPerson.id });
});
afterAll(async () => {
await db.disconnect();
await app.close();
});
describe('GET /person', () => {
beforeEach(async () => {});

View File

@ -1,21 +1,22 @@
import { LoginResponseDto } from '@app/domain';
import { ServerInfoController } from '@app/immich';
import { INestApplication } from '@nestjs/common';
import { api } from '@test/api';
import { db } from '@test/db';
import { errorStub } from '@test/fixtures';
import { createTestApp } from '@test/test-utils';
import { testApp } from '@test/test-utils';
import request from 'supertest';
describe(`${ServerInfoController.name} (e2e)`, () => {
let app: INestApplication;
let server: any;
let accessToken: string;
let loginResponse: LoginResponseDto;
beforeAll(async () => {
app = await createTestApp();
server = app.getHttpServer();
[server] = await testApp.create();
});
afterAll(async () => {
await testApp.teardown();
});
beforeEach(async () => {
@ -25,11 +26,6 @@ describe(`${ServerInfoController.name} (e2e)`, () => {
accessToken = loginResponse.accessToken;
});
afterAll(async () => {
await db.disconnect();
await app.close();
});
describe('GET /server-info', () => {
it('should require authentication', async () => {
const { status, body } = await request(server).get('/server-info');

View File

@ -1,5 +1,5 @@
import { PostgreSqlContainer } from '@testcontainers/postgresql';
import * as fs from 'fs';
import { access } from 'fs/promises';
import path from 'path';
export default async () => {
@ -23,8 +23,7 @@ export default async () => {
}
const directoryExists = async (dirPath: string) =>
await fs.promises
.access(dirPath)
await access(dirPath)
.then(() => true)
.catch(() => false);

View File

@ -1,16 +1,10 @@
import { AlbumResponseDto, LoginResponseDto, SharedLinkResponseDto } from '@app/domain';
import { PartnerController } from '@app/immich';
import { LibraryType, SharedLinkType } from '@app/infra/entities';
import { INestApplication } from '@nestjs/common';
import { api } from '@test/api';
import { db } from '@test/db';
import { errorStub, uuidStub } from '@test/fixtures';
import {
IMMICH_TEST_ASSET_PATH,
IMMICH_TEST_ASSET_TEMP_PATH,
createTestApp,
restoreTempFolder,
} from '@test/test-utils';
import { IMMICH_TEST_ASSET_PATH, IMMICH_TEST_ASSET_TEMP_PATH, restoreTempFolder, testApp } from '@test/test-utils';
import { cp } from 'fs/promises';
import request from 'supertest';
@ -22,7 +16,6 @@ const user1Dto = {
};
describe(`${PartnerController.name} (e2e)`, () => {
let app: INestApplication;
let server: any;
let admin: LoginResponseDto;
let user1: LoginResponseDto;
@ -30,8 +23,12 @@ describe(`${PartnerController.name} (e2e)`, () => {
let sharedLink: SharedLinkResponseDto;
beforeAll(async () => {
app = await createTestApp(true);
server = app.getHttpServer();
[server] = await testApp.create({ jobs: true });
});
afterAll(async () => {
await testApp.teardown();
await restoreTempFolder();
});
beforeEach(async () => {
@ -49,12 +46,6 @@ describe(`${PartnerController.name} (e2e)`, () => {
});
});
afterAll(async () => {
await db.disconnect();
await app.close();
await restoreTempFolder();
});
describe('GET /shared-link', () => {
it('should require authentication', async () => {
const { status, body } = await request(server).get('/shared-link');

View File

@ -2,10 +2,11 @@ import { LoginResponseDto, UserResponseDto, UserService } from '@app/domain';
import { AppModule, UserController } from '@app/immich';
import { UserEntity } from '@app/infra/entities';
import { INestApplication } from '@nestjs/common';
import { getRepositoryToken } from '@nestjs/typeorm';
import { api } from '@test/api';
import { db } from '@test/db';
import { errorStub, userSignupStub, userStub } from '@test/fixtures';
import { createTestApp } from '@test/test-utils';
import { testApp } from '@test/test-utils';
import request from 'supertest';
import { Repository } from 'typeorm';
@ -18,10 +19,12 @@ describe(`${UserController.name}`, () => {
let userRepository: Repository<UserEntity>;
beforeAll(async () => {
app = await createTestApp();
userRepository = app.select(AppModule).get('UserEntityRepository');
[server, app] = await testApp.create();
userRepository = app.select(AppModule).get(getRepositoryToken(UserEntity));
});
server = app.getHttpServer();
afterAll(async () => {
await testApp.teardown();
});
beforeEach(async () => {

View File

@ -5,6 +5,7 @@ export const newMetadataRepositoryMock = (): jest.Mocked<IMetadataRepository> =>
deleteCache: jest.fn(),
getExifTags: jest.fn(),
init: jest.fn(),
teardown: jest.fn(),
reverseGeocode: jest.fn(),
};
};

View File

@ -1,9 +1,8 @@
import { dataSource } from '@app/infra';
import { IJobRepository, JobItem, JobItemHandler, QueueName } from '@app/domain';
import { AppModule } from '@app/immich';
import { INestApplication, Logger } from '@nestjs/common';
import { Test, TestingModule } from '@nestjs/testing';
import { dataSource } from '@app/infra';
import { INestApplication } from '@nestjs/common';
import { Test } from '@nestjs/testing';
import * as fs from 'fs';
import path from 'path';
import { AppService } from '../src/microservices/app.service';
@ -36,38 +35,48 @@ export const db = {
let _handler: JobItemHandler = () => Promise.resolve();
export async function createTestApp(runJobs = false, log = false): Promise<INestApplication> {
const moduleBuilder = Test.createTestingModule({
imports: [AppModule],
providers: [AppService],
})
.overrideProvider(IJobRepository)
.useValue({
addHandler: (_queueName: QueueName, _concurrency: number, handler: JobItemHandler) => (_handler = handler),
queue: (item: JobItem) => runJobs && _handler(item),
resume: jest.fn(),
empty: jest.fn(),
setConcurrency: jest.fn(),
getQueueStatus: jest.fn(),
getJobCounts: jest.fn(),
pause: jest.fn(),
} as IJobRepository);
const moduleFixture: TestingModule = await moduleBuilder.compile();
const app = moduleFixture.createNestApplication();
if (log) {
app.useLogger(new Logger());
} else {
app.useLogger(false);
}
await app.init();
const appService = app.get(AppService);
await appService.init();
return app;
interface TestAppOptions {
jobs: boolean;
}
let app: INestApplication;
export const testApp = {
create: async (options?: TestAppOptions): Promise<[any, INestApplication]> => {
const { jobs } = options || { jobs: false };
const moduleFixture = await Test.createTestingModule({ imports: [AppModule], providers: [AppService] })
.overrideProvider(IJobRepository)
.useValue({
addHandler: (_queueName: QueueName, _concurrency: number, handler: JobItemHandler) => (_handler = handler),
queue: (item: JobItem) => jobs && _handler(item),
resume: jest.fn(),
empty: jest.fn(),
setConcurrency: jest.fn(),
getQueueStatus: jest.fn(),
getJobCounts: jest.fn(),
pause: jest.fn(),
} as IJobRepository)
.compile();
app = await moduleFixture.createNestApplication().init();
if (jobs) {
await app.get(AppService).init();
}
return [app.getHttpServer(), app];
},
reset: async () => {
await db.reset();
},
teardown: async () => {
await app.get(AppService).teardown();
await db.disconnect();
await app.close();
},
};
export const runAllTests: boolean = process.env.IMMICH_RUN_ALL_TESTS === 'true';
const directoryExists = async (dirPath: string) =>