Add agentpr pr edit subcommand
ci/woodpecker/pr/pre-commit Pipeline was successful
ci/woodpecker/pr/build Pipeline was successful
ci/woodpecker/pr/test Pipeline was successful

Update a PR's title and/or body as the agent user, sending only the
fields supplied.
This commit is contained in:
2026-09-19 12:32:07 +10:00
parent cdced6536e
commit 7bc4082cb0
6 changed files with 171 additions and 8 deletions
+74
View File
@@ -98,6 +98,80 @@ func TestCreatePRRequestBody(t *testing.T) {
}
}
// An edit must send only the fields it was given: Gitea overwrites whatever
// key it receives, so an omitted --title arriving as "" would blank the title.
func TestEditPRSendsOnlySuppliedFields(t *testing.T) {
title, body, empty := "new title", "new body", ""
tests := []struct {
name string
opts EditPROptions
want map[string]any
}{
{"body only", EditPROptions{Body: &body}, map[string]any{"body": "new body"}},
{"title only", EditPROptions{Title: &title}, map[string]any{"title": "new title"}},
{"both", EditPROptions{Title: &title, Body: &body}, map[string]any{"title": "new title", "body": "new body"}},
{"explicit empty body is sent", EditPROptions{Body: &empty}, map[string]any{"body": ""}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var gotBody map[string]any
var gotMethod, gotPath string
mux := http.NewServeMux()
mux.HandleFunc("/api/v1/repos/unkin/repo/pulls/7", func(w http.ResponseWriter, r *http.Request) {
gotMethod, gotPath = r.Method, r.URL.Path
_ = json.NewDecoder(r.Body).Decode(&gotBody)
_, _ = io.WriteString(w, `{"number":7,"title":"new title","html_url":"https://git.unkin.net/unkin/repo/pulls/7"}`)
})
srv := httptest.NewServer(mux)
defer srv.Close()
c := &GiteaClient{BaseURL: srv.URL, Token: "gitea-abc", HTTP: srv.Client()}
pr, err := c.EditPR("unkin/repo", 7, tt.opts)
if err != nil {
t.Fatalf("EditPR: %v", err)
}
if gotMethod != http.MethodPatch {
t.Errorf("method = %s, want PATCH", gotMethod)
}
if gotPath != "/api/v1/repos/unkin/repo/pulls/7" {
t.Errorf("path = %q", gotPath)
}
if len(gotBody) != len(tt.want) {
t.Errorf("payload = %v, want exactly the supplied fields %v", gotBody, tt.want)
}
for k, v := range tt.want {
if gotBody[k] != v {
t.Errorf("payload[%q] = %v, want %v", k, gotBody[k], v)
}
}
if pr.Number != 7 || pr.HTMLURL == "" {
t.Errorf("parsed PR = %+v", pr)
}
})
}
}
// A non-2xx must surface the API's own message rather than a bare status.
func TestEditPRAPIError(t *testing.T) {
mux := http.NewServeMux()
mux.HandleFunc("/api/v1/repos/unkin/repo/pulls/7", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusUnprocessableEntity)
_, _ = io.WriteString(w, `{"message":"title cannot be empty"}`)
})
srv := httptest.NewServer(mux)
defer srv.Close()
title := ""
c := &GiteaClient{BaseURL: srv.URL, Token: "t", HTTP: srv.Client()}
_, err := c.EditPR("unkin/repo", 7, EditPROptions{Title: &title})
if err == nil {
t.Fatal("expected error on 422")
}
if !strings.Contains(err.Error(), "title cannot be empty") {
t.Errorf("error %q should carry the API message", err)
}
}
func TestCreateComment(t *testing.T) {
var gotBody map[string]string
mux := http.NewServeMux()