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,661 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Tests for unhandled rejection prevention.
|
|
5
|
-
*
|
|
6
|
-
* When close() runs, it rejects pending promises synchronously. exec() and
|
|
7
|
-
* getMailboxLock() attach .catch(noop) before returning, so the rejection is
|
|
8
|
-
* observed immediately and does not trigger Node.js unhandledRejection.
|
|
9
|
-
* These tests verify that no unhandled rejections escape while the caller
|
|
10
|
-
* still receives the expected error.
|
|
11
|
-
*
|
|
12
|
-
* Key fix: exec() and getMailboxLock() are non-async, returning the promise
|
|
13
|
-
* directly (with .catch(noop)), so the caller gets the same promise object
|
|
14
|
-
* that has the noop handler. This prevents the async-wrapper double-promise
|
|
15
|
-
* issue where .catch(noop) on an inner promise doesn't protect the outer
|
|
16
|
-
* async wrapper promise.
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
const net = require('net');
|
|
20
|
-
const { ImapFlow } = require('../lib/imap-flow');
|
|
21
|
-
const { installRejectionDetector, slowConsumer } = require('./fixtures/test-client');
|
|
22
|
-
|
|
23
|
-
// Create a mock IMAP server with optional custom behavior.
|
|
24
|
-
// options.extraCapabilities - additional capabilities (e.g., 'IDLE')
|
|
25
|
-
// options.onCommand(socket, tag, command) - custom handler; return true if handled
|
|
26
|
-
function createMockServer(options) {
|
|
27
|
-
const extraCaps = options && options.extraCapabilities ? ' ' + options.extraCapabilities : '';
|
|
28
|
-
const onCommand = options && options.onCommand;
|
|
29
|
-
|
|
30
|
-
const server = net.createServer(socket => {
|
|
31
|
-
socket.write('* OK Mock IMAP Server ready\r\n');
|
|
32
|
-
|
|
33
|
-
socket.on('data', data => {
|
|
34
|
-
const lines = data
|
|
35
|
-
.toString()
|
|
36
|
-
.split('\r\n')
|
|
37
|
-
.filter(l => l.trim());
|
|
38
|
-
|
|
39
|
-
for (const line of lines) {
|
|
40
|
-
const parts = line.split(' ');
|
|
41
|
-
const tag = parts[0];
|
|
42
|
-
const command = parts[1] ? parts[1].toUpperCase() : '';
|
|
43
|
-
|
|
44
|
-
if (onCommand && onCommand(socket, tag, command, line)) {
|
|
45
|
-
continue;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
if (command === 'CAPABILITY') {
|
|
49
|
-
socket.write(`* CAPABILITY IMAP4rev1 AUTH=PLAIN${extraCaps}\r\n`);
|
|
50
|
-
socket.write(`${tag} OK CAPABILITY completed\r\n`);
|
|
51
|
-
} else if (command === 'LOGIN') {
|
|
52
|
-
socket.write(`${tag} OK LOGIN completed\r\n`);
|
|
53
|
-
} else if (command === 'LOGOUT') {
|
|
54
|
-
socket.write('* BYE Server logging out\r\n');
|
|
55
|
-
socket.write(`${tag} OK LOGOUT completed\r\n`);
|
|
56
|
-
socket.end();
|
|
57
|
-
} else if (command === 'NAMESPACE') {
|
|
58
|
-
socket.write('* NAMESPACE (("" "/")) NIL NIL\r\n');
|
|
59
|
-
socket.write(`${tag} OK NAMESPACE completed\r\n`);
|
|
60
|
-
} else if (command === 'COMPRESS') {
|
|
61
|
-
socket.write(`${tag} NO COMPRESS not supported\r\n`);
|
|
62
|
-
} else if (command === 'ENABLE') {
|
|
63
|
-
socket.write(`${tag} OK ENABLE completed\r\n`);
|
|
64
|
-
} else if (command === 'ID') {
|
|
65
|
-
socket.write('* ID NIL\r\n');
|
|
66
|
-
socket.write(`${tag} OK ID completed\r\n`);
|
|
67
|
-
} else if (command === 'SELECT' || command === 'EXAMINE') {
|
|
68
|
-
socket.write('* 1 EXISTS\r\n');
|
|
69
|
-
socket.write('* 1 RECENT\r\n');
|
|
70
|
-
socket.write('* OK [UIDVALIDITY 1] UIDs valid\r\n');
|
|
71
|
-
socket.write('* OK [UIDNEXT 2] Predicted next UID\r\n');
|
|
72
|
-
socket.write(`${tag} OK SELECT completed\r\n`);
|
|
73
|
-
} else if (command === 'NOOP') {
|
|
74
|
-
socket.write(`${tag} OK NOOP completed\r\n`);
|
|
75
|
-
} else if (tag && command) {
|
|
76
|
-
socket.write(`${tag} OK Command completed\r\n`);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
socket.on('error', () => {});
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
return server;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// Drives a connection to the one state both IDLE-waiter tests need: IDLE issued but never
|
|
88
|
-
// acknowledged with a "+", so preCheck() cannot send DONE and anything it queues stays queued
|
|
89
|
-
// until close() tears the IDLE command down. Hands the connected client to `run`, and always
|
|
90
|
-
// closes the server so the test ends.
|
|
91
|
-
function withQueuedIdleWaiter(test, options, run, onError) {
|
|
92
|
-
const server = createMockServer({
|
|
93
|
-
extraCapabilities: 'IDLE',
|
|
94
|
-
onCommand(socket, tag, command) {
|
|
95
|
-
if (command === 'IDLE') {
|
|
96
|
-
return true;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
server.listen(0, '127.0.0.1', async () => {
|
|
102
|
-
const client = new ImapFlow(
|
|
103
|
-
Object.assign(
|
|
104
|
-
{
|
|
105
|
-
host: '127.0.0.1',
|
|
106
|
-
port: server.address().port,
|
|
107
|
-
secure: false,
|
|
108
|
-
logger: false,
|
|
109
|
-
disableAutoIdle: true,
|
|
110
|
-
auth: { user: 'test', pass: 'test' }
|
|
111
|
-
},
|
|
112
|
-
options
|
|
113
|
-
)
|
|
114
|
-
);
|
|
115
|
-
|
|
116
|
-
try {
|
|
117
|
-
await client.connect();
|
|
118
|
-
await client.mailboxOpen('INBOX');
|
|
119
|
-
|
|
120
|
-
client.idle().catch(() => {
|
|
121
|
-
// Expected: IDLE rejects when the connection is closed
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
// Let the IDLE command reach the server and install preCheck()
|
|
125
|
-
await new Promise(r => setTimeout(r, 100));
|
|
126
|
-
test.equal(typeof client.preCheck, 'function', 'IDLE should have installed preCheck()');
|
|
127
|
-
|
|
128
|
-
await run(client);
|
|
129
|
-
} catch (err) {
|
|
130
|
-
if (onError) {
|
|
131
|
-
onError(err);
|
|
132
|
-
}
|
|
133
|
-
test.ok(false, 'Unexpected error: ' + err.message);
|
|
134
|
-
} finally {
|
|
135
|
-
server.close(() => test.done());
|
|
136
|
-
}
|
|
137
|
-
});
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
exports['Unhandled Rejection Prevention'] = {
|
|
141
|
-
'exec() + close() race should not cause unhandled rejection'(test) {
|
|
142
|
-
test.expect(2);
|
|
143
|
-
|
|
144
|
-
const client = new ImapFlow({
|
|
145
|
-
host: '127.0.0.1',
|
|
146
|
-
port: 1,
|
|
147
|
-
secure: false,
|
|
148
|
-
logger: false
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
// Set up state so exec() doesn't reject synchronously
|
|
152
|
-
client.state = client.states.AUTHENTICATED;
|
|
153
|
-
client.socket = new net.Socket();
|
|
154
|
-
|
|
155
|
-
const detector = installRejectionDetector(test);
|
|
156
|
-
|
|
157
|
-
// Create a pending request, then immediately close.
|
|
158
|
-
// close() rejects the request synchronously. The .catch(noop) on the
|
|
159
|
-
// exec() promise ensures the rejection is observed immediately.
|
|
160
|
-
let promise = client.exec('NOOP');
|
|
161
|
-
client.close();
|
|
162
|
-
|
|
163
|
-
// Wait for setImmediate and microtask rejections to settle
|
|
164
|
-
setTimeout(() => {
|
|
165
|
-
detector.check();
|
|
166
|
-
promise.catch(err => {
|
|
167
|
-
test.equal(err.code, 'NoConnection', 'caller should receive NoConnection error');
|
|
168
|
-
test.done();
|
|
169
|
-
});
|
|
170
|
-
}, 100);
|
|
171
|
-
},
|
|
172
|
-
|
|
173
|
-
'exec() on LOGOUT state should not cause unhandled rejection'(test) {
|
|
174
|
-
test.expect(2);
|
|
175
|
-
|
|
176
|
-
const client = new ImapFlow({
|
|
177
|
-
host: '127.0.0.1',
|
|
178
|
-
port: 1,
|
|
179
|
-
secure: false,
|
|
180
|
-
logger: false
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
client.state = client.states.LOGOUT;
|
|
184
|
-
|
|
185
|
-
const detector = installRejectionDetector(test);
|
|
186
|
-
|
|
187
|
-
// exec() detects LOGOUT and returns a rejected promise with .catch(noop)
|
|
188
|
-
let promise = client.exec('NOOP');
|
|
189
|
-
|
|
190
|
-
setTimeout(() => {
|
|
191
|
-
detector.check();
|
|
192
|
-
promise.catch(err => {
|
|
193
|
-
test.equal(err.code, 'NoConnection', 'should reject with NoConnection');
|
|
194
|
-
test.done();
|
|
195
|
-
});
|
|
196
|
-
}, 100);
|
|
197
|
-
},
|
|
198
|
-
|
|
199
|
-
'exec() with destroyed socket should not cause unhandled rejection'(test) {
|
|
200
|
-
test.expect(2);
|
|
201
|
-
|
|
202
|
-
const client = new ImapFlow({
|
|
203
|
-
host: '127.0.0.1',
|
|
204
|
-
port: 1,
|
|
205
|
-
secure: false,
|
|
206
|
-
logger: false
|
|
207
|
-
});
|
|
208
|
-
|
|
209
|
-
client.state = client.states.AUTHENTICATED;
|
|
210
|
-
let sock = new net.Socket();
|
|
211
|
-
sock.destroy();
|
|
212
|
-
client.socket = sock;
|
|
213
|
-
|
|
214
|
-
const detector = installRejectionDetector(test);
|
|
215
|
-
|
|
216
|
-
let promise = client.exec('NOOP');
|
|
217
|
-
|
|
218
|
-
setTimeout(() => {
|
|
219
|
-
detector.check();
|
|
220
|
-
promise.catch(err => {
|
|
221
|
-
test.equal(err.code, 'EConnectionClosed', 'should reject with EConnectionClosed');
|
|
222
|
-
test.done();
|
|
223
|
-
});
|
|
224
|
-
}, 100);
|
|
225
|
-
},
|
|
226
|
-
|
|
227
|
-
'multiple pending exec() + close() should not cause unhandled rejections'(test) {
|
|
228
|
-
test.expect(7);
|
|
229
|
-
|
|
230
|
-
const client = new ImapFlow({
|
|
231
|
-
host: '127.0.0.1',
|
|
232
|
-
port: 1,
|
|
233
|
-
secure: false,
|
|
234
|
-
logger: false
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
client.state = client.states.AUTHENTICATED;
|
|
238
|
-
client.socket = new net.Socket();
|
|
239
|
-
|
|
240
|
-
const detector = installRejectionDetector(test);
|
|
241
|
-
|
|
242
|
-
let p1 = client.exec('NOOP');
|
|
243
|
-
let p2 = client.exec('NOOP');
|
|
244
|
-
let p3 = client.exec('NOOP');
|
|
245
|
-
client.close();
|
|
246
|
-
|
|
247
|
-
setTimeout(() => {
|
|
248
|
-
detector.check();
|
|
249
|
-
|
|
250
|
-
let settled = 0;
|
|
251
|
-
const checkDone = () => {
|
|
252
|
-
if (++settled === 3) {
|
|
253
|
-
test.done();
|
|
254
|
-
}
|
|
255
|
-
};
|
|
256
|
-
|
|
257
|
-
p1.catch(err => {
|
|
258
|
-
test.ok(err, 'p1 should reject');
|
|
259
|
-
test.equal(err.code, 'NoConnection', 'p1 error code');
|
|
260
|
-
checkDone();
|
|
261
|
-
});
|
|
262
|
-
p2.catch(err => {
|
|
263
|
-
test.ok(err, 'p2 should reject');
|
|
264
|
-
test.equal(err.code, 'NoConnection', 'p2 error code');
|
|
265
|
-
checkDone();
|
|
266
|
-
});
|
|
267
|
-
p3.catch(err => {
|
|
268
|
-
test.ok(err, 'p3 should reject');
|
|
269
|
-
test.equal(err.code, 'NoConnection', 'p3 error code');
|
|
270
|
-
checkDone();
|
|
271
|
-
});
|
|
272
|
-
}, 100);
|
|
273
|
-
},
|
|
274
|
-
|
|
275
|
-
'getMailboxLock() + close() race should not cause unhandled rejection'(test) {
|
|
276
|
-
test.expect(2);
|
|
277
|
-
|
|
278
|
-
const server = createMockServer();
|
|
279
|
-
|
|
280
|
-
server.listen(0, '127.0.0.1', async () => {
|
|
281
|
-
const port = server.address().port;
|
|
282
|
-
|
|
283
|
-
const client = new ImapFlow({
|
|
284
|
-
host: '127.0.0.1',
|
|
285
|
-
port,
|
|
286
|
-
secure: false,
|
|
287
|
-
logger: false,
|
|
288
|
-
auth: {
|
|
289
|
-
user: 'test',
|
|
290
|
-
pass: 'test'
|
|
291
|
-
}
|
|
292
|
-
});
|
|
293
|
-
|
|
294
|
-
const detector = installRejectionDetector(test);
|
|
295
|
-
|
|
296
|
-
try {
|
|
297
|
-
await client.connect();
|
|
298
|
-
|
|
299
|
-
// Request a lock, then immediately close before it resolves.
|
|
300
|
-
// close() rejects pending locks synchronously.
|
|
301
|
-
let lockPromise = client.getMailboxLock('INBOX');
|
|
302
|
-
client.close();
|
|
303
|
-
|
|
304
|
-
try {
|
|
305
|
-
await lockPromise;
|
|
306
|
-
test.ok(false, 'lockPromise should have rejected');
|
|
307
|
-
} catch (err) {
|
|
308
|
-
// Wait for any deferred unhandled rejection
|
|
309
|
-
await new Promise(r => setTimeout(r, 100));
|
|
310
|
-
detector.check();
|
|
311
|
-
test.equal(err.code, 'NoConnection', 'caller should receive NoConnection error');
|
|
312
|
-
}
|
|
313
|
-
} catch (err) {
|
|
314
|
-
detector.check();
|
|
315
|
-
test.ok(false, 'unexpected error: ' + err.message);
|
|
316
|
-
} finally {
|
|
317
|
-
server.close(() => test.done());
|
|
318
|
-
}
|
|
319
|
-
});
|
|
320
|
-
},
|
|
321
|
-
|
|
322
|
-
'connect() + close() during greeting timeout should not cause unhandled rejection'(test) {
|
|
323
|
-
test.expect(2);
|
|
324
|
-
|
|
325
|
-
// Server that accepts TCP but never sends a greeting.
|
|
326
|
-
// The client's greetingTimeout will fire.
|
|
327
|
-
const server = net.createServer(socket => {
|
|
328
|
-
// Intentionally send nothing - let client timeout
|
|
329
|
-
socket.on('error', () => {});
|
|
330
|
-
});
|
|
331
|
-
|
|
332
|
-
server.listen(0, '127.0.0.1', () => {
|
|
333
|
-
const port = server.address().port;
|
|
334
|
-
|
|
335
|
-
const client = new ImapFlow({
|
|
336
|
-
host: '127.0.0.1',
|
|
337
|
-
port,
|
|
338
|
-
secure: false,
|
|
339
|
-
logger: false,
|
|
340
|
-
greetingTimeout: 200,
|
|
341
|
-
auth: {
|
|
342
|
-
user: 'test',
|
|
343
|
-
pass: 'test'
|
|
344
|
-
}
|
|
345
|
-
});
|
|
346
|
-
|
|
347
|
-
const detector = installRejectionDetector(test);
|
|
348
|
-
|
|
349
|
-
// Close shortly after the TCP connection is established but
|
|
350
|
-
// before the greeting is received
|
|
351
|
-
const origSetSocket = client.setSocketHandlers;
|
|
352
|
-
let closeTriggered = false;
|
|
353
|
-
client.setSocketHandlers = function () {
|
|
354
|
-
origSetSocket.call(this);
|
|
355
|
-
if (!closeTriggered) {
|
|
356
|
-
closeTriggered = true;
|
|
357
|
-
// Close on the next tick to simulate race
|
|
358
|
-
setImmediate(() => client.close());
|
|
359
|
-
}
|
|
360
|
-
};
|
|
361
|
-
|
|
362
|
-
client.connect().then(
|
|
363
|
-
() => {
|
|
364
|
-
detector.check();
|
|
365
|
-
test.ok(false, 'connect() should have rejected');
|
|
366
|
-
server.close(() => test.done());
|
|
367
|
-
},
|
|
368
|
-
err => {
|
|
369
|
-
setTimeout(() => {
|
|
370
|
-
detector.check();
|
|
371
|
-
test.ok(err && err.code, 'connect() should reject with an error code');
|
|
372
|
-
server.close(() => test.done());
|
|
373
|
-
}, 100);
|
|
374
|
-
}
|
|
375
|
-
);
|
|
376
|
-
});
|
|
377
|
-
},
|
|
378
|
-
|
|
379
|
-
'BYE during IDLE should not cause unhandled rejection (Death 1)'(test) {
|
|
380
|
-
test.expect(2);
|
|
381
|
-
|
|
382
|
-
const server = createMockServer({
|
|
383
|
-
extraCapabilities: 'IDLE',
|
|
384
|
-
onCommand(socket, tag, command) {
|
|
385
|
-
if (command === 'IDLE') {
|
|
386
|
-
socket.write('+ idling\r\n');
|
|
387
|
-
// After a short delay, send BYE and close (simulates token expiry)
|
|
388
|
-
setTimeout(() => {
|
|
389
|
-
try {
|
|
390
|
-
socket.write('* BYE Session invalidated - AccessTokenExpired\r\n');
|
|
391
|
-
socket.end();
|
|
392
|
-
} catch {
|
|
393
|
-
// socket may already be closed
|
|
394
|
-
}
|
|
395
|
-
}, 50);
|
|
396
|
-
return true;
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
});
|
|
400
|
-
|
|
401
|
-
server.listen(0, '127.0.0.1', async () => {
|
|
402
|
-
const port = server.address().port;
|
|
403
|
-
|
|
404
|
-
const client = new ImapFlow({
|
|
405
|
-
host: '127.0.0.1',
|
|
406
|
-
port,
|
|
407
|
-
secure: false,
|
|
408
|
-
logger: false,
|
|
409
|
-
auth: {
|
|
410
|
-
user: 'test',
|
|
411
|
-
pass: 'test'
|
|
412
|
-
}
|
|
413
|
-
});
|
|
414
|
-
|
|
415
|
-
const detector = installRejectionDetector(test);
|
|
416
|
-
|
|
417
|
-
try {
|
|
418
|
-
await client.connect();
|
|
419
|
-
await client.mailboxOpen('INBOX');
|
|
420
|
-
|
|
421
|
-
// Start IDLE and wait for the BYE-triggered close
|
|
422
|
-
await new Promise((resolve, reject) => {
|
|
423
|
-
client.idle().catch(() => {
|
|
424
|
-
// Expected: IDLE rejects when BYE arrives
|
|
425
|
-
});
|
|
426
|
-
|
|
427
|
-
client.on('close', () => {
|
|
428
|
-
// Wait for any deferred unhandled rejections to surface
|
|
429
|
-
setTimeout(resolve, 100);
|
|
430
|
-
});
|
|
431
|
-
|
|
432
|
-
// Safety timeout
|
|
433
|
-
setTimeout(() => reject(new Error('Timeout waiting for close')), 5000);
|
|
434
|
-
});
|
|
435
|
-
|
|
436
|
-
detector.check();
|
|
437
|
-
test.ok(true, 'No unhandled rejection during IDLE + BYE');
|
|
438
|
-
} catch (err) {
|
|
439
|
-
detector.check();
|
|
440
|
-
test.ok(false, 'Unexpected error: ' + err.message);
|
|
441
|
-
} finally {
|
|
442
|
-
server.close(() => test.done());
|
|
443
|
-
}
|
|
444
|
-
});
|
|
445
|
-
},
|
|
446
|
-
|
|
447
|
-
'close() stamps the rejection site, command and connection id on the error'(test) {
|
|
448
|
-
test.expect(5);
|
|
449
|
-
|
|
450
|
-
const client = new ImapFlow({
|
|
451
|
-
host: '127.0.0.1',
|
|
452
|
-
port: 1,
|
|
453
|
-
secure: false,
|
|
454
|
-
logger: false,
|
|
455
|
-
id: 'test-cid'
|
|
456
|
-
});
|
|
457
|
-
|
|
458
|
-
client.state = client.states.AUTHENTICATED;
|
|
459
|
-
client.socket = new net.Socket();
|
|
460
|
-
|
|
461
|
-
const detector = installRejectionDetector(test);
|
|
462
|
-
|
|
463
|
-
let promise = client.exec('NOOP');
|
|
464
|
-
client.close();
|
|
465
|
-
|
|
466
|
-
setTimeout(() => {
|
|
467
|
-
detector.check();
|
|
468
|
-
promise.catch(err => {
|
|
469
|
-
test.equal(err.code, 'NoConnection', 'caller should receive NoConnection error');
|
|
470
|
-
test.equal(err.rejectedFrom, 'pendingRequest', 'error should name the rejection site');
|
|
471
|
-
test.equal(err.command, 'NOOP', 'error should name the command it belonged to');
|
|
472
|
-
test.equal(err.cid, 'test-cid', 'error should carry the connection id');
|
|
473
|
-
test.done();
|
|
474
|
-
});
|
|
475
|
-
}, 100);
|
|
476
|
-
},
|
|
477
|
-
|
|
478
|
-
'a preCheck() waiter rejection names its own site, not the IDLE command'(test) {
|
|
479
|
-
test.expect(8);
|
|
480
|
-
|
|
481
|
-
withQueuedIdleWaiter(test, { id: 'waiter-cid' }, async client => {
|
|
482
|
-
let waiter = client.preCheck();
|
|
483
|
-
client.close();
|
|
484
|
-
|
|
485
|
-
try {
|
|
486
|
-
await waiter;
|
|
487
|
-
test.ok(false, 'the waiter should have rejected');
|
|
488
|
-
} catch (err) {
|
|
489
|
-
test.equal(err.rejectedFrom, 'preCheckWaiter', 'the waiter names its own rejection site');
|
|
490
|
-
test.equal(err.cid, 'waiter-cid', 'the waiter error carries the connection id');
|
|
491
|
-
test.equal(err.code, 'NoConnection', 'the original code is preserved for callers that branch on it');
|
|
492
|
-
test.strictEqual(err.command, undefined, 'the command belongs to the site the error came from, not to the waiter');
|
|
493
|
-
test.ok(err.cause, 'the command failure travels on as the cause');
|
|
494
|
-
test.equal(err.cause.rejectedFrom, 'pendingRequest', 'the cause still names the command site');
|
|
495
|
-
test.equal(err.cause.command, 'IDLE', 'the cause still names the command');
|
|
496
|
-
}
|
|
497
|
-
});
|
|
498
|
-
},
|
|
499
|
-
|
|
500
|
-
'preCheck() waiter rejected by close() should not cause unhandled rejection'(test) {
|
|
501
|
-
test.expect(2);
|
|
502
|
-
|
|
503
|
-
const detector = installRejectionDetector(test);
|
|
504
|
-
|
|
505
|
-
withQueuedIdleWaiter(
|
|
506
|
-
test,
|
|
507
|
-
{},
|
|
508
|
-
async client => {
|
|
509
|
-
// Request an IDLE break and drop the returned promise on the floor, so the queued
|
|
510
|
-
// waiter has no handler of its own when close() rejects it
|
|
511
|
-
client.preCheck();
|
|
512
|
-
client.close();
|
|
513
|
-
|
|
514
|
-
await new Promise(r => setTimeout(r, 100));
|
|
515
|
-
detector.check();
|
|
516
|
-
},
|
|
517
|
-
() => detector.check()
|
|
518
|
-
);
|
|
519
|
-
},
|
|
520
|
-
|
|
521
|
-
'BAD response to FETCH should not cause unhandled rejection (Death 2)'(test) {
|
|
522
|
-
test.expect(3);
|
|
523
|
-
|
|
524
|
-
const server = createMockServer({
|
|
525
|
-
onCommand(socket, tag, command) {
|
|
526
|
-
if (command === 'UID' || command === 'FETCH') {
|
|
527
|
-
// Simulate "Server Unavailable" error for any FETCH variant
|
|
528
|
-
socket.write(`${tag} BAD Server Unavailable. 15\r\n`);
|
|
529
|
-
return true;
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
});
|
|
533
|
-
|
|
534
|
-
server.listen(0, '127.0.0.1', async () => {
|
|
535
|
-
const port = server.address().port;
|
|
536
|
-
|
|
537
|
-
const client = new ImapFlow({
|
|
538
|
-
host: '127.0.0.1',
|
|
539
|
-
port,
|
|
540
|
-
secure: false,
|
|
541
|
-
logger: false,
|
|
542
|
-
auth: {
|
|
543
|
-
user: 'test',
|
|
544
|
-
pass: 'test'
|
|
545
|
-
}
|
|
546
|
-
});
|
|
547
|
-
|
|
548
|
-
const detector = installRejectionDetector(test);
|
|
549
|
-
|
|
550
|
-
try {
|
|
551
|
-
await client.connect();
|
|
552
|
-
await client.mailboxOpen('INBOX');
|
|
553
|
-
|
|
554
|
-
// Attempt a FETCH that will get BAD response
|
|
555
|
-
try {
|
|
556
|
-
await client.fetchOne('*', { uid: true }, { uid: true });
|
|
557
|
-
test.ok(false, 'fetchOne should have rejected');
|
|
558
|
-
} catch (err) {
|
|
559
|
-
test.equal(err.message, 'Command failed', 'Should get Command failed error');
|
|
560
|
-
test.equal(err.responseStatus, 'BAD', 'Response status should be BAD');
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
// Wait for any deferred unhandled rejections
|
|
564
|
-
await new Promise(r => setTimeout(r, 100));
|
|
565
|
-
detector.check();
|
|
566
|
-
|
|
567
|
-
client.close();
|
|
568
|
-
} catch (err) {
|
|
569
|
-
detector.check();
|
|
570
|
-
test.ok(false, 'Unexpected error: ' + err.message);
|
|
571
|
-
} finally {
|
|
572
|
-
server.close(() => test.done());
|
|
573
|
-
}
|
|
574
|
-
});
|
|
575
|
-
},
|
|
576
|
-
|
|
577
|
-
'a download losing its connection mid-chunk should not cause unhandled rejection (Death 3)'(test) {
|
|
578
|
-
// The reported production crash, end to end over a real socket: a chunked download whose
|
|
579
|
-
// consumer is applying backpressure, and a connection that goes away with the next
|
|
580
|
-
// UID FETCH in flight. close() rejects that pending request, and the rejection has to
|
|
581
|
-
// reach the content stream rather than a promise nobody holds.
|
|
582
|
-
test.expect(4);
|
|
583
|
-
|
|
584
|
-
const CHUNK = 64 * 1024;
|
|
585
|
-
const TOTAL = CHUNK * 8;
|
|
586
|
-
let fetchCount = 0;
|
|
587
|
-
|
|
588
|
-
const server = createMockServer({
|
|
589
|
-
onCommand(socket, tag, command) {
|
|
590
|
-
if (command !== 'UID') {
|
|
591
|
-
return;
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
fetchCount++;
|
|
595
|
-
if (fetchCount > 3) {
|
|
596
|
-
// the production event: the socket ends with a UID FETCH pending
|
|
597
|
-
socket.destroy();
|
|
598
|
-
return true;
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
// one BODY[]<offset> chunk, in the literal form download() asks for
|
|
602
|
-
socket.write(`* 1 FETCH (UID 1 RFC822.SIZE ${TOTAL} BODY[]<${(fetchCount - 1) * CHUNK}> {${CHUNK}}\r\n`);
|
|
603
|
-
socket.write(Buffer.alloc(CHUNK, 0x61));
|
|
604
|
-
socket.write(')\r\n');
|
|
605
|
-
socket.write(`${tag} OK FETCH completed\r\n`);
|
|
606
|
-
return true;
|
|
607
|
-
}
|
|
608
|
-
});
|
|
609
|
-
|
|
610
|
-
server.listen(0, '127.0.0.1', async () => {
|
|
611
|
-
const client = new ImapFlow({
|
|
612
|
-
host: '127.0.0.1',
|
|
613
|
-
port: server.address().port,
|
|
614
|
-
secure: false,
|
|
615
|
-
logger: false,
|
|
616
|
-
disableAutoIdle: true,
|
|
617
|
-
auth: { user: 'test', pass: 'test' }
|
|
618
|
-
});
|
|
619
|
-
|
|
620
|
-
// the connection is destroyed under the client on purpose
|
|
621
|
-
client.on('error', () => false);
|
|
622
|
-
|
|
623
|
-
const detector = installRejectionDetector(test);
|
|
624
|
-
|
|
625
|
-
try {
|
|
626
|
-
await client.connect();
|
|
627
|
-
await client.mailboxOpen('INBOX');
|
|
628
|
-
|
|
629
|
-
let { content } = await client.download('1', false, { uid: true, chunkSize: CHUNK });
|
|
630
|
-
|
|
631
|
-
let received = 0;
|
|
632
|
-
let streamErr = null;
|
|
633
|
-
content.on('error', err => {
|
|
634
|
-
streamErr = err;
|
|
635
|
-
});
|
|
636
|
-
// The delay is load-bearing: it has to outlast a loopback round trip, or the
|
|
637
|
-
// pipeline drains between chunks, writeChunk() never returns false, and the
|
|
638
|
-
// backpressure wait this test exists for is never entered
|
|
639
|
-
content.pipe(slowConsumer({ delay: 30, onChunk: chunk => (received += chunk.length) }));
|
|
640
|
-
|
|
641
|
-
await new Promise(resolve => {
|
|
642
|
-
content.on('close', resolve);
|
|
643
|
-
content.on('error', resolve);
|
|
644
|
-
});
|
|
645
|
-
await new Promise(r => setTimeout(r, 100));
|
|
646
|
-
detector.check();
|
|
647
|
-
|
|
648
|
-
test.ok(received >= CHUNK, 'a chunk drained through the consumer before the connection went away');
|
|
649
|
-
test.ok(streamErr, 'the failure surfaced on the content stream');
|
|
650
|
-
test.equal(streamErr && streamErr.code, 'NoConnection', 'the caller gets the connection error');
|
|
651
|
-
|
|
652
|
-
client.close();
|
|
653
|
-
} catch (err) {
|
|
654
|
-
detector.check();
|
|
655
|
-
test.ok(false, 'Unexpected error: ' + err.message);
|
|
656
|
-
} finally {
|
|
657
|
-
server.close(() => test.done());
|
|
658
|
-
}
|
|
659
|
-
});
|
|
660
|
-
}
|
|
661
|
-
};
|