imapflow 1.2.3 → 1.2.5

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.
@@ -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 }, mailbox);
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: Text searches'] = test => {
37
+ module.exports['Search Compiler: Empty query'] = test => {
33
38
  let connection = createMockConnection();
34
- let mailbox = createMockMailbox();
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.ok(compiled.length > 0);
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: Flag searches'] = test => {
622
+ module.exports['Search Compiler: HEADER with null/invalid object'] = test => {
51
623
  let connection = createMockConnection();
52
- let mailbox = createMockMailbox();
53
624
 
54
- let compiled = searchCompiler(
55
- connection,
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
- answered: false
59
- },
60
- mailbox
61
- );
655
+ from: 'test@example.com'
656
+ }
657
+ });
62
658
 
63
- test.ok(Array.isArray(compiled));
64
- test.ok(compiled.length > 0);
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: Date searches'] = test => {
665
+ module.exports['Search Compiler: NOT ignored when falsy'] = test => {
69
666
  let connection = createMockConnection();
70
- let mailbox = createMockMailbox();
71
667
 
72
- let compiled = searchCompiler(
73
- connection,
74
- {
75
- since: new Date('2023-01-01')
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
- module.exports['Search Compiler: Empty query'] = test => {
677
+ // ============================================
678
+ // OR operator tests
679
+ // ============================================
680
+
681
+ module.exports['Search Compiler: OR with two conditions'] = test => {
86
682
  let connection = createMockConnection();
87
- let mailbox = createMockMailbox();
683
+ let compiled = searchCompiler(connection, {
684
+ or: [{ from: 'alice@example.com' }, { from: 'bob@example.com' }]
685
+ });
88
686
 
89
- let compiled = searchCompiler(connection, {}, mailbox);
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
- test.ok(Array.isArray(compiled));
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
  };