Files
Javier Uruen Val 3dddc2abcd add initial tests
2025-03-17 13:26:14 +01:00

372 lines
11 KiB
Go

package github
import (
"context"
"encoding/json"
"net/http"
"testing"
"github.com/google/go-github/v69/github"
"github.com/mark3labs/mcp-go/mcp"
"github.com/migueleliasweb/go-github-mock/src/mock"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_GetIssue(t *testing.T) {
// Verify tool definition once
mockClient := github.NewClient(nil)
tool, _ := getIssue(mockClient)
assert.Equal(t, "get_issue", tool.Name)
assert.NotEmpty(t, tool.Description)
assert.Contains(t, tool.InputSchema.Properties, "owner")
assert.Contains(t, tool.InputSchema.Properties, "repo")
assert.Contains(t, tool.InputSchema.Properties, "issue_number")
assert.ElementsMatch(t, tool.InputSchema.Required, []string{"owner", "repo", "issue_number"})
// Setup mock issue for success case
mockIssue := &github.Issue{
Number: github.Ptr(42),
Title: github.Ptr("Test Issue"),
Body: github.Ptr("This is a test issue"),
State: github.Ptr("open"),
HTMLURL: github.Ptr("https://github.com/owner/repo/issues/42"),
}
tests := []struct {
name string
mockedClient *http.Client
requestArgs map[string]interface{}
expectError bool
expectedIssue *github.Issue
expectedErrMsg string
}{
{
name: "successful issue retrieval",
mockedClient: mock.NewMockedHTTPClient(
mock.WithRequestMatch(
mock.GetReposIssuesByOwnerByRepoByIssueNumber,
mockIssue,
),
),
requestArgs: map[string]interface{}{
"owner": "owner",
"repo": "repo",
"issue_number": float64(42),
},
expectError: false,
expectedIssue: mockIssue,
},
{
name: "issue not found",
mockedClient: mock.NewMockedHTTPClient(
mock.WithRequestMatchHandler(
mock.GetReposIssuesByOwnerByRepoByIssueNumber,
mockResponse(t, http.StatusNotFound, `{"message": "Issue not found"}`),
),
),
requestArgs: map[string]interface{}{
"owner": "owner",
"repo": "repo",
"issue_number": float64(999),
},
expectError: true,
expectedErrMsg: "failed to get issue",
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
// Setup client with mock
client := github.NewClient(tc.mockedClient)
_, handler := getIssue(client)
// Create call request
request := createMCPRequest(tc.requestArgs)
// Call handler
result, err := handler(context.Background(), request)
// Verify results
if tc.expectError {
require.Error(t, err)
assert.Contains(t, err.Error(), tc.expectedErrMsg)
return
}
require.NoError(t, err)
textContent := getTextResult(t, result)
// Unmarshal and verify the result
var returnedIssue github.Issue
err = json.Unmarshal([]byte(textContent.Text), &returnedIssue)
require.NoError(t, err)
assert.Equal(t, *tc.expectedIssue.Number, *returnedIssue.Number)
assert.Equal(t, *tc.expectedIssue.Title, *returnedIssue.Title)
assert.Equal(t, *tc.expectedIssue.Body, *returnedIssue.Body)
})
}
}
func Test_AddIssueComment(t *testing.T) {
// Verify tool definition once
mockClient := github.NewClient(nil)
tool, _ := addIssueComment(mockClient)
assert.Equal(t, "add_issue_comment", tool.Name)
assert.NotEmpty(t, tool.Description)
assert.Contains(t, tool.InputSchema.Properties, "owner")
assert.Contains(t, tool.InputSchema.Properties, "repo")
assert.Contains(t, tool.InputSchema.Properties, "issue_number")
assert.Contains(t, tool.InputSchema.Properties, "body")
assert.ElementsMatch(t, tool.InputSchema.Required, []string{"owner", "repo", "issue_number", "body"})
// Setup mock comment for success case
mockComment := &github.IssueComment{
ID: github.Ptr(int64(123)),
Body: github.Ptr("This is a test comment"),
User: &github.User{
Login: github.Ptr("testuser"),
},
HTMLURL: github.Ptr("https://github.com/owner/repo/issues/42#issuecomment-123"),
}
tests := []struct {
name string
mockedClient *http.Client
requestArgs map[string]interface{}
expectError bool
expectedComment *github.IssueComment
expectedErrMsg string
}{
{
name: "successful comment creation",
mockedClient: mock.NewMockedHTTPClient(
mock.WithRequestMatchHandler(
mock.PostReposIssuesCommentsByOwnerByRepoByIssueNumber,
mockResponse(t, http.StatusCreated, mockComment),
),
),
requestArgs: map[string]interface{}{
"owner": "owner",
"repo": "repo",
"issue_number": float64(42),
"body": "This is a test comment",
},
expectError: false,
expectedComment: mockComment,
},
{
name: "comment creation fails",
mockedClient: mock.NewMockedHTTPClient(
mock.WithRequestMatchHandler(
mock.PostReposIssuesCommentsByOwnerByRepoByIssueNumber,
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusUnprocessableEntity)
_, _ = w.Write([]byte(`{"message": "Invalid request"}`))
}),
),
),
requestArgs: map[string]interface{}{
"owner": "owner",
"repo": "repo",
"issue_number": float64(42),
"body": "",
},
expectError: true,
expectedErrMsg: "failed to create comment",
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
// Setup client with mock
client := github.NewClient(tc.mockedClient)
_, handler := addIssueComment(client)
// Create call request
request := mcp.CallToolRequest{
Params: struct {
Name string `json:"name"`
Arguments map[string]interface{} `json:"arguments,omitempty"`
Meta *struct {
ProgressToken mcp.ProgressToken `json:"progressToken,omitempty"`
} `json:"_meta,omitempty"`
}{
Arguments: tc.requestArgs,
},
}
// Call handler
result, err := handler(context.Background(), request)
// Verify results
if tc.expectError {
require.Error(t, err)
assert.Contains(t, err.Error(), tc.expectedErrMsg)
return
}
require.NoError(t, err)
// Parse the result and get the text content if no error
textContent := getTextResult(t, result)
// Unmarshal and verify the result
var returnedComment github.IssueComment
err = json.Unmarshal([]byte(textContent.Text), &returnedComment)
require.NoError(t, err)
assert.Equal(t, *tc.expectedComment.ID, *returnedComment.ID)
assert.Equal(t, *tc.expectedComment.Body, *returnedComment.Body)
assert.Equal(t, *tc.expectedComment.User.Login, *returnedComment.User.Login)
})
}
}
func Test_SearchIssues(t *testing.T) {
// Verify tool definition once
mockClient := github.NewClient(nil)
tool, _ := searchIssues(mockClient)
assert.Equal(t, "search_issues", tool.Name)
assert.NotEmpty(t, tool.Description)
assert.Contains(t, tool.InputSchema.Properties, "q")
assert.Contains(t, tool.InputSchema.Properties, "sort")
assert.Contains(t, tool.InputSchema.Properties, "order")
assert.Contains(t, tool.InputSchema.Properties, "per_page")
assert.Contains(t, tool.InputSchema.Properties, "page")
assert.ElementsMatch(t, tool.InputSchema.Required, []string{"q"})
// Setup mock search results
mockSearchResult := &github.IssuesSearchResult{
Total: github.Ptr(2),
IncompleteResults: github.Ptr(false),
Issues: []*github.Issue{
{
Number: github.Ptr(42),
Title: github.Ptr("Bug: Something is broken"),
Body: github.Ptr("This is a bug report"),
State: github.Ptr("open"),
HTMLURL: github.Ptr("https://github.com/owner/repo/issues/42"),
Comments: github.Ptr(5),
User: &github.User{
Login: github.Ptr("user1"),
},
},
{
Number: github.Ptr(43),
Title: github.Ptr("Feature: Add new functionality"),
Body: github.Ptr("This is a feature request"),
State: github.Ptr("open"),
HTMLURL: github.Ptr("https://github.com/owner/repo/issues/43"),
Comments: github.Ptr(3),
User: &github.User{
Login: github.Ptr("user2"),
},
},
},
}
tests := []struct {
name string
mockedClient *http.Client
requestArgs map[string]interface{}
expectError bool
expectedResult *github.IssuesSearchResult
expectedErrMsg string
}{
{
name: "successful issues search with all parameters",
mockedClient: mock.NewMockedHTTPClient(
mock.WithRequestMatch(
mock.GetSearchIssues,
mockSearchResult,
),
),
requestArgs: map[string]interface{}{
"q": "repo:owner/repo is:issue is:open",
"sort": "created",
"order": "desc",
"page": float64(1),
"per_page": float64(30),
},
expectError: false,
expectedResult: mockSearchResult,
},
{
name: "issues search with minimal parameters",
mockedClient: mock.NewMockedHTTPClient(
mock.WithRequestMatch(
mock.GetSearchIssues,
mockSearchResult,
),
),
requestArgs: map[string]interface{}{
"q": "repo:owner/repo is:issue is:open",
},
expectError: false,
expectedResult: mockSearchResult,
},
{
name: "search issues fails",
mockedClient: mock.NewMockedHTTPClient(
mock.WithRequestMatchHandler(
mock.GetSearchIssues,
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusBadRequest)
_, _ = w.Write([]byte(`{"message": "Validation Failed"}`))
}),
),
),
requestArgs: map[string]interface{}{
"q": "invalid:query",
},
expectError: true,
expectedErrMsg: "failed to search issues",
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
// Setup client with mock
client := github.NewClient(tc.mockedClient)
_, handler := searchIssues(client)
// Create call request
request := createMCPRequest(tc.requestArgs)
// Call handler
result, err := handler(context.Background(), request)
// Verify results
if tc.expectError {
require.Error(t, err)
assert.Contains(t, err.Error(), tc.expectedErrMsg)
return
}
require.NoError(t, err)
// Parse the result and get the text content if no error
textContent := getTextResult(t, result)
// Unmarshal and verify the result
var returnedResult github.IssuesSearchResult
err = json.Unmarshal([]byte(textContent.Text), &returnedResult)
require.NoError(t, err)
assert.Equal(t, *tc.expectedResult.Total, *returnedResult.Total)
assert.Equal(t, *tc.expectedResult.IncompleteResults, *returnedResult.IncompleteResults)
assert.Len(t, returnedResult.Issues, len(tc.expectedResult.Issues))
for i, issue := range returnedResult.Issues {
assert.Equal(t, *tc.expectedResult.Issues[i].Number, *issue.Number)
assert.Equal(t, *tc.expectedResult.Issues[i].Title, *issue.Title)
assert.Equal(t, *tc.expectedResult.Issues[i].State, *issue.State)
assert.Equal(t, *tc.expectedResult.Issues[i].HTMLURL, *issue.HTMLURL)
assert.Equal(t, *tc.expectedResult.Issues[i].User.Login, *issue.User.Login)
}
})
}
}