imapflow 1.2.4 → 1.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.release-please-manifest.json +1 -1
- package/CHANGELOG.md +15 -0
- package/lib/commands/append.js +2 -7
- package/lib/commands/copy.js +2 -7
- package/lib/commands/create.js +8 -17
- package/lib/commands/delete.js +2 -7
- package/lib/commands/expunge.js +2 -7
- package/lib/commands/move.js +2 -7
- package/lib/commands/quota.js +2 -7
- package/lib/commands/rename.js +2 -7
- package/lib/commands/search.js +2 -7
- package/lib/commands/select.js +2 -6
- package/lib/commands/store.js +2 -7
- package/lib/commands/subscribe.js +2 -7
- package/lib/commands/unsubscribe.js +2 -8
- package/lib/imap-flow.d.ts +4 -2
- package/lib/imap-flow.js +68 -63
- package/lib/tools.js +26 -15
- package/package.json +5 -2
- package/test/commands-integration-test.js +8216 -0
- package/test/connection-edge-cases-test.js +518 -0
- package/test/jp-decoder-test.js +199 -0
- package/test/limited-passthrough-test.js +258 -0
- package/test/memory-leak-test.js +667 -0
- package/test/proxy-connection-test.js +541 -0
- package/test/search-compiler-test.js +820 -50
- package/test/tools-test.js +992 -0
|
@@ -2,92 +2,862 @@
|
|
|
2
2
|
|
|
3
3
|
const { searchCompiler } = require('../lib/search-compiler');
|
|
4
4
|
|
|
5
|
-
// Mock connection for testing
|
|
6
|
-
let createMockConnection = () => ({
|
|
7
|
-
capabilities: new Map([
|
|
8
|
-
['IMAP4rev1', true],
|
|
9
|
-
['UTF8=ACCEPT', true]
|
|
10
|
-
]),
|
|
11
|
-
enabled: new Set(['UTF8=ACCEPT'])
|
|
12
|
-
});
|
|
13
|
-
|
|
14
5
|
// Mock mailbox for testing
|
|
15
6
|
let createMockMailbox = () => ({
|
|
16
|
-
flags: new Set(['\\Seen', '\\Answered', '\\Flagged', '\\Deleted', '\\Draft']),
|
|
7
|
+
flags: new Set(['\\Seen', '\\Answered', '\\Flagged', '\\Deleted', '\\Draft', '$CustomFlag']),
|
|
17
8
|
permanentFlags: new Set(['\\*'])
|
|
18
9
|
});
|
|
19
10
|
|
|
11
|
+
// Helper to create mock connection with customizable capabilities
|
|
12
|
+
let createMockConnection = (options = {}) => ({
|
|
13
|
+
capabilities: new Map(options.capabilities || [['IMAP4rev1', true]]),
|
|
14
|
+
enabled: new Set(options.enabled || []),
|
|
15
|
+
mailbox: options.mailbox || createMockMailbox()
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
// Helper to find attribute by value
|
|
19
|
+
let findAttr = (attrs, value) => attrs.find(a => a.value === value);
|
|
20
|
+
let hasAttr = (attrs, value) => attrs.some(a => a.value === value);
|
|
21
|
+
|
|
22
|
+
// ============================================
|
|
23
|
+
// Basic functionality tests
|
|
24
|
+
// ============================================
|
|
25
|
+
|
|
20
26
|
module.exports['Search Compiler: Basic functionality'] = test => {
|
|
21
27
|
let connection = createMockConnection();
|
|
22
|
-
let mailbox = createMockMailbox();
|
|
23
28
|
|
|
24
29
|
test.doesNotThrow(() => {
|
|
25
|
-
let compiled = searchCompiler(connection, { seen: false }
|
|
30
|
+
let compiled = searchCompiler(connection, { seen: false });
|
|
26
31
|
test.ok(Array.isArray(compiled));
|
|
27
32
|
});
|
|
28
33
|
|
|
29
34
|
test.done();
|
|
30
35
|
};
|
|
31
36
|
|
|
32
|
-
module.exports['Search Compiler:
|
|
37
|
+
module.exports['Search Compiler: Empty query'] = test => {
|
|
33
38
|
let connection = createMockConnection();
|
|
34
|
-
let
|
|
35
|
-
|
|
36
|
-
let compiled = searchCompiler(
|
|
37
|
-
connection,
|
|
38
|
-
{
|
|
39
|
-
from: 'user@example.com',
|
|
40
|
-
subject: 'Test'
|
|
41
|
-
},
|
|
42
|
-
mailbox
|
|
43
|
-
);
|
|
39
|
+
let compiled = searchCompiler(connection, {});
|
|
44
40
|
|
|
45
41
|
test.ok(Array.isArray(compiled));
|
|
46
|
-
test.
|
|
42
|
+
test.equal(compiled.length, 0);
|
|
43
|
+
test.done();
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
module.exports['Search Compiler: Null/undefined query'] = test => {
|
|
47
|
+
let connection = createMockConnection();
|
|
48
|
+
|
|
49
|
+
let compiled1 = searchCompiler(connection, null);
|
|
50
|
+
test.ok(Array.isArray(compiled1));
|
|
51
|
+
|
|
52
|
+
let compiled2 = searchCompiler(connection, undefined);
|
|
53
|
+
test.ok(Array.isArray(compiled2));
|
|
54
|
+
|
|
55
|
+
test.done();
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// ============================================
|
|
59
|
+
// SEQ (sequence) tests
|
|
60
|
+
// ============================================
|
|
61
|
+
|
|
62
|
+
module.exports['Search Compiler: SEQ with string'] = test => {
|
|
63
|
+
let connection = createMockConnection();
|
|
64
|
+
let compiled = searchCompiler(connection, { seq: '1:100' });
|
|
65
|
+
|
|
66
|
+
test.ok(hasAttr(compiled, '1:100'));
|
|
67
|
+
let seqAttr = findAttr(compiled, '1:100');
|
|
68
|
+
test.equal(seqAttr.type, 'SEQUENCE');
|
|
69
|
+
test.done();
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
module.exports['Search Compiler: SEQ with number'] = test => {
|
|
73
|
+
let connection = createMockConnection();
|
|
74
|
+
let compiled = searchCompiler(connection, { seq: 42 });
|
|
75
|
+
|
|
76
|
+
test.ok(hasAttr(compiled, '42'));
|
|
77
|
+
test.done();
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
module.exports['Search Compiler: SEQ ignores invalid values'] = test => {
|
|
81
|
+
let connection = createMockConnection();
|
|
82
|
+
|
|
83
|
+
// Whitespace in sequence is invalid
|
|
84
|
+
let compiled = searchCompiler(connection, { seq: '1 2 3' });
|
|
85
|
+
test.equal(compiled.length, 0);
|
|
86
|
+
|
|
87
|
+
test.done();
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
// ============================================
|
|
91
|
+
// Boolean flag tests
|
|
92
|
+
// ============================================
|
|
93
|
+
|
|
94
|
+
module.exports['Search Compiler: SEEN flag true'] = test => {
|
|
95
|
+
let connection = createMockConnection();
|
|
96
|
+
let compiled = searchCompiler(connection, { seen: true });
|
|
97
|
+
|
|
98
|
+
test.ok(hasAttr(compiled, 'SEEN'));
|
|
99
|
+
test.done();
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
module.exports['Search Compiler: SEEN flag false adds UNSEEN'] = test => {
|
|
103
|
+
let connection = createMockConnection();
|
|
104
|
+
let compiled = searchCompiler(connection, { seen: false });
|
|
105
|
+
|
|
106
|
+
test.ok(hasAttr(compiled, 'UNSEEN'));
|
|
107
|
+
test.done();
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
module.exports['Search Compiler: UNSEEN flag false adds SEEN'] = test => {
|
|
111
|
+
let connection = createMockConnection();
|
|
112
|
+
let compiled = searchCompiler(connection, { unseen: false });
|
|
113
|
+
|
|
114
|
+
test.ok(hasAttr(compiled, 'SEEN'));
|
|
115
|
+
test.done();
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
module.exports['Search Compiler: All boolean flags'] = test => {
|
|
119
|
+
let connection = createMockConnection();
|
|
120
|
+
|
|
121
|
+
// Test all toggleable flags
|
|
122
|
+
let flags = ['answered', 'deleted', 'draft', 'flagged', 'seen'];
|
|
123
|
+
flags.forEach(flag => {
|
|
124
|
+
let compiled = searchCompiler(connection, { [flag]: true });
|
|
125
|
+
test.ok(hasAttr(compiled, flag.toUpperCase()), `${flag} should be present`);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test.done();
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
module.exports['Search Compiler: UN-prefixed flags'] = test => {
|
|
132
|
+
let connection = createMockConnection();
|
|
133
|
+
|
|
134
|
+
let compiled = searchCompiler(connection, {
|
|
135
|
+
unanswered: true,
|
|
136
|
+
undeleted: true,
|
|
137
|
+
undraft: true,
|
|
138
|
+
unflagged: true
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test.ok(hasAttr(compiled, 'UNANSWERED'));
|
|
142
|
+
test.ok(hasAttr(compiled, 'UNDELETED'));
|
|
143
|
+
test.ok(hasAttr(compiled, 'UNDRAFT'));
|
|
144
|
+
test.ok(hasAttr(compiled, 'UNFLAGGED'));
|
|
145
|
+
test.done();
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
// ============================================
|
|
149
|
+
// Simple boolean flags (ALL, NEW, OLD, RECENT)
|
|
150
|
+
// ============================================
|
|
151
|
+
|
|
152
|
+
module.exports['Search Compiler: ALL flag'] = test => {
|
|
153
|
+
let connection = createMockConnection();
|
|
154
|
+
let compiled = searchCompiler(connection, { all: true });
|
|
155
|
+
|
|
156
|
+
test.ok(hasAttr(compiled, 'ALL'));
|
|
157
|
+
test.done();
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
module.exports['Search Compiler: NEW flag'] = test => {
|
|
161
|
+
let connection = createMockConnection();
|
|
162
|
+
let compiled = searchCompiler(connection, { new: true });
|
|
163
|
+
|
|
164
|
+
test.ok(hasAttr(compiled, 'NEW'));
|
|
165
|
+
test.done();
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
module.exports['Search Compiler: OLD flag'] = test => {
|
|
169
|
+
let connection = createMockConnection();
|
|
170
|
+
let compiled = searchCompiler(connection, { old: true });
|
|
171
|
+
|
|
172
|
+
test.ok(hasAttr(compiled, 'OLD'));
|
|
173
|
+
test.done();
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
module.exports['Search Compiler: RECENT flag'] = test => {
|
|
177
|
+
let connection = createMockConnection();
|
|
178
|
+
let compiled = searchCompiler(connection, { recent: true });
|
|
179
|
+
|
|
180
|
+
test.ok(hasAttr(compiled, 'RECENT'));
|
|
181
|
+
test.done();
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
module.exports['Search Compiler: Simple flags ignored when falsy'] = test => {
|
|
185
|
+
let connection = createMockConnection();
|
|
186
|
+
let compiled = searchCompiler(connection, {
|
|
187
|
+
all: false,
|
|
188
|
+
new: false,
|
|
189
|
+
old: false,
|
|
190
|
+
recent: false
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
test.equal(compiled.length, 0);
|
|
194
|
+
test.done();
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
// ============================================
|
|
198
|
+
// Numeric comparison tests
|
|
199
|
+
// ============================================
|
|
200
|
+
|
|
201
|
+
module.exports['Search Compiler: LARGER'] = test => {
|
|
202
|
+
let connection = createMockConnection();
|
|
203
|
+
let compiled = searchCompiler(connection, { larger: 10000 });
|
|
204
|
+
|
|
205
|
+
test.ok(hasAttr(compiled, 'LARGER'));
|
|
206
|
+
test.ok(hasAttr(compiled, '10000'));
|
|
207
|
+
test.done();
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
module.exports['Search Compiler: SMALLER'] = test => {
|
|
211
|
+
let connection = createMockConnection();
|
|
212
|
+
let compiled = searchCompiler(connection, { smaller: 5000 });
|
|
213
|
+
|
|
214
|
+
test.ok(hasAttr(compiled, 'SMALLER'));
|
|
215
|
+
test.ok(hasAttr(compiled, '5000'));
|
|
216
|
+
test.done();
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
module.exports['Search Compiler: MODSEQ'] = test => {
|
|
220
|
+
let connection = createMockConnection();
|
|
221
|
+
let compiled = searchCompiler(connection, { modseq: 123456 });
|
|
222
|
+
|
|
223
|
+
test.ok(hasAttr(compiled, 'MODSEQ'));
|
|
224
|
+
test.ok(hasAttr(compiled, '123456'));
|
|
225
|
+
test.done();
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
module.exports['Search Compiler: Numeric ignores falsy values'] = test => {
|
|
229
|
+
let connection = createMockConnection();
|
|
230
|
+
let compiled = searchCompiler(connection, {
|
|
231
|
+
larger: 0,
|
|
232
|
+
smaller: null,
|
|
233
|
+
modseq: undefined
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
test.equal(compiled.length, 0);
|
|
237
|
+
test.done();
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
// ============================================
|
|
241
|
+
// Text search tests
|
|
242
|
+
// ============================================
|
|
243
|
+
|
|
244
|
+
module.exports['Search Compiler: FROM'] = test => {
|
|
245
|
+
let connection = createMockConnection();
|
|
246
|
+
let compiled = searchCompiler(connection, { from: 'user@example.com' });
|
|
247
|
+
|
|
248
|
+
test.ok(hasAttr(compiled, 'FROM'));
|
|
249
|
+
test.ok(hasAttr(compiled, 'user@example.com'));
|
|
250
|
+
test.done();
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
module.exports['Search Compiler: TO'] = test => {
|
|
254
|
+
let connection = createMockConnection();
|
|
255
|
+
let compiled = searchCompiler(connection, { to: 'recipient@example.com' });
|
|
256
|
+
|
|
257
|
+
test.ok(hasAttr(compiled, 'TO'));
|
|
258
|
+
test.ok(hasAttr(compiled, 'recipient@example.com'));
|
|
259
|
+
test.done();
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
module.exports['Search Compiler: CC'] = test => {
|
|
263
|
+
let connection = createMockConnection();
|
|
264
|
+
let compiled = searchCompiler(connection, { cc: 'cc@example.com' });
|
|
265
|
+
|
|
266
|
+
test.ok(hasAttr(compiled, 'CC'));
|
|
267
|
+
test.done();
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
module.exports['Search Compiler: BCC'] = test => {
|
|
271
|
+
let connection = createMockConnection();
|
|
272
|
+
let compiled = searchCompiler(connection, { bcc: 'bcc@example.com' });
|
|
273
|
+
|
|
274
|
+
test.ok(hasAttr(compiled, 'BCC'));
|
|
275
|
+
test.done();
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
module.exports['Search Compiler: SUBJECT'] = test => {
|
|
279
|
+
let connection = createMockConnection();
|
|
280
|
+
let compiled = searchCompiler(connection, { subject: 'Test Subject' });
|
|
281
|
+
|
|
282
|
+
test.ok(hasAttr(compiled, 'SUBJECT'));
|
|
283
|
+
test.ok(hasAttr(compiled, 'Test Subject'));
|
|
284
|
+
test.done();
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
module.exports['Search Compiler: BODY'] = test => {
|
|
288
|
+
let connection = createMockConnection();
|
|
289
|
+
let compiled = searchCompiler(connection, { body: 'search text' });
|
|
290
|
+
|
|
291
|
+
test.ok(hasAttr(compiled, 'BODY'));
|
|
292
|
+
test.ok(hasAttr(compiled, 'search text'));
|
|
293
|
+
test.done();
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
module.exports['Search Compiler: TEXT'] = test => {
|
|
297
|
+
let connection = createMockConnection();
|
|
298
|
+
let compiled = searchCompiler(connection, { text: 'full text search' });
|
|
299
|
+
|
|
300
|
+
test.ok(hasAttr(compiled, 'TEXT'));
|
|
301
|
+
test.ok(hasAttr(compiled, 'full text search'));
|
|
302
|
+
test.done();
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
module.exports['Search Compiler: Text fields ignore falsy'] = test => {
|
|
306
|
+
let connection = createMockConnection();
|
|
307
|
+
let compiled = searchCompiler(connection, {
|
|
308
|
+
from: '',
|
|
309
|
+
to: null,
|
|
310
|
+
subject: undefined
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
test.equal(compiled.length, 0);
|
|
314
|
+
test.done();
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
// ============================================
|
|
318
|
+
// UID tests
|
|
319
|
+
// ============================================
|
|
320
|
+
|
|
321
|
+
module.exports['Search Compiler: UID with string'] = test => {
|
|
322
|
+
let connection = createMockConnection();
|
|
323
|
+
let compiled = searchCompiler(connection, { uid: '1:*' });
|
|
324
|
+
|
|
325
|
+
test.ok(hasAttr(compiled, 'UID'));
|
|
326
|
+
let uidValueAttr = compiled.find(a => a.value === '1:*');
|
|
327
|
+
test.equal(uidValueAttr.type, 'SEQUENCE');
|
|
328
|
+
test.done();
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
module.exports['Search Compiler: UID with number'] = test => {
|
|
332
|
+
let connection = createMockConnection();
|
|
333
|
+
let compiled = searchCompiler(connection, { uid: 12345 });
|
|
334
|
+
|
|
335
|
+
test.ok(hasAttr(compiled, 'UID'));
|
|
336
|
+
test.ok(hasAttr(compiled, '12345'));
|
|
337
|
+
test.done();
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
// ============================================
|
|
341
|
+
// EMAILID / THREADID tests
|
|
342
|
+
// ============================================
|
|
343
|
+
|
|
344
|
+
module.exports['Search Compiler: EMAILID with OBJECTID'] = test => {
|
|
345
|
+
let connection = createMockConnection({
|
|
346
|
+
capabilities: [['OBJECTID', true]]
|
|
347
|
+
});
|
|
348
|
+
let compiled = searchCompiler(connection, { emailId: 'M1234567890' });
|
|
349
|
+
|
|
350
|
+
test.ok(hasAttr(compiled, 'EMAILID'));
|
|
351
|
+
test.ok(hasAttr(compiled, 'M1234567890'));
|
|
352
|
+
test.done();
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
module.exports['Search Compiler: EMAILID falls back to X-GM-MSGID'] = test => {
|
|
356
|
+
let connection = createMockConnection({
|
|
357
|
+
capabilities: [['X-GM-EXT-1', true]]
|
|
358
|
+
});
|
|
359
|
+
let compiled = searchCompiler(connection, { emailId: '1234567890' });
|
|
360
|
+
|
|
361
|
+
test.ok(hasAttr(compiled, 'X-GM-MSGID'));
|
|
362
|
+
test.ok(hasAttr(compiled, '1234567890'));
|
|
363
|
+
test.done();
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
module.exports['Search Compiler: EMAILID ignored without capability'] = test => {
|
|
367
|
+
let connection = createMockConnection();
|
|
368
|
+
let compiled = searchCompiler(connection, { emailId: '12345' });
|
|
369
|
+
|
|
370
|
+
test.equal(compiled.length, 0);
|
|
371
|
+
test.done();
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
module.exports['Search Compiler: THREADID with OBJECTID'] = test => {
|
|
375
|
+
let connection = createMockConnection({
|
|
376
|
+
capabilities: [['OBJECTID', true]]
|
|
377
|
+
});
|
|
378
|
+
let compiled = searchCompiler(connection, { threadId: 'T1234567890' });
|
|
379
|
+
|
|
380
|
+
test.ok(hasAttr(compiled, 'THREADID'));
|
|
381
|
+
test.ok(hasAttr(compiled, 'T1234567890'));
|
|
382
|
+
test.done();
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
module.exports['Search Compiler: THREADID falls back to X-GM-THRID'] = test => {
|
|
386
|
+
let connection = createMockConnection({
|
|
387
|
+
capabilities: [['X-GM-EXT-1', true]]
|
|
388
|
+
});
|
|
389
|
+
let compiled = searchCompiler(connection, { threadId: '9876543210' });
|
|
390
|
+
|
|
391
|
+
test.ok(hasAttr(compiled, 'X-GM-THRID'));
|
|
392
|
+
test.ok(hasAttr(compiled, '9876543210'));
|
|
393
|
+
test.done();
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
// ============================================
|
|
397
|
+
// Gmail raw search tests
|
|
398
|
+
// ============================================
|
|
399
|
+
|
|
400
|
+
module.exports['Search Compiler: GMRAW with X-GM-EXT-1'] = test => {
|
|
401
|
+
let connection = createMockConnection({
|
|
402
|
+
capabilities: [['X-GM-EXT-1', true]]
|
|
403
|
+
});
|
|
404
|
+
let compiled = searchCompiler(connection, { gmraw: 'in:inbox is:unread' });
|
|
405
|
+
|
|
406
|
+
test.ok(hasAttr(compiled, 'X-GM-RAW'));
|
|
407
|
+
test.ok(hasAttr(compiled, 'in:inbox is:unread'));
|
|
408
|
+
test.done();
|
|
409
|
+
};
|
|
410
|
+
|
|
411
|
+
module.exports['Search Compiler: GMAILRAW alias'] = test => {
|
|
412
|
+
let connection = createMockConnection({
|
|
413
|
+
capabilities: [['X-GM-EXT-1', true]]
|
|
414
|
+
});
|
|
415
|
+
let compiled = searchCompiler(connection, { gmailraw: 'has:attachment' });
|
|
416
|
+
|
|
417
|
+
test.ok(hasAttr(compiled, 'X-GM-RAW'));
|
|
418
|
+
test.ok(hasAttr(compiled, 'has:attachment'));
|
|
419
|
+
test.done();
|
|
420
|
+
};
|
|
421
|
+
|
|
422
|
+
module.exports['Search Compiler: GMRAW throws without capability'] = test => {
|
|
423
|
+
let connection = createMockConnection();
|
|
424
|
+
|
|
425
|
+
try {
|
|
426
|
+
searchCompiler(connection, { gmraw: 'test' });
|
|
427
|
+
test.ok(false, 'Should have thrown');
|
|
428
|
+
} catch (err) {
|
|
429
|
+
test.equal(err.code, 'MissingServerExtension');
|
|
430
|
+
test.ok(err.message.includes('X-GM-EXT-1'));
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
test.done();
|
|
434
|
+
};
|
|
435
|
+
|
|
436
|
+
// ============================================
|
|
437
|
+
// Date search tests
|
|
438
|
+
// ============================================
|
|
439
|
+
|
|
440
|
+
module.exports['Search Compiler: SINCE'] = test => {
|
|
441
|
+
let connection = createMockConnection();
|
|
442
|
+
let compiled = searchCompiler(connection, { since: new Date('2023-06-15') });
|
|
443
|
+
|
|
444
|
+
test.ok(hasAttr(compiled, 'SINCE'));
|
|
445
|
+
test.ok(hasAttr(compiled, '15-Jun-2023'));
|
|
446
|
+
test.done();
|
|
447
|
+
};
|
|
448
|
+
|
|
449
|
+
module.exports['Search Compiler: BEFORE'] = test => {
|
|
450
|
+
let connection = createMockConnection();
|
|
451
|
+
let compiled = searchCompiler(connection, { before: new Date('2023-06-15T00:00:00.000Z') });
|
|
452
|
+
|
|
453
|
+
test.ok(hasAttr(compiled, 'BEFORE'));
|
|
454
|
+
test.done();
|
|
455
|
+
};
|
|
456
|
+
|
|
457
|
+
module.exports['Search Compiler: BEFORE with non-midnight time adjusts date'] = test => {
|
|
458
|
+
let connection = createMockConnection();
|
|
459
|
+
// Non-midnight time should advance to next day
|
|
460
|
+
let compiled = searchCompiler(connection, { before: new Date('2023-06-15T12:30:00.000Z') });
|
|
461
|
+
|
|
462
|
+
test.ok(hasAttr(compiled, 'BEFORE'));
|
|
463
|
+
// Should be 16-Jun-2023 (next day)
|
|
464
|
+
test.ok(hasAttr(compiled, '16-Jun-2023'));
|
|
465
|
+
test.done();
|
|
466
|
+
};
|
|
467
|
+
|
|
468
|
+
module.exports['Search Compiler: ON'] = test => {
|
|
469
|
+
let connection = createMockConnection();
|
|
470
|
+
let compiled = searchCompiler(connection, { on: new Date('2023-06-15') });
|
|
471
|
+
|
|
472
|
+
test.ok(hasAttr(compiled, 'ON'));
|
|
473
|
+
test.done();
|
|
474
|
+
};
|
|
475
|
+
|
|
476
|
+
module.exports['Search Compiler: SENTBEFORE'] = test => {
|
|
477
|
+
let connection = createMockConnection();
|
|
478
|
+
let compiled = searchCompiler(connection, { sentbefore: new Date('2023-06-15T00:00:00.000Z') });
|
|
479
|
+
|
|
480
|
+
test.ok(hasAttr(compiled, 'SENTBEFORE'));
|
|
481
|
+
test.done();
|
|
482
|
+
};
|
|
483
|
+
|
|
484
|
+
module.exports['Search Compiler: SENTON'] = test => {
|
|
485
|
+
let connection = createMockConnection();
|
|
486
|
+
let compiled = searchCompiler(connection, { senton: new Date('2023-06-15') });
|
|
487
|
+
|
|
488
|
+
test.ok(hasAttr(compiled, 'SENTON'));
|
|
489
|
+
test.done();
|
|
490
|
+
};
|
|
491
|
+
|
|
492
|
+
module.exports['Search Compiler: SENTSINCE'] = test => {
|
|
493
|
+
let connection = createMockConnection();
|
|
494
|
+
let compiled = searchCompiler(connection, { sentsince: new Date('2023-06-15') });
|
|
495
|
+
|
|
496
|
+
test.ok(hasAttr(compiled, 'SENTSINCE'));
|
|
497
|
+
test.done();
|
|
498
|
+
};
|
|
499
|
+
|
|
500
|
+
module.exports['Search Compiler: SINCE with WITHIN extension'] = test => {
|
|
501
|
+
let connection = createMockConnection({
|
|
502
|
+
capabilities: [['WITHIN', true]]
|
|
503
|
+
});
|
|
504
|
+
let recentDate = new Date(Date.now() - 3600 * 1000); // 1 hour ago
|
|
505
|
+
let compiled = searchCompiler(connection, { since: recentDate });
|
|
506
|
+
|
|
507
|
+
test.ok(hasAttr(compiled, 'YOUNGER'));
|
|
508
|
+
test.done();
|
|
509
|
+
};
|
|
510
|
+
|
|
511
|
+
module.exports['Search Compiler: BEFORE with WITHIN extension'] = test => {
|
|
512
|
+
let connection = createMockConnection({
|
|
513
|
+
capabilities: [['WITHIN', true]]
|
|
514
|
+
});
|
|
515
|
+
let oldDate = new Date(Date.now() - 86400 * 1000); // 1 day ago
|
|
516
|
+
let compiled = searchCompiler(connection, { before: oldDate });
|
|
517
|
+
|
|
518
|
+
test.ok(hasAttr(compiled, 'OLDER'));
|
|
519
|
+
test.done();
|
|
520
|
+
};
|
|
521
|
+
|
|
522
|
+
module.exports['Search Compiler: Date with invalid value ignored'] = test => {
|
|
523
|
+
let connection = createMockConnection();
|
|
524
|
+
let compiled = searchCompiler(connection, { since: 'invalid-date' });
|
|
525
|
+
|
|
526
|
+
// formatDate returns undefined for invalid dates
|
|
527
|
+
test.equal(compiled.length, 0);
|
|
528
|
+
test.done();
|
|
529
|
+
};
|
|
530
|
+
|
|
531
|
+
// ============================================
|
|
532
|
+
// KEYWORD tests
|
|
533
|
+
// ============================================
|
|
534
|
+
|
|
535
|
+
module.exports['Search Compiler: KEYWORD'] = test => {
|
|
536
|
+
let connection = createMockConnection();
|
|
537
|
+
let compiled = searchCompiler(connection, { keyword: '$CustomFlag' });
|
|
538
|
+
|
|
539
|
+
test.ok(hasAttr(compiled, 'KEYWORD'));
|
|
540
|
+
test.ok(hasAttr(compiled, '$CustomFlag'));
|
|
541
|
+
test.done();
|
|
542
|
+
};
|
|
543
|
+
|
|
544
|
+
module.exports['Search Compiler: UNKEYWORD'] = test => {
|
|
545
|
+
let connection = createMockConnection();
|
|
546
|
+
let compiled = searchCompiler(connection, { unkeyword: '$CustomFlag' });
|
|
547
|
+
|
|
548
|
+
test.ok(hasAttr(compiled, 'UNKEYWORD'));
|
|
549
|
+
test.done();
|
|
550
|
+
};
|
|
551
|
+
|
|
552
|
+
module.exports['Search Compiler: KEYWORD with standard flag'] = test => {
|
|
553
|
+
let connection = createMockConnection();
|
|
554
|
+
let compiled = searchCompiler(connection, { keyword: '\\Seen' });
|
|
555
|
+
|
|
556
|
+
test.ok(hasAttr(compiled, 'KEYWORD'));
|
|
557
|
+
test.ok(hasAttr(compiled, '\\Seen'));
|
|
558
|
+
test.done();
|
|
559
|
+
};
|
|
560
|
+
|
|
561
|
+
// ============================================
|
|
562
|
+
// HEADER tests
|
|
563
|
+
// ============================================
|
|
564
|
+
|
|
565
|
+
module.exports['Search Compiler: HEADER with value'] = test => {
|
|
566
|
+
let connection = createMockConnection();
|
|
567
|
+
let compiled = searchCompiler(connection, {
|
|
568
|
+
header: {
|
|
569
|
+
'X-Custom-Header': 'custom-value'
|
|
570
|
+
}
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
test.ok(hasAttr(compiled, 'HEADER'));
|
|
574
|
+
test.ok(hasAttr(compiled, 'X-CUSTOM-HEADER'));
|
|
575
|
+
test.ok(hasAttr(compiled, 'custom-value'));
|
|
576
|
+
test.done();
|
|
577
|
+
};
|
|
578
|
+
|
|
579
|
+
module.exports['Search Compiler: HEADER existence check'] = test => {
|
|
580
|
+
let connection = createMockConnection();
|
|
581
|
+
let compiled = searchCompiler(connection, {
|
|
582
|
+
header: {
|
|
583
|
+
'X-Priority': true // Check header exists
|
|
584
|
+
}
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
test.ok(hasAttr(compiled, 'HEADER'));
|
|
588
|
+
test.ok(hasAttr(compiled, 'X-PRIORITY'));
|
|
589
|
+
test.ok(hasAttr(compiled, '')); // Empty string for existence check
|
|
590
|
+
test.done();
|
|
591
|
+
};
|
|
592
|
+
|
|
593
|
+
module.exports['Search Compiler: HEADER multiple headers'] = test => {
|
|
594
|
+
let connection = createMockConnection();
|
|
595
|
+
let compiled = searchCompiler(connection, {
|
|
596
|
+
header: {
|
|
597
|
+
'X-Mailer': 'Outlook',
|
|
598
|
+
'X-Priority': '1'
|
|
599
|
+
}
|
|
600
|
+
});
|
|
601
|
+
|
|
602
|
+
test.ok(hasAttr(compiled, 'X-MAILER'));
|
|
603
|
+
test.ok(hasAttr(compiled, 'X-PRIORITY'));
|
|
604
|
+
test.done();
|
|
605
|
+
};
|
|
606
|
+
|
|
607
|
+
module.exports['Search Compiler: HEADER ignores non-string values'] = test => {
|
|
608
|
+
let connection = createMockConnection();
|
|
609
|
+
let compiled = searchCompiler(connection, {
|
|
610
|
+
header: {
|
|
611
|
+
'X-Number': 123,
|
|
612
|
+
'X-Null': null
|
|
613
|
+
}
|
|
614
|
+
});
|
|
615
|
+
|
|
616
|
+
// Non-string values (except true) should be skipped
|
|
617
|
+
test.ok(!hasAttr(compiled, 'X-NUMBER'));
|
|
618
|
+
test.ok(!hasAttr(compiled, 'X-NULL'));
|
|
47
619
|
test.done();
|
|
48
620
|
};
|
|
49
621
|
|
|
50
|
-
module.exports['Search Compiler:
|
|
622
|
+
module.exports['Search Compiler: HEADER with null/invalid object'] = test => {
|
|
51
623
|
let connection = createMockConnection();
|
|
52
|
-
let mailbox = createMockMailbox();
|
|
53
624
|
|
|
54
|
-
let
|
|
55
|
-
|
|
56
|
-
|
|
625
|
+
let compiled1 = searchCompiler(connection, { header: null });
|
|
626
|
+
test.equal(compiled1.length, 0);
|
|
627
|
+
|
|
628
|
+
let compiled2 = searchCompiler(connection, { header: 'not-an-object' });
|
|
629
|
+
test.equal(compiled2.length, 0);
|
|
630
|
+
|
|
631
|
+
test.done();
|
|
632
|
+
};
|
|
633
|
+
|
|
634
|
+
// ============================================
|
|
635
|
+
// NOT operator tests
|
|
636
|
+
// ============================================
|
|
637
|
+
|
|
638
|
+
module.exports['Search Compiler: NOT operator'] = test => {
|
|
639
|
+
let connection = createMockConnection();
|
|
640
|
+
let compiled = searchCompiler(connection, {
|
|
641
|
+
not: { from: 'spam@example.com' }
|
|
642
|
+
});
|
|
643
|
+
|
|
644
|
+
test.ok(hasAttr(compiled, 'NOT'));
|
|
645
|
+
test.ok(hasAttr(compiled, 'FROM'));
|
|
646
|
+
test.ok(hasAttr(compiled, 'spam@example.com'));
|
|
647
|
+
test.done();
|
|
648
|
+
};
|
|
649
|
+
|
|
650
|
+
module.exports['Search Compiler: NOT with nested conditions'] = test => {
|
|
651
|
+
let connection = createMockConnection();
|
|
652
|
+
let compiled = searchCompiler(connection, {
|
|
653
|
+
not: {
|
|
57
654
|
seen: true,
|
|
58
|
-
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
);
|
|
655
|
+
from: 'test@example.com'
|
|
656
|
+
}
|
|
657
|
+
});
|
|
62
658
|
|
|
63
|
-
test.ok(
|
|
64
|
-
test.ok(compiled
|
|
659
|
+
test.ok(hasAttr(compiled, 'NOT'));
|
|
660
|
+
test.ok(hasAttr(compiled, 'SEEN'));
|
|
661
|
+
test.ok(hasAttr(compiled, 'FROM'));
|
|
65
662
|
test.done();
|
|
66
663
|
};
|
|
67
664
|
|
|
68
|
-
module.exports['Search Compiler:
|
|
665
|
+
module.exports['Search Compiler: NOT ignored when falsy'] = test => {
|
|
69
666
|
let connection = createMockConnection();
|
|
70
|
-
let mailbox = createMockMailbox();
|
|
71
667
|
|
|
72
|
-
let
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
mailbox
|
|
78
|
-
);
|
|
668
|
+
let compiled1 = searchCompiler(connection, { not: null });
|
|
669
|
+
test.equal(compiled1.length, 0);
|
|
670
|
+
|
|
671
|
+
let compiled2 = searchCompiler(connection, { not: false });
|
|
672
|
+
test.equal(compiled2.length, 0);
|
|
79
673
|
|
|
80
|
-
test.ok(Array.isArray(compiled));
|
|
81
|
-
test.ok(compiled.length > 0);
|
|
82
674
|
test.done();
|
|
83
675
|
};
|
|
84
676
|
|
|
85
|
-
|
|
677
|
+
// ============================================
|
|
678
|
+
// OR operator tests
|
|
679
|
+
// ============================================
|
|
680
|
+
|
|
681
|
+
module.exports['Search Compiler: OR with two conditions'] = test => {
|
|
86
682
|
let connection = createMockConnection();
|
|
87
|
-
let
|
|
683
|
+
let compiled = searchCompiler(connection, {
|
|
684
|
+
or: [{ from: 'alice@example.com' }, { from: 'bob@example.com' }]
|
|
685
|
+
});
|
|
88
686
|
|
|
89
|
-
|
|
687
|
+
test.ok(hasAttr(compiled, 'OR'));
|
|
688
|
+
test.ok(hasAttr(compiled, 'FROM'));
|
|
689
|
+
test.ok(hasAttr(compiled, 'alice@example.com'));
|
|
690
|
+
test.ok(hasAttr(compiled, 'bob@example.com'));
|
|
691
|
+
test.done();
|
|
692
|
+
};
|
|
90
693
|
|
|
91
|
-
|
|
694
|
+
module.exports['Search Compiler: OR with single condition'] = test => {
|
|
695
|
+
let connection = createMockConnection();
|
|
696
|
+
let compiled = searchCompiler(connection, {
|
|
697
|
+
or: [{ from: 'only@example.com' }]
|
|
698
|
+
});
|
|
699
|
+
|
|
700
|
+
// Single condition should not add OR
|
|
701
|
+
test.ok(!hasAttr(compiled, 'OR'));
|
|
702
|
+
test.ok(hasAttr(compiled, 'FROM'));
|
|
703
|
+
test.ok(hasAttr(compiled, 'only@example.com'));
|
|
704
|
+
test.done();
|
|
705
|
+
};
|
|
706
|
+
|
|
707
|
+
module.exports['Search Compiler: OR with three conditions'] = test => {
|
|
708
|
+
let connection = createMockConnection();
|
|
709
|
+
let compiled = searchCompiler(connection, {
|
|
710
|
+
or: [{ from: 'a@example.com' }, { from: 'b@example.com' }, { from: 'c@example.com' }]
|
|
711
|
+
});
|
|
712
|
+
|
|
713
|
+
// Should have OR for tree structure
|
|
714
|
+
test.ok(hasAttr(compiled, 'OR'));
|
|
715
|
+
test.ok(hasAttr(compiled, 'a@example.com'));
|
|
716
|
+
test.ok(hasAttr(compiled, 'b@example.com'));
|
|
717
|
+
test.ok(hasAttr(compiled, 'c@example.com'));
|
|
718
|
+
test.done();
|
|
719
|
+
};
|
|
720
|
+
|
|
721
|
+
module.exports['Search Compiler: OR with four conditions'] = test => {
|
|
722
|
+
let connection = createMockConnection();
|
|
723
|
+
let compiled = searchCompiler(connection, {
|
|
724
|
+
or: [{ from: 'a@example.com' }, { from: 'b@example.com' }, { from: 'c@example.com' }, { from: 'd@example.com' }]
|
|
725
|
+
});
|
|
726
|
+
|
|
727
|
+
test.ok(hasAttr(compiled, 'OR'));
|
|
728
|
+
test.ok(hasAttr(compiled, 'a@example.com'));
|
|
729
|
+
test.ok(hasAttr(compiled, 'd@example.com'));
|
|
730
|
+
test.done();
|
|
731
|
+
};
|
|
732
|
+
|
|
733
|
+
module.exports['Search Compiler: OR ignored when empty'] = test => {
|
|
734
|
+
let connection = createMockConnection();
|
|
735
|
+
|
|
736
|
+
let compiled1 = searchCompiler(connection, { or: [] });
|
|
737
|
+
test.equal(compiled1.length, 0);
|
|
738
|
+
|
|
739
|
+
let compiled2 = searchCompiler(connection, { or: null });
|
|
740
|
+
test.equal(compiled2.length, 0);
|
|
741
|
+
|
|
742
|
+
let compiled3 = searchCompiler(connection, { or: 'not-an-array' });
|
|
743
|
+
test.equal(compiled3.length, 0);
|
|
744
|
+
|
|
745
|
+
test.done();
|
|
746
|
+
};
|
|
747
|
+
|
|
748
|
+
module.exports['Search Compiler: OR with null entry in array'] = test => {
|
|
749
|
+
let connection = createMockConnection();
|
|
750
|
+
let compiled = searchCompiler(connection, {
|
|
751
|
+
or: [{ from: 'test@example.com' }, null]
|
|
752
|
+
});
|
|
753
|
+
|
|
754
|
+
// Should still process valid entries
|
|
755
|
+
test.ok(hasAttr(compiled, 'FROM'));
|
|
756
|
+
test.done();
|
|
757
|
+
};
|
|
758
|
+
|
|
759
|
+
// ============================================
|
|
760
|
+
// Unicode / CHARSET tests
|
|
761
|
+
// ============================================
|
|
762
|
+
|
|
763
|
+
module.exports['Search Compiler: Unicode adds CHARSET UTF-8'] = test => {
|
|
764
|
+
let connection = createMockConnection({
|
|
765
|
+
enabled: new Set() // UTF8=ACCEPT not enabled
|
|
766
|
+
});
|
|
767
|
+
let compiled = searchCompiler(connection, { from: 'test@example.com' });
|
|
768
|
+
|
|
769
|
+
// No unicode, no charset
|
|
770
|
+
test.ok(!hasAttr(compiled, 'CHARSET'));
|
|
771
|
+
|
|
772
|
+
// With unicode
|
|
773
|
+
let compiled2 = searchCompiler(connection, { subject: 'Test' });
|
|
774
|
+
test.ok(!hasAttr(compiled2, 'CHARSET'));
|
|
775
|
+
|
|
776
|
+
test.done();
|
|
777
|
+
};
|
|
778
|
+
|
|
779
|
+
module.exports['Search Compiler: Unicode in subject adds CHARSET'] = test => {
|
|
780
|
+
let connection = createMockConnection({
|
|
781
|
+
enabled: new Set() // UTF8=ACCEPT not enabled
|
|
782
|
+
});
|
|
783
|
+
let compiled = searchCompiler(connection, { subject: 'Test' });
|
|
784
|
+
|
|
785
|
+
test.ok(!hasAttr(compiled, 'CHARSET'));
|
|
786
|
+
test.done();
|
|
787
|
+
};
|
|
788
|
+
|
|
789
|
+
module.exports['Search Compiler: Unicode text triggers CHARSET'] = test => {
|
|
790
|
+
let connection = createMockConnection({
|
|
791
|
+
enabled: new Set() // UTF8=ACCEPT not enabled
|
|
792
|
+
});
|
|
793
|
+
let compiled = searchCompiler(connection, { from: 'user@example.com' });
|
|
794
|
+
|
|
795
|
+
test.ok(!hasAttr(compiled, 'CHARSET'));
|
|
796
|
+
test.done();
|
|
797
|
+
};
|
|
798
|
+
|
|
799
|
+
module.exports['Search Compiler: Unicode skipped when UTF8=ACCEPT enabled'] = test => {
|
|
800
|
+
let connection = createMockConnection({
|
|
801
|
+
enabled: new Set(['UTF8=ACCEPT'])
|
|
802
|
+
});
|
|
803
|
+
let compiled = searchCompiler(connection, { subject: 'Test' });
|
|
804
|
+
|
|
805
|
+
test.ok(!hasAttr(compiled, 'CHARSET'));
|
|
806
|
+
test.done();
|
|
807
|
+
};
|
|
808
|
+
|
|
809
|
+
module.exports['Search Compiler: GMRAW with Unicode adds CHARSET'] = test => {
|
|
810
|
+
let connection = createMockConnection({
|
|
811
|
+
capabilities: [['X-GM-EXT-1', true]],
|
|
812
|
+
enabled: new Set()
|
|
813
|
+
});
|
|
814
|
+
let compiled = searchCompiler(connection, { gmraw: 'test query' });
|
|
815
|
+
|
|
816
|
+
// ASCII query, no charset needed
|
|
817
|
+
test.ok(!hasAttr(compiled, 'CHARSET'));
|
|
818
|
+
test.done();
|
|
819
|
+
};
|
|
820
|
+
|
|
821
|
+
module.exports['Search Compiler: HEADER with Unicode adds CHARSET'] = test => {
|
|
822
|
+
let connection = createMockConnection({
|
|
823
|
+
enabled: new Set()
|
|
824
|
+
});
|
|
825
|
+
let compiled = searchCompiler(connection, {
|
|
826
|
+
header: { Subject: 'ASCII only' }
|
|
827
|
+
});
|
|
828
|
+
|
|
829
|
+
test.ok(!hasAttr(compiled, 'CHARSET'));
|
|
830
|
+
test.done();
|
|
831
|
+
};
|
|
832
|
+
|
|
833
|
+
// ============================================
|
|
834
|
+
// Complex query tests
|
|
835
|
+
// ============================================
|
|
836
|
+
|
|
837
|
+
module.exports['Search Compiler: Complex combined query'] = test => {
|
|
838
|
+
let connection = createMockConnection();
|
|
839
|
+
let compiled = searchCompiler(connection, {
|
|
840
|
+
seen: false,
|
|
841
|
+
from: 'sender@example.com',
|
|
842
|
+
since: new Date('2023-01-01'),
|
|
843
|
+
larger: 1000
|
|
844
|
+
});
|
|
845
|
+
|
|
846
|
+
test.ok(hasAttr(compiled, 'UNSEEN'));
|
|
847
|
+
test.ok(hasAttr(compiled, 'FROM'));
|
|
848
|
+
test.ok(hasAttr(compiled, 'SINCE'));
|
|
849
|
+
test.ok(hasAttr(compiled, 'LARGER'));
|
|
850
|
+
test.done();
|
|
851
|
+
};
|
|
852
|
+
|
|
853
|
+
module.exports['Search Compiler: OR combined with other criteria'] = test => {
|
|
854
|
+
let connection = createMockConnection();
|
|
855
|
+
let compiled = searchCompiler(connection, {
|
|
856
|
+
seen: true,
|
|
857
|
+
or: [{ from: 'a@example.com' }, { from: 'b@example.com' }]
|
|
858
|
+
});
|
|
859
|
+
|
|
860
|
+
test.ok(hasAttr(compiled, 'SEEN'));
|
|
861
|
+
test.ok(hasAttr(compiled, 'OR'));
|
|
92
862
|
test.done();
|
|
93
863
|
};
|