9 "github.com/mjl-/mox/imapclient"
12var searchMsg = strings.ReplaceAll(`Date: Mon, 1 Jan 2022 10:00:00 +0100 (CEST)
13From: mjl <mjl@mox.example>
15To: mox <mox@mox.example>
18Reply-To: <noreply@mox.example>
19Message-Id: <123@mox.example>
21Content-Type: multipart/alternative; boundary=x
24Content-Type: text/plain; charset=utf-8
29Content-Type: text/html; charset=utf-8
36func (tc *testconn) xsearch(nums ...uint32) {
39 tc.xuntagged(imapclient.UntaggedSearch(nums))
42func (tc *testconn) xsearchmodseq(modseq int64, nums ...uint32) {
49 tc.xuntagged(imapclient.UntaggedSearchModSeq{Nums: nums, ModSeq: modseq})
52func (tc *testconn) xesearch(exp imapclient.UntaggedEsearch) {
55 exp.Correlator = tc.client.LastTag
59func TestSearch(t *testing.T) {
62 tc.client.Login("mjl@mox.example", "testtest")
63 tc.client.Select("inbox")
65 // Add 5 and delete first 4 messages. So UIDs start at 5.
66 received := time.Date(2020, time.January, 1, 10, 0, 0, 0, time.UTC)
67 for i := 0; i < 5; i++ {
68 tc.client.Append("inbox", nil, &received, []byte(exampleMsg))
70 tc.client.StoreFlagsSet("1:4", true, `\Deleted`)
73 received = time.Date(2022, time.January, 1, 9, 0, 0, 0, time.UTC)
74 tc.client.Append("inbox", nil, &received, []byte(searchMsg))
76 received = time.Date(2022, time.January, 1, 9, 0, 0, 0, time.UTC)
77 mostFlags := []string{
91 tc.client.Append("inbox", mostFlags, &received, []byte(searchMsg))
93 // We now have sequence numbers 1,2,3 and UIDs 5,6,7.
95 tc.transactf("ok", "search all")
98 tc.transactf("ok", "uid search all")
101 tc.transactf("ok", "search answered")
104 tc.transactf("ok", `search bcc "bcc@mox.example"`)
107 tc.transactf("ok", "search before 1-Jan-2038")
109 tc.transactf("ok", "search before 1-Jan-2020")
110 tc.xsearch() // Before is about received, not date header of message.
112 tc.transactf("ok", `search body "Joe"`)
114 tc.transactf("ok", `search body "Joe" body "bogus"`)
116 tc.transactf("ok", `search body "Joe" text "Blurdybloop"`)
118 tc.transactf("ok", `search body "Joe" not text "mox"`)
120 tc.transactf("ok", `search body "Joe" not not body "Joe"`)
122 tc.transactf("ok", `search body "this is plain text"`)
124 tc.transactf("ok", `search body "this is html"`)
127 tc.transactf("ok", `search cc "xcc@mox.example"`)
130 tc.transactf("ok", `search deleted`)
133 tc.transactf("ok", `search flagged`)
136 tc.transactf("ok", `search from "foobar@Blurdybloop.example"`)
139 tc.transactf("ok", `search keyword $Forwarded`)
142 tc.transactf("ok", `search keyword Custom1`)
145 tc.transactf("ok", `search keyword custom2`)
148 tc.transactf("ok", `search new`)
149 tc.xsearch() // New requires a message to be recent. We pretend all messages are not recent.
151 tc.transactf("ok", `search old`)
154 tc.transactf("ok", `search on 1-Jan-2022`)
157 tc.transactf("ok", `search recent`)
160 tc.transactf("ok", `search seen`)
163 tc.transactf("ok", `search since 1-Jan-2020`)
166 tc.transactf("ok", `search subject "afternoon"`)
169 tc.transactf("ok", `search text "Joe"`)
172 tc.transactf("ok", `search to "mooch@owatagu.siam.edu.example"`)
175 tc.transactf("ok", `search unanswered`)
178 tc.transactf("ok", `search undeleted`)
181 tc.transactf("ok", `search unflagged`)
184 tc.transactf("ok", `search unkeyword $Junk`)
187 tc.transactf("ok", `search unkeyword custom1`)
190 tc.transactf("ok", `search unseen`)
193 tc.transactf("ok", `search draft`)
196 tc.transactf("ok", `search header "subject" "afternoon"`)
199 tc.transactf("ok", `search larger 1`)
202 tc.transactf("ok", `search not text "mox"`)
205 tc.transactf("ok", `search or seen unseen`)
208 tc.transactf("ok", `search or unseen seen`)
211 tc.transactf("ok", `search sentbefore 8-Feb-1994`)
214 tc.transactf("ok", `search senton 7-Feb-1994`)
217 tc.transactf("ok", `search sentsince 6-Feb-1994`)
220 tc.transactf("ok", `search smaller 9999999`)
223 tc.transactf("ok", `search uid 1`)
226 tc.transactf("ok", `search uid 5`)
229 tc.transactf("ok", `search or larger 1000000 smaller 1`)
232 tc.transactf("ok", `search undraft`)
235 tc.transactf("no", `search charset unknown text "mox"`)
236 tc.transactf("ok", `search charset us-ascii text "mox"`)
238 tc.transactf("ok", `search charset utf-8 text "mox"`)
241 esearchall := func(ss string) imapclient.UntaggedEsearch {
242 return imapclient.UntaggedEsearch{All: esearchall0(ss)}
245 uint32ptr := func(v uint32) *uint32 {
249 // Do new-style ESEARCH requests with RETURN. We should get an ESEARCH response.
250 tc.transactf("ok", "search return () all")
251 tc.xesearch(esearchall("1:3")) // Without any options, "ALL" is implicit.
253 tc.transactf("ok", "search return (min max count all) all")
254 tc.xesearch(imapclient.UntaggedEsearch{Min: 1, Max: 3, Count: uint32ptr(3), All: esearchall0("1:3")})
256 tc.transactf("ok", "UID search return (min max count all) all")
257 tc.xesearch(imapclient.UntaggedEsearch{UID: true, Min: 5, Max: 7, Count: uint32ptr(3), All: esearchall0("5:7")})
259 tc.transactf("ok", "search return (min) all")
260 tc.xesearch(imapclient.UntaggedEsearch{Min: 1})
262 tc.transactf("ok", "search return (min) 3")
263 tc.xesearch(imapclient.UntaggedEsearch{Min: 3})
265 tc.transactf("ok", "search return (min) NOT all")
266 tc.xesearch(imapclient.UntaggedEsearch{}) // Min not present if no match.
268 tc.transactf("ok", "search return (max) all")
269 tc.xesearch(imapclient.UntaggedEsearch{Max: 3})
271 tc.transactf("ok", "search return (max) 1")
272 tc.xesearch(imapclient.UntaggedEsearch{Max: 1})
274 tc.transactf("ok", "search return (max) not all")
275 tc.xesearch(imapclient.UntaggedEsearch{}) // Max not present if no match.
277 tc.transactf("ok", "search return (min max) all")
278 tc.xesearch(imapclient.UntaggedEsearch{Min: 1, Max: 3})
280 tc.transactf("ok", "search return (min max) 1")
281 tc.xesearch(imapclient.UntaggedEsearch{Min: 1, Max: 1})
283 tc.transactf("ok", "search return (min max) not all")
284 tc.xesearch(imapclient.UntaggedEsearch{})
286 tc.transactf("ok", "search return (all) not all")
287 tc.xesearch(imapclient.UntaggedEsearch{}) // All not present if no match.
289 tc.transactf("ok", "search return (min max all) not all")
290 tc.xesearch(imapclient.UntaggedEsearch{})
292 tc.transactf("ok", "search return (min max all count) not all")
293 tc.xesearch(imapclient.UntaggedEsearch{Count: uint32ptr(0)})
295 tc.transactf("ok", "search return (min max count all) 1,3")
296 tc.xesearch(imapclient.UntaggedEsearch{Min: 1, Max: 3, Count: uint32ptr(2), All: esearchall0("1,3")})
298 tc.transactf("ok", "search return (min max count all) UID 5,7")
299 tc.xesearch(imapclient.UntaggedEsearch{Min: 1, Max: 3, Count: uint32ptr(2), All: esearchall0("1,3")})
301 tc.transactf("ok", "uid search return (min max count all) 1,3")
302 tc.xesearch(imapclient.UntaggedEsearch{UID: true, Min: 5, Max: 7, Count: uint32ptr(2), All: esearchall0("5,7")})
304 tc.transactf("ok", "uid search return (min max count all) UID 5,7")
305 tc.xesearch(imapclient.UntaggedEsearch{UID: true, Min: 5, Max: 7, Count: uint32ptr(2), All: esearchall0("5,7")})
307 tc.transactf("no", `search return () charset unknown text "mox"`)
308 tc.transactf("ok", `search return () charset us-ascii text "mox"`)
309 tc.xesearch(esearchall("2:3"))
310 tc.transactf("ok", `search return () charset utf-8 text "mox"`)
311 tc.xesearch(esearchall("2:3"))
313 tc.transactf("bad", `search return (unknown) all`)
315 tc.transactf("ok", "search return (save) 2")
317 tc.transactf("ok", "fetch $ (uid)")
318 tc.xuntagged(imapclient.UntaggedFetch{Seq: 2, Attrs: []imapclient.FetchAttr{imapclient.FetchUID(6)}})
320 tc.transactf("ok", "search return (all) $")
321 tc.xesearch(esearchall("2"))
323 tc.transactf("ok", "search return (save) $")
326 tc.transactf("ok", "search return (save all) all")
327 tc.xesearch(esearchall("1:3"))
329 tc.transactf("ok", "search return (all save) all")
330 tc.xesearch(esearchall("1:3"))
332 tc.transactf("ok", "search return (min save) all")
333 tc.xesearch(imapclient.UntaggedEsearch{Min: 1})
334 tc.transactf("ok", "fetch $ (uid)")
335 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{imapclient.FetchUID(5)}})
337 // Do a seemingly old-style search command with IMAP4rev2 enabled. We'll still get ESEARCH responses.
338 tc.client.Enable("IMAP4rev2")
339 tc.transactf("ok", `search undraft`)
340 tc.xesearch(esearchall("1:2"))
343// esearchall makes an UntaggedEsearch response with All set, for comparisons.
344func esearchall0(ss string) imapclient.NumSet {
345 seqset := imapclient.NumSet{}
346 for _, rs := range strings.Split(ss, ",") {
347 t := strings.Split(rs, ":")
354 v, err := strconv.ParseUint(t[0], 10, 32)
362 v, err := strconv.ParseUint(t[1], 10, 32)
370 seqset.Ranges = append(seqset.Ranges, imapclient.NumRange{First: first, Last: last})