fix(test): route MikroORM's dynamic imports through vitest from the setup file, not the ORM config

This commit is contained in:
jubnl
2026-09-21 10:35:16 +02:00
parent 2bc9e927be
commit 418bd1c6b5
4 changed files with 26 additions and 11 deletions
-7
View File
@@ -27,11 +27,4 @@ export default defineConfig({
// itself, but the CLI and the migrator's logging still read it, and naming a
// different file here than the one actually open is a trap.
dbName: resolveDbPath(),
// MikroORM loads migrations and seeders through its own dynamic import(). Under
// vitest that native import() sits outside the transform pipeline, so a
// seeder's extensionless `'../../app-config'` import fails with
// ERR_UNSUPPORTED_DIR_IMPORT. Declared here, the import() runs inside a module
// vitest transforms; in production it resolves the compiled dist files as before.
// Same reason tests/unit/db/restore-migrates-forward.test.ts sets it.
dynamicImportProvider: (id: string) => import(id),
});
+6 -1
View File
@@ -36,6 +36,10 @@ import { readSchemaSnapshot } from '../../src/db/schema-snapshot';
* which only works on a database the ORM's migration table already knows.
* The legacy `createTestDb()` (in test-db.ts) stays for unit suites that never
* boot the app.
*
* @returns a normalised copy: the snapshot's seeded first-run `admin` row and
* the `users` autoincrement sequence have already been removed (see below), so
* the first user any caller creates lands on id 1.
*/
export function createSnapshotTestDb(): Database.Database {
const snapshot = readSchemaSnapshot();
@@ -43,7 +47,8 @@ export function createSnapshotTestDb(): Database.Database {
throw new Error('No schema snapshot: run under vitest (tests/global-setup.ts writes it once per run).');
}
const db = new Database(snapshot);
db.exec('PRAGMA journal_mode = WAL');
// No journal_mode PRAGMA here: this is a deserialised in-memory database, so
// WAL mode (a disk-file concept) is a no-op on it.
db.exec('PRAGMA busy_timeout = 5000');
db.exec('PRAGMA foreign_keys = ON');
// The snapshot carries the first-run seeded `admin` row (id 1); the legacy
+7 -3
View File
@@ -35,9 +35,13 @@ import type { RateLimitService } from '../../src/nest/common/rate-limit.service'
// file imports AuthPublicController (for resetRateLimits below), so a vi.mock
// factory MUST import from db-mock.ts directly, never from here, or it
// re-enters src/db/database while its own mock for that module is still being
// built and captures the real one. Re-exported so existing non-factory
// importers of test-db.ts keep working unchanged.
export { CAN_ACCESS_TRIP_SQL, createSnapshotTestDb, buildDbMock } from './db-mock';
// built and captures the real one. Only buildDbMock/CAN_ACCESS_TRIP_SQL are
// re-exported (the two a couple of unit suites use against their own
// createTestDb()) — createSnapshotTestDb is deliberately NOT re-exported here,
// so the re-entrant-import foot-gun above isn't reachable through this file at
// all: nothing importing test-db.ts can accidentally build a vi.mock factory
// that re-enters src/db/database.
export { CAN_ACCESS_TRIP_SQL, buildDbMock } from './db-mock';
// Tables to clear on reset, child-before-parent to be safe (FK checks are OFF during reset).
// Keep in sync with schema.ts + migrations.ts. Intentionally excluded: categories, addons,
+13
View File
@@ -24,6 +24,19 @@ process.env.NODE_ENV = 'test';
process.env.COOKIE_SECURE = 'false';
process.env.LOG_LEVEL = 'error'; // suppress info/debug logs in test output
// MikroORM loads migrations and seeders through its own dynamic import()
// (core/utils/fs-utils.js: `globalThis.dynamicImportProvider ?? (id => import(id))`,
// read at call time). Under vitest that native import() sits outside the
// transform pipeline, so a seeder's extensionless `'../../app-config'` import
// fails with ERR_UNSUPPORTED_DIR_IMPORT. Routed through this module's import()
// it goes through vite-node instead. Test-only on purpose: set from the ORM
// config it would compile to `require('file://…')` under the server's CommonJS
// target and crash every production boot at the seeder step.
interface DynamicImportGlobal {
dynamicImportProvider?: (id: string) => Promise<unknown>;
}
(globalThis as DynamicImportGlobal).dynamicImportProvider = (id) => import(id);
// Several services fire notification sends as unawaited dynamic-import chains
// (`import('…/notificationService').then(({ send }) => send(…).catch(…))`).
// Give those chains one macrotask turn to settle after every test, while the