test(ai): assert close() releases the SQLite connection

This commit is contained in:
pr-eval
2026-07-28 10:47:48 +02:00
committed by can1357
parent 3fcebdfa7a
commit 9f5fb4d8ed
@@ -0,0 +1,64 @@
/**
* Regression coverage: `SqliteAuthCredentialStore.close()` must finalize every
* cached prepared statement before `#db.close()`.
*
* An unfinalized statement makes `sqlite3_close()` return SQLITE_BUSY, which
* bun:sqlite swallows — the connection (and its `-wal`/`-shm` handles) stays
* open. On Windows that leaves the files locked and breaks temp-dir teardown;
* on POSIX it is silent but still leaks descriptors.
*
* Observable contract: after `close()`, a fresh connection can take exclusive
* access to the file (`PRAGMA journal_mode=DELETE` needs it). With a leaked
* statement this throws "database is locked".
*/
import { Database } from "bun:sqlite";
import { afterEach, beforeEach, expect, test } from "bun:test";
import * as fs from "node:fs/promises";
import * as os from "node:os";
import * as path from "node:path";
import { SqliteAuthCredentialStore } from "@oh-my-pi/pi-ai/auth-storage";
import { removeWithRetries } from "../../utils/src/temp";
let tempDir = "";
beforeEach(async () => {
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "pi-ai-close-handles-"));
});
afterEach(async () => {
if (tempDir) {
await removeWithRetries(tempDir);
tempDir = "";
}
});
function expectExclusiveAccess(dbPath: string): void {
const observer = new Database(dbPath);
try {
// Leaving WAL requires an exclusive lock; a still-open connection from
// the closed store makes this throw SQLITE_BUSY.
observer.run("PRAGMA journal_mode=DELETE");
} finally {
observer.close();
}
}
test("close() releases the connection so the DB file can be locked exclusively", async () => {
const dbPath = path.join(tempDir, "agent.db");
const store = await SqliteAuthCredentialStore.open(dbPath);
store.close();
expect(() => expectExclusiveAccess(dbPath)).not.toThrow();
});
test("close() releases the connection after the lease/cache statements have run", async () => {
const dbPath = path.join(tempDir, "used.db");
const store = await SqliteAuthCredentialStore.open(dbPath);
// Exercise statements that close() previously forgot to finalize.
store.deleteCachePrefix("sticky:");
store.releaseCredentialRefreshLease(1, "owner");
store.close();
expect(() => expectExclusiveAccess(dbPath)).not.toThrow();
});