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,101 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const { ImapFlow } = require('../lib/imap-flow');
|
|
4
|
-
const { AuthenticationFailure } = require('../lib/tools');
|
|
5
|
-
|
|
6
|
-
module.exports['Authentication: Password auth configuration'] = test => {
|
|
7
|
-
let client = new ImapFlow({
|
|
8
|
-
host: 'imap.example.com',
|
|
9
|
-
auth: {
|
|
10
|
-
user: 'testuser',
|
|
11
|
-
pass: 'testpass'
|
|
12
|
-
}
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
test.equal(client.options.auth.user, 'testuser');
|
|
16
|
-
test.equal(client.options.auth.pass, 'testpass');
|
|
17
|
-
test.done();
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
module.exports['Authentication: OAuth2 auth configuration'] = test => {
|
|
21
|
-
let client = new ImapFlow({
|
|
22
|
-
host: 'imap.example.com',
|
|
23
|
-
auth: {
|
|
24
|
-
user: 'testuser',
|
|
25
|
-
accessToken: 'oauth2_token_here'
|
|
26
|
-
}
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
test.equal(client.options.auth.user, 'testuser');
|
|
30
|
-
test.equal(client.options.auth.accessToken, 'oauth2_token_here');
|
|
31
|
-
test.done();
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
module.exports['Authentication: Login method specification'] = test => {
|
|
35
|
-
let client = new ImapFlow({
|
|
36
|
-
host: 'imap.example.com',
|
|
37
|
-
auth: {
|
|
38
|
-
user: 'testuser',
|
|
39
|
-
pass: 'testpass',
|
|
40
|
-
loginMethod: 'AUTH=PLAIN'
|
|
41
|
-
}
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
test.equal(client.options.auth.loginMethod, 'AUTH=PLAIN');
|
|
45
|
-
test.done();
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
module.exports['Authentication: SASL PLAIN with authzid for impersonation'] = test => {
|
|
49
|
-
let client = new ImapFlow({
|
|
50
|
-
host: 'imap.example.com',
|
|
51
|
-
auth: {
|
|
52
|
-
user: 'admin@example.com',
|
|
53
|
-
pass: 'adminpass',
|
|
54
|
-
authzid: 'user@example.com',
|
|
55
|
-
loginMethod: 'AUTH=PLAIN'
|
|
56
|
-
}
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
test.equal(client.options.auth.user, 'admin@example.com');
|
|
60
|
-
test.equal(client.options.auth.pass, 'adminpass');
|
|
61
|
-
test.equal(client.options.auth.authzid, 'user@example.com');
|
|
62
|
-
test.equal(client.options.auth.loginMethod, 'AUTH=PLAIN');
|
|
63
|
-
test.done();
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
module.exports['Authentication: AuthenticationFailure error structure'] = test => {
|
|
67
|
-
let error = new AuthenticationFailure('Invalid credentials');
|
|
68
|
-
|
|
69
|
-
test.ok(error instanceof Error);
|
|
70
|
-
test.equal(error.constructor.name, 'AuthenticationFailure');
|
|
71
|
-
test.equal(error.message, 'Invalid credentials');
|
|
72
|
-
test.done();
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
module.exports['Authentication: Verify-only mode'] = test => {
|
|
76
|
-
let client = new ImapFlow({
|
|
77
|
-
host: 'imap.example.com',
|
|
78
|
-
auth: {
|
|
79
|
-
user: 'testuser',
|
|
80
|
-
pass: 'testpass'
|
|
81
|
-
},
|
|
82
|
-
verifyOnly: true
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
test.equal(client.options.verifyOnly, true);
|
|
86
|
-
test.done();
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
module.exports['Authentication: Disable auto IDLE'] = test => {
|
|
90
|
-
let client = new ImapFlow({
|
|
91
|
-
host: 'imap.example.com',
|
|
92
|
-
auth: {
|
|
93
|
-
user: 'testuser',
|
|
94
|
-
pass: 'testpass'
|
|
95
|
-
},
|
|
96
|
-
disableAutoIdle: true
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
test.equal(client.options.disableAutoIdle, true);
|
|
100
|
-
test.done();
|
|
101
|
-
};
|
package/test/auto-idle-test.js
DELETED
|
@@ -1,470 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
// Auto-IDLE policy: how the `autoIdleDelay` option is normalized, when the timer is allowed to
|
|
4
|
-
// arm at all, and how the socket inactivity watchdog treats connections that are supposed to be
|
|
5
|
-
// quiet.
|
|
6
|
-
//
|
|
7
|
-
// All of it matters more than the option looks. The configured value reaches setTimeout directly,
|
|
8
|
-
// where NaN, a negative number and anything above the 32-bit range all become a 1ms timer, i.e.
|
|
9
|
-
// an IDLE/DONE round trip around every single command. Arming while a caller still owns the
|
|
10
|
-
// connection injects that round trip - or, with `missingIdleCommand` set to SELECT or STATUS, a
|
|
11
|
-
// mailbox poll - between two of that caller's own commands. And the watchdog has to tell a
|
|
12
|
-
// connection that is legitimately quiet (idling, a stalled download, a held lock) from one whose
|
|
13
|
-
// command reply is overdue.
|
|
14
|
-
|
|
15
|
-
const { makeClient, makeIdleReadyClient, chunkedFetchOne } = require('./fixtures/test-client');
|
|
16
|
-
const { withFakeTimers } = require('./fixtures/fake-timers');
|
|
17
|
-
|
|
18
|
-
const DEFAULT_DELAY = 15 * 1000;
|
|
19
|
-
const DEFAULT_SOCKET_TIMEOUT = 5 * 60 * 1000;
|
|
20
|
-
const SOCKET_MARGIN = 1000;
|
|
21
|
-
const TIMEOUT_MAX = 2 ** 31 - 1;
|
|
22
|
-
|
|
23
|
-
// Captures warn entries, so the "silently ignored configuration" cases can be asserted as
|
|
24
|
-
// reported rather than guessed at.
|
|
25
|
-
const makeLoggingClient = (overrides = {}) => {
|
|
26
|
-
let warnings = [];
|
|
27
|
-
let client = makeClient({
|
|
28
|
-
...overrides,
|
|
29
|
-
logger: { trace() {}, debug() {}, info() {}, warn: entry => warnings.push(entry), error() {}, fatal() {} }
|
|
30
|
-
});
|
|
31
|
-
return { client, warnings };
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
// ============================================================================
|
|
35
|
-
// option normalization
|
|
36
|
-
// ============================================================================
|
|
37
|
-
|
|
38
|
-
module.exports['Auto-IDLE: an unset delay uses the default'] = test => {
|
|
39
|
-
let { client, warnings } = makeLoggingClient();
|
|
40
|
-
test.equal(client.autoIdleDelay, DEFAULT_DELAY);
|
|
41
|
-
test.equal(warnings.length, 0, 'the default is not a misconfiguration');
|
|
42
|
-
test.done();
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
module.exports['Auto-IDLE: a configured delay is honored and reaches setTimeout'] = async test => {
|
|
46
|
-
await withFakeTimers(async timers => {
|
|
47
|
-
let client = makeIdleReadyClient({ autoIdleDelay: 1234 });
|
|
48
|
-
test.equal(client.autoIdleDelay, 1234);
|
|
49
|
-
|
|
50
|
-
client.autoidle();
|
|
51
|
-
|
|
52
|
-
let armed = timers.pending();
|
|
53
|
-
test.equal(armed.length, 1, 'exactly one auto-IDLE timer is armed');
|
|
54
|
-
test.equal(armed[0].delay, 1234, 'the configured delay is what the timer uses');
|
|
55
|
-
|
|
56
|
-
client.close();
|
|
57
|
-
});
|
|
58
|
-
test.done();
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
module.exports['Auto-IDLE: numeric strings are accepted'] = test => {
|
|
62
|
-
// The normal shape of a value coming from an environment variable or a JSON/YAML config file
|
|
63
|
-
test.equal(makeClient({ autoIdleDelay: '2000' }).autoIdleDelay, 2000);
|
|
64
|
-
test.equal(makeClient({ autoIdleDelay: ' 2000 ' }).autoIdleDelay, 2000);
|
|
65
|
-
test.done();
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
module.exports['Auto-IDLE: zero is honored, fractions are floored'] = test => {
|
|
69
|
-
test.equal(makeClient({ autoIdleDelay: 0 }).autoIdleDelay, 0);
|
|
70
|
-
test.equal(makeClient({ autoIdleDelay: 1500.9 }).autoIdleDelay, 1500);
|
|
71
|
-
test.done();
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
module.exports['Auto-IDLE: unusable values fall back to the default and are reported'] = test => {
|
|
75
|
-
// Every one of these is something setTimeout would turn into a 1ms timer, or something a
|
|
76
|
-
// caller plausibly means as "off" - which is what disableAutoIdle is for.
|
|
77
|
-
for (let value of [NaN, -1, -0.5, Infinity, -Infinity, 'soon', '', ' ', true, false, {}, []]) {
|
|
78
|
-
let { client, warnings } = makeLoggingClient({ autoIdleDelay: value });
|
|
79
|
-
test.equal(client.autoIdleDelay, DEFAULT_DELAY, `${String(value)} falls back to the default`);
|
|
80
|
-
test.equal(warnings.length, 1, `${String(value)} is reported rather than silently swallowed`);
|
|
81
|
-
test.equal(warnings[0].msg, 'Adjusted unusable autoIdleDelay option');
|
|
82
|
-
test.equal(warnings[0].reason, 'not a non-negative finite number', `${String(value)} is reported with the right reason`);
|
|
83
|
-
}
|
|
84
|
-
test.done();
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
module.exports['Auto-IDLE: the delay is capped below socketTimeout'] = test => {
|
|
88
|
-
// A delay at or above socketTimeout means the inactivity watchdog fires before IDLE ever
|
|
89
|
-
// starts. `idling` is still false at that point, so the handler emits ETIMEOUT and tears down
|
|
90
|
-
// a quiet but perfectly healthy connection instead of letting it enter IDLE.
|
|
91
|
-
let { client, warnings } = makeLoggingClient({ autoIdleDelay: 10 * 60 * 1000 });
|
|
92
|
-
test.equal(client.autoIdleDelay, DEFAULT_SOCKET_TIMEOUT - SOCKET_MARGIN);
|
|
93
|
-
test.equal(warnings.length, 1, 'the caller is told the value was capped');
|
|
94
|
-
|
|
95
|
-
// The cap follows a custom socketTimeout, and keeps the delay inside the range setTimeout can
|
|
96
|
-
// represent: 2 ** 31 and larger would otherwise silently become a 1ms timer.
|
|
97
|
-
test.equal(makeClient({ autoIdleDelay: 60000, socketTimeout: 30000 }).autoIdleDelay, 29000);
|
|
98
|
-
test.equal(makeClient({ autoIdleDelay: 2 ** 31 }).autoIdleDelay, DEFAULT_SOCKET_TIMEOUT - SOCKET_MARGIN);
|
|
99
|
-
test.equal(makeClient({ autoIdleDelay: Number.MAX_SAFE_INTEGER }).autoIdleDelay, DEFAULT_SOCKET_TIMEOUT - SOCKET_MARGIN);
|
|
100
|
-
test.done();
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
module.exports['Auto-IDLE: the cap stays inside the timer range for a huge socketTimeout'] = test => {
|
|
104
|
-
// socket.setTimeout truncates an over-range socketTimeout on its own, so a huge watchdog
|
|
105
|
-
// deadline still works - but the auto-IDLE delay must not inherit the raw value, or
|
|
106
|
-
// setTimeout would turn it into a 1ms timer and IDLE would follow every single command.
|
|
107
|
-
test.equal(makeClient({ socketTimeout: 2 ** 32, autoIdleDelay: 2 ** 31 }).autoIdleDelay, TIMEOUT_MAX - SOCKET_MARGIN);
|
|
108
|
-
test.equal(makeClient({ socketTimeout: 2200000000, autoIdleDelay: 2150000000 }).autoIdleDelay, TIMEOUT_MAX - SOCKET_MARGIN);
|
|
109
|
-
test.done();
|
|
110
|
-
};
|
|
111
|
-
|
|
112
|
-
module.exports['Auto-IDLE: capping the default for a short socketTimeout is silent'] = test => {
|
|
113
|
-
let { client, warnings } = makeLoggingClient({ socketTimeout: 5000 });
|
|
114
|
-
test.equal(client.autoIdleDelay, 4000, 'the default is capped too');
|
|
115
|
-
test.equal(warnings.length, 0, 'but nothing was misconfigured, so nothing is reported');
|
|
116
|
-
test.done();
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
module.exports['Auto-IDLE: an invalid value keeps its own reason when the default is then capped'] = test => {
|
|
120
|
-
// 'soon' falls back to the 15s default, which a 5s socketTimeout then caps to 4s. The
|
|
121
|
-
// warning must still name what was wrong with the configured value - the cap applied to
|
|
122
|
-
// the fallback, not to anything the caller asked for.
|
|
123
|
-
let { client, warnings } = makeLoggingClient({ autoIdleDelay: 'soon', socketTimeout: 5000 });
|
|
124
|
-
test.equal(client.autoIdleDelay, 4000);
|
|
125
|
-
test.equal(warnings.length, 1);
|
|
126
|
-
test.equal(warnings[0].reason, 'not a non-negative finite number');
|
|
127
|
-
test.done();
|
|
128
|
-
};
|
|
129
|
-
|
|
130
|
-
// ============================================================================
|
|
131
|
-
// when the timer may arm
|
|
132
|
-
// ============================================================================
|
|
133
|
-
|
|
134
|
-
module.exports['Auto-IDLE: no timer is armed while a mailbox lock is held'] = async test => {
|
|
135
|
-
await withFakeTimers(async timers => {
|
|
136
|
-
let client = makeIdleReadyClient({ autoIdleDelay: 200 });
|
|
137
|
-
|
|
138
|
-
let lock = await client.getMailboxLock('INBOX');
|
|
139
|
-
test.ok(client.currentLock, 'the lock was granted through the fast path');
|
|
140
|
-
|
|
141
|
-
client.autoidle();
|
|
142
|
-
test.equal(timers.count(), 0, 'IDLE must not be injected between a lock holder own commands');
|
|
143
|
-
|
|
144
|
-
lock.release();
|
|
145
|
-
let armed = timers.pending();
|
|
146
|
-
test.equal(armed.length, 1, 'releasing the lock re-arms auto-IDLE');
|
|
147
|
-
test.equal(armed[0].delay, 200);
|
|
148
|
-
|
|
149
|
-
client.close();
|
|
150
|
-
});
|
|
151
|
-
test.done();
|
|
152
|
-
};
|
|
153
|
-
|
|
154
|
-
module.exports['Auto-IDLE: acquiring a lock through SELECT leaves no timer behind'] = async test => {
|
|
155
|
-
await withFakeTimers(async timers => {
|
|
156
|
-
let client = makeIdleReadyClient({ autoIdleDelay: 200 });
|
|
157
|
-
client.mailbox = false;
|
|
158
|
-
// The SELECT that opens the mailbox goes through run(), which re-arms auto-IDLE when it
|
|
159
|
-
// settles - a moment before currentLock is set. That timer would fire inside the lock.
|
|
160
|
-
client.mailboxOpen = async path => {
|
|
161
|
-
client.mailbox = { path, readOnly: false };
|
|
162
|
-
client.autoidle();
|
|
163
|
-
return client.mailbox;
|
|
164
|
-
};
|
|
165
|
-
|
|
166
|
-
let lock = await client.getMailboxLock('INBOX');
|
|
167
|
-
test.ok(client.currentLock, 'the lock was granted through the SELECT path');
|
|
168
|
-
test.equal(timers.count(), 0, 'the timer armed while opening the mailbox was cleared');
|
|
169
|
-
|
|
170
|
-
lock.release();
|
|
171
|
-
test.equal(timers.count(), 1, 'and auto-IDLE resumes once the lock is released');
|
|
172
|
-
|
|
173
|
-
client.close();
|
|
174
|
-
});
|
|
175
|
-
test.done();
|
|
176
|
-
};
|
|
177
|
-
|
|
178
|
-
module.exports['Auto-IDLE: releasing a lock with another one queued does not arm'] = async test => {
|
|
179
|
-
await withFakeTimers(async timers => {
|
|
180
|
-
let client = makeIdleReadyClient({ autoIdleDelay: 200 });
|
|
181
|
-
|
|
182
|
-
let lock = await client.getMailboxLock('INBOX');
|
|
183
|
-
// Queue a second request; the connection is not free when the first holder lets go
|
|
184
|
-
let queued = client.getMailboxLock('INBOX');
|
|
185
|
-
test.equal(client.locks.length, 1, 'the second request is waiting');
|
|
186
|
-
|
|
187
|
-
lock.release();
|
|
188
|
-
test.equal(timers.count(), 0, 'the next holder owns the connection, so IDLE stays off');
|
|
189
|
-
|
|
190
|
-
(await queued).release();
|
|
191
|
-
test.equal(timers.count(), 1, 'the last release re-arms auto-IDLE');
|
|
192
|
-
|
|
193
|
-
client.close();
|
|
194
|
-
});
|
|
195
|
-
test.done();
|
|
196
|
-
};
|
|
197
|
-
|
|
198
|
-
module.exports['Auto-IDLE: no timer is armed while a command is in flight or queued'] = async test => {
|
|
199
|
-
await withFakeTimers(async timers => {
|
|
200
|
-
let client = makeIdleReadyClient({ autoIdleDelay: 200 });
|
|
201
|
-
|
|
202
|
-
// Concurrent commands each clear the timer on entry, so without this guard the first one
|
|
203
|
-
// to settle would arm a timer that fires while the others are still running.
|
|
204
|
-
client.currentRequest = { tag: 'A001', command: 'FETCH', sent: true };
|
|
205
|
-
client.autoidle();
|
|
206
|
-
test.equal(timers.count(), 0, 'a command in flight owns the connection');
|
|
207
|
-
|
|
208
|
-
client.currentRequest = false;
|
|
209
|
-
client.requestQueue = [{ tag: 'A002', command: 'FETCH' }];
|
|
210
|
-
client.autoidle();
|
|
211
|
-
test.equal(timers.count(), 0, 'so does a command still waiting in the queue');
|
|
212
|
-
|
|
213
|
-
client.requestQueue = [];
|
|
214
|
-
client.autoidle();
|
|
215
|
-
test.equal(timers.count(), 1, 'an idle connection arms the timer');
|
|
216
|
-
|
|
217
|
-
client.close();
|
|
218
|
-
});
|
|
219
|
-
test.done();
|
|
220
|
-
};
|
|
221
|
-
|
|
222
|
-
module.exports['Auto-IDLE: the armed timer re-checks the busy guard when it fires'] = async test => {
|
|
223
|
-
await withFakeTimers(async timers => {
|
|
224
|
-
let client = makeIdleReadyClient({ autoIdleDelay: 200 });
|
|
225
|
-
let idleCalls = 0;
|
|
226
|
-
client.idle = async () => {
|
|
227
|
-
idleCalls++;
|
|
228
|
-
};
|
|
229
|
-
|
|
230
|
-
client.autoidle();
|
|
231
|
-
test.equal(timers.count(), 1, 'the timer was armed on a free connection');
|
|
232
|
-
|
|
233
|
-
// Ownership taken without clearing the timer - the guard must not depend on every
|
|
234
|
-
// ownership-taking path remembering its clearTimeout.
|
|
235
|
-
client.currentRequest = { tag: 'A001', command: 'FETCH', sent: true };
|
|
236
|
-
await timers.fire();
|
|
237
|
-
test.equal(idleCalls, 0, 'a connection that is busy at fire time does not start IDLE');
|
|
238
|
-
|
|
239
|
-
client.currentRequest = false;
|
|
240
|
-
client.autoidle();
|
|
241
|
-
await timers.fire();
|
|
242
|
-
test.equal(idleCalls, 1, 'a connection that is free at fire time does');
|
|
243
|
-
|
|
244
|
-
client.close();
|
|
245
|
-
});
|
|
246
|
-
test.done();
|
|
247
|
-
};
|
|
248
|
-
|
|
249
|
-
module.exports['Auto-IDLE: no timer is armed while a download is streaming'] = async test => {
|
|
250
|
-
await withFakeTimers(async timers => {
|
|
251
|
-
let client = makeIdleReadyClient({ autoIdleDelay: 200 });
|
|
252
|
-
let body = Buffer.from('A'.repeat(20));
|
|
253
|
-
let armedAfterChunk = [];
|
|
254
|
-
|
|
255
|
-
let serveChunk = chunkedFetchOne(body);
|
|
256
|
-
client.fetchOne = async (range, query) => {
|
|
257
|
-
// Stands in for run(), which clears the auto-IDLE timer on entry and re-arms it once
|
|
258
|
-
// the command settles. The pause between two chunks is where IDLE would slip in.
|
|
259
|
-
clearTimeout(client.idleStartTimer);
|
|
260
|
-
let part = await serveChunk(range, query);
|
|
261
|
-
client.autoidle();
|
|
262
|
-
armedAfterChunk.push(timers.count());
|
|
263
|
-
return part;
|
|
264
|
-
};
|
|
265
|
-
|
|
266
|
-
let { content } = await client.download('1', false, { chunkSize: 4 });
|
|
267
|
-
let received = [];
|
|
268
|
-
for await (let chunk of content) {
|
|
269
|
-
received.push(chunk);
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
test.equal(Buffer.concat(received).toString(), 'A'.repeat(20), 'the whole body arrived');
|
|
273
|
-
test.ok(armedAfterChunk.length > 2, 'the body really was fetched in several chunks');
|
|
274
|
-
test.deepEqual(
|
|
275
|
-
armedAfterChunk.slice(1).filter(count => count !== 0),
|
|
276
|
-
[],
|
|
277
|
-
'no chunk boundary inside the download armed an auto-IDLE timer'
|
|
278
|
-
);
|
|
279
|
-
test.equal(timers.count(), 1, 'auto-IDLE resumes once the download stream is finished');
|
|
280
|
-
|
|
281
|
-
client.close();
|
|
282
|
-
});
|
|
283
|
-
test.done();
|
|
284
|
-
};
|
|
285
|
-
|
|
286
|
-
module.exports['Auto-IDLE: a download counts as busy before control returns to the event loop'] = async test => {
|
|
287
|
-
await withFakeTimers(async () => {
|
|
288
|
-
let client = makeIdleReadyClient({ autoIdleDelay: 200 });
|
|
289
|
-
let body = Buffer.from('B'.repeat(12));
|
|
290
|
-
client.fetchOne = chunkedFetchOne(body);
|
|
291
|
-
|
|
292
|
-
let { content } = await client.download('1', false, { chunkSize: 4 });
|
|
293
|
-
// The head chunk's own FETCH re-arms auto-IDLE a moment before the download exists;
|
|
294
|
-
// with a very short delay that timer fires before the deferred chunk loop starts, so
|
|
295
|
-
// the busy guard must already see the download when download() hands the stream back.
|
|
296
|
-
test.equal(client._openDownloads, 1, 'the download is counted before streaming starts');
|
|
297
|
-
|
|
298
|
-
let received = [];
|
|
299
|
-
for await (let chunk of content) {
|
|
300
|
-
received.push(chunk);
|
|
301
|
-
}
|
|
302
|
-
test.equal(Buffer.concat(received).toString(), 'B'.repeat(12), 'the whole body arrived');
|
|
303
|
-
test.equal(client._openDownloads, 0, 'the download is released once the stream is done');
|
|
304
|
-
|
|
305
|
-
client.close();
|
|
306
|
-
});
|
|
307
|
-
test.done();
|
|
308
|
-
};
|
|
309
|
-
|
|
310
|
-
module.exports['Auto-IDLE: a failing command still re-arms the timer'] = async test => {
|
|
311
|
-
await withFakeTimers(async timers => {
|
|
312
|
-
let client = makeIdleReadyClient({ autoIdleDelay: 300 });
|
|
313
|
-
// run() clears the timer before dispatching, so re-arming only on the success path would
|
|
314
|
-
// leave auto-IDLE off for good after one rejected command.
|
|
315
|
-
client.runInternal = async () => {
|
|
316
|
-
throw new Error('command failed');
|
|
317
|
-
};
|
|
318
|
-
|
|
319
|
-
let err;
|
|
320
|
-
try {
|
|
321
|
-
await client.run('NOOP');
|
|
322
|
-
} catch (E) {
|
|
323
|
-
err = E;
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
test.ok(err, 'the failure still reaches the caller');
|
|
327
|
-
let armed = timers.pending();
|
|
328
|
-
test.equal(armed.length, 1, 'auto-IDLE is re-armed after a failed command');
|
|
329
|
-
test.equal(armed[0].delay, 300);
|
|
330
|
-
|
|
331
|
-
client.close();
|
|
332
|
-
});
|
|
333
|
-
test.done();
|
|
334
|
-
};
|
|
335
|
-
|
|
336
|
-
module.exports['Auto-IDLE: a failing IDLE break still re-arms the timer'] = async test => {
|
|
337
|
-
await withFakeTimers(async timers => {
|
|
338
|
-
let client = makeIdleReadyClient({ autoIdleDelay: 300 });
|
|
339
|
-
// run() awaits preCheck() (breaking an active IDLE) before dispatching. A break that
|
|
340
|
-
// rejects exits run() before the command ever starts, and must still re-arm auto-IDLE
|
|
341
|
-
// exactly like a failed command does.
|
|
342
|
-
client.preCheck = async () => {
|
|
343
|
-
throw new Error('IDLE break failed');
|
|
344
|
-
};
|
|
345
|
-
|
|
346
|
-
let err;
|
|
347
|
-
try {
|
|
348
|
-
await client.run('NOOP');
|
|
349
|
-
} catch (E) {
|
|
350
|
-
err = E;
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
test.ok(err, 'the failure still reaches the caller');
|
|
354
|
-
let armed = timers.pending();
|
|
355
|
-
test.equal(armed.length, 1, 'auto-IDLE is re-armed after the failed IDLE break');
|
|
356
|
-
test.equal(armed[0].delay, 300);
|
|
357
|
-
|
|
358
|
-
client.close();
|
|
359
|
-
});
|
|
360
|
-
test.done();
|
|
361
|
-
};
|
|
362
|
-
|
|
363
|
-
// ============================================================================
|
|
364
|
-
// the socket inactivity watchdog
|
|
365
|
-
// ============================================================================
|
|
366
|
-
|
|
367
|
-
// A client wired up for _socketTimeout() with recording stubs: recovery commands land in
|
|
368
|
-
// `recovered`, emitted errors in `errors`. run() is stubbed, so nothing here re-arms auto-IDLE -
|
|
369
|
-
// which is deliberate: the handler itself must never start IDLE, that decision belongs to
|
|
370
|
-
// autoidle() once the recovery NOOP settles for real.
|
|
371
|
-
const makeWatchdogClient = () => {
|
|
372
|
-
let client = makeIdleReadyClient();
|
|
373
|
-
let errors = [];
|
|
374
|
-
let recovered = [];
|
|
375
|
-
client.on('error', err => errors.push(err));
|
|
376
|
-
client.run = async command => recovered.push(command);
|
|
377
|
-
client.idle = async () => recovered.push('IDLE');
|
|
378
|
-
client.writeSocket = client.socket;
|
|
379
|
-
client.setSocketHandlers();
|
|
380
|
-
return { client, errors, recovered };
|
|
381
|
-
};
|
|
382
|
-
|
|
383
|
-
const drainImmediate = () => new Promise(resolve => setImmediate(resolve));
|
|
384
|
-
|
|
385
|
-
module.exports['Auto-IDLE: a stalled download survives the socket watchdog'] = async test => {
|
|
386
|
-
// Suppressing auto-IDLE during a download means `idling` is false when the inactivity
|
|
387
|
-
// watchdog fires, so without the download clause the watchdog would tear down a connection
|
|
388
|
-
// whose only problem is a consumer that stopped draining.
|
|
389
|
-
let { client, errors, recovered } = makeWatchdogClient();
|
|
390
|
-
client._openDownloads = 1;
|
|
391
|
-
|
|
392
|
-
client._socketTimeout();
|
|
393
|
-
await drainImmediate();
|
|
394
|
-
|
|
395
|
-
test.deepEqual(errors, [], 'no ETIMEOUT is emitted while a download is open');
|
|
396
|
-
test.deepEqual(recovered, ['NOOP'], 'kept alive with a NOOP only - IDLE mid-download is what the busy guard exists to prevent');
|
|
397
|
-
|
|
398
|
-
client._openDownloads = 0;
|
|
399
|
-
client._socketTimeout();
|
|
400
|
-
test.equal(errors.length, 1, 'once the download is done a quiet socket is a timeout again');
|
|
401
|
-
test.equal(errors[0].code, 'ETIMEOUT');
|
|
402
|
-
|
|
403
|
-
client.close();
|
|
404
|
-
test.ok(client.isClosed, 'the socket stub supports everything close() needs');
|
|
405
|
-
test.done();
|
|
406
|
-
};
|
|
407
|
-
|
|
408
|
-
module.exports['Auto-IDLE: a stuck chunk FETCH mid-download is a dead connection'] = async test => {
|
|
409
|
-
// A quiet socket while a command is awaiting its reply means the reply is overdue. A
|
|
410
|
-
// recovery NOOP would only queue behind the stuck command and never reach the wire, so
|
|
411
|
-
// the watchdog must report the timeout instead of recovering into a silent hang.
|
|
412
|
-
let { client, errors, recovered } = makeWatchdogClient();
|
|
413
|
-
client._openDownloads = 1;
|
|
414
|
-
client.currentRequest = { tag: 'A001', command: 'FETCH', sent: true };
|
|
415
|
-
|
|
416
|
-
client._socketTimeout();
|
|
417
|
-
await drainImmediate();
|
|
418
|
-
|
|
419
|
-
test.deepEqual(recovered, [], 'no recovery is attempted behind a stuck command');
|
|
420
|
-
test.equal(errors.length, 1, 'the caller learns the connection is dead');
|
|
421
|
-
test.equal(errors[0].code, 'ETIMEOUT');
|
|
422
|
-
|
|
423
|
-
client.close();
|
|
424
|
-
test.done();
|
|
425
|
-
};
|
|
426
|
-
|
|
427
|
-
module.exports['Auto-IDLE: a held mailbox lock keeps a quiet connection alive'] = async test => {
|
|
428
|
-
// A lock holder pausing between commands for longer than socketTimeout is legitimate (the
|
|
429
|
-
// held-lock diagnostic warns only after 30 minutes), and with auto-IDLE declining to arm
|
|
430
|
-
// during a lock there is no IDLE traffic to keep the socket busy - the watchdog has to.
|
|
431
|
-
let { client, errors, recovered } = makeWatchdogClient();
|
|
432
|
-
client.currentLock = { lockId: 1 };
|
|
433
|
-
|
|
434
|
-
client._socketTimeout();
|
|
435
|
-
await drainImmediate();
|
|
436
|
-
|
|
437
|
-
test.deepEqual(errors, [], 'the lock holder keeps its connection');
|
|
438
|
-
test.deepEqual(recovered, ['NOOP'], 'kept alive with a NOOP only, no IDLE inside the lock');
|
|
439
|
-
|
|
440
|
-
client.currentLock = false;
|
|
441
|
-
client._socketTimeout();
|
|
442
|
-
test.equal(errors.length, 1, 'with the lock gone a quiet socket is a timeout again');
|
|
443
|
-
test.equal(errors[0].code, 'ETIMEOUT');
|
|
444
|
-
|
|
445
|
-
client.close();
|
|
446
|
-
test.done();
|
|
447
|
-
};
|
|
448
|
-
|
|
449
|
-
module.exports['Auto-IDLE: idling recovers even with the IDLE command in flight'] = async test => {
|
|
450
|
-
// During true IDLE the in-flight command IS the IDLE command; run() breaks it through
|
|
451
|
-
// preCheck() before the recovery NOOP goes out, so it does not count as stuck. A recovery
|
|
452
|
-
// NOOP that then never settles does: the next timeout must fail rather than queue another.
|
|
453
|
-
let { client, errors, recovered } = makeWatchdogClient();
|
|
454
|
-
client.idling = true;
|
|
455
|
-
client.currentRequest = { tag: 'A001', command: 'IDLE', sent: true };
|
|
456
|
-
|
|
457
|
-
client._socketTimeout();
|
|
458
|
-
await drainImmediate();
|
|
459
|
-
test.deepEqual(errors, [], 'the idling connection is not torn down');
|
|
460
|
-
test.deepEqual(recovered, ['NOOP'], 'it is recovered with a NOOP');
|
|
461
|
-
|
|
462
|
-
client.idling = false;
|
|
463
|
-
client.currentRequest = { tag: 'A002', command: 'NOOP', sent: true };
|
|
464
|
-
client._socketTimeout();
|
|
465
|
-
test.equal(errors.length, 1, 'a recovery NOOP that never settled is a dead connection');
|
|
466
|
-
test.equal(errors[0].code, 'ETIMEOUT');
|
|
467
|
-
|
|
468
|
-
client.close();
|
|
469
|
-
test.done();
|
|
470
|
-
};
|