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,431 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
// Live integration tests against a real IMAP4rev2 server (Dovecot 2.4+ in Docker).
|
|
4
|
-
// Started via `npm run test:rev2` (see run-rev2-tests.sh) - excluded from the
|
|
5
|
-
// Gruntfile nodeunit config so `npm test` stays Docker-free.
|
|
6
|
-
//
|
|
7
|
-
// The container uses a static passdb: any username logs in with the password
|
|
8
|
-
// "pass", and every user gets a fresh mail home, so each test connects with a
|
|
9
|
-
// unique user for isolation.
|
|
10
|
-
|
|
11
|
-
const { ImapFlow } = require('../../lib/imap-flow');
|
|
12
|
-
|
|
13
|
-
const HOST = process.env.IMAPFLOW_TEST_HOST || '127.0.0.1';
|
|
14
|
-
const PORT = Number(process.env.IMAPFLOW_TEST_PORT) || 31143;
|
|
15
|
-
|
|
16
|
-
let userCounter = 0;
|
|
17
|
-
|
|
18
|
-
const connectClient = async (options, logs) => {
|
|
19
|
-
let client = new ImapFlow(
|
|
20
|
-
Object.assign(
|
|
21
|
-
{
|
|
22
|
-
host: HOST,
|
|
23
|
-
port: PORT,
|
|
24
|
-
secure: false,
|
|
25
|
-
// The test container upgrades via STARTTLS with a self-signed cert
|
|
26
|
-
tls: { rejectUnauthorized: false },
|
|
27
|
-
auth: { user: `livetest-${Date.now()}-${++userCounter}`, pass: 'pass' },
|
|
28
|
-
logger: false,
|
|
29
|
-
emitLogs: !!logs
|
|
30
|
-
},
|
|
31
|
-
options || {}
|
|
32
|
-
)
|
|
33
|
-
);
|
|
34
|
-
if (logs) {
|
|
35
|
-
client.on('log', entry => logs.push(entry));
|
|
36
|
-
}
|
|
37
|
-
await client.connect();
|
|
38
|
-
return client;
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
const wireLines = logs => logs.filter(entry => entry && typeof entry.msg === 'string' && ['c', 's'].includes(entry.src));
|
|
42
|
-
const clientSent = (logs, needle) => wireLines(logs).some(entry => entry.src === 'c' && entry.msg.includes(needle));
|
|
43
|
-
// Matches only an untagged response for the given command ("* ESEARCH ...").
|
|
44
|
-
// A plain substring check would false-positive on capability listings - the
|
|
45
|
-
// login response advertises tokens like ESEARCH in its CAPABILITY list.
|
|
46
|
-
const serverSentUntagged = (logs, command) => {
|
|
47
|
-
let re = new RegExp(`^\\* ${command}( |$)`);
|
|
48
|
-
return wireLines(logs).some(entry => entry.src === 's' && re.test(entry.msg));
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
module.exports['Live rev2: connect negotiates ENABLE IMAP4rev2'] = async test => {
|
|
52
|
-
const client = await connectClient();
|
|
53
|
-
try {
|
|
54
|
-
test.ok(client.capabilities.has('IMAP4rev2'), 'server should advertise IMAP4rev2');
|
|
55
|
-
test.ok(client.enabled.has('IMAP4REV2'), 'IMAP4rev2 should be enabled');
|
|
56
|
-
// The single merged ENABLE call must not lose the other extensions
|
|
57
|
-
test.ok(client.enabled.has('CONDSTORE'), 'CONDSTORE should still be enabled');
|
|
58
|
-
} finally {
|
|
59
|
-
await client.logout();
|
|
60
|
-
}
|
|
61
|
-
test.done();
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
module.exports['Live rev2: list uses RETURN (SUBSCRIBED) and skips LSUB'] = async test => {
|
|
65
|
-
const logs = [];
|
|
66
|
-
const client = await connectClient(null, logs);
|
|
67
|
-
try {
|
|
68
|
-
const folders = await client.list();
|
|
69
|
-
|
|
70
|
-
test.ok(clientSent(logs, 'SUBSCRIBED'), 'LIST should request RETURN (SUBSCRIBED)');
|
|
71
|
-
test.ok(!clientSent(logs, 'LSUB'), 'LSUB must not be issued on a rev2 session');
|
|
72
|
-
|
|
73
|
-
const sent = folders.find(folder => folder.path === 'Sent');
|
|
74
|
-
test.ok(sent, 'Sent mailbox should be listed');
|
|
75
|
-
test.equal(sent.subscribed, true, 'Sent should be reported as subscribed');
|
|
76
|
-
test.equal(sent.specialUse, '\\Sent', 'special-use flag should be honored');
|
|
77
|
-
// \HasNoChildren must survive the extended LIST (RETURN (CHILDREN) requested)
|
|
78
|
-
test.ok(sent.flags.has('\\HasNoChildren'), 'child info should be present');
|
|
79
|
-
for (let folder of folders) {
|
|
80
|
-
test.ok(!folder.flags.has('\\Subscribed'), '\\Subscribed must be folded into the subscribed property');
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
const inbox = folders.find(folder => folder.path === 'INBOX');
|
|
84
|
-
test.ok(inbox, 'INBOX should be listed');
|
|
85
|
-
test.equal(inbox.subscribed, true, 'INBOX is always reported as subscribed');
|
|
86
|
-
} finally {
|
|
87
|
-
await client.logout();
|
|
88
|
-
}
|
|
89
|
-
test.done();
|
|
90
|
-
};
|
|
91
|
-
|
|
92
|
-
module.exports['Live rev2: subscription state round-trips'] = async test => {
|
|
93
|
-
const client = await connectClient();
|
|
94
|
-
try {
|
|
95
|
-
await client.mailboxCreate('RoundTrip');
|
|
96
|
-
await client.mailboxSubscribe('RoundTrip');
|
|
97
|
-
let folders = await client.list();
|
|
98
|
-
test.equal(folders.find(folder => folder.path === 'RoundTrip').subscribed, true);
|
|
99
|
-
|
|
100
|
-
await client.mailboxUnsubscribe('RoundTrip');
|
|
101
|
-
folders = await client.list();
|
|
102
|
-
test.ok(!folders.find(folder => folder.path === 'RoundTrip').subscribed);
|
|
103
|
-
} finally {
|
|
104
|
-
await client.logout();
|
|
105
|
-
}
|
|
106
|
-
test.done();
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
module.exports['Live rev2: deleted-but-subscribed folders stay out of listings'] = async test => {
|
|
110
|
-
const client = await connectClient();
|
|
111
|
-
try {
|
|
112
|
-
await client.mailboxCreate('Ghost');
|
|
113
|
-
await client.mailboxSubscribe('Ghost');
|
|
114
|
-
await client.mailboxDelete('Ghost');
|
|
115
|
-
|
|
116
|
-
const folders = await client.list();
|
|
117
|
-
// The RETURN (SUBSCRIBED) option must not resurrect phantom subscriptions
|
|
118
|
-
// the way a raw LSUB would
|
|
119
|
-
test.ok(!folders.find(folder => folder.path === 'Ghost'), 'deleted folder must not be listed');
|
|
120
|
-
} finally {
|
|
121
|
-
await client.logout();
|
|
122
|
-
}
|
|
123
|
-
test.done();
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
module.exports['Live rev2: UTF-8 mailbox names round-trip'] = async test => {
|
|
127
|
-
const client = await connectClient();
|
|
128
|
-
try {
|
|
129
|
-
const name = 'T\u00f5rva \u00f5un';
|
|
130
|
-
await client.mailboxCreate(name);
|
|
131
|
-
const folders = await client.list();
|
|
132
|
-
const created = folders.find(folder => folder.path === name);
|
|
133
|
-
test.ok(created, 'UTF-8 mailbox should be listed under its literal name');
|
|
134
|
-
|
|
135
|
-
const mailbox = await client.mailboxOpen(name);
|
|
136
|
-
test.equal(mailbox.path, name);
|
|
137
|
-
await client.mailboxClose();
|
|
138
|
-
await client.mailboxDelete(name);
|
|
139
|
-
} finally {
|
|
140
|
-
await client.logout();
|
|
141
|
-
}
|
|
142
|
-
test.done();
|
|
143
|
-
};
|
|
144
|
-
|
|
145
|
-
module.exports['Live rev2: statusQuery is answered inline via LIST-STATUS'] = async test => {
|
|
146
|
-
const logs = [];
|
|
147
|
-
const client = await connectClient(null, logs);
|
|
148
|
-
try {
|
|
149
|
-
await client.append('INBOX', Buffer.from('Subject: status probe\r\n\r\nstatus probe body\r\n'));
|
|
150
|
-
|
|
151
|
-
const folders = await client.list({ statusQuery: { messages: true, unseen: true } });
|
|
152
|
-
|
|
153
|
-
// Pin the inline LIST-STATUS behavior: the STATUS request must ride on the
|
|
154
|
-
// LIST command itself, and no standalone STATUS command may be issued -
|
|
155
|
-
// a plain substring check would also pass on the fallback path
|
|
156
|
-
const listLine = wireLines(logs).find(entry => entry.src === 'c' && /^\S+ LIST /.test(entry.msg));
|
|
157
|
-
test.ok(listLine && listLine.msg.includes('RETURN') && listLine.msg.includes('STATUS'), 'LIST should carry RETURN (STATUS ...)');
|
|
158
|
-
test.ok(!wireLines(logs).some(entry => entry.src === 'c' && /^\S+ STATUS /.test(entry.msg)), 'no standalone STATUS command should be needed');
|
|
159
|
-
const inbox = folders.find(folder => folder.path === 'INBOX');
|
|
160
|
-
test.equal(inbox.status.messages, 1, 'inline STATUS should report the appended message');
|
|
161
|
-
} finally {
|
|
162
|
-
await client.logout();
|
|
163
|
-
}
|
|
164
|
-
test.done();
|
|
165
|
-
};
|
|
166
|
-
|
|
167
|
-
module.exports['Live rev2: plain search results are collected'] = async test => {
|
|
168
|
-
const logs = [];
|
|
169
|
-
const client = await connectClient(null, logs);
|
|
170
|
-
try {
|
|
171
|
-
await client.append('INBOX', Buffer.from('Subject: first\r\n\r\nfirst\r\n'));
|
|
172
|
-
await client.append('INBOX', Buffer.from('Subject: second\r\n\r\nsecond\r\n'));
|
|
173
|
-
|
|
174
|
-
await client.mailboxOpen('INBOX');
|
|
175
|
-
const results = await client.search({ all: true });
|
|
176
|
-
|
|
177
|
-
// RFC 9051 deprecates the untagged SEARCH response in favor of ESEARCH,
|
|
178
|
-
// but Dovecot 2.4 still answers a plain SEARCH with the legacy form even
|
|
179
|
-
// on an ENABLEd rev2 session (verified on the wire). The client accepts
|
|
180
|
-
// both forms, so assert that the results arrived via one of them - the
|
|
181
|
-
// ESEARCH-answered variant of a plain SEARCH is covered by the mock
|
|
182
|
-
// suite, and a real ESEARCH response is exercised live in the
|
|
183
|
-
// returnOptions test below.
|
|
184
|
-
test.ok(serverSentUntagged(logs, 'SEARCH') || serverSentUntagged(logs, 'ESEARCH'), 'results should arrive via an untagged SEARCH or ESEARCH response');
|
|
185
|
-
test.deepEqual(results, [1, 2]);
|
|
186
|
-
} finally {
|
|
187
|
-
await client.logout();
|
|
188
|
-
}
|
|
189
|
-
test.done();
|
|
190
|
-
};
|
|
191
|
-
|
|
192
|
-
module.exports['Live rev2: returnOptions search is answered via a real ESEARCH response'] = async test => {
|
|
193
|
-
const logs = [];
|
|
194
|
-
const client = await connectClient(null, logs);
|
|
195
|
-
try {
|
|
196
|
-
await client.append('INBOX', Buffer.from('Subject: first\r\n\r\nfirst\r\n'));
|
|
197
|
-
await client.append('INBOX', Buffer.from('Subject: second\r\n\r\nsecond\r\n'));
|
|
198
|
-
|
|
199
|
-
await client.mailboxOpen('INBOX');
|
|
200
|
-
const result = await client.search({ all: true }, { returnOptions: ['ALL', 'COUNT'] });
|
|
201
|
-
|
|
202
|
-
// SEARCH RETURN (...) makes Dovecot answer with a genuine untagged
|
|
203
|
-
// ESEARCH response - this exercises the ESEARCH parsing path against a
|
|
204
|
-
// real server
|
|
205
|
-
test.ok(serverSentUntagged(logs, 'ESEARCH'), 'server should reply with an untagged ESEARCH response');
|
|
206
|
-
test.ok(!serverSentUntagged(logs, 'SEARCH'), 'no legacy untagged SEARCH response is expected');
|
|
207
|
-
test.equal(result.count, 2);
|
|
208
|
-
test.equal(result.all, '1:2');
|
|
209
|
-
} finally {
|
|
210
|
-
await client.logout();
|
|
211
|
-
}
|
|
212
|
-
test.done();
|
|
213
|
-
};
|
|
214
|
-
|
|
215
|
-
module.exports['Live rev2: SEARCHRES saved result is usable as a sequence set'] = async test => {
|
|
216
|
-
// SEARCH RETURN (SAVE) stores the result server-side (RFC 5182) and '$'
|
|
217
|
-
// references it in later commands. '$' has to make it through the outgoing
|
|
218
|
-
// sequence-set validation for that to work at all.
|
|
219
|
-
const client = await connectClient();
|
|
220
|
-
try {
|
|
221
|
-
test.ok(client.capabilities.has('SEARCHRES'), 'Dovecot should advertise SEARCHRES');
|
|
222
|
-
|
|
223
|
-
await client.append('INBOX', Buffer.from('Subject: first\r\n\r\nfirst\r\n'));
|
|
224
|
-
await client.append('INBOX', Buffer.from('Subject: second\r\n\r\nsecond\r\n'));
|
|
225
|
-
|
|
226
|
-
await client.mailboxOpen('INBOX');
|
|
227
|
-
await client.search({ subject: 'second' }, { uid: true, returnOptions: ['SAVE'] });
|
|
228
|
-
|
|
229
|
-
let flagResult = await client.messageFlagsAdd('$', ['\\Flagged'], { uid: true });
|
|
230
|
-
test.ok(flagResult, 'STORE against the saved result must succeed');
|
|
231
|
-
|
|
232
|
-
let messages = [];
|
|
233
|
-
for await (let msg of client.fetch('$', { flags: true, envelope: true }, { uid: true })) {
|
|
234
|
-
messages.push(msg);
|
|
235
|
-
}
|
|
236
|
-
test.equal(messages.length, 1, 'the saved result references exactly the matched message');
|
|
237
|
-
test.equal(messages[0].envelope.subject, 'second');
|
|
238
|
-
test.ok(messages[0].flags.has('\\Flagged'), 'the flag change applied to the saved result');
|
|
239
|
-
} finally {
|
|
240
|
-
await client.logout();
|
|
241
|
-
}
|
|
242
|
-
test.done();
|
|
243
|
-
};
|
|
244
|
-
|
|
245
|
-
module.exports['Live rev2: MODSEQ search criterion surfaces modseq from the ESEARCH response'] = async test => {
|
|
246
|
-
const client = await connectClient();
|
|
247
|
-
try {
|
|
248
|
-
await client.append('INBOX', Buffer.from('Subject: first\r\n\r\nfirst\r\n'));
|
|
249
|
-
await client.append('INBOX', Buffer.from('Subject: second\r\n\r\nsecond\r\n'));
|
|
250
|
-
|
|
251
|
-
await client.mailboxOpen('INBOX');
|
|
252
|
-
// RFC 7162: a MODSEQ criterion on a CONDSTORE session makes the server
|
|
253
|
-
// append MODSEQ to the ESEARCH response
|
|
254
|
-
const result = await client.search({ modseq: 1 }, { returnOptions: ['ALL', 'COUNT'] });
|
|
255
|
-
|
|
256
|
-
test.equal(result.count, 2);
|
|
257
|
-
test.equal(result.all, '1:2');
|
|
258
|
-
test.ok(typeof result.modseq === 'bigint' && result.modseq > 0n, 'modseq should surface as a positive BigInt');
|
|
259
|
-
} finally {
|
|
260
|
-
await client.logout();
|
|
261
|
-
}
|
|
262
|
-
test.done();
|
|
263
|
-
};
|
|
264
|
-
|
|
265
|
-
module.exports['Live rev2: STATUS reports SIZE and DELETED'] = async test => {
|
|
266
|
-
const logs = [];
|
|
267
|
-
const client = await connectClient(null, logs);
|
|
268
|
-
try {
|
|
269
|
-
const raw = Buffer.from('Subject: sized\r\n\r\nsized body\r\n');
|
|
270
|
-
await client.append('INBOX', raw, ['\\Deleted']);
|
|
271
|
-
await client.append('INBOX', Buffer.from('Subject: kept\r\n\r\nkept body\r\n'));
|
|
272
|
-
|
|
273
|
-
const status = await client.status('INBOX', { messages: true, size: true, deleted: true });
|
|
274
|
-
|
|
275
|
-
test.ok(clientSent(logs, 'SIZE'), 'STATUS should request SIZE');
|
|
276
|
-
test.ok(clientSent(logs, 'DELETED'), 'STATUS should request DELETED');
|
|
277
|
-
test.equal(status.messages, 2);
|
|
278
|
-
test.equal(status.deleted, 1, 'one message carries the \\Deleted flag');
|
|
279
|
-
test.ok(Number.isSafeInteger(status.size) && status.size >= raw.length, 'mailbox size should cover at least the first appended message');
|
|
280
|
-
} finally {
|
|
281
|
-
await client.logout();
|
|
282
|
-
}
|
|
283
|
-
test.done();
|
|
284
|
-
};
|
|
285
|
-
|
|
286
|
-
module.exports['Live rev2: statusQuery returns SIZE and DELETED inline via LIST-STATUS'] = async test => {
|
|
287
|
-
const client = await connectClient();
|
|
288
|
-
try {
|
|
289
|
-
await client.append('INBOX', Buffer.from('Subject: probe\r\n\r\nprobe body\r\n'), ['\\Deleted']);
|
|
290
|
-
|
|
291
|
-
const folders = await client.list({ statusQuery: { messages: true, size: true, deleted: true } });
|
|
292
|
-
const inbox = folders.find(folder => folder.path === 'INBOX');
|
|
293
|
-
test.equal(inbox.status.messages, 1);
|
|
294
|
-
test.equal(inbox.status.deleted, 1);
|
|
295
|
-
test.ok(Number.isSafeInteger(inbox.status.size) && inbox.status.size > 0);
|
|
296
|
-
} finally {
|
|
297
|
-
await client.logout();
|
|
298
|
-
}
|
|
299
|
-
test.done();
|
|
300
|
-
};
|
|
301
|
-
|
|
302
|
-
module.exports['Live rev2: SELECT response carries an untagged LIST and re-select gets CLOSED'] = async test => {
|
|
303
|
-
const logs = [];
|
|
304
|
-
const client = await connectClient(null, logs);
|
|
305
|
-
try {
|
|
306
|
-
await client.mailboxCreate('Closer');
|
|
307
|
-
const mailbox = await client.mailboxOpen('INBOX');
|
|
308
|
-
test.equal(mailbox.path, 'INBOX');
|
|
309
|
-
|
|
310
|
-
// RFC 9051 6.3.1: the SELECT response includes an untagged LIST for the
|
|
311
|
-
// selected mailbox - the client must consume it without issue. Scoped to
|
|
312
|
-
// the SELECT exchange itself: mailboxOpen() also issues its own LIST
|
|
313
|
-
// command first, whose untagged replies would satisfy a global check
|
|
314
|
-
// even if the SELECT response omitted the LIST
|
|
315
|
-
const lines = wireLines(logs);
|
|
316
|
-
const selectIdx = lines.findIndex(entry => entry.src === 'c' && /^\S+ SELECT /.test(entry.msg));
|
|
317
|
-
test.ok(selectIdx >= 0, 'SELECT command should be on the wire');
|
|
318
|
-
const selectTag = lines[selectIdx].msg.split(' ')[0];
|
|
319
|
-
const doneIdx = lines.findIndex((entry, i) => i > selectIdx && entry.src === 's' && entry.msg.startsWith(`${selectTag} `));
|
|
320
|
-
const listInSelect = lines.some((entry, i) => i > selectIdx && i < doneIdx && entry.src === 's' && /^\* LIST( |$)/.test(entry.msg));
|
|
321
|
-
test.ok(listInSelect, 'rev2 SELECT response should include an untagged LIST response');
|
|
322
|
-
|
|
323
|
-
// switching mailboxes must produce a CLOSED response code for the old one
|
|
324
|
-
await client.mailboxOpen('Closer');
|
|
325
|
-
const closed = wireLines(logs).some(entry => entry.src === 's' && entry.msg.includes('[CLOSED]'));
|
|
326
|
-
test.ok(closed, 're-select should carry a CLOSED response code');
|
|
327
|
-
test.equal(client.mailbox.path, 'Closer', 'client state should track the newly selected mailbox');
|
|
328
|
-
|
|
329
|
-
await client.mailboxClose();
|
|
330
|
-
await client.mailboxDelete('Closer');
|
|
331
|
-
} finally {
|
|
332
|
-
await client.logout();
|
|
333
|
-
}
|
|
334
|
-
test.done();
|
|
335
|
-
};
|
|
336
|
-
|
|
337
|
-
module.exports['Live rev2: binary fetch uses the folded-in FETCH BINARY'] = async test => {
|
|
338
|
-
const logs = [];
|
|
339
|
-
const client = await connectClient(null, logs);
|
|
340
|
-
try {
|
|
341
|
-
// BINARY sections only allow numeric part specifiers, so use a multipart
|
|
342
|
-
// message - part "1" of a single-part message would resolve to the TEXT
|
|
343
|
-
// section, which must stay a BODY fetch. The base64 encoding gives the
|
|
344
|
-
// server-side BINARY decoding something to undo.
|
|
345
|
-
const content = [
|
|
346
|
-
'Subject: bin',
|
|
347
|
-
'MIME-Version: 1.0',
|
|
348
|
-
'Content-Type: multipart/mixed; boundary=bb',
|
|
349
|
-
'',
|
|
350
|
-
'--bb',
|
|
351
|
-
'Content-Type: text/plain',
|
|
352
|
-
'Content-Transfer-Encoding: base64',
|
|
353
|
-
'',
|
|
354
|
-
Buffer.from('binary body').toString('base64'),
|
|
355
|
-
'--bb--',
|
|
356
|
-
''
|
|
357
|
-
].join('\r\n');
|
|
358
|
-
await client.append('INBOX', Buffer.from(content));
|
|
359
|
-
|
|
360
|
-
await client.mailboxOpen('INBOX');
|
|
361
|
-
const { content: downloadStream } = await client.download('1', '1', { binary: true });
|
|
362
|
-
const chunks = [];
|
|
363
|
-
for await (let chunk of downloadStream) {
|
|
364
|
-
chunks.push(chunk);
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
test.ok(clientSent(logs, 'BINARY.PEEK[1]'), 'client should issue a BINARY fetch for the numeric part on a rev2 session');
|
|
368
|
-
test.equal(Buffer.concat(chunks).toString().trim(), 'binary body', 'BINARY fetch should return the decoded content');
|
|
369
|
-
} finally {
|
|
370
|
-
await client.logout();
|
|
371
|
-
}
|
|
372
|
-
test.done();
|
|
373
|
-
};
|
|
374
|
-
|
|
375
|
-
module.exports['Live rev2: MOVE reports COPYUID from the untagged OK'] = async test => {
|
|
376
|
-
const client = await connectClient();
|
|
377
|
-
try {
|
|
378
|
-
await client.append('INBOX', Buffer.from('Subject: mover\r\n\r\nmover body\r\n'));
|
|
379
|
-
await client.mailboxCreate('Moved');
|
|
380
|
-
|
|
381
|
-
await client.mailboxOpen('INBOX');
|
|
382
|
-
const result = await client.messageMove('1', 'Moved');
|
|
383
|
-
|
|
384
|
-
// RFC 9051 6.4.8: the server is REQUIRED to send COPYUID in an untagged OK
|
|
385
|
-
// before the EXPUNGEs - verify the client captured it
|
|
386
|
-
test.ok(result, 'move should succeed');
|
|
387
|
-
test.equal(result.path, 'INBOX');
|
|
388
|
-
test.equal(result.destination, 'Moved');
|
|
389
|
-
test.ok(result.uidMap && result.uidMap.size === 1, 'COPYUID must be captured from the untagged OK');
|
|
390
|
-
|
|
391
|
-
await client.mailboxClose();
|
|
392
|
-
await client.mailboxDelete('Moved');
|
|
393
|
-
} finally {
|
|
394
|
-
await client.logout();
|
|
395
|
-
}
|
|
396
|
-
test.done();
|
|
397
|
-
};
|
|
398
|
-
|
|
399
|
-
module.exports['Live rev2: message lifecycle smoke test'] = async test => {
|
|
400
|
-
const client = await connectClient();
|
|
401
|
-
try {
|
|
402
|
-
await client.append('INBOX', Buffer.from('Subject: smoke\r\n\r\nsmoke body\r\n'), ['\\Seen']);
|
|
403
|
-
|
|
404
|
-
await client.mailboxOpen('INBOX');
|
|
405
|
-
const message = await client.fetchOne('1', { envelope: true, flags: true, uid: true });
|
|
406
|
-
test.equal(message.envelope.subject, 'smoke');
|
|
407
|
-
test.ok(message.flags.has('\\Seen'));
|
|
408
|
-
|
|
409
|
-
await client.mailboxCreate('Smoke');
|
|
410
|
-
await client.messageCopy('1', 'Smoke');
|
|
411
|
-
await client.messageMove('1', 'Smoke');
|
|
412
|
-
|
|
413
|
-
const status = await client.status('Smoke', { messages: true });
|
|
414
|
-
test.equal(status.messages, 2, 'copy plus move should land two messages');
|
|
415
|
-
|
|
416
|
-
// uid expunge - UIDPLUS is folded into base rev2, so only the requested
|
|
417
|
-
// message may disappear
|
|
418
|
-
await client.mailboxOpen('Smoke');
|
|
419
|
-
const first = await client.fetchOne('1', { uid: true });
|
|
420
|
-
await client.messageDelete(`${first.uid}`, { uid: true });
|
|
421
|
-
const statusAfter = await client.status('Smoke', { messages: true });
|
|
422
|
-
test.equal(statusAfter.messages, 1, 'UID EXPUNGE must remove only the targeted message');
|
|
423
|
-
|
|
424
|
-
await client.mailboxClose();
|
|
425
|
-
await client.mailboxRename('Smoke', 'Smoke2');
|
|
426
|
-
await client.mailboxDelete('Smoke2');
|
|
427
|
-
} finally {
|
|
428
|
-
await client.logout();
|
|
429
|
-
}
|
|
430
|
-
test.done();
|
|
431
|
-
};
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
set -euo pipefail
|
|
3
|
-
|
|
4
|
-
# Runs the ImapFlow live integration tests against a real IMAP4rev2 server
|
|
5
|
-
# (Dovecot 2.4+ in Docker). Opt-in via `npm run test:rev2` - not part of the
|
|
6
|
-
# regular `npm test` run, which stays Docker-free.
|
|
7
|
-
#
|
|
8
|
-
# Environment overrides:
|
|
9
|
-
# IMAPFLOW_DOVECOT_IMAGE image to run (default dovecot/dovecot:2.4.4)
|
|
10
|
-
# IMAPFLOW_DOVECOT_PLATFORM e.g. linux/amd64; defaults to the host platform.
|
|
11
|
-
# Note: forcing linux/amd64 on Apple Silicon does
|
|
12
|
-
# not work - Rosetta cannot start Dovecot's
|
|
13
|
-
# privilege-separated login processes.
|
|
14
|
-
# IMAPFLOW_TEST_PORT host port to publish (default 31143)
|
|
15
|
-
|
|
16
|
-
CONTAINER_NAME="${IMAPFLOW_DOVECOT_CONTAINER:-imapflow-rev2-test}"
|
|
17
|
-
IMAGE="${IMAPFLOW_DOVECOT_IMAGE:-dovecot/dovecot:2.4.4}"
|
|
18
|
-
PORT="${IMAPFLOW_TEST_PORT:-31143}"
|
|
19
|
-
|
|
20
|
-
# Empty unless a platform override was requested; --platform=value keeps it a
|
|
21
|
-
# single argument so plain ${PLATFORM_ARG:+...} expansion works under set -u
|
|
22
|
-
PLATFORM_ARG="${IMAPFLOW_DOVECOT_PLATFORM:+--platform=$IMAPFLOW_DOVECOT_PLATFORM}"
|
|
23
|
-
|
|
24
|
-
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
25
|
-
PROJECT_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
26
|
-
|
|
27
|
-
cleanup() {
|
|
28
|
-
docker rm -f "$CONTAINER_NAME" >/dev/null 2>&1 || true
|
|
29
|
-
}
|
|
30
|
-
trap cleanup EXIT
|
|
31
|
-
cleanup
|
|
32
|
-
|
|
33
|
-
# Guard against a stale image cached for the wrong architecture: `docker run`
|
|
34
|
-
# without --platform silently reuses a local image even when its architecture
|
|
35
|
-
# does not match the host (e.g. an amd64 image left behind on an arm64 host),
|
|
36
|
-
# and Dovecot then fails to start with confusing emulation errors. Only applies
|
|
37
|
-
# when no explicit platform override was requested.
|
|
38
|
-
if [ -z "${IMAPFLOW_DOVECOT_PLATFORM:-}" ] && docker image inspect "$IMAGE" >/dev/null 2>&1; then
|
|
39
|
-
image_arch="$(docker image inspect --format '{{.Architecture}}' "$IMAGE" 2>/dev/null || true)"
|
|
40
|
-
host_arch="$(docker version --format '{{.Server.Arch}}' 2>/dev/null || true)"
|
|
41
|
-
if [ -n "$image_arch" ] && [ -n "$host_arch" ] && [ "$image_arch" != "$host_arch" ]; then
|
|
42
|
-
echo "Local $IMAGE image is $image_arch but the Docker host is $host_arch - re-pulling for linux/$host_arch..."
|
|
43
|
-
docker pull --platform "linux/$host_arch" "$IMAGE"
|
|
44
|
-
fi
|
|
45
|
-
fi
|
|
46
|
-
|
|
47
|
-
docker run ${PLATFORM_ARG:+"$PLATFORM_ARG"} -d --name "$CONTAINER_NAME" \
|
|
48
|
-
-e USER_PASSWORD=pass \
|
|
49
|
-
-v "$SCRIPT_DIR/dovecot-test.conf:/etc/dovecot/conf.d/99-imapflow-test.conf:ro" \
|
|
50
|
-
-p "127.0.0.1:$PORT:31143" \
|
|
51
|
-
"$IMAGE" >/dev/null
|
|
52
|
-
|
|
53
|
-
echo "Waiting for Dovecot to accept IMAP connections on port $PORT..."
|
|
54
|
-
for i in $(seq 1 30); do
|
|
55
|
-
if node -e "
|
|
56
|
-
const net = require('net');
|
|
57
|
-
const socket = net.connect(Number(process.argv[1]), '127.0.0.1');
|
|
58
|
-
const bail = code => { socket.destroy(); process.exit(code); };
|
|
59
|
-
socket.on('data', chunk => bail(chunk.toString().startsWith('* OK') ? 0 : 1));
|
|
60
|
-
socket.on('error', () => bail(1));
|
|
61
|
-
setTimeout(() => bail(1), 2000);
|
|
62
|
-
" "$PORT" 2>/dev/null; then
|
|
63
|
-
echo "Dovecot is ready"
|
|
64
|
-
break
|
|
65
|
-
fi
|
|
66
|
-
if [ "$i" = 30 ]; then
|
|
67
|
-
echo "Dovecot container did not become ready" >&2
|
|
68
|
-
docker logs "$CONTAINER_NAME" >&2 || true
|
|
69
|
-
exit 1
|
|
70
|
-
fi
|
|
71
|
-
sleep 1
|
|
72
|
-
done
|
|
73
|
-
|
|
74
|
-
cd "$PROJECT_DIR"
|
|
75
|
-
IMAPFLOW_TEST_HOST=127.0.0.1 IMAPFLOW_TEST_PORT="$PORT" npx nodeunit test/integration/rev2-live-test.js
|
package/test/integration-test.js
DELETED
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const { ImapFlow } = require('../lib/imap-flow');
|
|
4
|
-
|
|
5
|
-
module.exports['Integration: Basic client creation'] = test => {
|
|
6
|
-
let client = new ImapFlow({
|
|
7
|
-
host: 'imap.example.com',
|
|
8
|
-
auth: { user: 'test', pass: 'test' }
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
test.ok(client);
|
|
12
|
-
test.equal(client.state, client.states.NOT_AUTHENTICATED);
|
|
13
|
-
test.equal(client.authenticated, false);
|
|
14
|
-
test.done();
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
module.exports['Integration: Multiple client instances'] = test => {
|
|
18
|
-
let client1 = new ImapFlow({
|
|
19
|
-
host: 'imap1.example.com',
|
|
20
|
-
auth: { user: 'test1', pass: 'test1' }
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
let client2 = new ImapFlow({
|
|
24
|
-
host: 'imap2.example.com',
|
|
25
|
-
auth: { user: 'test2', pass: 'test2' }
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
test.ok(client1);
|
|
29
|
-
test.ok(client2);
|
|
30
|
-
test.notEqual(client1.id, client2.id);
|
|
31
|
-
test.done();
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
module.exports['Integration: Client configuration'] = test => {
|
|
35
|
-
let client = new ImapFlow({
|
|
36
|
-
host: 'imap.example.com',
|
|
37
|
-
port: 993,
|
|
38
|
-
secure: true,
|
|
39
|
-
auth: { user: 'test', pass: 'test' },
|
|
40
|
-
logger: false
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
test.equal(client.host, 'imap.example.com');
|
|
44
|
-
test.equal(client.port, 993);
|
|
45
|
-
test.equal(client.secureConnection, true);
|
|
46
|
-
test.done();
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
module.exports['Integration: Event emitter functionality'] = test => {
|
|
50
|
-
let client = new ImapFlow({
|
|
51
|
-
host: 'imap.example.com',
|
|
52
|
-
auth: { user: 'test', pass: 'test' }
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
let eventFired = false;
|
|
56
|
-
client.on('test-event', () => {
|
|
57
|
-
eventFired = true;
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
client.emit('test-event');
|
|
61
|
-
|
|
62
|
-
setTimeout(() => {
|
|
63
|
-
test.ok(eventFired);
|
|
64
|
-
test.done();
|
|
65
|
-
}, 10);
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
module.exports['Integration: Stats functionality'] = test => {
|
|
69
|
-
let client = new ImapFlow({
|
|
70
|
-
host: 'imap.example.com',
|
|
71
|
-
auth: { user: 'test', pass: 'test' }
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
let stats = client.stats();
|
|
75
|
-
test.ok(typeof stats === 'object');
|
|
76
|
-
test.ok(Object.prototype.hasOwnProperty.call(stats, 'sent'));
|
|
77
|
-
test.ok(Object.prototype.hasOwnProperty.call(stats, 'received'));
|
|
78
|
-
|
|
79
|
-
// Reset stats
|
|
80
|
-
let resetStats = client.stats(true);
|
|
81
|
-
test.ok(typeof resetStats === 'object');
|
|
82
|
-
test.done();
|
|
83
|
-
};
|