diff --git a/src/shared/opencode-storage-detection.test.ts b/src/shared/opencode-storage-detection.test.ts index 98792010c..12238e508 100644 --- a/src/shared/opencode-storage-detection.test.ts +++ b/src/shared/opencode-storage-detection.test.ts @@ -15,15 +15,27 @@ const SQLITE_VERSION = "1.1.53" // Other files (e.g., opencode-message-dir.test.ts) mock ./opencode-storage-detection globally, // making dynamic import unreliable. By inlining, we test the actual logic with controlled deps. const NOT_CACHED = Symbol("NOT_CACHED") -let cachedResult: boolean | typeof NOT_CACHED = NOT_CACHED +const FALSE_PENDING_RETRY = Symbol("FALSE_PENDING_RETRY") +let cachedResult: true | false | typeof NOT_CACHED | typeof FALSE_PENDING_RETRY = NOT_CACHED function isSqliteBackend(): boolean { - if (cachedResult !== NOT_CACHED) return cachedResult as boolean + if (cachedResult === true) return true + if (cachedResult === false) return false + if (cachedResult === FALSE_PENDING_RETRY) { + const versionOk = (() => { versionCheckCalls.push(SQLITE_VERSION); return versionReturnValue })() + const dbPath = join(TEST_DATA_DIR, "opencode", "opencode.db") + const dbExists = existsSync(dbPath) + const result = versionOk && dbExists + cachedResult = result + return result + } const versionOk = (() => { versionCheckCalls.push(SQLITE_VERSION); return versionReturnValue })() const dbPath = join(TEST_DATA_DIR, "opencode", "opencode.db") const dbExists = existsSync(dbPath) - cachedResult = versionOk && dbExists - return cachedResult + const result = versionOk && dbExists + if (result) { cachedResult = true } + else { cachedResult = FALSE_PENDING_RETRY } + return result } function resetSqliteBackendCache(): void { @@ -77,7 +89,7 @@ describe("isSqliteBackend", () => { expect(versionCheckCalls).toContain("1.1.53") }) - it("caches the result and does not re-check on subsequent calls", () => { + it("caches true permanently and does not re-check", () => { //#given versionReturnValue = true mkdirSync(join(TEST_DATA_DIR, "opencode"), { recursive: true }) @@ -91,4 +103,59 @@ describe("isSqliteBackend", () => { //#then expect(versionCheckCalls.length).toBe(1) }) + + it("retries once when first result is false, then caches permanently", () => { + //#given + versionReturnValue = true + + //#when: first call — DB does not exist + const first = isSqliteBackend() + + //#then + expect(first).toBe(false) + expect(versionCheckCalls.length).toBe(1) + + //#when: second call — DB still does not exist (retry) + const second = isSqliteBackend() + + //#then: retried once + expect(second).toBe(false) + expect(versionCheckCalls.length).toBe(2) + + //#when: third call — no more retries + const third = isSqliteBackend() + + //#then: no further checks + expect(third).toBe(false) + expect(versionCheckCalls.length).toBe(2) + }) + + it("recovers on retry when DB appears after first false", () => { + //#given + versionReturnValue = true + + //#when: first call — DB does not exist + const first = isSqliteBackend() + + //#then + expect(first).toBe(false) + + //#given: DB appears before retry + mkdirSync(join(TEST_DATA_DIR, "opencode"), { recursive: true }) + writeFileSync(DB_PATH, "") + + //#when: second call — retry finds DB + const second = isSqliteBackend() + + //#then: recovers to true and caches permanently + expect(second).toBe(true) + expect(versionCheckCalls.length).toBe(2) + + //#when: third call — cached true + const third = isSqliteBackend() + + //#then: no further checks + expect(third).toBe(true) + expect(versionCheckCalls.length).toBe(2) + }) }) \ No newline at end of file diff --git a/src/shared/opencode-storage-detection.ts b/src/shared/opencode-storage-detection.ts index 3e0aa4744..930f9e1f3 100644 --- a/src/shared/opencode-storage-detection.ts +++ b/src/shared/opencode-storage-detection.ts @@ -4,19 +4,29 @@ import { getDataDir } from "./data-path" import { isOpenCodeVersionAtLeast, OPENCODE_SQLITE_VERSION } from "./opencode-version" const NOT_CACHED = Symbol("NOT_CACHED") -let cachedResult: boolean | typeof NOT_CACHED = NOT_CACHED +const FALSE_PENDING_RETRY = Symbol("FALSE_PENDING_RETRY") +let cachedResult: true | false | typeof NOT_CACHED | typeof FALSE_PENDING_RETRY = NOT_CACHED export function isSqliteBackend(): boolean { - if (cachedResult !== NOT_CACHED) { - return cachedResult + if (cachedResult === true) return true + if (cachedResult === false) return false + + const check = (): boolean => { + const versionOk = isOpenCodeVersionAtLeast(OPENCODE_SQLITE_VERSION) + const dbPath = join(getDataDir(), "opencode", "opencode.db") + return versionOk && existsSync(dbPath) } - - const versionOk = isOpenCodeVersionAtLeast(OPENCODE_SQLITE_VERSION) - const dbPath = join(getDataDir(), "opencode", "opencode.db") - const dbExists = existsSync(dbPath) - - cachedResult = versionOk && dbExists - return cachedResult + + if (cachedResult === FALSE_PENDING_RETRY) { + const result = check() + cachedResult = result + return result + } + + const result = check() + if (result) { cachedResult = true } + else { cachedResult = FALSE_PENDING_RETRY } + return result } export function resetSqliteBackendCache(): void {