From ab6a685248f892895d1796565332f29c92d77436 Mon Sep 17 00:00:00 2001 From: carlos-alm Date: Wed, 8 Jul 2026 02:23:29 -0600 Subject: [PATCH] fix: derive loadConfig() rootDir from --db path in read-only query functions manifestoData, hybridSearchData, moduleBoundariesData, diffImpactData, complexityData, and auditData called loadConfig() bare or with process.cwd(), so `--db /other/repo/.codegraph/graph.db` invoked from a different directory read the invoking directory's .codegraphrc.json instead of the target repo's. Export a new resolveConfigForDbPath() helper from db/connection.ts, generalizing the private deriveRootDirFromDbPath() added in #1763, and use it at all six call sites named in #1881. resolveDbSettings() and resolveBusyTimeoutMs() now delegate to it too, so rootDir derivation lives in exactly one place. Also fixes a latent bug in audit.ts's manual rootDir computation, which broke for directory-style --db inputs (findDbPath's directory normalization was bypassed). --- src/db/connection.ts | 25 ++- src/db/index.ts | 1 + src/domain/analysis/diff-impact.ts | 8 +- src/domain/search/search/hybrid.ts | 8 +- src/features/audit.ts | 18 +- src/features/complexity-query.ts | 15 +- src/features/manifesto.ts | 10 +- src/features/structure-query.ts | 9 +- tests/unit/busy-timeout-query-sites.test.ts | 16 +- .../unit/loadconfig-dbpath-resolution.test.ts | 210 ++++++++++++++++++ 10 files changed, 278 insertions(+), 42 deletions(-) create mode 100644 tests/unit/loadconfig-dbpath-resolution.test.ts diff --git a/src/db/connection.ts b/src/db/connection.ts index 21cc90e8..285a84db 100644 --- a/src/db/connection.ts +++ b/src/db/connection.ts @@ -6,7 +6,7 @@ import { DEFAULTS, loadConfig } from '../infrastructure/config.js'; import { debug, warn } from '../infrastructure/logger.js'; import { getNative, isNativeAvailable } from '../infrastructure/native.js'; import { DbError, toErrorMessage } from '../shared/errors.js'; -import type { BetterSqlite3Database, NativeDatabase } from '../types.js'; +import type { BetterSqlite3Database, CodegraphConfig, NativeDatabase } from '../types.js'; import { getDatabase } from './better-sqlite3.js'; import { Repository } from './repository/base.js'; import { NativeRepository } from './repository/native-repository.js'; @@ -417,6 +417,24 @@ function deriveRootDirFromDbPath(customDbPath: string | undefined): string | und return resolvedDbPath ? path.dirname(path.dirname(resolvedDbPath)) : undefined; } +/** + * Load config with rootDir derived from the resolved DB path, rather than + * process.cwd(). This is the single entry point every read-only query + * function should use to call loadConfig() when it has (or can resolve) a + * `--db` path — so `--db /other/repo/.codegraph/graph.db` reads *that* + * repo's `.codegraphrc.json` instead of the invoking directory's. Shared by + * resolveDbSettings() and resolveBusyTimeoutMs() (below) plus the ad-hoc + * read-only query call sites (features/*, domain/analysis/*, domain/search/*) + * so rootDir derivation can't drift between them (issue #1881). + * + * MUST be called before opening any DB handle: loadConfig can throw (e.g. + * ConfigError via resolveSecrets on a malformed llm.apiKeyCommand config), + * and an already-open handle at that point would never be closed. + */ +export function resolveConfigForDbPath(customDbPath?: string): CodegraphConfig { + return loadConfig(deriveRootDirFromDbPath(customDbPath)); +} + /** * Resolve the effective engine for DB access (explicit opts.engine > config.build.engine > * 'auto') alongside config.db.busyTimeoutMs, in a single loadConfig() call. @@ -431,7 +449,7 @@ function resolveDbSettings( customDbPath: string | undefined, engineOpt: 'native' | 'wasm' | 'auto' | undefined, ): ResolvedDbSettings { - const config = loadConfig(deriveRootDirFromDbPath(customDbPath)); + const config = resolveConfigForDbPath(customDbPath); // config.build.engine is already populated from CODEGRAPH_ENGINE env by applyEnvOverrides, // so this covers both the env-var path and the .codegraphrc.json config-file path. return { @@ -452,8 +470,7 @@ function resolveDbSettings( * already-open handle at that point would never be closed. */ export function resolveBusyTimeoutMs(customDbPath?: string): number { - const config = loadConfig(deriveRootDirFromDbPath(customDbPath)); - return config.db?.busyTimeoutMs ?? DEFAULTS.db.busyTimeoutMs; + return resolveConfigForDbPath(customDbPath).db?.busyTimeoutMs ?? DEFAULTS.db.busyTimeoutMs; } /** Open a NativeRepository via rusqlite, throwing DbError if the DB file is missing. */ diff --git a/src/db/index.ts b/src/db/index.ts index 517bd649..e2c54813 100644 --- a/src/db/index.ts +++ b/src/db/index.ts @@ -16,6 +16,7 @@ export { openRepo, releaseAdvisoryLock, resolveBusyTimeoutMs, + resolveConfigForDbPath, } from './connection.js'; export { getBuildMeta, initSchema, MIGRATIONS, setBuildMeta } from './migrations.js'; export { diff --git a/src/domain/analysis/diff-impact.ts b/src/domain/analysis/diff-impact.ts index f5e116bb..46f2c6ce 100644 --- a/src/domain/analysis/diff-impact.ts +++ b/src/domain/analysis/diff-impact.ts @@ -1,7 +1,7 @@ import { execFileSync } from 'node:child_process'; import fs from 'node:fs'; import path from 'node:path'; -import { findDbPath, openReadonlyOrFail } from '../../db/index.js'; +import { findDbPath, openReadonlyOrFail, resolveConfigForDbPath } from '../../db/index.js'; import { cachedStmt } from '../../db/repository/cached-stmt.js'; import { evaluateBoundaries } from '../../features/boundaries.js'; import { coChangeForFiles } from '../../features/cochange.js'; @@ -272,8 +272,10 @@ export function diffImpactData( // Resolve config before opening the DB so config.db.busyTimeoutMs can be // threaded through to openReadonlyOrFail() (mirrors resolveDbSettings()'s // ordering in db/connection.ts — loadConfig can throw, and an already-open - // handle at that point would never be closed). - const config = opts.config || loadConfig(); + // handle at that point would never be closed). Derives rootDir from + // customDbPath (not process.cwd()) so `--db /other/repo/...` reads that + // repo's .codegraphrc.json (issue #1881). + const config = opts.config || resolveConfigForDbPath(customDbPath); const db = openReadonlyOrFail( customDbPath, config.db?.busyTimeoutMs ?? DEFAULTS.db.busyTimeoutMs, diff --git a/src/domain/search/search/hybrid.ts b/src/domain/search/search/hybrid.ts index 0a2420b1..a02587cd 100644 --- a/src/domain/search/search/hybrid.ts +++ b/src/domain/search/search/hybrid.ts @@ -1,5 +1,5 @@ -import { openReadonlyOrFail } from '../../../db/index.js'; -import { DEFAULTS, loadConfig } from '../../../infrastructure/config.js'; +import { openReadonlyOrFail, resolveConfigForDbPath } from '../../../db/index.js'; +import { DEFAULTS } from '../../../infrastructure/config.js'; import type { BetterSqlite3Database, CodegraphConfig } from '../../../types.js'; import { hasFtsIndex } from '../stores/fts5.js'; import { ftsSearchData } from './keyword.js'; @@ -178,7 +178,9 @@ export async function hybridSearchData( customDbPath: string | undefined, opts: SemanticSearchOpts = {}, ): Promise { - const config = opts.config || loadConfig(); + // Derive rootDir from customDbPath (not process.cwd()) so `--db /other/repo/...` + // reads that repo's .codegraphrc.json (issue #1881). + const config = opts.config || resolveConfigForDbPath(customDbPath); const searchCfg = config.search || ({} as CodegraphConfig['search']); const limit = opts.limit ?? searchCfg.topK ?? 15; const k = opts.rrfK ?? searchCfg.rrfK ?? 60; diff --git a/src/features/audit.ts b/src/features/audit.ts index 64a55b46..fb045950 100644 --- a/src/features/audit.ts +++ b/src/features/audit.ts @@ -1,9 +1,8 @@ -import path from 'node:path'; -import { openReadonlyOrFail } from '../db/index.js'; +import { openReadonlyOrFail, resolveConfigForDbPath } from '../db/index.js'; import { normalizeFileFilter } from '../db/query-builder.js'; import { bfsTransitiveCallers } from '../domain/analysis/impact.js'; import { explainData } from '../domain/queries.js'; -import { DEFAULTS, loadConfig } from '../infrastructure/config.js'; +import { DEFAULTS } from '../infrastructure/config.js'; import { debug } from '../infrastructure/logger.js'; import { isTestFile } from '../infrastructure/test-filter.js'; import { toErrorMessage } from '../shared/errors.js'; @@ -35,13 +34,7 @@ function resolveThresholds( config: unknown, ): Record { try { - const cfg = - config || - (() => { - const dbDir = customDbPath ? path.dirname(customDbPath) : process.cwd(); - const repoRoot = path.resolve(dbDir, '..'); - return loadConfig(repoRoot); - })(); + const cfg = config || resolveConfigForDbPath(customDbPath); const userRules = (cfg as Record).manifesto || {}; const resolved: Record = {}; for (const def of FUNCTION_RULES) { @@ -144,7 +137,10 @@ export function auditData( opts: AuditDataOpts = {}, ): AuditResult { const noTests = opts.noTests || false; - const config = opts.config || loadConfig(); + // Derive rootDir from customDbPath (not process.cwd()) so `--db /other/repo/...` + // reads that repo's .codegraphrc.json (issue #1881) — matches resolveThresholds() + // below, which already derived rootDir correctly. + const config = opts.config || resolveConfigForDbPath(customDbPath); const maxDepth = opts.depth || (config as unknown as { analysis?: { auditDepth?: number } }).analysis?.auditDepth || diff --git a/src/features/complexity-query.ts b/src/features/complexity-query.ts index 4fd93521..4c2444e0 100644 --- a/src/features/complexity-query.ts +++ b/src/features/complexity-query.ts @@ -5,9 +5,9 @@ * pagination) from compute-time concerns (AST traversal, metric algorithms). */ -import { openReadonlyOrFail } from '../db/index.js'; +import { openReadonlyOrFail, resolveConfigForDbPath } from '../db/index.js'; import { buildFileConditionSQL } from '../db/query-builder.js'; -import { DEFAULTS, loadConfig } from '../infrastructure/config.js'; +import { DEFAULTS } from '../infrastructure/config.js'; import { debug } from '../infrastructure/logger.js'; import { isTestFile } from '../infrastructure/test-filter.js'; import { paginateResult } from '../shared/paginate.js'; @@ -281,14 +281,19 @@ interface ComplexityQueryOpts { } /** Resolve query flags + effective manifesto thresholds from opts/config/DEFAULTS. */ -function resolveComplexityQueryOptions(opts: ComplexityQueryOpts): { +function resolveComplexityQueryOptions( + customDbPath: string | undefined, + opts: ComplexityQueryOpts, +): { sort: string; noTests: boolean; aboveThreshold: boolean; thresholds: any; busyTimeoutMs: number; } { - const config = opts.config || loadConfig(process.cwd()); + // Derive rootDir from customDbPath (not process.cwd()) so `--db /other/repo/...` + // reads that repo's .codegraphrc.json (issue #1881). + const config = opts.config || resolveConfigForDbPath(customDbPath); return { sort: opts.sort || 'cognitive', noTests: opts.noTests || false, @@ -330,7 +335,7 @@ export function complexityData( // resolveDbSettings()'s ordering in db/connection.ts, since loadConfig can // throw and an already-open handle at that point would never be closed. const { sort, noTests, aboveThreshold, thresholds, busyTimeoutMs } = - resolveComplexityQueryOptions(opts); + resolveComplexityQueryOptions(customDbPath, opts); const db = openReadonlyOrFail(customDbPath, busyTimeoutMs); try { const { where, params } = buildComplexityWhere({ diff --git a/src/features/manifesto.ts b/src/features/manifesto.ts index 8a7d3ada..bf020238 100644 --- a/src/features/manifesto.ts +++ b/src/features/manifesto.ts @@ -1,7 +1,7 @@ -import { openReadonlyOrFail } from '../db/index.js'; +import { openReadonlyOrFail, resolveConfigForDbPath } from '../db/index.js'; import { buildFileConditionSQL } from '../db/query-builder.js'; import { findCycles } from '../domain/graph/cycles.js'; -import { DEFAULTS, loadConfig } from '../infrastructure/config.js'; +import { DEFAULTS } from '../infrastructure/config.js'; import { debug } from '../infrastructure/logger.js'; import { paginateResult } from '../shared/paginate.js'; import type { BetterSqlite3Database, CodegraphConfig, ThresholdRule } from '../types.js'; @@ -482,8 +482,10 @@ export function manifestoData( // Resolve config before opening the DB so config.db.busyTimeoutMs can be // threaded through to openReadonlyOrFail() (mirrors resolveDbSettings()'s // ordering in db/connection.ts — loadConfig can throw, and an already-open - // handle at that point would never be closed). - const config = opts.config || loadConfig(process.cwd()); + // handle at that point would never be closed). Derives rootDir from + // customDbPath (not process.cwd()) so `--db /other/repo/...` reads that + // repo's .codegraphrc.json (issue #1881). + const config = opts.config || resolveConfigForDbPath(customDbPath); const db = openReadonlyOrFail( customDbPath, config.db?.busyTimeoutMs ?? DEFAULTS.db.busyTimeoutMs, diff --git a/src/features/structure-query.ts b/src/features/structure-query.ts index 80ea9248..0e835959 100644 --- a/src/features/structure-query.ts +++ b/src/features/structure-query.ts @@ -11,9 +11,10 @@ import { openReadonlyOrFail, openReadonlyWithNative, resolveBusyTimeoutMs, + resolveConfigForDbPath, testFilterSQL, } from '../db/index.js'; -import { DEFAULTS, loadConfig } from '../infrastructure/config.js'; +import { DEFAULTS } from '../infrastructure/config.js'; import { isTestFile } from '../infrastructure/test-filter.js'; import { normalizePath } from '../shared/constants.js'; import { paginateResult } from '../shared/paginate.js'; @@ -384,8 +385,10 @@ export function moduleBoundariesData( // Resolve config before opening the DB so config.db.busyTimeoutMs can be // threaded through to openReadonlyOrFail() (mirrors resolveDbSettings()'s // ordering in db/connection.ts — loadConfig can throw, and an already-open - // handle at that point would never be closed). - const config = opts.config || loadConfig(); + // handle at that point would never be closed). Derives rootDir from + // customDbPath (not process.cwd()) so `--db /other/repo/...` reads that + // repo's .codegraphrc.json (issue #1881). + const config = opts.config || resolveConfigForDbPath(customDbPath); const db = openReadonlyOrFail( customDbPath, config.db?.busyTimeoutMs ?? DEFAULTS.db.busyTimeoutMs, diff --git a/tests/unit/busy-timeout-query-sites.test.ts b/tests/unit/busy-timeout-query-sites.test.ts index 159ae6e1..98e82a73 100644 --- a/tests/unit/busy-timeout-query-sites.test.ts +++ b/tests/unit/busy-timeout-query-sites.test.ts @@ -101,11 +101,10 @@ describe('configured busyTimeoutMs reaches PRAGMA busy_timeout at ad-hoc read-on }); it('manifestoData (config load reordered to before the db opens) applies the configured busy_timeout', () => { - // manifestoData resolves its config via loadConfig(process.cwd()) — a - // pre-existing, cwd-based resolution this fix intentionally preserves - // (only the *timing* of the call moved, not what it resolves) — so the - // project config must be visible from process.cwd() here, not from dbPath. - const cwdSpy = vi.spyOn(process, 'cwd').mockReturnValue(tmpDir); + // manifestoData resolves its config from the resolved DB path's rootDir + // (issue #1881 fix), not process.cwd() — so an unrelated cwd must not + // affect resolution. + const cwdSpy = vi.spyOn(process, 'cwd').mockReturnValue(os.tmpdir()); const capture = captureBusyTimeoutPragmas(); try { manifestoData(dbPath); @@ -117,10 +116,9 @@ describe('configured busyTimeoutMs reaches PRAGMA busy_timeout at ad-hoc read-on }); it('hybridSearchData (already loaded config before the db opens) applies the configured busy_timeout', async () => { - // Same pre-existing cwd-based config resolution as manifestoData (bare - // loadConfig() defaults to process.cwd()) — preserved, not introduced, by - // this fix. - const cwdSpy = vi.spyOn(process, 'cwd').mockReturnValue(tmpDir); + // Same DB-path-derived config resolution as manifestoData (issue #1881 + // fix) — an unrelated cwd must not affect resolution. + const cwdSpy = vi.spyOn(process, 'cwd').mockReturnValue(os.tmpdir()); const capture = captureBusyTimeoutPragmas(); try { await hybridSearchData('nonexistent-query', dbPath); diff --git a/tests/unit/loadconfig-dbpath-resolution.test.ts b/tests/unit/loadconfig-dbpath-resolution.test.ts new file mode 100644 index 00000000..c1797e34 --- /dev/null +++ b/tests/unit/loadconfig-dbpath-resolution.test.ts @@ -0,0 +1,210 @@ +/** + * Regression tests for issue #1881: several read-only query functions + * resolved `loadConfig()` from `process.cwd()` (or bare, which also defaults + * to `process.cwd()`) instead of the rootDir derived from the resolved + * `--db` path. This meant `--db /other/repo/.codegraph/graph.db` invoked + * from a different directory read the *invoking* directory's + * `.codegraphrc.json` instead of the target repo's. + * + * Covers the shared `resolveConfigForDbPath()` helper directly, plus every + * call site named in the issue: + * - manifestoData (src/features/manifesto.ts) + * - hybridSearchData (src/domain/search/search/hybrid.ts) + * - moduleBoundariesData (src/features/structure-query.ts) + * - diffImpactData (src/domain/analysis/diff-impact.ts) + * - complexityData / resolveComplexityQueryOptions (src/features/complexity-query.ts) + * - auditData / resolveThresholds (src/features/audit.ts) + * + * Each call site test sets `process.cwd()` to an unrelated directory with no + * (or a differently-configured) `.codegraphrc.json` and asserts the + * configured `db.busyTimeoutMs` still reaches `PRAGMA busy_timeout`, + * proving resolution came from the `--db` path, not cwd. + */ +import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; +import Database from 'better-sqlite3'; +import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest'; +import { closeDb, initSchema, openDb, resolveConfigForDbPath } from '../../src/db/index.js'; +import { diffImpactData } from '../../src/domain/analysis/diff-impact.js'; +import { hybridSearchData } from '../../src/domain/search/search/hybrid.js'; +import { auditData } from '../../src/features/audit.js'; +import { complexityData } from '../../src/features/complexity-query.js'; +import { manifestoData } from '../../src/features/manifesto.js'; +import { moduleBoundariesData } from '../../src/features/structure-query.js'; +import { DEFAULTS } from '../../src/infrastructure/config.js'; + +const REPO_BUSY_TIMEOUT_MS = 55555; + +let repoDir: string; +let dbPath: string; +let unrelatedCwd: string; + +/** Capture every `busy_timeout = N` pragma issued against a real Database instance. */ +function captureBusyTimeoutPragmas(): { calls: string[]; restore: () => void } { + const original = Database.prototype.pragma; + const calls: string[] = []; + const spy = vi.spyOn(Database.prototype, 'pragma').mockImplementation(function ( + this: unknown, + sql: string, + ...rest: unknown[] + ) { + if (typeof sql === 'string' && sql.startsWith('busy_timeout')) calls.push(sql); + return original.apply(this, [sql, ...rest] as Parameters); + }); + return { calls, restore: () => spy.mockRestore() }; +} + +function insertNode( + db: Database.Database, + name: string, + kind: string, + file: string, + line: number, + endLine: number | null = null, +) { + return db + .prepare('INSERT INTO nodes (name, kind, file, line, end_line) VALUES (?, ?, ?, ?, ?)') + .run(name, kind, file, line, endLine).lastInsertRowid; +} + +beforeAll(() => { + repoDir = fs.mkdtempSync(path.join(os.tmpdir(), 'codegraph-dbpath-repo-')); + dbPath = path.join(repoDir, '.codegraph', 'graph.db'); + const db = openDb(dbPath); + initSchema(db); + insertNode(db, 'myFunction', 'function', 'src/a.js', 1, 10); + closeDb(db); + fs.writeFileSync( + path.join(repoDir, '.codegraphrc.json'), + JSON.stringify({ db: { busyTimeoutMs: REPO_BUSY_TIMEOUT_MS } }), + ); + + // A separate directory with no .codegraphrc.json, used as process.cwd() + // to prove config resolution ignores it in favor of the --db path. + unrelatedCwd = fs.mkdtempSync(path.join(os.tmpdir(), 'codegraph-dbpath-unrelated-')); +}); + +afterAll(() => { + if (repoDir) fs.rmSync(repoDir, { recursive: true, force: true }); + if (unrelatedCwd) fs.rmSync(unrelatedCwd, { recursive: true, force: true }); +}); + +describe('resolveConfigForDbPath', () => { + it('derives rootDir from a file-style --db path, not process.cwd()', () => { + const cwdSpy = vi.spyOn(process, 'cwd').mockReturnValue(unrelatedCwd); + try { + const config = resolveConfigForDbPath(dbPath); + expect(config.db?.busyTimeoutMs).toBe(REPO_BUSY_TIMEOUT_MS); + } finally { + cwdSpy.mockRestore(); + } + }); + + it('derives rootDir from a directory-style --db path (normalises via findDbPath)', () => { + const cwdSpy = vi.spyOn(process, 'cwd').mockReturnValue(unrelatedCwd); + try { + const config = resolveConfigForDbPath(repoDir); + expect(config.db?.busyTimeoutMs).toBe(REPO_BUSY_TIMEOUT_MS); + } finally { + cwdSpy.mockRestore(); + } + }); + + it('falls back to process.cwd() when no --db path is given', () => { + const cwdSpy = vi.spyOn(process, 'cwd').mockReturnValue(repoDir); + try { + const config = resolveConfigForDbPath(undefined); + expect(config.db?.busyTimeoutMs).toBe(REPO_BUSY_TIMEOUT_MS); + } finally { + cwdSpy.mockRestore(); + } + }); + + it('falls back to DEFAULTS.db.busyTimeoutMs when no project config sets it', () => { + const bareDir = fs.mkdtempSync(path.join(os.tmpdir(), 'codegraph-dbpath-bare-')); + try { + const bareDbPath = path.join(bareDir, '.codegraph', 'graph.db'); + const db = openDb(bareDbPath); + initSchema(db); + closeDb(db); + expect(resolveConfigForDbPath(bareDbPath).db?.busyTimeoutMs).toBe(DEFAULTS.db.busyTimeoutMs); + } finally { + fs.rmSync(bareDir, { recursive: true, force: true }); + } + }); +}); + +describe('configured project settings reach ad-hoc read-only query call sites via --db, not cwd', () => { + it('manifestoData resolves config from the --db path', () => { + const cwdSpy = vi.spyOn(process, 'cwd').mockReturnValue(unrelatedCwd); + const capture = captureBusyTimeoutPragmas(); + try { + manifestoData(dbPath); + } finally { + capture.restore(); + cwdSpy.mockRestore(); + } + expect(capture.calls).toContain(`busy_timeout = ${REPO_BUSY_TIMEOUT_MS}`); + }); + + it('hybridSearchData resolves config from the --db path', async () => { + const cwdSpy = vi.spyOn(process, 'cwd').mockReturnValue(unrelatedCwd); + const capture = captureBusyTimeoutPragmas(); + try { + await hybridSearchData('nonexistent-query', dbPath); + } finally { + capture.restore(); + cwdSpy.mockRestore(); + } + expect(capture.calls).toContain(`busy_timeout = ${REPO_BUSY_TIMEOUT_MS}`); + }); + + it('moduleBoundariesData resolves config from the --db path', () => { + const cwdSpy = vi.spyOn(process, 'cwd').mockReturnValue(unrelatedCwd); + const capture = captureBusyTimeoutPragmas(); + try { + moduleBoundariesData(dbPath); + } finally { + capture.restore(); + cwdSpy.mockRestore(); + } + expect(capture.calls).toContain(`busy_timeout = ${REPO_BUSY_TIMEOUT_MS}`); + }); + + it('diffImpactData resolves config from the --db path (before the git-root check)', () => { + const cwdSpy = vi.spyOn(process, 'cwd').mockReturnValue(unrelatedCwd); + const capture = captureBusyTimeoutPragmas(); + try { + diffImpactData(dbPath, {}); + } finally { + capture.restore(); + cwdSpy.mockRestore(); + } + expect(capture.calls).toContain(`busy_timeout = ${REPO_BUSY_TIMEOUT_MS}`); + }); + + it('complexityData resolves config from the --db path', () => { + const cwdSpy = vi.spyOn(process, 'cwd').mockReturnValue(unrelatedCwd); + const capture = captureBusyTimeoutPragmas(); + try { + complexityData(dbPath); + } finally { + capture.restore(); + cwdSpy.mockRestore(); + } + expect(capture.calls).toContain(`busy_timeout = ${REPO_BUSY_TIMEOUT_MS}`); + }); + + it('auditData resolves config from the --db path', () => { + const cwdSpy = vi.spyOn(process, 'cwd').mockReturnValue(unrelatedCwd); + const capture = captureBusyTimeoutPragmas(); + try { + auditData('myFunction', dbPath); + } finally { + capture.restore(); + cwdSpy.mockRestore(); + } + expect(capture.calls).toContain(`busy_timeout = ${REPO_BUSY_TIMEOUT_MS}`); + }); +});