diff --git a/scripts/test-server-sharded.ts b/scripts/test-server-sharded.ts index 81d5c3ecfb..b643a9eee6 100644 --- a/scripts/test-server-sharded.ts +++ b/scripts/test-server-sharded.ts @@ -1,12 +1,16 @@ export {}; /** - * `server/tests` in shards, because the whole directory in one process no longer runs. + * `server/tests` in shards: faster wall-clock, and headroom under PostgreSQL's connection ceiling. * - * Around a hundred of those files open a module-scope connection pool. PostgreSQL's default - * `max_connections` is 100, so a single `bun test server/tests` exhausts the server's connection - * slots during the import phase and dies with `sorry, too many clients already` BEFORE it reports - * anything. That is a harness ceiling, not a regression, and it is never a reason to change source. + * Around a hundred of those files open a module-scope connection pool. Constructing one costs no + * connection: the driver opens the pool's `max` (two by default) on the file's first query, and + * a pool nobody closes holds its slots for the rest of the run. PostgreSQL's default + * `max_connections` is 100, so a single `bun test server/tests` crosses that ceiling as a function + * of how many files have RUN, not how many have been imported, and dies with `sorry, too many + * clients already` BEFORE it reports anything. That is why #785 has every file close its pool and + * a guard test keep the pairing: an unclosed pool is a defect in the source, not a harness + * ceiling. * * Shards keep the pools-per-process count under the ceiling. The suite is split into fixed-size * groups of files and each group runs in its own `bun test` invocation, so each process opens its diff --git a/server/tests/account-mode.integration.test.ts b/server/tests/account-mode.integration.test.ts index 01c79f5a1d..f2f4160f3d 100644 --- a/server/tests/account-mode.integration.test.ts +++ b/server/tests/account-mode.integration.test.ts @@ -1,5 +1,5 @@ // server/tests/account-mode.integration.test.ts -import { afterEach, beforeEach, describe, expect, test } from "bun:test"; +import { afterAll, afterEach, beforeEach, describe, expect, test } from "bun:test"; import { randomUUID } from "node:crypto"; import { eq } from "drizzle-orm"; import { createAuditStore } from "../src/audit"; @@ -406,3 +406,7 @@ describe("switching back to Personal", () => { expect(rules[0]?.revokedAt).not.toBeNull(); }); }); + +afterAll(async () => { + await database.$client.close(); +}); diff --git a/server/tests/app-access-routes.integration.test.ts b/server/tests/app-access-routes.integration.test.ts index 88ee66890e..3915509b46 100644 --- a/server/tests/app-access-routes.integration.test.ts +++ b/server/tests/app-access-routes.integration.test.ts @@ -373,6 +373,7 @@ afterAll(async () => { await database .delete(users) .where(inArray(users.id, [person, other, admin, maker])); + await database.$client.close(); }); describe("what each app a Bot holds is, for the person asking", () => { diff --git a/server/tests/audit-cursor.integration.test.ts b/server/tests/audit-cursor.integration.test.ts index 6c91324800..eeed0e03c0 100644 --- a/server/tests/audit-cursor.integration.test.ts +++ b/server/tests/audit-cursor.integration.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, test } from "bun:test"; +import { afterAll, describe, expect, test } from "bun:test"; import { sql } from "drizzle-orm"; import { createApp } from "../src/app"; import { createAuditReader } from "../src/audit"; @@ -158,3 +158,7 @@ describe("a cursor over rows written within one millisecond", () => { expect(await walk(targetId)).toEqual(expected); }); }); + +afterAll(async () => { + await database.$client.close(); +}); diff --git a/server/tests/audit-initiator.integration.test.ts b/server/tests/audit-initiator.integration.test.ts index 0465428b9a..76e9e504fd 100644 --- a/server/tests/audit-initiator.integration.test.ts +++ b/server/tests/audit-initiator.integration.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, test } from "bun:test"; +import { afterAll, describe, expect, test } from "bun:test"; import { eq } from "drizzle-orm"; import { auditQueryFromUrl, @@ -291,3 +291,7 @@ describe("what started a run", () => { expect(reattribute()).rejects.toThrow(); }); }); + +afterAll(async () => { + await database.$client.close(); +}); diff --git a/server/tests/audit-retention.integration.test.ts b/server/tests/audit-retention.integration.test.ts index 3a2b9f4706..fb98464ec7 100644 --- a/server/tests/audit-retention.integration.test.ts +++ b/server/tests/audit-retention.integration.test.ts @@ -1,4 +1,4 @@ -import { afterEach, describe, expect, test } from "bun:test"; +import { afterAll, afterEach, describe, expect, test } from "bun:test"; import { eq, sql } from "drizzle-orm"; import postgres from "postgres"; import { sweepAuditTrail } from "../src/audit-retention"; @@ -309,3 +309,7 @@ describe("keeping the audit trail to a retention policy", () => { expect(deleted).toBe(1); }); }); + +afterAll(async () => { + await database.$client.close(); +}); diff --git a/server/tests/bot-lifecycle.integration.test.ts b/server/tests/bot-lifecycle.integration.test.ts index fc01397118..3aa8895464 100644 --- a/server/tests/bot-lifecycle.integration.test.ts +++ b/server/tests/bot-lifecycle.integration.test.ts @@ -229,6 +229,7 @@ afterAll(async () => { await database .delete(users) .where(inArray(users.id, [owner, other, `${prefix}-chooser`])); + await database.$client.close(); }); describe("pause", () => { diff --git a/server/tests/brokered-account-mode-backfill.integration.test.ts b/server/tests/brokered-account-mode-backfill.integration.test.ts index be7b15f1ca..d5a7b3010a 100644 --- a/server/tests/brokered-account-mode-backfill.integration.test.ts +++ b/server/tests/brokered-account-mode-backfill.integration.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, test } from "bun:test"; +import { afterAll, describe, expect, test } from "bun:test"; import { eq, inArray, sql } from "drizzle-orm"; import { createDatabase } from "../src/db/client"; import { mcpServers, mcpTools } from "../src/db/schema"; @@ -175,3 +175,7 @@ describe("the account-mode backfill migration", () => { }); }); }); + +afterAll(async () => { + await database.$client.close(); +}); diff --git a/server/tests/component-store.integration.test.ts b/server/tests/component-store.integration.test.ts index 89b71ab5d1..d1cfe5069b 100644 --- a/server/tests/component-store.integration.test.ts +++ b/server/tests/component-store.integration.test.ts @@ -70,6 +70,7 @@ afterAll(async () => { .where(inArray(componentExclusions.componentName, names)); await database.delete(components).where(inArray(components.name, names)); await database.delete(agents).where(inArray(agents.id, [...fixtureAgentIds])); + await database.$client.close(); }); describe("deciding whether a Bot may use a component", () => { diff --git a/server/tests/composio-connection-kinds.test.ts b/server/tests/composio-connection-kinds.test.ts index 844e156bf3..1eb37cb628 100644 --- a/server/tests/composio-connection-kinds.test.ts +++ b/server/tests/composio-connection-kinds.test.ts @@ -904,6 +904,7 @@ afterEach(() => useComposioClient(null)); afterAll(async () => { await clean(); + await database.$client.close(); }); /** Every action Composio was asked to run, so "nothing reached the vendor" is an assertion. */ diff --git a/server/tests/composio-connections.test.ts b/server/tests/composio-connections.test.ts index ed8267efdb..eabdf7decb 100644 --- a/server/tests/composio-connections.test.ts +++ b/server/tests/composio-connections.test.ts @@ -1122,6 +1122,7 @@ afterEach(() => useComposioClient(null)); afterAll(async () => { await clean(); await cleanForeign(); + await database.$client.close(); }); /** diff --git a/server/tests/computer-page-frame-store.integration.test.ts b/server/tests/computer-page-frame-store.integration.test.ts index 38840bc4fc..219a086283 100644 --- a/server/tests/computer-page-frame-store.integration.test.ts +++ b/server/tests/computer-page-frame-store.integration.test.ts @@ -1,4 +1,4 @@ -import { afterEach, describe, expect, test } from "bun:test"; +import { afterAll, afterEach, describe, expect, test } from "bun:test"; import { eq } from "drizzle-orm"; import { createPageFrameStore } from "../src/computer/page-frames"; import { createDatabase } from "../src/db/client"; @@ -208,3 +208,7 @@ describe("kept page frames", () => { expect(await store.load(id, "")).toBe(null); }); }); + +afterAll(async () => { + await database.$client.close(); +}); diff --git a/server/tests/computer-session-guard-replicas.integration.test.ts b/server/tests/computer-session-guard-replicas.integration.test.ts index 2484ffefdb..9df94797bb 100644 --- a/server/tests/computer-session-guard-replicas.integration.test.ts +++ b/server/tests/computer-session-guard-replicas.integration.test.ts @@ -106,6 +106,7 @@ afterAll(async () => { await database .delete(computerSnapshot) .where(eq(computerSnapshot.computerId, botId)); + await database.$client.close(); }); test("replica B refuses a ref that replica A took before the container was replaced", async () => { diff --git a/server/tests/computer-snapshot-store.integration.test.ts b/server/tests/computer-snapshot-store.integration.test.ts index 2c53e91c69..dcc27bea3f 100644 --- a/server/tests/computer-snapshot-store.integration.test.ts +++ b/server/tests/computer-snapshot-store.integration.test.ts @@ -1,4 +1,4 @@ -import { afterEach, describe, expect, test } from "bun:test"; +import { afterAll, afterEach, describe, expect, test } from "bun:test"; import type { SnapshotElement } from "../src/computer/schema"; import { createSnapshotStore, @@ -147,3 +147,7 @@ describe("a snapshot taken on one server", () => { ); }); }); + +afterAll(async () => { + await database.$client.close(); +}); diff --git a/server/tests/credential-rotation-leak.integration.test.ts b/server/tests/credential-rotation-leak.integration.test.ts index e701442c02..06a1be510a 100644 --- a/server/tests/credential-rotation-leak.integration.test.ts +++ b/server/tests/credential-rotation-leak.integration.test.ts @@ -84,6 +84,7 @@ afterAll(async () => { for (const id of created.splice(0)) { await database.delete(credentials).where(eq(credentials.id, id)); } + await database.$client.close(); }); describe("a rotation the database refuses", () => { diff --git a/server/tests/credentials.test.ts b/server/tests/credentials.test.ts index 771f4abafe..26d60bf9d7 100644 --- a/server/tests/credentials.test.ts +++ b/server/tests/credentials.test.ts @@ -1,4 +1,4 @@ -import { afterEach, describe, expect, test } from "bun:test"; +import { afterAll, afterEach, describe, expect, test } from "bun:test"; import { randomUUID } from "node:crypto"; import { eq } from "drizzle-orm"; import { createApp } from "../src/app"; @@ -865,3 +865,7 @@ describe("admin credential API", () => { expect(await rotate.text()).not.toContain("rotated-secret"); }); }); + +afterAll(async () => { + await database.$client.close(); +}); diff --git a/server/tests/database-pool-closing.test.ts b/server/tests/database-pool-closing.test.ts new file mode 100644 index 0000000000..7305e3eaf8 --- /dev/null +++ b/server/tests/database-pool-closing.test.ts @@ -0,0 +1,41 @@ +import { readdirSync, readFileSync } from "node:fs"; +import { join } from "node:path"; +import { describe, expect, test } from "bun:test"; + +/** + * Every pool a test file opens is closed by the same file (#785). + * + * The suite runs in a single process, so a module-scope pool nobody closes holds its two + * connections for the rest of the run. PostgreSQL's ceiling is 100, and when it is crossed the + * failure lands on whoever added the next file, not on anything they did wrong: #770's branch + * saw 114 failures and 402 `sorry, too many clients already`, none of them about the change + * under review. + * + * A pairing rule, checked against the file's own text: every `const = createDatabase(` + * needs a `.$client.close()` (or the `$client.end(` some files already use) somewhere in + * the same file. `server/tests` is outside `tsc`, so no type error would notice file 118 quietly + * reintroducing the leak; this is what notices. The close must sit in the file's LAST top-level + * `afterAll` (or one registered after every other hook): Bun runs `afterAll` hooks + * first-registered-first, and a pool closed before a cleanup's deletes takes those deletes with + * it. + */ +describe("every database pool a test file opens is closed", () => { + test("each createDatabase variable is closed or ended in its own file", () => { + const offenders: string[] = []; + for (const file of readdirSync(import.meta.dir) + .filter((name) => name.endsWith(".test.ts")) + .sort()) { + const text = readFileSync(join(import.meta.dir, file), "utf8"); + for (const match of text.matchAll(/const (\w+) = createDatabase\(/g)) { + const name = match[1]!; + if ( + !text.includes(`${name}.$client.close()`) && + !new RegExp(`\\b${name}\\.\\$client\\.end\\(`).test(text) + ) { + offenders.push(`${file}: ${name}`); + } + } + } + expect(offenders).toEqual([]); + }); +}); diff --git a/server/tests/database.test.ts b/server/tests/database.test.ts index ee55ca9d23..c632a63429 100644 --- a/server/tests/database.test.ts +++ b/server/tests/database.test.ts @@ -1,4 +1,4 @@ -import { expect, test } from "bun:test"; +import { afterAll, expect, test } from "bun:test"; import { createDatabase } from "../src/db/client"; import { TEST_POOL } from "./support/database"; @@ -10,3 +10,7 @@ test("creates a typed database boundary without opening a query", () => { expect(database.query.users).toBeDefined(); }); + +afterAll(async () => { + await database.$client.close(); +}); diff --git a/server/tests/headless-components.integration.test.ts b/server/tests/headless-components.integration.test.ts index c19af9a095..cc3f9e3cb7 100644 --- a/server/tests/headless-components.integration.test.ts +++ b/server/tests/headless-components.integration.test.ts @@ -82,6 +82,7 @@ afterAll(async () => { await database .delete(agents) .where(inArray(agents.id, [allowedBot, deniedBot])); + await database.$client.close(); }); function lifecycle(input: RunAgentInput, body: BaseEvent[]): BaseEvent[] { diff --git a/server/tests/jsonb-encoding.integration.test.ts b/server/tests/jsonb-encoding.integration.test.ts index f2d3794900..0fa14bef6d 100644 --- a/server/tests/jsonb-encoding.integration.test.ts +++ b/server/tests/jsonb-encoding.integration.test.ts @@ -23,6 +23,7 @@ const agentId = `agent_jsonb_${suite}`; afterAll(async () => { await database.delete(agents).where(eq(agents.id, agentId)); + await database.$client.close(); }); describe("what a jsonb column actually stores", () => { diff --git a/server/tests/last-sign-in-backfill.integration.test.ts b/server/tests/last-sign-in-backfill.integration.test.ts index 8e46a66df6..aefdd764ef 100644 --- a/server/tests/last-sign-in-backfill.integration.test.ts +++ b/server/tests/last-sign-in-backfill.integration.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, test } from "bun:test"; +import { afterAll, describe, expect, test } from "bun:test"; import { inArray, sql } from "drizzle-orm"; import { createDatabase } from "../src/db/client"; import { sessions, users } from "../src/db/schema"; @@ -112,3 +112,7 @@ describe("the backfill migration", () => { expect(observed.get(neverHere)).toBeNull(); }); }); + +afterAll(async () => { + await database.$client.close(); +}); diff --git a/server/tests/mcp-grant-removal.integration.test.ts b/server/tests/mcp-grant-removal.integration.test.ts index e359f980ab..f7b1960623 100644 --- a/server/tests/mcp-grant-removal.integration.test.ts +++ b/server/tests/mcp-grant-removal.integration.test.ts @@ -104,6 +104,7 @@ afterAll(async () => { .delete(mcpServers) .where(inArray(mcpServers.id, [serverId, keptServerId])); await database.delete(agents).where(eq(agents.id, botId)); + await database.$client.close(); }); test("removing an app takes its grants with it, and adding it back grants nothing", async () => { diff --git a/server/tests/offboarding-retirement.integration.test.ts b/server/tests/offboarding-retirement.integration.test.ts index d0539a7864..a0bd0a1131 100644 --- a/server/tests/offboarding-retirement.integration.test.ts +++ b/server/tests/offboarding-retirement.integration.test.ts @@ -29,6 +29,7 @@ afterAll(async () => { .where(eq(revokedAccess.email, memberEmail)); await database.delete(sessions).where(eq(sessions.userId, memberId)); await database.delete(users).where(inArray(users.id, [adminId, memberId])); + await database.$client.close(); }); function appFor(retire: () => Promise<{ retired: number }>) { diff --git a/server/tests/owner-app-grants.integration.test.ts b/server/tests/owner-app-grants.integration.test.ts index cbae0bfba2..b9f57d6f52 100644 --- a/server/tests/owner-app-grants.integration.test.ts +++ b/server/tests/owner-app-grants.integration.test.ts @@ -261,6 +261,7 @@ beforeEach(async () => { afterAll(async () => { await clear(); await database.delete(users).where(inArray(users.id, people)); + await database.$client.close(); }); describe("an owner giving their own app to their own Bot", () => { diff --git a/server/tests/page-frame-retention.integration.test.ts b/server/tests/page-frame-retention.integration.test.ts index ac392cd718..082bf12a34 100644 --- a/server/tests/page-frame-retention.integration.test.ts +++ b/server/tests/page-frame-retention.integration.test.ts @@ -1,4 +1,4 @@ -import { afterEach, describe, expect, test } from "bun:test"; +import { afterAll, afterEach, describe, expect, test } from "bun:test"; import { eq, sql } from "drizzle-orm"; import { startRetentionSweeps } from "../src/audit-retention"; import { @@ -120,3 +120,7 @@ describe("page frame retention", () => { expect(await kept()).toHaveLength(0); }); }); + +afterAll(async () => { + await database.$client.close(); +}); diff --git a/server/tests/people-last-sign-in.integration.test.ts b/server/tests/people-last-sign-in.integration.test.ts index e1ece9040b..71e4a7b7ec 100644 --- a/server/tests/people-last-sign-in.integration.test.ts +++ b/server/tests/people-last-sign-in.integration.test.ts @@ -1,4 +1,4 @@ -import { afterEach, describe, expect, test } from "bun:test"; +import { afterAll, afterEach, describe, expect, test } from "bun:test"; import { eq, inArray } from "drizzle-orm"; import { stampSignIn } from "../src/auth"; import { createDatabase } from "../src/db/client"; @@ -129,3 +129,7 @@ describe("when somebody was last here", () => { ).resolves.toBeUndefined(); }); }); + +afterAll(async () => { + await database.$client.close(); +}); diff --git a/server/tests/people-paging.integration.test.ts b/server/tests/people-paging.integration.test.ts index f615396107..30f264ab37 100644 --- a/server/tests/people-paging.integration.test.ts +++ b/server/tests/people-paging.integration.test.ts @@ -1,4 +1,4 @@ -import { afterEach, describe, expect, test } from "bun:test"; +import { afterAll, afterEach, describe, expect, test } from "bun:test"; import { inArray } from "drizzle-orm"; import { createDatabase } from "../src/db/client"; import { sessions, users } from "../src/db/schema"; @@ -193,3 +193,7 @@ describe("reading the people in a deployment", () => { expect(await store.find("nobody-by-that-id")).toBeUndefined(); }); }); + +afterAll(async () => { + await database.$client.close(); +}); diff --git a/server/tests/plugin-credential-binding.integration.test.ts b/server/tests/plugin-credential-binding.integration.test.ts index 5d5eef8f53..061127cdc4 100644 --- a/server/tests/plugin-credential-binding.integration.test.ts +++ b/server/tests/plugin-credential-binding.integration.test.ts @@ -134,6 +134,7 @@ afterAll(async () => { await database .delete(credentials) .where(inArray(credentials.id, [ownCredentialId, otherCredentialId])); + await database.$client.close(); }); async function storedUrl(id: string) { diff --git a/server/tests/plugin-curated-credential.integration.test.ts b/server/tests/plugin-curated-credential.integration.test.ts index 44ea8a58af..c98a08fe53 100644 --- a/server/tests/plugin-curated-credential.integration.test.ts +++ b/server/tests/plugin-curated-credential.integration.test.ts @@ -120,6 +120,7 @@ afterAll(async () => { personalCredentialId, ]), ); + await database.$client.close(); }); describe("a curated server may only be pointed at its own kind of credential", () => { diff --git a/server/tests/plugin-routes.integration.test.ts b/server/tests/plugin-routes.integration.test.ts index f4b1ae4e65..f2e3047efa 100644 --- a/server/tests/plugin-routes.integration.test.ts +++ b/server/tests/plugin-routes.integration.test.ts @@ -145,6 +145,7 @@ afterAll(async () => { ownCredentialId, ]), ); + await database.$client.close(); }); async function serverRow() { diff --git a/server/tests/plugin-store.integration.test.ts b/server/tests/plugin-store.integration.test.ts index 342559f8bf..72d712e665 100644 --- a/server/tests/plugin-store.integration.test.ts +++ b/server/tests/plugin-store.integration.test.ts @@ -4437,6 +4437,7 @@ afterAll(async () => { .where(eq(brokeredConnections.app, `sweep_witness_${suite}`)); await database.delete(agents).where(eq(agents.id, "bot_helper")); await database.delete(users).where(eq(users.id, "user_leaver")); + await database.$client.close(); }); test("a Composio connection row survives the person being deleted", async () => { diff --git a/server/tests/plugin-user-credential.integration.test.ts b/server/tests/plugin-user-credential.integration.test.ts index 988515fab2..b6d962181d 100644 --- a/server/tests/plugin-user-credential.integration.test.ts +++ b/server/tests/plugin-user-credential.integration.test.ts @@ -360,6 +360,7 @@ afterAll(async () => { await database.delete(agents).where(eq(agents.id, botId)); await database.delete(users).where(eq(users.id, askerId)); await database.delete(users).where(eq(users.id, otherId)); + await database.$client.close(); }); describe("a person who has not connected", () => { diff --git a/server/tests/policy-durability.integration.test.ts b/server/tests/policy-durability.integration.test.ts index 5e0b9edcdc..2bc99a420a 100644 --- a/server/tests/policy-durability.integration.test.ts +++ b/server/tests/policy-durability.integration.test.ts @@ -1,4 +1,4 @@ -import { afterEach, describe, expect, test } from "bun:test"; +import { afterAll, afterEach, describe, expect, test } from "bun:test"; import { eq } from "drizzle-orm"; import { createPolicyStore, @@ -113,3 +113,7 @@ describe("a boundary set while running", () => { expect(store.get()).toEqual(configured); }); }); + +afterAll(async () => { + await database.$client.close(); +}); diff --git a/server/tests/policy-fanout.integration.test.ts b/server/tests/policy-fanout.integration.test.ts index 2f8571c3b8..520fa295e7 100644 --- a/server/tests/policy-fanout.integration.test.ts +++ b/server/tests/policy-fanout.integration.test.ts @@ -1,4 +1,4 @@ -import { afterEach, describe, expect, test } from "bun:test"; +import { afterAll, afterEach, describe, expect, test } from "bun:test"; import { eq } from "drizzle-orm"; import { startPolicyListener } from "../src/computer/policy-listener"; import { @@ -220,3 +220,7 @@ describe("the announcement itself", () => { expect(store.get().deny).toEqual([RULE]); }); }); + +afterAll(async () => { + await database.$client.close(); +}); diff --git a/server/tests/sandboxed-components.integration.test.ts b/server/tests/sandboxed-components.integration.test.ts index 3bccd2f595..1332265234 100644 --- a/server/tests/sandboxed-components.integration.test.ts +++ b/server/tests/sandboxed-components.integration.test.ts @@ -39,6 +39,7 @@ afterAll(async () => { .delete(sandboxedComponents) .where(eq(sandboxedComponents.name, name)); await database.delete(components).where(eq(components.name, name)); + await database.$client.close(); }); describe("authoring a component without a rebuild", () => { diff --git a/server/tests/sandboxed-publication.integration.test.ts b/server/tests/sandboxed-publication.integration.test.ts index 46d7e82990..e464711147 100644 --- a/server/tests/sandboxed-publication.integration.test.ts +++ b/server/tests/sandboxed-publication.integration.test.ts @@ -1,4 +1,4 @@ -import { afterEach, describe, expect, test } from "bun:test"; +import { afterAll, afterEach, describe, expect, test } from "bun:test"; import { randomUUID } from "node:crypto"; import { eq, sql } from "drizzle-orm"; import type { MiddlewareHandler } from "hono"; @@ -244,3 +244,7 @@ describe("the generic Published switch on a sandboxed component", () => { ).not.toContain(name); }); }); + +afterAll(async () => { + await database.$client.close(); +}); diff --git a/server/tests/server-side-tools.integration.test.ts b/server/tests/server-side-tools.integration.test.ts index fd6ec6819b..b2249c3734 100644 --- a/server/tests/server-side-tools.integration.test.ts +++ b/server/tests/server-side-tools.integration.test.ts @@ -115,6 +115,7 @@ afterAll(async () => { await database.delete(agents).where(eq(agents.id, holderId)); await database.delete(agents).where(eq(agents.id, strangerId)); await mock.stop(); + await database.$client.close(); }); describe("the tools a Bot is handed on the server", () => { diff --git a/server/tests/shared-brokered-routes.test.ts b/server/tests/shared-brokered-routes.test.ts index a58cc8f284..a92cf7a0ab 100644 --- a/server/tests/shared-brokered-routes.test.ts +++ b/server/tests/shared-brokered-routes.test.ts @@ -1,5 +1,5 @@ // server/tests/shared-brokered-routes.test.ts -import { afterEach, beforeEach, describe, expect, test } from "bun:test"; +import { afterAll, afterEach, beforeEach, describe, expect, test } from "bun:test"; import { randomUUID } from "node:crypto"; import { eq } from "drizzle-orm"; import { Hono, type MiddlewareHandler } from "hono"; @@ -573,3 +573,7 @@ describe("removing a Shared app", () => { expect(line).toContain(app); }); }); + +afterAll(async () => { + await database.$client.close(); +}); diff --git a/server/tests/shared-brokered-schema.integration.test.ts b/server/tests/shared-brokered-schema.integration.test.ts index 5c281c3604..0ac43e7cb6 100644 --- a/server/tests/shared-brokered-schema.integration.test.ts +++ b/server/tests/shared-brokered-schema.integration.test.ts @@ -24,6 +24,7 @@ afterAll(async () => { await database.execute( sql`delete from brokered_connections where app = ${app}`, ); + await database.$client.close(); }); describe("brokered_connections", () => { diff --git a/server/tests/shared-brokered-store.integration.test.ts b/server/tests/shared-brokered-store.integration.test.ts index 0c125b87fa..5e14ce065d 100644 --- a/server/tests/shared-brokered-store.integration.test.ts +++ b/server/tests/shared-brokered-store.integration.test.ts @@ -1,5 +1,5 @@ // server/tests/shared-brokered-store.integration.test.ts -import { afterEach, beforeEach, describe, expect, test } from "bun:test"; +import { afterAll, afterEach, beforeEach, describe, expect, test } from "bun:test"; import { randomUUID } from "node:crypto"; import { and, eq, inArray } from "drizzle-orm"; import { createAuditStore } from "../src/audit"; @@ -781,3 +781,7 @@ describe("an account the vendor no longer holds", () => { expect(await rowsFor("person")).toEqual([admin]); }); }); + +afterAll(async () => { + await database.$client.close(); +}); diff --git a/server/tests/shared-computer-run.integration.test.ts b/server/tests/shared-computer-run.integration.test.ts index ecadfcece8..b2848f28d4 100644 --- a/server/tests/shared-computer-run.integration.test.ts +++ b/server/tests/shared-computer-run.integration.test.ts @@ -1,4 +1,4 @@ -import { afterEach, describe, expect, test } from "bun:test"; +import { afterAll, afterEach, describe, expect, test } from "bun:test"; import type { AuditEventInput, AuditStore } from "../src/audit"; import { StaleSnapshotError } from "../src/computer/client"; import { createComputerGateway } from "../src/computer/gateway"; @@ -275,3 +275,7 @@ describe("a Bot on the one shared computer, against the table", () => { } }); }); + +afterAll(async () => { + await database.$client.close(); +}); diff --git a/server/tests/shared-gated-mode.integration.test.ts b/server/tests/shared-gated-mode.integration.test.ts index 1d0a6e46ed..39e5e9264c 100644 --- a/server/tests/shared-gated-mode.integration.test.ts +++ b/server/tests/shared-gated-mode.integration.test.ts @@ -1,5 +1,5 @@ // server/tests/shared-gated-mode.integration.test.ts -import { afterEach, beforeEach, describe, expect, test } from "bun:test"; +import { afterAll, afterEach, beforeEach, describe, expect, test } from "bun:test"; import { randomUUID } from "node:crypto"; import { eq } from "drizzle-orm"; import { createAuditStore } from "../src/audit"; @@ -153,3 +153,7 @@ describe("connectionTokenFor's gatedMode guard", () => { expect(sentAs).toEqual([]); }); }); + +afterAll(async () => { + await database.$client.close(); +}); diff --git a/server/tests/shared-use-store.integration.test.ts b/server/tests/shared-use-store.integration.test.ts index 8c226408bc..9485e9192b 100644 --- a/server/tests/shared-use-store.integration.test.ts +++ b/server/tests/shared-use-store.integration.test.ts @@ -99,6 +99,7 @@ afterAll(async () => { ["owner", "admin", "named", "newhire", "stranger"].map(id), ), ); + await database.$client.close(); }); const call = ( diff --git a/server/tests/shared-wiring.integration.test.ts b/server/tests/shared-wiring.integration.test.ts index 614ba2fd7a..2829a93dc2 100644 --- a/server/tests/shared-wiring.integration.test.ts +++ b/server/tests/shared-wiring.integration.test.ts @@ -1,5 +1,5 @@ // server/tests/shared-wiring.integration.test.ts -import { afterEach, beforeEach, describe, expect, test } from "bun:test"; +import { afterAll, afterEach, beforeEach, describe, expect, test } from "bun:test"; import { randomUUID } from "node:crypto"; import { eq } from "drizzle-orm"; import { createApp } from "../src/app"; @@ -150,3 +150,7 @@ describe("a deployment built without them", () => { expect(response.status).toBe(503); }); }); + +afterAll(async () => { + await database.$client.close(); +}); diff --git a/server/tests/skill-ownership.integration.test.ts b/server/tests/skill-ownership.integration.test.ts index 33049383d6..ca87c5271c 100644 --- a/server/tests/skill-ownership.integration.test.ts +++ b/server/tests/skill-ownership.integration.test.ts @@ -102,6 +102,7 @@ afterAll(async () => { .delete(agents) .where(inArray(agents.id, [aliceBot, sharedBot])); await database.delete(users).where(inArray(users.id, [alice, bob])); + await database.$client.close(); }); describe("what a person sees", () => { diff --git a/server/tests/skill-tools.integration.test.ts b/server/tests/skill-tools.integration.test.ts index 449e35fe27..25796bd02b 100644 --- a/server/tests/skill-tools.integration.test.ts +++ b/server/tests/skill-tools.integration.test.ts @@ -97,6 +97,7 @@ afterAll(async () => { await database.delete(mcpServers).where(eq(mcpServers.id, server)); await database.delete(agents).where(eq(agents.id, bot)); await database.delete(users).where(eq(users.id, author)); + await database.$client.close(); }); async function saveSkill(tools: string[] | undefined, slug = skill) { diff --git a/server/tests/skill-uninstall-grants.integration.test.ts b/server/tests/skill-uninstall-grants.integration.test.ts index 53fc0c5bbd..9848e910b3 100644 --- a/server/tests/skill-uninstall-grants.integration.test.ts +++ b/server/tests/skill-uninstall-grants.integration.test.ts @@ -85,6 +85,7 @@ afterAll(async () => { .delete(agents) .where(inArray(agents.id, [aliceBot, sharedBot])); await database.delete(users).where(inArray(users.id, [alice, bob])); + await database.$client.close(); }); function routesAs(actor: { diff --git a/server/tests/store-account-ref.integration.test.ts b/server/tests/store-account-ref.integration.test.ts index e4b901db5e..3aab7ef2e9 100644 --- a/server/tests/store-account-ref.integration.test.ts +++ b/server/tests/store-account-ref.integration.test.ts @@ -9,7 +9,7 @@ // audit row. `brokered_connections_holder_check` is `(person AND user_id IS NOT NULL) OR // (deployment AND user_id IS NULL)`, and `''` satisfies `IS NOT NULL`, so the person-with-no-user // row IS writable and is what both tests below seed. -import { afterEach, describe, expect, test } from "bun:test"; +import { afterAll, afterEach, describe, expect, test } from "bun:test"; import { randomUUID } from "node:crypto"; import { eq, inArray } from "drizzle-orm"; import { createAuditStore } from "../src/audit"; @@ -134,3 +134,7 @@ describe("a stored pair that names nobody", () => { expect(outcome).toBeInstanceOf(PluginInvariantError); }); }); + +afterAll(async () => { + await database.$client.close(); +});