fix(milestone): persist due_on on create and edit (#189)
Fixes https://gitea.com/gitea/gitea-mcp/issues/187 The `due_on` argument was declared in the `milestone_write` schema but never read by `createMilestoneFn` or `editMilestoneFn`, so `opt.Deadline` was always nil and the field was silently dropped. This reuses the existing `params.GetOptionalTime` helper that already handles the analogous `deadline` field on issues and pull requests. --- This PR was written with the help of Claude Opus 4.7 Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/189 Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com> Co-authored-by: silverwind <me@silverwind.io> Co-committed-by: silverwind <me@silverwind.io>
This commit is contained in:
@@ -177,6 +177,7 @@ func createMilestoneFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallT
|
|||||||
if ok {
|
if ok {
|
||||||
opt.Description = description
|
opt.Description = description
|
||||||
}
|
}
|
||||||
|
opt.Deadline = params.GetOptionalTime(req.GetArguments(), "due_on")
|
||||||
|
|
||||||
client, err := gitea.ClientFromContext(ctx)
|
client, err := gitea.ClientFromContext(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -219,6 +220,7 @@ func editMilestoneFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToo
|
|||||||
if ok {
|
if ok {
|
||||||
opt.State = new(gitea_sdk.StateType(state))
|
opt.State = new(gitea_sdk.StateType(state))
|
||||||
}
|
}
|
||||||
|
opt.Deadline = params.GetOptionalTime(req.GetArguments(), "due_on")
|
||||||
|
|
||||||
client, err := gitea.ClientFromContext(ctx)
|
client, err := gitea.ClientFromContext(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
package milestone
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"maps"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"gitea.com/gitea/gitea-mcp/pkg/flag"
|
||||||
|
|
||||||
|
"github.com/mark3labs/mcp-go/mcp"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_milestoneWriteFn_dueOn(t *testing.T) {
|
||||||
|
const (
|
||||||
|
owner = "octo"
|
||||||
|
repo = "demo"
|
||||||
|
id = 42
|
||||||
|
due = "2026-05-18T23:59:59Z"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
mu sync.Mutex
|
||||||
|
bodies = map[string]map[string]any{}
|
||||||
|
)
|
||||||
|
|
||||||
|
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
switch r.URL.Path {
|
||||||
|
case "/api/v1/version":
|
||||||
|
_, _ = w.Write([]byte(`{"version":"1.12.0"}`))
|
||||||
|
case fmt.Sprintf("/api/v1/repos/%s/%s/milestones", owner, repo),
|
||||||
|
fmt.Sprintf("/api/v1/repos/%s/%s/milestones/%d", owner, repo, id):
|
||||||
|
mu.Lock()
|
||||||
|
var body map[string]any
|
||||||
|
_ = json.NewDecoder(r.Body).Decode(&body)
|
||||||
|
bodies[r.Method] = body
|
||||||
|
mu.Unlock()
|
||||||
|
_, _ = w.Write(fmt.Appendf(nil, `{"id":%d,"title":"v1","due_on":%q}`, id, due))
|
||||||
|
default:
|
||||||
|
http.NotFound(w, r)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
server := httptest.NewServer(handler)
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
origHost, origToken, origVersion := flag.Host, flag.Token, flag.Version
|
||||||
|
flag.Host, flag.Token, flag.Version = server.URL, "", "test"
|
||||||
|
defer func() { flag.Host, flag.Token, flag.Version = origHost, origToken, origVersion }()
|
||||||
|
|
||||||
|
args := map[string]any{"owner": owner, "repo": repo, "due_on": due}
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
fn func(context.Context, mcp.CallToolRequest) (*mcp.CallToolResult, error)
|
||||||
|
method string
|
||||||
|
extra map[string]any
|
||||||
|
}{
|
||||||
|
{"create", createMilestoneFn, http.MethodPost, map[string]any{"title": "v1"}},
|
||||||
|
{"edit", editMilestoneFn, http.MethodPatch, map[string]any{"id": float64(id)}},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
a := map[string]any{}
|
||||||
|
maps.Copy(a, args)
|
||||||
|
maps.Copy(a, tc.extra)
|
||||||
|
res, err := tc.fn(context.Background(), mcp.CallToolRequest{Params: mcp.CallToolParams{Arguments: a}})
|
||||||
|
if err != nil || res.IsError {
|
||||||
|
t.Fatalf("%s err=%v result=%v", tc.name, err, res)
|
||||||
|
}
|
||||||
|
mu.Lock()
|
||||||
|
body := bodies[tc.method]
|
||||||
|
mu.Unlock()
|
||||||
|
if got, _ := body["due_on"].(string); got != due {
|
||||||
|
t.Fatalf("%s: expected due_on=%q, got %v (body: %v)", tc.name, due, got, body)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user