imapflow 1.2.13 → 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 +8 -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 +80 -117
- package/lib/search-compiler.js +4 -10
- package/lib/tools.js +29 -36
- package/package.json +2 -2
- 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
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { resolveCharset } = require('../lib/charsets');
|
|
4
|
+
|
|
5
|
+
// ============================================
|
|
6
|
+
// Canonical name resolution
|
|
7
|
+
// ============================================
|
|
8
|
+
|
|
9
|
+
module.exports['Charsets: resolves canonical UTF-8'] = test => {
|
|
10
|
+
test.equal(resolveCharset('UTF-8'), 'UTF-8');
|
|
11
|
+
test.done();
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
module.exports['Charsets: resolves canonical ISO-8859-1'] = test => {
|
|
15
|
+
test.equal(resolveCharset('ISO-8859-1'), 'ISO-8859-1');
|
|
16
|
+
test.done();
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
module.exports['Charsets: resolves canonical windows-1252'] = test => {
|
|
20
|
+
test.equal(resolveCharset('windows-1252'), 'windows-1252');
|
|
21
|
+
test.done();
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
module.exports['Charsets: resolves canonical US-ASCII'] = test => {
|
|
25
|
+
test.equal(resolveCharset('US-ASCII'), 'US-ASCII');
|
|
26
|
+
test.done();
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// ============================================
|
|
30
|
+
// Alias resolution
|
|
31
|
+
// ============================================
|
|
32
|
+
|
|
33
|
+
module.exports['Charsets: resolves utf8 alias to UTF-8'] = test => {
|
|
34
|
+
test.equal(resolveCharset('utf8'), 'UTF-8');
|
|
35
|
+
test.done();
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
module.exports['Charsets: resolves win1252 alias to windows-1252'] = test => {
|
|
39
|
+
test.equal(resolveCharset('win1252'), 'windows-1252');
|
|
40
|
+
test.done();
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
module.exports['Charsets: resolves latin1 alias to ISO-8859-1'] = test => {
|
|
44
|
+
test.equal(resolveCharset('latin1'), 'ISO-8859-1');
|
|
45
|
+
test.done();
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
module.exports['Charsets: resolves ascii alias to US-ASCII'] = test => {
|
|
49
|
+
test.equal(resolveCharset('ascii'), 'US-ASCII');
|
|
50
|
+
test.done();
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
module.exports['Charsets: resolves usascii alias to US-ASCII'] = test => {
|
|
54
|
+
test.equal(resolveCharset('usascii'), 'US-ASCII');
|
|
55
|
+
test.done();
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// ============================================
|
|
59
|
+
// Case insensitivity
|
|
60
|
+
// ============================================
|
|
61
|
+
|
|
62
|
+
module.exports['Charsets: resolves lowercase utf-8 to UTF-8'] = test => {
|
|
63
|
+
test.equal(resolveCharset('utf-8'), 'UTF-8');
|
|
64
|
+
test.done();
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
module.exports['Charsets: resolves mixed-case Utf-8 to UTF-8'] = test => {
|
|
68
|
+
test.equal(resolveCharset('Utf-8'), 'UTF-8');
|
|
69
|
+
test.done();
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
module.exports['Charsets: resolves uppercase UTF8 (no separator) to UTF-8'] = test => {
|
|
73
|
+
test.equal(resolveCharset('UTF8'), 'UTF-8');
|
|
74
|
+
test.done();
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
// ============================================
|
|
78
|
+
// Separator stripping
|
|
79
|
+
// ============================================
|
|
80
|
+
|
|
81
|
+
module.exports['Charsets: resolves ISO_8859_1 (underscores) to ISO-8859-1'] = test => {
|
|
82
|
+
test.equal(resolveCharset('ISO_8859_1'), 'ISO-8859-1');
|
|
83
|
+
test.done();
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
module.exports['Charsets: resolves windows_1252 (underscores) to windows-1252'] = test => {
|
|
87
|
+
test.equal(resolveCharset('windows_1252'), 'windows-1252');
|
|
88
|
+
test.done();
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
// ============================================
|
|
92
|
+
// Japanese and CJK charsets
|
|
93
|
+
// ============================================
|
|
94
|
+
|
|
95
|
+
module.exports['Charsets: resolves ISO-2022-JP'] = test => {
|
|
96
|
+
test.equal(resolveCharset('ISO-2022-JP'), 'ISO-2022-JP');
|
|
97
|
+
test.done();
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
module.exports['Charsets: resolves Shift_JIS'] = test => {
|
|
101
|
+
test.equal(resolveCharset('Shift_JIS'), 'Shift_JIS');
|
|
102
|
+
test.done();
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
module.exports['Charsets: resolves EUC-JP'] = test => {
|
|
106
|
+
test.equal(resolveCharset('EUC-JP'), 'EUC-JP');
|
|
107
|
+
test.done();
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
module.exports['Charsets: resolves EUC-KR'] = test => {
|
|
111
|
+
test.equal(resolveCharset('EUC-KR'), 'EUC-KR');
|
|
112
|
+
test.done();
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
module.exports['Charsets: resolves GB2312'] = test => {
|
|
116
|
+
test.equal(resolveCharset('GB2312'), 'GB2312');
|
|
117
|
+
test.done();
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
module.exports['Charsets: resolves Big5'] = test => {
|
|
121
|
+
test.equal(resolveCharset('Big5'), 'Big5');
|
|
122
|
+
test.done();
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
module.exports['Charsets: resolves GBK'] = test => {
|
|
126
|
+
test.equal(resolveCharset('GBK'), 'GBK');
|
|
127
|
+
test.done();
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
// ============================================
|
|
131
|
+
// Unknown charsets return null
|
|
132
|
+
// ============================================
|
|
133
|
+
|
|
134
|
+
module.exports['Charsets: returns null for unknown charset x-unknown'] = test => {
|
|
135
|
+
test.strictEqual(resolveCharset('x-unknown'), null);
|
|
136
|
+
test.done();
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
module.exports['Charsets: returns null for unknown charset bogus'] = test => {
|
|
140
|
+
test.strictEqual(resolveCharset('bogus'), null);
|
|
141
|
+
test.done();
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
module.exports['Charsets: returns null for empty string'] = test => {
|
|
145
|
+
test.strictEqual(resolveCharset(''), null);
|
|
146
|
+
test.done();
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
// ============================================
|
|
150
|
+
// Invalid input throws TypeError
|
|
151
|
+
// ============================================
|
|
152
|
+
|
|
153
|
+
module.exports['Charsets: throws TypeError for null input'] = test => {
|
|
154
|
+
test.throws(() => resolveCharset(null));
|
|
155
|
+
test.done();
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
module.exports['Charsets: throws TypeError for undefined input'] = test => {
|
|
159
|
+
test.throws(() => resolveCharset(undefined));
|
|
160
|
+
test.done();
|
|
161
|
+
};
|
|
@@ -8247,3 +8247,64 @@ module.exports['Commands: enable updates connection.enabled'] = async test => {
|
|
|
8247
8247
|
test.ok(!connection.enabled.has('EXISTING')); // Old value should be gone
|
|
8248
8248
|
test.done();
|
|
8249
8249
|
};
|
|
8250
|
+
|
|
8251
|
+
// ============================================
|
|
8252
|
+
// Download / DownloadMany Tests
|
|
8253
|
+
// ============================================
|
|
8254
|
+
|
|
8255
|
+
const { ImapFlow } = require('../lib/imap-flow');
|
|
8256
|
+
|
|
8257
|
+
module.exports['Commands: download returns empty when no mailbox selected'] = async test => {
|
|
8258
|
+
let client = new ImapFlow({
|
|
8259
|
+
host: 'imap.example.com',
|
|
8260
|
+
port: 993,
|
|
8261
|
+
auth: { user: 'test', pass: 'test' }
|
|
8262
|
+
});
|
|
8263
|
+
// mailbox is not set by default
|
|
8264
|
+
let result = await client.download('1', '1.2');
|
|
8265
|
+
test.deepEqual(result, {});
|
|
8266
|
+
test.done();
|
|
8267
|
+
};
|
|
8268
|
+
|
|
8269
|
+
module.exports['Commands: downloadMany returns empty when no mailbox selected'] = async test => {
|
|
8270
|
+
let client = new ImapFlow({
|
|
8271
|
+
host: 'imap.example.com',
|
|
8272
|
+
port: 993,
|
|
8273
|
+
auth: { user: 'test', pass: 'test' }
|
|
8274
|
+
});
|
|
8275
|
+
let result = await client.downloadMany('1', ['2', '3']);
|
|
8276
|
+
test.deepEqual(result, {});
|
|
8277
|
+
test.done();
|
|
8278
|
+
};
|
|
8279
|
+
|
|
8280
|
+
module.exports['Commands: download with fetchOne returning null'] = async test => {
|
|
8281
|
+
let client = new ImapFlow({
|
|
8282
|
+
host: 'imap.example.com',
|
|
8283
|
+
port: 993,
|
|
8284
|
+
auth: { user: 'test', pass: 'test' }
|
|
8285
|
+
});
|
|
8286
|
+
// Set mailbox so download doesn't return {} early
|
|
8287
|
+
client.mailbox = { path: 'INBOX' };
|
|
8288
|
+
// Mock fetchOne to return null (message not found)
|
|
8289
|
+
client.fetchOne = async () => null;
|
|
8290
|
+
|
|
8291
|
+
// part '1' triggers the bodyStructure check path
|
|
8292
|
+
let result = await client.download('1', '1');
|
|
8293
|
+
test.equal(result.response, false);
|
|
8294
|
+
test.equal(result.chunk, false);
|
|
8295
|
+
test.done();
|
|
8296
|
+
};
|
|
8297
|
+
|
|
8298
|
+
module.exports['Commands: downloadMany with fetchOne returning null'] = async test => {
|
|
8299
|
+
let client = new ImapFlow({
|
|
8300
|
+
host: 'imap.example.com',
|
|
8301
|
+
port: 993,
|
|
8302
|
+
auth: { user: 'test', pass: 'test' }
|
|
8303
|
+
});
|
|
8304
|
+
client.mailbox = { path: 'INBOX' };
|
|
8305
|
+
client.fetchOne = async () => null;
|
|
8306
|
+
|
|
8307
|
+
let result = await client.downloadMany('1', ['2', '3']);
|
|
8308
|
+
test.equal(result.response, false);
|
|
8309
|
+
test.done();
|
|
8310
|
+
};
|
|
@@ -1569,3 +1569,97 @@ module.exports['Connection Edge: compress _deflate error after close does not cr
|
|
|
1569
1569
|
|
|
1570
1570
|
test.done();
|
|
1571
1571
|
};
|
|
1572
|
+
|
|
1573
|
+
// ============================================
|
|
1574
|
+
// Mailbox Lock Tests
|
|
1575
|
+
// ============================================
|
|
1576
|
+
|
|
1577
|
+
module.exports['Connection Edge: getMailboxLock queues lock in locks array'] = async test => {
|
|
1578
|
+
let client = new ImapFlow({
|
|
1579
|
+
host: 'imap.example.com',
|
|
1580
|
+
port: 993,
|
|
1581
|
+
auth: { user: 'test', pass: 'test' }
|
|
1582
|
+
});
|
|
1583
|
+
|
|
1584
|
+
// getMailboxLock should push to locks array then reject (no connection)
|
|
1585
|
+
try {
|
|
1586
|
+
await client.getMailboxLock('INBOX');
|
|
1587
|
+
test.ok(false, 'Should have thrown');
|
|
1588
|
+
} catch (err) {
|
|
1589
|
+
test.ok(err);
|
|
1590
|
+
test.equal(err.code, 'NoConnection');
|
|
1591
|
+
}
|
|
1592
|
+
|
|
1593
|
+
test.done();
|
|
1594
|
+
};
|
|
1595
|
+
|
|
1596
|
+
module.exports['Connection Edge: lockCounter increments with each getMailboxLock call'] = async test => {
|
|
1597
|
+
let client = new ImapFlow({
|
|
1598
|
+
host: 'imap.example.com',
|
|
1599
|
+
port: 993,
|
|
1600
|
+
auth: { user: 'test', pass: 'test' }
|
|
1601
|
+
});
|
|
1602
|
+
|
|
1603
|
+
let initial = client.lockCounter;
|
|
1604
|
+
let p1 = client.getMailboxLock('INBOX').catch(() => {});
|
|
1605
|
+
let p2 = client.getMailboxLock('Sent').catch(() => {});
|
|
1606
|
+
|
|
1607
|
+
test.equal(client.lockCounter, initial + 2);
|
|
1608
|
+
|
|
1609
|
+
await Promise.all([p1, p2]);
|
|
1610
|
+
test.done();
|
|
1611
|
+
};
|
|
1612
|
+
|
|
1613
|
+
module.exports['Connection Edge: lock rejected when connection not usable'] = async test => {
|
|
1614
|
+
let client = new ImapFlow({
|
|
1615
|
+
host: 'imap.example.com',
|
|
1616
|
+
port: 993,
|
|
1617
|
+
auth: { user: 'test', pass: 'test' }
|
|
1618
|
+
});
|
|
1619
|
+
|
|
1620
|
+
// usable is false by default, no socket
|
|
1621
|
+
try {
|
|
1622
|
+
await client.getMailboxLock('INBOX');
|
|
1623
|
+
test.ok(false, 'Should have thrown');
|
|
1624
|
+
} catch (err) {
|
|
1625
|
+
test.ok(err);
|
|
1626
|
+
test.equal(err.code, 'NoConnection');
|
|
1627
|
+
test.ok(err.message.includes('not available'));
|
|
1628
|
+
}
|
|
1629
|
+
test.done();
|
|
1630
|
+
};
|
|
1631
|
+
|
|
1632
|
+
module.exports['Connection Edge: processLocks with empty locks array completes'] = async test => {
|
|
1633
|
+
let client = new ImapFlow({
|
|
1634
|
+
host: 'imap.example.com',
|
|
1635
|
+
port: 993,
|
|
1636
|
+
auth: { user: 'test', pass: 'test' }
|
|
1637
|
+
});
|
|
1638
|
+
|
|
1639
|
+
// processLocks with no pending locks should complete without error
|
|
1640
|
+
await client.processLocks();
|
|
1641
|
+
test.equal(client.processingLock, false);
|
|
1642
|
+
test.done();
|
|
1643
|
+
};
|
|
1644
|
+
|
|
1645
|
+
module.exports['Connection Edge: multiple locks rejected when no connection'] = async test => {
|
|
1646
|
+
let client = new ImapFlow({
|
|
1647
|
+
host: 'imap.example.com',
|
|
1648
|
+
port: 993,
|
|
1649
|
+
auth: { user: 'test', pass: 'test' }
|
|
1650
|
+
});
|
|
1651
|
+
|
|
1652
|
+
// Queue multiple locks, all should reject
|
|
1653
|
+
let errors = [];
|
|
1654
|
+
let p1 = client.getMailboxLock('INBOX').catch(err => errors.push(err));
|
|
1655
|
+
let p2 = client.getMailboxLock('Sent').catch(err => errors.push(err));
|
|
1656
|
+
let p3 = client.getMailboxLock('Drafts').catch(err => errors.push(err));
|
|
1657
|
+
|
|
1658
|
+
await Promise.all([p1, p2, p3]);
|
|
1659
|
+
|
|
1660
|
+
test.equal(errors.length, 3);
|
|
1661
|
+
for (let err of errors) {
|
|
1662
|
+
test.equal(err.code, 'NoConnection');
|
|
1663
|
+
}
|
|
1664
|
+
test.done();
|
|
1665
|
+
};
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { parseCopyUid } = require('../lib/commands/copyuid-parser');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Builds a minimal IMAP response object with a COPYUID-style section.
|
|
7
|
+
*
|
|
8
|
+
* @param {Array} section - Array of section elements with value properties
|
|
9
|
+
* @returns {Object} Response object suitable for parseCopyUid
|
|
10
|
+
*/
|
|
11
|
+
function makeResponse(section) {
|
|
12
|
+
return { attributes: [{ section }] };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// ============================================
|
|
16
|
+
// Single UID pair
|
|
17
|
+
// ============================================
|
|
18
|
+
|
|
19
|
+
module.exports['CopyUID Parser: single UID pair sets uidValidity and uidMap entry'] = test => {
|
|
20
|
+
let map = {};
|
|
21
|
+
parseCopyUid(makeResponse([{ value: 'COPYUID' }, { value: '12345' }, { value: '1' }, { value: '100' }]), map);
|
|
22
|
+
|
|
23
|
+
test.equal(map.uidValidity, BigInt('12345'));
|
|
24
|
+
test.ok(map.uidMap instanceof Map);
|
|
25
|
+
test.equal(map.uidMap.get(1), 100);
|
|
26
|
+
test.equal(map.uidMap.size, 1);
|
|
27
|
+
test.done();
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// ============================================
|
|
31
|
+
// UID ranges
|
|
32
|
+
// ============================================
|
|
33
|
+
|
|
34
|
+
module.exports['CopyUID Parser: contiguous UID range maps all source UIDs to destinations'] = test => {
|
|
35
|
+
let map = {};
|
|
36
|
+
parseCopyUid(makeResponse([{ value: 'COPYUID' }, { value: '99' }, { value: '1:3' }, { value: '10:12' }]), map);
|
|
37
|
+
|
|
38
|
+
test.equal(map.uidValidity, BigInt('99'));
|
|
39
|
+
test.ok(map.uidMap instanceof Map);
|
|
40
|
+
test.equal(map.uidMap.size, 3);
|
|
41
|
+
test.equal(map.uidMap.get(1), 10);
|
|
42
|
+
test.equal(map.uidMap.get(2), 11);
|
|
43
|
+
test.equal(map.uidMap.get(3), 12);
|
|
44
|
+
test.done();
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// ============================================
|
|
48
|
+
// Comma-separated UIDs
|
|
49
|
+
// ============================================
|
|
50
|
+
|
|
51
|
+
module.exports['CopyUID Parser: comma-separated UIDs map each source to its destination'] = test => {
|
|
52
|
+
let map = {};
|
|
53
|
+
parseCopyUid(makeResponse([{ value: 'COPYUID' }, { value: '200' }, { value: '1,5,9' }, { value: '100,105,109' }]), map);
|
|
54
|
+
|
|
55
|
+
test.equal(map.uidMap.size, 3);
|
|
56
|
+
test.equal(map.uidMap.get(1), 100);
|
|
57
|
+
test.equal(map.uidMap.get(5), 105);
|
|
58
|
+
test.equal(map.uidMap.get(9), 109);
|
|
59
|
+
test.done();
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// ============================================
|
|
63
|
+
// Non-COPYUID response code
|
|
64
|
+
// ============================================
|
|
65
|
+
|
|
66
|
+
module.exports['CopyUID Parser: non-COPYUID response code leaves map unchanged'] = test => {
|
|
67
|
+
let map = {};
|
|
68
|
+
parseCopyUid(makeResponse([{ value: 'APPENDUID' }, { value: '12345' }, { value: '1' }, { value: '100' }]), map);
|
|
69
|
+
|
|
70
|
+
test.equal(map.uidValidity, undefined);
|
|
71
|
+
test.equal(map.uidMap, undefined);
|
|
72
|
+
test.done();
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
// ============================================
|
|
76
|
+
// Missing / malformed input
|
|
77
|
+
// ============================================
|
|
78
|
+
|
|
79
|
+
module.exports['CopyUID Parser: missing attributes leaves map unchanged'] = test => {
|
|
80
|
+
let map = {};
|
|
81
|
+
parseCopyUid({}, map);
|
|
82
|
+
|
|
83
|
+
test.equal(map.uidValidity, undefined);
|
|
84
|
+
test.equal(map.uidMap, undefined);
|
|
85
|
+
test.done();
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
module.exports['CopyUID Parser: empty attributes array leaves map unchanged'] = test => {
|
|
89
|
+
let map = {};
|
|
90
|
+
parseCopyUid({ attributes: [] }, map);
|
|
91
|
+
|
|
92
|
+
test.equal(map.uidValidity, undefined);
|
|
93
|
+
test.equal(map.uidMap, undefined);
|
|
94
|
+
test.done();
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
module.exports['CopyUID Parser: null section leaves map unchanged'] = test => {
|
|
98
|
+
let map = {};
|
|
99
|
+
parseCopyUid({ attributes: [{ section: null }] }, map);
|
|
100
|
+
|
|
101
|
+
test.equal(map.uidValidity, undefined);
|
|
102
|
+
test.equal(map.uidMap, undefined);
|
|
103
|
+
test.done();
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
// ============================================
|
|
107
|
+
// Non-numeric uidValidity
|
|
108
|
+
// ============================================
|
|
109
|
+
|
|
110
|
+
module.exports['CopyUID Parser: non-numeric uidValidity skips uidValidity but still builds uidMap'] = test => {
|
|
111
|
+
let map = {};
|
|
112
|
+
parseCopyUid(makeResponse([{ value: 'COPYUID' }, { value: 'abc' }, { value: '1' }, { value: '100' }]), map);
|
|
113
|
+
|
|
114
|
+
test.equal(map.uidValidity, undefined);
|
|
115
|
+
test.ok(map.uidMap instanceof Map);
|
|
116
|
+
test.equal(map.uidMap.get(1), 100);
|
|
117
|
+
test.done();
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
// ============================================
|
|
121
|
+
// Mismatched source/destination lengths
|
|
122
|
+
// ============================================
|
|
123
|
+
|
|
124
|
+
module.exports['CopyUID Parser: mismatched source and destination lengths sets uidValidity but skips uidMap'] = test => {
|
|
125
|
+
let map = {};
|
|
126
|
+
parseCopyUid(makeResponse([{ value: 'COPYUID' }, { value: '12345' }, { value: '1:3' }, { value: '10' }]), map);
|
|
127
|
+
|
|
128
|
+
test.equal(map.uidValidity, BigInt('12345'));
|
|
129
|
+
test.equal(map.uidMap, undefined);
|
|
130
|
+
test.done();
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
// ============================================
|
|
134
|
+
// Large uidValidity
|
|
135
|
+
// ============================================
|
|
136
|
+
|
|
137
|
+
module.exports['CopyUID Parser: large uidValidity is stored as BigInt'] = test => {
|
|
138
|
+
let map = {};
|
|
139
|
+
parseCopyUid(makeResponse([{ value: 'COPYUID' }, { value: '999999999999' }, { value: '1' }, { value: '100' }]), map);
|
|
140
|
+
|
|
141
|
+
test.equal(map.uidValidity, BigInt('999999999999'));
|
|
142
|
+
test.done();
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
module.exports['CopyUID Parser: uidValidity zero is stored as BigInt(0)'] = test => {
|
|
146
|
+
let map = {};
|
|
147
|
+
parseCopyUid(makeResponse([{ value: 'COPYUID' }, { value: '0' }, { value: '1' }, { value: '100' }]), map);
|
|
148
|
+
|
|
149
|
+
test.strictEqual(map.uidValidity, BigInt(0));
|
|
150
|
+
test.ok(map.uidMap instanceof Map);
|
|
151
|
+
test.equal(map.uidMap.get(1), 100);
|
|
152
|
+
test.done();
|
|
153
|
+
};
|
|
@@ -441,3 +441,69 @@ module.exports['IMAP Compiler: LITERAL byte length'] = test =>
|
|
|
441
441
|
'* CMD "(* 10B literal *)" "Vana kere"'
|
|
442
442
|
)
|
|
443
443
|
);
|
|
444
|
+
|
|
445
|
+
module.exports['IMAP Compiler: MongoDB binary object is unwrapped'] = test =>
|
|
446
|
+
asyncWrapper(test, async test => {
|
|
447
|
+
// Create a non-Buffer object with a .buffer property (simulates MongoDB Binary)
|
|
448
|
+
const mongoBinary = { buffer: Buffer.from('test data') };
|
|
449
|
+
const compiled = (
|
|
450
|
+
await compiler({
|
|
451
|
+
tag: '*',
|
|
452
|
+
command: 'CMD',
|
|
453
|
+
attributes: [mongoBinary]
|
|
454
|
+
})
|
|
455
|
+
).toString();
|
|
456
|
+
// The buffer content should appear in the output (unwrapped from the MongoDB binary wrapper)
|
|
457
|
+
test.ok(compiled.includes('test data'), 'should contain the buffer content after unwrapping');
|
|
458
|
+
test.ok(compiled.includes('CMD'), 'should contain command');
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
module.exports['IMAP Compiler: long string truncated in logging mode'] = test =>
|
|
462
|
+
asyncWrapper(test, async test => {
|
|
463
|
+
const longString = 'x'.repeat(150);
|
|
464
|
+
const compiled = (
|
|
465
|
+
await compiler(
|
|
466
|
+
{
|
|
467
|
+
tag: '*',
|
|
468
|
+
command: 'CMD',
|
|
469
|
+
attributes: [longString]
|
|
470
|
+
},
|
|
471
|
+
{ asArray: false, isLogging: true }
|
|
472
|
+
)
|
|
473
|
+
).toString();
|
|
474
|
+
test.ok(compiled.includes('150B string'), 'should show byte count instead of content');
|
|
475
|
+
test.ok(!compiled.includes('x'.repeat(150)), 'should not contain the full string');
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
module.exports['IMAP Compiler: literal truncated in logging mode'] = test =>
|
|
479
|
+
asyncWrapper(test, async test => {
|
|
480
|
+
const compiled = (
|
|
481
|
+
await compiler(
|
|
482
|
+
{
|
|
483
|
+
tag: '*',
|
|
484
|
+
command: 'CMD',
|
|
485
|
+
attributes: [{ type: 'LITERAL', value: Buffer.alloc(200) }]
|
|
486
|
+
},
|
|
487
|
+
{ asArray: false, isLogging: true }
|
|
488
|
+
)
|
|
489
|
+
).toString();
|
|
490
|
+
test.ok(compiled.includes('200B literal'), 'should show byte count for literal');
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
module.exports['IMAP Compiler: partial range in SECTION'] = test =>
|
|
494
|
+
asyncWrapper(test, async test => {
|
|
495
|
+
const compiled = (
|
|
496
|
+
await compiler({
|
|
497
|
+
tag: '*',
|
|
498
|
+
command: 'CMD',
|
|
499
|
+
attributes: [
|
|
500
|
+
{
|
|
501
|
+
type: 'SECTION',
|
|
502
|
+
section: [{ type: 'ATOM', value: 'BODY' }],
|
|
503
|
+
partial: [0, 50]
|
|
504
|
+
}
|
|
505
|
+
]
|
|
506
|
+
})
|
|
507
|
+
).toString();
|
|
508
|
+
test.ok(compiled.includes('<0.50>'), 'should contain partial range <0.50>');
|
|
509
|
+
});
|