imapflow 1.7.7 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (296) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/README.md +8 -2
  3. package/dist/cjs/charsets.d.ts +1 -0
  4. package/dist/cjs/charsets.js +294 -0
  5. package/dist/cjs/commands/append.d.ts +22 -0
  6. package/dist/cjs/commands/append.js +151 -0
  7. package/dist/cjs/commands/authenticate.d.ts +24 -0
  8. package/dist/cjs/commands/authenticate.js +223 -0
  9. package/dist/cjs/commands/capability.d.ts +8 -0
  10. package/dist/cjs/commands/capability.js +32 -0
  11. package/dist/cjs/commands/close.d.ts +8 -0
  12. package/dist/cjs/commands/close.js +39 -0
  13. package/dist/cjs/commands/compress.d.ts +8 -0
  14. package/dist/cjs/commands/compress.js +56 -0
  15. package/dist/cjs/commands/copy.d.ts +13 -0
  16. package/dist/cjs/commands/copy.js +44 -0
  17. package/dist/cjs/commands/copyuid-parser.d.ts +11 -0
  18. package/dist/cjs/commands/copyuid-parser.js +32 -0
  19. package/dist/cjs/commands/create.d.ts +11 -0
  20. package/dist/cjs/commands/create.js +80 -0
  21. package/dist/cjs/commands/delete.d.ts +11 -0
  22. package/dist/cjs/commands/delete.js +40 -0
  23. package/dist/cjs/commands/enable.d.ts +9 -0
  24. package/dist/cjs/commands/enable.js +61 -0
  25. package/dist/cjs/commands/esearch-parser.d.ts +17 -0
  26. package/dist/cjs/commands/esearch-parser.js +91 -0
  27. package/dist/cjs/commands/expunge.d.ts +12 -0
  28. package/dist/cjs/commands/expunge.js +60 -0
  29. package/dist/cjs/commands/fetch.d.ts +30 -0
  30. package/dist/cjs/commands/fetch.js +241 -0
  31. package/dist/cjs/commands/id.d.ts +10 -0
  32. package/dist/cjs/commands/id.js +80 -0
  33. package/dist/cjs/commands/idle.d.ts +9 -0
  34. package/dist/cjs/commands/idle.js +347 -0
  35. package/dist/cjs/commands/list.d.ts +16 -0
  36. package/dist/cjs/commands/list.js +518 -0
  37. package/dist/cjs/commands/login.d.ts +11 -0
  38. package/dist/cjs/commands/login.js +42 -0
  39. package/dist/cjs/commands/logout.d.ts +8 -0
  40. package/dist/cjs/commands/logout.js +47 -0
  41. package/dist/cjs/commands/move.d.ts +13 -0
  42. package/dist/cjs/commands/move.js +57 -0
  43. package/dist/cjs/commands/namespace.d.ts +25 -0
  44. package/dist/cjs/commands/namespace.js +139 -0
  45. package/dist/cjs/commands/noop.d.ts +8 -0
  46. package/dist/cjs/commands/noop.js +22 -0
  47. package/dist/cjs/commands/quota.d.ts +10 -0
  48. package/dist/cjs/commands/quota.js +119 -0
  49. package/dist/cjs/commands/rename.d.ts +12 -0
  50. package/dist/cjs/commands/rename.js +48 -0
  51. package/dist/cjs/commands/search.d.ts +15 -0
  52. package/dist/cjs/commands/search.js +228 -0
  53. package/dist/cjs/commands/select.d.ts +25 -0
  54. package/dist/cjs/commands/select.js +250 -0
  55. package/dist/cjs/commands/starttls.d.ts +8 -0
  56. package/dist/cjs/commands/starttls.js +30 -0
  57. package/dist/cjs/commands/status-fields.d.ts +14 -0
  58. package/dist/cjs/commands/status-fields.js +61 -0
  59. package/dist/cjs/commands/status.d.ts +12 -0
  60. package/dist/cjs/commands/status.js +108 -0
  61. package/dist/cjs/commands/store.d.ts +19 -0
  62. package/dist/cjs/commands/store.js +93 -0
  63. package/dist/cjs/commands/subscribe.d.ts +9 -0
  64. package/dist/cjs/commands/subscribe.js +31 -0
  65. package/dist/cjs/commands/unsubscribe.d.ts +9 -0
  66. package/dist/cjs/commands/unsubscribe.js +31 -0
  67. package/dist/cjs/connection-deadline.d.ts +49 -0
  68. package/dist/cjs/connection-deadline.js +91 -0
  69. package/dist/cjs/errors.d.ts +83 -0
  70. package/dist/cjs/errors.js +13 -0
  71. package/dist/cjs/handler/imap-compiler.d.ts +24 -0
  72. package/dist/cjs/handler/imap-compiler.js +285 -0
  73. package/dist/cjs/handler/imap-formal-syntax.d.ts +28 -0
  74. package/dist/cjs/handler/imap-formal-syntax.js +121 -0
  75. package/dist/cjs/handler/imap-handler.d.ts +9 -0
  76. package/dist/cjs/handler/imap-handler.js +10 -0
  77. package/dist/cjs/handler/imap-parser.d.ts +16 -0
  78. package/dist/cjs/handler/imap-parser.js +90 -0
  79. package/dist/cjs/handler/imap-stream.d.ts +181 -0
  80. package/dist/cjs/handler/imap-stream.js +446 -0
  81. package/dist/cjs/handler/limits.d.ts +25 -0
  82. package/dist/cjs/handler/limits.js +51 -0
  83. package/dist/cjs/handler/parser-instance.d.ts +68 -0
  84. package/dist/cjs/handler/parser-instance.js +223 -0
  85. package/dist/cjs/handler/token-parser.d.ts +91 -0
  86. package/dist/cjs/handler/token-parser.js +673 -0
  87. package/dist/cjs/handler/types.d.ts +91 -0
  88. package/dist/cjs/handler/types.js +4 -0
  89. package/dist/cjs/imap-commands.d.ts +16 -0
  90. package/dist/cjs/imap-commands.js +74 -0
  91. package/dist/cjs/imap-flow.d.ts +676 -0
  92. package/dist/cjs/imap-flow.js +3949 -0
  93. package/dist/cjs/jp-decoder.d.ts +12 -0
  94. package/dist/cjs/jp-decoder.js +79 -0
  95. package/dist/cjs/limited-passthrough.d.ts +25 -0
  96. package/dist/cjs/limited-passthrough.js +54 -0
  97. package/dist/cjs/logger.d.ts +3 -0
  98. package/dist/cjs/logger.js +11 -0
  99. package/dist/cjs/package-info.d.ts +3 -0
  100. package/dist/cjs/package-info.js +7 -0
  101. package/dist/cjs/package.json +3 -0
  102. package/dist/cjs/proxy-connection.d.ts +33 -0
  103. package/dist/cjs/proxy-connection.js +392 -0
  104. package/dist/cjs/search-compiler.d.ts +34 -0
  105. package/dist/cjs/search-compiler.js +476 -0
  106. package/dist/cjs/special-use.d.ts +22 -0
  107. package/dist/cjs/special-use.js +911 -0
  108. package/dist/cjs/tools.d.ts +427 -0
  109. package/dist/cjs/tools.js +1496 -0
  110. package/{lib/imap-flow.d.ts → dist/cjs/types.d.ts} +386 -516
  111. package/dist/cjs/types.js +5 -0
  112. package/dist/esm/charsets.d.ts +1 -0
  113. package/{lib → dist/esm}/charsets.js +1 -6
  114. package/dist/esm/commands/append.d.ts +22 -0
  115. package/{lib → dist/esm}/commands/append.js +22 -52
  116. package/dist/esm/commands/authenticate.d.ts +24 -0
  117. package/{lib → dist/esm}/commands/authenticate.js +62 -87
  118. package/dist/esm/commands/capability.d.ts +8 -0
  119. package/{lib → dist/esm}/commands/capability.js +6 -9
  120. package/dist/esm/commands/close.d.ts +8 -0
  121. package/{lib → dist/esm}/commands/close.js +6 -10
  122. package/dist/esm/commands/compress.d.ts +8 -0
  123. package/{lib → dist/esm}/commands/compress.js +7 -11
  124. package/dist/esm/commands/copy.d.ts +13 -0
  125. package/{lib → dist/esm}/commands/copy.js +12 -20
  126. package/dist/esm/commands/copyuid-parser.d.ts +11 -0
  127. package/{lib → dist/esm}/commands/copyuid-parser.js +9 -15
  128. package/dist/esm/commands/create.d.ts +11 -0
  129. package/{lib → dist/esm}/commands/create.js +13 -27
  130. package/dist/esm/commands/delete.d.ts +11 -0
  131. package/{lib → dist/esm}/commands/delete.js +9 -14
  132. package/dist/esm/commands/enable.d.ts +9 -0
  133. package/{lib → dist/esm}/commands/enable.js +23 -30
  134. package/dist/esm/commands/esearch-parser.d.ts +17 -0
  135. package/dist/esm/commands/esearch-parser.js +88 -0
  136. package/dist/esm/commands/expunge.d.ts +12 -0
  137. package/{lib → dist/esm}/commands/expunge.js +17 -22
  138. package/dist/esm/commands/fetch.d.ts +30 -0
  139. package/{lib → dist/esm}/commands/fetch.js +32 -64
  140. package/dist/esm/commands/id.d.ts +10 -0
  141. package/{lib → dist/esm}/commands/id.js +17 -23
  142. package/dist/esm/commands/idle.d.ts +9 -0
  143. package/{lib → dist/esm}/commands/idle.js +47 -81
  144. package/dist/esm/commands/list.d.ts +16 -0
  145. package/{lib → dist/esm}/commands/list.js +56 -121
  146. package/dist/esm/commands/login.d.ts +11 -0
  147. package/{lib → dist/esm}/commands/login.js +10 -15
  148. package/dist/esm/commands/logout.d.ts +8 -0
  149. package/{lib → dist/esm}/commands/logout.js +9 -11
  150. package/dist/esm/commands/move.d.ts +13 -0
  151. package/{lib → dist/esm}/commands/move.js +13 -21
  152. package/dist/esm/commands/namespace.d.ts +25 -0
  153. package/{lib → dist/esm}/commands/namespace.js +34 -44
  154. package/dist/esm/commands/noop.d.ts +8 -0
  155. package/{lib → dist/esm}/commands/noop.js +6 -7
  156. package/dist/esm/commands/quota.d.ts +10 -0
  157. package/{lib → dist/esm}/commands/quota.js +18 -36
  158. package/dist/esm/commands/rename.d.ts +12 -0
  159. package/{lib → dist/esm}/commands/rename.js +10 -15
  160. package/dist/esm/commands/search.d.ts +15 -0
  161. package/{lib → dist/esm}/commands/search.js +36 -135
  162. package/dist/esm/commands/select.d.ts +25 -0
  163. package/{lib → dist/esm}/commands/select.js +33 -64
  164. package/dist/esm/commands/starttls.d.ts +8 -0
  165. package/{lib → dist/esm}/commands/starttls.js +6 -8
  166. package/dist/esm/commands/status-fields.d.ts +14 -0
  167. package/{lib → dist/esm}/commands/status-fields.js +5 -16
  168. package/dist/esm/commands/status.d.ts +12 -0
  169. package/{lib → dist/esm}/commands/status.js +18 -29
  170. package/dist/esm/commands/store.d.ts +19 -0
  171. package/{lib → dist/esm}/commands/store.js +24 -37
  172. package/dist/esm/commands/subscribe.d.ts +9 -0
  173. package/{lib → dist/esm}/commands/subscribe.js +8 -12
  174. package/dist/esm/commands/unsubscribe.d.ts +9 -0
  175. package/{lib → dist/esm}/commands/unsubscribe.js +8 -12
  176. package/dist/esm/connection-deadline.d.ts +49 -0
  177. package/{lib → dist/esm}/connection-deadline.js +14 -25
  178. package/dist/esm/errors.d.ts +83 -0
  179. package/dist/esm/errors.js +9 -0
  180. package/dist/esm/handler/imap-compiler.d.ts +24 -0
  181. package/{lib → dist/esm}/handler/imap-compiler.js +22 -80
  182. package/dist/esm/handler/imap-formal-syntax.d.ts +28 -0
  183. package/dist/esm/handler/imap-formal-syntax.js +117 -0
  184. package/dist/esm/handler/imap-handler.d.ts +9 -0
  185. package/dist/esm/handler/imap-handler.js +9 -0
  186. package/dist/esm/handler/imap-parser.d.ts +16 -0
  187. package/{lib → dist/esm}/handler/imap-parser.js +31 -44
  188. package/dist/esm/handler/imap-stream.d.ts +181 -0
  189. package/{lib → dist/esm}/handler/imap-stream.js +29 -121
  190. package/dist/esm/handler/limits.d.ts +25 -0
  191. package/{lib → dist/esm}/handler/limits.js +13 -22
  192. package/dist/esm/handler/parser-instance.d.ts +68 -0
  193. package/{lib → dist/esm}/handler/parser-instance.js +19 -47
  194. package/dist/esm/handler/token-parser.d.ts +91 -0
  195. package/{lib → dist/esm}/handler/token-parser.js +71 -155
  196. package/dist/esm/handler/types.d.ts +91 -0
  197. package/dist/esm/handler/types.js +3 -0
  198. package/dist/esm/imap-commands.d.ts +16 -0
  199. package/dist/esm/imap-commands.js +67 -0
  200. package/dist/esm/imap-flow.d.ts +676 -0
  201. package/{lib → dist/esm}/imap-flow.js +785 -1802
  202. package/dist/esm/jp-decoder.d.ts +12 -0
  203. package/{lib → dist/esm}/jp-decoder.js +6 -21
  204. package/dist/esm/limited-passthrough.d.ts +25 -0
  205. package/{lib → dist/esm}/limited-passthrough.js +7 -20
  206. package/dist/esm/logger.d.ts +3 -0
  207. package/dist/esm/logger.js +4 -0
  208. package/dist/esm/package-info.d.ts +3 -0
  209. package/dist/esm/package-info.js +4 -0
  210. package/dist/esm/package.json +3 -0
  211. package/dist/esm/proxy-connection.d.ts +33 -0
  212. package/{lib → dist/esm}/proxy-connection.js +56 -127
  213. package/dist/esm/search-compiler.d.ts +34 -0
  214. package/{lib → dist/esm}/search-compiler.js +54 -110
  215. package/dist/esm/special-use.d.ts +22 -0
  216. package/dist/esm/special-use.js +907 -0
  217. package/dist/esm/tools.d.ts +427 -0
  218. package/dist/esm/tools.js +1446 -0
  219. package/dist/esm/types.d.ts +828 -0
  220. package/dist/esm/types.js +4 -0
  221. package/package.json +60 -20
  222. package/.gitattributes +0 -1
  223. package/.github/CODE_OF_CONDUCT.md +0 -76
  224. package/.github/FUNDING.yml +0 -4
  225. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -40
  226. package/.github/ISSUE_TEMPLATE/feature_request.md +0 -19
  227. package/.github/contributing.md +0 -17
  228. package/.github/workflows/release.yaml +0 -36
  229. package/.github/workflows/stale.yml +0 -29
  230. package/.github/workflows/test.yml +0 -51
  231. package/.ncurc.js +0 -4
  232. package/.prettierignore +0 -4
  233. package/.prettierrc.js +0 -8
  234. package/.release-please-manifest.json +0 -3
  235. package/CLAUDE.md +0 -104
  236. package/Gruntfile.js +0 -23
  237. package/eslint.config.js +0 -45
  238. package/lib/handler/imap-formal-syntax.js +0 -189
  239. package/lib/handler/imap-handler.js +0 -17
  240. package/lib/imap-commands.js +0 -45
  241. package/lib/logger.js +0 -5
  242. package/lib/special-use.js +0 -923
  243. package/lib/tools.js +0 -1612
  244. package/release-please-config.json +0 -10
  245. package/test/authentication-test.js +0 -101
  246. package/test/auto-idle-test.js +0 -470
  247. package/test/bodystructure-test.js +0 -899
  248. package/test/charsets-test.js +0 -161
  249. package/test/commands-branches-test.js +0 -1095
  250. package/test/commands-integration-test.js +0 -11124
  251. package/test/commands-test.js +0 -73
  252. package/test/connection-edge-cases-test.js +0 -1828
  253. package/test/connection-test.js +0 -162
  254. package/test/copyuid-parser-test.js +0 -173
  255. package/test/fetch-generator-test.js +0 -218
  256. package/test/fixtures/fake-timers.js +0 -115
  257. package/test/fixtures/serialized-mimetorture.js +0 -2738
  258. package/test/fixtures/test-client.js +0 -57
  259. package/test/fixtures/test-tls.js +0 -8
  260. package/test/handler-branches-test.js +0 -310
  261. package/test/idle-polling-test.js +0 -518
  262. package/test/imap-compiler-test.js +0 -809
  263. package/test/imap-flow-compress-test.js +0 -166
  264. package/test/imap-flow-coverage-test.js +0 -612
  265. package/test/imap-flow-fetch-download-test.js +0 -873
  266. package/test/imap-flow-internals-test.js +0 -725
  267. package/test/imap-flow-methods-test.js +0 -889
  268. package/test/imap-flow-proxy-paths-test.js +0 -366
  269. package/test/imap-flow-secure-test.js +0 -573
  270. package/test/imap-flow-server-test.js +0 -1474
  271. package/test/imap-formal-syntax-test.js +0 -293
  272. package/test/imap-parser-test.js +0 -1474
  273. package/test/imap-stream-edge-cases-test.js +0 -666
  274. package/test/imap-stream-test.js +0 -177
  275. package/test/imapflow-test.js +0 -258
  276. package/test/integration/README.md +0 -52
  277. package/test/integration/dovecot-test.conf +0 -27
  278. package/test/integration/rev2-live-test.js +0 -431
  279. package/test/integration/run-rev2-tests.sh +0 -75
  280. package/test/integration-test.js +0 -83
  281. package/test/jp-decoder-test.js +0 -304
  282. package/test/limited-passthrough-test.js +0 -299
  283. package/test/memory-cleanup-test.js +0 -144
  284. package/test/memory-leak-test.js +0 -667
  285. package/test/parser-limits-test.js +0 -292
  286. package/test/proxy-connection-test.js +0 -738
  287. package/test/reliability-improvements-test.js +0 -548
  288. package/test/search-compiler-test.js +0 -1300
  289. package/test/search-test.js +0 -329
  290. package/test/special-use-test.js +0 -418
  291. package/test/starttls-injection-test.js +0 -181
  292. package/test/tag-correlation-test.js +0 -333
  293. package/test/timer-policy-test.js +0 -227
  294. package/test/token-parser-test.js +0 -456
  295. package/test/tools-test.js +0 -2013
  296. package/test/unhandled-rejection-test.js +0 -593
