e2e/mrretarget_test.go

419f6dfdc5489a0c6374e36dd1ebbfca68040056
gitbay/e2e/mrretarget_test.go history · blame · raw

128 lines · 5425 bytes

  1package e2e
  2
  3import (
  4	"net/url"
  5	"os"
  6	"path/filepath"
  7	"strings"
  8	"testing"
  9)
 10
 11// TestMRRetarget moves an open merge request onto another branch, over
 12// SSH and from the browser. Retargeting changes which diff a review was
 13// of, so the existing approvals have to go stale with it.
 14func TestMRRetarget(t *testing.T) {
 15	t.Parallel()
 16	inst := startInstanceWith(t, "[web]\nmode = \"accounts\"\n")
 17	aliceKey := inst.newKey(t, "alice")
 18	bobKey := inst.newKey(t, "bob")
 19	inst.admin(t, "admin", "user", "create", "alice",
 20		"--key", aliceKey+".pub", "--email", "alice@example.test", "--verified")
 21	inst.admin(t, "admin", "user", "create", "bob",
 22		"--key", bobKey+".pub", "--email", "bob@example.test", "--verified")
 23
 24	if _, errOut, code := inst.ssh(t, aliceKey, "", "repo", "create", "alice/lib"); code != 0 {
 25		t.Fatalf("repo create: %s", errOut)
 26	}
 27	if _, errOut, code := inst.ssh(t, aliceKey, "", "repo", "access", "grant", "alice/lib", "bob", "write"); code != 0 {
 28		t.Fatalf("grant: %s", errOut)
 29	}
 30
 31	env := inst.gitEnv(aliceKey)
 32	work := t.TempDir()
 33	mustGit(t, work, env, "clone", inst.sshURL("alice/lib"), "w")
 34	dir := filepath.Join(work, "w")
 35	os.WriteFile(filepath.Join(dir, "lib.txt"), []byte("v1\n"), 0o644)
 36	mustGit(t, dir, env, "checkout", "-q", "-b", "main")
 37	mustGit(t, dir, env, "add", ".")
 38	mustGit(t, dir, env, "commit", "-q", "-m", "base")
 39	mustGit(t, dir, env, "push", "-q", "origin", "main")
 40	// A second long-lived branch to retarget onto.
 41	mustGit(t, dir, env, "checkout", "-q", "-b", "release")
 42	os.WriteFile(filepath.Join(dir, "release.txt"), []byte("1.0\n"), 0o644)
 43	mustGit(t, dir, env, "add", ".")
 44	mustGit(t, dir, env, "commit", "-q", "-m", "cut release")
 45	mustGit(t, dir, env, "push", "-q", "origin", "release")
 46	mustGit(t, dir, env, "checkout", "-q", "-b", "feat", "main")
 47	os.WriteFile(filepath.Join(dir, "feat.txt"), []byte("work\n"), 0o644)
 48	mustGit(t, dir, env, "add", ".")
 49	mustGit(t, dir, env, "commit", "-q", "-m", "add feat")
 50	mustGit(t, dir, env, "push", "-q", "origin", "feat")
 51
 52	if _, errOut, code := inst.ssh(t, aliceKey, "", "mr", "create", "alice/lib",
 53		"--source", "feat", "--target", "main", "--title", "'feature'"); code != 0 {
 54		t.Fatalf("mr create: %s", errOut)
 55	}
 56	if _, errOut, code := inst.ssh(t, bobKey, "", "mr", "review", "alice/lib", "1", "--approve"); code != 0 {
 57		t.Fatalf("review: %s", errOut)
 58	}
 59
 60	// Refusals first: an unknown branch, the branch it already targets,
 61	// and its own source.
 62	if _, errOut, code := inst.ssh(t, aliceKey, "", "mr", "retarget", "alice/lib", "1", "nope"); code != 3 ||
 63		!strings.Contains(errOut, "not found") {
 64		t.Fatalf("unknown branch: exit %d, %s", code, errOut)
 65	}
 66	if _, errOut, code := inst.ssh(t, aliceKey, "", "mr", "retarget", "alice/lib", "1", "main"); code != 2 ||
 67		!strings.Contains(errOut, "already targets") {
 68		t.Fatalf("same branch: exit %d, %s", code, errOut)
 69	}
 70	if _, errOut, code := inst.ssh(t, aliceKey, "", "mr", "retarget", "alice/lib", "1", "feat"); code != 2 ||
 71		!strings.Contains(errOut, "source branch") {
 72		t.Fatalf("source branch: exit %d, %s", code, errOut)
 73	}
 74	// Nobody outside the repo can move it.
 75	eveKey := inst.newKey(t, "eve")
 76	inst.admin(t, "admin", "user", "create", "eve", "--key", eveKey+".pub")
 77	if _, _, code := inst.ssh(t, eveKey, "", "mr", "retarget", "alice/lib", "1", "release"); code == 0 {
 78		t.Fatal("a stranger retargeted the merge request")
 79	}
 80
 81	if _, errOut, code := inst.ssh(t, aliceKey, "", "mr", "retarget", "alice/lib", "1", "release"); code != 0 {
 82		t.Fatalf("retarget: %s", errOut)
 83	}
 84	show := inst.mrShow(t, aliceKey, "alice/lib", "1")
 85	if show.TargetRef != "release" {
 86		t.Fatalf("target not moved: %q", show.TargetRef)
 87	}
 88	if len(show.Reviews) != 1 || !show.Reviews[0].Stale {
 89		t.Fatalf("approval survived the retarget: %+v", show.Reviews)
 90	}
 91	// The diff follows the new base: release.txt is on the target now, so
 92	// it is no longer part of the change.
 93	out, errOut, code := inst.ssh(t, aliceKey, "", "mr", "diff", "alice/lib", "1")
 94	if code != 0 {
 95		t.Fatalf("mr diff: %s", errOut)
 96	}
 97	if !strings.Contains(out, "feat.txt") || strings.Contains(out, "release.txt") {
 98		t.Fatalf("diff not rebased on the new target:\n%s", out)
 99	}
100
101	// The move is recorded on the conversation.
102	if out, _, _ := inst.ssh(t, aliceKey, "", "mr", "show", "alice/lib", "1"); !strings.Contains(out, "retargeted from main to release") {
103		t.Fatalf("no system comment for the move:\n%s", out)
104	}
105
106	// And the browser can do it, through the same command.
107	mrURL := inst.base() + "/alice/lib/mrs/1"
108	alice := inst.login(t, aliceKey)
109	_, body := browserGet(t, alice, mrURL)
110	if !strings.Contains(body, `action="/alice/lib/mrs/1/retarget"`) {
111		t.Fatalf("no retarget control on the MR page:\n%s", body)
112	}
113	if status, _ := browserPost(t, alice, mrURL+"/retarget", url.Values{"target": {"main"}}); status != 200 {
114		t.Fatalf("retarget post: %d", status)
115	}
116	if got := inst.mrShow(t, aliceKey, "alice/lib", "1").TargetRef; got != "main" {
117		t.Fatalf("web retarget did not land: %q", got)
118	}
119
120	// A merged merge request is settled.
121	if _, errOut, code := inst.ssh(t, aliceKey, "", "mr", "merge", "alice/lib", "1"); code != 0 {
122		t.Fatalf("merge: %s", errOut)
123	}
124	if _, errOut, code := inst.ssh(t, aliceKey, "", "mr", "retarget", "alice/lib", "1", "release"); code != 2 ||
125		!strings.Contains(errOut, "only an open merge request") {
126		t.Fatalf("merged MR retargeted: exit %d, %s", code, errOut)
127	}
128}