Skip to content

Commit c09adbd

Browse files
Merge branch 'main' into notifications-tooling
2 parents 6b25869 + b9a06d0 commit c09adbd

13 files changed

+767
-45
lines changed

e2e/e2e_test.go

Lines changed: 110 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"encoding/base64"
88
"encoding/json"
99
"fmt"
10+
"net/http"
1011
"os"
1112
"os/exec"
1213
"slices"
@@ -221,7 +222,6 @@ func TestGetMe(t *testing.T) {
221222
t.Parallel()
222223

223224
mcpClient := setupMCPClient(t)
224-
225225
ctx := context.Background()
226226

227227
// When we call the "get_me" tool
@@ -806,14 +806,13 @@ func TestDirectoryDeletion(t *testing.T) {
806806
}
807807

808808
func TestRequestCopilotReview(t *testing.T) {
809+
t.Parallel()
810+
809811
if getE2EHost() != "" && getE2EHost() != "https://github.com" {
810812
t.Skip("Skipping test because the host does not support copilot reviews")
811813
}
812814

813-
t.Parallel()
814-
815815
mcpClient := setupMCPClient(t)
816-
817816
ctx := context.Background()
818817

819818
// First, who am I
@@ -954,6 +953,112 @@ func TestRequestCopilotReview(t *testing.T) {
954953
require.Equal(t, "Bot", *reviewRequests.Users[0].Type, "expected review request to be for Bot")
955954
}
956955

956+
func TestAssignCopilotToIssue(t *testing.T) {
957+
t.Parallel()
958+
959+
if getE2EHost() != "" && getE2EHost() != "https://github.com" {
960+
t.Skip("Skipping test because the host does not support copilot being assigned to issues")
961+
}
962+
963+
mcpClient := setupMCPClient(t)
964+
ctx := context.Background()
965+
966+
// First, who am I
967+
getMeRequest := mcp.CallToolRequest{}
968+
getMeRequest.Params.Name = "get_me"
969+
970+
t.Log("Getting current user...")
971+
resp, err := mcpClient.CallTool(ctx, getMeRequest)
972+
require.NoError(t, err, "expected to call 'get_me' tool successfully")
973+
require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp))
974+
975+
require.False(t, resp.IsError, "expected result not to be an error")
976+
require.Len(t, resp.Content, 1, "expected content to have one item")
977+
978+
textContent, ok := resp.Content[0].(mcp.TextContent)
979+
require.True(t, ok, "expected content to be of type TextContent")
980+
981+
var trimmedGetMeText struct {
982+
Login string `json:"login"`
983+
}
984+
err = json.Unmarshal([]byte(textContent.Text), &trimmedGetMeText)
985+
require.NoError(t, err, "expected to unmarshal text content successfully")
986+
987+
currentOwner := trimmedGetMeText.Login
988+
989+
// Then create a repository with a README (via autoInit)
990+
repoName := fmt.Sprintf("github-mcp-server-e2e-%s-%d", t.Name(), time.Now().UnixMilli())
991+
createRepoRequest := mcp.CallToolRequest{}
992+
createRepoRequest.Params.Name = "create_repository"
993+
createRepoRequest.Params.Arguments = map[string]any{
994+
"name": repoName,
995+
"private": true,
996+
"autoInit": true,
997+
}
998+
999+
t.Logf("Creating repository %s/%s...", currentOwner, repoName)
1000+
_, err = mcpClient.CallTool(ctx, createRepoRequest)
1001+
require.NoError(t, err, "expected to call 'create_repository' tool successfully")
1002+
require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp))
1003+
1004+
// Cleanup the repository after the test
1005+
t.Cleanup(func() {
1006+
// MCP Server doesn't support deletions, but we can use the GitHub Client
1007+
ghClient := getRESTClient(t)
1008+
t.Logf("Deleting repository %s/%s...", currentOwner, repoName)
1009+
_, err := ghClient.Repositories.Delete(context.Background(), currentOwner, repoName)
1010+
require.NoError(t, err, "expected to delete repository successfully")
1011+
})
1012+
1013+
// Create an issue
1014+
createIssueRequest := mcp.CallToolRequest{}
1015+
createIssueRequest.Params.Name = "create_issue"
1016+
createIssueRequest.Params.Arguments = map[string]any{
1017+
"owner": currentOwner,
1018+
"repo": repoName,
1019+
"title": "Test issue to assign copilot to",
1020+
}
1021+
1022+
t.Logf("Creating issue in %s/%s...", currentOwner, repoName)
1023+
resp, err = mcpClient.CallTool(ctx, createIssueRequest)
1024+
require.NoError(t, err, "expected to call 'create_issue' tool successfully")
1025+
require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp))
1026+
1027+
// Assign copilot to the issue
1028+
assignCopilotRequest := mcp.CallToolRequest{}
1029+
assignCopilotRequest.Params.Name = "assign_copilot_to_issue"
1030+
assignCopilotRequest.Params.Arguments = map[string]any{
1031+
"owner": currentOwner,
1032+
"repo": repoName,
1033+
"issueNumber": 1,
1034+
}
1035+
1036+
t.Logf("Assigning copilot to issue in %s/%s...", currentOwner, repoName)
1037+
resp, err = mcpClient.CallTool(ctx, assignCopilotRequest)
1038+
require.NoError(t, err, "expected to call 'assign_copilot_to_issue' tool successfully")
1039+
1040+
textContent, ok = resp.Content[0].(mcp.TextContent)
1041+
require.True(t, ok, "expected content to be of type TextContent")
1042+
1043+
possibleExpectedFailure := "copilot isn't available as an assignee for this issue. Please inform the user to visit https://docs.github.com/en/copilot/using-github-copilot/using-copilot-coding-agent-to-work-on-tasks/about-assigning-tasks-to-copilot for more information."
1044+
if resp.IsError && textContent.Text == possibleExpectedFailure {
1045+
t.Skip("skipping because copilot wasn't available as an assignee on this issue, it's likely that the owner doesn't have copilot enabled in their settings")
1046+
}
1047+
1048+
require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp))
1049+
1050+
require.Equal(t, "successfully assigned copilot to issue", textContent.Text)
1051+
1052+
// Check that copilot is assigned to the issue
1053+
// MCP Server doesn't support getting assignees yet
1054+
ghClient := getRESTClient(t)
1055+
assignees, response, err := ghClient.Issues.Get(context.Background(), currentOwner, repoName, 1)
1056+
require.NoError(t, err, "expected to get issue successfully")
1057+
require.Equal(t, http.StatusOK, response.StatusCode, "expected to get issue successfully")
1058+
require.Len(t, assignees.Assignees, 1, "expected to find one assignee")
1059+
require.Equal(t, "Copilot", *assignees.Assignees[0].Login, "expected copilot to be assigned to the issue")
1060+
}
1061+
9571062
func TestPullRequestAtomicCreateAndSubmit(t *testing.T) {
9581063
t.Parallel()
9591064

@@ -1156,7 +1261,7 @@ func TestPullRequestReviewCommentSubmit(t *testing.T) {
11561261

11571262
t.Logf("Creating repository %s/%s...", currentOwner, repoName)
11581263
_, err = mcpClient.CallTool(ctx, createRepoRequest)
1159-
require.NoError(t, err, "expected to call 'get_me' tool successfully")
1264+
require.NoError(t, err, "expected to call 'create_repository' tool successfully")
11601265
require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp))
11611266

