e2e/release_test.go

ff759b53942049b2043e132f5482b442a7265b98
gitbay/e2e/release_test.go history · blame · raw

144 lines · 6569 bytes

  1package e2e
  2
  3import (
  4	"fmt"
  5	"net/http"
  6	"os"
  7	"path/filepath"
  8	"strings"
  9	"testing"
 10)
 11
 12func TestReleases(t *testing.T) {
 13	t.Parallel()
 14	inst := startInstance(t)
 15	aliceKey := inst.newKey(t, "alice")
 16	inst.admin(t, "admin", "user", "create", "alice", "--key", aliceKey+".pub")
 17
 18	if _, errOut, code := inst.ssh(t, aliceKey, "", "repo", "create", "alice/app"); code != 0 {
 19		t.Fatalf("repo create: %s", errOut)
 20	}
 21	work := t.TempDir()
 22	env := inst.gitEnv(aliceKey)
 23	mustGit(t, work, env, "clone", inst.sshURL("alice/app"), "w")
 24	dir := filepath.Join(work, "w")
 25	os.WriteFile(filepath.Join(dir, "a.txt"), []byte("a\n"), 0o644)
 26	mustGit(t, dir, env, "checkout", "-q", "-b", "main")
 27	mustGit(t, dir, env, "add", ".")
 28	mustGit(t, dir, env, "commit", "-q", "-m", "base")
 29	mustGit(t, dir, env, "tag", "-a", "v1.0", "-m", "first")
 30	mustGit(t, dir, env, "push", "-q", "origin", "main", "v1.0")
 31
 32	// Create: tag must exist; duplicates refused.
 33	if _, errOut, code := inst.ssh(t, aliceKey, "", "release", "create", "alice/app", "v9.9"); code != 3 || !strings.Contains(errOut, "push the tag first") {
 34		t.Fatalf("missing tag: exit %d, %s", code, errOut)
 35	}
 36	if _, errOut, code := inst.ssh(t, aliceKey, "", "release", "create", "alice/app", "v1.0",
 37		"--title", "'First light'", "--notes", "'the **first** release'"); code != 0 {
 38		t.Fatalf("release create: %s", errOut)
 39	}
 40	if _, _, code := inst.ssh(t, aliceKey, "", "release", "create", "alice/app", "v1.0"); code != 1 {
 41		t.Fatal("duplicate release accepted")
 42	}
 43
 44	// Edit: notes replace from stdin, absent flags keep their field.
 45	if _, _, code := inst.ssh(t, aliceKey, "", "release", "edit", "alice/app", "v1.0"); code != 2 {
 46		t.Fatal("edit with nothing to change accepted")
 47	}
 48	if _, errOut, code := inst.ssh(t, aliceKey, "the **rebuilt** notes\n", "release", "edit", "alice/app", "v1.0", "--file", "-"); code != 0 {
 49		t.Fatalf("release edit: %s", errOut)
 50	}
 51	out, _, _ := inst.ssh(t, aliceKey, "", "release", "show", "alice/app", "v1.0")
 52	if !strings.Contains(out, "the rebuilt notes") || !strings.Contains(out, "First light") {
 53		t.Fatalf("edit lost a field:\n%s", out)
 54	}
 55	if _, _, code := inst.ssh(t, aliceKey, "", "release", "edit", "alice/app", "v1.0", "--title", "'Second light'"); code != 0 {
 56		t.Fatal("title edit failed")
 57	}
 58	out, _, _ = inst.ssh(t, aliceKey, "", "release", "show", "alice/app", "v1.0")
 59	if !strings.Contains(out, "Second light") || !strings.Contains(out, "the rebuilt notes") {
 60		t.Fatalf("title edit lost notes:\n%s", out)
 61	}
 62	if _, _, code := inst.ssh(t, aliceKey, "", "release", "edit", "alice/app", "v9.9", "--title", "x"); code != 3 {
 63		t.Fatal("edit of missing release accepted")
 64	}
 65
 66	// Assets: upload from stdin, validation, dedup, round-trip.
 67	payload := "BINARY\x01\x02payload for the release asset\n"
 68	if _, errOut, code := inst.ssh(t, aliceKey, payload, "release", "asset", "add", "alice/app", "v1.0", "tool-linux-amd64"); code != 0 {
 69		t.Fatalf("asset add: %s", errOut)
 70	}
 71	if _, _, code := inst.ssh(t, aliceKey, payload, "release", "asset", "add", "alice/app", "v1.0", "tool-linux-amd64"); code != 1 {
 72		t.Fatal("duplicate asset accepted")
 73	}
 74	if _, errOut, code := inst.ssh(t, aliceKey, "", "release", "asset", "add", "alice/app", "v1.0", "empty-file"); code != 2 || !strings.Contains(errOut, "empty asset") {
 75		t.Fatalf("empty asset: exit %d, %s", code, errOut)
 76	}
 77	if _, _, code := inst.ssh(t, aliceKey, "x", "release", "asset", "add", "alice/app", "v1.0", "../evil"); code != 2 {
 78		t.Fatal("bad asset name accepted")
 79	}
 80	got, _, code := inst.ssh(t, aliceKey, "", "release", "asset", "get", "alice/app", "v1.0", "tool-linux-amd64")
 81	if code != 0 || got != payload {
 82		t.Fatalf("asset round-trip: exit %d, %q", code, got)
 83	}
 84	out, _, _ = inst.ssh(t, aliceKey, "", "release", "show", "alice/app", "v1.0", "--json")
 85	if !strings.Contains(out, `"name":"tool-linux-amd64"`) ||
 86		!strings.Contains(out, fmt.Sprintf(`"size":%d`, len(payload))) ||
 87		!strings.Contains(out, `"sha256":"`) || !strings.Contains(out, "Second light") {
 88		t.Fatalf("release show: %s", out)
 89	}
 90
 91	// Web: page renders notes and assets; download streams exact bytes.
 92	status, body := inst.get(t, "/alice/app/releases")
 93	if status != 200 || !strings.Contains(body, "Second light") ||
 94		!strings.Contains(body, "<strong>rebuilt</strong>") ||
 95		!strings.Contains(body, "tool-linux-amd64") {
 96		t.Fatalf("releases page: %d\n%s", status, body)
 97	}
 98	resp, err := http.Get(fmt.Sprintf("http://127.0.0.1:%d/alice/app/releases/download/v1.0/tool-linux-amd64", inst.httpPort))
 99	if err != nil {
100		t.Fatal(err)
101	}
102	dl := make([]byte, len(payload)+10)
103	n, _ := resp.Body.Read(dl)
104	resp.Body.Close()
105	if resp.StatusCode != 200 || string(dl[:n]) != payload ||
106		!strings.Contains(resp.Header.Get("Content-Disposition"), "tool-linux-amd64") {
107		t.Fatalf("web download: %d, %q", resp.StatusCode, dl[:n])
108	}
109	if resp, _ := http.Get(fmt.Sprintf("http://127.0.0.1:%d/alice/app/releases/download/v1.0/nope", inst.httpPort)); resp.StatusCode != 404 {
110		t.Fatalf("missing asset download: %d", resp.StatusCode)
111	}
112
113	// Remove, then delete: DB rows and disk both go.
114	if _, _, code := inst.ssh(t, aliceKey, "", "release", "asset", "remove", "alice/app", "v1.0", "tool-linux-amd64"); code != 0 {
115		t.Fatal("asset remove failed")
116	}
117	if _, _, code := inst.ssh(t, aliceKey, "", "release", "asset", "get", "alice/app", "v1.0", "tool-linux-amd64"); code != 3 {
118		t.Fatal("removed asset still served")
119	}
120	if _, _, code := inst.ssh(t, aliceKey, payload, "release", "asset", "add", "alice/app", "v1.0", "again"); code != 0 {
121		t.Fatal("re-add failed")
122	}
123	assetRoot := filepath.Join(inst.root, "repos", "alice", "app.git", "gitbay-releases")
124	if fis, err := os.ReadDir(assetRoot); err != nil || len(fis) == 0 {
125		t.Fatal("asset dir missing on disk")
126	}
127	if _, _, code := inst.ssh(t, aliceKey, "", "release", "delete", "alice/app", "v1.0", "--yes"); code != 0 {
128		t.Fatal("release delete failed")
129	}
130	if fis, _ := os.ReadDir(assetRoot); len(fis) != 0 {
131		t.Fatal("asset files survived release delete")
132	}
133	if out, _, _ := inst.ssh(t, aliceKey, "", "release", "list", "alice/app", "--json"); strings.Contains(out, "v1.0") {
134		t.Fatalf("deleted release listed: %s", out)
135	}
136
137	// Archived repos refuse release writes.
138	if _, _, code := inst.ssh(t, aliceKey, "", "repo", "archive", "alice/app"); code != 0 {
139		t.Fatal("archive failed")
140	}
141	if _, errOut, code := inst.ssh(t, aliceKey, "", "release", "create", "alice/app", "v1.0"); code != 4 || !strings.Contains(errOut, "archived") {
142		t.Fatalf("archived release create: exit %d, %s", code, errOut)
143	}
144}