Skip to content

Commit b9f493e

Browse files
committed
Support assigning copilot to issues
1 parent 4ccedee commit b9f493e

File tree

6 files changed

+713
-9
lines changed

6 files changed

+713
-9
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"
@@ -210,7 +211,6 @@ func TestGetMe(t *testing.T) {
210211
t.Parallel()
211212

212213
mcpClient := setupMCPClient(t)
213-
214214
ctx := context.Background()
215215

216216
// When we call the "get_me" tool
@@ -795,14 +795,13 @@ func TestDirectoryDeletion(t *testing.T) {
795795
}
796796

797797
func TestRequestCopilotReview(t *testing.T) {
798+
t.Parallel()
799+
798800
if getE2EHost() != "" && getE2EHost() != "https://github.com" {
799801
t.Skip("Skipping test because the host does not support copilot reviews")
800802
}
801803

802-
t.Parallel()
803-
804804
mcpClient := setupMCPClient(t)
805-
806805
ctx := context.Background()
807806

808807
// First, who am I
@@ -943,6 +942,112 @@ func TestRequestCopilotReview(t *testing.T) {
943942
require.Equal(t, "Bot", *reviewRequests.Users[0].Type, "expected review request to be for Bot")
944943
}
945944

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

@@ -1145,7 +1250,7 @@ func TestPullRequestReviewCommentSubmit(t *testing.T) {
11451250

11461251
t.Logf("Creating repository %s/%s...", currentOwner, repoName)
11471252
_, err = mcpClient.CallTool(ctx, createRepoRequest)
1148-
require.NoError(t, err, "expected to call 'get_me' tool successfully")
1253+
require.NoError(t, err, "expected to call 'create_repository' tool successfully")
11491254
require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp))
11501255

11511256
// Cleanup the repository after the test

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 {

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy