g1t/apps/web/app/lib/token-scopes.test.ts

111 lines5,172 bytesCodeBlame

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step1import assert from "node:assert/strict";
2import { test } from "node:test";
3
4import { DANGEROUS_SCOPES, OAUTH_DEFAULT_SCOPES, SCOPES, SCOPE_GROUPS } from "@g1t/contracts/scopes";
5
6import {
7 accessSummary,
8 consentedScopes,
9 describeExpiry,
10 everyScope,
11 expiryTtl,
12 grantFromForm,
13 impliedBy,
14 matchingPreset,
15 normalizeScopes,
16 requestedScopes,
17 scopesFromForm,
18} from "./token-scopes.ts";
19
20function form(fields: Record<string, string | string[]>) {
21 return {
22 get: (name: string) => {
23 const value = fields[name];
24 return Array.isArray(value) ? (value[0] ?? null) : (value ?? null);
25 },
26 getAll: (name: string) => {
27 const value = fields[name];
28 return value === undefined ? [] : Array.isArray(value) ? value : [value];
29 },
30 };
31}
32
33test("every scope is on the checklist exactly once, admin ones under Dangerous", () => {
34 const listed = [...SCOPE_GROUPS.flatMap((group) => group.scopes), ...DANGEROUS_SCOPES];
35 assert.deepEqual([...listed].sort(), SCOPES.map((row) => row.scope).sort());
36 assert.equal(new Set(listed).size, listed.length);
Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member37 assert.ok(DANGEROUS_SCOPES.every((scope) => scope.endsWith(":admin") || scope.endsWith(":delete")));
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step38});
39
40test("ticked boxes store the highest level of each resource", () => {
41 const parsed = scopesFromForm(
42 form({ scope: ["issues:read", "issues:write", "code:read", "agents:run", "bogus:read"] }),
43 );
44 assert.deepEqual(parsed, { ok: true, value: ["code:read", "issues:write", "agents:run"] });
45 assert.deepEqual(normalizeScopes(["repo:read", "repo:admin", "repo:write"]), ["repo:admin"]);
46});
47
48test("full access is null, and nothing ticked is refused", () => {
49 assert.deepEqual(scopesFromForm(form({ preset: "full", scope: "issues:read" })), { ok: true, value: null });
50 assert.equal(scopesFromForm(form({ preset: "full" }), { allowFull: false }).ok, false);
51 assert.equal(scopesFromForm(form({ preset: "custom" })).ok, false);
52 assert.deepEqual(grantFromForm(form({ scope: "memory:read" })), { ok: true, value: { scopes: ["memory:read"] } });
53});
54
55test("a higher level ticks the lower ones of its resource only", () => {
56 assert.equal(impliedBy(["issues:write"], "issues:read"), "issues:write");
57 assert.equal(impliedBy(["repo:admin"], "repo:write"), "repo:admin");
58 assert.equal(impliedBy(["issues:write"], "issues:write"), null);
59 assert.equal(impliedBy(["issues:write"], "pull_requests:read"), null);
60 assert.equal(impliedBy(["code:read"], "code:write"), null);
61});
62
63test("full access ticks the top level of everything", () => {
64 const all = everyScope();
65 assert.ok(all.includes("repo:admin"));
66 assert.ok(all.includes("agents:run"));
67 assert.ok(all.includes("code:write"));
68 assert.ok(!all.includes("code:read"));
69});
70
71test("presets are recognised however their scopes are written", () => {
72 assert.equal(matchingPreset(null), "full");
Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member73 assert.equal(matchingPreset(["repo:read", "code:write", "packages:write", "workflows:write"]), "ci");
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step74 assert.equal(matchingPreset([...OAUTH_DEFAULT_SCOPES]), "agent");
75 assert.equal(matchingPreset(["issues:read"]), null);
76});
77
78test("a token's access reads plainly", () => {
79 assert.equal(accessSummary({ scopes: null, legacy: true }), "Legacy · full access");
80 assert.equal(accessSummary({ scopes: null, legacy: false }), "Full access");
Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member81 assert.equal(accessSummary({ scopes: ["code:read", "code:write", "packages:write", "workflows:write", "repo:read"], legacy: false }), "CI");
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step82 assert.equal(accessSummary({ scopes: ["issues:read", "issues:write", "memory:read"], legacy: false }), "2 scopes");
83 assert.equal(accessSummary({ scopes: [], legacy: false }), "No scopes");
84});
85
86test("an application asking for nothing usable gets the default set, never admin", () => {
87 assert.deepEqual(requestedScopes(null), OAUTH_DEFAULT_SCOPES);
88 assert.deepEqual(requestedScopes("openid profile *"), OAUTH_DEFAULT_SCOPES);
89 assert.ok(!requestedScopes("").some((scope) => scope.endsWith(":admin")));
90 assert.deepEqual(requestedScopes("issues:write nonsense repo:read"), ["repo:read", "issues:write"]);
91});
92
93test("consent keeps only what was asked for", () => {
94 const requested = requestedScopes("repo:read issues:read issues:write");
95 assert.deepEqual(consentedScopes(form({ scope: ["issues:write", "repo:admin", "secrets:admin"] }), requested), ["issues:write"]);
96 assert.deepEqual(consentedScopes(form({ scope: ["issues:read", "repo:read"] }), requested), ["repo:read", "issues:read"]);
97 assert.deepEqual(consentedScopes(form({}), requested), []);
98});
99
100test("expiry choices and words", () => {
101 assert.equal(expiryTtl("7"), 7 * 86_400);
102 assert.equal(expiryTtl("never"), undefined);
103 assert.equal(expiryTtl("13"), 90 * 86_400);
104 assert.equal(expiryTtl(null), 90 * 86_400);
105 const now = Date.parse("2026-10-05T00:00:00Z");
106 assert.equal(describeExpiry(null, now), "No expiry");
107 assert.equal(describeExpiry("2026-10-04T00:00:00Z", now), "Expired");
108 assert.equal(describeExpiry("2026-10-12T00:00:00Z", now), "Expires in 7 days");
109 assert.equal(describeExpiry("2026-10-05T01:00:00Z", now), "Expires in 1 hour");
110 assert.equal(describeExpiry("2027-01-03T00:00:00Z", now), "Expires in 3 months");
111});