8 "github.com/mjl-/mox/imapclient"
11func TestMetadata(t *testing.T) {
12 testMetadata(t, false)
15func TestMetadataUIDOnly(t *testing.T) {
19func testMetadata(t *testing.T, uidonly bool) {
20 tc := start(t, uidonly)
23 tc.login("mjl@mox.example", password0)
25 tc.transactf("ok", `getmetadata "" /private/comment`)
28 tc.transactf("ok", `getmetadata inbox (/private/comment)`)
31 tc.transactf("ok", `setmetadata "" (/PRIVATE/COMMENT "global value")`)
32 tc.transactf("ok", `setmetadata inbox (/private/comment "mailbox value")`)
34 tc.transactf("ok", `create metabox`)
35 tc.transactf("ok", `setmetadata metabox (/private/comment "mailbox value")`)
36 tc.transactf("ok", `setmetadata metabox (/shared/comment "mailbox value")`)
37 tc.transactf("ok", `setmetadata metabox (/shared/comment nil)`) // Remove.
38 tc.transactf("ok", `delete metabox`) // Delete mailbox with live and expunged metadata.
40 tc.transactf("no", `setmetadata expungebox (/private/comment "mailbox value")`)
41 tc.xcodeWord("TRYCREATE")
43 tc.transactf("ok", `getmetadata "" ("/private/comment")`)
44 tc.xuntagged(imapclient.UntaggedMetadataAnnotations{
46 Annotations: []imapclient.Annotation{
47 {Key: "/private/comment", IsString: true, Value: []byte("global value")},
51 tc.transactf("ok", `setmetadata Inbox (/shared/comment "share")`)
53 tc.transactf("ok", `getmetadata inbox (/private/comment /private/unknown /shared/comment)`)
54 tc.xuntagged(imapclient.UntaggedMetadataAnnotations{
56 Annotations: []imapclient.Annotation{
57 {Key: "/private/comment", IsString: true, Value: []byte("mailbox value")},
58 {Key: "/shared/comment", IsString: true, Value: []byte("share")},
62 tc.transactf("no", `setmetadata doesnotexist (/private/comment "test")`) // Bad mailbox.
63 tc.transactf("no", `setmetadata Inbox (/badprefix/comment "")`)
64 tc.transactf("no", `setmetadata Inbox (/private/vendor "")`) // /*/vendor must have more components.
65 tc.transactf("no", `setmetadata Inbox (/private/vendor/stillbad "")`) // /*/vendor must have more components.
66 tc.transactf("ok", `setmetadata Inbox (/private/vendor/a/b "")`)
67 tc.transactf("bad", `setmetadata Inbox (/private/no* "")`)
68 tc.transactf("bad", `setmetadata Inbox (/private/no%% "")`)
69 tc.transactf("bad", `setmetadata Inbox (/private/notrailingslash/ "")`)
70 tc.transactf("bad", `setmetadata Inbox (/private//nodupslash "")`)
71 tc.transactf("bad", "setmetadata Inbox (/private/\001 \"\")")
72 tc.transactf("bad", "setmetadata Inbox (/private/\u007f \"\")")
73 tc.transactf("bad", `getmetadata (depth 0 depth 0) inbox (/private/a)`) // Duplicate option.
74 tc.transactf("bad", `getmetadata (depth badvalue) inbox (/private/a)`)
75 tc.transactf("bad", `getmetadata (maxsize invalid) inbox (/private/a)`)
76 tc.transactf("bad", `getmetadata (badoption) inbox (/private/a)`)
78 // Update existing annotation by key.
79 tc.transactf("ok", `setmetadata "" (/PRIVATE/COMMENT "global updated")`)
80 tc.transactf("ok", `setmetadata inbox (/private/comment "mailbox updated")`)
81 tc.transactf("ok", `getmetadata "" (/private/comment)`)
82 tc.xuntagged(imapclient.UntaggedMetadataAnnotations{
84 Annotations: []imapclient.Annotation{
85 {Key: "/private/comment", IsString: true, Value: []byte("global updated")},
88 tc.transactf("ok", `getmetadata inbox (/private/comment)`)
89 tc.xuntagged(imapclient.UntaggedMetadataAnnotations{
91 Annotations: []imapclient.Annotation{
92 {Key: "/private/comment", IsString: true, Value: []byte("mailbox updated")},
96 // Delete annotation with nil value.
97 tc.transactf("ok", `setmetadata "" (/private/comment nil)`)
98 tc.transactf("ok", `setmetadata inbox (/private/comment nil)`)
99 tc.transactf("ok", `getmetadata "" (/private/comment)`)
101 tc.transactf("ok", `getmetadata inbox (/private/comment)`)
104 // Create a literal8 value, not a string.
105 tc.transactf("ok", "setmetadata inbox (/private/comment ~{4+}\r\ntest)")
106 tc.transactf("ok", `getmetadata inbox (/private/comment)`)
107 tc.xuntagged(imapclient.UntaggedMetadataAnnotations{
109 Annotations: []imapclient.Annotation{
110 {Key: "/private/comment", IsString: false, Value: []byte("test")},
114 // Request with a maximum size, we don't get anything larger.
115 tc.transactf("ok", `setmetadata inbox (/private/another "longer")`)
116 tc.transactf("ok", `getmetadata (maxsize 4) inbox (/private/comment /private/another)`)
117 tc.xcode(imapclient.CodeMetadataLongEntries(6))
118 tc.xuntagged(imapclient.UntaggedMetadataAnnotations{
120 Annotations: []imapclient.Annotation{
121 {Key: "/private/comment", IsString: false, Value: []byte("test")},
125 // Request with various depth values.
126 tc.transactf("ok", `setmetadata inbox (/private/a "x" /private/a/b "x" /private/a/b/c "x" /private/a/b/c/d "x")`)
127 tc.transactf("ok", `getmetadata (depth 0) inbox (/private/a)`)
128 tc.xuntagged(imapclient.UntaggedMetadataAnnotations{
130 Annotations: []imapclient.Annotation{
131 {Key: "/private/a", IsString: true, Value: []byte("x")},
134 tc.transactf("ok", `getmetadata (depth 1) inbox (/private/a)`)
135 tc.xuntagged(imapclient.UntaggedMetadataAnnotations{
137 Annotations: []imapclient.Annotation{
138 {Key: "/private/a", IsString: true, Value: []byte("x")},
139 {Key: "/private/a/b", IsString: true, Value: []byte("x")},
142 tc.transactf("ok", `getmetadata (depth infinity) inbox (/private/a)`)
143 tc.xuntagged(imapclient.UntaggedMetadataAnnotations{
145 Annotations: []imapclient.Annotation{
146 {Key: "/private/a", IsString: true, Value: []byte("x")},
147 {Key: "/private/a/b", IsString: true, Value: []byte("x")},
148 {Key: "/private/a/b/c", IsString: true, Value: []byte("x")},
149 {Key: "/private/a/b/c/d", IsString: true, Value: []byte("x")},
152 // Same as previous, but ask for everything below /.
153 tc.transactf("ok", `getmetadata (depth infinity) inbox ("")`)
154 tc.xuntagged(imapclient.UntaggedMetadataAnnotations{
156 Annotations: []imapclient.Annotation{
157 {Key: "/private/a", IsString: true, Value: []byte("x")},
158 {Key: "/private/a/b", IsString: true, Value: []byte("x")},
159 {Key: "/private/a/b/c", IsString: true, Value: []byte("x")},
160 {Key: "/private/a/b/c/d", IsString: true, Value: []byte("x")},
161 {Key: "/private/another", IsString: true, Value: []byte("longer")},
162 {Key: "/private/comment", IsString: false, Value: []byte("test")},
163 {Key: "/private/vendor/a/b", IsString: true, Value: []byte("")},
164 {Key: "/shared/comment", IsString: true, Value: []byte("share")},
168 // Deleting a mailbox with an annotation should work and annotations should not
169 // come back when recreating mailbox.
170 tc.transactf("ok", "create testbox")
171 tc.transactf("ok", `setmetadata testbox (/private/a "x")`)
172 tc.transactf("ok", "delete testbox")
173 tc.transactf("ok", "create testbox")
174 tc.transactf("ok", `getmetadata testbox (/private/a)`)
177 // When renaming mailbox, annotations must be copied to destination mailbox.
178 tc.transactf("ok", "rename inbox newbox")
179 tc.transactf("ok", `getmetadata newbox (/private/a)`)
180 tc.xuntagged(imapclient.UntaggedMetadataAnnotations{
182 Annotations: []imapclient.Annotation{
183 {Key: "/private/a", IsString: true, Value: []byte("x")},
186 tc.transactf("ok", `getmetadata inbox (/private/a)`)
187 tc.xuntagged(imapclient.UntaggedMetadataAnnotations{
189 Annotations: []imapclient.Annotation{
190 {Key: "/private/a", IsString: true, Value: []byte("x")},
194 // Broadcast should not happen when metadata capability is not enabled.
195 tc2 := startNoSwitchboard(t, uidonly)
196 defer tc2.closeNoWait()
197 tc2.login("mjl@mox.example", password0)
198 tc2.client.Select("inbox")
201 tc2.readprefixline("+ ")
202 done := make(chan error)
207 done <- fmt.Errorf("%v", x)
210 untagged, _ := tc2.client.ReadUntagged()
211 var exists imapclient.UntaggedExists
212 tuntagged(tc2.t, untagged, &exists)
213 tc2.writelinef("done")
218 // Should not cause idle to return.
219 tc.transactf("ok", `setmetadata inbox (/private/a "y")`)
221 tc.transactf("ok", "append inbox {4+}\r\ntest")
223 timer := time.NewTimer(time.Second)
227 tc.check(err, "idle")
229 t.Fatalf("idle did not finish")
232 // Broadcast should happen when metadata capability is enabled.
233 tc2.client.Enable(imapclient.CapMetadata)
235 tc2.readprefixline("+ ")
236 done = make(chan error)
241 done <- fmt.Errorf("%v", x)
244 untagged, _ := tc2.client.ReadUntagged()
245 var metadataKeys imapclient.UntaggedMetadataKeys
246 tuntagged(tc2.t, untagged, &metadataKeys)
247 tc2.writelinef("done")
252 // Should cause idle to return.
253 tc.transactf("ok", `setmetadata inbox (/private/a "z")`)
255 timer = time.NewTimer(time.Second)
259 tc.check(err, "idle")
261 t.Fatalf("idle did not finish")
265func TestMetadataLimit(t *testing.T) {
266 tc := start(t, false)
269 tc.login("mjl@mox.example", password0)
271 maxKeys, maxSize := metadataMaxKeys, metadataMaxSize
273 metadataMaxKeys = maxKeys
274 metadataMaxSize = maxSize
277 metadataMaxSize = 1000
279 // Reach max total size limit.
280 buf := make([]byte, metadataMaxSize+1)
284 tc.cmdf("", "setmetadata inbox (/private/large ~{%d+}", len(buf))
286 tc.client.Writelinef(")")
288 tc.xcode(imapclient.CodeMetadataMaxSize(metadataMaxSize))
290 // Reach limit for max number.
291 for i := 1; i <= metadataMaxKeys; i++ {
292 tc.transactf("ok", `setmetadata inbox (/private/key%d "test")`, i)
294 tc.transactf("no", `setmetadata inbox (/private/toomany "test")`)
295 tc.xcode(imapclient.CodeMetadataTooMany{})