imapflow 1.2.12 → 1.2.14
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/.release-please-manifest.json +1 -1
- package/CHANGELOG.md +15 -0
- package/lib/charsets.js +1 -4
- package/lib/commands/append.js +1 -1
- package/lib/commands/copy.js +3 -24
- package/lib/commands/copyuid-parser.js +33 -0
- package/lib/commands/idle.js +5 -7
- package/lib/commands/list.js +1 -1
- package/lib/commands/move.js +4 -27
- package/lib/commands/store.js +1 -3
- package/lib/handler/imap-compiler.js +1 -3
- package/lib/handler/imap-formal-syntax.js +1 -7
- package/lib/handler/imap-parser.js +1 -1
- package/lib/handler/imap-stream.js +7 -13
- package/lib/handler/parser-instance.js +2 -2
- package/lib/handler/token-parser.js +9 -9
- package/lib/imap-flow.js +112 -124
- package/lib/search-compiler.js +4 -10
- package/lib/tools.js +29 -36
- package/package.json +4 -4
- package/test/charsets-test.js +161 -0
- package/test/commands-integration-test.js +61 -0
- package/test/connection-edge-cases-test.js +94 -0
- package/test/copyuid-parser-test.js +153 -0
- package/test/imap-compiler-test.js +66 -0
- package/test/imap-formal-syntax-test.js +275 -0
- package/test/imap-stream-edge-cases-test.js +268 -0
- package/test/imapflow-test.js +212 -0
- package/test/search-compiler-test.js +74 -0
- package/test/token-parser-test.js +374 -0
- package/test/tools-test.js +149 -2
- package/test/unhandled-rejection-test.js +327 -0
package/test/tools-test.js
CHANGED
|
@@ -270,6 +270,13 @@ module.exports['Tools: getFlagColor with color bits'] = test => {
|
|
|
270
270
|
test.done();
|
|
271
271
|
};
|
|
272
272
|
|
|
273
|
+
module.exports['Tools: getFlagColor with all bits set (index 7) defaults to red'] = test => {
|
|
274
|
+
// bit0=1, bit1=2, bit2=4 => color=7, FLAG_COLORS[7] is undefined => defaults to 'red'
|
|
275
|
+
let flags = new Set(['\\Flagged', '$MailFlagBit0', '$MailFlagBit1', '$MailFlagBit2']);
|
|
276
|
+
test.equal(tools.getFlagColor(flags), 'red');
|
|
277
|
+
test.done();
|
|
278
|
+
};
|
|
279
|
+
|
|
273
280
|
// ============================================
|
|
274
281
|
// getColorFlags tests
|
|
275
282
|
// ============================================
|
|
@@ -284,11 +291,11 @@ module.exports['Tools: getColorFlags with valid color'] = test => {
|
|
|
284
291
|
};
|
|
285
292
|
|
|
286
293
|
module.exports['Tools: getColorFlags with red (index 0)'] = test => {
|
|
287
|
-
// 'red' is index 0
|
|
294
|
+
// 'red' is index 0 — should add \\Flagged and remove all MailFlagBit flags
|
|
288
295
|
let result = tools.getColorFlags('red');
|
|
289
296
|
test.ok(Array.isArray(result.add));
|
|
290
297
|
test.ok(Array.isArray(result.remove));
|
|
291
|
-
test.ok(result.
|
|
298
|
+
test.ok(result.add.includes('\\Flagged'));
|
|
292
299
|
test.done();
|
|
293
300
|
};
|
|
294
301
|
|
|
@@ -990,3 +997,143 @@ module.exports['Tools: AuthenticationFailure error class'] = test => {
|
|
|
990
997
|
test.equal(error.message, 'Auth failed');
|
|
991
998
|
test.done();
|
|
992
999
|
};
|
|
1000
|
+
|
|
1001
|
+
// ============================================
|
|
1002
|
+
// enhanceCommandError tests
|
|
1003
|
+
// ============================================
|
|
1004
|
+
|
|
1005
|
+
module.exports['Tools: enhanceCommandError sets serverResponseCode'] = async test => {
|
|
1006
|
+
let err = new Error('Command failed');
|
|
1007
|
+
err.response = {
|
|
1008
|
+
tag: '*',
|
|
1009
|
+
command: 'NO',
|
|
1010
|
+
attributes: [
|
|
1011
|
+
{
|
|
1012
|
+
type: 'SECTION',
|
|
1013
|
+
section: [{ type: 'ATOM', value: 'NONEXISTENT' }]
|
|
1014
|
+
},
|
|
1015
|
+
{ type: 'ATOM', value: 'Mailbox' },
|
|
1016
|
+
{ type: 'ATOM', value: 'not' },
|
|
1017
|
+
{ type: 'ATOM', value: 'found' }
|
|
1018
|
+
]
|
|
1019
|
+
};
|
|
1020
|
+
let result = await tools.enhanceCommandError(err);
|
|
1021
|
+
test.equal(result.serverResponseCode, 'NONEXISTENT');
|
|
1022
|
+
test.equal(typeof result.response, 'string');
|
|
1023
|
+
test.done();
|
|
1024
|
+
};
|
|
1025
|
+
|
|
1026
|
+
module.exports['Tools: enhanceCommandError with no status code'] = async test => {
|
|
1027
|
+
let err = new Error('Command failed');
|
|
1028
|
+
err.response = { tag: '*', command: 'NO' };
|
|
1029
|
+
let result = await tools.enhanceCommandError(err);
|
|
1030
|
+
test.ok(!result.serverResponseCode);
|
|
1031
|
+
test.done();
|
|
1032
|
+
};
|
|
1033
|
+
|
|
1034
|
+
module.exports['Tools: enhanceCommandError with null response'] = async test => {
|
|
1035
|
+
let err = new Error('Command failed');
|
|
1036
|
+
err.response = null;
|
|
1037
|
+
let result = await tools.enhanceCommandError(err);
|
|
1038
|
+
test.equal(result.response, false);
|
|
1039
|
+
test.done();
|
|
1040
|
+
};
|
|
1041
|
+
|
|
1042
|
+
// ============================================
|
|
1043
|
+
// getDecoder additional tests
|
|
1044
|
+
// ============================================
|
|
1045
|
+
|
|
1046
|
+
module.exports['Tools: getDecoder with eucjp charset'] = test => {
|
|
1047
|
+
let decoder = tools.getDecoder('eucjp');
|
|
1048
|
+
test.ok(decoder);
|
|
1049
|
+
test.equal(decoder.constructor.name, 'JPDecoder');
|
|
1050
|
+
test.done();
|
|
1051
|
+
};
|
|
1052
|
+
|
|
1053
|
+
module.exports['Tools: getDecoder with euc-jp (hyphenated) returns JPDecoder'] = test => {
|
|
1054
|
+
let decoder = tools.getDecoder('euc-jp');
|
|
1055
|
+
test.ok(decoder);
|
|
1056
|
+
test.equal(decoder.constructor.name, 'JPDecoder');
|
|
1057
|
+
test.done();
|
|
1058
|
+
};
|
|
1059
|
+
|
|
1060
|
+
module.exports['Tools: getDecoder with jis charset'] = test => {
|
|
1061
|
+
let decoder = tools.getDecoder('jis');
|
|
1062
|
+
test.ok(decoder);
|
|
1063
|
+
test.equal(decoder.constructor.name, 'JPDecoder');
|
|
1064
|
+
test.done();
|
|
1065
|
+
};
|
|
1066
|
+
|
|
1067
|
+
module.exports['Tools: getDecoder with windows-1252 returns iconv stream'] = test => {
|
|
1068
|
+
let decoder = tools.getDecoder('windows-1252');
|
|
1069
|
+
test.ok(decoder);
|
|
1070
|
+
test.notEqual(decoder.constructor.name, 'JPDecoder');
|
|
1071
|
+
test.ok(typeof decoder.write === 'function');
|
|
1072
|
+
test.done();
|
|
1073
|
+
};
|
|
1074
|
+
|
|
1075
|
+
module.exports['Tools: getDecoder with no arg defaults to ascii'] = test => {
|
|
1076
|
+
let decoder = tools.getDecoder();
|
|
1077
|
+
test.ok(decoder);
|
|
1078
|
+
test.ok(typeof decoder.write === 'function');
|
|
1079
|
+
test.done();
|
|
1080
|
+
};
|
|
1081
|
+
|
|
1082
|
+
// ============================================
|
|
1083
|
+
// getColorFlags tests
|
|
1084
|
+
// ============================================
|
|
1085
|
+
|
|
1086
|
+
module.exports['Tools: getColorFlags returns non-null for all valid colors'] = test => {
|
|
1087
|
+
let colors = ['red', 'orange', 'yellow', 'green', 'blue', 'purple', 'grey'];
|
|
1088
|
+
for (let color of colors) {
|
|
1089
|
+
let result = tools.getColorFlags(color);
|
|
1090
|
+
test.ok(result, `getColorFlags('${color}') should not be null`);
|
|
1091
|
+
test.ok(Array.isArray(result.add), `${color} should have add array`);
|
|
1092
|
+
test.ok(Array.isArray(result.remove), `${color} should have remove array`);
|
|
1093
|
+
}
|
|
1094
|
+
test.done();
|
|
1095
|
+
};
|
|
1096
|
+
|
|
1097
|
+
module.exports['Tools: getColorFlags red has Flagged but no MailFlagBit set'] = test => {
|
|
1098
|
+
let result = tools.getColorFlags('red');
|
|
1099
|
+
// red = index 0, all bits 0 — adds \\Flagged, removes all MailFlagBit flags
|
|
1100
|
+
test.ok(result.add.includes('\\Flagged'));
|
|
1101
|
+
test.ok(result.remove.includes('$MailFlagBit0'));
|
|
1102
|
+
test.ok(result.remove.includes('$MailFlagBit1'));
|
|
1103
|
+
test.ok(result.remove.includes('$MailFlagBit2'));
|
|
1104
|
+
test.done();
|
|
1105
|
+
};
|
|
1106
|
+
|
|
1107
|
+
module.exports['Tools: getColorFlags orange has MailFlagBit0 set'] = test => {
|
|
1108
|
+
let result = tools.getColorFlags('orange');
|
|
1109
|
+
// orange = index 1, bit 0 set
|
|
1110
|
+
test.ok(result.add.includes('\\Flagged'));
|
|
1111
|
+
test.ok(result.add.includes('$MailFlagBit0'));
|
|
1112
|
+
test.ok(result.remove.includes('$MailFlagBit1'));
|
|
1113
|
+
test.ok(result.remove.includes('$MailFlagBit2'));
|
|
1114
|
+
test.done();
|
|
1115
|
+
};
|
|
1116
|
+
|
|
1117
|
+
module.exports['Tools: getColorFlags yellow has MailFlagBit1 set'] = test => {
|
|
1118
|
+
let result = tools.getColorFlags('yellow');
|
|
1119
|
+
// yellow = index 2, bit 1 set
|
|
1120
|
+
test.ok(result.add.includes('\\Flagged'));
|
|
1121
|
+
test.ok(result.add.includes('$MailFlagBit1'));
|
|
1122
|
+
test.ok(result.remove.includes('$MailFlagBit0'));
|
|
1123
|
+
test.ok(result.remove.includes('$MailFlagBit2'));
|
|
1124
|
+
test.done();
|
|
1125
|
+
};
|
|
1126
|
+
|
|
1127
|
+
module.exports['Tools: getColorFlags returns null for invalid color'] = test => {
|
|
1128
|
+
test.equal(tools.getColorFlags('invalid'), null);
|
|
1129
|
+
test.equal(tools.getColorFlags('pink'), null);
|
|
1130
|
+
test.done();
|
|
1131
|
+
};
|
|
1132
|
+
|
|
1133
|
+
module.exports['Tools: getColorFlags with null returns result not null'] = test => {
|
|
1134
|
+
// null input: colorCode becomes null, which is not < 0, so it falls through
|
|
1135
|
+
let result = tools.getColorFlags(null);
|
|
1136
|
+
test.ok(result);
|
|
1137
|
+
test.ok(result.remove.includes('\\Flagged'));
|
|
1138
|
+
test.done();
|
|
1139
|
+
};
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Tests for unhandled rejection prevention.
|
|
5
|
+
*
|
|
6
|
+
* When close() runs, it rejects pending promises via setImmediate. Without
|
|
7
|
+
* guards, the rejection can fire before the caller's handler is attached,
|
|
8
|
+
* causing Node.js unhandledRejection. These tests verify that no unhandled
|
|
9
|
+
* rejections escape while the caller still receives the expected error.
|
|
10
|
+
*
|
|
11
|
+
* Key fix: exec() and getMailboxLock() are non-async, returning the promise
|
|
12
|
+
* directly (with .catch(noop)), so the caller gets the same promise object
|
|
13
|
+
* that has the noop handler. This prevents the async-wrapper double-promise
|
|
14
|
+
* issue where .catch(noop) on an inner promise doesn't protect the outer
|
|
15
|
+
* async wrapper promise.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const net = require('net');
|
|
19
|
+
const { ImapFlow } = require('../lib/imap-flow');
|
|
20
|
+
|
|
21
|
+
// Create a simple mock IMAP server
|
|
22
|
+
function createMockServer() {
|
|
23
|
+
const server = net.createServer(socket => {
|
|
24
|
+
socket.write('* OK Mock IMAP Server ready\r\n');
|
|
25
|
+
|
|
26
|
+
socket.on('data', data => {
|
|
27
|
+
const lines = data
|
|
28
|
+
.toString()
|
|
29
|
+
.split('\r\n')
|
|
30
|
+
.filter(l => l.trim());
|
|
31
|
+
|
|
32
|
+
for (const line of lines) {
|
|
33
|
+
const parts = line.split(' ');
|
|
34
|
+
const tag = parts[0];
|
|
35
|
+
const command = parts[1] ? parts[1].toUpperCase() : '';
|
|
36
|
+
|
|
37
|
+
if (command === 'CAPABILITY') {
|
|
38
|
+
socket.write('* CAPABILITY IMAP4rev1 AUTH=PLAIN\r\n');
|
|
39
|
+
socket.write(`${tag} OK CAPABILITY completed\r\n`);
|
|
40
|
+
} else if (command === 'LOGIN') {
|
|
41
|
+
socket.write(`${tag} OK LOGIN completed\r\n`);
|
|
42
|
+
} else if (command === 'LOGOUT') {
|
|
43
|
+
socket.write('* BYE Server logging out\r\n');
|
|
44
|
+
socket.write(`${tag} OK LOGOUT completed\r\n`);
|
|
45
|
+
socket.end();
|
|
46
|
+
} else if (command === 'NAMESPACE') {
|
|
47
|
+
socket.write('* NAMESPACE (("" "/")) NIL NIL\r\n');
|
|
48
|
+
socket.write(`${tag} OK NAMESPACE completed\r\n`);
|
|
49
|
+
} else if (command === 'COMPRESS') {
|
|
50
|
+
socket.write(`${tag} NO COMPRESS not supported\r\n`);
|
|
51
|
+
} else if (command === 'ENABLE') {
|
|
52
|
+
socket.write(`${tag} OK ENABLE completed\r\n`);
|
|
53
|
+
} else if (command === 'ID') {
|
|
54
|
+
socket.write('* ID NIL\r\n');
|
|
55
|
+
socket.write(`${tag} OK ID completed\r\n`);
|
|
56
|
+
} else if (command === 'NOOP') {
|
|
57
|
+
socket.write(`${tag} OK NOOP completed\r\n`);
|
|
58
|
+
} else if (tag && command) {
|
|
59
|
+
socket.write(`${tag} OK Command completed\r\n`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
socket.on('error', () => {});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
return server;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Helper: install an unhandledRejection detector
|
|
71
|
+
function installRejectionDetector(test) {
|
|
72
|
+
let unhandled = false;
|
|
73
|
+
let unhandledReason = null;
|
|
74
|
+
const handler = reason => {
|
|
75
|
+
unhandled = true;
|
|
76
|
+
unhandledReason = reason;
|
|
77
|
+
};
|
|
78
|
+
process.on('unhandledRejection', handler);
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
check() {
|
|
82
|
+
process.removeListener('unhandledRejection', handler);
|
|
83
|
+
test.equal(unhandled, false, 'no unhandledRejection should fire' + (unhandledReason ? ': ' + unhandledReason.message : ''));
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
exports['Unhandled Rejection Prevention'] = {
|
|
89
|
+
'exec() + close() race should not cause unhandled rejection'(test) {
|
|
90
|
+
test.expect(2);
|
|
91
|
+
|
|
92
|
+
const client = new ImapFlow({
|
|
93
|
+
host: '127.0.0.1',
|
|
94
|
+
port: 1,
|
|
95
|
+
secure: false,
|
|
96
|
+
logger: false
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// Set up state so exec() doesn't reject synchronously
|
|
100
|
+
client.state = client.states.AUTHENTICATED;
|
|
101
|
+
client.socket = new net.Socket();
|
|
102
|
+
|
|
103
|
+
const detector = installRejectionDetector(test);
|
|
104
|
+
|
|
105
|
+
// Create a pending request, then immediately close.
|
|
106
|
+
// close() rejects the request via setImmediate. Without the fix,
|
|
107
|
+
// trySend()'s async chain rejects the promise through a microtask
|
|
108
|
+
// before the caller's handler is attached.
|
|
109
|
+
let promise = client.exec('NOOP');
|
|
110
|
+
client.close();
|
|
111
|
+
|
|
112
|
+
// Wait for setImmediate and microtask rejections to settle
|
|
113
|
+
setTimeout(() => {
|
|
114
|
+
detector.check();
|
|
115
|
+
promise.catch(err => {
|
|
116
|
+
test.equal(err.code, 'NoConnection', 'caller should receive NoConnection error');
|
|
117
|
+
test.done();
|
|
118
|
+
});
|
|
119
|
+
}, 100);
|
|
120
|
+
},
|
|
121
|
+
|
|
122
|
+
'exec() on LOGOUT state should not cause unhandled rejection'(test) {
|
|
123
|
+
test.expect(2);
|
|
124
|
+
|
|
125
|
+
const client = new ImapFlow({
|
|
126
|
+
host: '127.0.0.1',
|
|
127
|
+
port: 1,
|
|
128
|
+
secure: false,
|
|
129
|
+
logger: false
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
client.state = client.states.LOGOUT;
|
|
133
|
+
|
|
134
|
+
const detector = installRejectionDetector(test);
|
|
135
|
+
|
|
136
|
+
// exec() detects LOGOUT and returns a rejected promise with .catch(noop)
|
|
137
|
+
let promise = client.exec('NOOP');
|
|
138
|
+
|
|
139
|
+
setTimeout(() => {
|
|
140
|
+
detector.check();
|
|
141
|
+
promise.catch(err => {
|
|
142
|
+
test.equal(err.code, 'NoConnection', 'should reject with NoConnection');
|
|
143
|
+
test.done();
|
|
144
|
+
});
|
|
145
|
+
}, 100);
|
|
146
|
+
},
|
|
147
|
+
|
|
148
|
+
'exec() with destroyed socket should not cause unhandled rejection'(test) {
|
|
149
|
+
test.expect(2);
|
|
150
|
+
|
|
151
|
+
const client = new ImapFlow({
|
|
152
|
+
host: '127.0.0.1',
|
|
153
|
+
port: 1,
|
|
154
|
+
secure: false,
|
|
155
|
+
logger: false
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
client.state = client.states.AUTHENTICATED;
|
|
159
|
+
let sock = new net.Socket();
|
|
160
|
+
sock.destroy();
|
|
161
|
+
client.socket = sock;
|
|
162
|
+
|
|
163
|
+
const detector = installRejectionDetector(test);
|
|
164
|
+
|
|
165
|
+
let promise = client.exec('NOOP');
|
|
166
|
+
|
|
167
|
+
setTimeout(() => {
|
|
168
|
+
detector.check();
|
|
169
|
+
promise.catch(err => {
|
|
170
|
+
test.equal(err.code, 'EConnectionClosed', 'should reject with EConnectionClosed');
|
|
171
|
+
test.done();
|
|
172
|
+
});
|
|
173
|
+
}, 100);
|
|
174
|
+
},
|
|
175
|
+
|
|
176
|
+
'multiple pending exec() + close() should not cause unhandled rejections'(test) {
|
|
177
|
+
test.expect(7);
|
|
178
|
+
|
|
179
|
+
const client = new ImapFlow({
|
|
180
|
+
host: '127.0.0.1',
|
|
181
|
+
port: 1,
|
|
182
|
+
secure: false,
|
|
183
|
+
logger: false
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
client.state = client.states.AUTHENTICATED;
|
|
187
|
+
client.socket = new net.Socket();
|
|
188
|
+
|
|
189
|
+
const detector = installRejectionDetector(test);
|
|
190
|
+
|
|
191
|
+
let p1 = client.exec('NOOP');
|
|
192
|
+
let p2 = client.exec('NOOP');
|
|
193
|
+
let p3 = client.exec('NOOP');
|
|
194
|
+
client.close();
|
|
195
|
+
|
|
196
|
+
setTimeout(() => {
|
|
197
|
+
detector.check();
|
|
198
|
+
|
|
199
|
+
let settled = 0;
|
|
200
|
+
const checkDone = () => {
|
|
201
|
+
if (++settled === 3) {
|
|
202
|
+
test.done();
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
p1.catch(err => {
|
|
207
|
+
test.ok(err, 'p1 should reject');
|
|
208
|
+
test.equal(err.code, 'NoConnection', 'p1 error code');
|
|
209
|
+
checkDone();
|
|
210
|
+
});
|
|
211
|
+
p2.catch(err => {
|
|
212
|
+
test.ok(err, 'p2 should reject');
|
|
213
|
+
test.equal(err.code, 'NoConnection', 'p2 error code');
|
|
214
|
+
checkDone();
|
|
215
|
+
});
|
|
216
|
+
p3.catch(err => {
|
|
217
|
+
test.ok(err, 'p3 should reject');
|
|
218
|
+
test.equal(err.code, 'NoConnection', 'p3 error code');
|
|
219
|
+
checkDone();
|
|
220
|
+
});
|
|
221
|
+
}, 100);
|
|
222
|
+
},
|
|
223
|
+
|
|
224
|
+
'getMailboxLock() + close() race should not cause unhandled rejection'(test) {
|
|
225
|
+
test.expect(2);
|
|
226
|
+
|
|
227
|
+
const server = createMockServer();
|
|
228
|
+
|
|
229
|
+
server.listen(0, '127.0.0.1', async () => {
|
|
230
|
+
const port = server.address().port;
|
|
231
|
+
|
|
232
|
+
const client = new ImapFlow({
|
|
233
|
+
host: '127.0.0.1',
|
|
234
|
+
port,
|
|
235
|
+
secure: false,
|
|
236
|
+
logger: false,
|
|
237
|
+
auth: {
|
|
238
|
+
user: 'test',
|
|
239
|
+
pass: 'test'
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
const detector = installRejectionDetector(test);
|
|
244
|
+
|
|
245
|
+
try {
|
|
246
|
+
await client.connect();
|
|
247
|
+
|
|
248
|
+
// Request a lock, then immediately close before it resolves.
|
|
249
|
+
// close() rejects pending locks via setImmediate.
|
|
250
|
+
let lockPromise = client.getMailboxLock('INBOX');
|
|
251
|
+
client.close();
|
|
252
|
+
|
|
253
|
+
try {
|
|
254
|
+
await lockPromise;
|
|
255
|
+
test.ok(false, 'lockPromise should have rejected');
|
|
256
|
+
} catch (err) {
|
|
257
|
+
// Wait for any deferred unhandled rejection
|
|
258
|
+
await new Promise(r => setTimeout(r, 100));
|
|
259
|
+
detector.check();
|
|
260
|
+
test.equal(err.code, 'NoConnection', 'caller should receive NoConnection error');
|
|
261
|
+
}
|
|
262
|
+
} catch (err) {
|
|
263
|
+
detector.check();
|
|
264
|
+
test.ok(false, 'unexpected error: ' + err.message);
|
|
265
|
+
} finally {
|
|
266
|
+
server.close(() => test.done());
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
},
|
|
270
|
+
|
|
271
|
+
'connect() + close() during greeting timeout should not cause unhandled rejection'(test) {
|
|
272
|
+
test.expect(2);
|
|
273
|
+
|
|
274
|
+
// Server that accepts TCP but never sends a greeting.
|
|
275
|
+
// The client's greetingTimeout will fire.
|
|
276
|
+
const server = net.createServer(socket => {
|
|
277
|
+
// Intentionally send nothing - let client timeout
|
|
278
|
+
socket.on('error', () => {});
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
server.listen(0, '127.0.0.1', () => {
|
|
282
|
+
const port = server.address().port;
|
|
283
|
+
|
|
284
|
+
const client = new ImapFlow({
|
|
285
|
+
host: '127.0.0.1',
|
|
286
|
+
port,
|
|
287
|
+
secure: false,
|
|
288
|
+
logger: false,
|
|
289
|
+
greetingTimeout: 200,
|
|
290
|
+
auth: {
|
|
291
|
+
user: 'test',
|
|
292
|
+
pass: 'test'
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
const detector = installRejectionDetector(test);
|
|
297
|
+
|
|
298
|
+
// Close shortly after the TCP connection is established but
|
|
299
|
+
// before the greeting is received
|
|
300
|
+
const origSetSocket = client.setSocketHandlers;
|
|
301
|
+
let closeTriggered = false;
|
|
302
|
+
client.setSocketHandlers = function () {
|
|
303
|
+
origSetSocket.call(this);
|
|
304
|
+
if (!closeTriggered) {
|
|
305
|
+
closeTriggered = true;
|
|
306
|
+
// Close on the next tick to simulate race
|
|
307
|
+
setImmediate(() => client.close());
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
client.connect().then(
|
|
312
|
+
() => {
|
|
313
|
+
detector.check();
|
|
314
|
+
test.ok(false, 'connect() should have rejected');
|
|
315
|
+
server.close(() => test.done());
|
|
316
|
+
},
|
|
317
|
+
err => {
|
|
318
|
+
setTimeout(() => {
|
|
319
|
+
detector.check();
|
|
320
|
+
test.ok(err && err.code, 'connect() should reject with an error code');
|
|
321
|
+
server.close(() => test.done());
|
|
322
|
+
}, 100);
|
|
323
|
+
}
|
|
324
|
+
);
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
};
|