10 "github.com/mjl-/mox/imapclient"
13var searchMsg = strings.ReplaceAll(`Date: Mon, 1 Jan 2022 10:00:00 +0100 (CEST)
14From: mjl <mjl@mox.example>
16To: mox <mox@mox.example>
19Reply-To: <noreply@mox.example>
20Message-Id: <123@mox.example>
22Content-Type: multipart/alternative; boundary=x
25Content-Type: text/plain; charset=utf-8
30Content-Type: text/html; charset=utf-8
37func uint32ptr(v uint32) *uint32 {
41func (tc *testconn) xsearch(nums ...uint32) {
44 tc.xuntagged(imapclient.UntaggedSearch(nums))
47func (tc *testconn) xsearchmodseq(modseq int64, nums ...uint32) {
54 tc.xuntagged(imapclient.UntaggedSearchModSeq{Nums: nums, ModSeq: modseq})
57func (tc *testconn) xesearch(exp imapclient.UntaggedEsearch) {
60 exp.Tag = tc.client.LastTag
64func TestSearch(t *testing.T) {
67 tc.client.Login("mjl@mox.example", password0)
68 tc.client.Select("inbox")
70 // Add 5 and delete first 4 messages. So UIDs start at 5.
71 received := time.Date(2020, time.January, 1, 10, 0, 0, 0, time.UTC)
72 saveDate := time.Now()
74 tc.client.Append("inbox", makeAppendTime(exampleMsg, received))
76 tc.client.StoreFlagsSet("1:4", true, `\Deleted`)
79 received = time.Date(2022, time.January, 1, 9, 0, 0, 0, time.UTC)
80 tc.client.Append("inbox", makeAppendTime(searchMsg, received))
82 received = time.Date(2022, time.January, 1, 9, 0, 0, 0, time.UTC)
83 mostFlags := []string{
97 tc.client.Append("inbox", imapclient.Append{Flags: mostFlags, Received: &received, Size: int64(len(searchMsg)), Data: strings.NewReader(searchMsg)})
99 // We now have sequence numbers 1,2,3 and UIDs 5,6,7.
101 // We need to be selected. Not the case for ESEARCH command.
103 tc.transactf("no", "search all")
104 tc.client.Select("inbox")
106 tc.transactf("ok", "search all")
109 tc.transactf("ok", "uid search all")
112 tc.transactf("ok", "search answered")
115 tc.transactf("ok", `search bcc "bcc@mox.example"`)
118 tc.transactf("ok", "search before 1-Jan-2038")
120 tc.transactf("ok", "search before 1-Jan-2020")
121 tc.xsearch() // Before is about received, not date header of message.
123 // WITHIN extension with OLDER & YOUNGER.
124 tc.transactf("ok", "search older 60")
126 tc.transactf("ok", "search younger 60")
129 // SAVEDATE extension.
130 tc.transactf("ok", "search savedbefore %s", saveDate.Add(24*time.Hour).Format("2-Jan-2006"))
132 tc.transactf("ok", "search savedbefore %s", saveDate.Add(-24*time.Hour).Format("2-Jan-2006"))
134 tc.transactf("ok", "search savedon %s", saveDate.Format("2-Jan-2006"))
136 tc.transactf("ok", "search savedon %s", saveDate.Add(-24*time.Hour).Format("2-Jan-2006"))
138 tc.transactf("ok", "search savedsince %s", saveDate.Add(-24*time.Hour).Format("2-Jan-2006"))
140 tc.transactf("ok", "search savedsince %s", saveDate.Add(24*time.Hour).Format("2-Jan-2006"))
143 tc.transactf("ok", `search body "Joe"`)
145 tc.transactf("ok", `search body "Joe" body "bogus"`)
147 tc.transactf("ok", `search body "Joe" text "Blurdybloop"`)
149 tc.transactf("ok", `search body "Joe" not text "mox"`)
151 tc.transactf("ok", `search body "Joe" not not body "Joe"`)
153 tc.transactf("ok", `search body "this is plain text"`)
155 tc.transactf("ok", `search body "this is html"`)
158 tc.transactf("ok", `search cc "xcc@mox.example"`)
161 tc.transactf("ok", `search deleted`)
164 tc.transactf("ok", `search flagged`)
167 tc.transactf("ok", `search from "foobar@Blurdybloop.example"`)
170 tc.transactf("ok", `search keyword $Forwarded`)
173 tc.transactf("ok", `search keyword Custom1`)
176 tc.transactf("ok", `search keyword custom2`)
179 tc.transactf("ok", `search new`)
180 tc.xsearch() // New requires a message to be recent. We pretend all messages are not recent.
182 tc.transactf("ok", `search old`)
185 tc.transactf("ok", `search on 1-Jan-2022`)
188 tc.transactf("ok", `search recent`)
191 tc.transactf("ok", `search seen`)
194 tc.transactf("ok", `search since 1-Jan-2020`)
197 tc.transactf("ok", `search subject "afternoon"`)
200 tc.transactf("ok", `search text "Joe"`)
203 tc.transactf("ok", `search to "mooch@owatagu.siam.edu.example"`)
206 tc.transactf("ok", `search unanswered`)
209 tc.transactf("ok", `search undeleted`)
212 tc.transactf("ok", `search unflagged`)
215 tc.transactf("ok", `search unkeyword $Junk`)
218 tc.transactf("ok", `search unkeyword custom1`)
221 tc.transactf("ok", `search unseen`)
224 tc.transactf("ok", `search draft`)
227 tc.transactf("ok", `search header "subject" "afternoon"`)
230 tc.transactf("ok", `search larger 1`)
233 tc.transactf("ok", `search not text "mox"`)
236 tc.transactf("ok", `search or seen unseen`)
239 tc.transactf("ok", `search or unseen seen`)
242 tc.transactf("ok", `search sentbefore 8-Feb-1994`)
245 tc.transactf("ok", `search senton 7-Feb-1994`)
248 tc.transactf("ok", `search sentsince 6-Feb-1994`)
251 tc.transactf("ok", `search smaller 9999999`)
254 tc.transactf("ok", `search uid 1`)
257 tc.transactf("ok", `search uid 5`)
260 tc.transactf("ok", `search or larger 1000000 smaller 1`)
263 tc.transactf("ok", `search undraft`)
266 tc.transactf("no", `search charset unknown text "mox"`)
267 tc.transactf("ok", `search charset us-ascii text "mox"`)
269 tc.transactf("ok", `search charset utf-8 text "mox"`)
272 // Check for properly formed INPROGRESS response code.
273 orig := inProgressPeriod
275 tc.cmdf("tag1", "search undraft")
278 inprogress := func(cur, goal uint32) imapclient.UntaggedResult {
279 return imapclient.UntaggedResult{
281 RespText: imapclient.RespText{
283 CodeArg: imapclient.CodeInProgress{Tag: "tag1", Current: &cur, Goal: &goal},
284 More: "still searching",
289 imapclient.UntaggedSearch([]uint32{1, 2}),
290 // Due to inProgressPeriod 0, we get an inprogress response for each message in the mailbox.
295 inProgressPeriod = orig
297 esearchall := func(ss string) imapclient.UntaggedEsearch {
298 return imapclient.UntaggedEsearch{All: esearchall0(ss)}
301 // Do new-style ESEARCH requests with RETURN. We should get an ESEARCH response.
302 tc.transactf("ok", "search return () all")
303 tc.xesearch(esearchall("1:3")) // Without any options, "ALL" is implicit.
305 tc.transactf("ok", "search return (min max count all) all")
306 tc.xesearch(imapclient.UntaggedEsearch{Min: 1, Max: 3, Count: uint32ptr(3), All: esearchall0("1:3")})
308 tc.transactf("ok", "UID search return (min max count all) all")
309 tc.xesearch(imapclient.UntaggedEsearch{UID: true, Min: 5, Max: 7, Count: uint32ptr(3), All: esearchall0("5:7")})
311 tc.transactf("ok", "search return (min) all")
312 tc.xesearch(imapclient.UntaggedEsearch{Min: 1})
314 tc.transactf("ok", "search return (min) 3")
315 tc.xesearch(imapclient.UntaggedEsearch{Min: 3})
317 tc.transactf("ok", "search return (min) NOT all")
318 tc.xesearch(imapclient.UntaggedEsearch{}) // Min not present if no match.
320 tc.transactf("ok", "search return (max) all")
321 tc.xesearch(imapclient.UntaggedEsearch{Max: 3})
323 tc.transactf("ok", "search return (max) 1")
324 tc.xesearch(imapclient.UntaggedEsearch{Max: 1})
326 tc.transactf("ok", "search return (max) not all")
327 tc.xesearch(imapclient.UntaggedEsearch{}) // Max not present if no match.
329 tc.transactf("ok", "search return (min max) all")
330 tc.xesearch(imapclient.UntaggedEsearch{Min: 1, Max: 3})
332 tc.transactf("ok", "search return (min max) 1")
333 tc.xesearch(imapclient.UntaggedEsearch{Min: 1, Max: 1})
335 tc.transactf("ok", "search return (min max) not all")
336 tc.xesearch(imapclient.UntaggedEsearch{})
338 tc.transactf("ok", "search return (all) not all")
339 tc.xesearch(imapclient.UntaggedEsearch{}) // All not present if no match.
341 tc.transactf("ok", "search return (min max all) not all")
342 tc.xesearch(imapclient.UntaggedEsearch{})
344 tc.transactf("ok", "search return (min max all count) not all")
345 tc.xesearch(imapclient.UntaggedEsearch{Count: uint32ptr(0)})
347 tc.transactf("ok", "search return (min max count all) 1,3")
348 tc.xesearch(imapclient.UntaggedEsearch{Min: 1, Max: 3, Count: uint32ptr(2), All: esearchall0("1,3")})
350 tc.transactf("ok", "search return (min max count all) UID 5,7")
351 tc.xesearch(imapclient.UntaggedEsearch{Min: 1, Max: 3, Count: uint32ptr(2), All: esearchall0("1,3")})
353 tc.transactf("ok", "uid search return (min max count all) 1,3")
354 tc.xesearch(imapclient.UntaggedEsearch{UID: true, Min: 5, Max: 7, Count: uint32ptr(2), All: esearchall0("5,7")})
356 tc.transactf("ok", "uid search return (min max count all) UID 5,7")
357 tc.xesearch(imapclient.UntaggedEsearch{UID: true, Min: 5, Max: 7, Count: uint32ptr(2), All: esearchall0("5,7")})
359 tc.transactf("no", `search return () charset unknown text "mox"`)
360 tc.transactf("ok", `search return () charset us-ascii text "mox"`)
361 tc.xesearch(esearchall("2:3"))
362 tc.transactf("ok", `search return () charset utf-8 text "mox"`)
363 tc.xesearch(esearchall("2:3"))
365 tc.transactf("bad", `search return (unknown) all`)
367 tc.transactf("ok", "search return (save) 2")
369 tc.transactf("ok", "fetch $ (uid)")
370 tc.xuntagged(imapclient.UntaggedFetch{Seq: 2, Attrs: []imapclient.FetchAttr{imapclient.FetchUID(6)}})
372 tc.transactf("ok", "search return (all) $")
373 tc.xesearch(esearchall("2"))
375 tc.transactf("ok", "search return (save) $")
378 tc.transactf("ok", "search return (save all) all")
379 tc.xesearch(esearchall("1:3"))
381 tc.transactf("ok", "search return (all save) all")
382 tc.xesearch(esearchall("1:3"))
384 tc.transactf("ok", "search return (min save) all")
385 tc.xesearch(imapclient.UntaggedEsearch{Min: 1})
386 tc.transactf("ok", "fetch $ (uid)")
387 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{imapclient.FetchUID(5)}})
389 // Do a seemingly old-style search command with IMAP4rev2 enabled. We'll still get ESEARCH responses.
390 tc.client.Enable("IMAP4rev2")
391 tc.transactf("ok", `search undraft`)
392 tc.xesearch(esearchall("1:2"))
394 // Long commands should be rejected, not allocating too much memory.
395 lit := make([]byte, 100*1024+1)
399 writeTextLit := func(n int, expok bool) {
400 _, err := fmt.Fprintf(tc.client, " TEXT ")
401 tcheck(t, err, "write text")
403 _, err = fmt.Fprintf(tc.client, "{%d}\r\n", n)
404 tcheck(t, err, "write literal size")
405 line, err := tc.client.Readline()
406 tcheck(t, err, "read line")
407 if expok && !strings.HasPrefix(line, "+") {
408 tcheck(t, fmt.Errorf("no continuation after writing size: %s", line), "sending literal")
409 } else if !expok && !strings.HasPrefix(line, "x0 BAD [TOOBIG]") {
410 tcheck(t, fmt.Errorf("got line %s", line), "expected TOOBIG error")
415 _, err = tc.client.Write(lit[:n])
416 tcheck(t, err, "write literal data")
419 // More than 100k for a literal.
420 _, err := fmt.Fprintf(tc.client, "x0 uid search")
421 tcheck(t, err, "write start of uit search")
422 writeTextLit(100*1024+1, false)
424 // More than 1mb total for literals.
425 _, err = fmt.Fprintf(tc.client, "x0 uid search")
426 tcheck(t, err, "write start of uit search")
428 writeTextLit(100*1024, true)
430 writeTextLit(1, false)
432 // More than 1000 literals.
433 _, err = fmt.Fprintf(tc.client, "x0 uid search")
434 tcheck(t, err, "write start of uit search")
436 writeTextLit(1, true)
438 writeTextLit(1, false)
441// esearchall makes an UntaggedEsearch response with All set, for comparisons.
442func esearchall0(ss string) imapclient.NumSet {
443 seqset := imapclient.NumSet{}
444 for _, rs := range strings.Split(ss, ",") {
445 t := strings.Split(rs, ":")
452 v, err := strconv.ParseUint(t[0], 10, 32)
460 v, err := strconv.ParseUint(t[1], 10, 32)
468 seqset.Ranges = append(seqset.Ranges, imapclient.NumRange{First: first, Last: last})
473// Test the MULTISEARCH extension. Where we don't need to have a mailbox selected,
474// operating without messag sequence numbers, and return untagged esearch responses
475// that include the mailbox and uidvalidity.
476func TestSearchMulti(t *testing.T) {
477 testSearchMulti(t, false)
478 testSearchMulti(t, true)
481// Run multisearch tests with or without a mailbox selected.
482func testSearchMulti(t *testing.T, selected bool) {
483 defer mockUIDValidity()()
487 tc.client.Login("mjl@mox.example", password0)
488 tc.client.Select("inbox")
490 // Add 5 messages to Inbox and delete first 4 messages. So UIDs start at 5.
491 received := time.Date(2020, time.January, 1, 10, 0, 0, 0, time.UTC)
493 tc.client.Append("inbox", makeAppendTime(exampleMsg, received))
495 tc.client.StoreFlagsSet("1:4", true, `\Deleted`)
498 // Unselecting mailbox, esearch works in authenticated state.
503 received = time.Date(2022, time.January, 1, 9, 0, 0, 0, time.UTC)
504 tc.client.Append("inbox", makeAppendTime(searchMsg, received))
506 received = time.Date(2022, time.January, 1, 9, 0, 0, 0, time.UTC)
507 mostFlags := []string{
521 tc.client.Append("Archive", imapclient.Append{Flags: mostFlags, Received: &received, Size: int64(len(searchMsg)), Data: strings.NewReader(searchMsg)})
523 // We now have sequence numbers 1,2,3 and UIDs 5,6,7 in Inbox, and UID 1 in Archive.
525 // Basic esearch with mailboxes.
526 tc.cmdf("Tag1", `Esearch In (Personal) Return () All`)
529 imapclient.UntaggedEsearch{Tag: "Tag1", Mailbox: "Inbox", UIDValidity: 1, UID: true, All: esearchall0("5:7")},
530 imapclient.UntaggedEsearch{Tag: "Tag1", Mailbox: "Archive", UIDValidity: 1, UID: true, All: esearchall0("1")},
533 // Again, but with progress information.
534 orig := inProgressPeriod
536 inprogress := func(cur, goal uint32) imapclient.UntaggedResult {
537 return imapclient.UntaggedResult{
539 RespText: imapclient.RespText{
541 CodeArg: imapclient.CodeInProgress{Tag: "Tag1", Current: &cur, Goal: &goal},
542 More: "still searching",
546 tc.cmdf("Tag1", `Esearch In (Personal) Return () All`)
549 imapclient.UntaggedEsearch{Tag: "Tag1", Mailbox: "Inbox", UIDValidity: 1, UID: true, All: esearchall0("5:7")},
550 imapclient.UntaggedEsearch{Tag: "Tag1", Mailbox: "Archive", UIDValidity: 1, UID: true, All: esearchall0("1")},
556 inProgressPeriod = orig
558 // Explicit mailboxes listed, including non-existent one that is ignored,
559 // duplicates are ignored as well.
560 tc.cmdf("Tag1", `Esearch In (Mailboxes (INBOX Archive Archive)) Return (Min Max Count All) All`)
563 imapclient.UntaggedEsearch{Tag: "Tag1", Mailbox: "Inbox", UIDValidity: 1, UID: true, Min: 5, Max: 7, Count: uint32ptr(3), All: esearchall0("5:7")},
564 imapclient.UntaggedEsearch{Tag: "Tag1", Mailbox: "Archive", UIDValidity: 1, UID: true, Min: 1, Max: 1, Count: uint32ptr(1), All: esearchall0("1")},
567 // No response if none of the mailboxes exist.
568 tc.cmdf("Tag1", `Esearch In (Mailboxes bogus Mailboxes (nonexistent)) Return (Min Max Count All) All`)
572 // Inboxes evaluates to just inbox on new account. We'll add more mailboxes
573 // matching "inboxes" later on.
574 tc.cmdf("Tag1", `Esearch In (Inboxes) Return () All`)
577 imapclient.UntaggedEsearch{Tag: "Tag1", Mailbox: "Inbox", UIDValidity: 1, UID: true, All: esearchall0("5:7")},
580 // Subscribed is set for created mailboxes by default.
581 tc.cmdf("Tag1", `Esearch In (Subscribed) Return (Max) All`)
584 imapclient.UntaggedEsearch{Tag: "Tag1", Mailbox: "Inbox", UIDValidity: 1, UID: true, Max: 7},
585 imapclient.UntaggedEsearch{Tag: "Tag1", Mailbox: "Archive", UIDValidity: 1, UID: true, Max: 1},
588 // Asking for max does a reverse search.
589 tc.cmdf("Tag1", `Esearch In (Personal) Return (Max) All`)
592 imapclient.UntaggedEsearch{Tag: "Tag1", Mailbox: "Inbox", UIDValidity: 1, UID: true, Max: 7},
593 imapclient.UntaggedEsearch{Tag: "Tag1", Mailbox: "Archive", UIDValidity: 1, UID: true, Max: 1},
597 tc.cmdf("Tag1", `Esearch In (Personal) Return (Min) All`)
600 imapclient.UntaggedEsearch{Tag: "Tag1", Mailbox: "Inbox", UIDValidity: 1, UID: true, Min: 5},
601 imapclient.UntaggedEsearch{Tag: "Tag1", Mailbox: "Archive", UIDValidity: 1, UID: true, Min: 1},
604 // Min and max do forward and reverse search, stopping early.
605 tc.cmdf("Tag1", `Esearch In (Personal) Return (Min Max) All`)
608 imapclient.UntaggedEsearch{Tag: "Tag1", Mailbox: "Inbox", UIDValidity: 1, UID: true, Min: 5, Max: 7},
609 imapclient.UntaggedEsearch{Tag: "Tag1", Mailbox: "Archive", UIDValidity: 1, UID: true, Min: 1, Max: 1},
613 // With only 1 inbox, we can use SAVE with Inboxes. Can't anymore when we have multiple.
614 tc.transactf("ok", `Esearch In (Inboxes) Return (Save) All`)
617 // Using search result ($) works with selected mailbox.
618 tc.cmdf("Tag1", `Esearch In (Selected) Return () $`)
621 imapclient.UntaggedEsearch{Tag: "Tag1", Mailbox: "Inbox", UIDValidity: 1, UID: true, All: esearchall0("5:7")},
624 // Cannot use "selected" if we are not in selected state.
625 tc.transactf("bad", `Esearch In (Selected) Return () All`)
628 // Add more "inboxes", and other mailboxes for testing "subtree" and "subtree-one".
635 "Other/Sub1", // sub1@mox.example in config.
637 "Other/Sub2/SubA", // ruleset for sub2@mox.example in config.
639 "List", // ruleset for a mailing list
641 for _, name := range more {
642 tc.client.Create(name, nil)
643 tc.client.Append(name, makeAppendTime(exampleMsg, received))
646 // Cannot use SAVE with multiple mailboxes that match.
647 tc.transactf("bad", `Esearch In (Inboxes) Return (Save) All`)
649 // "inboxes" includes everything below Inbox, and also anything that we might
650 // deliver to based on account addresses and rulesets, but not mailing lists.
651 tc.cmdf("Tag1", `Esearch In (Inboxes) Return () All`)
654 imapclient.UntaggedEsearch{Tag: "Tag1", Mailbox: "Inbox", UIDValidity: 1, UID: true, All: esearchall0("5:7")},
655 imapclient.UntaggedEsearch{Tag: "Tag1", Mailbox: "Inbox/Sub1", UIDValidity: 3, UID: true, All: esearchall0("1")},
656 imapclient.UntaggedEsearch{Tag: "Tag1", Mailbox: "Inbox/Sub2", UIDValidity: 4, UID: true, All: esearchall0("1")},
657 imapclient.UntaggedEsearch{Tag: "Tag1", Mailbox: "Inbox/Sub2/SubA", UIDValidity: 5, UID: true, All: esearchall0("1")},
658 imapclient.UntaggedEsearch{Tag: "Tag1", Mailbox: "Inbox/Sub2/SubB", UIDValidity: 6, UID: true, All: esearchall0("1")},
659 imapclient.UntaggedEsearch{Tag: "Tag1", Mailbox: "Other/Sub1", UIDValidity: 8, UID: true, All: esearchall0("1")},
660 imapclient.UntaggedEsearch{Tag: "Tag1", Mailbox: "Other/Sub2/SubA", UIDValidity: 10, UID: true, All: esearchall0("1")},
664 tc.cmdf("Tag1", `Esearch In (Subtree Other) Return () All`)
667 imapclient.UntaggedEsearch{Tag: "Tag1", Mailbox: "Other", UIDValidity: 7, UID: true, All: esearchall0("1")},
668 imapclient.UntaggedEsearch{Tag: "Tag1", Mailbox: "Other/Sub1", UIDValidity: 8, UID: true, All: esearchall0("1")},
669 imapclient.UntaggedEsearch{Tag: "Tag1", Mailbox: "Other/Sub2", UIDValidity: 9, UID: true, All: esearchall0("1")},
670 imapclient.UntaggedEsearch{Tag: "Tag1", Mailbox: "Other/Sub2/SubA", UIDValidity: 10, UID: true, All: esearchall0("1")},
671 imapclient.UntaggedEsearch{Tag: "Tag1", Mailbox: "Other/Sub2/SubB", UIDValidity: 11, UID: true, All: esearchall0("1")},
675 tc.cmdf("Tag1", `Esearch In (Subtree-One Other) Return () All`)
678 imapclient.UntaggedEsearch{Tag: "Tag1", Mailbox: "Other", UIDValidity: 7, UID: true, All: esearchall0("1")},
679 imapclient.UntaggedEsearch{Tag: "Tag1", Mailbox: "Other/Sub1", UIDValidity: 8, UID: true, All: esearchall0("1")},
680 imapclient.UntaggedEsearch{Tag: "Tag1", Mailbox: "Other/Sub2", UIDValidity: 9, UID: true, All: esearchall0("1")},
683 // Search with sequence set also for non-selected mailboxes(!). The min/max would
684 // get the first and last message, but the message sequence set forces a scan.
685 tc.cmdf("Tag1", `Esearch In (Mailboxes Inbox) Return (Min Max) 1:*`)
688 imapclient.UntaggedEsearch{Tag: "Tag1", Mailbox: "Inbox", UIDValidity: 1, UID: true, Min: 5, Max: 7},
691 // Search with uid set with "$highnum:*" forces getting highest uid.
692 tc.cmdf("Tag1", `Esearch In (Mailboxes Inbox) Return (Min Max) Uid *:100`)
695 imapclient.UntaggedEsearch{Tag: "Tag1", Mailbox: "Inbox", UIDValidity: 1, UID: true, Min: 7, Max: 7},
697 tc.cmdf("Tag1", `Esearch In (Mailboxes Inbox) Return (Min Max) Uid 100:*`)
700 imapclient.UntaggedEsearch{Tag: "Tag1", Mailbox: "Inbox", UIDValidity: 1, UID: true, Min: 7, Max: 7},
702 tc.cmdf("Tag1", `Esearch In (Mailboxes Inbox) Return (Min Max) Uid 1:*`)
705 imapclient.UntaggedEsearch{Tag: "Tag1", Mailbox: "Inbox", UIDValidity: 1, UID: true, Min: 5, Max: 7},
708 // We use another session to add a new message to Inbox and to Archive. Searching
709 // with Inbox selected will not return the new message since it isn't available in
710 // the session yet. The message in Archive is returned, since there is no session
712 tc2 := startNoSwitchboard(t)
713 defer tc2.closeNoWait()
714 tc2.client.Login("mjl@mox.example", password0)
715 tc2.client.Append("inbox", makeAppendTime(searchMsg, received))
716 tc2.client.Append("Archive", makeAppendTime(searchMsg, received))
718 tc.cmdf("Tag1", `Esearch In (Mailboxes (Inbox Archive)) Return (Count) All`)
722 imapclient.UntaggedEsearch{Tag: "Tag1", Mailbox: "Inbox", UIDValidity: 1, UID: true, Count: uint32ptr(3)},
723 imapclient.UntaggedEsearch{Tag: "Tag1", Mailbox: "Archive", UIDValidity: 1, UID: true, Count: uint32ptr(2)},
724 imapclient.UntaggedExists(4),
725 imapclient.UntaggedFetch{Seq: 4, Attrs: []imapclient.FetchAttr{imapclient.FetchUID(8), imapclient.FetchFlags(nil)}},
729 imapclient.UntaggedEsearch{Tag: "Tag1", Mailbox: "Inbox", UIDValidity: 1, UID: true, Count: uint32ptr(4)},
730 imapclient.UntaggedEsearch{Tag: "Tag1", Mailbox: "Archive", UIDValidity: 1, UID: true, Count: uint32ptr(2)},
735 // Saving a search result, and then using it with another mailbox results in error.
736 tc.transactf("ok", `Esearch In (Mailboxes Inbox) Return (Save) All`)
737 tc.transactf("no", `Esearch In (Mailboxes Archive) Return () $`)
739 tc.transactf("bad", `Esearch In (Inboxes) Return (Save) All`) // Need a selected mailbox with SAVE.
740 tc.transactf("no", `Esearch In (Inboxes) Return () $`) // Cannot use saved result with non-selected mailbox.
743 tc.transactf("bad", `Esearch In () Return () All`) // Missing values for "IN"-list.
744 tc.transactf("bad", `Esearch In (Bogus) Return () All`) // Bogus word for "IN".
745 tc.transactf("bad", `Esearch In ("Selected") Return () All`) // IN-words can't be quoted.
746 tc.transactf("bad", `Esearch In (Selected-Delayed) Return () All`) // From NOTIFY, not in ESEARCH.
747 tc.transactf("bad", `Esearch In (Subtree-One) Return () All`) // After subtree-one we need a list.
748 tc.transactf("bad", `Esearch In (Subtree-One ) Return () All`) // After subtree-one we need a list.
749 tc.transactf("bad", `Esearch In (Subtree-One (Test) ) Return () All`) // Bogus space.
754 // From now on, we are in selected state.
756 tc.cmdf("Tag1", `Esearch In (Selected) Return () All`)
759 imapclient.UntaggedEsearch{Tag: "Tag1", Mailbox: "Inbox", UIDValidity: 1, UID: true, All: esearchall0("5:8")},
763 tc.transactf("ok", `Esearch In (Selected) Return (Save) All`)
766 tc.cmdf("Tag1", `Esearch In (Selected) Return () $`)
769 imapclient.UntaggedEsearch{Tag: "Tag1", Mailbox: "Inbox", UIDValidity: 1, UID: true, All: esearchall0("5:8")},
772 // Inbox happens to be the selected mailbox, so OK.
773 tc.cmdf("Tag1", `Esearch In (Mailboxes Inbox) Return () $`)
776 imapclient.UntaggedEsearch{Tag: "Tag1", Mailbox: "Inbox", UIDValidity: 1, UID: true, All: esearchall0("5:8")},
779 // Non-selected mailboxes aren't allowed to use the saved result.
780 tc.transactf("no", `Esearch In (Mailboxes Archive) Return () $`)
781 tc.transactf("no", `Esearch In (Mailboxes Archive) Return () uid $`)
783 tc.cmdf("Tag1", `Esearch In (Selected) Return (Save Min Max) All`)
786 imapclient.UntaggedEsearch{Tag: "Tag1", Mailbox: "Inbox", UIDValidity: 1, UID: true, Min: 5, Max: 8},
788 tc.cmdf("Tag1", `Esearch In (Selected) Return () $`)
791 imapclient.UntaggedEsearch{Tag: "Tag1", Mailbox: "Inbox", UIDValidity: 1, UID: true, All: esearchall0("5,8")},
794 tc.cmdf("Tag1", `Esearch In (Selected) Return (Save Min) All`)
797 imapclient.UntaggedEsearch{Tag: "Tag1", Mailbox: "Inbox", UIDValidity: 1, UID: true, Min: 5},
800 tc.cmdf("Tag1", `Esearch In (Selected) Return () $`)
803 imapclient.UntaggedEsearch{Tag: "Tag1", Mailbox: "Inbox", UIDValidity: 1, UID: true, All: esearchall0("5")},
806 tc.cmdf("Tag1", `Esearch In (Selected) Return (Save Max) All`)
809 imapclient.UntaggedEsearch{Tag: "Tag1", Mailbox: "Inbox", UIDValidity: 1, UID: true, Max: 8},
812 tc.cmdf("Tag1", `Esearch In (Selected) Return () $`)
815 imapclient.UntaggedEsearch{Tag: "Tag1", Mailbox: "Inbox", UIDValidity: 1, UID: true, All: esearchall0("8")},
818 tc.cmdf("Tag1", `Esearch In (Selected) Return (Save Min Max Count) All`)
821 imapclient.UntaggedEsearch{Tag: "Tag1", Mailbox: "Inbox", UIDValidity: 1, UID: true, Min: 5, Max: 8, Count: uint32ptr(4)},
824 tc.cmdf("Tag1", `Esearch In (Selected) Return () $`)
827 imapclient.UntaggedEsearch{Tag: "Tag1", Mailbox: "Inbox", UIDValidity: 1, UID: true, All: esearchall0("5:8")},