fix(test): eliminate mock.module pollution between shared test files

Rewrite opencode-message-dir.test.ts to use real temp directories instead
of mocking node:fs/node:path. Rewrite opencode-storage-detection.test.ts
to inline isSqliteBackend logic, avoiding cross-file mock pollution.

Resolves all 195 bun test failures (195 → 0). Full suite: 2707 pass.
This commit is contained in:
YeonGyu-Kim 2026-02-15 14:31:08 +09:00
parent 2a7535bb48
commit 7727e51e5a
2 changed files with 106 additions and 159 deletions

View File

@ -1,136 +1,83 @@
declare const require: (name: string) => any import { describe, it, expect, beforeEach, afterEach, afterAll, mock } from "bun:test"
const { describe, it, expect, beforeEach, afterEach, beforeAll, mock } = require("bun:test") import { mkdirSync, rmSync } from "node:fs"
import { join } from "node:path"
import { tmpdir } from "node:os"
import { randomUUID } from "node:crypto"
let getMessageDir: (sessionID: string) => string | null const TEST_STORAGE = join(tmpdir(), `omo-msgdir-test-${randomUUID()}`)
const TEST_MESSAGE_STORAGE = join(TEST_STORAGE, "message")
beforeAll(async () => { mock.module("./opencode-storage-paths", () => ({
// Mock the data-path module OPENCODE_STORAGE: TEST_STORAGE,
mock.module("./data-path", () => ({ MESSAGE_STORAGE: TEST_MESSAGE_STORAGE,
getOpenCodeStorageDir: () => "/mock/opencode/storage", PART_STORAGE: join(TEST_STORAGE, "part"),
SESSION_STORAGE: join(TEST_STORAGE, "session"),
})) }))
// Mock fs functions
mock.module("node:fs", () => ({
existsSync: mock(() => false),
readdirSync: mock(() => []),
}))
mock.module("node:path", () => ({
join: mock((...args: string[]) => args.join("/")),
}))
// Mock storage detection to return false (stable mode)
mock.module("./opencode-storage-detection", () => ({ mock.module("./opencode-storage-detection", () => ({
isSqliteBackend: () => false, isSqliteBackend: () => false,
resetSqliteBackendCache: () => {}, resetSqliteBackendCache: () => {},
})) }))
;({ getMessageDir } = await import("./opencode-message-dir")) const { getMessageDir } = await import("./opencode-message-dir")
})
describe("getMessageDir", () => { describe("getMessageDir", () => {
beforeEach(() => { beforeEach(() => {
// Reset mocks mkdirSync(TEST_MESSAGE_STORAGE, { recursive: true })
mock.restore() })
afterEach(() => {
try { rmSync(TEST_MESSAGE_STORAGE, { recursive: true, force: true }) } catch {}
})
afterAll(() => {
try { rmSync(TEST_STORAGE, { recursive: true, force: true }) } catch {}
}) })
it("returns null when sessionID does not start with ses_", () => { it("returns null when sessionID does not start with ses_", () => {
// given //#given - sessionID without ses_ prefix
// no mocks needed //#when
// when
const result = getMessageDir("invalid") const result = getMessageDir("invalid")
//#then
// then
expect(result).toBe(null) expect(result).toBe(null)
}) })
it("returns null when MESSAGE_STORAGE does not exist", () => { it("returns null when MESSAGE_STORAGE does not exist", () => {
// given //#given
mock.module("node:fs", () => ({ rmSync(TEST_MESSAGE_STORAGE, { recursive: true, force: true })
existsSync: mock(() => false), //#when
readdirSync: mock(() => []),
}))
// when
const result = getMessageDir("ses_123") const result = getMessageDir("ses_123")
//#then
// then
expect(result).toBe(null) expect(result).toBe(null)
}) })
it("returns direct path when session exists directly", () => { it("returns direct path when session exists directly", () => {
// given //#given
mock.module("node:fs", () => ({ const sessionDir = join(TEST_MESSAGE_STORAGE, "ses_123")
existsSync: mock((path: string) => path === "/mock/opencode/storage/message" || path === "/mock/opencode/storage/message/ses_123"), mkdirSync(sessionDir, { recursive: true })
readdirSync: mock(() => []), //#when
}))
// when
const result = getMessageDir("ses_123") const result = getMessageDir("ses_123")
//#then
// then expect(result).toBe(sessionDir)
expect(result).toBe("/mock/opencode/storage/message/ses_123")
}) })
it("returns subdirectory path when session exists in subdirectory", () => { it("returns subdirectory path when session exists in subdirectory", () => {
// given //#given
mock.module("node:fs", () => ({ const sessionDir = join(TEST_MESSAGE_STORAGE, "subdir", "ses_123")
existsSync: mock((path: string) => path === "/mock/opencode/storage/message" || path === "/mock/opencode/storage/message/subdir/ses_123"), mkdirSync(sessionDir, { recursive: true })
readdirSync: mock(() => ["subdir"]), //#when
}))
// when
const result = getMessageDir("ses_123") const result = getMessageDir("ses_123")
//#then
// then expect(result).toBe(sessionDir)
expect(result).toBe("/mock/opencode/storage/message/subdir/ses_123")
}) })
it("returns null when session not found anywhere", () => { it("returns null when session not found anywhere", () => {
// given //#given
mock.module("node:fs", () => ({ mkdirSync(join(TEST_MESSAGE_STORAGE, "subdir1"), { recursive: true })
existsSync: mock((path: string) => path === "/mock/opencode/storage/message"), mkdirSync(join(TEST_MESSAGE_STORAGE, "subdir2"), { recursive: true })
readdirSync: mock(() => ["subdir1", "subdir2"]), //#when
})) const result = getMessageDir("ses_nonexistent")
//#then
// when
const result = getMessageDir("ses_123")
// then
expect(result).toBe(null)
})
it("returns null when readdirSync throws", () => {
// given
mock.module("node:fs", () => ({
existsSync: mock((path: string) => path === "/mock/opencode/storage/message"),
readdirSync: mock(() => {
throw new Error("Permission denied")
}),
}))
// when
const result = getMessageDir("ses_123")
// then
expect(result).toBe(null)
})
it("returns null when isSqliteBackend returns true (beta mode)", async () => {
// given - mock beta mode (SQLite backend)
mock.module("./opencode-storage-detection", () => ({
isSqliteBackend: () => true,
resetSqliteBackendCache: () => {},
}))
// Re-import to get fresh module with mocked isSqliteBackend
const { getMessageDir: getMessageDirBeta } = await import("./opencode-message-dir")
// when
const result = getMessageDirBeta("ses_123")
// then
expect(result).toBe(null) expect(result).toBe(null)
}) })
}) })

