1ddb78d0e3
Co-authored-by: Sam Morrow <info@sam-morrow.com>
1627 lines
63 KiB
Go
1627 lines
63 KiB
Go
//go:build e2e
|
|
|
|
package e2e_test
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"os"
|
|
"os/exec"
|
|
"slices"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/github/github-mcp-server/internal/ghmcp"
|
|
"github.com/github/github-mcp-server/pkg/github"
|
|
"github.com/github/github-mcp-server/pkg/translations"
|
|
gogithub "github.com/google/go-github/v72/github"
|
|
mcpClient "github.com/mark3labs/mcp-go/client"
|
|
"github.com/mark3labs/mcp-go/mcp"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
var (
|
|
// Shared variables and sync.Once instances to ensure one-time execution
|
|
getTokenOnce sync.Once
|
|
token string
|
|
|
|
getHostOnce sync.Once
|
|
host string
|
|
|
|
buildOnce sync.Once
|
|
buildError error
|
|
)
|
|
|
|
// getE2EToken ensures the environment variable is checked only once and returns the token
|
|
func getE2EToken(t *testing.T) string {
|
|
getTokenOnce.Do(func() {
|
|
token = os.Getenv("GITHUB_MCP_SERVER_E2E_TOKEN")
|
|
if token == "" {
|
|
t.Fatalf("GITHUB_MCP_SERVER_E2E_TOKEN environment variable is not set")
|
|
}
|
|
})
|
|
return token
|
|
}
|
|
|
|
// getE2EHost ensures the environment variable is checked only once and returns the host
|
|
func getE2EHost() string {
|
|
getHostOnce.Do(func() {
|
|
host = os.Getenv("GITHUB_MCP_SERVER_E2E_HOST")
|
|
})
|
|
return host
|
|
}
|
|
|
|
func getRESTClient(t *testing.T) *gogithub.Client {
|
|
// Get token and ensure Docker image is built
|
|
token := getE2EToken(t)
|
|
|
|
// Create a new GitHub client with the token
|
|
ghClient := gogithub.NewClient(nil).WithAuthToken(token)
|
|
|
|
if host := getE2EHost(); host != "" && host != "https://github.com" {
|
|
var err error
|
|
// Currently this works for GHEC because the API is exposed at the api subdomain and the path prefix
|
|
// but it would be preferable to extract the host parsing from the main server logic, and use it here.
|
|
ghClient, err = ghClient.WithEnterpriseURLs(host, host)
|
|
require.NoError(t, err, "expected to create GitHub client with host")
|
|
}
|
|
|
|
return ghClient
|
|
}
|
|
|
|
// ensureDockerImageBuilt makes sure the Docker image is built only once across all tests
|
|
func ensureDockerImageBuilt(t *testing.T) {
|
|
buildOnce.Do(func() {
|
|
t.Log("Building Docker image for e2e tests...")
|
|
cmd := exec.Command("docker", "build", "-t", "github/e2e-github-mcp-server", ".")
|
|
cmd.Dir = ".." // Run this in the context of the root, where the Dockerfile is located.
|
|
output, err := cmd.CombinedOutput()
|
|
buildError = err
|
|
if err != nil {
|
|
t.Logf("Docker build output: %s", string(output))
|
|
}
|
|
})
|
|
|
|
// Check if the build was successful
|
|
require.NoError(t, buildError, "expected to build Docker image successfully")
|
|
}
|
|
|
|
// clientOpts holds configuration options for the MCP client setup
|
|
type clientOpts struct {
|
|
// Toolsets to enable in the MCP server
|
|
enabledToolsets []string
|
|
}
|
|
|
|
// clientOption defines a function type for configuring ClientOpts
|
|
type clientOption func(*clientOpts)
|
|
|
|
// withToolsets returns an option that either sets the GITHUB_TOOLSETS envvar when executing in docker,
|
|
// or sets the toolsets in the MCP server when running in-process.
|
|
func withToolsets(toolsets []string) clientOption {
|
|
return func(opts *clientOpts) {
|
|
opts.enabledToolsets = toolsets
|
|
}
|
|
}
|
|
|
|
func setupMCPClient(t *testing.T, options ...clientOption) *mcpClient.Client {
|
|
// Get token and ensure Docker image is built
|
|
token := getE2EToken(t)
|
|
|
|
// Create and configure options
|
|
opts := &clientOpts{}
|
|
|
|
// Apply all options to configure the opts struct
|
|
for _, option := range options {
|
|
option(opts)
|
|
}
|
|
|
|
// By default, we run the tests including the Docker image, but with DEBUG
|
|
// enabled, we run the server in-process, allowing for easier debugging.
|
|
var client *mcpClient.Client
|
|
if os.Getenv("GITHUB_MCP_SERVER_E2E_DEBUG") == "" {
|
|
ensureDockerImageBuilt(t)
|
|
|
|
// Prepare Docker arguments
|
|
args := []string{
|
|
"docker",
|
|
"run",
|
|
"-i",
|
|
"--rm",
|
|
"-e",
|
|
"GITHUB_PERSONAL_ACCESS_TOKEN", // Personal access token is all required
|
|
}
|
|
|
|
host := getE2EHost()
|
|
if host != "" {
|
|
args = append(args, "-e", "GITHUB_HOST")
|
|
}
|
|
|
|
// Add toolsets environment variable to the Docker arguments
|
|
if len(opts.enabledToolsets) > 0 {
|
|
args = append(args, "-e", "GITHUB_TOOLSETS")
|
|
}
|
|
|
|
// Add the image name
|
|
args = append(args, "github/e2e-github-mcp-server")
|
|
|
|
// Construct the env vars for the MCP Client to execute docker with
|
|
dockerEnvVars := []string{
|
|
fmt.Sprintf("GITHUB_PERSONAL_ACCESS_TOKEN=%s", token),
|
|
fmt.Sprintf("GITHUB_TOOLSETS=%s", strings.Join(opts.enabledToolsets, ",")),
|
|
}
|
|
|
|
if host != "" {
|
|
dockerEnvVars = append(dockerEnvVars, fmt.Sprintf("GITHUB_HOST=%s", host))
|
|
}
|
|
|
|
// Create the client
|
|
t.Log("Starting Stdio MCP client...")
|
|
var err error
|
|
client, err = mcpClient.NewStdioMCPClient(args[0], dockerEnvVars, args[1:]...)
|
|
require.NoError(t, err, "expected to create client successfully")
|
|
} else {
|
|
// We need this because the fully compiled server has a default for the viper config, which is
|
|
// not in scope for using the MCP server directly. This probably indicates that we should refactor
|
|
// so that there is a shared setup mechanism, but let's wait till we feel more friction.
|
|
enabledToolsets := opts.enabledToolsets
|
|
if enabledToolsets == nil {
|
|
enabledToolsets = github.DefaultTools
|
|
}
|
|
|
|
ghServer, err := ghmcp.NewMCPServer(ghmcp.MCPServerConfig{
|
|
Token: token,
|
|
EnabledToolsets: enabledToolsets,
|
|
Host: getE2EHost(),
|
|
Translator: translations.NullTranslationHelper,
|
|
})
|
|
require.NoError(t, err, "expected to construct MCP server successfully")
|
|
|
|
t.Log("Starting In Process MCP client...")
|
|
client, err = mcpClient.NewInProcessClient(ghServer)
|
|
require.NoError(t, err, "expected to create in-process client successfully")
|
|
}
|
|
|
|
t.Cleanup(func() {
|
|
require.NoError(t, client.Close(), "expected to close client successfully")
|
|
})
|
|
|
|
// Initialize the client
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
defer cancel()
|
|
|
|
request := mcp.InitializeRequest{}
|
|
request.Params.ProtocolVersion = "2025-03-26"
|
|
request.Params.ClientInfo = mcp.Implementation{
|
|
Name: "e2e-test-client",
|
|
Version: "0.0.1",
|
|
}
|
|
|
|
result, err := client.Initialize(ctx, request)
|
|
require.NoError(t, err, "failed to initialize client")
|
|
require.Equal(t, "github-mcp-server", result.ServerInfo.Name, "unexpected server name")
|
|
|
|
return client
|
|
}
|
|
|
|
func TestGetMe(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
mcpClient := setupMCPClient(t)
|
|
ctx := context.Background()
|
|
|
|
// When we call the "get_me" tool
|
|
request := mcp.CallToolRequest{}
|
|
request.Params.Name = "get_me"
|
|
|
|
response, err := mcpClient.CallTool(ctx, request)
|
|
require.NoError(t, err, "expected to call 'get_me' tool successfully")
|
|
|
|
require.False(t, response.IsError, "expected result not to be an error")
|
|
require.Len(t, response.Content, 1, "expected content to have one item")
|
|
|
|
textContent, ok := response.Content[0].(mcp.TextContent)
|
|
require.True(t, ok, "expected content to be of type TextContent")
|
|
|
|
var trimmedContent struct {
|
|
Login string `json:"login"`
|
|
}
|
|
err = json.Unmarshal([]byte(textContent.Text), &trimmedContent)
|
|
require.NoError(t, err, "expected to unmarshal text content successfully")
|
|
|
|
// Then the login in the response should match the login obtained via the same
|
|
// token using the GitHub API.
|
|
ghClient := getRESTClient(t)
|
|
user, _, err := ghClient.Users.Get(context.Background(), "")
|
|
require.NoError(t, err, "expected to get user successfully")
|
|
require.Equal(t, trimmedContent.Login, *user.Login, "expected login to match")
|
|
|
|
}
|
|
|
|
func TestToolsets(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
mcpClient := setupMCPClient(
|
|
t,
|
|
withToolsets([]string{"repos", "issues"}),
|
|
)
|
|
|
|
ctx := context.Background()
|
|
|
|
request := mcp.ListToolsRequest{}
|
|
response, err := mcpClient.ListTools(ctx, request)
|
|
require.NoError(t, err, "expected to list tools successfully")
|
|
|
|
// We could enumerate the tools here, but we'll need to expose that information
|
|
// declaratively in the MCP server, so for the moment let's just check the existence
|
|
// of an issue and repo tool, and the non-existence of a pull_request tool.
|
|
var toolsContains = func(expectedName string) bool {
|
|
return slices.ContainsFunc(response.Tools, func(tool mcp.Tool) bool {
|
|
return tool.Name == expectedName
|
|
})
|
|
}
|
|
|
|
require.True(t, toolsContains("get_issue"), "expected to find 'get_issue' tool")
|
|
require.True(t, toolsContains("list_branches"), "expected to find 'list_branches' tool")
|
|
require.False(t, toolsContains("get_pull_request"), "expected not to find 'get_pull_request' tool")
|
|
}
|
|
|
|
func TestTags(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
mcpClient := setupMCPClient(t)
|
|
|
|
ctx := context.Background()
|
|
|
|
// First, who am I
|
|
getMeRequest := mcp.CallToolRequest{}
|
|
getMeRequest.Params.Name = "get_me"
|
|
|
|
t.Log("Getting current user...")
|
|
resp, err := mcpClient.CallTool(ctx, getMeRequest)
|
|
require.NoError(t, err, "expected to call 'get_me' tool successfully")
|
|
require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp))
|
|
|
|
require.False(t, resp.IsError, "expected result not to be an error")
|
|
require.Len(t, resp.Content, 1, "expected content to have one item")
|
|
|
|
textContent, ok := resp.Content[0].(mcp.TextContent)
|
|
require.True(t, ok, "expected content to be of type TextContent")
|
|
|
|
var trimmedGetMeText struct {
|
|
Login string `json:"login"`
|
|
}
|
|
err = json.Unmarshal([]byte(textContent.Text), &trimmedGetMeText)
|
|
require.NoError(t, err, "expected to unmarshal text content successfully")
|
|
|
|
currentOwner := trimmedGetMeText.Login
|
|
|
|
// Then create a repository with a README (via autoInit)
|
|
repoName := fmt.Sprintf("github-mcp-server-e2e-%s-%d", t.Name(), time.Now().UnixMilli())
|
|
createRepoRequest := mcp.CallToolRequest{}
|
|
createRepoRequest.Params.Name = "create_repository"
|
|
createRepoRequest.Params.Arguments = map[string]any{
|
|
"name": repoName,
|
|
"private": true,
|
|
"autoInit": true,
|
|
}
|
|
|
|
t.Logf("Creating repository %s/%s...", currentOwner, repoName)
|
|
_, err = mcpClient.CallTool(ctx, createRepoRequest)
|
|
require.NoError(t, err, "expected to call 'get_me' tool successfully")
|
|
require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp))
|
|
|
|
// Cleanup the repository after the test
|
|
t.Cleanup(func() {
|
|
// MCP Server doesn't support deletions, but we can use the GitHub Client
|
|
ghClient := getRESTClient(t)
|
|
t.Logf("Deleting repository %s/%s...", currentOwner, repoName)
|
|
_, err := ghClient.Repositories.Delete(context.Background(), currentOwner, repoName)
|
|
require.NoError(t, err, "expected to delete repository successfully")
|
|
})
|
|
|
|
// Then create a tag
|
|
// MCP Server doesn't support tag creation, but we can use the GitHub Client
|
|
ghClient := getRESTClient(t)
|
|
t.Logf("Creating tag %s/%s:%s...", currentOwner, repoName, "v0.0.1")
|
|
ref, _, err := ghClient.Git.GetRef(context.Background(), currentOwner, repoName, "refs/heads/main")
|
|
require.NoError(t, err, "expected to get ref successfully")
|
|
|
|
tagObj, _, err := ghClient.Git.CreateTag(context.Background(), currentOwner, repoName, &gogithub.Tag{
|
|
Tag: gogithub.Ptr("v0.0.1"),
|
|
Message: gogithub.Ptr("v0.0.1"),
|
|
Object: &gogithub.GitObject{
|
|
SHA: ref.Object.SHA,
|
|
Type: gogithub.Ptr("commit"),
|
|
},
|
|
})
|
|
require.NoError(t, err, "expected to create tag object successfully")
|
|
|
|
_, _, err = ghClient.Git.CreateRef(context.Background(), currentOwner, repoName, &gogithub.Reference{
|
|
Ref: gogithub.Ptr("refs/tags/v0.0.1"),
|
|
Object: &gogithub.GitObject{
|
|
SHA: tagObj.SHA,
|
|
},
|
|
})
|
|
require.NoError(t, err, "expected to create tag ref successfully")
|
|
|
|
// List the tags
|
|
listTagsRequest := mcp.CallToolRequest{}
|
|
listTagsRequest.Params.Name = "list_tags"
|
|
listTagsRequest.Params.Arguments = map[string]any{
|
|
"owner": currentOwner,
|
|
"repo": repoName,
|
|
}
|
|
|
|
t.Logf("Listing tags for %s/%s...", currentOwner, repoName)
|
|
resp, err = mcpClient.CallTool(ctx, listTagsRequest)
|
|
require.NoError(t, err, "expected to call 'list_tags' tool successfully")
|
|
require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp))
|
|
|
|
require.False(t, resp.IsError, "expected result not to be an error")
|
|
require.Len(t, resp.Content, 1, "expected content to have one item")
|
|
|
|
textContent, ok = resp.Content[0].(mcp.TextContent)
|
|
require.True(t, ok, "expected content to be of type TextContent")
|
|
|
|
var trimmedTags []struct {
|
|
Name string `json:"name"`
|
|
Commit struct {
|
|
SHA string `json:"sha"`
|
|
} `json:"commit"`
|
|
}
|
|
err = json.Unmarshal([]byte(textContent.Text), &trimmedTags)
|
|
require.NoError(t, err, "expected to unmarshal text content successfully")
|
|
|
|
require.Len(t, trimmedTags, 1, "expected to find one tag")
|
|
require.Equal(t, "v0.0.1", trimmedTags[0].Name, "expected tag name to match")
|
|
require.Equal(t, *ref.Object.SHA, trimmedTags[0].Commit.SHA, "expected tag SHA to match")
|
|
|
|
// And fetch an individual tag
|
|
getTagRequest := mcp.CallToolRequest{}
|
|
getTagRequest.Params.Name = "get_tag"
|
|
getTagRequest.Params.Arguments = map[string]any{
|
|
"owner": currentOwner,
|
|
"repo": repoName,
|
|
"tag": "v0.0.1",
|
|
}
|
|
|
|
t.Logf("Getting tag %s/%s:%s...", currentOwner, repoName, "v0.0.1")
|
|
resp, err = mcpClient.CallTool(ctx, getTagRequest)
|
|
require.NoError(t, err, "expected to call 'get_tag' tool successfully")
|
|
require.False(t, resp.IsError, "expected result not to be an error")
|
|
|
|
var trimmedTag []struct { // don't understand why this is an array
|
|
Name string `json:"name"`
|
|
Commit struct {
|
|
SHA string `json:"sha"`
|
|
} `json:"commit"`
|
|
}
|
|
err = json.Unmarshal([]byte(textContent.Text), &trimmedTag)
|
|
require.NoError(t, err, "expected to unmarshal text content successfully")
|
|
require.Len(t, trimmedTag, 1, "expected to find one tag")
|
|
require.Equal(t, "v0.0.1", trimmedTag[0].Name, "expected tag name to match")
|
|
require.Equal(t, *ref.Object.SHA, trimmedTag[0].Commit.SHA, "expected tag SHA to match")
|
|
}
|
|
|
|
func TestFileDeletion(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
mcpClient := setupMCPClient(t)
|
|
|
|
ctx := context.Background()
|
|
|
|
// First, who am I
|
|
getMeRequest := mcp.CallToolRequest{}
|
|
getMeRequest.Params.Name = "get_me"
|
|
|
|
t.Log("Getting current user...")
|
|
resp, err := mcpClient.CallTool(ctx, getMeRequest)
|
|
require.NoError(t, err, "expected to call 'get_me' tool successfully")
|
|
require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp))
|
|
|
|
require.False(t, resp.IsError, "expected result not to be an error")
|
|
require.Len(t, resp.Content, 1, "expected content to have one item")
|
|
|
|
textContent, ok := resp.Content[0].(mcp.TextContent)
|
|
require.True(t, ok, "expected content to be of type TextContent")
|
|
|
|
var trimmedGetMeText struct {
|
|
Login string `json:"login"`
|
|
}
|
|
err = json.Unmarshal([]byte(textContent.Text), &trimmedGetMeText)
|
|
require.NoError(t, err, "expected to unmarshal text content successfully")
|
|
|
|
currentOwner := trimmedGetMeText.Login
|
|
|
|
// Then create a repository with a README (via autoInit)
|
|
repoName := fmt.Sprintf("github-mcp-server-e2e-%s-%d", t.Name(), time.Now().UnixMilli())
|
|
createRepoRequest := mcp.CallToolRequest{}
|
|
createRepoRequest.Params.Name = "create_repository"
|
|
createRepoRequest.Params.Arguments = map[string]any{
|
|
"name": repoName,
|
|
"private": true,
|
|
"autoInit": true,
|
|
}
|
|
t.Logf("Creating repository %s/%s...", currentOwner, repoName)
|
|
_, err = mcpClient.CallTool(ctx, createRepoRequest)
|
|
require.NoError(t, err, "expected to call 'get_me' tool successfully")
|
|
require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp))
|
|
|
|
// Cleanup the repository after the test
|
|
t.Cleanup(func() {
|
|
// MCP Server doesn't support deletions, but we can use the GitHub Client
|
|
ghClient := getRESTClient(t)
|
|
t.Logf("Deleting repository %s/%s...", currentOwner, repoName)
|
|
_, err := ghClient.Repositories.Delete(context.Background(), currentOwner, repoName)
|
|
require.NoError(t, err, "expected to delete repository successfully")
|
|
})
|
|
|
|
// Create a branch on which to create a new commit
|
|
createBranchRequest := mcp.CallToolRequest{}
|
|
createBranchRequest.Params.Name = "create_branch"
|
|
createBranchRequest.Params.Arguments = map[string]any{
|
|
"owner": currentOwner,
|
|
"repo": repoName,
|
|
"branch": "test-branch",
|
|
"from_branch": "main",
|
|
}
|
|
|
|
t.Logf("Creating branch in %s/%s...", currentOwner, repoName)
|
|
resp, err = mcpClient.CallTool(ctx, createBranchRequest)
|
|
require.NoError(t, err, "expected to call 'create_branch' tool successfully")
|
|
require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp))
|
|
|
|
// Create a commit with a new file
|
|
commitRequest := mcp.CallToolRequest{}
|
|
commitRequest.Params.Name = "create_or_update_file"
|
|
commitRequest.Params.Arguments = map[string]any{
|
|
"owner": currentOwner,
|
|
"repo": repoName,
|
|
"path": "test-file.txt",
|
|
"content": fmt.Sprintf("Created by e2e test %s", t.Name()),
|
|
"message": "Add test file",
|
|
"branch": "test-branch",
|
|
}
|
|
|
|
t.Logf("Creating commit with new file in %s/%s...", currentOwner, repoName)
|
|
resp, err = mcpClient.CallTool(ctx, commitRequest)
|
|
require.NoError(t, err, "expected to call 'create_or_update_file' tool successfully")
|
|
require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp))
|
|
|
|
// Check the file exists
|
|
getFileContentsRequest := mcp.CallToolRequest{}
|
|
getFileContentsRequest.Params.Name = "get_file_contents"
|
|
getFileContentsRequest.Params.Arguments = map[string]any{
|
|
"owner": currentOwner,
|
|
"repo": repoName,
|
|
"path": "test-file.txt",
|
|
"branch": "test-branch",
|
|
}
|
|
|
|
t.Logf("Getting file contents in %s/%s...", currentOwner, repoName)
|
|
resp, err = mcpClient.CallTool(ctx, getFileContentsRequest)
|
|
require.NoError(t, err, "expected to call 'get_file_contents' tool successfully")
|
|
require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp))
|
|
|
|
embeddedResource, ok := resp.Content[1].(mcp.EmbeddedResource)
|
|
require.True(t, ok, "expected content to be of type EmbeddedResource")
|
|
|
|
// raw api
|
|
textResource, ok := embeddedResource.Resource.(mcp.TextResourceContents)
|
|
require.True(t, ok, "expected embedded resource to be of type TextResourceContents")
|
|
|
|
require.Equal(t, fmt.Sprintf("Created by e2e test %s", t.Name()), textResource.Text, "expected file content to match")
|
|
|
|
// Delete the file
|
|
deleteFileRequest := mcp.CallToolRequest{}
|
|
deleteFileRequest.Params.Name = "delete_file"
|
|
deleteFileRequest.Params.Arguments = map[string]any{
|
|
"owner": currentOwner,
|
|
"repo": repoName,
|
|
"path": "test-file.txt",
|
|
"message": "Delete test file",
|
|
"branch": "test-branch",
|
|
}
|
|
|
|
t.Logf("Deleting file in %s/%s...", currentOwner, repoName)
|
|
resp, err = mcpClient.CallTool(ctx, deleteFileRequest)
|
|
require.NoError(t, err, "expected to call 'delete_file' tool successfully")
|
|
require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp))
|
|
|
|
// See that there is a commit that removes the file
|
|
listCommitsRequest := mcp.CallToolRequest{}
|
|
listCommitsRequest.Params.Name = "list_commits"
|
|
listCommitsRequest.Params.Arguments = map[string]any{
|
|
"owner": currentOwner,
|
|
"repo": repoName,
|
|
"sha": "test-branch", // can be SHA or branch, which is an unfortunate API design
|
|
}
|
|
|
|
t.Logf("Listing commits in %s/%s...", currentOwner, repoName)
|
|
resp, err = mcpClient.CallTool(ctx, listCommitsRequest)
|
|
require.NoError(t, err, "expected to call 'list_commits' tool successfully")
|
|
require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp))
|
|
|
|
textContent, ok = resp.Content[0].(mcp.TextContent)
|
|
require.True(t, ok, "expected content to be of type TextContent")
|
|
|
|
var trimmedListCommitsText []struct {
|
|
SHA string `json:"sha"`
|
|
Commit struct {
|
|
Message string `json:"message"`
|
|
}
|
|
Files []struct {
|
|
Filename string `json:"filename"`
|
|
Deletions int `json:"deletions"`
|
|
}
|
|
}
|
|
err = json.Unmarshal([]byte(textContent.Text), &trimmedListCommitsText)
|
|
require.NoError(t, err, "expected to unmarshal text content successfully")
|
|
require.GreaterOrEqual(t, len(trimmedListCommitsText), 1, "expected to find at least one commit")
|
|
|
|
deletionCommit := trimmedListCommitsText[0]
|
|
require.Equal(t, "Delete test file", deletionCommit.Commit.Message, "expected commit message to match")
|
|
|
|
// Now get the commit so we can look at the file changes because list_commits doesn't include them
|
|
getCommitRequest := mcp.CallToolRequest{}
|
|
getCommitRequest.Params.Name = "get_commit"
|
|
getCommitRequest.Params.Arguments = map[string]any{
|
|
"owner": currentOwner,
|
|
"repo": repoName,
|
|
"sha": deletionCommit.SHA,
|
|
}
|
|
|
|
t.Logf("Getting commit %s/%s:%s...", currentOwner, repoName, deletionCommit.SHA)
|
|
resp, err = mcpClient.CallTool(ctx, getCommitRequest)
|
|
require.NoError(t, err, "expected to call 'get_commit' tool successfully")
|
|
require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp))
|
|
|
|
textContent, ok = resp.Content[0].(mcp.TextContent)
|
|
require.True(t, ok, "expected content to be of type TextContent")
|
|
|
|
var trimmedGetCommitText struct {
|
|
Files []struct {
|
|
Filename string `json:"filename"`
|
|
Deletions int `json:"deletions"`
|
|
}
|
|
}
|
|
err = json.Unmarshal([]byte(textContent.Text), &trimmedGetCommitText)
|
|
require.NoError(t, err, "expected to unmarshal text content successfully")
|
|
require.Len(t, trimmedGetCommitText.Files, 1, "expected to find one file change")
|
|
require.Equal(t, "test-file.txt", trimmedGetCommitText.Files[0].Filename, "expected filename to match")
|
|
require.Equal(t, 1, trimmedGetCommitText.Files[0].Deletions, "expected one deletion")
|
|
}
|
|
|
|
func TestDirectoryDeletion(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
mcpClient := setupMCPClient(t)
|
|
|
|
ctx := context.Background()
|
|
|
|
// First, who am I
|
|
getMeRequest := mcp.CallToolRequest{}
|
|
getMeRequest.Params.Name = "get_me"
|
|
|
|
t.Log("Getting current user...")
|
|
resp, err := mcpClient.CallTool(ctx, getMeRequest)
|
|
require.NoError(t, err, "expected to call 'get_me' tool successfully")
|
|
require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp))
|
|
|
|
require.False(t, resp.IsError, "expected result not to be an error")
|
|
require.Len(t, resp.Content, 1, "expected content to have one item")
|
|
|
|
textContent, ok := resp.Content[0].(mcp.TextContent)
|
|
require.True(t, ok, "expected content to be of type TextContent")
|
|
|
|
var trimmedGetMeText struct {
|
|
Login string `json:"login"`
|
|
}
|
|
err = json.Unmarshal([]byte(textContent.Text), &trimmedGetMeText)
|
|
require.NoError(t, err, "expected to unmarshal text content successfully")
|
|
|
|
currentOwner := trimmedGetMeText.Login
|
|
|
|
// Then create a repository with a README (via autoInit)
|
|
repoName := fmt.Sprintf("github-mcp-server-e2e-%s-%d", t.Name(), time.Now().UnixMilli())
|
|
createRepoRequest := mcp.CallToolRequest{}
|
|
createRepoRequest.Params.Name = "create_repository"
|
|
createRepoRequest.Params.Arguments = map[string]any{
|
|
"name": repoName,
|
|
"private": true,
|
|
"autoInit": true,
|
|
}
|
|
t.Logf("Creating repository %s/%s...", currentOwner, repoName)
|
|
_, err = mcpClient.CallTool(ctx, createRepoRequest)
|
|
require.NoError(t, err, "expected to call 'get_me' tool successfully")
|
|
require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp))
|
|
|
|
// Cleanup the repository after the test
|
|
t.Cleanup(func() {
|
|
// MCP Server doesn't support deletions, but we can use the GitHub Client
|
|
ghClient := getRESTClient(t)
|
|
t.Logf("Deleting repository %s/%s...", currentOwner, repoName)
|
|
_, err := ghClient.Repositories.Delete(context.Background(), currentOwner, repoName)
|
|
require.NoError(t, err, "expected to delete repository successfully")
|
|
})
|
|
|
|
// Create a branch on which to create a new commit
|
|
createBranchRequest := mcp.CallToolRequest{}
|
|
createBranchRequest.Params.Name = "create_branch"
|
|
createBranchRequest.Params.Arguments = map[string]any{
|
|
"owner": currentOwner,
|
|
"repo": repoName,
|
|
"branch": "test-branch",
|
|
"from_branch": "main",
|
|
}
|
|
|
|
t.Logf("Creating branch in %s/%s...", currentOwner, repoName)
|
|
resp, err = mcpClient.CallTool(ctx, createBranchRequest)
|
|
require.NoError(t, err, "expected to call 'create_branch' tool successfully")
|
|
require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp))
|
|
|
|
// Create a commit with a new file
|
|
commitRequest := mcp.CallToolRequest{}
|
|
commitRequest.Params.Name = "create_or_update_file"
|
|
commitRequest.Params.Arguments = map[string]any{
|
|
"owner": currentOwner,
|
|
"repo": repoName,
|
|
"path": "test-dir/test-file.txt",
|
|
"content": fmt.Sprintf("Created by e2e test %s", t.Name()),
|
|
"message": "Add test file",
|
|
"branch": "test-branch",
|
|
}
|
|
|
|
t.Logf("Creating commit with new file in %s/%s...", currentOwner, repoName)
|
|
resp, err = mcpClient.CallTool(ctx, commitRequest)
|
|
require.NoError(t, err, "expected to call 'create_or_update_file' tool successfully")
|
|
require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp))
|
|
|
|
textContent, ok = resp.Content[0].(mcp.TextContent)
|
|
require.True(t, ok, "expected content to be of type TextContent")
|
|
|
|
// Check the file exists
|
|
getFileContentsRequest := mcp.CallToolRequest{}
|
|
getFileContentsRequest.Params.Name = "get_file_contents"
|
|
getFileContentsRequest.Params.Arguments = map[string]any{
|
|
"owner": currentOwner,
|
|
"repo": repoName,
|
|
"path": "test-dir/test-file.txt",
|
|
"branch": "test-branch",
|
|
}
|
|
|
|
t.Logf("Getting file contents in %s/%s...", currentOwner, repoName)
|
|
resp, err = mcpClient.CallTool(ctx, getFileContentsRequest)
|
|
require.NoError(t, err, "expected to call 'get_file_contents' tool successfully")
|
|
require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp))
|
|
|
|
embeddedResource, ok := resp.Content[1].(mcp.EmbeddedResource)
|
|
require.True(t, ok, "expected content to be of type EmbeddedResource")
|
|
|
|
// raw api
|
|
textResource, ok := embeddedResource.Resource.(mcp.TextResourceContents)
|
|
require.True(t, ok, "expected embedded resource to be of type TextResourceContents")
|
|
|
|
require.Equal(t, fmt.Sprintf("Created by e2e test %s", t.Name()), textResource.Text, "expected file content to match")
|
|
|
|
// Delete the directory containing the file
|
|
deleteFileRequest := mcp.CallToolRequest{}
|
|
deleteFileRequest.Params.Name = "delete_file"
|
|
deleteFileRequest.Params.Arguments = map[string]any{
|
|
"owner": currentOwner,
|
|
"repo": repoName,
|
|
"path": "test-dir",
|
|
"message": "Delete test directory",
|
|
"branch": "test-branch",
|
|
}
|
|
|
|
t.Logf("Deleting directory in %s/%s...", currentOwner, repoName)
|
|
resp, err = mcpClient.CallTool(ctx, deleteFileRequest)
|
|
require.NoError(t, err, "expected to call 'delete_file' tool successfully")
|
|
require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp))
|
|
|
|
// See that there is a commit that removes the directory
|
|
listCommitsRequest := mcp.CallToolRequest{}
|
|
listCommitsRequest.Params.Name = "list_commits"
|
|
listCommitsRequest.Params.Arguments = map[string]any{
|
|
"owner": currentOwner,
|
|
"repo": repoName,
|
|
"sha": "test-branch", // can be SHA or branch, which is an unfortunate API design
|
|
}
|
|
|
|
t.Logf("Listing commits in %s/%s...", currentOwner, repoName)
|
|
resp, err = mcpClient.CallTool(ctx, listCommitsRequest)
|
|
require.NoError(t, err, "expected to call 'list_commits' tool successfully")
|
|
require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp))
|
|
|
|
textContent, ok = resp.Content[0].(mcp.TextContent)
|
|
require.True(t, ok, "expected content to be of type TextContent")
|
|
|
|
var trimmedListCommitsText []struct {
|
|
SHA string `json:"sha"`
|
|
Commit struct {
|
|
Message string `json:"message"`
|
|
}
|
|
Files []struct {
|
|
Filename string `json:"filename"`
|
|
Deletions int `json:"deletions"`
|
|
} `json:"files"`
|
|
}
|
|
err = json.Unmarshal([]byte(textContent.Text), &trimmedListCommitsText)
|
|
require.NoError(t, err, "expected to unmarshal text content successfully")
|
|
require.GreaterOrEqual(t, len(trimmedListCommitsText), 1, "expected to find at least one commit")
|
|
|
|
deletionCommit := trimmedListCommitsText[0]
|
|
require.Equal(t, "Delete test directory", deletionCommit.Commit.Message, "expected commit message to match")
|
|
|
|
// Now get the commit so we can look at the file changes because list_commits doesn't include them
|
|
getCommitRequest := mcp.CallToolRequest{}
|
|
getCommitRequest.Params.Name = "get_commit"
|
|
getCommitRequest.Params.Arguments = map[string]any{
|
|
"owner": currentOwner,
|
|
"repo": repoName,
|
|
"sha": deletionCommit.SHA,
|
|
}
|
|
|
|
t.Logf("Getting commit %s/%s:%s...", currentOwner, repoName, deletionCommit.SHA)
|
|
resp, err = mcpClient.CallTool(ctx, getCommitRequest)
|
|
require.NoError(t, err, "expected to call 'get_commit' tool successfully")
|
|
require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp))
|
|
|
|
textContent, ok = resp.Content[0].(mcp.TextContent)
|
|
require.True(t, ok, "expected content to be of type TextContent")
|
|
|
|
var trimmedGetCommitText struct {
|
|
Files []struct {
|
|
Filename string `json:"filename"`
|
|
Deletions int `json:"deletions"`
|
|
}
|
|
}
|
|
err = json.Unmarshal([]byte(textContent.Text), &trimmedGetCommitText)
|
|
require.NoError(t, err, "expected to unmarshal text content successfully")
|
|
require.Len(t, trimmedGetCommitText.Files, 1, "expected to find one file change")
|
|
require.Equal(t, "test-dir/test-file.txt", trimmedGetCommitText.Files[0].Filename, "expected filename to match")
|
|
require.Equal(t, 1, trimmedGetCommitText.Files[0].Deletions, "expected one deletion")
|
|
}
|
|
|
|
func TestRequestCopilotReview(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
if getE2EHost() != "" && getE2EHost() != "https://github.com" {
|
|
t.Skip("Skipping test because the host does not support copilot reviews")
|
|
}
|
|
|
|
mcpClient := setupMCPClient(t)
|
|
ctx := context.Background()
|
|
|
|
// First, who am I
|
|
getMeRequest := mcp.CallToolRequest{}
|
|
getMeRequest.Params.Name = "get_me"
|
|
|
|
t.Log("Getting current user...")
|
|
resp, err := mcpClient.CallTool(ctx, getMeRequest)
|
|
require.NoError(t, err, "expected to call 'get_me' tool successfully")
|
|
require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp))
|
|
|
|
require.False(t, resp.IsError, "expected result not to be an error")
|
|
require.Len(t, resp.Content, 1, "expected content to have one item")
|
|
|
|
textContent, ok := resp.Content[0].(mcp.TextContent)
|
|
require.True(t, ok, "expected content to be of type TextContent")
|
|
|
|
var trimmedGetMeText struct {
|
|
Login string `json:"login"`
|
|
}
|
|
err = json.Unmarshal([]byte(textContent.Text), &trimmedGetMeText)
|
|
require.NoError(t, err, "expected to unmarshal text content successfully")
|
|
|
|
currentOwner := trimmedGetMeText.Login
|
|
|
|
// Then create a repository with a README (via autoInit)
|
|
repoName := fmt.Sprintf("github-mcp-server-e2e-%s-%d", t.Name(), time.Now().UnixMilli())
|
|
createRepoRequest := mcp.CallToolRequest{}
|
|
createRepoRequest.Params.Name = "create_repository"
|
|
createRepoRequest.Params.Arguments = map[string]any{
|
|
"name": repoName,
|
|
"private": true,
|
|
"autoInit": true,
|
|
}
|
|
|
|
t.Logf("Creating repository %s/%s...", currentOwner, repoName)
|
|
_, err = mcpClient.CallTool(ctx, createRepoRequest)
|
|
require.NoError(t, err, "expected to call 'create_repository' tool successfully")
|
|
require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp))
|
|
|
|
// Cleanup the repository after the test
|
|
t.Cleanup(func() {
|
|
// MCP Server doesn't support deletions, but we can use the GitHub Client
|
|
ghClient := gogithub.NewClient(nil).WithAuthToken(getE2EToken(t))
|
|
t.Logf("Deleting repository %s/%s...", currentOwner, repoName)
|
|
_, err := ghClient.Repositories.Delete(context.Background(), currentOwner, repoName)
|
|
require.NoError(t, err, "expected to delete repository successfully")
|
|
})
|
|
|
|
// Create a branch on which to create a new commit
|
|
createBranchRequest := mcp.CallToolRequest{}
|
|
createBranchRequest.Params.Name = "create_branch"
|
|
createBranchRequest.Params.Arguments = map[string]any{
|
|
"owner": currentOwner,
|
|
"repo": repoName,
|
|
"branch": "test-branch",
|
|
"from_branch": "main",
|
|
}
|
|
|
|
t.Logf("Creating branch in %s/%s...", currentOwner, repoName)
|
|
resp, err = mcpClient.CallTool(ctx, createBranchRequest)
|
|
require.NoError(t, err, "expected to call 'create_branch' tool successfully")
|
|
require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp))
|
|
|
|
// Create a commit with a new file
|
|
commitRequest := mcp.CallToolRequest{}
|
|
commitRequest.Params.Name = "create_or_update_file"
|
|
commitRequest.Params.Arguments = map[string]any{
|
|
"owner": currentOwner,
|
|
"repo": repoName,
|
|
"path": "test-file.txt",
|
|
"content": fmt.Sprintf("Created by e2e test %s", t.Name()),
|
|
"message": "Add test file",
|
|
"branch": "test-branch",
|
|
}
|
|
|
|
t.Logf("Creating commit with new file in %s/%s...", currentOwner, repoName)
|
|
resp, err = mcpClient.CallTool(ctx, commitRequest)
|
|
require.NoError(t, err, "expected to call 'create_or_update_file' tool successfully")
|
|
require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp))
|
|
|
|
textContent, ok = resp.Content[0].(mcp.TextContent)
|
|
require.True(t, ok, "expected content to be of type TextContent")
|
|
|
|
var trimmedCommitText struct {
|
|
SHA string `json:"sha"`
|
|
}
|
|
err = json.Unmarshal([]byte(textContent.Text), &trimmedCommitText)
|
|
require.NoError(t, err, "expected to unmarshal text content successfully")
|
|
commitId := trimmedCommitText.SHA
|
|
|
|
// Create a pull request
|
|
prRequest := mcp.CallToolRequest{}
|
|
prRequest.Params.Name = "create_pull_request"
|
|
prRequest.Params.Arguments = map[string]any{
|
|
"owner": currentOwner,
|
|
"repo": repoName,
|
|
"title": "Test PR",
|
|
"body": "This is a test PR",
|
|
"head": "test-branch",
|
|
"base": "main",
|
|
"commitId": commitId,
|
|
}
|
|
|
|
t.Logf("Creating pull request in %s/%s...", currentOwner, repoName)
|
|
resp, err = mcpClient.CallTool(ctx, prRequest)
|
|
require.NoError(t, err, "expected to call 'create_pull_request' tool successfully")
|
|
require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp))
|
|
|
|
// Request a copilot review
|
|
requestCopilotReviewRequest := mcp.CallToolRequest{}
|
|
requestCopilotReviewRequest.Params.Name = "request_copilot_review"
|
|
requestCopilotReviewRequest.Params.Arguments = map[string]any{
|
|
"owner": currentOwner,
|
|
"repo": repoName,
|
|
"pullNumber": 1,
|
|
}
|
|
|
|
t.Logf("Requesting Copilot review for pull request in %s/%s...", currentOwner, repoName)
|
|
resp, err = mcpClient.CallTool(ctx, requestCopilotReviewRequest)
|
|
require.NoError(t, err, "expected to call 'request_copilot_review' tool successfully")
|
|
require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp))
|
|
|
|
textContent, ok = resp.Content[0].(mcp.TextContent)
|
|
require.True(t, ok, "expected content to be of type TextContent")
|
|
require.Equal(t, "", textContent.Text, "expected content to be empty")
|
|
|
|
// Finally, get requested reviews and see copilot is in there
|
|
// MCP Server doesn't support requesting reviews yet, but we can use the GitHub Client
|
|
ghClient := gogithub.NewClient(nil).WithAuthToken(getE2EToken(t))
|
|
t.Logf("Getting reviews for pull request in %s/%s...", currentOwner, repoName)
|
|
reviewRequests, _, err := ghClient.PullRequests.ListReviewers(context.Background(), currentOwner, repoName, 1, nil)
|
|
require.NoError(t, err, "expected to get review requests successfully")
|
|
|
|
// Check that there is one review request from copilot
|
|
require.Len(t, reviewRequests.Users, 1, "expected to find one review request")
|
|
require.Equal(t, "Copilot", *reviewRequests.Users[0].Login, "expected review request to be for Copilot")
|
|
require.Equal(t, "Bot", *reviewRequests.Users[0].Type, "expected review request to be for Bot")
|
|
}
|
|
|
|
func TestAssignCopilotToIssue(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
if getE2EHost() != "" && getE2EHost() != "https://github.com" {
|
|
t.Skip("Skipping test because the host does not support copilot being assigned to issues")
|
|
}
|
|
|
|
mcpClient := setupMCPClient(t)
|
|
ctx := context.Background()
|
|
|
|
// First, who am I
|
|
getMeRequest := mcp.CallToolRequest{}
|
|
getMeRequest.Params.Name = "get_me"
|
|
|
|
t.Log("Getting current user...")
|
|
resp, err := mcpClient.CallTool(ctx, getMeRequest)
|
|
require.NoError(t, err, "expected to call 'get_me' tool successfully")
|
|
require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp))
|
|
|
|
require.False(t, resp.IsError, "expected result not to be an error")
|
|
require.Len(t, resp.Content, 1, "expected content to have one item")
|
|
|
|
textContent, ok := resp.Content[0].(mcp.TextContent)
|
|
require.True(t, ok, "expected content to be of type TextContent")
|
|
|
|
var trimmedGetMeText struct {
|
|
Login string `json:"login"`
|
|
}
|
|
err = json.Unmarshal([]byte(textContent.Text), &trimmedGetMeText)
|
|
require.NoError(t, err, "expected to unmarshal text content successfully")
|
|
|
|
currentOwner := trimmedGetMeText.Login
|
|
|
|
// Then create a repository with a README (via autoInit)
|
|
repoName := fmt.Sprintf("github-mcp-server-e2e-%s-%d", t.Name(), time.Now().UnixMilli())
|
|
createRepoRequest := mcp.CallToolRequest{}
|
|
createRepoRequest.Params.Name = "create_repository"
|
|
createRepoRequest.Params.Arguments = map[string]any{
|
|
"name": repoName,
|
|
"private": true,
|
|
"autoInit": true,
|
|
}
|
|
|
|
t.Logf("Creating repository %s/%s...", currentOwner, repoName)
|
|
_, err = mcpClient.CallTool(ctx, createRepoRequest)
|
|
require.NoError(t, err, "expected to call 'create_repository' tool successfully")
|
|
require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp))
|
|
|
|
// Cleanup the repository after the test
|
|
t.Cleanup(func() {
|
|
// MCP Server doesn't support deletions, but we can use the GitHub Client
|
|
ghClient := getRESTClient(t)
|
|
t.Logf("Deleting repository %s/%s...", currentOwner, repoName)
|
|
_, err := ghClient.Repositories.Delete(context.Background(), currentOwner, repoName)
|
|
require.NoError(t, err, "expected to delete repository successfully")
|
|
})
|
|
|
|
// Create an issue
|
|
createIssueRequest := mcp.CallToolRequest{}
|
|
createIssueRequest.Params.Name = "create_issue"
|
|
createIssueRequest.Params.Arguments = map[string]any{
|
|
"owner": currentOwner,
|
|
"repo": repoName,
|
|
"title": "Test issue to assign copilot to",
|
|
}
|
|
|
|
t.Logf("Creating issue in %s/%s...", currentOwner, repoName)
|
|
resp, err = mcpClient.CallTool(ctx, createIssueRequest)
|
|
require.NoError(t, err, "expected to call 'create_issue' tool successfully")
|
|
require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp))
|
|
|
|
// Assign copilot to the issue
|
|
assignCopilotRequest := mcp.CallToolRequest{}
|
|
assignCopilotRequest.Params.Name = "assign_copilot_to_issue"
|
|
assignCopilotRequest.Params.Arguments = map[string]any{
|
|
"owner": currentOwner,
|
|
"repo": repoName,
|
|
"issueNumber": 1,
|
|
}
|
|
|
|
t.Logf("Assigning copilot to issue in %s/%s...", currentOwner, repoName)
|
|
resp, err = mcpClient.CallTool(ctx, assignCopilotRequest)
|
|
require.NoError(t, err, "expected to call 'assign_copilot_to_issue' tool successfully")
|
|
|
|
textContent, ok = resp.Content[0].(mcp.TextContent)
|
|
require.True(t, ok, "expected content to be of type TextContent")
|
|
|
|
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."
|
|
if resp.IsError && textContent.Text == possibleExpectedFailure {
|
|
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")
|
|
}
|
|
|
|
require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp))
|
|
|
|
require.Equal(t, "successfully assigned copilot to issue", textContent.Text)
|
|
|
|
// Check that copilot is assigned to the issue
|
|
// MCP Server doesn't support getting assignees yet
|
|
ghClient := getRESTClient(t)
|
|
assignees, response, err := ghClient.Issues.Get(context.Background(), currentOwner, repoName, 1)
|
|
require.NoError(t, err, "expected to get issue successfully")
|
|
require.Equal(t, http.StatusOK, response.StatusCode, "expected to get issue successfully")
|
|
require.Len(t, assignees.Assignees, 1, "expected to find one assignee")
|
|
require.Equal(t, "Copilot", *assignees.Assignees[0].Login, "expected copilot to be assigned to the issue")
|
|
}
|
|
|
|
func TestPullRequestAtomicCreateAndSubmit(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
mcpClient := setupMCPClient(t)
|
|
|
|
ctx := context.Background()
|
|
|
|
// First, who am I
|
|
getMeRequest := mcp.CallToolRequest{}
|
|
getMeRequest.Params.Name = "get_me"
|
|
|
|
t.Log("Getting current user...")
|
|
resp, err := mcpClient.CallTool(ctx, getMeRequest)
|
|
require.NoError(t, err, "expected to call 'get_me' tool successfully")
|
|
require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp))
|
|
|
|
require.False(t, resp.IsError, "expected result not to be an error")
|
|
require.Len(t, resp.Content, 1, "expected content to have one item")
|
|
|
|
textContent, ok := resp.Content[0].(mcp.TextContent)
|
|
require.True(t, ok, "expected content to be of type TextContent")
|
|
|
|
var trimmedGetMeText struct {
|
|
Login string `json:"login"`
|
|
}
|
|
err = json.Unmarshal([]byte(textContent.Text), &trimmedGetMeText)
|
|
require.NoError(t, err, "expected to unmarshal text content successfully")
|
|
|
|
currentOwner := trimmedGetMeText.Login
|
|
|
|
// Then create a repository with a README (via autoInit)
|
|
repoName := fmt.Sprintf("github-mcp-server-e2e-%s-%d", t.Name(), time.Now().UnixMilli())
|
|
createRepoRequest := mcp.CallToolRequest{}
|
|
createRepoRequest.Params.Name = "create_repository"
|
|
createRepoRequest.Params.Arguments = map[string]any{
|
|
"name": repoName,
|
|
"private": true,
|
|
"autoInit": true,
|
|
}
|
|
|
|
t.Logf("Creating repository %s/%s...", currentOwner, repoName)
|
|
_, err = mcpClient.CallTool(ctx, createRepoRequest)
|
|
require.NoError(t, err, "expected to call 'get_me' tool successfully")
|
|
require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp))
|
|
|
|
// Cleanup the repository after the test
|
|
t.Cleanup(func() {
|
|
// MCP Server doesn't support deletions, but we can use the GitHub Client
|
|
ghClient := getRESTClient(t)
|
|
t.Logf("Deleting repository %s/%s...", currentOwner, repoName)
|
|
_, err := ghClient.Repositories.Delete(context.Background(), currentOwner, repoName)
|
|
require.NoError(t, err, "expected to delete repository successfully")
|
|
})
|
|
|
|
// Create a branch on which to create a new commit
|
|
createBranchRequest := mcp.CallToolRequest{}
|
|
createBranchRequest.Params.Name = "create_branch"
|
|
createBranchRequest.Params.Arguments = map[string]any{
|
|
"owner": currentOwner,
|
|
"repo": repoName,
|
|
"branch": "test-branch",
|
|
"from_branch": "main",
|
|
}
|
|
|
|
t.Logf("Creating branch in %s/%s...", currentOwner, repoName)
|
|
resp, err = mcpClient.CallTool(ctx, createBranchRequest)
|
|
require.NoError(t, err, "expected to call 'create_branch' tool successfully")
|
|
require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp))
|
|
|
|
// Create a commit with a new file
|
|
commitRequest := mcp.CallToolRequest{}
|
|
commitRequest.Params.Name = "create_or_update_file"
|
|
commitRequest.Params.Arguments = map[string]any{
|
|
"owner": currentOwner,
|
|
"repo": repoName,
|
|
"path": "test-file.txt",
|
|
"content": fmt.Sprintf("Created by e2e test %s", t.Name()),
|
|
"message": "Add test file",
|
|
"branch": "test-branch",
|
|
}
|
|
|
|
t.Logf("Creating commit with new file in %s/%s...", currentOwner, repoName)
|
|
resp, err = mcpClient.CallTool(ctx, commitRequest)
|
|
require.NoError(t, err, "expected to call 'create_or_update_file' tool successfully")
|
|
require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp))
|
|
|
|
textContent, ok = resp.Content[0].(mcp.TextContent)
|
|
require.True(t, ok, "expected content to be of type TextContent")
|
|
|
|
var trimmedCommitText struct {
|
|
Commit struct {
|
|
SHA string `json:"sha"`
|
|
} `json:"commit"`
|
|
}
|
|
err = json.Unmarshal([]byte(textContent.Text), &trimmedCommitText)
|
|
require.NoError(t, err, "expected to unmarshal text content successfully")
|
|
commitID := trimmedCommitText.Commit.SHA
|
|
|
|
// Create a pull request
|
|
prRequest := mcp.CallToolRequest{}
|
|
prRequest.Params.Name = "create_pull_request"
|
|
prRequest.Params.Arguments = map[string]any{
|
|
"owner": currentOwner,
|
|
"repo": repoName,
|
|
"title": "Test PR",
|
|
"body": "This is a test PR",
|
|
"head": "test-branch",
|
|
"base": "main",
|
|
}
|
|
|
|
t.Logf("Creating pull request in %s/%s...", currentOwner, repoName)
|
|
resp, err = mcpClient.CallTool(ctx, prRequest)
|
|
require.NoError(t, err, "expected to call 'create_pull_request' tool successfully")
|
|
require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp))
|
|
|
|
// Create and submit a review
|
|
createAndSubmitReviewRequest := mcp.CallToolRequest{}
|
|
createAndSubmitReviewRequest.Params.Name = "create_and_submit_pull_request_review"
|
|
createAndSubmitReviewRequest.Params.Arguments = map[string]any{
|
|
"owner": currentOwner,
|
|
"repo": repoName,
|
|
"pullNumber": 1,
|
|
"event": "COMMENT", // the only event we can use as the creator of the PR
|
|
"body": "Looks good if you like bad code I guess!",
|
|
"commitID": commitID,
|
|
}
|
|
|
|
t.Logf("Creating and submitting review for pull request in %s/%s...", currentOwner, repoName)
|
|
resp, err = mcpClient.CallTool(ctx, createAndSubmitReviewRequest)
|
|
require.NoError(t, err, "expected to call 'create_and_submit_pull_request_review' tool successfully")
|
|
require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp))
|
|
|
|
// Finally, get the list of reviews and see that our review has been submitted
|
|
getPullRequestsReview := mcp.CallToolRequest{}
|
|
getPullRequestsReview.Params.Name = "get_pull_request_reviews"
|
|
getPullRequestsReview.Params.Arguments = map[string]any{
|
|
"owner": currentOwner,
|
|
"repo": repoName,
|
|
"pullNumber": 1,
|
|
}
|
|
|
|
t.Logf("Getting reviews for pull request in %s/%s...", currentOwner, repoName)
|
|
resp, err = mcpClient.CallTool(ctx, getPullRequestsReview)
|
|
require.NoError(t, err, "expected to call 'get_pull_request_reviews' tool successfully")
|
|
require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp))
|
|
|
|
textContent, ok = resp.Content[0].(mcp.TextContent)
|
|
require.True(t, ok, "expected content to be of type TextContent")
|
|
|
|
var reviews []struct {
|
|
State string `json:"state"`
|
|
}
|
|
err = json.Unmarshal([]byte(textContent.Text), &reviews)
|
|
require.NoError(t, err, "expected to unmarshal text content successfully")
|
|
|
|
// Check that there is one review
|
|
require.Len(t, reviews, 1, "expected to find one review")
|
|
require.Equal(t, "COMMENTED", reviews[0].State, "expected review state to be COMMENTED")
|
|
}
|
|
|
|
func TestPullRequestReviewCommentSubmit(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
mcpClient := setupMCPClient(t)
|
|
|
|
ctx := context.Background()
|
|
|
|
// First, who am I
|
|
getMeRequest := mcp.CallToolRequest{}
|
|
getMeRequest.Params.Name = "get_me"
|
|
|
|
t.Log("Getting current user...")
|
|
resp, err := mcpClient.CallTool(ctx, getMeRequest)
|
|
require.NoError(t, err, "expected to call 'get_me' tool successfully")
|
|
require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp))
|
|
|
|
require.False(t, resp.IsError, "expected result not to be an error")
|
|
require.Len(t, resp.Content, 1, "expected content to have one item")
|
|
|
|
textContent, ok := resp.Content[0].(mcp.TextContent)
|
|
require.True(t, ok, "expected content to be of type TextContent")
|
|
|
|
var trimmedGetMeText struct {
|
|
Login string `json:"login"`
|
|
}
|
|
err = json.Unmarshal([]byte(textContent.Text), &trimmedGetMeText)
|
|
require.NoError(t, err, "expected to unmarshal text content successfully")
|
|
|
|
currentOwner := trimmedGetMeText.Login
|
|
|
|
// Then create a repository with a README (via autoInit)
|
|
repoName := fmt.Sprintf("github-mcp-server-e2e-%s-%d", t.Name(), time.Now().UnixMilli())
|
|
createRepoRequest := mcp.CallToolRequest{}
|
|
createRepoRequest.Params.Name = "create_repository"
|
|
createRepoRequest.Params.Arguments = map[string]any{
|
|
"name": repoName,
|
|
"private": true,
|
|
"autoInit": true,
|
|
}
|
|
|
|
t.Logf("Creating repository %s/%s...", currentOwner, repoName)
|
|
_, err = mcpClient.CallTool(ctx, createRepoRequest)
|
|
require.NoError(t, err, "expected to call 'create_repository' tool successfully")
|
|
require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp))
|
|
|
|
// Cleanup the repository after the test
|
|
t.Cleanup(func() {
|
|
// MCP Server doesn't support deletions, but we can use the GitHub Client
|
|
ghClient := getRESTClient(t)
|
|
t.Logf("Deleting repository %s/%s...", currentOwner, repoName)
|
|
_, err := ghClient.Repositories.Delete(context.Background(), currentOwner, repoName)
|
|
require.NoError(t, err, "expected to delete repository successfully")
|
|
})
|
|
|
|
// Create a branch on which to create a new commit
|
|
createBranchRequest := mcp.CallToolRequest{}
|
|
createBranchRequest.Params.Name = "create_branch"
|
|
createBranchRequest.Params.Arguments = map[string]any{
|
|
"owner": currentOwner,
|
|
"repo": repoName,
|
|
"branch": "test-branch",
|
|
"from_branch": "main",
|
|
}
|
|
|
|
t.Logf("Creating branch in %s/%s...", currentOwner, repoName)
|
|
resp, err = mcpClient.CallTool(ctx, createBranchRequest)
|
|
require.NoError(t, err, "expected to call 'create_branch' tool successfully")
|
|
require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp))
|
|
|
|
// Create a commit with a new file
|
|
commitRequest := mcp.CallToolRequest{}
|
|
commitRequest.Params.Name = "create_or_update_file"
|
|
commitRequest.Params.Arguments = map[string]any{
|
|
"owner": currentOwner,
|
|
"repo": repoName,
|
|
"path": "test-file.txt",
|
|
"content": fmt.Sprintf("Created by e2e test %s\nwith multiple lines", t.Name()),
|
|
"message": "Add test file",
|
|
"branch": "test-branch",
|
|
}
|
|
|
|
t.Logf("Creating commit with new file in %s/%s...", currentOwner, repoName)
|
|
resp, err = mcpClient.CallTool(ctx, commitRequest)
|
|
require.NoError(t, err, "expected to call 'create_or_update_file' tool successfully")
|
|
require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp))
|
|
|
|
textContent, ok = resp.Content[0].(mcp.TextContent)
|
|
require.True(t, ok, "expected content to be of type TextContent")
|
|
|
|
var trimmedCommitText struct {
|
|
Commit struct {
|
|
SHA string `json:"sha"`
|
|
} `json:"commit"`
|
|
}
|
|
err = json.Unmarshal([]byte(textContent.Text), &trimmedCommitText)
|
|
require.NoError(t, err, "expected to unmarshal text content successfully")
|
|
commitId := trimmedCommitText.Commit.SHA
|
|
|
|
// Create a pull request
|
|
prRequest := mcp.CallToolRequest{}
|
|
prRequest.Params.Name = "create_pull_request"
|
|
prRequest.Params.Arguments = map[string]any{
|
|
"owner": currentOwner,
|
|
"repo": repoName,
|
|
"title": "Test PR",
|
|
"body": "This is a test PR",
|
|
"head": "test-branch",
|
|
"base": "main",
|
|
}
|
|
|
|
t.Logf("Creating pull request in %s/%s...", currentOwner, repoName)
|
|
resp, err = mcpClient.CallTool(ctx, prRequest)
|
|
require.NoError(t, err, "expected to call 'create_pull_request' tool successfully")
|
|
require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp))
|
|
|
|
// Create a review for the pull request, but we can't approve it
|
|
// because the current owner also owns the PR.
|
|
createPendingPullRequestReviewRequest := mcp.CallToolRequest{}
|
|
createPendingPullRequestReviewRequest.Params.Name = "create_pending_pull_request_review"
|
|
createPendingPullRequestReviewRequest.Params.Arguments = map[string]any{
|
|
"owner": currentOwner,
|
|
"repo": repoName,
|
|
"pullNumber": 1,
|
|
}
|
|
|
|
t.Logf("Creating pending review for pull request in %s/%s...", currentOwner, repoName)
|
|
resp, err = mcpClient.CallTool(ctx, createPendingPullRequestReviewRequest)
|
|
require.NoError(t, err, "expected to call 'create_pending_pull_request_review' tool successfully")
|
|
require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp))
|
|
|
|
textContent, ok = resp.Content[0].(mcp.TextContent)
|
|
require.True(t, ok, "expected content to be of type TextContent")
|
|
require.Equal(t, "pending pull request created", textContent.Text)
|
|
|
|
// Add a file review comment
|
|
addFileReviewCommentRequest := mcp.CallToolRequest{}
|
|
addFileReviewCommentRequest.Params.Name = "add_pull_request_review_comment_to_pending_review"
|
|
addFileReviewCommentRequest.Params.Arguments = map[string]any{
|
|
"owner": currentOwner,
|
|
"repo": repoName,
|
|
"pullNumber": 1,
|
|
"path": "test-file.txt",
|
|
"subjectType": "FILE",
|
|
"body": "File review comment",
|
|
}
|
|
|
|
t.Logf("Adding file review comment to pull request in %s/%s...", currentOwner, repoName)
|
|
resp, err = mcpClient.CallTool(ctx, addFileReviewCommentRequest)
|
|
require.NoError(t, err, "expected to call 'add_pull_request_review_comment_to_pending_review' tool successfully")
|
|
require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp))
|
|
|
|
// Add a single line review comment
|
|
addSingleLineReviewCommentRequest := mcp.CallToolRequest{}
|
|
addSingleLineReviewCommentRequest.Params.Name = "add_pull_request_review_comment_to_pending_review"
|
|
addSingleLineReviewCommentRequest.Params.Arguments = map[string]any{
|
|
"owner": currentOwner,
|
|
"repo": repoName,
|
|
"pullNumber": 1,
|
|
"path": "test-file.txt",
|
|
"subjectType": "LINE",
|
|
"body": "Single line review comment",
|
|
"line": 1,
|
|
"side": "RIGHT",
|
|
"commitId": commitId,
|
|
}
|
|
|
|
t.Logf("Adding single line review comment to pull request in %s/%s...", currentOwner, repoName)
|
|
resp, err = mcpClient.CallTool(ctx, addSingleLineReviewCommentRequest)
|
|
require.NoError(t, err, "expected to call 'add_pull_request_review_comment_to_pending_review' tool successfully")
|
|
require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp))
|
|
|
|
// Add a multiline review comment
|
|
addMultilineReviewCommentRequest := mcp.CallToolRequest{}
|
|
addMultilineReviewCommentRequest.Params.Name = "add_pull_request_review_comment_to_pending_review"
|
|
addMultilineReviewCommentRequest.Params.Arguments = map[string]any{
|
|
"owner": currentOwner,
|
|
"repo": repoName,
|
|
"pullNumber": 1,
|
|
"path": "test-file.txt",
|
|
"subjectType": "LINE",
|
|
"body": "Multiline review comment",
|
|
"startLine": 1,
|
|
"line": 2,
|
|
"startSide": "RIGHT",
|
|
"side": "RIGHT",
|
|
"commitId": commitId,
|
|
}
|
|
|
|
t.Logf("Adding multi line review comment to pull request in %s/%s...", currentOwner, repoName)
|
|
resp, err = mcpClient.CallTool(ctx, addMultilineReviewCommentRequest)
|
|
require.NoError(t, err, "expected to call 'add_pull_request_review_comment_to_pending_review' tool successfully")
|
|
require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp))
|
|
|
|
// Submit the review
|
|
submitReviewRequest := mcp.CallToolRequest{}
|
|
submitReviewRequest.Params.Name = "submit_pending_pull_request_review"
|
|
submitReviewRequest.Params.Arguments = map[string]any{
|
|
"owner": currentOwner,
|
|
"repo": repoName,
|
|
"pullNumber": 1,
|
|
"event": "COMMENT", // the only event we can use as the creator of the PR
|
|
"body": "Looks good if you like bad code I guess!",
|
|
}
|
|
|
|
t.Logf("Submitting review for pull request in %s/%s...", currentOwner, repoName)
|
|
resp, err = mcpClient.CallTool(ctx, submitReviewRequest)
|
|
require.NoError(t, err, "expected to call 'submit_pending_pull_request_review' tool successfully")
|
|
require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp))
|
|
|
|
// Finally, get the review and see that it has been created
|
|
getPullRequestsReview := mcp.CallToolRequest{}
|
|
getPullRequestsReview.Params.Name = "get_pull_request_reviews"
|
|
getPullRequestsReview.Params.Arguments = map[string]any{
|
|
"owner": currentOwner,
|
|
"repo": repoName,
|
|
"pullNumber": 1,
|
|
}
|
|
|
|
t.Logf("Getting reviews for pull request in %s/%s...", currentOwner, repoName)
|
|
resp, err = mcpClient.CallTool(ctx, getPullRequestsReview)
|
|
require.NoError(t, err, "expected to call 'get_pull_request_reviews' tool successfully")
|
|
require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp))
|
|
|
|
textContent, ok = resp.Content[0].(mcp.TextContent)
|
|
require.True(t, ok, "expected content to be of type TextContent")
|
|
|
|
var reviews []struct {
|
|
ID int `json:"id"`
|
|
State string `json:"state"`
|
|
}
|
|
err = json.Unmarshal([]byte(textContent.Text), &reviews)
|
|
require.NoError(t, err, "expected to unmarshal text content successfully")
|
|
|
|
// Check that there is one review
|
|
require.Len(t, reviews, 1, "expected to find one review")
|
|
require.Equal(t, "COMMENTED", reviews[0].State, "expected review state to be COMMENTED")
|
|
|
|
// Check that there are three review comments
|
|
// MCP Server doesn't support this, but we can use the GitHub Client
|
|
ghClient := getRESTClient(t)
|
|
comments, _, err := ghClient.PullRequests.ListReviewComments(context.Background(), currentOwner, repoName, 1, int64(reviews[0].ID), nil)
|
|
require.NoError(t, err, "expected to list review comments successfully")
|
|
require.Equal(t, 3, len(comments), "expected to find three review comments")
|
|
}
|
|
|
|
func TestPullRequestReviewDeletion(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
mcpClient := setupMCPClient(t)
|
|
|
|
ctx := context.Background()
|
|
|
|
// First, who am I
|
|
getMeRequest := mcp.CallToolRequest{}
|
|
getMeRequest.Params.Name = "get_me"
|
|
|
|
t.Log("Getting current user...")
|
|
resp, err := mcpClient.CallTool(ctx, getMeRequest)
|
|
require.NoError(t, err, "expected to call 'get_me' tool successfully")
|
|
require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp))
|
|
|
|
require.False(t, resp.IsError, "expected result not to be an error")
|
|
require.Len(t, resp.Content, 1, "expected content to have one item")
|
|
|
|
textContent, ok := resp.Content[0].(mcp.TextContent)
|
|
require.True(t, ok, "expected content to be of type TextContent")
|
|
|
|
var trimmedGetMeText struct {
|
|
Login string `json:"login"`
|
|
}
|
|
err = json.Unmarshal([]byte(textContent.Text), &trimmedGetMeText)
|
|
require.NoError(t, err, "expected to unmarshal text content successfully")
|
|
|
|
currentOwner := trimmedGetMeText.Login
|
|
|
|
// Then create a repository with a README (via autoInit)
|
|
repoName := fmt.Sprintf("github-mcp-server-e2e-%s-%d", t.Name(), time.Now().UnixMilli())
|
|
createRepoRequest := mcp.CallToolRequest{}
|
|
createRepoRequest.Params.Name = "create_repository"
|
|
createRepoRequest.Params.Arguments = map[string]any{
|
|
"name": repoName,
|
|
"private": true,
|
|
"autoInit": true,
|
|
}
|
|
|
|
t.Logf("Creating repository %s/%s...", currentOwner, repoName)
|
|
_, err = mcpClient.CallTool(ctx, createRepoRequest)
|
|
require.NoError(t, err, "expected to call 'get_me' tool successfully")
|
|
require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp))
|
|
|
|
// Cleanup the repository after the test
|
|
t.Cleanup(func() {
|
|
// MCP Server doesn't support deletions, but we can use the GitHub Client
|
|
ghClient := getRESTClient(t)
|
|
t.Logf("Deleting repository %s/%s...", currentOwner, repoName)
|
|
_, err := ghClient.Repositories.Delete(context.Background(), currentOwner, repoName)
|
|
require.NoError(t, err, "expected to delete repository successfully")
|
|
})
|
|
|
|
// Create a branch on which to create a new commit
|
|
createBranchRequest := mcp.CallToolRequest{}
|
|
createBranchRequest.Params.Name = "create_branch"
|
|
createBranchRequest.Params.Arguments = map[string]any{
|
|
"owner": currentOwner,
|
|
"repo": repoName,
|
|
"branch": "test-branch",
|
|
"from_branch": "main",
|
|
}
|
|
|
|
t.Logf("Creating branch in %s/%s...", currentOwner, repoName)
|
|
resp, err = mcpClient.CallTool(ctx, createBranchRequest)
|
|
require.NoError(t, err, "expected to call 'create_branch' tool successfully")
|
|
require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp))
|
|
|
|
// Create a commit with a new file
|
|
commitRequest := mcp.CallToolRequest{}
|
|
commitRequest.Params.Name = "create_or_update_file"
|
|
commitRequest.Params.Arguments = map[string]any{
|
|
"owner": currentOwner,
|
|
"repo": repoName,
|
|
"path": "test-file.txt",
|
|
"content": fmt.Sprintf("Created by e2e test %s", t.Name()),
|
|
"message": "Add test file",
|
|
"branch": "test-branch",
|
|
}
|
|
|
|
t.Logf("Creating commit with new file in %s/%s...", currentOwner, repoName)
|
|
resp, err = mcpClient.CallTool(ctx, commitRequest)
|
|
require.NoError(t, err, "expected to call 'create_or_update_file' tool successfully")
|
|
require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp))
|
|
|
|
// Create a pull request
|
|
prRequest := mcp.CallToolRequest{}
|
|
prRequest.Params.Name = "create_pull_request"
|
|
prRequest.Params.Arguments = map[string]any{
|
|
"owner": currentOwner,
|
|
"repo": repoName,
|
|
"title": "Test PR",
|
|
"body": "This is a test PR",
|
|
"head": "test-branch",
|
|
"base": "main",
|
|
}
|
|
|
|
t.Logf("Creating pull request in %s/%s...", currentOwner, repoName)
|
|
resp, err = mcpClient.CallTool(ctx, prRequest)
|
|
require.NoError(t, err, "expected to call 'create_pull_request' tool successfully")
|
|
require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp))
|
|
|
|
// Create a review for the pull request, but we can't approve it
|
|
// because the current owner also owns the PR.
|
|
createPendingPullRequestReviewRequest := mcp.CallToolRequest{}
|
|
createPendingPullRequestReviewRequest.Params.Name = "create_pending_pull_request_review"
|
|
createPendingPullRequestReviewRequest.Params.Arguments = map[string]any{
|
|
"owner": currentOwner,
|
|
"repo": repoName,
|
|
"pullNumber": 1,
|
|
}
|
|
|
|
t.Logf("Creating pending review for pull request in %s/%s...", currentOwner, repoName)
|
|
resp, err = mcpClient.CallTool(ctx, createPendingPullRequestReviewRequest)
|
|
require.NoError(t, err, "expected to call 'create_pending_pull_request_review' tool successfully")
|
|
require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp))
|
|
|
|
textContent, ok = resp.Content[0].(mcp.TextContent)
|
|
require.True(t, ok, "expected content to be of type TextContent")
|
|
require.Equal(t, "pending pull request created", textContent.Text)
|
|
|
|
// See that there is a pending review
|
|
getPullRequestsReview := mcp.CallToolRequest{}
|
|
getPullRequestsReview.Params.Name = "get_pull_request_reviews"
|
|
getPullRequestsReview.Params.Arguments = map[string]any{
|
|
"owner": currentOwner,
|
|
"repo": repoName,
|
|
"pullNumber": 1,
|
|
}
|
|
|
|
t.Logf("Getting reviews for pull request in %s/%s...", currentOwner, repoName)
|
|
resp, err = mcpClient.CallTool(ctx, getPullRequestsReview)
|
|
require.NoError(t, err, "expected to call 'get_pull_request_reviews' tool successfully")
|
|
require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp))
|
|
|
|
textContent, ok = resp.Content[0].(mcp.TextContent)
|
|
require.True(t, ok, "expected content to be of type TextContent")
|
|
|
|
var reviews []struct {
|
|
State string `json:"state"`
|
|
}
|
|
err = json.Unmarshal([]byte(textContent.Text), &reviews)
|
|
require.NoError(t, err, "expected to unmarshal text content successfully")
|
|
|
|
// Check that there is one review
|
|
require.Len(t, reviews, 1, "expected to find one review")
|
|
require.Equal(t, "PENDING", reviews[0].State, "expected review state to be PENDING")
|
|
|
|
// Delete the review
|
|
deleteReviewRequest := mcp.CallToolRequest{}
|
|
deleteReviewRequest.Params.Name = "delete_pending_pull_request_review"
|
|
deleteReviewRequest.Params.Arguments = map[string]any{
|
|
"owner": currentOwner,
|
|
"repo": repoName,
|
|
"pullNumber": 1,
|
|
}
|
|
|
|
t.Logf("Deleting review for pull request in %s/%s...", currentOwner, repoName)
|
|
resp, err = mcpClient.CallTool(ctx, deleteReviewRequest)
|
|
require.NoError(t, err, "expected to call 'delete_pending_pull_request_review' tool successfully")
|
|
require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp))
|
|
|
|
// See that there are no reviews
|
|
t.Logf("Getting reviews for pull request in %s/%s...", currentOwner, repoName)
|
|
resp, err = mcpClient.CallTool(ctx, getPullRequestsReview)
|
|
require.NoError(t, err, "expected to call 'get_pull_request_reviews' tool successfully")
|
|
require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp))
|
|
|
|
textContent, ok = resp.Content[0].(mcp.TextContent)
|
|
require.True(t, ok, "expected content to be of type TextContent")
|
|
|
|
var noReviews []struct{}
|
|
err = json.Unmarshal([]byte(textContent.Text), &noReviews)
|
|
require.NoError(t, err, "expected to unmarshal text content successfully")
|
|
require.Len(t, noReviews, 0, "expected to find no reviews")
|
|
}
|