test: support server-side unit testing (#2485)
This commit is contained in:
11
server/test/db.ts
Normal file
11
server/test/db.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { resetTestDb, seedTestDb } from '@server/utils/seedTestDb';
|
||||
import { before, beforeEach } from 'node:test';
|
||||
|
||||
export function setupTestDb() {
|
||||
before(async () => {
|
||||
await seedTestDb();
|
||||
});
|
||||
beforeEach(async () => {
|
||||
await resetTestDb();
|
||||
});
|
||||
}
|
||||
122
server/test/index.mts
Normal file
122
server/test/index.mts
Normal file
@@ -0,0 +1,122 @@
|
||||
// Runs unit tests using the `node:test` runner.
|
||||
|
||||
import { Command, Option } from 'commander';
|
||||
import { createWriteStream } from 'node:fs';
|
||||
import { glob } from 'node:fs/promises';
|
||||
import { join, resolve } from 'node:path';
|
||||
import { run } from 'node:test';
|
||||
import * as reporters from 'node:test/reporters';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const resolveImport = (specifier: string) =>
|
||||
fileURLToPath(import.meta.resolve(specifier));
|
||||
const BASE_DIR = join(import.meta.dirname, '../..');
|
||||
|
||||
const program = new Command();
|
||||
program
|
||||
.name('test')
|
||||
.argument('[file...]', 'Test file(s) to run (default: all)')
|
||||
.option(
|
||||
'-m, --test-name-pattern <pattern>',
|
||||
'Run tests matching the given pattern',
|
||||
(v, acc: string[]) => [...acc, v],
|
||||
[] as string[]
|
||||
)
|
||||
.option(
|
||||
'--test-reporter <reporter>',
|
||||
'Test reporter to use (repeatable)',
|
||||
(v, acc: string[]) => [...acc, v],
|
||||
[] as string[]
|
||||
)
|
||||
.option(
|
||||
'--test-reporter-destination <dest>',
|
||||
'Test reporter destination: stdout, stderr, or a file path (repeatable)',
|
||||
(v, acc: string[]) => [...acc, v],
|
||||
[] as string[]
|
||||
)
|
||||
.option(
|
||||
'--coverage, --experimental-test-coverage',
|
||||
'Enable code coverage collection'
|
||||
)
|
||||
// ignore additional options passed by vscode test runner
|
||||
.addOption(new Option('--test').hideHelp())
|
||||
.parse();
|
||||
|
||||
const positionals: string[] = program.args;
|
||||
const opts = program.opts<{
|
||||
testNamePattern: string[];
|
||||
testReporter: string[];
|
||||
testReporterDestination: string[];
|
||||
experimentalTestCoverage: boolean;
|
||||
}>();
|
||||
|
||||
let files: string[];
|
||||
|
||||
if (positionals.length > 0) {
|
||||
files = positionals.map((f) => resolve(f));
|
||||
} else {
|
||||
files = [];
|
||||
for await (const entry of glob(join(BASE_DIR, 'server/**/*.test.ts'))) {
|
||||
files.push(resolve(entry));
|
||||
}
|
||||
files.sort();
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
process.env.NODE_ENV = 'test';
|
||||
// configure ts
|
||||
process.env.TS_NODE_PROJECT = resolveImport('../tsconfig.json');
|
||||
process.env.TS_NODE_FILES = 'true';
|
||||
|
||||
const stream = run({
|
||||
files,
|
||||
execArgv: [
|
||||
'--experimental-test-module-mocks',
|
||||
'-r',
|
||||
'ts-node/register',
|
||||
'-r',
|
||||
'tsconfig-paths/register',
|
||||
'-r',
|
||||
resolveImport('./setup.ts'),
|
||||
],
|
||||
coverage: opts.experimentalTestCoverage,
|
||||
coverageExcludeGlobs: [
|
||||
join(BASE_DIR, 'server/test/**'),
|
||||
join(BASE_DIR, 'server/migration/**'),
|
||||
],
|
||||
testNamePatterns: opts.testNamePattern,
|
||||
});
|
||||
|
||||
// In CI, write a JUnit report to a file for use by GitHub
|
||||
if (process.env.CI) {
|
||||
const reportStream = createWriteStream(join(BASE_DIR, 'report.xml'));
|
||||
stream.compose(reporters.junit).pipe(reportStream);
|
||||
}
|
||||
|
||||
if (opts.testReporter.length > 0) {
|
||||
for (let i = 0; i < opts.testReporter.length; i++) {
|
||||
const reporterName = opts.testReporter[i];
|
||||
// check built-in reporters, otherwise import
|
||||
const reporter =
|
||||
reporterName in reporters
|
||||
? reporters[reporterName as keyof typeof reporters]
|
||||
: await import(reporterName).then((m) => m.default);
|
||||
|
||||
if (reporter == null) {
|
||||
console.error('Invalid test reporter: ', reporterName);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const destArg = opts.testReporterDestination[i];
|
||||
const dest =
|
||||
destArg === 'stdout' || destArg == null
|
||||
? process.stdout
|
||||
: destArg === 'stderr'
|
||||
? process.stderr
|
||||
: createWriteStream(destArg);
|
||||
|
||||
stream.compose(reporter).pipe(dest);
|
||||
}
|
||||
} else {
|
||||
stream.compose(reporters.spec).pipe(process.stdout);
|
||||
}
|
||||
10
server/test/setup.ts
Normal file
10
server/test/setup.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import logger from '@server/logger';
|
||||
import { after, before } from 'node:test';
|
||||
|
||||
before(() => {
|
||||
if (process.env.VERBOSE != 'true') logger.silent = true;
|
||||
});
|
||||
|
||||
after(() => {
|
||||
if (process.env.VERBOSE != 'true') logger.silent = false;
|
||||
});
|
||||
Reference in New Issue
Block a user