feat(forge): add missing collection wrappers
Some checks failed
Security Scan / security (push) Successful in 17s
Test / test (push) Has been cancelled

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Virgil 2026-04-02 06:57:47 +00:00
parent 597f4e0bb8
commit 49daff2cb6
16 changed files with 611 additions and 3 deletions

View file

@ -25,6 +25,27 @@ func newBranchService(c *Client) *BranchService {
}
}
// ListBranches returns all branches for a repository.
func (s *BranchService) ListBranches(ctx context.Context, owner, repo string) ([]types.Branch, error) {
path := ResolvePath("/api/v1/repos/{owner}/{repo}/branches", pathParams("owner", owner, "repo", repo))
return ListAll[types.Branch](ctx, s.client, path, nil)
}
// IterBranches returns an iterator over all branches for a repository.
func (s *BranchService) IterBranches(ctx context.Context, owner, repo string) iter.Seq2[types.Branch, error] {
path := ResolvePath("/api/v1/repos/{owner}/{repo}/branches", pathParams("owner", owner, "repo", repo))
return ListIter[types.Branch](ctx, s.client, path, nil)
}
// CreateBranch creates a new branch in a repository.
func (s *BranchService) CreateBranch(ctx context.Context, owner, repo string, opts *types.CreateBranchRepoOption) (*types.Branch, error) {
var out types.Branch
if err := s.client.Post(ctx, ResolvePath("/api/v1/repos/{owner}/{repo}/branches", pathParams("owner", owner, "repo", repo)), opts, &out); err != nil {
return nil, err
}
return &out, nil
}
// ListBranchProtections returns all branch protections for a repository.
func (s *BranchService) ListBranchProtections(ctx context.Context, owner, repo string) ([]types.BranchProtection, error) {
path := ResolvePath("/api/v1/repos/{owner}/{repo}/branch_protections", pathParams("owner", owner, "repo", repo))

66
branches_extra_test.go Normal file
View file

@ -0,0 +1,66 @@
package forge
import (
"context"
json "github.com/goccy/go-json"
"net/http"
"net/http/httptest"
"testing"
"dappco.re/go/core/forge/types"
)
func TestBranchService_ListBranches_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/branches" {
t.Errorf("wrong path: %s", r.URL.Path)
}
w.Header().Set("X-Total-Count", "1")
json.NewEncoder(w).Encode([]types.Branch{{Name: "main"}})
}))
defer srv.Close()
f := NewForge(srv.URL, "tok")
branches, err := f.Branches.ListBranches(context.Background(), "core", "go-forge")
if err != nil {
t.Fatal(err)
}
if len(branches) != 1 || branches[0].Name != "main" {
t.Fatalf("got %#v", branches)
}
}
func TestBranchService_CreateBranch_Good(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
t.Errorf("expected POST, got %s", r.Method)
}
if r.URL.Path != "/api/v1/repos/core/go-forge/branches" {
t.Errorf("wrong path: %s", r.URL.Path)
}
var body types.CreateBranchRepoOption
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
t.Fatal(err)
}
if body.BranchName != "release/v1" {
t.Fatalf("unexpected body: %+v", body)
}
json.NewEncoder(w).Encode(types.Branch{Name: body.BranchName})
}))
defer srv.Close()
f := NewForge(srv.URL, "tok")
branch, err := f.Branches.CreateBranch(context.Background(), "core", "go-forge", &types.CreateBranchRepoOption{
BranchName: "release/v1",
OldRefName: "main",
})
if err != nil {
t.Fatal(err)
}
if branch.Name != "release/v1" {
t.Fatalf("got name=%q", branch.Name)
}
}

View file

