internal/store/inbox_test.go

v1.18.0
gitbay/internal/store/inbox_test.go history · blame · raw

174 lines · 5318 bytes

  1package store
  2
  3import "testing"
  4
  5func inboxFixture(t *testing.T) (s *Store, repoID, owner, other int64) {
  6	t.Helper()
  7	s = open(t)
  8	if err := s.MigrateUp(); err != nil {
  9		t.Fatal(err)
 10	}
 11	owner, err := s.CreateUser("cmc", true)
 12	if err != nil {
 13		t.Fatal(err)
 14	}
 15	other, err = s.CreateUser("kim", false)
 16	if err != nil {
 17		t.Fatal(err)
 18	}
 19	repoID, err = s.CreateRepo("user", owner, "lib", "public")
 20	if err != nil {
 21		t.Fatal(err)
 22	}
 23	return s, repoID, owner, other
 24}
 25
 26func TestInboxUnreadAndRead(t *testing.T) {
 27	s, repoID, owner, _ := inboxFixture(t)
 28	for _, n := range []string{"opened issue #1", "commented on #1", "closed #1"} {
 29		if err := s.AddNotice(owner, repoID, "issue", "kim", n, "cmc/lib/issues/1"); err != nil {
 30			t.Fatal(err)
 31		}
 32	}
 33	if got := s.UnreadNotices(owner); got != 3 {
 34		t.Fatalf("unread = %d, want 3", got)
 35	}
 36
 37	// Newest first, and the repo path is resolved from the polymorphic owner.
 38	got, err := s.Inbox(owner, true, 0, 0)
 39	if err != nil {
 40		t.Fatal(err)
 41	}
 42	if len(got) != 3 || got[0].Summary != "closed #1" || got[0].RepoPath != "cmc/lib" {
 43		t.Fatalf("inbox = %+v", got)
 44	}
 45
 46	// Marking one read drops it from the unread list but not from --all.
 47	if n, err := s.MarkNoticesRead(owner, []int64{got[0].ID}); err != nil || n != 1 {
 48		t.Fatalf("MarkNoticesRead = %d, %v", n, err)
 49	}
 50	if got := s.UnreadNotices(owner); got != 2 {
 51		t.Fatalf("unread after read = %d, want 2", got)
 52	}
 53	all, _ := s.Inbox(owner, false, 0, 0)
 54	if len(all) != 3 || all[0].ReadAt == "" {
 55		t.Fatalf("all = %+v", all)
 56	}
 57
 58	// The rest go in one sweep, and a second sweep changes nothing.
 59	if n, _ := s.MarkNoticesRead(owner, nil); n != 2 {
 60		t.Fatalf("sweep marked %d, want 2", n)
 61	}
 62	if n, _ := s.MarkNoticesRead(owner, nil); n != 0 {
 63		t.Fatalf("second sweep marked %d, want 0", n)
 64	}
 65}
 66
 67// An id belonging to someone else matches nothing, so `notifications read
 68// <id>` cannot reach into another account's inbox.
 69func TestInboxIsPerUser(t *testing.T) {
 70	s, repoID, owner, other := inboxFixture(t)
 71	if err := s.AddNotice(owner, repoID, "issue", "kim", "opened issue #1", "cmc/lib/issues/1"); err != nil {
 72		t.Fatal(err)
 73	}
 74	mine, _ := s.Inbox(owner, true, 0, 0)
 75	if n, err := s.MarkNoticesRead(other, []int64{mine[0].ID}); err != nil || n != 0 {
 76		t.Fatalf("cross-user read marked %d rows (%v)", n, err)
 77	}
 78	if s.UnreadNotices(owner) != 1 {
 79		t.Fatal("another user's read cleared the owner's notice")
 80	}
 81	if got, _ := s.Inbox(other, true, 0, 0); len(got) != 0 {
 82		t.Fatalf("other user sees %+v", got)
 83	}
 84}
 85
 86func TestInboxPaging(t *testing.T) {
 87	s, repoID, owner, _ := inboxFixture(t)
 88	for i := 0; i < 5; i++ {
 89		if err := s.AddNotice(owner, repoID, "issue", "kim", "note", "cmc/lib/issues/1"); err != nil {
 90			t.Fatal(err)
 91		}
 92	}
 93	first, err := s.Inbox(owner, true, 2, 0)
 94	if err != nil {
 95		t.Fatal(err)
 96	}
 97	if len(first) != 2 {
 98		t.Fatalf("first page %d rows, want 2", len(first))
 99	}
100	next, err := s.Inbox(owner, true, 2, first[1].ID)
101	if err != nil {
102		t.Fatal(err)
103	}
104	if len(next) != 2 || next[0].ID >= first[1].ID {
105		t.Fatalf("second page %+v does not follow %+v", next, first)
106	}
107}
108
109// Watching widens the recipients, muting removes a user who would
110// otherwise be told, and the actor is never notified of their own action.
111func TestNotifyRecipients(t *testing.T) {
112	s, repoID, owner, other := inboxFixture(t)
113	third, err := s.CreateUser("lee", false)
114	if err != nil {
115		t.Fatal(err)
116	}
117
118	got, err := s.NotifyRecipients(repoID, other, []int64{owner, other}, true)
119	if err != nil {
120		t.Fatal(err)
121	}
122	if len(got) != 1 || got[0] != owner {
123		t.Fatalf("default recipients = %v, want [%d]", got, owner)
124	}
125
126	if err := s.SetRepoWatch(repoID, third, "watching"); err != nil {
127		t.Fatal(err)
128	}
129	if got, _ := s.NotifyRecipients(repoID, other, []int64{owner}, true); len(got) != 2 {
130		t.Fatalf("watcher not added: %v", got)
131	}
132
133	// A watcher who is also a target is listed once.
134	if got, _ := s.NotifyRecipients(repoID, other, []int64{owner, third}, true); len(got) != 2 {
135		t.Fatalf("watcher duplicated: %v", got)
136	}
137	// A direct notice stays with its targets; watchers are not added.
138	if got, _ := s.NotifyRecipients(repoID, other, []int64{owner}, false); len(got) != 1 || got[0] != owner {
139		t.Fatalf("direct notice widened: %v", got)
140	}
141
142	// Muting beats owning the repository.
143	if err := s.SetRepoWatch(repoID, owner, "muted"); err != nil {
144		t.Fatal(err)
145	}
146	got, _ = s.NotifyRecipients(repoID, other, []int64{owner}, true)
147	if len(got) != 1 || got[0] != third {
148		t.Fatalf("muted owner still notified: %v", got)
149	}
150	if s.RepoWatchState(repoID, owner) != "muted" {
151		t.Fatal("watch state not recorded")
152	}
153	// Clearing returns the owner to the default and they are told again.
154	if err := s.ClearRepoWatch(repoID, owner); err != nil {
155		t.Fatal(err)
156	}
157	if s.RepoWatchState(repoID, owner) != "" {
158		t.Fatal("clear left a state")
159	}
160	if got, _ := s.NotifyRecipients(repoID, other, []int64{owner}, true); len(got) != 2 {
161		t.Fatalf("cleared owner not notified: %v", got)
162	}
163	if err := s.SetRepoWatch(repoID, owner, "muted"); err != nil {
164		t.Fatal(err)
165	}
166
167	// Watching after muting replaces the row rather than adding one.
168	if err := s.SetRepoWatch(repoID, owner, "watching"); err != nil {
169		t.Fatal(err)
170	}
171	if s.RepoWatchState(repoID, owner) != "watching" {
172		t.Fatal("watch did not replace mute")
173	}
174}