e2e/dashboard_test.go

v1.0.0
gitbay/e2e/dashboard_test.go history · blame · raw

194 lines · 7867 bytes

  1package e2e
  2
  3import (
  4	"encoding/json"
  5	"os"
  6	"path/filepath"
  7	"strings"
  8	"testing"
  9)
 10
 11func TestDashboard(t *testing.T) {
 12	inst := startInstanceWith(t, "[web]\nmode = \"accounts\"\n")
 13	aliceKey := inst.newKey(t, "alice")
 14	bobKey := inst.newKey(t, "bob")
 15	inst.admin(t, "admin", "user", "create", "alice",
 16		"--key", aliceKey+".pub", "--email", "alice@example.test", "--verified")
 17	inst.admin(t, "admin", "user", "create", "bob", "--key", bobKey+".pub")
 18
 19	// Alice's repo with an open issue and an open MR authored by bob.
 20	if _, errOut, code := inst.ssh(t, aliceKey, "", "repo", "create", "alice/app"); code != 0 {
 21		t.Fatalf("repo create: %s", errOut)
 22	}
 23	if _, _, code := inst.ssh(t, aliceKey, "", "repo", "access", "grant", "alice/app", "bob", "write"); code != 0 {
 24		t.Fatal("grant failed")
 25	}
 26	work := t.TempDir()
 27	env := inst.gitEnv(aliceKey)
 28	mustGit(t, work, env, "clone", inst.sshURL("alice/app"), "w")
 29	dir := filepath.Join(work, "w")
 30	os.WriteFile(filepath.Join(dir, "a.txt"), []byte("a\n"), 0o644)
 31	mustGit(t, dir, env, "checkout", "-q", "-b", "main")
 32	mustGit(t, dir, env, "add", ".")
 33	mustGit(t, dir, env, "commit", "-q", "-m", "base")
 34	mustGit(t, dir, env, "push", "-q", "origin", "main")
 35	mustGit(t, dir, env, "checkout", "-q", "-b", "feat")
 36	os.WriteFile(filepath.Join(dir, "a.txt"), []byte("a\nb\n"), 0o644)
 37	mustGit(t, dir, env, "add", ".")
 38	mustGit(t, dir, env, "commit", "-q", "-m", "feat")
 39	mustGit(t, dir, env, "push", "-q", "origin", "feat")
 40	if _, _, code := inst.ssh(t, bobKey, "", "mr", "create", "alice/app",
 41		"--source", "feat", "--target", "main", "--title", "'from bob'"); code != 0 {
 42		t.Fatal("mr create failed")
 43	}
 44	if _, _, code := inst.ssh(t, aliceKey, "", "issue", "create", "alice/app", "--title", "'todo one'"); code != 0 {
 45		t.Fatal("issue create failed")
 46	}
 47
 48	// Pins: read access required; unpinning something never pinned 404s.
 49	if _, _, code := inst.ssh(t, aliceKey, "", "repo", "pin", "alice/app"); code != 0 {
 50		t.Fatal("pin failed")
 51	}
 52	if _, _, code := inst.ssh(t, aliceKey, "", "repo", "unpin", "alice/app"); code != 0 {
 53		t.Fatal("unpin failed")
 54	}
 55	if _, _, code := inst.ssh(t, aliceKey, "", "repo", "unpin", "alice/app"); code != 3 {
 56		t.Fatal("double unpin should be not-found")
 57	}
 58	if _, _, code := inst.ssh(t, aliceKey, "", "repo", "pin", "alice/app"); code != 0 {
 59		t.Fatal("re-pin failed")
 60	}
 61
 62	// Anonymous homepage: landing with explore link, repos on /explore.
 63	status, body := inst.get(t, "/")
 64	if status != 200 || !strings.Contains(body, `href="/explore"`) || !strings.Contains(body, "CLI-first") {
 65		t.Fatalf("landing: %d", status)
 66	}
 67	if strings.Contains(body, "alice/app") {
 68		t.Fatal("landing lists repositories")
 69	}
 70	_, body = inst.get(t, "/explore")
 71	if !strings.Contains(body, "alice/app") {
 72		t.Fatal("explore missing public repo")
 73	}
 74
 75	// Logged-in homepage: dashboard with pinned repo and open items.
 76	out, errOut, code := inst.ssh(t, aliceKey, "", "web", "login", "--json")
 77	if code != 0 {
 78		t.Fatalf("web login: %s", errOut)
 79	}
 80	var env2 struct {
 81		Data struct {
 82			URL string `json:"url"`
 83		} `json:"data"`
 84	}
 85	json.Unmarshal([]byte(out), &env2)
 86	loginPath := env2.Data.URL[strings.Index(env2.Data.URL, "/login"):]
 87	browser := newBrowser(t)
 88	if status, _ := browserGet(t, browser, inst.base()+loginPath); status != 200 {
 89		t.Fatalf("login: %d", status)
 90	}
 91	status, body = browserGet(t, browser, inst.base()+"/")
 92	if status != 200 || !strings.Contains(body, ">Dashboard</h1>") {
 93		t.Fatalf("dashboard: %d", status)
 94	}
 95	for _, want := range []string{
 96		">Pinned</h2>", ">app<", // pinned group in the aside
 97		"Waiting on your review", "Assigned to you", "Recent activity",
 98		"from bob", "alice/app!1", "todo one", "alice/app#1",
 99		`href="/alice/app/mrs/1"`, `href="/alice/app/issues/1"`,
100	} {
101		if !strings.Contains(body, want) {
102			t.Errorf("dashboard missing %q", want)
103		}
104	}
105
106	// The diff has its own view rather than a fold at the foot of the
107	// conversation: the default view offers it, and asking for it renders
108	// the stat line and the patch.
109	_, body = inst.get(t, "/alice/app/mrs/1")
110	if !strings.Contains(body, `href="/alice/app/mrs/1?view=diff"`) {
111		t.Fatal("merge request missing the files-changed view")
112	}
113	if strings.Contains(body, `class="difftable"`) {
114		t.Fatal("diff rendered on the conversation view")
115	}
116	_, body = inst.get(t, "/alice/app/mrs/1?view=diff")
117	if !strings.Contains(body, "1 file changed") || !strings.Contains(body, `class="difftable"`) {
118		t.Fatalf("diff view missing stat or patch:\n%s", body)
119	}
120}
121
122// TestDashboardQueues covers the parts of the dashboard that answer "what
123// needs me": the review queue, assigned issues, and the activity feed.
124func TestDashboardQueues(t *testing.T) {
125	inst := startInstanceWith(t, "[web]\nmode = \"accounts\"\n")
126	aliceKey := inst.newKey(t, "alice")
127	bobKey := inst.newKey(t, "bob")
128	inst.admin(t, "admin", "user", "create", "alice",
129		"--key", aliceKey+".pub", "--email", "alice@example.test", "--verified")
130	inst.admin(t, "admin", "user", "create", "bob",
131		"--key", bobKey+".pub", "--email", "bob@example.test", "--verified")
132
133	if _, errOut, code := inst.ssh(t, aliceKey, "", "repo", "create", "alice/app"); code != 0 {
134		t.Fatalf("repo create: %s", errOut)
135	}
136	if _, errOut, code := inst.ssh(t, aliceKey, "", "repo", "access", "grant", "alice/app", "bob", "write"); code != 0 {
137		t.Fatalf("grant: %s", errOut)
138	}
139	env := inst.gitEnv(aliceKey)
140	work := t.TempDir()
141	mustGit(t, work, env, "clone", inst.sshURL("alice/app"), "w")
142	dir := filepath.Join(work, "w")
143	os.WriteFile(filepath.Join(dir, "a.txt"), []byte("a\n"), 0o644)
144	mustGit(t, dir, env, "checkout", "-q", "-b", "main")
145	mustGit(t, dir, env, "add", ".")
146	mustGit(t, dir, env, "commit", "-q", "-m", "base")
147	mustGit(t, dir, env, "push", "-q", "origin", "main")
148
149	// Bob opens a merge request: it lands in alice's review queue.
150	bobEnv := inst.gitEnv(bobKey)
151	bobWork := t.TempDir()
152	mustGit(t, bobWork, bobEnv, "clone", inst.sshURL("alice/app"), "w")
153	bobDir := filepath.Join(bobWork, "w")
154	mustGit(t, bobDir, bobEnv, "checkout", "-q", "-b", "fix", "origin/main")
155	os.WriteFile(filepath.Join(bobDir, "b.txt"), []byte("b\n"), 0o644)
156	mustGit(t, bobDir, bobEnv, "add", ".")
157	mustGit(t, bobDir, bobEnv, "commit", "-q", "-m", "the fix")
158	mustGit(t, bobDir, bobEnv, "push", "-q", "origin", "fix")
159	if _, errOut, code := inst.ssh(t, bobKey, "", "mr", "create", "alice/app",
160		"--source", "fix", "--target", "main", "--title", "'needs a look'"); code != 0 {
161		t.Fatalf("mr create: %s", errOut)
162	}
163	// And an issue assigned to alice.
164	if _, errOut, code := inst.ssh(t, bobKey, "", "issue", "create", "alice/app", "--title", "'please handle'"); code != 0 {
165		t.Fatalf("issue create: %s", errOut)
166	}
167	if _, errOut, code := inst.ssh(t, aliceKey, "", "issue", "assign", "alice/app", "1", "--add", "alice"); code != 0 {
168		t.Fatalf("assign: %s", errOut)
169	}
170
171	_, body := browserGet(t, inst.login(t, aliceKey), inst.base()+"/")
172	if !strings.Contains(body, "needs a look") {
173		t.Fatalf("review queue missing the MR:\n%s", body)
174	}
175	if !strings.Contains(body, "please handle") {
176		t.Fatalf("assigned issues missing the issue:\n%s", body)
177	}
178	// The feed reports what happened, phrased and linked.
179	for _, want := range []string{"opened merge request", "opened issue", `href="/alice/app/mrs/1"`} {
180		if !strings.Contains(body, want) {
181			t.Fatalf("feed missing %q:\n%s", want, body)
182		}
183	}
184
185	// Once alice reviews, the merge request leaves her queue.
186	if _, errOut, code := inst.ssh(t, aliceKey, "", "mr", "review", "alice/app", "1", "--approve"); code != 0 {
187		t.Fatalf("review: %s", errOut)
188	}
189	_, after := browserGet(t, inst.login(t, aliceKey), inst.base()+"/")
190	queue := after[strings.Index(after, "Waiting on your review"):strings.Index(after, "Assigned to you")]
191	if strings.Contains(queue, "needs a look") {
192		t.Fatalf("reviewed MR still waiting:\n%s", queue)
193	}
194}