test(server): make server testing utils (#5544)

This commit is contained in:
liuyi
2024-01-11 06:40:55 +00:00
parent 9253e522aa
commit 12fdb18a80
20 changed files with 238 additions and 451 deletions

View File

@@ -1,14 +1,14 @@
import { randomUUID } from 'node:crypto';
import { INestApplication, ModuleMetadata } from '@nestjs/common';
import { Query, Resolver } from '@nestjs/graphql';
import { Test, TestingModuleBuilder } from '@nestjs/testing';
import { PrismaClient } from '@prisma/client';
import graphqlUploadExpress from 'graphql-upload/graphqlUploadExpress.mjs';
import { TestingModule } from '@nestjs/testing';
import { hashSync } from '@node-rs/argon2';
import { PrismaClient, type User } from '@prisma/client';
import { AppModule, FunctionalityModules } from '../../src/app';
import { UserFeaturesInit1698652531198 } from '../../src/data/migrations/1698652531198-user-features-init';
import { GqlModule } from '../../src/graphql.module';
import { RevertCommand, RunCommand } from '../../src/data/commands/run';
export async function flushDB() {
const client = new PrismaClient();
await client.$connect();
async function flushDB(client: PrismaClient) {
const result: { tablename: string }[] =
await client.$queryRaw`SELECT tablename
FROM pg_catalog.pg_tables
@@ -22,41 +22,99 @@ export async function flushDB() {
.filter(name => !name.includes('migrations'))
.join(', ')}`
);
await client.$disconnect();
}
export class FakePrisma {
fakeUser: User = {
id: randomUUID(),
name: 'Alex Yang',
avatarUrl: '',
email: 'alex.yang@example.org',
password: hashSync('123456'),
emailVerified: new Date(),
createdAt: new Date(),
};
async function initFeatureConfigs(db: PrismaClient) {
await UserFeaturesInit1698652531198.up(db);
}
get user() {
// eslint-disable-next-line @typescript-eslint/no-this-alias
const prisma = this;
return {
async findFirst() {
return prisma.fakeUser;
},
async findUnique() {
return this.findFirst();
},
async update() {
return this.findFirst();
},
};
export async function initTestingDB(db: PrismaClient) {
await flushDB(db);
await initFeatureConfigs(db);
}
interface TestingModuleMeatdata extends ModuleMetadata {
tapModule?(m: TestingModuleBuilder): void;
tapApp?(app: INestApplication): void;
}
function dedupeModules(modules: NonNullable<ModuleMetadata['imports']>) {
const map = new Map();
modules.forEach(m => {
if ('module' in m) {
map.set(m.module, m);
} else {
map.set(m, m);
}
});
return Array.from(map.values());
}
@Resolver(() => String)
class MockResolver {
@Query(() => String)
hello() {
return 'hello world';
}
}
export async function initFeatureConfigs(module: TestingModule) {
const run = module.get(RunCommand);
const revert = module.get(RevertCommand);
await Promise.allSettled([revert.run(['UserFeaturesInit1698652531198'])]);
await run.runOne('UserFeaturesInit1698652531198');
export async function createTestingModule(
moduleDef: TestingModuleMeatdata = {}
) {
// setting up
let imports = moduleDef.imports ?? [];
imports =
imports[0] === AppModule
? [AppModule]
: dedupeModules([...FunctionalityModules, GqlModule, ...imports]);
const builder = Test.createTestingModule({
imports,
providers: [MockResolver, ...(moduleDef.providers ?? [])],
controllers: moduleDef.controllers,
});
if (moduleDef.tapModule) {
moduleDef.tapModule(builder);
}
const m = await builder.compile();
const prisma = m.get(PrismaClient);
if (prisma instanceof PrismaClient) {
await flushDB(prisma);
await initFeatureConfigs(prisma);
}
return m;
}
export async function createTestingApp(moduleDef: TestingModuleMeatdata = {}) {
const m = await createTestingModule(moduleDef);
const app = m.createNestApplication({
cors: true,
bodyParser: true,
rawBody: true,
});
app.use(
graphqlUploadExpress({
maxFileSize: 10 * 1024 * 1024,
maxFiles: 5,
})
);
if (moduleDef.tapApp) {
moduleDef.tapApp(app);
}
await app.init();
return {
module: m,
app,
};
}