@remit/mailbox-service 0.0.112 → 0.0.114

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 (270) hide show
  1. package/dist/account-credentials.d.ts +83 -0
  2. package/dist/account-credentials.js +104 -0
  3. package/dist/attribute-mapper.d.ts +36 -0
  4. package/dist/attribute-mapper.js +119 -0
  5. package/dist/body-parse.d.ts +28 -0
  6. package/{src/body-parse.ts → dist/body-parse.js} +18 -26
  7. package/dist/body-part-mapper.d.ts +92 -0
  8. package/dist/body-part-mapper.js +301 -0
  9. package/dist/body-sync-queue.d.ts +59 -0
  10. package/dist/body-sync-queue.js +56 -0
  11. package/dist/body-sync-terminal.d.ts +58 -0
  12. package/dist/body-sync-terminal.js +95 -0
  13. package/dist/body-sync.d.ts +351 -0
  14. package/dist/body-sync.js +1204 -0
  15. package/dist/calendar-parts.d.ts +23 -0
  16. package/dist/calendar-parts.js +18 -0
  17. package/dist/classification-backfill.d.ts +91 -0
  18. package/dist/classification-backfill.js +180 -0
  19. package/dist/classify-message.d.ts +31 -0
  20. package/dist/classify-message.js +89 -0
  21. package/dist/connection-factory.d.ts +54 -0
  22. package/dist/connection-factory.js +54 -0
  23. package/dist/connection-test.d.ts +41 -0
  24. package/dist/connection-test.js +152 -0
  25. package/dist/external-move.d.ts +94 -0
  26. package/dist/external-move.js +173 -0
  27. package/dist/filter-operations.d.ts +42 -0
  28. package/dist/filter-operations.js +211 -0
  29. package/dist/filters/filter-config.d.ts +14 -0
  30. package/dist/filters/filter-config.js +10 -0
  31. package/{src/filters/list-id.ts → dist/filters/list-id.d.ts} +2 -16
  32. package/dist/filters/list-id.js +26 -0
  33. package/dist/filters/match.d.ts +69 -0
  34. package/dist/filters/match.js +165 -0
  35. package/dist/filters/pipeline.d.ts +64 -0
  36. package/dist/filters/pipeline.js +100 -0
  37. package/{src/flag-push-terminal.ts → dist/flag-push-terminal.d.ts} +16 -72
  38. package/dist/flag-push-terminal.js +63 -0
  39. package/dist/flag-push.d.ts +69 -0
  40. package/dist/flag-push.js +111 -0
  41. package/dist/flag-queue.d.ts +175 -0
  42. package/dist/flag-queue.js +291 -0
  43. package/dist/folder-operations.d.ts +39 -0
  44. package/dist/folder-operations.js +112 -0
  45. package/dist/folder-placement.d.ts +13 -0
  46. package/dist/folder-placement.js +23 -0
  47. package/dist/heuristics/authenticationResults.d.ts +10 -0
  48. package/dist/heuristics/authenticationResults.js +187 -0
  49. package/dist/heuristics/classifyByHeaders.d.ts +82 -0
  50. package/dist/heuristics/classifyByHeaders.js +365 -0
  51. package/dist/heuristics/classifyPlacement.d.ts +67 -0
  52. package/dist/heuristics/classifyPlacement.js +153 -0
  53. package/dist/heuristics/machineSenders.d.ts +21 -0
  54. package/{src/heuristics/machineSenders.ts → dist/heuristics/machineSenders.js} +34 -40
  55. package/dist/heuristics/senderMismatch.d.ts +81 -0
  56. package/dist/heuristics/senderMismatch.js +192 -0
  57. package/dist/heuristics/socialDomains.d.ts +14 -0
  58. package/{src/heuristics/socialDomains.ts → dist/heuristics/socialDomains.js} +9 -9
  59. package/dist/heuristics/transactionalDomains.d.ts +18 -0
  60. package/{src/heuristics/transactionalDomains.ts → dist/heuristics/transactionalDomains.js} +8 -8
  61. package/dist/imapflow-connection.d.ts +377 -0
  62. package/dist/imapflow-connection.js +1147 -0
  63. package/dist/index.d.ts +63 -0
  64. package/dist/index.js +65 -0
  65. package/dist/list-id-backfill.d.ts +83 -0
  66. package/dist/list-id-backfill.js +132 -0
  67. package/dist/mailbox-cursor-rebuild.d.ts +74 -0
  68. package/dist/mailbox-cursor-rebuild.js +71 -0
  69. package/{src/mailbox-cursor.ts → dist/mailbox-cursor.d.ts} +20 -98
  70. package/dist/mailbox-cursor.js +102 -0
  71. package/dist/mailbox-intent.d.ts +41 -0
  72. package/{src/mailbox-intent.ts → dist/mailbox-intent.js} +38 -64
  73. package/dist/mailbox-management.d.ts +240 -0
  74. package/dist/mailbox-management.js +576 -0
  75. package/dist/mailbox-presence.d.ts +52 -0
  76. package/{src/mailbox-presence.ts → dist/mailbox-presence.js} +19 -30
  77. package/dist/mailbox-queue.d.ts +129 -0
  78. package/dist/mailbox-queue.js +216 -0
  79. package/dist/mailbox-sync.d.ts +93 -0
  80. package/dist/mailbox-sync.js +490 -0
  81. package/dist/mailbox-upstream.d.ts +38 -0
  82. package/{src/mailbox-upstream.ts → dist/mailbox-upstream.js} +37 -47
  83. package/dist/message-category.d.ts +51 -0
  84. package/{src/message-category.ts → dist/message-category.js} +28 -71
  85. package/dist/message-move.d.ts +285 -0
  86. package/dist/message-move.js +858 -0
  87. package/dist/message-parser.d.ts +14 -0
  88. package/dist/message-parser.js +17 -0
  89. package/{src/message-presence.ts → dist/message-presence.d.ts} +1 -13
  90. package/dist/message-presence.js +29 -0
  91. package/dist/message-read.d.ts +51 -0
  92. package/dist/message-read.js +31 -0
  93. package/dist/message-sync.d.ts +345 -0
  94. package/dist/message-sync.js +1358 -0
  95. package/dist/mime-walker.d.ts +102 -0
  96. package/dist/mime-walker.js +198 -0
  97. package/dist/organize-job.d.ts +20 -0
  98. package/dist/organize-job.js +37 -0
  99. package/dist/outbox-attachment-content.d.ts +37 -0
  100. package/dist/outbox-attachment-content.js +50 -0
  101. package/dist/outbox-attachment-filename.d.ts +19 -0
  102. package/dist/outbox-attachment-filename.js +64 -0
  103. package/dist/outbox-attachment.d.ts +148 -0
  104. package/dist/outbox-attachment.js +273 -0
  105. package/dist/outbox-queue.d.ts +73 -0
  106. package/dist/outbox-queue.js +280 -0
  107. package/{src/outbox-status.ts → dist/outbox-status.d.ts} +1 -6
  108. package/dist/outbox-status.js +18 -0
  109. package/dist/ownership.d.ts +24 -0
  110. package/dist/ownership.js +83 -0
  111. package/dist/placement-move-terminal.d.ts +67 -0
  112. package/dist/placement-move-terminal.js +88 -0
  113. package/dist/placement-move.d.ts +83 -0
  114. package/dist/placement-move.js +216 -0
  115. package/{src/placement-settled.ts → dist/placement-settled.d.ts} +13 -68
  116. package/dist/placement-settled.js +75 -0
  117. package/dist/quarantine.d.ts +95 -0
  118. package/dist/quarantine.js +131 -0
  119. package/dist/refusal.d.ts +31 -0
  120. package/dist/refusal.js +3 -0
  121. package/{src/restore-source-placement.ts → dist/restore-source-placement.d.ts} +29 -97
  122. package/dist/restore-source-placement.js +79 -0
  123. package/dist/search-text-backfill.d.ts +15 -0
  124. package/dist/search-text-backfill.js +33 -0
  125. package/dist/snippet.d.ts +61 -0
  126. package/dist/snippet.js +129 -0
  127. package/dist/spam-report.d.ts +86 -0
  128. package/dist/spam-report.js +241 -0
  129. package/dist/sync-warning.d.ts +21 -0
  130. package/dist/sync-warning.js +26 -0
  131. package/dist/sync-watermarks.d.ts +138 -0
  132. package/dist/sync-watermarks.js +205 -0
  133. package/dist/test-helpers/fake-imap.d.ts +151 -0
  134. package/dist/test-helpers/fake-imap.js +508 -0
  135. package/dist/test-helpers/folder-roles.d.ts +34 -0
  136. package/dist/test-helpers/folder-roles.js +47 -0
  137. package/dist/test-helpers/mime-tree-generator.d.ts +82 -0
  138. package/dist/test-helpers/mime-tree-generator.js +329 -0
  139. package/dist/test-helpers/test-store.d.ts +69 -0
  140. package/dist/test-helpers/test-store.js +193 -0
  141. package/dist/text/normalizer.d.ts +20 -0
  142. package/dist/text/normalizer.js +102 -0
  143. package/dist/thread-message-rows.d.ts +69 -0
  144. package/dist/thread-message-rows.js +40 -0
  145. package/dist/thread-root.d.ts +7 -0
  146. package/dist/thread-root.js +25 -0
  147. package/dist/types.d.ts +343 -0
  148. package/dist/types.js +19 -0
  149. package/package.json +86 -88
  150. package/src/account-credentials.ts +0 -210
  151. package/src/attribute-mapper.test.ts +0 -104
  152. package/src/attribute-mapper.ts +0 -148
  153. package/src/body-parse.test.ts +0 -55
  154. package/src/body-part-mapper.property.test.ts +0 -354
  155. package/src/body-part-mapper.ts +0 -410
  156. package/src/body-sync-queue.test.ts +0 -64
  157. package/src/body-sync-queue.ts +0 -131
  158. package/src/body-sync-terminal.test.ts +0 -148
  159. package/src/body-sync-terminal.ts +0 -174
  160. package/src/body-sync.test.ts +0 -925
  161. package/src/body-sync.ts +0 -1891
  162. package/src/calendar-parts.ts +0 -41
  163. package/src/classification-backfill.test.ts +0 -285
  164. package/src/classification-backfill.ts +0 -363
  165. package/src/classify-message.test.ts +0 -142
  166. package/src/classify-message.ts +0 -116
  167. package/src/connection-factory.ts +0 -102
  168. package/src/connection-test.ts +0 -208
  169. package/src/credentials.test.ts +0 -181
  170. package/src/external-move.test.ts +0 -262
  171. package/src/external-move.ts +0 -285
  172. package/src/filter-operations.test.ts +0 -319
  173. package/src/filter-operations.ts +0 -377
  174. package/src/filter-update.test.ts +0 -313
  175. package/src/filters/filter-config.test.ts +0 -87
  176. package/src/filters/filter-config.ts +0 -34
  177. package/src/filters/list-id.test.ts +0 -50
  178. package/src/filters/match.test.ts +0 -392
  179. package/src/filters/match.ts +0 -225
  180. package/src/filters/pipeline.test.ts +0 -199
  181. package/src/filters/pipeline.ts +0 -170
  182. package/src/flag-push.test.ts +0 -112
  183. package/src/flag-push.ts +0 -198
  184. package/src/flag-queue.test.ts +0 -197
  185. package/src/flag-queue.ts +0 -509
  186. package/src/folder-operations.test.ts +0 -273
  187. package/src/folder-operations.ts +0 -217
  188. package/src/folder-placement.ts +0 -56
  189. package/src/forwarded-message.test.ts +0 -122
  190. package/src/heuristics/authenticationResults.test.ts +0 -189
  191. package/src/heuristics/authenticationResults.ts +0 -212
  192. package/src/heuristics/classifyByHeaders.test.ts +0 -889
  193. package/src/heuristics/classifyByHeaders.ts +0 -467
  194. package/src/heuristics/classifyPlacement.test.ts +0 -366
  195. package/src/heuristics/classifyPlacement.ts +0 -211
  196. package/src/heuristics/classifyRealMail.test.ts +0 -242
  197. package/src/heuristics/machineSenders.test.ts +0 -101
  198. package/src/heuristics/senderMismatch.test.ts +0 -346
  199. package/src/heuristics/senderMismatch.ts +0 -259
  200. package/src/imapflow-connect-retry.test.ts +0 -97
  201. package/src/imapflow-connection.test.ts +0 -503
  202. package/src/imapflow-connection.ts +0 -1476
  203. package/src/index.ts +0 -397
  204. package/src/list-id-backfill.test.ts +0 -207
  205. package/src/list-id-backfill.ts +0 -274
  206. package/src/mailbox-cursor-rebuild.test.ts +0 -171
  207. package/src/mailbox-cursor-rebuild.ts +0 -146
  208. package/src/mailbox-cursor.test.ts +0 -141
  209. package/src/mailbox-delete-intent.test.ts +0 -119
  210. package/src/mailbox-management.test.ts +0 -243
  211. package/src/mailbox-management.ts +0 -838
  212. package/src/mailbox-presence.test.ts +0 -74
  213. package/src/mailbox-queue.ts +0 -397
  214. package/src/mailbox-rename-intent.test.ts +0 -220
  215. package/src/mailbox-rename-subtree.test.ts +0 -287
  216. package/src/mailbox-sync.test.ts +0 -286
  217. package/src/mailbox-sync.ts +0 -724
  218. package/src/message-move.test.ts +0 -726
  219. package/src/message-move.ts +0 -1518
  220. package/src/message-parser.ts +0 -26
  221. package/src/message-presence.test.ts +0 -50
  222. package/src/message-read.ts +0 -87
  223. package/src/message-save-retry.test.ts +0 -90
  224. package/src/message-sync-flags.test.ts +0 -167
  225. package/src/message-sync.test.ts +0 -762
  226. package/src/message-sync.ts +0 -2133
  227. package/src/mime-walker.test.ts +0 -311
  228. package/src/mime-walker.ts +0 -306
  229. package/src/organize-job.ts +0 -93
  230. package/src/outbox-attachment-content.test.ts +0 -153
  231. package/src/outbox-attachment-content.ts +0 -104
  232. package/src/outbox-attachment-filename.test.ts +0 -132
  233. package/src/outbox-attachment-filename.ts +0 -76
  234. package/src/outbox-attachment.test.ts +0 -626
  235. package/src/outbox-attachment.ts +0 -536
  236. package/src/outbox-failed-recovery.test.ts +0 -299
  237. package/src/outbox-queue.test.ts +0 -315
  238. package/src/outbox-queue.ts +0 -525
  239. package/src/ownership.ts +0 -140
  240. package/src/placement-move-terminal.ts +0 -145
  241. package/src/placement-move.ts +0 -363
  242. package/src/placement-settled.test.ts +0 -147
  243. package/src/placement.test.ts +0 -404
  244. package/src/quarantine.test.ts +0 -133
  245. package/src/quarantine.ts +0 -229
  246. package/src/refusal.ts +0 -29
  247. package/src/snippet.test.ts +0 -213
  248. package/src/snippet.ts +0 -152
  249. package/src/spam-report.test.ts +0 -329
  250. package/src/spam-report.ts +0 -357
  251. package/src/sync-warning.test.ts +0 -213
  252. package/src/sync-warning.ts +0 -56
  253. package/src/sync-watermarks.test.ts +0 -409
  254. package/src/sync-watermarks.ts +0 -319
  255. package/src/system-flag-wire-format.test.ts +0 -77
  256. package/src/test-helpers/fake-imap.ts +0 -690
  257. package/src/test-helpers/folder-roles.ts +0 -66
  258. package/src/test-helpers/mime-tree-generator.ts +0 -461
  259. package/src/test-helpers/test-queue.ts +0 -56
  260. package/src/test-helpers/test-store.ts +0 -299
  261. package/src/text/normalizer.test.ts +0 -223
  262. package/src/text/normalizer.ts +0 -140
  263. package/src/thread-message-rows.ts +0 -77
  264. package/src/thread-root.test.ts +0 -29
  265. package/src/thread-root.ts +0 -51
  266. package/src/types/mailparser-augment.d.ts +0 -23
  267. package/src/types/natural-porter-stemmer.d.ts +0 -50
  268. package/src/types/stopword.d.ts +0 -69
  269. package/src/types.ts +0 -371
  270. package/tsconfig.json +0 -8
