Skip to content

Commit e381b33

Browse files
committed
refactor: improve bot permission validation readability
- Reduce nesting in bot validation block - Add missing test cases for admin permissions - Test edge cases: null user type, missing permissions object - All tests pass (283/283)
1 parent d5200e4 commit e381b33

File tree

2 files changed

+142
-25
lines changed

2 files changed

+142
-25
lines changed

src/github/validation/permissions.ts

Lines changed: 26 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ export async function checkWritePermissions(
3737
if (actorType === "Bot") {
3838
core.info(`GitHub App detected: ${actor}, checking write permissions`);
3939

40+
// Try collaborator permissions first
4041
try {
4142
const response = await octokit.repos.getCollaboratorPermissionLevel({
4243
owner: repository.owner,
@@ -47,42 +48,42 @@ export async function checkWritePermissions(
4748
const permissionLevel = response.data.permission;
4849
core.info(`App permission level: ${permissionLevel}`);
4950

50-
if (permissionLevel === "admin" || permissionLevel === "write") {
51+
const hasWriteAccess = permissionLevel === "admin" || permissionLevel === "write";
52+
if (hasWriteAccess) {
5153
core.info(`App has write access: ${permissionLevel}`);
5254
return true;
53-
} else {
54-
core.warning(`App has insufficient permissions: ${permissionLevel}`);
55-
return false;
5655
}
56+
57+
core.warning(`App has insufficient permissions: ${permissionLevel}`);
58+
return false;
5759
} catch (error) {
5860
core.warning(
5961
`Could not check collaborator permissions for bot, checking app installation: ${error}`,
6062
);
63+
}
6164

62-
try {
63-
const installation = await octokit.apps.getRepoInstallation({
64-
owner: repository.owner,
65-
repo: repository.repo,
66-
});
65+
// Fallback to app installation permissions
66+
try {
67+
const installation = await octokit.apps.getRepoInstallation({
68+
owner: repository.owner,
69+
repo: repository.repo,
70+
});
6771

68-
core.info(`App installation found: ${installation.data.id}`);
72+
core.info(`App installation found: ${installation.data.id}`);
6973

70-
const permissions = installation.data.permissions;
71-
if (
72-
permissions &&
73-
(permissions.contents === "write" ||
74-
permissions.contents === "admin")
75-
) {
76-
core.info(`App has write permissions via installation`);
77-
return true;
78-
} else {
79-
core.warning(`App lacks write permissions in installation`);
80-
return false;
81-
}
82-
} catch (installationError) {
83-
core.warning(`App lacks repository access: ${installationError}`);
84-
return false;
74+
const permissions = installation.data.permissions;
75+
const hasWriteAccess = permissions?.contents === "write" || permissions?.contents === "admin";
76+
77+
if (hasWriteAccess) {
78+
core.info(`App has write permissions via installation`);
79+
return true;
8580
}
81+
82+
core.warning(`App lacks write permissions in installation`);
83+
return false;
84+
} catch (installationError) {
85+
core.warning(`App lacks repository access: ${installationError}`);
86+
return false;
8687
}
8788
}
8889

test/permissions-validation.test.ts

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,4 +244,120 @@ describe("checkWritePermissions", () => {
244244
"Failed to check permissions for test-user:",
245245
);
246246
});
247+
248+
test("should grant write permissions to users with admin access", async () => {
249+
const mockOctokit = {
250+
users: {
251+
getByUsername: async () => ({
252+
data: { type: "User" },
253+
}),
254+
},
255+
repos: {
256+
getCollaboratorPermissionLevel: async () => ({
257+
data: { permission: "admin" },
258+
}),
259+
},
260+
} as any;
261+
262+
const context = createContext("admin-user");
263+
const result = await checkWritePermissions(mockOctokit, context);
264+
265+
expect(result).toBe(true);
266+
});
267+
268+
test("should grant write permissions to bots with admin access via collaborator", async () => {
269+
const mockOctokit = {
270+
users: {
271+
getByUsername: async () => ({
272+
data: { type: "Bot" },
273+
}),
274+
},
275+
repos: {
276+
getCollaboratorPermissionLevel: async () => ({
277+
data: { permission: "admin" },
278+
}),
279+
},
280+
} as any;
281+
282+
const context = createContext("admin-bot");
283+
const result = await checkWritePermissions(mockOctokit, context);
284+
285+
expect(result).toBe(true);
286+
});
287+
288+
test("should grant write permissions to bots with admin access via installation", async () => {
289+
const mockOctokit = {
290+
users: {
291+
getByUsername: async () => ({
292+
data: { type: "Bot" },
293+
}),
294+
},
295+
repos: {
296+
getCollaboratorPermissionLevel: async () => {
297+
throw new Error("Not found");
298+
},
299+
},
300+
apps: {
301+
getRepoInstallation: async () => ({
302+
data: {
303+
id: 123,
304+
permissions: { contents: "admin" },
305+
},
306+
}),
307+
},
308+
} as any;
309+
310+
const context = createContext("admin-bot");
311+
const result = await checkWritePermissions(mockOctokit, context);
312+
313+
expect(result).toBe(true);
314+
});
315+
316+
test("should continue when getBotActorType fails", async () => {
317+
const mockOctokit = {
318+
users: {
319+
getByUsername: async () => {
320+
throw new Error("User not found");
321+
},
322+
},
323+
repos: {
324+
getCollaboratorPermissionLevel: async () => ({
325+
data: { permission: "write" },
326+
}),
327+
},
328+
} as any;
329+
330+
const context = createContext("unknown-user");
331+
const result = await checkWritePermissions(mockOctokit, context);
332+
333+
expect(result).toBe(true);
334+
});
335+
336+
test("should handle installation without permissions object", async () => {
337+
const mockOctokit = {
338+
users: {
339+
getByUsername: async () => ({
340+
data: { type: "Bot" },
341+
}),
342+
},
343+
repos: {
344+
getCollaboratorPermissionLevel: async () => {
345+
throw new Error("Not found");
346+
},
347+
},
348+
apps: {
349+
getRepoInstallation: async () => ({
350+
data: {
351+
id: 123,
352+
// permissions is undefined
353+
},
354+
}),
355+
},
356+
} as any;
357+
358+
const context = createContext("bot-no-perms");
359+
const result = await checkWritePermissions(mockOctokit, context);
360+
361+
expect(result).toBe(false);
362+
});
247363
});

0 commit comments

Comments
 (0)