@ -84,6 +84,16 @@ func (s *CommitService) GetCombinedStatus(ctx context.Context, owner, repo, ref
return &out, nil
}
// GetCombinedStatusByRef returns the combined status for a given commit reference.
func (s *CommitService) GetCombinedStatusByRef(ctx context.Context, owner, repo, ref string) (*types.CombinedStatus, error) {
path := ResolvePath("/api/v1/repos/{owner}/{repo}/commits/{ref}/status", pathParams("owner", owner, "repo", repo, "ref", ref))
var out types.CombinedStatus
if err := s.client.Get(ctx, path, &out); err != nil {
return nil, err
}
return &out, nil
}
// ListStatuses returns all commit statuses for a given ref.
func (s *CommitService) ListStatuses(ctx context.Context, owner, repo, ref string) ([]types.CommitStatus, error) {
path := ResolvePath("/api/v1/repos/{owner}/{repo}/commits/{ref}/statuses", pathParams("owner", owner, "repo", repo, "ref", ref))

36
commits_extra_test.go Normal file
View file

@ -0,0 +1,36 @@
package forge
import (
"context"
json "github.com/goccy/go-json"
"net/http"
"net/http/httptest"
"testing"
"dappco.re/go/core/forge/types"
)
func TestCommitService_GetCombinedStatusByRef_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/commits/main/status" {
t.Errorf("wrong path: %s", r.URL.Path)
}
json.NewEncoder(w).Encode(types.CombinedStatus{
SHA: "main",
TotalCount: 3,
})
}))
defer srv.Close()
f := NewForge(srv.URL, "tok")
status, err := f.Commits.GetCombinedStatusByRef(context.Background(), "core", "go-forge", "main")
if err != nil {
t.Fatal(err)
}
if status.SHA != "main" || status.TotalCount != 3 {
t.Fatalf("got %#v", status)
}
}

View file

@ -142,6 +142,27 @@ func (s *IssueService) IterSearchIssues(ctx context.Context, opts SearchIssuesOp
return ListIter[types.Issue](ctx, s.client, "/api/v1/repos/issues/search", opts.queryParams())
}
// ListIssues returns all issues in a repository.
func (s *IssueService) ListIssues(ctx context.Context, owner, repo string) ([]types.Issue, error) {
path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues", pathParams("owner", owner, "repo", repo))
return ListAll[types.Issue](ctx, s.client, path, nil)
}
// IterIssues returns an iterator over all issues in a repository.
func (s *IssueService) IterIssues(ctx context.Context, owner, repo string) iter.Seq2[types.Issue, error] {
path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues", pathParams("owner", owner, "repo", repo))
return ListIter[types.Issue](ctx, s.client, path, nil)
}
// CreateIssue creates a new issue in a repository.
func (s *IssueService) CreateIssue(ctx context.Context, owner, repo string, opts *types.CreateIssueOption) (*types.Issue, error) {
var out types.Issue
if err := s.client.Post(ctx, ResolvePath("/api/v1/repos/{owner}/{repo}/issues", pathParams("owner", owner, "repo", repo)), opts, &out); err != nil {
return nil, err
}
return &out, nil
}
// Pin pins an issue.
func (s *IssueService) Pin(ctx context.Context, owner, repo string, index int64) error {
path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}/pin", pathParams("owner", owner, "repo", repo, "index", int64String(index)))

63
issues_extra_test.go Normal file
View file

@ -0,0 +1,63 @@
package forge
import (
"context"
json "github.com/goccy/go-json"
"net/http"
"net/http/httptest"
"testing"
"dappco.re/go/core/forge/types"
)
func TestIssueService_ListIssues_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/issues" {
t.Errorf("wrong path: %s", r.URL.Path)
}
w.Header().Set("X-Total-Count", "1")
json.NewEncoder(w).Encode([]types.Issue{{ID: 1, Title: "bug"}})
}))
defer srv.Close()
f := NewForge(srv.URL, "tok")
issues, err := f.Issues.ListIssues(context.Background(), "core", "go-forge")
if err != nil {
t.Fatal(err)
}
if len(issues) != 1 || issues[0].Title != "bug" {
t.Fatalf("got %#v", issues)
}
}
func TestIssueService_CreateIssue_Good(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
t.Errorf("expected POST, got %s", r.Method)
}
if r.URL.Path != "/api/v1/repos/core/go-forge/issues" {
t.Errorf("wrong path: %s", r.URL.Path)
}
var body types.CreateIssueOption
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
t.Fatal(err)
}
if body.Title != "new issue" {
t.Fatalf("unexpected body: %+v", body)
}
json.NewEncoder(w).Encode(types.Issue{ID: 1, Index: 1, Title: body.Title})
}))
defer srv.Close()
f := NewForge(srv.URL, "tok")
issue, err := f.Issues.CreateIssue(context.Background(), "core", "go-forge", &types.CreateIssueOption{Title: "new issue"})
if err != nil {
t.Fatal(err)
}
if issue.Title != "new issue" {
t.Fatalf("got title=%q", issue.Title)
}
}

