internal/store/mrs_test.go

v1.42.0
gitbay/internal/store/mrs_test.go history · blame · raw

330 lines · 9920 bytes

  1package store
  2
  3import "testing"
  4
  5func mrFixture(t *testing.T) (*Store, int64, int64) {
  6	t.Helper()
  7	s := open(t)
  8	if err := s.MigrateUp(); err != nil {
  9		t.Fatal(err)
 10	}
 11	uid, err := s.CreateUser("cmc", true)
 12	if err != nil {
 13		t.Fatal(err)
 14	}
 15	repoID, err := s.CreateRepo("user", uid, "lib", "public")
 16	if err != nil {
 17		t.Fatal(err)
 18	}
 19	if _, err := s.CreateMR(repoID, uid, repoID, "feature", "main", "t", "", "abc123", "md", false); err != nil {
 20		t.Fatal(err)
 21	}
 22	return s, repoID, uid
 23}
 24
 25// A merged or closed MR records who resolved it and when: the state alone
 26// cannot say it, and updated_at moves for every edit.
 27func TestResolutionStamps(t *testing.T) {
 28	s, repoID, uid := mrFixture(t)
 29	mr, err := s.MRByNumber(repoID, 1)
 30	if err != nil {
 31		t.Fatal(err)
 32	}
 33	if mr.MergedAt != "" || mr.ClosedAt != "" {
 34		t.Fatalf("open MR carries a stamp: %+v", mr)
 35	}
 36	if err := s.MarkMerged(mr.ID, "base1", uid, ""); err != nil {
 37		t.Fatal(err)
 38	}
 39	mr, _ = s.MRByNumber(repoID, 1)
 40	if mr.State != "merged" || mr.MergedAt == "" || mr.MergedBy != "cmc" {
 41		t.Fatalf("merge stamp: %+v", mr)
 42	}
 43
 44	// Reopening — a source branch that came back — clears the stamp.
 45	if err := s.SetMRState(mr.ID, "open"); err != nil {
 46		t.Fatal(err)
 47	}
 48	mr, _ = s.MRByNumber(repoID, 1)
 49	if mr.MergedAt != "" || mr.MergedBy != "" {
 50		t.Fatalf("reopen kept the merge stamp: %+v", mr)
 51	}
 52
 53	if err := s.MarkClosed(mr.ID, uid, ""); err != nil {
 54		t.Fatal(err)
 55	}
 56	mr, _ = s.MRByNumber(repoID, 1)
 57	if mr.State != "closed" || mr.ClosedAt == "" || mr.ClosedBy != "cmc" {
 58		t.Fatalf("close stamp: %+v", mr)
 59	}
 60}
 61
 62// A merge request closed without merging can record the request that
 63// carried its change forward; MRsSuperseding is the reverse lookup, and
 64// 0 clears the field (#223).
 65func TestSupersededBy(t *testing.T) {
 66	s, repoID, uid := mrFixture(t)
 67	if _, err := s.CreateMR(repoID, uid, repoID, "feature2", "main", "t2", "", "def456", "md", false); err != nil {
 68		t.Fatal(err)
 69	}
 70	mr1, _ := s.MRByNumber(repoID, 1)
 71	if err := s.MarkClosed(mr1.ID, uid, ""); err != nil {
 72		t.Fatal(err)
 73	}
 74	if err := s.SetSupersededBy(mr1.ID, 2); err != nil {
 75		t.Fatal(err)
 76	}
 77	mr1, _ = s.MRByNumber(repoID, 1)
 78	if mr1.SupersededBy != 2 {
 79		t.Fatalf("SupersededBy = %d, want 2", mr1.SupersededBy)
 80	}
 81	superseding, err := s.MRsSuperseding(repoID, 2)
 82	if err != nil {
 83		t.Fatal(err)
 84	}
 85	if len(superseding) != 1 || superseding[0].Number != 1 {
 86		t.Fatalf("MRsSuperseding(repoID, 2) = %+v", superseding)
 87	}
 88	if err := s.SetSupersededBy(mr1.ID, 0); err != nil {
 89		t.Fatal(err)
 90	}
 91	mr1, _ = s.MRByNumber(repoID, 1)
 92	if mr1.SupersededBy != 0 {
 93		t.Fatalf("SupersededBy after clear = %d, want 0", mr1.SupersededBy)
 94	}
 95	superseding, err = s.MRsSuperseding(repoID, 2)
 96	if err != nil {
 97		t.Fatal(err)
 98	}
 99	if len(superseding) != 0 {
100		t.Fatalf("MRsSuperseding(repoID, 2) after clear = %+v", superseding)
101	}
102}
103
104// An import carries the upstream time but no local account for the actor.
105func TestResolutionStampImported(t *testing.T) {
106	s, repoID, _ := mrFixture(t)
107	mr, _ := s.MRByNumber(repoID, 1)
108	if err := s.MarkMerged(mr.ID, "base1", 0, "2024-03-02T10:00:00Z"); err != nil {
109		t.Fatal(err)
110	}
111	mr, _ = s.MRByNumber(repoID, 1)
112	if mr.MergedAt != "2024-03-02T10:00:00Z" || mr.MergedBy != "" {
113		t.Fatalf("imported merge stamp: %+v", mr)
114	}
115}
116
117// PreferredVerifiedEmail falls back to a verified secondary when the
118// primary is not verified, unlike PrimaryVerifiedEmail (#158).
119func TestPreferredVerifiedEmail(t *testing.T) {
120	s := open(t)
121	if err := s.MigrateUp(); err != nil {
122		t.Fatal(err)
123	}
124	uid, err := s.CreateUser("gus", false)
125	if err != nil {
126		t.Fatal(err)
127	}
128
129	if addr, err := s.PreferredVerifiedEmail(uid); err != nil || addr != "" {
130		t.Fatalf("no addresses at all: %q, %v", addr, err)
131	}
132
133	if err := s.AddEmail(uid, "primary@example.test", "", true); err != nil {
134		t.Fatal(err)
135	}
136	if addr, err := s.PreferredVerifiedEmail(uid); err != nil || addr != "" {
137		t.Fatalf("unverified primary only: %q, %v", addr, err)
138	}
139	if addr, err := s.PrimaryVerifiedEmail(uid); err != nil || addr != "" {
140		t.Fatalf("PrimaryVerifiedEmail on an unverified primary: %q, %v", addr, err)
141	}
142
143	if err := s.AddEmail(uid, "secondary@example.test", "smtp", false); err != nil {
144		t.Fatal(err)
145	}
146	if addr, err := s.PreferredVerifiedEmail(uid); err != nil || addr != "secondary@example.test" {
147		t.Fatalf("unverified primary, verified secondary: %q, %v", addr, err)
148	}
149	// PrimaryVerifiedEmail keeps meaning exactly what it says: still "",
150	// because the primary itself is still unverified.
151	if addr, err := s.PrimaryVerifiedEmail(uid); err != nil || addr != "" {
152		t.Fatalf("PrimaryVerifiedEmail with only the secondary verified: %q, %v", addr, err)
153	}
154
155	if err := s.VerifyEmail(uid, "primary@example.test", "admin"); err != nil {
156		t.Fatal(err)
157	}
158	if addr, err := s.PreferredVerifiedEmail(uid); err != nil || addr != "primary@example.test" {
159		t.Fatalf("both verified, primary should win: %q, %v", addr, err)
160	}
161}
162
163// With no verified primary, the choice among verified secondaries must not
164// depend on insertion or row order.
165func TestPreferredVerifiedEmailDeterministicTiebreak(t *testing.T) {
166	s := open(t)
167	if err := s.MigrateUp(); err != nil {
168		t.Fatal(err)
169	}
170	uid, err := s.CreateUser("gus", false)
171	if err != nil {
172		t.Fatal(err)
173	}
174	if err := s.AddEmail(uid, "primary@example.test", "", true); err != nil {
175		t.Fatal(err)
176	}
177	if err := s.AddEmail(uid, "zzz@example.test", "smtp", false); err != nil {
178		t.Fatal(err)
179	}
180	if err := s.AddEmail(uid, "aaa@example.test", "smtp", false); err != nil {
181		t.Fatal(err)
182	}
183	if addr, err := s.PreferredVerifiedEmail(uid); err != nil || addr != "aaa@example.test" {
184		t.Fatalf("tiebreak should be alphabetical: %q, %v", addr, err)
185	}
186}
187
188// MRCommentCounts folds conversation comments and diff-thread roots into
189// one count per MR, for the list page. System comments, diff-thread
190// replies, and pending (unpublished) diff comments do not count.
191func TestMRCommentCounts(t *testing.T) {
192	s, repoID, uid := mrFixture(t)
193	mr1, err := s.MRByNumber(repoID, 1)
194	if err != nil {
195		t.Fatal(err)
196	}
197	if _, err := s.CreateMR(repoID, uid, repoID, "feature2", "main", "t2", "", "def456", "md", false); err != nil {
198		t.Fatal(err)
199	}
200	mr2, err := s.MRByNumber(repoID, 2)
201	if err != nil {
202		t.Fatal(err)
203	}
204
205	if err := s.AddMRComment(mr1.ID, uid, "hi", "md"); err != nil {
206		t.Fatal(err)
207	}
208	if err := s.AddMRSystemComment(mr1.ID, uid, "merged"); err != nil {
209		t.Fatal(err)
210	}
211	rootID, err := s.AddDiffComment(mr1.ID, uid, "abc123", "file.txt", "new", 1, 0, "root", 0, false)
212	if err != nil {
213		t.Fatal(err)
214	}
215	if _, err := s.AddDiffComment(mr1.ID, uid, "abc123", "file.txt", "new", 1, 0, "reply", rootID, false); err != nil {
216		t.Fatal(err)
217	}
218	if _, err := s.AddDiffComment(mr1.ID, uid, "abc123", "file.txt", "new", 2, 0, "pending root", 0, true); err != nil {
219		t.Fatal(err)
220	}
221
222	counts, err := s.MRCommentCounts(repoID, []int64{mr1.ID, mr2.ID})
223	if err != nil {
224		t.Fatal(err)
225	}
226	if counts[mr1.ID] != 2 {
227		t.Fatalf("mr1 count = %d, want 2 (1 comment + 1 diff root)", counts[mr1.ID])
228	}
229	if counts[mr2.ID] != 0 {
230		t.Fatalf("mr2 count = %d, want 0", counts[mr2.ID])
231	}
232
233	if empty, err := s.MRCommentCounts(repoID, nil); err != nil || len(empty) != 0 {
234		t.Fatalf("MRCommentCounts(nil) = %v, %v", empty, err)
235	}
236}
237
238// The list batches return what the per-item calls return, keyed by item.
239func TestListPageBatches(t *testing.T) {
240	s, repoID, uid := mrFixture(t)
241	mr1, err := s.MRByNumber(repoID, 1)
242	if err != nil {
243		t.Fatal(err)
244	}
245	if err := s.AddMRReview(mr1.ID, uid, "approve", "abc123"); err != nil {
246		t.Fatal(err)
247	}
248	reviews, err := s.MRReviewsFor([]int64{mr1.ID, mr1.ID + 99})
249	if err != nil {
250		t.Fatal(err)
251	}
252	if got := reviews[mr1.ID]; len(got) != 1 || got[0].Reviewer != "cmc" || got[0].Verdict != "approve" {
253		t.Errorf("reviews = %+v", got)
254	}
255	if len(reviews[mr1.ID+99]) != 0 {
256		t.Errorf("reviews for a missing MR: %+v", reviews[mr1.ID+99])
257	}
258
259	if err := s.SetCommitStatus(repoID, "abc123", "ci/test", "failure", "", "", uid); err != nil {
260		t.Fatal(err)
261	}
262	if err := s.SetCommitStatus(repoID, "abc123", "ci/build", "success", "", "", uid); err != nil {
263		t.Fatal(err)
264	}
265	statuses, err := s.CommitStatusesFor(repoID, []string{"abc123", "def456"})
266	if err != nil {
267		t.Fatal(err)
268	}
269	if got := statuses["abc123"]; len(got) != 2 || got[0].Context != "ci/build" || got[1].State != "failure" {
270		t.Errorf("statuses = %+v", got)
271	}
272	if len(statuses["def456"]) != 0 {
273		t.Errorf("statuses for a commit with none: %+v", statuses["def456"])
274	}
275
276	n, err := s.CreateIssue(repoID, uid, "t", "", "md")
277	if err != nil {
278		t.Fatal(err)
279	}
280	issue, err := s.IssueByNumber(repoID, n)
281	if err != nil {
282		t.Fatal(err)
283	}
284	if err := s.AddIssueComment(issue.ID, uid, "hi", "md"); err != nil {
285		t.Fatal(err)
286	}
287	if err := s.AddIssueSystemComment(issue.ID, uid, "closed"); err != nil {
288		t.Fatal(err)
289	}
290	counts, err := s.IssueCommentCounts([]int64{issue.ID})
291	if err != nil {
292		t.Fatal(err)
293	}
294	if counts[issue.ID] != 1 {
295		t.Errorf("comment count = %d, want 1", counts[issue.ID])
296	}
297}
298
299func TestLabelAndRequestBatches(t *testing.T) {
300	s, repoID, uid := mrFixture(t)
301	mr, err := s.MRByNumber(repoID, 1)
302	if err != nil {
303		t.Fatal(err)
304	}
305	if err := s.SetMRReviewRequest(mr.ID, uid, true); err != nil {
306		t.Fatal(err)
307	}
308	reqs, err := s.MRReviewRequestsFor([]int64{mr.ID})
309	if err != nil {
310		t.Fatal(err)
311	}
312	if got := reqs[mr.ID]; len(got) != 1 || got[0] != "cmc" {
313		t.Errorf("review requests = %v", got)
314	}
315	n, err := s.CreateIssue(repoID, uid, "t", "", "md")
316	if err != nil {
317		t.Fatal(err)
318	}
319	issue, err := s.IssueByNumber(repoID, n)
320	if err != nil {
321		t.Fatal(err)
322	}
323	labels, assignees, err := s.IssueLabelsFor([]int64{issue.ID})
324	if err != nil {
325		t.Fatal(err)
326	}
327	if len(labels[issue.ID]) != 0 || len(assignees[issue.ID]) != 0 {
328		t.Errorf("fresh issue: labels %v, assignees %v", labels[issue.ID], assignees[issue.ID])
329	}
330}