internal/control/mr_test.go
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}