Skip to content

Commit ff5e309

Browse files
committed
add initial tests
1 parent 09366fa commit ff5e309

14 files changed

+3206
-20
lines changed

go.mod

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,27 @@ require (
66
github.com/aws/smithy-go v1.22.3
77
github.com/google/go-github/v69 v69.2.0
88
github.com/mark3labs/mcp-go v0.11.2
9+
github.com/migueleliasweb/go-github-mock v1.1.0
910
github.com/sirupsen/logrus v1.9.3
1011
github.com/spf13/cobra v1.9.1
1112
github.com/spf13/viper v1.19.0
13+
github.com/stretchr/testify v1.9.0
1214
golang.org/x/exp v0.0.0-20230905200255-921286631fa9
1315
)
1416

1517
require (
18+
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
1619
github.com/fsnotify/fsnotify v1.7.0 // indirect
20+
github.com/google/go-github/v64 v64.0.0 // indirect
1721
github.com/google/go-querystring v1.1.0 // indirect
1822
github.com/google/uuid v1.6.0 // indirect
23+
github.com/gorilla/mux v1.8.0 // indirect
1924
github.com/hashicorp/hcl v1.0.0 // indirect
2025
github.com/inconshreveable/mousetrap v1.1.0 // indirect
2126
github.com/magiconair/properties v1.8.7 // indirect
2227
github.com/mitchellh/mapstructure v1.5.0 // indirect
2328
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
29+
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
2430
github.com/sagikazarmark/locafero v0.4.0 // indirect
2531
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
2632
github.com/sourcegraph/conc v0.3.0 // indirect
@@ -31,7 +37,8 @@ require (
3137
go.uber.org/atomic v1.9.0 // indirect
3238
go.uber.org/multierr v1.9.0 // indirect
3339
golang.org/x/sys v0.18.0 // indirect
34-
golang.org/x/text v0.14.0 // indirect
40+
golang.org/x/text v0.19.0 // indirect
41+
golang.org/x/time v0.5.0 // indirect
3542
gopkg.in/ini.v1 v1.67.0 // indirect
3643
gopkg.in/yaml.v3 v3.0.1 // indirect
3744
)

go.sum

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,16 @@ github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyT
1212
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
1313
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
1414
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
15+
github.com/google/go-github/v64 v64.0.0 h1:4G61sozmY3eiPAjjoOHponXDBONm+utovTKbyUb2Qdg=
16+
github.com/google/go-github/v64 v64.0.0/go.mod h1:xB3vqMQNdHzilXBiO2I+M7iEFtHf+DP/omBOv6tQzVo=
1517
github.com/google/go-github/v69 v69.2.0 h1:wR+Wi/fN2zdUx9YxSmYE0ktiX9IAR/BeePzeaUUbEHE=
1618
github.com/google/go-github/v69 v69.2.0/go.mod h1:xne4jymxLR6Uj9b7J7PyTpkMYstEMMwGZa0Aehh1azM=
1719
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
1820
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
1921
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
2022
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
23+
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
24+
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
2125
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
2226
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
2327
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
@@ -30,6 +34,8 @@ github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0V
3034
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
3135
github.com/mark3labs/mcp-go v0.11.2 h1:mCxWFUTrcXOtJIn9t7F8bxAL8rpE/ZZTTnx3PU/VNdA=
3236
github.com/mark3labs/mcp-go v0.11.2/go.mod h1:cjMlBU0cv/cj9kjlgmRhoJ5JREdS7YX83xeIG9Ko/jE=
37+
github.com/migueleliasweb/go-github-mock v1.1.0 h1:GKaOBPsrPGkAKgtfuWY8MclS1xR6MInkx1SexJucMwE=
38+
github.com/migueleliasweb/go-github-mock v1.1.0/go.mod h1:pYe/XlGs4BGMfRY4vmeixVsODHnVDDhJ9zoi0qzSMHc=
3339
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
3440
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
3541
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
@@ -80,8 +86,10 @@ golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqR
8086
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
8187
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
8288
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
83-
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
84-
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
89+
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
90+
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
91+
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
92+
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
8593
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
8694
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
8795
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=

pkg/github/code_scanning.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"encoding/json"
66
"fmt"
77
"io"
8+
"net/http"
89

910
"github.com/google/go-github/v69/github"
1011
"github.com/mark3labs/mcp-go/mcp"
@@ -38,7 +39,7 @@ func getCodeScanningAlert(client *github.Client) (tool mcp.Tool, handler server.
3839
}
3940
defer func() { _ = resp.Body.Close() }()
4041

41-
if resp.StatusCode != 200 {
42+
if resp.StatusCode != http.StatusOK {
4243
body, err := io.ReadAll(resp.Body)
4344
if err != nil {
4445
return nil, fmt.Errorf("failed to read response body: %w", err)
@@ -90,7 +91,7 @@ func listCodeScanningAlerts(client *github.Client) (tool mcp.Tool, handler serve
9091
}
9192
defer func() { _ = resp.Body.Close() }()
9293

93-
if resp.StatusCode != 200 {
94+
if resp.StatusCode != http.StatusOK {
9495
body, err := io.ReadAll(resp.Body)
9596
if err != nil {
9697
return nil, fmt.Errorf("failed to read response body: %w", err)

pkg/github/code_scanning_test.go

Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
package github
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"net/http"
7+
"testing"
8+
9+
"github.com/google/go-github/v69/github"
10+
"github.com/migueleliasweb/go-github-mock/src/mock"
11+
"github.com/stretchr/testify/assert"
12+
"github.com/stretchr/testify/require"
13+
)
14+
15+
func Test_GetCodeScanningAlert(t *testing.T) {
16+
// Verify tool definition once
17+
mockClient := github.NewClient(nil)
18+
tool, _ := getCodeScanningAlert(mockClient)
19+
20+
assert.Equal(t, "get_code_scanning_alert", tool.Name)
21+
assert.NotEmpty(t, tool.Description)
22+
assert.Contains(t, tool.InputSchema.Properties, "owner")
23+
assert.Contains(t, tool.InputSchema.Properties, "repo")
24+
assert.Contains(t, tool.InputSchema.Properties, "alert_number")
25+
assert.ElementsMatch(t, tool.InputSchema.Required, []string{"owner", "repo", "alert_number"})
26+
27+
// Setup mock alert for success case
28+
mockAlert := &github.Alert{
29+
Number: github.Ptr(42),
30+
State: github.Ptr("open"),
31+
Rule: &github.Rule{ID: github.Ptr("test-rule"), Description: github.Ptr("Test Rule Description")},
32+
HTMLURL: github.Ptr("https://github.com/owner/repo/security/code-scanning/42"),
33+
}
34+
35+
tests := []struct {
36+
name string
37+
mockedClient *http.Client
38+
requestArgs map[string]interface{}
39+
expectError bool
40+
expectedAlert *github.Alert
41+
expectedErrMsg string
42+
}{
43+
{
44+
name: "successful alert fetch",
45+
mockedClient: mock.NewMockedHTTPClient(
46+
mock.WithRequestMatch(
47+
mock.GetReposCodeScanningAlertsByOwnerByRepoByAlertNumber,
48+
mockAlert,
49+
),
50+
),
51+
requestArgs: map[string]interface{}{
52+
"owner": "owner",
53+
"repo": "repo",
54+
"alert_number": float64(42),
55+
},
56+
expectError: false,
57+
expectedAlert: mockAlert,
58+
},
59+
{
60+
name: "alert fetch fails",
61+
mockedClient: mock.NewMockedHTTPClient(
62+
mock.WithRequestMatchHandler(
63+
mock.GetReposCodeScanningAlertsByOwnerByRepoByAlertNumber,
64+
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
65+
w.WriteHeader(http.StatusNotFound)
66+
_, _ = w.Write([]byte(`{"message": "Not Found"}`))
67+
}),
68+
),
69+
),
70+
requestArgs: map[string]interface{}{
71+
"owner": "owner",
72+
"repo": "repo",
73+
"alert_number": float64(9999),
74+
},
75+
expectError: true,
76+
expectedErrMsg: "failed to get alert",
77+
},
78+
}
79+
80+
for _, tc := range tests {
81+
t.Run(tc.name, func(t *testing.T) {
82+
// Setup client with mock
83+
client := github.NewClient(tc.mockedClient)
84+
_, handler := getCodeScanningAlert(client)
85+
86+
// Create call request
87+
request := createMCPRequest(tc.requestArgs)
88+
89+
// Call handler
90+
result, err := handler(context.Background(), request)
91+
92+
// Verify results
93+
if tc.expectError {
94+
require.Error(t, err)
95+
assert.Contains(t, err.Error(), tc.expectedErrMsg)
96+
return
97+
}
98+
99+
require.NoError(t, err)
100+
101+
// Parse the result and get the text content if no error
102+
textContent := getTextResult(t, result)
103+
104+
// Unmarshal and verify the result
105+
var returnedAlert github.Alert
106+
err = json.Unmarshal([]byte(textContent.Text), &returnedAlert)
107+
assert.NoError(t, err)
108+
assert.Equal(t, *tc.expectedAlert.Number, *returnedAlert.Number)
109+
assert.Equal(t, *tc.expectedAlert.State, *returnedAlert.State)
110+
assert.Equal(t, *tc.expectedAlert.Rule.ID, *returnedAlert.Rule.ID)
111+
assert.Equal(t, *tc.expectedAlert.HTMLURL, *returnedAlert.HTMLURL)
112+
113+
})
114+
}
115+
}
116+
117+
func Test_ListCodeScanningAlerts(t *testing.T) {
118+
// Verify tool definition once
119+
mockClient := github.NewClient(nil)
120+
tool, _ := listCodeScanningAlerts(mockClient)
121+
122+
assert.Equal(t, "list_code_scanning_alerts", tool.Name)
123+
assert.NotEmpty(t, tool.Description)
124+
assert.Contains(t, tool.InputSchema.Properties, "owner")
125+
assert.Contains(t, tool.InputSchema.Properties, "repo")
126+
assert.Contains(t, tool.InputSchema.Properties, "ref")
127+
assert.Contains(t, tool.InputSchema.Properties, "state")
128+
assert.Contains(t, tool.InputSchema.Properties, "severity")
129+
assert.ElementsMatch(t, tool.InputSchema.Required, []string{"owner", "repo"})
130+
131+
// Setup mock alerts for success case
132+
mockAlerts := []*github.Alert{
133+
{
134+
Number: github.Ptr(42),
135+
State: github.Ptr("open"),
136+
Rule: &github.Rule{ID: github.Ptr("test-rule-1"), Description: github.Ptr("Test Rule 1")},
137+
HTMLURL: github.Ptr("https://github.com/owner/repo/security/code-scanning/42"),
138+
},
139+
{
140+
Number: github.Ptr(43),
141+
State: github.Ptr("fixed"),
142+
Rule: &github.Rule{ID: github.Ptr("test-rule-2"), Description: github.Ptr("Test Rule 2")},
143+
HTMLURL: github.Ptr("https://github.com/owner/repo/security/code-scanning/43"),
144+
},
145+
}
146+
147+
tests := []struct {
148+
name string
149+
mockedClient *http.Client
150+
requestArgs map[string]interface{}
151+
expectError bool
152+
expectedAlerts []*github.Alert
153+
expectedErrMsg string
154+
}{
155+
{
156+
name: "successful alerts listing",
157+
mockedClient: mock.NewMockedHTTPClient(
158+
mock.WithRequestMatch(
159+
mock.GetReposCodeScanningAlertsByOwnerByRepo,
160+
mockAlerts,
161+
),
162+
),
163+
requestArgs: map[string]interface{}{
164+
"owner": "owner",
165+
"repo": "repo",
166+
"ref": "main",
167+
"state": "open",
168+
"severity": "high",
169+
},
170+
expectError: false,
171+
expectedAlerts: mockAlerts,
172+
},
173+
{
174+
name: "alerts listing fails",
175+
mockedClient: mock.NewMockedHTTPClient(
176+
mock.WithRequestMatchHandler(
177+
mock.GetReposCodeScanningAlertsByOwnerByRepo,
178+
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
179+
w.WriteHeader(http.StatusUnauthorized)
180+
_, _ = w.Write([]byte(`{"message": "Unauthorized access"}`))
181+
}),
182+
),
183+
),
184+
requestArgs: map[string]interface{}{
185+
"owner": "owner",
186+
"repo": "repo",
187+
},
188+
expectError: true,
189+
expectedErrMsg: "failed to list alerts",
190+
},
191+
}
192+
193+
for _, tc := range tests {
194+
t.Run(tc.name, func(t *testing.T) {
195+
// Setup client with mock
196+
client := github.NewClient(tc.mockedClient)
197+
_, handler := listCodeScanningAlerts(client)
198+
199+
// Create call request
200+
request := createMCPRequest(tc.requestArgs)
201+
202+
// Call handler
203+
result, err := handler(context.Background(), request)
204+
205+
// Verify results
206+
if tc.expectError {
207+
require.Error(t, err)
208+
assert.Contains(t, err.Error(), tc.expectedErrMsg)
209+
return
210+
}
211+
212+
require.NoError(t, err)
213+
214+
// Parse the result and get the text content if no error
215+
textContent := getTextResult(t, result)
216+
217+
// Unmarshal and verify the result
218+
var returnedAlerts []*github.Alert
219+
err = json.Unmarshal([]byte(textContent.Text), &returnedAlerts)
220+
assert.NoError(t, err)
221+
assert.Len(t, returnedAlerts, len(tc.expectedAlerts))
222+
for i, alert := range returnedAlerts {
223+
assert.Equal(t, *tc.expectedAlerts[i].Number, *alert.Number)
224+
assert.Equal(t, *tc.expectedAlerts[i].State, *alert.State)
225+
assert.Equal(t, *tc.expectedAlerts[i].Rule.ID, *alert.Rule.ID)
226+
assert.Equal(t, *tc.expectedAlerts[i].HTMLURL, *alert.HTMLURL)
227+
}
228+
})
229+
}
230+
}

pkg/github/helper_test.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package github
2+
3+
import (
4+
"encoding/json"
5+
"github.com/stretchr/testify/assert"
6+
"net/http"
7+
"testing"
8+
9+
"github.com/mark3labs/mcp-go/mcp"
10+
"github.com/stretchr/testify/require"
11+
)
12+
13+
// mockResponse is a helper function to create a mock HTTP response handler
14+
// that returns a specified status code and marshalled body.
15+
func mockResponse(t *testing.T, code int, body interface{}) http.HandlerFunc {
16+
t.Helper()
17+
return func(w http.ResponseWriter, r *http.Request) {
18+
w.WriteHeader(code)
19+
b, err := json.Marshal(body)
20+
require.NoError(t, err)
21+
_, _ = w.Write(b)
22+
}
23+
}
24+
25+
// createMCPRequest is a helper function to create a MCP request with the given arguments.
26+
func createMCPRequest(args map[string]interface{}) mcp.CallToolRequest {
27+
return mcp.CallToolRequest{
28+
Params: struct {
29+
Name string `json:"name"`
30+
Arguments map[string]interface{} `json:"arguments,omitempty"`
31+
Meta *struct {
32+
ProgressToken mcp.ProgressToken `json:"progressToken,omitempty"`
33+
} `json:"_meta,omitempty"`
34+
}{
35+
Arguments: args,
36+
},
37+
}
38+
}
39+
40+
// getTextResult is a helper function that returns a text result from a tool call.
41+
func getTextResult(t *testing.T, result *mcp.CallToolResult) mcp.TextContent {
42+
t.Helper()
43+
assert.NotNil(t, result)
44+
require.Len(t, result.Content, 1)
45+
require.IsType(t, mcp.TextContent{}, result.Content[0])
46+
textContent := result.Content[0].(mcp.TextContent)
47+
assert.Equal(t, "text", textContent.Type)
48+
return textContent
49+
}

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