imapflow 1.7.8 → 2.0.1

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 +520 -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 +3956 -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} +387 -517
  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 +60 -123
  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 +769 -1790
  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 -101
  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 -909
  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 -661
@@ -1,329 +0,0 @@
1
- 'use strict';
2
-
3
- const searchCmd = require('../lib/commands/search');
4
- const { parseEsearchResponse } = searchCmd;
5
- const { ImapFlow } = require('../lib/imap-flow');
6
-
7
- // Mock connection — capabilities is Map (matches real ImapFlow)
8
- function makeConnection({ hasEsearch = true } = {}) {
9
- const caps = new Map();
10
- if (hasEsearch) {
11
- caps.set('ESEARCH', true);
12
- }
13
- return {
14
- state: 'SELECTED',
15
- states: { SELECTED: 'SELECTED' },
16
- capabilities: caps,
17
- enabled: new Set(),
18
- exec: async () => ({ next: () => {} }),
19
- log: { warn: () => {} }
20
- };
21
- }
22
-
23
- // ── Parser tests ───────────────────────────────────────────────────────────
24
-
25
- module.exports['ESEARCH: parseEsearchResponse COUNT only'] = test => {
26
- const attrs = [
27
- { type: 'ATOM', value: 'COUNT' },
28
- { type: 'ATOM', value: '42' }
29
- ];
30
- const result = parseEsearchResponse(attrs);
31
- test.equal(result.count, 42);
32
- test.equal(result.min, undefined);
33
- test.equal(result.max, undefined);
34
- test.done();
35
- };
36
-
37
- module.exports['ESEARCH: parseEsearchResponse MIN MAX'] = test => {
38
- const attrs = [
39
- { type: 'ATOM', value: 'MIN' },
40
- { type: 'ATOM', value: '1001' },
41
- { type: 'ATOM', value: 'MAX' },
42
- { type: 'ATOM', value: '9876' }
43
- ];
44
- const result = parseEsearchResponse(attrs);
45
- test.equal(result.min, 1001);
46
- test.equal(result.max, 9876);
47
- test.done();
48
- };
49
-
50
- module.exports['ESEARCH: parseEsearchResponse ALL keeps compact string'] = test => {
51
- const attrs = [
52
- { type: 'ATOM', value: 'ALL' },
53
- { type: 'ATOM', value: '1001,1005:1010,1020' }
54
- ];
55
- const result = parseEsearchResponse(attrs);
56
- // Must be preserved as compact string — NOT an array
57
- test.equal(typeof result.all, 'string');
58
- test.equal(result.all, '1001,1005:1010,1020');
59
- test.done();
60
- };
61
-
62
- module.exports['ESEARCH: parseEsearchResponse PARTIAL (Array form)'] = test => {
63
- // Parser represents parenthesized groups as plain Arrays
64
- const attrs = [
65
- { type: 'ATOM', value: 'PARTIAL' },
66
- [
67
- { type: 'ATOM', value: '1:100' },
68
- { type: 'ATOM', value: '1001,1003:1010,1015' }
69
- ]
70
- ];
71
- const result = parseEsearchResponse(attrs);
72
- test.deepEqual(result.partial, { range: '1:100', messages: '1001,1003:1010,1015' });
73
- test.done();
74
- };
75
-
76
- module.exports['ESEARCH: parseEsearchResponse COUNT + PARTIAL combined'] = test => {
77
- const attrs = [
78
- { type: 'ATOM', value: 'COUNT' },
79
- { type: 'ATOM', value: '34201' },
80
- { type: 'ATOM', value: 'PARTIAL' },
81
- [
82
- { type: 'ATOM', value: '1:100' },
83
- { type: 'ATOM', value: '2001,2003:2020' }
84
- ]
85
- ];
86
- const result = parseEsearchResponse(attrs);
87
- test.equal(result.count, 34201);
88
- test.deepEqual(result.partial, { range: '1:100', messages: '2001,2003:2020' });
89
- test.done();
90
- };
91
-
92
- // ── Command-building tests ─────────────────────────────────────────────────
93
-
94
- module.exports['ESEARCH: emits RETURN clause when returnOptions present and server has ESEARCH'] = test => {
95
- const conn = makeConnection({ hasEsearch: true });
96
- let capturedCommand = null;
97
- let capturedAttributes = null;
98
- conn.exec = async (command, attributes) => {
99
- capturedCommand = command;
100
- capturedAttributes = JSON.stringify(attributes);
101
- return { next: () => {} };
102
- };
103
- searchCmd(conn, { seen: false }, { uid: true, returnOptions: ['COUNT'] })
104
- .then(() => {
105
- test.equal(capturedCommand, 'UID SEARCH');
106
- test.ok(capturedAttributes.includes('"RETURN"'), 'should include RETURN atom');
107
- test.ok(capturedAttributes.includes('"COUNT"'), 'should include COUNT in return list');
108
- test.done();
109
- })
110
- .catch(err => test.done(err));
111
- };
112
-
113
- module.exports['ESEARCH: RETURN clause includes PARTIAL range atom'] = test => {
114
- const conn = makeConnection({ hasEsearch: true });
115
- let capturedAttributes = null;
116
- conn.exec = async (command, attributes) => {
117
- capturedAttributes = JSON.stringify(attributes);
118
- return { next: () => {} };
119
- };
120
- searchCmd(conn, { seen: false }, { uid: true, returnOptions: [{ partial: '1:100' }] })
121
- .then(() => {
122
- test.ok(capturedAttributes.includes('"PARTIAL"'), 'should include PARTIAL atom');
123
- test.ok(capturedAttributes.includes('"1:100"'), 'should include range string');
124
- test.done();
125
- })
126
- .catch(err => test.done(err));
127
- };
128
-
129
- module.exports['ESEARCH: no RETURN clause when server lacks ESEARCH capability'] = test => {
130
- const conn = makeConnection({ hasEsearch: false });
131
- let capturedAttributes = null;
132
- conn.exec = async (command, attributes, handlers) => {
133
- capturedAttributes = JSON.stringify(attributes);
134
- // Simulate plain SEARCH response
135
- const searchHandler = handlers && handlers.untagged && handlers.untagged.SEARCH;
136
- if (searchHandler) {
137
- await searchHandler({
138
- attributes: [{ value: '1' }, { value: '2' }, { value: '3' }]
139
- });
140
- }
141
- return { next: () => {} };
142
- };
143
- searchCmd(conn, { seen: false }, { uid: true, returnOptions: ['COUNT', 'ALL'] })
144
- .then(result => {
145
- test.ok(!capturedAttributes.includes('"RETURN"'), 'should NOT include RETURN when no ESEARCH');
146
- test.ok(Array.isArray(result), 'should return number[] when ESEARCH unavailable');
147
- test.deepEqual(result, [1, 2, 3]);
148
- test.done();
149
- })
150
- .catch(err => test.done(err));
151
- };
152
-
153
- module.exports['ESEARCH: parseEsearchResponse skips non-ATOM tokens'] = test => {
154
- const attrs = [
155
- [{ type: 'ATOM', value: 'stray' }], // non-ATOM (Array) token is skipped
156
- { type: 'ATOM', value: 'COUNT' },
157
- { type: 'ATOM', value: '9' }
158
- ];
159
- const result = parseEsearchResponse(attrs);
160
- test.equal(result.count, 9);
161
- test.done();
162
- };
163
-
164
- module.exports['ESEARCH: parseEsearchResponse skips trailing key with no value'] = test => {
165
- const attrs = [{ type: 'ATOM', value: 'COUNT' }]; // dangling key, no value
166
- const result = parseEsearchResponse(attrs);
167
- test.deepEqual(result, {});
168
- test.done();
169
- };
170
-
171
- module.exports['ESEARCH: untagged handler parses ESEARCH response'] = test => {
172
- const conn = makeConnection({ hasEsearch: true });
173
- conn.exec = async (command, attributes, handlers) => {
174
- const esearch = handlers && handlers.untagged && handlers.untagged.ESEARCH;
175
- // leading (TAG "A1") list + UID atom are stripped before parsing
176
- await esearch({
177
- attributes: [
178
- [
179
- { type: 'ATOM', value: 'TAG' },
180
- { type: 'STRING', value: 'A1' }
181
- ],
182
- { type: 'ATOM', value: 'UID' },
183
- { type: 'ATOM', value: 'COUNT' },
184
- { type: 'ATOM', value: '7' }
185
- ]
186
- });
187
- // also exercise the empty-attributes guard
188
- await esearch({ attributes: null });
189
- return { next: () => {} };
190
- };
191
- searchCmd(conn, { seen: false }, { uid: true, returnOptions: ['COUNT'] })
192
- .then(result => {
193
- test.equal(result.count, 7);
194
- test.done();
195
- })
196
- .catch(err => test.done(err));
197
- };
198
-
199
- module.exports['ESEARCH: command path returns false on exec error'] = test => {
200
- const conn = makeConnection({ hasEsearch: true });
201
- conn.exec = async () => {
202
- let err = new Error('search failed');
203
- err.responseStatus = 'NO';
204
- throw err;
205
- };
206
- searchCmd(conn, { seen: false }, { uid: true, returnOptions: ['COUNT'] })
207
- .then(result => {
208
- test.equal(result, false);
209
- test.done();
210
- })
211
- .catch(err => test.done(err));
212
- };
213
-
214
- module.exports['ESEARCH: parseEsearchResponse ignores unknown keywords'] = test => {
215
- // Unknown result keywords must be skipped with their value so the
216
- // key/value stream stays aligned for the entries that follow
217
- const attrs = [
218
- { type: 'ATOM', value: 'RELEVANCY' },
219
- { type: 'ATOM', value: '87' },
220
- { type: 'ATOM', value: 'COUNT' },
221
- { type: 'ATOM', value: '5' }
222
- ];
223
- const result = parseEsearchResponse(attrs);
224
- test.equal(result.count, 5);
225
- test.equal(result.relevancy, undefined, 'unknown keys should not appear in result');
226
- test.done();
227
- };
228
-
229
- module.exports['ESEARCH: parseEsearchResponse parses MODSEQ as BigInt'] = test => {
230
- // RFC 7162: CONDSTORE sessions append MODSEQ to ESEARCH responses when the
231
- // search used a MODSEQ criterion
232
- const attrs = [
233
- { type: 'ATOM', value: 'COUNT' },
234
- { type: 'ATOM', value: '5' },
235
- { type: 'ATOM', value: 'MODSEQ' },
236
- { type: 'ATOM', value: '9007199254740993' }
237
- ];
238
- const result = parseEsearchResponse(attrs);
239
- test.equal(result.count, 5);
240
- test.strictEqual(result.modseq, 9007199254740993n, 'modseq should be an exact BigInt');
241
- test.done();
242
- };
243
-
244
- module.exports['ESEARCH: parseEsearchResponse drops non-numeric MODSEQ'] = test => {
245
- const attrs = [
246
- { type: 'ATOM', value: 'MODSEQ' },
247
- { type: 'ATOM', value: 'bogus' },
248
- { type: 'ATOM', value: 'COUNT' },
249
- { type: 'ATOM', value: '3' }
250
- ];
251
- const result = parseEsearchResponse(attrs);
252
- test.equal(result.modseq, undefined, 'invalid modseq must be dropped');
253
- test.equal(result.count, 3, 'stream must stay aligned after a dropped value');
254
- test.done();
255
- };
256
-
257
- module.exports['ESEARCH: backward compat — no returnOptions returns number[]'] = test => {
258
- const conn = makeConnection({ hasEsearch: true });
259
- conn.exec = async (command, attributes, handlers) => {
260
- const searchHandler = handlers && handlers.untagged && handlers.untagged.SEARCH;
261
- if (searchHandler) {
262
- await searchHandler({
263
- attributes: [{ value: '10' }, { value: '20' }]
264
- });
265
- }
266
- return { next: () => {} };
267
- };
268
- // No returnOptions — must return number[] even if server has ESEARCH
269
- searchCmd(conn, { seen: true }, { uid: true })
270
- .then(result => {
271
- test.ok(Array.isArray(result));
272
- test.deepEqual(result, [10, 20]);
273
- test.done();
274
- })
275
- .catch(err => test.done(err));
276
- };
277
-
278
- // ── imap-flow.js public API fallback test ─────────────────────────────────
279
- module.exports['imap-flow: search() derives ESearchResult when server has no ESEARCH'] = test => {
280
- const client = new ImapFlow({
281
- host: 'imap.example.com',
282
- port: 993,
283
- auth: { user: 'test', pass: 'test' },
284
- logger: false
285
- });
286
-
287
- // Simulate a selected mailbox and no ESEARCH capability
288
- client.mailbox = { path: 'INBOX' };
289
- client.state = client.states.SELECTED;
290
- client.capabilities = new Map(); // no ESEARCH
291
-
292
- // Stub run() to return a sorted number[]
293
- client.run = async () => [10, 20, 30, 40, 50];
294
-
295
- client
296
- .search({ seen: false }, { uid: true, returnOptions: ['COUNT', 'MIN', 'MAX', 'ALL'] })
297
- .then(result => {
298
- test.equal(typeof result, 'object', 'should return object, not array');
299
- test.ok(!Array.isArray(result), 'should not be an array');
300
- test.equal(result.count, 5);
301
- test.equal(result.min, 10);
302
- test.equal(result.max, 50);
303
- // packMessageRange([10,20,30,40,50]) → "10,20,30,40,50" (non-contiguous)
304
- test.ok(typeof result.all === 'string' && result.all.length > 0, 'all should be non-empty compact string');
305
- test.done();
306
- })
307
- .catch(err => test.done(err));
308
- };
309
-
310
- module.exports['imap-flow: search() fallback with empty result set'] = test => {
311
- const client = new ImapFlow({
312
- host: 'imap.example.com',
313
- port: 993,
314
- auth: { user: 'test', pass: 'test' },
315
- logger: false
316
- });
317
- client.mailbox = { path: 'INBOX' };
318
- client.state = client.states.SELECTED;
319
- client.capabilities = new Map();
320
- client.run = async () => [];
321
- client
322
- .search({}, { uid: true, returnOptions: ['COUNT', 'ALL'] })
323
- .then(result => {
324
- test.equal(result.count, 0);
325
- test.equal(result.all, undefined, 'all should be absent for empty result');
326
- test.done();
327
- })
328
- .catch(err => test.done(err));
329
- };