test(coding-agent): prevented sqlite database handle leaks in tests
- Track and explicitly close in-memory SQLite database handles in MCP auth tests. - Resolved Bun GC-related crashes occurring when parallel tests finalize dangling database handles.
This commit is contained in:
@@ -73,6 +73,17 @@ describe("/mcp auth commands", () => {
|
||||
let agentDir = "";
|
||||
let configPath = "";
|
||||
let originalMcpHost: string | undefined;
|
||||
// Track every in-memory auth store so afterEach can close the underlying
|
||||
// bun:sqlite Database. Leaked Database handles are JSDestructibleObjects that
|
||||
// JSC otherwise finalizes during an arbitrary later GC sweep — under
|
||||
// `bun test --parallel` that sweep can run mid-suite on the shared VM and
|
||||
// trip a Bun GC crash (SIGABRT "Pure virtual function called").
|
||||
const openAuthStores: AuthStorage[] = [];
|
||||
function freshAuthStorage(): AuthStorage {
|
||||
const storage = new AuthStorage(new SqliteAuthCredentialStore(new Database(":memory:")));
|
||||
openAuthStores.push(storage);
|
||||
return storage;
|
||||
}
|
||||
|
||||
beforeAll(() => {
|
||||
initTheme();
|
||||
@@ -105,6 +116,7 @@ describe("/mcp auth commands", () => {
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
while (openAuthStores.length > 0) openAuthStores.pop()?.close();
|
||||
vi.restoreAllMocks();
|
||||
restoreEnvValue("MCP_HOST", originalMcpHost);
|
||||
setProjectDir(originalProjectDir);
|
||||
@@ -119,7 +131,7 @@ describe("/mcp auth commands", () => {
|
||||
});
|
||||
|
||||
test("stores definition-only OAuth credentials under the expanded URL key", async () => {
|
||||
const authStorage = new AuthStorage(new SqliteAuthCredentialStore(new Database(":memory:")));
|
||||
const authStorage = freshAuthStorage();
|
||||
await authStorage.reload();
|
||||
const connectToServer = vi.spyOn(mcpClient, "connectToServer").mockRejectedValue(AUTH_ERROR);
|
||||
vi.spyOn(oauthFlow.MCPOAuthFlow.prototype, "login").mockResolvedValue({
|
||||
@@ -156,7 +168,7 @@ describe("/mcp auth commands", () => {
|
||||
});
|
||||
|
||||
test("reuses embedded DCR client secret during reauth token exchange", async () => {
|
||||
const authStorage = new AuthStorage(new SqliteAuthCredentialStore(new Database(":memory:")));
|
||||
const authStorage = freshAuthStorage();
|
||||
await authStorage.reload();
|
||||
await authStorage.set(oauthFlow.mcpOAuthCredentialId(EXPANDED_SERVER_URL), {
|
||||
type: "oauth",
|
||||
@@ -201,7 +213,7 @@ describe("/mcp auth commands", () => {
|
||||
});
|
||||
|
||||
test("clears both expanded and stale raw URL-keyed credentials on unauth", async () => {
|
||||
const authStorage = new AuthStorage(new SqliteAuthCredentialStore(new Database(":memory:")));
|
||||
const authStorage = freshAuthStorage();
|
||||
await authStorage.reload();
|
||||
await authStorage.set(oauthFlow.mcpOAuthCredentialId(EXPANDED_SERVER_URL), {
|
||||
type: "oauth",
|
||||
@@ -230,7 +242,7 @@ describe("/mcp auth commands", () => {
|
||||
});
|
||||
|
||||
test("clears url-keyed auth for discovered definition-only servers", async () => {
|
||||
const authStorage = new AuthStorage(new SqliteAuthCredentialStore(new Database(":memory:")));
|
||||
const authStorage = freshAuthStorage();
|
||||
await authStorage.reload();
|
||||
await authStorage.set(oauthFlow.mcpOAuthCredentialId(EXPANDED_SERVER_URL), {
|
||||
type: "oauth",
|
||||
|
||||
@@ -62,6 +62,7 @@ describe("MCPManager OAuth refresh failure", () => {
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
authStorage.close();
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
|
||||
@@ -41,6 +41,7 @@ describe("per-profile MCP OAuth binding", () => {
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
authStorage.close();
|
||||
setProfile(originalProfile);
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user