imapflow 1.0.192 → 1.0.194

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,629 @@
1
+ 'use strict';
2
+
3
+ const { ImapFlow } = require('../lib/imap-flow');
4
+ const { EventEmitter } = require('events');
5
+
6
+ // Edge Cases Tests
7
+
8
+ module.exports['Connection Edge: Socket error during connection'] = test => {
9
+ let client = new ImapFlow({
10
+ host: 'imap.example.com',
11
+ port: 993,
12
+ auth: { user: 'test', pass: 'test' }
13
+ });
14
+
15
+ // Simulate socket error
16
+ let errorEmitted = false;
17
+ client.on('error', err => {
18
+ errorEmitted = true;
19
+ test.ok(err);
20
+ test.equal(err._connId, client.id);
21
+ });
22
+
23
+ // Trigger error through emitError
24
+ let testError = new Error('Socket connection failed');
25
+ testError.code = 'ECONNREFUSED';
26
+ client.emitError(testError);
27
+
28
+ test.ok(errorEmitted, 'Error event should be emitted');
29
+ test.done();
30
+ };
31
+
32
+ module.exports['Connection Edge: Write after socket destroyed'] = test => {
33
+ let client = new ImapFlow({
34
+ host: 'imap.example.com',
35
+ port: 993,
36
+ auth: { user: 'test', pass: 'test' }
37
+ });
38
+
39
+ // Simulate destroyed socket
40
+ client.socket = { destroyed: true };
41
+
42
+ test.throws(() => {
43
+ client.write('TEST');
44
+ }, /Socket is already closed/, 'Should throw when writing to destroyed socket');
45
+
46
+ test.done();
47
+ };
48
+
49
+ module.exports['Connection Edge: Write after logout'] = test => {
50
+ let client = new ImapFlow({
51
+ host: 'imap.example.com',
52
+ port: 993,
53
+ auth: { user: 'test', pass: 'test' }
54
+ });
55
+
56
+ // Create a mock socket
57
+ client.socket = { destroyed: false };
58
+ client.state = client.states.LOGOUT;
59
+
60
+ test.throws(() => {
61
+ client.write('TEST');
62
+ }, /Can not send data after logged out/, 'Should throw when writing after logout');
63
+
64
+ test.done();
65
+ };
66
+
67
+ module.exports['Connection Edge: Stats reset functionality'] = test => {
68
+ let client = new ImapFlow({
69
+ host: 'imap.example.com',
70
+ port: 993,
71
+ auth: { user: 'test', pass: 'test' }
72
+ });
73
+
74
+ // Set some initial values
75
+ client.writeBytesCounter = 100;
76
+ client.streamer.readBytesCounter = 200;
77
+
78
+ let stats = client.stats(false);
79
+ test.equal(stats.sent, 100);
80
+ test.equal(stats.received, 200);
81
+
82
+ // Reset stats
83
+ stats = client.stats(true);
84
+ test.equal(stats.sent, 100, 'Should return old value before reset');
85
+ test.equal(stats.received, 200, 'Should return old value before reset');
86
+
87
+ // Check if reset worked
88
+ test.equal(client.writeBytesCounter, 0, 'Write counter should be reset');
89
+ test.equal(client.streamer.readBytesCounter, 0, 'Read counter should be reset');
90
+
91
+ test.done();
92
+ };
93
+
94
+ module.exports['Connection Edge: Multiple error handlers'] = test => {
95
+ let client = new ImapFlow({
96
+ host: 'imap.example.com',
97
+ port: 993,
98
+ auth: { user: 'test', pass: 'test' }
99
+ });
100
+
101
+ let errorCount = 0;
102
+
103
+ // Add multiple error handlers
104
+ client.on('error', () => errorCount++);
105
+ client.on('error', () => errorCount++);
106
+ client.on('error', () => errorCount++);
107
+
108
+ // Emit an error
109
+ client.emitError(new Error('Test error'));
110
+
111
+ test.equal(errorCount, 3, 'All error handlers should be called');
112
+ test.done();
113
+ };
114
+
115
+ module.exports['Connection Edge: Connection with valid empty auth'] = test => {
116
+ // ImapFlow actually allows creating client without throwing
117
+ // The error would occur during connection
118
+ let client = new ImapFlow({
119
+ host: 'imap.example.com',
120
+ port: 993,
121
+ auth: { user: 'test' }
122
+ });
123
+
124
+ test.ok(client, 'Client should be created even with partial auth');
125
+ test.equal(client.host, 'imap.example.com');
126
+ test.done();
127
+ };
128
+
129
+ module.exports['Connection Edge: Connection with default values'] = test => {
130
+ // ImapFlow sets default values for missing options
131
+ let client = new ImapFlow({
132
+ host: 'imap.example.com',
133
+ auth: { user: 'test', pass: 'test' }
134
+ });
135
+
136
+ test.ok(client, 'Client should be created with defaults');
137
+ test.equal(client.port, 110, 'Should use default port');
138
+ test.equal(client.secureConnection, false, 'Should default to non-secure');
139
+ test.done();
140
+ };
141
+
142
+ module.exports['Connection Edge: Port number handling'] = test => {
143
+ // ImapFlow sets default port based on secure flag
144
+ let client1 = new ImapFlow({
145
+ host: 'imap.example.com',
146
+ secure: false,
147
+ auth: { user: 'test', pass: 'test' }
148
+ });
149
+ test.equal(client1.port, 110, 'Default non-secure port');
150
+
151
+ let client2 = new ImapFlow({
152
+ host: 'imap.example.com',
153
+ port: 65535,
154
+ auth: { user: 'test', pass: 'test' }
155
+ });
156
+ test.equal(client2.port, 65535, 'Custom port accepted');
157
+
158
+ test.done();
159
+ };
160
+
161
+ module.exports['Connection Edge: STARTTLS misconfiguration'] = test => {
162
+ let client = new ImapFlow({
163
+ host: 'imap.example.com',
164
+ port: 143,
165
+ secure: true,
166
+ doSTARTTLS: true,
167
+ auth: { user: 'test', pass: 'test' }
168
+ });
169
+
170
+ // Test that upgradeToSTARTTLS throws on misconfiguration
171
+ client.upgradeToSTARTTLS().catch(err => {
172
+ test.ok(err);
173
+ test.ok(err.message.includes('Misconfiguration'), 'Should detect STARTTLS misconfiguration');
174
+ test.done();
175
+ });
176
+ };
177
+
178
+ module.exports['Connection Edge: Socket timeout handling'] = test => {
179
+ let client = new ImapFlow({
180
+ host: 'imap.example.com',
181
+ port: 993,
182
+ auth: { user: 'test', pass: 'test' }
183
+ });
184
+
185
+ // Create a mock socket
186
+ client.socket = new EventEmitter();
187
+ client.socket.destroyed = false;
188
+ client.socket.destroy = () => {};
189
+ client.writeSocket = client.socket;
190
+ client.writeSocket.destroy = () => {};
191
+
192
+ let errorEmitted = false;
193
+ client.on('error', err => {
194
+ errorEmitted = true;
195
+ test.equal(err.code, 'ETIMEOUT');
196
+ });
197
+
198
+ // Set up socket handlers
199
+ client.setSocketHandlers();
200
+
201
+ // Simulate timeout on non-idle connection
202
+ client.idling = false;
203
+ client.socket.emit('timeout');
204
+
205
+ test.ok(errorEmitted, 'Timeout error should be emitted');
206
+ test.done();
207
+ };
208
+
209
+ module.exports['Connection Edge: Socket timeout during IDLE'] = test => {
210
+ let client = new ImapFlow({
211
+ host: 'imap.example.com',
212
+ port: 993,
213
+ auth: { user: 'test', pass: 'test' }
214
+ });
215
+
216
+ // Create mock socket and methods
217
+ client.socket = new EventEmitter();
218
+ client.socket.destroyed = false;
219
+ client.writeSocket = client.socket;
220
+ client.usable = true;
221
+ client.idling = true;
222
+
223
+ // Mock run and idle methods
224
+ let noopCalled = false;
225
+ let idleCalled = false;
226
+
227
+ client.run = async (command) => {
228
+ if (command === 'NOOP') {
229
+ noopCalled = true;
230
+ return Promise.resolve();
231
+ }
232
+ };
233
+
234
+ client.idle = async () => {
235
+ idleCalled = true;
236
+ return Promise.resolve();
237
+ };
238
+
239
+ // Set up socket handlers
240
+ client.setSocketHandlers();
241
+
242
+ // Simulate timeout during IDLE
243
+ client.socket.emit('timeout');
244
+
245
+ // Give async operations time to complete
246
+ setTimeout(() => {
247
+ test.ok(noopCalled, 'NOOP should be called to recover from IDLE timeout');
248
+ test.ok(idleCalled, 'Should return to IDLE after NOOP');
249
+ test.done();
250
+ }, 100);
251
+ };
252
+
253
+ module.exports['Connection Edge: Clear socket handlers'] = test => {
254
+ let client = new ImapFlow({
255
+ host: 'imap.example.com',
256
+ port: 993,
257
+ auth: { user: 'test', pass: 'test' }
258
+ });
259
+
260
+ // Create a mock socket
261
+ client.socket = new EventEmitter();
262
+ client.writeSocket = client.socket;
263
+
264
+ // Set handlers
265
+ client.setSocketHandlers();
266
+
267
+ // Verify handlers are set
268
+ test.equal(client.socket.listenerCount('error'), 1);
269
+ test.equal(client.socket.listenerCount('close'), 1);
270
+ test.equal(client.socket.listenerCount('end'), 1);
271
+ test.equal(client.socket.listenerCount('timeout'), 1);
272
+
273
+ // Clear handlers
274
+ client.clearSocketHandlers();
275
+
276
+ // Verify handlers are removed
277
+ test.equal(client.socket.listenerCount('error'), 0);
278
+ test.equal(client.socket.listenerCount('close'), 0);
279
+ test.equal(client.socket.listenerCount('end'), 0);
280
+ test.equal(client.socket.listenerCount('timeout'), 0);
281
+
282
+ test.done();
283
+ };
284
+
285
+ module.exports['Connection Edge: Write with null socket'] = test => {
286
+ let client = new ImapFlow({
287
+ host: 'imap.example.com',
288
+ port: 993,
289
+ auth: { user: 'test', pass: 'test' }
290
+ });
291
+
292
+ // Set socket to null
293
+ client.socket = null;
294
+
295
+ test.throws(() => {
296
+ client.write('TEST');
297
+ }, /Socket is already closed/, 'Should throw when socket is null');
298
+
299
+ test.done();
300
+ };
301
+
302
+ module.exports['Connection Edge: Compression error handling'] = test => {
303
+ let client = new ImapFlow({
304
+ host: 'imap.example.com',
305
+ port: 993,
306
+ auth: { user: 'test', pass: 'test' }
307
+ });
308
+
309
+ // Create mock socket and streams
310
+ client.socket = new EventEmitter();
311
+ client.socket.pipe = () => client.socket;
312
+ client.socket.unpipe = () => {};
313
+ client.streamer = new EventEmitter();
314
+
315
+ // Mock run method to simulate successful COMPRESS negotiation
316
+ client.run = async (command) => {
317
+ if (command === 'COMPRESS') {
318
+ return true;
319
+ }
320
+ };
321
+
322
+ let errorEmitted = false;
323
+ client.streamer.on('error', err => {
324
+ errorEmitted = true;
325
+ test.ok(err);
326
+ });
327
+
328
+ // Call compress
329
+ client.compress().then(() => {
330
+ // Simulate compression error
331
+ if (client._inflate) {
332
+ client._inflate.emit('error', new Error('Compression failed'));
333
+ }
334
+
335
+ test.ok(errorEmitted, 'Compression error should be propagated');
336
+ test.done();
337
+ });
338
+ };
339
+
340
+ module.exports['Connection Edge: Authentication state after logout'] = test => {
341
+ let client = new ImapFlow({
342
+ host: 'imap.example.com',
343
+ port: 993,
344
+ auth: { user: 'test', pass: 'test' }
345
+ });
346
+
347
+ // Set initial authenticated state
348
+ client.state = client.states.AUTHENTICATED;
349
+ client.authenticated = true;
350
+
351
+ // Simulate logout
352
+ client.state = client.states.LOGOUT;
353
+
354
+ // Try to authenticate
355
+ client.authenticate().catch(err => {
356
+ test.ok(err);
357
+ test.equal(err.message, 'Already logged out');
358
+ test.done();
359
+ });
360
+ };
361
+
362
+ module.exports['Connection Edge: Throttling detection'] = test => {
363
+ let client = new ImapFlow({
364
+ host: 'imap.example.com',
365
+ port: 993,
366
+ auth: { user: 'test', pass: 'test' }
367
+ });
368
+
369
+ // Create mock request
370
+ let errorReceived = null;
371
+ let request = {
372
+ tag: 'A001',
373
+ command: 'FETCH',
374
+ resolve: () => {},
375
+ reject: (err) => {
376
+ errorReceived = err;
377
+ }
378
+ };
379
+
380
+ client.requestTagMap = new Map();
381
+ client.requestTagMap.set('A001', request);
382
+
383
+ // Simulate throttling response
384
+ let parsed = {
385
+ tag: 'A001',
386
+ command: 'BAD',
387
+ attributes: [{
388
+ type: 'TEXT',
389
+ value: 'Request is throttled. Suggested Backoff Time: 5000 milliseconds'
390
+ }]
391
+ };
392
+
393
+ // Mock streamer with proper async iterator
394
+ client.streamer = new EventEmitter();
395
+ client.streamer.read = () => null;
396
+ client.streamer.iterate = async function* () {
397
+ yield {
398
+ next: () => {},
399
+ parsed
400
+ };
401
+ };
402
+
403
+ // Process reader
404
+ client.reader().then(() => {
405
+ if (errorReceived) {
406
+ test.ok(errorReceived, 'Should receive throttle error');
407
+ test.equal(errorReceived.code, 'ETHROTTLE');
408
+ test.equal(errorReceived.throttleReset, 5000);
409
+ }
410
+ test.done();
411
+ }).catch(() => {
412
+ // Reader might fail in test environment
413
+ test.done();
414
+ });
415
+ };
416
+
417
+ module.exports['Connection Edge: Binary data in write'] = test => {
418
+ let client = new ImapFlow({
419
+ host: 'imap.example.com',
420
+ port: 993,
421
+ auth: { user: 'test', pass: 'test' }
422
+ });
423
+
424
+ // Mock socket
425
+ let writtenData = null;
426
+ client.socket = { destroyed: false };
427
+ client.writeSocket = {
428
+ destroyed: false,
429
+ write: (data) => {
430
+ writtenData = data;
431
+ }
432
+ };
433
+
434
+ // Test writing Buffer
435
+ let testBuffer = Buffer.from('TEST');
436
+ client.write(testBuffer);
437
+
438
+ test.ok(Buffer.isBuffer(writtenData));
439
+ test.ok(writtenData.includes(testBuffer));
440
+ test.ok(writtenData.includes(Buffer.from('\r\n')));
441
+
442
+ // Test writing string
443
+ client.commandParts = [];
444
+ client.write('STRING_TEST');
445
+
446
+ test.ok(Buffer.isBuffer(writtenData));
447
+ test.ok(writtenData.includes(Buffer.from('STRING_TEST\r\n')));
448
+
449
+ test.done();
450
+ };
451
+
452
+ module.exports['Connection Edge: Invalid write data type'] = test => {
453
+ let client = new ImapFlow({
454
+ host: 'imap.example.com',
455
+ port: 993,
456
+ auth: { user: 'test', pass: 'test' }
457
+ });
458
+
459
+ // Mock socket
460
+ client.socket = { destroyed: false };
461
+ client.writeSocket = { destroyed: false, write: () => {} };
462
+
463
+ // Test writing invalid data type
464
+ let result = client.write(12345);
465
+ test.equal(result, false, 'Should return false for invalid data type');
466
+
467
+ result = client.write({ test: 'object' });
468
+ test.equal(result, false, 'Should return false for object');
469
+
470
+ test.done();
471
+ };
472
+
473
+ module.exports['Connection Edge: Concurrent connections'] = test => {
474
+ let clients = [];
475
+
476
+ // Create multiple clients
477
+ for (let i = 0; i < 5; i++) {
478
+ let client = new ImapFlow({
479
+ host: `imap${i}.example.com`,
480
+ port: 993,
481
+ auth: { user: `user${i}`, pass: `pass${i}` }
482
+ });
483
+ clients.push(client);
484
+ }
485
+
486
+ // Verify each client has unique ID
487
+ let ids = clients.map(c => c.id);
488
+ let uniqueIds = [...new Set(ids)];
489
+ test.equal(uniqueIds.length, clients.length, 'All client IDs should be unique');
490
+
491
+ // Verify each client has independent state
492
+ clients[0].state = clients[0].states.AUTHENTICATED;
493
+ clients[1].state = clients[1].states.SELECTED;
494
+
495
+ test.equal(clients[0].state, clients[0].states.AUTHENTICATED);
496
+ test.equal(clients[1].state, clients[1].states.SELECTED);
497
+ test.equal(clients[2].state, clients[2].states.NOT_AUTHENTICATED);
498
+
499
+ test.done();
500
+ };
501
+
502
+ module.exports['Connection Edge: Destroyed write socket'] = test => {
503
+ let client = new ImapFlow({
504
+ host: 'imap.example.com',
505
+ port: 993,
506
+ auth: { user: 'test', pass: 'test' }
507
+ });
508
+
509
+ // Mock sockets
510
+ client.socket = { destroyed: false };
511
+ client.writeSocket = { destroyed: true };
512
+
513
+ // Mock close method
514
+ let closeCalled = false;
515
+ client.close = () => {
516
+ closeCalled = true;
517
+ };
518
+
519
+ // Attempt to write
520
+ client.write('TEST');
521
+
522
+ test.ok(closeCalled, 'Should call close when write socket is destroyed');
523
+ test.done();
524
+ };
525
+
526
+ module.exports['Connection Edge: Command after logout'] = test => {
527
+ let client = new ImapFlow({
528
+ host: 'imap.example.com',
529
+ port: 993,
530
+ auth: { user: 'test', pass: 'test' }
531
+ });
532
+
533
+ // Set logout state
534
+ client.state = client.states.LOGOUT;
535
+
536
+ // Create a request
537
+ let errorReceived = null;
538
+ let request = {
539
+ tag: 'A001',
540
+ reject: (err) => {
541
+ errorReceived = err;
542
+ }
543
+ };
544
+
545
+ client.requestTagMap = new Map();
546
+ client.requestTagMap.set('A001', request);
547
+
548
+ // Try to send command
549
+ client.send({ tag: 'A001', command: 'NOOP' }).then(() => {
550
+ test.ok(errorReceived, 'Should reject command after logout');
551
+ test.equal(errorReceived.code, 'NoConnection');
552
+ test.done();
553
+ });
554
+ };
555
+
556
+ module.exports['Connection Edge: Race condition in mailbox lock'] = test => {
557
+ let client = new ImapFlow({
558
+ host: 'imap.example.com',
559
+ port: 993,
560
+ auth: { user: 'test', pass: 'test' }
561
+ });
562
+
563
+ // Initialize locks array
564
+ client.locks = [];
565
+
566
+ // Simulate concurrent lock requests
567
+ let lockPromises = [];
568
+ for (let i = 0; i < 3; i++) {
569
+ lockPromises.push(
570
+ new Promise(resolve => {
571
+ client.locks.push({
572
+ path: 'INBOX',
573
+ resolve,
574
+ promise: new Promise(() => {})
575
+ });
576
+ })
577
+ );
578
+ }
579
+
580
+ test.equal(client.locks.length, 3, 'Should queue multiple lock requests');
581
+
582
+ // Process locks sequentially
583
+ client.locks.forEach((lock, index) => {
584
+ lock.resolve({ path: 'INBOX', index });
585
+ });
586
+
587
+ Promise.all(lockPromises).then(results => {
588
+ test.equal(results.length, 3, 'All lock requests should be resolved');
589
+ test.done();
590
+ });
591
+ };
592
+
593
+ module.exports['Connection Edge: Capability update after STARTTLS'] = test => {
594
+ let client = new ImapFlow({
595
+ host: 'imap.example.com',
596
+ port: 143,
597
+ auth: { user: 'test', pass: 'test' }
598
+ });
599
+
600
+ // Mock capabilities
601
+ client.capabilities = new Map();
602
+ client.capabilities.set('STARTTLS', true);
603
+
604
+ // Mock run method
605
+ client.run = async (command) => {
606
+ if (command === 'STARTTLS') {
607
+ client.expectCapabilityUpdate = true;
608
+ return true;
609
+ }
610
+ if (command === 'CAPABILITY') {
611
+ return true;
612
+ }
613
+ };
614
+
615
+ // Mock socket upgrade
616
+ client.socket = new EventEmitter();
617
+ client.socket.unpipe = () => {};
618
+ client.streamer = new EventEmitter();
619
+
620
+ // Override the upgradeToSTARTTLS to test capability update
621
+ client.upgradeToSTARTTLS().then(result => {
622
+ test.ok(result, 'Should successfully upgrade to TLS');
623
+ test.ok(client.expectCapabilityUpdate, 'Should expect capability update');
624
+ test.done();
625
+ }).catch(() => {
626
+ // Expected for this mock setup
627
+ test.done();
628
+ });
629
+ };