Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 11 additions & 7 deletions scripts/test-server-sharded.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,18 @@ export {};
/**
* `server/tests` in shards, because the whole directory in one process no longer runs.
*
* 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, and a pool opens its
* connections on first use and holds them until it is closed. So the connection count is a
* function of how many files have RUN, not of how many have been imported, and PostgreSQL's
* default `max_connections` is 100. When files left their pools open, a single
* `bun test server/tests` died with `sorry, too many clients already` BEFORE it reported
* anything — a failure that landed on whoever added the next file, not on the file that leaked.
*
* 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
* own pools and gives them back when it exits.
* Every file now closes its own pool, and `server/tests/database-pool-closing.test.ts` refuses
* a new file that does not, so a single-process run tracks the file currently running rather
* than the whole suite. Sharding stays because it is faster, not because the ceiling demands it.
* The suite is split into fixed-size groups of files and each group runs in its own `bun test`
* invocation.
*
* Why this script exists at all, rather than a remembered shell incantation: a sharded sweep is the
* only thing that has ever caught a test file that went red and stayed red. A targeted run passes,
Expand Down
6 changes: 5 additions & 1 deletion server/tests/account-mode.integration.test.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -406,3 +406,7 @@ describe("switching back to Personal", () => {
expect(rules[0]?.revokedAt).not.toBeNull();
});
});

afterAll(async () => {
await database.$client.close();
});
4 changes: 4 additions & 0 deletions server/tests/agent-handoff-endtoend.integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -252,3 +252,7 @@ describe("a hop, from the tool call to the delivery", () => {
).toBe(true);
});
});

afterAll(async () => {
await database.$client.close();
});
4 changes: 4 additions & 0 deletions server/tests/agent-handoff.integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -251,3 +251,7 @@ describe("a hop, against the database", () => {
});
});
});

afterAll(async () => {
await database.$client.close();
});
4 changes: 4 additions & 0 deletions server/tests/agent-key-rotation.integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,3 +159,7 @@ describe("editing a Bot's key", () => {
expect(await liveKeysFor(profile.id)).toHaveLength(0);
});
});

afterAll(async () => {
await database.$client.close();
});
4 changes: 4 additions & 0 deletions server/tests/app-access-routes.integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1673,3 +1673,7 @@ describe("a teammate on somebody else's Team Bot", () => {
});
});
});

afterAll(async () => {
await database.$client.close();
});
6 changes: 5 additions & 1 deletion server/tests/audit-cursor.integration.test.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -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();
});
6 changes: 5 additions & 1 deletion server/tests/audit-initiator.integration.test.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -291,3 +291,7 @@ describe("what started a run", () => {
expect(reattribute()).rejects.toThrow();
});
});

afterAll(async () => {
await database.$client.close();
});
6 changes: 5 additions & 1 deletion server/tests/audit-retention.integration.test.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -309,3 +309,7 @@ describe("keeping the audit trail to a retention policy", () => {
expect(deleted).toBe(1);
});
});

afterAll(async () => {
await database.$client.close();
});
4 changes: 4 additions & 0 deletions server/tests/auth-mobile-scheme.integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,7 @@ test("a deployment that runs the native app still lets it sign in", async () =>
process.env.EXPO_PROJECT_ID = "0b6c3a0e-2a5e-4a4c-9d5a-2f3b8f0c1d2e";
expect(await sendsSessionTo(createAuth(config, database))).toBe(true);
});

afterAll(async () => {
await database.$client.close();
});
4 changes: 4 additions & 0 deletions server/tests/bot-lifecycle.integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -706,3 +706,7 @@ describe("reset", () => {
).toEqual([]);
});
});

afterAll(async () => {
await database.$client.close();
});
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -175,3 +175,7 @@ describe("the account-mode backfill migration", () => {
});
});
});

afterAll(async () => {
await database.$client.close();
});
4 changes: 4 additions & 0 deletions server/tests/composio-connect-adopts-held-account.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -451,3 +451,7 @@ describe("Connect on a Shared app whose team account Composio already holds", ()
});
});
});

afterAll(async () => {
await database.$client.close();
});
4 changes: 4 additions & 0 deletions server/tests/composio-connection-kinds.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2043,3 +2043,7 @@ describe("what a brokered connection writes into the append-only trail", () => {
]).toEqual([""]);
});
});

afterAll(async () => {
await database.$client.close();
});
4 changes: 4 additions & 0 deletions server/tests/composio-connections.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5226,3 +5226,7 @@ test("a connect spends its check on the account it just made", async () => {
* action the check spent — is asserted by the check-does-not-come-back-clean test above, which now
* reaches that state by the ordinary path instead of only through a vendor that would not act.
*/

afterAll(async () => {
await database.$client.close();
});
4 changes: 4 additions & 0 deletions server/tests/computer-culler.integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -357,3 +357,7 @@ describe("suspending computers nobody is using", () => {
expect(stopped).toEqual([person]);
});
});

afterAll(async () => {
await database.$client.close();
});
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -208,3 +208,7 @@ describe("kept page frames", () => {
expect(await store.load(id, "")).toBe(null);
});
});

afterAll(async () => {
await database.$client.close();
});
Original file line number Diff line number Diff line change
Expand Up @@ -145,3 +145,7 @@ test("CONTROL: replica B allows the same ref while the run is still current", as
});
expect(result).toMatchObject({ action: "click" });
});

afterAll(async () => {
await database.$client.close();
});
6 changes: 5 additions & 1 deletion server/tests/computer-snapshot-store.integration.test.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -147,3 +147,7 @@ describe("a snapshot taken on one server", () => {
);
});
});