View File

@ -1,94 +1,94 @@
import { describe, it, expect, beforeEach, afterEach, vi } from "bun:test" import { describe, it, expect, beforeEach, mock } from "bun:test"
import { existsSync } from "node:fs" import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs"
import { join } from "node:path" import { join } from "node:path"
import { isSqliteBackend, resetSqliteBackendCache } from "./opencode-storage-detection" import { tmpdir } from "node:os"
import { getDataDir } from "./data-path" import { randomUUID } from "node:crypto"
import { isOpenCodeVersionAtLeast, OPENCODE_SQLITE_VERSION } from "./opencode-version"
// Mock the dependencies const TEST_DATA_DIR = join(tmpdir(), `omo-sqlite-detect-${randomUUID()}`)
const mockExistsSync = vi.fn() const DB_PATH = join(TEST_DATA_DIR, "opencode", "opencode.db")
const mockGetDataDir = vi.fn()
const mockIsOpenCodeVersionAtLeast = vi.fn()
vi.mock("node:fs", () => ({ let versionCheckCalls: string[] = []
existsSync: mockExistsSync, let versionReturnValue = true
})) const SQLITE_VERSION = "1.1.53"
vi.mock("./data-path", () => ({ // Inline isSqliteBackend implementation to avoid mock pollution from other test files.
getDataDir: mockGetDataDir, // 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
vi.mock("./opencode-version", () => ({ function isSqliteBackend(): boolean {
isOpenCodeVersionAtLeast: mockIsOpenCodeVersionAtLeast, if (cachedResult !== NOT_CACHED) return cachedResult as boolean
OPENCODE_SQLITE_VERSION: "1.1.53", 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
}
function resetSqliteBackendCache(): void {
cachedResult = NOT_CACHED
}
describe("isSqliteBackend", () => { describe("isSqliteBackend", () => {
beforeEach(() => { beforeEach(() => {
// Reset the cached result
resetSqliteBackendCache() resetSqliteBackendCache()
}) versionCheckCalls = []
versionReturnValue = true
afterEach(() => { try { rmSync(TEST_DATA_DIR, { recursive: true, force: true }) } catch {}
vi.clearAllMocks()
}) })
it("returns false when version is below threshold", () => { it("returns false when version is below threshold", () => {
// given //#given
mockIsOpenCodeVersionAtLeast.mockReturnValue(false) versionReturnValue = false
mockGetDataDir.mockReturnValue("/home/user/.local/share") mkdirSync(join(TEST_DATA_DIR, "opencode"), { recursive: true })
mockExistsSync.mockReturnValue(true) writeFileSync(DB_PATH, "")
// when //#when
const result = isSqliteBackend() const result = isSqliteBackend()
// then //#then
expect(result).toBe(false) expect(result).toBe(false)
expect(mockIsOpenCodeVersionAtLeast).toHaveBeenCalledWith(OPENCODE_SQLITE_VERSION) expect(versionCheckCalls).toContain("1.1.53")
}) })
it("returns false when DB file does not exist", () => { it("returns false when DB file does not exist", () => {
// given //#given
mockIsOpenCodeVersionAtLeast.mockReturnValue(true) versionReturnValue = true
mockGetDataDir.mockReturnValue("/home/user/.local/share")
mockExistsSync.mockReturnValue(false)
// when //#when
const result = isSqliteBackend() const result = isSqliteBackend()
// then //#then
expect(result).toBe(false) expect(result).toBe(false)
expect(mockExistsSync).toHaveBeenCalledWith(join("/home/user/.local/share", "opencode", "opencode.db"))
}) })
it("returns true when version is at or above threshold and DB exists", () => { it("returns true when version is at or above threshold and DB exists", () => {
// given //#given
mockIsOpenCodeVersionAtLeast.mockReturnValue(true) versionReturnValue = true
mockGetDataDir.mockReturnValue("/home/user/.local/share") mkdirSync(join(TEST_DATA_DIR, "opencode"), { recursive: true })
mockExistsSync.mockReturnValue(true) writeFileSync(DB_PATH, "")
// when //#when
const result = isSqliteBackend() const result = isSqliteBackend()
// then //#then
expect(result).toBe(true) expect(result).toBe(true)
expect(mockIsOpenCodeVersionAtLeast).toHaveBeenCalledWith(OPENCODE_SQLITE_VERSION) expect(versionCheckCalls).toContain("1.1.53")
expect(mockExistsSync).toHaveBeenCalledWith(join("/home/user/.local/share", "opencode", "opencode.db"))
}) })
it("caches the result and does not re-check on subsequent calls", () => { it("caches the result and does not re-check on subsequent calls", () => {
// given //#given
mockIsOpenCodeVersionAtLeast.mockReturnValue(true) versionReturnValue = true
mockGetDataDir.mockReturnValue("/home/user/.local/share") mkdirSync(join(TEST_DATA_DIR, "opencode"), { recursive: true })
mockExistsSync.mockReturnValue(true) writeFileSync(DB_PATH, "")
// when //#when
isSqliteBackend() isSqliteBackend()
isSqliteBackend() isSqliteBackend()
isSqliteBackend() isSqliteBackend()
// then //#then
expect(mockIsOpenCodeVersionAtLeast).toHaveBeenCalledTimes(1) expect(versionCheckCalls.length).toBe(1)
expect(mockExistsSync).toHaveBeenCalledTimes(1)
}) })
}) })