diff --git a/forge/issues.go b/forge/issues.go index 30cf1ef..c9d4620 100644 --- a/forge/issues.go +++ b/forge/issues.go @@ -39,19 +39,32 @@ func (c *Client) ListIssues(owner, repo string, opts ListIssuesOpts) ([]*forgejo page = 1 } - listOpt := forgejo.ListIssueOption{ - ListOptions: forgejo.ListOptions{Page: page, PageSize: limit}, - State: state, - Type: forgejo.IssueTypeIssue, - Labels: opts.Labels, + var all []*forgejo.Issue + + for { + listOpt := forgejo.ListIssueOption{ + ListOptions: forgejo.ListOptions{Page: page, PageSize: limit}, + State: state, + Type: forgejo.IssueTypeIssue, + Labels: opts.Labels, + } + + issues, resp, err := c.api.ListRepoIssues(owner, repo, listOpt) + if err != nil { + return nil, log.E("forge.ListIssues", "failed to list issues", err) + } + + all = append(all, issues...) + if len(issues) < limit || len(issues) == 0 { + break + } + if resp != nil && resp.LastPage > 0 && page >= resp.LastPage { + break + } + page++ } - issues, _, err := c.api.ListRepoIssues(owner, repo, listOpt) - if err != nil { - return nil, log.E("forge.ListIssues", "failed to list issues", err) - } - - return issues, nil + return all, nil } // GetIssue returns a single issue by number. diff --git a/forge/issues_test.go b/forge/issues_test.go index 8ac225f..823f45a 100644 --- a/forge/issues_test.go +++ b/forge/issues_test.go @@ -3,6 +3,8 @@ package forge import ( + "net/http" + "net/http/httptest" "testing" forgejo "codeberg.org/mvdkleijn/forgejo-sdk/forgejo/v2" @@ -11,6 +13,34 @@ import ( "github.com/stretchr/testify/require" ) +func newPaginatedIssuesClient(t *testing.T) (*Client, *httptest.Server) { + t.Helper() + + 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/issues", func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Query().Get("page") { + case "2": + jsonResponse(w, []map[string]any{ + {"id": 2, "number": 2, "title": "Issue 2", "state": "open", "body": "Second issue"}, + }) + case "3": + jsonResponse(w, []map[string]any{}) + default: + jsonResponse(w, []map[string]any{ + {"id": 1, "number": 1, "title": "Issue 1", "state": "open", "body": "First issue"}, + }) + } + }) + + srv := httptest.NewServer(mux) + client, err := New(srv.URL, "test-token") + require.NoError(t, err) + return client, srv +} + func TestClient_ListIssues_Good(t *testing.T) { client, srv := newTestClient(t) defer srv.Close() @@ -21,6 +51,17 @@ func TestClient_ListIssues_Good(t *testing.T) { assert.Equal(t, "Issue 1", issues[0].Title) } +func TestClient_ListIssues_Good_Paginates_Good(t *testing.T) { + client, srv := newPaginatedIssuesClient(t) + defer srv.Close() + + issues, err := client.ListIssues("test-org", "org-repo", ListIssuesOpts{Limit: 1}) + require.NoError(t, err) + require.Len(t, issues, 2) + assert.Equal(t, "Issue 1", issues[0].Title) + assert.Equal(t, "Issue 2", issues[1].Title) +} + func TestClient_ListIssues_Good_StateMapping_Good(t *testing.T) { tests := []struct { name string diff --git a/gitea/issues.go b/gitea/issues.go index ce6d8d0..e6c033a 100644 --- a/gitea/issues.go +++ b/gitea/issues.go @@ -38,16 +38,29 @@ func (c *Client) ListIssues(owner, repo string, opts ListIssuesOpts) ([]*gitea.I page = 1 } - issues, _, err := c.api.ListRepoIssues(owner, repo, gitea.ListIssueOption{ - ListOptions: gitea.ListOptions{Page: page, PageSize: limit}, - State: state, - Type: gitea.IssueTypeIssue, - }) - if err != nil { - return nil, log.E("gitea.ListIssues", "failed to list issues", err) + var all []*gitea.Issue + + for { + issues, resp, err := c.api.ListRepoIssues(owner, repo, gitea.ListIssueOption{ + ListOptions: gitea.ListOptions{Page: page, PageSize: limit}, + State: state, + Type: gitea.IssueTypeIssue, + }) + if err != nil { + return nil, log.E("gitea.ListIssues", "failed to list issues", err) + } + + all = append(all, issues...) + if len(issues) < limit || len(issues) == 0 { + break + } + if resp != nil && resp.LastPage > 0 && page >= resp.LastPage { + break + } + page++ } - return issues, nil + return all, nil } // GetIssue returns a single issue by number. diff --git a/gitea/issues_test.go b/gitea/issues_test.go index 5708740..59e2b91 100644 --- a/gitea/issues_test.go +++ b/gitea/issues_test.go @@ -3,6 +3,8 @@ package gitea import ( + "net/http" + "net/http/httptest" "testing" giteaSDK "code.gitea.io/sdk/gitea" @@ -11,6 +13,34 @@ import ( "github.com/stretchr/testify/require" ) +func newPaginatedIssuesClient(t *testing.T) (*Client, *httptest.Server) { + t.Helper() + + 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/issues", func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Query().Get("page") { + case "2": + jsonResponse(w, []map[string]any{ + {"id": 2, "number": 2, "title": "Issue 2", "state": "open", "body": "Second issue"}, + }) + case "3": + jsonResponse(w, []map[string]any{}) + default: + jsonResponse(w, []map[string]any{ + {"id": 1, "number": 1, "title": "Issue 1", "state": "open", "body": "First issue"}, + }) + } + }) + + srv := httptest.NewServer(mux) + client, err := New(srv.URL, "test-token") + require.NoError(t, err) + return client, srv +} + func TestClient_ListIssues_Good(t *testing.T) { client, srv := newTestClient(t) defer srv.Close() @@ -21,6 +51,17 @@ func TestClient_ListIssues_Good(t *testing.T) { assert.Equal(t, "Issue 1", issues[0].Title) } +func TestClient_ListIssues_Good_Paginates_Good(t *testing.T) { + client, srv := newPaginatedIssuesClient(t) + defer srv.Close() + + issues, err := client.ListIssues("test-org", "org-repo", ListIssuesOpts{Limit: 1}) + require.NoError(t, err) + require.Len(t, issues, 2) + assert.Equal(t, "Issue 1", issues[0].Title) + assert.Equal(t, "Issue 2", issues[1].Title) +} + func TestClient_ListIssues_Good_StateMapping_Good(t *testing.T) { tests := []struct { name string