From e846c91a161189bd40d1b799b191a18ef2866ef5 Mon Sep 17 00:00:00 2001 From: jubnl Date: Mon, 21 Sep 2026 10:13:40 +0200 Subject: [PATCH] test(server): integration suites boot on the migrated schema snapshot --- server/src/mikro-orm.config.ts | 7 + server/tests/helpers/db-mock.ts | 126 ++++++++++++++++++ server/tests/helpers/test-db.ts | 121 ++++++----------- server/tests/integration/admin.test.ts | 32 +---- server/tests/integration/api-docs.test.ts | 26 +--- server/tests/integration/app-config.test.ts | 26 +--- server/tests/integration/assignments.test.ts | 32 +---- server/tests/integration/atlas.test.ts | 32 +---- server/tests/integration/auth.test.ts | 37 +---- server/tests/integration/backup.test.ts | 32 +---- server/tests/integration/bootstrap.test.ts | 31 +---- server/tests/integration/budget.test.ts | 32 +---- server/tests/integration/categories.test.ts | 27 +--- server/tests/integration/collab.test.ts | 32 +---- server/tests/integration/collections.test.ts | 26 +--- server/tests/integration/dayNotes.test.ts | 32 +---- server/tests/integration/dayReorder.test.ts | 23 +--- server/tests/integration/days.test.ts | 32 +---- server/tests/integration/files.test.ts | 32 +---- server/tests/integration/help.test.ts | 15 +-- server/tests/integration/immich.test.ts | 32 +---- server/tests/integration/journey.test.ts | 37 +---- .../integration/leg-mode-incoming.test.ts | 36 +---- server/tests/integration/maps.test.ts | 32 +---- server/tests/integration/mcp.test.ts | 32 +---- .../tests/integration/memories-immich.test.ts | 57 +++----- .../integration/memories-synology.test.ts | 27 +--- .../integration/memories-unified.test.ts | 27 +--- server/tests/integration/misc.test.ts | 32 +---- .../tests/integration/notifications.test.ts | 32 +---- .../tests/integration/oauth-authorize.test.ts | 25 +--- server/tests/integration/oauth.test.ts | 32 +---- server/tests/integration/oidc.test.ts | 29 +--- server/tests/integration/packing.test.ts | 32 +---- server/tests/integration/places.test.ts | 32 +---- server/tests/integration/profile.test.ts | 32 +---- server/tests/integration/reservations.test.ts | 32 +---- .../tests/integration/scheduler-gate.test.ts | 26 +--- server/tests/integration/security.test.ts | 32 +---- server/tests/integration/settings.test.ts | 27 +--- server/tests/integration/share.test.ts | 32 +---- .../tests/integration/systemNotices.test.ts | 27 +--- server/tests/integration/tags.test.ts | 27 +--- server/tests/integration/todo.test.ts | 27 +--- server/tests/integration/trips.test.ts | 37 +---- .../tests/integration/uploads-static.test.ts | 24 +--- server/tests/integration/vacay.test.ts | 32 +---- .../tests/unit/nest/route-inventory.test.ts | 5 +- 48 files changed, 368 insertions(+), 1239 deletions(-) create mode 100644 server/tests/helpers/db-mock.ts diff --git a/server/src/mikro-orm.config.ts b/server/src/mikro-orm.config.ts index 6f3220b6e..668e84db6 100644 --- a/server/src/mikro-orm.config.ts +++ b/server/src/mikro-orm.config.ts @@ -27,4 +27,11 @@ 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), }); diff --git a/server/tests/helpers/db-mock.ts b/server/tests/helpers/db-mock.ts new file mode 100644 index 000000000..026187452 --- /dev/null +++ b/server/tests/helpers/db-mock.ts @@ -0,0 +1,126 @@ +/** + * Imported from inside `vi.mock('…/db/database')` factories, so it must stay a + * leaf: anything under src/nest pulled in here would load `db/database` while + * its mock is still being built and capture the real module. + * + * `tests/helpers/test-db.ts` used to define `createSnapshotTestDb`/`buildDbMock` + * itself, but it also imports `AuthPublicController` (for `resetRateLimits`), + * which drags in the whole auth graph — including `jwt-verify.ts`, which + * imports `../../db/database`. A `vi.mock('../../src/db/database', async () => + * { const { createSnapshotTestDb, buildDbMock } = await import('../helpers/ + * test-db'); ... })` factory that re-enters `db/database` while building its + * own mock for that exact module gets handed the REAL, unmocked module for + * that one import — Vitest can't give a still-building mock to its own + * re-entrant importer. The real module's test-mode singleton then opens its + * OWN copy of the schema snapshot (`initDb()` → `readSchemaSnapshot()`), so + * whichever code path captured `db` this way sees a pristine, never-reset + * database (the snapshot's seeded `admin@trek.local`) instead of the mock + * `buildDbMock` builds — while everything that imports `db/database` AFTER + * the factory returns gets the real mock. Which one a given guard/service + * saw then depended on unrelated import order, not on anything the test + * controls — e.g. a fresh `role: 'user'` account reading as `role: 'admin'`, + * or a trip the caller owns coming back "not found". + * + * So this module only imports `better-sqlite3` and `schema-snapshot.ts` + * (node builtins only) — nothing that could transitively import + * `src/db/database.ts` again. Keep it that way. + */ + +import Database from 'better-sqlite3'; +import { readSchemaSnapshot } from '../../src/db/schema-snapshot'; + +/** + * A copy of the migrated-and-seeded database the vitest global setup wrote + * (`tests/global-setup.ts`), for suites that boot the app with `buildApp()`. + * Those run the MikroORM migrator and seeders against the mocked connection, + * 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. + */ +export function createSnapshotTestDb(): Database.Database { + const snapshot = readSchemaSnapshot(); + if (!snapshot) { + 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'); + db.exec('PRAGMA busy_timeout = 5000'); + db.exec('PRAGMA foreign_keys = ON'); + return db; +} + +/** + * Byte-for-byte the statement in src/db/database.ts. + * + * Exported because the same query is copied into ~90 test files, and every one + * of those copies had dropped `t.currency` — so the budget domain, which reads + * exactly that column off the access row, was only ever exercising its 'EUR' + * fallback. Import this instead of retyping it. + */ +export const CAN_ACCESS_TRIP_SQL = ` + SELECT t.id, t.user_id, t.currency FROM trips t + LEFT JOIN trip_members m ON m.trip_id = t.id AND m.user_id = ? + WHERE t.id = ? AND (t.user_id = ? OR m.user_id IS NOT NULL) + `; + +/** + * Returns the mock factory for vi.mock('../../src/db/database', ...). + * The returned object mirrors the shape of database.ts exports. + * + * @example + * vi.mock('../../src/db/database', async () => { + * const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock'); + * return buildDbMock(createSnapshotTestDb()); + * }); + */ +export function buildDbMock(testDb: Database.Database) { + return { + db: testDb, + closeDb: () => {}, + reinitialize: () => {}, + getPlaceWithTags: (placeId: number | string) => { + interface PlaceRow { + id: number; + category_id: number | null; + category_name: string | null; + category_color: string | null; + category_icon: string | null; + [key: string]: unknown; + } + const place = testDb.prepare(` + SELECT p.*, c.name as category_name, c.color as category_color, c.icon as category_icon + FROM places p + LEFT JOIN categories c ON p.category_id = c.id + WHERE p.id = ? + `).get(placeId) as PlaceRow | undefined; + + if (!place) return null; + + const tags = testDb.prepare(` + SELECT t.* FROM tags t + JOIN place_tags pt ON t.id = pt.tag_id + WHERE pt.place_id = ? + `).all(placeId); + + return { + ...place, + category: place.category_id ? { + id: place.category_id, + name: place.category_name, + color: place.category_color, + icon: place.category_icon, + } : null, + tags, + }; + }, + canAccessTrip: (tripId: number | string, userId: number) => { + return testDb.prepare(CAN_ACCESS_TRIP_SQL).get(userId, tripId, userId); + }, + isOwner: (tripId: number | string, userId: number) => { + return !!testDb.prepare('SELECT id FROM trips WHERE id = ? AND user_id = ?').get(tripId, userId); + }, + getRawConnection: () => testDb, + registerReinitializeHook: () => {}, + runDemoSeed: () => {}, + }; +} diff --git a/server/tests/helpers/test-db.ts b/server/tests/helpers/test-db.ts index 9ae7d7e24..02692d932 100644 --- a/server/tests/helpers/test-db.ts +++ b/server/tests/helpers/test-db.ts @@ -1,19 +1,26 @@ /** * In-memory SQLite test database helper. * - * Usage in an integration test file: + * Usage in a `buildApp()` integration test file — `createSnapshotTestDb`/ + * `buildDbMock` come through the `db-mock.ts` leaf module directly, not + * through this file, so the mock factory doesn't re-enter `src/db/database` + * while it is still being built (see db-mock.ts's header comment): * - * import { createTestDb, resetTestDb } from '../helpers/test-db'; - * import { buildDbMock } from '../helpers/test-db'; - * - * // Declare at module scope (before vi.mock so it's available in factory) - * const testDb = createTestDb(); - * - * vi.mock('../../src/db/database', () => buildDbMock(testDb)); + * vi.mock('../../src/db/database', async () => { + * const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock'); + * return buildDbMock(createSnapshotTestDb()); + * }); * vi.mock('../../src/config', () => TEST_CONFIG); * + * import { db as testDb } from '../../src/db/database'; + * import { resetTestDb, resetRateLimits } from '../helpers/test-db'; + * * beforeEach(() => resetTestDb(testDb)); * afterAll(() => testDb.close()); + * + * For unit suites that never boot the app, `createTestDb()` (this file) still + * builds its own throwaway `:memory:` database via the legacy schema/migration + * scripts. */ import Database from 'better-sqlite3'; @@ -23,6 +30,15 @@ import { runMigrations } from '../../src/db/migrations'; import { AuthPublicController } from '../../src/nest/auth/auth-public.controller'; import type { RateLimitService } from '../../src/nest/common/rate-limit.service'; +// createSnapshotTestDb / buildDbMock / CAN_ACCESS_TRIP_SQL live in db-mock.ts, a +// leaf module with no src/nest imports — see its header comment for why. This +// 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'; + // 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, // photo_providers, photo_provider_fields, schema_version (seed/config data, not user data). @@ -185,8 +201,14 @@ export function setCollabFeature( } function seedDefaults(db: Database.Database): void { - const insertCat = db.prepare('INSERT OR IGNORE INTO categories (name, color, icon) VALUES (?, ?, ?)'); - for (const cat of DEFAULT_CATEGORIES) insertCat.run(cat.name, cat.color, cat.icon); + // Not INSERT OR IGNORE: categories.name has no unique constraint, and the + // migrated snapshot (createSnapshotTestDb) already holds the same ten rows — + // IGNORE only dedupes on a conflicting constraint, so it would insert a + // second copy of each. Absence-of-name is what both callers actually want. + const insertCat = db.prepare( + 'INSERT INTO categories (name, color, icon) SELECT ?, ?, ? WHERE NOT EXISTS (SELECT 1 FROM categories WHERE name = ?)', + ); + for (const cat of DEFAULT_CATEGORIES) insertCat.run(cat.name, cat.color, cat.icon, cat.name); const insertAddon = db.prepare('INSERT OR IGNORE INTO addons (id, name, description, type, icon, enabled, sort_order) VALUES (?, ?, ?, ?, ?, ?, ?)'); for (const a of DEFAULT_ADDONS) insertAddon.run(a.id, a.name, a.description, a.type, a.icon, a.enabled, a.sort_order); @@ -226,81 +248,18 @@ export function resetTestDb(db: Database.Database): void { db.exec(`DELETE FROM "${table}"`); } } + // Restart ids at 1, exactly as a fresh legacy database did — the snapshot's + // seeded `admin` user (id 1) was just deleted above, and the factories assume + // a clean autoincrement sequence. + const seqTable = existingTables.has('sqlite_sequence'); + if (seqTable) { + const clear = db.prepare('DELETE FROM sqlite_sequence WHERE name = ?'); + for (const table of RESET_TABLES) if (existingTables.has(table)) clear.run(table); + } db.exec('PRAGMA foreign_keys = ON'); seedDefaults(db); } -/** - * Byte-for-byte the statement in src/db/database.ts. - * - * Exported because the same query is copied into ~90 test files, and every one - * of those copies had dropped `t.currency` — so the budget domain, which reads - * exactly that column off the access row, was only ever exercising its 'EUR' - * fallback. Import this instead of retyping it. - */ -export const CAN_ACCESS_TRIP_SQL = ` - SELECT t.id, t.user_id, t.currency FROM trips t - LEFT JOIN trip_members m ON m.trip_id = t.id AND m.user_id = ? - WHERE t.id = ? AND (t.user_id = ? OR m.user_id IS NOT NULL) - `; - -/** - * Returns the mock factory for vi.mock('../../src/db/database', ...). - * The returned object mirrors the shape of database.ts exports. - * - * @example - * const testDb = createTestDb(); - * vi.mock('../../src/db/database', () => buildDbMock(testDb)); - */ -export function buildDbMock(testDb: Database.Database) { - return { - db: testDb, - closeDb: () => {}, - reinitialize: () => {}, - getPlaceWithTags: (placeId: number | string) => { - interface PlaceRow { - id: number; - category_id: number | null; - category_name: string | null; - category_color: string | null; - category_icon: string | null; - [key: string]: unknown; - } - const place = testDb.prepare(` - SELECT p.*, c.name as category_name, c.color as category_color, c.icon as category_icon - FROM places p - LEFT JOIN categories c ON p.category_id = c.id - WHERE p.id = ? - `).get(placeId) as PlaceRow | undefined; - - if (!place) return null; - - const tags = testDb.prepare(` - SELECT t.* FROM tags t - JOIN place_tags pt ON t.id = pt.tag_id - WHERE pt.place_id = ? - `).all(placeId); - - return { - ...place, - category: place.category_id ? { - id: place.category_id, - name: place.category_name, - color: place.category_color, - icon: place.category_icon, - } : null, - tags, - }; - }, - canAccessTrip: (tripId: number | string, userId: number) => { - return testDb.prepare(CAN_ACCESS_TRIP_SQL).get(userId, tripId, userId); - }, - isOwner: (tripId: number | string, userId: number) => { - return !!testDb.prepare('SELECT id FROM trips WHERE id = ? AND user_id = ?').get(tripId, userId); - }, - }; -} - /** * Resets the Nest per-IP rate-limit buckets between tests — the buildApp() drop-in * for the legacy `loginAttempts.clear(); mfaAttempts.clear()`. diff --git a/server/tests/integration/admin.test.ts b/server/tests/integration/admin.test.ts index 9d120d4dd..9f8875ed8 100644 --- a/server/tests/integration/admin.test.ts +++ b/server/tests/integration/admin.test.ts @@ -7,31 +7,10 @@ import request from 'supertest'; import type { Application } from 'express'; import type { INestApplication } from '@nestjs/common'; -const { testDb, dbMock } = vi.hoisted(() => { - const Database = require('better-sqlite3'); - const db = new Database(':memory:'); - db.exec('PRAGMA journal_mode = WAL'); - db.exec('PRAGMA foreign_keys = ON'); - db.exec('PRAGMA busy_timeout = 5000'); - const mock = { - db, - closeDb: () => {}, - reinitialize: () => {}, - getPlaceWithTags: (placeId: number) => { - const place: any = db.prepare(`SELECT p.*, c.name as category_name, c.color as category_color, c.icon as category_icon FROM places p LEFT JOIN categories c ON p.category_id = c.id WHERE p.id = ?`).get(placeId); - if (!place) return null; - const tags = db.prepare(`SELECT t.* FROM tags t JOIN place_tags pt ON t.id = pt.tag_id WHERE pt.place_id = ?`).all(placeId); - return { ...place, category: place.category_id ? { id: place.category_id, name: place.category_name, color: place.category_color, icon: place.category_icon } : null, tags }; - }, - canAccessTrip: (tripId: any, userId: number) => - db.prepare(`SELECT t.id, t.user_id FROM trips t LEFT JOIN trip_members m ON m.trip_id = t.id AND m.user_id = ? WHERE t.id = ? AND (t.user_id = ? OR m.user_id IS NOT NULL)`).get(userId, tripId, userId), - isOwner: (tripId: any, userId: number) => - !!db.prepare('SELECT id FROM trips WHERE id = ? AND user_id = ?').get(tripId, userId), - }; - return { testDb: db, dbMock: mock }; +vi.mock('../../src/db/database', async () => { + const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock'); + return buildDbMock(createSnapshotTestDb()); }); - -vi.mock('../../src/db/database', () => dbMock); vi.mock('../../src/config', () => ({ JWT_SECRET: 'test-jwt-secret-for-trek-testing-only', ENCRYPTION_KEY: 'a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2', @@ -43,9 +22,8 @@ vi.mock('../../src/config', () => ({ })); vi.mock('../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn() })); +import { db as testDb } from '../../src/db/database'; import { buildApp } from '../../src/bootstrap'; -import { createTables } from '../../src/db/schema'; -import { runMigrations } from '../../src/db/migrations'; import { resetTestDb, resetRateLimits } from '../helpers/test-db'; import { createUser, createAdmin, createInviteToken, createTrip, createBudgetItem, createJourney, createJourneyEntry, addJourneyContributor, addTripPhoto, createCategory, createTag, createTodoItem, createMcpToken, createBucketListItem, createVisitedCountry, createCollabNote, addTripMember } from '../helpers/factories'; import { authCookie } from '../helpers/auth'; @@ -54,8 +32,6 @@ let nestApp: INestApplication; let app: Application; beforeAll(async () => { - createTables(testDb); - runMigrations(testDb); nestApp = await buildApp(); app = nestApp.getHttpAdapter().getInstance(); }); diff --git a/server/tests/integration/api-docs.test.ts b/server/tests/integration/api-docs.test.ts index 795f66294..8de133939 100644 --- a/server/tests/integration/api-docs.test.ts +++ b/server/tests/integration/api-docs.test.ts @@ -8,25 +8,10 @@ import { describe, it, expect, vi, beforeAll, afterAll } from 'vitest'; import request from 'supertest'; import type { INestApplication } from '@nestjs/common'; -const { testDb, dbMock } = vi.hoisted(() => { - // eslint-disable-next-line @typescript-eslint/no-require-imports - const Database = require('better-sqlite3'); - const db = new Database(':memory:'); - db.exec('PRAGMA journal_mode = WAL'); - db.exec('PRAGMA foreign_keys = ON'); - db.exec('PRAGMA busy_timeout = 5000'); - const mock = { - db, - closeDb: () => {}, - reinitialize: () => {}, - getPlaceWithTags: () => null, - canAccessTrip: () => undefined, - isOwner: () => false, - }; - return { testDb: db, dbMock: mock }; +vi.mock('../../src/db/database', async () => { + const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock'); + return buildDbMock(createSnapshotTestDb()); }); - -vi.mock('../../src/db/database', () => dbMock); vi.mock('../../src/config', () => ({ JWT_SECRET: 'test-jwt-secret-for-trek-testing-only', ENCRYPTION_KEY: 'a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2', @@ -38,8 +23,7 @@ vi.mock('../../src/config', () => ({ })); vi.mock('../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn() })); -import { createTables } from '../../src/db/schema'; -import { runMigrations } from '../../src/db/migrations'; +import { db as testDb } from '../../src/db/database'; import { resetTestDb } from '../helpers/test-db'; import { buildApp } from '../../src/bootstrap'; import { apiDocsEnabled } from '../../src/nest/common/api-docs.kill-switch'; @@ -50,8 +34,6 @@ describe('API-DOCS (#1412) — flag-gated OpenAPI surface', () => { let prevFlag: string | undefined; beforeAll(async () => { - createTables(testDb); - runMigrations(testDb); resetTestDb(testDb); prevFlag = process.env.TREK_API_DOCS_ENABLED; process.env.TREK_API_DOCS_ENABLED = 'true'; diff --git a/server/tests/integration/app-config.test.ts b/server/tests/integration/app-config.test.ts index d02b3e9ac..ef9418fbd 100644 --- a/server/tests/integration/app-config.test.ts +++ b/server/tests/integration/app-config.test.ts @@ -7,25 +7,10 @@ import { describe, it, expect, vi, afterEach, beforeAll } from 'vitest'; import type { INestApplication } from '@nestjs/common'; -const { testDb, dbMock } = vi.hoisted(() => { - // eslint-disable-next-line @typescript-eslint/no-require-imports - const Database = require('better-sqlite3'); - const db = new Database(':memory:'); - db.exec('PRAGMA journal_mode = WAL'); - db.exec('PRAGMA foreign_keys = ON'); - db.exec('PRAGMA busy_timeout = 5000'); - const mock = { - db, - closeDb: () => {}, - reinitialize: () => {}, - getPlaceWithTags: () => null, - canAccessTrip: () => undefined, - isOwner: () => false, - }; - return { testDb: db, dbMock: mock }; +vi.mock('../../src/db/database', async () => { + const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock'); + return buildDbMock(createSnapshotTestDb()); }); - -vi.mock('../../src/db/database', () => dbMock); vi.mock('../../src/config', () => ({ JWT_SECRET: 'test-jwt-secret-for-trek-testing-only', ENCRYPTION_KEY: 'a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2', @@ -37,8 +22,7 @@ vi.mock('../../src/config', () => ({ })); vi.mock('../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn() })); -import { createTables } from '../../src/db/schema'; -import { runMigrations } from '../../src/db/migrations'; +import { db as testDb } from '../../src/db/database'; import { resetTestDb } from '../helpers/test-db'; import { buildApp } from '../../src/bootstrap'; import { httpConfig, RuntimeEnvService } from '../../src/nest/app-config'; @@ -50,8 +34,6 @@ describe('AppConfigModule in the real buildApp()', () => { let prevDemo: string | undefined; beforeAll(() => { - createTables(testDb); - runMigrations(testDb); resetTestDb(testDb); prevForceHttps = process.env.FORCE_HTTPS; prevDemo = process.env.DEMO_MODE; diff --git a/server/tests/integration/assignments.test.ts b/server/tests/integration/assignments.test.ts index a8396ffb7..66d61c90e 100644 --- a/server/tests/integration/assignments.test.ts +++ b/server/tests/integration/assignments.test.ts @@ -7,31 +7,10 @@ import request from 'supertest'; import type { Application } from 'express'; import type { INestApplication } from '@nestjs/common'; -const { testDb, dbMock } = vi.hoisted(() => { - const Database = require('better-sqlite3'); - const db = new Database(':memory:'); - db.exec('PRAGMA journal_mode = WAL'); - db.exec('PRAGMA foreign_keys = ON'); - db.exec('PRAGMA busy_timeout = 5000'); - const mock = { - db, - closeDb: () => {}, - reinitialize: () => {}, - getPlaceWithTags: (placeId: number) => { - const place: any = db.prepare(`SELECT p.*, c.name as category_name, c.color as category_color, c.icon as category_icon FROM places p LEFT JOIN categories c ON p.category_id = c.id WHERE p.id = ?`).get(placeId); - if (!place) return null; - const tags = db.prepare(`SELECT t.* FROM tags t JOIN place_tags pt ON t.id = pt.tag_id WHERE pt.place_id = ?`).all(placeId); - return { ...place, category: place.category_id ? { id: place.category_id, name: place.category_name, color: place.category_color, icon: place.category_icon } : null, tags }; - }, - canAccessTrip: (tripId: any, userId: number) => - db.prepare(`SELECT t.id, t.user_id FROM trips t LEFT JOIN trip_members m ON m.trip_id = t.id AND m.user_id = ? WHERE t.id = ? AND (t.user_id = ? OR m.user_id IS NOT NULL)`).get(userId, tripId, userId), - isOwner: (tripId: any, userId: number) => - !!db.prepare('SELECT id FROM trips WHERE id = ? AND user_id = ?').get(tripId, userId), - }; - return { testDb: db, dbMock: mock }; +vi.mock('../../src/db/database', async () => { + const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock'); + return buildDbMock(createSnapshotTestDb()); }); - -vi.mock('../../src/db/database', () => dbMock); vi.mock('../../src/config', () => ({ JWT_SECRET: 'test-jwt-secret-for-trek-testing-only', ENCRYPTION_KEY: 'a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2', @@ -43,9 +22,8 @@ vi.mock('../../src/config', () => ({ })); vi.mock('../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn() })); +import { db as testDb } from '../../src/db/database'; import { buildApp } from '../../src/bootstrap'; -import { createTables } from '../../src/db/schema'; -import { runMigrations } from '../../src/db/migrations'; import { resetTestDb, resetRateLimits } from '../helpers/test-db'; import { createUser, createTrip, createDay, createPlace, addTripMember, createTag } from '../helpers/factories'; import { authCookie } from '../helpers/auth'; @@ -54,8 +32,6 @@ let nestApp: INestApplication; let app: Application; beforeAll(async () => { - createTables(testDb); - runMigrations(testDb); nestApp = await buildApp(); app = nestApp.getHttpAdapter().getInstance(); }); diff --git a/server/tests/integration/atlas.test.ts b/server/tests/integration/atlas.test.ts index 6eafbb90b..04862bcab 100644 --- a/server/tests/integration/atlas.test.ts +++ b/server/tests/integration/atlas.test.ts @@ -7,31 +7,10 @@ import request from 'supertest'; import type { Application } from 'express'; import type { INestApplication } from '@nestjs/common'; -const { testDb, dbMock } = vi.hoisted(() => { - const Database = require('better-sqlite3'); - const db = new Database(':memory:'); - db.exec('PRAGMA journal_mode = WAL'); - db.exec('PRAGMA foreign_keys = ON'); - db.exec('PRAGMA busy_timeout = 5000'); - const mock = { - db, - closeDb: () => {}, - reinitialize: () => {}, - getPlaceWithTags: (placeId: number) => { - const place: any = db.prepare(`SELECT p.*, c.name as category_name, c.color as category_color, c.icon as category_icon FROM places p LEFT JOIN categories c ON p.category_id = c.id WHERE p.id = ?`).get(placeId); - if (!place) return null; - const tags = db.prepare(`SELECT t.* FROM tags t JOIN place_tags pt ON t.id = pt.tag_id WHERE pt.place_id = ?`).all(placeId); - return { ...place, category: place.category_id ? { id: place.category_id, name: place.category_name, color: place.category_color, icon: place.category_icon } : null, tags }; - }, - canAccessTrip: (tripId: any, userId: number) => - db.prepare(`SELECT t.id, t.user_id FROM trips t LEFT JOIN trip_members m ON m.trip_id = t.id AND m.user_id = ? WHERE t.id = ? AND (t.user_id = ? OR m.user_id IS NOT NULL)`).get(userId, tripId, userId), - isOwner: (tripId: any, userId: number) => - !!db.prepare('SELECT id FROM trips WHERE id = ? AND user_id = ?').get(tripId, userId), - }; - return { testDb: db, dbMock: mock }; +vi.mock('../../src/db/database', async () => { + const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock'); + return buildDbMock(createSnapshotTestDb()); }); - -vi.mock('../../src/db/database', () => dbMock); vi.mock('../../src/config', () => ({ JWT_SECRET: 'test-jwt-secret-for-trek-testing-only', ENCRYPTION_KEY: 'a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2', @@ -43,9 +22,8 @@ vi.mock('../../src/config', () => ({ })); vi.mock('../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn() })); +import { db as testDb } from '../../src/db/database'; import { buildApp } from '../../src/bootstrap'; -import { createTables } from '../../src/db/schema'; -import { runMigrations } from '../../src/db/migrations'; import { resetTestDb, resetRateLimits } from '../helpers/test-db'; import { createUser, createTrip } from '../helpers/factories'; import { authCookie } from '../helpers/auth'; @@ -76,8 +54,6 @@ beforeAll(async () => { return new Response('{}', { status: 200, headers: { 'Content-Type': 'application/json' } }); }); - createTables(testDb); - runMigrations(testDb); nestApp = await buildApp(); app = nestApp.getHttpAdapter().getInstance(); diff --git a/server/tests/integration/auth.test.ts b/server/tests/integration/auth.test.ts index ce1b56b2c..84e18e091 100644 --- a/server/tests/integration/auth.test.ts +++ b/server/tests/integration/auth.test.ts @@ -13,36 +13,10 @@ import { authenticator } from 'otplib'; // ───────────────────────────────────────────────────────────────────────────── // Step 1: Bare in-memory DB — schema applied in beforeAll after mocks register // ───────────────────────────────────────────────────────────────────────────── -const { testDb, dbMock } = vi.hoisted(() => { - const Database = require('better-sqlite3'); - const db = new Database(':memory:'); - db.exec('PRAGMA journal_mode = WAL'); - db.exec('PRAGMA foreign_keys = ON'); - db.exec('PRAGMA busy_timeout = 5000'); - - const mock = { - db, - closeDb: () => {}, - reinitialize: () => {}, - getPlaceWithTags: (placeId: number) => { - const place: any = db.prepare(` - SELECT p.*, c.name as category_name, c.color as category_color, c.icon as category_icon - FROM places p LEFT JOIN categories c ON p.category_id = c.id WHERE p.id = ? - `).get(placeId); - if (!place) return null; - const tags = db.prepare(`SELECT t.* FROM tags t JOIN place_tags pt ON t.id = pt.tag_id WHERE pt.place_id = ?`).all(placeId); - return { ...place, category: place.category_id ? { id: place.category_id, name: place.category_name, color: place.category_color, icon: place.category_icon } : null, tags }; - }, - canAccessTrip: (tripId: any, userId: number) => - db.prepare(`SELECT t.id, t.user_id FROM trips t LEFT JOIN trip_members m ON m.trip_id = t.id AND m.user_id = ? WHERE t.id = ? AND (t.user_id = ? OR m.user_id IS NOT NULL)`).get(userId, tripId, userId), - isOwner: (tripId: any, userId: number) => - !!db.prepare('SELECT id FROM trips WHERE id = ? AND user_id = ?').get(tripId, userId), - }; - - return { testDb: db, dbMock: mock }; +vi.mock('../../src/db/database', async () => { + const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock'); + return buildDbMock(createSnapshotTestDb()); }); - -vi.mock('../../src/db/database', () => dbMock); vi.mock('../../src/config', () => ({ JWT_SECRET: 'test-jwt-secret-for-trek-testing-only', ENCRYPTION_KEY: 'a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2', @@ -54,9 +28,8 @@ vi.mock('../../src/config', () => ({ })); vi.mock('../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn() })); +import { db as testDb } from '../../src/db/database'; import { buildApp } from '../../src/bootstrap'; -import { createTables } from '../../src/db/schema'; -import { runMigrations } from '../../src/db/migrations'; import { resetTestDb, resetRateLimits } from '../helpers/test-db'; import { createUser, createAdmin, createUserWithMfa, createInviteToken, createTrip, createBudgetItem, createJourney, createJourneyEntry, addJourneyContributor, addTripPhoto, createCategory, createTag, createTodoItem, createMcpToken, createBucketListItem, createVisitedCountry, createCollabNote, addTripMember } from '../helpers/factories'; import { authCookie, authHeader } from '../helpers/auth'; @@ -65,8 +38,6 @@ let nestApp: INestApplication; let app: Application; beforeAll(async () => { - createTables(testDb); - runMigrations(testDb); nestApp = await buildApp(); app = nestApp.getHttpAdapter().getInstance(); }); diff --git a/server/tests/integration/backup.test.ts b/server/tests/integration/backup.test.ts index 3cd70dfbe..132c1e0a0 100644 --- a/server/tests/integration/backup.test.ts +++ b/server/tests/integration/backup.test.ts @@ -11,31 +11,10 @@ import request from 'supertest'; import type { Application } from 'express'; import type { INestApplication } from '@nestjs/common'; -const { testDb, dbMock } = vi.hoisted(() => { - const Database = require('better-sqlite3'); - const db = new Database(':memory:'); - db.exec('PRAGMA journal_mode = WAL'); - db.exec('PRAGMA foreign_keys = ON'); - db.exec('PRAGMA busy_timeout = 5000'); - const mock = { - db, - closeDb: () => {}, - reinitialize: () => {}, - getPlaceWithTags: (placeId: number) => { - const place: any = db.prepare(`SELECT p.*, c.name as category_name, c.color as category_color, c.icon as category_icon FROM places p LEFT JOIN categories c ON p.category_id = c.id WHERE p.id = ?`).get(placeId); - if (!place) return null; - const tags = db.prepare(`SELECT t.* FROM tags t JOIN place_tags pt ON t.id = pt.tag_id WHERE pt.place_id = ?`).all(placeId); - return { ...place, category: place.category_id ? { id: place.category_id, name: place.category_name, color: place.category_color, icon: place.category_icon } : null, tags }; - }, - canAccessTrip: (tripId: any, userId: number) => - db.prepare(`SELECT t.id, t.user_id FROM trips t LEFT JOIN trip_members m ON m.trip_id = t.id AND m.user_id = ? WHERE t.id = ? AND (t.user_id = ? OR m.user_id IS NOT NULL)`).get(userId, tripId, userId), - isOwner: (tripId: any, userId: number) => - !!db.prepare('SELECT id FROM trips WHERE id = ? AND user_id = ?').get(tripId, userId), - }; - return { testDb: db, dbMock: mock }; +vi.mock('../../src/db/database', async () => { + const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock'); + return buildDbMock(createSnapshotTestDb()); }); - -vi.mock('../../src/db/database', () => dbMock); vi.mock('../../src/config', () => ({ JWT_SECRET: 'test-jwt-secret-for-trek-testing-only', ENCRYPTION_KEY: 'a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2', @@ -78,9 +57,8 @@ vi.mock('../../src/nest/backup/auto-backup.settings', async () => { }; }); +import { db as testDb } from '../../src/db/database'; import { buildApp } from '../../src/bootstrap'; -import { createTables } from '../../src/db/schema'; -import { runMigrations } from '../../src/db/migrations'; import { resetTestDb, resetRateLimits } from '../helpers/test-db'; import { createAdmin, createUser } from '../helpers/factories'; import { authCookie } from '../helpers/auth'; @@ -94,8 +72,6 @@ let nestApp: INestApplication; let app: Application; beforeAll(async () => { - createTables(testDb); - runMigrations(testDb); nestApp = await buildApp(); app = nestApp.getHttpAdapter().getInstance(); }); diff --git a/server/tests/integration/bootstrap.test.ts b/server/tests/integration/bootstrap.test.ts index f588acff4..25019f2a2 100644 --- a/server/tests/integration/bootstrap.test.ts +++ b/server/tests/integration/bootstrap.test.ts @@ -10,30 +10,10 @@ import { describe, it, expect, vi, beforeAll, afterAll } from 'vitest'; import request from 'supertest'; import type { INestApplication } from '@nestjs/common'; -const { testDb, dbMock } = vi.hoisted(() => { - // eslint-disable-next-line @typescript-eslint/no-require-imports - const Database = require('better-sqlite3'); - const db = new Database(':memory:'); - db.exec('PRAGMA journal_mode = WAL'); - db.exec('PRAGMA foreign_keys = ON'); - db.exec('PRAGMA busy_timeout = 5000'); - const mock = { - db, - closeDb: () => {}, - reinitialize: () => {}, - getPlaceWithTags: () => null, - canAccessTrip: (tripId: any, userId: number) => - db - .prepare( - `SELECT t.id, t.user_id FROM trips t LEFT JOIN trip_members m ON m.trip_id = t.id AND m.user_id = ? WHERE t.id = ? AND (t.user_id = ? OR m.user_id IS NOT NULL)`, - ) - .get(userId, tripId, userId), - isOwner: (tripId: any, userId: number) => !!db.prepare('SELECT id FROM trips WHERE id = ? AND user_id = ?').get(tripId, userId), - }; - return { testDb: db, dbMock: mock }; +vi.mock('../../src/db/database', async () => { + const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock'); + return buildDbMock(createSnapshotTestDb()); }); - -vi.mock('../../src/db/database', () => dbMock); vi.mock('../../src/config', () => ({ JWT_SECRET: 'test-jwt-secret-for-trek-testing-only', ENCRYPTION_KEY: 'a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2', @@ -58,8 +38,7 @@ vi.mock('../../src/nest/scheduling/cron-registrar.service', () => ({ }, })); -import { createTables } from '../../src/db/schema'; -import { runMigrations } from '../../src/db/migrations'; +import { db as testDb } from '../../src/db/database'; import { resetTestDb } from '../helpers/test-db'; import { createUser } from '../helpers/factories'; import { authCookie } from '../helpers/auth'; @@ -70,8 +49,6 @@ describe('BOOTSTRAP (F6) — unified NestJS app serves the whole surface', () => let instance: import('express').Application; beforeAll(async () => { - createTables(testDb); - runMigrations(testDb); resetTestDb(testDb); app = await buildApp(); instance = app.getHttpAdapter().getInstance(); diff --git a/server/tests/integration/budget.test.ts b/server/tests/integration/budget.test.ts index a7b4c19ea..399773367 100644 --- a/server/tests/integration/budget.test.ts +++ b/server/tests/integration/budget.test.ts @@ -7,31 +7,10 @@ import request from 'supertest'; import type { Application } from 'express'; import type { INestApplication } from '@nestjs/common'; -const { testDb, dbMock } = vi.hoisted(() => { - const Database = require('better-sqlite3'); - const db = new Database(':memory:'); - db.exec('PRAGMA journal_mode = WAL'); - db.exec('PRAGMA foreign_keys = ON'); - db.exec('PRAGMA busy_timeout = 5000'); - const mock = { - db, - closeDb: () => {}, - reinitialize: () => {}, - getPlaceWithTags: (placeId: number) => { - const place: any = db.prepare(`SELECT p.*, c.name as category_name, c.color as category_color, c.icon as category_icon FROM places p LEFT JOIN categories c ON p.category_id = c.id WHERE p.id = ?`).get(placeId); - if (!place) return null; - const tags = db.prepare(`SELECT t.* FROM tags t JOIN place_tags pt ON t.id = pt.tag_id WHERE pt.place_id = ?`).all(placeId); - return { ...place, category: place.category_id ? { id: place.category_id, name: place.category_name, color: place.category_color, icon: place.category_icon } : null, tags }; - }, - canAccessTrip: (tripId: any, userId: number) => - db.prepare(`SELECT t.id, t.user_id FROM trips t LEFT JOIN trip_members m ON m.trip_id = t.id AND m.user_id = ? WHERE t.id = ? AND (t.user_id = ? OR m.user_id IS NOT NULL)`).get(userId, tripId, userId), - isOwner: (tripId: any, userId: number) => - !!db.prepare('SELECT id FROM trips WHERE id = ? AND user_id = ?').get(tripId, userId), - }; - return { testDb: db, dbMock: mock }; +vi.mock('../../src/db/database', async () => { + const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock'); + return buildDbMock(createSnapshotTestDb()); }); - -vi.mock('../../src/db/database', () => dbMock); vi.mock('../../src/config', () => ({ JWT_SECRET: 'test-jwt-secret-for-trek-testing-only', ENCRYPTION_KEY: 'a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2', @@ -43,9 +22,8 @@ vi.mock('../../src/config', () => ({ })); vi.mock('../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn() })); +import { db as testDb } from '../../src/db/database'; import { buildApp } from '../../src/bootstrap'; -import { createTables } from '../../src/db/schema'; -import { runMigrations } from '../../src/db/migrations'; import { resetTestDb, resetRateLimits } from '../helpers/test-db'; import { createUser, createTrip, createBudgetItem, addTripMember, createReservation } from '../helpers/factories'; import { authCookie } from '../helpers/auth'; @@ -54,8 +32,6 @@ let nestApp: INestApplication; let app: Application; beforeAll(async () => { - createTables(testDb); - runMigrations(testDb); nestApp = await buildApp(); app = nestApp.getHttpAdapter().getInstance(); }); diff --git a/server/tests/integration/categories.test.ts b/server/tests/integration/categories.test.ts index bc32a5c25..847bf8462 100644 --- a/server/tests/integration/categories.test.ts +++ b/server/tests/integration/categories.test.ts @@ -7,26 +7,10 @@ import request from 'supertest'; import type { Application } from 'express'; import type { INestApplication } from '@nestjs/common'; -const { testDb, dbMock } = vi.hoisted(() => { - const Database = require('better-sqlite3'); - const db = new Database(':memory:'); - db.exec('PRAGMA journal_mode = WAL'); - db.exec('PRAGMA foreign_keys = ON'); - db.exec('PRAGMA busy_timeout = 5000'); - const mock = { - db, - closeDb: () => {}, - reinitialize: () => {}, - getPlaceWithTags: () => null, - canAccessTrip: (tripId: any, userId: number) => - db.prepare(`SELECT t.id, t.user_id FROM trips t LEFT JOIN trip_members m ON m.trip_id = t.id AND m.user_id = ? WHERE t.id = ? AND (t.user_id = ? OR m.user_id IS NOT NULL)`).get(userId, tripId, userId), - isOwner: (tripId: any, userId: number) => - !!db.prepare('SELECT id FROM trips WHERE id = ? AND user_id = ?').get(tripId, userId), - }; - return { testDb: db, dbMock: mock }; +vi.mock('../../src/db/database', async () => { + const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock'); + return buildDbMock(createSnapshotTestDb()); }); - -vi.mock('../../src/db/database', () => dbMock); vi.mock('../../src/config', () => ({ JWT_SECRET: 'test-jwt-secret-for-trek-testing-only', ENCRYPTION_KEY: 'a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2', @@ -38,9 +22,8 @@ vi.mock('../../src/config', () => ({ })); vi.mock('../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn() })); +import { db as testDb } from '../../src/db/database'; import { buildApp } from '../../src/bootstrap'; -import { createTables } from '../../src/db/schema'; -import { runMigrations } from '../../src/db/migrations'; import { resetTestDb, resetRateLimits } from '../helpers/test-db'; import { createUser, createAdmin } from '../helpers/factories'; import { authCookie } from '../helpers/auth'; @@ -49,8 +32,6 @@ let nestApp: INestApplication; let app: Application; beforeAll(async () => { - createTables(testDb); - runMigrations(testDb); nestApp = await buildApp(); app = nestApp.getHttpAdapter().getInstance(); }); diff --git a/server/tests/integration/collab.test.ts b/server/tests/integration/collab.test.ts index 83b72eef2..45abec366 100644 --- a/server/tests/integration/collab.test.ts +++ b/server/tests/integration/collab.test.ts @@ -12,31 +12,10 @@ import type { INestApplication } from '@nestjs/common'; import path from 'path'; import fs from 'fs'; -const { testDb, dbMock } = vi.hoisted(() => { - const Database = require('better-sqlite3'); - const db = new Database(':memory:'); - db.exec('PRAGMA journal_mode = WAL'); - db.exec('PRAGMA foreign_keys = ON'); - db.exec('PRAGMA busy_timeout = 5000'); - const mock = { - db, - closeDb: () => {}, - reinitialize: () => {}, - getPlaceWithTags: (placeId: number) => { - const place: any = db.prepare(`SELECT p.*, c.name as category_name, c.color as category_color, c.icon as category_icon FROM places p LEFT JOIN categories c ON p.category_id = c.id WHERE p.id = ?`).get(placeId); - if (!place) return null; - const tags = db.prepare(`SELECT t.* FROM tags t JOIN place_tags pt ON t.id = pt.tag_id WHERE pt.place_id = ?`).all(placeId); - return { ...place, category: place.category_id ? { id: place.category_id, name: place.category_name, color: place.category_color, icon: place.category_icon } : null, tags }; - }, - canAccessTrip: (tripId: any, userId: number) => - db.prepare(`SELECT t.id, t.user_id FROM trips t LEFT JOIN trip_members m ON m.trip_id = t.id AND m.user_id = ? WHERE t.id = ? AND (t.user_id = ? OR m.user_id IS NOT NULL)`).get(userId, tripId, userId), - isOwner: (tripId: any, userId: number) => - !!db.prepare('SELECT id FROM trips WHERE id = ? AND user_id = ?').get(tripId, userId), - }; - return { testDb: db, dbMock: mock }; +vi.mock('../../src/db/database', async () => { + const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock'); + return buildDbMock(createSnapshotTestDb()); }); - -vi.mock('../../src/db/database', () => dbMock); vi.mock('../../src/config', () => ({ JWT_SECRET: 'test-jwt-secret-for-trek-testing-only', ENCRYPTION_KEY: 'a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2', @@ -48,9 +27,8 @@ vi.mock('../../src/config', () => ({ })); vi.mock('../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn() })); +import { db as testDb } from '../../src/db/database'; import { buildApp } from '../../src/bootstrap'; -import { createTables } from '../../src/db/schema'; -import { runMigrations } from '../../src/db/migrations'; import { resetTestDb, resetRateLimits } from '../helpers/test-db'; import { createUser, createTrip, addTripMember } from '../helpers/factories'; import { authCookie, generateToken } from '../helpers/auth'; @@ -72,8 +50,6 @@ const FIXTURE_PDF = path.join(__dirname, '../fixtures/test.pdf'); const uploadsDir = path.join(__dirname, '../../uploads/files'); beforeAll(async () => { - createTables(testDb); - runMigrations(testDb); nestApp = await buildApp(); app = nestApp.getHttpAdapter().getInstance(); if (!fs.existsSync(uploadsDir)) fs.mkdirSync(uploadsDir, { recursive: true }); diff --git a/server/tests/integration/collections.test.ts b/server/tests/integration/collections.test.ts index e9187b24a..cba75f71d 100644 --- a/server/tests/integration/collections.test.ts +++ b/server/tests/integration/collections.test.ts @@ -13,25 +13,10 @@ import type { INestApplication } from '@nestjs/common'; import path from 'path'; import fs from 'fs'; -const { testDb, dbMock } = vi.hoisted(() => { - const Database = require('better-sqlite3'); - const db = new Database(':memory:'); - db.exec('PRAGMA journal_mode = WAL'); - db.exec('PRAGMA foreign_keys = ON'); - db.exec('PRAGMA busy_timeout = 5000'); - const mock = { - db, - closeDb: () => {}, - reinitialize: () => {}, - canAccessTrip: (tripId: any, userId: number) => - db.prepare(`SELECT t.id, t.user_id FROM trips t LEFT JOIN trip_members m ON m.trip_id = t.id AND m.user_id = ? WHERE t.id = ? AND (t.user_id = ? OR m.user_id IS NOT NULL)`).get(userId, tripId, userId), - isOwner: (tripId: any, userId: number) => - !!db.prepare('SELECT id FROM trips WHERE id = ? AND user_id = ?').get(tripId, userId), - }; - return { testDb: db, dbMock: mock }; +vi.mock('../../src/db/database', async () => { + const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock'); + return buildDbMock(createSnapshotTestDb()); }); - -vi.mock('../../src/db/database', () => dbMock); vi.mock('../../src/config', () => ({ JWT_SECRET: 'test-jwt-secret-for-trek-testing-only', ENCRYPTION_KEY: 'a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2', @@ -43,9 +28,8 @@ vi.mock('../../src/config', () => ({ })); vi.mock('../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn(), getOnlineUserIds: vi.fn(() => []) })); +import { db as testDb } from '../../src/db/database'; import { buildApp } from '../../src/bootstrap'; -import { createTables } from '../../src/db/schema'; -import { runMigrations } from '../../src/db/migrations'; import { resetTestDb, resetRateLimits } from '../helpers/test-db'; import { createUser } from '../helpers/factories'; import { authCookie } from '../helpers/auth'; @@ -65,8 +49,6 @@ function createCollectionPlace(collectionId: number, ownerId: number): number { } beforeAll(async () => { - createTables(testDb); - runMigrations(testDb); nestApp = await buildApp(); app = nestApp.getHttpAdapter().getInstance(); }); diff --git a/server/tests/integration/dayNotes.test.ts b/server/tests/integration/dayNotes.test.ts index c82c0ee1f..5ba6b9363 100644 --- a/server/tests/integration/dayNotes.test.ts +++ b/server/tests/integration/dayNotes.test.ts @@ -7,31 +7,10 @@ import request from 'supertest'; import type { Application } from 'express'; import type { INestApplication } from '@nestjs/common'; -const { testDb, dbMock } = vi.hoisted(() => { - const Database = require('better-sqlite3'); - const db = new Database(':memory:'); - db.exec('PRAGMA journal_mode = WAL'); - db.exec('PRAGMA foreign_keys = ON'); - db.exec('PRAGMA busy_timeout = 5000'); - const mock = { - db, - closeDb: () => {}, - reinitialize: () => {}, - getPlaceWithTags: (placeId: number) => { - const place: any = db.prepare(`SELECT p.*, c.name as category_name, c.color as category_color, c.icon as category_icon FROM places p LEFT JOIN categories c ON p.category_id = c.id WHERE p.id = ?`).get(placeId); - if (!place) return null; - const tags = db.prepare(`SELECT t.* FROM tags t JOIN place_tags pt ON t.id = pt.tag_id WHERE pt.place_id = ?`).all(placeId); - return { ...place, category: place.category_id ? { id: place.category_id, name: place.category_name, color: place.category_color, icon: place.category_icon } : null, tags }; - }, - canAccessTrip: (tripId: any, userId: number) => - db.prepare(`SELECT t.id, t.user_id FROM trips t LEFT JOIN trip_members m ON m.trip_id = t.id AND m.user_id = ? WHERE t.id = ? AND (t.user_id = ? OR m.user_id IS NOT NULL)`).get(userId, tripId, userId), - isOwner: (tripId: any, userId: number) => - !!db.prepare('SELECT id FROM trips WHERE id = ? AND user_id = ?').get(tripId, userId), - }; - return { testDb: db, dbMock: mock }; +vi.mock('../../src/db/database', async () => { + const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock'); + return buildDbMock(createSnapshotTestDb()); }); - -vi.mock('../../src/db/database', () => dbMock); vi.mock('../../src/config', () => ({ JWT_SECRET: 'test-jwt-secret-for-trek-testing-only', ENCRYPTION_KEY: 'a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2', @@ -43,9 +22,8 @@ vi.mock('../../src/config', () => ({ })); vi.mock('../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn() })); +import { db as testDb } from '../../src/db/database'; import { buildApp } from '../../src/bootstrap'; -import { createTables } from '../../src/db/schema'; -import { runMigrations } from '../../src/db/migrations'; import { resetTestDb, resetRateLimits } from '../helpers/test-db'; import { createUser, createTrip, createDay, addTripMember } from '../helpers/factories'; import { authCookie } from '../helpers/auth'; @@ -54,8 +32,6 @@ let nestApp: INestApplication; let app: Application; beforeAll(async () => { - createTables(testDb); - runMigrations(testDb); nestApp = await buildApp(); app = nestApp.getHttpAdapter().getInstance(); }); diff --git a/server/tests/integration/dayReorder.test.ts b/server/tests/integration/dayReorder.test.ts index 3626efa30..b12d011ea 100644 --- a/server/tests/integration/dayReorder.test.ts +++ b/server/tests/integration/dayReorder.test.ts @@ -4,19 +4,12 @@ * to slots while content rides along by id, booking-date re-stamp, permutation * validation, the accommodation-inversion guard, and insert (dated + dateless). */ -import { describe, it, expect, vi, beforeAll, beforeEach, afterAll } from 'vitest'; +import { describe, it, expect, vi, beforeEach, afterAll } from 'vitest'; -const { testDb, dbMock } = vi.hoisted(() => { - // eslint-disable-next-line @typescript-eslint/no-require-imports - const Database = require('better-sqlite3'); - const db = new Database(':memory:'); - db.exec('PRAGMA journal_mode = WAL'); - db.exec('PRAGMA foreign_keys = ON'); - db.exec('PRAGMA busy_timeout = 5000'); - return { testDb: db, dbMock: { db, closeDb: () => {}, reinitialize: () => {}, canAccessTrip: vi.fn() } }; +vi.mock('../../src/db/database', async () => { + const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock'); + return buildDbMock(createSnapshotTestDb()); }); - -vi.mock('../../src/db/database', () => dbMock); vi.mock('../../src/config', () => ({ JWT_SECRET: 'test-jwt-secret-for-trek-testing-only', ENCRYPTION_KEY: 'a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2', @@ -24,8 +17,7 @@ vi.mock('../../src/config', () => ({ DEFAULT_LANGUAGE: 'en', })); -import { createTables } from '../../src/db/schema'; -import { runMigrations } from '../../src/db/migrations'; +import { db as testDb } from '../../src/db/database'; import { resetTestDb } from '../helpers/test-db'; import { createUser, createTrip, createPlace, createDay, createDayAssignment, createReservation, createDayAccommodation } from '../helpers/factories'; import { DatabaseService } from '../../src/nest/database/database.service'; @@ -40,11 +32,6 @@ const insertDay = (tripId: number, position?: number) => svc.insert(tripId, posi let userId: number; -beforeAll(() => { - createTables(testDb); - runMigrations(testDb); -}); - beforeEach(() => { resetTestDb(testDb); userId = createUser(testDb).user.id; diff --git a/server/tests/integration/days.test.ts b/server/tests/integration/days.test.ts index e0bbaf6de..38c3ed512 100644 --- a/server/tests/integration/days.test.ts +++ b/server/tests/integration/days.test.ts @@ -10,31 +10,10 @@ import type { INestApplication } from '@nestjs/common'; // ───────────────────────────────────────────────────────────────────────────── // In-memory DB — schema applied in beforeAll after mocks register // ───────────────────────────────────────────────────────────────────────────── -const { testDb, dbMock } = vi.hoisted(() => { - const Database = require('better-sqlite3'); - const db = new Database(':memory:'); - db.exec('PRAGMA journal_mode = WAL'); - db.exec('PRAGMA foreign_keys = ON'); - db.exec('PRAGMA busy_timeout = 5000'); - const mock = { - db, - closeDb: () => {}, - reinitialize: () => {}, - getPlaceWithTags: (placeId: number) => { - const place: any = db.prepare(`SELECT p.*, c.name as category_name, c.color as category_color, c.icon as category_icon FROM places p LEFT JOIN categories c ON p.category_id = c.id WHERE p.id = ?`).get(placeId); - if (!place) return null; - const tags = db.prepare(`SELECT t.* FROM tags t JOIN place_tags pt ON t.id = pt.tag_id WHERE pt.place_id = ?`).all(placeId); - return { ...place, category: place.category_id ? { id: place.category_id, name: place.category_name, color: place.category_color, icon: place.category_icon } : null, tags }; - }, - canAccessTrip: (tripId: any, userId: number) => - db.prepare(`SELECT t.id, t.user_id FROM trips t LEFT JOIN trip_members m ON m.trip_id = t.id AND m.user_id = ? WHERE t.id = ? AND (t.user_id = ? OR m.user_id IS NOT NULL)`).get(userId, tripId, userId), - isOwner: (tripId: any, userId: number) => - !!db.prepare('SELECT id FROM trips WHERE id = ? AND user_id = ?').get(tripId, userId), - }; - return { testDb: db, dbMock: mock }; +vi.mock('../../src/db/database', async () => { + const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock'); + return buildDbMock(createSnapshotTestDb()); }); - -vi.mock('../../src/db/database', () => dbMock); vi.mock('../../src/config', () => ({ JWT_SECRET: 'test-jwt-secret-for-trek-testing-only', ENCRYPTION_KEY: 'a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2', @@ -46,9 +25,8 @@ vi.mock('../../src/config', () => ({ })); vi.mock('../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn() })); +import { db as testDb } from '../../src/db/database'; import { buildApp } from '../../src/bootstrap'; -import { createTables } from '../../src/db/schema'; -import { runMigrations } from '../../src/db/migrations'; import { resetTestDb, resetRateLimits } from '../helpers/test-db'; import { createUser, createTrip, createDay, createPlace, addTripMember } from '../helpers/factories'; import { authCookie } from '../helpers/auth'; @@ -56,8 +34,6 @@ import { authCookie } from '../helpers/auth'; let nestApp: INestApplication; let app: Application; beforeAll(async () => { - createTables(testDb); - runMigrations(testDb); nestApp = await buildApp(); app = nestApp.getHttpAdapter().getInstance(); }); diff --git a/server/tests/integration/files.test.ts b/server/tests/integration/files.test.ts index fda7f26bd..135956d01 100644 --- a/server/tests/integration/files.test.ts +++ b/server/tests/integration/files.test.ts @@ -14,31 +14,10 @@ import type { INestApplication } from '@nestjs/common'; import path from 'path'; import fs from 'fs'; -const { testDb, dbMock } = vi.hoisted(() => { - const Database = require('better-sqlite3'); - const db = new Database(':memory:'); - db.exec('PRAGMA journal_mode = WAL'); - db.exec('PRAGMA foreign_keys = ON'); - db.exec('PRAGMA busy_timeout = 5000'); - const mock = { - db, - closeDb: () => {}, - reinitialize: () => {}, - getPlaceWithTags: (placeId: number) => { - const place: any = db.prepare(`SELECT p.*, c.name as category_name, c.color as category_color, c.icon as category_icon FROM places p LEFT JOIN categories c ON p.category_id = c.id WHERE p.id = ?`).get(placeId); - if (!place) return null; - const tags = db.prepare(`SELECT t.* FROM tags t JOIN place_tags pt ON t.id = pt.tag_id WHERE pt.place_id = ?`).all(placeId); - return { ...place, category: place.category_id ? { id: place.category_id, name: place.category_name, color: place.category_color, icon: place.category_icon } : null, tags }; - }, - canAccessTrip: (tripId: any, userId: number) => - db.prepare(`SELECT t.id, t.user_id FROM trips t LEFT JOIN trip_members m ON m.trip_id = t.id AND m.user_id = ? WHERE t.id = ? AND (t.user_id = ? OR m.user_id IS NOT NULL)`).get(userId, tripId, userId), - isOwner: (tripId: any, userId: number) => - !!db.prepare('SELECT id FROM trips WHERE id = ? AND user_id = ?').get(tripId, userId), - }; - return { testDb: db, dbMock: mock }; +vi.mock('../../src/db/database', async () => { + const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock'); + return buildDbMock(createSnapshotTestDb()); }); - -vi.mock('../../src/db/database', () => dbMock); vi.mock('../../src/config', () => ({ JWT_SECRET: 'test-jwt-secret-for-trek-testing-only', ENCRYPTION_KEY: 'a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2', @@ -50,9 +29,8 @@ vi.mock('../../src/config', () => ({ })); vi.mock('../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn() })); +import { db as testDb } from '../../src/db/database'; import { buildApp } from '../../src/bootstrap'; -import { createTables } from '../../src/db/schema'; -import { runMigrations } from '../../src/db/migrations'; import { resetTestDb, resetRateLimits } from '../helpers/test-db'; import { createUser, createTrip, createReservation, createPlace, addTripMember } from '../helpers/factories'; import { authCookie, generateToken } from '../helpers/auth'; @@ -66,8 +44,6 @@ const FIXTURE_IMG = path.join(__dirname, '../fixtures/small-image.jpg'); const uploadsDir = path.join(__dirname, '../../uploads/files'); beforeAll(async () => { - createTables(testDb); - runMigrations(testDb); nestApp = await buildApp(); app = nestApp.getHttpAdapter().getInstance(); if (!fs.existsSync(uploadsDir)) fs.mkdirSync(uploadsDir, { recursive: true }); diff --git a/server/tests/integration/help.test.ts b/server/tests/integration/help.test.ts index a25e15e02..675b54a77 100644 --- a/server/tests/integration/help.test.ts +++ b/server/tests/integration/help.test.ts @@ -10,14 +10,10 @@ import request from 'supertest'; import type { Application } from 'express'; import type { INestApplication } from '@nestjs/common'; -const { testDb, dbMock } = vi.hoisted(() => { - const Database = require('better-sqlite3'); - const db = new Database(':memory:'); - db.exec('PRAGMA foreign_keys = ON'); - return { testDb: db, dbMock: { db, closeDb: () => {}, reinitialize: () => {} } }; +vi.mock('../../src/db/database', async () => { + const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock'); + return buildDbMock(createSnapshotTestDb()); }); - -vi.mock('../../src/db/database', () => dbMock); vi.mock('../../src/config', () => ({ JWT_SECRET: 'test-jwt-secret-for-trek-testing-only', ENCRYPTION_KEY: 'a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2', @@ -29,9 +25,8 @@ vi.mock('../../src/config', () => ({ })); vi.mock('../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn() })); +import { db as testDb } from '../../src/db/database'; import { buildApp } from '../../src/bootstrap'; -import { createTables } from '../../src/db/schema'; -import { runMigrations } from '../../src/db/migrations'; let nestApp: INestApplication; let app: Application; @@ -40,8 +35,6 @@ const fetchSpy = vi.fn(async () => new Response('{}', { status: 200 })); beforeAll(async () => { // Any outbound fetch here is a bug: help must be served from disk. vi.stubGlobal('fetch', fetchSpy); - createTables(testDb); - runMigrations(testDb); nestApp = await buildApp(); app = nestApp.getHttpAdapter().getInstance(); }); diff --git a/server/tests/integration/immich.test.ts b/server/tests/integration/immich.test.ts index 4fd674bbb..fb3e4fd06 100644 --- a/server/tests/integration/immich.test.ts +++ b/server/tests/integration/immich.test.ts @@ -10,31 +10,10 @@ import request from 'supertest'; import type { Application } from 'express'; import type { INestApplication } from '@nestjs/common'; -const { testDb, dbMock } = vi.hoisted(() => { - const Database = require('better-sqlite3'); - const db = new Database(':memory:'); - db.exec('PRAGMA journal_mode = WAL'); - db.exec('PRAGMA foreign_keys = ON'); - db.exec('PRAGMA busy_timeout = 5000'); - const mock = { - db, - closeDb: () => {}, - reinitialize: () => {}, - getPlaceWithTags: (placeId: number) => { - const place: any = db.prepare(`SELECT p.*, c.name as category_name, c.color as category_color, c.icon as category_icon FROM places p LEFT JOIN categories c ON p.category_id = c.id WHERE p.id = ?`).get(placeId); - if (!place) return null; - const tags = db.prepare(`SELECT t.* FROM tags t JOIN place_tags pt ON t.id = pt.tag_id WHERE pt.place_id = ?`).all(placeId); - return { ...place, category: place.category_id ? { id: place.category_id, name: place.category_name, color: place.category_color, icon: place.category_icon } : null, tags }; - }, - canAccessTrip: (tripId: any, userId: number) => - db.prepare(`SELECT t.id, t.user_id FROM trips t LEFT JOIN trip_members m ON m.trip_id = t.id AND m.user_id = ? WHERE t.id = ? AND (t.user_id = ? OR m.user_id IS NOT NULL)`).get(userId, tripId, userId), - isOwner: (tripId: any, userId: number) => - !!db.prepare('SELECT id FROM trips WHERE id = ? AND user_id = ?').get(tripId, userId), - }; - return { testDb: db, dbMock: mock }; +vi.mock('../../src/db/database', async () => { + const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock'); + return buildDbMock(createSnapshotTestDb()); }); - -vi.mock('../../src/db/database', () => dbMock); vi.mock('../../src/config', () => ({ JWT_SECRET: 'test-jwt-secret-for-trek-testing-only', ENCRYPTION_KEY: 'a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2', @@ -70,9 +49,8 @@ vi.mock('../../src/utils/ssrfGuard', async () => { }; }); +import { db as testDb } from '../../src/db/database'; import { buildApp } from '../../src/bootstrap'; -import { createTables } from '../../src/db/schema'; -import { runMigrations } from '../../src/db/migrations'; import { resetTestDb, resetRateLimits, setAddonEnabled } from '../helpers/test-db'; import { createUser } from '../helpers/factories'; import { authCookie } from '../helpers/auth'; @@ -81,8 +59,6 @@ let nestApp: INestApplication; let app: Application; beforeAll(async () => { - createTables(testDb); - runMigrations(testDb); nestApp = await buildApp(); app = nestApp.getHttpAdapter().getInstance(); }); diff --git a/server/tests/integration/journey.test.ts b/server/tests/integration/journey.test.ts index 57790c6a9..80dc6219d 100644 --- a/server/tests/integration/journey.test.ts +++ b/server/tests/integration/journey.test.ts @@ -10,36 +10,10 @@ import type { INestApplication } from '@nestjs/common'; // ───────────────────────────────────────────────────────────────────────────── // Step 1: Bare in-memory DB — schema applied in beforeAll after mocks register // ───────────────────────────────────────────────────────────────────────────── -const { testDb, dbMock } = vi.hoisted(() => { - const Database = require('better-sqlite3'); - const db = new Database(':memory:'); - db.exec('PRAGMA journal_mode = WAL'); - db.exec('PRAGMA foreign_keys = ON'); - db.exec('PRAGMA busy_timeout = 5000'); - - const mock = { - db, - closeDb: () => {}, - reinitialize: () => {}, - getPlaceWithTags: (placeId: number) => { - const place: any = db.prepare(` - SELECT p.*, c.name as category_name, c.color as category_color, c.icon as category_icon - FROM places p LEFT JOIN categories c ON p.category_id = c.id WHERE p.id = ? - `).get(placeId); - if (!place) return null; - const tags = db.prepare(`SELECT t.* FROM tags t JOIN place_tags pt ON t.id = pt.tag_id WHERE pt.place_id = ?`).all(placeId); - return { ...place, category: place.category_id ? { id: place.category_id, name: place.category_name, color: place.category_color, icon: place.category_icon } : null, tags }; - }, - canAccessTrip: (tripId: any, userId: number) => - db.prepare(`SELECT t.id, t.user_id FROM trips t LEFT JOIN trip_members m ON m.trip_id = t.id AND m.user_id = ? WHERE t.id = ? AND (t.user_id = ? OR m.user_id IS NOT NULL)`).get(userId, tripId, userId), - isOwner: (tripId: any, userId: number) => - !!db.prepare('SELECT id FROM trips WHERE id = ? AND user_id = ?').get(tripId, userId), - }; - - return { testDb: db, dbMock: mock }; +vi.mock('../../src/db/database', async () => { + const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock'); + return buildDbMock(createSnapshotTestDb()); }); - -vi.mock('../../src/db/database', () => dbMock); vi.mock('../../src/config', () => ({ JWT_SECRET: 'test-jwt-secret-for-trek-testing-only', ENCRYPTION_KEY: 'a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2', @@ -59,9 +33,8 @@ vi.mock('../../src/services/memories/immichService', () => ({ getImmichCredentials: vi.fn(() => null), })); +import { db as testDb } from '../../src/db/database'; import { buildApp } from '../../src/bootstrap'; -import { createTables } from '../../src/db/schema'; -import { runMigrations } from '../../src/db/migrations'; import { resetTestDb, resetRateLimits } from '../helpers/test-db'; import { createUser, @@ -78,8 +51,6 @@ let nestApp: INestApplication; let app: Application; beforeAll(async () => { - createTables(testDb); - runMigrations(testDb); nestApp = await buildApp(); app = nestApp.getHttpAdapter().getInstance(); }); diff --git a/server/tests/integration/leg-mode-incoming.test.ts b/server/tests/integration/leg-mode-incoming.test.ts index 8f864fcb9..085bdd9a6 100644 --- a/server/tests/integration/leg-mode-incoming.test.ts +++ b/server/tests/integration/leg-mode-incoming.test.ts @@ -71,31 +71,10 @@ describe('incoming_leg_transport_mode migration', () => { // tests/integration/assignments.test.ts verbatim. // ───────────────────────────────────────────────────────────────────────────── -const { testDb2, dbMock } = vi.hoisted(() => { - const Database2 = require('better-sqlite3'); - const db = new Database2(':memory:'); - db.exec('PRAGMA journal_mode = WAL'); - db.exec('PRAGMA foreign_keys = ON'); - db.exec('PRAGMA busy_timeout = 5000'); - const mock = { - db, - closeDb: () => {}, - reinitialize: () => {}, - getPlaceWithTags: (placeId: number) => { - const place: any = db.prepare(`SELECT p.*, c.name as category_name, c.color as category_color, c.icon as category_icon FROM places p LEFT JOIN categories c ON p.category_id = c.id WHERE p.id = ?`).get(placeId); - if (!place) return null; - const tags = db.prepare(`SELECT t.* FROM tags t JOIN place_tags pt ON t.id = pt.tag_id WHERE pt.place_id = ?`).all(placeId); - return { ...place, category: place.category_id ? { id: place.category_id, name: place.category_name, color: place.category_color, icon: place.category_icon } : null, tags }; - }, - canAccessTrip: (tripId: any, userId: number) => - db.prepare(`SELECT t.id, t.user_id FROM trips t LEFT JOIN trip_members m ON m.trip_id = t.id AND m.user_id = ? WHERE t.id = ? AND (t.user_id = ? OR m.user_id IS NOT NULL)`).get(userId, tripId, userId), - isOwner: (tripId: any, userId: number) => - !!db.prepare('SELECT id FROM trips WHERE id = ? AND user_id = ?').get(tripId, userId), - }; - return { testDb2: db, dbMock: mock }; +vi.mock('../../src/db/database', async () => { + const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock'); + return buildDbMock(createSnapshotTestDb()); }); - -vi.mock('../../src/db/database', () => dbMock); vi.mock('../../src/config', () => ({ JWT_SECRET: 'test-jwt-secret-for-trek-testing-only', ENCRYPTION_KEY: 'a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2', @@ -107,6 +86,7 @@ vi.mock('../../src/config', () => ({ })); vi.mock('../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn() })); +import { db as testDb2 } from '../../src/db/database'; import { buildApp } from '../../src/bootstrap'; import { resetTestDb, resetRateLimits } from '../helpers/test-db'; import { createUser, createTrip, createDay, createPlace } from '../helpers/factories'; @@ -117,8 +97,6 @@ describe('incoming_leg_transport_mode read-path parity', () => { let app: Application; beforeAll(async () => { - createTables(testDb2); - runMigrations(testDb2); nestApp = await buildApp(); app = nestApp.getHttpAdapter().getInstance(); }); @@ -180,7 +158,7 @@ describe('incoming_leg_transport_mode read-path parity', () => { .send({ transport_mode: 'cycling' }); expect(res.status).toBe(200); - const row = testDb2.prepare('SELECT leg_transport_mode, incoming_leg_transport_mode FROM day_assignments WHERE id = ?').get(assignmentId); + const row = testDb2.prepare('SELECT leg_transport_mode, incoming_leg_transport_mode FROM day_assignments WHERE id = ?').get(assignmentId) as { leg_transport_mode: string | null; incoming_leg_transport_mode: string | null }; expect(row.leg_transport_mode).toBe('cycling'); expect(row.incoming_leg_transport_mode).toBeNull(); }); @@ -203,7 +181,7 @@ describe('incoming_leg_transport_mode read-path parity', () => { .send({ transport_mode: 'transit', direction: 'incoming' }); expect(res.status).toBe(200); - const row = testDb2.prepare('SELECT incoming_leg_transport_mode FROM day_assignments WHERE id = ?').get(assignmentId); + const row = testDb2.prepare('SELECT incoming_leg_transport_mode FROM day_assignments WHERE id = ?').get(assignmentId) as { incoming_leg_transport_mode: string | null }; expect(row.incoming_leg_transport_mode).toBe('transit'); }); @@ -250,7 +228,7 @@ describe('incoming_leg_transport_mode read-path parity', () => { .send({ transport_mode: 'transit', direction: 'incoming' }) .expect(200); - const row = testDb2.prepare('SELECT leg_transport_mode, incoming_leg_transport_mode FROM day_assignments WHERE id = ?').get(assignmentId); + const row = testDb2.prepare('SELECT leg_transport_mode, incoming_leg_transport_mode FROM day_assignments WHERE id = ?').get(assignmentId) as { leg_transport_mode: string | null; incoming_leg_transport_mode: string | null }; expect(row.leg_transport_mode).toBe('cycling'); expect(row.incoming_leg_transport_mode).toBe('transit'); diff --git a/server/tests/integration/maps.test.ts b/server/tests/integration/maps.test.ts index e5597b5de..cce7795dc 100644 --- a/server/tests/integration/maps.test.ts +++ b/server/tests/integration/maps.test.ts @@ -10,31 +10,10 @@ import request from 'supertest'; import type { Application } from 'express'; import type { INestApplication } from '@nestjs/common'; -const { testDb, dbMock } = vi.hoisted(() => { - const Database = require('better-sqlite3'); - const db = new Database(':memory:'); - db.exec('PRAGMA journal_mode = WAL'); - db.exec('PRAGMA foreign_keys = ON'); - db.exec('PRAGMA busy_timeout = 5000'); - const mock = { - db, - closeDb: () => {}, - reinitialize: () => {}, - getPlaceWithTags: (placeId: number) => { - const place: any = db.prepare(`SELECT p.*, c.name as category_name, c.color as category_color, c.icon as category_icon FROM places p LEFT JOIN categories c ON p.category_id = c.id WHERE p.id = ?`).get(placeId); - if (!place) return null; - const tags = db.prepare(`SELECT t.* FROM tags t JOIN place_tags pt ON t.id = pt.tag_id WHERE pt.place_id = ?`).all(placeId); - return { ...place, category: place.category_id ? { id: place.category_id, name: place.category_name, color: place.category_color, icon: place.category_icon } : null, tags }; - }, - canAccessTrip: (tripId: any, userId: number) => - db.prepare(`SELECT t.id, t.user_id FROM trips t LEFT JOIN trip_members m ON m.trip_id = t.id AND m.user_id = ? WHERE t.id = ? AND (t.user_id = ? OR m.user_id IS NOT NULL)`).get(userId, tripId, userId), - isOwner: (tripId: any, userId: number) => - !!db.prepare('SELECT id FROM trips WHERE id = ? AND user_id = ?').get(tripId, userId), - }; - return { testDb: db, dbMock: mock }; +vi.mock('../../src/db/database', async () => { + const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock'); + return buildDbMock(createSnapshotTestDb()); }); - -vi.mock('../../src/db/database', () => dbMock); vi.mock('../../src/config', () => ({ JWT_SECRET: 'test-jwt-secret-for-trek-testing-only', ENCRYPTION_KEY: 'a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2', @@ -46,9 +25,8 @@ vi.mock('../../src/config', () => ({ })); vi.mock('../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn() })); +import { db as testDb } from '../../src/db/database'; import { buildApp } from '../../src/bootstrap'; -import { createTables } from '../../src/db/schema'; -import { runMigrations } from '../../src/db/migrations'; import { resetTestDb, resetRateLimits } from '../helpers/test-db'; import { createUser } from '../helpers/factories'; import { authCookie } from '../helpers/auth'; @@ -71,8 +49,6 @@ let mapsService: { }; beforeAll(async () => { - createTables(testDb); - runMigrations(testDb); nestApp = await buildApp(); app = nestApp.getHttpAdapter().getInstance(); const maps = nestApp.get(MapsService); diff --git a/server/tests/integration/mcp.test.ts b/server/tests/integration/mcp.test.ts index 82cc92d66..e9de21c4a 100644 --- a/server/tests/integration/mcp.test.ts +++ b/server/tests/integration/mcp.test.ts @@ -10,31 +10,10 @@ import request from 'supertest'; import type { Application } from 'express'; import type { INestApplication } from '@nestjs/common'; -const { testDb, dbMock } = vi.hoisted(() => { - const Database = require('better-sqlite3'); - const db = new Database(':memory:'); - db.exec('PRAGMA journal_mode = WAL'); - db.exec('PRAGMA foreign_keys = ON'); - db.exec('PRAGMA busy_timeout = 5000'); - const mock = { - db, - closeDb: () => {}, - reinitialize: () => {}, - getPlaceWithTags: (placeId: number) => { - const place: any = db.prepare(`SELECT p.*, c.name as category_name, c.color as category_color, c.icon as category_icon FROM places p LEFT JOIN categories c ON p.category_id = c.id WHERE p.id = ?`).get(placeId); - if (!place) return null; - const tags = db.prepare(`SELECT t.* FROM tags t JOIN place_tags pt ON t.id = pt.tag_id WHERE pt.place_id = ?`).all(placeId); - return { ...place, category: place.category_id ? { id: place.category_id, name: place.category_name, color: place.category_color, icon: place.category_icon } : null, tags }; - }, - canAccessTrip: (tripId: any, userId: number) => - db.prepare(`SELECT t.id, t.user_id FROM trips t LEFT JOIN trip_members m ON m.trip_id = t.id AND m.user_id = ? WHERE t.id = ? AND (t.user_id = ? OR m.user_id IS NOT NULL)`).get(userId, tripId, userId), - isOwner: (tripId: any, userId: number) => - !!db.prepare('SELECT id FROM trips WHERE id = ? AND user_id = ?').get(tripId, userId), - }; - return { testDb: db, dbMock: mock }; +vi.mock('../../src/db/database', async () => { + const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock'); + return buildDbMock(createSnapshotTestDb()); }); - -vi.mock('../../src/db/database', () => dbMock); vi.mock('../../src/config', () => ({ JWT_SECRET: 'test-jwt-secret-for-trek-testing-only', ENCRYPTION_KEY: 'a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2', @@ -46,9 +25,8 @@ vi.mock('../../src/config', () => ({ })); vi.mock('../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn() })); +import { db as testDb } from '../../src/db/database'; import { buildApp } from '../../src/bootstrap'; -import { createTables } from '../../src/db/schema'; -import { runMigrations } from '../../src/db/migrations'; import { resetTestDb, resetRateLimits } from '../helpers/test-db'; import { createUser } from '../helpers/factories'; import { generateToken } from '../helpers/auth'; @@ -89,8 +67,6 @@ let nestApp: INestApplication; let app: Application; beforeAll(async () => { - createTables(testDb); - runMigrations(testDb); nestApp = await buildApp(); app = nestApp.getHttpAdapter().getInstance(); }); diff --git a/server/tests/integration/memories-immich.test.ts b/server/tests/integration/memories-immich.test.ts index f6b8fc4eb..61609b9a8 100644 --- a/server/tests/integration/memories-immich.test.ts +++ b/server/tests/integration/memories-immich.test.ts @@ -11,43 +11,18 @@ import request from 'supertest'; import type { Application } from 'express'; import type { INestApplication } from '@nestjs/common'; -// ── Hoisted DB mock ────────────────────────────────────────────────────────── +// ── Non-database hoisted state (kept separate from the db mock below) ──────── -const { testDb, dbMock, immichState } = vi.hoisted(() => { - const Database = require('better-sqlite3'); - const db = new Database(':memory:'); - db.exec('PRAGMA journal_mode = WAL'); - db.exec('PRAGMA foreign_keys = ON'); - db.exec('PRAGMA busy_timeout = 5000'); - - // Mutable fixture driving the fake Immich responses. Tests may override - // `albumAssetPages` to exercise pagination, and read `searchCalls` to assert - // on the request bodies TREK sends to POST /api/search/metadata. - const state: { - albumAssets: any[]; - albumAssetPages: any[][] | null; - searchCalls: any[]; - /** Immich v2 embeds `assets` in the album detail body; v3 does not (#1492). */ - albumDetailHasAssets: boolean; - } = { - albumAssets: [], - albumAssetPages: null, - searchCalls: [], - albumDetailHasAssets: false, - }; - - const mock = { - db, - closeDb: () => {}, - reinitialize: () => {}, - getPlaceWithTags: () => null, - canAccessTrip: (tripId: any, userId: number) => - db.prepare(`SELECT t.id, t.user_id FROM trips t LEFT JOIN trip_members m ON m.trip_id = t.id AND m.user_id = ? WHERE t.id = ? AND (t.user_id = ? OR m.user_id IS NOT NULL)`).get(userId, tripId, userId), - isOwner: (tripId: any, userId: number) => - !!db.prepare('SELECT id FROM trips WHERE id = ? AND user_id = ?').get(tripId, userId), - }; - return { testDb: db, dbMock: mock, immichState: state }; -}); +// Mutable fixture driving the fake Immich responses. Tests may override +// `albumAssetPages` to exercise pagination, and read `searchCalls` to assert +// on the request bodies TREK sends to POST /api/search/metadata. +const immichState = vi.hoisted(() => ({ + albumAssets: [] as any[], + albumAssetPages: null as any[][] | null, + searchCalls: [] as any[], + /** Immich v2 embeds `assets` in the album detail body; v3 does not (#1492). */ + albumDetailHasAssets: false, +})); /** * Default contents of `album-uuid-1` as Immich returns them from @@ -62,7 +37,10 @@ const DEFAULT_ALBUM_ASSETS = [ { id: 'asset-legacy-hidden', type: 'VIDEO', fileCreatedAt: '2024-06-04T10:00:00.000Z', isVisible: false }, ]; -vi.mock('../../src/db/database', () => dbMock); +vi.mock('../../src/db/database', async () => { + const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock'); + return buildDbMock(createSnapshotTestDb()); +}); vi.mock('../../src/config', () => ({ JWT_SECRET: 'test-jwt-secret-for-trek-testing-only', ENCRYPTION_KEY: 'a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2', @@ -219,9 +197,8 @@ vi.mock('../../src/utils/ssrfGuard', async () => { }; }); +import { db as testDb } from '../../src/db/database'; import { buildApp } from '../../src/bootstrap'; -import { createTables } from '../../src/db/schema'; -import { runMigrations } from '../../src/db/migrations'; import { resetTestDb, resetRateLimits, setAddonEnabled } from '../helpers/test-db'; import { createUser, createTrip, addTripMember, addTripPhoto, addAlbumLink, setImmichCredentials } from '../helpers/factories'; import { authCookie } from '../helpers/auth'; @@ -233,8 +210,6 @@ let app: Application; const IMMICH = '/api/integrations/memories/immich'; beforeAll(async () => { - createTables(testDb); - runMigrations(testDb); nestApp = await buildApp(); app = nestApp.getHttpAdapter().getInstance(); }); diff --git a/server/tests/integration/memories-synology.test.ts b/server/tests/integration/memories-synology.test.ts index d3cd3c038..a4c637019 100644 --- a/server/tests/integration/memories-synology.test.ts +++ b/server/tests/integration/memories-synology.test.ts @@ -15,26 +15,10 @@ import type { INestApplication } from '@nestjs/common'; // ── Hoisted DB mock ────────────────────────────────────────────────────────── -const { testDb, dbMock } = vi.hoisted(() => { - const Database = require('better-sqlite3'); - const db = new Database(':memory:'); - db.exec('PRAGMA journal_mode = WAL'); - db.exec('PRAGMA foreign_keys = ON'); - db.exec('PRAGMA busy_timeout = 5000'); - const mock = { - db, - closeDb: () => {}, - reinitialize: () => {}, - getPlaceWithTags: () => null, - canAccessTrip: (tripId: any, userId: number) => - db.prepare(`SELECT t.id, t.user_id FROM trips t LEFT JOIN trip_members m ON m.trip_id = t.id AND m.user_id = ? WHERE t.id = ? AND (t.user_id = ? OR m.user_id IS NOT NULL)`).get(userId, tripId, userId), - isOwner: (tripId: any, userId: number) => - !!db.prepare('SELECT id FROM trips WHERE id = ? AND user_id = ?').get(tripId, userId), - }; - return { testDb: db, dbMock: mock }; +vi.mock('../../src/db/database', async () => { + const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock'); + return buildDbMock(createSnapshotTestDb()); }); - -vi.mock('../../src/db/database', () => dbMock); vi.mock('../../src/config', () => ({ JWT_SECRET: 'test-jwt-secret-for-trek-testing-only', ENCRYPTION_KEY: 'a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2', @@ -196,9 +180,8 @@ vi.mock('../../src/utils/ssrfGuard', async () => { }; }); +import { db as testDb } from '../../src/db/database'; import { buildApp } from '../../src/bootstrap'; -import { createTables } from '../../src/db/schema'; -import { runMigrations } from '../../src/db/migrations'; import { resetTestDb, resetRateLimits, setAddonEnabled } from '../helpers/test-db'; import { createUser, createTrip, addTripMember, addTripPhoto, setSynologyCredentials } from '../helpers/factories'; import { authCookie } from '../helpers/auth'; @@ -210,8 +193,6 @@ let app: Application; const SYNO = '/api/integrations/memories/synologyphotos'; beforeAll(async () => { - createTables(testDb); - runMigrations(testDb); nestApp = await buildApp(); app = nestApp.getHttpAdapter().getInstance(); }); diff --git a/server/tests/integration/memories-unified.test.ts b/server/tests/integration/memories-unified.test.ts index f954011d0..00af86911 100644 --- a/server/tests/integration/memories-unified.test.ts +++ b/server/tests/integration/memories-unified.test.ts @@ -13,26 +13,10 @@ import type { INestApplication } from '@nestjs/common'; // ── Hoisted DB mock ────────────────────────────────────────────────────────── -const { testDb, dbMock } = vi.hoisted(() => { - const Database = require('better-sqlite3'); - const db = new Database(':memory:'); - db.exec('PRAGMA journal_mode = WAL'); - db.exec('PRAGMA foreign_keys = ON'); - db.exec('PRAGMA busy_timeout = 5000'); - const mock = { - db, - closeDb: () => {}, - reinitialize: () => {}, - getPlaceWithTags: () => null, - canAccessTrip: (tripId: any, userId: number) => - db.prepare(`SELECT t.id, t.user_id FROM trips t LEFT JOIN trip_members m ON m.trip_id = t.id AND m.user_id = ? WHERE t.id = ? AND (t.user_id = ? OR m.user_id IS NOT NULL)`).get(userId, tripId, userId), - isOwner: (tripId: any, userId: number) => - !!db.prepare('SELECT id FROM trips WHERE id = ? AND user_id = ?').get(tripId, userId), - }; - return { testDb: db, dbMock: mock }; +vi.mock('../../src/db/database', async () => { + const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock'); + return buildDbMock(createSnapshotTestDb()); }); - -vi.mock('../../src/db/database', () => dbMock); vi.mock('../../src/config', () => ({ JWT_SECRET: 'test-jwt-secret-for-trek-testing-only', ENCRYPTION_KEY: 'a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2', @@ -52,9 +36,8 @@ vi.mock('../../src/utils/ssrfGuard', async () => { }; }); +import { db as testDb } from '../../src/db/database'; import { buildApp } from '../../src/bootstrap'; -import { createTables } from '../../src/db/schema'; -import { runMigrations } from '../../src/db/migrations'; import { resetTestDb, resetRateLimits, setAddonEnabled } from '../helpers/test-db'; import { createUser, createTrip, addTripMember, addTripPhoto, addAlbumLink } from '../helpers/factories'; import { authCookie } from '../helpers/auth'; @@ -65,8 +48,6 @@ let app: Application; const BASE = '/api/integrations/memories/unified'; beforeAll(async () => { - createTables(testDb); - runMigrations(testDb); nestApp = await buildApp(); app = nestApp.getHttpAdapter().getInstance(); }); diff --git a/server/tests/integration/misc.test.ts b/server/tests/integration/misc.test.ts index 308d0cf4a..c2f1fae3a 100644 --- a/server/tests/integration/misc.test.ts +++ b/server/tests/integration/misc.test.ts @@ -7,31 +7,10 @@ import request from 'supertest'; import type { Application } from 'express'; import type { INestApplication } from '@nestjs/common'; -const { testDb, dbMock } = vi.hoisted(() => { - const Database = require('better-sqlite3'); - const db = new Database(':memory:'); - db.exec('PRAGMA journal_mode = WAL'); - db.exec('PRAGMA foreign_keys = ON'); - db.exec('PRAGMA busy_timeout = 5000'); - const mock = { - db, - closeDb: () => {}, - reinitialize: () => {}, - getPlaceWithTags: (placeId: number) => { - const place: any = db.prepare(`SELECT p.*, c.name as category_name, c.color as category_color, c.icon as category_icon FROM places p LEFT JOIN categories c ON p.category_id = c.id WHERE p.id = ?`).get(placeId); - if (!place) return null; - const tags = db.prepare(`SELECT t.* FROM tags t JOIN place_tags pt ON t.id = pt.tag_id WHERE pt.place_id = ?`).all(placeId); - return { ...place, category: place.category_id ? { id: place.category_id, name: place.category_name, color: place.category_color, icon: place.category_icon } : null, tags }; - }, - canAccessTrip: (tripId: any, userId: number) => - db.prepare(`SELECT t.id, t.user_id FROM trips t LEFT JOIN trip_members m ON m.trip_id = t.id AND m.user_id = ? WHERE t.id = ? AND (t.user_id = ? OR m.user_id IS NOT NULL)`).get(userId, tripId, userId), - isOwner: (tripId: any, userId: number) => - !!db.prepare('SELECT id FROM trips WHERE id = ? AND user_id = ?').get(tripId, userId), - }; - return { testDb: db, dbMock: mock }; +vi.mock('../../src/db/database', async () => { + const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock'); + return buildDbMock(createSnapshotTestDb()); }); - -vi.mock('../../src/db/database', () => dbMock); vi.mock('../../src/config', () => ({ JWT_SECRET: 'test-jwt-secret-for-trek-testing-only', ENCRYPTION_KEY: 'a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2', @@ -43,9 +22,8 @@ vi.mock('../../src/config', () => ({ })); vi.mock('../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn() })); +import { db as testDb } from '../../src/db/database'; import { buildApp } from '../../src/bootstrap'; -import { createTables } from '../../src/db/schema'; -import { runMigrations } from '../../src/db/migrations'; import { resetTestDb, resetRateLimits } from '../helpers/test-db'; import { createUser } from '../helpers/factories'; import { authCookie } from '../helpers/auth'; @@ -54,8 +32,6 @@ let nestApp: INestApplication; let app: Application; beforeAll(async () => { - createTables(testDb); - runMigrations(testDb); nestApp = await buildApp(); app = nestApp.getHttpAdapter().getInstance(); }); diff --git a/server/tests/integration/notifications.test.ts b/server/tests/integration/notifications.test.ts index 5006b9294..07e68f870 100644 --- a/server/tests/integration/notifications.test.ts +++ b/server/tests/integration/notifications.test.ts @@ -10,31 +10,10 @@ import request from 'supertest'; import type { Application } from 'express'; import type { INestApplication } from '@nestjs/common'; -const { testDb, dbMock } = vi.hoisted(() => { - const Database = require('better-sqlite3'); - const db = new Database(':memory:'); - db.exec('PRAGMA journal_mode = WAL'); - db.exec('PRAGMA foreign_keys = ON'); - db.exec('PRAGMA busy_timeout = 5000'); - const mock = { - db, - closeDb: () => {}, - reinitialize: () => {}, - getPlaceWithTags: (placeId: number) => { - const place: any = db.prepare(`SELECT p.*, c.name as category_name, c.color as category_color, c.icon as category_icon FROM places p LEFT JOIN categories c ON p.category_id = c.id WHERE p.id = ?`).get(placeId); - if (!place) return null; - const tags = db.prepare(`SELECT t.* FROM tags t JOIN place_tags pt ON t.id = pt.tag_id WHERE pt.place_id = ?`).all(placeId); - return { ...place, category: place.category_id ? { id: place.category_id, name: place.category_name, color: place.category_color, icon: place.category_icon } : null, tags }; - }, - canAccessTrip: (tripId: any, userId: number) => - db.prepare(`SELECT t.id, t.user_id FROM trips t LEFT JOIN trip_members m ON m.trip_id = t.id AND m.user_id = ? WHERE t.id = ? AND (t.user_id = ? OR m.user_id IS NOT NULL)`).get(userId, tripId, userId), - isOwner: (tripId: any, userId: number) => - !!db.prepare('SELECT id FROM trips WHERE id = ? AND user_id = ?').get(tripId, userId), - }; - return { testDb: db, dbMock: mock }; +vi.mock('../../src/db/database', async () => { + const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock'); + return buildDbMock(createSnapshotTestDb()); }); - -vi.mock('../../src/db/database', () => dbMock); vi.mock('../../src/config', () => ({ JWT_SECRET: 'test-jwt-secret-for-trek-testing-only', ENCRYPTION_KEY: 'a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2', @@ -59,9 +38,8 @@ vi.mock('../../src/nest/notifications/transports/webhook.service', async (import return actual; }); +import { db as testDb } from '../../src/db/database'; import { buildApp } from '../../src/bootstrap'; -import { createTables } from '../../src/db/schema'; -import { runMigrations } from '../../src/db/migrations'; import { resetTestDb, resetRateLimits } from '../helpers/test-db'; import { createUser, createAdmin, disableNotificationPref } from '../helpers/factories'; import { authCookie } from '../helpers/auth'; @@ -70,8 +48,6 @@ let nestApp: INestApplication; let app: Application; beforeAll(async () => { - createTables(testDb); - runMigrations(testDb); nestApp = await buildApp(); app = nestApp.getHttpAdapter().getInstance(); }); diff --git a/server/tests/integration/oauth-authorize.test.ts b/server/tests/integration/oauth-authorize.test.ts index c0fd8a886..99a989eaf 100644 --- a/server/tests/integration/oauth-authorize.test.ts +++ b/server/tests/integration/oauth-authorize.test.ts @@ -14,24 +14,10 @@ import type { Application } from 'express'; import type { INestApplication } from '@nestjs/common'; import crypto from 'crypto'; -const { testDb, dbMock } = vi.hoisted(() => { - const Database = require('better-sqlite3'); - const db = new Database(':memory:'); - db.exec('PRAGMA journal_mode = WAL'); - db.exec('PRAGMA foreign_keys = ON'); - db.exec('PRAGMA busy_timeout = 5000'); - const mock = { - db, - closeDb: () => {}, - reinitialize: () => {}, - getPlaceWithTags: () => null, - canAccessTrip: () => undefined, - isOwner: () => false, - }; - return { testDb: db, dbMock: mock }; +vi.mock('../../src/db/database', async () => { + const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock'); + return buildDbMock(createSnapshotTestDb()); }); - -vi.mock('../../src/db/database', () => dbMock); vi.mock('../../src/config', () => ({ JWT_SECRET: 'test-jwt-secret-for-trek-testing-only', ENCRYPTION_KEY: 'a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2', @@ -50,9 +36,8 @@ vi.mock('../../src/app-config', async (importOriginal) => { vi.mock('../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn() })); vi.mock('../../src/mcp/sessionManager', () => ({ revokeUserSessions: vi.fn(), revokeUserSessionsForClient: vi.fn(), sessions: new Map() })); +import { db as testDb } from '../../src/db/database'; import { buildApp } from '../../src/bootstrap'; -import { createTables } from '../../src/db/schema'; -import { runMigrations } from '../../src/db/migrations'; import { resetTestDb, resetRateLimits } from '../helpers/test-db'; import { createUser } from '../helpers/factories'; import { OauthService } from '../../src/nest/oauth/oauth.service'; @@ -93,8 +78,6 @@ async function registerClient(redirectUri = 'https://client.example.com/cb', sco } beforeAll(async () => { - createTables(testDb); - runMigrations(testDb); nestApp = await buildApp(); app = nestApp.getHttpAdapter().getInstance(); }); diff --git a/server/tests/integration/oauth.test.ts b/server/tests/integration/oauth.test.ts index 8d0b61a1d..42b96ccdf 100644 --- a/server/tests/integration/oauth.test.ts +++ b/server/tests/integration/oauth.test.ts @@ -9,31 +9,10 @@ import type { Application } from 'express'; import type { INestApplication } from '@nestjs/common'; import crypto from 'crypto'; -const { testDb, dbMock } = vi.hoisted(() => { - const Database = require('better-sqlite3'); - const db = new Database(':memory:'); - db.exec('PRAGMA journal_mode = WAL'); - db.exec('PRAGMA foreign_keys = ON'); - db.exec('PRAGMA busy_timeout = 5000'); - const mock = { - db, - closeDb: () => {}, - reinitialize: () => {}, - getPlaceWithTags: (placeId: number) => { - const place: any = db.prepare(`SELECT p.*, c.name as category_name, c.color as category_color, c.icon as category_icon FROM places p LEFT JOIN categories c ON p.category_id = c.id WHERE p.id = ?`).get(placeId); - if (!place) return null; - const tags = db.prepare(`SELECT t.* FROM tags t JOIN place_tags pt ON t.id = pt.tag_id WHERE pt.place_id = ?`).all(placeId); - return { ...place, category: place.category_id ? { id: place.category_id, name: place.category_name, color: place.category_color, icon: place.category_icon } : null, tags }; - }, - canAccessTrip: (tripId: any, userId: number) => - db.prepare(`SELECT t.id, t.user_id FROM trips t LEFT JOIN trip_members m ON m.trip_id = t.id AND m.user_id = ? WHERE t.id = ? AND (t.user_id = ? OR m.user_id IS NOT NULL)`).get(userId, tripId, userId), - isOwner: (tripId: any, userId: number) => - !!db.prepare('SELECT id FROM trips WHERE id = ? AND user_id = ?').get(tripId, userId), - }; - return { testDb: db, dbMock: mock }; +vi.mock('../../src/db/database', async () => { + const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock'); + return buildDbMock(createSnapshotTestDb()); }); - -vi.mock('../../src/db/database', () => dbMock); vi.mock('../../src/config', () => ({ JWT_SECRET: 'test-jwt-secret-for-trek-testing-only', ENCRYPTION_KEY: 'a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2', @@ -52,9 +31,8 @@ vi.mock('../../src/app-config', async (importOriginal) => { vi.mock('../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn() })); vi.mock('../../src/mcp/sessionManager', () => ({ revokeUserSessions: vi.fn(), revokeUserSessionsForClient: vi.fn(), sessions: new Map() })); +import { db as testDb } from '../../src/db/database'; import { buildApp } from '../../src/bootstrap'; -import { createTables } from '../../src/db/schema'; -import { runMigrations } from '../../src/db/migrations'; import { resetTestDb, resetRateLimits } from '../helpers/test-db'; import { createUser } from '../helpers/factories'; import { authCookie } from '../helpers/auth'; @@ -96,8 +74,6 @@ function setMcpEnabled(enabled: boolean) { } beforeAll(async () => { - createTables(testDb); - runMigrations(testDb); nestApp = await buildApp(); app = nestApp.getHttpAdapter().getInstance(); }); diff --git a/server/tests/integration/oidc.test.ts b/server/tests/integration/oidc.test.ts index 76482a275..436fa83b9 100644 --- a/server/tests/integration/oidc.test.ts +++ b/server/tests/integration/oidc.test.ts @@ -12,28 +12,12 @@ import request from 'supertest'; import type { Application } from 'express'; import type { INestApplication } from '@nestjs/common'; -// ── DB mock (inline vi.hoisted pattern) ────────────────────────────────────── +// ── DB mock (async vi.mock factory over the migrated schema snapshot) ──────── -const { testDb, dbMock } = vi.hoisted(() => { - const Database = require('better-sqlite3'); - const db = new Database(':memory:'); - db.exec('PRAGMA journal_mode = WAL'); - db.exec('PRAGMA foreign_keys = ON'); - db.exec('PRAGMA busy_timeout = 5000'); - const mock = { - db, - closeDb: () => {}, - reinitialize: () => {}, - getPlaceWithTags: () => null, - canAccessTrip: (tripId: any, userId: number) => - db.prepare(`SELECT t.id, t.user_id FROM trips t LEFT JOIN trip_members m ON m.trip_id = t.id AND m.user_id = ? WHERE t.id = ? AND (t.user_id = ? OR m.user_id IS NOT NULL)`).get(userId, tripId, userId), - isOwner: (tripId: any, userId: number) => - !!db.prepare('SELECT id FROM trips WHERE id = ? AND user_id = ?').get(tripId, userId), - }; - return { testDb: db, dbMock: mock }; +vi.mock('../../src/db/database', async () => { + const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock'); + return buildDbMock(createSnapshotTestDb()); }); - -vi.mock('../../src/db/database', () => dbMock); vi.mock('../../src/config', () => ({ JWT_SECRET: 'test-jwt-secret-for-trek-testing-only', ENCRYPTION_KEY: 'a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2', @@ -48,10 +32,9 @@ vi.mock('../../src/config', () => ({ })); vi.mock('../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn() })); +import { db as testDb } from '../../src/db/database'; import type { MockInstance } from 'vitest'; import { buildApp } from '../../src/bootstrap'; -import { createTables } from '../../src/db/schema'; -import { runMigrations } from '../../src/db/migrations'; import { resetTestDb, resetRateLimits } from '../helpers/test-db'; import { createUser } from '../helpers/factories'; import { OidcService } from '../../src/nest/oidc/oidc.service'; @@ -81,8 +64,6 @@ let mockGetUserInfo: MockInstance; let mockVerifyIdToken: MockInstance; beforeAll(async () => { - createTables(testDb); - runMigrations(testDb); nestApp = await buildApp(); app = nestApp.getHttpAdapter().getInstance(); // Stub only the HTTP-calling methods on the container's OidcService — the diff --git a/server/tests/integration/packing.test.ts b/server/tests/integration/packing.test.ts index e5e561e81..0dc340f40 100644 --- a/server/tests/integration/packing.test.ts +++ b/server/tests/integration/packing.test.ts @@ -7,31 +7,10 @@ import request from 'supertest'; import type { Application } from 'express'; import type { INestApplication } from '@nestjs/common'; -const { testDb, dbMock } = vi.hoisted(() => { - const Database = require('better-sqlite3'); - const db = new Database(':memory:'); - db.exec('PRAGMA journal_mode = WAL'); - db.exec('PRAGMA foreign_keys = ON'); - db.exec('PRAGMA busy_timeout = 5000'); - const mock = { - db, - closeDb: () => {}, - reinitialize: () => {}, - getPlaceWithTags: (placeId: number) => { - const place: any = db.prepare(`SELECT p.*, c.name as category_name, c.color as category_color, c.icon as category_icon FROM places p LEFT JOIN categories c ON p.category_id = c.id WHERE p.id = ?`).get(placeId); - if (!place) return null; - const tags = db.prepare(`SELECT t.* FROM tags t JOIN place_tags pt ON t.id = pt.tag_id WHERE pt.place_id = ?`).all(placeId); - return { ...place, category: place.category_id ? { id: place.category_id, name: place.category_name, color: place.category_color, icon: place.category_icon } : null, tags }; - }, - canAccessTrip: (tripId: any, userId: number) => - db.prepare(`SELECT t.id, t.user_id FROM trips t LEFT JOIN trip_members m ON m.trip_id = t.id AND m.user_id = ? WHERE t.id = ? AND (t.user_id = ? OR m.user_id IS NOT NULL)`).get(userId, tripId, userId), - isOwner: (tripId: any, userId: number) => - !!db.prepare('SELECT id FROM trips WHERE id = ? AND user_id = ?').get(tripId, userId), - }; - return { testDb: db, dbMock: mock }; +vi.mock('../../src/db/database', async () => { + const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock'); + return buildDbMock(createSnapshotTestDb()); }); - -vi.mock('../../src/db/database', () => dbMock); vi.mock('../../src/config', () => ({ JWT_SECRET: 'test-jwt-secret-for-trek-testing-only', ENCRYPTION_KEY: 'a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2', @@ -43,9 +22,8 @@ vi.mock('../../src/config', () => ({ })); vi.mock('../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn() })); +import { db as testDb } from '../../src/db/database'; import { buildApp } from '../../src/bootstrap'; -import { createTables } from '../../src/db/schema'; -import { runMigrations } from '../../src/db/migrations'; import { resetTestDb, resetRateLimits } from '../helpers/test-db'; import { createUser, createTrip, createPackingItem, addTripMember } from '../helpers/factories'; import { authCookie } from '../helpers/auth'; @@ -54,8 +32,6 @@ let nestApp: INestApplication; let app: Application; beforeAll(async () => { - createTables(testDb); - runMigrations(testDb); nestApp = await buildApp(); app = nestApp.getHttpAdapter().getInstance(); }); diff --git a/server/tests/integration/places.test.ts b/server/tests/integration/places.test.ts index 583cc3565..9d5d349fb 100644 --- a/server/tests/integration/places.test.ts +++ b/server/tests/integration/places.test.ts @@ -13,31 +13,10 @@ import type { Application } from 'express'; import type { INestApplication } from '@nestjs/common'; import path from 'path'; -const { testDb, dbMock } = vi.hoisted(() => { - const Database = require('better-sqlite3'); - const db = new Database(':memory:'); - db.exec('PRAGMA journal_mode = WAL'); - db.exec('PRAGMA foreign_keys = ON'); - db.exec('PRAGMA busy_timeout = 5000'); - const mock = { - db, - closeDb: () => {}, - reinitialize: () => {}, - getPlaceWithTags: (placeId: number) => { - const place: any = db.prepare(`SELECT p.*, c.name as category_name, c.color as category_color, c.icon as category_icon FROM places p LEFT JOIN categories c ON p.category_id = c.id WHERE p.id = ?`).get(placeId); - if (!place) return null; - const tags = db.prepare(`SELECT t.* FROM tags t JOIN place_tags pt ON t.id = pt.tag_id WHERE pt.place_id = ?`).all(placeId); - return { ...place, category: place.category_id ? { id: place.category_id, name: place.category_name, color: place.category_color, icon: place.category_icon } : null, tags }; - }, - canAccessTrip: (tripId: any, userId: number) => - db.prepare(`SELECT t.id, t.user_id FROM trips t LEFT JOIN trip_members m ON m.trip_id = t.id AND m.user_id = ? WHERE t.id = ? AND (t.user_id = ? OR m.user_id IS NOT NULL)`).get(userId, tripId, userId), - isOwner: (tripId: any, userId: number) => - !!db.prepare('SELECT id FROM trips WHERE id = ? AND user_id = ?').get(tripId, userId), - }; - return { testDb: db, dbMock: mock }; +vi.mock('../../src/db/database', async () => { + const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock'); + return buildDbMock(createSnapshotTestDb()); }); - -vi.mock('../../src/db/database', () => dbMock); vi.mock('../../src/config', () => ({ JWT_SECRET: 'test-jwt-secret-for-trek-testing-only', ENCRYPTION_KEY: 'a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2', @@ -49,9 +28,8 @@ vi.mock('../../src/config', () => ({ })); vi.mock('../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn() })); +import { db as testDb } from '../../src/db/database'; import { buildApp } from '../../src/bootstrap'; -import { createTables } from '../../src/db/schema'; -import { runMigrations } from '../../src/db/migrations'; import { resetTestDb, resetRateLimits } from '../helpers/test-db'; import { createUser, createAdmin, createTrip, createPlace, addTripMember } from '../helpers/factories'; import { authCookie } from '../helpers/auth'; @@ -73,8 +51,6 @@ const KML_MALFORMED_FIXTURE = path.join(__dirname, '../fixtures/test-malformed.k const KMZ_FIXTURE = path.join(__dirname, '../fixtures/test.kmz'); beforeAll(async () => { - createTables(testDb); - runMigrations(testDb); nestApp = await buildApp(); app = nestApp.getHttpAdapter().getInstance(); }); diff --git a/server/tests/integration/profile.test.ts b/server/tests/integration/profile.test.ts index 4b79ab541..35da10446 100644 --- a/server/tests/integration/profile.test.ts +++ b/server/tests/integration/profile.test.ts @@ -8,31 +8,10 @@ import type { Application } from 'express'; import type { INestApplication } from '@nestjs/common'; import path from 'path'; -const { testDb, dbMock } = vi.hoisted(() => { - const Database = require('better-sqlite3'); - const db = new Database(':memory:'); - db.exec('PRAGMA journal_mode = WAL'); - db.exec('PRAGMA foreign_keys = ON'); - db.exec('PRAGMA busy_timeout = 5000'); - const mock = { - db, - closeDb: () => {}, - reinitialize: () => {}, - getPlaceWithTags: (placeId: number) => { - const place: any = db.prepare(`SELECT p.*, c.name as category_name, c.color as category_color, c.icon as category_icon FROM places p LEFT JOIN categories c ON p.category_id = c.id WHERE p.id = ?`).get(placeId); - if (!place) return null; - const tags = db.prepare(`SELECT t.* FROM tags t JOIN place_tags pt ON t.id = pt.tag_id WHERE pt.place_id = ?`).all(placeId); - return { ...place, category: place.category_id ? { id: place.category_id, name: place.category_name, color: place.category_color, icon: place.category_icon } : null, tags }; - }, - canAccessTrip: (tripId: any, userId: number) => - db.prepare(`SELECT t.id, t.user_id FROM trips t LEFT JOIN trip_members m ON m.trip_id = t.id AND m.user_id = ? WHERE t.id = ? AND (t.user_id = ? OR m.user_id IS NOT NULL)`).get(userId, tripId, userId), - isOwner: (tripId: any, userId: number) => - !!db.prepare('SELECT id FROM trips WHERE id = ? AND user_id = ?').get(tripId, userId), - }; - return { testDb: db, dbMock: mock }; +vi.mock('../../src/db/database', async () => { + const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock'); + return buildDbMock(createSnapshotTestDb()); }); - -vi.mock('../../src/db/database', () => dbMock); vi.mock('../../src/config', () => ({ JWT_SECRET: 'test-jwt-secret-for-trek-testing-only', ENCRYPTION_KEY: 'a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2', @@ -44,9 +23,8 @@ vi.mock('../../src/config', () => ({ })); vi.mock('../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn() })); +import { db as testDb } from '../../src/db/database'; import { buildApp } from '../../src/bootstrap'; -import { createTables } from '../../src/db/schema'; -import { runMigrations } from '../../src/db/migrations'; import { resetTestDb, resetRateLimits } from '../helpers/test-db'; import { createUser, createAdmin, createTrip } from '../helpers/factories'; import { authCookie } from '../helpers/auth'; @@ -57,8 +35,6 @@ const FIXTURE_JPEG = path.join(__dirname, '../fixtures/small-image.jpg'); const FIXTURE_PDF = path.join(__dirname, '../fixtures/test.pdf'); beforeAll(async () => { - createTables(testDb); - runMigrations(testDb); nestApp = await buildApp(); app = nestApp.getHttpAdapter().getInstance(); }); diff --git a/server/tests/integration/reservations.test.ts b/server/tests/integration/reservations.test.ts index 8b70f9ba4..0b2787367 100644 --- a/server/tests/integration/reservations.test.ts +++ b/server/tests/integration/reservations.test.ts @@ -7,31 +7,10 @@ import request from 'supertest'; import type { Application } from 'express'; import type { INestApplication } from '@nestjs/common'; -const { testDb, dbMock } = vi.hoisted(() => { - const Database = require('better-sqlite3'); - const db = new Database(':memory:'); - db.exec('PRAGMA journal_mode = WAL'); - db.exec('PRAGMA foreign_keys = ON'); - db.exec('PRAGMA busy_timeout = 5000'); - const mock = { - db, - closeDb: () => {}, - reinitialize: () => {}, - getPlaceWithTags: (placeId: number) => { - const place: any = db.prepare(`SELECT p.*, c.name as category_name, c.color as category_color, c.icon as category_icon FROM places p LEFT JOIN categories c ON p.category_id = c.id WHERE p.id = ?`).get(placeId); - if (!place) return null; - const tags = db.prepare(`SELECT t.* FROM tags t JOIN place_tags pt ON t.id = pt.tag_id WHERE pt.place_id = ?`).all(placeId); - return { ...place, category: place.category_id ? { id: place.category_id, name: place.category_name, color: place.category_color, icon: place.category_icon } : null, tags }; - }, - canAccessTrip: (tripId: any, userId: number) => - db.prepare(`SELECT t.id, t.user_id FROM trips t LEFT JOIN trip_members m ON m.trip_id = t.id AND m.user_id = ? WHERE t.id = ? AND (t.user_id = ? OR m.user_id IS NOT NULL)`).get(userId, tripId, userId), - isOwner: (tripId: any, userId: number) => - !!db.prepare('SELECT id FROM trips WHERE id = ? AND user_id = ?').get(tripId, userId), - }; - return { testDb: db, dbMock: mock }; +vi.mock('../../src/db/database', async () => { + const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock'); + return buildDbMock(createSnapshotTestDb()); }); - -vi.mock('../../src/db/database', () => dbMock); vi.mock('../../src/config', () => ({ JWT_SECRET: 'test-jwt-secret-for-trek-testing-only', ENCRYPTION_KEY: 'a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2', @@ -43,9 +22,8 @@ vi.mock('../../src/config', () => ({ })); vi.mock('../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn() })); +import { db as testDb } from '../../src/db/database'; import { buildApp } from '../../src/bootstrap'; -import { createTables } from '../../src/db/schema'; -import { runMigrations } from '../../src/db/migrations'; import { resetTestDb, resetRateLimits } from '../helpers/test-db'; import { createUser, createTrip, createDay, createPlace, createReservation, addTripMember } from '../helpers/factories'; import { authCookie } from '../helpers/auth'; @@ -54,8 +32,6 @@ let nestApp: INestApplication; let app: Application; beforeAll(async () => { - createTables(testDb); - runMigrations(testDb); nestApp = await buildApp(); app = nestApp.getHttpAdapter().getInstance(); }); diff --git a/server/tests/integration/scheduler-gate.test.ts b/server/tests/integration/scheduler-gate.test.ts index 7bb9f12cd..c37cad99b 100644 --- a/server/tests/integration/scheduler-gate.test.ts +++ b/server/tests/integration/scheduler-gate.test.ts @@ -11,25 +11,10 @@ import { describe, it, expect, vi, beforeAll, afterAll } from 'vitest'; import type { INestApplication } from '@nestjs/common'; -const { testDb, dbMock } = vi.hoisted(() => { - // eslint-disable-next-line @typescript-eslint/no-require-imports - const Database = require('better-sqlite3'); - const db = new Database(':memory:'); - db.exec('PRAGMA journal_mode = WAL'); - db.exec('PRAGMA foreign_keys = ON'); - db.exec('PRAGMA busy_timeout = 5000'); - const mock = { - db, - closeDb: () => {}, - reinitialize: () => {}, - getPlaceWithTags: () => null, - canAccessTrip: () => undefined, - isOwner: () => false, - }; - return { testDb: db, dbMock: mock }; +vi.mock('../../src/db/database', async () => { + const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock'); + return buildDbMock(createSnapshotTestDb()); }); - -vi.mock('../../src/db/database', () => dbMock); vi.mock('../../src/config', () => ({ JWT_SECRET: 'test-jwt-secret-for-trek-testing-only', ENCRYPTION_KEY: 'a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2', @@ -41,9 +26,8 @@ vi.mock('../../src/config', () => ({ })); vi.mock('../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn() })); +import { db as testDb } from '../../src/db/database'; import { SchedulerRegistry } from '@nestjs/schedule'; -import { createTables } from '../../src/db/schema'; -import { runMigrations } from '../../src/db/migrations'; import { CronRegistrarService } from '../../src/nest/scheduling/cron-registrar.service'; import { buildApp } from '../../src/bootstrap'; @@ -51,8 +35,6 @@ describe('SCHED-GATE — the harness boots without scheduling anything', () => { let app: INestApplication; beforeAll(async () => { - createTables(testDb); - runMigrations(testDb); app = await buildApp(); }); diff --git a/server/tests/integration/security.test.ts b/server/tests/integration/security.test.ts index 9e721c1c6..8ef63af42 100644 --- a/server/tests/integration/security.test.ts +++ b/server/tests/integration/security.test.ts @@ -14,31 +14,10 @@ import type { INestApplication } from '@nestjs/common'; import path from 'path'; import fs from 'fs'; -const { testDb, dbMock } = vi.hoisted(() => { - const Database = require('better-sqlite3'); - const db = new Database(':memory:'); - db.exec('PRAGMA journal_mode = WAL'); - db.exec('PRAGMA foreign_keys = ON'); - db.exec('PRAGMA busy_timeout = 5000'); - const mock = { - db, - closeDb: () => {}, - reinitialize: () => {}, - getPlaceWithTags: (placeId: number) => { - const place: any = db.prepare(`SELECT p.*, c.name as category_name, c.color as category_color, c.icon as category_icon FROM places p LEFT JOIN categories c ON p.category_id = c.id WHERE p.id = ?`).get(placeId); - if (!place) return null; - const tags = db.prepare(`SELECT t.* FROM tags t JOIN place_tags pt ON t.id = pt.tag_id WHERE pt.place_id = ?`).all(placeId); - return { ...place, category: place.category_id ? { id: place.category_id, name: place.category_name, color: place.category_color, icon: place.category_icon } : null, tags }; - }, - canAccessTrip: (tripId: any, userId: number) => - db.prepare(`SELECT t.id, t.user_id FROM trips t LEFT JOIN trip_members m ON m.trip_id = t.id AND m.user_id = ? WHERE t.id = ? AND (t.user_id = ? OR m.user_id IS NOT NULL)`).get(userId, tripId, userId), - isOwner: (tripId: any, userId: number) => - !!db.prepare('SELECT id FROM trips WHERE id = ? AND user_id = ?').get(tripId, userId), - }; - return { testDb: db, dbMock: mock }; +vi.mock('../../src/db/database', async () => { + const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock'); + return buildDbMock(createSnapshotTestDb()); }); - -vi.mock('../../src/db/database', () => dbMock); vi.mock('../../src/config', () => ({ JWT_SECRET: 'test-jwt-secret-for-trek-testing-only', ENCRYPTION_KEY: 'a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2', @@ -50,9 +29,8 @@ vi.mock('../../src/config', () => ({ })); vi.mock('../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn() })); +import { db as testDb } from '../../src/db/database'; import { buildApp } from '../../src/bootstrap'; -import { createTables } from '../../src/db/schema'; -import { runMigrations } from '../../src/db/migrations'; import { resetTestDb, resetRateLimits } from '../helpers/test-db'; import { createUser, createTrip } from '../helpers/factories'; import { authCookie, authHeader, generateToken } from '../helpers/auth'; @@ -63,8 +41,6 @@ const FIXTURE_IMG = path.join(__dirname, '../fixtures/small-image.jpg'); const uploadsDir = path.join(__dirname, '../../uploads/files'); beforeAll(async () => { - createTables(testDb); - runMigrations(testDb); nestApp = await buildApp(); app = nestApp.getHttpAdapter().getInstance(); if (!fs.existsSync(uploadsDir)) fs.mkdirSync(uploadsDir, { recursive: true }); diff --git a/server/tests/integration/settings.test.ts b/server/tests/integration/settings.test.ts index 0d393ddd8..2fdbf41ea 100644 --- a/server/tests/integration/settings.test.ts +++ b/server/tests/integration/settings.test.ts @@ -7,26 +7,10 @@ import request from 'supertest'; import type { Application } from 'express'; import type { INestApplication } from '@nestjs/common'; -const { testDb, dbMock } = vi.hoisted(() => { - const Database = require('better-sqlite3'); - const db = new Database(':memory:'); - db.exec('PRAGMA journal_mode = WAL'); - db.exec('PRAGMA foreign_keys = ON'); - db.exec('PRAGMA busy_timeout = 5000'); - const mock = { - db, - closeDb: () => {}, - reinitialize: () => {}, - getPlaceWithTags: () => null, - canAccessTrip: (tripId: any, userId: number) => - db.prepare(`SELECT t.id, t.user_id FROM trips t LEFT JOIN trip_members m ON m.trip_id = t.id AND m.user_id = ? WHERE t.id = ? AND (t.user_id = ? OR m.user_id IS NOT NULL)`).get(userId, tripId, userId), - isOwner: (tripId: any, userId: number) => - !!db.prepare('SELECT id FROM trips WHERE id = ? AND user_id = ?').get(tripId, userId), - }; - return { testDb: db, dbMock: mock }; +vi.mock('../../src/db/database', async () => { + const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock'); + return buildDbMock(createSnapshotTestDb()); }); - -vi.mock('../../src/db/database', () => dbMock); vi.mock('../../src/config', () => ({ JWT_SECRET: 'test-jwt-secret-for-trek-testing-only', ENCRYPTION_KEY: 'a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2', @@ -38,9 +22,8 @@ vi.mock('../../src/config', () => ({ })); vi.mock('../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn() })); +import { db as testDb } from '../../src/db/database'; import { buildApp } from '../../src/bootstrap'; -import { createTables } from '../../src/db/schema'; -import { runMigrations } from '../../src/db/migrations'; import { resetTestDb, resetRateLimits } from '../helpers/test-db'; import { createUser } from '../helpers/factories'; import { authCookie } from '../helpers/auth'; @@ -49,8 +32,6 @@ let nestApp: INestApplication; let app: Application; beforeAll(async () => { - createTables(testDb); - runMigrations(testDb); nestApp = await buildApp(); app = nestApp.getHttpAdapter().getInstance(); }); diff --git a/server/tests/integration/share.test.ts b/server/tests/integration/share.test.ts index fc3cd29cc..84c146998 100644 --- a/server/tests/integration/share.test.ts +++ b/server/tests/integration/share.test.ts @@ -7,31 +7,10 @@ import request from 'supertest'; import type { Application } from 'express'; import type { INestApplication } from '@nestjs/common'; -const { testDb, dbMock } = vi.hoisted(() => { - const Database = require('better-sqlite3'); - const db = new Database(':memory:'); - db.exec('PRAGMA journal_mode = WAL'); - db.exec('PRAGMA foreign_keys = ON'); - db.exec('PRAGMA busy_timeout = 5000'); - const mock = { - db, - closeDb: () => {}, - reinitialize: () => {}, - getPlaceWithTags: (placeId: number) => { - const place: any = db.prepare(`SELECT p.*, c.name as category_name, c.color as category_color, c.icon as category_icon FROM places p LEFT JOIN categories c ON p.category_id = c.id WHERE p.id = ?`).get(placeId); - if (!place) return null; - const tags = db.prepare(`SELECT t.* FROM tags t JOIN place_tags pt ON t.id = pt.tag_id WHERE pt.place_id = ?`).all(placeId); - return { ...place, category: place.category_id ? { id: place.category_id, name: place.category_name, color: place.category_color, icon: place.category_icon } : null, tags }; - }, - canAccessTrip: (tripId: any, userId: number) => - db.prepare(`SELECT t.id, t.user_id FROM trips t LEFT JOIN trip_members m ON m.trip_id = t.id AND m.user_id = ? WHERE t.id = ? AND (t.user_id = ? OR m.user_id IS NOT NULL)`).get(userId, tripId, userId), - isOwner: (tripId: any, userId: number) => - !!db.prepare('SELECT id FROM trips WHERE id = ? AND user_id = ?').get(tripId, userId), - }; - return { testDb: db, dbMock: mock }; +vi.mock('../../src/db/database', async () => { + const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock'); + return buildDbMock(createSnapshotTestDb()); }); - -vi.mock('../../src/db/database', () => dbMock); vi.mock('../../src/config', () => ({ JWT_SECRET: 'test-jwt-secret-for-trek-testing-only', ENCRYPTION_KEY: 'a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2', @@ -43,9 +22,8 @@ vi.mock('../../src/config', () => ({ })); vi.mock('../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn() })); +import { db as testDb } from '../../src/db/database'; import { buildApp } from '../../src/bootstrap'; -import { createTables } from '../../src/db/schema'; -import { runMigrations } from '../../src/db/migrations'; import { resetTestDb, resetRateLimits } from '../helpers/test-db'; import { createUser, createTrip, addTripMember, createDay, createPlace, createDayAssignment, createDayNote } from '../helpers/factories'; import { authCookie } from '../helpers/auth'; @@ -77,8 +55,6 @@ let nestApp: INestApplication; let app: Application; beforeAll(async () => { - createTables(testDb); - runMigrations(testDb); nestApp = await buildApp(); app = nestApp.getHttpAdapter().getInstance(); }); diff --git a/server/tests/integration/systemNotices.test.ts b/server/tests/integration/systemNotices.test.ts index 26b4b4b89..2395a0b2b 100644 --- a/server/tests/integration/systemNotices.test.ts +++ b/server/tests/integration/systemNotices.test.ts @@ -10,26 +10,10 @@ import type { INestApplication } from '@nestjs/common'; // ───────────────────────────────────────────────────────────────────────────── // Bare in-memory DB — schema applied in beforeAll after mocks register // ───────────────────────────────────────────────────────────────────────────── -const { testDb, dbMock } = vi.hoisted(() => { - const Database = require('better-sqlite3'); - const db = new Database(':memory:'); - db.exec('PRAGMA journal_mode = WAL'); - db.exec('PRAGMA foreign_keys = ON'); - db.exec('PRAGMA busy_timeout = 5000'); - - const mock = { - db, - closeDb: () => {}, - reinitialize: () => {}, - getPlaceWithTags: () => null, - canAccessTrip: () => null, - isOwner: () => false, - }; - - return { testDb: db, dbMock: mock }; +vi.mock('../../src/db/database', async () => { + const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock'); + return buildDbMock(createSnapshotTestDb()); }); - -vi.mock('../../src/db/database', () => dbMock); vi.mock('../../src/config', () => ({ JWT_SECRET: 'test-jwt-secret-for-trek-testing-only', ENCRYPTION_KEY: 'a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2', @@ -41,9 +25,8 @@ vi.mock('../../src/config', () => ({ })); vi.mock('../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn() })); +import { db as testDb } from '../../src/db/database'; import { buildApp } from '../../src/bootstrap'; -import { createTables } from '../../src/db/schema'; -import { runMigrations } from '../../src/db/migrations'; import { resetTestDb } from '../helpers/test-db'; import { createUser, createAdmin } from '../helpers/factories'; import { authCookie } from '../helpers/auth'; @@ -68,8 +51,6 @@ const TEST_NOTICE: SystemNotice = { }; beforeAll(async () => { - createTables(testDb); - runMigrations(testDb); nestApp = await buildApp(); app = nestApp.getHttpAdapter().getInstance(); }); diff --git a/server/tests/integration/tags.test.ts b/server/tests/integration/tags.test.ts index 6b7961ff0..c4fe6f48e 100644 --- a/server/tests/integration/tags.test.ts +++ b/server/tests/integration/tags.test.ts @@ -7,26 +7,10 @@ import request from 'supertest'; import type { Application } from 'express'; import type { INestApplication } from '@nestjs/common'; -const { testDb, dbMock } = vi.hoisted(() => { - const Database = require('better-sqlite3'); - const db = new Database(':memory:'); - db.exec('PRAGMA journal_mode = WAL'); - db.exec('PRAGMA foreign_keys = ON'); - db.exec('PRAGMA busy_timeout = 5000'); - const mock = { - db, - closeDb: () => {}, - reinitialize: () => {}, - getPlaceWithTags: () => null, - canAccessTrip: (tripId: any, userId: number) => - db.prepare(`SELECT t.id, t.user_id FROM trips t LEFT JOIN trip_members m ON m.trip_id = t.id AND m.user_id = ? WHERE t.id = ? AND (t.user_id = ? OR m.user_id IS NOT NULL)`).get(userId, tripId, userId), - isOwner: (tripId: any, userId: number) => - !!db.prepare('SELECT id FROM trips WHERE id = ? AND user_id = ?').get(tripId, userId), - }; - return { testDb: db, dbMock: mock }; +vi.mock('../../src/db/database', async () => { + const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock'); + return buildDbMock(createSnapshotTestDb()); }); - -vi.mock('../../src/db/database', () => dbMock); vi.mock('../../src/config', () => ({ JWT_SECRET: 'test-jwt-secret-for-trek-testing-only', ENCRYPTION_KEY: 'a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2', @@ -38,9 +22,8 @@ vi.mock('../../src/config', () => ({ })); vi.mock('../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn() })); +import { db as testDb } from '../../src/db/database'; import { buildApp } from '../../src/bootstrap'; -import { createTables } from '../../src/db/schema'; -import { runMigrations } from '../../src/db/migrations'; import { resetTestDb, resetRateLimits } from '../helpers/test-db'; import { createUser } from '../helpers/factories'; import { authCookie } from '../helpers/auth'; @@ -49,8 +32,6 @@ let nestApp: INestApplication; let app: Application; beforeAll(async () => { - createTables(testDb); - runMigrations(testDb); nestApp = await buildApp(); app = nestApp.getHttpAdapter().getInstance(); }); diff --git a/server/tests/integration/todo.test.ts b/server/tests/integration/todo.test.ts index c5bad7e9f..e0ca5d22a 100644 --- a/server/tests/integration/todo.test.ts +++ b/server/tests/integration/todo.test.ts @@ -7,26 +7,10 @@ import request from 'supertest'; import type { Application } from 'express'; import type { INestApplication } from '@nestjs/common'; -const { testDb, dbMock } = vi.hoisted(() => { - const Database = require('better-sqlite3'); - const db = new Database(':memory:'); - db.exec('PRAGMA journal_mode = WAL'); - db.exec('PRAGMA foreign_keys = ON'); - db.exec('PRAGMA busy_timeout = 5000'); - const mock = { - db, - closeDb: () => {}, - reinitialize: () => {}, - getPlaceWithTags: () => null, - canAccessTrip: (tripId: any, userId: number) => - db.prepare(`SELECT t.id, t.user_id FROM trips t LEFT JOIN trip_members m ON m.trip_id = t.id AND m.user_id = ? WHERE t.id = ? AND (t.user_id = ? OR m.user_id IS NOT NULL)`).get(userId, tripId, userId), - isOwner: (tripId: any, userId: number) => - !!db.prepare('SELECT id FROM trips WHERE id = ? AND user_id = ?').get(tripId, userId), - }; - return { testDb: db, dbMock: mock }; +vi.mock('../../src/db/database', async () => { + const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock'); + return buildDbMock(createSnapshotTestDb()); }); - -vi.mock('../../src/db/database', () => dbMock); vi.mock('../../src/config', () => ({ JWT_SECRET: 'test-jwt-secret-for-trek-testing-only', ENCRYPTION_KEY: 'a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2', @@ -38,9 +22,8 @@ vi.mock('../../src/config', () => ({ })); vi.mock('../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn() })); +import { db as testDb } from '../../src/db/database'; import { buildApp } from '../../src/bootstrap'; -import { createTables } from '../../src/db/schema'; -import { runMigrations } from '../../src/db/migrations'; import { resetTestDb, resetRateLimits } from '../helpers/test-db'; import { createUser, createTrip, addTripMember } from '../helpers/factories'; import { authCookie } from '../helpers/auth'; @@ -50,8 +33,6 @@ let nestApp: INestApplication; let app: Application; beforeAll(async () => { - createTables(testDb); - runMigrations(testDb); nestApp = await buildApp(); app = nestApp.getHttpAdapter().getInstance(); }); diff --git a/server/tests/integration/trips.test.ts b/server/tests/integration/trips.test.ts index 8a78b573c..2206c43e1 100644 --- a/server/tests/integration/trips.test.ts +++ b/server/tests/integration/trips.test.ts @@ -10,36 +10,10 @@ import type { INestApplication } from '@nestjs/common'; // ───────────────────────────────────────────────────────────────────────────── // Step 1: Bare in-memory DB — schema applied in beforeAll after mocks register // ───────────────────────────────────────────────────────────────────────────── -const { testDb, dbMock } = vi.hoisted(() => { - const Database = require('better-sqlite3'); - const db = new Database(':memory:'); - db.exec('PRAGMA journal_mode = WAL'); - db.exec('PRAGMA foreign_keys = ON'); - db.exec('PRAGMA busy_timeout = 5000'); - - const mock = { - db, - closeDb: () => {}, - reinitialize: () => {}, - getPlaceWithTags: (placeId: number) => { - const place: any = db.prepare(` - SELECT p.*, c.name as category_name, c.color as category_color, c.icon as category_icon - FROM places p LEFT JOIN categories c ON p.category_id = c.id WHERE p.id = ? - `).get(placeId); - if (!place) return null; - const tags = db.prepare(`SELECT t.* FROM tags t JOIN place_tags pt ON t.id = pt.tag_id WHERE pt.place_id = ?`).all(placeId); - return { ...place, category: place.category_id ? { id: place.category_id, name: place.category_name, color: place.category_color, icon: place.category_icon } : null, tags }; - }, - canAccessTrip: (tripId: any, userId: number) => - db.prepare(`SELECT t.id, t.user_id FROM trips t LEFT JOIN trip_members m ON m.trip_id = t.id AND m.user_id = ? WHERE t.id = ? AND (t.user_id = ? OR m.user_id IS NOT NULL)`).get(userId, tripId, userId), - isOwner: (tripId: any, userId: number) => - !!db.prepare('SELECT id FROM trips WHERE id = ? AND user_id = ?').get(tripId, userId), - }; - - return { testDb: db, dbMock: mock }; +vi.mock('../../src/db/database', async () => { + const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock'); + return buildDbMock(createSnapshotTestDb()); }); - -vi.mock('../../src/db/database', () => dbMock); vi.mock('../../src/config', () => ({ JWT_SECRET: 'test-jwt-secret-for-trek-testing-only', ENCRYPTION_KEY: 'a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2', @@ -51,9 +25,8 @@ vi.mock('../../src/config', () => ({ })); vi.mock('../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn() })); +import { db as testDb } from '../../src/db/database'; import { buildApp } from '../../src/bootstrap'; -import { createTables } from '../../src/db/schema'; -import { runMigrations } from '../../src/db/migrations'; import { resetTestDb, resetRateLimits } from '../helpers/test-db'; import { createUser, createAdmin, createTrip, addTripMember, createPlace, createReservation, createTag, createDayAccommodation, createBudgetItem, createPackingItem, createDayNote, createDayAssignment } from '../helpers/factories'; import { authCookie } from '../helpers/auth'; @@ -63,8 +36,6 @@ let nestApp: INestApplication; let app: Application; beforeAll(async () => { - createTables(testDb); - runMigrations(testDb); nestApp = await buildApp(); app = nestApp.getHttpAdapter().getInstance(); }); diff --git a/server/tests/integration/uploads-static.test.ts b/server/tests/integration/uploads-static.test.ts index e96c0a60d..c588794d8 100644 --- a/server/tests/integration/uploads-static.test.ts +++ b/server/tests/integration/uploads-static.test.ts @@ -20,23 +20,10 @@ import type { INestApplication } from '@nestjs/common'; import path from 'path'; import fs from 'fs'; -const { testDb, dbMock } = vi.hoisted(() => { - const Database = require('better-sqlite3'); - const db = new Database(':memory:'); - db.exec('PRAGMA journal_mode = WAL'); - db.exec('PRAGMA foreign_keys = ON'); - db.exec('PRAGMA busy_timeout = 5000'); - const mock = { - db, - closeDb: () => {}, - reinitialize: () => {}, - canAccessTrip: () => undefined, - isOwner: () => false, - }; - return { testDb: db, dbMock: mock }; +vi.mock('../../src/db/database', async () => { + const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock'); + return buildDbMock(createSnapshotTestDb()); }); - -vi.mock('../../src/db/database', () => dbMock); vi.mock('../../src/config', () => ({ JWT_SECRET: 'test-jwt-secret-for-trek-testing-only', ENCRYPTION_KEY: 'a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2', @@ -48,9 +35,8 @@ vi.mock('../../src/config', () => ({ })); vi.mock('../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn(), getOnlineUserIds: vi.fn(() => []) })); +import { db as testDb } from '../../src/db/database'; import { buildApp } from '../../src/bootstrap'; -import { createTables } from '../../src/db/schema'; -import { runMigrations } from '../../src/db/migrations'; let nestApp: INestApplication; let app: Application; @@ -73,8 +59,6 @@ function writeFixture(rel: string, bytes: Buffer = BYTES): void { } beforeAll(async () => { - createTables(testDb); - runMigrations(testDb); nestApp = await buildApp(); app = nestApp.getHttpAdapter().getInstance(); // Written AFTER buildApp(): LocalDriver init ensures + realpaths the diff --git a/server/tests/integration/vacay.test.ts b/server/tests/integration/vacay.test.ts index b10450733..45a2a4d97 100644 --- a/server/tests/integration/vacay.test.ts +++ b/server/tests/integration/vacay.test.ts @@ -7,31 +7,10 @@ import request from 'supertest'; import type { Application } from 'express'; import type { INestApplication } from '@nestjs/common'; -const { testDb, dbMock } = vi.hoisted(() => { - const Database = require('better-sqlite3'); - const db = new Database(':memory:'); - db.exec('PRAGMA journal_mode = WAL'); - db.exec('PRAGMA foreign_keys = ON'); - db.exec('PRAGMA busy_timeout = 5000'); - const mock = { - db, - closeDb: () => {}, - reinitialize: () => {}, - getPlaceWithTags: (placeId: number) => { - const place: any = db.prepare(`SELECT p.*, c.name as category_name, c.color as category_color, c.icon as category_icon FROM places p LEFT JOIN categories c ON p.category_id = c.id WHERE p.id = ?`).get(placeId); - if (!place) return null; - const tags = db.prepare(`SELECT t.* FROM tags t JOIN place_tags pt ON t.id = pt.tag_id WHERE pt.place_id = ?`).all(placeId); - return { ...place, category: place.category_id ? { id: place.category_id, name: place.category_name, color: place.category_color, icon: place.category_icon } : null, tags }; - }, - canAccessTrip: (tripId: any, userId: number) => - db.prepare(`SELECT t.id, t.user_id FROM trips t LEFT JOIN trip_members m ON m.trip_id = t.id AND m.user_id = ? WHERE t.id = ? AND (t.user_id = ? OR m.user_id IS NOT NULL)`).get(userId, tripId, userId), - isOwner: (tripId: any, userId: number) => - !!db.prepare('SELECT id FROM trips WHERE id = ? AND user_id = ?').get(tripId, userId), - }; - return { testDb: db, dbMock: mock }; +vi.mock('../../src/db/database', async () => { + const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock'); + return buildDbMock(createSnapshotTestDb()); }); - -vi.mock('../../src/db/database', () => dbMock); vi.mock('../../src/config', () => ({ JWT_SECRET: 'test-jwt-secret-for-trek-testing-only', ENCRYPTION_KEY: 'a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2', @@ -51,6 +30,7 @@ vi.stubGlobal('fetch', vi.fn().mockResolvedValue({ ]), })); +import { db as testDb } from '../../src/db/database'; import { VacayService } from '../../src/nest/vacay/vacay.service'; // Stub VacayService.getCountries to avoid a real HTTP call to nager.at (the @@ -61,8 +41,6 @@ vi.spyOn(VacayService.prototype, 'getCountries').mockResolvedValue({ }); import { buildApp } from '../../src/bootstrap'; -import { createTables } from '../../src/db/schema'; -import { runMigrations } from '../../src/db/migrations'; import { resetTestDb, resetRateLimits } from '../helpers/test-db'; import { createUser } from '../helpers/factories'; import { authCookie } from '../helpers/auth'; @@ -71,8 +49,6 @@ let nestApp: INestApplication; let app: Application; beforeAll(async () => { - createTables(testDb); - runMigrations(testDb); nestApp = await buildApp(); app = nestApp.getHttpAdapter().getInstance(); }); diff --git a/server/tests/unit/nest/route-inventory.test.ts b/server/tests/unit/nest/route-inventory.test.ts index 9864908d7..4403c8f5d 100644 --- a/server/tests/unit/nest/route-inventory.test.ts +++ b/server/tests/unit/nest/route-inventory.test.ts @@ -10,9 +10,8 @@ import { describe, it, expect, vi } from 'vitest'; vi.mock('../../../src/db/database', async () => { - const Database = (await import('better-sqlite3')).default; - const db = new Database(':memory:'); - return { db, closeDb: () => {}, reinitialize: () => {}, canAccessTrip: () => null, isOwner: () => false, getPlaceWithTags: () => null }; + const { createSnapshotTestDb, buildDbMock } = await import('../../helpers/db-mock'); + return buildDbMock(createSnapshotTestDb()); }); import { Test } from '@nestjs/testing';