@@ -238,3 +238,222 @@ func Test_ListCodeScanningAlerts(t *testing.T) {
238
238
})
239
239
}
240
240
}
241
+
242
+ func Test_UpdateCodeScanningAlert (t * testing.T ) {
243
+ // Verify tool definition
244
+ mockClient := github .NewClient (nil )
245
+ tool , _ := UpdateCodeScanningAlert (stubGetClientFn (mockClient ), translations .NullTranslationHelper )
246
+
247
+ assert .Equal (t , "update_code_scanning_alert" , tool .Name )
248
+ assert .NotEmpty (t , tool .Description )
249
+ assert .Contains (t , tool .InputSchema .Properties , "owner" )
250
+ assert .Contains (t , tool .InputSchema .Properties , "repo" )
251
+ assert .Contains (t , tool .InputSchema .Properties , "alertNumber" )
252
+ assert .Contains (t , tool .InputSchema .Properties , "state" )
253
+ assert .ElementsMatch (t , tool .InputSchema .Required , []string {"owner" , "repo" , "alertNumber" , "state" })
254
+
255
+ // Mock alert for success
256
+ mockAlert := & github.Alert {
257
+ Number : github .Ptr (42 ),
258
+ State : github .Ptr ("open" ),
259
+ Rule : & github.Rule {ID : github .Ptr ("rule-id" ), Description : github .Ptr ("desc" )},
260
+ HTMLURL : github .Ptr ("https://github.com/owner/repo/security/code-scanning/42" ),
261
+ }
262
+
263
+ tests := []struct {
264
+ name string
265
+ mockedClient * http.Client
266
+ requestArgs map [string ]interface {}
267
+ expectError bool
268
+ expectedAlert * github.Alert
269
+ expectedErrMsg string
270
+ }{
271
+ {
272
+ name : "successful alert update" ,
273
+ mockedClient : mock .NewMockedHTTPClient (
274
+ mock .WithRequestMatch (
275
+ mock .PatchReposCodeScanningAlertsByOwnerByRepoByAlertNumber ,
276
+ mockAlert ,
277
+ ),
278
+ ),
279
+ requestArgs : map [string ]interface {}{
280
+ "owner" : "owner" ,
281
+ "repo" : "repo" ,
282
+ "alertNumber" : float64 (42 ),
283
+ "state" : "open" ,
284
+ },
285
+ expectError : false ,
286
+ expectedAlert : mockAlert ,
287
+ },
288
+ {
289
+ name : "update fails" ,
290
+ mockedClient : mock .NewMockedHTTPClient (
291
+ mock .WithRequestMatchHandler (
292
+ mock .PatchReposCodeScanningAlertsByOwnerByRepoByAlertNumber ,
293
+ http .HandlerFunc (func (w http.ResponseWriter , _ * http.Request ) {
294
+ w .WriteHeader (http .StatusBadRequest )
295
+ _ , _ = w .Write ([]byte (`{"message": "Invalid request"}` ))
296
+ }),
297
+ ),
298
+ ),
299
+ requestArgs : map [string ]interface {}{
300
+ "owner" : "owner" ,
301
+ "repo" : "repo" ,
302
+ "alertNumber" : float64 (9999 ),
303
+ "state" : "open" ,
304
+ },
305
+ expectError : true ,
306
+ expectedErrMsg : "failed to update alert" ,
307
+ },
308
+ {
309
+ name : "error when dismissed_reason not provided" ,
310
+ mockedClient : nil , // early exit happens before any HTTP call
311
+ requestArgs : map [string ]interface {}{
312
+ "owner" : "owner" ,
313
+ "repo" : "repo" ,
314
+ "alertNumber" : float64 (42 ),
315
+ "state" : "dismissed" ,
316
+ "dismissed_reason" : "" ,
317
+ },
318
+ expectError : true ,
319
+ expectedErrMsg : "dismissed_reason required for 'dismissed' state" ,
320
+ },
321
+ }
322
+
323
+ for _ , tc := range tests {
324
+ t .Run (tc .name , func (t * testing.T ) {
325
+ client := github .NewClient (tc .mockedClient )
326
+ _ , handler := UpdateCodeScanningAlert (stubGetClientFn (client ), translations .NullTranslationHelper )
327
+ request := createMCPRequest (tc .requestArgs )
328
+
329
+ result , err := handler (context .Background (), request )
330
+ if tc .expectError {
331
+ require .Error (t , err )
332
+ assert .Contains (t , err .Error (), tc .expectedErrMsg )
333
+ return
334
+ }
335
+
336
+ require .NoError (t , err )
337
+ text := getTextResult (t , result )
338
+ var got github.Alert
339
+ require .NoError (t , json .Unmarshal ([]byte (text .Text ), & got ))
340
+
341
+ assert .Equal (t , * tc .expectedAlert .Number , * got .Number )
342
+ assert .Equal (t , * tc .expectedAlert .State , * got .State )
343
+ assert .Equal (t , * tc .expectedAlert .Rule .ID , * got .Rule .ID )
344
+ assert .Equal (t , * tc .expectedAlert .HTMLURL , * got .HTMLURL )
345
+ })
346
+ }
347
+ }
348
+
349
+ func Test_ListOrgCodeScanningAlerts (t * testing.T ) {
350
+ // Verify tool definition
351
+ mockClient := github .NewClient (nil )
352
+ tool , _ := ListOrgCodeScanningAlerts (stubGetClientFn (mockClient ), translations .NullTranslationHelper )
353
+
354
+ assert .Equal (t , "list_org_code_scanning_alerts" , tool .Name )
355
+ assert .NotEmpty (t , tool .Description )
356
+ assert .Contains (t , tool .InputSchema .Properties , "org" )
357
+ assert .Contains (t , tool .InputSchema .Properties , "sort" )
358
+ assert .Contains (t , tool .InputSchema .Properties , "severity" )
359
+ assert .Contains (t , tool .InputSchema .Properties , "tool_name" )
360
+ assert .Contains (t , tool .InputSchema .Properties , "state" )
361
+ assert .ElementsMatch (t , tool .InputSchema .Required , []string {"org" })
362
+
363
+ // Mock alerts for success
364
+ mockAlerts := []* github.Alert {
365
+ {
366
+ Number : github .Ptr (100 ),
367
+ State : github .Ptr ("open" ),
368
+ Rule : & github.Rule {ID : github .Ptr ("org-rule-1" ), Description : github .Ptr ("desc1" )},
369
+ HTMLURL : github .Ptr ("https://github.com/org/repo/security/code-scanning/100" ),
370
+ },
371
+ {
372
+ Number : github .Ptr (101 ),
373
+ State : github .Ptr ("dismissed" ),
374
+ Rule : & github.Rule {ID : github .Ptr ("org-rule-2" ), Description : github .Ptr ("desc2" )},
375
+ HTMLURL : github .Ptr ("https://github.com/org/repo/security/code-scanning/101" ),
376
+ },
377
+ }
378
+
379
+ tests := []struct {
380
+ name string
381
+ mockedClient * http.Client
382
+ requestArgs map [string ]interface {}
383
+ expectError bool
384
+ expectedAlerts []* github.Alert
385
+ expectedErrMsg string
386
+ }{
387
+ {
388
+ name : "successful org alerts listing" ,
389
+ mockedClient : mock .NewMockedHTTPClient (
390
+ mock .WithRequestMatchHandler (
391
+ mock .GetOrgsCodeScanningAlertsByOrg ,
392
+ expectQueryParams (t , map [string ]string {
393
+ "state" : "open" ,
394
+ "severity" : "high" ,
395
+ "tool_name" : "codeql" ,
396
+ "sort" : "created" ,
397
+ }).andThen (
398
+ mockResponse (t , http .StatusOK , mockAlerts ),
399
+ ),
400
+ ),
401
+ ),
402
+ requestArgs : map [string ]interface {}{
403
+ "org" : "org" ,
404
+ "state" : "open" ,
405
+ "severity" : "high" ,
406
+ "tool_name" : "codeql" ,
407
+ "sort" : "created" ,
408
+ },
409
+ expectError : false ,
410
+ expectedAlerts : mockAlerts ,
411
+ },
412
+ {
413
+ name : "org alerts listing fails" ,
414
+ mockedClient : mock .NewMockedHTTPClient (
415
+ mock .WithRequestMatchHandler (
416
+ mock .GetOrgsCodeScanningAlertsByOrg ,
417
+ http .HandlerFunc (func (w http.ResponseWriter , _ * http.Request ) {
418
+ w .WriteHeader (http .StatusForbidden )
419
+ _ , _ = w .Write ([]byte (`{"message":"Forbidden"}` ))
420
+ }),
421
+ ),
422
+ ),
423
+ requestArgs : map [string ]interface {}{
424
+ "org" : "org" ,
425
+ },
426
+ expectError : true ,
427
+ expectedErrMsg : "failed to list organization alerts" ,
428
+ },
429
+ }
430
+
431
+ for _ , tc := range tests {
432
+ t .Run (tc .name , func (t * testing.T ) {
433
+ client := github .NewClient (tc .mockedClient )
434
+ _ , handler := ListOrgCodeScanningAlerts (stubGetClientFn (client ), translations .NullTranslationHelper )
435
+ request := createMCPRequest (tc .requestArgs )
436
+
437
+ result , err := handler (context .Background (), request )
438
+ if tc .expectError {
439
+ require .Error (t , err )
440
+ assert .Contains (t , err .Error (), tc .expectedErrMsg )
441
+ return
442
+ }
443
+
444
+ require .NoError (t , err )
445
+ text := getTextResult (t , result )
446
+
447
+ var got []* github.Alert
448
+ require .NoError (t , json .Unmarshal ([]byte (text .Text ), & got ))
449
+ assert .Len (t , got , len (tc .expectedAlerts ))
450
+
451
+ for i := range got {
452
+ assert .Equal (t , * tc .expectedAlerts [i ].Number , * got [i ].Number )
453
+ assert .Equal (t , * tc .expectedAlerts [i ].State , * got [i ].State )
454
+ assert .Equal (t , * tc .expectedAlerts [i ].Rule .ID , * got [i ].Rule .ID )
455
+ assert .Equal (t , * tc .expectedAlerts [i ].HTMLURL , * got [i ].HTMLURL )
456
+ }
457
+ })
458
+ }
459
+ }
0 commit comments