@@ -1525,3 +1525,251 @@ func TestPullRequestReviewDeletion(t *testing.T) {
1525
1525
require .NoError (t ,err ,"expected to unmarshal text content successfully" )
1526
1526
require .Len (t ,noReviews ,0 ,"expected to find no reviews" )
1527
1527
}
1528
+
1529
+ func TestE2E_ListNotifications (t * testing.T ) {
1530
+ t .Parallel ()
1531
+ client := setupMCPClient (t )
1532
+ ctx := context .Background ()
1533
+
1534
+ request := mcp.CallToolRequest {}
1535
+ request .Params .Name = "list_notifications"
1536
+ request .Params .Arguments = map [string ]any {}
1537
+
1538
+ resp ,err := client .CallTool (ctx ,request )
1539
+ require .NoError (t ,err ,"expected to call 'list_notifications' tool successfully" )
1540
+ require .False (t ,resp .IsError ,fmt .Sprintf ("expected result not to be an error: %+v" ,resp ))
1541
+ require .Len (t ,resp .Content ,1 ,"expected content to have one item" )
1542
+
1543
+ var notifications []struct {
1544
+ ID string `json:"id"`
1545
+ }
1546
+ textContent ,ok := resp .Content [0 ].(mcp.TextContent )
1547
+ require .True (t ,ok ,"expected content to be of type TextContent" )
1548
+ err = json .Unmarshal ([]byte (textContent .Text ),& notifications )
1549
+ require .NoError (t ,err ,"expected to unmarshal text content successfully" )
1550
+ }
1551
+
1552
+ func TestE2E_ManageNotificationSubscription (t * testing.T ) {
1553
+ t .Parallel ()
1554
+ client := setupMCPClient (t )
1555
+ ctx := context .Background ()
1556
+
1557
+ // List notifications to get a valid notificationID
1558
+ listReq := mcp.CallToolRequest {}
1559
+ listReq .Params .Name = "list_notifications"
1560
+ listReq .Params .Arguments = map [string ]any {}
1561
+ resp ,err := client .CallTool (ctx ,listReq )
1562
+ require .NoError (t ,err )
1563
+ require .False (t ,resp .IsError )
1564
+ if len (resp .Content )== 0 {
1565
+ return t .Skip ("No notifications available to test subscription management" )
1566
+ }
1567
+ textContent ,ok := resp .Content [0 ].(mcp.TextContent )
1568
+ require .True (t ,ok )
1569
+ var notifications []struct {
1570
+ ID string `json:"id"`
1571
+ }
1572
+ err = json .Unmarshal ([]byte (textContent .Text ),& notifications )
1573
+ require .NoError (t ,err )
1574
+ require .NotEmpty (t ,notifications )
1575
+ notificationID := notifications [0 ].ID
1576
+
1577
+ // Ignore notification
1578
+ ignoreReq := mcp.CallToolRequest {}
1579
+ ignoreReq .Params .Name = "manage_notification_subscription"
1580
+ ignoreReq .Params .Arguments = map [string ]any {
1581
+ "notificationID" :notificationID ,
1582
+ "action" :"ignore" ,
1583
+ }
1584
+ resp ,err = client .CallTool (ctx ,ignoreReq )
1585
+ require .NoError (t ,err )
1586
+ require .False (t ,resp .IsError )
1587
+ textContent ,ok = resp .Content [0 ].(mcp.TextContent )
1588
+ require .True (t ,ok )
1589
+ require .Contains (t ,textContent .Text ,"ignored" )
1590
+
1591
+ // Watch notification
1592
+ watchReq := mcp.CallToolRequest {}
1593
+ watchReq .Params .Name = "manage_notification_subscription"
1594
+ watchReq .Params .Arguments = map [string ]any {
1595
+ "notificationID" :notificationID ,
1596
+ "action" :"watch" ,
1597
+ }
1598
+ resp ,err = client .CallTool (ctx ,watchReq )
1599
+ require .NoError (t ,err )
1600
+ require .False (t ,resp .IsError )
1601
+ textContent ,ok = resp .Content [0 ].(mcp.TextContent )
1602
+ require .True (t ,ok )
1603
+ require .Contains (t ,textContent .Text ,"subscribed" )
1604
+
1605
+ // Delete notification subscription
1606
+ deleteReq := mcp.CallToolRequest {}
1607
+ deleteReq .Params .Name = "manage_notification_subscription"
1608
+ deleteReq .Params .Arguments = map [string ]any {
1609
+ "notificationID" :notificationID ,
1610
+ "action" :"delete" ,
1611
+ }
1612
+ resp ,err = client .CallTool (ctx ,deleteReq )
1613
+ require .NoError (t ,err )
1614
+ require .False (t ,resp .IsError )
1615
+ textContent ,ok = resp .Content [0 ].(mcp.TextContent )
1616
+ require .True (t ,ok )
1617
+ require .Contains (t ,textContent .Text ,"deleted" )
1618
+ }
1619
+
1620
+ func TestE2E_ManageRepositoryNotificationSubscription (t * testing.T ) {
1621
+ t .Parallel ()
1622
+ client := setupMCPClient (t )
1623
+ ctx := context .Background ()
1624
+
1625
+ // Use a well-known repo for the test (e.g., the user's own repo)
1626
+ owner := "github"
1627
+ repo := "github-mcp-server"
1628
+
1629
+ // Ignore repo notifications
1630
+ ignoreReq := mcp.CallToolRequest {}
1631
+ ignoreReq .Params .Name = "manage_repository_notification_subscription"
1632
+ ignoreReq .Params .Arguments = map [string ]any {
1633
+ "owner" :owner ,
1634
+ "repo" :repo ,
1635
+ "action" :"ignore" ,
1636
+ }
1637
+ resp ,err := client .CallTool (ctx ,ignoreReq )
1638
+ require .NoError (t ,err )
1639
+ require .False (t ,resp .IsError )
1640
+ textContent ,ok := resp .Content [0 ].(mcp.TextContent )
1641
+ require .True (t ,ok )
1642
+ require .Contains (t ,textContent .Text ,"ignored" )
1643
+
1644
+ // Watch repo notifications
1645
+ watchReq := mcp.CallToolRequest {}
1646
+ watchReq .Params .Name = "manage_repository_notification_subscription"
1647
+ watchReq .Params .Arguments = map [string ]any {
1648
+ "owner" :owner ,
1649
+ "repo" :repo ,
1650
+ "action" :"watch" ,
1651
+ }
1652
+ resp ,err = client .CallTool (ctx ,watchReq )
1653
+ require .NoError (t ,err )
1654
+ require .False (t ,resp .IsError )
1655
+ textContent ,ok = resp .Content [0 ].(mcp.TextContent )
1656
+ require .True (t ,ok )
1657
+ require .Contains (t ,textContent .Text ,"subscribed" )
1658
+
1659
+ // Delete repo notification subscription
1660
+ deleteReq := mcp.CallToolRequest {}
1661
+ deleteReq .Params .Name = "manage_repository_notification_subscription"
1662
+ deleteReq .Params .Arguments = map [string ]any {
1663
+ "owner" :owner ,
1664
+ "repo" :repo ,
1665
+ "action" :"delete" ,
1666
+ }
1667
+ resp ,err = client .CallTool (ctx ,deleteReq )
1668
+ require .NoError (t ,err )
1669
+ require .False (t ,resp .IsError )
1670
+ textContent ,ok = resp .Content [0 ].(mcp.TextContent )
1671
+ require .True (t ,ok )
1672
+ require .Contains (t ,textContent .Text ,"deleted" )
1673
+ }
1674
+
1675
+ func TestE2E_DismissNotification (t * testing.T ) {
1676
+ t .Parallel ()
1677
+ client := setupMCPClient (t )
1678
+ ctx := context .Background ()
1679
+
1680
+ // List notifications to get a valid threadID
1681
+ listReq := mcp.CallToolRequest {}
1682
+ listReq .Params .Name = "list_notifications"
1683
+ listReq .Params .Arguments = map [string ]any {}
1684
+ resp ,err := client .CallTool (ctx ,listReq )
1685
+ require .NoError (t ,err )
1686
+ require .False (t ,resp .IsError )
1687
+ if len (resp .Content )== 0 {
1688
+ return t .Skip ("No notifications available to test dismissal" )
1689
+ }
1690
+ textContent ,ok := resp .Content [0 ].(mcp.TextContent )
1691
+ require .True (t ,ok )
1692
+ var notifications []struct {
1693
+ ID string `json:"id"`
1694
+ }
1695
+ err = json .Unmarshal ([]byte (textContent .Text ),& notifications )
1696
+ require .NoError (t ,err )
1697
+ require .NotEmpty (t ,notifications )
1698
+ threadID := notifications [0 ].ID
1699
+
1700
+ // Dismiss notification (mark as read)
1701
+ dismissReq := mcp.CallToolRequest {}
1702
+ dismissReq .Params .Name = "dismiss_notification"
1703
+ dismissReq .Params .Arguments = map [string ]any {
1704
+ "threadID" :threadID ,
1705
+ "state" :"read" ,
1706
+ }
1707
+ resp ,err = client .CallTool (ctx ,dismissReq )
1708
+ require .NoError (t ,err )
1709
+ require .False (t ,resp .IsError )
1710
+ textContent ,ok = resp .Content [0 ].(mcp.TextContent )
1711
+ require .True (t ,ok )
1712
+ require .Contains (t ,textContent .Text ,"read" )
1713
+ }
1714
+
1715
+ func TestE2E_MarkAllNotificationsRead (t * testing.T ) {
1716
+ t .Parallel ()
1717
+ client := setupMCPClient (t )
1718
+ ctx := context .Background ()
1719
+
1720
+ // Limit to notifications updated within the last hour
1721
+ oneHourAgo := nowMinusOneHourRFC3339 ()
1722
+ markAllReq := mcp.CallToolRequest {}
1723
+ markAllReq .Params .Name = "mark_all_notifications_read"
1724
+ markAllReq .Params .Arguments = map [string ]any {
1725
+ "since" :oneHourAgo ,
1726
+ }
1727
+ resp ,err := client .CallTool (ctx ,markAllReq )
1728
+ require .NoError (t ,err )
1729
+ require .False (t ,resp .IsError )
1730
+ textContent ,ok := resp .Content [0 ].(mcp.TextContent )
1731
+ require .True (t ,ok )
1732
+ require .Contains (t ,textContent .Text ,"All notifications marked as read" )
1733
+
1734
+ }
1735
+
1736
+ // nowMinusOneHourRFC3339 returns the RFC3339 timestamp for one hour ago from now (UTC)
1737
+ func nowMinusOneHourRFC3339 ()string {
1738
+ return time .Now ().UTC ().Add (- 1 * time .Hour ).Format (time .RFC3339 )
1739
+ }
1740
+
1741
+ func TestE2E_GetNotificationDetails (t * testing.T ) {
1742
+ t .Parallel ()
1743
+ client := setupMCPClient (t )
1744
+ ctx := context .Background ()
1745
+
1746
+ // List notifications to get a valid notificationID
1747
+ listReq := mcp.CallToolRequest {}
1748
+ listReq .Params .Name = "list_notifications"
1749
+ listReq .Params .Arguments = map [string ]any {}
1750
+ resp ,err := client .CallTool (ctx ,listReq )
1751
+ require .NoError (t ,err )
1752
+ require .False (t ,resp .IsError )
1753
+ textContent ,ok := resp .Content [0 ].(mcp.TextContent )
1754
+ require .True (t ,ok )
1755
+ var notifications []struct {
1756
+ ID string `json:"id"`
1757
+ }
1758
+ err = json .Unmarshal ([]byte (textContent .Text ),& notifications )
1759
+ require .NoError (t ,err )
1760
+ require .NotEmpty (t ,notifications )
1761
+ notificationID := notifications [0 ].ID
1762
+
1763
+ // Get notification details
1764
+ detailsReq := mcp.CallToolRequest {}
1765
+ detailsReq .Params .Name = "get_notification_details"
1766
+ detailsReq .Params .Arguments = map [string ]any {
1767
+ "notificationID" :notificationID ,
1768
+ }
1769
+ resp ,err = client .CallTool (ctx ,detailsReq )
1770
+ require .NoError (t ,err )
1771
+ require .False (t ,resp .IsError )
1772
+ textContent ,ok = resp .Content [0 ].(mcp.TextContent )
1773
+ require .True (t ,ok )
1774
+ require .Contains (t ,textContent .Text ,notificationID )
1775
+ }