internal/store/mrs_test.go

419f6dfdc5489a0c6374e36dd1ebbfca68040056
gitbay/internal/store/mrs_test.go history · blame · raw

236 lines · 7291 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, "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, "reply", rootID, false); err != nil {
216		t.Fatal(err)
217	}
218	if _, err := s.AddDiffComment(mr1.ID, uid, "abc123", "file.txt", "new", 2, "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}