feat(scm): add issue comment iterators
Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
d852087c45
commit
5bb8e61708
7 changed files with 185 additions and 77 deletions
|
|
@ -88,7 +88,7 @@ The `gitea/` package mirrors this using `GITEA_URL`/`GITEA_TOKEN` and `gitea.*`
|
|||
|------|-----------|
|
||||
| `client.go` | `New`, `NewFromConfig`, `GetCurrentUser`, `ForkRepo`, `CreatePullRequest` |
|
||||
| `repos.go` | `ListOrgRepos`, `ListOrgReposIter`, `ListUserRepos`, `ListUserReposIter`, `GetRepo`, `CreateOrgRepo`, `DeleteRepo`, `MigrateRepo` |
|
||||
| `issues.go` | `ListIssues`, `GetIssue`, `CreateIssue`, `EditIssue`, `AssignIssue`, `ListPullRequests`, `ListPullRequestsIter`, `GetPullRequest`, `CreateIssueComment`, `ListIssueComments`, `CloseIssue` |
|
||||
| `issues.go` | `ListIssues`, `ListIssuesIter`, `GetIssue`, `CreateIssue`, `EditIssue`, `AssignIssue`, `ListPullRequests`, `ListPullRequestsIter`, `GetPullRequest`, `CreateIssueComment`, `ListIssueComments`, `ListIssueCommentsIter`, `CloseIssue` |
|
||||
| `labels.go` | `ListOrgLabels`, `ListRepoLabels`, `CreateRepoLabel`, `GetLabelByName`, `EnsureLabel`, `AddIssueLabels`, `RemoveIssueLabel` |
|
||||
| `prs.go` | `MergePullRequest`, `SetPRDraft`, `ListPRReviews`, `GetCombinedStatus`, `DismissReview` |
|
||||
| `webhooks.go` | `CreateRepoWebhook`, `ListRepoWebhooks` |
|
||||
|
|
|
|||
|
|
@ -278,6 +278,32 @@ func (c *Client) ListIssueComments(owner, repo string, number int64) ([]*forgejo
|
|||
return all, nil
|
||||
}
|
||||
|
||||
// ListIssueCommentsIter returns an iterator over comments for an issue.
|
||||
// Usage: ListIssueCommentsIter(...)
|
||||
func (c *Client) ListIssueCommentsIter(owner, repo string, number int64) iter.Seq2[*forgejo.Comment, error] {
|
||||
return func(yield func(*forgejo.Comment, error) bool) {
|
||||
page := 1
|
||||
for {
|
||||
comments, resp, err := c.api.ListIssueComments(owner, repo, number, forgejo.ListIssueCommentOptions{
|
||||
ListOptions: forgejo.ListOptions{Page: page, PageSize: commentPageSize},
|
||||
})
|
||||
if err != nil {
|
||||
yield(nil, log.E("forge.ListIssueComments", "failed to list comments", err))
|
||||
return
|
||||
}
|
||||
for _, comment := range comments {
|
||||
if !yield(comment, nil) {
|
||||
return
|
||||
}
|
||||
}
|
||||
if resp == nil || page >= resp.LastPage {
|
||||
break
|
||||
}
|
||||
page++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CloseIssue closes an issue by setting its state to closed.
|
||||
// Usage: CloseIssue(...)
|
||||
func (c *Client) CloseIssue(owner, repo string, number int64) error {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ package forge
|
|||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
forgejo "codeberg.org/mvdkleijn/forgejo-sdk/forgejo/v2"
|
||||
|
|
@ -41,6 +42,43 @@ func newPaginatedIssuesClient(t *testing.T) (*Client, *httptest.Server) {
|
|||
return client, srv
|
||||
}
|
||||
|
||||
func newPaginatedCommentsClient(t *testing.T) (*Client, *httptest.Server) {
|
||||
t.Helper()
|
||||
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/api/v1/version", func(w http.ResponseWriter, r *http.Request) {
|
||||
jsonResponse(w, map[string]string{"version": "1.21.0"})
|
||||
})
|
||||
mux.HandleFunc("/api/v1/repos/test-org/org-repo/issues/1/comments", func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.Query().Get("page") {
|
||||
case "2":
|
||||
jsonResponse(w, []map[string]any{
|
||||
{"id": 150, "body": "comment 51", "user": map[string]any{"login": "user51"}, "created_at": "2026-01-02T00:00:00Z", "updated_at": "2026-01-02T00:00:00Z"},
|
||||
})
|
||||
case "3":
|
||||
jsonResponse(w, []map[string]any{})
|
||||
default:
|
||||
w.Header().Set("Link", `</api/v1/repos/test-org/org-repo/issues/1/comments?page=2>; rel="next", </api/v1/repos/test-org/org-repo/issues/1/comments?page=2>; rel="last"`)
|
||||
comments := make([]map[string]any, 0, 50)
|
||||
for i := 1; i <= 50; i++ {
|
||||
comments = append(comments, map[string]any{
|
||||
"id": 99 + i,
|
||||
"body": "comment " + strconv.Itoa(i),
|
||||
"user": map[string]any{"login": "user" + strconv.Itoa(i)},
|
||||
"created_at": "2026-01-01T00:00:00Z",
|
||||
"updated_at": "2026-01-01T00:00:00Z",
|
||||
})
|
||||
}
|
||||
jsonResponse(w, comments)
|
||||
}
|
||||
})
|
||||
|
||||
srv := httptest.NewServer(mux)
|
||||
client, err := New(srv.URL, "test-token")
|
||||
require.NoError(t, err)
|
||||
return client, srv
|
||||
}
|
||||
|
||||
func TestClient_ListIssues_Good(t *testing.T) {
|
||||
client, srv := newTestClient(t)
|
||||
defer srv.Close()
|
||||
|
|
@ -293,6 +331,22 @@ func TestClient_ListIssueComments_Bad_ServerError_Good(t *testing.T) {
|
|||
assert.Contains(t, err.Error(), "failed to list comments")
|
||||
}
|
||||
|
||||
func TestClient_ListIssueCommentsIter_Good_Paginates_Good(t *testing.T) {
|
||||
client, srv := newPaginatedCommentsClient(t)
|
||||
defer srv.Close()
|
||||
|
||||
var bodies []string
|
||||
for comment, err := range client.ListIssueCommentsIter("test-org", "org-repo", 1) {
|
||||
require.NoError(t, err)
|
||||
bodies = append(bodies, comment.Body)
|
||||
}
|
||||
|
||||
require.Len(t, bodies, 51)
|
||||
assert.Equal(t, "comment 1", bodies[0])
|
||||
assert.Equal(t, "comment 50", bodies[49])
|
||||
assert.Equal(t, "comment 51", bodies[50])
|
||||
}
|
||||
|
||||
func TestClient_CloseIssue_Good(t *testing.T) {
|
||||
client, srv := newTestClient(t)
|
||||
defer srv.Close()
|
||||
|
|
|
|||
|
|
@ -5,8 +5,6 @@ package forge
|
|||
import (
|
||||
"time"
|
||||
|
||||
forgejo "codeberg.org/mvdkleijn/forgejo-sdk/forgejo/v2"
|
||||
|
||||
"dappco.re/go/core/log"
|
||||
)
|
||||
|
||||
|
|
@ -78,19 +76,11 @@ func (c *Client) GetPRMeta(owner, repo string, pr int64) (*PRMeta, error) {
|
|||
// Fetch comment count from the issue side (PRs are issues in Forgejo).
|
||||
// Paginate to get an accurate count.
|
||||
count := 0
|
||||
page := 1
|
||||
for {
|
||||
comments, _, listErr := c.api.ListIssueComments(owner, repo, pr, forgejo.ListIssueCommentOptions{
|
||||
ListOptions: forgejo.ListOptions{Page: page, PageSize: commentPageSize},
|
||||
})
|
||||
if listErr != nil {
|
||||
for _, err := range c.ListIssueCommentsIter(owner, repo, pr) {
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
count += len(comments)
|
||||
if len(comments) < commentPageSize {
|
||||
break
|
||||
}
|
||||
page++
|
||||
count++
|
||||
}
|
||||
meta.CommentCount = count
|
||||
|
||||
|
|
@ -101,37 +91,21 @@ func (c *Client) GetPRMeta(owner, repo string, pr int64) (*PRMeta, error) {
|
|||
// Usage: GetCommentBodies(...)
|
||||
func (c *Client) GetCommentBodies(owner, repo string, pr int64) ([]Comment, error) {
|
||||
var comments []Comment
|
||||
page := 1
|
||||
|
||||
for {
|
||||
raw, _, err := c.api.ListIssueComments(owner, repo, pr, forgejo.ListIssueCommentOptions{
|
||||
ListOptions: forgejo.ListOptions{Page: page, PageSize: commentPageSize},
|
||||
})
|
||||
for raw, err := range c.ListIssueCommentsIter(owner, repo, pr) {
|
||||
if err != nil {
|
||||
return nil, log.E("forge.GetCommentBodies", "failed to get PR comments", err)
|
||||
}
|
||||
|
||||
if len(raw) == 0 {
|
||||
break
|
||||
comment := Comment{
|
||||
ID: raw.ID,
|
||||
Body: raw.Body,
|
||||
CreatedAt: raw.Created,
|
||||
UpdatedAt: raw.Updated,
|
||||
}
|
||||
|
||||
for _, rc := range raw {
|
||||
comment := Comment{
|
||||
ID: rc.ID,
|
||||
Body: rc.Body,
|
||||
CreatedAt: rc.Created,
|
||||
UpdatedAt: rc.Updated,
|
||||
}
|
||||
if rc.Poster != nil {
|
||||
comment.Author = rc.Poster.UserName
|
||||
}
|
||||
comments = append(comments, comment)
|
||||
if raw.Poster != nil {
|
||||
comment.Author = raw.Poster.UserName
|
||||
}
|
||||
|
||||
if len(raw) < commentPageSize {
|
||||
break
|
||||
}
|
||||
page++
|
||||
comments = append(comments, comment)
|
||||
}
|
||||
|
||||
return comments, nil
|
||||
|
|
|
|||
|
|
@ -202,6 +202,32 @@ func (c *Client) ListPullRequestsIter(owner, repo string, state string) iter.Seq
|
|||
}
|
||||
}
|
||||
|
||||
// ListIssueCommentsIter returns an iterator over comments for an issue.
|
||||
// Usage: ListIssueCommentsIter(...)
|
||||
func (c *Client) ListIssueCommentsIter(owner, repo string, number int64) iter.Seq2[*gitea.Comment, error] {
|
||||
return func(yield func(*gitea.Comment, error) bool) {
|
||||
page := 1
|
||||
for {
|
||||
comments, resp, err := c.api.ListIssueComments(owner, repo, number, gitea.ListIssueCommentOptions{
|
||||
ListOptions: gitea.ListOptions{Page: page, PageSize: commentPageSize},
|
||||
})
|
||||
if err != nil {
|
||||
yield(nil, log.E("gitea.ListIssueComments", "failed to list comments", err))
|
||||
return
|
||||
}
|
||||
for _, comment := range comments {
|
||||
if !yield(comment, nil) {
|
||||
return
|
||||
}
|
||||
}
|
||||
if resp == nil || page >= resp.LastPage {
|
||||
break
|
||||
}
|
||||
page++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetPullRequest returns a single pull request by number.
|
||||
// Usage: GetPullRequest(...)
|
||||
func (c *Client) GetPullRequest(owner, repo string, number int64) (*gitea.PullRequest, error) {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ package gitea
|
|||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
giteaSDK "code.gitea.io/sdk/gitea"
|
||||
|
|
@ -41,6 +42,43 @@ func newPaginatedIssuesClient(t *testing.T) (*Client, *httptest.Server) {
|
|||
return client, srv
|
||||
}
|
||||
|
||||
func newPaginatedCommentsClient(t *testing.T) (*Client, *httptest.Server) {
|
||||
t.Helper()
|
||||
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/api/v1/version", func(w http.ResponseWriter, r *http.Request) {
|
||||
jsonResponse(w, map[string]string{"version": "1.21.0"})
|
||||
})
|
||||
mux.HandleFunc("/api/v1/repos/test-org/org-repo/issues/1/comments", func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.Query().Get("page") {
|
||||
case "2":
|
||||
jsonResponse(w, []map[string]any{
|
||||
{"id": 150, "body": "comment 51", "user": map[string]any{"login": "user51"}, "created_at": "2026-01-02T00:00:00Z", "updated_at": "2026-01-02T00:00:00Z"},
|
||||
})
|
||||
case "3":
|
||||
jsonResponse(w, []map[string]any{})
|
||||
default:
|
||||
w.Header().Set("Link", `</api/v1/repos/test-org/org-repo/issues/1/comments?page=2>; rel="next", </api/v1/repos/test-org/org-repo/issues/1/comments?page=2>; rel="last"`)
|
||||
comments := make([]map[string]any, 0, 50)
|
||||
for i := 1; i <= 50; i++ {
|
||||
comments = append(comments, map[string]any{
|
||||
"id": 99 + i,
|
||||
"body": "comment " + strconv.Itoa(i),
|
||||
"user": map[string]any{"login": "user" + strconv.Itoa(i)},
|
||||
"created_at": "2026-01-01T00:00:00Z",
|
||||
"updated_at": "2026-01-01T00:00:00Z",
|
||||
})
|
||||
}
|
||||
jsonResponse(w, comments)
|
||||
}
|
||||
})
|
||||
|
||||
srv := httptest.NewServer(mux)
|
||||
client, err := New(srv.URL, "test-token")
|
||||
require.NoError(t, err)
|
||||
return client, srv
|
||||
}
|
||||
|
||||
func TestClient_ListIssues_Good(t *testing.T) {
|
||||
client, srv := newTestClient(t)
|
||||
defer srv.Close()
|
||||
|
|
@ -136,6 +174,22 @@ func TestClient_GetIssue_Bad_ServerError_Good(t *testing.T) {
|
|||
assert.Contains(t, err.Error(), "failed to get issue")
|
||||
}
|
||||
|
||||
func TestClient_ListIssueCommentsIter_Good_Paginates_Good(t *testing.T) {
|
||||
client, srv := newPaginatedCommentsClient(t)
|
||||
defer srv.Close()
|
||||
|
||||
var bodies []string
|
||||
for comment, err := range client.ListIssueCommentsIter("test-org", "org-repo", 1) {
|
||||
require.NoError(t, err)
|
||||
bodies = append(bodies, comment.Body)
|
||||
}
|
||||
|
||||
require.Len(t, bodies, 51)
|
||||
assert.Equal(t, "comment 1", bodies[0])
|
||||
assert.Equal(t, "comment 50", bodies[49])
|
||||
assert.Equal(t, "comment 51", bodies[50])
|
||||
}
|
||||
|
||||
func TestClient_CreateIssue_Good(t *testing.T) {
|
||||
client, srv := newTestClient(t)
|
||||
defer srv.Close()
|
||||
|
|
|
|||
|
|
@ -5,8 +5,6 @@ package gitea
|
|||
import (
|
||||
"time"
|
||||
|
||||
"code.gitea.io/sdk/gitea"
|
||||
|
||||
"dappco.re/go/core/log"
|
||||
)
|
||||
|
||||
|
|
@ -78,19 +76,11 @@ func (c *Client) GetPRMeta(owner, repo string, pr int64) (*PRMeta, error) {
|
|||
// Fetch comment count from the issue side (PRs are issues in Gitea).
|
||||
// Paginate to get an accurate count.
|
||||
count := 0
|
||||
page := 1
|
||||
for {
|
||||
comments, _, listErr := c.api.ListIssueComments(owner, repo, pr, gitea.ListIssueCommentOptions{
|
||||
ListOptions: gitea.ListOptions{Page: page, PageSize: commentPageSize},
|
||||
})
|
||||
if listErr != nil {
|
||||
for _, err := range c.ListIssueCommentsIter(owner, repo, pr) {
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
count += len(comments)
|
||||
if len(comments) < commentPageSize {
|
||||
break
|
||||
}
|
||||
page++
|
||||
count++
|
||||
}
|
||||
meta.CommentCount = count
|
||||
|
||||
|
|
@ -102,37 +92,21 @@ func (c *Client) GetPRMeta(owner, repo string, pr int64) (*PRMeta, error) {
|
|||
// Usage: GetCommentBodies(...)
|
||||
func (c *Client) GetCommentBodies(owner, repo string, pr int64) ([]Comment, error) {
|
||||
var comments []Comment
|
||||
page := 1
|
||||
|
||||
for {
|
||||
raw, _, err := c.api.ListIssueComments(owner, repo, pr, gitea.ListIssueCommentOptions{
|
||||
ListOptions: gitea.ListOptions{Page: page, PageSize: commentPageSize},
|
||||
})
|
||||
for raw, err := range c.ListIssueCommentsIter(owner, repo, pr) {
|
||||
if err != nil {
|
||||
return nil, log.E("gitea.GetCommentBodies", "failed to get PR comments", err)
|
||||
}
|
||||
|
||||
if len(raw) == 0 {
|
||||
break
|
||||
comment := Comment{
|
||||
ID: raw.ID,
|
||||
Body: raw.Body,
|
||||
CreatedAt: raw.Created,
|
||||
UpdatedAt: raw.Updated,
|
||||
}
|
||||
|
||||
for _, rc := range raw {
|
||||
comment := Comment{
|
||||
ID: rc.ID,
|
||||
Body: rc.Body,
|
||||
CreatedAt: rc.Created,
|
||||
UpdatedAt: rc.Updated,
|
||||
}
|
||||
if rc.Poster != nil {
|
||||
comment.Author = rc.Poster.UserName
|
||||
}
|
||||
comments = append(comments, comment)
|
||||
if raw.Poster != nil {
|
||||
comment.Author = raw.Poster.UserName
|
||||
}
|
||||
|
||||
if len(raw) < commentPageSize {
|
||||
break
|
||||
}
|
||||
page++
|
||||
comments = append(comments, comment)
|
||||
}
|
||||
|
||||
return comments, nil
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue