Skip to content

Commit c192013

Browse files
CopilotJoannaaKL
andcommitted
migrate tests from go-github-mock to internal testify-based mock
Co-authored-by: JoannaaKL <67866556+JoannaaKL@users.noreply.github.com>
1 parent e46b48f commit c192013

5 files changed

Lines changed: 201 additions & 16 deletions

File tree

pkg/github/actions_test.go

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@ import (
1515
"github.com/github/github-mcp-server/internal/profiler"
1616
"github.com/github/github-mcp-server/internal/toolsnaps"
1717
buffer "github.com/github/github-mcp-server/pkg/buffer"
18+
mock "github.com/github/github-mcp-server/pkg/github/testmock"
1819
"github.com/github/github-mcp-server/pkg/translations"
1920
"github.com/google/go-github/v79/github"
2021
"github.com/google/jsonschema-go/jsonschema"
21-
"github.com/migueleliasweb/go-github-mock/src/mock"
2222
"github.com/stretchr/testify/assert"
2323
"github.com/stretchr/testify/require"
2424
)
@@ -1540,7 +1540,7 @@ func Test_ListWorkflowRuns_Behavioral(t *testing.T) {
15401540
name: "successful workflow runs listing",
15411541
mockedClient: mock.NewMockedHTTPClient(
15421542
mock.WithRequestMatchHandler(
1543-
mock.GetReposActionsWorkflowsRunsByOwnerByRepoByWorkflowId,
1543+
mock.GetReposActionsWorkflowsRunsByOwnerByRepoByWorkflowID,
15441544
http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
15451545
runs := &github.WorkflowRuns{
15461546
TotalCount: github.Ptr(2),
@@ -1627,7 +1627,7 @@ func Test_GetWorkflowRun_Behavioral(t *testing.T) {
16271627
name: "successful get workflow run",
16281628
mockedClient: mock.NewMockedHTTPClient(
16291629
mock.WithRequestMatchHandler(
1630-
mock.GetReposActionsRunsByOwnerByRepoByRunId,
1630+
mock.GetReposActionsRunsByOwnerByRepoByRunID,
16311631
http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
16321632
run := &github.WorkflowRun{
16331633
ID: github.Ptr(int64(12345)),
@@ -1703,7 +1703,7 @@ func Test_GetWorkflowRunLogs_Behavioral(t *testing.T) {
17031703
name: "successful get workflow run logs",
17041704
mockedClient: mock.NewMockedHTTPClient(
17051705
mock.WithRequestMatchHandler(
1706-
mock.GetReposActionsRunsLogsByOwnerByRepoByRunId,
1706+
mock.GetReposActionsRunsLogsByOwnerByRepoByRunID,
17071707
http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
17081708
w.Header().Set("Location", "https://github.com/logs/run/12345")
17091709
w.WriteHeader(http.StatusFound)
@@ -1773,7 +1773,7 @@ func Test_ListWorkflowJobs_Behavioral(t *testing.T) {
17731773
name: "successful list workflow jobs",
17741774
mockedClient: mock.NewMockedHTTPClient(
17751775
mock.WithRequestMatchHandler(
1776-
mock.GetReposActionsRunsJobsByOwnerByRepoByRunId,
1776+
mock.GetReposActionsRunsJobsByOwnerByRepoByRunID,
17771777
http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
17781778
jobs := &github.Jobs{
17791779
TotalCount: github.Ptr(2),
@@ -1954,7 +1954,7 @@ func Test_ActionsList_ListWorkflowRuns(t *testing.T) {
19541954
t.Run("successful workflow runs list", func(t *testing.T) {
19551955
mockedClient := mock.NewMockedHTTPClient(
19561956
mock.WithRequestMatchHandler(
1957-
mock.GetReposActionsWorkflowsRunsByOwnerByRepoByWorkflowId,
1957+
mock.GetReposActionsWorkflowsRunsByOwnerByRepoByWorkflowID,
19581958
http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
19591959
runs := &github.WorkflowRuns{
19601960
TotalCount: github.Ptr(1),
@@ -2070,7 +2070,7 @@ func Test_ActionsGet_GetWorkflow(t *testing.T) {
20702070
t.Run("successful workflow get", func(t *testing.T) {
20712071
mockedClient := mock.NewMockedHTTPClient(
20722072
mock.WithRequestMatchHandler(
2073-
mock.GetReposActionsWorkflowsByOwnerByRepoByWorkflowId,
2073+
mock.GetReposActionsWorkflowsByOwnerByRepoByWorkflowID,
20742074
http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
20752075
workflow := &github.Workflow{
20762076
ID: github.Ptr(int64(1)),
@@ -2116,7 +2116,7 @@ func Test_ActionsGet_GetWorkflowRun(t *testing.T) {
21162116
t.Run("successful workflow run get", func(t *testing.T) {
21172117
mockedClient := mock.NewMockedHTTPClient(
21182118
mock.WithRequestMatchHandler(
2119-
mock.GetReposActionsRunsByOwnerByRepoByRunId,
2119+
mock.GetReposActionsRunsByOwnerByRepoByRunID,
21202120
http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
21212121
run := &github.WorkflowRun{
21222122
ID: github.Ptr(int64(12345)),
@@ -2187,7 +2187,7 @@ func Test_ActionsRunTrigger_RunWorkflow(t *testing.T) {
21872187
name: "successful workflow run",
21882188
mockedClient: mock.NewMockedHTTPClient(
21892189
mock.WithRequestMatchHandler(
2190-
mock.PostReposActionsWorkflowsDispatchesByOwnerByRepoByWorkflowId,
2190+
mock.PostReposActionsWorkflowsDispatchesByOwnerByRepoByWorkflowID,
21912191
http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
21922192
w.WriteHeader(http.StatusNoContent)
21932193
}),
@@ -2381,7 +2381,7 @@ func Test_ActionsGetJobLogs_SingleJob(t *testing.T) {
23812381
t.Run("successful single job logs with URL", func(t *testing.T) {
23822382
mockedClient := mock.NewMockedHTTPClient(
23832383
mock.WithRequestMatchHandler(
2384-
mock.GetReposActionsJobsLogsByOwnerByRepoByJobId,
2384+
mock.GetReposActionsJobsLogsByOwnerByRepoByJobID,
23852385
http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
23862386
w.Header().Set("Location", "https://github.com/logs/job/123")
23872387
w.WriteHeader(http.StatusFound)
@@ -2422,7 +2422,7 @@ func Test_ActionsGetJobLogs_FailedJobs(t *testing.T) {
24222422
t.Run("successful failed jobs logs", func(t *testing.T) {
24232423
mockedClient := mock.NewMockedHTTPClient(
24242424
mock.WithRequestMatchHandler(
2425-
mock.GetReposActionsRunsJobsByOwnerByRepoByRunId,
2425+
mock.GetReposActionsRunsJobsByOwnerByRepoByRunID,
24262426
http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
24272427
jobs := &github.Jobs{
24282428
TotalCount: github.Ptr(3),
@@ -2449,7 +2449,7 @@ func Test_ActionsGetJobLogs_FailedJobs(t *testing.T) {
24492449
}),
24502450
),
24512451
mock.WithRequestMatchHandler(
2452-
mock.GetReposActionsJobsLogsByOwnerByRepoByJobId,
2452+
mock.GetReposActionsJobsLogsByOwnerByRepoByJobID,
24532453
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
24542454
w.Header().Set("Location", "https://github.com/logs/job/"+r.URL.Path[len(r.URL.Path)-1:])
24552455
w.WriteHeader(http.StatusFound)
@@ -2487,7 +2487,7 @@ func Test_ActionsGetJobLogs_FailedJobs(t *testing.T) {
24872487
t.Run("no failed jobs found", func(t *testing.T) {
24882488
mockedClient := mock.NewMockedHTTPClient(
24892489
mock.WithRequestMatchHandler(
2490-
mock.GetReposActionsRunsJobsByOwnerByRepoByRunId,
2490+
mock.GetReposActionsRunsJobsByOwnerByRepoByRunID,
24912491
http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
24922492
jobs := &github.Jobs{
24932493
TotalCount: github.Ptr(2),

pkg/github/helper_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ const (
5959
PatchReposIssuesByOwnerByRepoByIssueNumber = "PATCH /repos/{owner}/{repo}/issues/{issue_number}"
6060
GetReposIssuesSubIssuesByOwnerByRepoByIssueNumber = "GET /repos/{owner}/{repo}/issues/{issue_number}/sub_issues"
6161
PostReposIssuesSubIssuesByOwnerByRepoByIssueNumber = "POST /repos/{owner}/{repo}/issues/{issue_number}/sub_issues"
62-
DeleteReposIssuesSubIssueByOwnerByRepoByIssueNumber = "DELETE /repos/{owner}/{repo}/issues/{issue_number}/sub_issues"
62+
DeleteReposIssuesSubIssueByOwnerByRepoByIssueNumber = "DELETE /repos/{owner}/{repo}/issues/{issue_number}/sub_issue"
6363
PatchReposIssuesSubIssuesPriorityByOwnerByRepoByIssueNumber = "PATCH /repos/{owner}/{repo}/issues/{issue_number}/sub_issues/priority"
6464

6565
// Pull request endpoints

pkg/github/issues_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@ import (
1313

1414
"github.com/github/github-mcp-server/internal/githubv4mock"
1515
"github.com/github/github-mcp-server/internal/toolsnaps"
16+
mock "github.com/github/github-mcp-server/pkg/github/testmock"
1617
"github.com/github/github-mcp-server/pkg/lockdown"
1718
"github.com/github/github-mcp-server/pkg/translations"
1819
"github.com/google/go-github/v79/github"
1920
"github.com/google/jsonschema-go/jsonschema"
20-
"github.com/migueleliasweb/go-github-mock/src/mock"
2121
"github.com/shurcooL/githubv4"
2222
"github.com/stretchr/testify/assert"
2323
"github.com/stretchr/testify/require"

pkg/github/projects_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@ import (
88
"testing"
99

1010
"github.com/github/github-mcp-server/internal/toolsnaps"
11+
mock "github.com/github/github-mcp-server/pkg/github/testmock"
1112
"github.com/github/github-mcp-server/pkg/translations"
1213
gh "github.com/google/go-github/v79/github"
1314
"github.com/google/jsonschema-go/jsonschema"
14-
"github.com/migueleliasweb/go-github-mock/src/mock"
1515
"github.com/stretchr/testify/assert"
1616
"github.com/stretchr/testify/require"
1717
)

pkg/github/testmock/testmock.go

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
package mock
2+
3+
import (
4+
"bytes"
5+
"encoding/json"
6+
"io"
7+
"net/http"
8+
"strings"
9+
)
10+
11+
// EndpointPattern mirrors the structure used by migueleliasweb/go-github-mock.
12+
type EndpointPattern struct {
13+
Pattern string
14+
Method string
15+
}
16+
17+
type Option func(map[string]http.HandlerFunc)
18+
19+
// WithRequestMatch registers a handler that returns the provided response with HTTP 200.
20+
func WithRequestMatch(pattern EndpointPattern, response any) Option {
21+
return func(handlers map[string]http.HandlerFunc) {
22+
handlers[key(pattern)] = func(w http.ResponseWriter, _ *http.Request) {
23+
w.WriteHeader(http.StatusOK)
24+
switch body := response.(type) {
25+
case string:
26+
_, _ = w.Write([]byte(body))
27+
default:
28+
if body == nil {
29+
return
30+
}
31+
if data, err := json.Marshal(body); err == nil {
32+
_, _ = w.Write(data)
33+
}
34+
}
35+
}
36+
}
37+
}
38+
39+
// WithRequestMatchHandler registers a custom handler for the given pattern.
40+
func WithRequestMatchHandler(pattern EndpointPattern, handler http.HandlerFunc) Option {
41+
return func(handlers map[string]http.HandlerFunc) {
42+
handlers[key(pattern)] = handler
43+
}
44+
}
45+
46+
// NewMockedHTTPClient creates an HTTP client that routes requests through registered handlers.
47+
func NewMockedHTTPClient(options ...Option) *http.Client {
48+
handlers := make(map[string]http.HandlerFunc)
49+
for _, opt := range options {
50+
if opt != nil {
51+
opt(handlers)
52+
}
53+
}
54+
return &http.Client{Transport: &transport{handlers: handlers}}
55+
}
56+
57+
type transport struct {
58+
handlers map[string]http.HandlerFunc
59+
}
60+
61+
func (t *transport) RoundTrip(req *http.Request) (*http.Response, error) {
62+
if handler, ok := t.handlers[key(EndpointPattern{Method: req.Method, Pattern: req.URL.Path})]; ok {
63+
return executeHandler(handler, req), nil
64+
}
65+
66+
for patternKey, handler := range t.handlers {
67+
method, pattern := splitKey(patternKey)
68+
if method != req.Method {
69+
continue
70+
}
71+
if matchPath(pattern, req.URL.Path) {
72+
return executeHandler(handler, req), nil
73+
}
74+
}
75+
76+
return &http.Response{
77+
StatusCode: http.StatusNotFound,
78+
Body: io.NopCloser(bytes.NewReader([]byte("not found"))),
79+
Request: req,
80+
}, nil
81+
}
82+
83+
func executeHandler(handler http.HandlerFunc, req *http.Request) *http.Response {
84+
rec := &responseRecorder{
85+
header: make(http.Header),
86+
body: &bytes.Buffer{},
87+
}
88+
handler(rec, req)
89+
return &http.Response{
90+
StatusCode: rec.statusCode,
91+
Header: rec.header,
92+
Body: io.NopCloser(bytes.NewReader(rec.body.Bytes())),
93+
Request: req,
94+
}
95+
}
96+
97+
type responseRecorder struct {
98+
statusCode int
99+
header http.Header
100+
body *bytes.Buffer
101+
}
102+
103+
func (r *responseRecorder) Header() http.Header {
104+
return r.header
105+
}
106+
107+
func (r *responseRecorder) Write(data []byte) (int, error) {
108+
if r.statusCode == 0 {
109+
r.statusCode = http.StatusOK
110+
}
111+
return r.body.Write(data)
112+
}
113+
114+
func (r *responseRecorder) WriteHeader(statusCode int) {
115+
r.statusCode = statusCode
116+
}
117+
118+
func key(p EndpointPattern) string {
119+
return strings.ToUpper(p.Method) + " " + p.Pattern
120+
}
121+
122+
func splitKey(k string) (method, pattern string) {
123+
parts := strings.SplitN(k, " ", 2)
124+
if len(parts) == 2 {
125+
return parts[0], parts[1]
126+
}
127+
return "", ""
128+
}
129+
130+
func matchPath(pattern, path string) bool {
131+
if pattern == path {
132+
return true
133+
}
134+
135+
patternParts := strings.Split(strings.Trim(pattern, "/"), "/")
136+
pathParts := strings.Split(strings.Trim(path, "/"), "/")
137+
138+
if len(patternParts) != len(pathParts) {
139+
return false
140+
}
141+
142+
for i := range patternParts {
143+
if strings.HasPrefix(patternParts[i], "{") && strings.HasSuffix(patternParts[i], "}") {
144+
continue
145+
}
146+
if patternParts[i] != pathParts[i] {
147+
return false
148+
}
149+
}
150+
return true
151+
}
152+
153+
// MustMarshal marshals the provided value or panics on error.
154+
func MustMarshal(v any) []byte {
155+
data, err := json.Marshal(v)
156+
if err != nil {
157+
panic(err)
158+
}
159+
return data
160+
}
161+
162+
// REST endpoint patterns used in actions, issues and projects tests.
163+
var (
164+
GetReposActionsJobsLogsByOwnerByRepoByJobID = EndpointPattern{Pattern: "/repos/{owner}/{repo}/actions/jobs/{job_id}/logs", Method: http.MethodGet}
165+
GetReposActionsRunsByOwnerByRepo = EndpointPattern{Pattern: "/repos/{owner}/{repo}/actions/runs", Method: http.MethodGet}
166+
GetReposActionsRunsByOwnerByRepoByRunID = EndpointPattern{Pattern: "/repos/{owner}/{repo}/actions/runs/{run_id}", Method: http.MethodGet}
167+
GetReposActionsRunsJobsByOwnerByRepoByRunID = EndpointPattern{Pattern: "/repos/{owner}/{repo}/actions/runs/{run_id}/jobs", Method: http.MethodGet}
168+
GetReposActionsRunsLogsByOwnerByRepoByRunID = EndpointPattern{Pattern: "/repos/{owner}/{repo}/actions/runs/{run_id}/logs", Method: http.MethodGet}
169+
GetReposActionsWorkflowsByOwnerByRepo = EndpointPattern{Pattern: "/repos/{owner}/{repo}/actions/workflows", Method: http.MethodGet}
170+
GetReposActionsWorkflowsByOwnerByRepoByWorkflowID = EndpointPattern{Pattern: "/repos/{owner}/{repo}/actions/workflows/{workflow_id}", Method: http.MethodGet}
171+
GetReposActionsWorkflowsRunsByOwnerByRepoByWorkflowID = EndpointPattern{Pattern: "/repos/{owner}/{repo}/actions/workflows/{workflow_id}/runs", Method: http.MethodGet}
172+
PostReposActionsWorkflowsDispatchesByOwnerByRepoByWorkflowID = EndpointPattern{Pattern: "/repos/{owner}/{repo}/actions/workflows/{workflow_id}/dispatches", Method: http.MethodPost}
173+
174+
GetReposIssuesByOwnerByRepoByIssueNumber = EndpointPattern{Pattern: "/repos/{owner}/{repo}/issues/{issue_number}", Method: http.MethodGet}
175+
GetReposIssuesCommentsByOwnerByRepoByIssueNumber = EndpointPattern{Pattern: "/repos/{owner}/{repo}/issues/{issue_number}/comments", Method: http.MethodGet}
176+
GetReposIssuesSubIssuesByOwnerByRepoByIssueNumber = EndpointPattern{Pattern: "/repos/{owner}/{repo}/issues/{issue_number}/sub_issues", Method: http.MethodGet}
177+
PostReposIssuesByOwnerByRepo = EndpointPattern{Pattern: "/repos/{owner}/{repo}/issues", Method: http.MethodPost}
178+
PostReposIssuesCommentsByOwnerByRepoByIssueNumber = EndpointPattern{Pattern: "/repos/{owner}/{repo}/issues/{issue_number}/comments", Method: http.MethodPost}
179+
PostReposIssuesSubIssuesByOwnerByRepoByIssueNumber = EndpointPattern{Pattern: "/repos/{owner}/{repo}/issues/{issue_number}/sub_issues", Method: http.MethodPost}
180+
DeleteReposIssuesSubIssueByOwnerByRepoByIssueNumber = EndpointPattern{Pattern: "/repos/{owner}/{repo}/issues/{issue_number}/sub_issue", Method: http.MethodDelete}
181+
PatchReposIssuesByOwnerByRepoByIssueNumber = EndpointPattern{Pattern: "/repos/{owner}/{repo}/issues/{issue_number}", Method: http.MethodPatch}
182+
PatchReposIssuesSubIssuesPriorityByOwnerByRepoByIssueNumber = EndpointPattern{Pattern: "/repos/{owner}/{repo}/issues/{issue_number}/sub_issues/priority", Method: http.MethodPatch}
183+
184+
GetSearchIssues = EndpointPattern{Pattern: "/search/issues", Method: http.MethodGet}
185+
)

0 commit comments

Comments
 (0)