imapflow 1.7.7 → 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 +27 -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 +785 -1802
- 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 -57
- 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 -873
- 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 -593
|
@@ -1,518 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
// Fallback polling lifecycle (servers without IDLE): one explicitly identified polling session
|
|
4
|
-
// that cannot run or reschedule after cancellation, reuses the real STATUS/SELECT state
|
|
5
|
-
// handling, and always leaves `idling` describing reality.
|
|
6
|
-
|
|
7
|
-
const idleCommand = require('../lib/commands/idle.js');
|
|
8
|
-
const imapCommands = require('../lib/imap-commands.js');
|
|
9
|
-
const { withFakeTimers } = require('./fixtures/fake-timers');
|
|
10
|
-
|
|
11
|
-
const createConnection = (overrides = {}) => {
|
|
12
|
-
const states = { NOT_AUTHENTICATED: 1, AUTHENTICATED: 2, SELECTED: 3, LOGOUT: 4 };
|
|
13
|
-
|
|
14
|
-
const connection = {
|
|
15
|
-
states,
|
|
16
|
-
state: states.SELECTED,
|
|
17
|
-
id: 'polling-test',
|
|
18
|
-
capabilities: new Map(overrides.capabilities || []), // no IDLE -> polling fallback
|
|
19
|
-
enabled: new Set(overrides.enabled || []),
|
|
20
|
-
folders: new Map([['INBOX', { path: 'INBOX', delimiter: '/' }]]),
|
|
21
|
-
mailbox: {
|
|
22
|
-
path: 'INBOX',
|
|
23
|
-
exists: 3,
|
|
24
|
-
uidNext: 10,
|
|
25
|
-
uidValidity: BigInt(1),
|
|
26
|
-
highestModseq: BigInt(1)
|
|
27
|
-
},
|
|
28
|
-
namespace: { delimiter: '/', prefix: '' },
|
|
29
|
-
socket: { destroyed: false },
|
|
30
|
-
currentSelectCommand: { command: 'SELECT', arguments: [{ type: 'ATOM', value: 'INBOX' }] },
|
|
31
|
-
missingIdleCommand: 'NOOP',
|
|
32
|
-
idling: false,
|
|
33
|
-
preCheck: false,
|
|
34
|
-
events: [],
|
|
35
|
-
commands: [],
|
|
36
|
-
log: { warn: () => {}, info: () => {}, error: () => {}, debug: () => {}, trace: () => {} },
|
|
37
|
-
emit(event, payload) {
|
|
38
|
-
connection.events.push({ event, payload });
|
|
39
|
-
},
|
|
40
|
-
run: async () => false,
|
|
41
|
-
// Fallback polling dispatches through runInternal(), which ImapFlow resolves against the
|
|
42
|
-
// command registry - so a poll runs the real SELECT/STATUS implementation.
|
|
43
|
-
runInternal: async (command, ...args) => {
|
|
44
|
-
let handler = imapCommands.get(command.toUpperCase());
|
|
45
|
-
return handler ? await handler(connection, ...args) : false;
|
|
46
|
-
},
|
|
47
|
-
exec: async (command, attributes, options) => {
|
|
48
|
-
connection.commands.push(command);
|
|
49
|
-
return connection.respond(command, attributes, options);
|
|
50
|
-
},
|
|
51
|
-
respond: async () => ({ next: () => {}, response: { attributes: [{ value: 'OK' }] } })
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
// capabilities/enabled are already converted into a Map/Set above
|
|
55
|
-
let rest = Object.assign({}, overrides);
|
|
56
|
-
delete rest.capabilities;
|
|
57
|
-
delete rest.enabled;
|
|
58
|
-
|
|
59
|
-
return Object.assign(connection, rest);
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
module.exports['Polling: break before the first poll stops the loop'] = async test => {
|
|
63
|
-
await withFakeTimers(async timers => {
|
|
64
|
-
let connection = createConnection({
|
|
65
|
-
exec: async command => {
|
|
66
|
-
connection.commands.push(command);
|
|
67
|
-
// break while the very first poll is still in flight
|
|
68
|
-
await connection.preCheck();
|
|
69
|
-
return { next: () => {} };
|
|
70
|
-
}
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
let idlePromise = idleCommand(connection, 60000);
|
|
74
|
-
await timers.drain();
|
|
75
|
-
await idlePromise;
|
|
76
|
-
|
|
77
|
-
test.deepEqual(connection.commands, ['NOOP'], 'only the immediate first poll ran');
|
|
78
|
-
test.equal(connection.idling, false, 'idling reset after the break');
|
|
79
|
-
test.equal(connection.preCheck, false, 'the session released preCheck');
|
|
80
|
-
test.equal(timers.count(), 0, 'no polling timer is left armed');
|
|
81
|
-
|
|
82
|
-
// Advancing several intervals must not produce another command
|
|
83
|
-
await timers.fire();
|
|
84
|
-
await timers.fire();
|
|
85
|
-
test.deepEqual(connection.commands, ['NOOP'], 'a cancelled session never polls again');
|
|
86
|
-
test.done();
|
|
87
|
-
});
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
module.exports['Polling: break during an in-flight poll cannot reschedule'] = async test => {
|
|
91
|
-
await withFakeTimers(async timers => {
|
|
92
|
-
let releasePoll;
|
|
93
|
-
let pollStarted;
|
|
94
|
-
let connection = createConnection({
|
|
95
|
-
exec: async command => {
|
|
96
|
-
connection.commands.push(command);
|
|
97
|
-
await new Promise(resolve => {
|
|
98
|
-
releasePoll = resolve;
|
|
99
|
-
if (pollStarted) {
|
|
100
|
-
pollStarted();
|
|
101
|
-
}
|
|
102
|
-
});
|
|
103
|
-
return { next: () => {} };
|
|
104
|
-
}
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
let started = new Promise(resolve => (pollStarted = resolve));
|
|
108
|
-
let idlePromise = idleCommand(connection, 60000);
|
|
109
|
-
await started;
|
|
110
|
-
|
|
111
|
-
// Cancel while the poll is still awaiting its response, then let it finish
|
|
112
|
-
await connection.preCheck();
|
|
113
|
-
releasePoll();
|
|
114
|
-
await idlePromise;
|
|
115
|
-
|
|
116
|
-
test.deepEqual(connection.commands, ['NOOP'], 'the in-flight poll completed but scheduled nothing');
|
|
117
|
-
test.equal(timers.count(), 0, 'the completing poll did not arm a new timer');
|
|
118
|
-
test.equal(connection.idling, false, 'idling reset');
|
|
119
|
-
|
|
120
|
-
await timers.fire();
|
|
121
|
-
test.deepEqual(connection.commands, ['NOOP'], 'no later command is sent');
|
|
122
|
-
test.done();
|
|
123
|
-
});
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
module.exports['Polling: break after a poll but before the next timer fires stops the loop'] = async test => {
|
|
127
|
-
await withFakeTimers(async timers => {
|
|
128
|
-
let connection = createConnection();
|
|
129
|
-
|
|
130
|
-
let idlePromise = idleCommand(connection, 60000);
|
|
131
|
-
await timers.drain();
|
|
132
|
-
|
|
133
|
-
test.deepEqual(connection.commands, ['NOOP'], 'the immediate first poll ran');
|
|
134
|
-
test.equal(timers.count(), 1, 'the next poll is scheduled');
|
|
135
|
-
test.ok(timers.pending()[0].unrefd, 'the background polling timer does not keep the process alive');
|
|
136
|
-
|
|
137
|
-
await connection.preCheck();
|
|
138
|
-
await idlePromise;
|
|
139
|
-
|
|
140
|
-
test.equal(timers.count(), 0, 'the armed timer was cleared on cancellation');
|
|
141
|
-
|
|
142
|
-
await timers.fire();
|
|
143
|
-
await timers.fire();
|
|
144
|
-
test.deepEqual(connection.commands, ['NOOP'], 'no command after cancellation');
|
|
145
|
-
test.done();
|
|
146
|
-
});
|
|
147
|
-
};
|
|
148
|
-
|
|
149
|
-
module.exports['Polling: an old session does not clear a newer session preCheck'] = async test => {
|
|
150
|
-
await withFakeTimers(async timers => {
|
|
151
|
-
let releasePoll;
|
|
152
|
-
let pollStarted;
|
|
153
|
-
let connection = createConnection({
|
|
154
|
-
exec: async command => {
|
|
155
|
-
connection.commands.push(command);
|
|
156
|
-
if (connection.commands.length === 1) {
|
|
157
|
-
await new Promise(resolve => {
|
|
158
|
-
releasePoll = resolve;
|
|
159
|
-
pollStarted();
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
return { next: () => {} };
|
|
163
|
-
}
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
let started = new Promise(resolve => (pollStarted = resolve));
|
|
167
|
-
let firstSession = idleCommand(connection, 60000);
|
|
168
|
-
await started;
|
|
169
|
-
|
|
170
|
-
// Cancel the first session while its poll is in flight, then immediately re-enter IDLE
|
|
171
|
-
await connection.preCheck();
|
|
172
|
-
let secondSession = idleCommand(connection, 60000);
|
|
173
|
-
await timers.drain();
|
|
174
|
-
|
|
175
|
-
let newPreCheck = connection.preCheck;
|
|
176
|
-
test.equal(typeof newPreCheck, 'function', 'the new session installed its own preCheck');
|
|
177
|
-
|
|
178
|
-
// The stale poll now finishes: it must not touch the new session's ownership
|
|
179
|
-
releasePoll();
|
|
180
|
-
await firstSession;
|
|
181
|
-
|
|
182
|
-
test.equal(connection.preCheck, newPreCheck, 'the stale session left the newer preCheck in place');
|
|
183
|
-
test.equal(connection.idling, true, 'the stale session did not clear the newer idling state');
|
|
184
|
-
|
|
185
|
-
await connection.preCheck();
|
|
186
|
-
await secondSession;
|
|
187
|
-
test.equal(connection.idling, false, 'the newer session cleaned up on its own break');
|
|
188
|
-
test.done();
|
|
189
|
-
});
|
|
190
|
-
};
|
|
191
|
-
|
|
192
|
-
module.exports['Polling: STATUS polling applies mailbox state and emits exists only on change'] = async test => {
|
|
193
|
-
await withFakeTimers(async timers => {
|
|
194
|
-
let messages = 3;
|
|
195
|
-
let connection = createConnection({
|
|
196
|
-
capabilities: [['CONDSTORE', true]],
|
|
197
|
-
missingIdleCommand: 'STATUS',
|
|
198
|
-
respond: async (command, attributes, options) => {
|
|
199
|
-
if (command === 'STATUS') {
|
|
200
|
-
let statusHandler = options.untagged.STATUS;
|
|
201
|
-
await statusHandler({
|
|
202
|
-
attributes: [
|
|
203
|
-
{ type: 'STRING', value: 'INBOX' },
|
|
204
|
-
[
|
|
205
|
-
{ type: 'ATOM', value: 'MESSAGES' },
|
|
206
|
-
{ type: 'ATOM', value: String(messages) },
|
|
207
|
-
{ type: 'ATOM', value: 'UIDNEXT' },
|
|
208
|
-
{ type: 'ATOM', value: '11' },
|
|
209
|
-
{ type: 'ATOM', value: 'HIGHESTMODSEQ' },
|
|
210
|
-
{ type: 'ATOM', value: '42' }
|
|
211
|
-
]
|
|
212
|
-
]
|
|
213
|
-
});
|
|
214
|
-
}
|
|
215
|
-
return { next: () => {}, response: { attributes: [{ value: 'OK' }] } };
|
|
216
|
-
}
|
|
217
|
-
});
|
|
218
|
-
|
|
219
|
-
let idlePromise = idleCommand(connection, 60000);
|
|
220
|
-
await timers.drain();
|
|
221
|
-
|
|
222
|
-
test.deepEqual(connection.commands, ['STATUS'], 'STATUS was used for polling');
|
|
223
|
-
test.equal(connection.mailbox.uidNext, 11, 'uidNext updated from the poll');
|
|
224
|
-
test.equal(connection.mailbox.highestModseq, BigInt(42), 'highestModseq updated on a CONDSTORE session');
|
|
225
|
-
test.deepEqual(connection.events, [], 'an unchanged message count emits no event');
|
|
226
|
-
|
|
227
|
-
// Second poll reports a new count -> exactly one exists event
|
|
228
|
-
messages = 5;
|
|
229
|
-
await timers.fire();
|
|
230
|
-
|
|
231
|
-
test.equal(connection.mailbox.exists, 5, 'exists updated from the poll');
|
|
232
|
-
test.equal(connection.events.length, 1, 'one event for the changed count');
|
|
233
|
-
test.equal(connection.events[0].event, 'exists');
|
|
234
|
-
test.equal(connection.events[0].payload.count, 5);
|
|
235
|
-
test.equal(connection.events[0].payload.prevCount, 3);
|
|
236
|
-
|
|
237
|
-
await connection.preCheck();
|
|
238
|
-
await idlePromise;
|
|
239
|
-
test.done();
|
|
240
|
-
});
|
|
241
|
-
};
|
|
242
|
-
|
|
243
|
-
module.exports['Polling: STATUS omits HIGHESTMODSEQ without CONDSTORE'] = async test => {
|
|
244
|
-
await withFakeTimers(async timers => {
|
|
245
|
-
let requestedItems = [];
|
|
246
|
-
let connection = createConnection({
|
|
247
|
-
missingIdleCommand: 'STATUS',
|
|
248
|
-
respond: async (command, attributes) => {
|
|
249
|
-
if (command === 'STATUS') {
|
|
250
|
-
requestedItems = attributes[1].map(entry => entry.value);
|
|
251
|
-
}
|
|
252
|
-
return { next: () => {}, response: { attributes: [{ value: 'OK' }] } };
|
|
253
|
-
}
|
|
254
|
-
});
|
|
255
|
-
|
|
256
|
-
let idlePromise = idleCommand(connection, 60000);
|
|
257
|
-
await timers.drain();
|
|
258
|
-
|
|
259
|
-
test.ok(requestedItems.includes('MESSAGES'), 'MESSAGES is polled');
|
|
260
|
-
test.ok(requestedItems.includes('UIDNEXT'), 'UIDNEXT is polled');
|
|
261
|
-
test.ok(!requestedItems.includes('HIGHESTMODSEQ'), 'HIGHESTMODSEQ is not requested without CONDSTORE');
|
|
262
|
-
|
|
263
|
-
await connection.preCheck();
|
|
264
|
-
await idlePromise;
|
|
265
|
-
test.done();
|
|
266
|
-
});
|
|
267
|
-
};
|
|
268
|
-
|
|
269
|
-
module.exports['Polling: failed SELECT polling deselects and stops the loop'] = async test => {
|
|
270
|
-
await withFakeTimers(async timers => {
|
|
271
|
-
let connection = createConnection({
|
|
272
|
-
missingIdleCommand: 'SELECT',
|
|
273
|
-
respond: async command => {
|
|
274
|
-
if (command === 'SELECT') {
|
|
275
|
-
let err = new Error('Command failed');
|
|
276
|
-
err.responseStatus = 'NO';
|
|
277
|
-
throw err;
|
|
278
|
-
}
|
|
279
|
-
return { next: () => {}, response: { attributes: [{ value: 'OK' }] } };
|
|
280
|
-
}
|
|
281
|
-
});
|
|
282
|
-
|
|
283
|
-
let idlePromise = idleCommand(connection, 60000);
|
|
284
|
-
await timers.drain();
|
|
285
|
-
await idlePromise;
|
|
286
|
-
|
|
287
|
-
test.equal(connection.state, connection.states.AUTHENTICATED, 'a failed reselect drops to AUTHENTICATED');
|
|
288
|
-
test.equal(connection.mailbox, false, 'mailbox state cleared');
|
|
289
|
-
test.equal(connection.currentSelectCommand, false, 'the saved select command is cleared');
|
|
290
|
-
test.equal(connection.idling, false, 'idling reset');
|
|
291
|
-
test.equal(connection.preCheck, false, 'preCheck released');
|
|
292
|
-
test.equal(timers.count(), 0, 'polling stopped');
|
|
293
|
-
test.ok(
|
|
294
|
-
connection.events.some(entry => entry.event === 'mailboxClose'),
|
|
295
|
-
'the mailbox close transition was emitted, as with a caller-issued SELECT'
|
|
296
|
-
);
|
|
297
|
-
test.done();
|
|
298
|
-
});
|
|
299
|
-
};
|
|
300
|
-
|
|
301
|
-
module.exports['Polling: a closed connection stops the loop instead of polling'] = async test => {
|
|
302
|
-
await withFakeTimers(async timers => {
|
|
303
|
-
let connection = createConnection();
|
|
304
|
-
|
|
305
|
-
let idlePromise = idleCommand(connection, 60000);
|
|
306
|
-
await timers.drain();
|
|
307
|
-
test.deepEqual(connection.commands, ['NOOP'], 'first poll ran on a live connection');
|
|
308
|
-
|
|
309
|
-
// Transport dies between polls
|
|
310
|
-
connection.socket.destroyed = true;
|
|
311
|
-
await timers.fire();
|
|
312
|
-
await idlePromise;
|
|
313
|
-
|
|
314
|
-
test.deepEqual(connection.commands, ['NOOP'], 'no command is sent on a dead transport');
|
|
315
|
-
test.equal(connection.idling, false, 'idling reset when the loop stops');
|
|
316
|
-
test.equal(connection.preCheck, false, 'preCheck released');
|
|
317
|
-
test.done();
|
|
318
|
-
});
|
|
319
|
-
};
|
|
320
|
-
|
|
321
|
-
module.exports['Polling: a rejected poll resets idling'] = async test => {
|
|
322
|
-
await withFakeTimers(async timers => {
|
|
323
|
-
let connection = createConnection({
|
|
324
|
-
exec: async command => {
|
|
325
|
-
connection.commands.push(command);
|
|
326
|
-
let err = new Error('Connection not available');
|
|
327
|
-
err.code = 'NoConnection';
|
|
328
|
-
throw err;
|
|
329
|
-
}
|
|
330
|
-
});
|
|
331
|
-
|
|
332
|
-
let idlePromise = idleCommand(connection, 60000);
|
|
333
|
-
await timers.drain();
|
|
334
|
-
await idlePromise;
|
|
335
|
-
|
|
336
|
-
test.equal(connection.idling, false, 'idling reset after a rejected poll');
|
|
337
|
-
test.equal(connection.preCheck, false, 'preCheck released after a rejected poll');
|
|
338
|
-
test.equal(timers.count(), 0, 'nothing left scheduled');
|
|
339
|
-
test.done();
|
|
340
|
-
});
|
|
341
|
-
};
|
|
342
|
-
|
|
343
|
-
module.exports['Polling: no polling session without a saved select command'] = async test => {
|
|
344
|
-
let connection = createConnection({ currentSelectCommand: false });
|
|
345
|
-
await idleCommand(connection, 60000);
|
|
346
|
-
test.deepEqual(connection.commands, [], 'nothing polled');
|
|
347
|
-
test.equal(connection.idling, false, 'idling untouched');
|
|
348
|
-
test.done();
|
|
349
|
-
};
|
|
350
|
-
|
|
351
|
-
module.exports['Polling: a falsy STATUS result stops the loop as PollFailed'] = async test => {
|
|
352
|
-
await withFakeTimers(async timers => {
|
|
353
|
-
let warnings = [];
|
|
354
|
-
let connection = createConnection({
|
|
355
|
-
missingIdleCommand: 'STATUS',
|
|
356
|
-
// Only STATUS is ever polled; a failure without a NO status makes the
|
|
357
|
-
// real STATUS implementation swallow the error and return false
|
|
358
|
-
respond: async () => {
|
|
359
|
-
throw new Error('Command failed');
|
|
360
|
-
}
|
|
361
|
-
});
|
|
362
|
-
connection.log.warn = entry => warnings.push(entry);
|
|
363
|
-
|
|
364
|
-
let idlePromise = idleCommand(connection, 60000);
|
|
365
|
-
await timers.drain();
|
|
366
|
-
await idlePromise;
|
|
367
|
-
|
|
368
|
-
test.ok(
|
|
369
|
-
warnings.some(entry => entry && entry.err && entry.err.code === 'PollFailed'),
|
|
370
|
-
'the falsy STATUS result surfaced as a PollFailed error'
|
|
371
|
-
);
|
|
372
|
-
test.equal(connection.idling, false, 'idling reset after the failed poll');
|
|
373
|
-
test.equal(connection.preCheck, false, 'preCheck released');
|
|
374
|
-
test.equal(timers.count(), 0, 'polling stopped');
|
|
375
|
-
test.done();
|
|
376
|
-
});
|
|
377
|
-
};
|
|
378
|
-
|
|
379
|
-
module.exports['Polling: a repeated break call is a no-op'] = async test => {
|
|
380
|
-
await withFakeTimers(async timers => {
|
|
381
|
-
let connection = createConnection();
|
|
382
|
-
|
|
383
|
-
let idlePromise = idleCommand(connection, 60000);
|
|
384
|
-
await timers.drain();
|
|
385
|
-
test.deepEqual(connection.commands, ['NOOP'], 'the immediate first poll ran');
|
|
386
|
-
|
|
387
|
-
// A caller may hold on to the break function and invoke it more than once
|
|
388
|
-
let preCheck = connection.preCheck;
|
|
389
|
-
await preCheck();
|
|
390
|
-
await preCheck();
|
|
391
|
-
await idlePromise;
|
|
392
|
-
|
|
393
|
-
test.equal(connection.idling, false, 'idling reset');
|
|
394
|
-
test.equal(connection.preCheck, false, 'preCheck released');
|
|
395
|
-
test.equal(timers.count(), 0, 'no timer left armed');
|
|
396
|
-
|
|
397
|
-
await timers.fire();
|
|
398
|
-
test.deepEqual(connection.commands, ['NOOP'], 'no further poll after the duplicate break');
|
|
399
|
-
test.done();
|
|
400
|
-
});
|
|
401
|
-
};
|
|
402
|
-
|
|
403
|
-
module.exports['Polling: a break in the initiation tick prevents the first poll'] = async test => {
|
|
404
|
-
await withFakeTimers(async timers => {
|
|
405
|
-
let connection = createConnection();
|
|
406
|
-
// Simulate a break request (e.g. a command being queued) landing in the same
|
|
407
|
-
// tick the loop is initiated: trap the loop installing its own preCheck and
|
|
408
|
-
// break through it immediately, before the first poll has started
|
|
409
|
-
let installedPreCheck = connection.preCheck;
|
|
410
|
-
Object.defineProperty(connection, 'preCheck', {
|
|
411
|
-
get: () => installedPreCheck,
|
|
412
|
-
set: value => {
|
|
413
|
-
installedPreCheck = value;
|
|
414
|
-
if (typeof value === 'function') {
|
|
415
|
-
value();
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
});
|
|
419
|
-
|
|
420
|
-
let idlePromise = idleCommand(connection, 60000);
|
|
421
|
-
await timers.drain();
|
|
422
|
-
await idlePromise;
|
|
423
|
-
|
|
424
|
-
test.deepEqual(connection.commands, [], 'the already-cancelled session never polled');
|
|
425
|
-
test.equal(connection.idling, false, 'idling reset');
|
|
426
|
-
test.equal(connection.preCheck, false, 'preCheck released');
|
|
427
|
-
test.equal(timers.count(), 0, 'no timer left armed');
|
|
428
|
-
test.done();
|
|
429
|
-
});
|
|
430
|
-
};
|
|
431
|
-
|
|
432
|
-
module.exports['Polling: a restarted session resumes the schedule instead of polling again'] = async test => {
|
|
433
|
-
await withFakeTimers(async timers => {
|
|
434
|
-
let connection = createConnection();
|
|
435
|
-
|
|
436
|
-
let first = idleCommand(connection, 60000);
|
|
437
|
-
await timers.drain();
|
|
438
|
-
test.deepEqual(connection.commands, ['NOOP'], 'the first session polls immediately');
|
|
439
|
-
|
|
440
|
-
await connection.preCheck();
|
|
441
|
-
await first;
|
|
442
|
-
|
|
443
|
-
// Auto-IDLE restarts the loop after every caller command. An unconditional first poll here
|
|
444
|
-
// would tie the poll rate to how often the caller runs commands instead of to the poll
|
|
445
|
-
// interval - with a short autoIdleDelay, a command every few seconds means a poll every
|
|
446
|
-
// few seconds.
|
|
447
|
-
let second = idleCommand(connection, 60000);
|
|
448
|
-
await timers.drain();
|
|
449
|
-
test.deepEqual(connection.commands, ['NOOP'], 'the restarted session does not poll again');
|
|
450
|
-
test.equal(timers.count(), 1, 'it waits out the remainder of the interval instead');
|
|
451
|
-
test.ok(timers.pending()[0].delay <= 60000, 'and never longer than a full interval');
|
|
452
|
-
|
|
453
|
-
await connection.preCheck();
|
|
454
|
-
await second;
|
|
455
|
-
|
|
456
|
-
// Once a full interval has elapsed, a fresh session polls at once again
|
|
457
|
-
connection._lastPollAt = Date.now() - 61000;
|
|
458
|
-
let third = idleCommand(connection, 60000);
|
|
459
|
-
await timers.drain();
|
|
460
|
-
test.deepEqual(connection.commands, ['NOOP', 'NOOP'], 'a session starting after the interval polls right away');
|
|
461
|
-
|
|
462
|
-
await connection.preCheck();
|
|
463
|
-
await third;
|
|
464
|
-
test.done();
|
|
465
|
-
});
|
|
466
|
-
};
|
|
467
|
-
|
|
468
|
-
module.exports['Polling: a failed poll does not defer the next session'] = async test => {
|
|
469
|
-
await withFakeTimers(async timers => {
|
|
470
|
-
let failNext = true;
|
|
471
|
-
let connection = createConnection({
|
|
472
|
-
exec: async command => {
|
|
473
|
-
connection.commands.push(command);
|
|
474
|
-
if (failNext) {
|
|
475
|
-
failNext = false;
|
|
476
|
-
throw new Error('poll failed');
|
|
477
|
-
}
|
|
478
|
-
return { next: () => {} };
|
|
479
|
-
}
|
|
480
|
-
});
|
|
481
|
-
|
|
482
|
-
// The failing poll cancels its own session. The attempt checked nothing, so it must not
|
|
483
|
-
// count as a poll: only a completed poll moves the schedule stamp forward.
|
|
484
|
-
let first = idleCommand(connection, 60000);
|
|
485
|
-
await timers.drain();
|
|
486
|
-
await first;
|
|
487
|
-
test.deepEqual(connection.commands, ['NOOP'], 'the first poll ran and failed');
|
|
488
|
-
|
|
489
|
-
let second = idleCommand(connection, 60000);
|
|
490
|
-
await timers.drain();
|
|
491
|
-
test.deepEqual(connection.commands, ['NOOP', 'NOOP'], 'the next session retries immediately instead of waiting out an interval');
|
|
492
|
-
|
|
493
|
-
await connection.preCheck();
|
|
494
|
-
await second;
|
|
495
|
-
test.done();
|
|
496
|
-
});
|
|
497
|
-
};
|
|
498
|
-
|
|
499
|
-
module.exports['Polling: a backward clock step never defers the next poll past one interval'] = async test => {
|
|
500
|
-
await withFakeTimers(async timers => {
|
|
501
|
-
let connection = createConnection();
|
|
502
|
-
|
|
503
|
-
// A last-poll stamp in the future is what an NTP step or a VM clock sync leaves behind.
|
|
504
|
-
// Without the clamp the remainder math would schedule the next poll a full clock jump
|
|
505
|
-
// plus one interval away.
|
|
506
|
-
connection._lastPollAt = Date.now() + 60 * 60 * 1000;
|
|
507
|
-
|
|
508
|
-
let idlePromise = idleCommand(connection, 60000);
|
|
509
|
-
await timers.drain();
|
|
510
|
-
test.deepEqual(connection.commands, [], 'no immediate poll - the schedule is resumed');
|
|
511
|
-
test.equal(timers.count(), 1, 'a poll timer is armed');
|
|
512
|
-
test.ok(timers.pending()[0].delay <= 60000, 'and it is never more than one interval away');
|
|
513
|
-
|
|
514
|
-
await connection.preCheck();
|
|
515
|
-
await idlePromise;
|
|
516
|
-
test.done();
|
|
517
|
-
});
|
|
518
|
-
};
|