feat: add title parameter to merge_pull_request tool (#134)

## Summary
- Add missing `title` parameter to `merge_pull_request` tool for custom merge commit titles
- Use `params.GetIndex()` for consistent index parameter handling (supports both string and number inputs)
- Add test for `MergePullRequestFn`

Closes #120

## Test plan
- [x] `go test ./operation/pull/ -run TestMergePullRequestFn` passes
- [x] All existing tests pass (`go test ./...`)
- [x] Build succeeds (`make build`)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/134
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: silverwind <silverwind@noreply.gitea.com>
Co-committed-by: silverwind <silverwind@noreply.gitea.com>
This commit is contained in:
silverwind
2026-02-25 19:05:09 +00:00
committed by Lunny Xiao
parent 6540693771
commit 4a2935d898
2 changed files with 133 additions and 9 deletions

View File

@@ -117,6 +117,123 @@ func TestEditPullRequestFn(t *testing.T) {
}
}
func TestMergePullRequestFn(t *testing.T) {
const (
owner = "octo"
repo = "demo"
index = 5
)
var (
mu sync.Mutex
gotMethod string
gotPath string
gotBody map[string]any
)
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/api/v1/version":
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"version":"1.12.0"}`))
case fmt.Sprintf("/api/v1/repos/%s/%s", owner, repo):
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"private":false}`))
case fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/merge", owner, repo, index):
mu.Lock()
gotMethod = r.Method
gotPath = r.URL.Path
var body map[string]any
_ = json.NewDecoder(r.Body).Decode(&body)
gotBody = body
mu.Unlock()
w.WriteHeader(http.StatusOK)
default:
http.NotFound(w, r)
}
})
server := httptest.NewServer(handler)
defer server.Close()
origHost := flag.Host
origToken := flag.Token
origVersion := flag.Version
flag.Host = server.URL
flag.Token = ""
flag.Version = "test"
defer func() {
flag.Host = origHost
flag.Token = origToken
flag.Version = origVersion
}()
req := mcp.CallToolRequest{
Params: mcp.CallToolParams{
Arguments: map[string]any{
"owner": owner,
"repo": repo,
"index": float64(index),
"merge_style": "squash",
"title": "feat: my squashed commit",
"message": "Squash merge of PR #5",
"delete_branch": true,
},
},
}
result, err := MergePullRequestFn(context.Background(), req)
if err != nil {
t.Fatalf("MergePullRequestFn() error = %v", err)
}
mu.Lock()
defer mu.Unlock()
if gotMethod != http.MethodPost {
t.Fatalf("expected POST request, got %s", gotMethod)
}
if gotPath != fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/merge", owner, repo, index) {
t.Fatalf("unexpected path: %s", gotPath)
}
if gotBody["Do"] != "squash" {
t.Fatalf("expected Do 'squash', got %v", gotBody["Do"])
}
if gotBody["MergeTitleField"] != "feat: my squashed commit" {
t.Fatalf("expected MergeTitleField 'feat: my squashed commit', got %v", gotBody["MergeTitleField"])
}
if gotBody["MergeMessageField"] != "Squash merge of PR #5" {
t.Fatalf("expected MergeMessageField 'Squash merge of PR #5', got %v", gotBody["MergeMessageField"])
}
if gotBody["delete_branch_after_merge"] != true {
t.Fatalf("expected delete_branch_after_merge true, got %v", gotBody["delete_branch_after_merge"])
}
if len(result.Content) == 0 {
t.Fatalf("expected content in result")
}
textContent, ok := mcp.AsTextContent(result.Content[0])
if !ok {
t.Fatalf("expected text content, got %T", result.Content[0])
}
var parsed struct {
Result map[string]any `json:"Result"`
}
if err := json.Unmarshal([]byte(textContent.Text), &parsed); err != nil {
t.Fatalf("unmarshal result text: %v", err)
}
if parsed.Result["merged"] != true {
t.Fatalf("expected merged=true, got %v", parsed.Result["merged"])
}
if parsed.Result["merge_style"] != "squash" {
t.Fatalf("expected merge_style 'squash', got %v", parsed.Result["merge_style"])
}
if parsed.Result["branch_deleted"] != true {
t.Fatalf("expected branch_deleted=true, got %v", parsed.Result["branch_deleted"])
}
}
func TestGetPullRequestDiffFn(t *testing.T) {
const (
owner = "octo"