Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
CodeIssuesDiscussionsWikiPull RequestsProjectsCommitsActionsReleasesContributorsPulse● GatesSecuritySettingsDeploymentsPipelineInsightsAgents✨ Explain✨ Ask AI✨ Workspace✨ Spec✨ Tests▓ Debt Map✨ NL Search🏛 Archaeology
Blame · Line-by-line history

agent-identity.test.ts

Each line is annotated with the commit that last touched it. Click any SHA to jump to that commit and see the surrounding change.

agent-identity.test.tsBlame244 lines · 1 contributor
a6d8fd5Claude1/**
2 * Block K2 — Agent identity wrapper tests.
3 *
4 * Pure helpers + graceful-null-return DB paths only. Style follows
5 * commit-statuses.test.ts: tight `describe` groups, single-assertion-ish
6 * `it` blocks, no live DB dependency.
7 */
8
9import { describe, it, expect } from "bun:test";
10import {
11 AGENT_PERMISSIONS,
12 AGENT_SLUG_PREFIX,
13 agentSlug,
14 ensureAgentApp,
15 installAgentForRepo,
16 isAgentBotUsername,
17 isAgentPermission,
18 issueAgentToken,
19 normaliseAgentPermissions,
20 parseAgentPermissions,
21 requireAgentPermission,
22 revokeAgentToken,
23 revokeAgentTokenByRaw,
24 uninstallAgent,
25 verifyAgentToken,
26} from "../lib/agent-identity";
27import { hasPermission } from "../lib/marketplace";
28
29describe("agent-identity — AGENT_PERMISSIONS vocabulary", () => {
30 it("includes the full marketplace read/write pairs", () => {
31 for (const family of [
32 "contents",
33 "issues",
34 "pulls",
35 "checks",
36 "deployments",
37 ]) {
38 expect(AGENT_PERMISSIONS).toContain(`${family}:read`);
39 expect(AGENT_PERMISSIONS).toContain(`${family}:write`);
40 }
41 expect(AGENT_PERMISSIONS).toContain("metadata:read");
42 });
43
44 it("adds the new agent:invoke permission", () => {
45 expect(AGENT_PERMISSIONS).toContain("agent:invoke");
46 });
47
48 it("has no duplicates", () => {
49 const set = new Set(AGENT_PERMISSIONS);
50 expect(set.size).toBe(AGENT_PERMISSIONS.length);
51 });
52});
53
54describe("agent-identity — isAgentPermission", () => {
55 it("accepts every declared permission", () => {
56 for (const p of AGENT_PERMISSIONS) {
57 expect(isAgentPermission(p)).toBe(true);
58 }
59 });
60
61 it("rejects unknown values", () => {
62 expect(isAgentPermission("bogus:thing")).toBe(false);
63 expect(isAgentPermission("")).toBe(false);
64 expect(isAgentPermission("CONTENTS:READ")).toBe(false);
65 });
66});
67
68describe("agent-identity — normaliseAgentPermissions", () => {
69 it("drops unknown values", () => {
70 const out = normaliseAgentPermissions([
71 "contents:read",
72 "bogus",
73 "agent:invoke",
74 ]);
75 expect(out).toEqual(["contents:read", "agent:invoke"]);
76 });
77
78 it("de-duplicates and preserves first-appearance order", () => {
79 const out = normaliseAgentPermissions([
80 "issues:write",
81 "contents:read",
82 "issues:write",
83 "contents:read",
84 ]);
85 expect(out).toEqual(["issues:write", "contents:read"]);
86 });
87
88 it("returns [] for empty input", () => {
89 expect(normaliseAgentPermissions([])).toEqual([]);
90 });
91});
92
93describe("agent-identity — parseAgentPermissions", () => {
94 it("reads JSON array out of DB column", () => {
95 const raw = JSON.stringify(["contents:read", "agent:invoke", "bogus"]);
96 expect(parseAgentPermissions(raw)).toEqual([
97 "contents:read",
98 "agent:invoke",
99 ]);
100 });
101
102 it("handles null / undefined / empty / invalid JSON", () => {
103 expect(parseAgentPermissions(null)).toEqual([]);
104 expect(parseAgentPermissions(undefined)).toEqual([]);
105 expect(parseAgentPermissions("")).toEqual([]);
106 expect(parseAgentPermissions("not json")).toEqual([]);
107 expect(parseAgentPermissions("{}")).toEqual([]);
108 });
109});
110
111describe("agent-identity — agentSlug", () => {
112 it("prefixes bare kinds with agent-", () => {
113 expect(agentSlug("reviewer")).toBe("agent-reviewer");
114 });
115
116 it("leaves already-prefixed slugs alone", () => {
117 expect(agentSlug("agent-reviewer")).toBe("agent-reviewer");
118 });
119
120 it("lowercases + collapses punctuation", () => {
121 expect(agentSlug("AI Reviewer!")).toBe("agent-ai-reviewer");
122 });
123
124 it("falls back to agent-unknown on empty/whitespace input", () => {
125 expect(agentSlug("")).toBe("agent-unknown");
126 expect(agentSlug(" ")).toBe("agent-unknown");
127 });
128
129 it("exports the expected prefix constant", () => {
130 expect(AGENT_SLUG_PREFIX).toBe("agent-");
131 });
132});
133
134describe("agent-identity — isAgentBotUsername", () => {
135 it("accepts agent-* bots", () => {
136 expect(isAgentBotUsername("agent-reviewer[bot]")).toBe(true);
137 expect(isAgentBotUsername("agent-merge-sentry[bot]")).toBe(true);
138 });
139
140 it("rejects non-agent bots and plain usernames", () => {
141 expect(isAgentBotUsername("dependabot[bot]")).toBe(false);
142 expect(isAgentBotUsername("agent-reviewer")).toBe(false);
143 expect(isAgentBotUsername("alice")).toBe(false);
144 expect(isAgentBotUsername("")).toBe(false);
145 expect(isAgentBotUsername(null)).toBe(false);
146 expect(isAgentBotUsername(undefined)).toBe(false);
147 });
148});
149
150describe("agent-identity — hasPermission (through this layer)", () => {
151 it("write implies read on every family", () => {
152 expect(hasPermission(["contents:write"], "contents:read")).toBe(true);
153 expect(hasPermission(["issues:write"], "issues:read")).toBe(true);
154 expect(hasPermission(["pulls:write"], "pulls:read")).toBe(true);
155 expect(hasPermission(["checks:write"], "checks:read")).toBe(true);
156 expect(hasPermission(["deployments:write"], "deployments:read")).toBe(true);
157 });
158
159 it("read does NOT imply write", () => {
160 expect(hasPermission(["contents:read"], "contents:write")).toBe(false);
161 expect(hasPermission(["pulls:read"], "pulls:write")).toBe(false);
162 });
163
164 it("exact match still wins", () => {
165 expect(hasPermission(["agent:invoke"], "agent:invoke")).toBe(true);
166 });
167
168 it("missing permission returns false", () => {
169 expect(hasPermission([], "contents:read")).toBe(false);
170 expect(hasPermission(["metadata:read"], "contents:read")).toBe(false);
171 });
172});
173
174describe("agent-identity — verifyAgentToken", () => {
175 it("rejects tokens without the ghi_ prefix", async () => {
176 expect(await verifyAgentToken("")).toBeNull();
177 expect(await verifyAgentToken("glc_not_an_install_token")).toBeNull();
178 expect(await verifyAgentToken("ghp_personal_token")).toBeNull();
179 expect(await verifyAgentToken("bearer whatever")).toBeNull();
180 });
181
182 it("rejects an obviously-bogus ghi_ token (no matching install)", async () => {
183 // No DATABASE_URL in this test env → verifyInstallToken returns null.
184 expect(await verifyAgentToken("ghi_deadbeef")).toBeNull();
185 });
186});
187
188describe("agent-identity — requireAgentPermission", () => {
189 it("throws on invalid/missing tokens", async () => {
190 await expect(
191 requireAgentPermission("", "contents:read")
192 ).rejects.toThrow(/invalid|expired/);
193 await expect(
194 requireAgentPermission("ghi_deadbeef", "contents:read")
195 ).rejects.toThrow(/invalid|expired/);
196 });
197});
198
199describe("agent-identity — DB helpers fail gracefully", () => {
200 it("ensureAgentApp returns null when DB is unreachable", async () => {
201 const out = await ensureAgentApp("reviewer", "Reviewer", [
202 "contents:read",
203 ]);
204 // Test env has no DATABASE_URL so this must degrade to null, not throw.
205 expect(out).toBeNull();
206 });
207
208 it("installAgentForRepo returns null when app is missing / DB down", async () => {
209 const out = await installAgentForRepo(
210 "agent-nope",
211 "00000000-0000-0000-0000-000000000000",
212 "00000000-0000-0000-0000-000000000000",
213 ["contents:read"]
214 );
215 expect(out).toBeNull();
216 });
217
218 it("issueAgentToken returns null when no install exists", async () => {
219 const out = await issueAgentToken(
220 "agent-nope",
221 "00000000-0000-0000-0000-000000000000"
222 );
223 expect(out).toBeNull();
224 });
225
226 it("revokeAgentToken returns false for empty hash / unknown token", async () => {
227 expect(await revokeAgentToken("")).toBe(false);
228 expect(await revokeAgentToken("not-a-real-hash")).toBe(false);
229 });
230
231 it("revokeAgentTokenByRaw returns false for empty / unknown tokens", async () => {
232 expect(await revokeAgentTokenByRaw("")).toBe(false);
233 expect(await revokeAgentTokenByRaw("ghi_never_issued")).toBe(false);
234 });
235
236 it("uninstallAgent returns false when app/install missing", async () => {
237 expect(
238 await uninstallAgent(
239 "agent-nope",
240 "00000000-0000-0000-0000-000000000000"
241 )
242 ).toBe(false);
243 });
244});