From d852087c459cbdc9536e0eda512e8e2b4df68fae Mon Sep 17 00:00:00 2001 From: Virgil Date: Wed, 1 Apr 2026 08:47:55 +0000 Subject: [PATCH] feat(forge): add org iterator Co-Authored-By: Virgil --- forge/orgs.go | 31 ++++++++++++++++++++++++++++++ forge/orgs_test.go | 48 ++++++++++++++++++++++++++++++++++++++++++++++ forge/prs_test.go | 2 +- 3 files changed, 80 insertions(+), 1 deletion(-) diff --git a/forge/orgs.go b/forge/orgs.go index 1e59c3c..03c39f3 100644 --- a/forge/orgs.go +++ b/forge/orgs.go @@ -3,6 +3,8 @@ package forge import ( + "iter" + forgejo "codeberg.org/mvdkleijn/forgejo-sdk/forgejo/v2" "dappco.re/go/core/log" @@ -33,6 +35,35 @@ func (c *Client) ListMyOrgs() ([]*forgejo.Organization, error) { return all, nil } +// ListMyOrgsIter returns an iterator over organisations for the authenticated user. +// Usage: ListMyOrgsIter(...) +func (c *Client) ListMyOrgsIter() iter.Seq2[*forgejo.Organization, error] { + return func(yield func(*forgejo.Organization, error) bool) { + page := 1 + + for { + orgs, resp, err := c.api.ListMyOrgs(forgejo.ListOrgsOptions{ + ListOptions: forgejo.ListOptions{Page: page, PageSize: 50}, + }) + if err != nil { + yield(nil, log.E("forge.ListMyOrgs", "failed to list orgs", err)) + return + } + + for _, org := range orgs { + if !yield(org, nil) { + return + } + } + + if resp == nil || page >= resp.LastPage { + break + } + page++ + } + } +} + // GetOrg returns a single organisation by name. // Usage: GetOrg(...) func (c *Client) GetOrg(name string) (*forgejo.Organization, error) { diff --git a/forge/orgs_test.go b/forge/orgs_test.go index c93bfb2..1af5171 100644 --- a/forge/orgs_test.go +++ b/forge/orgs_test.go @@ -3,6 +3,8 @@ package forge import ( + "net/http" + "net/http/httptest" "testing" forgejo "codeberg.org/mvdkleijn/forgejo-sdk/forgejo/v2" @@ -21,6 +23,41 @@ func TestClient_ListMyOrgs_Good(t *testing.T) { assert.Equal(t, "test-org", orgs[0].UserName) } +func TestClient_ListMyOrgsIter_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/user/orgs", func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Query().Get("page") { + case "2": + jsonResponse(w, []map[string]any{ + {"id": 101, "login": "second-org", "username": "second-org", "full_name": "Second Organisation"}, + }) + default: + w.Header().Set("Link", "; rel=\"next\", ; rel=\"last\"") + jsonResponse(w, []map[string]any{ + {"id": 100, "login": "test-org", "username": "test-org", "full_name": "Test Organisation"}, + }) + } + }) + + srv := httptest.NewServer(mux) + defer srv.Close() + + client, err := New(srv.URL, "test-token") + require.NoError(t, err) + + var names []string + for org, err := range client.ListMyOrgsIter() { + require.NoError(t, err) + names = append(names, org.UserName) + } + + require.Len(t, names, 2) + assert.Equal(t, []string{"test-org", "second-org"}, names) +} + func TestClient_ListMyOrgs_Bad_ServerError_Good(t *testing.T) { client, srv := newErrorServer(t) defer srv.Close() @@ -30,6 +67,17 @@ func TestClient_ListMyOrgs_Bad_ServerError_Good(t *testing.T) { assert.Contains(t, err.Error(), "failed to list orgs") } +func TestClient_ListMyOrgsIter_Bad_ServerError_Good(t *testing.T) { + client, srv := newErrorServer(t) + defer srv.Close() + + for _, err := range client.ListMyOrgsIter() { + assert.Error(t, err) + assert.Contains(t, err.Error(), "failed to list orgs") + break + } +} + func TestClient_GetOrg_Good(t *testing.T) { client, srv := newTestClient(t) defer srv.Close() diff --git a/forge/prs_test.go b/forge/prs_test.go index 12cf502..13e47f6 100644 --- a/forge/prs_test.go +++ b/forge/prs_test.go @@ -90,7 +90,7 @@ func TestClient_ListPRReviewsIter_Good_Paginates_Good(t *testing.T) { var states []string for review, err := range client.ListPRReviewsIter("test-org", "org-repo", 1) { require.NoError(t, err) - states = append(states, review.State) + states = append(states, string(review.State)) } require.Equal(t, []string{"APPROVED", "REQUEST_CHANGES"}, states)