| 1 | import { describe, expect, it } from "vitest"; |
| 2 | import { Directory, type Account, type Repo } from "../../src/lib/directory.js"; |
| 3 | import { testD1 } from "./stubs/d1.js"; |
| 4 | import type { Env } from "../../src/env.js"; |
| 5 | |
| 6 | /** |
| 7 | * Who can see what, by the batched lookups (reposVisibleTo, publicRepos, readersOf) against the |
| 8 | * one-at-a-time roleFor they stand in for. |
| 9 | */ |
| 10 | function world() { |
| 11 | const DB = testD1(); |
| 12 | const t = "2026-10-01T00:00:00.000Z"; |
| 13 | const acct = DB.sqlite.prepare("INSERT INTO accounts (id, login, kind, status, site_admin, owner_id, created_at) VALUES (?, ?, ?, ?, ?, ?, ?)"); |
| 14 | acct.run("ada", "ada", "user", "active", 0, null, t); |
| 15 | acct.run("bob", "bob", "user", "active", 0, null, t); |
| 16 | acct.run("cy", "cy", "user", "active", 0, null, t); |
| 17 | acct.run("dee", "dee", "user", "disabled", 0, null, t); |
| 18 | acct.run("root", "root", "user", "active", 1, null, t); |
| 19 | acct.run("bot", "ada-bot", "agent", "active", 0, "ada", t); |
| 20 | acct.run("acme", "acme", "org", "active", 0, null, t); |
| 21 | acct.run("shut", "shut", "org", "active", 0, null, t); |
| 22 | DB.sqlite.prepare("UPDATE accounts SET base_role = 'none' WHERE id = 'shut'").run(); |
| 23 | const member = DB.sqlite.prepare("INSERT INTO org_members (org_id, user_id, role, created_at) VALUES (?, ?, ?, '2026-10-01')"); |
| 24 | member.run("acme", "bob", "member"); |
| 25 | member.run("shut", "bob", "member"); |
| 26 | member.run("shut", "cy", "admin"); |
| 27 | const repo = DB.sqlite.prepare("INSERT INTO repos (id, owner_id, name, visibility, created_at, updated_at, pushed_at) VALUES (?, ?, ?, ?, ?, ?, ?)"); |
| 28 | repo.run("r-ada", "ada", "notes", "private", t, t, "2026-10-02T00:00:00.000Z"); |
| 29 | repo.run("r-acme", "acme", "app", "private", t, t, "2026-10-03T00:00:00.000Z"); |
| 30 | repo.run("r-shut", "shut", "vault", "private", t, t, "2026-10-04T00:00:00.000Z"); |
| 31 | repo.run("r-shut-pub", "shut", "site", "public", t, t, "2026-10-05T00:00:00.000Z"); |
| 32 | repo.run("r-pub", "cy", "tool", "public", t, t, "2026-10-06T00:00:00.000Z"); |
| 33 | DB.sqlite.prepare("UPDATE repos SET description = 'A fast 100% thing', topics = '[\"cli\"]' WHERE id = 'r-pub'").run(); |
| 34 | DB.sqlite.prepare("INSERT INTO repo_access (repo_id, account_id, role) VALUES ('r-ada', 'cy', 'read')").run(); |
| 35 | const dir = new Directory({ DB } as unknown as Env); |
| 36 | return { dir }; |
| 37 | } |
| 38 | |
| 39 | const ids = (rs: Repo[]) => rs.map((r) => r.id); |
| 40 | |
| 41 | describe("repository visibility at scale", () => { |
| 42 | it("lists the repositories an account works with, not every public one", async () => { |
| 43 | const { dir } = world(); |
| 44 | const get = async (id: string) => (await dir.account(id))!; |
| 45 | expect(ids(await dir.reposVisibleTo(await get("ada")))).toEqual(["r-ada"]); |
| 46 | // bob: acme's repository as a member (base read), shut's public one but not its private one (base none). |
| 47 | expect(ids(await dir.reposVisibleTo(await get("bob")))).toEqual(["r-shut-pub", "r-acme"]); |
| 48 | // cy: own public repo, shut's as an admin, ada's shared with them. |
| 49 | expect(ids(await dir.reposVisibleTo(await get("cy")))).toEqual(["r-pub", "r-shut-pub", "r-shut", "r-ada"]); |
| 50 | // An agent sees what its owner works with. |
| 51 | expect(ids(await dir.reposVisibleTo(await get("bot")))).toEqual(["r-ada"]); |
| 52 | expect(await dir.reposVisibleTo(await get("dee"))).toEqual([]); |
| 53 | // One owner's repositories still include its public ones for anyone. |
| 54 | expect(ids(await dir.reposVisibleTo(await get("ada"), "cy"))).toEqual(["r-pub"]); |
| 55 | expect(ids(await dir.reposVisibleTo(await get("ada"), "shut"))).toEqual(["r-shut-pub"]); |
| 56 | }); |
| 57 | |
| 58 | it("finds public repositories by words and owners in D1", async () => { |
| 59 | const { dir } = world(); |
| 60 | expect(ids(await dir.publicRepos(10))).toEqual(["r-pub", "r-shut-pub"]); |
| 61 | expect(ids(await dir.publicRepos(10, { words: ["TOOL"] }))).toEqual(["r-pub"]); |
| 62 | expect(ids(await dir.publicRepos(10, { words: ["100%", "cli"] }))).toEqual(["r-pub"]); |
| 63 | // % and _ are matched literally. |
| 64 | expect(ids(await dir.publicRepos(10, { words: ["1_0"] }))).toEqual([]); |
| 65 | expect(ids(await dir.publicRepos(10, { words: ["shut/"] }))).toEqual(["r-shut-pub"]); |
| 66 | expect(ids(await dir.publicRepos(10, { owners: ["SHUT"] }))).toEqual(["r-shut-pub"]); |
| 67 | }); |
| 68 | |
| 69 | it("finds a repository's readers in a few queries, as roleFor would one by one", async () => { |
| 70 | const { dir } = world(); |
| 71 | const everyone = (await dir.accountsByIds(["ada", "bob", "cy", "dee", "root", "bot", "acme", "nobody"])) as Account[]; |
| 72 | expect(everyone.map((a) => a.id).sort()).toEqual(["acme", "ada", "bob", "bot", "cy", "dee", "root"]); |
| 73 | for (const id of ["r-ada", "r-acme", "r-shut", "r-shut-pub", "r-pub"]) { |
| 74 | const repo = (await dir.repoById(id))!; |
| 75 | const want = new Set<string>(); |
| 76 | for (const a of everyone) if (a.kind !== "org" && (await dir.roleFor(a, repo))) want.add(a.id); |
| 77 | expect([...(await dir.readersOf(repo, everyone))].sort(), id).toEqual([...want].sort()); |
| 78 | } |
| 79 | expect((await dir.accountsByLogins(["ADA", "ada-bot", "ghost"])).map((a) => a.id).sort()).toEqual(["ada", "bot"]); |
| 80 | }); |
| 81 | }); |