imapflow 1.3.6 → 1.3.7

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.
@@ -0,0 +1,738 @@
1
+ 'use strict';
2
+
3
+ // Unit tests that exercise ImapFlow public/command methods against a real
4
+ // ImapFlow instance with `run` (and occasionally lower-level helpers) stubbed.
5
+ // This deterministically covers the thin command wrappers, range resolution,
6
+ // the untagged response handlers, and the fetch/download orchestration without
7
+ // requiring a live IMAP server.
8
+
9
+ const { ImapFlow } = require('../lib/imap-flow');
10
+
11
+ // Build a real ImapFlow instance with logging disabled and a selected mailbox.
12
+ // `run` is stubbed by default to record calls and return a configurable value.
13
+ const makeClient = (overrides = {}) => {
14
+ let client = new ImapFlow({
15
+ host: 'imap.example.com',
16
+ port: 993,
17
+ auth: { user: 'test', pass: 'test' },
18
+ logger: false,
19
+ ...overrides
20
+ });
21
+ client.socket = { destroyed: false, destroy: () => {} };
22
+ client.usable = true;
23
+ client.mailbox = {
24
+ path: 'INBOX',
25
+ exists: 10,
26
+ flags: new Set(['\\Seen', '\\Flagged']),
27
+ permanentFlags: new Set(['\\*'])
28
+ };
29
+ client.state = client.states.SELECTED;
30
+ return client;
31
+ };
32
+
33
+ // Records every run() invocation so assertions can verify the dispatched command.
34
+ const recordRun = (client, returnValue) => {
35
+ let calls = [];
36
+ client.run = async (...args) => {
37
+ calls.push(args);
38
+ return typeof returnValue === 'function' ? returnValue(...args) : returnValue;
39
+ };
40
+ return calls;
41
+ };
42
+
43
+ // ============================================================================
44
+ // Thin command wrappers
45
+ // ============================================================================
46
+
47
+ module.exports['Methods: getQuota defaults path to INBOX'] = async test => {
48
+ let client = makeClient();
49
+ let calls = recordRun(client, { path: 'INBOX' });
50
+ let res = await client.getQuota();
51
+ test.deepEqual(calls[0], ['QUOTA', 'INBOX']);
52
+ test.deepEqual(res, { path: 'INBOX' });
53
+ test.done();
54
+ };
55
+
56
+ module.exports['Methods: getQuota uses given path'] = async test => {
57
+ let client = makeClient();
58
+ let calls = recordRun(client, false);
59
+ await client.getQuota('Archive');
60
+ test.deepEqual(calls[0], ['QUOTA', 'Archive']);
61
+ test.done();
62
+ };
63
+
64
+ module.exports['Methods: list builds folders map'] = async test => {
65
+ let client = makeClient();
66
+ recordRun(client, [
67
+ { path: 'INBOX', name: 'INBOX' },
68
+ { path: 'Sent', name: 'Sent' }
69
+ ]);
70
+ let folders = await client.list();
71
+ test.equal(folders.length, 2);
72
+ test.ok(client.folders instanceof Map);
73
+ test.equal(client.folders.get('Sent').name, 'Sent');
74
+ test.done();
75
+ };
76
+
77
+ module.exports['Methods: list passes options'] = async test => {
78
+ let client = makeClient();
79
+ let calls = recordRun(client, []);
80
+ await client.list({ statusQuery: { messages: true } });
81
+ test.deepEqual(calls[0][0], 'LIST');
82
+ test.deepEqual(calls[0][3], { statusQuery: { messages: true } });
83
+ test.done();
84
+ };
85
+
86
+ module.exports['Methods: listTree returns tree structure'] = async test => {
87
+ let client = makeClient();
88
+ recordRun(client, [
89
+ { path: 'INBOX', name: 'INBOX', delimiter: '/', parent: [], flags: new Set() },
90
+ { path: 'INBOX/Sub', name: 'Sub', delimiter: '/', parent: ['INBOX'], flags: new Set() }
91
+ ]);
92
+ let tree = await client.listTree();
93
+ test.ok(tree);
94
+ test.ok(Array.isArray(tree.folders));
95
+ test.ok(client.folders instanceof Map);
96
+ test.done();
97
+ };
98
+
99
+ module.exports['Methods: noop dispatches NOOP'] = async test => {
100
+ let client = makeClient();
101
+ let calls = recordRun(client, true);
102
+ await client.noop();
103
+ test.deepEqual(calls[0], ['NOOP']);
104
+ test.done();
105
+ };
106
+
107
+ module.exports['Methods: mailboxCreate dispatches CREATE'] = async test => {
108
+ let client = makeClient();
109
+ let calls = recordRun(client, { path: 'New', created: true });
110
+ let res = await client.mailboxCreate(['parent', 'child']);
111
+ test.deepEqual(calls[0], ['CREATE', ['parent', 'child']]);
112
+ test.equal(res.created, true);
113
+ test.done();
114
+ };
115
+
116
+ module.exports['Methods: mailboxRename dispatches RENAME'] = async test => {
117
+ let client = makeClient();
118
+ let calls = recordRun(client, { path: 'a', newPath: 'b' });
119
+ await client.mailboxRename('a', 'b');
120
+ test.deepEqual(calls[0], ['RENAME', 'a', 'b']);
121
+ test.done();
122
+ };
123
+
124
+ module.exports['Methods: mailboxDelete dispatches DELETE'] = async test => {
125
+ let client = makeClient();
126
+ let calls = recordRun(client, { path: 'a' });
127
+ await client.mailboxDelete('a');
128
+ test.deepEqual(calls[0], ['DELETE', 'a']);
129
+ test.done();
130
+ };
131
+
132
+ module.exports['Methods: mailboxSubscribe dispatches SUBSCRIBE'] = async test => {
133
+ let client = makeClient();
134
+ let calls = recordRun(client, true);
135
+ await client.mailboxSubscribe('a');
136
+ test.deepEqual(calls[0], ['SUBSCRIBE', 'a']);
137
+ test.done();
138
+ };
139
+
140
+ module.exports['Methods: mailboxUnsubscribe dispatches UNSUBSCRIBE'] = async test => {
141
+ let client = makeClient();
142
+ let calls = recordRun(client, true);
143
+ await client.mailboxUnsubscribe('a');
144
+ test.deepEqual(calls[0], ['UNSUBSCRIBE', 'a']);
145
+ test.done();
146
+ };
147
+
148
+ module.exports['Methods: mailboxOpen dispatches SELECT'] = async test => {
149
+ let client = makeClient();
150
+ let calls = recordRun(client, { path: 'INBOX' });
151
+ await client.mailboxOpen('INBOX', { readOnly: true });
152
+ test.deepEqual(calls[0], ['SELECT', 'INBOX', { readOnly: true }]);
153
+ test.done();
154
+ };
155
+
156
+ module.exports['Methods: mailboxClose dispatches CLOSE'] = async test => {
157
+ let client = makeClient();
158
+ let calls = recordRun(client, true);
159
+ await client.mailboxClose();
160
+ test.deepEqual(calls[0], ['CLOSE']);
161
+ test.done();
162
+ };
163
+
164
+ module.exports['Methods: status dispatches STATUS'] = async test => {
165
+ let client = makeClient();
166
+ let calls = recordRun(client, { path: 'INBOX', unseen: 3 });
167
+ let res = await client.status('INBOX', { unseen: true });
168
+ test.deepEqual(calls[0], ['STATUS', 'INBOX', { unseen: true }]);
169
+ test.equal(res.unseen, 3);
170
+ test.done();
171
+ };
172
+
173
+ // ============================================================================
174
+ // idle()
175
+ // ============================================================================
176
+
177
+ module.exports['Methods: idle runs IDLE when not already idling'] = async test => {
178
+ let client = makeClient();
179
+ let calls = recordRun(client, true);
180
+ await client.idle();
181
+ test.deepEqual(calls[0], ['IDLE', client.maxIdleTime]);
182
+ test.done();
183
+ };
184
+
185
+ module.exports['Methods: idle is a no-op when already idling'] = async test => {
186
+ let client = makeClient();
187
+ client.idling = true;
188
+ let calls = recordRun(client, true);
189
+ let res = await client.idle();
190
+ test.equal(calls.length, 0);
191
+ test.equal(res, undefined);
192
+ test.done();
193
+ };
194
+
195
+ // ============================================================================
196
+ // Flag operations + range resolution
197
+ // ============================================================================
198
+
199
+ module.exports['Methods: messageFlagsSet resolves range and dispatches STORE'] = async test => {
200
+ let client = makeClient();
201
+ let calls = recordRun(client, true);
202
+ let res = await client.messageFlagsSet('1:5', ['\\Seen'], { uid: true });
203
+ test.equal(res, true);
204
+ test.equal(calls[0][0], 'STORE');
205
+ test.equal(calls[0][1], '1:5');
206
+ test.deepEqual(calls[0][3], { operation: 'set', uid: true });
207
+ test.done();
208
+ };
209
+
210
+ module.exports['Methods: messageFlagsSet works without an options argument'] = async test => {
211
+ let client = makeClient();
212
+ let calls = recordRun(client, true);
213
+ let res = await client.messageFlagsSet('1', ['\\Seen']); // no options -> options || {}
214
+ test.equal(res, true);
215
+ test.equal(calls[0][0], 'STORE');
216
+ test.done();
217
+ };
218
+
219
+ module.exports['Methods: search derivation handles non-string returnOptions entries'] = async test => {
220
+ let client = makeClient();
221
+ recordRun(client, [1, 2, 3]); // no ESEARCH -> client-side derivation runs
222
+ // a non-string returnOption (object) is passed through untouched by the mapper
223
+ let res = await client.search({ all: true }, { returnOptions: ['count', { partial: '1:2' }] });
224
+ test.equal(res.count, 3);
225
+ test.done();
226
+ };
227
+
228
+ module.exports['Methods: messageFlagsSet returns false for empty range'] = async test => {
229
+ let client = makeClient();
230
+ let calls = recordRun(client, true);
231
+ let res = await client.messageFlagsSet([], ['\\Seen'], {});
232
+ test.equal(res, false);
233
+ test.equal(calls.length, 0);
234
+ test.done();
235
+ };
236
+
237
+ module.exports['Methods: messageFlagsAdd dispatches STORE add'] = async test => {
238
+ let client = makeClient();
239
+ let calls = recordRun(client, true);
240
+ await client.messageFlagsAdd('1', ['\\Flagged']);
241
+ test.equal(calls[0][3].operation, 'add');
242
+ test.done();
243
+ };
244
+
245
+ module.exports['Methods: messageFlagsAdd returns false on empty range'] = async test => {
246
+ let client = makeClient();
247
+ recordRun(client, true);
248
+ let res = await client.messageFlagsAdd([], ['\\Flagged']);
249
+ test.equal(res, false);
250
+ test.done();
251
+ };
252
+
253
+ module.exports['Methods: messageFlagsRemove dispatches STORE remove'] = async test => {
254
+ let client = makeClient();
255
+ let calls = recordRun(client, true);
256
+ await client.messageFlagsRemove('1', ['\\Flagged']);
257
+ test.equal(calls[0][3].operation, 'remove');
258
+ test.done();
259
+ };
260
+
261
+ module.exports['Methods: messageFlagsRemove returns false on empty range'] = async test => {
262
+ let client = makeClient();
263
+ recordRun(client, true);
264
+ let res = await client.messageFlagsRemove([], ['\\Flagged']);
265
+ test.equal(res, false);
266
+ test.done();
267
+ };
268
+
269
+ module.exports['Methods: setFlagColor returns false for empty range'] = async test => {
270
+ let client = makeClient();
271
+ recordRun(client, true);
272
+ let res = await client.setFlagColor([], 'red');
273
+ test.equal(res, false);
274
+ test.done();
275
+ };
276
+
277
+ module.exports['Methods: setFlagColor returns false for invalid color'] = async test => {
278
+ let client = makeClient();
279
+ recordRun(client, true);
280
+ let res = await client.setFlagColor('1', 'not-a-color');
281
+ test.equal(res, false);
282
+ test.done();
283
+ };
284
+
285
+ module.exports['Methods: setFlagColor red issues add and remove STORE'] = async test => {
286
+ let client = makeClient();
287
+ let calls = recordRun(client, true);
288
+ // red sets \\Flagged and clears the MailFlagBit* keywords -> both add and remove
289
+ let res = await client.setFlagColor('1:*', 'red');
290
+ test.ok(res);
291
+ let ops = calls.map(c => c[3].operation);
292
+ test.ok(ops.includes('add'));
293
+ test.ok(ops.includes('remove'));
294
+ test.done();
295
+ };
296
+
297
+ module.exports['Methods: setFlagColor removal (null color) issues only a remove STORE'] = async test => {
298
+ test.expect(5);
299
+ let client = makeClient();
300
+ let calls = recordRun(client, true);
301
+ // A null/empty color clears the color: getColorFlags returns an empty add set and a
302
+ // non-empty remove set, so the add STORE is skipped and only the remove STORE runs.
303
+ let res = await client.setFlagColor('1', null);
304
+ test.ok(res);
305
+ test.equal(calls.length, 1);
306
+ test.equal(calls[0][0], 'STORE');
307
+ test.equal(calls[0][3].operation, 'remove');
308
+ test.deepEqual(calls[0][2], ['\\Flagged', '$MailFlagBit0', '$MailFlagBit1', '$MailFlagBit2']);
309
+ test.done();
310
+ };
311
+
312
+ module.exports['Methods: messageDelete resolves range and dispatches EXPUNGE'] = async test => {
313
+ let client = makeClient();
314
+ let calls = recordRun(client, true);
315
+ let res = await client.messageDelete('1:3', { uid: true });
316
+ test.equal(res, true);
317
+ test.equal(calls[0][0], 'EXPUNGE');
318
+ test.equal(calls[0][1], '1:3');
319
+ test.done();
320
+ };
321
+
322
+ module.exports['Methods: messageDelete returns false on empty range'] = async test => {
323
+ let client = makeClient();
324
+ recordRun(client, true);
325
+ let res = await client.messageDelete([]);
326
+ test.equal(res, false);
327
+ test.done();
328
+ };
329
+
330
+ // ============================================================================
331
+ // append / copy / move
332
+ // ============================================================================
333
+
334
+ module.exports['Methods: append dispatches APPEND'] = async test => {
335
+ let client = makeClient();
336
+ let calls = recordRun(client, { destination: 'INBOX', uid: 5 });
337
+ let res = await client.append('INBOX', 'raw message', ['\\Seen'], new Date(2020, 1, 1));
338
+ test.equal(calls[0][0], 'APPEND');
339
+ test.equal(res.uid, 5);
340
+ test.done();
341
+ };
342
+
343
+ module.exports['Methods: append returns false when run returns falsy'] = async test => {
344
+ let client = makeClient();
345
+ recordRun(client, false);
346
+ let res = await client.append('INBOX', 'raw');
347
+ test.equal(res, false);
348
+ test.done();
349
+ };
350
+
351
+ module.exports['Methods: messageCopy resolves range and dispatches COPY'] = async test => {
352
+ let client = makeClient();
353
+ let calls = recordRun(client, { destination: 'Backup' });
354
+ await client.messageCopy('1:*', 'Backup', { uid: false });
355
+ test.equal(calls[0][0], 'COPY');
356
+ test.equal(calls[0][2], 'Backup');
357
+ test.done();
358
+ };
359
+
360
+ module.exports['Methods: messageCopy returns false on empty range'] = async test => {
361
+ let client = makeClient();
362
+ recordRun(client, true);
363
+ let res = await client.messageCopy([], 'Backup');
364
+ test.equal(res, false);
365
+ test.done();
366
+ };
367
+
368
+ module.exports['Methods: messageMove resolves range and dispatches MOVE'] = async test => {
369
+ let client = makeClient();
370
+ let calls = recordRun(client, { destination: 'Trash' });
371
+ await client.messageMove('1', 'Trash');
372
+ test.equal(calls[0][0], 'MOVE');
373
+ test.equal(calls[0][2], 'Trash');
374
+ test.done();
375
+ };
376
+
377
+ module.exports['Methods: messageMove returns false on empty range'] = async test => {
378
+ let client = makeClient();
379
+ recordRun(client, true);
380
+ let res = await client.messageMove([], 'Trash');
381
+ test.equal(res, false);
382
+ test.done();
383
+ };
384
+
385
+ // ============================================================================
386
+ // resolveRange branches
387
+ // ============================================================================
388
+
389
+ module.exports['Methods: resolveRange converts number'] = async test => {
390
+ let client = makeClient();
391
+ let res = await client.resolveRange(5, {});
392
+ test.equal(res, '5');
393
+ test.done();
394
+ };
395
+
396
+ module.exports['Methods: resolveRange converts bigint'] = async test => {
397
+ let client = makeClient();
398
+ let res = await client.resolveRange(BigInt(7), {});
399
+ test.equal(res, '7');
400
+ test.done();
401
+ };
402
+
403
+ module.exports['Methods: resolveRange star uses mailbox.exists'] = async test => {
404
+ let client = makeClient();
405
+ let options = {};
406
+ let res = await client.resolveRange('*', options);
407
+ test.equal(res, '10');
408
+ test.equal(options.uid, false);
409
+ test.done();
410
+ };
411
+
412
+ module.exports['Methods: resolveRange star returns false on empty mailbox'] = async test => {
413
+ let client = makeClient();
414
+ client.mailbox.exists = 0;
415
+ let res = await client.resolveRange('*', {});
416
+ test.equal(res, false);
417
+ test.done();
418
+ };
419
+
420
+ module.exports['Methods: resolveRange all:true => 1:*'] = async test => {
421
+ let client = makeClient();
422
+ let res = await client.resolveRange({ all: true }, {});
423
+ test.equal(res, '1:*');
424
+ test.done();
425
+ };
426
+
427
+ module.exports['Methods: resolveRange uid object'] = async test => {
428
+ let client = makeClient();
429
+ let options = {};
430
+ let res = await client.resolveRange({ uid: '100:200' }, options);
431
+ test.equal(res, '100:200');
432
+ test.equal(options.uid, true);
433
+ test.done();
434
+ };
435
+
436
+ module.exports['Methods: resolveRange search query runs SEARCH and packs'] = async test => {
437
+ let client = makeClient();
438
+ let options = {};
439
+ client.run = async (cmd, query, opts) => {
440
+ test.equal(cmd, 'SEARCH');
441
+ test.equal(opts.uid, true);
442
+ return [1, 2, 3, 5];
443
+ };
444
+ let res = await client.resolveRange({ seen: false }, options);
445
+ test.equal(res, '1:3,5');
446
+ test.done();
447
+ };
448
+
449
+ module.exports['Methods: resolveRange search query with no hits'] = async test => {
450
+ let client = makeClient();
451
+ client.run = async () => [];
452
+ let res = await client.resolveRange({ seen: false }, {});
453
+ test.equal(res, false);
454
+ test.done();
455
+ };
456
+
457
+ module.exports['Methods: resolveRange array joins with commas'] = async test => {
458
+ let client = makeClient();
459
+ let res = await client.resolveRange([1, 2, 4], {});
460
+ test.equal(res, '1,2,4');
461
+ test.done();
462
+ };
463
+
464
+ module.exports['Methods: resolveRange empty string returns false'] = async test => {
465
+ let client = makeClient();
466
+ let res = await client.resolveRange('', {});
467
+ test.equal(res, false);
468
+ test.done();
469
+ };
470
+
471
+ // ============================================================================
472
+ // ensureSelectedMailbox
473
+ // ============================================================================
474
+
475
+ module.exports['Methods: ensureSelectedMailbox returns false without path'] = async test => {
476
+ let client = makeClient();
477
+ let res = await client.ensureSelectedMailbox(null);
478
+ test.equal(res, false);
479
+ test.done();
480
+ };
481
+
482
+ module.exports['Methods: ensureSelectedMailbox returns true when already selected'] = async test => {
483
+ let client = makeClient();
484
+ let res = await client.ensureSelectedMailbox('INBOX');
485
+ test.equal(res, true);
486
+ test.done();
487
+ };
488
+
489
+ module.exports['Methods: ensureSelectedMailbox opens a different mailbox'] = async test => {
490
+ let client = makeClient();
491
+ let calls = recordRun(client, { path: 'Other' });
492
+ await client.ensureSelectedMailbox('Other');
493
+ test.equal(calls[0][0], 'SELECT');
494
+ test.equal(calls[0][1], 'Other');
495
+ test.done();
496
+ };
497
+
498
+ // ============================================================================
499
+ // search()
500
+ // ============================================================================
501
+
502
+ module.exports['Methods: search returns undefined without mailbox'] = async test => {
503
+ let client = makeClient();
504
+ client.mailbox = false;
505
+ let res = await client.search({ seen: false });
506
+ test.equal(res, undefined);
507
+ test.done();
508
+ };
509
+
510
+ module.exports['Methods: search returns server result'] = async test => {
511
+ let client = makeClient();
512
+ recordRun(client, [1, 2, 3]);
513
+ let res = await client.search({ seen: false });
514
+ test.deepEqual(res, [1, 2, 3]);
515
+ test.done();
516
+ };
517
+
518
+ module.exports['Methods: search returns false when run returns falsy'] = async test => {
519
+ let client = makeClient();
520
+ recordRun(client, null);
521
+ let res = await client.search({ all: true });
522
+ test.equal(res, false);
523
+ test.done();
524
+ };
525
+
526
+ module.exports['Methods: search derives ESearch COUNT/MIN/MAX/ALL client-side'] = async test => {
527
+ let client = makeClient();
528
+ recordRun(client, [2, 4, 6, 8]);
529
+ let res = await client.search({ all: true }, { returnOptions: ['count', 'min', 'max', 'all'] });
530
+ test.equal(res.count, 4);
531
+ test.equal(res.min, 2);
532
+ test.equal(res.max, 8);
533
+ test.equal(res.all, '2,4,6,8');
534
+ test.done();
535
+ };
536
+
537
+ module.exports['Methods: search returnOptions PARTIAL only returns raw array'] = async test => {
538
+ let client = makeClient();
539
+ recordRun(client, [1, 2, 3]);
540
+ let res = await client.search({ all: true }, { returnOptions: ['partial'] });
541
+ test.deepEqual(res, [1, 2, 3]);
542
+ test.done();
543
+ };
544
+
545
+ // ============================================================================
546
+ // Untagged response handlers
547
+ // ============================================================================
548
+
549
+ module.exports['Methods: untaggedExists ignores when no mailbox'] = async test => {
550
+ let client = makeClient();
551
+ client.mailbox = false;
552
+ await client.untaggedExists({ command: '12' });
553
+ test.ok(true);
554
+ test.done();
555
+ };
556
+
557
+ module.exports['Methods: untaggedExists ignores invalid command'] = async test => {
558
+ let client = makeClient();
559
+ let fired = false;
560
+ client.on('exists', () => {
561
+ fired = true;
562
+ });
563
+ await client.untaggedExists({ command: 'NaNvalue' });
564
+ test.equal(fired, false);
565
+ test.done();
566
+ };
567
+
568
+ module.exports['Methods: untaggedExists no-op when count unchanged'] = async test => {
569
+ let client = makeClient();
570
+ client.mailbox.exists = 10;
571
+ let fired = false;
572
+ client.on('exists', () => {
573
+ fired = true;
574
+ });
575
+ await client.untaggedExists({ command: '10' });
576
+ test.equal(fired, false);
577
+ test.done();
578
+ };
579
+
580
+ module.exports['Methods: untaggedExists emits exists event on change'] = async test => {
581
+ let client = makeClient();
582
+ client.mailbox.exists = 10;
583
+ let evt = null;
584
+ client.on('exists', e => {
585
+ evt = e;
586
+ });
587
+ await client.untaggedExists({ command: '12' });
588
+ test.ok(evt);
589
+ test.equal(evt.count, 12);
590
+ test.equal(evt.prevCount, 10);
591
+ test.equal(client.mailbox.exists, 12);
592
+ test.done();
593
+ };
594
+
595
+ module.exports['Methods: untaggedExpunge ignores when no mailbox'] = async test => {
596
+ let client = makeClient();
597
+ client.mailbox = false;
598
+ await client.untaggedExpunge({ command: '2' });
599
+ test.ok(true);
600
+ test.done();
601
+ };
602
+
603
+ module.exports['Methods: untaggedExpunge ignores invalid command'] = async test => {
604
+ let client = makeClient();
605
+ let fired = false;
606
+ client.on('expunge', () => {
607
+ fired = true;
608
+ });
609
+ await client.untaggedExpunge({ command: 'x' });
610
+ test.equal(fired, false);
611
+ test.done();
612
+ };
613
+
614
+ module.exports['Methods: untaggedExpunge emits expunge event'] = async test => {
615
+ let client = makeClient();
616
+ client.mailbox.exists = 10;
617
+ let evt = null;
618
+ client.on('expunge', e => {
619
+ evt = e;
620
+ });
621
+ await client.untaggedExpunge({ command: '3' });
622
+ test.ok(evt);
623
+ test.equal(evt.seq, 3);
624
+ test.equal(evt.vanished, false);
625
+ test.equal(client.mailbox.exists, 9);
626
+ test.done();
627
+ };
628
+
629
+ module.exports['Methods: untaggedExpunge uses expungeHandler when set'] = async test => {
630
+ let payloads = [];
631
+ let client = makeClient({ expungeHandler: async payload => payloads.push(payload) });
632
+ client.mailbox.exists = 5;
633
+ await client.untaggedExpunge({ command: '2' });
634
+ test.equal(payloads.length, 1);
635
+ test.equal(payloads[0].seq, 2);
636
+ test.done();
637
+ };
638
+
639
+ module.exports['Methods: untaggedExpunge handles expungeHandler error'] = async test => {
640
+ let client = makeClient({
641
+ expungeHandler: async () => {
642
+ throw new Error('handler boom');
643
+ }
644
+ });
645
+ client.mailbox.exists = 5;
646
+ // Should not throw despite handler error
647
+ await client.untaggedExpunge({ command: '2' });
648
+ test.ok(true);
649
+ test.done();
650
+ };
651
+
652
+ module.exports['Methods: untaggedVanished ignores when no mailbox'] = async test => {
653
+ let client = makeClient();
654
+ client.mailbox = false;
655
+ await client.untaggedVanished({ attributes: [{ value: '1:3' }] });
656
+ test.ok(true);
657
+ test.done();
658
+ };
659
+
660
+ module.exports['Methods: untaggedVanished emits expunge per uid'] = async test => {
661
+ let client = makeClient();
662
+ let events = [];
663
+ client.on('expunge', e => events.push(e));
664
+ await client.untaggedVanished({ attributes: [{ value: '1:3' }] });
665
+ test.equal(events.length, 3);
666
+ test.equal(events[0].uid, 1);
667
+ test.equal(events[0].vanished, true);
668
+ test.equal(events[0].earlier, false);
669
+ test.done();
670
+ };
671
+
672
+ module.exports['Methods: untaggedVanished EARLIER tag sets earlier flag'] = async test => {
673
+ let client = makeClient();
674
+ let events = [];
675
+ client.on('expunge', e => events.push(e));
676
+ await client.untaggedVanished({
677
+ attributes: [[{ value: 'EARLIER' }], { value: '5' }]
678
+ });
679
+ test.equal(events.length, 1);
680
+ test.equal(events[0].uid, 5);
681
+ test.equal(events[0].earlier, true);
682
+ test.done();
683
+ };
684
+
685
+ module.exports['Methods: untaggedVanished routes through expungeHandler'] = async test => {
686
+ let payloads = [];
687
+ let client = makeClient({ expungeHandler: async p => payloads.push(p) });
688
+ await client.untaggedVanished({ attributes: [{ value: '7' }] });
689
+ test.equal(payloads.length, 1);
690
+ test.equal(payloads[0].uid, 7);
691
+ test.done();
692
+ };
693
+
694
+ module.exports['Methods: untaggedVanished swallows expungeHandler error'] = async test => {
695
+ let client = makeClient({
696
+ expungeHandler: async () => {
697
+ throw new Error('vanished boom');
698
+ }
699
+ });
700
+ await client.untaggedVanished({ attributes: [{ value: '7' }] });
701
+ test.ok(true);
702
+ test.done();
703
+ };
704
+
705
+ module.exports['Methods: untaggedFetch ignores when no mailbox'] = async test => {
706
+ let client = makeClient();
707
+ client.mailbox = false;
708
+ await client.untaggedFetch({ command: '1', attributes: [] });
709
+ test.ok(true);
710
+ test.done();
711
+ };
712
+
713
+ module.exports['Methods: untaggedFetch emits flags event'] = async test => {
714
+ let client = makeClient();
715
+ let evt = null;
716
+ client.on('flags', e => {
717
+ evt = e;
718
+ });
719
+ // Build an untagged FETCH with FLAGS and UID attributes
720
+ let untagged = {
721
+ command: '1',
722
+ attributes: [
723
+ { type: 'ATOM', value: 'FETCH' },
724
+ [
725
+ { type: 'ATOM', value: 'UID' },
726
+ { type: 'ATOM', value: '100' },
727
+ { type: 'ATOM', value: 'FLAGS' },
728
+ [{ type: 'ATOM', value: '\\Seen' }]
729
+ ]
730
+ ]
731
+ };
732
+ await client.untaggedFetch(untagged);
733
+ test.ok(evt);
734
+ test.equal(evt.uid, 100);
735
+ test.ok(evt.flags instanceof Set);
736
+ test.ok(evt.flags.has('\\Seen'));
737
+ test.done();
738
+ };