From 272b029ae89d52b1122cad26d7610ec0ec14896c Mon Sep 17 00:00:00 2001 From: Andrew Nesbitt Date: Thu, 24 Sep 2026 17:22:27 +0100 Subject: [PATCH] Report HTTP status in Gitea/Forgejo API errors The Gitea SDK returns errors containing only the server's message field, which Forgejo blanks on 500s in production and which is sometimes cryptic (409 'Release has no Tag' means a release already exists). Add a wrapErr helper that includes the operation name and HTTP status, apply it across the gitea backend, and map the release-create 409 to a message that says what happened. Fixes #166 --- gitea/branches.go | 16 +---- gitea/ci.go | 16 +---- gitea/collaborators.go | 21 ++----- gitea/commit_statuses.go | 11 +--- gitea/commits.go | 2 +- gitea/deploy_keys.go | 21 ++----- gitea/errors.go | 32 ++++++++++ gitea/errors_test.go | 55 +++++++++++++++++ gitea/files.go | 11 +--- gitea/gitea.go | 38 ++++-------- gitea/issues.go | 50 ++++------------ gitea/labels.go | 29 ++------- gitea/milestones.go | 37 +++--------- gitea/notifications.go | 30 ++++------ gitea/prs.go | 51 ++++------------ gitea/reactions.go | 21 ++----- gitea/releases.go | 52 ++++------------ gitea/releases_test.go | 126 +++++++++++++++++++++++++++++++++++++++ gitea/reviews.go | 21 ++----- gitea/secrets.go | 16 +---- 20 files changed, 311 insertions(+), 345 deletions(-) create mode 100644 gitea/errors.go create mode 100644 gitea/errors_test.go create mode 100644 gitea/releases_test.go diff --git a/gitea/branches.go b/gitea/branches.go index 316b74b..8d62604 100644 --- a/gitea/branches.go +++ b/gitea/branches.go @@ -3,7 +3,6 @@ package gitea import ( "context" forge "github.com/git-pkgs/forge" - "net/http" "code.gitea.io/sdk/gitea" ) @@ -29,10 +28,7 @@ func (s *giteaBranchService) List(ctx context.Context, owner, repo string, opts ListOptions: gitea.ListOptions{Page: page, PageSize: perPage}, }) if err != nil { - if resp != nil && resp.StatusCode == http.StatusNotFound { - return nil, forge.ErrNotFound - } - return nil, err + return nil, wrapErr("list branches", resp, err) } for _, b := range branches { branch := forge.Branch{ @@ -63,10 +59,7 @@ func (s *giteaBranchService) Create(ctx context.Context, owner, repo, name, from OldBranchName: from, }) if err != nil { - if resp != nil && resp.StatusCode == http.StatusNotFound { - return nil, forge.ErrNotFound - } - return nil, err + return nil, wrapErr("create branch", resp, err) } result := forge.Branch{ @@ -81,10 +74,7 @@ func (s *giteaBranchService) Create(ctx context.Context, owner, repo, name, from func (s *giteaBranchService) Delete(ctx context.Context, owner, repo, name string) error { _, resp, err := s.client.DeleteRepoBranch(owner, repo, name) if err != nil { - if resp != nil && resp.StatusCode == http.StatusNotFound { - return forge.ErrNotFound - } - return err + return wrapErr("delete branch", resp, err) } return nil } diff --git a/gitea/ci.go b/gitea/ci.go index c014f55..9fab03c 100644 --- a/gitea/ci.go +++ b/gitea/ci.go @@ -4,7 +4,6 @@ import ( "bytes" "context" "io" - "net/http" forge "github.com/git-pkgs/forge" @@ -93,10 +92,7 @@ func (s *giteaCIService) ListRuns(_ context.Context, owner, repo string, opts fo for { resp, httpResp, err := s.client.ListRepoActionRuns(owner, repo, gOpts) if err != nil { - if httpResp != nil && httpResp.StatusCode == http.StatusNotFound { - return nil, forge.ErrNotFound - } - return nil, err + return nil, wrapErr("list workflow runs", httpResp, err) } for _, r := range resp.WorkflowRuns { all = append(all, convertGiteaWorkflowRun(r)) @@ -117,10 +113,7 @@ func (s *giteaCIService) ListRuns(_ context.Context, owner, repo string, opts fo func (s *giteaCIService) GetRun(_ context.Context, owner, repo string, runID int64) (*forge.CIRun, error) { r, resp, err := s.client.GetRepoActionRun(owner, repo, runID) if err != nil { - if resp != nil && resp.StatusCode == http.StatusNotFound { - return nil, forge.ErrNotFound - } - return nil, err + return nil, wrapErr("get workflow run", resp, err) } result := convertGiteaWorkflowRun(r) @@ -149,10 +142,7 @@ func (s *giteaCIService) RetryRun(_ context.Context, _, _ string, _ int64) error func (s *giteaCIService) GetJobLog(_ context.Context, owner, repo string, jobID int64) (io.ReadCloser, error) { data, resp, err := s.client.GetRepoActionJobLogs(owner, repo, jobID) if err != nil { - if resp != nil && resp.StatusCode == http.StatusNotFound { - return nil, forge.ErrNotFound - } - return nil, err + return nil, wrapErr("get job log", resp, err) } return io.NopCloser(bytes.NewReader(data)), nil } diff --git a/gitea/collaborators.go b/gitea/collaborators.go index 5d3b397..83b4836 100644 --- a/gitea/collaborators.go +++ b/gitea/collaborators.go @@ -3,7 +3,6 @@ package gitea import ( "context" forge "github.com/git-pkgs/forge" - "net/http" "code.gitea.io/sdk/gitea" ) @@ -29,10 +28,7 @@ func (s *giteaCollaboratorService) List(ctx context.Context, owner, repo string, ListOptions: gitea.ListOptions{Page: page, PageSize: perPage}, }) if err != nil { - if resp != nil && resp.StatusCode == http.StatusNotFound { - return nil, forge.ErrNotFound - } - return nil, err + return nil, wrapErr("list collaborators", resp, err) } for _, u := range users { perm, err := s.getPermission(owner, repo, u.UserName) @@ -60,10 +56,7 @@ func (s *giteaCollaboratorService) List(ctx context.Context, owner, repo string, func (s *giteaCollaboratorService) getPermission(owner, repo, username string) (string, error) { result, resp, err := s.client.CollaboratorPermission(owner, repo, username) if err != nil { - if resp != nil && resp.StatusCode == http.StatusNotFound { - return "", forge.ErrNotFound - } - return "", err + return "", wrapErr("get collaborator permission", resp, err) } switch result.Permission { case "admin", "owner": @@ -86,10 +79,7 @@ func (s *giteaCollaboratorService) Add(ctx context.Context, owner, repo, usernam Permission: perm, }) if err != nil { - if resp != nil && resp.StatusCode == http.StatusNotFound { - return forge.ErrNotFound - } - return err + return wrapErr("add collaborator", resp, err) } return nil } @@ -110,10 +100,7 @@ func giteaPermission(permission forge.AccessLevel) string { func (s *giteaCollaboratorService) Remove(ctx context.Context, owner, repo, username string) error { resp, err := s.client.DeleteCollaborator(owner, repo, username) if err != nil { - if resp != nil && resp.StatusCode == http.StatusNotFound { - return forge.ErrNotFound - } - return err + return wrapErr("remove collaborator", resp, err) } return nil } diff --git a/gitea/commit_statuses.go b/gitea/commit_statuses.go index bd26fcb..23149c4 100644 --- a/gitea/commit_statuses.go +++ b/gitea/commit_statuses.go @@ -3,7 +3,6 @@ package gitea import ( "context" forge "github.com/git-pkgs/forge" - "net/http" "code.gitea.io/sdk/gitea" ) @@ -50,10 +49,7 @@ func (s *giteaCommitStatusService) List(ctx context.Context, owner, repo, sha st ListOptions: gitea.ListOptions{Page: page, PageSize: defaultPageSize}, }) if err != nil { - if resp != nil && resp.StatusCode == http.StatusNotFound { - return nil, forge.ErrNotFound - } - return nil, err + return nil, wrapErr("list commit statuses", resp, err) } for _, st := range statuses { cs := forge.CommitStatus{ @@ -84,10 +80,7 @@ func (s *giteaCommitStatusService) Set(ctx context.Context, owner, repo, sha str Context: opts.Context, }) if err != nil { - if resp != nil && resp.StatusCode == http.StatusNotFound { - return nil, forge.ErrNotFound - } - return nil, err + return nil, wrapErr("set commit status", resp, err) } cs := &forge.CommitStatus{ diff --git a/gitea/commits.go b/gitea/commits.go index d2f9d64..807fd2c 100644 --- a/gitea/commits.go +++ b/gitea/commits.go @@ -38,7 +38,7 @@ func (s *giteaCommitService) ResolveCommit(_ context.Context, owner, repo, ref s if resp != nil && resp.StatusCode == http.StatusNotFound { return "", forge.CommitRefError(owner, repo, ref, forge.ErrNotFound) } - return "", forge.CommitRefError(owner, repo, ref, err) + return "", forge.CommitRefError(owner, repo, ref, wrapErr("get commit", resp, err)) } if commit == nil || commit.CommitMeta == nil { return "", forge.CommitRefError(owner, repo, ref, errors.New("empty response")) diff --git a/gitea/deploy_keys.go b/gitea/deploy_keys.go index 212f331..9046e73 100644 --- a/gitea/deploy_keys.go +++ b/gitea/deploy_keys.go @@ -3,7 +3,6 @@ package gitea import ( "context" forge "github.com/git-pkgs/forge" - "net/http" "code.gitea.io/sdk/gitea" ) @@ -29,10 +28,7 @@ func (s *giteaDeployKeyService) List(ctx context.Context, owner, repo string, op ListOptions: gitea.ListOptions{Page: page, PageSize: perPage}, }) if err != nil { - if resp != nil && resp.StatusCode == http.StatusNotFound { - return nil, forge.ErrNotFound - } - return nil, err + return nil, wrapErr("list deploy keys", resp, err) } for _, k := range keys { all = append(all, forge.DeployKey{ @@ -59,10 +55,7 @@ func (s *giteaDeployKeyService) List(ctx context.Context, owner, repo string, op func (s *giteaDeployKeyService) Get(ctx context.Context, owner, repo string, id int64) (*forge.DeployKey, error) { k, resp, err := s.client.GetDeployKey(owner, repo, id) if err != nil { - if resp != nil && resp.StatusCode == http.StatusNotFound { - return nil, forge.ErrNotFound - } - return nil, err + return nil, wrapErr("get deploy key", resp, err) } return &forge.DeployKey{ @@ -81,10 +74,7 @@ func (s *giteaDeployKeyService) Create(ctx context.Context, owner, repo string, ReadOnly: opts.ReadOnly, }) if err != nil { - if resp != nil && resp.StatusCode == http.StatusNotFound { - return nil, forge.ErrNotFound - } - return nil, err + return nil, wrapErr("create deploy key", resp, err) } return &forge.DeployKey{ @@ -99,10 +89,7 @@ func (s *giteaDeployKeyService) Create(ctx context.Context, owner, repo string, func (s *giteaDeployKeyService) Delete(ctx context.Context, owner, repo string, id int64) error { resp, err := s.client.DeleteDeployKey(owner, repo, id) if err != nil { - if resp != nil && resp.StatusCode == http.StatusNotFound { - return forge.ErrNotFound - } - return err + return wrapErr("delete deploy key", resp, err) } return nil } diff --git a/gitea/errors.go b/gitea/errors.go new file mode 100644 index 0000000..a67d0c8 --- /dev/null +++ b/gitea/errors.go @@ -0,0 +1,32 @@ +package gitea + +import ( + "fmt" + forge "github.com/git-pkgs/forge" + "net/http" + "strings" + + "code.gitea.io/sdk/gitea" +) + +// wrapErr adds the operation name and HTTP status to an SDK error. The SDK +// returns errors containing only the server's "message" field, which Forgejo +// blanks on 500s in production mode and which can otherwise be cryptic +// (e.g. "Release has no Tag" for a 409). Without the status code the caller +// has nothing to go on. +func wrapErr(op string, resp *gitea.Response, err error) error { + if resp != nil && resp.StatusCode == http.StatusNotFound { + return forge.ErrNotFound + } + msg := strings.TrimSpace(err.Error()) + if resp == nil { + if msg == "" { + return fmt.Errorf("%s: %w", op, err) + } + return fmt.Errorf("%s: %s", op, msg) + } + if msg == "" { + return fmt.Errorf("%s: %s", op, resp.Status) + } + return fmt.Errorf("%s: %s: %s", op, resp.Status, msg) +} diff --git a/gitea/errors_test.go b/gitea/errors_test.go new file mode 100644 index 0000000..221b7e3 --- /dev/null +++ b/gitea/errors_test.go @@ -0,0 +1,55 @@ +package gitea + +import ( + "errors" + forge "github.com/git-pkgs/forge" + "net/http" + "testing" + + "code.gitea.io/sdk/gitea" +) + +func resp(code int, status string) *gitea.Response { + return &gitea.Response{Response: &http.Response{StatusCode: code, Status: status}} +} + +func TestWrapErr(t *testing.T) { + tests := []struct { + name string + op string + resp *gitea.Response + err error + want string + }{ + {"blank message", "create issue", resp(500, "500 Internal Server Error"), errors.New(""), "create issue: 500 Internal Server Error"}, + {"whitespace message", "create issue", resp(500, "500 Internal Server Error"), errors.New(" \n"), "create issue: 500 Internal Server Error"}, + {"with message", "create issue", resp(422, "422 Unprocessable Entity"), errors.New("bad input"), "create issue: 422 Unprocessable Entity: bad input"}, + {"nil resp with message", "list labels", nil, errors.New("dial tcp: connection refused"), "list labels: dial tcp: connection refused"}, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + got := wrapErr(tc.op, tc.resp, tc.err) + if got.Error() != tc.want { + t.Errorf("want %q, got %q", tc.want, got.Error()) + } + }) + } +} + +func TestWrapErrNotFound(t *testing.T) { + got := wrapErr("get issue", resp(404, "404 Not Found"), errors.New("The target couldn't be found.")) + if !errors.Is(got, forge.ErrNotFound) { + t.Errorf("404 should return ErrNotFound sentinel, got %v", got) + } +} + +func TestWrapErrNilRespBlankMessage(t *testing.T) { + inner := errors.New("") + got := wrapErr("get issue", nil, inner) + if got.Error() != "get issue: " { + t.Errorf("want %q, got %q", "get issue: ", got.Error()) + } + if !errors.Is(got, inner) { + t.Error("nil-resp blank error should wrap the original for errors.Is") + } +} diff --git a/gitea/files.go b/gitea/files.go index c486a5b..cc0ba28 100644 --- a/gitea/files.go +++ b/gitea/files.go @@ -4,7 +4,6 @@ import ( "context" "encoding/base64" forge "github.com/git-pkgs/forge" - "net/http" "code.gitea.io/sdk/gitea" ) @@ -20,10 +19,7 @@ func (f *giteaForge) Files() forge.FileService { func (s *giteaFileService) Get(ctx context.Context, owner, repo, path, ref string) (*forge.FileContent, error) { cr, resp, err := s.client.GetContents(owner, repo, ref, path) if err != nil { - if resp != nil && resp.StatusCode == http.StatusNotFound { - return nil, forge.ErrNotFound - } - return nil, err + return nil, wrapErr("get file", resp, err) } var content []byte @@ -49,10 +45,7 @@ func (s *giteaFileService) Get(ctx context.Context, owner, repo, path, ref strin func (s *giteaFileService) List(ctx context.Context, owner, repo, path, ref string) ([]forge.FileEntry, error) { items, resp, err := s.client.ListContents(owner, repo, ref, path) if err != nil { - if resp != nil && resp.StatusCode == http.StatusNotFound { - return nil, forge.ErrNotFound - } - return nil, err + return nil, wrapErr("list files", resp, err) } entries := make([]forge.FileEntry, len(items)) diff --git a/gitea/gitea.go b/gitea/gitea.go index c970f77..cff2485 100644 --- a/gitea/gitea.go +++ b/gitea/gitea.go @@ -87,10 +87,7 @@ func convertGiteaRepo(r *gitea.Repository) forge.Repository { func (s *giteaRepoService) Get(ctx context.Context, owner, repo string) (*forge.Repository, error) { r, resp, err := s.client.GetRepo(owner, repo) if err != nil { - if resp != nil && resp.StatusCode == http.StatusNotFound { - return nil, forge.ErrNotFound - } - return nil, err + return nil, wrapErr("get repo", resp, err) } result := convertGiteaRepo(r) @@ -134,7 +131,7 @@ func (s *giteaRepoService) listOrgRepos(_ context.Context, owner string, perPage if resp != nil && resp.StatusCode == http.StatusNotFound { return nil, forge.ErrOwnerNotFound } - return nil, err + return nil, wrapErr("list org repos", resp, err) } for _, r := range gRepos { repo := convertGiteaRepo(r) @@ -166,7 +163,7 @@ func (s *giteaRepoService) listUserRepos(_ context.Context, owner string, perPag if resp != nil && resp.StatusCode == http.StatusNotFound { return nil, forge.ErrOwnerNotFound } - return nil, err + return nil, wrapErr("list user repos", resp, err) } for _, r := range gRepos { repo := convertGiteaRepo(r) @@ -229,7 +226,7 @@ func (s *giteaRepoService) Create(ctx context.Context, opts forge.CreateRepoOpts if resp != nil && resp.StatusCode == http.StatusNotFound { return nil, forge.ErrOwnerNotFound } - return nil, err + return nil, wrapErr("create repo", resp, err) } result := convertGiteaRepo(r) @@ -276,10 +273,7 @@ func (s *giteaRepoService) Edit(ctx context.Context, owner, repo string, opts fo r, resp, err := s.client.EditRepo(owner, repo, gOpts) if err != nil { - if resp != nil && resp.StatusCode == http.StatusNotFound { - return nil, forge.ErrNotFound - } - return nil, err + return nil, wrapErr("edit repo", resp, err) } result := convertGiteaRepo(r) @@ -289,10 +283,7 @@ func (s *giteaRepoService) Edit(ctx context.Context, owner, repo string, opts fo func (s *giteaRepoService) Delete(ctx context.Context, owner, repo string) error { resp, err := s.client.DeleteRepo(owner, repo) if err != nil { - if resp != nil && resp.StatusCode == http.StatusNotFound { - return forge.ErrNotFound - } - return err + return wrapErr("delete repo", resp, err) } return nil } @@ -310,10 +301,7 @@ func (s *giteaRepoService) Fork(ctx context.Context, owner, repo string, opts fo r, resp, err := s.client.CreateFork(owner, repo, gOpts) if err != nil { - if resp != nil && resp.StatusCode == http.StatusNotFound { - return nil, forge.ErrNotFound - } - return nil, err + return nil, wrapErr("fork repo", resp, err) } result := convertGiteaRepo(r) @@ -333,10 +321,7 @@ func (s *giteaRepoService) ListForks(ctx context.Context, owner, repo string, op ListOptions: gitea.ListOptions{Page: page, PageSize: perPage}, }) if err != nil { - if resp != nil && resp.StatusCode == http.StatusNotFound { - return nil, forge.ErrNotFound - } - return nil, err + return nil, wrapErr("list forks", resp, err) } for _, r := range forks { all = append(all, convertGiteaRepo(r)) @@ -362,10 +347,7 @@ func (s *giteaRepoService) ListTags(ctx context.Context, owner, repo string) ([] ListOptions: gitea.ListOptions{Page: page, PageSize: defaultPageSize}, }) if err != nil { - if resp != nil && resp.StatusCode == http.StatusNotFound { - return nil, forge.ErrNotFound - } - return nil, err + return nil, wrapErr("list tags", resp, err) } for _, t := range tags { tag := forge.Tag{Name: t.Name} @@ -414,7 +396,7 @@ func (s *giteaRepoService) Search(ctx context.Context, opts forge.SearchRepoOpts if resp != nil && resp.StatusCode == http.StatusNotFound { return nil, nil } - return nil, err + return nil, wrapErr("search repos", resp, err) } var repos []forge.Repository diff --git a/gitea/issues.go b/gitea/issues.go index 32944bc..7be9630 100644 --- a/gitea/issues.go +++ b/gitea/issues.go @@ -4,7 +4,6 @@ import ( "context" "fmt" forge "github.com/git-pkgs/forge" - "net/http" "code.gitea.io/sdk/gitea" ) @@ -102,10 +101,7 @@ func convertGiteaComment(c *gitea.Comment) forge.Comment { func (s *giteaIssueService) Get(ctx context.Context, owner, repo string, number int) (*forge.Issue, error) { i, resp, err := s.client.GetIssue(owner, repo, int64(number)) if err != nil { - if resp != nil && resp.StatusCode == http.StatusNotFound { - return nil, forge.ErrNotFound - } - return nil, err + return nil, wrapErr("get issue", resp, err) } result := convertGiteaIssue(i) return &result, nil @@ -142,10 +138,7 @@ func (s *giteaIssueService) List(ctx context.Context, owner, repo string, opts f for { issues, resp, err := s.client.ListRepoIssues(owner, repo, gOpts) if err != nil { - if resp != nil && resp.StatusCode == http.StatusNotFound { - return nil, forge.ErrNotFound - } - return nil, err + return nil, wrapErr("list issues", resp, err) } for _, i := range issues { all = append(all, convertGiteaIssue(i)) @@ -188,10 +181,7 @@ func (s *giteaIssueService) Create(ctx context.Context, owner, repo string, opts i, resp, err := s.client.CreateIssue(owner, repo, gOpts) if err != nil { - if resp != nil && resp.StatusCode == http.StatusNotFound { - return nil, forge.ErrNotFound - } - return nil, err + return nil, wrapErr("create issue", resp, err) } result := convertGiteaIssue(i) return &result, nil @@ -227,8 +217,8 @@ func (s *giteaIssueService) Update(ctx context.Context, owner, repo string, numb if err != nil { return nil, fmt.Errorf("resolving labels: %w", err) } - if _, _, err := s.client.ReplaceIssueLabels(owner, repo, int64(number), gitea.IssueLabelsOption{Labels: ids}); err != nil { - return nil, fmt.Errorf("replacing labels: %w", err) + if _, resp, err := s.client.ReplaceIssueLabels(owner, repo, int64(number), gitea.IssueLabelsOption{Labels: ids}); err != nil { + return nil, wrapErr("replace issue labels", resp, err) } if !changed { return s.Get(ctx, owner, repo, number) @@ -241,10 +231,7 @@ func (s *giteaIssueService) Update(ctx context.Context, owner, repo string, numb i, resp, err := s.client.EditIssue(owner, repo, int64(number), gOpts) if err != nil { - if resp != nil && resp.StatusCode == http.StatusNotFound { - return nil, forge.ErrNotFound - } - return nil, err + return nil, wrapErr("update issue", resp, err) } result := convertGiteaIssue(i) return &result, nil @@ -257,10 +244,7 @@ func (s *giteaIssueService) Close(ctx context.Context, owner, repo string, numbe } _, resp, err := s.client.EditIssue(owner, repo, int64(number), gOpts) if err != nil { - if resp != nil && resp.StatusCode == http.StatusNotFound { - return forge.ErrNotFound - } - return err + return wrapErr("close issue", resp, err) } return nil } @@ -272,10 +256,7 @@ func (s *giteaIssueService) Reopen(ctx context.Context, owner, repo string, numb } _, resp, err := s.client.EditIssue(owner, repo, int64(number), gOpts) if err != nil { - if resp != nil && resp.StatusCode == http.StatusNotFound { - return forge.ErrNotFound - } - return err + return wrapErr("reopen issue", resp, err) } return nil } @@ -283,10 +264,7 @@ func (s *giteaIssueService) Reopen(ctx context.Context, owner, repo string, numb func (s *giteaIssueService) Delete(ctx context.Context, owner, repo string, number int) error { resp, err := s.client.DeleteIssue(owner, repo, int64(number)) if err != nil { - if resp != nil && resp.StatusCode == http.StatusNotFound { - return forge.ErrNotFound - } - return err + return wrapErr("delete issue", resp, err) } return nil } @@ -296,10 +274,7 @@ func (s *giteaIssueService) CreateComment(ctx context.Context, owner, repo strin Body: body, }) if err != nil { - if resp != nil && resp.StatusCode == http.StatusNotFound { - return nil, forge.ErrNotFound - } - return nil, err + return nil, wrapErr("create issue comment", resp, err) } result := convertGiteaComment(c) return &result, nil @@ -317,10 +292,7 @@ func (s *giteaIssueService) ListComments(ctx context.Context, owner, repo string ListOptions: gitea.ListOptions{Page: page, PageSize: defaultPageSize}, }) if err != nil { - if resp != nil && resp.StatusCode == http.StatusNotFound { - return nil, forge.ErrNotFound - } - return nil, err + return nil, wrapErr("list issue comments", resp, err) } for _, c := range comments { all = append(all, convertGiteaComment(c)) diff --git a/gitea/labels.go b/gitea/labels.go index ae4db2b..5fb3c18 100644 --- a/gitea/labels.go +++ b/gitea/labels.go @@ -39,10 +39,7 @@ func (s *giteaLabelService) List(ctx context.Context, owner, repo string, opts f ListOptions: gitea.ListOptions{Page: page, PageSize: perPage}, }) if err != nil { - if resp != nil && resp.StatusCode == http.StatusNotFound { - return nil, forge.ErrNotFound - } - return nil, err + return nil, wrapErr("list labels", resp, err) } for _, l := range labels { all = append(all, convertGiteaLabel(l)) @@ -69,10 +66,7 @@ func (s *giteaLabelService) findLabelByName(owner, repo, name string) (*gitea.La ListOptions: gitea.ListOptions{Page: page, PageSize: defaultPageSize}, }) if err != nil { - if resp != nil && resp.StatusCode == http.StatusNotFound { - return nil, forge.ErrNotFound - } - return nil, err + return nil, wrapErr("list labels", resp, err) } for _, l := range labels { if l.Name == name { @@ -102,10 +96,7 @@ func resolveLabelIDs(client *gitea.Client, owner, repo string, names []string) ( ListOptions: gitea.ListOptions{Page: page, PageSize: defaultPageSize}, }) if err != nil { - if resp != nil && resp.StatusCode == http.StatusNotFound { - return nil, forge.ErrNotFound - } - return nil, err + return nil, wrapErr("list labels", resp, err) } for _, l := range labels { if _, ok := nameSet[l.Name]; ok { @@ -150,13 +141,11 @@ func (s *giteaLabelService) Create(ctx context.Context, owner, repo string, opts if err != nil { if resp != nil { switch resp.StatusCode { - case http.StatusNotFound: - return nil, forge.ErrNotFound case http.StatusConflict, http.StatusUnprocessableEntity: return nil, forge.ErrLabelExists } } - return nil, err + return nil, wrapErr("create label", resp, err) } result := convertGiteaLabel(l) return &result, nil @@ -191,10 +180,7 @@ func (s *giteaLabelService) Update(ctx context.Context, owner, repo, name string l, resp, err := s.client.EditLabel(owner, repo, existing.ID, gOpts) if err != nil { - if resp != nil && resp.StatusCode == http.StatusNotFound { - return nil, forge.ErrNotFound - } - return nil, err + return nil, wrapErr("update label", resp, err) } result := convertGiteaLabel(l) return &result, nil @@ -208,10 +194,7 @@ func (s *giteaLabelService) Delete(ctx context.Context, owner, repo, name string resp, err := s.client.DeleteLabel(owner, repo, existing.ID) if err != nil { - if resp != nil && resp.StatusCode == http.StatusNotFound { - return forge.ErrNotFound - } - return err + return wrapErr("delete label", resp, err) } return nil } diff --git a/gitea/milestones.go b/gitea/milestones.go index 64a1ba8..afceecb 100644 --- a/gitea/milestones.go +++ b/gitea/milestones.go @@ -63,10 +63,7 @@ func (s *giteaMilestoneService) List(ctx context.Context, owner, repo string, op for { milestones, resp, err := s.client.ListRepoMilestones(owner, repo, gOpts) if err != nil { - if resp != nil && resp.StatusCode == http.StatusNotFound { - return nil, forge.ErrNotFound - } - return nil, err + return nil, wrapErr("list milestones", resp, err) } for _, m := range milestones { all = append(all, convertGiteaMilestone(m)) @@ -93,7 +90,7 @@ func resolveMilestoneID(client *gitea.Client, owner, repo, name string) (int64, if resp != nil && resp.StatusCode == http.StatusNotFound { return 0, fmt.Errorf("milestone not found: %s", name) } - return 0, err + return 0, wrapErr("get milestone", resp, err) } return m.ID, nil } @@ -101,10 +98,7 @@ func resolveMilestoneID(client *gitea.Client, owner, repo, name string) (int64, func (s *giteaMilestoneService) Get(ctx context.Context, owner, repo string, id int) (*forge.Milestone, error) { m, resp, err := s.client.GetMilestone(owner, repo, int64(id)) if err != nil { - if resp != nil && resp.StatusCode == http.StatusNotFound { - return nil, forge.ErrNotFound - } - return nil, err + return nil, wrapErr("get milestone", resp, err) } result := convertGiteaMilestone(m) return &result, nil @@ -121,10 +115,7 @@ func (s *giteaMilestoneService) Create(ctx context.Context, owner, repo string, m, resp, err := s.client.CreateMilestone(owner, repo, gOpts) if err != nil { - if resp != nil && resp.StatusCode == http.StatusNotFound { - return nil, forge.ErrNotFound - } - return nil, err + return nil, wrapErr("create milestone", resp, err) } result := convertGiteaMilestone(m) return &result, nil @@ -164,10 +155,7 @@ func (s *giteaMilestoneService) Update(ctx context.Context, owner, repo string, m, resp, err := s.client.EditMilestone(owner, repo, int64(id), gOpts) if err != nil { - if resp != nil && resp.StatusCode == http.StatusNotFound { - return nil, forge.ErrNotFound - } - return nil, err + return nil, wrapErr("update milestone", resp, err) } result := convertGiteaMilestone(m) return &result, nil @@ -178,10 +166,7 @@ func (s *giteaMilestoneService) Close(ctx context.Context, owner, repo string, i gOpts := gitea.EditMilestoneOption{State: &closed} _, resp, err := s.client.EditMilestone(owner, repo, int64(id), gOpts) if err != nil { - if resp != nil && resp.StatusCode == http.StatusNotFound { - return forge.ErrNotFound - } - return err + return wrapErr("close milestone", resp, err) } return nil } @@ -191,10 +176,7 @@ func (s *giteaMilestoneService) Reopen(ctx context.Context, owner, repo string, gOpts := gitea.EditMilestoneOption{State: &open} _, resp, err := s.client.EditMilestone(owner, repo, int64(id), gOpts) if err != nil { - if resp != nil && resp.StatusCode == http.StatusNotFound { - return forge.ErrNotFound - } - return err + return wrapErr("reopen milestone", resp, err) } return nil } @@ -202,10 +184,7 @@ func (s *giteaMilestoneService) Reopen(ctx context.Context, owner, repo string, func (s *giteaMilestoneService) Delete(ctx context.Context, owner, repo string, id int) error { resp, err := s.client.DeleteMilestone(owner, repo, int64(id)) if err != nil { - if resp != nil && resp.StatusCode == http.StatusNotFound { - return forge.ErrNotFound - } - return err + return wrapErr("delete milestone", resp, err) } return nil } diff --git a/gitea/notifications.go b/gitea/notifications.go index 45e8f62..f88b9d4 100644 --- a/gitea/notifications.go +++ b/gitea/notifications.go @@ -2,7 +2,6 @@ package gitea import ( "context" - "net/http" "strconv" "strings" @@ -97,10 +96,7 @@ func (s *giteaNotificationService) listRepoNotifications(owner, repo string, pag Status: statuses, }) if err != nil { - if resp != nil && resp.StatusCode == http.StatusNotFound { - return nil, forge.ErrNotFound - } - return nil, err + return nil, wrapErr("list notifications", resp, err) } for _, n := range notifications { all = append(all, convertGiteaNotification(n)) @@ -121,7 +117,7 @@ func (s *giteaNotificationService) listAllNotifications(page, perPage int, statu Status: statuses, }) if err != nil { - return nil, err + return nil, wrapErr("list notifications", resp, err) } for _, n := range notifications { all = append(all, convertGiteaNotification(n)) @@ -142,10 +138,7 @@ func (s *giteaNotificationService) MarkRead(ctx context.Context, opts forge.Mark } _, resp, err := s.client.ReadNotification(id) if err != nil { - if resp != nil && resp.StatusCode == http.StatusNotFound { - return forge.ErrNotFound - } - return err + return wrapErr("mark notification read", resp, err) } return nil } @@ -154,17 +147,17 @@ func (s *giteaNotificationService) MarkRead(ctx context.Context, opts forge.Mark if i := strings.LastIndex(opts.Repo, "/"); i > 0 { _, resp, err := s.client.ReadRepoNotifications(opts.Repo[:i], opts.Repo[i+1:], gitea.MarkNotificationOptions{}) if err != nil { - if resp != nil && resp.StatusCode == http.StatusNotFound { - return forge.ErrNotFound - } - return err + return wrapErr("mark notifications read", resp, err) } return nil } } - _, _, err := s.client.ReadNotifications(gitea.MarkNotificationOptions{}) - return err + _, resp, err := s.client.ReadNotifications(gitea.MarkNotificationOptions{}) + if err != nil { + return wrapErr("mark notifications read", resp, err) + } + return nil } func (s *giteaNotificationService) Get(ctx context.Context, id string) (*forge.Notification, error) { @@ -175,10 +168,7 @@ func (s *giteaNotificationService) Get(ctx context.Context, id string) (*forge.N n, resp, err := s.client.GetNotification(nID) if err != nil { - if resp != nil && resp.StatusCode == http.StatusNotFound { - return nil, forge.ErrNotFound - } - return nil, err + return nil, wrapErr("get notification", resp, err) } result := convertGiteaNotification(n) diff --git a/gitea/prs.go b/gitea/prs.go index f7fe901..0058ac2 100644 --- a/gitea/prs.go +++ b/gitea/prs.go @@ -4,7 +4,6 @@ import ( "context" "fmt" forge "github.com/git-pkgs/forge" - "net/http" "code.gitea.io/sdk/gitea" ) @@ -134,10 +133,7 @@ func convertGiteaPR(pr *gitea.PullRequest) forge.PullRequest { func (s *giteaPRService) Get(ctx context.Context, owner, repo string, number int) (*forge.PullRequest, error) { pr, resp, err := s.client.GetPullRequest(owner, repo, int64(number)) if err != nil { - if resp != nil && resp.StatusCode == http.StatusNotFound { - return nil, forge.ErrNotFound - } - return nil, err + return nil, wrapErr("get pull request", resp, err) } result := convertGiteaPR(pr) return &result, nil @@ -173,10 +169,7 @@ func (s *giteaPRService) List(ctx context.Context, owner, repo string, opts forg for { prs, resp, err := s.client.ListRepoPullRequests(owner, repo, gOpts) if err != nil { - if resp != nil && resp.StatusCode == http.StatusNotFound { - return nil, forge.ErrNotFound - } - return nil, err + return nil, wrapErr("list pull requests", resp, err) } for _, pr := range prs { all = append(all, convertGiteaPR(pr)) @@ -214,10 +207,7 @@ func (s *giteaPRService) Create(ctx context.Context, owner, repo string, opts fo pr, resp, err := s.client.CreatePullRequest(owner, repo, gOpts) if err != nil { - if resp != nil && resp.StatusCode == http.StatusNotFound { - return nil, forge.ErrNotFound - } - return nil, err + return nil, wrapErr("create pull request", resp, err) } result := convertGiteaPR(pr) return &result, nil @@ -250,10 +240,7 @@ func (s *giteaPRService) Update(ctx context.Context, owner, repo string, number pr, resp, err := s.client.EditPullRequest(owner, repo, int64(number), gOpts) if err != nil { - if resp != nil && resp.StatusCode == http.StatusNotFound { - return nil, forge.ErrNotFound - } - return nil, err + return nil, wrapErr("update pull request", resp, err) } result := convertGiteaPR(pr) return &result, nil @@ -266,10 +253,7 @@ func (s *giteaPRService) Close(ctx context.Context, owner, repo string, number i } _, resp, err := s.client.EditPullRequest(owner, repo, int64(number), gOpts) if err != nil { - if resp != nil && resp.StatusCode == http.StatusNotFound { - return forge.ErrNotFound - } - return err + return wrapErr("close pull request", resp, err) } return nil } @@ -281,10 +265,7 @@ func (s *giteaPRService) Reopen(ctx context.Context, owner, repo string, number } _, resp, err := s.client.EditPullRequest(owner, repo, int64(number), gOpts) if err != nil { - if resp != nil && resp.StatusCode == http.StatusNotFound { - return forge.ErrNotFound - } - return err + return wrapErr("reopen pull request", resp, err) } return nil } @@ -304,10 +285,7 @@ func (s *giteaPRService) Merge(ctx context.Context, owner, repo string, number i _, resp, err := s.client.MergePullRequest(owner, repo, int64(number), gOpts) if err != nil { - if resp != nil && resp.StatusCode == http.StatusNotFound { - return forge.ErrNotFound - } - return err + return wrapErr("merge pull request", resp, err) } return nil } @@ -315,10 +293,7 @@ func (s *giteaPRService) Merge(ctx context.Context, owner, repo string, number i func (s *giteaPRService) Diff(ctx context.Context, owner, repo string, number int) (string, error) { raw, resp, err := s.client.GetPullRequestDiff(owner, repo, int64(number), gitea.PullRequestDiffOptions{}) if err != nil { - if resp != nil && resp.StatusCode == http.StatusNotFound { - return "", forge.ErrNotFound - } - return "", err + return "", wrapErr("get pull request diff", resp, err) } return string(raw), nil } @@ -328,10 +303,7 @@ func (s *giteaPRService) CreateComment(ctx context.Context, owner, repo string, Body: body, }) if err != nil { - if resp != nil && resp.StatusCode == http.StatusNotFound { - return nil, forge.ErrNotFound - } - return nil, err + return nil, wrapErr("create pull request comment", resp, err) } result := convertGiteaComment(c) return &result, nil @@ -345,10 +317,7 @@ func (s *giteaPRService) ListComments(ctx context.Context, owner, repo string, n ListOptions: gitea.ListOptions{Page: page, PageSize: defaultPageSize}, }) if err != nil { - if resp != nil && resp.StatusCode == http.StatusNotFound { - return nil, forge.ErrNotFound - } - return nil, err + return nil, wrapErr("list pull request comments", resp, err) } for _, c := range comments { all = append(all, convertGiteaComment(c)) diff --git a/gitea/reactions.go b/gitea/reactions.go index e441e86..f45ec21 100644 --- a/gitea/reactions.go +++ b/gitea/reactions.go @@ -2,7 +2,6 @@ package gitea import ( "context" - "net/http" "code.gitea.io/sdk/gitea" forge "github.com/git-pkgs/forge" @@ -21,10 +20,7 @@ func convertGiteaReaction(r *gitea.Reaction) forge.Reaction { func (s *giteaIssueService) ListReactions(ctx context.Context, owner, repo string, number int, commentID int64) ([]forge.Reaction, error) { reactions, resp, err := s.client.GetIssueCommentReactions(owner, repo, commentID) if err != nil { - if resp != nil && resp.StatusCode == http.StatusNotFound { - return nil, forge.ErrNotFound - } - return nil, err + return nil, wrapErr("list reactions", resp, err) } var all []forge.Reaction for _, r := range reactions { @@ -36,10 +32,7 @@ func (s *giteaIssueService) ListReactions(ctx context.Context, owner, repo strin func (s *giteaIssueService) AddReaction(ctx context.Context, owner, repo string, number int, commentID int64, reaction string) (*forge.Reaction, error) { r, resp, err := s.client.PostIssueCommentReaction(owner, repo, commentID, reaction) if err != nil { - if resp != nil && resp.StatusCode == http.StatusNotFound { - return nil, forge.ErrNotFound - } - return nil, err + return nil, wrapErr("add reaction", resp, err) } result := convertGiteaReaction(r) return &result, nil @@ -49,10 +42,7 @@ func (s *giteaPRService) ListReactions(ctx context.Context, owner, repo string, // Gitea uses the same issue comment reactions API for PR comments reactions, resp, err := s.client.GetIssueCommentReactions(owner, repo, commentID) if err != nil { - if resp != nil && resp.StatusCode == http.StatusNotFound { - return nil, forge.ErrNotFound - } - return nil, err + return nil, wrapErr("list reactions", resp, err) } var all []forge.Reaction for _, r := range reactions { @@ -64,10 +54,7 @@ func (s *giteaPRService) ListReactions(ctx context.Context, owner, repo string, func (s *giteaPRService) AddReaction(ctx context.Context, owner, repo string, number int, commentID int64, reaction string) (*forge.Reaction, error) { r, resp, err := s.client.PostIssueCommentReaction(owner, repo, commentID, reaction) if err != nil { - if resp != nil && resp.StatusCode == http.StatusNotFound { - return nil, forge.ErrNotFound - } - return nil, err + return nil, wrapErr("add reaction", resp, err) } result := convertGiteaReaction(r) return &result, nil diff --git a/gitea/releases.go b/gitea/releases.go index a3842f0..167ed15 100644 --- a/gitea/releases.go +++ b/gitea/releases.go @@ -2,6 +2,7 @@ package gitea import ( "context" + "fmt" forge "github.com/git-pkgs/forge" "io" "net/http" @@ -69,10 +70,7 @@ func (s *giteaReleaseService) List(ctx context.Context, owner, repo string, opts ListOptions: gitea.ListOptions{Page: page, PageSize: perPage}, }) if err != nil { - if resp != nil && resp.StatusCode == http.StatusNotFound { - return nil, forge.ErrNotFound - } - return nil, err + return nil, wrapErr("list releases", resp, err) } for _, r := range releases { all = append(all, convertGiteaRelease(r)) @@ -93,10 +91,7 @@ func (s *giteaReleaseService) List(ctx context.Context, owner, repo string, opts func (s *giteaReleaseService) Get(ctx context.Context, owner, repo, tag string) (*forge.Release, error) { r, resp, err := s.client.GetReleaseByTag(owner, repo, tag) if err != nil { - if resp != nil && resp.StatusCode == http.StatusNotFound { - return nil, forge.ErrNotFound - } - return nil, err + return nil, wrapErr("get release", resp, err) } result := convertGiteaRelease(r) return &result, nil @@ -108,10 +103,7 @@ func (s *giteaReleaseService) GetLatest(ctx context.Context, owner, repo string) ListOptions: gitea.ListOptions{Page: 1, PageSize: latestReleasesPageSize}, }) if err != nil { - if resp != nil && resp.StatusCode == http.StatusNotFound { - return nil, forge.ErrNotFound - } - return nil, err + return nil, wrapErr("get latest release", resp, err) } for _, r := range releases { if !r.IsDraft && !r.IsPrerelease { @@ -140,10 +132,10 @@ func (s *giteaReleaseService) Create(ctx context.Context, owner, repo string, op r, resp, err := s.client.CreateRelease(owner, repo, gOpts) if err != nil { - if resp != nil && resp.StatusCode == http.StatusNotFound { - return nil, forge.ErrNotFound + if resp != nil && resp.StatusCode == http.StatusConflict { + return nil, fmt.Errorf("release for tag %q already exists", opts.TagName) } - return nil, err + return nil, wrapErr("create release", resp, err) } result := convertGiteaRelease(r) return &result, nil @@ -152,10 +144,7 @@ func (s *giteaReleaseService) Create(ctx context.Context, owner, repo string, op func (s *giteaReleaseService) Update(ctx context.Context, owner, repo, tag string, opts forge.UpdateReleaseOpts) (*forge.Release, error) { existing, resp, err := s.client.GetReleaseByTag(owner, repo, tag) if err != nil { - if resp != nil && resp.StatusCode == http.StatusNotFound { - return nil, forge.ErrNotFound - } - return nil, err + return nil, wrapErr("update release", resp, err) } gOpts := gitea.EditReleaseOption{} @@ -193,10 +182,7 @@ func (s *giteaReleaseService) Update(ctx context.Context, owner, repo, tag strin r, resp, err := s.client.EditRelease(owner, repo, existing.ID, gOpts) if err != nil { - if resp != nil && resp.StatusCode == http.StatusNotFound { - return nil, forge.ErrNotFound - } - return nil, err + return nil, wrapErr("update release", resp, err) } result := convertGiteaRelease(r) return &result, nil @@ -205,18 +191,12 @@ func (s *giteaReleaseService) Update(ctx context.Context, owner, repo, tag strin func (s *giteaReleaseService) Delete(ctx context.Context, owner, repo, tag string) error { existing, resp, err := s.client.GetReleaseByTag(owner, repo, tag) if err != nil { - if resp != nil && resp.StatusCode == http.StatusNotFound { - return forge.ErrNotFound - } - return err + return wrapErr("delete release", resp, err) } resp, err = s.client.DeleteRelease(owner, repo, existing.ID) if err != nil { - if resp != nil && resp.StatusCode == http.StatusNotFound { - return forge.ErrNotFound - } - return err + return wrapErr("delete release", resp, err) } return nil } @@ -224,19 +204,13 @@ func (s *giteaReleaseService) Delete(ctx context.Context, owner, repo, tag strin func (s *giteaReleaseService) UploadAsset(ctx context.Context, owner, repo, tag string, file *os.File) (*forge.ReleaseAsset, error) { existing, resp, err := s.client.GetReleaseByTag(owner, repo, tag) if err != nil { - if resp != nil && resp.StatusCode == http.StatusNotFound { - return nil, forge.ErrNotFound - } - return nil, err + return nil, wrapErr("upload release asset", resp, err) } name := filepath.Base(file.Name()) a, resp, err := s.client.CreateReleaseAttachment(owner, repo, existing.ID, file, name) if err != nil { - if resp != nil && resp.StatusCode == http.StatusNotFound { - return nil, forge.ErrNotFound - } - return nil, err + return nil, wrapErr("upload release asset", resp, err) } result := forge.ReleaseAsset{ diff --git a/gitea/releases_test.go b/gitea/releases_test.go new file mode 100644 index 0000000..9cb35f2 --- /dev/null +++ b/gitea/releases_test.go @@ -0,0 +1,126 @@ +package gitea + +import ( + "context" + "errors" + forge "github.com/git-pkgs/forge" + "net/http" + "net/http/httptest" + "strings" + "testing" +) + +func TestCreateReleaseAlreadyExists(t *testing.T) { + // Forgejo/Gitea return 409 {"message":"Release has no Tag"} when a + // release already exists for the given tag. The message refers to an + // internal is_tag flag and is meaningless to callers; surface the tag + // name and the fact that it already exists instead. + mux := http.NewServeMux() + mux.HandleFunc("GET /api/v1/version", giteaVersionHandler) + mux.HandleFunc("POST /api/v1/repos/o/r/releases", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusConflict) + _, _ = w.Write([]byte(`{"message":"Release has no Tag","url":"https://example.com/api/swagger"}`)) + }) + srv := httptest.NewServer(mux) + defer srv.Close() + + f := New(srv.URL, "", nil) + _, err := f.Releases().Create(context.Background(), "o", "r", forge.CreateReleaseOpts{ + TagName: "v1.0.0", + Title: "v1.0.0", + }) + if err == nil { + t.Fatal("expected error, got nil") + } + if !strings.Contains(err.Error(), "v1.0.0") { + t.Errorf("error should mention the tag name, got %q", err.Error()) + } + if !strings.Contains(err.Error(), "already exists") { + t.Errorf("error should say the release already exists, got %q", err.Error()) + } + if strings.Contains(err.Error(), "Release has no Tag") { + t.Errorf("error should not surface the raw server message verbatim, got %q", err.Error()) + } +} + +func TestCreateReleaseServerErrorBlankMessage(t *testing.T) { + // Forgejo blanks the message on 500s in production mode, so the SDK + // returns an error whose Error() is "". Callers must at least see the + // HTTP status. + mux := http.NewServeMux() + mux.HandleFunc("GET /api/v1/version", giteaVersionHandler) + mux.HandleFunc("POST /api/v1/repos/o/r/releases", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusInternalServerError) + _, _ = w.Write([]byte(`{"message":"","url":"https://example.com/api/swagger"}`)) + }) + srv := httptest.NewServer(mux) + defer srv.Close() + + f := New(srv.URL, "", nil) + _, err := f.Releases().Create(context.Background(), "o", "r", forge.CreateReleaseOpts{ + TagName: "v1.0.0", + Title: "v1.0.0", + }) + if err == nil { + t.Fatal("expected error, got nil") + } + if err.Error() == "" { + t.Fatalf("error string is empty") + } + if !strings.Contains(err.Error(), "500") { + t.Errorf("error should include the HTTP status, got %q", err.Error()) + } + if !strings.Contains(err.Error(), "create release") { + t.Errorf("error should name the operation, got %q", err.Error()) + } +} + +func TestCreateReleaseServerErrorWithMessage(t *testing.T) { + mux := http.NewServeMux() + mux.HandleFunc("GET /api/v1/version", giteaVersionHandler) + mux.HandleFunc("POST /api/v1/repos/o/r/releases", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusUnprocessableEntity) + _, _ = w.Write([]byte(`{"message":"tag name is protected"}`)) + }) + srv := httptest.NewServer(mux) + defer srv.Close() + + f := New(srv.URL, "", nil) + _, err := f.Releases().Create(context.Background(), "o", "r", forge.CreateReleaseOpts{ + TagName: "v1.0.0", + Title: "v1.0.0", + }) + if err == nil { + t.Fatal("expected error, got nil") + } + if !strings.Contains(err.Error(), "tag name is protected") { + t.Errorf("error should keep the server message, got %q", err.Error()) + } + if !strings.Contains(err.Error(), "422") { + t.Errorf("error should include the HTTP status, got %q", err.Error()) + } +} + +func TestCreateReleaseNotFound(t *testing.T) { + mux := http.NewServeMux() + mux.HandleFunc("GET /api/v1/version", giteaVersionHandler) + mux.HandleFunc("POST /api/v1/repos/o/r/releases", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusNotFound) + _, _ = w.Write([]byte(`{"message":"repo not found"}`)) + }) + srv := httptest.NewServer(mux) + defer srv.Close() + + f := New(srv.URL, "", nil) + _, err := f.Releases().Create(context.Background(), "o", "r", forge.CreateReleaseOpts{ + TagName: "v1.0.0", + Title: "v1.0.0", + }) + if !errors.Is(err, forge.ErrNotFound) { + t.Fatalf("expected ErrNotFound, got %v", err) + } +} diff --git a/gitea/reviews.go b/gitea/reviews.go index e4369f5..279ba6d 100644 --- a/gitea/reviews.go +++ b/gitea/reviews.go @@ -3,7 +3,6 @@ package gitea import ( "context" forge "github.com/git-pkgs/forge" - "net/http" "strings" "code.gitea.io/sdk/gitea" @@ -70,10 +69,7 @@ func (s *giteaReviewService) List(ctx context.Context, owner, repo string, numbe ListOptions: gitea.ListOptions{Page: page, PageSize: perPage}, }) if err != nil { - if resp != nil && resp.StatusCode == http.StatusNotFound { - return nil, forge.ErrNotFound - } - return nil, err + return nil, wrapErr("list reviews", resp, err) } for _, r := range reviews { all = append(all, convertGiteaReview(r)) @@ -108,10 +104,7 @@ func (s *giteaReviewService) Submit(ctx context.Context, owner, repo string, num Body: opts.Body, }) if err != nil { - if resp != nil && resp.StatusCode == http.StatusNotFound { - return nil, forge.ErrNotFound - } - return nil, err + return nil, wrapErr("submit review", resp, err) } result := convertGiteaReview(review) return &result, nil @@ -122,10 +115,7 @@ func (s *giteaReviewService) RequestReviewers(ctx context.Context, owner, repo s Reviewers: users, }) if err != nil { - if resp != nil && resp.StatusCode == http.StatusNotFound { - return forge.ErrNotFound - } - return err + return wrapErr("request reviewers", resp, err) } return nil } @@ -135,10 +125,7 @@ func (s *giteaReviewService) RemoveReviewers(ctx context.Context, owner, repo st Reviewers: users, }) if err != nil { - if resp != nil && resp.StatusCode == http.StatusNotFound { - return forge.ErrNotFound - } - return err + return wrapErr("remove reviewers", resp, err) } return nil } diff --git a/gitea/secrets.go b/gitea/secrets.go index 64bb561..5238760 100644 --- a/gitea/secrets.go +++ b/gitea/secrets.go @@ -3,7 +3,6 @@ package gitea import ( "context" forge "github.com/git-pkgs/forge" - "net/http" "code.gitea.io/sdk/gitea" ) @@ -29,10 +28,7 @@ func (s *giteaSecretService) List(ctx context.Context, owner, repo string, opts ListOptions: gitea.ListOptions{Page: page, PageSize: perPage}, }) if err != nil { - if resp != nil && resp.StatusCode == http.StatusNotFound { - return nil, forge.ErrNotFound - } - return nil, err + return nil, wrapErr("list secrets", resp, err) } for _, sec := range secrets { all = append(all, forge.Secret{ @@ -58,10 +54,7 @@ func (s *giteaSecretService) Set(ctx context.Context, owner, repo string, opts f Data: opts.Value, }) if err != nil { - if resp != nil && resp.StatusCode == http.StatusNotFound { - return forge.ErrNotFound - } - return err + return wrapErr("set secret", resp, err) } return nil } @@ -69,10 +62,7 @@ func (s *giteaSecretService) Set(ctx context.Context, owner, repo string, opts f func (s *giteaSecretService) Delete(ctx context.Context, owner, repo, name string) error { resp, err := s.client.DeleteRepoActionSecret(owner, repo, name) if err != nil { - if resp != nil && resp.StatusCode == http.StatusNotFound { - return forge.ErrNotFound - } - return err + return wrapErr("delete secret", resp, err) } return nil }