Skip to content
168 linesCodeBlameRaw
1import assert from "node:assert/strict";
2import { test } from "node:test";
3
4import { SCOPE_RESOURCES, permissionsOf, scopesOfPermissions } from "@g1t/contracts/scopes";
5
6import {
7 changesTo,
8 currentTokensPath,
9 expiryChoices,
10 keptOutOfAll,
11 levelLabel,
12 lifetimeFromForm,
13 permissionChips,
14 permissionGroups,
15 policyNote,
16 reachSummary,
17 resourcesFor,
18 statusBadge,
19 tokenFromForm,
20} from "./access-tokens.ts";
21
22function form(fields: Record<string, string | string[]>) {
23 return {
24 get: (name: string) => {
25 const value = fields[name];
26 return Array.isArray(value) ? (value[0] ?? null) : (value ?? null);
27 },
28 getAll: (name: string) => {
29 const value = fields[name];
30 return value === undefined ? [] : Array.isArray(value) ? value : [value];
31 },
32 };
33}
34
35const policy = {
36 allowTokensForAllWorkspaces: true,
37 allowTokensForThisWorkspace: true,
38 requireApproval: true,
39 maxLifetimeDays: null,
40 forbidNoExpiry: false,
41};
42
43test("a token made for one workspace reads its reach, repositories and permissions", () => {
44 const parsed = tokenFromForm(
45 form({
46 name: "release bot",
47 workspace: "Acme",
48 expires: "30",
49 repository_selection: "selected",
50 repo: ["web", "acme/api", "web"],
51 "perm.code": "write",
52 "perm.issues": "none",
53 "perm.workflow_files": "write",
54 "perm.packages": "delete",
55 }),
56 );
57 assert.ok(parsed.ok);
58 assert.equal(parsed.value.workspace, "acme");
59 assert.equal(parsed.value.ttlSeconds, 30 * 86_400);
60 assert.deepEqual(parsed.value.repositories, ["web", "acme/api"]);
61 assert.deepEqual(parsed.value.permissions, { code: "write", workflow_files: "write", packages: "delete" });
62});
63
64test("a token reaches every workspace, or none, and may never expire", () => {
65 const all = tokenFromForm(form({ name: "laptop", workspace: "*", expires: "never", "perm.repo": "read", repository_selection: "selected" }));
66 assert.ok(all.ok);
67 assert.equal(all.value.workspace, null);
68 assert.equal(all.value.repositorySelection, "all", "only a token for one workspace selects repositories");
69 assert.equal(all.value.ttlSeconds, null);
70 const none = tokenFromForm(form({ name: "inbox", workspace: "-", expires: "7", "perm.notifications": "write" }));
71 assert.ok(none.ok);
72 assert.equal(none.value.workspace, null);
73 assert.equal(none.value.repositorySelection, "public");
74});
75
76test("a workspace's own token holds nothing about a person, and reaches all or selected repositories", () => {
77 const parsed = tokenFromForm(
78 form({ name: "deploy", expires: "90", repository_selection: "public", "perm.code": "write", "perm.notifications": "write" }),
79 { workspaceOwned: true, owner: "acme" },
80 );
81 assert.ok(parsed.ok);
82 assert.equal(parsed.value.owner, "acme");
83 assert.equal(parsed.value.repositorySelection, "all");
84 assert.deepEqual(parsed.value.permissions, { code: "write" });
85 assert.ok(resourcesFor(true).every((row) => row.group !== "account"));
86 assert.equal(resourcesFor(false).length, SCOPE_RESOURCES.length);
87 assert.ok(permissionGroups(true).every((group) => group.group !== "account"));
88});
89
90test("editing reads neither the expiry nor needs a name", () => {
91 const parsed = tokenFromForm(form({ expires: "9999", "perm.issues": "read" }), { editing: true });
92 assert.ok(parsed.ok);
93 assert.equal(parsed.value.name, "");
94});
95
96test("mistakes are named", () => {
97 const error = (fields: Record<string, string | string[]>) => (tokenFromForm(form(fields)) as { ok: false; error: string }).error;
98 assert.match(error({ workspace: "acme" }), /Name/);
99 assert.match(error({ name: "x", expires: "400", "perm.repo": "read" }), /366/);
100 assert.match(error({ name: "x", workspace: "acme", repository_selection: "selected", "perm.repo": "read" }), /repository/);
101 assert.match(error({ name: "x", "perm.workflow_files": "read" }), /Workflow files/);
102 assert.match(error({ name: "x", "perm.issues": "admin" }), /Issues/);
103 assert.match(error({ name: "x" }), /at least one permission/);
104});
105
106test("permissions are scopes read per resource", () => {
107 assert.deepEqual(permissionsOf(["repo:read", "repo:admin", "issues:write", "bogus"]), { repo: "admin", issues: "write" });
108 assert.deepEqual(scopesOfPermissions({ issues: "write", repo: "read" }), ["repo:read", "issues:write"]);
109 assert.equal(Object.keys(permissionsOf(null)).length, SCOPE_RESOURCES.length, "full access is every resource");
110 assert.equal(levelLabel("workflow_files", "write"), "Write");
111 assert.equal(levelLabel("issues", "write"), "Read and write");
112 assert.equal(levelLabel("packages", "delete"), "Read, write and delete");
113});
114
115test("lifetimes follow the rules of every workspace a token reaches", () => {
116 assert.deepEqual(expiryChoices([null]), { days: [7, 30, 60, 90, 180, 366], never: true });
117 assert.deepEqual(expiryChoices([{ ...policy, maxLifetimeDays: 90 }]), { days: [7, 30, 60, 90], never: false });
118 assert.deepEqual(expiryChoices([policy, { ...policy, maxLifetimeDays: 45 }]), { days: [7, 30, 45], never: false });
119 assert.equal(expiryChoices([{ ...policy, forbidNoExpiry: true }]).never, false);
120 assert.deepEqual(lifetimeFromForm(""), { ok: true, value: null });
121 assert.deepEqual(lifetimeFromForm("90"), { ok: true, value: 90 });
122 assert.equal(lifetimeFromForm("0").ok, false);
123});
124
125test("lists read a token in a line", () => {
126 const token = {
127 scopes: ["repo:read", "code:write", "issues:read"],
128 workspace: "acme",
129 repositorySelection: "selected" as const,
130 repositories: ["acme/web", "acme/api"],
131 };
132 assert.equal(reachSummary(token), "acme · 2 repositories");
133 assert.equal(reachSummary({ workspace: null, repositorySelection: "all" }), "All your workspaces");
134 assert.equal(reachSummary({ workspace: null, repositorySelection: "public" }), "Your account and public repositories");
135 assert.equal(reachSummary({ workspaceOwned: true, repositorySelection: "all" }), "All repositories");
136 assert.deepEqual(
137 permissionChips(token).map((chip) => chip.label),
138 ["Repositories: read", "Code: write", "Issues: read"],
139 );
140 assert.ok(permissionChips({ scopes: ["repo:admin"] })[0]!.dangerous);
141 assert.deepEqual(statusBadge("pending"), { label: "Pending approval", tone: "warn" });
142 assert.equal(statusBadge("active"), null);
143 assert.match(policyNote("acme", policy, false) ?? "", /must approve/);
144 assert.equal(policyNote("acme", policy, true), null, "owners' own tokens never wait");
145 assert.match(policyNote("acme", { ...policy, allowTokensForThisWorkspace: false }, true) ?? "", /does not allow/);
146 assert.deepEqual(keptOutOfAll([{ slug: "acme", policy: { ...policy, allowTokensForAllWorkspaces: false } }, { slug: "globex", policy }]), ["acme"]);
147});
148
149test("old addresses of the token settings go to the current pages", () => {
150 assert.equal(currentTokensPath("/settings/tokens", new URLSearchParams("tab=classic")), "/settings/tokens");
151 assert.equal(currentTokensPath("/settings/tokens", new URLSearchParams("tab=classic&edit=tok_01ABC")), "/settings/tokens/tok_01ABC");
152 assert.equal(currentTokensPath("/acme/-/personal-access-tokens", new URLSearchParams("kind=fine_grained")), "/acme/-/personal-access-tokens");
153 assert.equal(currentTokensPath("/settings/tokens", new URLSearchParams("")), null);
154});
155
156test("saving a token sends only what changed", () => {
157 const token = {
158 id: "tok_1", name: "ci", createdAt: "", lastUsedAt: null, createdBy: null, legacy: false, expiresAt: null,
159 scopes: ["repo:read", "repo:write", "code:read", "code:write"],
160 workspace: "acme", repositorySelection: "selected" as const, repositories: ["acme/web"], description: null,
161 };
162 const same = { owner: null, name: "", description: null, ttlSeconds: null, workspace: "acme", repositorySelection: "selected" as const, repositories: ["Acme/Web"], permissions: { repo: "write" as const, code: "write" as const } };
163 assert.deepEqual(changesTo(token, same), {}, "the same permissions and repositories ask nothing again");
164 assert.deepEqual(changesTo(token, { ...same, permissions: { repo: "write", code: "read" } }), { permissions: { repo: "write", code: "read" } });
165 assert.deepEqual(changesTo(token, { ...same, repositories: ["acme/web", "acme/api"] }), { repositorySelection: "selected", repositories: ["acme/web", "acme/api"] });
166 assert.deepEqual(changesTo(token, { ...same, repositorySelection: "all", repositories: [] }), { repositorySelection: "all" });
167 assert.deepEqual(changesTo(token, { ...same, name: "release", description: "ships" }), { name: "release", description: "ships" });
168});