import { beforeEach, describe, expect, mock, test } from "bun:test"; const kv = new Map(); mock.module("cloudflare:workers", () => ({ env: { KV: { get: async (key: string) => kv.get(key) ?? null, put: async (key: string, value: string) => void kv.set(key, value), }, }, waitUntil: () => undefined, })); const repoIds = ["repo-a", "repo-b", "repo-c"]; mock.module("./db", () => ({ all: async (sql: string, ...params: unknown[]) => { if (sql.includes("WHERE id > ?")) { const [cursor, limit] = params as [string, number]; return repoIds .filter((id) => id > cursor) .slice(0, limit) .map((id) => ({ id })); } return repoIds.map((id) => ({ id })); }, chunks: (items: T[]) => [items], placeholders: (count: number) => Array.from({ length: count }, () => "?").join(", "), })); mock.module("./scoped-db/locators", () => ({ forgetLocator: async () => undefined, forgetScope: async () => undefined, })); const { memoryScopes } = await import("./scoped-db/testing"); const scopes = memoryScopes(); const visited: string[] = []; mock.module("./scoped-db", () => ({ ...scopes.module, repoDb: (repoId: string) => { visited.push(repoId); return scopes.repoDb(repoId); }, })); const { backfillDeployTokenIndex, recordDeployToken, revokeAllDeployTokensCreatedBy, revokeDeployTokensMintedBy } = await import("./deploy-token-index"); function insertDeploy(repoId: string, id: string, createdBy: string, parent: string | null): void { scopes .raw("repo", repoId) .query( `INSERT INTO deploy_tokens (id, repo_id, name, token_hash, token_prefix, permission, created_by, created_at, parent_token_id) VALUES (?, ?, ?, ?, 'p', 'read', ?, 0, ?)`, ) .run(id, repoId, id, `h-${id}`, createdBy, parent); } function remaining(repoId: string): string[] { return (scopes.raw("repo", repoId).query("SELECT id FROM deploy_tokens ORDER BY id").all() as { id: string }[]).map( (row) => row.id, ); } beforeEach(() => { kv.clear(); visited.length = 0; for (const repoId of repoIds) scopes.raw("repo", repoId).exec("DELETE FROM deploy_tokens"); scopes.raw("account", "alice").exec("DELETE FROM deploy_token_index"); }); describe("deploy token index", () => { test("before the backfill finishes, revocation still reaches every repository", async () => { insertDeploy("repo-c", "old", "alice", "pat-1"); await revokeDeployTokensMintedBy("alice", ["pat-1"]); expect(remaining("repo-c")).toEqual([]); }); test("once backfilled, revoking a personal token only visits repositories holding its deploy tokens", async () => { insertDeploy("repo-a", "old", "alice", "pat-1"); insertDeploy("repo-b", "other", "alice", "pat-2"); while (kv.get("backfill:deploy-token-index") !== "done") await backfillDeployTokenIndex(); insertDeploy("repo-c", "new", "alice", "pat-1"); await recordDeployToken("alice", { id: "new", repoId: "repo-c", parentTokenId: "pat-1" }); visited.length = 0; await revokeDeployTokensMintedBy("alice", ["pat-1"]); expect(visited.sort()).toEqual(["repo-a", "repo-c"]); expect([...remaining("repo-a"), ...remaining("repo-b"), ...remaining("repo-c")]).toEqual(["other"]); }); test("a password reset revokes every deploy token the user created, through the index", async () => { kv.set("backfill:deploy-token-index", "done"); insertDeploy("repo-b", "web", "alice", null); await recordDeployToken("alice", { id: "web", repoId: "repo-b", parentTokenId: null }); insertDeploy("repo-a", "bob", "bob", null); visited.length = 0; await revokeAllDeployTokensCreatedBy("alice"); expect(visited).toEqual(["repo-b"]); expect(remaining("repo-b")).toEqual([]); expect(remaining("repo-a")).toEqual(["bob"]); }); });