afterAll(async () => {
await database.$client.close();
});
4 changes: 4 additions & 0 deletions server/tests/credential-rotation-leak.integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,3 +162,7 @@ describe("a rotation the database refuses", () => {
expect(sentence).not.toContain("ciphertext");
});
});

afterAll(async () => {
await database.$client.close();
});
6 changes: 5 additions & 1 deletion server/tests/credentials.test.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -865,3 +865,7 @@ describe("admin credential API", () => {
expect(await rotate.text()).not.toContain("rotated-secret");
});
});

afterAll(async () => {
await database.$client.close();
});
30 changes: 30 additions & 0 deletions server/tests/database-pool-closing.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { expect, test } from "bun:test";

/**
* Every test file that opens a database pool closes it.
*
* `bun test` walks this directory in one process, and a pool a file leaves open keeps its
* connections until the process exits. Enough of those and the suite dies against PostgreSQL's
* connection ceiling as `sorry, too many clients already` — a failure that lands on whoever adds
* the next file, not on the file that leaked.
*
* The close belongs in the file's last-registered `afterAll`, because Bun runs those
* first-registered-first: a pool closed before the file's own cleanup takes the cleanup's queries
* with it.
*
* A guard rather than a lint rule because "opens and closes" is a pairing, not an import
* restriction, and `server/tests` is outside `tsc`, so types will not catch a missing one either.
*/
test("a file that opens a database pool closes it", async () => {
const files = await Array.fromAsync(
new Bun.Glob("server/tests/**/*.test.ts").scan({ cwd: process.cwd() }),
);
const leaking: string[] = [];
for (const file of files) {
const source = await Bun.file(file).text();
if (source.includes("createDatabase(") && !source.includes("$client.close")) {
leaking.push(file);
}
}
expect(leaking).toEqual([]);
});
4 changes: 4 additions & 0 deletions server/tests/delivery-store.integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -279,3 +279,7 @@ test("a push token registered by one person cannot be moved to another person's
]);
expect(await store.devices(owner)).toEqual([]);
});

afterAll(async () => {
await db.$client.close();
});
4 changes: 4 additions & 0 deletions server/tests/enterprise-controls.integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -967,3 +967,7 @@ describe("SCIM 2.0 through Better Auth", () => {
expect(after).toEqual([]);
});
});

afterAll(async () => {
await database.$client.close();
});
4 changes: 4 additions & 0 deletions server/tests/headless-components.integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -363,3 +363,7 @@ describe("an unattended routine turn answering with a governed chart", () => {
}
});
});

afterAll(async () => {
await database.$client.close();
});
4 changes: 4 additions & 0 deletions server/tests/isolated-bots.integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -664,3 +664,7 @@ describe("duplicating", () => {
}
});
});

afterAll(async () => {
await database.$client.close();
});
4 changes: 4 additions & 0 deletions server/tests/jsonb-encoding.integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,7 @@ describe("what a jsonb column actually stores", () => {
// Left in place: the trail is append-only, so a test cannot tidy up after itself. One row.
});
});

afterAll(async () => {
await database.$client.close();
});
6 changes: 5 additions & 1 deletion server/tests/last-sign-in-backfill.integration.test.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -112,3 +112,7 @@ describe("the backfill migration", () => {
expect(observed.get(neverHere)).toBeNull();
});
});

afterAll(async () => {
await database.$client.close();
});
4 changes: 4 additions & 0 deletions server/tests/mcp-grant-removal.integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -230,3 +230,7 @@ test("a re-added app a Bot was never granted again refuses the call", async () =
.catch(() => undefined);
}
});

afterAll(async () => {
await database.$client.close();
});
4 changes: 4 additions & 0 deletions server/tests/memory-intelligence.integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -384,3 +384,7 @@ test("editing an Intelligence-only memory while Intelligence is down answers una
store.update(ada, `intelligence:${id}`, { content: "Speaks Spanish" }),
).rejects.toBeInstanceOf(MemoryUnavailableError);
});

afterAll(async () => {
await database.$client.close();
});
4 changes: 4 additions & 0 deletions server/tests/offboarding-retirement.integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,3 +117,7 @@ test("a broker that will not answer still leaves the removal on the trail, and a
expect(attempts).toBe(2);
expect(events).toEqual(["person.access_revoked"]);
});

afterAll(async () => {
await database.$client.close();
});
4 changes: 4 additions & 0 deletions server/tests/owner-app-grants.integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -715,3 +715,7 @@ describe("a Shared app whose rule lets Bot owners decide", () => {
expect(await allowed(bot, owner)).toBe(true);
});
});

afterAll(async () => {
await database.$client.close();
});
6 changes: 5 additions & 1 deletion server/tests/page-frame-retention.integration.test.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -120,3 +120,7 @@ describe("page frame retention", () => {
expect(await kept()).toHaveLength(0);
});
});

afterAll(async () => {
await database.$client.close();
});
6 changes: 5 additions & 1 deletion server/tests/people-last-sign-in.integration.test.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -129,3 +129,7 @@ describe("when somebody was last here", () => {
).resolves.toBeUndefined();
});
});

afterAll(async () => {
await database.$client.close();
});
6 changes: 5 additions & 1 deletion server/tests/people-paging.integration.test.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -214,3 +214,7 @@ describe("reading the people in a deployment", () => {
expect(await store.find("nobody-by-that-id")).toBeUndefined();
});
});

afterAll(async () => {
await database.$client.close();
});
Loading