379 lines
12 KiB
Go
379 lines
12 KiB
Go
package gitea_test
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"git.knownelement.com/ukrrs/mopac-gitea-go/gitea"
|
|
"git.knownelement.com/ukrrs/mopac-gitea-go/internal/fakegitea"
|
|
)
|
|
|
|
const fakeToken = "fake-gitea-token-0123456789"
|
|
|
|
func newClient(t *testing.T) (*gitea.Client, *fakegitea.Server) {
|
|
t.Helper()
|
|
srv := fakegitea.New(fakeToken, "ukrrs")
|
|
t.Cleanup(srv.Close)
|
|
c := gitea.New(gitea.Config{BaseURL: srv.URL, Token: fakeToken, Timeout: 5 * time.Second})
|
|
return c, srv
|
|
}
|
|
|
|
// --- transport core: auth, error mapping, redaction --------------------------
|
|
|
|
func TestGetRepoRoundTrip(t *testing.T) {
|
|
c, srv := newClient(t)
|
|
srv.AddRepo("ukrrs", "MOPAC", "main", "main", "feat/quota")
|
|
|
|
repo, err := c.GetRepo(context.Background(), "ukrrs", "MOPAC")
|
|
if err != nil {
|
|
t.Fatalf("GetRepo: %v", err)
|
|
}
|
|
if repo.FullName != "ukrrs/MOPAC" || repo.DefaultBranch != "main" || repo.Owner.Login != "ukrrs" {
|
|
t.Fatalf("repo = %+v", repo)
|
|
}
|
|
reqs := srv.Requests()
|
|
if len(reqs) != 1 || reqs[0].Method != "GET" || reqs[0].Path != "/api/v1/repos/ukrrs/MOPAC" {
|
|
t.Fatalf("requests = %+v", reqs)
|
|
}
|
|
// The token lives in the Authorization header only: never in the
|
|
// path, query, or body the server recorded.
|
|
for _, r := range reqs {
|
|
if strings.Contains(r.Path+r.Query+r.Body, fakeToken) {
|
|
t.Fatalf("token leaked into request: %+v", r)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestErrorMapping(t *testing.T) {
|
|
tests := []struct {
|
|
status int
|
|
body string
|
|
wantSent error
|
|
}{
|
|
{401, `{"message":"bad credentials: token %s"}`, gitea.ErrAuth},
|
|
{403, `{"message":"forbidden for token %s"}`, gitea.ErrAuth},
|
|
{404, `{"message":"The target couldn't be found. Echo %s"}`, gitea.ErrNotFound},
|
|
{409, `{"message":"conflict, token %s"}`, gitea.ErrValidation},
|
|
{422, `{"message":"validation failed for %s"}`, gitea.ErrValidation},
|
|
{500, `{"message":"server exploded with token %s"}`, gitea.ErrServer},
|
|
{503, `{"message":"down for %s"}`, gitea.ErrServer},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run("http_"+strconv.Itoa(tt.status), func(t *testing.T) {
|
|
c, srv := newClient(t)
|
|
srv.Fail = &fakegitea.FailSpec{Status: tt.status, Body: tt.body}
|
|
_, err := c.GetRepo(context.Background(), "ukrrs", "MOPAC")
|
|
if err == nil {
|
|
t.Fatalf("want error for http %d", tt.status)
|
|
}
|
|
if !errors.Is(err, tt.wantSent) {
|
|
t.Fatalf("err = %v, want %v", err, tt.wantSent)
|
|
}
|
|
if !strings.Contains(err.Error(), "http "+strconv.Itoa(tt.status)) {
|
|
t.Fatalf("err = %q, want http code in message", err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestTokenNeverLeaks(t *testing.T) {
|
|
c, srv := newClient(t)
|
|
srv.Fail = &fakegitea.FailSpec{Status: 401, Body: `{"message":"token %s was rejected"}`}
|
|
_, err := c.GetRepo(context.Background(), "ukrrs", "MOPAC")
|
|
if err == nil || !errors.Is(err, gitea.ErrAuth) {
|
|
t.Fatalf("err = %v, want auth failure", err)
|
|
}
|
|
if strings.Contains(err.Error(), fakeToken) {
|
|
t.Fatalf("error leaks the token: %q", err)
|
|
}
|
|
if strings.Contains(err.Error(), "was rejected") {
|
|
t.Fatalf("error leaks response body: %q", err)
|
|
}
|
|
}
|
|
|
|
func TestUnreachable(t *testing.T) {
|
|
c, srv := newClient(t)
|
|
srv.Close() // port gone; every call must map to ErrUnreachable, not panic
|
|
_, err := c.GetRepo(context.Background(), "ukrrs", "MOPAC")
|
|
if !errors.Is(err, gitea.ErrUnreachable) {
|
|
t.Fatalf("err = %v, want ErrUnreachable", err)
|
|
}
|
|
}
|
|
|
|
func TestMalformedResponse(t *testing.T) {
|
|
c, srv := newClient(t)
|
|
srv.Fail = &fakegitea.FailSpec{Status: 200, Body: `<html>not json</html>`}
|
|
_, err := c.GetRepo(context.Background(), "ukrrs", "MOPAC")
|
|
if !errors.Is(err, gitea.ErrMalformedResponse) {
|
|
t.Fatalf("err = %v, want ErrMalformedResponse", err)
|
|
}
|
|
}
|
|
|
|
func TestBadTokenRejected(t *testing.T) {
|
|
srv := fakegitea.New(fakeToken, "ukrrs")
|
|
t.Cleanup(srv.Close)
|
|
c := gitea.New(gitea.Config{BaseURL: srv.URL, Token: "wrong-token-abcdef"})
|
|
_, err := c.GetRepo(context.Background(), "ukrrs", "MOPAC")
|
|
if !errors.Is(err, gitea.ErrAuth) {
|
|
t.Fatalf("err = %v, want ErrAuth", err)
|
|
}
|
|
if strings.Contains(err.Error(), "wrong-token-abcdef") {
|
|
t.Fatalf("presented token echoed in error: %q", err)
|
|
}
|
|
}
|
|
|
|
// --- repos ------------------------------------------------------------------
|
|
|
|
func TestCreateRepoRoundTrip(t *testing.T) {
|
|
c, srv := newClient(t)
|
|
|
|
repo, err := c.CreateRepo(context.Background(), gitea.RepoParams{
|
|
Owner: "",
|
|
Name: "vertical-x",
|
|
Description: "one vertical",
|
|
Private: true,
|
|
AutoInit: true,
|
|
DefaultBranch: "main",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("CreateRepo: %v", err)
|
|
}
|
|
if repo.FullName != "ukrrs/vertical-x" || !repo.Private || repo.DefaultBranch != "main" {
|
|
t.Fatalf("repo = %+v", repo)
|
|
}
|
|
// POST /user/repos carries the write shape (scalars only).
|
|
var post map[string]any
|
|
if err := json.Unmarshal([]byte(srv.Requests()[0].Body), &post); err != nil {
|
|
t.Fatalf("POST body not json: %v", err)
|
|
}
|
|
for _, k := range []string{"name", "description", "private", "auto_init", "default_branch"} {
|
|
if _, ok := post[k]; !ok {
|
|
t.Errorf("POST body missing %q: %v", k, post)
|
|
}
|
|
}
|
|
if post["name"] != "vertical-x" || post["private"] != true {
|
|
t.Errorf("POST body = %v", post)
|
|
}
|
|
}
|
|
|
|
func TestCreateRepoInOrg(t *testing.T) {
|
|
c, srv := newClient(t)
|
|
repo, err := c.CreateRepo(context.Background(), gitea.RepoParams{Owner: "ukrrs-labs", Name: "org-repo"})
|
|
if err != nil {
|
|
t.Fatalf("CreateRepo: %v", err)
|
|
}
|
|
if repo.Owner.Login != "ukrrs-labs" || repo.FullName != "ukrrs-labs/org-repo" {
|
|
t.Fatalf("repo = %+v", repo)
|
|
}
|
|
if reqs := srv.Requests(); reqs[0].Path != "/api/v1/orgs/ukrrs-labs/repos" {
|
|
t.Fatalf("path = %s, want orgs endpoint", reqs[0].Path)
|
|
}
|
|
}
|
|
|
|
func TestCreateRepoDuplicateConflict(t *testing.T) {
|
|
c, _ := newClient(t)
|
|
if _, err := c.CreateRepo(context.Background(), gitea.RepoParams{Name: "dup", AutoInit: true}); err != nil {
|
|
t.Fatalf("first create: %v", err)
|
|
}
|
|
_, err := c.CreateRepo(context.Background(), gitea.RepoParams{Name: "dup"})
|
|
if !errors.Is(err, gitea.ErrValidation) { // 409 -> validation sentinel
|
|
t.Fatalf("err = %v, want ErrValidation (409)", err)
|
|
}
|
|
}
|
|
|
|
func TestListRepos(t *testing.T) {
|
|
c, srv := newClient(t)
|
|
srv.AddRepo("ukrrs", "alpha", "main", "main")
|
|
srv.AddRepo("ukrrs", "beta", "main", "main")
|
|
srv.AddRepo("other", "gamma", "main", "main")
|
|
|
|
tests := []struct {
|
|
name string
|
|
owner string
|
|
want []string
|
|
path string
|
|
}{
|
|
{name: "self", owner: "", want: []string{"ukrrs/alpha", "ukrrs/beta"}, path: "/api/v1/user/repos"},
|
|
{name: "other user", owner: "other", want: []string{"other/gamma"}, path: "/api/v1/users/other/repos"},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
repos, err := c.ListRepos(context.Background(), tt.owner)
|
|
if err != nil {
|
|
t.Fatalf("ListRepos: %v", err)
|
|
}
|
|
var got []string
|
|
for _, r := range repos {
|
|
got = append(got, r.FullName)
|
|
}
|
|
if strings.Join(got, ",") != strings.Join(tt.want, ",") {
|
|
t.Fatalf("got %v, want %v", got, tt.want)
|
|
}
|
|
if reqs := srv.Requests(); reqs[len(reqs)-1].Path != tt.path {
|
|
t.Fatalf("path = %s, want %s", reqs[len(reqs)-1].Path, tt.path)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetRepoMissing(t *testing.T) {
|
|
c, _ := newClient(t)
|
|
_, err := c.GetRepo(context.Background(), "ukrrs", "absent")
|
|
if !errors.Is(err, gitea.ErrNotFound) {
|
|
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)
|
|
}
|
|
})
|
|
}
|
|
}
|