19
orgs.go
View file

@ -41,6 +41,25 @@ func newOrgService(c *Client) *OrgService {
}
}
// ListOrgs returns all organisations.
func (s *OrgService) ListOrgs(ctx context.Context) ([]types.Organization, error) {
return ListAll[types.Organization](ctx, s.client, "/api/v1/orgs", nil)
}
// IterOrgs returns an iterator over all organisations.
func (s *OrgService) IterOrgs(ctx context.Context) iter.Seq2[types.Organization, error] {
return ListIter[types.Organization](ctx, s.client, "/api/v1/orgs", nil)
}
// CreateOrg creates a new organisation.
func (s *OrgService) CreateOrg(ctx context.Context, opts *types.CreateOrgOption) (*types.Organization, error) {
var out types.Organization
if err := s.client.Post(ctx, "/api/v1/orgs", opts, &out); err != nil {
return nil, err
}
return &out, nil
}
// ListMembers returns all members of an organisation.
func (s *OrgService) ListMembers(ctx context.Context, org string) ([]types.User, error) {
path := ResolvePath("/api/v1/orgs/{org}/members", pathParams("org", org))

63
orgs_extra_test.go Normal file
View file

@ -0,0 +1,63 @@
package forge
import (
"context"
json "github.com/goccy/go-json"
"net/http"
"net/http/httptest"
"testing"
"dappco.re/go/core/forge/types"
)
func TestOrgService_ListOrgs_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/orgs" {
t.Errorf("wrong path: %s", r.URL.Path)
}
w.Header().Set("X-Total-Count", "1")
json.NewEncoder(w).Encode([]types.Organization{{ID: 1, Name: "core"}})
}))
defer srv.Close()
f := NewForge(srv.URL, "tok")
orgs, err := f.Orgs.ListOrgs(context.Background())
if err != nil {
t.Fatal(err)
}
if len(orgs) != 1 || orgs[0].Name != "core" {
t.Fatalf("got %#v", orgs)
}
}
func TestOrgService_CreateOrg_Good(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
t.Errorf("expected POST, got %s", r.Method)
}
if r.URL.Path != "/api/v1/orgs" {
t.Errorf("wrong path: %s", r.URL.Path)
}
var body types.CreateOrgOption
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
t.Fatal(err)
}
if body.UserName != "core" {
t.Fatalf("unexpected body: %+v", body)
}
json.NewEncoder(w).Encode(types.Organization{ID: 1, Name: body.UserName})
}))
defer srv.Close()
f := NewForge(srv.URL, "tok")
org, err := f.Orgs.CreateOrg(context.Background(), &types.CreateOrgOption{UserName: "core"})
if err != nil {
t.Fatal(err)
}
if org.Name != "core" {
t.Fatalf("got name=%q", org.Name)
}
}

View file