@@ -1,762 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import { afterEach, beforeEach, describe, it } from "node:test";
3
- import type { AccountItem, MailboxItem } from "@remit/data-ports";
4
- import { deriveAddressId, deriveCopyMessageId } from "@remit/data-ports/id";
5
- import {
6
- MailboxCursorState,
7
- MessageKeywordFlag,
8
- MessageSystemFlag,
9
- } from "@remit/domain-enums";
10
- import { FlagPushService } from "./flag-push.js";
11
- import { FlagQueueService } from "./flag-queue.js";
12
- import { MessageMoveService } from "./message-move.js";
13
- import { MessageSyncService } from "./message-sync.js";
14
- import { QuarantineService } from "./quarantine.js";
15
- import { SyncWarningService } from "./sync-warning.js";
16
- import {
17
- eml,
18
- type FakeImapConnection,
19
- FakeImapServer,
20
- } from "./test-helpers/fake-imap.js";
21
- import {
22
- captureQueue,
23
- messageAtUid,
24
- openStore,
25
- type QueueCapture,
26
- type Store,
27
- seedAccount,
28
- seedMailbox,
29
- silentLog,
30
- } from "./test-helpers/test-store.js";
31
-
32
- const SPAMMER = eml({
33
- From: "Pharma Deals <sales@pharma.example>",
34
- To: "victim@ischen.nl",
35
- "Message-ID": "<deal@pharma.example>",
36
- });
37
-
38
- let store: Store;
39
- let capture: QueueCapture;
40
- let imap: FakeImapServer;
41
- let connection: FakeImapConnection;
42
- let account: AccountItem;
43
- let inbox: MailboxItem;
44
- let junk: MailboxItem;
45
- let trash: MailboxItem;
46
- let receipts: MailboxItem;
47
- let allMail: MailboxItem;
48
- let logMark: number;
49
-
50
- beforeEach(async () => {
51
- capture = captureQueue();
52
- store = await openStore();
53
- imap = new FakeImapServer();
54
- account = await seedAccount(store);
55
- inbox = await seedMailbox(store, account, "INBOX");
56
- junk = await seedMailbox(store, account, "INBOX/Rubbish", {
57
- specialUse: "Junk",
58
- });
59
- trash = await seedMailbox(store, account, "Trash", { specialUse: "Trash" });
60
- receipts = await seedMailbox(store, account, "Receipts");
61
- allMail = await seedMailbox(store, account, "[Gmail]/All Mail", {
62
- specialUse: "All",
63
- });
64
- for (const mailbox of [inbox, junk, trash, receipts, allMail]) {
65
- imap.addFolder(mailbox.fullPath);
66
- }
67
- connection = imap.connection();
68
- logMark = 0;
69
- });
70
-
71
- afterEach(async () => {
72
- capture.restore();
73
- await store.close();
74
- });
75
-
76
- const flagPushService = () =>
77
- new FlagPushService({
78
- markerService: store.flagPush,
79
- sqsQueueUrl: "https://sqs.test/flags",
80
- });
81
-
82
- const syncService = () =>
83
- new MessageSyncService(
84
- { getConnection: () => connection, close: async () => {} },
85
- store.mailbox,
86
- store.mailboxSpecialUse,
87
- store.message,
88
- store.envelope,
89
- store.address,
90
- store.threadMessage,
91
- new SyncWarningService(store.syncWarning),
92
- silentLog,
93
- store.flagPush,
94
- store.messageFlag,
95
- new QuarantineService(
96
- store.quarantine,
97
- store.mailboxSpecialUse,
98
- "sha-test",
99
- silentLog,
100
- ),
101
- );
102
-
103
- const userMoves = () =>
104
- new MessageMoveService({
105
- messageService: store.message,
106
- mailboxService: store.mailbox,
107
- mailboxSpecialUseService: store.mailboxSpecialUse,
108
- threadMessageService: store.threadMessage,
109
- addressService: store.address,
110
- sqsQueueUrl: "http://localhost:9324/000000000000/remit-messages.fifo",
111
- });
112
-
113
- const flagQueue = () =>
114
- new FlagQueueService({
115
- messageFlagService: store.messageFlag,
116
- messageService: store.message,
117
- threadMessageService: store.threadMessage,
118
- flagPushService: flagPushService(),
119
- });
120
-
121
- const sync = (mailbox: MailboxItem, batchSize = 50) =>
122
- syncService().syncMessages(
123
- mailbox.mailboxId,
124
- account.accountId,
125
- account.accountConfigId,
126
- batchSize,
127
- );
128
-
129
- const settle = async (mailbox: MailboxItem) => {
130
- for (;;) {
131
- const result = await sync(mailbox);
132
- if (!result.hasMore && result.syncedCount === 0) return;
133
- }
134
- };
135
-
136
- const stored = (mailbox: MailboxItem) =>
137
- store.mailbox.get(account.accountId, mailbox.mailboxId);
138
-
139
- const threadOf = async (messageId: string) =>
140
- (
141
- await store.threadMessage.findAllByMessageId(
142
- account.accountConfigId,
143
- messageId,
144
- )
145
- )[0];
146
-
147
- const address = (email: string) =>
148
- store.address.getAddress(
149
- account.accountConfigId,
150
- deriveAddressId(account.accountConfigId, email),
151
- );
152
-
153
- const sent = (verb: "SEARCH" | "FETCH", marker = "") =>
154
- imap.log
155
- .slice(logMark)
156
- .filter((line) => line.startsWith(`${verb} `) && line.includes(marker))
157
- .length;
158
-
159
- const serverMove = async (from: MailboxItem, uid: number, to: MailboxItem) => {
160
- const otherClient = imap.connection();
161
- await otherClient.openBox(from.fullPath);
162
- const { uidMap } = await otherClient.moveMessages([uid], to.fullPath);
163
- const moved = uidMap.get(uid);
164
- if (moved === undefined) throw new Error(`no uid ${uid} in ${from.fullPath}`);
165
- return moved;
166
- };
167
-
168
- const arrive = async (mailbox: MailboxItem, raw: Buffer = SPAMMER) => {
169
- const uid = imap.deliver(mailbox.fullPath, raw);
170
- await sync(mailbox);
171
- return { uid, message: await messageAtUid(store, mailbox, uid) };
172
- };
173
-
174
- describe("harvesting envelope display names", () => {
175
- const spoofed = eml({
176
- From: '"matthijs@ischen.nl" <aramirez@secresaludguaviare.gov.co>',
177
- });
178
-
179
- it("drops a name that is an address the sender does not own, wherever it lands", async () => {
180
- const { message } = await arrive(inbox, spoofed);
181
-
182
- const sender = await address("aramirez@secresaludguaviare.gov.co");
183
- assert.equal(sender.displayName ?? "", "");
184
- assert.equal(
185
- sender.normalizedCompound,
186
- "aramirez@secresaludguaviare.gov.co",
187
- );
188
- const data = await store.envelope.getMessageData(message.messageId);
189
- assert.ok(data.envelopeAddress.length > 0);
190
- for (const entry of data.envelopeAddress) {
191
- if (entry.normalizedEmail !== "aramirez@secresaludguaviare.gov.co")
192
- continue;
193
- assert.equal(entry.displayName ?? "", "");
194
- }
195
- const thread = await threadOf(message.messageId);
196
- assert.equal(thread.fromName, undefined);
197
- assert.equal(thread.fromEmail, "aramirez@secresaludguaviare.gov.co");
198
- });
199
-
200
- it("keeps what a name says besides the address it claims", async () => {
201
- const { message } = await arrive(
202
- inbox,
203
- eml({ From: '"Support <matthijs@ischen.nl>" <aramirez@example.gov>' }),
204
- );
205
-
206
- assert.equal((await threadOf(message.messageId)).fromName, "Support");
207
- assert.equal(
208
- (await address("aramirez@example.gov")).displayName,
209
- "Support",
210
- );
211
- });
212
-
213
- it("harvests every envelope role", async () => {
214
- await arrive(
215
- inbox,
216
- eml({
217
- From: "Someone <from@example.com>",
218
- To: "Someone <to@example.com>",
219
- Cc: "Someone <cc@example.com>",
220
- "Reply-To": "Someone <reply-to@example.com>",
221
- }),
222
- );
223
-
224
- for (const email of [
225
- "from@example.com",
226
- "to@example.com",
227
- "cc@example.com",
228
- "reply-to@example.com",
229
- ]) {
230
- assert.equal((await address(email)).displayName, "Someone");
231
- }
232
- });
233
- });
234
-
235
- describe("what a folder says about the senders on its mail", () => {
236
- it("harvests the senders of ordinary mail as correspondents", async () => {
237
- await arrive(inbox);
238
-
239
- assert.equal(
240
- (await address("sales@pharma.example")).flags.junkOnly,
241
- undefined,
242
- );
243
- });
244
-
245
- it("withholds the senders of mail in the appointed Junk folder", async () => {
246
- const { message } = await arrive(junk);
247
-
248
- assert.equal(
249
- (await address("sales@pharma.example")).flags.junkOnly?.value,
250
- true,
251
- );
252
- const data = await store.envelope.getMessageData(message.messageId);
253
- assert.ok(
254
- data.envelopeAddress.some(
255
- (entry) => entry.normalizedEmail === "sales@pharma.example",
256
- ),
257
- );
258
- });
259
-
260
- it("keeps mail in Trash from deciding either way", async () => {
261
- await arrive(trash);
262
-
263
- assert.equal(
264
- (await address("sales@pharma.example")).flags.junkOnly,
265
- undefined,
266
- );
267
- });
268
- });
269
-
270
- describe("a message the database already holds, sighted in another folder", () => {
271
- it("follows another client's move into Junk, with the new folder's UID", async () => {
272
- const { uid, message } = await arrive(inbox);
273
- const moved = await serverMove(inbox, uid, junk);
274
-
275
- const result = await sync(junk);
276
-
277
- const row = await store.message.get(message.messageId);
278
- assert.equal(row.mailboxId, junk.mailboxId);
279
- assert.equal(row.uid, moved);
280
- const thread = await threadOf(message.messageId);
281
- assert.equal(thread.mailboxId, junk.mailboxId);
282
- assert.equal(thread.uid, moved);
283
- assert.deepEqual(result.syncedMessageIds, [message.messageId]);
284
- assert.equal(
285
- (await address("sales@pharma.example")).flags.junkOnly?.value,
286
- true,
287
- );
288
- });
289
-
290
- it("follows the rescue back out of Junk and restores the sender", async () => {
291
- const { uid, message } = await arrive(junk);
292
- await serverMove(junk, uid, inbox);
293
-
294
- await sync(inbox);
295
-
296
- assert.equal(
297
- (await store.message.get(message.messageId)).mailboxId,
298
- inbox.mailboxId,
299
- );
300
- assert.equal(
301
- (await address("sales@pharma.example")).flags.junkOnly,
302
- undefined,
303
- );
304
- });
305
-
306
- it("marks the listing row deleted in Trash and clears it on the way out", async () => {
307
- imap.condstore = false;
308
- const { uid, message } = await arrive(inbox);
309
- const inTrash = await serverMove(inbox, uid, trash);
310
- await sync(trash);
311
- assert.equal((await threadOf(message.messageId)).isDeleted, true);
312
-
313
- await serverMove(trash, inTrash, inbox);
314
- await sync(inbox);
315
-
316
- assert.equal((await threadOf(message.messageId)).isDeleted, false);
317
- });
318
-
319
- it("leaves a labelled message in the folder that still holds it", async () => {
320
- const { message } = await arrive(inbox);
321
- imap.deliver("Receipts", SPAMMER);
322
-
323
- const result = await sync(receipts);
324
-
325
- assert.equal(
326
- (await store.message.get(message.messageId)).mailboxId,
327
- inbox.mailboxId,
328
- );
329
- assert.equal(
330
- (await threadOf(message.messageId)).mailboxId,
331
- inbox.mailboxId,
332
- );
333
- assert.equal(result.cursorStalled, false);
334
- assert.equal((await stored(receipts)).highWaterMarkUid, 1);
335
- });
336
-
337
- it("holds the watermark when the folder it came from cannot be asked", async () => {
338
- const { uid, message } = await arrive(inbox);
339
- await serverMove(inbox, uid, junk);
340
- imap.removeFolder("INBOX");
341
-
342
- await sync(junk);
343
-
344
- assert.equal(
345
- (await store.message.get(message.messageId)).mailboxId,
346
- inbox.mailboxId,
347
- );
348
- assert.equal((await stored(junk)).highWaterMarkUid, 0);
349
- });
350
-
351
- it("declines a sighting in a folder that copies every message", async () => {
352
- const { message } = await arrive(inbox);
353
- imap.deliver("[Gmail]/All Mail", SPAMMER);
354
-
355
- await sync(allMail);
356
-
357
- assert.equal(
358
- (await store.message.get(message.messageId)).mailboxId,
359
- inbox.mailboxId,
360
- );
361
- });
362
-
363
- it("declines a sighting of a copy the user put in that folder", async () => {
364
- const { message } = await arrive(inbox);
365
- await userMoves().copyMessage(
366
- account.accountConfigId,
367
- message.messageId,
368
- junk.mailboxId,
369
- account.accountId,
370
- );
371
- const copied = imap.deliver("INBOX/Rubbish", SPAMMER);
372
- await store.message.updateUid(
373
- deriveCopyMessageId(message.messageId, junk.mailboxId),
374
- copied,
375
- junk.mailboxId,
376
- );
377
-
378
- await sync(junk);
379
-
380
- assert.equal(
381
- (await store.message.get(message.messageId)).mailboxId,
382
- inbox.mailboxId,
383
- );
384
- });
385
-
386
- it("follows another client's move into a folder already on CHANGEDSINCE", async () => {
387
- const { uid, message } = await arrive(inbox);
388
- await settle(junk);
389
- await serverMove(inbox, uid, junk);
390
-
391
- await sync(junk);
392
-
393
- assert.equal(
394
- (await store.message.get(message.messageId)).mailboxId,
395
- junk.mailboxId,
396
- );
397
- });
398
-
399
- it("holds a row whose own move has not settled", async () => {
400
- const { uid, message } = await arrive(inbox);
401
- await userMoves().moveMessage(
402
- account.accountConfigId,
403
- message.messageId,
404
- receipts.mailboxId,
405
- account.accountId,
406
- );
407
- await serverMove(inbox, uid, junk);
408
-
409
- await sync(junk);
410
-
411
- const row = await store.message.get(message.messageId);
412
- assert.equal(row.mailboxId, receipts.mailboxId);
413
- assert.equal(row.status, "moving");
414
- });
415
- });
416
-
417
- describe("CHANGEDSINCE picks up what another client changed", () => {
418
- const seeded = async (count = 1) => {
419
- const uids: number[] = [];
420
- for (let i = 0; i < count; i++) {
421
- uids.push(
422
- imap.deliver("INBOX", eml({ "Message-ID": `<m${i}@example.com>` })),
423
- );
424
- }
425
- await settle(inbox);
426
- logMark = imap.log.length;
427
- const messages = await Promise.all(
428
- uids.map((uid) => messageAtUid(store, inbox, uid)),
429
- );
430
- return { uids, messages };
431
- };
432
-
433
- it("applies a read-state change without enumerating the folder", async () => {
434
- const {
435
- uids: [uid],
436
- messages: [message],
437
- } = await seeded();
438
- imap.store("INBOX", [uid], [MessageSystemFlag.Seen]);
439
-
440
- await sync(inbox);
441
-
442
- assert.equal(sent("SEARCH"), 0);
443
- assert.equal((await threadOf(message.messageId)).isRead, true);
444
- assert.ok(
445
- await store.messageFlag.hasFlag(
446
- message.messageId,
447
- MessageSystemFlag.Seen,
448
- ),
449
- );
450
- });
451
-
452
- it("stars with the default colour and unstars clearing it", async () => {
453
- const {
454
- uids: [uid],
455
- messages: [message],
456
- } = await seeded();
457
- imap.store("INBOX", [uid], [MessageSystemFlag.Flagged]);
458
- await sync(inbox);
459
- let thread = await threadOf(message.messageId);
460
- assert.equal(thread.hasStars, true);
461
- assert.equal(thread.star, "yellow");
462
-
463
- imap.store("INBOX", [uid], []);
464
- await sync(inbox);
465
-
466
- thread = await threadOf(message.messageId);
467
- assert.equal(thread.hasStars, false);
468
- assert.equal(thread.star, "none");
469
- assert.equal(
470
- await store.messageFlag.hasFlag(
471
- message.messageId,
472
- MessageSystemFlag.Flagged,
473
- ),
474
- false,
475
- );
476
- });
477
-
478
- it("keeps a colour the user chose when the server re-flags", async () => {
479
- const {
480
- uids: [uid],
481
- messages: [message],
482
- } = await seeded();
483
- await flagQueue().updateFlags(
484
- account.accountConfigId,
485
- message.messageId,
486
- account.accountId,
487
- { isStarred: false, starColor: "purple" },
488
- );
489
- await store.flagPush.delete(message.messageId, MessageSystemFlag.Flagged);
490
- imap.store("INBOX", [uid], [MessageSystemFlag.Flagged]);
491
-
492
- await sync(inbox);
493
-
494
- assert.equal((await threadOf(message.messageId)).star, "purple");
495
- });
496
-
497
- it("leaves a field alone while the user's flip is still owed to the server", async () => {
498
- const {
499
- uids: [uid],
500
- messages: [message],
501
- } = await seeded();
502
- const flags = new FlagQueueService({
503
- messageFlagService: store.messageFlag,
504
- messageService: store.message,
505
- threadMessageService: store.threadMessage,
506
- flagPushService: flagPushService(),
507
- });
508
- await flags.markAsRead(
509
- account.accountConfigId,
510
- message.messageId,
511
- account.accountId,
512
- );
513
- imap.store("INBOX", [uid], [MessageSystemFlag.Flagged]);
514
-
515
- await sync(inbox);
516
-
517
- const thread = await threadOf(message.messageId);
518
- assert.equal(thread.isRead, true);
519
- assert.equal(thread.hasStars, true);
520
- });
521
-
522
- it("leaves the user's next flip a real flip", async () => {
523
- const {
524
- uids: [uid],
525
- messages: [message],
526
- } = await seeded();
527
- imap.store("INBOX", [uid], [MessageSystemFlag.Seen]);
528
- await sync(inbox);
529
-
530
- await new FlagQueueService({
531
- messageFlagService: store.messageFlag,
532
- messageService: store.message,
533
- threadMessageService: store.threadMessage,
534
- flagPushService: flagPushService(),
535
- }).markAsUnread(
536
- account.accountConfigId,
537
- message.messageId,
538
- account.accountId,
539
- );
540
-
541
- assert.equal(
542
- (await store.flagPush.find(message.messageId, MessageSystemFlag.Seen))
543
- ?.operation,
544
- "remove",
545
- );
546
- assert.equal((await threadOf(message.messageId)).isRead, false);
547
- });
548
-
549
- it("mirrors $Junk and $NotJunk as message state, never as sender standing", async () => {
550
- const {
551
- uids: [first, second],
552
- messages: [junked, cleared],
553
- } = await seeded(2);
554
- const before = await address("sender@example.com");
555
- imap.store("INBOX", [first], [MessageKeywordFlag.Junk]);
556
- imap.store("INBOX", [second], [MessageKeywordFlag.NotJunk]);
557
-
558
- await sync(inbox);
559
-
560
- assert.ok(
561
- await store.messageFlag.hasFlag(
562
- junked.messageId,
563
- MessageKeywordFlag.Junk,
564
- ),
565
- );
566
- assert.ok(
567
- await store.messageFlag.hasFlag(
568
- cleared.messageId,
569
- MessageKeywordFlag.NotJunk,
570
- ),
571
- );
572
- assert.deepEqual((await address("sender@example.com")).flags, before.flags);
573
- });
574
-
575
- it("discovers new mail and hands it to body sync", async () => {
576
- await seeded();
577
- const uid = imap.deliver(
578
- "INBOX",
579
- eml({ "Message-ID": "<new@example.com>" }),
580
- );
581
-
582
- const result = await sync(inbox);
583
-
584
- assert.equal(sent("SEARCH"), 0);
585
- assert.deepEqual(
586
- result.syncedMessages.map((message) => message.uid),
587
- [uid],
588
- );
589
- assert.equal((await stored(inbox)).highWaterMarkUid, uid);
590
- assert.equal(
591
- (await stored(inbox)).highestModseq,
592
- imap.highestModseq().toString(),
593
- );
594
- });
595
-
596
- it("splits one bulk change across rounds without losing its tail", async () => {
597
- const { uids, messages } = await seeded(60);
598
- imap.store("INBOX", uids, [MessageSystemFlag.Seen]);
599
-
600
- const first = await sync(inbox, 50);
601
- assert.equal(first.hasMore, true);
602
- imap.store("INBOX", uids, [
603
- MessageSystemFlag.Seen,
604
- MessageSystemFlag.Flagged,
605
- ]);
606
- await settle(inbox);
607
-
608
- for (const message of messages) {
609
- const thread = await threadOf(message.messageId);
610
- assert.equal(thread.isRead, true);
611
- assert.equal(thread.hasStars, true);
612
- }
613
- assert.equal(
614
- (await stored(inbox)).highestModseq,
615
- imap.highestModseq().toString(),
616
- );
617
- });
618
- });
619
-
620
- describe("a server without CONDSTORE", () => {
621
- it("enumerates every round and never seeds a mod-sequence", async () => {
622
- imap.condstore = false;
623
- imap.deliver("INBOX", eml({}));
624
- await settle(inbox);
625
-
626
- await sync(inbox);
627
-
628
- assert.equal(sent("FETCH", "CHANGEDSINCE"), 0);
629
- assert.ok(sent("SEARCH") > 0);
630
- assert.equal((await stored(inbox)).highestModseq, "0");
631
- });
632
-
633
- it("seeds the mod-sequence only once enumeration has nothing left", async () => {
634
- imap.deliver("INBOX", eml({ "Message-ID": "<a@example.com>" }));
635
- imap.deliver("INBOX", eml({ "Message-ID": "<b@example.com>" }));
636
-
637
- await sync(inbox, 1);
638
- assert.equal((await stored(inbox)).highestModseq, "0");
639
-
640
- await settle(inbox);
641
- assert.equal(
642
- (await stored(inbox)).highestModseq,
643
- imap.highestModseq().toString(),
644
- );
645
- });
646
- });
647
-
648
- describe("a UIDVALIDITY change", () => {
649
- it("trips the cursor, then rebuilds on the new axis and reseeds", async () => {
650
- imap.deliver("INBOX", eml({ "Message-ID": "<a@example.com>" }));
651
- await settle(inbox);
652
- imap.renumber("INBOX", 777);
653
-
654
- const tripped = await sync(inbox);
655
- assert.deepEqual(tripped.syncedMessages, []);
656
- assert.equal(
657
- (await stored(inbox)).cursorState,
658
- MailboxCursorState.cursor_invalid,
659
- );
660
-
661
- await sync(inbox);
662
-
663
- const rebuilt = await stored(inbox);
664
- assert.equal(rebuilt.cursorState, MailboxCursorState.normal);
665
- assert.equal(rebuilt.uidValidity, 777);
666
- assert.equal(rebuilt.highestModseq, imap.highestModseq().toString());
667
- });
668
-
669
- it("withholds the seed and the watermark when the rebuild missed a UID", async () => {
670
- for (const id of ["a", "b", "c"]) {
671
- imap.deliver("INBOX", eml({ "Message-ID": `<${id}@example.com>` }));
672
- }
673
- await settle(inbox);
674
- imap.renumber("INBOX", 777);
675
- const arrived = imap.deliver(
676
- "INBOX",
677
- eml({ "Message-ID": "<d@example.com>" }),
678
- );
679
- await sync(inbox);
680
- const fetch = connection.fetchMessages;
681
- connection.fetchMessages = async (uids) =>
682
- (await fetch(uids)).filter((row) => row.uid !== arrived);
683
-
684
- await sync(inbox);
685
-
686
- const rebuilt = await stored(inbox);
687
- assert.equal(rebuilt.highestModseq, "0");
688
- assert.ok(rebuilt.highWaterMarkUid < arrived);
689
- });
690
- });
691
-
692
- describe("a FETCH that leaves a UID unaccounted for", () => {
693
- it("holds the watermark below a row the FETCH dropped", async () => {
694
- for (const id of ["a", "b", "c"]) {
695
- imap.deliver("INBOX", eml({ "Message-ID": `<${id}@example.com>` }));
696
- }
697
- const fetch = connection.fetchMessages;
698
- connection.fetchMessages = async (uids) =>
699
- (await fetch(uids)).filter((row) => row.uid !== 2);
700
-
701
- await sync(inbox);
702
-
703
- const mailbox = await stored(inbox);
704
- assert.equal(mailbox.highWaterMarkUid, 1);
705
- assert.equal(mailbox.highestModseq, "0");
706
- });
707
-
708
- it("holds a row with no ENVELOPE for retry rather than setting it aside", async () => {
709
- imap.deliver("INBOX", eml({}));
710
- const fetch = connection.fetchMessages;
711
- connection.fetchMessages = async (uids) =>
712
- (await fetch(uids)).map((row) => ({ ...row, envelope: undefined }));
713
-
714
- const result = await sync(inbox);
715
-
716
- assert.equal(result.syncedCount, 0);
717
- assert.equal((await stored(inbox)).highWaterMarkUid, 0);
718
- assert.deepEqual(
719
- await store.quarantine.listByAccountConfigId(account.accountConfigId),
720
- [],
721
- );
722
- });
723
-
724
- it("passes over a UID already quarantined without fetching it", async () => {
725
- const first = imap.deliver(
726
- "INBOX",
727
- eml({ "Message-ID": "<a@example.com>" }),
728
- );
729
- const second = imap.deliver(
730
- "INBOX",
731
- eml({ "Message-ID": "<b@example.com>" }),
732
- );
733
- await new QuarantineService(
734
- store.quarantine,
735
- store.mailboxSpecialUse,
736
- "sha-test",
737
- silentLog,
738
- ).record(
739
- {
740
- accountId: account.accountId,
741
- accountConfigId: account.accountConfigId,
742
- mailboxId: inbox.mailboxId,
743
- mailboxPath: "INBOX",
744
- uidValidity: 7,
745
- attempts: 3,
746
- },
747
- first,
748
- { stage: "BodyParse", code: "UnreadableBody", message: "no" },
749
- );
750
- const asked: number[][] = [];
751
- const fetch = connection.fetchMessages;
752
- connection.fetchMessages = async (uids) => {
753
- asked.push(uids);
754
- return fetch(uids);
755
- };
756
-
757
- await sync(inbox);
758
-
759
- assert.deepEqual(asked, [[second]]);
760
- assert.equal((await stored(inbox)).highWaterMarkUid, second);
761
- });
762
- });