e2e/diffcomment_test.go

bd5cf5d7d1f34fa780660fd7562b9ffd9746ee27
gitbay/e2e/diffcomment_test.go history · blame · raw

134 lines · 5599 bytes

  1package e2e
  2
  3import (
  4	"encoding/json"
  5	"fmt"
  6	"os"
  7	"path/filepath"
  8	"strings"
  9	"testing"
 10)
 11
 12func TestDiffComments(t *testing.T) {
 13	t.Parallel()
 14	inst := startInstance(t)
 15	aliceKey := inst.newKey(t, "alice")
 16	bobKey := inst.newKey(t, "bob")
 17	eveKey := inst.newKey(t, "eve")
 18	inst.admin(t, "admin", "user", "create", "alice",
 19		"--key", aliceKey+".pub", "--email", "alice@example.test", "--verified")
 20	inst.admin(t, "admin", "user", "create", "bob", "--key", bobKey+".pub")
 21	inst.admin(t, "admin", "user", "create", "eve", "--key", eveKey+".pub")
 22
 23	// MR with a real multi-line diff.
 24	if _, errOut, code := inst.ssh(t, aliceKey, "", "repo", "create", "alice/lib"); code != 0 {
 25		t.Fatalf("repo create: %s", errOut)
 26	}
 27	if _, _, code := inst.ssh(t, aliceKey, "", "repo", "access", "grant", "alice/lib", "bob", "write"); code != 0 {
 28		t.Fatal("grant failed")
 29	}
 30	work := t.TempDir()
 31	env := inst.gitEnv(aliceKey)
 32	mustGit(t, work, env, "clone", inst.sshURL("alice/lib"), "w")
 33	dir := filepath.Join(work, "w")
 34	os.WriteFile(filepath.Join(dir, "main.go"), []byte("package main\n\nfunc main() {\n}\n"), 0o644)
 35	mustGit(t, dir, env, "checkout", "-q", "-b", "main")
 36	mustGit(t, dir, env, "add", ".")
 37	mustGit(t, dir, env, "commit", "-q", "-m", "base")
 38	mustGit(t, dir, env, "push", "-q", "origin", "main")
 39	mustGit(t, dir, env, "checkout", "-q", "-b", "feat")
 40	os.WriteFile(filepath.Join(dir, "main.go"), []byte("package main\n\nimport \"fmt\"\n\nfunc main() {\n\tfmt.Println(\"hi\")\n}\n"), 0o644)
 41	mustGit(t, dir, env, "add", ".")
 42	mustGit(t, dir, env, "commit", "-q", "-m", "add greeting")
 43	mustGit(t, dir, env, "push", "-q", "origin", "feat")
 44	if _, errOut, code := inst.ssh(t, aliceKey, "", "mr", "create", "alice/lib",
 45		"--source", "feat", "--target", "main", "--title", "'greeting'"); code != 0 {
 46		t.Fatalf("mr create: %s", errOut)
 47	}
 48
 49	// A thread on a real diff line; a path outside the diff is refused.
 50	if _, errOut, code := inst.ssh(t, bobKey, "", "mr", "diff-comment", "alice/lib", "1",
 51		"--path", "nope.go", "--line", "1", "--message", "'x'"); code != 2 || !strings.Contains(errOut, "not part of") {
 52		t.Fatalf("off-diff path: exit %d, %s", code, errOut)
 53	}
 54	out, errOut, code := inst.ssh(t, bobKey, "", "mr", "diff-comment", "alice/lib", "1",
 55		"--path", "main.go", "--line", "6", "--message", "'use log instead of fmt'", "--json")
 56	if code != 0 {
 57		t.Fatalf("diff-comment: %s", errOut)
 58	}
 59	var env2 struct {
 60		Data struct {
 61			Thread int64 `json:"thread"`
 62		} `json:"data"`
 63	}
 64	json.Unmarshal([]byte(out), &env2)
 65	thread := env2.Data.Thread
 66
 67	// Reply joins the thread; replying to a reply is refused.
 68	out, _, code = inst.ssh(t, aliceKey, "", "mr", "diff-comment", "alice/lib", "1",
 69		"--reply", fmt.Sprint(thread), "--message", "'will do'", "--json")
 70	if code != 0 {
 71		t.Fatalf("reply failed: %s", out)
 72	}
 73	var env3 struct {
 74		Data struct {
 75			ID int64 `json:"id"`
 76		} `json:"data"`
 77	}
 78	json.Unmarshal([]byte(out), &env3)
 79	if _, errOut, code = inst.ssh(t, bobKey, "", "mr", "diff-comment", "alice/lib", "1",
 80		"--reply", fmt.Sprint(env3.Data.ID), "--message", "'nested'"); code != 1 || !strings.Contains(errOut, "thread root") {
 81		t.Fatalf("nested reply: exit %d, %s", code, errOut)
 82	}
 83
 84	// Threads listing shows the thread, both comments, fresh.
 85	out, _, _ = inst.ssh(t, aliceKey, "", "mr", "threads", "alice/lib", "1", "--json")
 86	if !strings.Contains(out, `"path":"main.go"`) || !strings.Contains(out, `"line":6`) ||
 87		!strings.Contains(out, "will do") || strings.Contains(out, `"stale":true`) {
 88		t.Fatalf("threads: %s", out)
 89	}
 90
 91	// mr show counts the unresolved thread; the web renders it inline.
 92	out, _, _ = inst.ssh(t, aliceKey, "", "mr", "show", "alice/lib", "1", "--json")
 93	if !strings.Contains(out, `"unresolved_threads":1`) {
 94		t.Fatalf("mr show count: %s", out)
 95	}
 96	status, body := inst.get(t, "/alice/lib/mrs/1?view=diff")
 97	if status != 200 || !strings.Contains(body, "use log instead of fmt") ||
 98		!strings.Contains(body, `class="thread`) {
 99		t.Fatalf("web thread: %d", status)
100	}
101	// Detached threads belong to the conversation, not the current diff.
102	if _, conv := inst.get(t, "/alice/lib/mrs/1"); strings.Contains(conv, "Threads on earlier revisions") {
103		t.Fatal("fresh thread rendered as detached")
104	}
105
106	// Resolution: eve (read-only outsider) cannot; the thread author can;
107	// count drops; unresolve restores it.
108	if _, _, code = inst.ssh(t, eveKey, "", "mr", "resolve", "alice/lib", "1", fmt.Sprint(thread)); code != 4 {
109		t.Fatal("outsider resolved a thread")
110	}
111	if _, errOut, code = inst.ssh(t, bobKey, "", "mr", "resolve", "alice/lib", "1", fmt.Sprint(thread)); code != 0 {
112		t.Fatalf("resolve: %s", errOut)
113	}
114	out, _, _ = inst.ssh(t, aliceKey, "", "mr", "show", "alice/lib", "1", "--json")
115	if strings.Contains(out, `"unresolved_threads"`) {
116		t.Fatalf("resolved thread still counted: %s", out)
117	}
118	if _, _, code = inst.ssh(t, bobKey, "", "mr", "unresolve", "alice/lib", "1", fmt.Sprint(thread)); code != 0 {
119		t.Fatal("unresolve failed")
120	}
121
122	// Force-push moves the head: the thread goes stale and the web moves it
123	// to the earlier-revisions section.
124	mustGit(t, dir, env, "commit", "-q", "--amend", "-m", "add greeting (amended)")
125	mustGit(t, dir, env, "push", "-q", "--force", "origin", "feat")
126	out, _, _ = inst.ssh(t, aliceKey, "", "mr", "threads", "alice/lib", "1", "--json")
127	if !strings.Contains(out, `"stale":true`) {
128		t.Fatalf("thread not stale after force-push: %s", out)
129	}
130	_, body = inst.get(t, "/alice/lib/mrs/1")
131	if !strings.Contains(body, "Threads on earlier revisions") {
132		t.Fatal("stale thread not moved to detached section")
133	}
134}