e2e/milestone_test.go
151 lines · 6314 bytes
1 symbol in this file
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}