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.
- package/.release-please-manifest.json +1 -1
- package/CHANGELOG.md +14 -0
- package/lib/imap-flow.d.ts +4 -2
- package/lib/imap-flow.js +23 -0
- package/package.json +10 -7
- package/test/commands-integration-test.js +8216 -0
- package/test/jp-decoder-test.js +199 -0
- package/test/limited-passthrough-test.js +258 -0
- package/test/memory-cleanup-test.js +144 -0
- package/test/proxy-connection-test.js +541 -0
- package/test/search-compiler-test.js +820 -50
- package/test/tools-test.js +788 -0
|
@@ -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
|
+
};
|