mirror of
https://github.com/immich-app/immich.git
synced 2025-01-23 12:12:45 +01:00
refactor: e2e tests (#4536)
This commit is contained in:
parent
31987bc043
commit
4b59f83288
22 changed files with 189 additions and 201 deletions
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
|
@ -21,7 +21,7 @@ jobs:
|
||||||
submodules: "recursive"
|
submodules: "recursive"
|
||||||
|
|
||||||
- name: Run e2e tests
|
- 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:
|
doc-tests:
|
||||||
name: Run documentation checks
|
name: Run documentation checks
|
||||||
|
|
4
Makefile
4
Makefile
|
@ -20,7 +20,7 @@ pull-stage:
|
||||||
docker-compose -f ./docker/docker-compose.staging.yml pull
|
docker-compose -f ./docker/docker-compose.staging.yml pull
|
||||||
|
|
||||||
test-e2e:
|
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:
|
prod:
|
||||||
docker-compose -f ./docker/docker-compose.prod.yml up --build -V --remove-orphans
|
docker-compose -f ./docker/docker-compose.prod.yml up --build -V --remove-orphans
|
||||||
|
@ -32,4 +32,4 @@ api:
|
||||||
cd ./server && npm run api:generate
|
cd ./server && npm run api:generate
|
||||||
|
|
||||||
attach-server:
|
attach-server:
|
||||||
docker exec -it docker_immich-server_1 sh
|
docker exec -it docker_immich-server_1 sh
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
version: "3.8"
|
version: "3.8"
|
||||||
|
|
||||||
# Compose file for dockerized end-to-end testing of the backend
|
name: "immich-test-e2e"
|
||||||
|
|
||||||
services:
|
services:
|
||||||
immich-server-test:
|
immich-server:
|
||||||
image: immich-server-test
|
image: immich-server-dev:latest
|
||||||
build:
|
build:
|
||||||
context: ../server
|
context: ../server
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
|
@ -14,27 +14,20 @@ services:
|
||||||
- ../server:/usr/src/app
|
- ../server:/usr/src/app
|
||||||
- /usr/src/app/node_modules
|
- /usr/src/app/node_modules
|
||||||
environment:
|
environment:
|
||||||
- DB_HOSTNAME=immich-database-test
|
- DB_HOSTNAME=database
|
||||||
- DB_USERNAME=postgres
|
- DB_USERNAME=postgres
|
||||||
- DB_PASSWORD=postgres
|
- DB_PASSWORD=postgres
|
||||||
- DB_DATABASE_NAME=e2e_test
|
- DB_DATABASE_NAME=e2e_test
|
||||||
- IMMICH_RUN_ALL_TESTS=true
|
- IMMICH_RUN_ALL_TESTS=true
|
||||||
depends_on:
|
depends_on:
|
||||||
- immich-database-test
|
- database
|
||||||
networks:
|
|
||||||
- immich-test-network
|
|
||||||
|
|
||||||
immich-database-test:
|
database:
|
||||||
container_name: immich-database-test
|
|
||||||
image: postgres:14-alpine@sha256:28407a9961e76f2d285dc6991e8e48893503cc3836a4755bbc2d40bcc272a441
|
image: postgres:14-alpine@sha256:28407a9961e76f2d285dc6991e8e48893503cc3836a4755bbc2d40bcc272a441
|
||||||
|
command: -c fsync=off
|
||||||
environment:
|
environment:
|
||||||
POSTGRES_PASSWORD: postgres
|
POSTGRES_PASSWORD: postgres
|
||||||
POSTGRES_USER: postgres
|
POSTGRES_USER: postgres
|
||||||
POSTGRES_DB: e2e_test
|
POSTGRES_DB: e2e_test
|
||||||
networks:
|
|
||||||
- immich-test-network
|
|
||||||
logging:
|
logging:
|
||||||
driver: none
|
driver: none
|
||||||
|
|
||||||
networks:
|
|
||||||
immich-test-network:
|
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
"test:watch": "jest --watch",
|
"test:watch": "jest --watch",
|
||||||
"test:cov": "jest --coverage",
|
"test:cov": "jest --coverage",
|
||||||
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
|
"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": "typeorm",
|
||||||
"typeorm:migrations:create": "typeorm migration:create",
|
"typeorm:migrations:create": "typeorm migration:create",
|
||||||
"typeorm:migrations:generate": "typeorm migration:generate -d ./dist/infra/database.config.js",
|
"typeorm:migrations:generate": "typeorm migration:generate -d ./dist/infra/database.config.js",
|
||||||
|
|
|
@ -109,6 +109,10 @@ export class MetadataService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async teardown() {
|
||||||
|
await this.repository.teardown();
|
||||||
|
}
|
||||||
|
|
||||||
async handleLivePhotoLinking(job: IEntityJob) {
|
async handleLivePhotoLinking(job: IEntityJob) {
|
||||||
const { id } = job;
|
const { id } = job;
|
||||||
const [asset] = await this.assetRepository.getByIds([id]);
|
const [asset] = await this.assetRepository.getByIds([id]);
|
||||||
|
|
|
@ -26,6 +26,7 @@ export interface ImmichTags extends Omit<Tags, 'FocalLength'> {
|
||||||
|
|
||||||
export interface IMetadataRepository {
|
export interface IMetadataRepository {
|
||||||
init(options: Partial<InitOptions>): Promise<void>;
|
init(options: Partial<InitOptions>): Promise<void>;
|
||||||
|
teardown(): Promise<void>;
|
||||||
reverseGeocode(point: GeoPoint): Promise<ReverseGeocodeResult>;
|
reverseGeocode(point: GeoPoint): Promise<ReverseGeocodeResult>;
|
||||||
deleteCache(): Promise<void>;
|
deleteCache(): Promise<void>;
|
||||||
getExifTags(path: string): Promise<ImmichTags | null>;
|
getExifTags(path: string): Promise<ImmichTags | null>;
|
||||||
|
|
|
@ -45,6 +45,10 @@ export class MetadataRepository implements IMetadataRepository {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async teardown() {
|
||||||
|
await exiftool.end();
|
||||||
|
}
|
||||||
|
|
||||||
async deleteCache() {
|
async deleteCache() {
|
||||||
const dumpDirectory = REVERSE_GEOCODING_DUMP_DIRECTORY;
|
const dumpDirectory = REVERSE_GEOCODING_DUMP_DIRECTORY;
|
||||||
if (dumpDirectory) {
|
if (dumpDirectory) {
|
||||||
|
|
|
@ -103,4 +103,8 @@ export class AppService {
|
||||||
await this.metadataService.init();
|
await this.metadataService.init();
|
||||||
await this.searchService.init();
|
await this.searchService.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async teardown() {
|
||||||
|
await this.metadataService.teardown();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,11 +2,10 @@ import { AlbumResponseDto, LoginResponseDto } from '@app/domain';
|
||||||
import { AlbumController } from '@app/immich';
|
import { AlbumController } from '@app/immich';
|
||||||
import { AssetFileUploadResponseDto } from '@app/immich/api-v1/asset/response-dto/asset-file-upload-response.dto';
|
import { AssetFileUploadResponseDto } from '@app/immich/api-v1/asset/response-dto/asset-file-upload-response.dto';
|
||||||
import { SharedLinkType } from '@app/infra/entities';
|
import { SharedLinkType } from '@app/infra/entities';
|
||||||
import { INestApplication } from '@nestjs/common';
|
|
||||||
import { api } from '@test/api';
|
import { api } from '@test/api';
|
||||||
import { db } from '@test/db';
|
import { db } from '@test/db';
|
||||||
import { errorStub, uuidStub } from '@test/fixtures';
|
import { errorStub, uuidStub } from '@test/fixtures';
|
||||||
import { createTestApp } from '@test/test-utils';
|
import { testApp } from '@test/test-utils';
|
||||||
import request from 'supertest';
|
import request from 'supertest';
|
||||||
|
|
||||||
const user1SharedUser = 'user1SharedUser';
|
const user1SharedUser = 'user1SharedUser';
|
||||||
|
@ -17,7 +16,6 @@ const user2SharedLink = 'user2SharedLink';
|
||||||
const user2NotShared = 'user2NotShared';
|
const user2NotShared = 'user2NotShared';
|
||||||
|
|
||||||
describe(`${AlbumController.name} (e2e)`, () => {
|
describe(`${AlbumController.name} (e2e)`, () => {
|
||||||
let app: INestApplication;
|
|
||||||
let server: any;
|
let server: any;
|
||||||
let admin: LoginResponseDto;
|
let admin: LoginResponseDto;
|
||||||
let user1: LoginResponseDto;
|
let user1: LoginResponseDto;
|
||||||
|
@ -27,9 +25,11 @@ describe(`${AlbumController.name} (e2e)`, () => {
|
||||||
let user2Albums: AlbumResponseDto[];
|
let user2Albums: AlbumResponseDto[];
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
app = await createTestApp();
|
[server] = await testApp.create();
|
||||||
|
});
|
||||||
|
|
||||||
server = app.getHttpServer();
|
afterAll(async () => {
|
||||||
|
await testApp.teardown();
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
|
@ -37,24 +37,30 @@ describe(`${AlbumController.name} (e2e)`, () => {
|
||||||
await api.authApi.adminSignUp(server);
|
await api.authApi.adminSignUp(server);
|
||||||
admin = await api.authApi.adminLogin(server);
|
admin = await api.authApi.adminLogin(server);
|
||||||
|
|
||||||
await api.userApi.create(server, admin.accessToken, {
|
await Promise.all([
|
||||||
email: 'user1@immich.app',
|
api.userApi.create(server, admin.accessToken, {
|
||||||
password: 'Password123',
|
email: 'user1@immich.app',
|
||||||
firstName: 'User 1',
|
password: 'Password123',
|
||||||
lastName: 'Test',
|
firstName: 'User 1',
|
||||||
});
|
lastName: 'Test',
|
||||||
user1 = await api.authApi.login(server, { email: 'user1@immich.app', password: 'Password123' });
|
}),
|
||||||
|
api.userApi.create(server, admin.accessToken, {
|
||||||
|
email: 'user2@immich.app',
|
||||||
|
password: 'Password123',
|
||||||
|
firstName: 'User 2',
|
||||||
|
lastName: 'Test',
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
await api.userApi.create(server, admin.accessToken, {
|
[user1, user2] = await Promise.all([
|
||||||
email: 'user2@immich.app',
|
api.authApi.login(server, { email: 'user1@immich.app', password: 'Password123' }),
|
||||||
password: 'Password123',
|
api.authApi.login(server, { email: 'user2@immich.app', password: 'Password123' }),
|
||||||
firstName: 'User 2',
|
]);
|
||||||
lastName: 'Test',
|
|
||||||
});
|
|
||||||
user2 = await api.authApi.login(server, { email: 'user2@immich.app', password: 'Password123' });
|
|
||||||
|
|
||||||
user1Asset = await api.assetApi.upload(server, user1.accessToken, 'example');
|
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, {
|
api.albumApi.create(server, user1.accessToken, {
|
||||||
albumName: user1SharedUser,
|
albumName: user1SharedUser,
|
||||||
sharedWithUserIds: [user2.userId],
|
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: user1SharedLink, assetIds: [user1Asset.id] }),
|
||||||
api.albumApi.create(server, user1.accessToken, { albumName: user1NotShared, assetIds: [user1Asset.id] }),
|
api.albumApi.create(server, user1.accessToken, { albumName: user1NotShared, assetIds: [user1Asset.id] }),
|
||||||
]);
|
|
||||||
|
|
||||||
// add shared link to user1SharedLink album
|
// user 2
|
||||||
await api.sharedLinkApi.create(server, user1.accessToken, {
|
|
||||||
type: SharedLinkType.ALBUM,
|
|
||||||
albumId: user1Albums[1].id,
|
|
||||||
});
|
|
||||||
|
|
||||||
user2Albums = await Promise.all([
|
|
||||||
api.albumApi.create(server, user2.accessToken, {
|
api.albumApi.create(server, user2.accessToken, {
|
||||||
albumName: user2SharedUser,
|
albumName: user2SharedUser,
|
||||||
sharedWithUserIds: [user1.userId],
|
sharedWithUserIds: [user1.userId],
|
||||||
|
@ -80,16 +79,22 @@ describe(`${AlbumController.name} (e2e)`, () => {
|
||||||
api.albumApi.create(server, user2.accessToken, { albumName: user2NotShared }),
|
api.albumApi.create(server, user2.accessToken, { albumName: user2NotShared }),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// add shared link to user2SharedLink album
|
user1Albums = albums.slice(0, 3);
|
||||||
await api.sharedLinkApi.create(server, user2.accessToken, {
|
user2Albums = albums.slice(3);
|
||||||
type: SharedLinkType.ALBUM,
|
|
||||||
albumId: user2Albums[1].id,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
afterAll(async () => {
|
await Promise.all([
|
||||||
await db.disconnect();
|
// add shared link to user1SharedLink album
|
||||||
await app.close();
|
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', () => {
|
describe('GET /album', () => {
|
||||||
|
|
|
@ -12,7 +12,7 @@ import { AssetEntity, AssetType, SharedLinkType } from '@app/infra/entities';
|
||||||
import { INestApplication } from '@nestjs/common';
|
import { INestApplication } from '@nestjs/common';
|
||||||
import { api } from '@test/api';
|
import { api } from '@test/api';
|
||||||
import { errorStub, uuidStub } from '@test/fixtures';
|
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 { randomBytes } from 'crypto';
|
||||||
import request from 'supertest';
|
import request from 'supertest';
|
||||||
|
|
||||||
|
@ -86,12 +86,14 @@ describe(`${AssetController.name} (e2e)`, () => {
|
||||||
let asset4: AssetEntity;
|
let asset4: AssetEntity;
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
app = await createTestApp();
|
[server, app] = await testApp.create();
|
||||||
|
|
||||||
server = app.getHttpServer();
|
|
||||||
assetRepository = app.get<IAssetRepository>(IAssetRepository);
|
assetRepository = app.get<IAssetRepository>(IAssetRepository);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await testApp.teardown();
|
||||||
|
});
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await db.reset();
|
await db.reset();
|
||||||
await api.authApi.adminSignUp(server);
|
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', () => {
|
describe('POST /asset/upload', () => {
|
||||||
it('should require authentication', async () => {
|
it('should require authentication', async () => {
|
||||||
const { status, body } = await request(server)
|
const { status, body } = await request(server)
|
||||||
|
@ -589,9 +586,11 @@ describe(`${AssetController.name} (e2e)`, () => {
|
||||||
|
|
||||||
describe('GET /asset/map-marker', () => {
|
describe('GET /asset/map-marker', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await assetRepository.save({ id: asset1.id, isArchived: true });
|
await Promise.all([
|
||||||
await assetRepository.upsertExif({ assetId: asset1.id, latitude: 0, longitude: 0 });
|
assetRepository.save({ id: asset1.id, isArchived: true }),
|
||||||
await assetRepository.upsertExif({ assetId: asset2.id, latitude: 0, longitude: 0 });
|
assetRepository.upsertExif({ assetId: asset1.id, latitude: 0, longitude: 0 }),
|
||||||
|
assetRepository.upsertExif({ assetId: asset2.id, latitude: 0, longitude: 0 }),
|
||||||
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should require authentication', async () => {
|
it('should require authentication', async () => {
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import { AuthController } from '@app/immich';
|
import { AuthController } from '@app/immich';
|
||||||
import { INestApplication } from '@nestjs/common';
|
|
||||||
import { api } from '@test/api';
|
import { api } from '@test/api';
|
||||||
import { db } from '@test/db';
|
import { db } from '@test/db';
|
||||||
import {
|
import {
|
||||||
|
@ -12,7 +11,7 @@ import {
|
||||||
signupResponseStub,
|
signupResponseStub,
|
||||||
uuidStub,
|
uuidStub,
|
||||||
} from '@test/fixtures';
|
} from '@test/fixtures';
|
||||||
import { createTestApp } from '@test/test-utils';
|
import { testApp } from '@test/test-utils';
|
||||||
import request from 'supertest';
|
import request from 'supertest';
|
||||||
|
|
||||||
const firstName = 'Immich';
|
const firstName = 'Immich';
|
||||||
|
@ -21,13 +20,16 @@ const password = 'Password123';
|
||||||
const email = 'admin@immich.app';
|
const email = 'admin@immich.app';
|
||||||
|
|
||||||
describe(`${AuthController.name} (e2e)`, () => {
|
describe(`${AuthController.name} (e2e)`, () => {
|
||||||
let app: INestApplication;
|
|
||||||
let server: any;
|
let server: any;
|
||||||
let accessToken: string;
|
let accessToken: string;
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
app = await createTestApp();
|
await testApp.reset();
|
||||||
server = app.getHttpServer();
|
[server] = await testApp.create();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await testApp.teardown();
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
|
@ -37,11 +39,6 @@ describe(`${AuthController.name} (e2e)`, () => {
|
||||||
accessToken = response.accessToken;
|
accessToken = response.accessToken;
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(async () => {
|
|
||||||
await db.disconnect();
|
|
||||||
await app.close();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('POST /auth/admin-sign-up', () => {
|
describe('POST /auth/admin-sign-up', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await db.reset();
|
await db.reset();
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
import { LoginResponseDto } from '@app/domain';
|
import { LoginResponseDto } from '@app/domain';
|
||||||
import { AssetType, LibraryType } from '@app/infra/entities';
|
import { AssetType, LibraryType } from '@app/infra/entities';
|
||||||
import { INestApplication } from '@nestjs/common';
|
|
||||||
import { api } from '@test/api';
|
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)`, () => {
|
describe(`Supported file formats (e2e)`, () => {
|
||||||
let app: INestApplication;
|
|
||||||
let server: any;
|
let server: any;
|
||||||
let admin: LoginResponseDto;
|
let admin: LoginResponseDto;
|
||||||
|
|
||||||
|
@ -170,8 +168,11 @@ describe(`Supported file formats (e2e)`, () => {
|
||||||
const testsToRun = formatTests.filter((formatTest) => formatTest.runTest);
|
const testsToRun = formatTests.filter((formatTest) => formatTest.runTest);
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
app = await createTestApp(true);
|
[server] = await testApp.create({ jobs: true });
|
||||||
server = app.getHttpServer();
|
});
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await testApp.teardown();
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
|
@ -181,11 +182,6 @@ describe(`Supported file formats (e2e)`, () => {
|
||||||
await api.userApi.setExternalPath(server, admin.accessToken, admin.userId, '/');
|
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) => {
|
it.each(testsToRun)('should import file of format $format', async (testedFormat) => {
|
||||||
const library = await api.libraryApi.create(server, admin.accessToken, {
|
const library = await api.libraryApi.create(server, admin.accessToken, {
|
||||||
type: LibraryType.EXTERNAL,
|
type: LibraryType.EXTERNAL,
|
||||||
|
|
|
@ -1,22 +1,14 @@
|
||||||
import { LibraryResponseDto, LoginResponseDto } from '@app/domain';
|
import { LibraryResponseDto, LoginResponseDto } from '@app/domain';
|
||||||
import { LibraryController } from '@app/immich';
|
import { LibraryController } from '@app/immich';
|
||||||
import { AssetType, LibraryType } from '@app/infra/entities';
|
import { AssetType, LibraryType } from '@app/infra/entities';
|
||||||
import { INestApplication } from '@nestjs/common';
|
|
||||||
import { api } from '@test/api';
|
import { api } from '@test/api';
|
||||||
import {
|
import { IMMICH_TEST_ASSET_PATH, IMMICH_TEST_ASSET_TEMP_PATH, db, restoreTempFolder, testApp } from '@test/test-utils';
|
||||||
IMMICH_TEST_ASSET_PATH,
|
|
||||||
IMMICH_TEST_ASSET_TEMP_PATH,
|
|
||||||
createTestApp,
|
|
||||||
db,
|
|
||||||
restoreTempFolder,
|
|
||||||
} from '@test/test-utils';
|
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import request from 'supertest';
|
import request from 'supertest';
|
||||||
import { utimes } from 'utimes';
|
import { utimes } from 'utimes';
|
||||||
import { errorStub, uuidStub } from '../fixtures';
|
import { errorStub, uuidStub } from '../fixtures';
|
||||||
|
|
||||||
describe(`${LibraryController.name} (e2e)`, () => {
|
describe(`${LibraryController.name} (e2e)`, () => {
|
||||||
let app: INestApplication;
|
|
||||||
let server: any;
|
let server: any;
|
||||||
let admin: LoginResponseDto;
|
let admin: LoginResponseDto;
|
||||||
|
|
||||||
|
@ -35,8 +27,12 @@ describe(`${LibraryController.name} (e2e)`, () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
app = await createTestApp(true);
|
[server] = await testApp.create({ jobs: true });
|
||||||
server = app.getHttpServer();
|
});
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await testApp.teardown();
|
||||||
|
await restoreTempFolder();
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
|
@ -46,12 +42,6 @@ describe(`${LibraryController.name} (e2e)`, () => {
|
||||||
admin = await api.authApi.adminLogin(server);
|
admin = await api.authApi.adminLogin(server);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(async () => {
|
|
||||||
await db.disconnect();
|
|
||||||
await app.close();
|
|
||||||
await restoreTempFolder();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('GET /library', () => {
|
describe('GET /library', () => {
|
||||||
it('should require authentication', async () => {
|
it('should require authentication', async () => {
|
||||||
const { status, body } = await request(server).get('/library');
|
const { status, body } = await request(server).get('/library');
|
||||||
|
|
|
@ -1,18 +1,19 @@
|
||||||
import { OAuthController } from '@app/immich';
|
import { OAuthController } from '@app/immich';
|
||||||
import { INestApplication } from '@nestjs/common';
|
|
||||||
import { api } from '@test/api';
|
import { api } from '@test/api';
|
||||||
import { db } from '@test/db';
|
import { db } from '@test/db';
|
||||||
import { errorStub } from '@test/fixtures';
|
import { errorStub } from '@test/fixtures';
|
||||||
import { createTestApp } from '@test/test-utils';
|
import { testApp } from '@test/test-utils';
|
||||||
import request from 'supertest';
|
import request from 'supertest';
|
||||||
|
|
||||||
describe(`${OAuthController.name} (e2e)`, () => {
|
describe(`${OAuthController.name} (e2e)`, () => {
|
||||||
let app: INestApplication;
|
|
||||||
let server: any;
|
let server: any;
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
app = await createTestApp();
|
[server] = await testApp.create();
|
||||||
server = app.getHttpServer();
|
});
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await testApp.teardown();
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
|
@ -20,11 +21,6 @@ describe(`${OAuthController.name} (e2e)`, () => {
|
||||||
await api.authApi.adminSignUp(server);
|
await api.authApi.adminSignUp(server);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(async () => {
|
|
||||||
await db.disconnect();
|
|
||||||
await app.close();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('POST /oauth/authorize', () => {
|
describe('POST /oauth/authorize', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await db.reset();
|
await db.reset();
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { INestApplication } from '@nestjs/common';
|
||||||
import { api } from '@test/api';
|
import { api } from '@test/api';
|
||||||
import { db } from '@test/db';
|
import { db } from '@test/db';
|
||||||
import { errorStub } from '@test/fixtures';
|
import { errorStub } from '@test/fixtures';
|
||||||
import { createTestApp } from '@test/test-utils';
|
import { testApp } from '@test/test-utils';
|
||||||
import request from 'supertest';
|
import request from 'supertest';
|
||||||
|
|
||||||
const user1Dto = {
|
const user1Dto = {
|
||||||
|
@ -31,27 +31,29 @@ describe(`${PartnerController.name} (e2e)`, () => {
|
||||||
let user2: LoginResponseDto;
|
let user2: LoginResponseDto;
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
app = await createTestApp();
|
[server, app] = await testApp.create();
|
||||||
server = app.getHttpServer();
|
|
||||||
repository = app.get<IPartnerRepository>(IPartnerRepository);
|
repository = app.get<IPartnerRepository>(IPartnerRepository);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await testApp.teardown();
|
||||||
|
});
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await db.reset();
|
await db.reset();
|
||||||
await api.authApi.adminSignUp(server);
|
await api.authApi.adminSignUp(server);
|
||||||
loginResponse = await api.authApi.adminLogin(server);
|
loginResponse = await api.authApi.adminLogin(server);
|
||||||
accessToken = loginResponse.accessToken;
|
accessToken = loginResponse.accessToken;
|
||||||
|
|
||||||
await api.userApi.create(server, accessToken, user1Dto);
|
await Promise.all([
|
||||||
user1 = await api.authApi.login(server, { email: user1Dto.email, password: user1Dto.password });
|
api.userApi.create(server, accessToken, user1Dto),
|
||||||
|
api.userApi.create(server, accessToken, user2Dto),
|
||||||
|
]);
|
||||||
|
|
||||||
await api.userApi.create(server, accessToken, user2Dto);
|
[user1, user2] = await Promise.all([
|
||||||
user2 = await api.authApi.login(server, { email: user2Dto.email, password: user2Dto.password });
|
api.authApi.login(server, { email: user1Dto.email, password: user1Dto.password }),
|
||||||
});
|
api.authApi.login(server, { email: user2Dto.email, password: user2Dto.password }),
|
||||||
|
]);
|
||||||
afterAll(async () => {
|
|
||||||
await db.disconnect();
|
|
||||||
await app.close();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('GET /partner', () => {
|
describe('GET /partner', () => {
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { INestApplication } from '@nestjs/common';
|
||||||
import { api } from '@test/api';
|
import { api } from '@test/api';
|
||||||
import { db } from '@test/db';
|
import { db } from '@test/db';
|
||||||
import { errorStub, uuidStub } from '@test/fixtures';
|
import { errorStub, uuidStub } from '@test/fixtures';
|
||||||
import { createTestApp } from '@test/test-utils';
|
import { testApp } from '@test/test-utils';
|
||||||
import request from 'supertest';
|
import request from 'supertest';
|
||||||
|
|
||||||
describe(`${PersonController.name}`, () => {
|
describe(`${PersonController.name}`, () => {
|
||||||
|
@ -18,11 +18,14 @@ describe(`${PersonController.name}`, () => {
|
||||||
let hiddenPerson: PersonEntity;
|
let hiddenPerson: PersonEntity;
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
app = await createTestApp();
|
[server, app] = await testApp.create();
|
||||||
server = app.getHttpServer();
|
|
||||||
personRepository = app.get<IPersonRepository>(IPersonRepository);
|
personRepository = app.get<IPersonRepository>(IPersonRepository);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await testApp.teardown();
|
||||||
|
});
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await db.reset();
|
await db.reset();
|
||||||
await api.authApi.adminSignUp(server);
|
await api.authApi.adminSignUp(server);
|
||||||
|
@ -46,11 +49,6 @@ describe(`${PersonController.name}`, () => {
|
||||||
await personRepository.createFace({ assetId: faceAsset.id, personId: hiddenPerson.id });
|
await personRepository.createFace({ assetId: faceAsset.id, personId: hiddenPerson.id });
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(async () => {
|
|
||||||
await db.disconnect();
|
|
||||||
await app.close();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('GET /person', () => {
|
describe('GET /person', () => {
|
||||||
beforeEach(async () => {});
|
beforeEach(async () => {});
|
||||||
|
|
||||||
|
|
|
@ -1,21 +1,22 @@
|
||||||
import { LoginResponseDto } from '@app/domain';
|
import { LoginResponseDto } from '@app/domain';
|
||||||
import { ServerInfoController } from '@app/immich';
|
import { ServerInfoController } from '@app/immich';
|
||||||
import { INestApplication } from '@nestjs/common';
|
|
||||||
import { api } from '@test/api';
|
import { api } from '@test/api';
|
||||||
import { db } from '@test/db';
|
import { db } from '@test/db';
|
||||||
import { errorStub } from '@test/fixtures';
|
import { errorStub } from '@test/fixtures';
|
||||||
import { createTestApp } from '@test/test-utils';
|
import { testApp } from '@test/test-utils';
|
||||||
import request from 'supertest';
|
import request from 'supertest';
|
||||||
|
|
||||||
describe(`${ServerInfoController.name} (e2e)`, () => {
|
describe(`${ServerInfoController.name} (e2e)`, () => {
|
||||||
let app: INestApplication;
|
|
||||||
let server: any;
|
let server: any;
|
||||||
let accessToken: string;
|
let accessToken: string;
|
||||||
let loginResponse: LoginResponseDto;
|
let loginResponse: LoginResponseDto;
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
app = await createTestApp();
|
[server] = await testApp.create();
|
||||||
server = app.getHttpServer();
|
});
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await testApp.teardown();
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
|
@ -25,11 +26,6 @@ describe(`${ServerInfoController.name} (e2e)`, () => {
|
||||||
accessToken = loginResponse.accessToken;
|
accessToken = loginResponse.accessToken;
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(async () => {
|
|
||||||
await db.disconnect();
|
|
||||||
await app.close();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('GET /server-info', () => {
|
describe('GET /server-info', () => {
|
||||||
it('should require authentication', async () => {
|
it('should require authentication', async () => {
|
||||||
const { status, body } = await request(server).get('/server-info');
|
const { status, body } = await request(server).get('/server-info');
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { PostgreSqlContainer } from '@testcontainers/postgresql';
|
import { PostgreSqlContainer } from '@testcontainers/postgresql';
|
||||||
import * as fs from 'fs';
|
import { access } from 'fs/promises';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
|
||||||
export default async () => {
|
export default async () => {
|
||||||
|
@ -23,8 +23,7 @@ export default async () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const directoryExists = async (dirPath: string) =>
|
const directoryExists = async (dirPath: string) =>
|
||||||
await fs.promises
|
await access(dirPath)
|
||||||
.access(dirPath)
|
|
||||||
.then(() => true)
|
.then(() => true)
|
||||||
.catch(() => false);
|
.catch(() => false);
|
||||||
|
|
||||||
|
|
|
@ -1,16 +1,10 @@
|
||||||
import { AlbumResponseDto, LoginResponseDto, SharedLinkResponseDto } from '@app/domain';
|
import { AlbumResponseDto, LoginResponseDto, SharedLinkResponseDto } from '@app/domain';
|
||||||
import { PartnerController } from '@app/immich';
|
import { PartnerController } from '@app/immich';
|
||||||
import { LibraryType, SharedLinkType } from '@app/infra/entities';
|
import { LibraryType, SharedLinkType } from '@app/infra/entities';
|
||||||
import { INestApplication } from '@nestjs/common';
|
|
||||||
import { api } from '@test/api';
|
import { api } from '@test/api';
|
||||||
import { db } from '@test/db';
|
import { db } from '@test/db';
|
||||||
import { errorStub, uuidStub } from '@test/fixtures';
|
import { errorStub, uuidStub } from '@test/fixtures';
|
||||||
import {
|
import { IMMICH_TEST_ASSET_PATH, IMMICH_TEST_ASSET_TEMP_PATH, restoreTempFolder, testApp } from '@test/test-utils';
|
||||||
IMMICH_TEST_ASSET_PATH,
|
|
||||||
IMMICH_TEST_ASSET_TEMP_PATH,
|
|
||||||
createTestApp,
|
|
||||||
restoreTempFolder,
|
|
||||||
} from '@test/test-utils';
|
|
||||||
import { cp } from 'fs/promises';
|
import { cp } from 'fs/promises';
|
||||||
import request from 'supertest';
|
import request from 'supertest';
|
||||||
|
|
||||||
|
@ -22,7 +16,6 @@ const user1Dto = {
|
||||||
};
|
};
|
||||||
|
|
||||||
describe(`${PartnerController.name} (e2e)`, () => {
|
describe(`${PartnerController.name} (e2e)`, () => {
|
||||||
let app: INestApplication;
|
|
||||||
let server: any;
|
let server: any;
|
||||||
let admin: LoginResponseDto;
|
let admin: LoginResponseDto;
|
||||||
let user1: LoginResponseDto;
|
let user1: LoginResponseDto;
|
||||||
|
@ -30,8 +23,12 @@ describe(`${PartnerController.name} (e2e)`, () => {
|
||||||
let sharedLink: SharedLinkResponseDto;
|
let sharedLink: SharedLinkResponseDto;
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
app = await createTestApp(true);
|
[server] = await testApp.create({ jobs: true });
|
||||||
server = app.getHttpServer();
|
});
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await testApp.teardown();
|
||||||
|
await restoreTempFolder();
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
|
@ -49,12 +46,6 @@ describe(`${PartnerController.name} (e2e)`, () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(async () => {
|
|
||||||
await db.disconnect();
|
|
||||||
await app.close();
|
|
||||||
await restoreTempFolder();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('GET /shared-link', () => {
|
describe('GET /shared-link', () => {
|
||||||
it('should require authentication', async () => {
|
it('should require authentication', async () => {
|
||||||
const { status, body } = await request(server).get('/shared-link');
|
const { status, body } = await request(server).get('/shared-link');
|
||||||
|
|
|
@ -2,10 +2,11 @@ import { LoginResponseDto, UserResponseDto, UserService } from '@app/domain';
|
||||||
import { AppModule, UserController } from '@app/immich';
|
import { AppModule, UserController } from '@app/immich';
|
||||||
import { UserEntity } from '@app/infra/entities';
|
import { UserEntity } from '@app/infra/entities';
|
||||||
import { INestApplication } from '@nestjs/common';
|
import { INestApplication } from '@nestjs/common';
|
||||||
|
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||||
import { api } from '@test/api';
|
import { api } from '@test/api';
|
||||||
import { db } from '@test/db';
|
import { db } from '@test/db';
|
||||||
import { errorStub, userSignupStub, userStub } from '@test/fixtures';
|
import { errorStub, userSignupStub, userStub } from '@test/fixtures';
|
||||||
import { createTestApp } from '@test/test-utils';
|
import { testApp } from '@test/test-utils';
|
||||||
import request from 'supertest';
|
import request from 'supertest';
|
||||||
import { Repository } from 'typeorm';
|
import { Repository } from 'typeorm';
|
||||||
|
|
||||||
|
@ -18,10 +19,12 @@ describe(`${UserController.name}`, () => {
|
||||||
let userRepository: Repository<UserEntity>;
|
let userRepository: Repository<UserEntity>;
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
app = await createTestApp();
|
[server, app] = await testApp.create();
|
||||||
userRepository = app.select(AppModule).get('UserEntityRepository');
|
userRepository = app.select(AppModule).get(getRepositoryToken(UserEntity));
|
||||||
|
});
|
||||||
|
|
||||||
server = app.getHttpServer();
|
afterAll(async () => {
|
||||||
|
await testApp.teardown();
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
|
|
|
@ -5,6 +5,7 @@ export const newMetadataRepositoryMock = (): jest.Mocked<IMetadataRepository> =>
|
||||||
deleteCache: jest.fn(),
|
deleteCache: jest.fn(),
|
||||||
getExifTags: jest.fn(),
|
getExifTags: jest.fn(),
|
||||||
init: jest.fn(),
|
init: jest.fn(),
|
||||||
|
teardown: jest.fn(),
|
||||||
reverseGeocode: jest.fn(),
|
reverseGeocode: jest.fn(),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
import { dataSource } from '@app/infra';
|
|
||||||
|
|
||||||
import { IJobRepository, JobItem, JobItemHandler, QueueName } from '@app/domain';
|
import { IJobRepository, JobItem, JobItemHandler, QueueName } from '@app/domain';
|
||||||
import { AppModule } from '@app/immich';
|
import { AppModule } from '@app/immich';
|
||||||
import { INestApplication, Logger } from '@nestjs/common';
|
import { dataSource } from '@app/infra';
|
||||||
import { Test, TestingModule } from '@nestjs/testing';
|
import { INestApplication } from '@nestjs/common';
|
||||||
|
import { Test } from '@nestjs/testing';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { AppService } from '../src/microservices/app.service';
|
import { AppService } from '../src/microservices/app.service';
|
||||||
|
@ -36,38 +35,48 @@ export const db = {
|
||||||
|
|
||||||
let _handler: JobItemHandler = () => Promise.resolve();
|
let _handler: JobItemHandler = () => Promise.resolve();
|
||||||
|
|
||||||
export async function createTestApp(runJobs = false, log = false): Promise<INestApplication> {
|
interface TestAppOptions {
|
||||||
const moduleBuilder = Test.createTestingModule({
|
jobs: boolean;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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';
|
export const runAllTests: boolean = process.env.IMMICH_RUN_ALL_TESTS === 'true';
|
||||||
|
|
||||||
const directoryExists = async (dirPath: string) =>
|
const directoryExists = async (dirPath: string) =>
|
||||||
|
|
Loading…
Reference in a new issue