@@ -1525,3 +1525,251 @@ func TestPullRequestReviewDeletion(t *testing.T) {
15251525 require .NoError (t , err , "expected to unmarshal text content successfully" )
15261526 require .Len (t , noReviews , 0 , "expected to find no reviews" )
15271527}
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+ }
0 commit comments