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
22 changed files with 189 additions and 201 deletions

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) =>