Files

167 lines
6.6 KiB
Go

package forge
import (
"context"
"encoding/json"
"io"
"net/http"
"net/http/httptest"
"strings"
"testing"
)
func TestParse(t *testing.T) {
cases := []struct {
url string
kind Kind
want Repo
}{
{"https://github.com/acme/site.git", "", Repo{GitHub, "github.com", "acme/site", "https://api.github.com"}},
{"[email protected]:acme/site.git", "", Repo{GitHub, "github.com", "acme/site", "https://api.github.com"}},
{"https://gitlab.com/group/sub/site", "", Repo{GitLab, "gitlab.com", "group/sub/site", "https://gitlab.com/api/v4"}},
{"https://codeberg.org/me/site.git", "", Repo{Forgejo, "codeberg.org", "me/site", "https://codeberg.org/api/v1"}},
{"ssh://[email protected]:2222/me/site.git", Gitea, Repo{Gitea, "git.example.org", "me/site", "https://git.example.org/api/v1"}},
{"https://ghe.example.com/team/site", GitHub, Repo{GitHub, "ghe.example.com", "team/site", "https://ghe.example.com/api/v3"}},
{"[email protected]:ws/site.git", "", Repo{Bitbucket, "bitbucket.org", "ws/site", "https://api.bitbucket.org/2.0"}},
{"https://git.example.org/me/site.git", "", Repo{Git, "git.example.org", "me/site", ""}},
{"/srv/git/site.git", "", Repo{Kind: Git}},
}
for _, c := range cases {
got, err := Parse(c.url, c.kind)
if err != nil {
t.Errorf("%s: %v", c.url, err)
continue
}
if got != c.want {
t.Errorf("%s: got %+v, want %+v", c.url, got, c.want)
}
}
if _, err := Parse("https://example.org/x", "svn"); err == nil {
t.Error("accepted an unknown platform")
}
}
const secret = "s3cret-for-tests"
func TestVerify(t *testing.T) {
body := []byte(`{"ref":"refs/heads/main"}`)
sig := hmacHex(secret, body)
good := map[Kind]http.Header{
GitHub: {"X-Hub-Signature-256": {"sha256=" + sig}},
Gitea: {"X-Gitea-Signature": {sig}},
Forgejo: {"X-Forgejo-Signature": {sig}},
Gogs: {"X-Gogs-Signature": {sig}},
Bitbucket: {"X-Hub-Signature": {"sha256=" + sig}},
GitLab: {"X-Gitlab-Token": {secret}},
Git: {"X-Hotdog-Token": {secret}},
}
for kind, h := range good {
if err := Verify(kind, h, body, secret); err != nil {
t.Errorf("%s: good delivery refused: %v", kind, err)
}
if err := Verify(kind, h, append(body, ' '), secret); err == nil && kind != GitLab && kind != Git {
t.Errorf("%s: tampered body accepted", kind)
}
if err := Verify(kind, http.Header{}, body, secret); err == nil {
t.Errorf("%s: unsigned delivery accepted", kind)
}
if err := Verify(kind, h, body, ""); err == nil {
t.Errorf("%s: accepted with no secret configured", kind)
}
}
if err := Verify(GitLab, http.Header{"X-Gitlab-Token": {"wrong"}}, body, secret); err == nil {
t.Error("gitlab: wrong token accepted")
}
}
func TestParsePush(t *testing.T) {
sha := strings.Repeat("a", 40)
cases := []struct {
kind Kind
h http.Header
body string
want Push
err error
}{
{GitHub, http.Header{"X-Github-Event": {"push"}}, `{"ref":"refs/heads/draft/x","after":"` + sha + `"}`, Push{"draft/x", sha, false}, nil},
{GitHub, http.Header{"X-Github-Event": {"push"}}, `{"ref":"refs/heads/old","after":"` + zeros + `","deleted":true}`, Push{"old", zeros, true}, nil},
{GitHub, http.Header{"X-Github-Event": {"ping"}}, `{}`, Push{}, ErrNotPush},
{GitHub, http.Header{"X-Github-Event": {"push"}}, `{"ref":"refs/tags/v1","after":"` + sha + `"}`, Push{}, ErrNotPush},
{Gitea, http.Header{"X-Gitea-Event": {"push"}}, `{"ref":"refs/heads/main","after":"` + sha + `"}`, Push{"main", sha, false}, nil},
{Forgejo, http.Header{"X-Forgejo-Event": {"push"}}, `{"ref":"refs/heads/main","after":"` + sha + `"}`, Push{"main", sha, false}, nil},
{GitLab, http.Header{"X-Gitlab-Event": {"Push Hook"}}, `{"ref":"refs/heads/main","after":"` + sha + `"}`, Push{"main", sha, false}, nil},
{Bitbucket, http.Header{"X-Event-Key": {"repo:push"}}, `{"push":{"changes":[{"new":{"type":"branch","name":"main","target":{"hash":"` + sha + `"}}}]}}`, Push{"main", sha, false}, nil},
{Bitbucket, http.Header{"X-Event-Key": {"repo:push"}}, `{"push":{"changes":[{"new":null,"old":{"type":"branch","name":"gone"}}]}}`, Push{"gone", "", true}, nil},
{Git, http.Header{"Content-Type": {"application/x-www-form-urlencoded"}}, "ref=refs%2Fheads%2Fmain&after=" + sha, Push{"main", sha, false}, nil},
}
for i, c := range cases {
got, err := ParsePush(c.kind, c.h, []byte(c.body))
if err != c.err || got != c.want {
t.Errorf("case %d (%s): got %+v, %v; want %+v, %v", i, c.kind, got, err, c.want, c.err)
}
}
}
func TestSetStatus(t *testing.T) {
type seen struct {
method, path, query, auth, private string
body map[string]string
}
var last seen
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
b, _ := io.ReadAll(r.Body)
last = seen{r.Method, r.URL.EscapedPath(), r.URL.RawQuery, r.Header.Get("Authorization"), r.Header.Get("PRIVATE-TOKEN"), nil}
if len(b) > 0 {
_ = json.Unmarshal(b, &last.body)
}
w.WriteHeader(201)
}))
defer srv.Close()
st := Status{State: Success, URL: "https://main-abc.preview.test/", Description: "Preview ready"}
cases := []struct {
repo Repo
check func() string
}{
{Repo{GitHub, "", "acme/site", srv.URL}, func() string {
if last.path != "/repos/acme/site/statuses/abc123" || last.auth != "Bearer tok" || last.body["state"] != "success" || last.body["target_url"] != st.URL {
return "github request wrong"
}
return ""
}},
{Repo{Gitea, "", "me/site", srv.URL}, func() string {
if last.path != "/repos/me/site/statuses/abc123" || last.auth != "token tok" || last.body["context"] != "hotdog-cms/preview" {
return "gitea request wrong"
}
return ""
}},
{Repo{GitLab, "", "group/sub/site", srv.URL}, func() string {
if last.path != "/projects/group%2Fsub%2Fsite/statuses/abc123" || last.private != "tok" || !strings.Contains(last.query, "state=success") {
return "gitlab request wrong: " + last.path + "?" + last.query
}
return ""
}},
{Repo{Bitbucket, "", "ws/site", srv.URL}, func() string {
if last.path != "/repositories/ws/site/commit/abc123/statuses/build" || last.body["state"] != "SUCCESSFUL" {
return "bitbucket request wrong"
}
return ""
}},
}
for _, c := range cases {
cl := &Client{Repo: c.repo, Token: "tok"}
if err := cl.SetStatus(context.Background(), "abc123", st); err != nil {
t.Errorf("%s: %v", c.repo.Kind, err)
continue
}
if msg := c.check(); msg != "" {
t.Errorf("%s: %s (%+v)", c.repo.Kind, msg, last)
}
}
last = seen{}
for _, k := range []Kind{Git, Gogs} {
if err := (&Client{Repo: Repo{Kind: k, API: srv.URL}, Token: "tok"}).SetStatus(context.Background(), "abc", st); err != nil || last.method != "" {
t.Errorf("%s: posted a status on a platform without them", k)
}
}
}