@ -25,6 +25,27 @@ func newPullService(c *Client) *PullService {
}
}
// ListPullRequests returns all pull requests in a repository.
func (s *PullService) ListPullRequests(ctx context.Context, owner, repo string) ([]types.PullRequest, error) {
path := ResolvePath("/api/v1/repos/{owner}/{repo}/pulls", pathParams("owner", owner, "repo", repo))
return ListAll[types.PullRequest](ctx, s.client, path, nil)
}
// IterPullRequests returns an iterator over all pull requests in a repository.
func (s *PullService) IterPullRequests(ctx context.Context, owner, repo string) iter.Seq2[types.PullRequest, error] {
path := ResolvePath("/api/v1/repos/{owner}/{repo}/pulls", pathParams("owner", owner, "repo", repo))
return ListIter[types.PullRequest](ctx, s.client, path, nil)
}
// CreatePullRequest creates a pull request in a repository.
func (s *PullService) CreatePullRequest(ctx context.Context, owner, repo string, opts *types.CreatePullRequestOption) (*types.PullRequest, error) {
var out types.PullRequest
if err := s.client.Post(ctx, ResolvePath("/api/v1/repos/{owner}/{repo}/pulls", pathParams("owner", owner, "repo", repo)), opts, &out); err != nil {
return nil, err
}
return &out, nil
}
// Merge merges a pull request. Method is one of "merge", "rebase", "rebase-merge", "squash", "fast-forward-only", "manually-merged".
func (s *PullService) Merge(ctx context.Context, owner, repo string, index int64, method string) error {
path := ResolvePath("/api/v1/repos/{owner}/{repo}/pulls/{index}/merge", pathParams("owner", owner, "repo", repo, "index", int64String(index)))

67
pulls_extra_test.go Normal file
View file

@ -0,0 +1,67 @@
package forge
import (
"context"
json "github.com/goccy/go-json"
"net/http"
"net/http/httptest"
"testing"
"dappco.re/go/core/forge/types"
)
func TestPullService_ListPullRequests_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/pulls" {
t.Errorf("wrong path: %s", r.URL.Path)
}
w.Header().Set("X-Total-Count", "1")
json.NewEncoder(w).Encode([]types.PullRequest{{ID: 1, Title: "add feature"}})
}))
defer srv.Close()
f := NewForge(srv.URL, "tok")
prs, err := f.Pulls.ListPullRequests(context.Background(), "core", "go-forge")
if err != nil {
t.Fatal(err)
}
if len(prs) != 1 || prs[0].Title != "add feature" {
t.Fatalf("got %#v", prs)
}
}
func TestPullService_CreatePullRequest_Good(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
t.Errorf("expected POST, got %s", r.Method)
}
if r.URL.Path != "/api/v1/repos/core/go-forge/pulls" {
t.Errorf("wrong path: %s", r.URL.Path)
}
var body types.CreatePullRequestOption
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
t.Fatal(err)
}
if body.Title != "add feature" {
t.Fatalf("unexpected body: %+v", body)
}
json.NewEncoder(w).Encode(types.PullRequest{ID: 1, Title: body.Title, Index: 1})
}))
defer srv.Close()
f := NewForge(srv.URL, "tok")
pr, err := f.Pulls.CreatePullRequest(context.Background(), "core", "go-forge", &types.CreatePullRequestOption{
Title: "add feature",
Base: "main",
Head: "feature",
})
if err != nil {
t.Fatal(err)
}
if pr.Title != "add feature" {
t.Fatalf("got title=%q", pr.Title)
}
}

View file

