diff --git a/issues.go b/issues.go index e19858d..6c78e7d 100644 --- a/issues.go +++ b/issues.go @@ -161,14 +161,26 @@ func (s *IssueService) SetDeadline(ctx context.Context, owner, repo string, inde // AddReaction adds a reaction to an issue. func (s *IssueService) AddReaction(ctx context.Context, owner, repo string, index int64, reaction string) error { path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}/reactions", pathParams("owner", owner, "repo", repo, "index", int64String(index))) - body := map[string]string{"content": reaction} + body := types.EditReactionOption{Reaction: reaction} return s.client.Post(ctx, path, body, nil) } +// ListReactions returns all reactions on an issue. +func (s *IssueService) ListReactions(ctx context.Context, owner, repo string, index int64) ([]types.Reaction, error) { + path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}/reactions", pathParams("owner", owner, "repo", repo, "index", int64String(index))) + return ListAll[types.Reaction](ctx, s.client, path, nil) +} + +// IterReactions returns an iterator over all reactions on an issue. +func (s *IssueService) IterReactions(ctx context.Context, owner, repo string, index int64) iter.Seq2[types.Reaction, error] { + path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}/reactions", pathParams("owner", owner, "repo", repo, "index", int64String(index))) + return ListIter[types.Reaction](ctx, s.client, path, nil) +} + // DeleteReaction removes a reaction from an issue. func (s *IssueService) DeleteReaction(ctx context.Context, owner, repo string, index int64, reaction string) error { path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}/reactions", pathParams("owner", owner, "repo", repo, "index", int64String(index))) - body := map[string]string{"content": reaction} + body := types.EditReactionOption{Reaction: reaction} return s.client.DeleteWithBody(ctx, path, body) } @@ -260,6 +272,34 @@ func (s *IssueService) CreateComment(ctx context.Context, owner, repo string, in return &out, nil } +// ListCommentReactions returns all reactions on an issue comment. +func (s *IssueService) ListCommentReactions(ctx context.Context, owner, repo string, id int64) ([]types.Reaction, error) { + path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/comments/{id}/reactions", pathParams("owner", owner, "repo", repo, "id", int64String(id))) + return ListAll[types.Reaction](ctx, s.client, path, nil) +} + +// IterCommentReactions returns an iterator over all reactions on an issue comment. +func (s *IssueService) IterCommentReactions(ctx context.Context, owner, repo string, id int64) iter.Seq2[types.Reaction, error] { + path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/comments/{id}/reactions", pathParams("owner", owner, "repo", repo, "id", int64String(id))) + return ListIter[types.Reaction](ctx, s.client, path, nil) +} + +// AddCommentReaction adds a reaction to an issue comment. +func (s *IssueService) AddCommentReaction(ctx context.Context, owner, repo string, id int64, reaction string) (*types.Reaction, error) { + path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/comments/{id}/reactions", pathParams("owner", owner, "repo", repo, "id", int64String(id))) + var out types.Reaction + if err := s.client.Post(ctx, path, types.EditReactionOption{Reaction: reaction}, &out); err != nil { + return nil, err + } + return &out, nil +} + +// DeleteCommentReaction removes a reaction from an issue comment. +func (s *IssueService) DeleteCommentReaction(ctx context.Context, owner, repo string, id int64, reaction string) error { + path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/comments/{id}/reactions", pathParams("owner", owner, "repo", repo, "id", int64String(id))) + return s.client.DeleteWithBody(ctx, path, types.EditReactionOption{Reaction: reaction}) +} + func attachmentUploadQuery(opts *AttachmentUploadOptions) map[string]string { if opts == nil { return nil diff --git a/issues_test.go b/issues_test.go index c94fedf..e9d2ef5 100644 --- a/issues_test.go +++ b/issues_test.go @@ -353,6 +353,151 @@ func TestIssueService_CreateComment_Good(t *testing.T) { } } +func TestIssueService_ListReactions_Good(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet { + t.Errorf("expected GET, got %s", r.Method) + } + if r.URL.Path != "/api/v1/repos/core/go-forge/issues/1/reactions" { + t.Errorf("wrong path: %s", r.URL.Path) + http.NotFound(w, r) + return + } + w.Header().Set("X-Total-Count", "2") + json.NewEncoder(w).Encode([]types.Reaction{ + {Reaction: "+1", User: &types.User{ID: 1, UserName: "alice"}}, + {Reaction: "heart", User: &types.User{ID: 2, UserName: "bob"}}, + }) + })) + defer srv.Close() + + f := NewForge(srv.URL, "tok") + reactions, err := f.Issues.ListReactions(context.Background(), "core", "go-forge", 1) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(reactions, []types.Reaction{ + {Reaction: "+1", User: &types.User{ID: 1, UserName: "alice"}}, + {Reaction: "heart", User: &types.User{ID: 2, UserName: "bob"}}, + }) { + t.Fatalf("got %#v", reactions) + } +} + +func TestIssueService_IterReactions_Good(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet { + t.Errorf("expected GET, got %s", r.Method) + } + if r.URL.Path != "/api/v1/repos/core/go-forge/issues/1/reactions" { + t.Errorf("wrong path: %s", r.URL.Path) + http.NotFound(w, r) + return + } + w.Header().Set("X-Total-Count", "1") + json.NewEncoder(w).Encode([]types.Reaction{ + {Reaction: "+1", User: &types.User{ID: 1, UserName: "alice"}}, + }) + })) + defer srv.Close() + + f := NewForge(srv.URL, "tok") + var seen []types.Reaction + for reaction, err := range f.Issues.IterReactions(context.Background(), "core", "go-forge", 1) { + if err != nil { + t.Fatal(err) + } + seen = append(seen, reaction) + } + if !reflect.DeepEqual(seen, []types.Reaction{{Reaction: "+1", User: &types.User{ID: 1, UserName: "alice"}}}) { + t.Fatalf("got %#v", seen) + } +} + +func TestIssueService_ListCommentReactions_Good(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet { + t.Errorf("expected GET, got %s", r.Method) + } + if r.URL.Path != "/api/v1/repos/core/go-forge/issues/comments/7/reactions" { + t.Errorf("wrong path: %s", r.URL.Path) + http.NotFound(w, r) + return + } + w.Header().Set("X-Total-Count", "1") + json.NewEncoder(w).Encode([]types.Reaction{ + {Reaction: "eyes", User: &types.User{ID: 3, UserName: "carol"}}, + }) + })) + defer srv.Close() + + f := NewForge(srv.URL, "tok") + reactions, err := f.Issues.ListCommentReactions(context.Background(), "core", "go-forge", 7) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(reactions, []types.Reaction{ + {Reaction: "eyes", User: &types.User{ID: 3, UserName: "carol"}}, + }) { + t.Fatalf("got %#v", reactions) + } +} + +func TestIssueService_AddCommentReaction_Good(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + t.Errorf("expected POST, got %s", r.Method) + } + if r.URL.Path != "/api/v1/repos/core/go-forge/issues/comments/7/reactions" { + t.Errorf("wrong path: %s", r.URL.Path) + http.NotFound(w, r) + return + } + var body types.EditReactionOption + json.NewDecoder(r.Body).Decode(&body) + if body.Reaction != "heart" { + t.Fatalf("got body=%#v", body) + } + w.WriteHeader(http.StatusCreated) + json.NewEncoder(w).Encode(types.Reaction{Reaction: body.Reaction, User: &types.User{ID: 4, UserName: "dave"}}) + })) + defer srv.Close() + + f := NewForge(srv.URL, "tok") + reaction, err := f.Issues.AddCommentReaction(context.Background(), "core", "go-forge", 7, "heart") + if err != nil { + t.Fatal(err) + } + if reaction.Reaction != "heart" || reaction.User.UserName != "dave" { + t.Fatalf("got %#v", reaction) + } +} + +func TestIssueService_DeleteCommentReaction_Good(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodDelete { + t.Errorf("expected DELETE, got %s", r.Method) + } + if r.URL.Path != "/api/v1/repos/core/go-forge/issues/comments/7/reactions" { + t.Errorf("wrong path: %s", r.URL.Path) + http.NotFound(w, r) + return + } + var body types.EditReactionOption + json.NewDecoder(r.Body).Decode(&body) + if body.Reaction != "heart" { + t.Fatalf("got body=%#v", body) + } + w.WriteHeader(http.StatusNoContent) + })) + defer srv.Close() + + f := NewForge(srv.URL, "tok") + if err := f.Issues.DeleteCommentReaction(context.Background(), "core", "go-forge", 7, "heart"); err != nil { + t.Fatal(err) + } +} + func TestIssueService_ListAttachments_Good(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet {