@@ -21,6 +21,7 @@ import (
2121 gogithub "github.com/google/go-github/v73/github"
2222 mcpClient "github.com/mark3labs/mcp-go/client"
2323 "github.com/mark3labs/mcp-go/mcp"
24+ "github.com/stretchr/testify/assert"
2425 "github.com/stretchr/testify/require"
2526)
2627
@@ -1624,3 +1625,155 @@ func TestPullRequestReviewDeletion(t *testing.T) {
16241625 require .NoError (t , err , "expected to unmarshal text content successfully" )
16251626 require .Len (t , noReviews , 0 , "expected to find no reviews" )
16261627}
1628+
1629+ func TestFindClosingPullRequests (t * testing.T ) {
1630+ t .Parallel ()
1631+
1632+ mcpClient := setupMCPClient (t , withToolsets ([]string {"issues" }))
1633+
1634+ ctx := context .Background ()
1635+
1636+ // Test with well-known GitHub repositories and issues
1637+ testCases := []struct {
1638+ name string
1639+ issues []interface {}
1640+ limit int
1641+ expectError bool
1642+ expectedResults int
1643+ expectSomeWithClosingPR bool
1644+ }{
1645+ {
1646+ name : "Single issue test - should handle gracefully even if no closing PRs" ,
1647+ issues : []interface {}{"octocat/Hello-World#1" },
1648+ limit : 5 ,
1649+ expectError : false ,
1650+ expectedResults : 1 ,
1651+ },
1652+ {
1653+ name : "Multiple issues test" ,
1654+ issues : []interface {}{"octocat/Hello-World#1" , "github/docs#1" },
1655+ limit : 3 ,
1656+ expectError : false ,
1657+ expectedResults : 2 ,
1658+ },
1659+ {
1660+ name : "Invalid issue format should return error" ,
1661+ issues : []interface {}{"invalid-format" },
1662+ expectError : true ,
1663+ },
1664+ {
1665+ name : "Empty issues array should return error" ,
1666+ issues : []interface {}{},
1667+ expectError : true ,
1668+ },
1669+ {
1670+ name : "Limit too high should return error" ,
1671+ issues : []interface {}{"octocat/Hello-World#1" },
1672+ limit : 150 ,
1673+ expectError : true ,
1674+ },
1675+ }
1676+
1677+ for _ , tc := range testCases {
1678+ t .Run (tc .name , func (t * testing.T ) {
1679+ // Prepare the request
1680+ findClosingPRsRequest := mcp.CallToolRequest {}
1681+ findClosingPRsRequest .Params .Name = "find_closing_pull_requests"
1682+
1683+ // Build arguments map
1684+ args := map [string ]any {
1685+ "issues" : tc .issues ,
1686+ }
1687+ if tc .limit > 0 {
1688+ args ["limit" ] = tc .limit
1689+ }
1690+ findClosingPRsRequest .Params .Arguments = args
1691+
1692+ t .Logf ("Calling find_closing_pull_requests with issues: %v" , tc .issues )
1693+ resp , err := mcpClient .CallTool (ctx , findClosingPRsRequest )
1694+
1695+ if tc .expectError {
1696+ // We expect either an error or an error response
1697+ if err != nil {
1698+ t .Logf ("Expected error occurred: %v" , err )
1699+ return
1700+ }
1701+ require .True (t , resp .IsError , "Expected error response" )
1702+ t .Logf ("Expected error in response: %+v" , resp )
1703+ return
1704+ }
1705+
1706+ require .NoError (t , err , "expected to call 'find_closing_pull_requests' tool successfully" )
1707+ require .False (t , resp .IsError , fmt .Sprintf ("expected result not to be an error: %+v" , resp ))
1708+
1709+ // Verify we got content
1710+ require .NotEmpty (t , resp .Content , "Expected response content" )
1711+
1712+ textContent , ok := resp .Content [0 ].(mcp.TextContent )
1713+ require .True (t , ok , "expected content to be of type TextContent" )
1714+
1715+ t .Logf ("Response: %s" , textContent .Text )
1716+
1717+ // Parse the JSON response
1718+ var response struct {
1719+ Results []struct {
1720+ Issue string `json:"issue"`
1721+ Owner string `json:"owner"`
1722+ Repo string `json:"repo"`
1723+ IssueNumber int `json:"issueNumber"`
1724+ ClosingPullRequests []struct {
1725+ Number int `json:"number"`
1726+ Title string `json:"title"`
1727+ Body string `json:"body"`
1728+ State string `json:"state"`
1729+ URL string `json:"url"`
1730+ Merged bool `json:"merged"`
1731+ } `json:"closingPullRequests"`
1732+ TotalCount int `json:"totalCount"`
1733+ Error string `json:"error,omitempty"`
1734+ } `json:"results"`
1735+ }
1736+
1737+ err = json .Unmarshal ([]byte (textContent .Text ), & response )
1738+ require .NoError (t , err , "expected to unmarshal response successfully" )
1739+
1740+ // Verify the response structure
1741+ require .Len (t , response .Results , tc .expectedResults , "Expected specific number of results" )
1742+
1743+ // Log and verify each result
1744+ for i , result := range response .Results {
1745+ t .Logf ("Result %d:" , i + 1 )
1746+ t .Logf (" Issue: %s" , result .Issue )
1747+ t .Logf (" Owner: %s, Repo: %s, Number: %d" , result .Owner , result .Repo , result .IssueNumber )
1748+ t .Logf (" Total closing PRs: %d" , result .TotalCount )
1749+ if result .Error != "" {
1750+ t .Logf (" Error: %s" , result .Error )
1751+ }
1752+
1753+ // Verify basic structure
1754+ assert .NotEmpty (t , result .Issue , "Issue reference should not be empty" )
1755+ assert .NotEmpty (t , result .Owner , "Owner should not be empty" )
1756+ assert .NotEmpty (t , result .Repo , "Repo should not be empty" )
1757+ assert .Greater (t , result .IssueNumber , 0 , "Issue number should be positive" )
1758+
1759+ // Log details of any closing PRs found
1760+ for j , pr := range result .ClosingPullRequests {
1761+ t .Logf (" Closing PR %d:" , j + 1 )
1762+ t .Logf (" Number: %d" , pr .Number )
1763+ t .Logf (" Title: %s" , pr .Title )
1764+ t .Logf (" State: %s, Merged: %t" , pr .State , pr .Merged )
1765+ t .Logf (" URL: %s" , pr .URL )
1766+
1767+ // Verify PR structure
1768+ assert .Greater (t , pr .Number , 0 , "PR number should be positive" )
1769+ assert .NotEmpty (t , pr .Title , "PR title should not be empty" )
1770+ assert .NotEmpty (t , pr .State , "PR state should not be empty" )
1771+ assert .NotEmpty (t , pr .URL , "PR URL should not be empty" )
1772+ }
1773+
1774+ // The actual count of closing PRs should match the returned array length
1775+ assert .Equal (t , len (result .ClosingPullRequests ), result .TotalCount , "ClosingPullRequests length should match TotalCount" )
1776+ }
1777+ })
1778+ }
1779+ }
0 commit comments