e2e/milestone_test.go

1ed9fb9399b21da8e6cf45be8389792aac82d5bc
gitbay/e2e/milestone_test.go history · blame · raw

151 lines · 6314 bytes

  1package e2e
  2
  3import (
  4	"encoding/json"
  5	"os"
  6	"path/filepath"
  7	"strings"
  8	"testing"
  9)
 10
 11func TestMilestonesAndTemplates(t *testing.T) {
 12	t.Parallel()
 13	inst := startInstanceWith(t, "[web]\nmode = \"accounts\"\n")
 14	aliceKey := inst.newKey(t, "alice")
 15	bobKey := inst.newKey(t, "bob")
 16	inst.admin(t, "admin", "user", "create", "alice",
 17		"--key", aliceKey+".pub", "--email", "alice@example.test", "--verified")
 18	inst.admin(t, "admin", "user", "create", "bob", "--key", bobKey+".pub")
 19
 20	if _, errOut, code := inst.ssh(t, aliceKey, "", "repo", "create", "alice/app"); code != 0 {
 21		t.Fatalf("repo create: %s", errOut)
 22	}
 23	work := t.TempDir()
 24	env := inst.gitEnv(aliceKey)
 25	mustGit(t, work, env, "clone", inst.sshURL("alice/app"), "w")
 26	dir := filepath.Join(work, "w")
 27	os.MkdirAll(filepath.Join(dir, ".gitbay"), 0o755)
 28	os.WriteFile(filepath.Join(dir, ".gitbay", "issue-template.md"),
 29		[]byte("## Steps to reproduce\n\n## Expected\n"), 0o644)
 30	os.WriteFile(filepath.Join(dir, ".gitbay", "issue-template-feature.md"),
 31		[]byte("## Motivation\n"), 0o644)
 32	os.WriteFile(filepath.Join(dir, "a.txt"), []byte("a\n"), 0o644)
 33	mustGit(t, dir, env, "checkout", "-q", "-b", "main")
 34	mustGit(t, dir, env, "add", ".")
 35	mustGit(t, dir, env, "commit", "-q", "-m", "base")
 36	mustGit(t, dir, env, "push", "-q", "origin", "main")
 37	mustGit(t, dir, env, "checkout", "-q", "-b", "feat")
 38	os.WriteFile(filepath.Join(dir, "b.txt"), []byte("b\n"), 0o644)
 39	mustGit(t, dir, env, "add", ".")
 40	mustGit(t, dir, env, "commit", "-q", "-m", "feat")
 41	mustGit(t, dir, env, "push", "-q", "origin", "feat")
 42	if _, _, code := inst.ssh(t, aliceKey, "", "mr", "create", "alice/app",
 43		"--source", "feat", "--target", "main", "--title", "'feat'"); code != 0 {
 44		t.Fatal("mr create failed")
 45	}
 46	for _, title := range []string{"'one'", "'two'"} {
 47		if _, _, code := inst.ssh(t, aliceKey, "", "issue", "create", "alice/app", "--title", title); code != 0 {
 48			t.Fatal("issue create failed")
 49		}
 50	}
 51
 52	// Milestone lifecycle and validation.
 53	if _, errOut, code := inst.ssh(t, aliceKey, "", "milestone", "create", "alice/app", "v1.0",
 54		"--description", "'first release'", "--due", "2027-01-01"); code != 0 {
 55		t.Fatalf("milestone create: %s", errOut)
 56	}
 57	if _, _, code := inst.ssh(t, aliceKey, "", "milestone", "create", "alice/app", "v1.0"); code != 1 {
 58		t.Fatal("duplicate milestone accepted")
 59	}
 60	if _, errOut, code := inst.ssh(t, aliceKey, "", "milestone", "create", "alice/app", "v2.0", "--due", "soon"); code != 2 || !strings.Contains(errOut, "YYYY-MM-DD") {
 61		t.Fatalf("bad due accepted: %s", errOut)
 62	}
 63	if _, _, code := inst.ssh(t, bobKey, "", "milestone", "create", "alice/app", "sneaky"); code != 4 {
 64		t.Fatal("read-only user created a milestone")
 65	}
 66
 67	// Attach: issue 1, MR 1; unknown title refused; clearing works.
 68	if _, errOut, code := inst.ssh(t, aliceKey, "", "issue", "milestone", "alice/app", "1", "v1.0"); code != 0 {
 69		t.Fatalf("issue milestone: %s", errOut)
 70	}
 71	if _, _, code := inst.ssh(t, aliceKey, "", "mr", "milestone", "alice/app", "1", "v1.0"); code != 0 {
 72		t.Fatal("mr milestone failed")
 73	}
 74	if _, _, code := inst.ssh(t, aliceKey, "", "issue", "milestone", "alice/app", "2", "v9.9"); code != 3 {
 75		t.Fatal("unknown milestone accepted")
 76	}
 77	out, _, _ := inst.ssh(t, aliceKey, "", "issue", "show", "alice/app", "1", "--json")
 78	if !strings.Contains(out, `"milestone":"v1.0"`) {
 79		t.Fatalf("issue show milestone: %s", out)
 80	}
 81	out, _, _ = inst.ssh(t, aliceKey, "", "mr", "show", "alice/app", "1", "--json")
 82	if !strings.Contains(out, `"milestone":"v1.0"`) {
 83		t.Fatalf("mr show milestone: %s", out)
 84	}
 85
 86	// Progress: 2 open (issue 1 + MR 1); closing the issue moves it.
 87	out, _, _ = inst.ssh(t, aliceKey, "", "milestone", "list", "alice/app", "--json")
 88	if !strings.Contains(out, `"open":2`) || !strings.Contains(out, `"due":"2027-01-01"`) {
 89		t.Fatalf("milestone list: %s", out)
 90	}
 91	if _, _, code := inst.ssh(t, aliceKey, "", "issue", "close", "alice/app", "1"); code != 0 {
 92		t.Fatal("issue close failed")
 93	}
 94	out, _, _ = inst.ssh(t, aliceKey, "", "milestone", "list", "alice/app", "--json")
 95	if !strings.Contains(out, `"open":1`) || !strings.Contains(out, `"closed":1`) {
 96		t.Fatalf("milestone progress: %s", out)
 97	}
 98
 99	// Close/reopen; closed milestones leave the default list.
100	if _, _, code := inst.ssh(t, aliceKey, "", "milestone", "close", "alice/app", "v1.0"); code != 0 {
101		t.Fatal("milestone close failed")
102	}
103	out, _, _ = inst.ssh(t, aliceKey, "", "milestone", "list", "alice/app")
104	if strings.Contains(out, "v1.0") {
105		t.Fatal("closed milestone in open list")
106	}
107	if _, _, code := inst.ssh(t, aliceKey, "", "milestone", "reopen", "alice/app", "v1.0"); code != 0 {
108		t.Fatal("milestone reopen failed")
109	}
110
111	// Web: milestones page with progress; issue page links the milestone.
112	status, body := inst.get(t, "/alice/app/milestones")
113	if status != 200 || !strings.Contains(body, "v1.0") || !strings.Contains(body, "50%") ||
114		!strings.Contains(body, "first release") {
115		t.Fatalf("milestones page: %d\n%s", status, body)
116	}
117	_, body = inst.get(t, "/alice/app/issues/1")
118	if !strings.Contains(body, ">v1.0</a>") {
119		t.Fatal("issue page missing milestone link")
120	}
121
122	// Templates over SSH.
123	out, _, _ = inst.ssh(t, aliceKey, "", "issue", "templates", "alice/app", "--json")
124	if !strings.Contains(out, `"name":"issue-template.md"`) ||
125		!strings.Contains(out, "Steps to reproduce") ||
126		!strings.Contains(out, `"name":"issue-template-feature.md"`) {
127		t.Fatalf("issue templates: %s", out)
128	}
129
130	// Templates on the web form (login required).
131	out, errOut, code := inst.ssh(t, aliceKey, "", "web", "login", "--json")
132	if code != 0 {
133		t.Fatalf("web login: %s", errOut)
134	}
135	var env2 struct {
136		Data struct {
137			URL string `json:"url"`
138		} `json:"data"`
139	}
140	json.Unmarshal([]byte(out), &env2)
141	browser := newBrowser(t)
142	browserGet(t, browser, inst.base()+env2.Data.URL[strings.Index(env2.Data.URL, "/login"):])
143	_, body = browserGet(t, browser, inst.base()+"/alice/app/issues/new")
144	if !strings.Contains(body, "Steps to reproduce") || !strings.Contains(body, "issue-template-feature.md") {
145		t.Fatalf("issue form not prefilled:\n%s", body)
146	}
147	_, body = browserGet(t, browser, inst.base()+"/alice/app/issues/new?template=issue-template-feature.md")
148	if !strings.Contains(body, "Motivation") {
149		t.Fatal("template switch failed")
150	}
151}