diff --git a/branches.go b/branches.go index b7d4fd1..305fadc 100644 --- a/branches.go +++ b/branches.go @@ -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)) diff --git a/branches_extra_test.go b/branches_extra_test.go new file mode 100644 index 0000000..ce1bbfb --- /dev/null +++ b/branches_extra_test.go @@ -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) + } +} diff --git a/commits.go b/commits.go index a7fd518..38631fc 100644 --- a/commits.go +++ b/commits.go @@ -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)) diff --git a/commits_extra_test.go b/commits_extra_test.go new file mode 100644 index 0000000..379124b --- /dev/null +++ b/commits_extra_test.go @@ -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) + } +} diff --git a/issues.go b/issues.go index b993ebe..450b0eb 100644 --- a/issues.go +++ b/issues.go @@ -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))) diff --git a/issues_extra_test.go b/issues_extra_test.go new file mode 100644 index 0000000..27d942d --- /dev/null +++ b/issues_extra_test.go @@ -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) + } +} diff --git a/orgs.go b/orgs.go index 20b732a..d963fe4 100644 --- a/orgs.go +++ b/orgs.go @@ -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)) diff --git a/orgs_extra_test.go b/orgs_extra_test.go new file mode 100644 index 0000000..348333b --- /dev/null +++ b/orgs_extra_test.go @@ -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) + } +} diff --git a/pulls.go b/pulls.go index ca5f865..895d9b3 100644 --- a/pulls.go +++ b/pulls.go @@ -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))) diff --git a/pulls_extra_test.go b/pulls_extra_test.go new file mode 100644 index 0000000..473b2f9 --- /dev/null +++ b/pulls_extra_test.go @@ -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) + } +} diff --git a/releases.go b/releases.go index 48a8176..872aabb 100644 --- a/releases.go +++ b/releases.go @@ -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) } diff --git a/releases_extra_test.go b/releases_extra_test.go new file mode 100644 index 0000000..9b58159 --- /dev/null +++ b/releases_extra_test.go @@ -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) + } +} diff --git a/users.go b/users.go index a097792..a9d3609 100644 --- a/users.go +++ b/users.go @@ -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) diff --git a/users_extra_test.go b/users_extra_test.go new file mode 100644 index 0000000..bcd80a3 --- /dev/null +++ b/users_extra_test.go @@ -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) + } +} diff --git a/webhooks.go b/webhooks.go index a700ea3..bdd0818 100644 --- a/webhooks.go +++ b/webhooks.go @@ -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))) diff --git a/webhooks_extra_test.go b/webhooks_extra_test.go new file mode 100644 index 0000000..b250582 --- /dev/null +++ b/webhooks_extra_test.go @@ -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) + } +}