internal/control/mr_test.go

e9566eed86ebcd185c4b85f63d667b5e671fe787
gitbay/internal/control/mr_test.go history · blame · raw

179 lines · 7231 bytes

  1package control
  2
  3import (
  4	"bytes"
  5	"encoding/json"
  6	"strconv"
  7	"strings"
  8	"testing"
  9
 10	"gitbay.org/gitbay/internal/protocol"
 11	"gitbay.org/gitbay/internal/store"
 12)
 13
 14// mrTestCtx runs control commands as owner against st, capturing output.
 15func mrTestCtx(st *store.Store, owner store.User) (*Ctx, *bytes.Buffer, *bytes.Buffer) {
 16	out, errOut := &bytes.Buffer{}, &bytes.Buffer{}
 17	c := &Ctx{User: owner, Scope: "full", Store: st, Stdout: out, Stderr: errOut}
 18	return c, out, errOut
 19}
 20
 21// twoMRTestRepo is a repository with two open merge requests, both
 22// authored by the returned owner, so authorOrWrite never gets in the way.
 23func twoMRTestRepo(t *testing.T) (*store.Store, store.Repo, store.User) {
 24	t.Helper()
 25	st, repo, uid := newQueueTestRepo(t)
 26	owner := store.User{ID: uid, Username: "alice"}
 27	if _, err := st.CreateMR(repo.ID, uid, repo.ID, "feature1", "main", "one", "", "abc111", "md", false); err != nil {
 28		t.Fatal(err)
 29	}
 30	if _, err := st.CreateMR(repo.ID, uid, repo.ID, "feature2", "main", "two", "", "abc222", "md", false); err != nil {
 31		t.Fatal(err)
 32	}
 33	return st, repo, owner
 34}
 35
 36func mrShowJSON(t *testing.T, st *store.Store, owner store.User, path string, n int64) mrOut {
 37	t.Helper()
 38	c, out, errOut := mrTestCtx(st, owner)
 39	if code := Dispatch(c, []string{"mr", "show", path, strconv.FormatInt(n, 10), "--json"}); code != protocol.ExitOK {
 40		t.Fatalf("mr show: exit %d, %s", code, errOut.String())
 41	}
 42	var env struct {
 43		Data mrOut `json:"data"`
 44	}
 45	if err := json.Unmarshal(out.Bytes(), &env); err != nil {
 46		t.Fatalf("mr show JSON: %v\n%s", err, out.String())
 47	}
 48	return env.Data
 49}
 50
 51// eventDataFor pulls the most recent data_json for a kind, so a test can
 52// check what mr close recorded without a store accessor built just for it.
 53func eventDataFor(t *testing.T, st *store.Store, kind string) string {
 54	t.Helper()
 55	var data string
 56	err := st.DB.QueryRow("SELECT data_json FROM events WHERE kind = ? ORDER BY id DESC LIMIT 1", kind).Scan(&data)
 57	if err != nil {
 58		t.Fatalf("event %s: %v", kind, err)
 59	}
 60	return data
 61}
 62
 63// Closing a merge request can name the one that carries its change
 64// forward; mr show and the mr.closed event both then carry it (#223).
 65func TestMRCloseWithBy(t *testing.T) {
 66	st, repo, owner := twoMRTestRepo(t)
 67	c, _, errOut := mrTestCtx(st, owner)
 68	if code := Dispatch(c, []string{"mr", "close", repo.Path(), "1", "--by", "2"}); code != protocol.ExitOK {
 69		t.Fatalf("mr close: exit %d, %s", code, errOut.String())
 70	}
 71	got := mrShowJSON(t, st, owner, repo.Path(), 1)
 72	if got.State != "closed" || got.SupersededBy != 2 {
 73		t.Fatalf("mr show !1 = %+v, want closed superseded_by 2", got)
 74	}
 75	if data := eventDataFor(t, st, "mr.closed"); !strings.Contains(data, `"by":2`) {
 76		t.Fatalf("mr.closed event = %s, want it to carry by:2", data)
 77	}
 78}
 79
 80// mr close --by refuses a merge request naming itself.
 81func TestMRCloseBySelfRefused(t *testing.T) {
 82	st, repo, owner := twoMRTestRepo(t)
 83	c, _, errOut := mrTestCtx(st, owner)
 84	code := Dispatch(c, []string{"mr", "close", repo.Path(), "1", "--by", "1"})
 85	if code != protocol.ExitUsage {
 86		t.Fatalf("exit = %d, want %d; stderr: %s", code, protocol.ExitUsage, errOut.String())
 87	}
 88	if !strings.Contains(errOut.String(), "cannot supersede itself") {
 89		t.Fatalf("stderr = %q, want it to say a merge request cannot supersede itself", errOut.String())
 90	}
 91}
 92
 93// mr close --by refuses a merge request number that does not exist in
 94// the repository.
 95func TestMRCloseByMissingRefused(t *testing.T) {
 96	st, repo, owner := twoMRTestRepo(t)
 97	c, _, errOut := mrTestCtx(st, owner)
 98	code := Dispatch(c, []string{"mr", "close", repo.Path(), "1", "--by", "99"})
 99	if code != protocol.ExitNotFound {
100		t.Fatalf("exit = %d, want %d; stderr: %s", code, protocol.ExitNotFound, errOut.String())
101	}
102	if !strings.Contains(errOut.String(), "no merge request !99") {
103		t.Fatalf("stderr = %q, want it to name !99 as missing", errOut.String())
104	}
105}
106
107// mr edit --superseded-by sets and clears the field on a closed merge
108// request.
109func TestMREditSupersededBySetAndClear(t *testing.T) {
110	st, repo, owner := twoMRTestRepo(t)
111	c, _, errOut := mrTestCtx(st, owner)
112	if code := Dispatch(c, []string{"mr", "close", repo.Path(), "1"}); code != protocol.ExitOK {
113		t.Fatalf("mr close: exit %d, %s", code, errOut.String())
114	}
115	c, _, errOut = mrTestCtx(st, owner)
116	if code := Dispatch(c, []string{"mr", "edit", repo.Path(), "1", "--superseded-by", "2"}); code != protocol.ExitOK {
117		t.Fatalf("mr edit --superseded-by 2: exit %d, %s", code, errOut.String())
118	}
119	if got := mrShowJSON(t, st, owner, repo.Path(), 1); got.SupersededBy != 2 {
120		t.Fatalf("SupersededBy = %d, want 2", got.SupersededBy)
121	}
122	c, _, errOut = mrTestCtx(st, owner)
123	if code := Dispatch(c, []string{"mr", "edit", repo.Path(), "1", "--superseded-by", "none"}); code != protocol.ExitOK {
124		t.Fatalf("mr edit --superseded-by none: exit %d, %s", code, errOut.String())
125	}
126	if got := mrShowJSON(t, st, owner, repo.Path(), 1); got.SupersededBy != 0 {
127		t.Fatalf("SupersededBy after clear = %d, want 0", got.SupersededBy)
128	}
129}
130
131// mr edit --superseded-by refuses a self-reference the same way mr close
132// --by does.
133func TestMREditSupersededBySelfRefused(t *testing.T) {
134	st, repo, owner := twoMRTestRepo(t)
135	c, _, errOut := mrTestCtx(st, owner)
136	if code := Dispatch(c, []string{"mr", "close", repo.Path(), "1"}); code != protocol.ExitOK {
137		t.Fatalf("mr close: exit %d, %s", code, errOut.String())
138	}
139	c, _, errOut = mrTestCtx(st, owner)
140	code := Dispatch(c, []string{"mr", "edit", repo.Path(), "1", "--superseded-by", "1"})
141	if code != protocol.ExitUsage {
142		t.Fatalf("exit = %d, want %d; stderr: %s", code, protocol.ExitUsage, errOut.String())
143	}
144	if !strings.Contains(errOut.String(), "cannot supersede itself") {
145		t.Fatalf("stderr = %q, want it to say a merge request cannot supersede itself", errOut.String())
146	}
147}
148
149// mr edit --superseded-by refuses a merge request number that does not
150// exist in the repository.
151func TestMREditSupersededByMissingRefused(t *testing.T) {
152	st, repo, owner := twoMRTestRepo(t)
153	c, _, errOut := mrTestCtx(st, owner)
154	if code := Dispatch(c, []string{"mr", "close", repo.Path(), "1"}); code != protocol.ExitOK {
155		t.Fatalf("mr close: exit %d, %s", code, errOut.String())
156	}
157	c, _, errOut = mrTestCtx(st, owner)
158	code := Dispatch(c, []string{"mr", "edit", repo.Path(), "1", "--superseded-by", "99"})
159	if code != protocol.ExitNotFound {
160		t.Fatalf("exit = %d, want %d; stderr: %s", code, protocol.ExitNotFound, errOut.String())
161	}
162	if !strings.Contains(errOut.String(), "no merge request !99") {
163		t.Fatalf("stderr = %q, want it to name !99 as missing", errOut.String())
164	}
165}
166
167// mr edit --superseded-by refuses an open merge request: only a closed
168// one can be superseded.
169func TestMREditSupersededByOnOpenMRRefused(t *testing.T) {
170	st, repo, owner := twoMRTestRepo(t)
171	c, _, errOut := mrTestCtx(st, owner)
172	code := Dispatch(c, []string{"mr", "edit", repo.Path(), "1", "--superseded-by", "2"})
173	if code != protocol.ExitUsage {
174		t.Fatalf("exit = %d, want %d; stderr: %s", code, protocol.ExitUsage, errOut.String())
175	}
176	if !strings.Contains(errOut.String(), "only a closed merge request can be superseded") {
177		t.Fatalf("stderr = %q, want the closed-only refusal", errOut.String())
178	}
179}