1package imapserver
2
3import (
4 "strings"
5 "testing"
6 "time"
7
8 "github.com/mjl-/mox/imapclient"
9 "github.com/mjl-/mox/store"
10)
11
12func TestNotify(t *testing.T) {
13 testNotify(t, false)
14}
15
16func TestNotifyUIDOnly(t *testing.T) {
17 testNotify(t, true)
18}
19
20func testNotify(t *testing.T, uidonly bool) {
21 defer mockUIDValidity()()
22 tc := start(t, uidonly)
23 defer tc.close()
24 tc.login("mjl@mox.example", password0)
25 tc.client.Select("inbox")
26
27 // Check for some invalid syntax.
28 tc.transactf("bad", "Notify")
29 tc.transactf("bad", "Notify bogus")
30 tc.transactf("bad", "Notify None ") // Trailing space.
31 tc.transactf("bad", "Notify Set")
32 tc.transactf("bad", "Notify Set ")
33 tc.transactf("bad", "Notify Set Status")
34 tc.transactf("bad", "Notify Set Status ()") // Empty list.
35 tc.transactf("bad", "Notify Set Status (UnknownSpecifier (messageNew))")
36 tc.transactf("bad", "Notify Set Status (Personal messageNew)") // Missing list around events.
37 tc.transactf("bad", "Notify Set Status (Personal (messageNew) )") // Trailing space.
38 tc.transactf("bad", "Notify Set Status (Personal (messageNew)) ") // Trailing space.
39
40 tc.transactf("bad", "Notify Set Status (Selected (mailboxName))") // MailboxName not allowed on Selected.
41 tc.transactf("bad", "Notify Set Status (Selected (messageNew))") // MessageNew must come with MessageExpunge.
42 tc.transactf("bad", "Notify Set Status (Selected (flagChange))") // flagChange must come with MessageNew and MessageExpunge.
43 tc.transactf("bad", "Notify Set Status (Selected (mailboxName)) (Selected-Delayed (mailboxName))") // Duplicate selected.
44 tc.transactf("no", "Notify Set Status (Selected (annotationChange))") // We don't implement annotation change.
45 tc.xcode(imapclient.CodeBadEvent{"MessageNew", "MessageExpunge", "FlagChange", "MailboxName", "SubscriptionChange", "MailboxMetadataChange", "ServerMetadataChange"})
46 tc.transactf("no", "Notify Set Status (Personal (unknownEvent))")
47 tc.xcode(imapclient.CodeBadEvent{"MessageNew", "MessageExpunge", "FlagChange", "MailboxName", "SubscriptionChange", "MailboxMetadataChange", "ServerMetadataChange"})
48
49 tc2 := startNoSwitchboard(t, uidonly)
50 defer tc2.closeNoWait()
51 tc2.login("mjl@mox.example", password0)
52 tc2.client.Select("inbox")
53
54 var modseq uint32 = 4
55
56 // Check that we don't get pending changes when we set "notify none". We first make
57 // changes that we drain with noop. Then add new pending changes and execute
58 // "notify none". Server should still process changes to the message sequence
59 // numbers of the selected mailbox.
60 tc2.client.Append("inbox", makeAppend(searchMsg)) // Results in exists and fetch.
61 modseq++
62 tc2.client.Append("Junk", makeAppend(searchMsg)) // Not selected, not mentioned.
63 modseq++
64 tc.transactf("ok", "noop")
65 tc.xuntagged(
66 imapclient.UntaggedExists(1),
67 tc.untaggedFetch(1, 1, imapclient.FetchFlags(nil)),
68 )
69 tc2.client.UIDStoreFlagsAdd("1:*", true, `\Deleted`)
70 modseq++
71 tc2.client.Expunge()
72 modseq++
73 tc.transactf("ok", "Notify None")
74 tc.xuntagged() // No untagged responses for delete/expunge.
75
76 // Enable notify, will first result in a the pending changes, then status.
77 tc.transactf("ok", "Notify Set Status (Selected (messageNew (Uid Modseq Bodystructure Preview) messageExpunge flagChange)) (personal (messageNew messageExpunge flagChange mailboxName subscriptionChange mailboxMetadataChange serverMetadataChange))")
78 tc.xuntagged(
79 imapclient.UntaggedResult{Status: imapclient.OK, Code: imapclient.CodeHighestModSeq(modseq), Text: "after condstore-enabling command"},
80 // note: no status for Inbox since it is selected.
81 imapclient.UntaggedStatus{Mailbox: "Drafts", Attrs: map[imapclient.StatusAttr]int64{imapclient.StatusMessages: 0, imapclient.StatusUIDNext: 1, imapclient.StatusUIDValidity: 1, imapclient.StatusUnseen: 0, imapclient.StatusHighestModSeq: 2}},
82 imapclient.UntaggedStatus{Mailbox: "Sent", Attrs: map[imapclient.StatusAttr]int64{imapclient.StatusMessages: 0, imapclient.StatusUIDNext: 1, imapclient.StatusUIDValidity: 1, imapclient.StatusUnseen: 0, imapclient.StatusHighestModSeq: 2}},
83 imapclient.UntaggedStatus{Mailbox: "Archive", Attrs: map[imapclient.StatusAttr]int64{imapclient.StatusMessages: 0, imapclient.StatusUIDNext: 1, imapclient.StatusUIDValidity: 1, imapclient.StatusUnseen: 0, imapclient.StatusHighestModSeq: 2}},
84 imapclient.UntaggedStatus{Mailbox: "Trash", Attrs: map[imapclient.StatusAttr]int64{imapclient.StatusMessages: 0, imapclient.StatusUIDNext: 1, imapclient.StatusUIDValidity: 1, imapclient.StatusUnseen: 0, imapclient.StatusHighestModSeq: 2}},
85 imapclient.UntaggedStatus{Mailbox: "Junk", Attrs: map[imapclient.StatusAttr]int64{imapclient.StatusMessages: 1, imapclient.StatusUIDNext: 2, imapclient.StatusUIDValidity: 1, imapclient.StatusUnseen: 1, imapclient.StatusHighestModSeq: int64(modseq - 2)}},
86 )
87
88 // Selecting the mailbox again results in a refresh of the message sequence
89 // numbers, with the deleted message gone (it wasn't acknowledged yet due to
90 // "notify none").
91 tc.client.Select("inbox")
92
93 // Add message, should result in EXISTS and FETCH with the configured attributes.
94 tc2.client.Append("inbox", makeAppend(searchMsg))
95 modseq++
96 tc.readuntagged(
97 imapclient.UntaggedExists(1),
98 tc.untaggedFetchUID(1, 2,
99 imapclient.FetchBodystructure{
100 RespAttr: "BODYSTRUCTURE",
101 Body: imapclient.BodyTypeMpart{
102 Bodies: []any{
103 imapclient.BodyTypeText{
104 MediaType: "TEXT",
105 MediaSubtype: "PLAIN",
106 BodyFields: imapclient.BodyFields{
107 Params: [][2]string{[...]string{"CHARSET", "utf-8"}},
108 Octets: 21,
109 },
110 Lines: 1,
111 Ext: &imapclient.BodyExtension1Part{
112 Disposition: ptr((*string)(nil)),
113 DispositionParams: ptr([][2]string(nil)),
114 Language: ptr([]string(nil)),
115 Location: ptr((*string)(nil)),
116 },
117 },
118 imapclient.BodyTypeText{
119 MediaType: "TEXT",
120 MediaSubtype: "HTML",
121 BodyFields: imapclient.BodyFields{
122 Params: [][2]string{[...]string{"CHARSET", "utf-8"}},
123 Octets: 15,
124 },
125 Lines: 1,
126 Ext: &imapclient.BodyExtension1Part{
127 Disposition: ptr((*string)(nil)),
128 DispositionParams: ptr([][2]string(nil)),
129 Language: ptr([]string(nil)),
130 Location: ptr((*string)(nil)),
131 },
132 },
133 },
134 MediaSubtype: "ALTERNATIVE",
135 Ext: &imapclient.BodyExtensionMpart{
136 Params: [][2]string{{"BOUNDARY", "x"}},
137 Disposition: ptr((*string)(nil)), // Present but nil.
138 DispositionParams: ptr([][2]string(nil)),
139 Language: ptr([]string(nil)),
140 Location: ptr((*string)(nil)),
141 },
142 },
143 },
144 imapclient.FetchPreview{Preview: ptr("this is plain text.")},
145 imapclient.FetchModSeq(modseq),
146 ),
147 )
148
149 // Change flags.
150 tc2.client.UIDStoreFlagsAdd("1:*", true, `\Deleted`)
151 modseq++
152 tc.readuntagged(tc.untaggedFetch(1, 2, imapclient.FetchFlags{`\Deleted`}, imapclient.FetchModSeq(modseq)))
153
154 // Remove message.
155 tc2.client.Expunge()
156 modseq++
157 if uidonly {
158 tc.readuntagged(imapclient.UntaggedVanished{UIDs: xparseNumSet("2")})
159 } else {
160 tc.readuntagged(imapclient.UntaggedExpunge(1))
161 }
162
163 // MailboxMetadataChange for mailbox annotation.
164 tc2.transactf("ok", `setmetadata Archive (/private/comment "test")`)
165 modseq++
166 tc.readuntagged(
167 imapclient.UntaggedMetadataKeys{Mailbox: "Archive", Keys: []string{"/private/comment"}},
168 )
169
170 // MailboxMetadataChange also for the selected Inbox.
171 tc2.transactf("ok", `setmetadata Inbox (/private/comment "test")`)
172 modseq++
173 tc.readuntagged(
174 imapclient.UntaggedMetadataKeys{Mailbox: "Inbox", Keys: []string{"/private/comment"}},
175 )
176
177 // ServerMetadataChange for server annotation.
178 tc2.transactf("ok", `setmetadata "" (/private/vendor/other/x "test")`)
179 modseq++
180 tc.readuntagged(
181 imapclient.UntaggedMetadataKeys{Mailbox: "", Keys: []string{"/private/vendor/other/x"}},
182 )
183
184 // SubscriptionChange for new subscription.
185 tc2.client.Subscribe("doesnotexist")
186 tc.readuntagged(
187 imapclient.UntaggedList{Mailbox: "doesnotexist", Separator: '/', Flags: []string{`\Subscribed`, `\NonExistent`}},
188 )
189
190 // SubscriptionChange for removed subscription.
191 tc2.client.Unsubscribe("doesnotexist")
192 tc.readuntagged(
193 imapclient.UntaggedList{Mailbox: "doesnotexist", Separator: '/', Flags: []string{`\NonExistent`}},
194 )
195
196 // SubscriptionChange for selected mailbox.
197 tc2.client.Unsubscribe("Inbox")
198 tc2.client.Subscribe("Inbox")
199 tc.readuntagged(
200 imapclient.UntaggedList{Mailbox: "Inbox", Separator: '/'},
201 imapclient.UntaggedList{Mailbox: "Inbox", Separator: '/', Flags: []string{`\Subscribed`}},
202 )
203
204 // MailboxName for creating mailbox.
205 tc2.client.Create("newbox", nil)
206 modseq++
207 tc.readuntagged(
208 imapclient.UntaggedList{Mailbox: "newbox", Separator: '/', Flags: []string{`\Subscribed`}},
209 )
210
211 // MailboxName for renaming mailbox.
212 tc2.client.Rename("newbox", "oldbox")
213 modseq++
214 tc.readuntagged(
215 imapclient.UntaggedList{Mailbox: "oldbox", Separator: '/', OldName: "newbox"},
216 )
217
218 // MailboxName for deleting mailbox.
219 tc2.client.Delete("oldbox")
220 modseq++
221 tc.readuntagged(
222 imapclient.UntaggedList{Mailbox: "oldbox", Separator: '/', Flags: []string{`\NonExistent`}},
223 )
224
225 // Add message again to check for modseq. First set notify again with fewer fetch
226 // attributes for simpler checking.
227 tc.transactf("ok", "Notify Set (personal (messageNew messageExpunge flagChange mailboxName subscriptionChange mailboxMetadataChange serverMetadataChange)) (Selected (messageNew (Uid Modseq) messageExpunge flagChange))")
228 tc2.client.Append("inbox", makeAppend(searchMsg))
229 modseq++
230 tc.readuntagged(
231 imapclient.UntaggedExists(1),
232 tc.untaggedFetchUID(1, 3, imapclient.FetchModSeq(modseq)),
233 )
234
235 // Next round of events must be ignored. We shouldn't get anything until we add a
236 // message to "testbox".
237 tc.transactf("ok", "Notify Set (Selected None) (mailboxes testbox (messageNew messageExpunge)) (personal None)")
238 tc2.client.Append("inbox", makeAppend(searchMsg)) // MessageNew
239 modseq++
240 tc2.client.UIDStoreFlagsAdd("1:*", true, `\Deleted`) // FlagChange
241 modseq++
242 tc2.client.Expunge() // MessageExpunge
243 modseq++
244 tc2.transactf("ok", `setmetadata Archive (/private/comment "test2")`) // MailboxMetadataChange
245 modseq++
246 tc2.transactf("ok", `setmetadata "" (/private/vendor/other/x "test2")`) // ServerMetadataChange
247 modseq++
248 tc2.client.Subscribe("doesnotexist2") // SubscriptionChange
249 tc2.client.Unsubscribe("doesnotexist2") // SubscriptionChange
250 tc2.client.Create("newbox2", nil) // MailboxName
251 modseq++
252 tc2.client.Rename("newbox2", "oldbox2") // MailboxName
253 modseq++
254 tc2.client.Delete("oldbox2") // MailboxName
255 modseq++
256 // Now trigger receiving a notification.
257 tc2.client.Create("testbox", nil) // MailboxName
258 modseq++
259 tc2.client.Append("testbox", makeAppend(searchMsg)) // MessageNew
260 modseq++
261 tc.readuntagged(
262 imapclient.UntaggedStatus{Mailbox: "testbox", Attrs: map[imapclient.StatusAttr]int64{imapclient.StatusMessages: 1, imapclient.StatusUIDNext: 2, imapclient.StatusUnseen: 1, imapclient.StatusHighestModSeq: int64(modseq)}},
263 )
264
265 // Test filtering per mailbox specifier. We create two mailboxes.
266 tc.client.Create("inbox/a/b", nil)
267 modseq++
268 tc.client.Create("other/a/b", nil)
269 modseq++
270 tc.client.Unsubscribe("other/a/b")
271
272 // Inboxes
273 tc3 := startNoSwitchboard(t, uidonly)
274 defer tc3.closeNoWait()
275 tc3.login("mjl@mox.example", password0)
276 tc3.transactf("ok", "Notify Set (Inboxes (messageNew messageExpunge))")
277
278 // Subscribed
279 tc4 := startNoSwitchboard(t, uidonly)
280 defer tc4.closeNoWait()
281 tc4.login("mjl@mox.example", password0)
282 tc4.transactf("ok", "Notify Set (Subscribed (messageNew messageExpunge))")
283
284 // Subtree
285 tc5 := startNoSwitchboard(t, uidonly)
286 defer tc5.closeNoWait()
287 tc5.login("mjl@mox.example", password0)
288 tc5.transactf("ok", "Notify Set (Subtree (Nonexistent inbox) (messageNew messageExpunge))")
289
290 // Subtree-One
291 tc6 := startNoSwitchboard(t, uidonly)
292 defer tc6.closeNoWait()
293 tc6.login("mjl@mox.example", password0)
294 tc6.transactf("ok", "Notify Set (Subtree-One (Nonexistent Inbox/a other) (messageNew messageExpunge))")
295
296 // We append to other/a/b first. It would normally come first in the notifications,
297 // but we check we only get the second event.
298 tc2.client.Append("other/a/b", makeAppend(searchMsg))
299 modseq++
300 tc2.client.Append("inbox/a/b", makeAppend(searchMsg))
301 modseq++
302
303 // No highestmodseq, these connections don't have CONDSTORE enabled.
304 tc3.readuntagged(
305 imapclient.UntaggedStatus{Mailbox: "Inbox/a/b", Attrs: map[imapclient.StatusAttr]int64{imapclient.StatusMessages: 1, imapclient.StatusUIDNext: 2, imapclient.StatusUnseen: 1}},
306 )
307 tc4.readuntagged(
308 imapclient.UntaggedStatus{Mailbox: "Inbox/a/b", Attrs: map[imapclient.StatusAttr]int64{imapclient.StatusMessages: 1, imapclient.StatusUIDNext: 2, imapclient.StatusUnseen: 1}},
309 )
310 tc5.readuntagged(
311 imapclient.UntaggedStatus{Mailbox: "Inbox/a/b", Attrs: map[imapclient.StatusAttr]int64{imapclient.StatusMessages: 1, imapclient.StatusUIDNext: 2, imapclient.StatusUnseen: 1}},
312 )
313 tc6.readuntagged(
314 imapclient.UntaggedStatus{Mailbox: "Inbox/a/b", Attrs: map[imapclient.StatusAttr]int64{imapclient.StatusMessages: 1, imapclient.StatusUIDNext: 2, imapclient.StatusUnseen: 1}},
315 )
316
317 // Test for STATUS events on non-selected mailbox for message events.
318 tc.transactf("ok", "notify set (personal (messageNew messageExpunge flagChange))")
319 tc.client.Unselect()
320 tc2.client.Create("statusbox", nil)
321 modseq++
322 tc2.client.Append("statusbox", makeAppend(searchMsg))
323 modseq++
324 tc.readuntagged(
325 imapclient.UntaggedStatus{Mailbox: "statusbox", Attrs: map[imapclient.StatusAttr]int64{imapclient.StatusMessages: 1, imapclient.StatusUIDNext: 2, imapclient.StatusUnseen: 1, imapclient.StatusHighestModSeq: int64(modseq)}},
326 )
327
328 // With Selected-Delayed, we only get the events for the selected mailbox for
329 // explicit commands. We still get other events.
330 tc.transactf("ok", "notify set (selected-delayed (messageNew messageExpunge flagChange)) (personal (messageNew messageExpunge flagChange))")
331 tc.client.Select("statusbox")
332 tc2.client.Append("inbox", makeAppend(searchMsg))
333 modseq++
334 tc2.client.UIDStoreFlagsSet("*", true, `\Seen`)
335 modseq++
336 tc2.client.Append("statusbox", imapclient.Append{Flags: []string{"newflag"}, Size: int64(len(searchMsg)), Data: strings.NewReader(searchMsg)})
337 modseq++
338 tc2.client.Select("statusbox")
339
340 tc.readuntagged(
341 imapclient.UntaggedStatus{Mailbox: "Inbox", Attrs: map[imapclient.StatusAttr]int64{imapclient.StatusMessages: 1, imapclient.StatusUIDNext: 6, imapclient.StatusUnseen: 1, imapclient.StatusHighestModSeq: int64(modseq - 2)}},
342 imapclient.UntaggedStatus{Mailbox: "Inbox", Attrs: map[imapclient.StatusAttr]int64{imapclient.StatusUIDValidity: 1, imapclient.StatusUnseen: 0, imapclient.StatusHighestModSeq: int64(modseq - 1)}},
343 )
344
345 tc.transactf("ok", "noop")
346 tc.xuntagged(
347 imapclient.UntaggedExists(2),
348 tc.untaggedFetch(2, 2, imapclient.FetchFlags{"newflag"}, imapclient.FetchModSeq(modseq)),
349 imapclient.UntaggedFlags{`\Seen`, `\Answered`, `\Flagged`, `\Deleted`, `\Draft`, `$Forwarded`, `$Junk`, `$NotJunk`, `$Phishing`, `$MDNSent`, `newflag`},
350 )
351
352 tc2.client.UIDStoreFlagsSet("2", true, `\Deleted`)
353 modseq++
354 tc2.client.Expunge()
355 modseq++
356 tc.transactf("ok", "noop")
357 if uidonly {
358 tc.xuntagged(
359 tc.untaggedFetch(2, 2, imapclient.FetchFlags{`\Deleted`}, imapclient.FetchModSeq(modseq-1)),
360 imapclient.UntaggedVanished{UIDs: xparseNumSet("2")},
361 )
362 } else {
363 tc.xuntagged(
364 tc.untaggedFetch(2, 2, imapclient.FetchFlags{`\Deleted`}, imapclient.FetchModSeq(modseq-1)),
365 imapclient.UntaggedExpunge(2),
366 )
367 }
368
369 // With Selected-Delayed, we should get events for selected mailboxes immediately when using IDLE.
370 tc2.client.UIDStoreFlagsSet("*", true, `\Answered`)
371 modseq++
372 tc2.client.Select("inbox")
373 tc2.client.UIDStoreFlagsClear("*", true, `\Seen`)
374 modseq++
375 tc2.client.Select("statusbox")
376
377 tc.readuntagged(
378 imapclient.UntaggedStatus{Mailbox: "Inbox", Attrs: map[imapclient.StatusAttr]int64{imapclient.StatusUIDValidity: 1, imapclient.StatusUnseen: 1, imapclient.StatusHighestModSeq: int64(modseq)}},
379 )
380
381 tc.conn.SetReadDeadline(time.Now().Add(3 * time.Second))
382 tc.cmdf("", "idle")
383 tc.readprefixline("+ ")
384 tc.readuntagged(tc.untaggedFetch(1, 1, imapclient.FetchFlags{`\Answered`}, imapclient.FetchModSeq(modseq-1)))
385 tc.writelinef("done")
386 tc.response("ok")
387 tc.conn.SetReadDeadline(time.Now().Add(30 * time.Second))
388
389 // If any event matches, we normally return it. But NONE prevents looking further.
390 tc.client.Unselect()
391 tc.transactf("ok", "notify set (mailboxes statusbox NONE) (personal (mailboxName))")
392 tc2.client.UIDStoreFlagsSet("*", true, `\Answered`) // Matches NONE, ignored.
393 //modseq++
394 tc2.client.Create("eventbox", nil)
395 //modseq++
396 tc.readuntagged(
397 imapclient.UntaggedList{Mailbox: "eventbox", Separator: '/', Flags: []string{`\Subscribed`}},
398 )
399
400 // Check we can return message contents.
401 tc.transactf("ok", "notify set (selected (messageNew (body[header] body[text]) messageExpunge))")
402 tc.client.Select("statusbox")
403 tc2.client.Append("statusbox", makeAppend(searchMsg))
404 // modseq++
405 offset := strings.Index(searchMsg, "\r\n\r\n")
406 tc.readuntagged(
407 imapclient.UntaggedExists(2),
408 tc.untaggedFetch(2, 3,
409 imapclient.FetchBody{
410 RespAttr: "BODY[HEADER]",
411 Section: "HEADER",
412 Body: searchMsg[:offset+4],
413 },
414 imapclient.FetchBody{
415 RespAttr: "BODY[TEXT]",
416 Section: "TEXT",
417 Body: searchMsg[offset+4:],
418 },
419 imapclient.FetchFlags(nil),
420 ),
421 )
422
423 // If we encounter an error during fetch, an untagged NO is returned.
424 // We ask for the 2nd part of a message, and we add a message with just 1 part.
425 tc.transactf("ok", "notify set (selected (messageNew (body[2]) messageExpunge))")
426 tc2.client.Append("statusbox", makeAppend(exampleMsg))
427 // modseq++
428 tc.readuntagged(
429 imapclient.UntaggedExists(3),
430 imapclient.UntaggedResult{Status: "NO", Text: "generating notify fetch response: requested part does not exist"},
431 tc.untaggedFetchUID(3, 4),
432 )
433
434 // When adding new tests, uncomment modseq++ lines above.
435}
436
437func TestNotifyOverflow(t *testing.T) {
438 testNotifyOverflow(t, false)
439}
440
441func TestNotifyOverflowUIDOnly(t *testing.T) {
442 testNotifyOverflow(t, true)
443}
444
445func testNotifyOverflow(t *testing.T, uidonly bool) {
446 orig := store.CommPendingChangesMax
447 store.CommPendingChangesMax = 3
448 defer func() {
449 store.CommPendingChangesMax = orig
450 }()
451
452 defer mockUIDValidity()()
453 tc := start(t, uidonly)
454 defer tc.close()
455 tc.login("mjl@mox.example", password0)
456 tc.client.Select("inbox")
457 tc.transactf("ok", "noop")
458
459 tc2 := startNoSwitchboard(t, uidonly)
460 defer tc2.closeNoWait()
461 tc2.login("mjl@mox.example", password0)
462 tc2.client.Select("inbox")
463
464 // Generates 4 changes, crossing max 3.
465 tc2.client.Append("inbox", makeAppend(searchMsg))
466 tc2.client.Append("inbox", makeAppend(searchMsg))
467
468 tc.transactf("ok", "noop")
469 tc.xuntagged(imapclient.UntaggedResult{Status: "OK", Code: imapclient.CodeWord("NOTIFICATIONOVERFLOW"), Text: "out of sync after too many pending changes"})
470
471 // Won't be getting any more notifications until we enable them again with NOTIFY.
472 tc2.client.Append("inbox", makeAppend(searchMsg))
473 tc.transactf("ok", "noop")
474 tc.xuntagged()
475
476 // Enable notify again. Without uidonly, we won't get a notification because the
477 // message isn't known in the session.
478 tc.transactf("ok", "notify set (selected (messageNew messageExpunge flagChange))")
479 tc2.client.UIDStoreFlagsAdd("1", true, `\Seen`)
480 if uidonly {
481 tc.readuntagged(tc.untaggedFetch(1, 1, imapclient.FetchFlags{`\Seen`}))
482 } else {
483 tc.transactf("ok", "noop")
484 tc.xuntagged()
485 }
486
487 // Reselect to get the message visible in the session.
488 tc.client.Select("inbox")
489 tc2.client.UIDStoreFlagsClear("1", true, `\Seen`)
490 tc.transactf("ok", "noop")
491 tc.xuntagged(tc.untaggedFetch(1, 1, imapclient.FetchFlags(nil)))
492
493 // Trigger overflow for changes for "selected-delayed".
494 store.CommPendingChangesMax = 10
495 delayedMax := selectedDelayedChangesMax
496 selectedDelayedChangesMax = 1
497 defer func() {
498 selectedDelayedChangesMax = delayedMax
499 }()
500 tc.transactf("ok", "notify set (selected-delayed (messageNew messageExpunge flagChange))")
501 tc2.client.UIDStoreFlagsAdd("1", true, `\Seen`)
502 tc2.client.UIDStoreFlagsClear("1", true, `\Seen`)
503 tc.transactf("ok", "noop")
504 tc.xuntagged(imapclient.UntaggedResult{Status: "OK", Code: imapclient.CodeWord("NOTIFICATIONOVERFLOW"), Text: "out of sync after too many pending changes for selected mailbox"})
505
506 // Again, no new notifications until we select and enable again.
507 tc2.client.UIDStoreFlagsAdd("1", true, `\Seen`)
508 tc.transactf("ok", "noop")
509 tc.xuntagged()
510
511 tc.client.Select("inbox")
512 tc.transactf("ok", "notify set (selected-delayed (messageNew messageExpunge flagChange))")
513 tc2.client.UIDStoreFlagsClear("1", true, `\Seen`)
514 tc.transactf("ok", "noop")
515 tc.xuntagged(tc.untaggedFetch(1, 1, imapclient.FetchFlags(nil)))
516}
517