@@ -28,29 +28,76 @@ global.github = mockGithub;
2828
2929describe ( "check_permissions_utils" , ( ) => {
3030 let parseRequiredPermissions ;
31+ let parseAllowedBots ;
3132 let checkRepositoryPermission ;
33+ let checkBotStatus ;
3234 let originalEnv ;
3335
3436 beforeEach ( async ( ) => {
3537 // Reset all mocks
3638 vi . clearAllMocks ( ) ;
3739
3840 // Store original environment
39- originalEnv = process . env . GH_AW_REQUIRED_ROLES ;
41+ originalEnv = {
42+ GH_AW_REQUIRED_ROLES : process . env . GH_AW_REQUIRED_ROLES ,
43+ GH_AW_ALLOWED_BOTS : process . env . GH_AW_ALLOWED_BOTS ,
44+ } ;
4045
4146 // Import the module functions
4247 const module = await import ( "./check_permissions_utils.cjs" ) ;
4348 parseRequiredPermissions = module . parseRequiredPermissions ;
49+ parseAllowedBots = module . parseAllowedBots ;
4450 checkRepositoryPermission = module . checkRepositoryPermission ;
51+ checkBotStatus = module . checkBotStatus ;
4552 } ) ;
4653
4754 afterEach ( ( ) => {
4855 // Restore original environment
49- if ( originalEnv !== undefined ) {
50- process . env . GH_AW_REQUIRED_ROLES = originalEnv ;
51- } else {
52- delete process . env . GH_AW_REQUIRED_ROLES ;
53- }
56+ Object . keys ( originalEnv ) . forEach ( key => {
57+ if ( originalEnv [ key ] !== undefined ) {
58+ process . env [ key ] = originalEnv [ key ] ;
59+ } else {
60+ delete process . env [ key ] ;
61+ }
62+ } ) ;
63+ } ) ;
64+
65+ describe ( "parseAllowedBots" , ( ) => {
66+ it ( "should parse comma-separated bot identifiers" , ( ) => {
67+ process . env . GH_AW_ALLOWED_BOTS = "dependabot[bot],renovate[bot],github-actions[bot]" ;
68+ const result = parseAllowedBots ( ) ;
69+ expect ( result ) . toEqual ( [ "dependabot[bot]" , "renovate[bot]" , "github-actions[bot]" ] ) ;
70+ } ) ;
71+
72+ it ( "should filter out empty strings" , ( ) => {
73+ process . env . GH_AW_ALLOWED_BOTS = "dependabot[bot],,renovate[bot]," ;
74+ const result = parseAllowedBots ( ) ;
75+ expect ( result ) . toEqual ( [ "dependabot[bot]" , "renovate[bot]" ] ) ;
76+ } ) ;
77+
78+ it ( "should filter out whitespace-only entries" , ( ) => {
79+ process . env . GH_AW_ALLOWED_BOTS = "dependabot[bot], ,renovate[bot]" ;
80+ const result = parseAllowedBots ( ) ;
81+ expect ( result ) . toEqual ( [ "dependabot[bot]" , "renovate[bot]" ] ) ;
82+ } ) ;
83+
84+ it ( "should return empty array when env var is not set" , ( ) => {
85+ delete process . env . GH_AW_ALLOWED_BOTS ;
86+ const result = parseAllowedBots ( ) ;
87+ expect ( result ) . toEqual ( [ ] ) ;
88+ } ) ;
89+
90+ it ( "should return empty array when env var is empty string" , ( ) => {
91+ process . env . GH_AW_ALLOWED_BOTS = "" ;
92+ const result = parseAllowedBots ( ) ;
93+ expect ( result ) . toEqual ( [ ] ) ;
94+ } ) ;
95+
96+ it ( "should handle single bot identifier" , ( ) => {
97+ process . env . GH_AW_ALLOWED_BOTS = "dependabot[bot]" ;
98+ const result = parseAllowedBots ( ) ;
99+ expect ( result ) . toEqual ( [ "dependabot[bot]" ] ) ;
100+ } ) ;
54101 } ) ;
55102
56103 describe ( "parseRequiredPermissions" , ( ) => {
@@ -222,4 +269,109 @@ describe("check_permissions_utils", () => {
222269 expect ( successLog [ 0 ] ) . toContain ( "write" ) ;
223270 } ) ;
224271 } ) ;
272+
273+ describe ( "checkBotStatus" , ( ) => {
274+ it ( "should identify bot by [bot] suffix" , async ( ) => {
275+ mockGithub . rest . repos . getCollaboratorPermissionLevel . mockResolvedValue ( {
276+ data : { permission : "write" } ,
277+ } ) ;
278+
279+ const result = await checkBotStatus ( "dependabot[bot]" , "testowner" , "testrepo" ) ;
280+
281+ expect ( result ) . toEqual ( {
282+ isBot : true ,
283+ isActive : true ,
284+ } ) ;
285+
286+ expect ( mockCore . info ) . toHaveBeenCalledWith ( "Checking if bot 'dependabot[bot]' is active on testowner/testrepo" ) ;
287+ expect ( mockCore . info ) . toHaveBeenCalledWith ( "Bot 'dependabot[bot]' is active with permission level: write" ) ;
288+ } ) ;
289+
290+ it ( "should return false for non-bot users" , async ( ) => {
291+ const result = await checkBotStatus ( "regularuser" , "testowner" , "testrepo" ) ;
292+
293+ expect ( result ) . toEqual ( {
294+ isBot : false ,
295+ isActive : false ,
296+ } ) ;
297+
298+ expect ( mockGithub . rest . repos . getCollaboratorPermissionLevel ) . not . toHaveBeenCalled ( ) ;
299+ } ) ;
300+
301+ it ( "should handle 404 error for inactive bot" , async ( ) => {
302+ const apiError = { status : 404 , message : "Not Found" } ;
303+ mockGithub . rest . repos . getCollaboratorPermissionLevel . mockRejectedValue ( apiError ) ;
304+
305+ const result = await checkBotStatus ( "renovate[bot]" , "testowner" , "testrepo" ) ;
306+
307+ expect ( result ) . toEqual ( {
308+ isBot : true ,
309+ isActive : false ,
310+ } ) ;
311+
312+ expect ( mockCore . warning ) . toHaveBeenCalledWith ( "Bot 'renovate[bot]' is not active/installed on testowner/testrepo" ) ;
313+ } ) ;
314+
315+ it ( "should handle other API errors" , async ( ) => {
316+ const apiError = new Error ( "API rate limit exceeded" ) ;
317+ mockGithub . rest . repos . getCollaboratorPermissionLevel . mockRejectedValue ( apiError ) ;
318+
319+ const result = await checkBotStatus ( "github-actions[bot]" , "testowner" , "testrepo" ) ;
320+
321+ expect ( result ) . toEqual ( {
322+ isBot : true ,
323+ isActive : false ,
324+ error : "API rate limit exceeded" ,
325+ } ) ;
326+
327+ expect ( mockCore . warning ) . toHaveBeenCalledWith ( "Failed to check bot status: API rate limit exceeded" ) ;
328+ } ) ;
329+
330+ it ( "should handle non-Error API failures" , async ( ) => {
331+ mockGithub . rest . repos . getCollaboratorPermissionLevel . mockRejectedValue ( "String error" ) ;
332+
333+ const result = await checkBotStatus ( "bot[bot]" , "testowner" , "testrepo" ) ;
334+
335+ expect ( result ) . toEqual ( {
336+ isBot : true ,
337+ isActive : false ,
338+ error : "String error" ,
339+ } ) ;
340+
341+ expect ( mockCore . warning ) . toHaveBeenCalledWith ( "Failed to check bot status: String error" ) ;
342+ } ) ;
343+
344+ it ( "should handle unexpected errors gracefully" , async ( ) => {
345+ // Simulate an error during bot detection
346+ const unexpectedError = new Error ( "Unexpected error" ) ;
347+ mockGithub . rest . repos . getCollaboratorPermissionLevel . mockImplementation ( ( ) => {
348+ throw unexpectedError ;
349+ } ) ;
350+
351+ const result = await checkBotStatus ( "test[bot]" , "testowner" , "testrepo" ) ;
352+
353+ expect ( result ) . toEqual ( {
354+ isBot : true ,
355+ isActive : false ,
356+ error : "Unexpected error" ,
357+ } ) ;
358+ } ) ;
359+
360+ it ( "should verify bot is installed on repository" , async ( ) => {
361+ mockGithub . rest . repos . getCollaboratorPermissionLevel . mockResolvedValue ( {
362+ data : { permission : "admin" } ,
363+ } ) ;
364+
365+ const result = await checkBotStatus ( "dependabot[bot]" , "testowner" , "testrepo" ) ;
366+
367+ expect ( mockGithub . rest . repos . getCollaboratorPermissionLevel ) . toHaveBeenCalledWith ( {
368+ owner : "testowner" ,
369+ repo : "testrepo" ,
370+ username : "dependabot[bot]" ,
371+ } ) ;
372+
373+ expect ( result . isBot ) . toBe ( true ) ;
374+ expect ( result . isActive ) . toBe ( true ) ;
375+ } ) ;
376+ } ) ;
225377} ) ;
0 commit comments