@ -44,6 +44,27 @@ func newReleaseService(c *Client) *ReleaseService {
}
}
// ListReleases returns all releases in a repository.
func (s *ReleaseService) ListReleases(ctx context.Context, owner, repo string) ([]types.Release, error) {
path := ResolvePath("/api/v1/repos/{owner}/{repo}/releases", pathParams("owner", owner, "repo", repo))
return ListAll[types.Release](ctx, s.client, path, nil)
}
// IterReleases returns an iterator over all releases in a repository.
func (s *ReleaseService) IterReleases(ctx context.Context, owner, repo string) iter.Seq2[types.Release, error] {
path := ResolvePath("/api/v1/repos/{owner}/{repo}/releases", pathParams("owner", owner, "repo", repo))
return ListIter[types.Release](ctx, s.client, path, nil)
}
// CreateRelease creates a release in a repository.
func (s *ReleaseService) CreateRelease(ctx context.Context, owner, repo string, opts *types.CreateReleaseOption) (*types.Release, error) {
var out types.Release
if err := s.client.Post(ctx, ResolvePath("/api/v1/repos/{owner}/{repo}/releases", pathParams("owner", owner, "repo", repo)), opts, &out); err != nil {
return nil, err
}
return &out, nil
}
// GetByTag returns a release by its tag name.
func (s *ReleaseService) GetByTag(ctx context.Context, owner, repo, tag string) (*types.Release, error) {
path := ResolvePath("/api/v1/repos/{owner}/{repo}/releases/tags/{tag}", pathParams("owner", owner, "repo", repo, "tag", tag))
@ -72,7 +93,7 @@ func (s *ReleaseService) DeleteByTag(ctx context.Context, owner, repo, tag strin
// ListAssets returns all assets for a release.
func (s *ReleaseService) ListAssets(ctx context.Context, owner, repo string, releaseID int64) ([]types.Attachment, error) {
path := ResolvePath("/api/v1/repos/{owner}/{repo}/releases/{releaseID}/assets", pathParams("owner", owner, "repo", repo, "releaseID", int64String(releaseID)))
path := ResolvePath("/api/v1/repos/{owner}/{repo}/releases/{id}/assets", pathParams("owner", owner, "repo", repo, "id", int64String(releaseID)))
return ListAll[types.Attachment](ctx, s.client, path, nil)
}
@ -81,7 +102,7 @@ func (s *ReleaseService) ListAssets(ctx context.Context, owner, repo string, rel
// If opts.ExternalURL is set, the upload uses the external_url form field and
// ignores filename/content.
func (s *ReleaseService) CreateAttachment(ctx context.Context, owner, repo string, releaseID int64, opts *ReleaseAttachmentUploadOptions, filename string, content goio.Reader) (*types.Attachment, error) {
path := ResolvePath("/api/v1/repos/{owner}/{repo}/releases/{releaseID}/assets", pathParams("owner", owner, "repo", repo, "releaseID", int64String(releaseID)))
path := ResolvePath("/api/v1/repos/{owner}/{repo}/releases/{id}/assets", pathParams("owner", owner, "repo", repo, "id", int64String(releaseID)))
fields := make(map[string]string, 1)
fieldName := "attachment"
if opts != nil && opts.ExternalURL != "" {
@ -119,7 +140,7 @@ func (s *ReleaseService) EditAsset(ctx context.Context, owner, repo string, rele
// IterAssets returns an iterator over all assets for a release.
func (s *ReleaseService) IterAssets(ctx context.Context, owner, repo string, releaseID int64) iter.Seq2[types.Attachment, error] {
path := ResolvePath("/api/v1/repos/{owner}/{repo}/releases/{releaseID}/assets", pathParams("owner", owner, "repo", repo, "releaseID", int64String(releaseID)))
path := ResolvePath("/api/v1/repos/{owner}/{repo}/releases/{id}/assets", pathParams("owner", owner, "repo", repo, "id", int64String(releaseID)))
return ListIter[types.Attachment](ctx, s.client, path, nil)
}

66
releases_extra_test.go Normal file
View file

@ -0,0 +1,66 @@
package forge
import (
"context"
json "github.com/goccy/go-json"
"net/http"
"net/http/httptest"
"testing"
"dappco.re/go/core/forge/types"
)
func TestReleaseService_ListReleases_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/releases" {
t.Errorf("wrong path: %s", r.URL.Path)
}
w.Header().Set("X-Total-Count", "1")
json.NewEncoder(w).Encode([]types.Release{{ID: 1, TagName: "v1.0.0"}})
}))
defer srv.Close()
f := NewForge(srv.URL, "tok")
releases, err := f.Releases.ListReleases(context.Background(), "core", "go-forge")
if err != nil {
t.Fatal(err)
}
if len(releases) != 1 || releases[0].TagName != "v1.0.0" {
t.Fatalf("got %#v", releases)
}
}
func TestReleaseService_CreateRelease_Good(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
t.Errorf("expected POST, got %s", r.Method)
}
if r.URL.Path != "/api/v1/repos/core/go-forge/releases" {
t.Errorf("wrong path: %s", r.URL.Path)
}
var body types.CreateReleaseOption
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
t.Fatal(err)
}
if body.TagName != "v1.0.0" {
t.Fatalf("unexpected body: %+v", body)
}
json.NewEncoder(w).Encode(types.Release{ID: 1, TagName: body.TagName})
}))
defer srv.Close()
f := NewForge(srv.URL, "tok")
release, err := f.Releases.CreateRelease(context.Background(), "core", "go-forge", &types.CreateReleaseOption{
TagName: "v1.0.0",
Title: "Release 1.0",
})
if err != nil {
t.Fatal(err)
}
if release.TagName != "v1.0.0" {
t.Fatalf("got tag=%q", release.TagName)
}
}

View file

