|
8 | 8 |
|
9 | 9 | "github.com/github/github-mcp-server/internal/githubv4mock" |
10 | 10 | "github.com/github/github-mcp-server/internal/toolsnaps" |
| 11 | + "github.com/github/github-mcp-server/pkg/http/headers" |
| 12 | + transportpkg "github.com/github/github-mcp-server/pkg/http/transport" |
11 | 13 | "github.com/github/github-mcp-server/pkg/inventory" |
12 | 14 | "github.com/github/github-mcp-server/pkg/translations" |
13 | 15 | gogithub "github.com/google/go-github/v87/github" |
@@ -1710,6 +1712,97 @@ func TestGranularSetIssueFields(t *testing.T) { |
1710 | 1712 | assert.Contains(t, textContent.Text, "confidence must be one of: low, medium, high") |
1711 | 1713 | }) |
1712 | 1714 |
|
| 1715 | + t.Run("confidence is sent when supplied", func(t *testing.T) { |
| 1716 | + confidence := "high" |
| 1717 | + matchers := []githubv4mock.Matcher{ |
| 1718 | + githubv4mock.NewQueryMatcher( |
| 1719 | + struct { |
| 1720 | + Repository struct { |
| 1721 | + Issue struct { |
| 1722 | + ID githubv4.ID |
| 1723 | + } `graphql:"issue(number: $issueNumber)"` |
| 1724 | + } `graphql:"repository(owner: $owner, name: $repo)"` |
| 1725 | + }{}, |
| 1726 | + map[string]any{ |
| 1727 | + "owner": githubv4.String("owner"), |
| 1728 | + "repo": githubv4.String("repo"), |
| 1729 | + "issueNumber": githubv4.Int(5), |
| 1730 | + }, |
| 1731 | + githubv4mock.DataResponse(map[string]any{ |
| 1732 | + "repository": map[string]any{ |
| 1733 | + "issue": map[string]any{"id": "ISSUE_123"}, |
| 1734 | + }, |
| 1735 | + }), |
| 1736 | + ), |
| 1737 | + githubv4mock.NewMutationMatcher( |
| 1738 | + struct { |
| 1739 | + SetIssueFieldValue struct { |
| 1740 | + Issue struct { |
| 1741 | + ID githubv4.ID |
| 1742 | + Number githubv4.Int |
| 1743 | + URL githubv4.String |
| 1744 | + } |
| 1745 | + IssueFieldValues []struct { |
| 1746 | + TextValue struct { |
| 1747 | + Value string |
| 1748 | + } `graphql:"... on IssueFieldTextValue"` |
| 1749 | + SingleSelectValue struct { |
| 1750 | + Name string |
| 1751 | + } `graphql:"... on IssueFieldSingleSelectValue"` |
| 1752 | + DateValue struct { |
| 1753 | + Value string |
| 1754 | + } `graphql:"... on IssueFieldDateValue"` |
| 1755 | + NumberValue struct { |
| 1756 | + Value float64 |
| 1757 | + } `graphql:"... on IssueFieldNumberValue"` |
| 1758 | + } |
| 1759 | + } `graphql:"setIssueFieldValue(input: $input)"` |
| 1760 | + }{}, |
| 1761 | + SetIssueFieldValueInput{ |
| 1762 | + IssueID: githubv4.ID("ISSUE_123"), |
| 1763 | + IssueFields: []IssueFieldCreateOrUpdateInput{ |
| 1764 | + { |
| 1765 | + FieldID: githubv4.ID("FIELD_1"), |
| 1766 | + TextValue: githubv4.NewString(githubv4.String("hello")), |
| 1767 | + Confidence: &confidence, |
| 1768 | + }, |
| 1769 | + }, |
| 1770 | + }, |
| 1771 | + nil, |
| 1772 | + githubv4mock.DataResponse(map[string]any{ |
| 1773 | + "setIssueFieldValue": map[string]any{ |
| 1774 | + "issue": map[string]any{ |
| 1775 | + "id": "ISSUE_123", |
| 1776 | + "number": 5, |
| 1777 | + "url": "https://github.com/owner/repo/issues/5", |
| 1778 | + }, |
| 1779 | + }, |
| 1780 | + }), |
| 1781 | + ), |
| 1782 | + } |
| 1783 | + |
| 1784 | + gqlClient := githubv4.NewClient(githubv4mock.NewMockedHTTPClient(matchers...)) |
| 1785 | + deps := BaseDeps{GQLClient: gqlClient} |
| 1786 | + serverTool := GranularSetIssueFields(translations.NullTranslationHelper) |
| 1787 | + handler := serverTool.Handler(deps) |
| 1788 | + |
| 1789 | + request := createMCPRequest(map[string]any{ |
| 1790 | + "owner": "owner", |
| 1791 | + "repo": "repo", |
| 1792 | + "issue_number": float64(5), |
| 1793 | + "fields": []any{ |
| 1794 | + map[string]any{ |
| 1795 | + "field_id": "FIELD_1", |
| 1796 | + "text_value": "hello", |
| 1797 | + "confidence": "high", |
| 1798 | + }, |
| 1799 | + }, |
| 1800 | + }) |
| 1801 | + result, err := handler(ContextWithDeps(context.Background(), deps), &request) |
| 1802 | + require.NoError(t, err) |
| 1803 | + assert.False(t, result.IsError, getTextResult(t, result).Text) |
| 1804 | + }) |
| 1805 | + |
1713 | 1806 | t.Run("successful set with suggest flag", func(t *testing.T) { |
1714 | 1807 | suggestTrue := githubv4.Boolean(true) |
1715 | 1808 | matchers := []githubv4mock.Matcher{ |
@@ -1802,4 +1895,101 @@ func TestGranularSetIssueFields(t *testing.T) { |
1802 | 1895 | require.NoError(t, err) |
1803 | 1896 | assert.False(t, result.IsError) |
1804 | 1897 | }) |
| 1898 | + |
| 1899 | + t.Run("sends GraphQL-Features: update_issue_suggestions header on mutation", func(t *testing.T) { |
| 1900 | + matchers := []githubv4mock.Matcher{ |
| 1901 | + githubv4mock.NewQueryMatcher( |
| 1902 | + struct { |
| 1903 | + Repository struct { |
| 1904 | + Issue struct { |
| 1905 | + ID githubv4.ID |
| 1906 | + } `graphql:"issue(number: $issueNumber)"` |
| 1907 | + } `graphql:"repository(owner: $owner, name: $repo)"` |
| 1908 | + }{}, |
| 1909 | + map[string]any{ |
| 1910 | + "owner": githubv4.String("owner"), |
| 1911 | + "repo": githubv4.String("repo"), |
| 1912 | + "issueNumber": githubv4.Int(5), |
| 1913 | + }, |
| 1914 | + githubv4mock.DataResponse(map[string]any{ |
| 1915 | + "repository": map[string]any{ |
| 1916 | + "issue": map[string]any{"id": "ISSUE_123"}, |
| 1917 | + }, |
| 1918 | + }), |
| 1919 | + ), |
| 1920 | + githubv4mock.NewMutationMatcher( |
| 1921 | + struct { |
| 1922 | + SetIssueFieldValue struct { |
| 1923 | + Issue struct { |
| 1924 | + ID githubv4.ID |
| 1925 | + Number githubv4.Int |
| 1926 | + URL githubv4.String |
| 1927 | + } |
| 1928 | + IssueFieldValues []struct { |
| 1929 | + TextValue struct { |
| 1930 | + Value string |
| 1931 | + } `graphql:"... on IssueFieldTextValue"` |
| 1932 | + SingleSelectValue struct { |
| 1933 | + Name string |
| 1934 | + } `graphql:"... on IssueFieldSingleSelectValue"` |
| 1935 | + DateValue struct { |
| 1936 | + Value string |
| 1937 | + } `graphql:"... on IssueFieldDateValue"` |
| 1938 | + NumberValue struct { |
| 1939 | + Value float64 |
| 1940 | + } `graphql:"... on IssueFieldNumberValue"` |
| 1941 | + } |
| 1942 | + } `graphql:"setIssueFieldValue(input: $input)"` |
| 1943 | + }{}, |
| 1944 | + SetIssueFieldValueInput{ |
| 1945 | + IssueID: githubv4.ID("ISSUE_123"), |
| 1946 | + IssueFields: []IssueFieldCreateOrUpdateInput{ |
| 1947 | + { |
| 1948 | + FieldID: githubv4.ID("FIELD_1"), |
| 1949 | + TextValue: githubv4.NewString(githubv4.String("hello")), |
| 1950 | + }, |
| 1951 | + }, |
| 1952 | + }, |
| 1953 | + nil, |
| 1954 | + githubv4mock.DataResponse(map[string]any{ |
| 1955 | + "setIssueFieldValue": map[string]any{ |
| 1956 | + "issue": map[string]any{ |
| 1957 | + "id": "ISSUE_123", |
| 1958 | + "number": 5, |
| 1959 | + "url": "https://github.com/owner/repo/issues/5", |
| 1960 | + }, |
| 1961 | + }, |
| 1962 | + }), |
| 1963 | + ), |
| 1964 | + } |
| 1965 | + |
| 1966 | + // Build a transport chain matching production: GraphQLFeaturesTransport |
| 1967 | + // wraps a header-capturing spy, which forwards to the mock's RoundTripper. |
| 1968 | + // This verifies the mutation request sets the update_issue_suggestions |
| 1969 | + // feature flag so the rationale/suggest input fields are accepted. |
| 1970 | + mockClient := githubv4mock.NewMockedHTTPClient(matchers...) |
| 1971 | + spy := &headerCaptureTransport{inner: mockClient.Transport} |
| 1972 | + httpClient := &http.Client{ |
| 1973 | + Transport: &transportpkg.GraphQLFeaturesTransport{Transport: spy}, |
| 1974 | + } |
| 1975 | + gqlClient := githubv4.NewClient(httpClient) |
| 1976 | + deps := BaseDeps{GQLClient: gqlClient} |
| 1977 | + serverTool := GranularSetIssueFields(translations.NullTranslationHelper) |
| 1978 | + handler := serverTool.Handler(deps) |
| 1979 | + |
| 1980 | + request := createMCPRequest(map[string]any{ |
| 1981 | + "owner": "owner", |
| 1982 | + "repo": "repo", |
| 1983 | + "issue_number": float64(5), |
| 1984 | + "fields": []any{ |
| 1985 | + map[string]any{"field_id": "FIELD_1", "text_value": "hello"}, |
| 1986 | + }, |
| 1987 | + }) |
| 1988 | + result, err := handler(ContextWithDeps(context.Background(), deps), &request) |
| 1989 | + require.NoError(t, err) |
| 1990 | + require.False(t, result.IsError, getTextResult(t, result).Text) |
| 1991 | + // The last request captured is the mutation; the preceding issue ID |
| 1992 | + // query does not require the feature flag. |
| 1993 | + assert.Equal(t, "update_issue_suggestions", spy.captured.Get(headers.GraphQLFeaturesHeader)) |
| 1994 | + }) |
1805 | 1995 | } |
0 commit comments