@@ -1,889 +0,0 @@
1
- 'use strict';
2
-
3
- // Unit tests that exercise ImapFlow public/command methods against a real
4
- // ImapFlow instance with `run` (and occasionally lower-level helpers) stubbed.
5
- // This deterministically covers the thin command wrappers, range resolution,
6
- // the untagged response handlers, and the fetch/download orchestration without
7
- // requiring a live IMAP server.
8
-
9
- const { ImapFlow } = require('../lib/imap-flow');
10
-
11
- // Build a real ImapFlow instance with logging disabled and a selected mailbox.
12
- // `run` is stubbed by default to record calls and return a configurable value.
13
- const makeClient = (overrides = {}) => {
14
- let client = new ImapFlow({
15
- host: 'imap.example.com',
16
- port: 993,
17
- auth: { user: 'test', pass: 'test' },
18
- logger: false,
19
- ...overrides
20
- });
21
- client.socket = { destroyed: false, destroy: () => {} };
22
- client.usable = true;
23
- client.mailbox = {
24
- path: 'INBOX',
25
- exists: 10,
26
- flags: new Set(['\\Seen', '\\Flagged']),
27
- permanentFlags: new Set(['\\*'])
28
- };
29
- client.state = client.states.SELECTED;
30
- return client;
31
- };
32
-
33
- // Records every run() invocation so assertions can verify the dispatched command.
34
- const recordRun = (client, returnValue) => {
35
- let calls = [];
36
- client.run = async (...args) => {
37
- calls.push(args);
38
- return typeof returnValue === 'function' ? returnValue(...args) : returnValue;
39
- };
40
- return calls;
41
- };
42
-
43
- // ============================================================================
44
- // Thin command wrappers
45
- // ============================================================================
46
-
47
- module.exports['Methods: getQuota defaults path to INBOX'] = async test => {
48
- let client = makeClient();
49
- let calls = recordRun(client, { path: 'INBOX' });
50
- let res = await client.getQuota();
51
- test.deepEqual(calls[0], ['QUOTA', 'INBOX']);
52
- test.deepEqual(res, { path: 'INBOX' });
53
- test.done();
54
- };
55
-
56
- module.exports['Methods: getQuota uses given path'] = async test => {
57
- let client = makeClient();
58
- let calls = recordRun(client, false);
59
- await client.getQuota('Archive');
60
- test.deepEqual(calls[0], ['QUOTA', 'Archive']);
61
- test.done();
62
- };
63
-
64
- module.exports['Methods: list builds folders map'] = async test => {
65
- let client = makeClient();
66
- recordRun(client, [
67
- { path: 'INBOX', name: 'INBOX' },
68
- { path: 'Sent', name: 'Sent' }
69
- ]);
70
- let folders = await client.list();
71
- test.equal(folders.length, 2);
72
- test.ok(client.folders instanceof Map);
73
- test.equal(client.folders.get('Sent').name, 'Sent');
74
- test.done();
75
- };
76
-
77
- module.exports['Methods: list passes options'] = async test => {
78
- let client = makeClient();
79
- let calls = recordRun(client, []);
80
- await client.list({ statusQuery: { messages: true } });
81
- test.deepEqual(calls[0][0], 'LIST');
82
- test.deepEqual(calls[0][3], { statusQuery: { messages: true } });
83
- test.done();
84
- };
85
-
86
- module.exports['Methods: listTree returns tree structure'] = async test => {
87
- let client = makeClient();
88
- recordRun(client, [
89
- { path: 'INBOX', name: 'INBOX', delimiter: '/', parent: [], flags: new Set() },
90
- { path: 'INBOX/Sub', name: 'Sub', delimiter: '/', parent: ['INBOX'], flags: new Set() }
91
- ]);
92
- let tree = await client.listTree();
93
- test.ok(tree);
94
- test.ok(Array.isArray(tree.folders));
95
- test.ok(client.folders instanceof Map);
96
- test.done();
97
- };
98
-
99
- module.exports['Methods: listTree passes through inline status objects'] = async test => {
100
- let client = makeClient();
101
- recordRun(client, [
102
- { path: 'INBOX', name: 'INBOX', delimiter: '/', parent: [], flags: new Set(), status: { path: 'INBOX', messages: 5, unseen: 2 } },
103
- { path: 'INBOX/Sub', name: 'Sub', delimiter: '/', parent: ['INBOX'], flags: new Set() }
104
- ]);
105
- let tree = await client.listTree({ statusQuery: { messages: true, unseen: true } });
106
- // The StatusObject must survive into the tree node - it used to be coerced
107
- // into a boolean by the tree builder
108
- test.deepEqual(tree.folders[0].status, { path: 'INBOX', messages: 5, unseen: 2 });
109
- test.done();
110
- };
111
-
112
- module.exports['Methods: noop dispatches NOOP'] = async test => {
113
- let client = makeClient();
114
- let calls = recordRun(client, true);
115
- await client.noop();
116
- test.deepEqual(calls[0], ['NOOP']);
117
- test.done();
118
- };
119
-
120
- module.exports['Methods: mailboxCreate dispatches CREATE'] = async test => {
121
- let client = makeClient();
122
- let calls = recordRun(client, { path: 'New', created: true });
123
- let res = await client.mailboxCreate(['parent', 'child']);
124
- test.deepEqual(calls[0], ['CREATE', ['parent', 'child']]);
125
- test.equal(res.created, true);
126
- test.done();
127
- };
128
-
129
- module.exports['Methods: mailboxRename dispatches RENAME'] = async test => {
130
- let client = makeClient();
131
- let calls = recordRun(client, { path: 'a', newPath: 'b' });
132
- await client.mailboxRename('a', 'b');
133
- test.deepEqual(calls[0], ['RENAME', 'a', 'b']);
134
- test.done();
135
- };
136
-
137
- module.exports['Methods: mailboxDelete dispatches DELETE'] = async test => {
138
- let client = makeClient();
139
- let calls = recordRun(client, { path: 'a' });
140
- await client.mailboxDelete('a');
141
- test.deepEqual(calls[0], ['DELETE', 'a']);
142
- test.done();
143
- };
144
-
145
- module.exports['Methods: mailboxSubscribe dispatches SUBSCRIBE'] = async test => {
146
- let client = makeClient();
147
- let calls = recordRun(client, true);
148
- await client.mailboxSubscribe('a');
149
- test.deepEqual(calls[0], ['SUBSCRIBE', 'a']);
150
- test.done();
151
- };
152
-
153
- module.exports['Methods: mailboxUnsubscribe dispatches UNSUBSCRIBE'] = async test => {
154
- let client = makeClient();
155
- let calls = recordRun(client, true);
156
- await client.mailboxUnsubscribe('a');
157
- test.deepEqual(calls[0], ['UNSUBSCRIBE', 'a']);
158
- test.done();
159
- };
160
-
161
- module.exports['Methods: mailboxOpen dispatches SELECT'] = async test => {
162
- let client = makeClient();
163
- let calls = recordRun(client, { path: 'INBOX' });
164
- await client.mailboxOpen('INBOX', { readOnly: true });
165
- test.deepEqual(calls[0], ['SELECT', 'INBOX', { readOnly: true }]);
166
- test.done();
167
- };
168
-
169
- module.exports['Methods: mailboxClose dispatches CLOSE'] = async test => {
170
- let client = makeClient();
171
- let calls = recordRun(client, true);
172
- await client.mailboxClose();
173
- test.deepEqual(calls[0], ['CLOSE']);
174
- test.done();
175
- };
176
-
177
- module.exports['Methods: status dispatches STATUS'] = async test => {
178
- let client = makeClient();
179
- let calls = recordRun(client, { path: 'INBOX', unseen: 3 });
180
- let res = await client.status('INBOX', { unseen: true });
181
- test.deepEqual(calls[0], ['STATUS', 'INBOX', { unseen: true }]);
182
- test.equal(res.unseen, 3);
183
- test.done();
184
- };
185
-
186
- // ============================================================================
187
- // idle()
188
- // ============================================================================
189
-
190
- module.exports['Methods: idle runs IDLE when not already idling'] = async test => {
191
- let client = makeClient();
192
- let calls = recordRun(client, true);
193
- await client.idle();
194
- test.deepEqual(calls[0], ['IDLE', client.maxIdleTime]);
195
- test.done();
196
- };
197
-
198
- module.exports['Methods: idle is a no-op when already idling'] = async test => {
199
- let client = makeClient();
200
- client.idling = true;
201
- let calls = recordRun(client, true);
202
- let res = await client.idle();
203
- test.equal(calls.length, 0);
204
- test.equal(res, undefined);
205
- test.done();
206
- };
207
-
208
- // ============================================================================
209
- // Flag operations + range resolution
210
- // ============================================================================
211
-
212
- module.exports['Methods: messageFlagsSet resolves range and dispatches STORE'] = async test => {
213
- let client = makeClient();
214
- let calls = recordRun(client, true);
215
- let res = await client.messageFlagsSet('1:5', ['\\Seen'], { uid: true });
216
- test.equal(res, true);
217
- test.equal(calls[0][0], 'STORE');
218
- test.equal(calls[0][1], '1:5');
219
- test.deepEqual(calls[0][3], { operation: 'set', uid: true });
220
- test.done();
221
- };
222
-
223
- module.exports['Methods: messageFlagsSet works without an options argument'] = async test => {
224
- let client = makeClient();
225
- let calls = recordRun(client, true);
226
- let res = await client.messageFlagsSet('1', ['\\Seen']); // no options -> options || {}
227
- test.equal(res, true);
228
- test.equal(calls[0][0], 'STORE');
229
- test.done();
230
- };
231
-
232
- module.exports['Methods: search derivation handles non-string returnOptions entries'] = async test => {
233
- let client = makeClient();
234
- recordRun(client, [1, 2, 3]); // no ESEARCH -> client-side derivation runs
235
- // a non-string returnOption (object) is passed through untouched by the mapper
236
- let res = await client.search({ all: true }, { returnOptions: ['count', { partial: '1:2' }] });
237
- test.equal(res.count, 3);
238
- test.done();
239
- };
240
-
241
- module.exports['Methods: messageFlagsSet returns false for empty range'] = async test => {
242
- let client = makeClient();
243
- let calls = recordRun(client, true);
244
- let res = await client.messageFlagsSet([], ['\\Seen'], {});
245
- test.equal(res, false);
246
- test.equal(calls.length, 0);
247
- test.done();
248
- };
249
-
250
- module.exports['Methods: messageFlagsAdd dispatches STORE add'] = async test => {
251
- let client = makeClient();
252
- let calls = recordRun(client, true);
253
- await client.messageFlagsAdd('1', ['\\Flagged']);
254
- test.equal(calls[0][3].operation, 'add');
255
- test.done();
256
- };
257
-
258
- module.exports['Methods: messageFlagsAdd returns false on empty range'] = async test => {
259
- let client = makeClient();
260
- recordRun(client, true);
261
- let res = await client.messageFlagsAdd([], ['\\Flagged']);
262
- test.equal(res, false);
263
- test.done();
264
- };
265
-
266
- module.exports['Methods: messageFlagsRemove dispatches STORE remove'] = async test => {
267
- let client = makeClient();
268
- let calls = recordRun(client, true);
269
- await client.messageFlagsRemove('1', ['\\Flagged']);
270
- test.equal(calls[0][3].operation, 'remove');
271
- test.done();
272
- };
273
-
274
- module.exports['Methods: messageFlagsRemove returns false on empty range'] = async test => {
275
- let client = makeClient();
276
- recordRun(client, true);
277
- let res = await client.messageFlagsRemove([], ['\\Flagged']);
278
- test.equal(res, false);
279
- test.done();
280
- };
281
-
282
- module.exports['Methods: setFlagColor returns false for empty range'] = async test => {
283
- let client = makeClient();
284
- recordRun(client, true);
285
- let res = await client.setFlagColor([], 'red');
286
- test.equal(res, false);
287
- test.done();
288
- };
289
-
290
- module.exports['Methods: setFlagColor returns false for invalid color'] = async test => {
291
- let client = makeClient();
292
- recordRun(client, true);
293
- let res = await client.setFlagColor('1', 'not-a-color');
294
- test.equal(res, false);
295
- test.done();
296
- };
297
-
298
- module.exports['Methods: setFlagColor red issues add and remove STORE'] = async test => {
299
- let client = makeClient();
300
- let calls = recordRun(client, true);
301
- // red sets \\Flagged and clears the MailFlagBit* keywords -> both add and remove
302
- let res = await client.setFlagColor('1:*', 'red');
303
- test.ok(res);
304
- let ops = calls.map(c => c[3].operation);
305
- test.ok(ops.includes('add'));
306
- test.ok(ops.includes('remove'));
307
- test.done();
308
- };
309
-
310
- module.exports['Methods: setFlagColor removal (null color) issues only a remove STORE'] = async test => {
311
- test.expect(5);
312
- let client = makeClient();
313
- let calls = recordRun(client, true);
314
- // A null/empty color clears the color: getColorFlags returns an empty add set and a
315
- // non-empty remove set, so the add STORE is skipped and only the remove STORE runs.
316
- let res = await client.setFlagColor('1', null);
317
- test.ok(res);
318
- test.equal(calls.length, 1);
319
- test.equal(calls[0][0], 'STORE');
320
- test.equal(calls[0][3].operation, 'remove');
321
- test.deepEqual(calls[0][2], ['\\Flagged', '$MailFlagBit0', '$MailFlagBit1', '$MailFlagBit2']);
322
- test.done();
323
- };
324
-
325
- module.exports['Methods: messageDelete resolves range and dispatches EXPUNGE'] = async test => {
326
- let client = makeClient();
327
- let calls = recordRun(client, true);
328
- let res = await client.messageDelete('1:3', { uid: true });
329
- test.equal(res, true);
330
- test.equal(calls[0][0], 'EXPUNGE');
331
- test.equal(calls[0][1], '1:3');
332
- test.done();
333
- };
334
-
335
- module.exports['Methods: messageDelete returns false on empty range'] = async test => {
336
- let client = makeClient();
337
- recordRun(client, true);
338
- let res = await client.messageDelete([]);
339
- test.equal(res, false);
340
- test.done();
341
- };
342
-
343
- // ============================================================================
344
- // append / copy / move
345
- // ============================================================================
346
-
347
- module.exports['Methods: append dispatches APPEND'] = async test => {
348
- let client = makeClient();
349
- let calls = recordRun(client, { destination: 'INBOX', uid: 5 });
350
- let res = await client.append('INBOX', 'raw message', ['\\Seen'], new Date(2020, 1, 1));
351
- test.equal(calls[0][0], 'APPEND');
352
- test.equal(res.uid, 5);
353
- test.done();
354
- };
355
-
356
- module.exports['Methods: append returns false when run returns falsy'] = async test => {
357
- let client = makeClient();
358
- recordRun(client, false);
359
- let res = await client.append('INBOX', 'raw');
360
- test.equal(res, false);
361
- test.done();
362
- };
363
-
364
- module.exports['Methods: messageCopy resolves range and dispatches COPY'] = async test => {
365
- let client = makeClient();
366
- let calls = recordRun(client, { destination: 'Backup' });
367
- await client.messageCopy('1:*', 'Backup', { uid: false });
368
- test.equal(calls[0][0], 'COPY');
369
- test.equal(calls[0][2], 'Backup');
370
- test.done();
371
- };
372
-
373
- module.exports['Methods: messageCopy returns false on empty range'] = async test => {
374
- let client = makeClient();
375
- recordRun(client, true);
376
- let res = await client.messageCopy([], 'Backup');
377
- test.equal(res, false);
378
- test.done();
379
- };
380
-
381
- module.exports['Methods: messageMove resolves range and dispatches MOVE'] = async test => {
382
- let client = makeClient();
383
- let calls = recordRun(client, { destination: 'Trash' });
384
- await client.messageMove('1', 'Trash');
385
- test.equal(calls[0][0], 'MOVE');
386
- test.equal(calls[0][2], 'Trash');
387
- test.done();
388
- };
389
-
390
- module.exports['Methods: messageMove returns false on empty range'] = async test => {
391
- let client = makeClient();
392
- recordRun(client, true);
393
- let res = await client.messageMove([], 'Trash');
394
- test.equal(res, false);
395
- test.done();
396
- };
397
-
398
- // ============================================================================
399
- // resolveRange branches
400
- // ============================================================================
401
-
402
- module.exports['Methods: resolveRange converts number'] = async test => {
403
- let client = makeClient();
404
- let res = await client.resolveRange(5, {});
405
- test.equal(res, '5');
406
- test.done();
407
- };
408
-
409
- module.exports['Methods: resolveRange converts bigint'] = async test => {
410
- let client = makeClient();
411
- let res = await client.resolveRange(BigInt(7), {});
412
- test.equal(res, '7');
413
- test.done();
414
- };
415
-
416
- module.exports['Methods: resolveRange star uses mailbox.exists'] = async test => {
417
- let client = makeClient();
418
- let options = {};
419
- let res = await client.resolveRange('*', options);
420
- test.equal(res, '10');
421
- test.equal(options.uid, false);
422
- test.done();
423
- };
424
-
425
- module.exports['Methods: resolveRange star returns false on empty mailbox'] = async test => {
426
- let client = makeClient();
427
- client.mailbox.exists = 0;
428
- let res = await client.resolveRange('*', {});
429
- test.equal(res, false);
430
- test.done();
431
- };
432
-
433
- module.exports['Methods: resolveRange all:true => 1:*'] = async test => {
434
- let client = makeClient();
435
- let res = await client.resolveRange({ all: true }, {});
436
- test.equal(res, '1:*');
437
- test.done();
438
- };
439
-
440
- module.exports['Methods: resolveRange uid object'] = async test => {
441
- let client = makeClient();
442
- let options = {};
443
- let res = await client.resolveRange({ uid: '100:200' }, options);
444
- test.equal(res, '100:200');
445
- test.equal(options.uid, true);
446
- test.done();
447
- };
448
-
449
- module.exports['Methods: resolveRange search query runs SEARCH and packs'] = async test => {
450
- let client = makeClient();
451
- let options = {};
452
- client.run = async (cmd, query, opts) => {
453
- test.equal(cmd, 'SEARCH');
454
- test.equal(opts.uid, true);
455
- return [1, 2, 3, 5];
456
- };
457
- let res = await client.resolveRange({ seen: false }, options);
458
- test.equal(res, '1:3,5');
459
- test.done();
460
- };
461
-
462
- module.exports['Methods: resolveRange search query with no hits'] = async test => {
463
- let client = makeClient();
464
- client.run = async () => [];
465
- let res = await client.resolveRange({ seen: false }, {});
466
- test.equal(res, false);
467
- test.done();
468
- };
469
-
470
- module.exports['Methods: resolveRange array joins with commas'] = async test => {
471
- let client = makeClient();
472
- let res = await client.resolveRange([1, 2, 4], {});
473
- test.equal(res, '1,2,4');
474
- test.done();
475
- };
476
-
477
- module.exports['Methods: resolveRange empty string returns false'] = async test => {
478
- let client = makeClient();
479
- let res = await client.resolveRange('', {});
480
- test.equal(res, false);
481
- test.done();
482
- };
483
-
484
- // ============================================================================
485
- // ensureSelectedMailbox
486
- // ============================================================================
487
-
488
- module.exports['Methods: ensureSelectedMailbox returns false without path'] = async test => {
489
- let client = makeClient();
490
- let res = await client.ensureSelectedMailbox(null);
491
- test.equal(res, false);
492
- test.done();
493
- };
494
-
495
- module.exports['Methods: ensureSelectedMailbox returns true when already selected'] = async test => {
496
- let client = makeClient();
497
- let res = await client.ensureSelectedMailbox('INBOX');
498
- test.equal(res, true);
499
- test.done();
500
- };
501
-
502
- module.exports['Methods: ensureSelectedMailbox opens a different mailbox'] = async test => {
503
- let client = makeClient();
504
- let calls = recordRun(client, { path: 'Other' });
505
- await client.ensureSelectedMailbox('Other');
506
- test.equal(calls[0][0], 'SELECT');
507
- test.equal(calls[0][1], 'Other');
508
- test.done();
509
- };
510
-
511
- // ============================================================================
512
- // search()
513
- // ============================================================================
514
-
515
- module.exports['Methods: search returns undefined without mailbox'] = async test => {
516
- let client = makeClient();
517
- client.mailbox = false;
518
- let res = await client.search({ seen: false });
519
- test.equal(res, undefined);
520
- test.done();
521
- };
522
-
523
- module.exports['Methods: search returns server result'] = async test => {
524
- let client = makeClient();
525
- recordRun(client, [1, 2, 3]);
526
- let res = await client.search({ seen: false });
527
- test.deepEqual(res, [1, 2, 3]);
528
- test.done();
529
- };
530
-
531
- module.exports['Methods: search returns false when run returns falsy'] = async test => {
532
- let client = makeClient();
533
- recordRun(client, null);
534
- let res = await client.search({ all: true });
535
- test.equal(res, false);
536
- test.done();
537
- };
538
-
539
- module.exports['Methods: search derives ESearch COUNT/MIN/MAX/ALL client-side'] = async test => {
540
- let client = makeClient();
541
- recordRun(client, [2, 4, 6, 8]);
542
- let res = await client.search({ all: true }, { returnOptions: ['count', 'min', 'max', 'all'] });
543
- test.equal(res.count, 4);
544
- test.equal(res.min, 2);
545
- test.equal(res.max, 8);
546
- test.equal(res.all, '2,4,6,8');
547
- test.done();
548
- };
549
-
550
- module.exports['Methods: search returnOptions PARTIAL only returns raw array'] = async test => {
551
- let client = makeClient();
552
- recordRun(client, [1, 2, 3]);
553
- let res = await client.search({ all: true }, { returnOptions: ['partial'] });
554
- test.deepEqual(res, [1, 2, 3]);
555
- test.done();
556
- };
557
-
558
- // ============================================================================
559
- // Untagged response handlers
560
- // ============================================================================
561
-
562
- module.exports['Methods: untaggedExists ignores when no mailbox'] = async test => {
563
- let client = makeClient();
564
- client.mailbox = false;
565
- await client.untaggedExists({ command: '12' });
566
- test.ok(true);
567
- test.done();
568
- };
569
-
570
- module.exports['Methods: untaggedExists ignores invalid command'] = async test => {
571
- let client = makeClient();
572
- let fired = false;
573
- client.on('exists', () => {
574
- fired = true;
575
- });
576
- await client.untaggedExists({ command: 'NaNvalue' });
577
- test.equal(fired, false);
578
- test.done();
579
- };
580
-
581
- module.exports['Methods: untaggedExists no-op when count unchanged'] = async test => {
582
- let client = makeClient();
583
- client.mailbox.exists = 10;
584
- let fired = false;
585
- client.on('exists', () => {
586
- fired = true;
587
- });
588
- await client.untaggedExists({ command: '10' });
589
- test.equal(fired, false);
590
- test.done();
591
- };
592
-
593
- module.exports['Methods: untaggedExists emits exists event on change'] = async test => {
594
- let client = makeClient();
595
- client.mailbox.exists = 10;
596
- let evt = null;
597
- client.on('exists', e => {
598
- evt = e;
599
- });
600
- await client.untaggedExists({ command: '12' });
601
- test.ok(evt);
602
- test.equal(evt.count, 12);
603
- test.equal(evt.prevCount, 10);
604
- test.equal(client.mailbox.exists, 12);
605
- test.done();
606
- };
607
-
608
- module.exports['Methods: untaggedExpunge ignores when no mailbox'] = async test => {
609
- let client = makeClient();
610
- client.mailbox = false;
611
- await client.untaggedExpunge({ command: '2' });
612
- test.ok(true);
613
- test.done();
614
- };
615
-
616
- module.exports['Methods: untaggedExpunge ignores invalid command'] = async test => {
617
- let client = makeClient();
618
- let fired = false;
619
- client.on('expunge', () => {
620
- fired = true;
621
- });
622
- await client.untaggedExpunge({ command: 'x' });
623
- test.equal(fired, false);
624
- test.done();
625
- };
626
-
627
- module.exports['Methods: untaggedExpunge emits expunge event'] = async test => {
628
- let client = makeClient();
629
- client.mailbox.exists = 10;
630
- let evt = null;
631
- client.on('expunge', e => {
632
- evt = e;
633
- });
634
- await client.untaggedExpunge({ command: '3' });
635
- test.ok(evt);
636
- test.equal(evt.seq, 3);
637
- test.equal(evt.vanished, false);
638
- test.equal(client.mailbox.exists, 9);
639
- test.done();
640
- };
641
-
642
- module.exports['Methods: untaggedExpunge uses expungeHandler when set'] = async test => {
643
- let payloads = [];
644
- let client = makeClient({ expungeHandler: async payload => payloads.push(payload) });
645
- client.mailbox.exists = 5;
646
- await client.untaggedExpunge({ command: '2' });
647
- test.equal(payloads.length, 1);
648
- test.equal(payloads[0].seq, 2);
649
- test.done();
650
- };
651
-
652
- module.exports['Methods: untaggedExpunge handles expungeHandler error'] = async test => {
653
- let client = makeClient({
654
- expungeHandler: async () => {
655
- throw new Error('handler boom');
656
- }
657
- });
658
- client.mailbox.exists = 5;
659
- // Should not throw despite handler error
660
- await client.untaggedExpunge({ command: '2' });
661
- test.ok(true);
662
- test.done();
663
- };
664
-
665
- module.exports['Methods: untaggedVanished ignores when no mailbox'] = async test => {
666
- let client = makeClient();
667
- client.mailbox = false;
668
- await client.untaggedVanished({ attributes: [{ value: '1:3' }] });
669
- test.ok(true);
670
- test.done();
671
- };
672
-
673
- module.exports['Methods: untaggedVanished emits expunge per uid'] = async test => {
674
- let client = makeClient();
675
- let events = [];
676
- client.on('expunge', e => events.push(e));
677
- await client.untaggedVanished({ attributes: [{ value: '1:3' }] });
678
- test.equal(events.length, 3);
679
- test.equal(events[0].uid, 1);
680
- test.equal(events[0].vanished, true);
681
- test.equal(events[0].earlier, false);
682
- test.done();
683
- };
684
-
685
- module.exports['Methods: untaggedVanished EARLIER tag sets earlier flag'] = async test => {
686
- let client = makeClient();
687
- let events = [];
688
- client.on('expunge', e => events.push(e));
689
- await client.untaggedVanished({
690
- attributes: [[{ value: 'EARLIER' }], { value: '5' }]
691
- });
692
- test.equal(events.length, 1);
693
- test.equal(events[0].uid, 5);
694
- test.equal(events[0].earlier, true);
695
- test.done();
696
- };
697
-
698
- module.exports['Methods: untaggedVanished routes through expungeHandler'] = async test => {
699
- let payloads = [];
700
- let client = makeClient({ expungeHandler: async p => payloads.push(p) });
701
- await client.untaggedVanished({ attributes: [{ value: '7' }] });
702
- test.equal(payloads.length, 1);
703
- test.equal(payloads[0].uid, 7);
704
- test.done();
705
- };
706
-
707
- module.exports['Methods: untaggedVanished swallows expungeHandler error'] = async test => {
708
- let client = makeClient({
709
- expungeHandler: async () => {
710
- throw new Error('vanished boom');
711
- }
712
- });
713
- await client.untaggedVanished({ attributes: [{ value: '7' }] });
714
- test.ok(true);
715
- test.done();
716
- };
717
-
718
- module.exports['Methods: untaggedFetch ignores when no mailbox'] = async test => {
719
- let client = makeClient();
720
- client.mailbox = false;
721
- await client.untaggedFetch({ command: '1', attributes: [] });
722
- test.ok(true);
723
- test.done();
724
- };
725
-
726
- module.exports['Methods: untaggedFetch emits flags event'] = async test => {
727
- let client = makeClient();
728
- let evt = null;
729
- client.on('flags', e => {
730
- evt = e;
731
- });
732
- // Build an untagged FETCH with FLAGS and UID attributes
733
- let untagged = {
734
- command: '1',
735
- attributes: [
736
- { type: 'ATOM', value: 'FETCH' },
737
- [{ type: 'ATOM', value: 'UID' }, { type: 'ATOM', value: '100' }, { type: 'ATOM', value: 'FLAGS' }, [{ type: 'ATOM', value: '\\Seen' }]]
738
- ]
739
- };
740
- await client.untaggedFetch(untagged);
741
- test.ok(evt);
742
- test.equal(evt.uid, 100);
743
- test.ok(evt.flags instanceof Set);
744
- test.ok(evt.flags.has('\\Seen'));
745
- test.done();
746
- };
747
-
748
- // ============================================
749
- // autoEnable
750
- // ============================================
751
-
752
- module.exports['Methods: autoEnable requests IMAP4rev2 alongside the base extensions'] = async test => {
753
- let client = makeClient();
754
- let calls = recordRun(client, new Set(['CONDSTORE', 'IMAP4REV2']));
755
- await client.autoEnable();
756
- test.equal(calls.length, 1);
757
- test.deepEqual(calls[0], ['ENABLE', ['CONDSTORE', 'UTF8=ACCEPT', 'IMAP4rev2']]);
758
- test.done();
759
- };
760
-
761
- module.exports['Methods: autoEnable honors disableIMAP4rev2'] = async test => {
762
- let client = makeClient({ disableIMAP4rev2: true });
763
- let calls = recordRun(client, new Set(['CONDSTORE']));
764
- await client.autoEnable();
765
- test.equal(calls.length, 1);
766
- test.deepEqual(calls[0], ['ENABLE', ['CONDSTORE', 'UTF8=ACCEPT']]);
767
- test.done();
768
- };
769
-
770
- module.exports['Methods: autoEnable includes QRESYNC when requested'] = async test => {
771
- let client = makeClient({ qresync: true });
772
- let calls = recordRun(client, new Set(['CONDSTORE', 'QRESYNC', 'IMAP4REV2']));
773
- await client.autoEnable();
774
- test.equal(calls.length, 1);
775
- test.deepEqual(calls[0], ['ENABLE', ['CONDSTORE', 'UTF8=ACCEPT', 'QRESYNC', 'IMAP4rev2']]);
776
- test.done();
777
- };
778
-
779
- module.exports['Methods: autoEnable retries without IMAP4rev2 when the whole command fails'] = async test => {
780
- let client = makeClient();
781
- // RFC 5161 requires unknown ENABLE arguments to be ignored, but a broken
782
- // server may reject the whole command over IMAP4rev2 - the retry keeps
783
- // CONDSTORE/UTF8=ACCEPT from being lost as collateral damage
784
- let calls = recordRun(client, (cmd, list) => (list.includes('IMAP4rev2') ? false : new Set(['CONDSTORE'])));
785
- await client.autoEnable();
786
- test.equal(calls.length, 2);
787
- test.deepEqual(calls[1], ['ENABLE', ['CONDSTORE', 'UTF8=ACCEPT']]);
788
- test.done();
789
- };
790
-
791
- module.exports['Methods: autoEnable does not retry when disableIMAP4rev2 already omitted it'] = async test => {
792
- let client = makeClient({ disableIMAP4rev2: true });
793
- let calls = recordRun(client, () => false);
794
- await client.autoEnable();
795
- test.equal(calls.length, 1);
796
- test.done();
797
- };
798
-
799
- module.exports['Methods: untaggedExpunge refuses an unusable sequence number'] = async test => {
800
- // Same bound untaggedExists() applies: an overflowing or non-decimal value must not
801
- // decrement the live message count
802
- let client = makeClient();
803
- client.mailbox.exists = 10;
804
- let events = [];
805
- client.on('expunge', e => events.push(e));
806
-
807
- await client.untaggedExpunge(false);
808
- for (let bad of ['9'.repeat(400), '1e5', '0', 'abc', '']) {
809
- await client.untaggedExpunge({ command: bad });
810
- }
811
- test.equal(client.mailbox.exists, 10, 'the live message count must be left alone');
812
- test.equal(events.length, 0);
813
-
814
- await client.untaggedExpunge({ command: '3' });
815
- test.equal(client.mailbox.exists, 9, 'a usable sequence number is still applied');
816
- test.equal(events.length, 1);
817
- test.equal(events[0].seq, 3);
818
- test.done();
819
- };
820
-
821
- module.exports['Methods: untaggedVanished survives a malformed response'] = async test => {
822
- // "* VANISHED (EARLIER)" leaves no sequence set at all. Throwing here would abort the
823
- // handler for the whole response, and handleResponse only warns - so every expunge the
824
- // response did carry would be dropped and the client's mailbox view would go stale.
825
- let client = makeClient();
826
- let events = [];
827
- client.on('expunge', e => events.push(e));
828
-
829
- await client.untaggedVanished({ attributes: [[{ value: 'EARLIER' }]] });
830
- await client.untaggedVanished({ attributes: [] });
831
- await client.untaggedVanished({});
832
- await client.untaggedVanished({ attributes: [[{ value: 'EARLIER' }], null] });
833
-
834
- test.equal(events.length, 0, 'nothing to report, and nothing thrown');
835
-
836
- // a well-formed response still works after the malformed ones
837
- await client.untaggedVanished({ attributes: [[{ value: 'EARLIER' }], { value: '5:6' }] });
838
- test.equal(events.length, 2);
839
- test.equal(events[0].uid, 5);
840
- test.equal(events[0].earlier, true);
841
- test.done();
842
- };
843
-
844
- module.exports['Methods: untaggedExists refuses an overflowing count'] = async test => {
845
- // Number('9'.repeat(400)) is Infinity: resolveRange('*') would then compile the literal
846
- // string "Infinity" and every range-based command would fail until the next SELECT
847
- let client = makeClient();
848
- client.mailbox = { path: 'INBOX', exists: 5 };
849
- let events = [];
850
- client.on('exists', e => events.push(e));
851
-
852
- await client.untaggedExists(false);
853
- await client.untaggedExists({});
854
- for (let bad of ['9'.repeat(400), '1e5', '-1', 'abc', '1.5', '']) {
855
- await client.untaggedExists({ command: bad });
856
- }
857
- test.equal(client.mailbox.exists, 5, 'the live message count must be left alone');
858
- test.equal(events.length, 0);
859
-
860
- await client.untaggedExists({ command: '7' });
861
- test.equal(client.mailbox.exists, 7, 'a usable count is still applied');
862
- test.equal(events.length, 1);
863
- test.done();
864
- };
865
-
866
- module.exports['Methods: throttleWait caps the delay and close() aborts it'] = async test => {
867
- let client = makeClient();
868
-
869
- // A server hint is unbounded; the wait may never outlive the client either
870
- let started = Date.now();
871
- let pending = client.throttleWait(7 * 24 * 3600 * 1000);
872
- test.equal(client._throttleWaits.size, 1, 'the wait is tracked so close() can reach it');
873
-
874
- client.close();
875
- test.equal(await pending, true, 'an aborted wait reports true');
876
- test.equal(client._throttleWaits.size, 0, 'the tracked wait is released');
877
- test.ok(Date.now() - started < 5000, 'close() must not wait out the delay');
878
-
879
- // normal expiry reports false and releases the entry. The back-off timer is deliberately
880
- // unref'd, so the test has to hold the event loop open itself - in a real session the
881
- // socket does that.
882
- let client2 = makeClient();
883
- let keepAlive = setTimeout(() => {}, 1000);
884
- test.equal(await client2.throttleWait(1), false);
885
- clearTimeout(keepAlive);
886
- test.equal(client2._throttleWaits.size, 0);
887
- client2.close();
888
- test.done();
889
- };