Skip to content

Commit b59ca26

Browse files
committed
test: add table-driven tests for releases management tools
Add comprehensive Go tests covering: - CreateRelease: success, draft/prerelease, missing tag, invalid tag - UpdateRelease: success, missing release_id - DeleteRelease: successful deletion - GetReleaseByID: successful retrieval - ListReleaseAssets: successful listing - DeleteReleaseAsset: successful deletion - isValidTagName: valid and invalid tag names Signed-off-by: Srikanth Patchava <spatchava@meta.com>
1 parent 8dd04dd commit b59ca26

1 file changed

Lines changed: 366 additions & 0 deletions

File tree

pkg/github/releases_test.go

Lines changed: 366 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,366 @@
1+
package github
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"net/http"
7+
"testing"
8+
9+
"github.com/github/github-mcp-server/pkg/translations"
10+
"github.com/google/go-github/v82/github"
11+
"github.com/google/jsonschema-go/jsonschema"
12+
"github.com/stretchr/testify/assert"
13+
"github.com/stretchr/testify/require"
14+
)
15+
16+
func Test_CreateRelease(t *testing.T) {
17+
toolDef := CreateRelease(translations.NullTranslationHelper)
18+
assert.Equal(t, "create_release", toolDef.Tool.Name)
19+
assert.NotEmpty(t, toolDef.Tool.Description)
20+
inputSchema := toolDef.Tool.InputSchema.(*jsonschema.Schema)
21+
assert.Contains(t, inputSchema.Properties, "owner")
22+
assert.Contains(t, inputSchema.Properties, "repo")
23+
assert.Contains(t, inputSchema.Properties, "tag_name")
24+
}
25+
26+
func Test_CreateRelease_Execute(t *testing.T) {
27+
serverTool := CreateRelease(translations.NullTranslationHelper)
28+
29+
tests := []struct {
30+
name string
31+
mockedClient *http.Client
32+
requestArgs map[string]any
33+
expectError bool
34+
expectedErrMsg string
35+
}{
36+
{
37+
name: "successful release creation",
38+
mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
39+
"POST /repos/owner/repo/releases": http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
40+
release := &github.RepositoryRelease{
41+
ID: github.Ptr(int64(1)),
42+
TagName: github.Ptr("v1.0.0"),
43+
Name: github.Ptr("Release 1.0.0"),
44+
Body: github.Ptr("First release"),
45+
Draft: github.Ptr(false),
46+
Prerelease: github.Ptr(false),
47+
HTMLURL: github.Ptr("https://github.com/owner/repo/releases/tag/v1.0.0"),
48+
}
49+
w.WriteHeader(http.StatusCreated)
50+
_ = json.NewEncoder(w).Encode(release)
51+
}),
52+
}),
53+
requestArgs: map[string]any{
54+
"owner": "owner",
55+
"repo": "repo",
56+
"tag_name": "v1.0.0",
57+
"name": "Release 1.0.0",
58+
"body": "First release",
59+
},
60+
expectError: false,
61+
},
62+
{
63+
name: "create draft prerelease",
64+
mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
65+
"POST /repos/owner/repo/releases": http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
66+
release := &github.RepositoryRelease{
67+
ID: github.Ptr(int64(2)),
68+
TagName: github.Ptr("v2.0.0-beta"),
69+
Name: github.Ptr("Beta Release"),
70+
Draft: github.Ptr(true),
71+
Prerelease: github.Ptr(true),
72+
HTMLURL: github.Ptr("https://github.com/owner/repo/releases/tag/v2.0.0-beta"),
73+
}
74+
w.WriteHeader(http.StatusCreated)
75+
_ = json.NewEncoder(w).Encode(release)
76+
}),
77+
}),
78+
requestArgs: map[string]any{
79+
"owner": "owner",
80+
"repo": "repo",
81+
"tag_name": "v2.0.0-beta",
82+
"name": "Beta Release",
83+
"draft": true,
84+
"prerelease": true,
85+
},
86+
expectError: false,
87+
},
88+
{
89+
name: "missing required tag_name",
90+
mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}),
91+
requestArgs: map[string]any{
92+
"owner": "owner",
93+
"repo": "repo",
94+
},
95+
expectError: true,
96+
expectedErrMsg: "missing required parameter: tag_name",
97+
},
98+
{
99+
name: "invalid tag name with spaces",
100+
mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}),
101+
requestArgs: map[string]any{
102+
"owner": "owner",
103+
"repo": "repo",
104+
"tag_name": "invalid tag",
105+
},
106+
expectError: true,
107+
expectedErrMsg: "invalid tag name",
108+
},
109+
}
110+
111+
for _, tc := range tests {
112+
t.Run(tc.name, func(t *testing.T) {
113+
client := github.NewClient(tc.mockedClient)
114+
deps := BaseDeps{
115+
Client: client,
116+
}
117+
handler := serverTool.Handler(deps)
118+
request := createMCPRequest(tc.requestArgs)
119+
120+
result, err := handler(ContextWithDeps(context.Background(), deps), &request)
121+
if tc.expectError {
122+
require.NoError(t, err)
123+
textContent := getTextResult(t, result)
124+
assert.Contains(t, textContent.Text, tc.expectedErrMsg)
125+
} else {
126+
require.NoError(t, err)
127+
require.NotNil(t, result)
128+
textContent := getTextResult(t, result)
129+
assert.NotEmpty(t, textContent.Text)
130+
}
131+
})
132+
}
133+
}
134+
135+
func Test_UpdateRelease(t *testing.T) {
136+
toolDef := UpdateRelease(translations.NullTranslationHelper)
137+
assert.Equal(t, "update_release", toolDef.Tool.Name)
138+
assert.NotEmpty(t, toolDef.Tool.Description)
139+
inputSchema := toolDef.Tool.InputSchema.(*jsonschema.Schema)
140+
assert.Contains(t, inputSchema.Properties, "owner")
141+
assert.Contains(t, inputSchema.Properties, "repo")
142+
assert.Contains(t, inputSchema.Properties, "release_id")
143+
}
144+
145+
func Test_UpdateRelease_Execute(t *testing.T) {
146+
serverTool := UpdateRelease(translations.NullTranslationHelper)
147+
148+
tests := []struct {
149+
name string
150+
mockedClient *http.Client
151+
requestArgs map[string]any
152+
expectError bool
153+
}{
154+
{
155+
name: "successful update",
156+
mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
157+
"PATCH /repos/owner/repo/releases/1": http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
158+
release := &github.RepositoryRelease{
159+
ID: github.Ptr(int64(1)),
160+
TagName: github.Ptr("v1.0.1"),
161+
Name: github.Ptr("Updated Release"),
162+
HTMLURL: github.Ptr("https://github.com/owner/repo/releases/tag/v1.0.1"),
163+
}
164+
w.WriteHeader(http.StatusOK)
165+
_ = json.NewEncoder(w).Encode(release)
166+
}),
167+
}),
168+
requestArgs: map[string]any{
169+
"owner": "owner",
170+
"repo": "repo",
171+
"release_id": float64(1),
172+
"name": "Updated Release",
173+
},
174+
expectError: false,
175+
},
176+
{
177+
name: "missing release_id",
178+
mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}),
179+
requestArgs: map[string]any{
180+
"owner": "owner",
181+
"repo": "repo",
182+
},
183+
expectError: true,
184+
},
185+
}
186+
187+
for _, tc := range tests {
188+
t.Run(tc.name, func(t *testing.T) {
189+
client := github.NewClient(tc.mockedClient)
190+
deps := BaseDeps{
191+
Client: client,
192+
}
193+
handler := serverTool.Handler(deps)
194+
request := createMCPRequest(tc.requestArgs)
195+
196+
result, err := handler(ContextWithDeps(context.Background(), deps), &request)
197+
if tc.expectError {
198+
require.NoError(t, err)
199+
textContent := getTextResult(t, result)
200+
assert.Contains(t, textContent.Text, "missing required parameter")
201+
} else {
202+
require.NoError(t, err)
203+
require.NotNil(t, result)
204+
}
205+
})
206+
}
207+
}
208+
209+
func Test_DeleteRelease(t *testing.T) {
210+
toolDef := DeleteRelease(translations.NullTranslationHelper)
211+
assert.Equal(t, "delete_release", toolDef.Tool.Name)
212+
inputSchema := toolDef.Tool.InputSchema.(*jsonschema.Schema)
213+
assert.Contains(t, inputSchema.Properties, "release_id")
214+
}
215+
216+
func Test_DeleteRelease_Execute(t *testing.T) {
217+
serverTool := DeleteRelease(translations.NullTranslationHelper)
218+
219+
t.Run("successful deletion", func(t *testing.T) {
220+
mockedClient := MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
221+
"DELETE /repos/owner/repo/releases/42": http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
222+
w.WriteHeader(http.StatusNoContent)
223+
}),
224+
})
225+
226+
client := github.NewClient(mockedClient)
227+
deps := BaseDeps{
228+
Client: client,
229+
}
230+
handler := serverTool.Handler(deps)
231+
request := createMCPRequest(map[string]any{
232+
"owner": "owner",
233+
"repo": "repo",
234+
"release_id": float64(42),
235+
})
236+
237+
result, err := handler(ContextWithDeps(context.Background(), deps), &request)
238+
require.NoError(t, err)
239+
textContent := getTextResult(t, result)
240+
assert.Contains(t, textContent.Text, "deleted successfully")
241+
})
242+
}
243+
244+
func Test_GetReleaseByID(t *testing.T) {
245+
serverTool := GetReleaseByID(translations.NullTranslationHelper)
246+
assert.Equal(t, "get_release", serverTool.Tool.Name)
247+
248+
t.Run("successful get", func(t *testing.T) {
249+
mockedClient := MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
250+
"GET /repos/owner/repo/releases/1": http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
251+
release := &github.RepositoryRelease{
252+
ID: github.Ptr(int64(1)),
253+
TagName: github.Ptr("v1.0.0"),
254+
Name: github.Ptr("Release 1.0.0"),
255+
HTMLURL: github.Ptr("https://github.com/owner/repo/releases/tag/v1.0.0"),
256+
}
257+
w.WriteHeader(http.StatusOK)
258+
_ = json.NewEncoder(w).Encode(release)
259+
}),
260+
})
261+
262+
client := github.NewClient(mockedClient)
263+
deps := BaseDeps{
264+
Client: client,
265+
}
266+
handler := serverTool.Handler(deps)
267+
request := createMCPRequest(map[string]any{
268+
"owner": "owner",
269+
"repo": "repo",
270+
"release_id": float64(1),
271+
})
272+
273+
result, err := handler(ContextWithDeps(context.Background(), deps), &request)
274+
require.NoError(t, err)
275+
textContent := getTextResult(t, result)
276+
assert.Contains(t, textContent.Text, "v1.0.0")
277+
})
278+
}
279+
280+
func Test_ListReleaseAssets(t *testing.T) {
281+
serverTool := ListReleaseAssets(translations.NullTranslationHelper)
282+
assert.Equal(t, "list_release_assets", serverTool.Tool.Name)
283+
284+
t.Run("successful list", func(t *testing.T) {
285+
mockedClient := MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
286+
"GET /repos/owner/repo/releases/1/assets": http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
287+
assets := []*github.ReleaseAsset{
288+
{
289+
ID: github.Ptr(int64(100)),
290+
Name: github.Ptr("app.zip"),
291+
ContentType: github.Ptr("application/zip"),
292+
Size: github.Ptr(1024),
293+
DownloadCount: github.Ptr(50),
294+
BrowserDownloadURL: github.Ptr("https://github.com/owner/repo/releases/download/v1.0.0/app.zip"),
295+
},
296+
}
297+
w.WriteHeader(http.StatusOK)
298+
_ = json.NewEncoder(w).Encode(assets)
299+
}),
300+
})
301+
302+
client := github.NewClient(mockedClient)
303+
deps := BaseDeps{
304+
Client: client,
305+
}
306+
handler := serverTool.Handler(deps)
307+
request := createMCPRequest(map[string]any{
308+
"owner": "owner",
309+
"repo": "repo",
310+
"release_id": float64(1),
311+
})
312+
313+
result, err := handler(ContextWithDeps(context.Background(), deps), &request)
314+
require.NoError(t, err)
315+
textContent := getTextResult(t, result)
316+
assert.Contains(t, textContent.Text, "app.zip")
317+
})
318+
}
319+
320+
func Test_DeleteReleaseAsset(t *testing.T) {
321+
serverTool := DeleteReleaseAsset(translations.NullTranslationHelper)
322+
assert.Equal(t, "delete_release_asset", serverTool.Tool.Name)
323+
324+
t.Run("successful deletion", func(t *testing.T) {
325+
mockedClient := MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
326+
"DELETE /repos/owner/repo/releases/assets/100": http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
327+
w.WriteHeader(http.StatusNoContent)
328+
}),
329+
})
330+
331+
client := github.NewClient(mockedClient)
332+
deps := BaseDeps{
333+
Client: client,
334+
}
335+
handler := serverTool.Handler(deps)
336+
request := createMCPRequest(map[string]any{
337+
"owner": "owner",
338+
"repo": "repo",
339+
"asset_id": float64(100),
340+
})
341+
342+
result, err := handler(ContextWithDeps(context.Background(), deps), &request)
343+
require.NoError(t, err)
344+
textContent := getTextResult(t, result)
345+
assert.Contains(t, textContent.Text, "deleted successfully")
346+
})
347+
}
348+
349+
func Test_isValidTagName(t *testing.T) {
350+
tests := []struct {
351+
tag string
352+
valid bool
353+
}{
354+
{"v1.0.0", true},
355+
{"release-2024", true},
356+
{"v2.0.0-beta.1", true},
357+
{"", false},
358+
{"tag with spaces", false},
359+
{"tag\ttab", false},
360+
}
361+
for _, tc := range tests {
362+
t.Run(tc.tag, func(t *testing.T) {
363+
assert.Equal(t, tc.valid, isValidTagName(tc.tag))
364+
})
365+
}
366+
}

0 commit comments

Comments
 (0)