From f40de0a017eb5ebb9a1bc8ca28e9986b18c36dee Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 9 Apr 2026 17:41:47 +0300 Subject: [PATCH] test(core): fix more tests --- apps/server/spec/setup.ts | 11 ++++++----- apps/server/src/share/sql.ts | 11 +++++++++++ apps/server/src/sql_provider.ts | 4 ++++ ...0233__migrate_geo_map_to_collection.spec.ts | 17 ++++++++++++++--- .../src/services/migration.spec.ts | 14 ++++++++++++-- .../trilium-core/src/services/sql/index.ts | 4 ---- packages/trilium-core/src/services/sql/sql.ts | 14 ++++++++++++++ .../src/test/fixtures}/document.db | Bin .../src/test/fixtures}/document_v214.db | Bin .../test/fixtures}/document_v214_migrated.db | Bin 10 files changed, 61 insertions(+), 14 deletions(-) rename {apps/server/spec/db => packages/trilium-core/src/test/fixtures}/document.db (100%) rename {apps/server/spec/db => packages/trilium-core/src/test/fixtures}/document_v214.db (100%) rename {apps/server/spec/db => packages/trilium-core/src/test/fixtures}/document_v214_migrated.db (100%) diff --git a/apps/server/spec/setup.ts b/apps/server/spec/setup.ts index 4c40a4f3be..4585f0f8c4 100644 --- a/apps/server/spec/setup.ts +++ b/apps/server/spec/setup.ts @@ -20,12 +20,13 @@ process.env.TRILIUM_PUBLIC_SERVER = "http://localhost:4200"; beforeAll(async () => { // Load the integration test database into memory. The fixture at - // spec/db/document.db is pre-seeded with the schema, demo content, and - // a known password ("demo1234") that the ETAPI tests log in with. Each - // test file runs in its own vitest fork (pool: "forks"), so each gets a - // fresh in-memory copy and mutations don't leak across files. + // packages/trilium-core/src/test/fixtures/document.db is pre-seeded with + // the schema, demo content, and a known password ("demo1234") that the + // ETAPI tests log in with. Each test file runs in its own vitest fork + // (pool: "forks"), so each gets a fresh in-memory copy and mutations + // don't leak across files. const dbProvider = new BetterSqlite3Provider(); - dbProvider.loadFromBuffer(readFileSync(join(__dirname, "db", "document.db"))); + dbProvider.loadFromBuffer(readFileSync(require.resolve("@triliumnext/core/src/test/fixtures/document.db"))); await initializeCore({ dbConfig: { diff --git a/apps/server/src/share/sql.ts b/apps/server/src/share/sql.ts index 5a127e791f..5f5e797e83 100644 --- a/apps/server/src/share/sql.ts +++ b/apps/server/src/share/sql.ts @@ -8,6 +8,17 @@ let dbConnection!: Database.Database; let dbConnectionReady = false; sql_init.dbReady.then(() => { + // The share module opens its own read-only connection to the on-disk + // database for isolation from the main read/write connection. In + // integration test mode the database is in-memory (loaded from a + // fixture buffer) and no file exists on disk, so opening one would + // throw SQLITE_CANTOPEN. Tests that exercise share functionality + // would need a different approach; skipping here keeps unrelated + // test files from failing on an unhandled rejection. + if (process.env.TRILIUM_INTEGRATION_TEST) { + return; + } + dbConnection = new Database(dataDir.DOCUMENT_PATH, { readonly: true, nativeBinding: process.env.BETTERSQLITE3_NATIVE_PATH || undefined diff --git a/apps/server/src/sql_provider.ts b/apps/server/src/sql_provider.ts index 86adb5b2c9..8195e08060 100644 --- a/apps/server/src/sql_provider.ts +++ b/apps/server/src/sql_provider.ts @@ -31,6 +31,10 @@ export default class BetterSqlite3Provider implements DatabaseProvider { } loadFromBuffer(buffer: NonSharedBuffer) { + // Close any existing connection so its file handles are released + // before we replace it. Important for repeated rebuilds in tests + // (each call would otherwise leak the previous handle). + this.dbConnection?.close(); this.dbConnection = new Database(buffer, dbOpts); } diff --git a/packages/trilium-core/src/migrations/0233__migrate_geo_map_to_collection.spec.ts b/packages/trilium-core/src/migrations/0233__migrate_geo_map_to_collection.spec.ts index 3251b9680b..506779e629 100644 --- a/packages/trilium-core/src/migrations/0233__migrate_geo_map_to_collection.spec.ts +++ b/packages/trilium-core/src/migrations/0233__migrate_geo_map_to_collection.spec.ts @@ -1,10 +1,19 @@ +import { readFileSync } from "node:fs"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; import { describe, expect, it, beforeEach } from "vitest"; import * as cls from "../services/context.js"; -import { getSql, rebuildIntegrationTestDatabase } from "../services/sql/index.js"; +import { getSql } from "../services/sql/index.js"; import becca from "../becca/becca.js"; import becca_loader from "../becca/becca_loader.js"; import migration from "./0233__migrate_geo_map_to_collection.js"; +// Resolve fixture path relative to this spec file. Spec files only ever run +// under vitest (which uses ESM via Vite), so import.meta.url is available; +// the CLAUDE.md restriction against import.meta.url applies to production +// code that gets bundled to CJS, not to test files. +const __dirname = dirname(fileURLToPath(import.meta.url)); + /** * Test suite for migration 0233 which converts geoMap notes to book type with viewConfig attachments. * @@ -29,8 +38,10 @@ describe("Migration 0233: Migrate geoMap to collection", () => { // beforeAll hooks fire. sql = getSql(); - // Set up a clean in-memory database for each test - rebuildIntegrationTestDatabase(); + // Reload the integration test database from the fixture for each test + // so mutations from one test don't leak into the next. + const dbBytes = readFileSync(join(__dirname, "../test/fixtures/document.db")); + sql.rebuildFromBuffer(dbBytes); await new Promise((resolve) => { cls.getContext().init(() => { diff --git a/packages/trilium-core/src/services/migration.spec.ts b/packages/trilium-core/src/services/migration.spec.ts index c88bbfb824..4961843f30 100644 --- a/packages/trilium-core/src/services/migration.spec.ts +++ b/packages/trilium-core/src/services/migration.spec.ts @@ -1,12 +1,22 @@ +import { readFileSync } from "node:fs"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; import { describe, expect, it } from "vitest"; import { getContext } from "./context.js"; +import { getSql } from "./sql/index.js"; + +// Resolve fixture path relative to this spec file. Spec files only ever run +// under vitest (which uses ESM via Vite), so import.meta.url is available; +// the CLAUDE.md restriction against import.meta.url applies to production +// code that gets bundled to CJS, not to test files. +const __dirname = dirname(fileURLToPath(import.meta.url)); describe("Migration", () => { it("migrates from v214", async () => { await new Promise((resolve) => { getContext().init(async () => { - const { getSql, rebuildIntegrationTestDatabase } = (await (import("./sql/index.js"))); - rebuildIntegrationTestDatabase("spec/db/document_v214.db"); + const dbBytes = readFileSync(join(__dirname, "../test/fixtures/document_v214.db")); + getSql().rebuildFromBuffer(dbBytes); const migration = (await import("./migration.js")).default; await migration.migrateIfNecessary(); diff --git a/packages/trilium-core/src/services/sql/index.ts b/packages/trilium-core/src/services/sql/index.ts index 9b6516df8b..09d06265f5 100644 --- a/packages/trilium-core/src/services/sql/index.ts +++ b/packages/trilium-core/src/services/sql/index.ts @@ -13,7 +13,3 @@ export function getSql(): SqlService { if (!sql) throw new Error("SQL not initialized"); return sql; } - -export function rebuildIntegrationTestDatabase(path?: string) { - throw new Error("Not implemented"); -} diff --git a/packages/trilium-core/src/services/sql/sql.ts b/packages/trilium-core/src/services/sql/sql.ts index 90631c5bfd..5d9a655417 100644 --- a/packages/trilium-core/src/services/sql/sql.ts +++ b/packages/trilium-core/src/services/sql/sql.ts @@ -27,6 +27,20 @@ export class SqlService { this.params = restParams; } + /** + * Replace the underlying database with a fresh in-memory copy of the + * given buffer. Used by integration tests that need a clean DB per test. + * + * Clears the prepared-statement cache because cached statements are + * bound to the previous connection and become invalid after the swap. + * + * Not safe to call inside a transaction. + */ + rebuildFromBuffer(buffer: Uint8Array) { + this.statementCache = {}; + this.dbConnection.loadFromBuffer(buffer); + } + insert(tableName: string, rec: T, replace = false) { const keys = Object.keys(rec || {}); if (keys.length === 0) { diff --git a/apps/server/spec/db/document.db b/packages/trilium-core/src/test/fixtures/document.db similarity index 100% rename from apps/server/spec/db/document.db rename to packages/trilium-core/src/test/fixtures/document.db diff --git a/apps/server/spec/db/document_v214.db b/packages/trilium-core/src/test/fixtures/document_v214.db similarity index 100% rename from apps/server/spec/db/document_v214.db rename to packages/trilium-core/src/test/fixtures/document_v214.db diff --git a/apps/server/spec/db/document_v214_migrated.db b/packages/trilium-core/src/test/fixtures/document_v214_migrated.db similarity index 100% rename from apps/server/spec/db/document_v214_migrated.db rename to packages/trilium-core/src/test/fixtures/document_v214_migrated.db