imapflow 1.7.8 → 2.0.0
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/CHANGELOG.md +20 -0
- package/README.md +8 -2
- package/dist/cjs/charsets.d.ts +1 -0
- package/dist/cjs/charsets.js +294 -0
- package/dist/cjs/commands/append.d.ts +22 -0
- package/dist/cjs/commands/append.js +151 -0
- package/dist/cjs/commands/authenticate.d.ts +24 -0
- package/dist/cjs/commands/authenticate.js +223 -0
- package/dist/cjs/commands/capability.d.ts +8 -0
- package/dist/cjs/commands/capability.js +32 -0
- package/dist/cjs/commands/close.d.ts +8 -0
- package/dist/cjs/commands/close.js +39 -0
- package/dist/cjs/commands/compress.d.ts +8 -0
- package/dist/cjs/commands/compress.js +56 -0
- package/dist/cjs/commands/copy.d.ts +13 -0
- package/dist/cjs/commands/copy.js +44 -0
- package/dist/cjs/commands/copyuid-parser.d.ts +11 -0
- package/dist/cjs/commands/copyuid-parser.js +32 -0
- package/dist/cjs/commands/create.d.ts +11 -0
- package/dist/cjs/commands/create.js +80 -0
- package/dist/cjs/commands/delete.d.ts +11 -0
- package/dist/cjs/commands/delete.js +40 -0
- package/dist/cjs/commands/enable.d.ts +9 -0
- package/dist/cjs/commands/enable.js +61 -0
- package/dist/cjs/commands/esearch-parser.d.ts +17 -0
- package/dist/cjs/commands/esearch-parser.js +91 -0
- package/dist/cjs/commands/expunge.d.ts +12 -0
- package/dist/cjs/commands/expunge.js +60 -0
- package/dist/cjs/commands/fetch.d.ts +30 -0
- package/dist/cjs/commands/fetch.js +241 -0
- package/dist/cjs/commands/id.d.ts +10 -0
- package/dist/cjs/commands/id.js +80 -0
- package/dist/cjs/commands/idle.d.ts +9 -0
- package/dist/cjs/commands/idle.js +347 -0
- package/dist/cjs/commands/list.d.ts +16 -0
- package/dist/cjs/commands/list.js +518 -0
- package/dist/cjs/commands/login.d.ts +11 -0
- package/dist/cjs/commands/login.js +42 -0
- package/dist/cjs/commands/logout.d.ts +8 -0
- package/dist/cjs/commands/logout.js +47 -0
- package/dist/cjs/commands/move.d.ts +13 -0
- package/dist/cjs/commands/move.js +57 -0
- package/dist/cjs/commands/namespace.d.ts +25 -0
- package/dist/cjs/commands/namespace.js +139 -0
- package/dist/cjs/commands/noop.d.ts +8 -0
- package/dist/cjs/commands/noop.js +22 -0
- package/dist/cjs/commands/quota.d.ts +10 -0
- package/dist/cjs/commands/quota.js +119 -0
- package/dist/cjs/commands/rename.d.ts +12 -0
- package/dist/cjs/commands/rename.js +48 -0
- package/dist/cjs/commands/search.d.ts +15 -0
- package/dist/cjs/commands/search.js +228 -0
- package/dist/cjs/commands/select.d.ts +25 -0
- package/dist/cjs/commands/select.js +250 -0
- package/dist/cjs/commands/starttls.d.ts +8 -0
- package/dist/cjs/commands/starttls.js +30 -0
- package/dist/cjs/commands/status-fields.d.ts +14 -0
- package/dist/cjs/commands/status-fields.js +61 -0
- package/dist/cjs/commands/status.d.ts +12 -0
- package/dist/cjs/commands/status.js +108 -0
- package/dist/cjs/commands/store.d.ts +19 -0
- package/dist/cjs/commands/store.js +93 -0
- package/dist/cjs/commands/subscribe.d.ts +9 -0
- package/dist/cjs/commands/subscribe.js +31 -0
- package/dist/cjs/commands/unsubscribe.d.ts +9 -0
- package/dist/cjs/commands/unsubscribe.js +31 -0
- package/dist/cjs/connection-deadline.d.ts +49 -0
- package/dist/cjs/connection-deadline.js +91 -0
- package/dist/cjs/errors.d.ts +83 -0
- package/dist/cjs/errors.js +13 -0
- package/dist/cjs/handler/imap-compiler.d.ts +24 -0
- package/dist/cjs/handler/imap-compiler.js +285 -0
- package/dist/cjs/handler/imap-formal-syntax.d.ts +28 -0
- package/dist/cjs/handler/imap-formal-syntax.js +121 -0
- package/dist/cjs/handler/imap-handler.d.ts +9 -0
- package/dist/cjs/handler/imap-handler.js +10 -0
- package/dist/cjs/handler/imap-parser.d.ts +16 -0
- package/dist/cjs/handler/imap-parser.js +90 -0
- package/dist/cjs/handler/imap-stream.d.ts +181 -0
- package/dist/cjs/handler/imap-stream.js +446 -0
- package/dist/cjs/handler/limits.d.ts +25 -0
- package/dist/cjs/handler/limits.js +51 -0
- package/dist/cjs/handler/parser-instance.d.ts +68 -0
- package/dist/cjs/handler/parser-instance.js +223 -0
- package/dist/cjs/handler/token-parser.d.ts +91 -0
- package/dist/cjs/handler/token-parser.js +673 -0
- package/dist/cjs/handler/types.d.ts +91 -0
- package/dist/cjs/handler/types.js +4 -0
- package/dist/cjs/imap-commands.d.ts +16 -0
- package/dist/cjs/imap-commands.js +74 -0
- package/dist/cjs/imap-flow.d.ts +676 -0
- package/dist/cjs/imap-flow.js +3949 -0
- package/dist/cjs/jp-decoder.d.ts +12 -0
- package/dist/cjs/jp-decoder.js +79 -0
- package/dist/cjs/limited-passthrough.d.ts +25 -0
- package/dist/cjs/limited-passthrough.js +54 -0
- package/dist/cjs/logger.d.ts +3 -0
- package/dist/cjs/logger.js +11 -0
- package/dist/cjs/package-info.d.ts +3 -0
- package/dist/cjs/package-info.js +7 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/proxy-connection.d.ts +33 -0
- package/dist/cjs/proxy-connection.js +392 -0
- package/dist/cjs/search-compiler.d.ts +34 -0
- package/dist/cjs/search-compiler.js +476 -0
- package/dist/cjs/special-use.d.ts +22 -0
- package/dist/cjs/special-use.js +911 -0
- package/dist/cjs/tools.d.ts +427 -0
- package/dist/cjs/tools.js +1496 -0
- package/{lib/imap-flow.d.ts → dist/cjs/types.d.ts} +386 -516
- package/dist/cjs/types.js +5 -0
- package/dist/esm/charsets.d.ts +1 -0
- package/{lib → dist/esm}/charsets.js +1 -6
- package/dist/esm/commands/append.d.ts +22 -0
- package/{lib → dist/esm}/commands/append.js +22 -52
- package/dist/esm/commands/authenticate.d.ts +24 -0
- package/{lib → dist/esm}/commands/authenticate.js +62 -87
- package/dist/esm/commands/capability.d.ts +8 -0
- package/{lib → dist/esm}/commands/capability.js +6 -9
- package/dist/esm/commands/close.d.ts +8 -0
- package/{lib → dist/esm}/commands/close.js +6 -10
- package/dist/esm/commands/compress.d.ts +8 -0
- package/{lib → dist/esm}/commands/compress.js +7 -11
- package/dist/esm/commands/copy.d.ts +13 -0
- package/{lib → dist/esm}/commands/copy.js +12 -20
- package/dist/esm/commands/copyuid-parser.d.ts +11 -0
- package/{lib → dist/esm}/commands/copyuid-parser.js +9 -15
- package/dist/esm/commands/create.d.ts +11 -0
- package/{lib → dist/esm}/commands/create.js +13 -27
- package/dist/esm/commands/delete.d.ts +11 -0
- package/{lib → dist/esm}/commands/delete.js +9 -14
- package/dist/esm/commands/enable.d.ts +9 -0
- package/{lib → dist/esm}/commands/enable.js +23 -30
- package/dist/esm/commands/esearch-parser.d.ts +17 -0
- package/dist/esm/commands/esearch-parser.js +88 -0
- package/dist/esm/commands/expunge.d.ts +12 -0
- package/{lib → dist/esm}/commands/expunge.js +17 -22
- package/dist/esm/commands/fetch.d.ts +30 -0
- package/{lib → dist/esm}/commands/fetch.js +32 -64
- package/dist/esm/commands/id.d.ts +10 -0
- package/{lib → dist/esm}/commands/id.js +17 -23
- package/dist/esm/commands/idle.d.ts +9 -0
- package/{lib → dist/esm}/commands/idle.js +47 -81
- package/dist/esm/commands/list.d.ts +16 -0
- package/{lib → dist/esm}/commands/list.js +56 -121
- package/dist/esm/commands/login.d.ts +11 -0
- package/{lib → dist/esm}/commands/login.js +10 -15
- package/dist/esm/commands/logout.d.ts +8 -0
- package/{lib → dist/esm}/commands/logout.js +9 -11
- package/dist/esm/commands/move.d.ts +13 -0
- package/{lib → dist/esm}/commands/move.js +13 -21
- package/dist/esm/commands/namespace.d.ts +25 -0
- package/{lib → dist/esm}/commands/namespace.js +34 -44
- package/dist/esm/commands/noop.d.ts +8 -0
- package/{lib → dist/esm}/commands/noop.js +6 -7
- package/dist/esm/commands/quota.d.ts +10 -0
- package/{lib → dist/esm}/commands/quota.js +18 -36
- package/dist/esm/commands/rename.d.ts +12 -0
- package/{lib → dist/esm}/commands/rename.js +10 -15
- package/dist/esm/commands/search.d.ts +15 -0
- package/{lib → dist/esm}/commands/search.js +36 -135
- package/dist/esm/commands/select.d.ts +25 -0
- package/{lib → dist/esm}/commands/select.js +33 -64
- package/dist/esm/commands/starttls.d.ts +8 -0
- package/{lib → dist/esm}/commands/starttls.js +6 -8
- package/dist/esm/commands/status-fields.d.ts +14 -0
- package/{lib → dist/esm}/commands/status-fields.js +5 -16
- package/dist/esm/commands/status.d.ts +12 -0
- package/{lib → dist/esm}/commands/status.js +18 -29
- package/dist/esm/commands/store.d.ts +19 -0
- package/{lib → dist/esm}/commands/store.js +24 -37
- package/dist/esm/commands/subscribe.d.ts +9 -0
- package/{lib → dist/esm}/commands/subscribe.js +8 -12
- package/dist/esm/commands/unsubscribe.d.ts +9 -0
- package/{lib → dist/esm}/commands/unsubscribe.js +8 -12
- package/dist/esm/connection-deadline.d.ts +49 -0
- package/{lib → dist/esm}/connection-deadline.js +14 -25
- package/dist/esm/errors.d.ts +83 -0
- package/dist/esm/errors.js +9 -0
- package/dist/esm/handler/imap-compiler.d.ts +24 -0
- package/{lib → dist/esm}/handler/imap-compiler.js +22 -80
- package/dist/esm/handler/imap-formal-syntax.d.ts +28 -0
- package/dist/esm/handler/imap-formal-syntax.js +117 -0
- package/dist/esm/handler/imap-handler.d.ts +9 -0
- package/dist/esm/handler/imap-handler.js +9 -0
- package/dist/esm/handler/imap-parser.d.ts +16 -0
- package/{lib → dist/esm}/handler/imap-parser.js +31 -44
- package/dist/esm/handler/imap-stream.d.ts +181 -0
- package/{lib → dist/esm}/handler/imap-stream.js +29 -121
- package/dist/esm/handler/limits.d.ts +25 -0
- package/{lib → dist/esm}/handler/limits.js +13 -22
- package/dist/esm/handler/parser-instance.d.ts +68 -0
- package/{lib → dist/esm}/handler/parser-instance.js +19 -47
- package/dist/esm/handler/token-parser.d.ts +91 -0
- package/{lib → dist/esm}/handler/token-parser.js +71 -155
- package/dist/esm/handler/types.d.ts +91 -0
- package/dist/esm/handler/types.js +3 -0
- package/dist/esm/imap-commands.d.ts +16 -0
- package/dist/esm/imap-commands.js +67 -0
- package/dist/esm/imap-flow.d.ts +676 -0
- package/{lib → dist/esm}/imap-flow.js +761 -1789
- package/dist/esm/jp-decoder.d.ts +12 -0
- package/{lib → dist/esm}/jp-decoder.js +6 -21
- package/dist/esm/limited-passthrough.d.ts +25 -0
- package/{lib → dist/esm}/limited-passthrough.js +7 -20
- package/dist/esm/logger.d.ts +3 -0
- package/dist/esm/logger.js +4 -0
- package/dist/esm/package-info.d.ts +3 -0
- package/dist/esm/package-info.js +4 -0
- package/dist/esm/package.json +3 -0
- package/dist/esm/proxy-connection.d.ts +33 -0
- package/{lib → dist/esm}/proxy-connection.js +56 -127
- package/dist/esm/search-compiler.d.ts +34 -0
- package/{lib → dist/esm}/search-compiler.js +54 -110
- package/dist/esm/special-use.d.ts +22 -0
- package/dist/esm/special-use.js +907 -0
- package/dist/esm/tools.d.ts +427 -0
- package/dist/esm/tools.js +1446 -0
- package/dist/esm/types.d.ts +828 -0
- package/dist/esm/types.js +4 -0
- package/package.json +60 -20
- package/.gitattributes +0 -1
- package/.github/CODE_OF_CONDUCT.md +0 -76
- package/.github/FUNDING.yml +0 -4
- package/.github/ISSUE_TEMPLATE/bug_report.md +0 -40
- package/.github/ISSUE_TEMPLATE/feature_request.md +0 -19
- package/.github/contributing.md +0 -17
- package/.github/workflows/release.yaml +0 -36
- package/.github/workflows/stale.yml +0 -29
- package/.github/workflows/test.yml +0 -51
- package/.ncurc.js +0 -4
- package/.prettierignore +0 -4
- package/.prettierrc.js +0 -8
- package/.release-please-manifest.json +0 -3
- package/CLAUDE.md +0 -104
- package/Gruntfile.js +0 -23
- package/eslint.config.js +0 -45
- package/lib/handler/imap-formal-syntax.js +0 -189
- package/lib/handler/imap-handler.js +0 -17
- package/lib/imap-commands.js +0 -45
- package/lib/logger.js +0 -5
- package/lib/special-use.js +0 -923
- package/lib/tools.js +0 -1612
- package/release-please-config.json +0 -10
- package/test/authentication-test.js +0 -101
- package/test/auto-idle-test.js +0 -470
- package/test/bodystructure-test.js +0 -899
- package/test/charsets-test.js +0 -161
- package/test/commands-branches-test.js +0 -1095
- package/test/commands-integration-test.js +0 -11124
- package/test/commands-test.js +0 -73
- package/test/connection-edge-cases-test.js +0 -1828
- package/test/connection-test.js +0 -162
- package/test/copyuid-parser-test.js +0 -173
- package/test/fetch-generator-test.js +0 -218
- package/test/fixtures/fake-timers.js +0 -115
- package/test/fixtures/serialized-mimetorture.js +0 -2738
- package/test/fixtures/test-client.js +0 -101
- package/test/fixtures/test-tls.js +0 -8
- package/test/handler-branches-test.js +0 -310
- package/test/idle-polling-test.js +0 -518
- package/test/imap-compiler-test.js +0 -809
- package/test/imap-flow-compress-test.js +0 -166
- package/test/imap-flow-coverage-test.js +0 -612
- package/test/imap-flow-fetch-download-test.js +0 -909
- package/test/imap-flow-internals-test.js +0 -725
- package/test/imap-flow-methods-test.js +0 -889
- package/test/imap-flow-proxy-paths-test.js +0 -366
- package/test/imap-flow-secure-test.js +0 -573
- package/test/imap-flow-server-test.js +0 -1474
- package/test/imap-formal-syntax-test.js +0 -293
- package/test/imap-parser-test.js +0 -1474
- package/test/imap-stream-edge-cases-test.js +0 -666
- package/test/imap-stream-test.js +0 -177
- package/test/imapflow-test.js +0 -258
- package/test/integration/README.md +0 -52
- package/test/integration/dovecot-test.conf +0 -27
- package/test/integration/rev2-live-test.js +0 -431
- package/test/integration/run-rev2-tests.sh +0 -75
- package/test/integration-test.js +0 -83
- package/test/jp-decoder-test.js +0 -304
- package/test/limited-passthrough-test.js +0 -299
- package/test/memory-cleanup-test.js +0 -144
- package/test/memory-leak-test.js +0 -667
- package/test/parser-limits-test.js +0 -292
- package/test/proxy-connection-test.js +0 -738
- package/test/reliability-improvements-test.js +0 -548
- package/test/search-compiler-test.js +0 -1300
- package/test/search-test.js +0 -329
- package/test/special-use-test.js +0 -418
- package/test/starttls-injection-test.js +0 -181
- package/test/tag-correlation-test.js +0 -333
- package/test/timer-policy-test.js +0 -227
- package/test/token-parser-test.js +0 -456
- package/test/tools-test.js +0 -2013
- package/test/unhandled-rejection-test.js +0 -661
|
@@ -1,1828 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const { ImapFlow } = require('../lib/imap-flow');
|
|
4
|
-
const { EventEmitter } = require('events');
|
|
5
|
-
const net = require('net');
|
|
6
|
-
|
|
7
|
-
// Edge Cases Tests
|
|
8
|
-
|
|
9
|
-
module.exports['Connection Edge: Socket error during connection'] = test => {
|
|
10
|
-
let client = new ImapFlow({
|
|
11
|
-
host: 'imap.example.com',
|
|
12
|
-
port: 993,
|
|
13
|
-
auth: { user: 'test', pass: 'test' }
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
// Simulate socket error
|
|
17
|
-
let errorEmitted = false;
|
|
18
|
-
client.on('error', err => {
|
|
19
|
-
errorEmitted = true;
|
|
20
|
-
test.ok(err);
|
|
21
|
-
test.equal(err._connId, client.id);
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
// Trigger error through emitError
|
|
25
|
-
let testError = new Error('Socket connection failed');
|
|
26
|
-
testError.code = 'ECONNREFUSED';
|
|
27
|
-
client.emitError(testError);
|
|
28
|
-
|
|
29
|
-
test.ok(errorEmitted, 'Error event should be emitted');
|
|
30
|
-
test.done();
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
module.exports['Connection Edge: Write after socket destroyed'] = test => {
|
|
34
|
-
let client = new ImapFlow({
|
|
35
|
-
host: 'imap.example.com',
|
|
36
|
-
port: 993,
|
|
37
|
-
auth: { user: 'test', pass: 'test' }
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
// Simulate destroyed socket
|
|
41
|
-
client.socket = { destroyed: true };
|
|
42
|
-
|
|
43
|
-
test.throws(
|
|
44
|
-
() => {
|
|
45
|
-
client.write('TEST');
|
|
46
|
-
},
|
|
47
|
-
/Socket is already closed/,
|
|
48
|
-
'Should throw when writing to destroyed socket'
|
|
49
|
-
);
|
|
50
|
-
|
|
51
|
-
test.done();
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
module.exports['Connection Edge: Write after logout'] = test => {
|
|
55
|
-
let client = new ImapFlow({
|
|
56
|
-
host: 'imap.example.com',
|
|
57
|
-
port: 993,
|
|
58
|
-
auth: { user: 'test', pass: 'test' }
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
// Create a mock socket
|
|
62
|
-
client.socket = { destroyed: false };
|
|
63
|
-
client.state = client.states.LOGOUT;
|
|
64
|
-
|
|
65
|
-
test.throws(
|
|
66
|
-
() => {
|
|
67
|
-
client.write('TEST');
|
|
68
|
-
},
|
|
69
|
-
/Can not send data after logged out/,
|
|
70
|
-
'Should throw when writing after logout'
|
|
71
|
-
);
|
|
72
|
-
|
|
73
|
-
test.done();
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
module.exports['Connection Edge: Stats reset functionality'] = test => {
|
|
77
|
-
let client = new ImapFlow({
|
|
78
|
-
host: 'imap.example.com',
|
|
79
|
-
port: 993,
|
|
80
|
-
auth: { user: 'test', pass: 'test' }
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
// Set some initial values
|
|
84
|
-
client.writeBytesCounter = 100;
|
|
85
|
-
client.streamer.readBytesCounter = 200;
|
|
86
|
-
|
|
87
|
-
let stats = client.stats(false);
|
|
88
|
-
test.equal(stats.sent, 100);
|
|
89
|
-
test.equal(stats.received, 200);
|
|
90
|
-
|
|
91
|
-
// Reset stats
|
|
92
|
-
stats = client.stats(true);
|
|
93
|
-
test.equal(stats.sent, 100, 'Should return old value before reset');
|
|
94
|
-
test.equal(stats.received, 200, 'Should return old value before reset');
|
|
95
|
-
|
|
96
|
-
// Check if reset worked
|
|
97
|
-
test.equal(client.writeBytesCounter, 0, 'Write counter should be reset');
|
|
98
|
-
test.equal(client.streamer.readBytesCounter, 0, 'Read counter should be reset');
|
|
99
|
-
|
|
100
|
-
test.done();
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
module.exports['Connection Edge: Multiple error handlers'] = test => {
|
|
104
|
-
let client = new ImapFlow({
|
|
105
|
-
host: 'imap.example.com',
|
|
106
|
-
port: 993,
|
|
107
|
-
auth: { user: 'test', pass: 'test' }
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
let errorCount = 0;
|
|
111
|
-
|
|
112
|
-
// Add multiple error handlers
|
|
113
|
-
client.on('error', () => errorCount++);
|
|
114
|
-
client.on('error', () => errorCount++);
|
|
115
|
-
client.on('error', () => errorCount++);
|
|
116
|
-
|
|
117
|
-
// Emit an error
|
|
118
|
-
client.emitError(new Error('Test error'));
|
|
119
|
-
|
|
120
|
-
test.equal(errorCount, 3, 'All error handlers should be called');
|
|
121
|
-
test.done();
|
|
122
|
-
};
|
|
123
|
-
|
|
124
|
-
module.exports['Connection Edge: Connection with valid empty auth'] = test => {
|
|
125
|
-
// ImapFlow actually allows creating client without throwing
|
|
126
|
-
// The error would occur during connection
|
|
127
|
-
let client = new ImapFlow({
|
|
128
|
-
host: 'imap.example.com',
|
|
129
|
-
port: 993,
|
|
130
|
-
auth: { user: 'test' }
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
test.ok(client, 'Client should be created even with partial auth');
|
|
134
|
-
test.equal(client.host, 'imap.example.com');
|
|
135
|
-
test.done();
|
|
136
|
-
};
|
|
137
|
-
|
|
138
|
-
module.exports['Connection Edge: Connection with default values'] = test => {
|
|
139
|
-
// ImapFlow sets default values for missing options
|
|
140
|
-
let client = new ImapFlow({
|
|
141
|
-
host: 'imap.example.com',
|
|
142
|
-
auth: { user: 'test', pass: 'test' }
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
test.ok(client, 'Client should be created with defaults');
|
|
146
|
-
test.equal(client.port, 143, 'Should use default port');
|
|
147
|
-
test.equal(client.secureConnection, false, 'Should default to non-secure');
|
|
148
|
-
test.done();
|
|
149
|
-
};
|
|
150
|
-
|
|
151
|
-
module.exports['Connection Edge: Port number handling'] = test => {
|
|
152
|
-
// ImapFlow sets default port based on secure flag
|
|
153
|
-
let client1 = new ImapFlow({
|
|
154
|
-
host: 'imap.example.com',
|
|
155
|
-
secure: false,
|
|
156
|
-
auth: { user: 'test', pass: 'test' }
|
|
157
|
-
});
|
|
158
|
-
test.equal(client1.port, 143, 'Default non-secure port');
|
|
159
|
-
|
|
160
|
-
let client2 = new ImapFlow({
|
|
161
|
-
host: 'imap.example.com',
|
|
162
|
-
port: 65535,
|
|
163
|
-
auth: { user: 'test', pass: 'test' }
|
|
164
|
-
});
|
|
165
|
-
test.equal(client2.port, 65535, 'Custom port accepted');
|
|
166
|
-
|
|
167
|
-
test.done();
|
|
168
|
-
};
|
|
169
|
-
|
|
170
|
-
module.exports['Connection Edge: STARTTLS misconfiguration'] = test => {
|
|
171
|
-
let client = new ImapFlow({
|
|
172
|
-
host: 'imap.example.com',
|
|
173
|
-
port: 143,
|
|
174
|
-
secure: true,
|
|
175
|
-
doSTARTTLS: true,
|
|
176
|
-
auth: { user: 'test', pass: 'test' }
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
// Test that upgradeToSTARTTLS throws on misconfiguration
|
|
180
|
-
client.upgradeToSTARTTLS().catch(err => {
|
|
181
|
-
test.ok(err);
|
|
182
|
-
test.ok(err.message.includes('Misconfiguration'), 'Should detect STARTTLS misconfiguration');
|
|
183
|
-
test.done();
|
|
184
|
-
});
|
|
185
|
-
};
|
|
186
|
-
|
|
187
|
-
module.exports['Connection Edge: Socket timeout handling'] = test => {
|
|
188
|
-
let client = new ImapFlow({
|
|
189
|
-
host: 'imap.example.com',
|
|
190
|
-
port: 993,
|
|
191
|
-
auth: { user: 'test', pass: 'test' }
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
// Create a mock socket
|
|
195
|
-
client.socket = new EventEmitter();
|
|
196
|
-
client.socket.destroyed = false;
|
|
197
|
-
client.socket.destroy = () => {};
|
|
198
|
-
client.writeSocket = client.socket;
|
|
199
|
-
client.writeSocket.destroy = () => {};
|
|
200
|
-
|
|
201
|
-
let errorEmitted = false;
|
|
202
|
-
client.on('error', err => {
|
|
203
|
-
errorEmitted = true;
|
|
204
|
-
test.equal(err.code, 'ETIMEOUT');
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
// Set up socket handlers
|
|
208
|
-
client.setSocketHandlers();
|
|
209
|
-
|
|
210
|
-
// Simulate timeout on non-idle connection
|
|
211
|
-
client.idling = false;
|
|
212
|
-
client.socket.emit('timeout');
|
|
213
|
-
|
|
214
|
-
test.ok(errorEmitted, 'Timeout error should be emitted');
|
|
215
|
-
test.done();
|
|
216
|
-
};
|
|
217
|
-
|
|
218
|
-
module.exports['Connection Edge: Socket timeout during IDLE'] = test => {
|
|
219
|
-
let client = new ImapFlow({
|
|
220
|
-
host: 'imap.example.com',
|
|
221
|
-
port: 993,
|
|
222
|
-
auth: { user: 'test', pass: 'test' }
|
|
223
|
-
});
|
|
224
|
-
|
|
225
|
-
// Create mock socket and methods
|
|
226
|
-
client.socket = new EventEmitter();
|
|
227
|
-
client.socket.destroyed = false;
|
|
228
|
-
client.writeSocket = client.socket;
|
|
229
|
-
client.usable = true;
|
|
230
|
-
client.idling = true;
|
|
231
|
-
|
|
232
|
-
// Mock run and idle methods
|
|
233
|
-
let noopCalled = false;
|
|
234
|
-
let idleCalled = false;
|
|
235
|
-
|
|
236
|
-
client.run = async command => {
|
|
237
|
-
if (command === 'NOOP') {
|
|
238
|
-
noopCalled = true;
|
|
239
|
-
return Promise.resolve();
|
|
240
|
-
}
|
|
241
|
-
};
|
|
242
|
-
|
|
243
|
-
client.idle = async () => {
|
|
244
|
-
idleCalled = true;
|
|
245
|
-
return Promise.resolve();
|
|
246
|
-
};
|
|
247
|
-
|
|
248
|
-
// Set up socket handlers
|
|
249
|
-
client.setSocketHandlers();
|
|
250
|
-
|
|
251
|
-
// Simulate timeout during IDLE
|
|
252
|
-
client.socket.emit('timeout');
|
|
253
|
-
|
|
254
|
-
// Give async operations time to complete
|
|
255
|
-
setTimeout(() => {
|
|
256
|
-
test.ok(noopCalled, 'NOOP should be called to recover from IDLE timeout');
|
|
257
|
-
// Returning to IDLE is autoidle()'s decision once the NOOP settles (run() re-arms it);
|
|
258
|
-
// the watchdog handler itself must not bypass the busy guard by calling idle() directly
|
|
259
|
-
test.equal(idleCalled, false, 'the timeout handler does not restart IDLE by itself');
|
|
260
|
-
test.done();
|
|
261
|
-
}, 100);
|
|
262
|
-
};
|
|
263
|
-
|
|
264
|
-
module.exports['Connection Edge: Clear socket handlers'] = test => {
|
|
265
|
-
let client = new ImapFlow({
|
|
266
|
-
host: 'imap.example.com',
|
|
267
|
-
port: 993,
|
|
268
|
-
auth: { user: 'test', pass: 'test' }
|
|
269
|
-
});
|
|
270
|
-
|
|
271
|
-
// Create a mock socket
|
|
272
|
-
client.socket = new EventEmitter();
|
|
273
|
-
client.writeSocket = client.socket;
|
|
274
|
-
|
|
275
|
-
// Set handlers
|
|
276
|
-
client.setSocketHandlers();
|
|
277
|
-
|
|
278
|
-
// Verify handlers are set
|
|
279
|
-
test.equal(client.socket.listenerCount('error'), 1);
|
|
280
|
-
test.equal(client.socket.listenerCount('close'), 1);
|
|
281
|
-
test.equal(client.socket.listenerCount('end'), 1);
|
|
282
|
-
test.equal(client.socket.listenerCount('timeout'), 1);
|
|
283
|
-
|
|
284
|
-
// Clear handlers
|
|
285
|
-
client.clearSocketHandlers();
|
|
286
|
-
|
|
287
|
-
// Verify handlers are removed
|
|
288
|
-
test.equal(client.socket.listenerCount('error'), 0);
|
|
289
|
-
test.equal(client.socket.listenerCount('close'), 0);
|
|
290
|
-
test.equal(client.socket.listenerCount('end'), 0);
|
|
291
|
-
test.equal(client.socket.listenerCount('timeout'), 0);
|
|
292
|
-
|
|
293
|
-
test.done();
|
|
294
|
-
};
|
|
295
|
-
|
|
296
|
-
module.exports['Connection Edge: Write with null socket'] = test => {
|
|
297
|
-
let client = new ImapFlow({
|
|
298
|
-
host: 'imap.example.com',
|
|
299
|
-
port: 993,
|
|
300
|
-
auth: { user: 'test', pass: 'test' }
|
|
301
|
-
});
|
|
302
|
-
|
|
303
|
-
// Set socket to null
|
|
304
|
-
client.socket = null;
|
|
305
|
-
|
|
306
|
-
test.throws(
|
|
307
|
-
() => {
|
|
308
|
-
client.write('TEST');
|
|
309
|
-
},
|
|
310
|
-
/Socket is already closed/,
|
|
311
|
-
'Should throw when socket is null'
|
|
312
|
-
);
|
|
313
|
-
|
|
314
|
-
test.done();
|
|
315
|
-
};
|
|
316
|
-
|
|
317
|
-
module.exports['Connection Edge: Compression error handling'] = test => {
|
|
318
|
-
let client = new ImapFlow({
|
|
319
|
-
host: 'imap.example.com',
|
|
320
|
-
port: 993,
|
|
321
|
-
auth: { user: 'test', pass: 'test' }
|
|
322
|
-
});
|
|
323
|
-
|
|
324
|
-
// Create mock socket and streams
|
|
325
|
-
client.socket = new EventEmitter();
|
|
326
|
-
client.socket.pipe = () => client.socket;
|
|
327
|
-
client.socket.unpipe = () => {};
|
|
328
|
-
client.streamer = new EventEmitter();
|
|
329
|
-
|
|
330
|
-
// Mock run method to simulate successful COMPRESS negotiation
|
|
331
|
-
client.run = async command => {
|
|
332
|
-
if (command === 'COMPRESS') {
|
|
333
|
-
return true;
|
|
334
|
-
}
|
|
335
|
-
};
|
|
336
|
-
|
|
337
|
-
let errorEmitted = false;
|
|
338
|
-
client.streamer.on('error', err => {
|
|
339
|
-
errorEmitted = true;
|
|
340
|
-
test.ok(err);
|
|
341
|
-
});
|
|
342
|
-
|
|
343
|
-
// Call compress
|
|
344
|
-
client.compress().then(() => {
|
|
345
|
-
// Simulate compression error
|
|
346
|
-
if (client._inflate) {
|
|
347
|
-
client._inflate.emit('error', new Error('Compression failed'));
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
test.ok(errorEmitted, 'Compression error should be propagated');
|
|
351
|
-
test.done();
|
|
352
|
-
});
|
|
353
|
-
};
|
|
354
|
-
|
|
355
|
-
module.exports['Connection Edge: Authentication state after logout'] = test => {
|
|
356
|
-
let client = new ImapFlow({
|
|
357
|
-
host: 'imap.example.com',
|
|
358
|
-
port: 993,
|
|
359
|
-
auth: { user: 'test', pass: 'test' }
|
|
360
|
-
});
|
|
361
|
-
|
|
362
|
-
// Set initial authenticated state
|
|
363
|
-
client.state = client.states.AUTHENTICATED;
|
|
364
|
-
client.authenticated = true;
|
|
365
|
-
|
|
366
|
-
// Simulate logout
|
|
367
|
-
client.state = client.states.LOGOUT;
|
|
368
|
-
|
|
369
|
-
// Try to authenticate
|
|
370
|
-
client.authenticate().catch(err => {
|
|
371
|
-
test.ok(err);
|
|
372
|
-
test.equal(err.message, 'Already logged out');
|
|
373
|
-
test.done();
|
|
374
|
-
});
|
|
375
|
-
};
|
|
376
|
-
|
|
377
|
-
module.exports['Connection Edge: Throttling detection'] = test => {
|
|
378
|
-
let client = new ImapFlow({
|
|
379
|
-
host: 'imap.example.com',
|
|
380
|
-
port: 993,
|
|
381
|
-
auth: { user: 'test', pass: 'test' }
|
|
382
|
-
});
|
|
383
|
-
|
|
384
|
-
// Create mock request
|
|
385
|
-
let errorReceived = null;
|
|
386
|
-
let request = {
|
|
387
|
-
tag: 'A001',
|
|
388
|
-
command: 'FETCH',
|
|
389
|
-
resolve: () => {},
|
|
390
|
-
reject: err => {
|
|
391
|
-
errorReceived = err;
|
|
392
|
-
}
|
|
393
|
-
};
|
|
394
|
-
|
|
395
|
-
client.requestTagMap = new Map();
|
|
396
|
-
client.requestTagMap.set('A001', request);
|
|
397
|
-
|
|
398
|
-
// Simulate throttling response
|
|
399
|
-
let parsed = {
|
|
400
|
-
tag: 'A001',
|
|
401
|
-
command: 'BAD',
|
|
402
|
-
attributes: [
|
|
403
|
-
{
|
|
404
|
-
type: 'TEXT',
|
|
405
|
-
value: 'Request is throttled. Suggested Backoff Time: 5000 milliseconds'
|
|
406
|
-
}
|
|
407
|
-
]
|
|
408
|
-
};
|
|
409
|
-
|
|
410
|
-
// Mock streamer with proper async iterator
|
|
411
|
-
client.streamer = new EventEmitter();
|
|
412
|
-
client.streamer.read = () => null;
|
|
413
|
-
client.streamer.iterate = async function* () {
|
|
414
|
-
yield {
|
|
415
|
-
next: () => {},
|
|
416
|
-
parsed
|
|
417
|
-
};
|
|
418
|
-
};
|
|
419
|
-
|
|
420
|
-
// Process reader
|
|
421
|
-
client
|
|
422
|
-
.reader()
|
|
423
|
-
.then(() => {
|
|
424
|
-
if (errorReceived) {
|
|
425
|
-
test.ok(errorReceived, 'Should receive throttle error');
|
|
426
|
-
test.equal(errorReceived.code, 'ETHROTTLE');
|
|
427
|
-
test.equal(errorReceived.throttleReset, 5000);
|
|
428
|
-
}
|
|
429
|
-
test.done();
|
|
430
|
-
})
|
|
431
|
-
.catch(() => {
|
|
432
|
-
// Reader might fail in test environment
|
|
433
|
-
test.done();
|
|
434
|
-
});
|
|
435
|
-
};
|
|
436
|
-
|
|
437
|
-
module.exports['Connection Edge: Binary data in write'] = test => {
|
|
438
|
-
let client = new ImapFlow({
|
|
439
|
-
host: 'imap.example.com',
|
|
440
|
-
port: 993,
|
|
441
|
-
auth: { user: 'test', pass: 'test' }
|
|
442
|
-
});
|
|
443
|
-
|
|
444
|
-
// Mock socket
|
|
445
|
-
let writtenData = null;
|
|
446
|
-
client.socket = { destroyed: false };
|
|
447
|
-
client.writeSocket = {
|
|
448
|
-
destroyed: false,
|
|
449
|
-
write: data => {
|
|
450
|
-
writtenData = data;
|
|
451
|
-
}
|
|
452
|
-
};
|
|
453
|
-
|
|
454
|
-
// Test writing Buffer
|
|
455
|
-
let testBuffer = Buffer.from('TEST');
|
|
456
|
-
client.write(testBuffer);
|
|
457
|
-
|
|
458
|
-
test.ok(Buffer.isBuffer(writtenData));
|
|
459
|
-
test.ok(writtenData.includes(testBuffer));
|
|
460
|
-
test.ok(writtenData.includes(Buffer.from('\r\n')));
|
|
461
|
-
|
|
462
|
-
// Test writing string
|
|
463
|
-
client.commandParts = [];
|
|
464
|
-
client.write('STRING_TEST');
|
|
465
|
-
|
|
466
|
-
test.ok(Buffer.isBuffer(writtenData));
|
|
467
|
-
test.ok(writtenData.includes(Buffer.from('STRING_TEST\r\n')));
|
|
468
|
-
|
|
469
|
-
test.done();
|
|
470
|
-
};
|
|
471
|
-
|
|
472
|
-
module.exports['Connection Edge: Invalid write data type'] = test => {
|
|
473
|
-
let client = new ImapFlow({
|
|
474
|
-
host: 'imap.example.com',
|
|
475
|
-
port: 993,
|
|
476
|
-
auth: { user: 'test', pass: 'test' }
|
|
477
|
-
});
|
|
478
|
-
|
|
479
|
-
// Mock socket
|
|
480
|
-
client.socket = { destroyed: false };
|
|
481
|
-
client.writeSocket = { destroyed: false, write: () => {} };
|
|
482
|
-
|
|
483
|
-
// Test writing invalid data type
|
|
484
|
-
let result = client.write(12345);
|
|
485
|
-
test.equal(result, false, 'Should return false for invalid data type');
|
|
486
|
-
|
|
487
|
-
result = client.write({ test: 'object' });
|
|
488
|
-
test.equal(result, false, 'Should return false for object');
|
|
489
|
-
|
|
490
|
-
test.done();
|
|
491
|
-
};
|
|
492
|
-
|
|
493
|
-
module.exports['Connection Edge: Concurrent connections'] = test => {
|
|
494
|
-
let clients = [];
|
|
495
|
-
|
|
496
|
-
// Create multiple clients
|
|
497
|
-
for (let i = 0; i < 5; i++) {
|
|
498
|
-
let client = new ImapFlow({
|
|
499
|
-
host: `imap${i}.example.com`,
|
|
500
|
-
port: 993,
|
|
501
|
-
auth: { user: `user${i}`, pass: `pass${i}` }
|
|
502
|
-
});
|
|
503
|
-
clients.push(client);
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
// Verify each client has unique ID
|
|
507
|
-
let ids = clients.map(c => c.id);
|
|
508
|
-
let uniqueIds = [...new Set(ids)];
|
|
509
|
-
test.equal(uniqueIds.length, clients.length, 'All client IDs should be unique');
|
|
510
|
-
|
|
511
|
-
// Verify each client has independent state
|
|
512
|
-
clients[0].state = clients[0].states.AUTHENTICATED;
|
|
513
|
-
clients[1].state = clients[1].states.SELECTED;
|
|
514
|
-
|
|
515
|
-
test.equal(clients[0].state, clients[0].states.AUTHENTICATED);
|
|
516
|
-
test.equal(clients[1].state, clients[1].states.SELECTED);
|
|
517
|
-
test.equal(clients[2].state, clients[2].states.NOT_AUTHENTICATED);
|
|
518
|
-
|
|
519
|
-
test.done();
|
|
520
|
-
};
|
|
521
|
-
|
|
522
|
-
module.exports['Connection Edge: Destroyed write socket'] = test => {
|
|
523
|
-
let client = new ImapFlow({
|
|
524
|
-
host: 'imap.example.com',
|
|
525
|
-
port: 993,
|
|
526
|
-
auth: { user: 'test', pass: 'test' }
|
|
527
|
-
});
|
|
528
|
-
|
|
529
|
-
// Mock sockets
|
|
530
|
-
client.socket = { destroyed: false };
|
|
531
|
-
client.writeSocket = { destroyed: true };
|
|
532
|
-
|
|
533
|
-
// Mock close method
|
|
534
|
-
let closeCalled = false;
|
|
535
|
-
client.close = () => {
|
|
536
|
-
closeCalled = true;
|
|
537
|
-
};
|
|
538
|
-
|
|
539
|
-
// Attempt to write
|
|
540
|
-
client.write('TEST');
|
|
541
|
-
|
|
542
|
-
test.ok(closeCalled, 'Should call close when write socket is destroyed');
|
|
543
|
-
test.done();
|
|
544
|
-
};
|
|
545
|
-
|
|
546
|
-
module.exports['Connection Edge: Command after logout'] = test => {
|
|
547
|
-
let client = new ImapFlow({
|
|
548
|
-
host: 'imap.example.com',
|
|
549
|
-
port: 993,
|
|
550
|
-
auth: { user: 'test', pass: 'test' }
|
|
551
|
-
});
|
|
552
|
-
|
|
553
|
-
// Set logout state
|
|
554
|
-
client.state = client.states.LOGOUT;
|
|
555
|
-
|
|
556
|
-
// Create a request
|
|
557
|
-
let errorReceived = null;
|
|
558
|
-
let request = {
|
|
559
|
-
tag: 'A001',
|
|
560
|
-
reject: err => {
|
|
561
|
-
errorReceived = err;
|
|
562
|
-
}
|
|
563
|
-
};
|
|
564
|
-
|
|
565
|
-
client.requestTagMap = new Map();
|
|
566
|
-
client.requestTagMap.set('A001', request);
|
|
567
|
-
|
|
568
|
-
// Try to send command
|
|
569
|
-
client.send({ tag: 'A001', command: 'NOOP' }).then(() => {
|
|
570
|
-
test.ok(errorReceived, 'Should reject command after logout');
|
|
571
|
-
test.equal(errorReceived.code, 'NoConnection');
|
|
572
|
-
test.done();
|
|
573
|
-
});
|
|
574
|
-
};
|
|
575
|
-
|
|
576
|
-
module.exports['Connection Edge: Race condition in mailbox lock'] = test => {
|
|
577
|
-
let client = new ImapFlow({
|
|
578
|
-
host: 'imap.example.com',
|
|
579
|
-
port: 993,
|
|
580
|
-
auth: { user: 'test', pass: 'test' }
|
|
581
|
-
});
|
|
582
|
-
|
|
583
|
-
// Initialize locks array
|
|
584
|
-
client.locks = [];
|
|
585
|
-
|
|
586
|
-
// Simulate concurrent lock requests
|
|
587
|
-
let lockPromises = [];
|
|
588
|
-
for (let i = 0; i < 3; i++) {
|
|
589
|
-
lockPromises.push(
|
|
590
|
-
new Promise(resolve => {
|
|
591
|
-
client.locks.push({
|
|
592
|
-
path: 'INBOX',
|
|
593
|
-
resolve,
|
|
594
|
-
promise: new Promise(() => {})
|
|
595
|
-
});
|
|
596
|
-
})
|
|
597
|
-
);
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
test.equal(client.locks.length, 3, 'Should queue multiple lock requests');
|
|
601
|
-
|
|
602
|
-
// Process locks sequentially
|
|
603
|
-
client.locks.forEach((lock, index) => {
|
|
604
|
-
lock.resolve({ path: 'INBOX', index });
|
|
605
|
-
});
|
|
606
|
-
|
|
607
|
-
Promise.all(lockPromises).then(results => {
|
|
608
|
-
test.equal(results.length, 3, 'All lock requests should be resolved');
|
|
609
|
-
test.done();
|
|
610
|
-
});
|
|
611
|
-
};
|
|
612
|
-
|
|
613
|
-
// NB! the capability discard/re-fetch behavior around STARTTLS is asserted end to
|
|
614
|
-
// end in imap-flow-secure-test.js ('Secure: STARTTLS upgrade completes a session',
|
|
615
|
-
// PRETLS-ONLY/POSTTLS-ONLY markers) - a mocked upgradeToSTARTTLS cannot reach the
|
|
616
|
-
// re-fetch code without a real TLS handshake, so no vacuous variant is kept here.
|
|
617
|
-
|
|
618
|
-
module.exports['Connection Edge: Event handlers attached before piping'] = test => {
|
|
619
|
-
let client = new ImapFlow({
|
|
620
|
-
host: 'imap.example.com',
|
|
621
|
-
port: 993,
|
|
622
|
-
auth: { user: 'test', pass: 'test' }
|
|
623
|
-
});
|
|
624
|
-
|
|
625
|
-
// Track the order of operations during the connection flow
|
|
626
|
-
let pipeCalledBeforeHandlers = false;
|
|
627
|
-
let eventHandlersAttached = false;
|
|
628
|
-
let pipeWasCalled = false;
|
|
629
|
-
|
|
630
|
-
// Override setEventHandlers to track when it's called
|
|
631
|
-
let originalSetEventHandlers = client.setEventHandlers.bind(client);
|
|
632
|
-
client.setEventHandlers = function () {
|
|
633
|
-
eventHandlersAttached = true;
|
|
634
|
-
return originalSetEventHandlers();
|
|
635
|
-
};
|
|
636
|
-
|
|
637
|
-
// Create a mock socket with pipe tracking
|
|
638
|
-
let mockSocket = new EventEmitter();
|
|
639
|
-
mockSocket.setKeepAlive = () => {};
|
|
640
|
-
mockSocket.setTimeout = () => {};
|
|
641
|
-
mockSocket.remotePort = 993;
|
|
642
|
-
mockSocket.remoteAddress = '127.0.0.1';
|
|
643
|
-
mockSocket.localAddress = '127.0.0.1';
|
|
644
|
-
mockSocket.localPort = 12345;
|
|
645
|
-
mockSocket.destroyed = false;
|
|
646
|
-
mockSocket.pipe = function (dest) {
|
|
647
|
-
pipeWasCalled = true;
|
|
648
|
-
if (!eventHandlersAttached) {
|
|
649
|
-
pipeCalledBeforeHandlers = true;
|
|
650
|
-
}
|
|
651
|
-
// Mock pipe behavior - just return the destination
|
|
652
|
-
return dest;
|
|
653
|
-
};
|
|
654
|
-
|
|
655
|
-
// Assign mock socket
|
|
656
|
-
client.socket = mockSocket;
|
|
657
|
-
client.writeSocket = mockSocket;
|
|
658
|
-
|
|
659
|
-
// Simulate the onConnect flow that happens in the actual code
|
|
660
|
-
client.setSocketHandlers();
|
|
661
|
-
client.setEventHandlers();
|
|
662
|
-
client.socket.pipe(client.streamer);
|
|
663
|
-
|
|
664
|
-
test.ok(eventHandlersAttached, 'Event handlers should be attached');
|
|
665
|
-
test.ok(pipeWasCalled, 'Socket pipe should be called');
|
|
666
|
-
test.ok(!pipeCalledBeforeHandlers, 'Event handlers should be attached before piping socket to streamer');
|
|
667
|
-
|
|
668
|
-
test.done();
|
|
669
|
-
};
|
|
670
|
-
|
|
671
|
-
module.exports['Connection Edge: Pending locks rejected on close'] = test => {
|
|
672
|
-
let client = new ImapFlow({
|
|
673
|
-
host: 'imap.example.com',
|
|
674
|
-
port: 993,
|
|
675
|
-
auth: { user: 'test', pass: 'test' }
|
|
676
|
-
});
|
|
677
|
-
|
|
678
|
-
// Initialize locks array with pending locks
|
|
679
|
-
let rejectedErrors = [];
|
|
680
|
-
client.locks = [
|
|
681
|
-
{
|
|
682
|
-
path: 'INBOX',
|
|
683
|
-
lockId: 'lock1',
|
|
684
|
-
resolve: () => {},
|
|
685
|
-
reject: err => rejectedErrors.push(err)
|
|
686
|
-
},
|
|
687
|
-
{
|
|
688
|
-
path: 'Sent',
|
|
689
|
-
lockId: 'lock2',
|
|
690
|
-
resolve: () => {},
|
|
691
|
-
reject: err => rejectedErrors.push(err)
|
|
692
|
-
}
|
|
693
|
-
];
|
|
694
|
-
|
|
695
|
-
// Mock socket to allow close() to run
|
|
696
|
-
client.socket = { destroyed: false, destroy: () => {} };
|
|
697
|
-
client.usable = true;
|
|
698
|
-
|
|
699
|
-
// Call close
|
|
700
|
-
client.close();
|
|
701
|
-
|
|
702
|
-
// Rejections happen via setImmediate, so check after next tick
|
|
703
|
-
setImmediate(() => {
|
|
704
|
-
test.equal(rejectedErrors.length, 2, 'All pending locks should be rejected');
|
|
705
|
-
test.equal(rejectedErrors[0].code, 'NoConnection', 'Error should have NoConnection code');
|
|
706
|
-
test.equal(rejectedErrors[1].code, 'NoConnection', 'Error should have NoConnection code');
|
|
707
|
-
test.equal(client.locks.length, 0, 'Locks array should be cleared');
|
|
708
|
-
test.done();
|
|
709
|
-
});
|
|
710
|
-
};
|
|
711
|
-
|
|
712
|
-
module.exports['Connection Edge: Lock rejection includes byeReason'] = test => {
|
|
713
|
-
let client = new ImapFlow({
|
|
714
|
-
host: 'imap.example.com',
|
|
715
|
-
port: 993,
|
|
716
|
-
auth: { user: 'test', pass: 'test' }
|
|
717
|
-
});
|
|
718
|
-
|
|
719
|
-
// Set byeReason before close
|
|
720
|
-
client.byeReason = 'Server shutting down';
|
|
721
|
-
|
|
722
|
-
let rejectedError = null;
|
|
723
|
-
client.locks = [
|
|
724
|
-
{
|
|
725
|
-
path: 'INBOX',
|
|
726
|
-
lockId: 'lock1',
|
|
727
|
-
resolve: () => {},
|
|
728
|
-
reject: err => {
|
|
729
|
-
rejectedError = err;
|
|
730
|
-
}
|
|
731
|
-
}
|
|
732
|
-
];
|
|
733
|
-
|
|
734
|
-
client.socket = { destroyed: false, destroy: () => {} };
|
|
735
|
-
client.usable = true;
|
|
736
|
-
|
|
737
|
-
client.close();
|
|
738
|
-
|
|
739
|
-
setImmediate(() => {
|
|
740
|
-
test.ok(rejectedError, 'Lock should be rejected');
|
|
741
|
-
test.equal(rejectedError.code, 'NoConnection');
|
|
742
|
-
test.equal(rejectedError.reason, 'Server shutting down', 'byeReason should be included');
|
|
743
|
-
test.done();
|
|
744
|
-
});
|
|
745
|
-
};
|
|
746
|
-
|
|
747
|
-
module.exports['Connection Edge: currentLock cleared on close'] = test => {
|
|
748
|
-
let client = new ImapFlow({
|
|
749
|
-
host: 'imap.example.com',
|
|
750
|
-
port: 993,
|
|
751
|
-
auth: { user: 'test', pass: 'test' }
|
|
752
|
-
});
|
|
753
|
-
|
|
754
|
-
// Simulate an active lock
|
|
755
|
-
client.currentLock = {
|
|
756
|
-
path: 'INBOX',
|
|
757
|
-
lockId: 'active-lock',
|
|
758
|
-
release: () => {}
|
|
759
|
-
};
|
|
760
|
-
client.locks = [];
|
|
761
|
-
|
|
762
|
-
client.socket = { destroyed: false, destroy: () => {} };
|
|
763
|
-
client.usable = true;
|
|
764
|
-
|
|
765
|
-
client.close();
|
|
766
|
-
|
|
767
|
-
test.equal(client.currentLock, false, 'currentLock should be cleared');
|
|
768
|
-
test.done();
|
|
769
|
-
};
|
|
770
|
-
|
|
771
|
-
module.exports['Connection Edge: Lock rejection handles missing reject function'] = test => {
|
|
772
|
-
let client = new ImapFlow({
|
|
773
|
-
host: 'imap.example.com',
|
|
774
|
-
port: 993,
|
|
775
|
-
auth: { user: 'test', pass: 'test' }
|
|
776
|
-
});
|
|
777
|
-
|
|
778
|
-
// Create lock with missing reject function (edge case)
|
|
779
|
-
let validRejected = false;
|
|
780
|
-
client.locks = [
|
|
781
|
-
{
|
|
782
|
-
path: 'INBOX',
|
|
783
|
-
lockId: 'lock1',
|
|
784
|
-
resolve: () => {}
|
|
785
|
-
// No reject function
|
|
786
|
-
},
|
|
787
|
-
{
|
|
788
|
-
path: 'Sent',
|
|
789
|
-
lockId: 'lock2',
|
|
790
|
-
resolve: () => {},
|
|
791
|
-
reject: () => {
|
|
792
|
-
validRejected = true;
|
|
793
|
-
}
|
|
794
|
-
}
|
|
795
|
-
];
|
|
796
|
-
|
|
797
|
-
client.socket = { destroyed: false, destroy: () => {} };
|
|
798
|
-
client.usable = true;
|
|
799
|
-
|
|
800
|
-
// Should not throw
|
|
801
|
-
test.doesNotThrow(() => {
|
|
802
|
-
client.close();
|
|
803
|
-
}, 'Should handle missing reject function gracefully');
|
|
804
|
-
|
|
805
|
-
setImmediate(() => {
|
|
806
|
-
test.ok(validRejected, 'Valid lock should still be rejected');
|
|
807
|
-
test.equal(client.locks.length, 0, 'Locks array should be cleared');
|
|
808
|
-
test.done();
|
|
809
|
-
});
|
|
810
|
-
};
|
|
811
|
-
|
|
812
|
-
module.exports['Connection Edge: Close with empty locks array'] = test => {
|
|
813
|
-
let client = new ImapFlow({
|
|
814
|
-
host: 'imap.example.com',
|
|
815
|
-
port: 993,
|
|
816
|
-
auth: { user: 'test', pass: 'test' }
|
|
817
|
-
});
|
|
818
|
-
|
|
819
|
-
client.locks = [];
|
|
820
|
-
client.currentLock = false;
|
|
821
|
-
client.socket = { destroyed: false, destroy: () => {} };
|
|
822
|
-
client.usable = true;
|
|
823
|
-
|
|
824
|
-
// Should not throw with empty locks
|
|
825
|
-
test.doesNotThrow(() => {
|
|
826
|
-
client.close();
|
|
827
|
-
}, 'Should handle empty locks array');
|
|
828
|
-
|
|
829
|
-
test.equal(client.currentLock, false);
|
|
830
|
-
test.done();
|
|
831
|
-
};
|
|
832
|
-
|
|
833
|
-
module.exports['Connection Edge: Lock rejection happens synchronously during close'] = test => {
|
|
834
|
-
let client = new ImapFlow({
|
|
835
|
-
host: 'imap.example.com',
|
|
836
|
-
port: 993,
|
|
837
|
-
auth: { user: 'test', pass: 'test' }
|
|
838
|
-
});
|
|
839
|
-
|
|
840
|
-
let rejectionTime = null;
|
|
841
|
-
|
|
842
|
-
client.locks = [
|
|
843
|
-
{
|
|
844
|
-
path: 'INBOX',
|
|
845
|
-
lockId: 'lock1',
|
|
846
|
-
resolve: () => {},
|
|
847
|
-
reject: () => {
|
|
848
|
-
rejectionTime = Date.now();
|
|
849
|
-
}
|
|
850
|
-
}
|
|
851
|
-
];
|
|
852
|
-
|
|
853
|
-
client.socket = { destroyed: false, destroy: () => {} };
|
|
854
|
-
client.usable = true;
|
|
855
|
-
|
|
856
|
-
client.close();
|
|
857
|
-
|
|
858
|
-
// Rejection should have happened synchronously during close()
|
|
859
|
-
test.ok(rejectionTime !== null, 'Rejection should happen synchronously during close()');
|
|
860
|
-
test.done();
|
|
861
|
-
};
|
|
862
|
-
|
|
863
|
-
module.exports['Connection Edge: Pending requests and locks both rejected on close'] = test => {
|
|
864
|
-
let client = new ImapFlow({
|
|
865
|
-
host: 'imap.example.com',
|
|
866
|
-
port: 993,
|
|
867
|
-
auth: { user: 'test', pass: 'test' }
|
|
868
|
-
});
|
|
869
|
-
|
|
870
|
-
let requestRejected = false;
|
|
871
|
-
let lockRejected = false;
|
|
872
|
-
|
|
873
|
-
// Add pending request - must be in requestQueue and requestTagMap
|
|
874
|
-
let request = {
|
|
875
|
-
tag: 'A001',
|
|
876
|
-
reject: err => {
|
|
877
|
-
requestRejected = true;
|
|
878
|
-
test.equal(err.code, 'NoConnection');
|
|
879
|
-
}
|
|
880
|
-
};
|
|
881
|
-
client.requestTagMap = new Map();
|
|
882
|
-
client.requestTagMap.set('A001', request);
|
|
883
|
-
client.requestQueue = [request];
|
|
884
|
-
|
|
885
|
-
// Add pending lock
|
|
886
|
-
client.locks = [
|
|
887
|
-
{
|
|
888
|
-
path: 'INBOX',
|
|
889
|
-
lockId: 'lock1',
|
|
890
|
-
resolve: () => {},
|
|
891
|
-
reject: err => {
|
|
892
|
-
lockRejected = true;
|
|
893
|
-
test.equal(err.code, 'NoConnection');
|
|
894
|
-
}
|
|
895
|
-
}
|
|
896
|
-
];
|
|
897
|
-
|
|
898
|
-
client.socket = { destroyed: false, destroy: () => {} };
|
|
899
|
-
client.usable = true;
|
|
900
|
-
|
|
901
|
-
client.close();
|
|
902
|
-
|
|
903
|
-
// Rejections happen synchronously during close()
|
|
904
|
-
test.ok(requestRejected, 'Pending request should be rejected');
|
|
905
|
-
test.ok(lockRejected, 'Pending lock should be rejected');
|
|
906
|
-
test.done();
|
|
907
|
-
};
|
|
908
|
-
|
|
909
|
-
module.exports['Connection Edge: exec throws NoConnection when in LOGOUT state'] = async test => {
|
|
910
|
-
let client = new ImapFlow({
|
|
911
|
-
host: 'imap.example.com',
|
|
912
|
-
port: 993,
|
|
913
|
-
auth: { user: 'test', pass: 'test' }
|
|
914
|
-
});
|
|
915
|
-
|
|
916
|
-
// Set state to LOGOUT
|
|
917
|
-
client.state = client.states.LOGOUT;
|
|
918
|
-
client.socket = { destroyed: false };
|
|
919
|
-
|
|
920
|
-
try {
|
|
921
|
-
await client.exec('NOOP', []);
|
|
922
|
-
test.ok(false, 'Should have thrown');
|
|
923
|
-
} catch (err) {
|
|
924
|
-
test.equal(err.code, 'NoConnection');
|
|
925
|
-
test.ok(err.message.includes('Connection not available'));
|
|
926
|
-
}
|
|
927
|
-
|
|
928
|
-
test.done();
|
|
929
|
-
};
|
|
930
|
-
|
|
931
|
-
module.exports['Connection Edge: exec throws NoConnection when isClosed is true'] = async test => {
|
|
932
|
-
let client = new ImapFlow({
|
|
933
|
-
host: 'imap.example.com',
|
|
934
|
-
port: 993,
|
|
935
|
-
auth: { user: 'test', pass: 'test' }
|
|
936
|
-
});
|
|
937
|
-
|
|
938
|
-
// Set isClosed flag
|
|
939
|
-
client.isClosed = true;
|
|
940
|
-
client.socket = { destroyed: false };
|
|
941
|
-
|
|
942
|
-
try {
|
|
943
|
-
await client.exec('NOOP', []);
|
|
944
|
-
test.ok(false, 'Should have thrown');
|
|
945
|
-
} catch (err) {
|
|
946
|
-
test.equal(err.code, 'NoConnection');
|
|
947
|
-
test.ok(err.message.includes('Connection not available'));
|
|
948
|
-
}
|
|
949
|
-
|
|
950
|
-
test.done();
|
|
951
|
-
};
|
|
952
|
-
|
|
953
|
-
module.exports['Connection Edge: getLogger emits log events when emitLogs is true'] = test => {
|
|
954
|
-
let client = new ImapFlow({
|
|
955
|
-
host: 'imap.example.com',
|
|
956
|
-
port: 993,
|
|
957
|
-
auth: { user: 'test', pass: 'test' },
|
|
958
|
-
emitLogs: true
|
|
959
|
-
});
|
|
960
|
-
|
|
961
|
-
let logEvents = [];
|
|
962
|
-
client.on('log', entry => {
|
|
963
|
-
logEvents.push(entry);
|
|
964
|
-
});
|
|
965
|
-
|
|
966
|
-
// Trigger logging through the logger
|
|
967
|
-
client.log.info({ msg: 'Test message', extra: 'data' });
|
|
968
|
-
|
|
969
|
-
test.ok(logEvents.length > 0, 'Log event should be emitted');
|
|
970
|
-
test.equal(logEvents[0].level, 'info');
|
|
971
|
-
test.equal(logEvents[0].msg, 'Test message');
|
|
972
|
-
test.equal(logEvents[0].extra, 'data');
|
|
973
|
-
test.ok(logEvents[0].cid, 'Should have connection id');
|
|
974
|
-
test.ok(logEvents[0].t, 'Should have timestamp');
|
|
975
|
-
test.done();
|
|
976
|
-
};
|
|
977
|
-
|
|
978
|
-
module.exports['Connection Edge: getLogger emits log with error object'] = test => {
|
|
979
|
-
let client = new ImapFlow({
|
|
980
|
-
host: 'imap.example.com',
|
|
981
|
-
port: 993,
|
|
982
|
-
auth: { user: 'test', pass: 'test' },
|
|
983
|
-
emitLogs: true
|
|
984
|
-
});
|
|
985
|
-
|
|
986
|
-
let logEvents = [];
|
|
987
|
-
client.on('log', entry => {
|
|
988
|
-
logEvents.push(entry);
|
|
989
|
-
});
|
|
990
|
-
|
|
991
|
-
// Trigger error logging
|
|
992
|
-
let testError = new Error('Test error');
|
|
993
|
-
testError.code = 'TEST_CODE';
|
|
994
|
-
client.log.error({ msg: 'Error occurred', err: testError });
|
|
995
|
-
|
|
996
|
-
test.ok(logEvents.length > 0, 'Log event should be emitted');
|
|
997
|
-
test.equal(logEvents[0].level, 'error');
|
|
998
|
-
test.ok(logEvents[0].err, 'Should have error object');
|
|
999
|
-
test.ok(logEvents[0].err.stack, 'Error should have stack');
|
|
1000
|
-
test.equal(logEvents[0].err.code, 'TEST_CODE', 'Error should have code');
|
|
1001
|
-
test.done();
|
|
1002
|
-
};
|
|
1003
|
-
|
|
1004
|
-
module.exports['Connection Edge: unbind removes socket listeners and returns sockets'] = test => {
|
|
1005
|
-
let client = new ImapFlow({
|
|
1006
|
-
host: 'imap.example.com',
|
|
1007
|
-
port: 993,
|
|
1008
|
-
auth: { user: 'test', pass: 'test' }
|
|
1009
|
-
});
|
|
1010
|
-
|
|
1011
|
-
// Create mock socket with unpipe method
|
|
1012
|
-
client.socket = new EventEmitter();
|
|
1013
|
-
client.socket.unpipe = () => {};
|
|
1014
|
-
client.writeSocket = client.socket;
|
|
1015
|
-
|
|
1016
|
-
// Set up socket handlers first
|
|
1017
|
-
client.setSocketHandlers();
|
|
1018
|
-
|
|
1019
|
-
// Verify handlers are set
|
|
1020
|
-
test.equal(client.socket.listenerCount('error'), 1);
|
|
1021
|
-
test.equal(client.socket.listenerCount('close'), 1);
|
|
1022
|
-
|
|
1023
|
-
// Call unbind
|
|
1024
|
-
let result = client.unbind();
|
|
1025
|
-
|
|
1026
|
-
// Verify handlers are removed
|
|
1027
|
-
test.equal(client.socket.listenerCount('error'), 0);
|
|
1028
|
-
test.equal(client.socket.listenerCount('close'), 0);
|
|
1029
|
-
test.equal(client.socket.listenerCount('end'), 0);
|
|
1030
|
-
test.equal(client.socket.listenerCount('timeout'), 0);
|
|
1031
|
-
|
|
1032
|
-
// Verify return value
|
|
1033
|
-
test.ok(result.readSocket, 'Should return readSocket');
|
|
1034
|
-
test.ok(result.writeSocket, 'Should return writeSocket');
|
|
1035
|
-
test.equal(result.readSocket, client.socket);
|
|
1036
|
-
test.equal(result.writeSocket, client.socket);
|
|
1037
|
-
// Non-compression path: the raw socket is the same object as read/write.
|
|
1038
|
-
test.equal(result.socket, client.socket);
|
|
1039
|
-
|
|
1040
|
-
test.done();
|
|
1041
|
-
};
|
|
1042
|
-
|
|
1043
|
-
module.exports['Connection Edge: unbind with inflate stream'] = test => {
|
|
1044
|
-
let client = new ImapFlow({
|
|
1045
|
-
host: 'imap.example.com',
|
|
1046
|
-
port: 993,
|
|
1047
|
-
auth: { user: 'test', pass: 'test' }
|
|
1048
|
-
});
|
|
1049
|
-
|
|
1050
|
-
// Create mock socket
|
|
1051
|
-
client.socket = new EventEmitter();
|
|
1052
|
-
client.socket.unpipe = () => {};
|
|
1053
|
-
|
|
1054
|
-
// Create a separate writeSocket (also needs to be EventEmitter for setSocketHandlers)
|
|
1055
|
-
client.writeSocket = new EventEmitter();
|
|
1056
|
-
client.writeSocket.customProp = 'writeSocket';
|
|
1057
|
-
|
|
1058
|
-
// Create mock inflate stream
|
|
1059
|
-
client._inflate = new EventEmitter();
|
|
1060
|
-
client._inflate.unpipe = () => {};
|
|
1061
|
-
|
|
1062
|
-
// Set up socket handlers
|
|
1063
|
-
client.setSocketHandlers();
|
|
1064
|
-
|
|
1065
|
-
// Call unbind
|
|
1066
|
-
let result = client.unbind();
|
|
1067
|
-
|
|
1068
|
-
// Verify return value uses inflate for read and writeSocket for write
|
|
1069
|
-
test.equal(result.readSocket, client._inflate);
|
|
1070
|
-
test.equal(result.writeSocket.customProp, 'writeSocket');
|
|
1071
|
-
|
|
1072
|
-
test.done();
|
|
1073
|
-
};
|
|
1074
|
-
|
|
1075
|
-
module.exports['Connection Edge: unbind survives post-handoff socket error (compression)'] = test => {
|
|
1076
|
-
let client = new ImapFlow({
|
|
1077
|
-
host: 'imap.example.com',
|
|
1078
|
-
port: 993,
|
|
1079
|
-
auth: { user: 'test', pass: 'test' }
|
|
1080
|
-
});
|
|
1081
|
-
|
|
1082
|
-
// Mock the compression topology: raw socket plus a separate PassThrough-like
|
|
1083
|
-
// writeSocket, an inflate read stream and a deflate stream. The writeSocket
|
|
1084
|
-
// and deflate error forwarders re-emit onto the raw socket, exactly like compress().
|
|
1085
|
-
client.socket = new EventEmitter();
|
|
1086
|
-
client.socket.unpipe = () => {};
|
|
1087
|
-
|
|
1088
|
-
client.writeSocket = new EventEmitter();
|
|
1089
|
-
client.writeSocket.on('error', err => {
|
|
1090
|
-
if (client.socket) {
|
|
1091
|
-
client.socket.emit('error', err);
|
|
1092
|
-
}
|
|
1093
|
-
});
|
|
1094
|
-
|
|
1095
|
-
client._inflate = new EventEmitter();
|
|
1096
|
-
client._inflate.unpipe = () => {};
|
|
1097
|
-
|
|
1098
|
-
client._deflate = new EventEmitter();
|
|
1099
|
-
client._deflate.on('error', err => {
|
|
1100
|
-
if (client.socket) {
|
|
1101
|
-
client.socket.emit('error', err);
|
|
1102
|
-
}
|
|
1103
|
-
});
|
|
1104
|
-
|
|
1105
|
-
client.setSocketHandlers();
|
|
1106
|
-
|
|
1107
|
-
let result = client.unbind();
|
|
1108
|
-
|
|
1109
|
-
// Explicit contract: raw socket is now exposed alongside read/write sockets.
|
|
1110
|
-
test.equal(result.socket, client.socket, 'Should expose the raw socket');
|
|
1111
|
-
test.equal(result.readSocket, client._inflate);
|
|
1112
|
-
test.equal(result.writeSocket, client.writeSocket);
|
|
1113
|
-
|
|
1114
|
-
// ImapFlow's own _socketError must be detached from writeSocket; only the
|
|
1115
|
-
// forwarder closure remains.
|
|
1116
|
-
test.equal(client.writeSocket.listenerCount('error'), 1);
|
|
1117
|
-
|
|
1118
|
-
// Emitting 'error' on a listener-less EventEmitter throws synchronously, so
|
|
1119
|
-
// each of these would crash the host before the fix. After unbind() the
|
|
1120
|
-
// benign listener on the orphaned raw socket must swallow them all.
|
|
1121
|
-
test.doesNotThrow(() => {
|
|
1122
|
-
client.socket.emit('error', Object.assign(new Error('upstream reset'), { code: 'ECONNRESET' }));
|
|
1123
|
-
}, 'Direct socket error after unbind must not throw');
|
|
1124
|
-
|
|
1125
|
-
test.doesNotThrow(() => {
|
|
1126
|
-
client.writeSocket.emit('error', new Error('write failure'));
|
|
1127
|
-
}, 'writeSocket error forwarded to the raw socket must not throw');
|
|
1128
|
-
|
|
1129
|
-
test.doesNotThrow(() => {
|
|
1130
|
-
client._deflate.emit('error', new Error('deflate failure'));
|
|
1131
|
-
}, 'deflate error forwarded to the raw socket must not throw');
|
|
1132
|
-
|
|
1133
|
-
// close() must remain safe to call after unbind().
|
|
1134
|
-
test.doesNotThrow(() => {
|
|
1135
|
-
client.close();
|
|
1136
|
-
}, 'close() after unbind must not throw');
|
|
1137
|
-
|
|
1138
|
-
test.done();
|
|
1139
|
-
};
|
|
1140
|
-
|
|
1141
|
-
module.exports['Connection Edge: resolveRange with number input'] = async test => {
|
|
1142
|
-
let client = new ImapFlow({
|
|
1143
|
-
host: 'imap.example.com',
|
|
1144
|
-
port: 993,
|
|
1145
|
-
auth: { user: 'test', pass: 'test' }
|
|
1146
|
-
});
|
|
1147
|
-
|
|
1148
|
-
client.mailbox = { exists: 10 };
|
|
1149
|
-
let options = {};
|
|
1150
|
-
|
|
1151
|
-
let result = await client.resolveRange(123, options);
|
|
1152
|
-
test.equal(result, '123');
|
|
1153
|
-
test.done();
|
|
1154
|
-
};
|
|
1155
|
-
|
|
1156
|
-
module.exports['Connection Edge: resolveRange with bigint input'] = async test => {
|
|
1157
|
-
let client = new ImapFlow({
|
|
1158
|
-
host: 'imap.example.com',
|
|
1159
|
-
port: 993,
|
|
1160
|
-
auth: { user: 'test', pass: 'test' }
|
|
1161
|
-
});
|
|
1162
|
-
|
|
1163
|
-
client.mailbox = { exists: 10 };
|
|
1164
|
-
let options = {};
|
|
1165
|
-
|
|
1166
|
-
let result = await client.resolveRange(BigInt(456), options);
|
|
1167
|
-
test.equal(result, '456');
|
|
1168
|
-
test.done();
|
|
1169
|
-
};
|
|
1170
|
-
|
|
1171
|
-
module.exports['Connection Edge: resolveRange with star and existing messages'] = async test => {
|
|
1172
|
-
let client = new ImapFlow({
|
|
1173
|
-
host: 'imap.example.com',
|
|
1174
|
-
port: 993,
|
|
1175
|
-
auth: { user: 'test', pass: 'test' }
|
|
1176
|
-
});
|
|
1177
|
-
|
|
1178
|
-
client.mailbox = { exists: 50 };
|
|
1179
|
-
let options = { uid: true };
|
|
1180
|
-
|
|
1181
|
-
let result = await client.resolveRange('*', options);
|
|
1182
|
-
test.equal(result, '50');
|
|
1183
|
-
test.equal(options.uid, false); // should be changed to sequence query
|
|
1184
|
-
test.done();
|
|
1185
|
-
};
|
|
1186
|
-
|
|
1187
|
-
module.exports['Connection Edge: resolveRange with star and no messages'] = async test => {
|
|
1188
|
-
let client = new ImapFlow({
|
|
1189
|
-
host: 'imap.example.com',
|
|
1190
|
-
port: 993,
|
|
1191
|
-
auth: { user: 'test', pass: 'test' }
|
|
1192
|
-
});
|
|
1193
|
-
|
|
1194
|
-
client.mailbox = { exists: 0 };
|
|
1195
|
-
let options = {};
|
|
1196
|
-
|
|
1197
|
-
let result = await client.resolveRange('*', options);
|
|
1198
|
-
test.equal(result, false);
|
|
1199
|
-
test.done();
|
|
1200
|
-
};
|
|
1201
|
-
|
|
1202
|
-
module.exports['Connection Edge: resolveRange with {all: true}'] = async test => {
|
|
1203
|
-
let client = new ImapFlow({
|
|
1204
|
-
host: 'imap.example.com',
|
|
1205
|
-
port: 993,
|
|
1206
|
-
auth: { user: 'test', pass: 'test' }
|
|
1207
|
-
});
|
|
1208
|
-
|
|
1209
|
-
client.mailbox = { exists: 10 };
|
|
1210
|
-
let options = {};
|
|
1211
|
-
|
|
1212
|
-
let result = await client.resolveRange({ all: true }, options);
|
|
1213
|
-
test.equal(result, '1:*');
|
|
1214
|
-
test.done();
|
|
1215
|
-
};
|
|
1216
|
-
|
|
1217
|
-
module.exports['Connection Edge: resolveRange with {uid: value}'] = async test => {
|
|
1218
|
-
let client = new ImapFlow({
|
|
1219
|
-
host: 'imap.example.com',
|
|
1220
|
-
port: 993,
|
|
1221
|
-
auth: { user: 'test', pass: 'test' }
|
|
1222
|
-
});
|
|
1223
|
-
|
|
1224
|
-
client.mailbox = { exists: 10 };
|
|
1225
|
-
let options = {};
|
|
1226
|
-
|
|
1227
|
-
let result = await client.resolveRange({ uid: '1:100' }, options);
|
|
1228
|
-
test.equal(result, '1:100');
|
|
1229
|
-
test.equal(options.uid, true);
|
|
1230
|
-
test.done();
|
|
1231
|
-
};
|
|
1232
|
-
|
|
1233
|
-
module.exports['Connection Edge: resolveRange with array input'] = async test => {
|
|
1234
|
-
let client = new ImapFlow({
|
|
1235
|
-
host: 'imap.example.com',
|
|
1236
|
-
port: 993,
|
|
1237
|
-
auth: { user: 'test', pass: 'test' }
|
|
1238
|
-
});
|
|
1239
|
-
|
|
1240
|
-
client.mailbox = { exists: 10 };
|
|
1241
|
-
let options = {};
|
|
1242
|
-
|
|
1243
|
-
let result = await client.resolveRange(['1', '2', '3'], options);
|
|
1244
|
-
test.equal(result, '1,2,3');
|
|
1245
|
-
test.done();
|
|
1246
|
-
};
|
|
1247
|
-
|
|
1248
|
-
module.exports['Connection Edge: resolveRange with empty/falsy input'] = async test => {
|
|
1249
|
-
let client = new ImapFlow({
|
|
1250
|
-
host: 'imap.example.com',
|
|
1251
|
-
port: 993,
|
|
1252
|
-
auth: { user: 'test', pass: 'test' }
|
|
1253
|
-
});
|
|
1254
|
-
|
|
1255
|
-
client.mailbox = { exists: 10 };
|
|
1256
|
-
let options = {};
|
|
1257
|
-
|
|
1258
|
-
let result = await client.resolveRange('', options);
|
|
1259
|
-
test.equal(result, false);
|
|
1260
|
-
|
|
1261
|
-
result = await client.resolveRange(null, options);
|
|
1262
|
-
test.equal(result, false);
|
|
1263
|
-
|
|
1264
|
-
test.done();
|
|
1265
|
-
};
|
|
1266
|
-
|
|
1267
|
-
module.exports['Connection Edge: ensureSelectedMailbox with no path'] = async test => {
|
|
1268
|
-
let client = new ImapFlow({
|
|
1269
|
-
host: 'imap.example.com',
|
|
1270
|
-
port: 993,
|
|
1271
|
-
auth: { user: 'test', pass: 'test' }
|
|
1272
|
-
});
|
|
1273
|
-
|
|
1274
|
-
let result = await client.ensureSelectedMailbox('');
|
|
1275
|
-
test.equal(result, false);
|
|
1276
|
-
|
|
1277
|
-
result = await client.ensureSelectedMailbox(null);
|
|
1278
|
-
test.equal(result, false);
|
|
1279
|
-
|
|
1280
|
-
test.done();
|
|
1281
|
-
};
|
|
1282
|
-
|
|
1283
|
-
module.exports['Connection Edge: ensureSelectedMailbox when already selected'] = async test => {
|
|
1284
|
-
let client = new ImapFlow({
|
|
1285
|
-
host: 'imap.example.com',
|
|
1286
|
-
port: 993,
|
|
1287
|
-
auth: { user: 'test', pass: 'test' }
|
|
1288
|
-
});
|
|
1289
|
-
|
|
1290
|
-
client.mailbox = { path: 'INBOX' };
|
|
1291
|
-
|
|
1292
|
-
let result = await client.ensureSelectedMailbox('INBOX');
|
|
1293
|
-
test.equal(result, true);
|
|
1294
|
-
test.done();
|
|
1295
|
-
};
|
|
1296
|
-
|
|
1297
|
-
module.exports['Connection Edge: autoidle clears existing timer'] = test => {
|
|
1298
|
-
let client = new ImapFlow({
|
|
1299
|
-
host: 'imap.example.com',
|
|
1300
|
-
port: 993,
|
|
1301
|
-
auth: { user: 'test', pass: 'test' }
|
|
1302
|
-
});
|
|
1303
|
-
|
|
1304
|
-
// Set a fake timer
|
|
1305
|
-
client.idleStartTimer = setTimeout(() => {}, 10000);
|
|
1306
|
-
client.state = client.states.NOT_AUTHENTICATED;
|
|
1307
|
-
|
|
1308
|
-
// Call autoidle - should clear the timer but not set new one (wrong state)
|
|
1309
|
-
client.autoidle();
|
|
1310
|
-
|
|
1311
|
-
// Should have been cleared
|
|
1312
|
-
test.ok(true);
|
|
1313
|
-
test.done();
|
|
1314
|
-
};
|
|
1315
|
-
|
|
1316
|
-
module.exports['Connection Edge: autoidle disabled by option'] = test => {
|
|
1317
|
-
let client = new ImapFlow({
|
|
1318
|
-
host: 'imap.example.com',
|
|
1319
|
-
port: 993,
|
|
1320
|
-
auth: { user: 'test', pass: 'test' },
|
|
1321
|
-
disableAutoIdle: true
|
|
1322
|
-
});
|
|
1323
|
-
|
|
1324
|
-
client.state = client.states.SELECTED;
|
|
1325
|
-
|
|
1326
|
-
// Call autoidle - should return early due to disableAutoIdle
|
|
1327
|
-
client.autoidle();
|
|
1328
|
-
|
|
1329
|
-
test.equal(client.idleStartTimer, undefined);
|
|
1330
|
-
test.done();
|
|
1331
|
-
};
|
|
1332
|
-
|
|
1333
|
-
module.exports['Connection Edge: getUntaggedHandler with numeric command'] = test => {
|
|
1334
|
-
let client = new ImapFlow({
|
|
1335
|
-
host: 'imap.example.com',
|
|
1336
|
-
port: 993,
|
|
1337
|
-
auth: { user: 'test', pass: 'test' }
|
|
1338
|
-
});
|
|
1339
|
-
|
|
1340
|
-
// Set up a current request with untagged handler
|
|
1341
|
-
client.currentRequest = {
|
|
1342
|
-
options: {
|
|
1343
|
-
untagged: {
|
|
1344
|
-
FETCH: () => 'fetch handler'
|
|
1345
|
-
}
|
|
1346
|
-
}
|
|
1347
|
-
};
|
|
1348
|
-
|
|
1349
|
-
// Test with numeric command and FETCH attribute
|
|
1350
|
-
let handler = client.getUntaggedHandler('123', [{ value: 'FETCH' }]);
|
|
1351
|
-
test.equal(handler(), 'fetch handler');
|
|
1352
|
-
|
|
1353
|
-
test.done();
|
|
1354
|
-
};
|
|
1355
|
-
|
|
1356
|
-
module.exports['Connection Edge: getUntaggedHandler from untaggedHandlers'] = test => {
|
|
1357
|
-
let client = new ImapFlow({
|
|
1358
|
-
host: 'imap.example.com',
|
|
1359
|
-
port: 993,
|
|
1360
|
-
auth: { user: 'test', pass: 'test' }
|
|
1361
|
-
});
|
|
1362
|
-
|
|
1363
|
-
// Set up global untagged handler
|
|
1364
|
-
client.untaggedHandlers = {
|
|
1365
|
-
CAPABILITY: () => 'capability handler'
|
|
1366
|
-
};
|
|
1367
|
-
|
|
1368
|
-
let handler = client.getUntaggedHandler('CAPABILITY', []);
|
|
1369
|
-
test.equal(handler(), 'capability handler');
|
|
1370
|
-
|
|
1371
|
-
test.done();
|
|
1372
|
-
};
|
|
1373
|
-
|
|
1374
|
-
module.exports['Connection Edge: getSectionHandler returns handler'] = test => {
|
|
1375
|
-
let client = new ImapFlow({
|
|
1376
|
-
host: 'imap.example.com',
|
|
1377
|
-
port: 993,
|
|
1378
|
-
auth: { user: 'test', pass: 'test' }
|
|
1379
|
-
});
|
|
1380
|
-
|
|
1381
|
-
// Set up section handler
|
|
1382
|
-
client.sectionHandlers = {
|
|
1383
|
-
CAPABILITY: () => 'section handler'
|
|
1384
|
-
};
|
|
1385
|
-
|
|
1386
|
-
let handler = client.getSectionHandler('CAPABILITY');
|
|
1387
|
-
test.equal(handler(), 'section handler');
|
|
1388
|
-
|
|
1389
|
-
// Test non-existent handler
|
|
1390
|
-
handler = client.getSectionHandler('NONEXISTENT');
|
|
1391
|
-
test.equal(handler, undefined);
|
|
1392
|
-
|
|
1393
|
-
test.done();
|
|
1394
|
-
};
|
|
1395
|
-
|
|
1396
|
-
module.exports['Connection Edge: constructor with custom clientInfo'] = test => {
|
|
1397
|
-
let client = new ImapFlow({
|
|
1398
|
-
host: 'imap.example.com',
|
|
1399
|
-
port: 993,
|
|
1400
|
-
auth: { user: 'test', pass: 'test' },
|
|
1401
|
-
clientInfo: {
|
|
1402
|
-
name: 'CustomClient',
|
|
1403
|
-
version: '1.0.0'
|
|
1404
|
-
}
|
|
1405
|
-
});
|
|
1406
|
-
|
|
1407
|
-
test.equal(client.clientInfo.name, 'CustomClient');
|
|
1408
|
-
test.equal(client.clientInfo.version, '1.0.0');
|
|
1409
|
-
test.ok(client.clientInfo.vendor); // Should still have default vendor
|
|
1410
|
-
test.done();
|
|
1411
|
-
};
|
|
1412
|
-
|
|
1413
|
-
module.exports['Connection Edge: constructor with secure port 993 detection'] = test => {
|
|
1414
|
-
// When port is 993 and secure is undefined, should default to secure
|
|
1415
|
-
let client = new ImapFlow({
|
|
1416
|
-
host: 'imap.example.com',
|
|
1417
|
-
port: 993,
|
|
1418
|
-
auth: { user: 'test', pass: 'test' }
|
|
1419
|
-
});
|
|
1420
|
-
|
|
1421
|
-
test.equal(client.secureConnection, true);
|
|
1422
|
-
test.done();
|
|
1423
|
-
};
|
|
1424
|
-
|
|
1425
|
-
module.exports['Connection Edge: constructor normalizes clientInfo diacritics'] = test => {
|
|
1426
|
-
let client = new ImapFlow({
|
|
1427
|
-
host: 'imap.example.com',
|
|
1428
|
-
port: 993,
|
|
1429
|
-
auth: { user: 'test', pass: 'test' },
|
|
1430
|
-
clientInfo: {
|
|
1431
|
-
name: 'Café Clïent',
|
|
1432
|
-
version: '1.0.0'
|
|
1433
|
-
}
|
|
1434
|
-
});
|
|
1435
|
-
|
|
1436
|
-
// Diacritics should be removed (NFD normalization splits é into e + accent, then accent is removed)
|
|
1437
|
-
test.equal(client.clientInfo.name, 'Cafe Client');
|
|
1438
|
-
test.done();
|
|
1439
|
-
};
|
|
1440
|
-
|
|
1441
|
-
module.exports['Connection Edge: constructor with servername option'] = test => {
|
|
1442
|
-
let client = new ImapFlow({
|
|
1443
|
-
host: '192.168.1.1',
|
|
1444
|
-
port: 993,
|
|
1445
|
-
auth: { user: 'test', pass: 'test' },
|
|
1446
|
-
servername: 'mail.example.com'
|
|
1447
|
-
});
|
|
1448
|
-
|
|
1449
|
-
test.equal(client.servername, 'mail.example.com');
|
|
1450
|
-
test.done();
|
|
1451
|
-
};
|
|
1452
|
-
|
|
1453
|
-
module.exports['Connection Edge: constructor servername from host when not IP'] = test => {
|
|
1454
|
-
let client = new ImapFlow({
|
|
1455
|
-
host: 'imap.example.com',
|
|
1456
|
-
port: 993,
|
|
1457
|
-
auth: { user: 'test', pass: 'test' }
|
|
1458
|
-
});
|
|
1459
|
-
|
|
1460
|
-
test.equal(client.servername, 'imap.example.com');
|
|
1461
|
-
test.done();
|
|
1462
|
-
};
|
|
1463
|
-
|
|
1464
|
-
module.exports['Connection Edge: constructor servername false for IP host'] = test => {
|
|
1465
|
-
let client = new ImapFlow({
|
|
1466
|
-
host: '192.168.1.1',
|
|
1467
|
-
port: 993,
|
|
1468
|
-
auth: { user: 'test', pass: 'test' }
|
|
1469
|
-
});
|
|
1470
|
-
|
|
1471
|
-
test.equal(client.servername, false);
|
|
1472
|
-
test.done();
|
|
1473
|
-
};
|
|
1474
|
-
|
|
1475
|
-
module.exports['Connection Edge: connect throws if called twice'] = async test => {
|
|
1476
|
-
let client = new ImapFlow({
|
|
1477
|
-
host: 'imap.example.com',
|
|
1478
|
-
port: 993,
|
|
1479
|
-
auth: { user: 'test', pass: 'test' }
|
|
1480
|
-
});
|
|
1481
|
-
|
|
1482
|
-
// Mark as already called
|
|
1483
|
-
client._connectCalled = true;
|
|
1484
|
-
|
|
1485
|
-
try {
|
|
1486
|
-
await client.connect();
|
|
1487
|
-
test.ok(false, 'Should have thrown');
|
|
1488
|
-
} catch (err) {
|
|
1489
|
-
test.ok(err.message.includes('re-use'));
|
|
1490
|
-
}
|
|
1491
|
-
|
|
1492
|
-
test.done();
|
|
1493
|
-
};
|
|
1494
|
-
|
|
1495
|
-
// Helper to create a mock client with compression enabled
|
|
1496
|
-
async function setupCompressedClient() {
|
|
1497
|
-
let client = new ImapFlow({
|
|
1498
|
-
host: 'imap.example.com',
|
|
1499
|
-
port: 993,
|
|
1500
|
-
auth: { user: 'test', pass: 'test' }
|
|
1501
|
-
});
|
|
1502
|
-
|
|
1503
|
-
let mockSocket = new EventEmitter();
|
|
1504
|
-
mockSocket.pipe = dest => dest;
|
|
1505
|
-
mockSocket.unpipe = () => {};
|
|
1506
|
-
mockSocket.destroy = () => {};
|
|
1507
|
-
mockSocket.destroyed = false;
|
|
1508
|
-
client.socket = mockSocket;
|
|
1509
|
-
// Mock streamer that tracks destruction like a real stream, so close() drives it into
|
|
1510
|
-
// the destroyed state the post-close guards actually check.
|
|
1511
|
-
client.streamer = Object.assign(new EventEmitter(), {
|
|
1512
|
-
destroyed: false,
|
|
1513
|
-
destroy() {
|
|
1514
|
-
this.destroyed = true;
|
|
1515
|
-
}
|
|
1516
|
-
});
|
|
1517
|
-
|
|
1518
|
-
client.run = async command => {
|
|
1519
|
-
if (command === 'COMPRESS') {
|
|
1520
|
-
return true;
|
|
1521
|
-
}
|
|
1522
|
-
};
|
|
1523
|
-
|
|
1524
|
-
await client.compress();
|
|
1525
|
-
return { client, mockSocket };
|
|
1526
|
-
}
|
|
1527
|
-
|
|
1528
|
-
module.exports['Connection Edge: compress writeSocket error forwarded to socket when live'] = async test => {
|
|
1529
|
-
let { client, mockSocket } = await setupCompressedClient();
|
|
1530
|
-
|
|
1531
|
-
let errorReceived = false;
|
|
1532
|
-
mockSocket.on('error', err => {
|
|
1533
|
-
errorReceived = true;
|
|
1534
|
-
test.equal(err.message, 'writeSocket error');
|
|
1535
|
-
});
|
|
1536
|
-
|
|
1537
|
-
client.writeSocket.emit('error', new Error('writeSocket error'));
|
|
1538
|
-
|
|
1539
|
-
test.ok(errorReceived, 'Error should be forwarded to socket');
|
|
1540
|
-
test.done();
|
|
1541
|
-
};
|
|
1542
|
-
|
|
1543
|
-
module.exports['Connection Edge: compress _deflate error forwarded to socket when live'] = async test => {
|
|
1544
|
-
let { client, mockSocket } = await setupCompressedClient();
|
|
1545
|
-
|
|
1546
|
-
let errorReceived = false;
|
|
1547
|
-
mockSocket.on('error', err => {
|
|
1548
|
-
errorReceived = true;
|
|
1549
|
-
test.equal(err.message, 'deflate error');
|
|
1550
|
-
});
|
|
1551
|
-
|
|
1552
|
-
client._deflate.emit('error', new Error('deflate error'));
|
|
1553
|
-
|
|
1554
|
-
test.ok(errorReceived, 'Error should be forwarded to socket');
|
|
1555
|
-
test.done();
|
|
1556
|
-
};
|
|
1557
|
-
|
|
1558
|
-
module.exports['Connection Edge: compress readable after close does not crash'] = async test => {
|
|
1559
|
-
let { client } = await setupCompressedClient();
|
|
1560
|
-
|
|
1561
|
-
// Save reference before close() nulls it
|
|
1562
|
-
let writeSocket = client.writeSocket;
|
|
1563
|
-
|
|
1564
|
-
client.close();
|
|
1565
|
-
|
|
1566
|
-
// Emit readable on the saved reference after close has nulled this.writeSocket
|
|
1567
|
-
// This should not throw (guards at lines 1048 and 1021)
|
|
1568
|
-
test.doesNotThrow(() => {
|
|
1569
|
-
writeSocket.emit('readable');
|
|
1570
|
-
});
|
|
1571
|
-
|
|
1572
|
-
test.done();
|
|
1573
|
-
};
|
|
1574
|
-
|
|
1575
|
-
module.exports['Connection Edge: compress writeSocket error after close does not crash'] = async test => {
|
|
1576
|
-
let { client } = await setupCompressedClient();
|
|
1577
|
-
|
|
1578
|
-
let writeSocket = client.writeSocket;
|
|
1579
|
-
|
|
1580
|
-
client.close();
|
|
1581
|
-
|
|
1582
|
-
// Emit error on saved writeSocket after close has nulled this.socket
|
|
1583
|
-
// This should not throw (guard at lines 1053-1055)
|
|
1584
|
-
test.doesNotThrow(() => {
|
|
1585
|
-
writeSocket.emit('error', new Error('late writeSocket error'));
|
|
1586
|
-
});
|
|
1587
|
-
|
|
1588
|
-
test.done();
|
|
1589
|
-
};
|
|
1590
|
-
|
|
1591
|
-
module.exports['Connection Edge: compress _deflate error after close does not crash'] = async test => {
|
|
1592
|
-
let { client } = await setupCompressedClient();
|
|
1593
|
-
|
|
1594
|
-
let deflate = client._deflate;
|
|
1595
|
-
|
|
1596
|
-
client.close();
|
|
1597
|
-
|
|
1598
|
-
// Emit error on saved _deflate after close has nulled this.socket
|
|
1599
|
-
// This should not throw (guard at lines 1060-1062)
|
|
1600
|
-
test.doesNotThrow(() => {
|
|
1601
|
-
deflate.emit('error', new Error('late deflate error'));
|
|
1602
|
-
});
|
|
1603
|
-
|
|
1604
|
-
test.done();
|
|
1605
|
-
};
|
|
1606
|
-
|
|
1607
|
-
module.exports['Connection Edge: compress _inflate error after close does not crash'] = async test => {
|
|
1608
|
-
let { client } = await setupCompressedClient();
|
|
1609
|
-
|
|
1610
|
-
let inflate = client._inflate;
|
|
1611
|
-
|
|
1612
|
-
// close() removes the streamer 'error' listener and destroys the streamer.
|
|
1613
|
-
// A late inflate error must not be forwarded into the destroyed streamer
|
|
1614
|
-
// (which would throw an unhandled 'error' and crash the process).
|
|
1615
|
-
client.close();
|
|
1616
|
-
|
|
1617
|
-
// Confirm we actually reached the post-close state the guard targets.
|
|
1618
|
-
test.ok(client.streamer.destroyed, 'streamer is destroyed after close()');
|
|
1619
|
-
|
|
1620
|
-
// Re-attach an 'error' listener so listenerCount('error') > 0: this isolates the
|
|
1621
|
-
// `!streamer.destroyed` term of the guard. If forwarding still happened, this listener
|
|
1622
|
-
// would throw and escape doesNotThrow — proving the destroyed-check is what suppresses it.
|
|
1623
|
-
client.streamer.on('error', () => {
|
|
1624
|
-
throw new Error('inflate error must not be forwarded into a destroyed streamer');
|
|
1625
|
-
});
|
|
1626
|
-
|
|
1627
|
-
test.doesNotThrow(() => {
|
|
1628
|
-
inflate.emit('error', new Error('late inflate error'));
|
|
1629
|
-
});
|
|
1630
|
-
|
|
1631
|
-
test.done();
|
|
1632
|
-
};
|
|
1633
|
-
|
|
1634
|
-
// ============================================
|
|
1635
|
-
// Mailbox Lock Tests
|
|
1636
|
-
// ============================================
|
|
1637
|
-
|
|
1638
|
-
module.exports['Connection Edge: getMailboxLock queues lock in locks array'] = async test => {
|
|
1639
|
-
let client = new ImapFlow({
|
|
1640
|
-
host: 'imap.example.com',
|
|
1641
|
-
port: 993,
|
|
1642
|
-
auth: { user: 'test', pass: 'test' }
|
|
1643
|
-
});
|
|
1644
|
-
|
|
1645
|
-
// getMailboxLock should push to locks array then reject (no connection)
|
|
1646
|
-
try {
|
|
1647
|
-
await client.getMailboxLock('INBOX');
|
|
1648
|
-
test.ok(false, 'Should have thrown');
|
|
1649
|
-
} catch (err) {
|
|
1650
|
-
test.ok(err);
|
|
1651
|
-
test.equal(err.code, 'NoConnection');
|
|
1652
|
-
}
|
|
1653
|
-
|
|
1654
|
-
test.done();
|
|
1655
|
-
};
|
|
1656
|
-
|
|
1657
|
-
module.exports['Connection Edge: lockCounter increments with each getMailboxLock call'] = async test => {
|
|
1658
|
-
let client = new ImapFlow({
|
|
1659
|
-
host: 'imap.example.com',
|
|
1660
|
-
port: 993,
|
|
1661
|
-
auth: { user: 'test', pass: 'test' }
|
|
1662
|
-
});
|
|
1663
|
-
|
|
1664
|
-
let initial = client.lockCounter;
|
|
1665
|
-
let p1 = client.getMailboxLock('INBOX').catch(() => {});
|
|
1666
|
-
let p2 = client.getMailboxLock('Sent').catch(() => {});
|
|
1667
|
-
|
|
1668
|
-
test.equal(client.lockCounter, initial + 2);
|
|
1669
|
-
|
|
1670
|
-
await Promise.all([p1, p2]);
|
|
1671
|
-
test.done();
|
|
1672
|
-
};
|
|
1673
|
-
|
|
1674
|
-
module.exports['Connection Edge: lock rejected when connection not usable'] = async test => {
|
|
1675
|
-
let client = new ImapFlow({
|
|
1676
|
-
host: 'imap.example.com',
|
|
1677
|
-
port: 993,
|
|
1678
|
-
auth: { user: 'test', pass: 'test' }
|
|
1679
|
-
});
|
|
1680
|
-
|
|
1681
|
-
// usable is false by default, no socket
|
|
1682
|
-
try {
|
|
1683
|
-
await client.getMailboxLock('INBOX');
|
|
1684
|
-
test.ok(false, 'Should have thrown');
|
|
1685
|
-
} catch (err) {
|
|
1686
|
-
test.ok(err);
|
|
1687
|
-
test.equal(err.code, 'NoConnection');
|
|
1688
|
-
test.ok(err.message.includes('not available'));
|
|
1689
|
-
}
|
|
1690
|
-
test.done();
|
|
1691
|
-
};
|
|
1692
|
-
|
|
1693
|
-
module.exports['Connection Edge: processLocks with empty locks array completes'] = async test => {
|
|
1694
|
-
let client = new ImapFlow({
|
|
1695
|
-
host: 'imap.example.com',
|
|
1696
|
-
port: 993,
|
|
1697
|
-
auth: { user: 'test', pass: 'test' }
|
|
1698
|
-
});
|
|
1699
|
-
|
|
1700
|
-
// processLocks with no pending locks should complete without error
|
|
1701
|
-
await client.processLocks();
|
|
1702
|
-
test.equal(client.processingLock, false);
|
|
1703
|
-
test.done();
|
|
1704
|
-
};
|
|
1705
|
-
|
|
1706
|
-
module.exports['Connection Edge: multiple locks rejected when no connection'] = async test => {
|
|
1707
|
-
let client = new ImapFlow({
|
|
1708
|
-
host: 'imap.example.com',
|
|
1709
|
-
port: 993,
|
|
1710
|
-
auth: { user: 'test', pass: 'test' }
|
|
1711
|
-
});
|
|
1712
|
-
|
|
1713
|
-
// Queue multiple locks, all should reject
|
|
1714
|
-
let errors = [];
|
|
1715
|
-
let p1 = client.getMailboxLock('INBOX').catch(err => errors.push(err));
|
|
1716
|
-
let p2 = client.getMailboxLock('Sent').catch(err => errors.push(err));
|
|
1717
|
-
let p3 = client.getMailboxLock('Drafts').catch(err => errors.push(err));
|
|
1718
|
-
|
|
1719
|
-
await Promise.all([p1, p2, p3]);
|
|
1720
|
-
|
|
1721
|
-
test.equal(errors.length, 3);
|
|
1722
|
-
for (let err of errors) {
|
|
1723
|
-
test.equal(err.code, 'NoConnection');
|
|
1724
|
-
}
|
|
1725
|
-
test.done();
|
|
1726
|
-
};
|
|
1727
|
-
|
|
1728
|
-
// ============================================
|
|
1729
|
-
// reader() survives a write() throw without stalling the stream
|
|
1730
|
-
// ============================================
|
|
1731
|
-
|
|
1732
|
-
module.exports['Connection Edge: reader survives write throw on + continuation'] = async test => {
|
|
1733
|
-
let client = new ImapFlow({
|
|
1734
|
-
host: 'imap.example.com',
|
|
1735
|
-
port: 993,
|
|
1736
|
-
auth: { user: 'test', pass: 'test' },
|
|
1737
|
-
logger: false
|
|
1738
|
-
});
|
|
1739
|
-
|
|
1740
|
-
client.currentRequest = false;
|
|
1741
|
-
client.commandParts = [Buffer.from('literal-bytes')];
|
|
1742
|
-
|
|
1743
|
-
let writeAttempted = false;
|
|
1744
|
-
client.write = () => {
|
|
1745
|
-
writeAttempted = true;
|
|
1746
|
-
throw new Error('write boom');
|
|
1747
|
-
};
|
|
1748
|
-
|
|
1749
|
-
let nextCalled = false;
|
|
1750
|
-
let done = false;
|
|
1751
|
-
client.streamer.read = () => {
|
|
1752
|
-
if (done) {
|
|
1753
|
-
return null;
|
|
1754
|
-
}
|
|
1755
|
-
done = true;
|
|
1756
|
-
return {
|
|
1757
|
-
payload: Buffer.from('+ Ready'),
|
|
1758
|
-
literals: [],
|
|
1759
|
-
next: () => {
|
|
1760
|
-
nextCalled = true;
|
|
1761
|
-
}
|
|
1762
|
-
};
|
|
1763
|
-
};
|
|
1764
|
-
|
|
1765
|
-
let rejected = false;
|
|
1766
|
-
await client.reader().catch(() => {
|
|
1767
|
-
rejected = true;
|
|
1768
|
-
});
|
|
1769
|
-
|
|
1770
|
-
test.ok(writeAttempted, 'the literal-continuation write path was exercised');
|
|
1771
|
-
test.equal(rejected, false, 'reader did not reject on write throw');
|
|
1772
|
-
test.ok(nextCalled, 'data.next() was still called so the stream is not stalled');
|
|
1773
|
-
test.done();
|
|
1774
|
-
};
|
|
1775
|
-
|
|
1776
|
-
// ============================================
|
|
1777
|
-
// BYE greeting surfaces its reason on the connect rejection
|
|
1778
|
-
// ============================================
|
|
1779
|
-
|
|
1780
|
-
module.exports['Connection Edge: BYE greeting surfaces reason on connect rejection'] = async test => {
|
|
1781
|
-
// Deterministic ordering: the server ends the socket only AFTER the client has parsed the
|
|
1782
|
-
// BYE and set byeReason, instead of racing a fixed 50ms timer that could flake on slow CI.
|
|
1783
|
-
let signalByeParsed;
|
|
1784
|
-
let byeParsed = new Promise(resolve => {
|
|
1785
|
-
signalByeParsed = resolve;
|
|
1786
|
-
});
|
|
1787
|
-
|
|
1788
|
-
let server = net.createServer(socket => {
|
|
1789
|
-
socket.on('error', () => {});
|
|
1790
|
-
socket.write('* BYE Server too busy\r\n');
|
|
1791
|
-
byeParsed.then(() => socket.end());
|
|
1792
|
-
});
|
|
1793
|
-
|
|
1794
|
-
await new Promise(resolve => server.listen(0, '127.0.0.1', resolve));
|
|
1795
|
-
let port = server.address().port;
|
|
1796
|
-
|
|
1797
|
-
let client = new ImapFlow({
|
|
1798
|
-
host: '127.0.0.1',
|
|
1799
|
-
port,
|
|
1800
|
-
secure: false,
|
|
1801
|
-
disableAutoIdle: true,
|
|
1802
|
-
logger: false,
|
|
1803
|
-
auth: { user: 'test', pass: 'test' }
|
|
1804
|
-
});
|
|
1805
|
-
client.on('error', () => {});
|
|
1806
|
-
|
|
1807
|
-
// Signal once the client's BYE handler has run, so byeReason is guaranteed set before close().
|
|
1808
|
-
let origServerBye = client.serverBye.bind(client);
|
|
1809
|
-
client.serverBye = async parsed => {
|
|
1810
|
-
await origServerBye(parsed);
|
|
1811
|
-
signalByeParsed();
|
|
1812
|
-
};
|
|
1813
|
-
|
|
1814
|
-
let connectErr = null;
|
|
1815
|
-
try {
|
|
1816
|
-
await client.connect();
|
|
1817
|
-
test.ok(false, 'connect() should reject after a BYE greeting');
|
|
1818
|
-
} catch (err) {
|
|
1819
|
-
connectErr = err;
|
|
1820
|
-
}
|
|
1821
|
-
|
|
1822
|
-
test.ok(connectErr, 'connect() rejected');
|
|
1823
|
-
test.equal(connectErr.reason, 'Server too busy', 'BYE reason surfaced on the rejection');
|
|
1824
|
-
|
|
1825
|
-
client.close();
|
|
1826
|
-
server.close();
|
|
1827
|
-
test.done();
|
|
1828
|
-
};
|