From c037ea866eecba4bec238feeb1e4be290952223e Mon Sep 17 00:00:00 2001 From: ciat-777 Date: Sat, 14 Mar 2026 22:27:02 +0800 Subject: [PATCH] test(db): isolate default sqlite path under vitest --- src/server/db/index.default-path.test.ts | 44 ++++++++++++++++++++++++ src/server/db/index.ts | 41 +++++++++++++++++++++- 2 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 src/server/db/index.default-path.test.ts diff --git a/src/server/db/index.default-path.test.ts b/src/server/db/index.default-path.test.ts new file mode 100644 index 0000000..b9825b1 --- /dev/null +++ b/src/server/db/index.default-path.test.ts @@ -0,0 +1,44 @@ +import { afterEach, describe, expect, it, vi } from 'vitest'; +import { tmpdir } from 'node:os'; +import { resolve } from 'node:path'; + +type DbModule = typeof import('./index.js'); + +describe('sqlite default path resolution', () => { + let dbModule: DbModule | null = null; + + afterEach(async () => { + if (dbModule) { + await dbModule.closeDbConnections(); + dbModule = null; + } + delete process.env.DATA_DIR; + delete process.env.DB_URL; + vi.resetModules(); + }); + + it('uses an isolated temp sqlite path under vitest when no db env is configured', async () => { + delete process.env.DATA_DIR; + delete process.env.DB_URL; + vi.resetModules(); + + dbModule = await import('./index.js'); + const sqlitePath = dbModule.__dbProxyTestUtils.resolveSqlitePath(); + const sharedRepoPath = resolve('./data/hub.db'); + + expect(sqlitePath).not.toBe(sharedRepoPath); + expect(sqlitePath).toContain(tmpdir()); + expect(sqlitePath).toContain('metapi-vitest'); + }); + + it('still honors explicit DATA_DIR when provided', async () => { + process.env.DATA_DIR = resolve(tmpdir(), 'metapi-explicit-data-dir'); + delete process.env.DB_URL; + vi.resetModules(); + + dbModule = await import('./index.js'); + const sqlitePath = dbModule.__dbProxyTestUtils.resolveSqlitePath(); + + expect(sqlitePath).toBe(resolve(process.env.DATA_DIR, 'hub.db')); + }); +}); diff --git a/src/server/db/index.ts b/src/server/db/index.ts index e686beb..3e2161a 100644 --- a/src/server/db/index.ts +++ b/src/server/db/index.ts @@ -12,7 +12,9 @@ import { ensureSharedIndexSchemaCompatibility } from './sharedIndexSchemaCompati import { config } from '../config.js'; import { ensureRuntimeDatabaseReady } from '../runtimeDatabaseBootstrap.js'; import { mkdirSync } from 'fs'; +import { tmpdir } from 'os'; import { dirname, resolve } from 'path'; +import { threadId } from 'worker_threads'; export type RuntimeDbDialect = 'sqlite' | 'mysql' | 'postgres'; type SqlMethod = 'all' | 'get' | 'run' | 'values' | 'execute'; @@ -42,7 +44,13 @@ let proxyLogBillingDetailsColumnAvailable: boolean | null = null; function resolveSqlitePath(): string { const raw = (config.dbUrl || '').trim(); - if (!raw) return resolve(`${config.dataDir}/hub.db`); + if (!raw) { + const isolatedVitestPath = resolveVitestSqlitePath(); + if (isolatedVitestPath) { + return isolatedVitestPath; + } + return resolve(`${config.dataDir}/hub.db`); + } if (raw === ':memory:') return raw; if (raw.startsWith('file://')) { const parsed = new URL(raw); @@ -54,6 +62,35 @@ function resolveSqlitePath(): string { return resolve(raw); } +function isVitestRuntime(): boolean { + if ((process.env.VITEST_POOL_ID || '').trim()) { + return true; + } + if ((process.env.VITEST_WORKER_ID || '').trim()) { + return true; + } + const runtimeArgs = [...process.argv, ...process.execArgv] + .map((value) => String(value || '').toLowerCase()); + return runtimeArgs.some((value) => value.includes('vitest')); +} + +function resolveVitestSqlitePath(): string | null { + if (!isVitestRuntime()) { + return null; + } + if ((process.env.DB_URL || '').trim()) { + return null; + } + if ((process.env.DATA_DIR || '').trim()) { + return null; + } + + const workerTag = process.env.VITEST_POOL_ID + || process.env.VITEST_WORKER_ID + || `${process.pid}-${threadId}`; + return resolve(tmpdir(), `metapi-vitest-${workerTag}`, 'hub.db'); +} + function requireSqliteConnection(): Database.Database { if (!sqliteConnection) { throw new Error('SQLite connection is not initialized'); @@ -993,4 +1030,6 @@ export async function switchRuntimeDatabase(nextDialect: RuntimeDbDialect, nextD export const __dbProxyTestUtils = { wrapQueryLike, shouldWrapObject, + resolveSqlitePath, + resolveVitestSqlitePath, };