fix(scm): paginate issue listings
Some checks failed
Security Scan / security (push) Failing after 10s
Test / test (push) Successful in 2m13s

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Virgil 2026-04-01 06:47:34 +00:00
parent 6233664c5d
commit 8021e5e2cb
4 changed files with 127 additions and 19 deletions

View file

@ -39,6 +39,9 @@ func (c *Client) ListIssues(owner, repo string, opts ListIssuesOpts) ([]*forgejo
page = 1
}
var all []*forgejo.Issue
for {
listOpt := forgejo.ListIssueOption{
ListOptions: forgejo.ListOptions{Page: page, PageSize: limit},
State: state,
@ -46,12 +49,22 @@ func (c *Client) ListIssues(owner, repo string, opts ListIssuesOpts) ([]*forgejo
Labels: opts.Labels,
}
issues, _, err := c.api.ListRepoIssues(owner, repo, listOpt)
issues, resp, err := c.api.ListRepoIssues(owner, repo, listOpt)
if err != nil {
return nil, log.E("forge.ListIssues", "failed to list issues", err)
}
return issues, nil
all = append(all, issues...)
if len(issues) < limit || len(issues) == 0 {
break
}
if resp != nil && resp.LastPage > 0 && page >= resp.LastPage {
break
}
page++
}
return all, nil
}
// GetIssue returns a single issue by number.

View file

@ -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

View file

@ -38,7 +38,10 @@ func (c *Client) ListIssues(owner, repo string, opts ListIssuesOpts) ([]*gitea.I
page = 1
}
issues, _, err := c.api.ListRepoIssues(owner, repo, gitea.ListIssueOption{
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,
@ -47,7 +50,17 @@ func (c *Client) ListIssues(owner, repo string, opts ListIssuesOpts) ([]*gitea.I
return nil, log.E("gitea.ListIssues", "failed to list issues", err)
}
return issues, nil
all = append(all, issues...)
if len(issues) < limit || len(issues) == 0 {
break
}
if resp != nil && resp.LastPage > 0 && page >= resp.LastPage {
break
}
page++
}
return all, nil
}
// GetIssue returns a single issue by number.

View file

@ -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