From 9bcee484d4c4bbdf14f6ef63daa4f286c2367857 Mon Sep 17 00:00:00 2001 From: jubnl Date: Thu, 24 Sep 2026 04:38:01 +0200 Subject: [PATCH] =?UTF-8?q?test(server):=20plan=204=20=E2=80=94=20collapse?= =?UTF-8?q?=20mcp=20tool=20test=20harnesses=20onto=20the=20ORM=20snapshot?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tests/unit/mcp/tools-addon-gating.test.ts | 31 +++----------- server/tests/unit/mcp/tools-airtrail.test.ts | 31 +++----------- ...ols-assignments-reservations-extra.test.ts | 31 +++----------- .../tests/unit/mcp/tools-assignments.test.ts | 31 +++----------- .../unit/mcp/tools-atlas-expanded.test.ts | 31 +++----------- server/tests/unit/mcp/tools-atlas.test.ts | 31 +++----------- .../unit/mcp/tools-budget-advanced.test.ts | 31 +++----------- server/tests/unit/mcp/tools-budget.test.ts | 31 +++----------- .../tests/unit/mcp/tools-categories.test.ts | 33 ++++----------- .../unit/mcp/tools-collab-polls-chat.test.ts | 31 +++----------- .../tests/unit/mcp/tools-collections.test.ts | 30 +++---------- .../mcp/tools-days-accommodations.test.ts | 42 +++---------------- server/tests/unit/mcp/tools-days.test.ts | 31 +++----------- .../tests/unit/mcp/tools-feeds-invite.test.ts | 31 +++----------- server/tests/unit/mcp/tools-files.test.ts | 31 +++----------- .../tests/unit/mcp/tools-help-addons.test.ts | 34 ++++----------- server/tests/unit/mcp/tools-journey.test.ts | 31 +++----------- .../unit/mcp/tools-leg-transport-mode.test.ts | 31 +++----------- server/tests/unit/mcp/tools-memories.test.ts | 31 +++----------- server/tests/unit/mcp/tools-notes.test.ts | 31 +++----------- .../unit/mcp/tools-notifications.test.ts | 31 +++----------- .../unit/mcp/tools-packing-advanced.test.ts | 31 +++----------- server/tests/unit/mcp/tools-packing.test.ts | 31 +++----------- server/tests/unit/mcp/tools-places.test.ts | 36 +++------------- .../unit/mcp/tools-plugin-search.test.ts | 33 +++------------ server/tests/unit/mcp/tools-prompts.test.ts | 38 ++++------------- .../tests/unit/mcp/tools-reservations.test.ts | 31 +++----------- server/tests/unit/mcp/tools-roadtrip.test.ts | 32 +++----------- server/tests/unit/mcp/tools-settings.test.ts | 31 +++----------- server/tests/unit/mcp/tools-share.test.ts | 31 +++----------- .../unit/mcp/tools-tags-maps-weather.test.ts | 31 +++----------- server/tests/unit/mcp/tools-todos.test.ts | 31 +++----------- server/tests/unit/mcp/tools-transit.test.ts | 33 +++------------ .../tests/unit/mcp/tools-transports.test.ts | 31 +++----------- .../tests/unit/mcp/tools-trip-members.test.ts | 31 +++----------- .../unit/mcp/tools-trip-warnings.test.ts | 33 +++------------ server/tests/unit/mcp/tools-trips.test.ts | 31 +++----------- server/tests/unit/mcp/tools-vacay.test.ts | 33 +++------------ 38 files changed, 201 insertions(+), 1013 deletions(-) diff --git a/server/tests/unit/mcp/tools-addon-gating.test.ts b/server/tests/unit/mcp/tools-addon-gating.test.ts index a87489144..83a75a0f1 100644 --- a/server/tests/unit/mcp/tools-addon-gating.test.ts +++ b/server/tests/unit/mcp/tools-addon-gating.test.ts @@ -2,27 +2,13 @@ * Unit tests for MCP addon gating and scope enforcement in tools. */ import { describe, it, expect, vi, beforeAll, beforeEach, afterAll } from 'vitest'; +import { db as testDb } from '../../../src/db/database'; -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', @@ -33,19 +19,12 @@ const { broadcastMock } = vi.hoisted(() => ({ broadcastMock: vi.fn() })); vi.mock('../../../src/websocket', () => ({ broadcast: broadcastMock })); -import { createTables } from '../../../src/db/schema'; -import { runMigrations } from '../../../src/db/migrations'; import { resetTestDb } from '../../helpers/test-db'; import { createUser, createTrip } from '../../helpers/factories'; import { createMcpHarness, parseToolResult, type McpHarness } from '../../helpers/mcp-harness'; import { setAddonEnabled } from '../../helpers/test-db'; import { ADDON_IDS } from '../../../src/addons'; -beforeAll(() => { - createTables(testDb); - runMigrations(testDb); -}); - beforeEach(() => { resetTestDb(testDb); broadcastMock.mockClear(); diff --git a/server/tests/unit/mcp/tools-airtrail.test.ts b/server/tests/unit/mcp/tools-airtrail.test.ts index d6172724d..f00dc4c4c 100644 --- a/server/tests/unit/mcp/tools-airtrail.test.ts +++ b/server/tests/unit/mcp/tools-airtrail.test.ts @@ -9,27 +9,13 @@ * reservations land in the test DB. */ import { describe, it, expect, vi, beforeAll, beforeEach, afterAll } from 'vitest'; +import { db as testDb } from '../../../src/db/database'; -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', @@ -39,8 +25,6 @@ vi.mock('../../../src/config', () => ({ const { broadcastMock } = vi.hoisted(() => ({ broadcastMock: vi.fn() })); vi.mock('../../../src/websocket', () => ({ broadcast: broadcastMock })); -import { createTables } from '../../../src/db/schema'; -import { runMigrations } from '../../../src/db/migrations'; import { resetTestDb, setAddonEnabled } from '../../helpers/test-db'; import { createUser, createTrip, addTripMember } from '../../helpers/factories'; import { createMcpHarness, parseToolResult, type McpHarness } from '../../helpers/mcp-harness'; @@ -64,11 +48,6 @@ beforeAll(async () => { // the prototype. Everything below listFlights (creds, mapper, dedupe) is real. const listFlightsMock = vi.spyOn(AirtrailClient.prototype, 'listFlights'); -beforeAll(() => { - createTables(testDb); - runMigrations(testDb); -}); - beforeEach(async () => { resetTestDb(testDb); broadcastMock.mockClear(); diff --git a/server/tests/unit/mcp/tools-assignments-reservations-extra.test.ts b/server/tests/unit/mcp/tools-assignments-reservations-extra.test.ts index 5c00fd898..9f86b71f5 100644 --- a/server/tests/unit/mcp/tools-assignments-reservations-extra.test.ts +++ b/server/tests/unit/mcp/tools-assignments-reservations-extra.test.ts @@ -3,27 +3,13 @@ * move_assignment, get_assignment_participants, set_assignment_participants, reorder_reservations. */ import { describe, it, expect, vi, beforeAll, beforeEach, afterAll } from 'vitest'; +import { db as testDb } from '../../../src/db/database'; -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', @@ -33,17 +19,10 @@ vi.mock('../../../src/config', () => ({ const { broadcastMock } = vi.hoisted(() => ({ broadcastMock: vi.fn() })); vi.mock('../../../src/websocket', () => ({ broadcast: broadcastMock })); -import { createTables } from '../../../src/db/schema'; -import { runMigrations } from '../../../src/db/migrations'; import { resetTestDb } from '../../helpers/test-db'; import { createUser, createTrip, createDay, createPlace, createDayAssignment, createReservation } from '../../helpers/factories'; import { createMcpHarness, parseToolResult, type McpHarness } from '../../helpers/mcp-harness'; -beforeAll(() => { - createTables(testDb); - runMigrations(testDb); -}); - beforeEach(() => { resetTestDb(testDb); broadcastMock.mockClear(); diff --git a/server/tests/unit/mcp/tools-assignments.test.ts b/server/tests/unit/mcp/tools-assignments.test.ts index 91b2786bc..9bd2b0f9f 100644 --- a/server/tests/unit/mcp/tools-assignments.test.ts +++ b/server/tests/unit/mcp/tools-assignments.test.ts @@ -3,27 +3,13 @@ * reorder_day_assignments, update_assignment_time, update_assignment_notes. */ import { describe, it, expect, vi, beforeAll, beforeEach, afterAll } from 'vitest'; +import { db as testDb } from '../../../src/db/database'; -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', @@ -33,8 +19,6 @@ vi.mock('../../../src/config', () => ({ const { broadcastMock } = vi.hoisted(() => ({ broadcastMock: vi.fn() })); vi.mock('../../../src/websocket', () => ({ broadcast: broadcastMock })); -import { createTables } from '../../../src/db/schema'; -import { runMigrations } from '../../../src/db/migrations'; import { resetTestDb } from '../../helpers/test-db'; import { createUser, createTrip, createDay, createPlace, createDayAssignment, createJourney } from '../../helpers/factories'; import { createMcpHarness, parseToolResult, type McpHarness } from '../../helpers/mcp-harness'; @@ -47,11 +31,6 @@ function skeletonFor(journeyId: number, placeId: number) { return testDb.prepare('SELECT * FROM journey_entries WHERE journey_id = ? AND source_place_id = ?').get(journeyId, placeId) as any; } -beforeAll(() => { - createTables(testDb); - runMigrations(testDb); -}); - beforeEach(() => { resetTestDb(testDb); broadcastMock.mockClear(); diff --git a/server/tests/unit/mcp/tools-atlas-expanded.test.ts b/server/tests/unit/mcp/tools-atlas-expanded.test.ts index ae2fcc98b..c7455426e 100644 --- a/server/tests/unit/mcp/tools-atlas-expanded.test.ts +++ b/server/tests/unit/mcp/tools-atlas-expanded.test.ts @@ -5,27 +5,13 @@ * Also covers resources trek://atlas/stats and trek://atlas/regions. */ import { describe, it, expect, vi, beforeAll, beforeEach, afterAll } from 'vitest'; +import { db as testDb } from '../../../src/db/database'; -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', @@ -36,19 +22,12 @@ const { broadcastMock } = vi.hoisted(() => ({ broadcastMock: vi.fn() })); vi.mock('../../../src/websocket', () => ({ broadcast: broadcastMock })); -import { createTables } from '../../../src/db/schema'; -import { runMigrations } from '../../../src/db/migrations'; import { resetTestDb } from '../../helpers/test-db'; import { createUser, createBucketListItem, createVisitedCountry, createTrip, createReservation } from '../../helpers/factories'; import { createMcpHarness, parseToolResult, parseResourceResult, type McpHarness } from '../../helpers/mcp-harness'; import { setAddonEnabled } from '../../helpers/test-db'; import { ADDON_IDS } from '../../../src/addons'; -beforeAll(() => { - createTables(testDb); - runMigrations(testDb); -}); - beforeEach(() => { setAddonEnabled(testDb, ADDON_IDS.ATLAS, true); resetTestDb(testDb); diff --git a/server/tests/unit/mcp/tools-atlas.test.ts b/server/tests/unit/mcp/tools-atlas.test.ts index e20d1c981..bfea120b1 100644 --- a/server/tests/unit/mcp/tools-atlas.test.ts +++ b/server/tests/unit/mcp/tools-atlas.test.ts @@ -3,27 +3,13 @@ * mark_country_visited, unmark_country_visited, create_bucket_list_item, delete_bucket_list_item. */ import { describe, it, expect, vi, beforeAll, beforeEach, afterAll } from 'vitest'; +import { db as testDb } from '../../../src/db/database'; -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', @@ -32,17 +18,10 @@ vi.mock('../../../src/config', () => ({ vi.mock('../../../src/websocket', () => ({ broadcast: vi.fn() })); -import { createTables } from '../../../src/db/schema'; -import { runMigrations } from '../../../src/db/migrations'; import { resetTestDb } from '../../helpers/test-db'; import { createUser, createBucketListItem, createVisitedCountry } from '../../helpers/factories'; import { createMcpHarness, parseToolResult, type McpHarness } from '../../helpers/mcp-harness'; -beforeAll(() => { - createTables(testDb); - runMigrations(testDb); -}); - beforeEach(() => { resetTestDb(testDb); delete process.env.DEMO_MODE; diff --git a/server/tests/unit/mcp/tools-budget-advanced.test.ts b/server/tests/unit/mcp/tools-budget-advanced.test.ts index 801c15cb7..1468e1110 100644 --- a/server/tests/unit/mcp/tools-budget-advanced.test.ts +++ b/server/tests/unit/mcp/tools-budget-advanced.test.ts @@ -4,27 +4,13 @@ * Resources: trek://trips/{tripId}/budget/per-person, trek://trips/{tripId}/budget/settlement. */ import { describe, it, expect, vi, beforeAll, beforeEach, afterAll } from 'vitest'; +import { db as testDb } from '../../../src/db/database'; -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', @@ -34,8 +20,6 @@ vi.mock('../../../src/config', () => ({ const { broadcastMock } = vi.hoisted(() => ({ broadcastMock: vi.fn() })); vi.mock('../../../src/websocket', () => ({ broadcast: broadcastMock })); -import { createTables } from '../../../src/db/schema'; -import { runMigrations } from '../../../src/db/migrations'; import { resetTestDb } from '../../helpers/test-db'; import { createUser, createTrip, createBudgetItem, addTripMember } from '../../helpers/factories'; import { createMcpHarness, parseToolResult, parseResourceResult, type McpHarness } from '../../helpers/mcp-harness'; @@ -50,11 +34,6 @@ import type { User } from '../../../src/types'; import { createTestUnitOfWork, createTestAppSettingsRepo } from '../../helpers/test-uow'; import { budgetRepoArgs } from '../../helpers/budget-repos'; -beforeAll(() => { - createTables(testDb); - runMigrations(testDb); -}); - beforeEach(() => { resetTestDb(testDb); broadcastMock.mockClear(); diff --git a/server/tests/unit/mcp/tools-budget.test.ts b/server/tests/unit/mcp/tools-budget.test.ts index a5084b376..219d13622 100644 --- a/server/tests/unit/mcp/tools-budget.test.ts +++ b/server/tests/unit/mcp/tools-budget.test.ts @@ -5,27 +5,13 @@ * tools-budget-advanced.test.ts). */ import { describe, it, expect, vi, beforeAll, beforeEach, afterAll } from 'vitest'; +import { db as testDb } from '../../../src/db/database'; -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', @@ -35,17 +21,10 @@ vi.mock('../../../src/config', () => ({ const { broadcastMock } = vi.hoisted(() => ({ broadcastMock: vi.fn() })); vi.mock('../../../src/websocket', () => ({ broadcast: broadcastMock })); -import { createTables } from '../../../src/db/schema'; -import { runMigrations } from '../../../src/db/migrations'; import { resetTestDb } from '../../helpers/test-db'; import { createUser, createTrip, createBudgetItem, createPlace, addTripMember } from '../../helpers/factories'; import { createMcpHarness, parseToolResult, parseResourceResult, type McpHarness } from '../../helpers/mcp-harness'; -beforeAll(() => { - createTables(testDb); - runMigrations(testDb); -}); - beforeEach(() => { resetTestDb(testDb); broadcastMock.mockClear(); diff --git a/server/tests/unit/mcp/tools-categories.test.ts b/server/tests/unit/mcp/tools-categories.test.ts index 4a6d76ec5..b496bcdfc 100644 --- a/server/tests/unit/mcp/tools-categories.test.ts +++ b/server/tests/unit/mcp/tools-categories.test.ts @@ -8,25 +8,13 @@ * registerResources fan-out anymore). */ import { describe, it, expect, vi, beforeAll, beforeEach, afterAll } from 'vitest'; +import { db as testDb } from '../../../src/db/database'; -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', @@ -36,8 +24,6 @@ vi.mock('../../../src/config', () => ({ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp'; import { Client } from '@modelcontextprotocol/sdk/client/index'; import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory'; -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 { createMcpHarness, parseToolResult, parseResourceResult, type McpHarness } from '../../helpers/mcp-harness'; @@ -51,12 +37,7 @@ import { DemoService } from '../../../src/nest/common/demo.service'; import { PermissionsService } from '../../../src/nest/permissions/permissions.service'; import { RealtimeService } from '../../../src/nest/realtime/realtime.service'; import { McpToolGuardsService } from '../../../src/nest/mcp-shared/mcp-tool-guards.service'; -import { createTestUnitOfWork, createTestAppSettingsRepo, createTestCategoriesRepo, sharedTestOrm } from '../../helpers/test-uow'; - -beforeAll(() => { - createTables(testDb); - runMigrations(testDb); -}); +import { createTestUnitOfWork, createTestAppSettingsRepo, createTestCategoriesRepo, createTestTripsRepo, createTestUsersRepo, sharedTestOrm } from '../../helpers/test-uow'; beforeEach(() => { resetTestDb(testDb); @@ -88,7 +69,7 @@ beforeAll(async () => { new CategoriesService(await createTestCategoriesRepo(categoriesDb.connection)), categoriesDb, new RuntimeEnvService(), - new McpToolGuardsService(categoriesDb, new PermissionsService(await createTestAppSettingsRepo(categoriesDb.connection), await createTestUnitOfWork(categoriesDb.connection)), new RealtimeService()), + new McpToolGuardsService(await createTestTripsRepo(categoriesDb.connection), await createTestUsersRepo(categoriesDb.connection), new PermissionsService(await createTestAppSettingsRepo(categoriesDb.connection), await createTestUnitOfWork(categoriesDb.connection)), new RealtimeService()), new DemoService(new RuntimeEnvService(), categoriesEm), ); }); diff --git a/server/tests/unit/mcp/tools-collab-polls-chat.test.ts b/server/tests/unit/mcp/tools-collab-polls-chat.test.ts index c4577fa06..5230f1855 100644 --- a/server/tests/unit/mcp/tools-collab-polls-chat.test.ts +++ b/server/tests/unit/mcp/tools-collab-polls-chat.test.ts @@ -7,27 +7,13 @@ * Resources: trek://trips/{tripId}/collab/polls, trek://trips/{tripId}/collab/messages. */ import { describe, it, expect, vi, beforeAll, beforeEach, afterAll } from 'vitest'; +import { db as testDb } from '../../../src/db/database'; -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,19 +24,12 @@ const { broadcastMock } = vi.hoisted(() => ({ broadcastMock: vi.fn() })); vi.mock('../../../src/websocket', () => ({ broadcast: broadcastMock })); -import { createTables } from '../../../src/db/schema'; -import { runMigrations } from '../../../src/db/migrations'; import { resetTestDb } from '../../helpers/test-db'; import { createUser, createTrip } from '../../helpers/factories'; import { createMcpHarness, parseToolResult, parseResourceResult, type McpHarness } from '../../helpers/mcp-harness'; import { setAddonEnabled } from '../../helpers/test-db'; import { ADDON_IDS } from '../../../src/addons'; -beforeAll(() => { - createTables(testDb); - runMigrations(testDb); -}); - beforeEach(() => { setAddonEnabled(testDb, ADDON_IDS.COLLAB, true); resetTestDb(testDb); diff --git a/server/tests/unit/mcp/tools-collections.test.ts b/server/tests/unit/mcp/tools-collections.test.ts index da3efb6ba..6d76fb28c 100644 --- a/server/tests/unit/mcp/tools-collections.test.ts +++ b/server/tests/unit/mcp/tools-collections.test.ts @@ -12,28 +12,14 @@ * unconditionally while REST and the plugin host gated; the port fixed that). */ import { describe, it, expect, vi, beforeAll, beforeEach, afterAll } from 'vitest'; +import { db as testDb } from '../../../src/db/database'; -const { testDb, dbMock, broadcastToUser } = 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 broadcastToUser = vi.fn(); - const mock = { - db, - closeDb: () => {}, - reinitialize: () => {}, - getPlaceWithTags: () => null, - canAccessTrip: (tripId: number | string, 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: number | string, userId: number) => - !!db.prepare('SELECT id FROM trips WHERE id = ? AND user_id = ?').get(tripId, userId), - }; - return { testDb: db, dbMock: mock, broadcastToUser }; +const { broadcastToUser } = vi.hoisted(() => ({ broadcastToUser: 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', @@ -42,8 +28,6 @@ vi.mock('../../../src/config', () => ({ vi.mock('../../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser })); const { notifSend } = vi.hoisted(() => ({ notifSend: vi.fn().mockResolvedValue(undefined) })); -import { createTables } from '../../../src/db/schema'; -import { runMigrations } from '../../../src/db/migrations'; import { createUser, createTrip, createPlace, createCategory } from '../../helpers/factories'; import { createMcpHarness, parseToolResult, type McpHarness } from '../../helpers/mcp-harness'; import { ADDON_IDS } from '../../../src/addons'; @@ -66,8 +50,6 @@ function clearCollections() { } beforeAll(async () => { - createTables(testDb); - runMigrations(testDb); // The collections addon is seeded DISABLED by default and every tool rides // the `when:` addon gate (post-fold quirk fix) — enable it for the suite. testDb.prepare('UPDATE addons SET enabled = 1 WHERE id = ?').run(ADDON_IDS.COLLECTIONS); diff --git a/server/tests/unit/mcp/tools-days-accommodations.test.ts b/server/tests/unit/mcp/tools-days-accommodations.test.ts index 833559683..0f9e77126 100644 --- a/server/tests/unit/mcp/tools-days-accommodations.test.ts +++ b/server/tests/unit/mcp/tools-days-accommodations.test.ts @@ -7,38 +7,13 @@ * ported to the DI-discovered DaysMcp). */ import { describe, it, expect, vi, beforeAll, beforeEach, afterAll } from 'vitest'; +import { db as testDb } from '../../../src/db/database'; -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: () => {}, - // Real-SQL implementation: create_place_accommodation's createPlace - // returns getPlaceWithTags(placeId) — a null stub would make the tool's - // atomic branch throw before the accommodation insert. - getPlaceWithTags: (placeId: number | string) => { - const place = 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) as { category_id: number | null; category_name: string; category_color: string; category_icon: string } | undefined; - 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,17 +23,10 @@ vi.mock('../../../src/config', () => ({ const { broadcastMock } = vi.hoisted(() => ({ broadcastMock: vi.fn() })); vi.mock('../../../src/websocket', () => ({ broadcast: broadcastMock })); -import { createTables } from '../../../src/db/schema'; -import { runMigrations } from '../../../src/db/migrations'; import { resetTestDb } from '../../helpers/test-db'; import { createUser, createTrip, createDay, createPlace, createDayAccommodation } from '../../helpers/factories'; import { createMcpHarness, parseToolResult, parseResourceResult, type McpHarness } from '../../helpers/mcp-harness'; -beforeAll(() => { - createTables(testDb); - runMigrations(testDb); -}); - beforeEach(() => { resetTestDb(testDb); broadcastMock.mockClear(); diff --git a/server/tests/unit/mcp/tools-days.test.ts b/server/tests/unit/mcp/tools-days.test.ts index d3057383e..2ef8834a5 100644 --- a/server/tests/unit/mcp/tools-days.test.ts +++ b/server/tests/unit/mcp/tools-days.test.ts @@ -5,27 +5,13 @@ * create_day's plain append is covered in tools-days-accommodations.test.ts. */ import { describe, it, expect, vi, beforeAll, beforeEach, afterAll } from 'vitest'; +import { db as testDb } from '../../../src/db/database'; -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', @@ -35,19 +21,12 @@ vi.mock('../../../src/config', () => ({ const { broadcastMock } = vi.hoisted(() => ({ broadcastMock: vi.fn() })); vi.mock('../../../src/websocket', () => ({ broadcast: broadcastMock })); -import { createTables } from '../../../src/db/schema'; -import { runMigrations } from '../../../src/db/migrations'; import { resetTestDb } from '../../helpers/test-db'; import { createUser, createTrip, createDay, createPlace, createDayAssignment, createDayAccommodation, } from '../../helpers/factories'; import { createMcpHarness, parseToolResult, parseResourceResult, type McpHarness } from '../../helpers/mcp-harness'; -beforeAll(() => { - createTables(testDb); - runMigrations(testDb); -}); - beforeEach(() => { resetTestDb(testDb); broadcastMock.mockClear(); diff --git a/server/tests/unit/mcp/tools-feeds-invite.test.ts b/server/tests/unit/mcp/tools-feeds-invite.test.ts index e2c9521f9..5302eed4b 100644 --- a/server/tests/unit/mcp/tools-feeds-invite.test.ts +++ b/server/tests/unit/mcp/tools-feeds-invite.test.ts @@ -8,27 +8,13 @@ * well as writes, and demo mode refused on every write. */ import { describe, it, expect, vi, beforeAll, beforeEach, afterAll } from 'vitest'; +import { db as testDb } from '../../../src/db/database'; -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: number | string, 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: number | string, 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', @@ -36,8 +22,6 @@ vi.mock('../../../src/config', () => ({ })); vi.mock('../../../src/websocket', () => ({ broadcast: vi.fn() })); -import { createTables } from '../../../src/db/schema'; -import { runMigrations } from '../../../src/db/migrations'; import { resetTestDb } from '../../helpers/test-db'; import { createUser, createTrip, addTripMember } from '../../helpers/factories'; import { invalidatePermissionsCache } from '../../../src/nest/permissions/permissions-cache'; @@ -78,11 +62,6 @@ interface FeedResult { feed_url: string | null } interface InviteLink { token: string; expires_at: string | null; created_at: string; url: string } interface InviteResult { invite_link: InviteLink | null } -beforeAll(() => { - createTables(testDb); - runMigrations(testDb); -}); - beforeEach(() => { resetTestDb(testDb); // trip_invite_tokens is not in the shared helper's RESET_TABLES, and trip ids diff --git a/server/tests/unit/mcp/tools-files.test.ts b/server/tests/unit/mcp/tools-files.test.ts index 2fbcbb9e3..e18b15ee1 100644 --- a/server/tests/unit/mcp/tools-files.test.ts +++ b/server/tests/unit/mcp/tools-files.test.ts @@ -9,29 +9,15 @@ * on files:read alone. */ import { describe, it, expect, vi, beforeAll, beforeEach, afterAll } from 'vitest'; +import { db as testDb } from '../../../src/db/database'; import fs from 'node:fs'; import nodePath from 'node: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: () => 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', () => 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', @@ -53,8 +39,6 @@ vi.mock('../../helpers/storage-fixture', async (importOriginal) => { return { makeStorageFixture: () => shared }; }); -import { createTables } from '../../../src/db/schema'; -import { runMigrations } from '../../../src/db/migrations'; import { resetTestDb } from '../../helpers/test-db'; import { invalidatePermissionsCache } from '../../../src/nest/permissions/permissions-cache'; import { createUser, createTrip, createDay, createPlace, createDayAssignment, createReservation, addTripMember } from '../../helpers/factories'; @@ -65,11 +49,6 @@ import { FilesMcp } from '../../../src/nest/files/files.mcp'; import { FILE_CONTENT_MAX } from '../../../src/nest/files/files.service'; import { StorageService } from '../../../src/nest/storage/storage.service'; -beforeAll(() => { - createTables(testDb); - runMigrations(testDb); -}); - beforeEach(() => { resetTestDb(testDb); broadcastMock.mockClear(); diff --git a/server/tests/unit/mcp/tools-help-addons.test.ts b/server/tests/unit/mcp/tools-help-addons.test.ts index 8b2ee117f..da0aa311d 100644 --- a/server/tests/unit/mcp/tools-help-addons.test.ts +++ b/server/tests/unit/mcp/tools-help-addons.test.ts @@ -3,27 +3,13 @@ * list_help_topics, get_help_page, list_addons. */ import { describe, it, expect, vi, beforeAll, beforeEach, afterAll } from 'vitest'; +import { db as testDb } from '../../../src/db/database'; -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', @@ -54,8 +40,6 @@ const { wiki } = vi.hoisted(() => { }); vi.mock('../../../src/nest/help/wiki', () => wiki); -import { createTables } from '../../../src/db/schema'; -import { runMigrations } from '../../../src/db/migrations'; import { resetTestDb, setAddonEnabled } from '../../helpers/test-db'; import { createUser } from '../../helpers/factories'; import { createMcpHarness, parseToolResult, type McpHarness } from '../../helpers/mcp-harness'; @@ -112,11 +96,6 @@ function toolText(result: unknown): string { return content.find((c) => c.type === 'text')?.text ?? ''; } -beforeAll(() => { - createTables(testDb); - runMigrations(testDb); -}); - beforeEach(() => { resetTestDb(testDb); broadcastMock.mockClear(); @@ -328,6 +307,9 @@ describe('Tool: list_addons', () => { const { user } = createUser(testDb); // Providers ride the journey addon now; migration 84 seeds it off. setAddonEnabled(testDb, ADDON_IDS.JOURNEY, true); + // PhotoProviderSeeder seeds immich disabled by default (enabled: 0) — flip + // it on the way the admin panel would, same idiom as setAddonEnabled above. + testDb.prepare("UPDATE photo_providers SET enabled = 1 WHERE id = 'immich'").run(); const row = testDb.prepare('SELECT name FROM photo_providers WHERE id = ?').get('immich') as { name: string; }; diff --git a/server/tests/unit/mcp/tools-journey.test.ts b/server/tests/unit/mcp/tools-journey.test.ts index cdabc0b06..df5124c71 100644 --- a/server/tests/unit/mcp/tools-journey.test.ts +++ b/server/tests/unit/mcp/tools-journey.test.ts @@ -4,27 +4,13 @@ * and create_journey_entry returns the enriched entry (parsed tags, photos array). */ import { describe, it, expect, vi, beforeAll, beforeEach, afterAll } from 'vitest'; +import { db as testDb } from '../../../src/db/database'; -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', @@ -51,19 +37,12 @@ vi.mock('../../../src/nest/atlas/atlas-geo', async (importOriginal) => ({ }, })); -import { createTables } from '../../../src/db/schema'; -import { runMigrations } from '../../../src/db/migrations'; import { resetTestDb } from '../../helpers/test-db'; import { createUser, createTrip } from '../../helpers/factories'; import { setAddonEnabled } from '../../helpers/test-db'; import { ADDON_IDS } from '../../../src/addons'; import { createMcpHarness, parseToolResult, parseResourceResult, type McpHarness } from '../../helpers/mcp-harness'; -beforeAll(() => { - createTables(testDb); - runMigrations(testDb); -}); - beforeEach(() => { resetTestDb(testDb); // The `when:` gate reads the injected AddonsService, so the toggle is the row diff --git a/server/tests/unit/mcp/tools-leg-transport-mode.test.ts b/server/tests/unit/mcp/tools-leg-transport-mode.test.ts index 00f95c59e..e068286ba 100644 --- a/server/tests/unit/mcp/tools-leg-transport-mode.test.ts +++ b/server/tests/unit/mcp/tools-leg-transport-mode.test.ts @@ -3,27 +3,13 @@ * set_day_default_transport_mode. */ import { describe, it, expect, vi, beforeAll, beforeEach, afterAll } from 'vitest'; +import { db as testDb } from '../../../src/db/database'; -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', @@ -33,17 +19,10 @@ vi.mock('../../../src/config', () => ({ const { broadcastMock } = vi.hoisted(() => ({ broadcastMock: vi.fn() })); vi.mock('../../../src/websocket', () => ({ broadcast: broadcastMock })); -import { createTables } from '../../../src/db/schema'; -import { runMigrations } from '../../../src/db/migrations'; import { resetTestDb } from '../../helpers/test-db'; import { createUser, createTrip, createDay, createPlace, createDayAssignment } from '../../helpers/factories'; import { createMcpHarness, parseToolResult, type McpHarness } from '../../helpers/mcp-harness'; -beforeAll(() => { - createTables(testDb); - runMigrations(testDb); -}); - beforeEach(() => { resetTestDb(testDb); broadcastMock.mockClear(); diff --git a/server/tests/unit/mcp/tools-memories.test.ts b/server/tests/unit/mcp/tools-memories.test.ts index f5bdae4ed..45fc7e70a 100644 --- a/server/tests/unit/mcp/tools-memories.test.ts +++ b/server/tests/unit/mcp/tools-memories.test.ts @@ -8,27 +8,13 @@ * route performs, and what lands in the DB. */ import { describe, it, expect, vi, beforeAll, beforeEach, afterAll } from 'vitest'; +import { db as testDb } from '../../../src/db/database'; -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,8 +24,6 @@ vi.mock('../../../src/config', () => ({ const { broadcastMock } = vi.hoisted(() => ({ broadcastMock: vi.fn() })); vi.mock('../../../src/websocket', () => ({ broadcast: broadcastMock, broadcastToUser: broadcastMock })); -import { createTables } from '../../../src/db/schema'; -import { runMigrations } from '../../../src/db/migrations'; import { resetTestDb, setAddonEnabled } from '../../helpers/test-db'; import { createUser, createJourney, createJourneyEntry, addJourneyContributor } from '../../helpers/factories'; import { ADDON_IDS } from '../../../src/addons'; @@ -75,11 +59,6 @@ function removeProvider(id: string): void { testDb.prepare('DELETE FROM photo_providers WHERE id = ?').run(id); } -beforeAll(() => { - createTables(testDb); - runMigrations(testDb); -}); - beforeEach(() => { resetTestDb(testDb); setAddonEnabled(testDb, ADDON_IDS.JOURNEY, true); diff --git a/server/tests/unit/mcp/tools-notes.test.ts b/server/tests/unit/mcp/tools-notes.test.ts index 83f5bd5f5..0e06fee0c 100644 --- a/server/tests/unit/mcp/tools-notes.test.ts +++ b/server/tests/unit/mcp/tools-notes.test.ts @@ -6,27 +6,13 @@ * DI-discovered via the same registry). */ import { describe, it, expect, vi, beforeAll, beforeEach, afterAll } from 'vitest'; +import { db as testDb } from '../../../src/db/database'; -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', @@ -43,17 +29,10 @@ vi.mock('fs', async (importOriginal) => { return { ...actual, unlinkSync: unlinkSyncMock }; }); -import { createTables } from '../../../src/db/schema'; -import { runMigrations } from '../../../src/db/migrations'; import { resetTestDb } from '../../helpers/test-db'; import { createUser, createTrip, createDay, createDayNote, createCollabNote } from '../../helpers/factories'; import { createMcpHarness, parseToolResult, parseResourceResult, type McpHarness } from '../../helpers/mcp-harness'; -beforeAll(() => { - createTables(testDb); - runMigrations(testDb); -}); - beforeEach(() => { resetTestDb(testDb); broadcastMock.mockClear(); diff --git a/server/tests/unit/mcp/tools-notifications.test.ts b/server/tests/unit/mcp/tools-notifications.test.ts index 23c400ce2..586bc5e41 100644 --- a/server/tests/unit/mcp/tools-notifications.test.ts +++ b/server/tests/unit/mcp/tools-notifications.test.ts @@ -10,27 +10,13 @@ * legacy registerResources fan-out anymore). */ import { describe, it, expect, vi, beforeAll, beforeEach, afterAll } from 'vitest'; +import { db as testDb } from '../../../src/db/database'; -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', @@ -39,17 +25,10 @@ vi.mock('../../../src/config', () => ({ vi.mock('../../../src/websocket', () => ({ broadcast: vi.fn() })); -import { createTables } from '../../../src/db/schema'; -import { runMigrations } from '../../../src/db/migrations'; import { resetTestDb } from '../../helpers/test-db'; import { createUser } from '../../helpers/factories'; import { createMcpHarness, parseToolResult, parseResourceResult, type McpHarness } from '../../helpers/mcp-harness'; -beforeAll(() => { - createTables(testDb); - runMigrations(testDb); -}); - beforeEach(() => { resetTestDb(testDb); delete process.env.DEMO_MODE; diff --git a/server/tests/unit/mcp/tools-packing-advanced.test.ts b/server/tests/unit/mcp/tools-packing-advanced.test.ts index 7e477e383..32dd54d4c 100644 --- a/server/tests/unit/mcp/tools-packing-advanced.test.ts +++ b/server/tests/unit/mcp/tools-packing-advanced.test.ts @@ -7,27 +7,13 @@ * live in tools-packing.test.ts. */ import { describe, it, expect, vi, beforeAll, beforeEach, afterAll } from 'vitest'; +import { db as testDb } from '../../../src/db/database'; -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', @@ -37,17 +23,10 @@ vi.mock('../../../src/config', () => ({ const { broadcastMock } = vi.hoisted(() => ({ broadcastMock: vi.fn() })); vi.mock('../../../src/websocket', () => ({ broadcast: broadcastMock })); -import { createTables } from '../../../src/db/schema'; -import { runMigrations } from '../../../src/db/migrations'; import { resetTestDb } from '../../helpers/test-db'; import { createUser, createAdmin, createTrip, createPackingItem } from '../../helpers/factories'; import { createMcpHarness, parseToolResult, type McpHarness } from '../../helpers/mcp-harness'; -beforeAll(() => { - createTables(testDb); - runMigrations(testDb); -}); - beforeEach(() => { resetTestDb(testDb); broadcastMock.mockClear(); diff --git a/server/tests/unit/mcp/tools-packing.test.ts b/server/tests/unit/mcp/tools-packing.test.ts index af0d215b6..9e24a910d 100644 --- a/server/tests/unit/mcp/tools-packing.test.ts +++ b/server/tests/unit/mcp/tools-packing.test.ts @@ -12,27 +12,13 @@ * legacy registerResources fan-out anymore). */ import { describe, it, expect, vi, beforeAll, beforeEach, afterAll } from 'vitest'; +import { db as testDb } from '../../../src/db/database'; -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', @@ -42,18 +28,11 @@ vi.mock('../../../src/config', () => ({ const { broadcastMock } = vi.hoisted(() => ({ broadcastMock: vi.fn() })); vi.mock('../../../src/websocket', () => ({ broadcast: broadcastMock })); -import { createTables } from '../../../src/db/schema'; -import { runMigrations } from '../../../src/db/migrations'; import { resetTestDb } from '../../helpers/test-db'; import { createUser, createTrip, createPackingItem, addTripMember } from '../../helpers/factories'; import { createMcpHarness, parseToolResult, parseResourceResult, type McpHarness } from '../../helpers/mcp-harness'; import { ADDON_IDS } from '../../../src/addons'; -beforeAll(() => { - createTables(testDb); - runMigrations(testDb); -}); - beforeEach(() => { resetTestDb(testDb); broadcastMock.mockClear(); diff --git a/server/tests/unit/mcp/tools-places.test.ts b/server/tests/unit/mcp/tools-places.test.ts index e86cdf11e..07e5d71a4 100644 --- a/server/tests/unit/mcp/tools-places.test.ts +++ b/server/tests/unit/mcp/tools-places.test.ts @@ -8,32 +8,13 @@ * exercise the @Tool/@ResourceTemplate path instead of the deleted registrar. */ import { describe, it, expect, vi, beforeAll, beforeEach, afterAll } from 'vitest'; +import { db as testDb } from '../../../src/db/database'; -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,8 +29,6 @@ const { searchPlacesMock } = vi.hoisted(() => ({ searchPlacesMock: vi.fn() })); // import enrichment), so the geo calls are stubbed on the prototype — see // beforeEach — rather than through a module mock. -import { createTables } from '../../../src/db/schema'; -import { runMigrations } from '../../../src/db/migrations'; import { resetTestDb } from '../../helpers/test-db'; import { createUser, createTrip, createPlace, createDay, createDayAssignment, createJourney } from '../../helpers/factories'; import { createMcpHarness, parseToolResult, parseResourceResult, type McpHarness } from '../../helpers/mcp-harness'; @@ -68,11 +47,6 @@ function imageOf(placeId: number): string | null { return (testDb.prepare('SELECT image_url FROM places WHERE id = ?').get(placeId) as { image_url: string | null }).image_url; } -beforeAll(() => { - createTables(testDb); - runMigrations(testDb); -}); - beforeEach(() => { resetTestDb(testDb); broadcastMock.mockClear(); diff --git a/server/tests/unit/mcp/tools-plugin-search.test.ts b/server/tests/unit/mcp/tools-plugin-search.test.ts index 8b9b3d05e..b20e4f401 100644 --- a/server/tests/unit/mcp/tools-plugin-search.test.ts +++ b/server/tests/unit/mcp/tools-plugin-search.test.ts @@ -3,34 +3,18 @@ * contributions/plugin-search.mcp.ts), the MCP counterpart of GET /api/plugin-search. */ import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'; +import { db as testDb } from '../../../src/db/database'; -const { testDb, dbMock } = vi.hoisted(() => { - const Database = require('better-sqlite3'); - const db = new Database(':memory:'); - db.exec('PRAGMA foreign_keys = ON'); - const mock = { - db, - closeDb: () => {}, - reinitialize: () => {}, - getPlaceWithTags: () => null, - canAccessTrip: (tripId: number, userId: number) => - db - .prepare( - 'SELECT t.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: number, userId: number) => - !!db.prepare('SELECT id FROM trips WHERE id = ? AND user_id = ?').get(tripId, userId), - }; - return { testDb: db, dbMock: mock }; -}); const { broadcastMock, pluginsEnabled } = vi.hoisted(() => ({ broadcastMock: vi.fn(), pluginsEnabled: vi.fn(() => true), })); -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/websocket', () => ({ broadcast: broadcastMock })); vi.mock('../../../src/config', () => ({ JWT_SECRET: 'test-jwt-secret-for-trek-testing-only', @@ -40,8 +24,6 @@ vi.mock('../../../src/config', () => ({ // The admin kill switch reads live env; drive it from the test instead of the process. vi.mock('../../../src/nest/plugins/kill-switch', () => ({ pluginsEnabled })); -import { runMigrations } from '../../../src/db/migrations'; -import { createTables } from '../../../src/db/schema'; import { createUser } from '../../helpers/factories'; import { createMcpHarness, parseToolResult, type McpHarness } from '../../helpers/mcp-harness'; import { resetTestDb } from '../../helpers/test-db'; @@ -50,11 +32,6 @@ import { PluginHooks } from '../../../src/nest/plugins/plugin-hooks.service'; const providersOfMock = vi.spyOn(PluginHooks.prototype, 'providersOf'); const searchPlacesMock = vi.spyOn(PluginHooks.prototype, 'searchPlaces'); -beforeAll(() => { - createTables(testDb); - runMigrations(testDb); -}); - beforeEach(() => { resetTestDb(testDb); broadcastMock.mockClear(); diff --git a/server/tests/unit/mcp/tools-prompts.test.ts b/server/tests/unit/mcp/tools-prompts.test.ts index 52e43c34b..a4d9d0675 100644 --- a/server/tests/unit/mcp/tools-prompts.test.ts +++ b/server/tests/unit/mcp/tools-prompts.test.ts @@ -8,31 +8,18 @@ * cases used to sidestep that by calling the callbacks directly. */ import { describe, it, expect, vi, beforeAll, beforeEach, afterEach, afterAll } from 'vitest'; +import { db as testDb } from '../../../src/db/database'; +import { CAN_ACCESS_TRIP_SQL } from '../../helpers/db-mock'; import { McpServer } from '@modelcontextprotocol/sdk/server/mcp'; import { Client } from '@modelcontextprotocol/sdk/client/index'; import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory'; import { ErrorCode } from '@modelcontextprotocol/sdk/types'; -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', () => 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', @@ -61,8 +48,6 @@ const { mockGetTripSummary } = vi.hoisted(() => ({ // The prompts read the summary through the injected read model (readModelStub // below wraps the same controllable mock) — trips.bridge is deleted. -import { createTables } from '../../../src/db/schema'; -import { runMigrations } from '../../../src/db/migrations'; import { resetTestDb } from '../../helpers/test-db'; import { createUser, createTrip, addTripMember, createPackingItem, createBudgetItem } from '../../helpers/factories'; import { createTestRegistry } from '../../../src/nest-mcp'; @@ -88,7 +73,7 @@ import type { TodoService } from '../../../src/nest/todo/todo.service'; import type { CollabService } from '../../../src/nest/collab/collab.service'; import { AddonsService } from '../../../src/nest/addons/addons.service'; import { notificationsStub } from '../../helpers/notifications'; -import { createTestUnitOfWork, createTestAppSettingsRepo, createTestTripsRepo, createTestTripMembersRepo, sharedTestOrm, createTestPlacesRepo } from '../../helpers/test-uow'; +import { createTestUnitOfWork, createTestAppSettingsRepo, createTestTripsRepo, createTestTripMembersRepo, createTestUsersRepo, sharedTestOrm, createTestPlacesRepo } from '../../helpers/test-uow'; import { budgetRepoArgs } from '../../helpers/budget-repos'; import { createTestPackingItemsRepo, @@ -105,7 +90,7 @@ import type { EntityManager } from '@mikro-orm/core'; // exercise it through a hand-built registry over a stub TripsService whose // getTripSummary is the same controllable mock the legacy path used. const tripsStub = { - canAccessTrip: (tripId: number, userId: number) => dbMock.canAccessTrip(tripId, userId), + canAccessTrip: (tripId: number, userId: number) => testDb.prepare(CAN_ACCESS_TRIP_SQL).get(userId, tripId, userId), } as unknown as TripsService; // getTripSummary moved to TripReadModelService with the trip split; the mock is // the same controllable one, one constructor slot further along. @@ -140,7 +125,7 @@ let budgetMcp: BudgetMcp; let tripPromptsMcp: TripPromptsMcp; beforeAll(async () => { promptEm = (await sharedTestOrm(testDb)).em; - promptGuards = new McpToolGuardsService(promptDbs(), new PermissionsService(await createTestAppSettingsRepo(testDb), await createTestUnitOfWork(testDb)), new RealtimeService()); + promptGuards = new McpToolGuardsService(await createTestTripsRepo(testDb), await createTestUsersRepo(testDb), new PermissionsService(await createTestAppSettingsRepo(testDb), await createTestUnitOfWork(testDb)), new RealtimeService()); tripsMcp = new TripsMcp( tripsStub, { listItems: () => [] } as unknown as TodoService, @@ -185,11 +170,6 @@ beforeAll(async () => { }); const authMcp = new AuthMcp(); -beforeAll(() => { - createTables(testDb); - runMigrations(testDb); -}); - beforeEach(() => { resetTestDb(testDb); broadcastMock.mockClear(); diff --git a/server/tests/unit/mcp/tools-reservations.test.ts b/server/tests/unit/mcp/tools-reservations.test.ts index 074128ae4..724db226d 100644 --- a/server/tests/unit/mcp/tools-reservations.test.ts +++ b/server/tests/unit/mcp/tools-reservations.test.ts @@ -3,27 +3,13 @@ * delete_reservation, link_hotel_accommodation. */ import { describe, it, expect, vi, beforeAll, beforeEach, afterAll } from 'vitest'; +import { db as testDb } from '../../../src/db/database'; -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', @@ -33,17 +19,10 @@ vi.mock('../../../src/config', () => ({ const { broadcastMock } = vi.hoisted(() => ({ broadcastMock: vi.fn() })); vi.mock('../../../src/websocket', () => ({ broadcast: broadcastMock })); -import { createTables } from '../../../src/db/schema'; -import { runMigrations } from '../../../src/db/migrations'; import { resetTestDb } from '../../helpers/test-db'; import { createUser, createTrip, createDay, createPlace, createReservation, createDayAssignment, createDayAccommodation, createCategory, addTripMember } from '../../helpers/factories'; import { createMcpHarness, parseToolResult, parseResourceResult, type McpHarness } from '../../helpers/mcp-harness'; -beforeAll(() => { - createTables(testDb); - runMigrations(testDb); -}); - beforeEach(() => { resetTestDb(testDb); broadcastMock.mockClear(); diff --git a/server/tests/unit/mcp/tools-roadtrip.test.ts b/server/tests/unit/mcp/tools-roadtrip.test.ts index a3ce90b9b..41f269926 100644 --- a/server/tests/unit/mcp/tools-roadtrip.test.ts +++ b/server/tests/unit/mcp/tools-roadtrip.test.ts @@ -9,28 +9,13 @@ * track check on the batch tool. */ import { describe, it, expect, vi, beforeAll, beforeEach, afterAll } from 'vitest'; +import { db as testDb } from '../../../src/db/database'; -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: unknown, 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: unknown, 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,8 +23,6 @@ vi.mock('../../../src/config', () => ({ })); vi.mock('../../../src/websocket', () => ({ broadcast: vi.fn() })); -import { createTables } from '../../../src/db/schema'; -import { runMigrations } from '../../../src/db/migrations'; import { resetTestDb, setAddonEnabled } from '../../helpers/test-db'; import { createUser, createTrip, createDay, createPlace } from '../../helpers/factories'; import { createMcpHarness, parseToolResult, type McpHarness } from '../../helpers/mcp-harness'; @@ -57,11 +40,6 @@ beforeAll(async () => { savePermissions = permissionsService.savePermissions.bind(permissionsService); }); -beforeAll(() => { - createTables(testDb); - runMigrations(testDb); -}); - beforeEach(() => { resetTestDb(testDb); // resetTestDb leaves the addons table alone, so each run restates it. The diff --git a/server/tests/unit/mcp/tools-settings.test.ts b/server/tests/unit/mcp/tools-settings.test.ts index 850a844d6..7b7c72a20 100644 --- a/server/tests/unit/mcp/tools-settings.test.ts +++ b/server/tests/unit/mcp/tools-settings.test.ts @@ -10,27 +10,13 @@ * that key nor llm_api_key nor an unknown name may be written. */ import { describe, it, expect, vi, beforeAll, beforeEach, afterAll } from 'vitest'; +import { db as testDb } from '../../../src/db/database'; -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', @@ -39,8 +25,6 @@ vi.mock('../../../src/config', () => ({ vi.mock('../../../src/websocket', () => ({ broadcast: vi.fn() })); -import { createTables } from '../../../src/db/schema'; -import { runMigrations } from '../../../src/db/migrations'; import { resetTestDb } from '../../helpers/test-db'; import { createUser } from '../../helpers/factories'; import { createMcpHarness, parseToolResult, type McpHarness } from '../../helpers/mcp-harness'; @@ -50,11 +34,6 @@ import { DISPLAY_PREFERENCE_KEYS } from '../../../src/nest/settings/settings.mcp import { MANAGED_LOCKED_SETTING_KEYS } from '../../../src/nest/common/managed'; import { isAdminOnlyEndpointSetting, ENCRYPTED_SETTING_KEYS, MASKED_SETTING_KEYS } from '../../../src/nest/settings/settings.service'; -beforeAll(() => { - createTables(testDb); - runMigrations(testDb); -}); - beforeEach(() => { resetTestDb(testDb); delete process.env.DEMO_MODE; diff --git a/server/tests/unit/mcp/tools-share.test.ts b/server/tests/unit/mcp/tools-share.test.ts index a21a6ed6e..1e5a5b491 100644 --- a/server/tests/unit/mcp/tools-share.test.ts +++ b/server/tests/unit/mcp/tools-share.test.ts @@ -5,27 +5,13 @@ * ride the canShareTrips predicate (no declarative trips:share mode exists). */ import { describe, it, expect, vi, beforeAll, beforeEach, afterAll } from 'vitest'; +import { db as testDb } from '../../../src/db/database'; -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', @@ -33,17 +19,10 @@ vi.mock('../../../src/config', () => ({ })); vi.mock('../../../src/websocket', () => ({ broadcast: vi.fn() })); -import { createTables } from '../../../src/db/schema'; -import { runMigrations } from '../../../src/db/migrations'; import { resetTestDb } from '../../helpers/test-db'; import { createUser, createTrip } from '../../helpers/factories'; import { createMcpHarness, parseToolResult, type McpHarness } from '../../helpers/mcp-harness'; -beforeAll(() => { - createTables(testDb); - runMigrations(testDb); -}); - beforeEach(() => { resetTestDb(testDb); delete process.env.DEMO_MODE; diff --git a/server/tests/unit/mcp/tools-tags-maps-weather.test.ts b/server/tests/unit/mcp/tools-tags-maps-weather.test.ts index 0c6e1fce7..948310e9d 100644 --- a/server/tests/unit/mcp/tools-tags-maps-weather.test.ts +++ b/server/tests/unit/mcp/tools-tags-maps-weather.test.ts @@ -5,27 +5,13 @@ * get_weather, get_detailed_weather. */ import { describe, it, expect, vi, beforeAll, beforeEach, afterEach, afterAll } from 'vitest'; +import { db as testDb } from '../../../src/db/database'; -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', @@ -56,8 +42,6 @@ vi.mock('../../../src/nest/maps/trek-places.client', async (importOriginal) => ( trekPlacesNearby: trekNearbyMock, })); -import { createTables } from '../../../src/db/schema'; -import { runMigrations } from '../../../src/db/migrations'; import { resetTestDb } from '../../helpers/test-db'; import { createUser } from '../../helpers/factories'; import { createMcpHarness, parseToolResult, type McpHarness } from '../../helpers/mcp-harness'; @@ -94,11 +78,6 @@ vi.spyOn(MapsService.prototype, 'resolveGoogleMapsUrl').mockResolvedValue({ name: 'Paris', } as never); -beforeAll(() => { - createTables(testDb); - runMigrations(testDb); -}); - beforeEach(() => { resetTestDb(testDb); broadcastMock.mockClear(); diff --git a/server/tests/unit/mcp/tools-todos.test.ts b/server/tests/unit/mcp/tools-todos.test.ts index ec2fbfe34..78a35f643 100644 --- a/server/tests/unit/mcp/tools-todos.test.ts +++ b/server/tests/unit/mcp/tools-todos.test.ts @@ -9,27 +9,13 @@ * legacy registerResources fan-out anymore). */ import { describe, it, expect, vi, beforeAll, beforeEach, afterAll } from 'vitest'; +import { db as testDb } from '../../../src/db/database'; -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', @@ -39,18 +25,11 @@ vi.mock('../../../src/config', () => ({ const { broadcastMock } = vi.hoisted(() => ({ broadcastMock: vi.fn() })); vi.mock('../../../src/websocket', () => ({ broadcast: broadcastMock })); -import { createTables } from '../../../src/db/schema'; -import { runMigrations } from '../../../src/db/migrations'; import { resetTestDb } from '../../helpers/test-db'; import { createUser, createTrip, createTodoItem } from '../../helpers/factories'; import { createMcpHarness, parseToolResult, parseResourceResult, type McpHarness } from '../../helpers/mcp-harness'; import { ADDON_IDS } from '../../../src/addons'; -beforeAll(() => { - createTables(testDb); - runMigrations(testDb); -}); - beforeEach(() => { resetTestDb(testDb); broadcastMock.mockClear(); diff --git a/server/tests/unit/mcp/tools-transit.test.ts b/server/tests/unit/mcp/tools-transit.test.ts index a9da54554..0adee4fad 100644 --- a/server/tests/unit/mcp/tools-transit.test.ts +++ b/server/tests/unit/mcp/tools-transit.test.ts @@ -1,5 +1,3 @@ -import { runMigrations } from '../../../src/db/migrations'; -import { createTables } from '../../../src/db/schema'; import { invalidatePermissionsCache } from '../../../src/nest/permissions/permissions-cache'; import { PermissionsService } from '../../../src/nest/permissions/permissions.service'; import { addTripMember, createDay, createTrip, createUser } from '../../helpers/factories'; @@ -7,33 +5,17 @@ import { createMcpHarness, parseToolResult, type McpHarness } from '../../helper import { resetTestDb } from '../../helpers/test-db'; import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'; +import { db as testDb } from '../../../src/db/database'; -const { testDb, dbMock } = vi.hoisted(() => { - const Database = require('better-sqlite3'); - const db = new Database(':memory:'); - db.exec('PRAGMA foreign_keys = ON'); - const mock = { - db, - closeDb: () => {}, - reinitialize: () => {}, - getPlaceWithTags: () => null, - canAccessTrip: (tripId: number, userId: number) => - db - .prepare( - 'SELECT t.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: number, userId: number) => - !!db.prepare('SELECT id FROM trips WHERE id = ? AND user_id = ?').get(tripId, userId), - }; - return { testDb: db, dbMock: mock }; -}); const { broadcastMock } = vi.hoisted(() => ({ broadcastMock: vi.fn(), })); -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/websocket', () => ({ broadcast: broadcastMock })); vi.mock('../../../src/config', () => ({ JWT_SECRET: 'test-jwt-secret-for-trek-testing-only', @@ -124,11 +106,6 @@ const itinerary = { ], }; -beforeAll(() => { - createTables(testDb); - runMigrations(testDb); -}); - beforeEach(() => { resetTestDb(testDb); geocodeMock.mockReset(); diff --git a/server/tests/unit/mcp/tools-transports.test.ts b/server/tests/unit/mcp/tools-transports.test.ts index afa465da3..d203890c4 100644 --- a/server/tests/unit/mcp/tools-transports.test.ts +++ b/server/tests/unit/mcp/tools-transports.test.ts @@ -5,27 +5,13 @@ * endpoints that can't be resolved produce a clean error instead of a SQL crash. */ import { describe, it, expect, vi, beforeAll, beforeEach, afterAll } from 'vitest'; +import { db as testDb } from '../../../src/db/database'; -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', @@ -35,17 +21,10 @@ vi.mock('../../../src/config', () => ({ const { broadcastMock } = vi.hoisted(() => ({ broadcastMock: vi.fn() })); vi.mock('../../../src/websocket', () => ({ broadcast: broadcastMock })); -import { createTables } from '../../../src/db/schema'; -import { runMigrations } from '../../../src/db/migrations'; import { resetTestDb } from '../../helpers/test-db'; import { createUser, createTrip, createDay } from '../../helpers/factories'; import { createMcpHarness, parseToolResult, type McpHarness } from '../../helpers/mcp-harness'; -beforeAll(() => { - createTables(testDb); - runMigrations(testDb); -}); - beforeEach(() => { resetTestDb(testDb); broadcastMock.mockClear(); diff --git a/server/tests/unit/mcp/tools-trip-members.test.ts b/server/tests/unit/mcp/tools-trip-members.test.ts index 4827781d2..9b5c0b0b9 100644 --- a/server/tests/unit/mcp/tools-trip-members.test.ts +++ b/server/tests/unit/mcp/tools-trip-members.test.ts @@ -5,27 +5,13 @@ * copy_trip, export_trip_ics, get_share_link, create_share_link, delete_share_link. */ import { describe, it, expect, vi, beforeAll, beforeEach, afterAll } from 'vitest'; +import { db as testDb } from '../../../src/db/database'; -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', @@ -35,18 +21,11 @@ vi.mock('../../../src/config', () => ({ const { broadcastMock } = vi.hoisted(() => ({ broadcastMock: vi.fn() })); vi.mock('../../../src/websocket', () => ({ broadcast: broadcastMock })); -import { createTables } from '../../../src/db/schema'; -import { runMigrations } from '../../../src/db/migrations'; import { resetTestDb } from '../../helpers/test-db'; import { invalidatePermissionsCache } from '../../../src/nest/permissions/permissions-cache'; import { createUser, createAdmin, createTrip, addTripMember } from '../../helpers/factories'; import { createMcpHarness, parseToolResult, type McpHarness } from '../../helpers/mcp-harness'; -beforeAll(() => { - createTables(testDb); - runMigrations(testDb); -}); - beforeEach(() => { resetTestDb(testDb); broadcastMock.mockClear(); diff --git a/server/tests/unit/mcp/tools-trip-warnings.test.ts b/server/tests/unit/mcp/tools-trip-warnings.test.ts index 81439b980..edf873024 100644 --- a/server/tests/unit/mcp/tools-trip-warnings.test.ts +++ b/server/tests/unit/mcp/tools-trip-warnings.test.ts @@ -3,34 +3,18 @@ * trip-warnings.mcp.ts), the MCP counterpart of GET /api/trip-warnings/:tripId. */ import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'; +import { db as testDb } from '../../../src/db/database'; -const { testDb, dbMock } = vi.hoisted(() => { - const Database = require('better-sqlite3'); - const db = new Database(':memory:'); - db.exec('PRAGMA foreign_keys = ON'); - const mock = { - db, - closeDb: () => {}, - reinitialize: () => {}, - getPlaceWithTags: () => null, - canAccessTrip: (tripId: number, userId: number) => - db - .prepare( - 'SELECT t.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: number, userId: number) => - !!db.prepare('SELECT id FROM trips WHERE id = ? AND user_id = ?').get(tripId, userId), - }; - return { testDb: db, dbMock: mock }; -}); const { broadcastMock, pluginsEnabled } = vi.hoisted(() => ({ broadcastMock: vi.fn(), pluginsEnabled: vi.fn(() => true), })); -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/websocket', () => ({ broadcast: broadcastMock })); vi.mock('../../../src/config', () => ({ JWT_SECRET: 'test-jwt-secret-for-trek-testing-only', @@ -40,8 +24,6 @@ vi.mock('../../../src/config', () => ({ // The admin kill switch reads live env; drive it from the test instead of the process. vi.mock('../../../src/nest/plugins/kill-switch', () => ({ pluginsEnabled })); -import { runMigrations } from '../../../src/db/migrations'; -import { createTables } from '../../../src/db/schema'; import { addTripMember, createTrip, createUser } from '../../helpers/factories'; import { createMcpHarness, parseToolResult, type McpHarness } from '../../helpers/mcp-harness'; import { resetTestDb } from '../../helpers/test-db'; @@ -53,11 +35,6 @@ import { PluginHooks } from '../../../src/nest/plugins/plugin-hooks.service'; const providersOfMock = vi.spyOn(PluginHooks.prototype, 'providersOf'); const tripWarningsMock = vi.spyOn(PluginHooks.prototype, 'tripWarnings'); -beforeAll(() => { - createTables(testDb); - runMigrations(testDb); -}); - beforeEach(() => { resetTestDb(testDb); broadcastMock.mockClear(); diff --git a/server/tests/unit/mcp/tools-trips.test.ts b/server/tests/unit/mcp/tools-trips.test.ts index af761f32c..4c8b5a240 100644 --- a/server/tests/unit/mcp/tools-trips.test.ts +++ b/server/tests/unit/mcp/tools-trips.test.ts @@ -8,27 +8,13 @@ * trips:write markers + the canReadTrips/canDeleteTrips predicates). */ import { describe, it, expect, vi, beforeAll, beforeEach, afterEach, afterAll } from 'vitest'; +import { db as testDb } from '../../../src/db/database'; -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,18 +24,11 @@ vi.mock('../../../src/config', () => ({ const { broadcastMock } = vi.hoisted(() => ({ broadcastMock: vi.fn() })); vi.mock('../../../src/websocket', () => ({ broadcast: broadcastMock })); -import { createTables } from '../../../src/db/schema'; -import { runMigrations } from '../../../src/db/migrations'; import { resetTestDb } from '../../helpers/test-db'; import { createUser, createTrip, createDay, createPlace, addTripMember, createBudgetItem, createPackingItem, createReservation, createDayNote, createCollabNote, createDayAssignment, createDayAccommodation } from '../../helpers/factories'; import { createMcpHarness, parseToolResult, parseResourceResult, type McpHarness } from '../../helpers/mcp-harness'; import { MAX_TRIP_DAYS } from '@trek/shared'; -beforeAll(() => { - createTables(testDb); - runMigrations(testDb); -}); - beforeEach(() => { resetTestDb(testDb); broadcastMock.mockClear(); diff --git a/server/tests/unit/mcp/tools-vacay.test.ts b/server/tests/unit/mcp/tools-vacay.test.ts index b8bd5d0d5..ad5d99ec4 100644 --- a/server/tests/unit/mcp/tools-vacay.test.ts +++ b/server/tests/unit/mcp/tools-vacay.test.ts @@ -14,27 +14,13 @@ * registerTools), so `withTools` must stay on even for resource reads. */ import { describe, it, expect, vi, beforeAll, beforeEach, afterEach, afterAll } from 'vitest'; +import { db as testDb } from '../../../src/db/database'; -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', @@ -46,8 +32,6 @@ vi.mock('../../../src/websocket', () => ({ broadcast: broadcastMock })); // share_vacay_calendar fires a user notification after inserting; stub it out -import { createTables } from '../../../src/db/schema'; -import { runMigrations } from '../../../src/db/migrations'; import { resetTestDb } from '../../helpers/test-db'; import { createUser } from '../../helpers/factories'; import { setAddonEnabled } from '../../helpers/test-db'; @@ -74,11 +58,6 @@ const schoolHolidaysSpy = vi.spyOn(VacayService.prototype, 'getSchoolHolidays'). data: [{ name: 'Sommerferien', startDate: '2025-07-28', endDate: '2025-09-08' }], }); -beforeAll(() => { - createTables(testDb); - runMigrations(testDb); -}); - beforeEach(() => { resetTestDb(testDb); // The `when:` gate reads the injected AddonsService, so the toggle is the row @@ -975,7 +954,7 @@ describe('Tool: decline_vacay_invite', () => { await h.client.callTool({ name: 'send_vacay_invite', arguments: { targetUserId: invitee.id } }); }); await withHarness(invitee.id, async (h) => { - const result = await h.client.callTool({ name: 'decline_vacay_invite', arguments: { planId: testDb.prepare('SELECT plan_id FROM vacay_plan_members WHERE user_id = ?').get(invitee.id)!.plan_id } }); + const result = await h.client.callTool({ name: 'decline_vacay_invite', arguments: { planId: (testDb.prepare('SELECT plan_id FROM vacay_plan_members WHERE user_id = ?').get(invitee.id) as { plan_id: number }).plan_id } }); const data = parseToolResult(result) as any; expect(data.success).toBe(true); });