1package imapserver
2
3import (
4 "testing"
5
6 "github.com/mjl-/mox/imapclient"
7 "github.com/mjl-/mox/store"
8)
9
10func TestListBasic(t *testing.T) {
11 testListBasic(t, false)
12}
13
14func TestListBasicUIDOnly(t *testing.T) {
15 testListBasic(t, true)
16}
17
18func testListBasic(t *testing.T, uidonly bool) {
19 tc := start(t, uidonly)
20 defer tc.close()
21
22 tc.login("mjl@mox.example", password0)
23
24 ulist := func(name string, flags ...string) imapclient.UntaggedList {
25 if len(flags) == 0 {
26 flags = nil
27 }
28 return imapclient.UntaggedList{Flags: flags, Separator: '/', Mailbox: name}
29 }
30
31 tc.last(tc.client.List("INBOX"))
32 tc.xuntagged(ulist("Inbox"))
33
34 tc.last(tc.client.List("Inbox"))
35 tc.xuntagged(ulist("Inbox"))
36
37 tc.last(tc.client.List("expungebox"))
38 tc.xuntagged()
39
40 tc.last(tc.client.List("%"))
41 tc.xuntagged(ulist("Archive", `\Archive`), ulist("Drafts", `\Drafts`), ulist("Inbox"), ulist("Junk", `\Junk`), ulist("Sent", `\Sent`), ulist("Trash", `\Trash`))
42
43 tc.last(tc.client.List("*"))
44 tc.xuntagged(ulist("Archive", `\Archive`), ulist("Drafts", `\Drafts`), ulist("Inbox"), ulist("Junk", `\Junk`), ulist("Sent", `\Sent`), ulist("Trash", `\Trash`))
45
46 tc.last(tc.client.List("A*"))
47 tc.xuntagged(ulist("Archive", `\Archive`))
48
49 tc.client.Create("Inbox/todo", nil)
50
51 tc.last(tc.client.List("Inbox*"))
52 tc.xuntagged(ulist("Inbox"), ulist("Inbox/todo"))
53
54 tc.last(tc.client.List("Inbox/%"))
55 tc.xuntagged(ulist("Inbox/todo"))
56
57 tc.last(tc.client.List("Inbox/*"))
58 tc.xuntagged(ulist("Inbox/todo"))
59
60 // Leading full INBOX is turned into Inbox, so mailbox matches.
61 tc.last(tc.client.List("INBOX/*"))
62 tc.xuntagged(ulist("Inbox/todo"))
63
64 // No match because we are only touching various casings of the full "INBOX".
65 tc.last(tc.client.List("INBO*"))
66 tc.xuntagged()
67}
68
69func TestListExtended(t *testing.T) {
70 testListExtended(t, false)
71}
72
73func TestListExtendedUIDOnly(t *testing.T) {
74 testListExtended(t, true)
75}
76
77func testListExtended(t *testing.T, uidonly bool) {
78 defer mockUIDValidity()()
79
80 tc := start(t, uidonly)
81 defer tc.close()
82
83 tc.login("mjl@mox.example", password0)
84
85 ulist := func(name string, flags ...string) imapclient.UntaggedList {
86 if len(flags) == 0 {
87 flags = nil
88 }
89 return imapclient.UntaggedList{Flags: flags, Separator: '/', Mailbox: name}
90 }
91
92 uidvals := map[string]uint32{}
93 use := store.DefaultInitialMailboxes.SpecialUse
94 for _, name := range []string{"Inbox", use.Archive, use.Draft, use.Junk, use.Sent, use.Trash} {
95 uidvals[name] = 1
96 }
97 for _, name := range store.DefaultInitialMailboxes.Regular {
98 uidvals[name] = 1
99 }
100 var uidvalnext uint32 = 3
101 uidval := func(name string) uint32 {
102 v, ok := uidvals[name]
103 if !ok {
104 v = uidvalnext
105 uidvals[name] = v
106 uidvalnext++
107 }
108 return v
109 }
110
111 ustatus := func(name string) imapclient.UntaggedStatus {
112 attrs := map[imapclient.StatusAttr]int64{
113 imapclient.StatusMessages: 0,
114 imapclient.StatusUIDNext: 1,
115 imapclient.StatusUIDValidity: int64(uidval(name)),
116 imapclient.StatusUnseen: 0,
117 imapclient.StatusDeleted: 0,
118 imapclient.StatusSize: 0,
119 imapclient.StatusRecent: 0,
120 imapclient.StatusAppendLimit: 0,
121 }
122 return imapclient.UntaggedStatus{Mailbox: name, Attrs: attrs}
123 }
124
125 const (
126 Fsubscribed = `\Subscribed`
127 Fhaschildren = `\HasChildren`
128 Fhasnochildren = `\HasNoChildren`
129 Fnonexistent = `\NonExistent`
130 Farchive = `\Archive`
131 Fdraft = `\Drafts`
132 Fjunk = `\Junk`
133 Fsent = `\Sent`
134 Ftrash = `\Trash`
135 )
136
137 // untaggedlist with flags subscribed and hasnochildren
138 xlist := func(name string, flags ...string) imapclient.UntaggedList {
139 flags = append([]string{Fhasnochildren, Fsubscribed}, flags...)
140 return ulist(name, flags...)
141 }
142
143 xchildlist := func(name string, flags ...string) imapclient.UntaggedList {
144 u := ulist(name, flags...)
145 comp := imapclient.TaggedExtComp{String: "SUBSCRIBED"}
146 u.Extended = []imapclient.MboxListExtendedItem{{Tag: "CHILDINFO", Val: imapclient.TaggedExtVal{Comp: &comp}}}
147 return u
148 }
149
150 tc.last(tc.client.ListFull(false, "INBOX"))
151 tc.xuntagged(xlist("Inbox"), ustatus("Inbox"))
152
153 tc.last(tc.client.ListFull(false, "Inbox"))
154 tc.xuntagged(xlist("Inbox"), ustatus("Inbox"))
155
156 tc.last(tc.client.ListFull(false, "%"))
157 tc.xuntagged(xlist("Archive", Farchive), ustatus("Archive"), xlist("Drafts", Fdraft), ustatus("Drafts"), xlist("Inbox"), ustatus("Inbox"), xlist("Junk", Fjunk), ustatus("Junk"), xlist("Sent", Fsent), ustatus("Sent"), xlist("Trash", Ftrash), ustatus("Trash"))
158
159 tc.last(tc.client.ListFull(false, "*"))
160 tc.xuntagged(xlist("Archive", Farchive), ustatus("Archive"), xlist("Drafts", Fdraft), ustatus("Drafts"), xlist("Inbox"), ustatus("Inbox"), xlist("Junk", Fjunk), ustatus("Junk"), xlist("Sent", Fsent), ustatus("Sent"), xlist("Trash", Ftrash), ustatus("Trash"))
161
162 tc.last(tc.client.ListFull(false, "A*"))
163 tc.xuntagged(xlist("Archive", Farchive), ustatus("Archive"))
164
165 tc.last(tc.client.ListFull(false, "A*", "Junk"))
166 tc.xuntagged(xlist("Archive", Farchive), ustatus("Archive"), xlist("Junk", Fjunk), ustatus("Junk"))
167
168 tc.client.Create("Inbox/todo", nil)
169
170 tc.last(tc.client.ListFull(false, "Inbox*"))
171 tc.xuntagged(ulist("Inbox", Fhaschildren, Fsubscribed), ustatus("Inbox"), xlist("Inbox/todo"), ustatus("Inbox/todo"))
172
173 tc.last(tc.client.ListFull(false, "Inbox/%"))
174 tc.xuntagged(xlist("Inbox/todo"), ustatus("Inbox/todo"))
175
176 tc.last(tc.client.ListFull(false, "Inbox/*"))
177 tc.xuntagged(xlist("Inbox/todo"), ustatus("Inbox/todo"))
178
179 // Leading full INBOX is turned into Inbox, so mailbox matches.
180 tc.last(tc.client.ListFull(false, "INBOX/*"))
181 tc.xuntagged(xlist("Inbox/todo"), ustatus("Inbox/todo"))
182
183 // No match because we are only touching various casings of the full "INBOX".
184 tc.last(tc.client.ListFull(false, "INBO*"))
185 tc.xuntagged()
186
187 tc.last(tc.client.ListFull(true, "Inbox"))
188 tc.xuntagged(xchildlist("Inbox", Fsubscribed, Fhaschildren), ustatus("Inbox"))
189
190 tc.client.Unsubscribe("Inbox")
191 tc.last(tc.client.ListFull(true, "Inbox"))
192 tc.xuntagged(xchildlist("Inbox", Fhaschildren), ustatus("Inbox"))
193
194 tc.client.Delete("Inbox/todo") // Still subscribed.
195 tc.last(tc.client.ListFull(true, "Inbox"))
196 tc.xuntagged(xchildlist("Inbox", Fhasnochildren), ustatus("Inbox"))
197
198 // Simple extended list without RETURN options.
199 tc.transactf("ok", `list "" ("inbox")`)
200 tc.xuntagged(ulist("Inbox"))
201
202 tc.transactf("ok", `list () "" ("inbox") return ()`)
203 tc.xuntagged(ulist("Inbox"))
204
205 tc.transactf("ok", `list "" ("inbox") return ()`)
206 tc.xuntagged(ulist("Inbox"))
207
208 tc.transactf("ok", `list () "" ("inbox")`)
209 tc.xuntagged(ulist("Inbox"))
210
211 tc.transactf("ok", `list (remote) "" ("inbox")`)
212 tc.xuntagged(ulist("Inbox"))
213
214 tc.transactf("ok", `list (remote) "" "/inbox"`)
215 tc.xuntagged()
216
217 tc.transactf("ok", `list (remote) "/inbox" ""`)
218 tc.xuntagged()
219
220 tc.transactf("ok", `list (remote) "inbox" ""`)
221 tc.xuntagged()
222
223 tc.transactf("ok", `list (remote) "inbox" "a"`)
224 tc.xuntagged()
225
226 tc.client.Create("inbox/a", nil)
227 tc.transactf("ok", `list (remote) "inbox" "a"`)
228 tc.xuntagged(ulist("Inbox/a"))
229
230 tc.client.Subscribe("x")
231 tc.transactf("ok", `list (subscribed) "" x return (subscribed)`)
232 tc.xuntagged(imapclient.UntaggedList{Flags: []string{`\Subscribed`, `\NonExistent`}, Separator: '/', Mailbox: "x"})
233
234 tc.transactf("bad", `list (recursivematch) "" "*"`) // Cannot have recursivematch without a base selection option like subscribed.
235 tc.transactf("bad", `list (recursivematch remote) "" "*"`) // "remote" is not a base selection option.
236 tc.transactf("bad", `list (unknown) "" "*"`) // Unknown selection options must result in BAD.
237 tc.transactf("bad", `list () "" "*" return (unknown)`) // Unknown return options must result in BAD.
238
239 // Return metadata.
240 tc.transactf("ok", `setmetadata inbox (/private/comment "y")`)
241 tc.transactf("ok", `list () "" ("inbox") return (metadata (/private/comment /shared/comment))`)
242 tc.xuntagged(
243 ulist("Inbox"),
244 imapclient.UntaggedMetadataAnnotations{
245 Mailbox: "Inbox",
246 Annotations: []imapclient.Annotation{
247 {Key: "/private/comment", IsString: true, Value: []byte("y")},
248 {Key: "/shared/comment"},
249 },
250 },
251 )
252
253 tc.transactf("bad", `list () "" ("inbox") return (metadata ())`) // Metadata list must be non-empty.
254 tc.transactf("bad", `list () "" ("inbox") return (metadata (/shared/comment "/private/comment" ))`) // Extra space.
255}
256