@ -535,6 +535,16 @@ func (s *UserService) IterMyFollowers(ctx context.Context) iter.Seq2[types.User,
return ListIter[types.User](ctx, s.client, "/api/v1/user/followers", nil)
}
// ListMyFollowing returns all users followed by the authenticated user.
func (s *UserService) ListMyFollowing(ctx context.Context) ([]types.User, error) {
return ListAll[types.User](ctx, s.client, "/api/v1/user/following", nil)
}
// IterMyFollowing returns an iterator over all users followed by the authenticated user.
func (s *UserService) IterMyFollowing(ctx context.Context) iter.Seq2[types.User, error] {
return ListIter[types.User](ctx, s.client, "/api/v1/user/following", nil)
}
// ListMyTeams returns all teams the authenticated user belongs to.
func (s *UserService) ListMyTeams(ctx context.Context) ([]types.Team, error) {
return ListAll[types.Team](ctx, s.client, "/api/v1/user/teams", nil)

34
users_extra_test.go Normal file
View file

@ -0,0 +1,34 @@
package forge
import (
"context"
json "github.com/goccy/go-json"
"net/http"
"net/http/httptest"
"testing"
"dappco.re/go/core/forge/types"
)
func TestUserService_ListMyFollowing_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/user/following" {
t.Errorf("wrong path: %s", r.URL.Path)
}
w.Header().Set("X-Total-Count", "1")
json.NewEncoder(w).Encode([]types.User{{ID: 1, UserName: "alice"}})
}))
defer srv.Close()
f := NewForge(srv.URL, "tok")
users, err := f.Users.ListMyFollowing(context.Background())
if err != nil {
t.Fatal(err)
}
if len(users) != 1 || users[0].UserName != "alice" {
t.Fatalf("got %#v", users)
}
}

View file

@ -26,6 +26,27 @@ func newWebhookService(c *Client) *WebhookService {
}
}
// ListHooks returns all webhooks for a repository.
func (s *WebhookService) ListHooks(ctx context.Context, owner, repo string) ([]types.Hook, error) {
path := ResolvePath("/api/v1/repos/{owner}/{repo}/hooks", pathParams("owner", owner, "repo", repo))
return ListAll[types.Hook](ctx, s.client, path, nil)
}
// IterHooks returns an iterator over all webhooks for a repository.
func (s *WebhookService) IterHooks(ctx context.Context, owner, repo string) iter.Seq2[types.Hook, error] {
path := ResolvePath("/api/v1/repos/{owner}/{repo}/hooks", pathParams("owner", owner, "repo", repo))
return ListIter[types.Hook](ctx, s.client, path, nil)
}
// CreateHook creates a webhook for a repository.
func (s *WebhookService) CreateHook(ctx context.Context, owner, repo string, opts *types.CreateHookOption) (*types.Hook, error) {
var out types.Hook
if err := s.client.Post(ctx, ResolvePath("/api/v1/repos/{owner}/{repo}/hooks", pathParams("owner", owner, "repo", repo)), opts, &out); err != nil {
return nil, err
}
return &out, nil
}
// TestHook triggers a test delivery for a webhook.
func (s *WebhookService) TestHook(ctx context.Context, owner, repo string, id int64) error {
path := ResolvePath("/api/v1/repos/{owner}/{repo}/hooks/{id}/tests", pathParams("owner", owner, "repo", repo, "id", int64String(id)))

69
webhooks_extra_test.go Normal file
View file

@ -0,0 +1,69 @@
package forge
import (
"context"
json "github.com/goccy/go-json"
"net/http"
"net/http/httptest"
"testing"
"dappco.re/go/core/forge/types"
)
func TestWebhookService_ListHooks_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/hooks" {
t.Errorf("wrong path: %s", r.URL.Path)
}
w.Header().Set("X-Total-Count", "1")
json.NewEncoder(w).Encode([]types.Hook{{ID: 1, Type: "forgejo"}})
}))
defer srv.Close()
f := NewForge(srv.URL, "tok")
hooks, err := f.Webhooks.ListHooks(context.Background(), "core", "go-forge")
if err != nil {
t.Fatal(err)
}
if len(hooks) != 1 || hooks[0].ID != 1 {
t.Fatalf("got %#v", hooks)
}
}
func TestWebhookService_CreateHook_Good(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
t.Errorf("expected POST, got %s", r.Method)
}
if r.URL.Path != "/api/v1/repos/core/go-forge/hooks" {
t.Errorf("wrong path: %s", r.URL.Path)
}
var body types.CreateHookOption
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
t.Fatal(err)
}
if body.Type != "forgejo" {
t.Fatalf("unexpected body: %+v", body)
}
json.NewEncoder(w).Encode(types.Hook{ID: 1, Type: body.Type})
}))
defer srv.Close()
f := NewForge(srv.URL, "tok")
hook, err := f.Webhooks.CreateHook(context.Background(), "core", "go-forge", &types.CreateHookOption{
Type: "forgejo",
Config: &types.CreateHookOptionConfig{
"content_type": "json",
"url": "https://example.com/hook",
},
})
if err != nil {
t.Fatal(err)
}
if hook.Type != "forgejo" {
t.Fatalf("got type=%q", hook.Type)
}
}