8 "github.com/mjl-/mox/imapclient"
11func TestMetadata(t *testing.T) {
15 tc.client.Login("mjl@mox.example", password0)
17 tc.transactf("ok", `getmetadata "" /private/comment`)
20 tc.transactf("ok", `getmetadata inbox (/private/comment)`)
23 tc.transactf("ok", `setmetadata "" (/PRIVATE/COMMENT "global value")`)
24 tc.transactf("ok", `setmetadata inbox (/private/comment "mailbox value")`)
26 tc.transactf("ok", `create metabox`)
27 tc.transactf("ok", `setmetadata metabox (/private/comment "mailbox value")`)
28 tc.transactf("ok", `setmetadata metabox (/shared/comment "mailbox value")`)
29 tc.transactf("ok", `setmetadata metabox (/shared/comment nil)`) // Remove.
30 tc.transactf("ok", `delete metabox`) // Delete mailbox with live and expunged metadata.
32 tc.transactf("no", `setmetadata expungebox (/private/comment "mailbox value")`)
35 tc.transactf("ok", `getmetadata "" ("/private/comment")`)
36 tc.xuntagged(imapclient.UntaggedMetadataAnnotations{
38 Annotations: []imapclient.Annotation{
39 {Key: "/private/comment", IsString: true, Value: []byte("global value")},
43 tc.transactf("ok", `setmetadata Inbox (/shared/comment "share")`)
45 tc.transactf("ok", `getmetadata inbox (/private/comment /private/unknown /shared/comment)`)
46 tc.xuntagged(imapclient.UntaggedMetadataAnnotations{
48 Annotations: []imapclient.Annotation{
49 {Key: "/private/comment", IsString: true, Value: []byte("mailbox value")},
50 {Key: "/shared/comment", IsString: true, Value: []byte("share")},
54 tc.transactf("no", `setmetadata doesnotexist (/private/comment "test")`) // Bad mailbox.
55 tc.transactf("no", `setmetadata Inbox (/badprefix/comment "")`)
56 tc.transactf("no", `setmetadata Inbox (/private/vendor "")`) // /*/vendor must have more components.
57 tc.transactf("no", `setmetadata Inbox (/private/vendor/stillbad "")`) // /*/vendor must have more components.
58 tc.transactf("ok", `setmetadata Inbox (/private/vendor/a/b "")`)
59 tc.transactf("bad", `setmetadata Inbox (/private/no* "")`)
60 tc.transactf("bad", `setmetadata Inbox (/private/no%% "")`)
61 tc.transactf("bad", `setmetadata Inbox (/private/notrailingslash/ "")`)
62 tc.transactf("bad", `setmetadata Inbox (/private//nodupslash "")`)
63 tc.transactf("bad", "setmetadata Inbox (/private/\001 \"\")")
64 tc.transactf("bad", "setmetadata Inbox (/private/\u007f \"\")")
65 tc.transactf("bad", `getmetadata (depth 0 depth 0) inbox (/private/a)`) // Duplicate option.
66 tc.transactf("bad", `getmetadata (depth badvalue) inbox (/private/a)`)
67 tc.transactf("bad", `getmetadata (maxsize invalid) inbox (/private/a)`)
68 tc.transactf("bad", `getmetadata (badoption) inbox (/private/a)`)
70 // Update existing annotation by key.
71 tc.transactf("ok", `setmetadata "" (/PRIVATE/COMMENT "global updated")`)
72 tc.transactf("ok", `setmetadata inbox (/private/comment "mailbox updated")`)
73 tc.transactf("ok", `getmetadata "" (/private/comment)`)
74 tc.xuntagged(imapclient.UntaggedMetadataAnnotations{
76 Annotations: []imapclient.Annotation{
77 {Key: "/private/comment", IsString: true, Value: []byte("global updated")},
80 tc.transactf("ok", `getmetadata inbox (/private/comment)`)
81 tc.xuntagged(imapclient.UntaggedMetadataAnnotations{
83 Annotations: []imapclient.Annotation{
84 {Key: "/private/comment", IsString: true, Value: []byte("mailbox updated")},
88 // Delete annotation with nil value.
89 tc.transactf("ok", `setmetadata "" (/private/comment nil)`)
90 tc.transactf("ok", `setmetadata inbox (/private/comment nil)`)
91 tc.transactf("ok", `getmetadata "" (/private/comment)`)
93 tc.transactf("ok", `getmetadata inbox (/private/comment)`)
96 // Create a literal8 value, not a string.
97 tc.transactf("ok", "setmetadata inbox (/private/comment ~{4+}\r\ntest)")
98 tc.transactf("ok", `getmetadata inbox (/private/comment)`)
99 tc.xuntagged(imapclient.UntaggedMetadataAnnotations{
101 Annotations: []imapclient.Annotation{
102 {Key: "/private/comment", IsString: false, Value: []byte("test")},
106 // Request with a maximum size, we don't get anything larger.
107 tc.transactf("ok", `setmetadata inbox (/private/another "longer")`)
108 tc.transactf("ok", `getmetadata (maxsize 4) inbox (/private/comment /private/another)`)
109 tc.xcodeArg(imapclient.CodeOther{Code: "METADATA", Args: []string{"LONGENTRIES", "6"}})
110 tc.xuntagged(imapclient.UntaggedMetadataAnnotations{
112 Annotations: []imapclient.Annotation{
113 {Key: "/private/comment", IsString: false, Value: []byte("test")},
117 // Request with various depth values.
118 tc.transactf("ok", `setmetadata inbox (/private/a "x" /private/a/b "x" /private/a/b/c "x" /private/a/b/c/d "x")`)
119 tc.transactf("ok", `getmetadata (depth 0) inbox (/private/a)`)
120 tc.xuntagged(imapclient.UntaggedMetadataAnnotations{
122 Annotations: []imapclient.Annotation{
123 {Key: "/private/a", IsString: true, Value: []byte("x")},
126 tc.transactf("ok", `getmetadata (depth 1) inbox (/private/a)`)
127 tc.xuntagged(imapclient.UntaggedMetadataAnnotations{
129 Annotations: []imapclient.Annotation{
130 {Key: "/private/a", IsString: true, Value: []byte("x")},
131 {Key: "/private/a/b", IsString: true, Value: []byte("x")},
134 tc.transactf("ok", `getmetadata (depth infinity) 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")},
140 {Key: "/private/a/b/c", IsString: true, Value: []byte("x")},
141 {Key: "/private/a/b/c/d", IsString: true, Value: []byte("x")},
144 // Same as previous, but ask for everything below /.
145 tc.transactf("ok", `getmetadata (depth infinity) inbox ("")`)
146 tc.xuntagged(imapclient.UntaggedMetadataAnnotations{
148 Annotations: []imapclient.Annotation{
149 {Key: "/private/a", IsString: true, Value: []byte("x")},
150 {Key: "/private/a/b", IsString: true, Value: []byte("x")},
151 {Key: "/private/a/b/c", IsString: true, Value: []byte("x")},
152 {Key: "/private/a/b/c/d", IsString: true, Value: []byte("x")},
153 {Key: "/private/another", IsString: true, Value: []byte("longer")},
154 {Key: "/private/comment", IsString: false, Value: []byte("test")},
155 {Key: "/private/vendor/a/b", IsString: true, Value: []byte("")},
156 {Key: "/shared/comment", IsString: true, Value: []byte("share")},
160 // Deleting a mailbox with an annotation should work and annotations should not
161 // come back when recreating mailbox.
162 tc.transactf("ok", "create testbox")
163 tc.transactf("ok", `setmetadata testbox (/private/a "x")`)
164 tc.transactf("ok", "delete testbox")
165 tc.transactf("ok", "create testbox")
166 tc.transactf("ok", `getmetadata testbox (/private/a)`)
169 // When renaming mailbox, annotations must be copied to destination mailbox.
170 tc.transactf("ok", "rename inbox newbox")
171 tc.transactf("ok", `getmetadata newbox (/private/a)`)
172 tc.xuntagged(imapclient.UntaggedMetadataAnnotations{
174 Annotations: []imapclient.Annotation{
175 {Key: "/private/a", IsString: true, Value: []byte("x")},
178 tc.transactf("ok", `getmetadata inbox (/private/a)`)
179 tc.xuntagged(imapclient.UntaggedMetadataAnnotations{
181 Annotations: []imapclient.Annotation{
182 {Key: "/private/a", IsString: true, Value: []byte("x")},
186 // Broadcast should not happen when metadata capability is not enabled.
187 tc2 := startNoSwitchboard(t)
188 defer tc2.closeNoWait()
189 tc2.client.Login("mjl@mox.example", password0)
190 tc2.client.Select("inbox")
193 tc2.readprefixline("+ ")
194 done := make(chan error)
199 done <- fmt.Errorf("%v", x)
202 untagged, _ := tc2.client.ReadUntagged()
203 var exists imapclient.UntaggedExists
204 tuntagged(tc2.t, untagged, &exists)
205 tc2.writelinef("done")
210 // Should not cause idle to return.
211 tc.transactf("ok", `setmetadata inbox (/private/a "y")`)
213 tc.transactf("ok", "append inbox {4+}\r\ntest")
215 timer := time.NewTimer(time.Second)
219 tc.check(err, "idle")
221 t.Fatalf("idle did not finish")
224 // Broadcast should happen when metadata capability is enabled.
225 tc2.client.Enable(string(imapclient.CapMetadata))
227 tc2.readprefixline("+ ")
228 done = make(chan error)
233 done <- fmt.Errorf("%v", x)
236 untagged, _ := tc2.client.ReadUntagged()
237 var metadataKeys imapclient.UntaggedMetadataKeys
238 tuntagged(tc2.t, untagged, &metadataKeys)
239 tc2.writelinef("done")
244 // Should cause idle to return.
245 tc.transactf("ok", `setmetadata inbox (/private/a "z")`)
247 timer = time.NewTimer(time.Second)
251 tc.check(err, "idle")
253 t.Fatalf("idle did not finish")
257func TestMetadataLimit(t *testing.T) {
261 tc.client.Login("mjl@mox.example", password0)
263 maxKeys, maxSize := metadataMaxKeys, metadataMaxSize
265 metadataMaxKeys = maxKeys
266 metadataMaxSize = maxSize
269 metadataMaxSize = 1000
271 // Reach max total size limit.
272 buf := make([]byte, metadataMaxSize+1)
276 tc.cmdf("", "setmetadata inbox (/private/large ~{%d+}", len(buf))
278 tc.client.Writelinef(")")
280 tc.xcodeArg(imapclient.CodeOther{Code: "METADATA", Args: []string{"MAXSIZE", fmt.Sprintf("%d", metadataMaxSize)}})
282 // Reach limit for max number.
283 for i := 1; i <= metadataMaxKeys; i++ {
284 tc.transactf("ok", `setmetadata inbox (/private/key%d "test")`, i)
286 tc.transactf("no", `setmetadata inbox (/private/toomany "test")`)
287 tc.xcodeArg(imapclient.CodeOther{Code: "METADATA", Args: []string{"TOOMANY"}})