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.
| One kind of access token; presence and status; usernames keep their case; the tour is a miniature of the real app; icons for password managers | 1 | import assert from "node:assert/strict"; |
| 2 | import { test } from "node:test"; | |
| 3 | ||
| 4 | import { SCOPE_RESOURCES, permissionsOf, scopesOfPermissions } from "@g1t/contracts/scopes"; | |
| 5 | ||
| 6 | import { | |
| 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 | ||
| 22 | function 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 | ||
| 35 | const policy = { | |
| 36 | allowTokensForAllWorkspaces: true, | |
| 37 | allowTokensForThisWorkspace: true, | |
| 38 | requireApproval: true, | |
| 39 | maxLifetimeDays: null, | |
| 40 | forbidNoExpiry: false, | |
| 41 | }; | |
| 42 | ||
| 43 | test("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 | ||
| 64 | test("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 | ||
| 76 | test("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 | ||
| 90 | test("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 | ||
| 96 | test("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 | ||
| 106 | test("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 | ||
| 115 | test("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 | ||
| 125 | test("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 | ||
| 149 | test("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 | ||
| 156 | test("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 | }); |