feat: add milestone list filters
All checks were successful
Security Scan / security (push) Successful in 11s
Test / test (push) Successful in 1m29s

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Virgil 2026-04-02 00:30:36 +00:00
parent cb54c357e1
commit b9962f2412
3 changed files with 87 additions and 7 deletions

View file

@ -138,7 +138,7 @@ Coverage notes: rows list direct tests when a symbol is named in test names or r
| method | LabelService.ListRepoLabels | `func (s *LabelService) ListRepoLabels(ctx context.Context, owner, repo string) ([]types.Label, error)` | ListRepoLabels returns all labels for a repository. | `TestLabelService_Good_ListRepoLabels` |
| method | MilestoneService.Create | `func (s *MilestoneService) Create(ctx context.Context, owner, repo string, opts *types.CreateMilestoneOption) (*types.Milestone, error)` | Create creates a new milestone. | No direct tests. |
| method | MilestoneService.Get | `func (s *MilestoneService) Get(ctx context.Context, owner, repo string, id int64) (*types.Milestone, error)` | Get returns a single milestone by ID. | No direct tests. |
| method | MilestoneService.ListAll | `func (s *MilestoneService) ListAll(ctx context.Context, params Params) ([]types.Milestone, error)` | ListAll returns all milestones for a repository. | No direct tests. |
| method | MilestoneService.ListAll | `func (s *MilestoneService) ListAll(ctx context.Context, params Params, filters ...MilestoneListOptions) ([]types.Milestone, error)` | ListAll returns all milestones for a repository. | No direct tests. |
| method | MiscService.GetGitignoreTemplate | `func (s *MiscService) GetGitignoreTemplate(ctx context.Context, name string) (*types.GitignoreTemplateInfo, error)` | GetGitignoreTemplate returns a single gitignore template by name. | `TestMiscService_Good_GetGitignoreTemplate` |
| method | MiscService.GetAPISettings | `func (s *MiscService) GetAPISettings(ctx context.Context) (*types.GeneralAPISettings, error)` | GetAPISettings returns the instance's global API settings. | `TestMiscService_GetAPISettings_Good` |
| method | MiscService.GetAttachmentSettings | `func (s *MiscService) GetAttachmentSettings(ctx context.Context) (*types.GeneralAttachmentSettings, error)` | GetAttachmentSettings returns the instance's global attachment settings. | `TestMiscService_GetAttachmentSettings_Good` |

View file

@ -7,6 +7,26 @@ import (
"dappco.re/go/core/forge/types"
)
// MilestoneListOptions controls filtering for repository milestone listings.
type MilestoneListOptions struct {
State string
Name string
}
func (o MilestoneListOptions) queryParams() map[string]string {
query := make(map[string]string, 2)
if o.State != "" {
query["state"] = o.State
}
if o.Name != "" {
query["name"] = o.Name
}
if len(query) == 0 {
return nil
}
return query
}
// MilestoneService handles repository milestones.
//
// Usage:
@ -22,21 +42,21 @@ func newMilestoneService(c *Client) *MilestoneService {
}
// 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) {
func (s *MilestoneService) List(ctx context.Context, params Params, opts ListOptions, filters ...MilestoneListOptions) (*PagedResult[types.Milestone], error) {
path := ResolvePath("/api/v1/repos/{owner}/{repo}/milestones", params)
return ListPage[types.Milestone](ctx, s.client, path, nil, opts)
return ListPage[types.Milestone](ctx, s.client, path, milestoneQuery(filters...), 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] {
func (s *MilestoneService) Iter(ctx context.Context, params Params, filters ...MilestoneListOptions) iter.Seq2[types.Milestone, error] {
path := ResolvePath("/api/v1/repos/{owner}/{repo}/milestones", params)
return ListIter[types.Milestone](ctx, s.client, path, nil)
return ListIter[types.Milestone](ctx, s.client, path, milestoneQuery(filters...))
}
// ListAll returns all milestones for a repository.
func (s *MilestoneService) ListAll(ctx context.Context, params Params) ([]types.Milestone, error) {
func (s *MilestoneService) ListAll(ctx context.Context, params Params, filters ...MilestoneListOptions) ([]types.Milestone, error) {
path := ResolvePath("/api/v1/repos/{owner}/{repo}/milestones", params)
return ListAll[types.Milestone](ctx, s.client, path, nil)
return ListAll[types.Milestone](ctx, s.client, path, milestoneQuery(filters...))
}
// Get returns a single milestone by ID.
@ -74,3 +94,23 @@ func (s *MilestoneService) Delete(ctx context.Context, owner, repo string, id in
path := ResolvePath("/api/v1/repos/{owner}/{repo}/milestones/{id}", pathParams("owner", owner, "repo", repo, "id", int64String(id)))
return s.client.Delete(ctx, path)
}
func milestoneQuery(filters ...MilestoneListOptions) map[string]string {
if len(filters) == 0 {
return nil
}
query := make(map[string]string, 2)
for _, filter := range filters {
if filter.State != "" {
query["state"] = filter.State
}
if filter.Name != "" {
query["name"] = filter.Name
}
}
if len(query) == 0 {
return nil
}
return query
}

View file

@ -49,6 +49,46 @@ func TestMilestoneService_List_Good(t *testing.T) {
}
}
func TestMilestoneService_ListWithFilters_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("state"); got != "all" {
t.Errorf("got state=%q, want %q", got, "all")
}
if got := r.URL.Query().Get("name"); got != "v1.0" {
t.Errorf("got name=%q, want %q", got, "v1.0")
}
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", "1")
json.NewEncoder(w).Encode([]types.Milestone{{ID: 1, Title: "v1.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},
MilestoneListOptions{State: "all", Name: "v1.0"},
)
if err != nil {
t.Fatal(err)
}
if len(page.Items) != 1 || page.Items[0].Title != "v1.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) {