Files
oh-my-pi/packages/coding-agent/test/history-storage-sqlite-compat.test.ts
T
oldschoola 14252e71cb fix: Windows test failures — path handling, EBUSY, SQLite handle leaks
Fix all Windows-specific test failures caused by path handling problems
and EBUSY errors from unclosed SQLite database handles.

Root causes fixed:
1. POSIX path assumptions: replaced hard-coded file:///tmp, /repo, etc.
   with pathToFileURL/path.resolve/path.join computed expectations
2. shortenPath() now normalizes backslashes to forward slashes after ~
   and respects home directory boundaries
3. HistoryStorage.resetInstance() leaked its Database — added #close()
   that finalizes all prepared statements and closes the DB
4. AgentStorage gained the same resetInstance()/#close() pattern
5. SqliteAuthCredentialStore.close() leaked one-off prepared statements
   from inline this.#db.prepare() calls — wrapped each in try/finally
6. model-cache.ts used a process-global DB even for custom dbPath —
   now opens/closes per-call via withModelCacheDb
7. createAgentSession leaked AuthStorage on construction failure —
   added ownsAuthStorage cleanup in catch block
8. MnemopiBackend.removeDbFiles() now truly best-effort (catches errors)
9. TempDir retry window expanded from 4x10ms to 40x25ms
10. TempDir prefix convention: non-@ prefixes created dirs relative to
    cwd instead of os.tmpdir() — all test temp dirs now use @ prefix
11. Shell-escaped interpolated paths in bash tool tests
12. git core.autocrlf false in autoresearch test repo init

All 522 previously-failing Windows tests now pass.
2026-06-18 21:32:38 -07:00

62 lines
2.1 KiB
TypeScript

import { Database } from "bun:sqlite";
import { afterEach, beforeEach, expect, it } from "bun:test";
import { HistoryStorage } from "@oh-my-pi/pi-coding-agent/session/history-storage";
import { TempDir } from "@oh-my-pi/pi-utils";
import { readTableSql } from "./helpers/sqlite-inspect";
const LEGACY_TIMESTAMP = 1_700_000_000;
let tempDir: TempDir | null = null;
beforeEach(() => {
HistoryStorage.resetInstance();
});
afterEach(async () => {
HistoryStorage.resetInstance();
if (tempDir) {
await Bun.sleep(0);
await tempDir.remove().catch(() => {});
tempDir = null;
}
});
it("migrates legacy history schema away from unixepoch defaults", async () => {
tempDir = TempDir.createSync("@omp-history-storage-legacy-");
const dbPath = tempDir.join("history.db");
const legacyDb = new Database(dbPath);
legacyDb.exec(`
CREATE TABLE history (
id INTEGER PRIMARY KEY AUTOINCREMENT,
prompt TEXT NOT NULL,
created_at INTEGER NOT NULL DEFAULT (unixepoch()),
cwd TEXT
);
`);
legacyDb
.prepare("INSERT INTO history (prompt, created_at, cwd) VALUES (?, ?, ?)")
.run("legacy prompt", LEGACY_TIMESTAMP, "/tmp/legacy");
legacyDb.close();
const storage = HistoryStorage.open(dbPath);
await storage.add("new prompt", "/tmp/new");
const db = new Database(dbPath, { readonly: true });
try {
const prompts = db.prepare("SELECT prompt FROM history ORDER BY id ASC").all() as Array<{ prompt: string }>;
expect(prompts).toEqual([{ prompt: "legacy prompt" }, { prompt: "new prompt" }]);
expect(readTableSql(dbPath, "history")).not.toContain("unixepoch(");
expect(readTableSql(dbPath, "history")).toContain("strftime('%s','now')");
const indexRow = db
.prepare("SELECT 1 AS present FROM sqlite_master WHERE type = 'index' AND name = 'idx_history_created_at'")
.get() as { present?: number } | undefined;
expect(indexRow?.present).toBe(1);
const ftsRow = db
.prepare("SELECT 1 AS present FROM sqlite_master WHERE type = 'table' AND name = 'history_fts'")
.get() as { present?: number } | undefined;
expect(ftsRow?.present).toBe(1);
} finally {
db.close();
}
});