Diffinery

codician/diffinerypublic

Open-source, agent-first GitHub and GitHub Actions alternative that runs on your own Cloudflare account: git hosting, pull requests, code review and drop-in GitHub Actions YAML on Workers, Durable Objects, R2 and Containers.

75 lines · 4.4 KBRaw History
1import { describe, expect, it } from "vitest";
2import { Directory, type Account, type RepoGrants } from "../../src/lib/directory.js";
3import { testD1 } from "./stubs/d1.js";
4import type { Env } from "../../src/env.js";
5
6/** roleFor with the grants loadRepo reads alongside the repository (grantsFor) gives the same role as reading them itself. */
7function world() {
8 const DB = testD1();
9 const t = "2026-10-01T00:00:00.000Z";
10 const acct = DB.sqlite.prepare("INSERT INTO accounts (id, login, kind, status, site_admin, owner_id, agent_policy, base_role, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)");
11 acct.run("ada", "ada", "user", "active", 0, null, null, null, t);
12 acct.run("bob", "bob", "user", "active", 0, null, null, null, t);
13 acct.run("cy", "cy", "user", "active", 0, null, null, null, t);
14 acct.run("dee", "dee", "user", "disabled", 0, null, null, null, t);
15 acct.run("root", "root", "user", "active", 1, null, null, null, t);
16 acct.run("eve", "eve", "user", "active", 0, null, null, null, t);
17 acct.run("acme", "acme", "org", "active", 0, null, null, "write", t);
18 acct.run("shut", "shut", "org", "active", 0, null, null, "none", t);
19 acct.run("ada-bot", "ada-bot", "agent", "active", 0, "ada", null, null, t);
20 acct.run("bob-bot", "bob-bot", "agent", "active", 0, "bob", JSON.stringify({ maxRole: "triage" }), null, t);
21 acct.run("cy-bot", "cy-bot", "agent", "active", 0, "cy", null, null, t);
22 acct.run("acme-bot", "acme-bot", "agent", "active", 0, "acme", null, null, t);
23 acct.run("dee-bot", "dee-bot", "agent", "active", 0, "dee", null, null, t);
24 const member = DB.sqlite.prepare("INSERT INTO org_members (org_id, user_id, role, created_at) VALUES (?, ?, ?, '2026-10-01')");
25 member.run("acme", "bob", "member");
26 member.run("acme", "ada", "owner");
27 member.run("shut", "bob", "member");
28 member.run("shut", "cy", "admin");
29 const repo = DB.sqlite.prepare("INSERT INTO repos (id, owner_id, name, visibility, created_at, updated_at, pushed_at) VALUES (?, ?, ?, ?, ?, ?, ?)");
30 repo.run("r-ada", "ada", "notes", "private", t, t, t);
31 repo.run("r-acme", "acme", "app", "private", t, t, t);
32 repo.run("r-acme-pub", "acme", "site", "public", t, t, t);
33 repo.run("r-shut", "shut", "vault", "private", t, t, t);
34 repo.run("r-pub", "cy", "tool", "public", t, t, t);
35 const grant = DB.sqlite.prepare("INSERT INTO repo_access (repo_id, account_id, role) VALUES (?, ?, ?)");
36 grant.run("r-ada", "cy", "read");
37 grant.run("r-acme", "eve", "maintain");
38 grant.run("r-shut", "bob", "write");
39 grant.run("r-acme", "bob-bot", "admin");
40 grant.run("r-ada", "ada-bot", "read");
41 grant.run("r-shut", "cy-bot", "triage");
42 return new Directory({ DB } as unknown as Env);
43}
44
45describe("roles from grants read alongside the repository", () => {
46 it("match roleFor reading them itself, for people and agents", async () => {
47 const dir = world();
48 const everyone = (await dir.accountsByIds(["ada", "bob", "cy", "dee", "root", "eve", "ada-bot", "bob-bot", "cy-bot", "acme-bot", "dee-bot"])) as Account[];
49 let compared = 0;
50 for (const id of ["r-ada", "r-acme", "r-acme-pub", "r-shut", "r-pub"]) {
51 const repo = (await dir.repoById(id))!;
52 for (const a of everyone) {
53 const owner = a.owner_id ? await dir.account(a.owner_id) : null;
54 const ids = a.kind === "agent" ? [a.id, ...(owner?.kind === "user" ? [owner.id] : [])] : [a.id];
55 const grants: RepoGrants[] = (await Promise.all(ids.map((x) => dir.grantsFor(repo.owner_login, repo.name, x)))).flat();
56 expect(grants.every((g) => g.repoId === repo.id)).toBe(true);
57 expect(await dir.roleFor(a, repo, owner, grants), `${a.login} on ${id}`).toBe(await dir.roleFor(a, repo));
58 compared++;
59 }
60 }
61 expect(compared).toBe(55);
62 });
63
64 it("are ignored for another repository, and read by name with .git and any case", async () => {
65 const dir = world();
66 const bob = (await dir.account("bob"))!;
67 const shut = (await dir.repoById("r-shut"))!;
68 // Grants read for acme/app don't stand in for shut/vault: roleFor reads shut/vault's own.
69 const other = await dir.grantsFor("acme", "app", "bob");
70 expect(other[0]!.repoId).toBe("r-acme");
71 expect(await dir.roleFor(bob, shut, undefined, other)).toBe("write");
72 expect((await dir.grantsFor("SHUT", "vault.git", "bob"))[0]).toMatchObject({ repoId: "r-shut", access: "write", memberRole: "member", baseRole: "none" });
73 expect(await dir.grantsFor("shut", "missing", "bob")).toEqual([]);
74 });
75});