imapflow 1.2.3 → 1.2.5
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 +14 -0
- package/lib/imap-flow.d.ts +4 -2
- package/lib/imap-flow.js +23 -0
- package/package.json +10 -7
- package/test/commands-integration-test.js +8216 -0
- package/test/jp-decoder-test.js +199 -0
- package/test/limited-passthrough-test.js +258 -0
- package/test/memory-cleanup-test.js +144 -0
- package/test/proxy-connection-test.js +541 -0
- package/test/search-compiler-test.js +820 -50
- package/test/tools-test.js +788 -0
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { JPDecoder } = require('../lib/jp-decoder');
|
|
4
|
+
const { PassThrough, Transform } = require('stream');
|
|
5
|
+
|
|
6
|
+
// Helper to collect stream output
|
|
7
|
+
const collectStream = stream =>
|
|
8
|
+
new Promise((resolve, reject) => {
|
|
9
|
+
const chunks = [];
|
|
10
|
+
stream.on('data', chunk => chunks.push(chunk));
|
|
11
|
+
stream.on('end', () => resolve(Buffer.concat(chunks)));
|
|
12
|
+
stream.on('error', reject);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
// ============================================
|
|
16
|
+
// Constructor tests
|
|
17
|
+
// ============================================
|
|
18
|
+
|
|
19
|
+
module.exports['JPDecoder: constructor sets charset'] = test => {
|
|
20
|
+
let decoder = new JPDecoder('iso-2022-jp');
|
|
21
|
+
test.equal(decoder.charset, 'iso-2022-jp');
|
|
22
|
+
test.deepEqual(decoder.chunks, []);
|
|
23
|
+
test.equal(decoder.chunklen, 0);
|
|
24
|
+
test.done();
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
module.exports['JPDecoder: is a Transform stream'] = test => {
|
|
28
|
+
let decoder = new JPDecoder('iso-2022-jp');
|
|
29
|
+
test.ok(decoder instanceof Transform);
|
|
30
|
+
test.ok(typeof decoder.pipe === 'function');
|
|
31
|
+
test.ok(typeof decoder.write === 'function');
|
|
32
|
+
test.done();
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// ============================================
|
|
36
|
+
// _transform tests
|
|
37
|
+
// ============================================
|
|
38
|
+
|
|
39
|
+
module.exports['JPDecoder: _transform accumulates buffer chunks'] = test => {
|
|
40
|
+
let decoder = new JPDecoder('iso-2022-jp');
|
|
41
|
+
let chunk1 = Buffer.from('hello');
|
|
42
|
+
let chunk2 = Buffer.from('world');
|
|
43
|
+
|
|
44
|
+
decoder._transform(chunk1, 'buffer', () => {
|
|
45
|
+
test.equal(decoder.chunks.length, 1);
|
|
46
|
+
test.equal(decoder.chunklen, 5);
|
|
47
|
+
|
|
48
|
+
decoder._transform(chunk2, 'buffer', () => {
|
|
49
|
+
test.equal(decoder.chunks.length, 2);
|
|
50
|
+
test.equal(decoder.chunklen, 10);
|
|
51
|
+
test.done();
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
module.exports['JPDecoder: _transform converts string to buffer'] = test => {
|
|
57
|
+
let decoder = new JPDecoder('iso-2022-jp');
|
|
58
|
+
let stringChunk = 'hello';
|
|
59
|
+
|
|
60
|
+
decoder._transform(stringChunk, 'utf8', () => {
|
|
61
|
+
test.equal(decoder.chunks.length, 1);
|
|
62
|
+
test.ok(Buffer.isBuffer(decoder.chunks[0]));
|
|
63
|
+
test.equal(decoder.chunks[0].toString(), 'hello');
|
|
64
|
+
test.done();
|
|
65
|
+
});
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
// ============================================
|
|
69
|
+
// _flush tests
|
|
70
|
+
// ============================================
|
|
71
|
+
|
|
72
|
+
module.exports['JPDecoder: _flush outputs accumulated data'] = async test => {
|
|
73
|
+
let decoder = new JPDecoder('utf-8');
|
|
74
|
+
let output = collectStream(decoder);
|
|
75
|
+
|
|
76
|
+
decoder.write(Buffer.from('hello '));
|
|
77
|
+
decoder.write(Buffer.from('world'));
|
|
78
|
+
decoder.end();
|
|
79
|
+
|
|
80
|
+
let result = await output;
|
|
81
|
+
test.equal(result.toString(), 'hello world');
|
|
82
|
+
test.done();
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
module.exports['JPDecoder: _flush converts ISO-2022-JP to Unicode'] = async test => {
|
|
86
|
+
let decoder = new JPDecoder('iso-2022-jp');
|
|
87
|
+
let output = collectStream(decoder);
|
|
88
|
+
|
|
89
|
+
// ISO-2022-JP encoded Japanese text for "nihongo" (Japanese)
|
|
90
|
+
// ESC $ B sequence switches to JIS X 0208, ESC ( B switches back to ASCII
|
|
91
|
+
let iso2022jp = Buffer.from([
|
|
92
|
+
0x1b,
|
|
93
|
+
0x24,
|
|
94
|
+
0x42, // ESC $ B - switch to JIS X 0208
|
|
95
|
+
0x46,
|
|
96
|
+
0x7c, // ni
|
|
97
|
+
0x4b,
|
|
98
|
+
0x5c, // hon
|
|
99
|
+
0x38,
|
|
100
|
+
0x6c, // go
|
|
101
|
+
0x1b,
|
|
102
|
+
0x28,
|
|
103
|
+
0x42 // ESC ( B - switch back to ASCII
|
|
104
|
+
]);
|
|
105
|
+
|
|
106
|
+
decoder.write(iso2022jp);
|
|
107
|
+
decoder.end();
|
|
108
|
+
|
|
109
|
+
let result = await output;
|
|
110
|
+
test.ok(result.length > 0);
|
|
111
|
+
test.done();
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
module.exports['JPDecoder: _flush handles conversion errors gracefully'] = async test => {
|
|
115
|
+
// Use an invalid/unknown charset to trigger error path
|
|
116
|
+
let decoder = new JPDecoder('invalid-charset-xyz');
|
|
117
|
+
let output = collectStream(decoder);
|
|
118
|
+
|
|
119
|
+
let input = Buffer.from('test data');
|
|
120
|
+
decoder.write(input);
|
|
121
|
+
decoder.end();
|
|
122
|
+
|
|
123
|
+
let result = await output;
|
|
124
|
+
// On error, should return original input
|
|
125
|
+
test.equal(result.toString(), 'test data');
|
|
126
|
+
test.done();
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
module.exports['JPDecoder: _flush handles empty input'] = async test => {
|
|
130
|
+
let decoder = new JPDecoder('iso-2022-jp');
|
|
131
|
+
let output = collectStream(decoder);
|
|
132
|
+
|
|
133
|
+
decoder.end();
|
|
134
|
+
|
|
135
|
+
let result = await output;
|
|
136
|
+
test.equal(result.length, 0);
|
|
137
|
+
test.done();
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
// ============================================
|
|
141
|
+
// Integration tests
|
|
142
|
+
// ============================================
|
|
143
|
+
|
|
144
|
+
module.exports['JPDecoder: works with pipe'] = async test => {
|
|
145
|
+
let source = new PassThrough();
|
|
146
|
+
let decoder = new JPDecoder('utf-8');
|
|
147
|
+
let output = collectStream(source.pipe(decoder));
|
|
148
|
+
|
|
149
|
+
source.write('hello ');
|
|
150
|
+
source.write('world');
|
|
151
|
+
source.end();
|
|
152
|
+
|
|
153
|
+
let result = await output;
|
|
154
|
+
test.equal(result.toString(), 'hello world');
|
|
155
|
+
test.done();
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
module.exports['JPDecoder: handles multiple small chunks'] = async test => {
|
|
159
|
+
let decoder = new JPDecoder('utf-8');
|
|
160
|
+
let output = collectStream(decoder);
|
|
161
|
+
|
|
162
|
+
// Write character by character
|
|
163
|
+
'hello'.split('').forEach(char => decoder.write(char));
|
|
164
|
+
decoder.end();
|
|
165
|
+
|
|
166
|
+
let result = await output;
|
|
167
|
+
test.equal(result.toString(), 'hello');
|
|
168
|
+
test.done();
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
module.exports['JPDecoder: handles Shift_JIS charset'] = async test => {
|
|
172
|
+
let decoder = new JPDecoder('shift_jis');
|
|
173
|
+
let output = collectStream(decoder);
|
|
174
|
+
|
|
175
|
+
// Shift_JIS encoded "test" in katakana (tesuto)
|
|
176
|
+
let shiftJis = Buffer.from([0x83, 0x65, 0x83, 0x58, 0x83, 0x67]);
|
|
177
|
+
|
|
178
|
+
decoder.write(shiftJis);
|
|
179
|
+
decoder.end();
|
|
180
|
+
|
|
181
|
+
let result = await output;
|
|
182
|
+
test.ok(result.length > 0);
|
|
183
|
+
test.done();
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
module.exports['JPDecoder: handles EUC-JP charset'] = async test => {
|
|
187
|
+
let decoder = new JPDecoder('euc-jp');
|
|
188
|
+
let output = collectStream(decoder);
|
|
189
|
+
|
|
190
|
+
// EUC-JP encoded Japanese character
|
|
191
|
+
let eucJp = Buffer.from([0xc6, 0xfc, 0xcb, 0xdc]); // nihon
|
|
192
|
+
|
|
193
|
+
decoder.write(eucJp);
|
|
194
|
+
decoder.end();
|
|
195
|
+
|
|
196
|
+
let result = await output;
|
|
197
|
+
test.ok(result.length > 0);
|
|
198
|
+
test.done();
|
|
199
|
+
};
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { LimitedPassthrough } = require('../lib/limited-passthrough');
|
|
4
|
+
const { PassThrough, Transform } = require('stream');
|
|
5
|
+
|
|
6
|
+
// Helper to collect stream output
|
|
7
|
+
const collectStream = stream =>
|
|
8
|
+
new Promise((resolve, reject) => {
|
|
9
|
+
const chunks = [];
|
|
10
|
+
stream.on('data', chunk => chunks.push(chunk));
|
|
11
|
+
stream.on('end', () => resolve(Buffer.concat(chunks)));
|
|
12
|
+
stream.on('error', reject);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
// ============================================
|
|
16
|
+
// Constructor tests
|
|
17
|
+
// ============================================
|
|
18
|
+
|
|
19
|
+
module.exports['LimitedPassthrough: constructor with options'] = test => {
|
|
20
|
+
let stream = new LimitedPassthrough({ maxBytes: 100 });
|
|
21
|
+
test.equal(stream.maxBytes, 100);
|
|
22
|
+
test.equal(stream.processed, 0);
|
|
23
|
+
test.equal(stream.limited, false);
|
|
24
|
+
test.done();
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
module.exports['LimitedPassthrough: constructor with no options'] = test => {
|
|
28
|
+
let stream = new LimitedPassthrough();
|
|
29
|
+
test.equal(stream.maxBytes, Infinity);
|
|
30
|
+
test.equal(stream.processed, 0);
|
|
31
|
+
test.equal(stream.limited, false);
|
|
32
|
+
test.done();
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
module.exports['LimitedPassthrough: constructor with null options'] = test => {
|
|
36
|
+
let stream = new LimitedPassthrough(null);
|
|
37
|
+
test.equal(stream.maxBytes, Infinity);
|
|
38
|
+
test.done();
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
module.exports['LimitedPassthrough: is a Transform stream'] = test => {
|
|
42
|
+
let stream = new LimitedPassthrough();
|
|
43
|
+
test.ok(stream instanceof Transform);
|
|
44
|
+
test.ok(typeof stream.pipe === 'function');
|
|
45
|
+
test.ok(typeof stream.write === 'function');
|
|
46
|
+
test.done();
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
// ============================================
|
|
50
|
+
// _transform tests - no limit
|
|
51
|
+
// ============================================
|
|
52
|
+
|
|
53
|
+
module.exports['LimitedPassthrough: passes all data when no limit'] = async test => {
|
|
54
|
+
let stream = new LimitedPassthrough();
|
|
55
|
+
let output = collectStream(stream);
|
|
56
|
+
|
|
57
|
+
stream.write(Buffer.from('hello '));
|
|
58
|
+
stream.write(Buffer.from('world'));
|
|
59
|
+
stream.end();
|
|
60
|
+
|
|
61
|
+
let result = await output;
|
|
62
|
+
test.equal(result.toString(), 'hello world');
|
|
63
|
+
test.equal(stream.processed, 11);
|
|
64
|
+
test.equal(stream.limited, false);
|
|
65
|
+
test.done();
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
// ============================================
|
|
69
|
+
// _transform tests - with limit
|
|
70
|
+
// ============================================
|
|
71
|
+
|
|
72
|
+
module.exports['LimitedPassthrough: limits output to maxBytes'] = async test => {
|
|
73
|
+
let stream = new LimitedPassthrough({ maxBytes: 5 });
|
|
74
|
+
let output = collectStream(stream);
|
|
75
|
+
|
|
76
|
+
stream.write(Buffer.from('hello world'));
|
|
77
|
+
stream.end();
|
|
78
|
+
|
|
79
|
+
let result = await output;
|
|
80
|
+
test.equal(result.toString(), 'hello');
|
|
81
|
+
test.equal(stream.processed, 5);
|
|
82
|
+
test.equal(stream.limited, true);
|
|
83
|
+
test.done();
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
module.exports['LimitedPassthrough: limits across multiple chunks'] = async test => {
|
|
87
|
+
let stream = new LimitedPassthrough({ maxBytes: 8 });
|
|
88
|
+
let output = collectStream(stream);
|
|
89
|
+
|
|
90
|
+
stream.write(Buffer.from('hello ')); // 6 bytes
|
|
91
|
+
stream.write(Buffer.from('world')); // 5 bytes, only 2 should pass
|
|
92
|
+
stream.end();
|
|
93
|
+
|
|
94
|
+
let result = await output;
|
|
95
|
+
test.equal(result.toString(), 'hello wo');
|
|
96
|
+
test.equal(stream.processed, 8);
|
|
97
|
+
test.equal(stream.limited, true);
|
|
98
|
+
test.done();
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
module.exports['LimitedPassthrough: drops data after limit reached'] = async test => {
|
|
102
|
+
let stream = new LimitedPassthrough({ maxBytes: 5 });
|
|
103
|
+
let output = collectStream(stream);
|
|
104
|
+
|
|
105
|
+
stream.write(Buffer.from('hello')); // exactly 5 bytes
|
|
106
|
+
stream.write(Buffer.from(' world')); // should be dropped
|
|
107
|
+
stream.write(Buffer.from('!')); // should be dropped
|
|
108
|
+
stream.end();
|
|
109
|
+
|
|
110
|
+
let result = await output;
|
|
111
|
+
test.equal(result.toString(), 'hello');
|
|
112
|
+
test.equal(stream.limited, true);
|
|
113
|
+
test.done();
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
module.exports['LimitedPassthrough: handles exact boundary'] = async test => {
|
|
117
|
+
let stream = new LimitedPassthrough({ maxBytes: 5 });
|
|
118
|
+
let output = collectStream(stream);
|
|
119
|
+
|
|
120
|
+
stream.write(Buffer.from('hello')); // exactly 5 bytes
|
|
121
|
+
stream.end();
|
|
122
|
+
|
|
123
|
+
let result = await output;
|
|
124
|
+
test.equal(result.toString(), 'hello');
|
|
125
|
+
test.equal(stream.processed, 5);
|
|
126
|
+
test.equal(stream.limited, true);
|
|
127
|
+
test.done();
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
module.exports['LimitedPassthrough: zero maxBytes treated as Infinity'] = async test => {
|
|
131
|
+
// Note: maxBytes: 0 is falsy, so constructor uses Infinity instead
|
|
132
|
+
let stream = new LimitedPassthrough({ maxBytes: 0 });
|
|
133
|
+
test.equal(stream.maxBytes, Infinity);
|
|
134
|
+
|
|
135
|
+
let output = collectStream(stream);
|
|
136
|
+
stream.write(Buffer.from('hello'));
|
|
137
|
+
stream.end();
|
|
138
|
+
|
|
139
|
+
let result = await output;
|
|
140
|
+
// All data passes through since 0 is treated as Infinity
|
|
141
|
+
test.equal(result.toString(), 'hello');
|
|
142
|
+
test.done();
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
module.exports['LimitedPassthrough: handles limit of 1 byte'] = async test => {
|
|
146
|
+
let stream = new LimitedPassthrough({ maxBytes: 1 });
|
|
147
|
+
let output = collectStream(stream);
|
|
148
|
+
|
|
149
|
+
stream.write(Buffer.from('hello'));
|
|
150
|
+
stream.end();
|
|
151
|
+
|
|
152
|
+
let result = await output;
|
|
153
|
+
test.equal(result.toString(), 'h');
|
|
154
|
+
test.equal(stream.processed, 1);
|
|
155
|
+
test.equal(stream.limited, true);
|
|
156
|
+
test.done();
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
// ============================================
|
|
160
|
+
// Edge cases
|
|
161
|
+
// ============================================
|
|
162
|
+
|
|
163
|
+
module.exports['LimitedPassthrough: handles empty writes'] = async test => {
|
|
164
|
+
let stream = new LimitedPassthrough({ maxBytes: 10 });
|
|
165
|
+
let output = collectStream(stream);
|
|
166
|
+
|
|
167
|
+
stream.write(Buffer.from(''));
|
|
168
|
+
stream.write(Buffer.from('hello'));
|
|
169
|
+
stream.write(Buffer.from(''));
|
|
170
|
+
stream.end();
|
|
171
|
+
|
|
172
|
+
let result = await output;
|
|
173
|
+
test.equal(result.toString(), 'hello');
|
|
174
|
+
test.equal(stream.processed, 5);
|
|
175
|
+
test.done();
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
module.exports['LimitedPassthrough: handles no writes'] = async test => {
|
|
179
|
+
let stream = new LimitedPassthrough({ maxBytes: 10 });
|
|
180
|
+
let output = collectStream(stream);
|
|
181
|
+
|
|
182
|
+
stream.end();
|
|
183
|
+
|
|
184
|
+
let result = await output;
|
|
185
|
+
test.equal(result.length, 0);
|
|
186
|
+
test.equal(stream.processed, 0);
|
|
187
|
+
test.equal(stream.limited, false);
|
|
188
|
+
test.done();
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
module.exports['LimitedPassthrough: tracks processed bytes correctly'] = async test => {
|
|
192
|
+
let stream = new LimitedPassthrough({ maxBytes: 100 });
|
|
193
|
+
let output = collectStream(stream);
|
|
194
|
+
|
|
195
|
+
stream.write(Buffer.from('12345')); // 5 bytes
|
|
196
|
+
test.equal(stream.processed, 5);
|
|
197
|
+
|
|
198
|
+
stream.write(Buffer.from('67890')); // 5 more bytes
|
|
199
|
+
test.equal(stream.processed, 10);
|
|
200
|
+
|
|
201
|
+
stream.end();
|
|
202
|
+
|
|
203
|
+
await output;
|
|
204
|
+
test.equal(stream.processed, 10);
|
|
205
|
+
test.done();
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
// ============================================
|
|
209
|
+
// Integration tests
|
|
210
|
+
// ============================================
|
|
211
|
+
|
|
212
|
+
module.exports['LimitedPassthrough: works with pipe'] = async test => {
|
|
213
|
+
let source = new PassThrough();
|
|
214
|
+
let limiter = new LimitedPassthrough({ maxBytes: 10 });
|
|
215
|
+
let output = collectStream(source.pipe(limiter));
|
|
216
|
+
|
|
217
|
+
source.write('hello ');
|
|
218
|
+
source.write('wonderful ');
|
|
219
|
+
source.write('world');
|
|
220
|
+
source.end();
|
|
221
|
+
|
|
222
|
+
let result = await output;
|
|
223
|
+
test.equal(result.toString(), 'hello wond');
|
|
224
|
+
test.equal(limiter.limited, true);
|
|
225
|
+
test.done();
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
module.exports['LimitedPassthrough: handles large data'] = async test => {
|
|
229
|
+
let stream = new LimitedPassthrough({ maxBytes: 1000 });
|
|
230
|
+
let output = collectStream(stream);
|
|
231
|
+
|
|
232
|
+
// Write 100 bytes at a time
|
|
233
|
+
for (let i = 0; i < 20; i++) {
|
|
234
|
+
stream.write(Buffer.alloc(100, 'x'));
|
|
235
|
+
}
|
|
236
|
+
stream.end();
|
|
237
|
+
|
|
238
|
+
let result = await output;
|
|
239
|
+
test.equal(result.length, 1000);
|
|
240
|
+
test.equal(stream.limited, true);
|
|
241
|
+
test.done();
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
module.exports['LimitedPassthrough: single byte writes'] = async test => {
|
|
245
|
+
let stream = new LimitedPassthrough({ maxBytes: 3 });
|
|
246
|
+
let output = collectStream(stream);
|
|
247
|
+
|
|
248
|
+
stream.write(Buffer.from('a'));
|
|
249
|
+
stream.write(Buffer.from('b'));
|
|
250
|
+
stream.write(Buffer.from('c'));
|
|
251
|
+
stream.write(Buffer.from('d')); // should be dropped
|
|
252
|
+
stream.write(Buffer.from('e')); // should be dropped
|
|
253
|
+
stream.end();
|
|
254
|
+
|
|
255
|
+
let result = await output;
|
|
256
|
+
test.equal(result.toString(), 'abc');
|
|
257
|
+
test.done();
|
|
258
|
+
};
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Tests for memory cleanup on connection close
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { ImapFlow } = require('../lib/imap-flow');
|
|
8
|
+
|
|
9
|
+
exports['Memory Cleanup Tests'] = {
|
|
10
|
+
'should clean up streamer on close without connection'(test) {
|
|
11
|
+
const client = new ImapFlow({
|
|
12
|
+
host: '127.0.0.1',
|
|
13
|
+
port: 1,
|
|
14
|
+
secure: false,
|
|
15
|
+
logger: false
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
// Check initial state
|
|
19
|
+
test.ok(client.streamer, 'streamer should exist');
|
|
20
|
+
test.ok(!client.streamer.destroyed, 'streamer should not be destroyed initially');
|
|
21
|
+
|
|
22
|
+
// Close without connecting
|
|
23
|
+
client.close();
|
|
24
|
+
|
|
25
|
+
// Verify cleanup
|
|
26
|
+
test.ok(client.streamer.destroyed, 'streamer should be destroyed after close');
|
|
27
|
+
test.equal(client.streamer.listenerCount('error'), 0, 'error listeners should be removed');
|
|
28
|
+
test.equal(client.folders.size, 0, 'folders should be cleared');
|
|
29
|
+
test.equal(client.requestTagMap.size, 0, 'requestTagMap should be cleared');
|
|
30
|
+
test.ok(client.isClosed, 'client should be marked as closed');
|
|
31
|
+
|
|
32
|
+
test.done();
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
'should remove event listeners on close'(test) {
|
|
36
|
+
const client = new ImapFlow({
|
|
37
|
+
host: '127.0.0.1',
|
|
38
|
+
port: 1,
|
|
39
|
+
secure: false,
|
|
40
|
+
logger: false
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// Add a readable listener as if connect was called
|
|
44
|
+
client.socketReadable = () => {};
|
|
45
|
+
client.streamer.on('readable', client.socketReadable);
|
|
46
|
+
|
|
47
|
+
// Verify listener was added
|
|
48
|
+
test.equal(client.streamer.listenerCount('readable'), 1, 'readable listener should be present');
|
|
49
|
+
|
|
50
|
+
client.close();
|
|
51
|
+
|
|
52
|
+
// Check listeners after close
|
|
53
|
+
test.equal(client.streamer.listenerCount('readable'), 0, 'readable listener should be removed');
|
|
54
|
+
test.equal(client.streamer.listenerCount('error'), 0, 'error listeners should be removed');
|
|
55
|
+
|
|
56
|
+
test.done();
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
'should clear internal structures on close'(test) {
|
|
60
|
+
const client = new ImapFlow({
|
|
61
|
+
host: '127.0.0.1',
|
|
62
|
+
port: 1,
|
|
63
|
+
secure: false,
|
|
64
|
+
logger: false
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// Add some data to internal structures
|
|
68
|
+
client.folders.set('INBOX', { path: 'INBOX' });
|
|
69
|
+
client.folders.set('Sent', { path: 'Sent' });
|
|
70
|
+
client.requestTagMap.set('A001', { tag: 'A001' });
|
|
71
|
+
client.requestTagMap.set('A002', { tag: 'A002' });
|
|
72
|
+
|
|
73
|
+
test.equal(client.folders.size, 2, 'folders should have entries');
|
|
74
|
+
test.equal(client.requestTagMap.size, 2, 'requestTagMap should have entries');
|
|
75
|
+
|
|
76
|
+
client.close();
|
|
77
|
+
|
|
78
|
+
test.equal(client.folders.size, 0, 'folders should be cleared after close');
|
|
79
|
+
test.equal(client.requestTagMap.size, 0, 'requestTagMap should be cleared after close');
|
|
80
|
+
|
|
81
|
+
test.done();
|
|
82
|
+
},
|
|
83
|
+
|
|
84
|
+
'should handle multiple close calls gracefully'(test) {
|
|
85
|
+
const client = new ImapFlow({
|
|
86
|
+
host: '127.0.0.1',
|
|
87
|
+
port: 1,
|
|
88
|
+
secure: false,
|
|
89
|
+
logger: false
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// Call close multiple times
|
|
93
|
+
test.doesNotThrow(() => {
|
|
94
|
+
client.close();
|
|
95
|
+
client.close();
|
|
96
|
+
client.close();
|
|
97
|
+
}, 'multiple close calls should not throw');
|
|
98
|
+
|
|
99
|
+
test.ok(client.isClosed, 'client should be marked as closed');
|
|
100
|
+
|
|
101
|
+
test.done();
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
'should properly set state on close'(test) {
|
|
105
|
+
const client = new ImapFlow({
|
|
106
|
+
host: '127.0.0.1',
|
|
107
|
+
port: 1,
|
|
108
|
+
secure: false,
|
|
109
|
+
logger: false
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test.equal(client.state, client.states.NOT_AUTHENTICATED, 'initial state should be NOT_AUTHENTICATED');
|
|
113
|
+
test.equal(client.usable, false, 'usable should be false initially');
|
|
114
|
+
test.equal(client.isClosed, false, 'isClosed should be false initially');
|
|
115
|
+
|
|
116
|
+
client.close();
|
|
117
|
+
|
|
118
|
+
test.equal(client.state, client.states.LOGOUT, 'state should be LOGOUT after close');
|
|
119
|
+
test.equal(client.usable, false, 'usable should be false after close');
|
|
120
|
+
test.equal(client.isClosed, true, 'isClosed should be true after close');
|
|
121
|
+
|
|
122
|
+
test.done();
|
|
123
|
+
},
|
|
124
|
+
|
|
125
|
+
'should emit close event'(test) {
|
|
126
|
+
const client = new ImapFlow({
|
|
127
|
+
host: '127.0.0.1',
|
|
128
|
+
port: 1,
|
|
129
|
+
secure: false,
|
|
130
|
+
logger: false
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
let closeEmitted = false;
|
|
134
|
+
client.on('close', () => {
|
|
135
|
+
closeEmitted = true;
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
client.close();
|
|
139
|
+
|
|
140
|
+
test.ok(closeEmitted, 'close event should be emitted');
|
|
141
|
+
|
|
142
|
+
test.done();
|
|
143
|
+
}
|
|
144
|
+
};
|