11621267
// Cleanup the repository after the test

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ go 1.23.7
44

55
require (
66
github.com/google/go-github/v69 v69.2.0
7-
github.com/mark3labs/mcp-go v0.27.0
7+
github.com/mark3labs/mcp-go v0.28.0
88
github.com/migueleliasweb/go-github-mock v1.3.0
99
github.com/sirupsen/logrus v1.9.3
1010
github.com/spf13/cobra v1.9.1

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
3131
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
3232
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
3333
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
34-
github.com/mark3labs/mcp-go v0.27.0 h1:iok9kU4DUIU2/XVLgFS2Q9biIDqstC0jY4EQTK2Erzc=
35-
github.com/mark3labs/mcp-go v0.27.0/go.mod h1:rXqOudj/djTORU/ThxYx8fqEVj/5pvTuuebQ2RC7uk4=
34+
github.com/mark3labs/mcp-go v0.28.0 h1:7yl4y5D1KYU2f/9Uxp7xfLIggfunHoESCRbrjcytcLM=
35+
github.com/mark3labs/mcp-go v0.28.0/go.mod h1:rXqOudj/djTORU/ThxYx8fqEVj/5pvTuuebQ2RC7uk4=
3636
github.com/migueleliasweb/go-github-mock v1.3.0 h1:2sVP9JEMB2ubQw1IKto3/fzF51oFC6eVWOOFDgQoq88=
3737
github.com/migueleliasweb/go-github-mock v1.3.0/go.mod h1:ipQhV8fTcj/G6m7BKzin08GaJ/3B5/SonRAkgrk0zCY=
3838
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=

internal/githubv4mock/objects_are_equal_values.go

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// The contents of this file are taken from https://github.com/stretchr/testify/blob/016e2e9c269209287f33ec203f340a9a723fe22c/assert/assertions.go#L166
22
// because I do not want to take a dependency on the entire testify module just to use this equality check.
33
//
4+
// There is a modification in objectsAreEqual to check that typed nils are equal, even if their types are different.
5+
//
46
// The original license, copied from https://github.com/stretchr/testify/blob/016e2e9c269209287f33ec203f340a9a723fe22c/LICENSE
57
//
68
// MIT License
@@ -69,8 +71,10 @@ func objectsAreEqualValues(expected, actual any) bool {
6971
//
7072
// This function does no assertion of any kind.
7173
func objectsAreEqual(expected, actual any) bool {
72-
if expected == nil || actual == nil {
73-
return expected == actual
74+
// There is a modification in objectsAreEqual to check that typed nils are equal, even if their types are different.
75+
// This is required because when a nil is provided as a variable, the type is not known.
76+
if isNil(expected) && isNil(actual) {
77+
return true
7478
}
7579

7680
exp, ok := expected.([]byte)
@@ -94,3 +98,16 @@ func objectsAreEqual(expected, actual any) bool {
9498
func isNumericType(t reflect.Type) bool {
9599
return t.Kind() >= reflect.Int && t.Kind() <= reflect.Complex128
96100
}
101+
102+
func isNil(i any) bool {
103+
if i == nil {
104+
return true
105+
}
106+
v := reflect.ValueOf(i)
107+
switch v.Kind() {
108+
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Pointer, reflect.Slice:
109+
return v.IsNil()
110+
default:
111+
return false
112+
}
113+
}

internal/githubv4mock/objects_are_equal_values_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
// The contents of this file are taken from https://github.com/stretchr/testify/blob/016e2e9c269209287f33ec203f340a9a723fe22c/assert/assertions_test.go#L140-L174
22
//
3+
// There is a modification to test objectsAreEqualValues to check that typed nils are equal, even if their types are different.
4+
35
// The original license, copied from https://github.com/stretchr/testify/blob/016e2e9c269209287f33ec203f340a9a723fe22c/LICENSE
46
//
57
// MIT License
@@ -55,6 +57,8 @@ func TestObjectsAreEqualValues(t *testing.T) {
5557
{3.14, complex128(1e+100 + 1e+100i), false},
5658
{complex128(1e+10 + 1e+10i), complex64(1e+10 + 1e+10i), true},
5759
{complex64(1e+10 + 1e+10i), complex128(1e+10 + 1e+10i), true},
60+
{(*string)(nil), nil, true}, // typed nil vs untyped nil
61+
{(*string)(nil), (*int)(nil), true}, // different typed nils
5862
}
5963

6064
for _, c := range cases {

pkg/github/helper_test.go

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -109,14 +109,12 @@ func mockResponse(t *testing.T, code int, body interface{}) http.HandlerFunc {
109109
}
110110

111111
// createMCPRequest is a helper function to create a MCP request with the given arguments.
112-
func createMCPRequest(args map[string]interface{}) mcp.CallToolRequest {
112+
func createMCPRequest(args map[string]any) mcp.CallToolRequest {
113113
return mcp.CallToolRequest{
114114
Params: struct {
115-
Name string `json:"name"`
116-
Arguments map[string]interface{} `json:"arguments,omitempty"`
117-
Meta *struct {
118-
ProgressToken mcp.ProgressToken `json:"progressToken,omitempty"`
119-
} `json:"_meta,omitempty"`
115+
Name string `json:"name"`
116+
Arguments map[string]any `json:"arguments,omitempty"`
117+
Meta *mcp.Meta `json:"_meta,omitempty"`
120118
}{
121119
Arguments: args,
122120
},

0 commit comments

Comments
 (0)