diff --git a/forge/prs.go b/forge/prs.go index b8af916..9ce3e36 100644 --- a/forge/prs.go +++ b/forge/prs.go @@ -4,6 +4,7 @@ package forge import ( "bytes" + "iter" fmt "dappco.re/go/core/scm/internal/ax/fmtx" json "dappco.re/go/core/scm/internal/ax/jsonx" "net/http" @@ -109,6 +110,33 @@ func (c *Client) ListPRReviews(owner, repo string, index int64) ([]*forgejo.Pull return all, nil } +// ListPRReviewsIter returns an iterator over reviews for a pull request. +// Usage: ListPRReviewsIter(...) +func (c *Client) ListPRReviewsIter(owner, repo string, index int64) iter.Seq2[*forgejo.PullReview, error] { + return func(yield func(*forgejo.PullReview, error) bool) { + page := 1 + + for { + reviews, resp, err := c.api.ListPullReviews(owner, repo, index, forgejo.ListPullReviewsOptions{ + ListOptions: forgejo.ListOptions{Page: page, PageSize: 50}, + }) + if err != nil { + yield(nil, log.E("forge.ListPRReviews", "failed to list reviews", err)) + return + } + for _, review := range reviews { + if !yield(review, nil) { + return + } + } + if resp == nil || page >= resp.LastPage { + break + } + page++ + } + } +} + // GetCombinedStatus returns the combined commit status for a ref (SHA or branch). // Usage: GetCombinedStatus(...) func (c *Client) GetCombinedStatus(owner, repo string, ref string) (*forgejo.CombinedStatus, error) { diff --git a/forge/prs_test.go b/forge/prs_test.go index 80c992b..12cf502 100644 --- a/forge/prs_test.go +++ b/forge/prs_test.go @@ -60,6 +60,42 @@ func TestClient_ListPRReviews_Good(t *testing.T) { require.Len(t, reviews, 1) } +func TestClient_ListPRReviewsIter_Good_Paginates_Good(t *testing.T) { + mux := http.NewServeMux() + mux.HandleFunc("/api/v1/version", func(w http.ResponseWriter, r *http.Request) { + jsonResponse(w, map[string]string{"version": "1.21.0"}) + }) + mux.HandleFunc("/api/v1/repos/test-org/org-repo/pulls/1/reviews", func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Query().Get("page") { + case "2": + jsonResponse(w, []map[string]any{ + {"id": 2, "state": "REQUEST_CHANGES", "user": map[string]any{"login": "reviewer2"}}, + }) + case "3": + jsonResponse(w, []map[string]any{}) + default: + w.Header().Set("Link", "; rel=\"next\", ; rel=\"last\"") + jsonResponse(w, []map[string]any{ + {"id": 1, "state": "APPROVED", "user": map[string]any{"login": "reviewer1"}}, + }) + } + }) + + srv := httptest.NewServer(mux) + defer srv.Close() + + client, err := New(srv.URL, "test-token") + require.NoError(t, err) + + var states []string + for review, err := range client.ListPRReviewsIter("test-org", "org-repo", 1) { + require.NoError(t, err) + states = append(states, review.State) + } + + require.Equal(t, []string{"APPROVED", "REQUEST_CHANGES"}, states) +} + func TestClient_ListPRReviews_Bad_ServerError_Good(t *testing.T) { client, srv := newErrorServer(t) defer srv.Close() @@ -69,6 +105,19 @@ func TestClient_ListPRReviews_Bad_ServerError_Good(t *testing.T) { assert.Contains(t, err.Error(), "failed to list reviews") } +func TestClient_ListPRReviewsIter_Bad_ServerError_Good(t *testing.T) { + client, srv := newErrorServer(t) + defer srv.Close() + + var got bool + for _, err := range client.ListPRReviewsIter("test-org", "org-repo", 1) { + assert.Error(t, err) + got = true + } + + assert.True(t, got) +} + func TestClient_GetCombinedStatus_Good(t *testing.T) { client, srv := newTestClient(t) defer srv.Close()