feat(milestones): add list and iterator helpers
All checks were successful
Security Scan / security (push) Successful in 10s
Test / test (push) Successful in 56s

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Virgil 2026-04-01 09:10:05 +00:00
parent 8e238c79de
commit c1f3ff5bca
2 changed files with 94 additions and 0 deletions

View file

@ -2,6 +2,7 @@ package forge
import (
"context"
"iter"
"dappco.re/go/core/forge/types"
)
@ -20,6 +21,18 @@ func newMilestoneService(c *Client) *MilestoneService {
return &MilestoneService{client: c}
}
// List returns a single page of milestones for a repository.
func (s *MilestoneService) List(ctx context.Context, params Params, opts ListOptions) (*PagedResult[types.Milestone], error) {
path := ResolvePath("/api/v1/repos/{owner}/{repo}/milestones", params)
return ListPage[types.Milestone](ctx, s.client, path, nil, opts)
}
// Iter returns an iterator over all milestones for a repository.
func (s *MilestoneService) Iter(ctx context.Context, params Params) iter.Seq2[types.Milestone, error] {
path := ResolvePath("/api/v1/repos/{owner}/{repo}/milestones", params)
return ListIter[types.Milestone](ctx, s.client, path, nil)
}
// ListAll returns all milestones for a repository.
func (s *MilestoneService) ListAll(ctx context.Context, params Params) ([]types.Milestone, error) {
path := ResolvePath("/api/v1/repos/{owner}/{repo}/milestones", params)

View file

@ -5,11 +5,92 @@ import (
json "github.com/goccy/go-json"
"net/http"
"net/http/httptest"
"reflect"
"testing"
"dappco.re/go/core/forge/types"
)
func TestMilestoneService_List_Good(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
t.Errorf("expected GET, got %s", r.Method)
}
if r.URL.Path != "/api/v1/repos/core/go-forge/milestones" {
t.Errorf("wrong path: %s", r.URL.Path)
}
if got := r.URL.Query().Get("page"); got != "1" {
t.Errorf("got page=%q, want %q", got, "1")
}
if got := r.URL.Query().Get("limit"); got != "1" {
t.Errorf("got limit=%q, want %q", got, "1")
}
w.Header().Set("X-Total-Count", "2")
json.NewEncoder(w).Encode([]types.Milestone{{ID: 2, Title: "v2.0"}})
}))
defer srv.Close()
f := NewForge(srv.URL, "tok")
page, err := f.Milestones.List(context.Background(), Params{"owner": "core", "repo": "go-forge"}, ListOptions{Page: 1, Limit: 1})
if err != nil {
t.Fatal(err)
}
if page.Page != 1 {
t.Errorf("got page=%d, want 1", page.Page)
}
if page.TotalCount != 2 {
t.Errorf("got total=%d, want 2", page.TotalCount)
}
if !page.HasMore {
t.Error("expected HasMore=true")
}
if len(page.Items) != 1 || page.Items[0].Title != "v2.0" {
t.Fatalf("unexpected items: %+v", page.Items)
}
}
func TestMilestoneService_Iter_Good(t *testing.T) {
requests := 0
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/milestones" {
t.Errorf("wrong path: %s", r.URL.Path)
}
switch requests {
case 1:
if got := r.URL.Query().Get("page"); got != "1" {
t.Errorf("got page=%q, want %q", got, "1")
}
w.Header().Set("X-Total-Count", "2")
json.NewEncoder(w).Encode([]types.Milestone{{ID: 1, Title: "v1.0"}})
case 2:
if got := r.URL.Query().Get("page"); got != "2" {
t.Errorf("got page=%q, want %q", got, "2")
}
w.Header().Set("X-Total-Count", "2")
json.NewEncoder(w).Encode([]types.Milestone{{ID: 2, Title: "v2.0"}})
default:
t.Fatalf("unexpected request %d", requests)
}
}))
defer srv.Close()
f := NewForge(srv.URL, "tok")
var got []string
for milestone, err := range f.Milestones.Iter(context.Background(), Params{"owner": "core", "repo": "go-forge"}) {
if err != nil {
t.Fatal(err)
}
got = append(got, milestone.Title)
}
if !reflect.DeepEqual(got, []string{"v1.0", "v2.0"}) {
t.Fatalf("got %v", got)
}
}
func TestMilestoneService_ListAll_Good(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {