Skip to content

Commit 56b23c3

Browse files
committed
Add simple e2e test
1 parent 71b0075 commit 56b23c3

File tree

2 files changed

+184
-0
lines changed

2 files changed

+184
-0
lines changed

e2e/README.md

+84
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
# End To End (e2e) Tests
2+
3+
The purpose of the E2E tests is to have a simple (currently) test that gives maintainers some confidence in the black box behavior of our artifacts. It does this by:
4+
* Building the `github-mcp-server` docker image
5+
* Running the image
6+
* Interacting with the server via stdio
7+
* Issuing requests that interact with the live GitHub API
8+
9+
## Running the Tests
10+
11+
A service must be running that supports image building and container creation via the `docker` CLI.
12+
13+
Since these tests require a token to interact with real resources on the GitHub API, it is gated behind the `e2e` build flag.
14+
15+
```
16+
GITHUB_MCP_SERVER_E2E_TOKEN=<YOUR TOKEN> go test -v --tags e2e ./e2e
17+
```
18+
19+
The `GITHUB_MCP_SERVER_E2E_TOKEN` environment variable is mapped to `GITHUB_PERSONAL_ACCESS_TOKEN` internally, but separated to avoid accidental reuse of credentials.
20+
21+
## Example
22+
23+
The following diff adjusts the `get_me` tool to return `foobar` as the user login.
24+
25+
```diff
26+
diff --git a/pkg/github/context_tools.go b/pkg/github/context_tools.go
27+
index 1c91d70..ac4ef2b 100644
28+
--- a/pkg/github/context_tools.go
29+
+++ b/pkg/github/context_tools.go
30+
@@ -39,6 +39,8 @@ func GetMe(getClient GetClientFn, t translations.TranslationHelperFunc) (tool mc
31+
return mcp.NewToolResultError(fmt.Sprintf("failed to get user: %s", string(body))), nil
32+
}
33+
34+
+ user.Login = sPtr("foobar")
35+
+
36+
r, err := json.Marshal(user)
37+
if err != nil {
38+
return nil, fmt.Errorf("failed to marshal user: %w", err)
39+
@@ -47,3 +49,7 @@ func GetMe(getClient GetClientFn, t translations.TranslationHelperFunc) (tool mc
40+
return mcp.NewToolResultText(string(r)), nil
41+
}
42+
}
43+
+
44+
+func sPtr(s string) *string {
45+
+ return &s
46+
+}
47+
```
48+
49+
Running the tests:
50+
51+
```
52+
➜ GITHUB_MCP_SERVER_E2E_TOKEN=$(gh auth token) go test -v --tags e2e ./e2e
53+
=== RUN TestE2E
54+
e2e_test.go:92: Building Docker image for e2e tests...
55+
e2e_test.go:36: Starting Stdio MCP client...
56+
=== RUN TestE2E/Initialize
57+
=== RUN TestE2E/CallTool_get_me
58+
e2e_test.go:85:
59+
Error Trace: /Users/williammartin/workspace/github-mcp-server/e2e/e2e_test.go:85
60+
Error: Not equal:
61+
expected: "foobar"
62+
actual : "williammartin"
63+
64+
Diff:
65+
--- Expected
66+
+++ Actual
67+
@@ -1 +1 @@
68+
-foobar
69+
+williammartin
70+
Test: TestE2E/CallTool_get_me
71+
Messages: expected login to match
72+
--- FAIL: TestE2E (1.05s)
73+
--- PASS: TestE2E/Initialize (0.09s)
74+
--- FAIL: TestE2E/CallTool_get_me (0.46s)
75+
FAIL
76+
FAIL github.com/github/github-mcp-server/e2e 1.433s
77+
FAIL
78+
```
79+
80+
## Limitations
81+
82+
The current test suite is intentionally very limited in scope. This is because the maintenance costs on e2e tests tend to increase significantly over time. To read about some challenges with GitHub integration tests, see [go-github integration tests README](https://github.com/google/go-github/blob/5b75aa86dba5cf4af2923afa0938774f37fa0a67/test/README.md). We will expand this suite circumspectly!
83+
84+
Currently, visibility into failures is not particularly good.

e2e/e2e_test.go

+100
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
//go:build e2e
2+
3+
package e2e_test
4+
5+
import (
6+
"context"
7+
"encoding/json"
8+
"os"
9+
"os/exec"
10+
"testing"
11+
"time"
12+
13+
"github.com/google/go-github/v69/github"
14+
mcpClient "github.com/mark3labs/mcp-go/client"
15+
"github.com/mark3labs/mcp-go/mcp"
16+
"github.com/stretchr/testify/require"
17+
)
18+
19+
func TestE2E(t *testing.T) {
20+
e2eServerToken := os.Getenv("GITHUB_MCP_SERVER_E2E_TOKEN")
21+
if e2eServerToken == "" {
22+
t.Fatalf("GITHUB_MCP_SERVER_E2E_TOKEN environment variable is not set")
23+
}
24+
25+
// Build the Docker image for the MCP server.
26+
buildDockerImage(t)
27+
28+
t.Setenv("GITHUB_PERSONAL_ACCESS_TOKEN", e2eServerToken) // The MCP Client merges the existing environment.
29+
args := []string{
30+
"docker",
31+
"run",
32+
"-i",
33+
"--rm",
34+
"-e",
35+
"GITHUB_PERSONAL_ACCESS_TOKEN",
36+
"github/e2e-github-mcp-server",
37+
}
38+
t.Log("Starting Stdio MCP client...")
39+
client, err := mcpClient.NewStdioMCPClient(args[0], []string{}, args[1:]...)
40+
require.NoError(t, err, "expected to create client successfully")
41+
42+
t.Run("Initialize", func(t *testing.T) {
43+
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
44+
defer cancel()
45+
46+
request := mcp.InitializeRequest{}
47+
request.Params.ProtocolVersion = "2025-03-26"
48+
request.Params.ClientInfo = mcp.Implementation{
49+
Name: "e2e-test-client",
50+
Version: "0.0.1",
51+
}
52+
53+
result, err := client.Initialize(ctx, request)
54+
require.NoError(t, err, "expected to initialize successfully")
55+
56+
require.Equal(t, "github-mcp-server", result.ServerInfo.Name)
57+
})
58+
59+
t.Run("CallTool get_me", func(t *testing.T) {
60+
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
61+
defer cancel()
62+
63+
// When we call the "get_me" tool
64+
request := mcp.CallToolRequest{}
65+
request.Params.Name = "get_me"
66+
67+
response, err := client.CallTool(ctx, request)
68+
require.NoError(t, err, "expected to call 'get_me' tool successfully")
69+
70+
require.False(t, response.IsError, "expected result not to be an error")
71+
require.Len(t, response.Content, 1, "expected content to have one item")
72+
73+
textContent, ok := response.Content[0].(mcp.TextContent)
74+
require.True(t, ok, "expected content to be of type TextContent")
75+
76+
var trimmedContent struct {
77+
Login string `json:"login"`
78+
}
79+
err = json.Unmarshal([]byte(textContent.Text), &trimmedContent)
80+
require.NoError(t, err, "expected to unmarshal text content successfully")
81+
82+
// Then the login in the response should match the login obtained via the same
83+
// token using the GitHub API.
84+
client := github.NewClient(nil).WithAuthToken(e2eServerToken)
85+
user, _, err := client.Users.Get(context.Background(), "")
86+
require.NoError(t, err, "expected to get user successfully")
87+
require.Equal(t, trimmedContent.Login, *user.Login, "expected login to match")
88+
})
89+
90+
require.NoError(t, client.Close(), "expected to close client successfully")
91+
}
92+
93+
func buildDockerImage(t *testing.T) {
94+
t.Log("Building Docker image for e2e tests...")
95+
96+
cmd := exec.Command("docker", "build", "-t", "github/e2e-github-mcp-server", ".")
97+
cmd.Dir = ".." // Run this in the context of the root, where the Dockerfile is located.
98+
output, err := cmd.CombinedOutput()
99+
require.NoError(t, err, "expected to build Docker image successfully, output: %s", string(output))
100+
}

0 commit comments

Comments
 (0)