mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2024-12-04 21:03:38 -05:00
f362d27d3f
Signed-off-by: Yannick Bungers <git@innay.de>
472 lines
16 KiB
TypeScript
472 lines
16 KiB
TypeScript
/*
|
|
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
|
*
|
|
* SPDX-License-Identifier: AGPL-3.0-only
|
|
*/
|
|
import { ConfigModule, ConfigService } from '@nestjs/config';
|
|
import { RouterModule, Routes } from '@nestjs/core';
|
|
import { EventEmitterModule } from '@nestjs/event-emitter';
|
|
import { NestExpressApplication } from '@nestjs/platform-express';
|
|
import { Test, TestingModule, TestingModuleBuilder } from '@nestjs/testing';
|
|
import { TypeOrmModule, TypeOrmModuleOptions } from '@nestjs/typeorm';
|
|
import { Connection, createConnection } from 'typeorm';
|
|
|
|
import { PrivateApiModule } from '../src/api/private/private-api.module';
|
|
import { PublicApiModule } from '../src/api/public/public-api.module';
|
|
import { setupApp } from '../src/app-init';
|
|
import { AuthTokenWithSecretDto } from '../src/auth/auth-token.dto';
|
|
import { AuthModule } from '../src/auth/auth.module';
|
|
import { AuthService } from '../src/auth/auth.service';
|
|
import { MockAuthGuard } from '../src/auth/mock-auth.guard';
|
|
import { TokenAuthGuard } from '../src/auth/token.strategy';
|
|
import { AuthorsModule } from '../src/authors/authors.module';
|
|
import { AppConfig } from '../src/config/app.config';
|
|
import { AuthConfig } from '../src/config/auth.config';
|
|
import { CustomizationConfig } from '../src/config/customization.config';
|
|
import { DatabaseConfig } from '../src/config/database.config';
|
|
import { ExternalServicesConfig } from '../src/config/external-services.config';
|
|
import { MediaConfig } from '../src/config/media.config';
|
|
import {
|
|
createDefaultMockAppConfig,
|
|
registerAppConfig,
|
|
} from '../src/config/mock/app.config.mock';
|
|
import {
|
|
createDefaultMockAuthConfig,
|
|
registerAuthConfig,
|
|
} from '../src/config/mock/auth.config.mock';
|
|
import {
|
|
createDefaultMockCustomizationConfig,
|
|
registerCustomizationConfig,
|
|
} from '../src/config/mock/customization.config.mock';
|
|
import {
|
|
createDefaultMockDatabaseConfig,
|
|
registerDatabaseConfig,
|
|
} from '../src/config/mock/database.config.mock';
|
|
import {
|
|
createDefaultMockExternalServicesConfig,
|
|
registerExternalServiceConfig,
|
|
} from '../src/config/mock/external-services.config.mock';
|
|
import {
|
|
createDefaultMockMediaConfig,
|
|
registerMediaConfig,
|
|
} from '../src/config/mock/media.config.mock';
|
|
import {
|
|
createDefaultMockNoteConfig,
|
|
registerNoteConfig,
|
|
} from '../src/config/mock/note.config.mock';
|
|
import { NoteConfig } from '../src/config/note.config';
|
|
import { ErrorExceptionMapping } from '../src/errors/error-mapping';
|
|
import { eventModuleConfig } from '../src/events';
|
|
import { FrontendConfigModule } from '../src/frontend-config/frontend-config.module';
|
|
import { GroupsModule } from '../src/groups/groups.module';
|
|
import { GroupsService } from '../src/groups/groups.service';
|
|
import { HistoryModule } from '../src/history/history.module';
|
|
import { HistoryService } from '../src/history/history.service';
|
|
import { IdentityModule } from '../src/identity/identity.module';
|
|
import { IdentityService } from '../src/identity/identity.service';
|
|
import { ConsoleLoggerService } from '../src/logger/console-logger.service';
|
|
import { LoggerModule } from '../src/logger/logger.module';
|
|
import { MediaModule } from '../src/media/media.module';
|
|
import { MediaService } from '../src/media/media.service';
|
|
import { MonitoringModule } from '../src/monitoring/monitoring.module';
|
|
import { AliasService } from '../src/notes/alias.service';
|
|
import { Note } from '../src/notes/note.entity';
|
|
import { NotesModule } from '../src/notes/notes.module';
|
|
import { NotesService } from '../src/notes/notes.service';
|
|
import { PermissionsModule } from '../src/permissions/permissions.module';
|
|
import { PermissionsService } from '../src/permissions/permissions.service';
|
|
import { RevisionsModule } from '../src/revisions/revisions.module';
|
|
import { RevisionsService } from '../src/revisions/revisions.service';
|
|
import { SessionModule } from '../src/sessions/session.module';
|
|
import { SessionService } from '../src/sessions/session.service';
|
|
import { User } from '../src/users/user.entity';
|
|
import { UsersModule } from '../src/users/users.module';
|
|
import { UsersService } from '../src/users/users.service';
|
|
|
|
interface CreateTestSetupParameters {
|
|
appConfigMock?: AppConfig;
|
|
authConfigMock?: AuthConfig;
|
|
customizationConfigMock?: CustomizationConfig;
|
|
databaseConfigMock?: DatabaseConfig;
|
|
externalServicesConfigMock?: ExternalServicesConfig;
|
|
mediaConfigMock?: MediaConfig;
|
|
noteConfigMock?: NoteConfig;
|
|
}
|
|
|
|
export class TestSetup {
|
|
moduleRef: TestingModule;
|
|
app: NestExpressApplication;
|
|
|
|
userService: UsersService;
|
|
groupService: GroupsService;
|
|
configService: ConfigService;
|
|
identityService: IdentityService;
|
|
notesService: NotesService;
|
|
mediaService: MediaService;
|
|
historyService: HistoryService;
|
|
aliasService: AliasService;
|
|
authService: AuthService;
|
|
sessionService: SessionService;
|
|
revisionsService: RevisionsService;
|
|
|
|
users: User[] = [];
|
|
authTokens: AuthTokenWithSecretDto[] = [];
|
|
anonymousNotes: Note[] = [];
|
|
ownedNotes: Note[] = [];
|
|
permissionsService: PermissionsService;
|
|
|
|
/**
|
|
* Cleans up remnants from a test run from the database
|
|
*/
|
|
public async cleanup() {
|
|
const appConnection = this.app.get<Connection>(Connection);
|
|
const connectionOptions = appConnection.options;
|
|
if (!connectionOptions.database) {
|
|
throw new Error('Database name not set in connection options');
|
|
}
|
|
if (connectionOptions.type === 'sqlite') {
|
|
// Bail out early, as SQLite runs from memory anyway
|
|
await this.app.close();
|
|
return;
|
|
}
|
|
if (appConnection.isConnected) {
|
|
await appConnection.close();
|
|
}
|
|
switch (connectionOptions.type) {
|
|
case 'postgres':
|
|
case 'mariadb': {
|
|
const connection = await createConnection({
|
|
type: connectionOptions.type,
|
|
username: 'hedgedoc',
|
|
password: 'hedgedoc',
|
|
});
|
|
await connection.query(`DROP DATABASE ${connectionOptions.database}`);
|
|
await connection.close();
|
|
}
|
|
}
|
|
await this.app.close();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Builder class for TestSetup
|
|
* Should be instantiated with the create() method
|
|
* The useable TestSetup is genereated using build()
|
|
*/
|
|
export class TestSetupBuilder {
|
|
// list of functions that should be executed before or after builing the TestingModule
|
|
private setupPreCompile: (() => Promise<void>)[] = [];
|
|
private setupPostCompile: (() => Promise<void>)[] = [];
|
|
|
|
private testingModuleBuilder: TestingModuleBuilder;
|
|
private testSetup = new TestSetup();
|
|
|
|
private testId: string;
|
|
|
|
/**
|
|
* Prepares a test database
|
|
* @param dbName The name of the database to use
|
|
* @private
|
|
*/
|
|
private static async setupTestDB(dbName: string) {
|
|
const dbType = process.env.HEDGEDOC_TEST_DB_TYPE;
|
|
if (!dbType || dbType === 'sqlite') {
|
|
return;
|
|
}
|
|
|
|
if (!['postgres', 'mariadb'].includes(dbType)) {
|
|
throw new Error('Unknown database type in HEDGEDOC_TEST_DB_TYPE');
|
|
}
|
|
|
|
const connection = await createConnection({
|
|
type: dbType as 'postgres' | 'mariadb',
|
|
username: dbType === 'mariadb' ? 'root' : 'hedgedoc',
|
|
password: 'hedgedoc',
|
|
});
|
|
|
|
await connection.query(`CREATE DATABASE ${dbName}`);
|
|
if (dbType === 'mariadb') {
|
|
await connection.query(
|
|
`GRANT ALL PRIVILEGES ON ${dbName}.* TO 'hedgedoc'@'%'`,
|
|
);
|
|
}
|
|
await connection.close();
|
|
}
|
|
|
|
private static getTestDBConf(dbName: string): TypeOrmModuleOptions {
|
|
switch (process.env.HEDGEDOC_TEST_DB_TYPE || 'sqlite') {
|
|
case 'sqlite':
|
|
return {
|
|
type: 'sqlite',
|
|
database: ':memory:',
|
|
autoLoadEntities: true,
|
|
synchronize: true,
|
|
dropSchema: true,
|
|
};
|
|
case 'postgres':
|
|
case 'mariadb':
|
|
return {
|
|
type: process.env.HEDGEDOC_TEST_DB_TYPE as 'postgres' | 'mariadb',
|
|
database: dbName,
|
|
username: 'hedgedoc',
|
|
password: 'hedgedoc',
|
|
autoLoadEntities: true,
|
|
synchronize: true,
|
|
dropSchema: true,
|
|
};
|
|
default:
|
|
throw new Error('Unknown database type in HEDGEDOC_TEST_DB_TYPE');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Creates a new instance of TestSetupBuilder
|
|
*/
|
|
public static create(mocks?: CreateTestSetupParameters): TestSetupBuilder {
|
|
const testSetupBuilder = new TestSetupBuilder();
|
|
testSetupBuilder.testId =
|
|
'hedgedoc_test_' + Math.random().toString(36).substring(2, 15);
|
|
const routes: Routes = [
|
|
{
|
|
path: '/api/v2',
|
|
module: PublicApiModule,
|
|
},
|
|
{
|
|
path: '/api/private',
|
|
module: PrivateApiModule,
|
|
},
|
|
];
|
|
process.env.HD_BASE_URL =
|
|
'https://md-' + testSetupBuilder.testId + '.example.com';
|
|
testSetupBuilder.testingModuleBuilder = Test.createTestingModule({
|
|
imports: [
|
|
RouterModule.register(routes),
|
|
TypeOrmModule.forRoot(
|
|
TestSetupBuilder.getTestDBConf(testSetupBuilder.testId),
|
|
),
|
|
ConfigModule.forRoot({
|
|
isGlobal: true,
|
|
load: [
|
|
registerAppConfig(
|
|
mocks?.appConfigMock ?? createDefaultMockAppConfig(),
|
|
),
|
|
registerAuthConfig(
|
|
mocks?.authConfigMock ?? createDefaultMockAuthConfig(),
|
|
),
|
|
registerCustomizationConfig(
|
|
mocks?.customizationConfigMock ??
|
|
createDefaultMockCustomizationConfig(),
|
|
),
|
|
registerDatabaseConfig(
|
|
mocks?.databaseConfigMock ?? createDefaultMockDatabaseConfig(),
|
|
),
|
|
registerExternalServiceConfig(
|
|
mocks?.externalServicesConfigMock ??
|
|
createDefaultMockExternalServicesConfig(),
|
|
),
|
|
registerMediaConfig(
|
|
mocks?.mediaConfigMock ?? createDefaultMockMediaConfig(),
|
|
),
|
|
registerNoteConfig(
|
|
mocks?.noteConfigMock ?? createDefaultMockNoteConfig(),
|
|
),
|
|
],
|
|
}),
|
|
NotesModule,
|
|
UsersModule,
|
|
RevisionsModule,
|
|
AuthorsModule,
|
|
PublicApiModule,
|
|
PrivateApiModule,
|
|
HistoryModule,
|
|
MonitoringModule,
|
|
PermissionsModule,
|
|
GroupsModule,
|
|
LoggerModule,
|
|
MediaModule,
|
|
AuthModule,
|
|
FrontendConfigModule,
|
|
IdentityModule,
|
|
SessionModule,
|
|
EventEmitterModule.forRoot(eventModuleConfig),
|
|
],
|
|
providers: [
|
|
{
|
|
provide: 'APP_FILTER',
|
|
useClass: ErrorExceptionMapping,
|
|
},
|
|
],
|
|
});
|
|
return testSetupBuilder;
|
|
}
|
|
|
|
/**
|
|
* Builds the final TestSetup from the configured builder
|
|
*/
|
|
public async build(): Promise<TestSetup> {
|
|
await TestSetupBuilder.setupTestDB(this.testId);
|
|
|
|
for (const setupFunction of this.setupPreCompile) {
|
|
await setupFunction();
|
|
}
|
|
|
|
this.testSetup.moduleRef = await this.testingModuleBuilder.compile();
|
|
|
|
this.testSetup.userService =
|
|
this.testSetup.moduleRef.get<UsersService>(UsersService);
|
|
this.testSetup.groupService =
|
|
this.testSetup.moduleRef.get<GroupsService>(GroupsService);
|
|
this.testSetup.configService =
|
|
this.testSetup.moduleRef.get<ConfigService>(ConfigService);
|
|
this.testSetup.identityService =
|
|
this.testSetup.moduleRef.get<IdentityService>(IdentityService);
|
|
this.testSetup.notesService =
|
|
this.testSetup.moduleRef.get<NotesService>(NotesService);
|
|
this.testSetup.mediaService =
|
|
this.testSetup.moduleRef.get<MediaService>(MediaService);
|
|
this.testSetup.historyService =
|
|
this.testSetup.moduleRef.get<HistoryService>(HistoryService);
|
|
this.testSetup.aliasService =
|
|
this.testSetup.moduleRef.get<AliasService>(AliasService);
|
|
this.testSetup.authService =
|
|
this.testSetup.moduleRef.get<AuthService>(AuthService);
|
|
this.testSetup.permissionsService =
|
|
this.testSetup.moduleRef.get<PermissionsService>(PermissionsService);
|
|
this.testSetup.sessionService =
|
|
this.testSetup.moduleRef.get<SessionService>(SessionService);
|
|
this.testSetup.revisionsService =
|
|
this.testSetup.moduleRef.get<RevisionsService>(RevisionsService);
|
|
|
|
this.testSetup.app = this.testSetup.moduleRef.createNestApplication();
|
|
|
|
await setupApp(
|
|
this.testSetup.app,
|
|
this.testSetup.configService.get<AppConfig>('appConfig'),
|
|
this.testSetup.configService.get<AuthConfig>('authConfig'),
|
|
this.testSetup.configService.get<MediaConfig>('mediaConfig'),
|
|
await this.testSetup.app.resolve(ConsoleLoggerService),
|
|
);
|
|
|
|
for (const setupFunction of this.setupPostCompile) {
|
|
await setupFunction();
|
|
}
|
|
return this.testSetup;
|
|
}
|
|
|
|
/**
|
|
* Enable mock authentication for the public API
|
|
*/
|
|
public withMockAuth() {
|
|
this.setupPreCompile.push(async () => {
|
|
this.testingModuleBuilder
|
|
.overrideGuard(TokenAuthGuard)
|
|
.useClass(MockAuthGuard);
|
|
return await Promise.resolve();
|
|
});
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Generate a few users, identities and auth tokens for testing
|
|
*/
|
|
public withUsers() {
|
|
this.setupPostCompile.push(async () => {
|
|
// Create users
|
|
this.testSetup.users.push(
|
|
await this.testSetup.userService.createUser(username1, 'Test User 1'),
|
|
);
|
|
this.testSetup.users.push(
|
|
await this.testSetup.userService.createUser(username2, 'Test User 2'),
|
|
);
|
|
this.testSetup.users.push(
|
|
await this.testSetup.userService.createUser(username3, 'Test User 3'),
|
|
);
|
|
|
|
// Create identities for login
|
|
await this.testSetup.identityService.createLocalIdentity(
|
|
this.testSetup.users[0],
|
|
password1,
|
|
);
|
|
await this.testSetup.identityService.createLocalIdentity(
|
|
this.testSetup.users[1],
|
|
password2,
|
|
);
|
|
await this.testSetup.identityService.createLocalIdentity(
|
|
this.testSetup.users[2],
|
|
password3,
|
|
);
|
|
|
|
// create auth tokens
|
|
this.testSetup.authTokens = await Promise.all(
|
|
this.testSetup.users.map(async (user) => {
|
|
return await this.testSetup.authService.addToken(
|
|
user,
|
|
'test',
|
|
new Date().getTime() + 60 * 60 * 1000,
|
|
);
|
|
}),
|
|
);
|
|
|
|
// create notes with owner
|
|
this.testSetup.ownedNotes.push(
|
|
await this.testSetup.notesService.createNote(
|
|
'Test Note 1',
|
|
this.testSetup.users[0],
|
|
'testAlias1',
|
|
),
|
|
);
|
|
this.testSetup.ownedNotes.push(
|
|
await this.testSetup.notesService.createNote(
|
|
'Test Note 2',
|
|
this.testSetup.users[1],
|
|
'testAlias2',
|
|
),
|
|
);
|
|
this.testSetup.ownedNotes.push(
|
|
await this.testSetup.notesService.createNote(
|
|
'Test Note 3',
|
|
this.testSetup.users[2],
|
|
'testAlias3',
|
|
),
|
|
);
|
|
});
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Generate a few anonymousNotes for testing
|
|
*/
|
|
public withNotes(): TestSetupBuilder {
|
|
this.setupPostCompile.push(async () => {
|
|
this.testSetup.anonymousNotes.push(
|
|
await this.testSetup.notesService.createNote(
|
|
'Anonymous Note 1',
|
|
null,
|
|
'anonAlias1',
|
|
),
|
|
);
|
|
this.testSetup.anonymousNotes.push(
|
|
await this.testSetup.notesService.createNote(
|
|
'Anonymous Note 2',
|
|
null,
|
|
'anonAlias2',
|
|
),
|
|
);
|
|
this.testSetup.anonymousNotes.push(
|
|
await this.testSetup.notesService.createNote(
|
|
'Anonymous Note 3',
|
|
null,
|
|
'anonAlias3',
|
|
),
|
|
);
|
|
});
|
|
return this;
|
|
}
|
|
}
|
|
|
|
export const username1 = 'testuser1';
|
|
export const password1 = 'AStrongP@sswordForUser1';
|
|
export const username2 = 'testuser2';
|
|
export const password2 = 'AStrongP@sswordForUser2';
|
|
export const username3 = 'testuser3';
|
|
export const password3 = 'AStrongP@sswordForUser3';
|