imapflow 1.0.193 → 1.0.195
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/.github/workflows/test.yml +1 -1
- package/CHANGELOG.md +21 -0
- package/lib/commands/fetch.js +17 -4
- package/lib/handler/imap-stream.js +7 -0
- package/lib/imap-flow.js +336 -265
- package/package.json +8 -8
- package/test/connection-edge-cases-test.js +629 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "imapflow",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.195",
|
|
4
4
|
"description": "IMAP Client for Node",
|
|
5
5
|
"main": "lib/imap-flow.js",
|
|
6
6
|
"types": "lib/imap-flow.d.ts",
|
|
@@ -31,8 +31,8 @@
|
|
|
31
31
|
"@babel/eslint-parser": "7.28.0",
|
|
32
32
|
"@babel/eslint-plugin": "7.27.1",
|
|
33
33
|
"@babel/plugin-syntax-class-properties": "7.12.13",
|
|
34
|
-
"@babel/preset-env": "7.28.
|
|
35
|
-
"@types/node": "24.
|
|
34
|
+
"@babel/preset-env": "7.28.3",
|
|
35
|
+
"@types/node": "24.3.0",
|
|
36
36
|
"eslint": "8.57.0",
|
|
37
37
|
"eslint-config-nodemailer": "1.2.0",
|
|
38
38
|
"eslint-config-prettier": "9.1.0",
|
|
@@ -42,18 +42,18 @@
|
|
|
42
42
|
"grunt-eslint": "24.3.0",
|
|
43
43
|
"imapflow-jsdoc-template": "3.4.0-imapflow.3",
|
|
44
44
|
"jsdoc": "4.0.4",
|
|
45
|
-
"st": "3.0.
|
|
46
|
-
"typescript": "5.
|
|
45
|
+
"st": "3.0.3",
|
|
46
|
+
"typescript": "5.9.2"
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
49
|
"encoding-japanese": "2.2.0",
|
|
50
|
-
"iconv-lite": "0.
|
|
50
|
+
"iconv-lite": "0.7.0",
|
|
51
51
|
"libbase64": "1.3.0",
|
|
52
52
|
"libmime": "5.3.7",
|
|
53
53
|
"libqp": "2.1.1",
|
|
54
54
|
"mailsplit": "5.4.6",
|
|
55
55
|
"nodemailer": "7.0.5",
|
|
56
|
-
"pino": "9.
|
|
57
|
-
"socks": "2.8.
|
|
56
|
+
"pino": "9.9.0",
|
|
57
|
+
"socks": "2.8.7"
|
|
58
58
|
}
|
|
59
59
|
}
|
|
@@ -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
|
+
};
|