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.
@@ -0,0 +1,788 @@
1
+ 'use strict';
2
+
3
+ const tools = require('../lib/tools');
4
+
5
+ // Mock connection for testing
6
+ let createMockConnection = (options = {}) => ({
7
+ enabled: new Set(options.utf8 ? ['UTF8=ACCEPT'] : []),
8
+ namespace: options.namespace || null
9
+ });
10
+
11
+ // ============================================
12
+ // encodePath / decodePath tests
13
+ // ============================================
14
+
15
+ module.exports['Tools: encodePath with ASCII path'] = test => {
16
+ let connection = createMockConnection();
17
+ let result = tools.encodePath(connection, 'INBOX');
18
+ test.equal(result, 'INBOX');
19
+ test.done();
20
+ };
21
+
22
+ module.exports['Tools: encodePath with Unicode path (no UTF8)'] = test => {
23
+ let connection = createMockConnection({ utf8: false });
24
+ let result = tools.encodePath(connection, 'Sent/Gesendete');
25
+ // ASCII path should remain unchanged
26
+ test.equal(result, 'Sent/Gesendete');
27
+ test.done();
28
+ };
29
+
30
+ module.exports['Tools: encodePath with Unicode when UTF8=ACCEPT enabled'] = test => {
31
+ let connection = createMockConnection({ utf8: true });
32
+ let result = tools.encodePath(connection, 'Posteingang/Ordner');
33
+ // With UTF8=ACCEPT, path should remain unchanged
34
+ test.equal(result, 'Posteingang/Ordner');
35
+ test.done();
36
+ };
37
+
38
+ module.exports['Tools: encodePath with null/undefined'] = test => {
39
+ let connection = createMockConnection();
40
+ test.equal(tools.encodePath(connection, null), '');
41
+ test.equal(tools.encodePath(connection, undefined), '');
42
+ test.done();
43
+ };
44
+
45
+ module.exports['Tools: decodePath with ASCII path'] = test => {
46
+ let connection = createMockConnection();
47
+ let result = tools.decodePath(connection, 'INBOX');
48
+ test.equal(result, 'INBOX');
49
+ test.done();
50
+ };
51
+
52
+ module.exports['Tools: decodePath with ampersand'] = test => {
53
+ let connection = createMockConnection({ utf8: false });
54
+ // UTF-7-IMAP encoded string
55
+ let result = tools.decodePath(connection, 'Test&-Folder');
56
+ test.ok(typeof result === 'string');
57
+ test.done();
58
+ };
59
+
60
+ module.exports['Tools: decodePath with UTF8=ACCEPT enabled'] = test => {
61
+ let connection = createMockConnection({ utf8: true });
62
+ let result = tools.decodePath(connection, 'Test&Folder');
63
+ // With UTF8=ACCEPT, should not decode
64
+ test.equal(result, 'Test&Folder');
65
+ test.done();
66
+ };
67
+
68
+ module.exports['Tools: decodePath with null/undefined'] = test => {
69
+ let connection = createMockConnection();
70
+ test.equal(tools.decodePath(connection, null), '');
71
+ test.equal(tools.decodePath(connection, undefined), '');
72
+ test.done();
73
+ };
74
+
75
+ // ============================================
76
+ // normalizePath tests
77
+ // ============================================
78
+
79
+ module.exports['Tools: normalizePath with INBOX (case insensitive)'] = test => {
80
+ let connection = createMockConnection();
81
+ test.equal(tools.normalizePath(connection, 'inbox'), 'INBOX');
82
+ test.equal(tools.normalizePath(connection, 'INBOX'), 'INBOX');
83
+ test.equal(tools.normalizePath(connection, 'InBox'), 'INBOX');
84
+ test.done();
85
+ };
86
+
87
+ module.exports['Tools: normalizePath with array path'] = test => {
88
+ let connection = createMockConnection({
89
+ namespace: { delimiter: '/', prefix: '' }
90
+ });
91
+ let result = tools.normalizePath(connection, ['Folder', 'Subfolder']);
92
+ test.equal(result, 'Folder/Subfolder');
93
+ test.done();
94
+ };
95
+
96
+ module.exports['Tools: normalizePath with namespace prefix'] = test => {
97
+ let connection = createMockConnection({
98
+ namespace: { delimiter: '.', prefix: 'INBOX.' }
99
+ });
100
+ let result = tools.normalizePath(connection, 'Sent');
101
+ test.equal(result, 'INBOX.Sent');
102
+ test.done();
103
+ };
104
+
105
+ module.exports['Tools: normalizePath skip namespace'] = test => {
106
+ let connection = createMockConnection({
107
+ namespace: { delimiter: '.', prefix: 'INBOX.' }
108
+ });
109
+ let result = tools.normalizePath(connection, 'Sent', true);
110
+ test.equal(result, 'Sent');
111
+ test.done();
112
+ };
113
+
114
+ module.exports['Tools: normalizePath already has prefix'] = test => {
115
+ let connection = createMockConnection({
116
+ namespace: { delimiter: '.', prefix: 'INBOX.' }
117
+ });
118
+ let result = tools.normalizePath(connection, 'INBOX.Sent');
119
+ test.equal(result, 'INBOX.Sent');
120
+ test.done();
121
+ };
122
+
123
+ // ============================================
124
+ // comparePaths tests
125
+ // ============================================
126
+
127
+ module.exports['Tools: comparePaths equal paths'] = test => {
128
+ let connection = createMockConnection();
129
+ test.equal(tools.comparePaths(connection, 'INBOX', 'INBOX'), true);
130
+ test.equal(tools.comparePaths(connection, 'inbox', 'INBOX'), true);
131
+ test.done();
132
+ };
133
+
134
+ module.exports['Tools: comparePaths different paths'] = test => {
135
+ let connection = createMockConnection();
136
+ test.equal(tools.comparePaths(connection, 'INBOX', 'Sent'), false);
137
+ test.done();
138
+ };
139
+
140
+ module.exports['Tools: comparePaths with null/undefined'] = test => {
141
+ let connection = createMockConnection();
142
+ test.equal(tools.comparePaths(connection, null, 'INBOX'), false);
143
+ test.equal(tools.comparePaths(connection, 'INBOX', null), false);
144
+ test.equal(tools.comparePaths(connection, null, null), false);
145
+ test.done();
146
+ };
147
+
148
+ // ============================================
149
+ // updateCapabilities tests
150
+ // ============================================
151
+
152
+ module.exports['Tools: updateCapabilities with valid list'] = test => {
153
+ let list = [{ value: 'IMAP4rev1' }, { value: 'IDLE' }, { value: 'NAMESPACE' }];
154
+ let result = tools.updateCapabilities(list);
155
+ test.ok(result instanceof Map);
156
+ test.equal(result.get('IMAP4rev1'), true);
157
+ test.equal(result.get('IDLE'), true);
158
+ test.equal(result.get('NAMESPACE'), true);
159
+ test.done();
160
+ };
161
+
162
+ module.exports['Tools: updateCapabilities with APPENDLIMIT'] = test => {
163
+ let list = [{ value: 'APPENDLIMIT=52428800' }];
164
+ let result = tools.updateCapabilities(list);
165
+ test.equal(result.get('APPENDLIMIT'), 52428800);
166
+ test.done();
167
+ };
168
+
169
+ module.exports['Tools: updateCapabilities with empty/null list'] = test => {
170
+ test.ok(tools.updateCapabilities(null) instanceof Map);
171
+ test.ok(tools.updateCapabilities([]) instanceof Map);
172
+ test.ok(tools.updateCapabilities(undefined) instanceof Map);
173
+ test.done();
174
+ };
175
+
176
+ module.exports['Tools: updateCapabilities skips non-string values'] = test => {
177
+ let list = [{ value: 'IDLE' }, { value: 123 }, { value: null }];
178
+ let result = tools.updateCapabilities(list);
179
+ test.equal(result.get('IDLE'), true);
180
+ test.equal(result.size, 1);
181
+ test.done();
182
+ };
183
+
184
+ // ============================================
185
+ // getStatusCode tests
186
+ // ============================================
187
+
188
+ module.exports['Tools: getStatusCode with valid response'] = test => {
189
+ let response = {
190
+ attributes: [
191
+ {
192
+ section: [{ value: 'TRYCREATE' }]
193
+ }
194
+ ]
195
+ };
196
+ test.equal(tools.getStatusCode(response), 'TRYCREATE');
197
+ test.done();
198
+ };
199
+
200
+ module.exports['Tools: getStatusCode with null/invalid response'] = test => {
201
+ test.equal(tools.getStatusCode(null), false);
202
+ test.equal(tools.getStatusCode({}), false);
203
+ test.equal(tools.getStatusCode({ attributes: [] }), false);
204
+ test.equal(tools.getStatusCode({ attributes: [{}] }), false);
205
+ test.done();
206
+ };
207
+
208
+ // ============================================
209
+ // getErrorText tests
210
+ // ============================================
211
+
212
+ module.exports['Tools: getErrorText with null response'] = async test => {
213
+ let result = await tools.getErrorText(null);
214
+ test.equal(result, false);
215
+ test.done();
216
+ };
217
+
218
+ module.exports['Tools: getErrorText with valid response'] = async test => {
219
+ let response = {
220
+ tag: '*',
221
+ command: 'OK',
222
+ attributes: [{ type: 'TEXT', value: 'Success' }]
223
+ };
224
+ let result = await tools.getErrorText(response);
225
+ test.ok(typeof result === 'string');
226
+ test.done();
227
+ };
228
+
229
+ // ============================================
230
+ // getFlagColor tests
231
+ // ============================================
232
+
233
+ module.exports['Tools: getFlagColor without Flagged'] = test => {
234
+ let flags = new Set(['\\Seen']);
235
+ test.equal(tools.getFlagColor(flags), null);
236
+ test.done();
237
+ };
238
+
239
+ module.exports['Tools: getFlagColor with Flagged only (red)'] = test => {
240
+ let flags = new Set(['\\Flagged']);
241
+ test.equal(tools.getFlagColor(flags), 'red');
242
+ test.done();
243
+ };
244
+
245
+ module.exports['Tools: getFlagColor with color bits'] = test => {
246
+ // bit0=1, bit1=0, bit2=0 => orange (index 1)
247
+ let flags = new Set(['\\Flagged', '$MailFlagBit0']);
248
+ test.equal(tools.getFlagColor(flags), 'orange');
249
+
250
+ // bit0=0, bit1=1, bit2=0 => yellow (index 2)
251
+ flags = new Set(['\\Flagged', '$MailFlagBit1']);
252
+ test.equal(tools.getFlagColor(flags), 'yellow');
253
+
254
+ // bit0=1, bit1=1, bit2=0 => green (index 3)
255
+ flags = new Set(['\\Flagged', '$MailFlagBit0', '$MailFlagBit1']);
256
+ test.equal(tools.getFlagColor(flags), 'green');
257
+
258
+ // bit0=0, bit1=0, bit2=1 => blue (index 4)
259
+ flags = new Set(['\\Flagged', '$MailFlagBit2']);
260
+ test.equal(tools.getFlagColor(flags), 'blue');
261
+
262
+ // bit0=1, bit1=0, bit2=1 => purple (index 5)
263
+ flags = new Set(['\\Flagged', '$MailFlagBit0', '$MailFlagBit2']);
264
+ test.equal(tools.getFlagColor(flags), 'purple');
265
+
266
+ // bit0=0, bit1=1, bit2=1 => grey (index 6)
267
+ flags = new Set(['\\Flagged', '$MailFlagBit1', '$MailFlagBit2']);
268
+ test.equal(tools.getFlagColor(flags), 'grey');
269
+
270
+ test.done();
271
+ };
272
+
273
+ // ============================================
274
+ // getColorFlags tests
275
+ // ============================================
276
+
277
+ module.exports['Tools: getColorFlags with valid color'] = test => {
278
+ // 'orange' is index 1, which is truthy
279
+ let result = tools.getColorFlags('orange');
280
+ test.ok(Array.isArray(result.add));
281
+ test.ok(Array.isArray(result.remove));
282
+ test.ok(result.add.includes('\\Flagged'));
283
+ test.done();
284
+ };
285
+
286
+ module.exports['Tools: getColorFlags with red (index 0)'] = test => {
287
+ // 'red' is index 0, which is falsy in JS - removes \\Flagged
288
+ let result = tools.getColorFlags('red');
289
+ test.ok(Array.isArray(result.add));
290
+ test.ok(Array.isArray(result.remove));
291
+ test.ok(result.remove.includes('\\Flagged'));
292
+ test.done();
293
+ };
294
+
295
+ module.exports['Tools: getColorFlags with null (remove flag)'] = test => {
296
+ let result = tools.getColorFlags(null);
297
+ test.ok(result.remove.includes('\\Flagged'));
298
+ test.done();
299
+ };
300
+
301
+ module.exports['Tools: getColorFlags with invalid color'] = test => {
302
+ let result = tools.getColorFlags('invalid-color');
303
+ test.equal(result, null);
304
+ test.done();
305
+ };
306
+
307
+ module.exports['Tools: getColorFlags sets correct bits'] = test => {
308
+ // orange = index 1 = bit0 set
309
+ let result = tools.getColorFlags('orange');
310
+ test.ok(result.add.includes('$MailFlagBit0'));
311
+ test.ok(result.remove.includes('$MailFlagBit1'));
312
+ test.ok(result.remove.includes('$MailFlagBit2'));
313
+
314
+ // green = index 3 = bit0 + bit1 set
315
+ result = tools.getColorFlags('green');
316
+ test.ok(result.add.includes('$MailFlagBit0'));
317
+ test.ok(result.add.includes('$MailFlagBit1'));
318
+ test.ok(result.remove.includes('$MailFlagBit2'));
319
+
320
+ test.done();
321
+ };
322
+
323
+ // ============================================
324
+ // isDate tests
325
+ // ============================================
326
+
327
+ module.exports['Tools: isDate with Date object'] = test => {
328
+ test.equal(tools.isDate(new Date()), true);
329
+ test.equal(tools.isDate(new Date('2023-01-01')), true);
330
+ test.done();
331
+ };
332
+
333
+ module.exports['Tools: isDate with non-Date'] = test => {
334
+ test.equal(tools.isDate('2023-01-01'), false);
335
+ test.equal(tools.isDate(12345), false);
336
+ test.equal(tools.isDate(null), false);
337
+ test.equal(tools.isDate({}), false);
338
+ test.done();
339
+ };
340
+
341
+ // ============================================
342
+ // formatDate tests
343
+ // ============================================
344
+
345
+ module.exports['Tools: formatDate with Date object'] = test => {
346
+ let date = new Date('2023-06-15T00:00:00.000Z');
347
+ let result = tools.formatDate(date);
348
+ test.equal(result, '15-Jun-2023');
349
+ test.done();
350
+ };
351
+
352
+ module.exports['Tools: formatDate with string'] = test => {
353
+ let result = tools.formatDate('2023-06-15');
354
+ test.equal(result, '15-Jun-2023');
355
+ test.done();
356
+ };
357
+
358
+ module.exports['Tools: formatDate with invalid date'] = test => {
359
+ let result = tools.formatDate('invalid');
360
+ test.equal(result, undefined);
361
+ test.done();
362
+ };
363
+
364
+ // ============================================
365
+ // formatDateTime tests
366
+ // ============================================
367
+
368
+ module.exports['Tools: formatDateTime with Date object'] = test => {
369
+ let date = new Date('2023-06-15T14:30:45.000Z');
370
+ let result = tools.formatDateTime(date);
371
+ test.ok(result.includes('Jun-2023'));
372
+ test.ok(result.includes('14:30:45'));
373
+ test.ok(result.includes('+0000'));
374
+ test.done();
375
+ };
376
+
377
+ module.exports['Tools: formatDateTime with null/undefined'] = test => {
378
+ test.equal(tools.formatDateTime(null), undefined);
379
+ test.equal(tools.formatDateTime(undefined), undefined);
380
+ test.done();
381
+ };
382
+
383
+ module.exports['Tools: formatDateTime with string'] = test => {
384
+ let result = tools.formatDateTime('2023-06-15T10:00:00Z');
385
+ test.ok(typeof result === 'string');
386
+ test.ok(result.includes('Jun-2023'));
387
+ test.done();
388
+ };
389
+
390
+ // ============================================
391
+ // formatFlag tests
392
+ // ============================================
393
+
394
+ module.exports['Tools: formatFlag with standard flags'] = test => {
395
+ test.equal(tools.formatFlag('\\Seen'), '\\Seen');
396
+ test.equal(tools.formatFlag('\\SEEN'), '\\Seen');
397
+ test.equal(tools.formatFlag('\\answered'), '\\Answered');
398
+ test.equal(tools.formatFlag('\\flagged'), '\\Flagged');
399
+ test.equal(tools.formatFlag('\\deleted'), '\\Deleted');
400
+ test.equal(tools.formatFlag('\\draft'), '\\Draft');
401
+ test.done();
402
+ };
403
+
404
+ module.exports['Tools: formatFlag with Recent (cannot set)'] = test => {
405
+ test.equal(tools.formatFlag('\\Recent'), false);
406
+ test.equal(tools.formatFlag('\\recent'), false);
407
+ test.done();
408
+ };
409
+
410
+ module.exports['Tools: formatFlag with custom flags'] = test => {
411
+ test.equal(tools.formatFlag('$CustomFlag'), '$CustomFlag');
412
+ test.equal(tools.formatFlag('MyFlag'), 'MyFlag');
413
+ test.done();
414
+ };
415
+
416
+ // ============================================
417
+ // canUseFlag tests
418
+ // ============================================
419
+
420
+ module.exports['Tools: canUseFlag with no mailbox'] = test => {
421
+ test.equal(tools.canUseFlag(null, '\\Seen'), true);
422
+ test.done();
423
+ };
424
+
425
+ module.exports['Tools: canUseFlag with wildcard permanent flags'] = test => {
426
+ let mailbox = { permanentFlags: new Set(['\\*']) };
427
+ test.equal(tools.canUseFlag(mailbox, '\\Seen'), true);
428
+ test.equal(tools.canUseFlag(mailbox, '$CustomFlag'), true);
429
+ test.done();
430
+ };
431
+
432
+ module.exports['Tools: canUseFlag with specific permanent flags'] = test => {
433
+ let mailbox = { permanentFlags: new Set(['\\Seen', '\\Flagged']) };
434
+ test.equal(tools.canUseFlag(mailbox, '\\Seen'), true);
435
+ test.equal(tools.canUseFlag(mailbox, '\\Flagged'), true);
436
+ test.equal(tools.canUseFlag(mailbox, '\\Deleted'), false);
437
+ test.done();
438
+ };
439
+
440
+ module.exports['Tools: canUseFlag with no permanent flags'] = test => {
441
+ let mailbox = { permanentFlags: null };
442
+ test.equal(tools.canUseFlag(mailbox, '\\Seen'), true);
443
+ test.done();
444
+ };
445
+
446
+ // ============================================
447
+ // expandRange tests
448
+ // ============================================
449
+
450
+ module.exports['Tools: expandRange with single values'] = test => {
451
+ let result = tools.expandRange('1,2,3');
452
+ test.deepEqual(result, [1, 2, 3]);
453
+ test.done();
454
+ };
455
+
456
+ module.exports['Tools: expandRange with range'] = test => {
457
+ let result = tools.expandRange('1:5');
458
+ test.deepEqual(result, [1, 2, 3, 4, 5]);
459
+ test.done();
460
+ };
461
+
462
+ module.exports['Tools: expandRange with reverse range'] = test => {
463
+ let result = tools.expandRange('5:1');
464
+ test.deepEqual(result, [5, 4, 3, 2, 1]);
465
+ test.done();
466
+ };
467
+
468
+ module.exports['Tools: expandRange with mixed'] = test => {
469
+ let result = tools.expandRange('1,3:5,10');
470
+ test.deepEqual(result, [1, 3, 4, 5, 10]);
471
+ test.done();
472
+ };
473
+
474
+ module.exports['Tools: expandRange with same start/end'] = test => {
475
+ let result = tools.expandRange('5:5');
476
+ test.deepEqual(result, [5]);
477
+ test.done();
478
+ };
479
+
480
+ // ============================================
481
+ // packMessageRange tests
482
+ // ============================================
483
+
484
+ module.exports['Tools: packMessageRange with sequential numbers'] = test => {
485
+ let result = tools.packMessageRange([1, 2, 3, 4, 5]);
486
+ test.equal(result, '1:5');
487
+ test.done();
488
+ };
489
+
490
+ module.exports['Tools: packMessageRange with gaps'] = test => {
491
+ let result = tools.packMessageRange([1, 2, 3, 7, 8, 9]);
492
+ test.equal(result, '1:3,7:9');
493
+ test.done();
494
+ };
495
+
496
+ module.exports['Tools: packMessageRange with single values'] = test => {
497
+ let result = tools.packMessageRange([1, 5, 10]);
498
+ test.equal(result, '1,5,10');
499
+ test.done();
500
+ };
501
+
502
+ module.exports['Tools: packMessageRange with unsorted input'] = test => {
503
+ let result = tools.packMessageRange([5, 1, 3, 2, 4]);
504
+ test.equal(result, '1:5');
505
+ test.done();
506
+ };
507
+
508
+ module.exports['Tools: packMessageRange with empty array'] = test => {
509
+ test.equal(tools.packMessageRange([]), '');
510
+ test.done();
511
+ };
512
+
513
+ module.exports['Tools: packMessageRange with non-array'] = test => {
514
+ test.equal(tools.packMessageRange(5), '5');
515
+ test.equal(tools.packMessageRange(null), '');
516
+ test.done();
517
+ };
518
+
519
+ // ============================================
520
+ // processName tests
521
+ // ============================================
522
+
523
+ module.exports['Tools: processName with quoted string'] = test => {
524
+ test.equal(tools.processName('"John Doe"'), 'John Doe');
525
+ test.done();
526
+ };
527
+
528
+ module.exports['Tools: processName with unquoted string'] = test => {
529
+ test.equal(tools.processName('John Doe'), 'John Doe');
530
+ test.done();
531
+ };
532
+
533
+ module.exports['Tools: processName with null/undefined'] = test => {
534
+ test.equal(tools.processName(null), '');
535
+ test.equal(tools.processName(undefined), '');
536
+ test.done();
537
+ };
538
+
539
+ module.exports['Tools: processName with short quoted'] = test => {
540
+ // String too short to have quotes removed (less than 3 chars)
541
+ test.equal(tools.processName('""'), '""');
542
+ test.equal(tools.processName('"a"'), 'a');
543
+ test.done();
544
+ };
545
+
546
+ // ============================================
547
+ // getFolderTree tests
548
+ // ============================================
549
+
550
+ module.exports['Tools: getFolderTree with flat folders'] = test => {
551
+ let folders = [
552
+ { name: 'INBOX', path: 'INBOX', flags: new Set(), parent: [] },
553
+ { name: 'Sent', path: 'Sent', flags: new Set(), parent: [] }
554
+ ];
555
+ let tree = tools.getFolderTree(folders);
556
+
557
+ test.ok(tree.root);
558
+ test.ok(Array.isArray(tree.folders));
559
+ test.equal(tree.folders.length, 2);
560
+ test.done();
561
+ };
562
+
563
+ module.exports['Tools: getFolderTree with nested folders'] = test => {
564
+ let folders = [
565
+ { name: 'INBOX', path: 'INBOX', flags: new Set(['\\HasChildren']), parent: [] },
566
+ { name: 'Work', path: 'INBOX/Work', flags: new Set(), parent: ['INBOX'] }
567
+ ];
568
+ let tree = tools.getFolderTree(folders);
569
+
570
+ test.ok(tree.root);
571
+ test.equal(tree.folders.length, 1);
572
+ test.equal(tree.folders[0].name, 'INBOX');
573
+ test.ok(Array.isArray(tree.folders[0].folders));
574
+ test.done();
575
+ };
576
+
577
+ module.exports['Tools: getFolderTree with Noselect flag'] = test => {
578
+ let folders = [{ name: 'Archive', path: 'Archive', flags: new Set(['\\Noselect']), parent: [] }];
579
+ let tree = tools.getFolderTree(folders);
580
+
581
+ test.equal(tree.folders[0].disabled, true);
582
+ test.done();
583
+ };
584
+
585
+ module.exports['Tools: getFolderTree with specialUse'] = test => {
586
+ let folders = [{ name: 'Sent', path: 'Sent', flags: new Set(), parent: [], specialUse: '\\Sent' }];
587
+ let tree = tools.getFolderTree(folders);
588
+
589
+ test.equal(tree.folders[0].specialUse, '\\Sent');
590
+ test.done();
591
+ };
592
+
593
+ module.exports['Tools: getFolderTree with delimiter'] = test => {
594
+ let folders = [{ name: 'Folder', path: 'Folder', flags: new Set(), parent: [], delimiter: '/' }];
595
+ let tree = tools.getFolderTree(folders);
596
+
597
+ test.equal(tree.folders[0].delimiter, '/');
598
+ test.done();
599
+ };
600
+
601
+ module.exports['Tools: getFolderTree updates existing entries'] = test => {
602
+ let folders = [
603
+ { name: 'INBOX', path: 'INBOX', flags: new Set(['\\HasChildren']), parent: [], listed: true },
604
+ { name: 'INBOX', path: 'INBOX', flags: new Set(['\\HasChildren']), parent: [], subscribed: true }
605
+ ];
606
+ let tree = tools.getFolderTree(folders);
607
+
608
+ // Should update the existing entry, not create duplicate
609
+ test.equal(tree.folders.length, 1);
610
+ test.done();
611
+ };
612
+
613
+ // ============================================
614
+ // parseEnvelope tests
615
+ // ============================================
616
+
617
+ module.exports['Tools: parseEnvelope with complete envelope'] = test => {
618
+ let entry = [
619
+ { value: 'Mon, 15 Jun 2023 10:00:00 +0000' }, // date
620
+ { value: 'Test Subject' }, // subject
621
+ [[{ value: 'Sender Name' }, null, { value: 'sender' }, { value: 'example.com' }]], // from
622
+ [[{ value: 'Sender Name' }, null, { value: 'sender' }, { value: 'example.com' }]], // sender
623
+ [[{ value: 'Reply Name' }, null, { value: 'reply' }, { value: 'example.com' }]], // reply-to
624
+ [[{ value: 'To Name' }, null, { value: 'to' }, { value: 'example.com' }]], // to
625
+ [[{ value: 'CC Name' }, null, { value: 'cc' }, { value: 'example.com' }]], // cc
626
+ [[{ value: 'BCC Name' }, null, { value: 'bcc' }, { value: 'example.com' }]], // bcc
627
+ { value: '<reply-id@example.com>' }, // in-reply-to
628
+ { value: '<message-id@example.com>' } // message-id
629
+ ];
630
+
631
+ let result = tools.parseEnvelope(entry);
632
+
633
+ test.ok(result.date instanceof Date);
634
+ test.equal(result.subject, 'Test Subject');
635
+ test.equal(result.from[0].address, 'sender@example.com');
636
+ test.equal(result.to[0].address, 'to@example.com');
637
+ test.equal(result.messageId, '<message-id@example.com>');
638
+ test.done();
639
+ };
640
+
641
+ module.exports['Tools: parseEnvelope with minimal envelope'] = test => {
642
+ let entry = [
643
+ null, // date
644
+ null, // subject
645
+ [], // from
646
+ [], // sender
647
+ [], // reply-to
648
+ [], // to
649
+ [], // cc
650
+ [], // bcc
651
+ null, // in-reply-to
652
+ null // message-id
653
+ ];
654
+
655
+ let result = tools.parseEnvelope(entry);
656
+ test.ok(typeof result === 'object');
657
+ test.equal(result.subject, undefined);
658
+ test.done();
659
+ };
660
+
661
+ module.exports['Tools: parseEnvelope with invalid date'] = test => {
662
+ let entry = [{ value: 'invalid-date' }, null, null, null, null, null, null, null, null, null];
663
+
664
+ let result = tools.parseEnvelope(entry);
665
+ test.equal(result.date, 'invalid-date');
666
+ test.done();
667
+ };
668
+
669
+ // ============================================
670
+ // getStructuredParams tests
671
+ // ============================================
672
+
673
+ module.exports['Tools: getStructuredParams with simple params'] = test => {
674
+ let arr = [{ value: 'charset' }, { value: 'utf-8' }, { value: 'name' }, { value: 'file.txt' }];
675
+
676
+ let result = tools.getStructuredParams(arr);
677
+ test.equal(result.charset, 'utf-8');
678
+ test.equal(result.name, 'file.txt');
679
+ test.done();
680
+ };
681
+
682
+ module.exports['Tools: getStructuredParams with null'] = test => {
683
+ let result = tools.getStructuredParams(null);
684
+ test.deepEqual(result, {});
685
+ test.done();
686
+ };
687
+
688
+ module.exports['Tools: getStructuredParams with continuation'] = test => {
689
+ // RFC 2231 continuation
690
+ let arr = [{ value: 'filename*0' }, { value: 'very' }, { value: 'filename*1' }, { value: 'long' }, { value: 'filename*2' }, { value: 'name.txt' }];
691
+
692
+ let result = tools.getStructuredParams(arr);
693
+ test.equal(result.filename, 'verylongname.txt');
694
+ test.done();
695
+ };
696
+
697
+ // ============================================
698
+ // parseBodystructure tests
699
+ // ============================================
700
+
701
+ module.exports['Tools: parseBodystructure with simple text'] = test => {
702
+ let entry = [
703
+ { value: 'TEXT' },
704
+ { value: 'PLAIN' },
705
+ [{ value: 'CHARSET' }, { value: 'UTF-8' }],
706
+ null, // id
707
+ null, // description
708
+ { value: '7BIT' }, // encoding
709
+ { value: '1234' }, // size
710
+ { value: '50' } // lines
711
+ ];
712
+
713
+ let result = tools.parseBodystructure(entry);
714
+ test.equal(result.type, 'text/plain');
715
+ test.equal(result.encoding, '7bit');
716
+ test.equal(result.size, 1234);
717
+ test.equal(result.parameters.charset, 'UTF-8');
718
+ test.done();
719
+ };
720
+
721
+ module.exports['Tools: parseBodystructure with multipart'] = test => {
722
+ let textPart = [{ value: 'TEXT' }, { value: 'PLAIN' }, null, null, null, { value: '7BIT' }, { value: '100' }, { value: '5' }];
723
+
724
+ let htmlPart = [{ value: 'TEXT' }, { value: 'HTML' }, null, null, null, { value: 'QUOTED-PRINTABLE' }, { value: '200' }, { value: '10' }];
725
+
726
+ let entry = [textPart, htmlPart, { value: 'ALTERNATIVE' }];
727
+
728
+ let result = tools.parseBodystructure(entry);
729
+ test.equal(result.type, 'multipart/alternative');
730
+ test.ok(Array.isArray(result.childNodes));
731
+ test.equal(result.childNodes.length, 2);
732
+ test.equal(result.childNodes[0].type, 'text/plain');
733
+ test.equal(result.childNodes[1].type, 'text/html');
734
+ test.done();
735
+ };
736
+
737
+ module.exports['Tools: parseBodystructure with attachment'] = test => {
738
+ let entry = [
739
+ { value: 'APPLICATION' },
740
+ { value: 'PDF' },
741
+ [{ value: 'NAME' }, { value: 'document.pdf' }],
742
+ null,
743
+ null,
744
+ { value: 'BASE64' },
745
+ { value: '50000' }
746
+ ];
747
+
748
+ let result = tools.parseBodystructure(entry);
749
+ test.equal(result.type, 'application/pdf');
750
+ test.equal(result.parameters.name, 'document.pdf');
751
+ test.done();
752
+ };
753
+
754
+ // ============================================
755
+ // getDecoder tests
756
+ // ============================================
757
+
758
+ module.exports['Tools: getDecoder with standard charset'] = test => {
759
+ let decoder = tools.getDecoder('utf-8');
760
+ test.ok(decoder);
761
+ test.ok(typeof decoder.write === 'function');
762
+ test.done();
763
+ };
764
+
765
+ module.exports['Tools: getDecoder with Japanese charset'] = test => {
766
+ let decoder = tools.getDecoder('iso-2022-jp');
767
+ test.ok(decoder);
768
+ test.equal(decoder.constructor.name, 'JPDecoder');
769
+ test.done();
770
+ };
771
+
772
+ module.exports['Tools: getDecoder with null/undefined'] = test => {
773
+ let decoder = tools.getDecoder(null);
774
+ test.ok(decoder);
775
+ test.done();
776
+ };
777
+
778
+ // ============================================
779
+ // AuthenticationFailure tests
780
+ // ============================================
781
+
782
+ module.exports['Tools: AuthenticationFailure error class'] = test => {
783
+ let error = new tools.AuthenticationFailure('Auth failed');
784
+ test.ok(error instanceof Error);
785
+ test.equal(error.authenticationFailed, true);
786
+ test.equal(error.message, 'Auth failed');
787
+ test.done();
788
+ };