internal/store/inbox_test.go
170 lines · 5065 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})
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}); 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}); len(got) != 2 {
135 t.Fatalf("watcher duplicated: %v", got)
136 }
137
138 // Muting beats owning the repository.
139 if err := s.SetRepoWatch(repoID, owner, "muted"); err != nil {
140 t.Fatal(err)
141 }
142 got, _ = s.NotifyRecipients(repoID, other, []int64{owner})
143 if len(got) != 1 || got[0] != third {
144 t.Fatalf("muted owner still notified: %v", got)
145 }
146 if s.RepoWatchState(repoID, owner) != "muted" {
147 t.Fatal("watch state not recorded")
148 }
149 // Clearing returns the owner to the default and they are told again.
150 if err := s.ClearRepoWatch(repoID, owner); err != nil {
151 t.Fatal(err)
152 }
153 if s.RepoWatchState(repoID, owner) != "" {
154 t.Fatal("clear left a state")
155 }
156 if got, _ := s.NotifyRecipients(repoID, other, []int64{owner}); len(got) != 2 {
157 t.Fatalf("cleared owner not notified: %v", got)
158 }
159 if err := s.SetRepoWatch(repoID, owner, "muted"); err != nil {
160 t.Fatal(err)
161 }
162
163 // Watching after muting replaces the row rather than adding one.
164 if err := s.SetRepoWatch(repoID, owner, "watching"); err != nil {
165 t.Fatal(err)
166 }
167 if s.RepoWatchState(repoID, owner) != "watching" {
168 t.Fatal("watch did not replace mute")
169 }
170}