feat(forge): add iterator variants for list endpoints
All checks were successful
Security Scan / security (push) Successful in 14s
Test / test (push) Successful in 1m54s

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Virgil 2026-04-02 06:24:47 +00:00
parent acdcfc07cf
commit db31014f5f
6 changed files with 206 additions and 0 deletions

View file

@ -217,6 +217,22 @@ func (s *AdminService) ListQuotaGroups(ctx context.Context) ([]types.QuotaGroup,
return ListAll[types.QuotaGroup](ctx, s.client, "/api/v1/admin/quota/groups", nil)
}
// IterQuotaGroups returns an iterator over all available quota groups.
func (s *AdminService) IterQuotaGroups(ctx context.Context) iter.Seq2[types.QuotaGroup, error] {
return func(yield func(types.QuotaGroup, error) bool) {
groups, err := s.ListQuotaGroups(ctx)
if err != nil {
yield(*new(types.QuotaGroup), err)
return
}
for _, group := range groups {
if !yield(group, nil) {
return
}
}
}
}
// CreateQuotaGroup creates a new quota group.
func (s *AdminService) CreateQuotaGroup(ctx context.Context, opts *types.CreateQuotaGroupOptions) (*types.QuotaGroup, error) {
var out types.QuotaGroup
@ -283,6 +299,22 @@ func (s *AdminService) ListQuotaRules(ctx context.Context) ([]types.QuotaRuleInf
return ListAll[types.QuotaRuleInfo](ctx, s.client, "/api/v1/admin/quota/rules", nil)
}
// IterQuotaRules returns an iterator over all available quota rules.
func (s *AdminService) IterQuotaRules(ctx context.Context) iter.Seq2[types.QuotaRuleInfo, error] {
return func(yield func(types.QuotaRuleInfo, error) bool) {
rules, err := s.ListQuotaRules(ctx)
if err != nil {
yield(*new(types.QuotaRuleInfo), err)
return
}
for _, rule := range rules {
if !yield(rule, nil) {
return
}
}
}
}
// CreateQuotaRule creates a new quota rule.
func (s *AdminService) CreateQuotaRule(ctx context.Context, opts *types.CreateQuotaRuleOptions) (*types.QuotaRuleInfo, error) {
var out types.QuotaRuleInfo

View file

@ -411,6 +411,39 @@ func TestAdminService_ListQuotaGroups_Good(t *testing.T) {
}
}
func TestAdminService_IterQuotaGroups_Good(t *testing.T) {
var requests int
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
requests++
if r.Method != http.MethodGet {
t.Errorf("expected GET, got %s", r.Method)
}
if r.URL.Path != "/api/v1/admin/quota/groups" {
t.Errorf("wrong path: %s", r.URL.Path)
}
json.NewEncoder(w).Encode([]types.QuotaGroup{
{Name: "default"},
{Name: "premium"},
})
}))
defer srv.Close()
f := NewForge(srv.URL, "tok")
var got []string
for group, err := range f.Admin.IterQuotaGroups(context.Background()) {
if err != nil {
t.Fatal(err)
}
got = append(got, group.Name)
}
if requests != 1 {
t.Fatalf("expected 1 request, got %d", requests)
}
if len(got) != 2 || got[0] != "default" || got[1] != "premium" {
t.Fatalf("got %#v", got)
}
}
func TestAdminService_CreateQuotaGroup_Good(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
@ -606,6 +639,39 @@ func TestAdminService_ListQuotaRules_Good(t *testing.T) {
}
}
func TestAdminService_IterQuotaRules_Good(t *testing.T) {
var requests int
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
requests++
if r.Method != http.MethodGet {
t.Errorf("expected GET, got %s", r.Method)
}
if r.URL.Path != "/api/v1/admin/quota/rules" {
t.Errorf("wrong path: %s", r.URL.Path)
}
json.NewEncoder(w).Encode([]types.QuotaRuleInfo{
{Name: "git"},
{Name: "artifacts"},
})
}))
defer srv.Close()
f := NewForge(srv.URL, "tok")
var got []string
for rule, err := range f.Admin.IterQuotaRules(context.Background()) {
if err != nil {
t.Fatal(err)
}
got = append(got, rule.Name)
}
if requests != 1 {
t.Fatalf("expected 1 request, got %d", requests)
}
if len(got) != 2 || got[0] != "git" || got[1] != "artifacts" {
t.Fatalf("got %#v", got)
}
}
func TestAdminService_CreateQuotaRule_Good(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {

View file

@ -94,6 +94,22 @@ func (s *CommitService) ListStatuses(ctx context.Context, owner, repo, ref strin
return out, nil
}
// IterStatuses returns an iterator over all commit statuses for a given ref.
func (s *CommitService) IterStatuses(ctx context.Context, owner, repo, ref string) iter.Seq2[types.CommitStatus, error] {
return func(yield func(types.CommitStatus, error) bool) {
statuses, err := s.ListStatuses(ctx, owner, repo, ref)
if err != nil {
yield(*new(types.CommitStatus), err)
return
}
for _, status := range statuses {
if !yield(status, nil) {
return
}
}
}
}
// CreateStatus creates a new commit status for the given SHA.
func (s *CommitService) CreateStatus(ctx context.Context, owner, repo, sha string, opts *types.CreateStatusOption) (*types.CommitStatus, error) {
path := ResolvePath("/api/v1/repos/{owner}/{repo}/statuses/{sha}", pathParams("owner", owner, "repo", repo, "sha", sha))

View file

@ -185,6 +185,39 @@ func TestCommitService_ListStatuses_Good(t *testing.T) {
}
}
func TestCommitService_IterStatuses_Good(t *testing.T) {
var requests int
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
requests++
if r.Method != http.MethodGet {
t.Errorf("expected GET, got %s", r.Method)
}
if r.URL.Path != "/api/v1/repos/core/go-forge/commits/abc123/statuses" {
t.Errorf("wrong path: %s", r.URL.Path)
}
json.NewEncoder(w).Encode([]types.CommitStatus{
{ID: 1, Context: "ci/build"},
{ID: 2, Context: "ci/test"},
})
}))
defer srv.Close()
f := NewForge(srv.URL, "tok")
var got []string
for status, err := range f.Commits.IterStatuses(context.Background(), "core", "go-forge", "abc123") {
if err != nil {
t.Fatal(err)
}
got = append(got, status.Context)
}
if requests != 1 {
t.Fatalf("expected 1 request, got %d", requests)
}
if len(got) != 2 || got[0] != "ci/build" || got[1] != "ci/test" {
t.Fatalf("got %#v", got)
}
}
func TestCommitService_CreateStatus_Good(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {

View file

@ -497,6 +497,22 @@ func (s *RepoService) ListIssueTemplates(ctx context.Context, owner, repo string
return ListAll[types.IssueTemplate](ctx, s.client, path, nil)
}
// IterIssueTemplates returns an iterator over all issue templates available for a repository.
func (s *RepoService) IterIssueTemplates(ctx context.Context, owner, repo string) iter.Seq2[types.IssueTemplate, error] {
return func(yield func(types.IssueTemplate, error) bool) {
templates, err := s.ListIssueTemplates(ctx, owner, repo)
if err != nil {
yield(*new(types.IssueTemplate), err)
return
}
for _, template := range templates {
if !yield(template, nil) {
return
}
}
}
}
// GetIssueConfig returns the issue config for a repository.
func (s *RepoService) GetIssueConfig(ctx context.Context, owner, repo string) (*types.IssueConfig, error) {
path := ResolvePath("/api/v1/repos/{owner}/{repo}/issue_config", pathParams("owner", owner, "repo", repo))

View file

@ -1009,6 +1009,49 @@ func TestRepoService_ListIssueTemplates_Good(t *testing.T) {
}
}
func TestRepoService_IterIssueTemplates_Good(t *testing.T) {
var requests int
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
requests++
if r.Method != http.MethodGet {
t.Errorf("expected GET, got %s", r.Method)
}
if r.URL.Path != "/api/v1/repos/core/go-forge/issue_templates" {
t.Errorf("wrong path: %s", r.URL.Path)
http.NotFound(w, r)
return
}
json.NewEncoder(w).Encode([]types.IssueTemplate{
{
Name: "bug report",
Title: "Bug report",
Content: "Describe the problem",
},
{
Name: "feature request",
Title: "Feature request",
Content: "Describe the idea",
},
})
}))
defer srv.Close()
f := NewForge(srv.URL, "tok")
var got []string
for template, err := range f.Repos.IterIssueTemplates(context.Background(), "core", "go-forge") {
if err != nil {
t.Fatal(err)
}
got = append(got, template.Name)
}
if requests != 1 {
t.Fatalf("expected 1 request, got %d", requests)
}
if len(got) != 2 || got[0] != "bug report" || got[1] != "feature request" {
t.Fatalf("got %#v", got)
}
}
func TestRepoService_GetIssueConfig_Good(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {