feat(gitea): branch list and commit-status endpoints
This commit is contained in:
@@ -0,0 +1,29 @@
|
|||||||
|
package gitea
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CommitInfo is the commit projection branches carry.
|
||||||
|
type CommitInfo struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Message string `json:"message,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Branch is the branch projection.
|
||||||
|
type Branch struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Commit CommitInfo `json:"commit"`
|
||||||
|
Protected bool `json:"protected"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListBranches lists a repository's branches.
|
||||||
|
func (c *Client) ListBranches(ctx context.Context, owner, repo string) ([]Branch, error) {
|
||||||
|
var out []Branch
|
||||||
|
path := "/repos/" + url.PathEscape(owner) + "/" + url.PathEscape(repo) + "/branches"
|
||||||
|
if err := c.do(ctx, "GET", path, nil, nil, &out); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
package gitea
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Commit status states (Gitea's closed set).
|
||||||
|
const (
|
||||||
|
StatusPending = "pending"
|
||||||
|
StatusSuccess = "success"
|
||||||
|
StatusError = "error"
|
||||||
|
StatusFailure = "failure"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CommitStatusParams is the status-create write shape.
|
||||||
|
type CommitStatusParams struct {
|
||||||
|
State string // one of the Status* constants
|
||||||
|
Context string // e.g. "ci/lint"; server defaults to "default"
|
||||||
|
Description string
|
||||||
|
TargetURL string `json:"target_url"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CommitStatus is the status projection (read shape).
|
||||||
|
type CommitStatus struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
State string `json:"state"`
|
||||||
|
Context string `json:"context"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
TargetURL string `json:"target_url"`
|
||||||
|
CreatedAt string `json:"created_at"`
|
||||||
|
UpdatedAt string `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CombinedStatus is the worst-of rollup Gitea reports per ref.
|
||||||
|
type CombinedStatus struct {
|
||||||
|
State string `json:"state"`
|
||||||
|
SHA string `json:"sha"`
|
||||||
|
TotalCount int `json:"total_count"`
|
||||||
|
Statuses []CommitStatus `json:"statuses"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateCommitStatus reports one status on a commit (sha may be any ref
|
||||||
|
// the server resolves: full or short sha, branch, tag).
|
||||||
|
func (c *Client) CreateCommitStatus(ctx context.Context, owner, repo, sha string, p CommitStatusParams) (*CommitStatus, error) {
|
||||||
|
var out CommitStatus
|
||||||
|
path := "/repos/" + url.PathEscape(owner) + "/" + url.PathEscape(repo) + "/statuses/" + url.PathEscape(sha)
|
||||||
|
if err := c.do(ctx, "POST", path, nil, p, &out); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListCommitStatuses lists the latest status per context for a ref.
|
||||||
|
func (c *Client) ListCommitStatuses(ctx context.Context, owner, repo, ref string) ([]CommitStatus, error) {
|
||||||
|
var out []CommitStatus
|
||||||
|
path := "/repos/" + url.PathEscape(owner) + "/" + url.PathEscape(repo) + "/commits/" + url.PathEscape(ref) + "/statuses"
|
||||||
|
if err := c.do(ctx, "GET", path, nil, nil, &out); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCombinedStatus fetches the worst-of rollup for a ref. Gitea answers
|
||||||
|
// 404 when the ref has no statuses at all.
|
||||||
|
func (c *Client) GetCombinedStatus(ctx context.Context, owner, repo, ref string) (*CombinedStatus, error) {
|
||||||
|
var out CombinedStatus
|
||||||
|
path := "/repos/" + url.PathEscape(owner) + "/" + url.PathEscape(repo) + "/commits/" + url.PathEscape(ref) + "/status"
|
||||||
|
if err := c.do(ctx, "GET", path, nil, nil, &out); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &out, nil
|
||||||
|
}
|
||||||
+148
@@ -228,3 +228,151 @@ func TestGetRepoMissing(t *testing.T) {
|
|||||||
t.Fatalf("err = %v, want ErrNotFound", err)
|
t.Fatalf("err = %v, want ErrNotFound", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- branches ---------------------------------------------------------------
|
||||||
|
|
||||||
|
func TestListBranches(t *testing.T) {
|
||||||
|
c, srv := newClient(t)
|
||||||
|
srv.AddRepo("ukrrs", "MOPAC", "main", "main", "feat/quota", "fix/lint")
|
||||||
|
|
||||||
|
branches, err := c.ListBranches(context.Background(), "ukrrs", "MOPAC")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ListBranches: %v", err)
|
||||||
|
}
|
||||||
|
if len(branches) != 3 {
|
||||||
|
t.Fatalf("got %d branches, want 3: %+v", len(branches), branches)
|
||||||
|
}
|
||||||
|
if branches[0].Name != "main" || branches[2].Name != "fix/lint" {
|
||||||
|
t.Fatalf("order = %+v", branches)
|
||||||
|
}
|
||||||
|
for _, b := range branches {
|
||||||
|
if len(b.Commit.ID) != 40 {
|
||||||
|
t.Errorf("branch %s tip sha = %q, want 40-hex", b.Name, b.Commit.ID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if reqs := srv.Requests(); reqs[0].Path != "/api/v1/repos/ukrrs/MOPAC/branches" {
|
||||||
|
t.Fatalf("path = %s", reqs[0].Path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestListBranchesMissingRepo(t *testing.T) {
|
||||||
|
c, _ := newClient(t)
|
||||||
|
_, err := c.ListBranches(context.Background(), "ukrrs", "absent")
|
||||||
|
if !errors.Is(err, gitea.ErrNotFound) {
|
||||||
|
t.Fatalf("err = %v, want ErrNotFound", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- commit statuses ----------------------------------------------------------
|
||||||
|
|
||||||
|
func TestCommitStatusFlow(t *testing.T) {
|
||||||
|
c, srv := newClient(t)
|
||||||
|
srv.AddRepo("ukrrs", "MOPAC", "main", "main")
|
||||||
|
sha := srv.AddBranch("ukrrs", "MOPAC", "feat/quota")
|
||||||
|
|
||||||
|
st, err := c.CreateCommitStatus(context.Background(), "ukrrs", "MOPAC", sha, gitea.CommitStatusParams{
|
||||||
|
State: gitea.StatusPending,
|
||||||
|
Context: "ci/lint",
|
||||||
|
Description: "lint queued",
|
||||||
|
TargetURL: "https://ci.example/run/1",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("CreateCommitStatus: %v", err)
|
||||||
|
}
|
||||||
|
if st.ID == 0 || st.State != "pending" || st.Context != "ci/lint" {
|
||||||
|
t.Fatalf("status = %+v", st)
|
||||||
|
}
|
||||||
|
if reqs := srv.Requests(); reqs[0].Path != "/api/v1/repos/ukrrs/MOPAC/statuses/"+sha {
|
||||||
|
t.Fatalf("path = %s", reqs[0].Path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Same context flips to success; a second context reports failure.
|
||||||
|
if _, err := c.CreateCommitStatus(context.Background(), "ukrrs", "MOPAC", sha, gitea.CommitStatusParams{
|
||||||
|
State: gitea.StatusSuccess,
|
||||||
|
Context: "ci/lint",
|
||||||
|
}); err != nil {
|
||||||
|
t.Fatalf("CreateCommitStatus flip: %v", err)
|
||||||
|
}
|
||||||
|
if _, err := c.CreateCommitStatus(context.Background(), "ukrrs", "MOPAC", sha, gitea.CommitStatusParams{
|
||||||
|
State: gitea.StatusFailure,
|
||||||
|
Context: "ci/test",
|
||||||
|
}); err != nil {
|
||||||
|
t.Fatalf("CreateCommitStatus second context: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listing by branch NAME resolves to the tip sha and folds to the
|
||||||
|
// latest status per context.
|
||||||
|
listed, err := c.ListCommitStatuses(context.Background(), "ukrrs", "MOPAC", "feat/quota")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ListCommitStatuses: %v", err)
|
||||||
|
}
|
||||||
|
if len(listed) != 2 {
|
||||||
|
t.Fatalf("got %d statuses, want 2 (latest per context): %+v", len(listed), listed)
|
||||||
|
}
|
||||||
|
byCtx := map[string]string{}
|
||||||
|
for _, s := range listed {
|
||||||
|
byCtx[s.Context] = s.State
|
||||||
|
}
|
||||||
|
if byCtx["ci/lint"] != "success" || byCtx["ci/test"] != "failure" {
|
||||||
|
t.Fatalf("folded = %v", byCtx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Combined status is the worst of the latest per context.
|
||||||
|
comb, err := c.GetCombinedStatus(context.Background(), "ukrrs", "MOPAC", "feat/quota")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("GetCombinedStatus: %v", err)
|
||||||
|
}
|
||||||
|
if comb.State != "failure" || comb.SHA != sha || comb.TotalCount != 2 {
|
||||||
|
t.Fatalf("combined = %+v", comb)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Short sha (>= 7 chars) resolves too.
|
||||||
|
short := sha[:7]
|
||||||
|
if _, err := c.GetCombinedStatus(context.Background(), "ukrrs", "MOPAC", short); err != nil {
|
||||||
|
t.Fatalf("short sha: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCommitStatusErrors(t *testing.T) {
|
||||||
|
c, srv := newClient(t)
|
||||||
|
srv.AddRepo("ukrrs", "MOPAC", "main", "main")
|
||||||
|
sha := srv.AddBranch("ukrrs", "MOPAC", "feat/quota")
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
run func() error
|
||||||
|
want error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "unknown sha",
|
||||||
|
run: func() error {
|
||||||
|
_, err := c.CreateCommitStatus(context.Background(), "ukrrs", "MOPAC", strings.Repeat("d", 40), gitea.CommitStatusParams{State: gitea.StatusSuccess})
|
||||||
|
return err
|
||||||
|
},
|
||||||
|
want: gitea.ErrNotFound,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid state",
|
||||||
|
run: func() error {
|
||||||
|
_, err := c.CreateCommitStatus(context.Background(), "ukrrs", "MOPAC", sha, gitea.CommitStatusParams{State: "green"})
|
||||||
|
return err
|
||||||
|
},
|
||||||
|
want: gitea.ErrValidation,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "combined with no statuses",
|
||||||
|
run: func() error {
|
||||||
|
_, err := c.GetCombinedStatus(context.Background(), "ukrrs", "MOPAC", "main")
|
||||||
|
return err
|
||||||
|
},
|
||||||
|
want: gitea.ErrNotFound,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if err := tt.run(); !errors.Is(err, tt.want) {
|
||||||
|
t.Fatalf("err = %v, want %v", err, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user