@remit/imap-worker 0.0.90 → 0.0.91

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 (172) hide show
  1. package/dist/account-check.d.ts +27 -0
  2. package/dist/account-check.js +70 -0
  3. package/dist/body-sync-gate.d.ts +12 -0
  4. package/dist/body-sync-gate.js +53 -0
  5. package/dist/cli.d.ts +2 -0
  6. package/dist/cli.js +193 -0
  7. package/dist/config-env.d.ts +5 -0
  8. package/dist/config-env.js +37 -0
  9. package/dist/connection-pool.d.ts +22 -0
  10. package/dist/connection-pool.js +97 -0
  11. package/dist/connection-scope.d.ts +8 -0
  12. package/dist/connection-scope.js +10 -0
  13. package/dist/context.d.ts +46 -0
  14. package/dist/context.js +15 -0
  15. package/dist/e2e-processor-shim.d.ts +2 -0
  16. package/dist/e2e-processor-shim.js +181 -0
  17. package/dist/emit.d.ts +15 -0
  18. package/dist/emit.js +76 -0
  19. package/dist/events.d.ts +203 -0
  20. package/dist/events.js +1 -0
  21. package/dist/failure-kind.d.ts +10 -0
  22. package/{src/failure-kind.ts → dist/failure-kind.js} +6 -6
  23. package/dist/handlers/append-sent-message.d.ts +3 -0
  24. package/dist/handlers/append-sent-message.js +152 -0
  25. package/dist/handlers/delete-account-objects.d.ts +3 -0
  26. package/dist/handlers/delete-account-objects.js +35 -0
  27. package/dist/handlers/empty-trash.d.ts +14 -0
  28. package/dist/handlers/empty-trash.js +217 -0
  29. package/dist/handlers/flag-push.d.ts +38 -0
  30. package/dist/handlers/flag-push.js +195 -0
  31. package/dist/handlers/imported-folder.d.ts +4 -0
  32. package/dist/handlers/imported-folder.js +23 -0
  33. package/dist/handlers/mailbox-management.d.ts +6 -0
  34. package/dist/handlers/mailbox-management.js +294 -0
  35. package/dist/handlers/mailbox-sync-order.d.ts +21 -0
  36. package/dist/handlers/mailbox-sync-order.js +50 -0
  37. package/dist/handlers/message-copy.d.ts +40 -0
  38. package/dist/handlers/message-copy.js +371 -0
  39. package/dist/handlers/message-delete-terminal.d.ts +60 -0
  40. package/dist/handlers/message-delete-terminal.js +77 -0
  41. package/dist/handlers/message-delete.d.ts +57 -0
  42. package/dist/handlers/message-delete.js +520 -0
  43. package/{src/handlers/message-move-terminal.ts → dist/handlers/message-move-terminal.d.ts} +18 -105
  44. package/dist/handlers/message-move-terminal.js +94 -0
  45. package/dist/handlers/message-move.d.ts +146 -0
  46. package/dist/handlers/message-move.js +490 -0
  47. package/dist/handlers/placement-move-push.d.ts +62 -0
  48. package/dist/handlers/placement-move-push.js +307 -0
  49. package/dist/handlers/probe-message-id.d.ts +2 -0
  50. package/dist/handlers/probe-message-id.js +6 -0
  51. package/dist/handlers/sync-mailboxes.d.ts +45 -0
  52. package/dist/handlers/sync-mailboxes.js +233 -0
  53. package/dist/handlers/sync-message-body.d.ts +23 -0
  54. package/dist/handlers/sync-message-body.js +182 -0
  55. package/dist/handlers/sync-messages.d.ts +29 -0
  56. package/dist/handlers/sync-messages.js +285 -0
  57. package/dist/index.d.ts +9 -0
  58. package/dist/index.js +67 -0
  59. package/dist/lambda-context.d.ts +8 -0
  60. package/dist/lambda-context.js +33 -0
  61. package/dist/mailbox-row.d.ts +9 -0
  62. package/dist/mailbox-row.js +6 -0
  63. package/dist/poller.d.ts +1 -0
  64. package/dist/poller.js +51 -0
  65. package/dist/process.d.ts +6 -0
  66. package/dist/process.js +3 -0
  67. package/dist/processor.d.ts +3 -0
  68. package/dist/processor.js +44 -0
  69. package/dist/queues.d.ts +10 -0
  70. package/dist/queues.js +1 -0
  71. package/dist/s3-account-objects.d.ts +3 -0
  72. package/dist/s3-account-objects.js +26 -0
  73. package/dist/scheduler/attachment-sweep.d.ts +11 -0
  74. package/dist/scheduler/attachment-sweep.js +23 -0
  75. package/dist/scheduler/calendar-subscriptions.d.ts +26 -0
  76. package/dist/scheduler/calendar-subscriptions.js +35 -0
  77. package/dist/scheduler/config.d.ts +27 -0
  78. package/{src/scheduler/config.ts → dist/scheduler/config.js} +7 -26
  79. package/{src/scheduler/decide-due.ts → dist/scheduler/decide-due.d.ts} +3 -12
  80. package/dist/scheduler/decide-due.js +17 -0
  81. package/dist/scheduler/handler.d.ts +21 -0
  82. package/{src/scheduler/handler.ts → dist/scheduler/handler.js} +25 -42
  83. package/{src/scheduler/loop.ts → dist/scheduler/loop.d.ts} +7 -23
  84. package/dist/scheduler/loop.js +8 -0
  85. package/dist/scheduler/run-tick.d.ts +62 -0
  86. package/dist/scheduler/run-tick.js +164 -0
  87. package/dist/scheduler/runner.d.ts +2 -0
  88. package/{src/scheduler/runner.ts → dist/scheduler/runner.js} +57 -78
  89. package/dist/scheduler/stranded-placement-redrive.d.ts +16 -0
  90. package/dist/scheduler/stranded-placement-redrive.js +172 -0
  91. package/dist/sqs-queues.d.ts +4 -0
  92. package/dist/sqs-queues.js +22 -0
  93. package/dist/with-oauth-lifecycle-deps.d.ts +5 -0
  94. package/dist/with-oauth-lifecycle-deps.js +17 -0
  95. package/dist/with-oauth-lifecycle.d.ts +56 -0
  96. package/dist/with-oauth-lifecycle.js +92 -0
  97. package/{src/worker-version.ts → dist/worker-version.d.ts} +1 -2
  98. package/dist/worker-version.js +12 -0
  99. package/package.json +75 -69
  100. package/src/account-check.test.ts +0 -149
  101. package/src/account-check.ts +0 -101
  102. package/src/body-sync-gate.test.ts +0 -185
  103. package/src/body-sync-gate.ts +0 -86
  104. package/src/cli.ts +0 -218
  105. package/src/connection-scope.test.ts +0 -266
  106. package/src/connection-scope.ts +0 -314
  107. package/src/credential-less-sync-fence.test.ts +0 -152
  108. package/src/e2e-processor-shim.ts +0 -232
  109. package/src/emit.test.ts +0 -145
  110. package/src/emit.ts +0 -115
  111. package/src/events.ts +0 -252
  112. package/src/failure-kind.test.ts +0 -42
  113. package/src/handlers/append-sent-message.sqlite.test.ts +0 -176
  114. package/src/handlers/append-sent-message.ts +0 -264
  115. package/src/handlers/delete-account-objects.test.ts +0 -126
  116. package/src/handlers/delete-account-objects.ts +0 -96
  117. package/src/handlers/empty-trash.sqlite.test.ts +0 -209
  118. package/src/handlers/empty-trash.ts +0 -352
  119. package/src/handlers/flag-push.ts +0 -285
  120. package/src/handlers/imported-folder.test.ts +0 -63
  121. package/src/handlers/imported-folder.ts +0 -38
  122. package/src/handlers/mailbox-management.sqlite.test.ts +0 -265
  123. package/src/handlers/mailbox-management.ts +0 -486
  124. package/src/handlers/mailbox-sync-order.test.ts +0 -93
  125. package/src/handlers/mailbox-sync-order.ts +0 -65
  126. package/src/handlers/message-copy.sqlite.test.ts +0 -298
  127. package/src/handlers/message-copy.ts +0 -569
  128. package/src/handlers/message-delete-terminal.ts +0 -147
  129. package/src/handlers/message-delete.sqlite.test.ts +0 -291
  130. package/src/handlers/message-delete.ts +0 -888
  131. package/src/handlers/message-move.sqlite.test.ts +0 -271
  132. package/src/handlers/message-move.test.ts +0 -216
  133. package/src/handlers/message-move.ts +0 -755
  134. package/src/handlers/placement-move-push.test.ts +0 -137
  135. package/src/handlers/placement-move-push.ts +0 -478
  136. package/src/handlers/placement-push.sqlite.test.ts +0 -299
  137. package/src/handlers/probe-message-id.sqlite.test.ts +0 -192
  138. package/src/handlers/probe-message-id.ts +0 -15
  139. package/src/handlers/sync-mailboxes.test.ts +0 -183
  140. package/src/handlers/sync-mailboxes.ts +0 -425
  141. package/src/handlers/sync-message-body.sqlite.test.ts +0 -314
  142. package/src/handlers/sync-message-body.test.ts +0 -150
  143. package/src/handlers/sync-message-body.ts +0 -366
  144. package/src/handlers/sync-messages.sqlite.test.ts +0 -195
  145. package/src/handlers/sync-messages.ts +0 -537
  146. package/src/index.test.ts +0 -22
  147. package/src/index.ts +0 -82
  148. package/src/mail-sync-selection.sqlite.test.ts +0 -227
  149. package/src/mailbox-row.ts +0 -20
  150. package/src/pending-sync.sqlite.test.ts +0 -165
  151. package/src/poller.ts +0 -55
  152. package/src/processor.ts +0 -62
  153. package/src/queues.ts +0 -40
  154. package/src/scheduler/attachment-sweep.sqlite.test.ts +0 -74
  155. package/src/scheduler/attachment-sweep.ts +0 -46
  156. package/src/scheduler/calendar-subscriptions.sqlite.test.ts +0 -147
  157. package/src/scheduler/calendar-subscriptions.ts +0 -103
  158. package/src/scheduler/config.test.ts +0 -40
  159. package/src/scheduler/decide-due.test.ts +0 -44
  160. package/src/scheduler/half-deleted-accounts.sqlite.test.ts +0 -196
  161. package/src/scheduler/loop.test.ts +0 -143
  162. package/src/scheduler/run-tick.sqlite.test.ts +0 -191
  163. package/src/scheduler/run-tick.ts +0 -281
  164. package/src/scheduler/stranded-placement-redrive.ts +0 -265
  165. package/src/stranded-placement.sqlite.test.ts +0 -799
  166. package/src/test-helpers/test-world.ts +0 -425
  167. package/src/test-helpers/worker-queues.ts +0 -11
  168. package/src/thread-join.sqlite.test.ts +0 -106
  169. package/src/with-oauth-lifecycle-deps.sqlite.test.ts +0 -269
  170. package/src/with-oauth-lifecycle-deps.ts +0 -71
  171. package/src/with-oauth-lifecycle.ts +0 -158
  172. package/tsconfig.json +0 -8
@@ -1,271 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import { after, before, describe, it } from "node:test";
3
- import {
4
- MailboxCursorState,
5
- MailboxSpecialUse,
6
- MessageMutation,
7
- MessageStatus,
8
- MessageSyncStatus,
9
- } from "@remit/domain-enums";
10
- import type { MessageMoveEvent } from "../events.js";
11
- import { mail, openStore, type World } from "../test-helpers/test-world.js";
12
- import { handleMessageMove } from "./message-move.js";
13
-
14
- let store: Awaited<ReturnType<typeof openStore>>;
15
-
16
- before(async () => {
17
- store = await openStore();
18
- });
19
-
20
- after(() => store.close());
21
-
22
- interface Arranged {
23
- w: World;
24
- messageId: string;
25
- uid: number;
26
- messageIdHeader: string;
27
- inboxId: string;
28
- archiveId: string;
29
- event: MessageMoveEvent;
30
- }
31
-
32
- /** A message in INBOX, moved to Archive through the service the API calls. */
33
- const arrange = async (
34
- options: { olderCopyInArchive?: boolean } = {},
35
- ): Promise<Arranged> => {
36
- const w = await store.world();
37
- const inbox = await w.folder("INBOX");
38
- const archive = await w.folder("Archive", {
39
- specialUse: MailboxSpecialUse.Archive,
40
- });
41
- const seeded = await w.message(inbox);
42
- if (options.olderCopyInArchive) {
43
- w.imap.deliver("Archive", mail(seeded.messageIdHeader));
44
- }
45
-
46
- await w.client.messageMove.moveMessage(
47
- w.account.accountConfigId,
48
- seeded.messageId,
49
- archive.mailboxId,
50
- w.account.accountId,
51
- );
52
- const event = w
53
- .queued()
54
- .find(
55
- (body) => body.type === "MESSAGE_MOVE",
56
- ) as unknown as MessageMoveEvent;
57
- assert.ok(event, "the move enqueued a MESSAGE_MOVE");
58
-
59
- return {
60
- w,
61
- messageId: seeded.messageId,
62
- uid: seeded.uid,
63
- messageIdHeader: seeded.messageIdHeader,
64
- inboxId: inbox.mailboxId,
65
- archiveId: archive.mailboxId,
66
- event,
67
- };
68
- };
69
-
70
- const run = (a: Arranged, receiveCount = 1) =>
71
- handleMessageMove(a.event, a.w.log, a.w.queues, receiveCount, a.w.scope);
72
-
73
- const resyncs = (w: World): unknown[] =>
74
- w
75
- .queued()
76
- .filter((body) => body.type === "SYNC_MESSAGES")
77
- .map((body) => body.mailboxId);
78
-
79
- const logged = (w: World, key: string, value: string): number =>
80
- w.logs.filter((line) => line.fields[key] === value).length;
81
-
82
- describe("a message move", () => {
83
- it("lands at the destination and settles the row on the uid the server gave it", async () => {
84
- const a = await arrange();
85
-
86
- await run(a);
87
-
88
- assert.deepEqual(a.w.imap.uids("INBOX"), []);
89
- const [archiveUid] = a.w.imap.uids("Archive", a.messageIdHeader);
90
- const row = await a.w.row(a.messageId);
91
- assert.equal(row?.mailboxId, a.archiveId);
92
- assert.equal(row?.uid, archiveUid);
93
- assert.equal(row?.status, MessageStatus.active);
94
- assert.deepEqual(await a.w.threadRows(a.messageId), [
95
- { mailboxId: a.archiveId, uid: archiveUid, isDeleted: false },
96
- ]);
97
- assert.deepEqual(resyncs(a.w), [a.inboxId, a.archiveId]);
98
- });
99
-
100
- // Issue #1122: without COPYUID the destination is asked by Message-ID, and
101
- // an older copy already there must not take the row.
102
- it("binds to the copy this move delivered when the server sends no COPYUID", async () => {
103
- const a = await arrange({ olderCopyInArchive: true });
104
- a.w.imap.uidplus = false;
105
-
106
- await run(a);
107
-
108
- const copies = a.w.imap.uids("Archive", a.messageIdHeader);
109
- assert.equal(copies.length, 2);
110
- assert.equal((await a.w.row(a.messageId))?.uid, Math.max(...copies));
111
- });
112
-
113
- it("issues nothing when a redelivery finds the move already settled", async () => {
114
- const a = await arrange();
115
- await run(a);
116
- const commands = a.w.imap.log.length;
117
-
118
- await run(a);
119
-
120
- assert.equal(a.w.imap.log.length, commands);
121
- });
122
-
123
- it("marks the row failed and redelivers when the connection drops mid-move", async () => {
124
- const a = await arrange();
125
- a.w.imap.failNext.MOVE = new Error("Connection reset");
126
-
127
- await assert.rejects(run(a), /Connection reset/);
128
-
129
- const row = await a.w.row(a.messageId);
130
- assert.equal(row?.status, MessageStatus.moving);
131
- assert.equal(row?.syncStatus, MessageSyncStatus.failed);
132
- });
133
-
134
- // Issue #655, BROKEN: the message never left, so the row goes back on the
135
- // pair the server confirmed and says a move gave up.
136
- it("puts the row back on its source at the ceiling when the message never left", async () => {
137
- const a = await arrange();
138
- a.w.imap.failNext.MOVE = new Error("Connection reset");
139
-
140
- await run(a, 3);
141
-
142
- const row = await a.w.row(a.messageId);
143
- assert.equal(row?.mailboxId, a.inboxId);
144
- assert.equal(row?.uid, a.uid);
145
- assert.notEqual(row?.status, MessageStatus.moving);
146
- assert.equal(row?.abandonedMutation, MessageMutation.move);
147
- assert.deepEqual(resyncs(a.w), [a.inboxId, a.archiveId]);
148
- });
149
-
150
- // Issue #655, RECONCILED: the message is gone from the source, so the stale
151
- // row goes and the resync re-projects it wherever it actually is.
152
- it("reconciles the row away at the ceiling when the source no longer holds it", async () => {
153
- const a = await arrange();
154
- a.w.imap.empty("INBOX");
155
- a.w.imap.failNext.MOVE = new Error("Connection reset");
156
-
157
- await run(a, 3);
158
-
159
- assert.equal(await a.w.row(a.messageId), undefined);
160
- assert.deepEqual(resyncs(a.w), [a.inboxId, a.archiveId]);
161
- });
162
-
163
- it("creates a destination the server does not have and redelivers", async () => {
164
- const a = await arrange();
165
- a.w.imap.removeFolder("Archive");
166
-
167
- await assert.rejects(run(a), /TRYCREATE/);
168
-
169
- assert.deepEqual(a.w.imap.commands("CREATE"), ["CREATE Archive"]);
170
- assert.deepEqual(a.w.imap.uids("INBOX"), [a.uid]);
171
- });
172
-
173
- it("hands the row back without connecting when the destination mailbox was deleted", async () => {
174
- const a = await arrange();
175
- await a.w.client.mailbox.delete(a.w.account.accountId, a.archiveId);
176
-
177
- await run(a);
178
-
179
- assert.equal(a.w.imap.connections, 0);
180
- const row = await a.w.row(a.messageId);
181
- assert.equal(row?.mailboxId, a.inboxId);
182
- assert.equal(row?.uid, a.uid);
183
- assert.equal(row?.status, MessageStatus.active);
184
- assert.equal(logged(a.w, "metric", "message_move_destination_gone"), 1);
185
- assert.deepEqual(resyncs(a.w), [a.inboxId]);
186
- });
187
-
188
- it("acks without connecting when the source mailbox was deleted", async () => {
189
- const a = await arrange();
190
- await a.w.client.mailbox.delete(a.w.account.accountId, a.inboxId);
191
-
192
- await run(a);
193
-
194
- assert.equal(a.w.imap.connections, 0);
195
- });
196
-
197
- // Issue #1203: a paused cursor that acked left the row naming the
198
- // destination with the source's uid, unmovable and undeletable for good.
199
- it("hands the row back to its source without connecting while the cursor is rebuilding", async () => {
200
- const a = await arrange();
201
- await a.w.client.mailbox.update(a.w.account.accountId, a.inboxId, {
202
- cursorState: MailboxCursorState.rebuilding,
203
- });
204
-
205
- await run(a);
206
-
207
- assert.equal(a.w.imap.connections, 0);
208
- const row = await a.w.row(a.messageId);
209
- assert.equal(row?.mailboxId, a.inboxId);
210
- assert.equal(row?.uid, a.uid);
211
- assert.equal(row?.status, MessageStatus.active);
212
- assert.equal(logged(a.w, "metric", "message_move_dropped_on_pause"), 1);
213
- assert.deepEqual(resyncs(a.w), [a.inboxId, a.archiveId]);
214
- });
215
-
216
- // The redelivery half of #1203: the source answers "no" because it was
217
- // recreated, and Archive holds an older copy. Settling on it would bind the
218
- // row to mail this move never touched.
219
- it("never settles a redelivered paused move off a sighting at the destination", async () => {
220
- const a = await arrange({ olderCopyInArchive: true });
221
- a.w.imap.empty("INBOX");
222
- await a.w.client.mailbox.update(a.w.account.accountId, a.inboxId, {
223
- cursorState: MailboxCursorState.rebuilding,
224
- });
225
-
226
- await run(a, 2);
227
-
228
- assert.deepEqual(a.w.imap.commands("MOVE"), []);
229
- const row = await a.w.row(a.messageId);
230
- assert.equal(row?.mailboxId, a.inboxId);
231
- assert.equal(row?.uid, a.uid);
232
- assert.equal(row?.syncStatus, MessageSyncStatus.abandoned);
233
- assert.equal(row?.abandonedMutation, MessageMutation.move);
234
- assert.equal(
235
- logged(a.w, "alert", "message_move_paused_placement_unproven"),
236
- 1,
237
- );
238
- });
239
-
240
- it("moves nothing when the source's UIDVALIDITY changed under it", async () => {
241
- const a = await arrange();
242
- a.w.imap.setUidValidity("INBOX", 2);
243
-
244
- await run(a);
245
-
246
- assert.deepEqual(a.w.imap.commands("MOVE"), []);
247
- assert.equal((await a.w.row(a.messageId))?.mailboxId, a.inboxId);
248
- });
249
-
250
- it("moves under the destination's current path when it was renamed after enqueue", async () => {
251
- const a = await arrange();
252
- a.w.imap.renameFolder("Archive", "Records");
253
- await a.w.client.mailbox.update(a.w.account.accountId, a.archiveId, {
254
- fullPath: "Records",
255
- });
256
-
257
- await run(a);
258
-
259
- assert.equal(a.w.imap.uids("Records", a.messageIdHeader).length, 1);
260
- assert.equal((await a.w.row(a.messageId))?.mailboxId, a.archiveId);
261
- });
262
-
263
- it("acks without connecting when the message row is already gone", async () => {
264
- const a = await arrange();
265
- await a.w.client.message.delete(a.messageId);
266
-
267
- await run(a);
268
-
269
- assert.equal(a.w.imap.connections, 0);
270
- });
271
- });
@@ -1,216 +0,0 @@
1
- import assert from "node:assert";
2
- import { describe, it } from "node:test";
3
- import type { ThreadMessageItem } from "@remit/data-ports";
4
- import {
5
- eml,
6
- FakeImapServer,
7
- } from "@remit/mailbox-service/test-helpers/fake-imap";
8
- import {
9
- buildThreadMessageMoveUpdate,
10
- probePausedPlacement,
11
- searchMailboxForHighestMessageIdUid,
12
- } from "./message-move.js";
13
-
14
- const sourceMailboxId = "source-mailbox-id-aaaaaaaaa";
15
- const destinationMailboxId = "destination-mailbox-aaaaa";
16
-
17
- const baseThreadMessage = {
18
- sentDate: 1700000000000,
19
- mailboxId: sourceMailboxId,
20
- isRead: true,
21
- isDeleted: false,
22
- hasStars: true,
23
- hasAttachment: false,
24
- } satisfies Pick<
25
- ThreadMessageItem,
26
- | "sentDate"
27
- | "mailboxId"
28
- | "isRead"
29
- | "isDeleted"
30
- | "hasStars"
31
- | "hasAttachment"
32
- >;
33
-
34
- describe("buildThreadMessageMoveUpdate", () => {
35
- // Regression for the same composites-direction landmine PR #186 fixed in
36
- // `flag-queue.ts`. The CURRENT row state must go in `composites`; the NEW
37
- // values must go in `set`. Flipping any of these silently drops the
38
- // ThreadMessage local update on every IMAP MOVE — the row stays in the
39
- // source mailbox while the IMAP server thinks it moved.
40
-
41
- it("set carries the NEW uid, mailboxId, and isDeleted=false", () => {
42
- const args = buildThreadMessageMoveUpdate(
43
- baseThreadMessage,
44
- 42,
45
- destinationMailboxId,
46
- );
47
-
48
- assert.strictEqual(args.set.uid, 42, "set.uid must be the NEW uid");
49
- assert.strictEqual(
50
- args.set.mailboxId,
51
- destinationMailboxId,
52
- "set.mailboxId must be the NEW destination mailbox",
53
- );
54
- assert.strictEqual(
55
- args.set.isDeleted,
56
- false,
57
- "set.isDeleted must be false (move is not a delete)",
58
- );
59
- });
60
-
61
- it("composites mirrors every CURRENT indexed attribute on the threadMessage", () => {
62
- const tm = {
63
- sentDate: 1700000000123,
64
- mailboxId: sourceMailboxId,
65
- isRead: false,
66
- isDeleted: true,
67
- hasStars: true,
68
- hasAttachment: true,
69
- };
70
-
71
- const args = buildThreadMessageMoveUpdate(tm, 99, destinationMailboxId);
72
-
73
- assert.deepStrictEqual(args.composites, {
74
- sentDate: tm.sentDate,
75
- mailboxId: tm.mailboxId,
76
- isRead: tm.isRead,
77
- isDeleted: tm.isDeleted,
78
- hasStars: tm.hasStars,
79
- hasAttachment: tm.hasAttachment,
80
- });
81
- });
82
- });
83
-
84
- const _accountId = "alice-account-aaaaaaaaaa";
85
-
86
- describe("probePausedPlacement — which folder a paused mutation left the message in (#1203)", () => {
87
- const HEADER = "<paused@example.com>";
88
-
89
- const serverHolding = (holdings: Record<string, number[]>) => {
90
- const server = new FakeImapServer();
91
- server.addFolder("INBOX");
92
- server.addFolder("Archive");
93
- for (const [path, uids] of Object.entries(holdings)) {
94
- for (const uid of uids)
95
- server.deliver(path, eml({ "Message-ID": HEADER }), { uid: uid });
96
- }
97
- return server;
98
- };
99
-
100
- const opened = (server: FakeImapServer): string[] =>
101
- server.log
102
- .filter((line) => /^(SELECT|EXAMINE) /.test(line))
103
- .map((line) => line.split(" ")[1] ?? "");
104
-
105
- const probe = (
106
- server: FakeImapServer,
107
- input: Partial<Parameters<typeof probePausedPlacement>[1]> = {},
108
- ) =>
109
- probePausedPlacement(server.connection(), {
110
- messageIdHeader: HEADER,
111
- sourceMailboxPath: "INBOX",
112
- destinationMailboxPath: "Archive",
113
- ...input,
114
- });
115
-
116
- it("stops at the source and never asks the destination when the source still holds it", async () => {
117
- const server = serverHolding({ INBOX: [4], Archive: [12] });
118
-
119
- assert.deepStrictEqual(await probe(server), { kind: "at-source" });
120
- assert.deepStrictEqual(
121
- opened(server),
122
- ["INBOX"],
123
- "a source hit ends it: with the mutation unrun, every destination hit is an older copy (#1122)",
124
- );
125
- });
126
-
127
- it("answers the destination's highest matching uid once the source has let go", async () => {
128
- const server = serverHolding({ Archive: [12, 77] });
129
-
130
- assert.deepStrictEqual(await probe(server), {
131
- kind: "at-destination",
132
- uid: 77,
133
- });
134
- });
135
-
136
- it("answers gone when neither folder holds it", async () => {
137
- assert.deepStrictEqual(await probe(serverHolding({})), { kind: "gone" });
138
- });
139
-
140
- it("answers gone, not at-source, when there is no destination to ask", async () => {
141
- const server = serverHolding({});
142
-
143
- assert.deepStrictEqual(
144
- await probe(server, { destinationMailboxPath: undefined }),
145
- { kind: "gone" },
146
- );
147
- assert.deepStrictEqual(opened(server), ["INBOX"]);
148
- });
149
-
150
- // A row with no Message-ID header cannot be asked about, and the server's
151
- // silence is not an answer. `unprobeable` keeps that distinct from `gone`.
152
- it("asks nothing at all when the row carries no Message-ID header", async () => {
153
- const server = serverHolding({ INBOX: [4] });
154
-
155
- assert.deepStrictEqual(
156
- await probe(server, { messageIdHeader: undefined }),
157
- {
158
- kind: "unprobeable",
159
- },
160
- );
161
- assert.deepStrictEqual(server.log, []);
162
- });
163
- });
164
-
165
- describe("searchMailboxForHighestMessageIdUid — the probe that binds a move to a UID (#912)", () => {
166
- const archiveHolding = (messages: Array<[uid: number, header: string]>) => {
167
- const server = new FakeImapServer();
168
- server.addFolder("Archive");
169
- for (const [uid, header] of messages)
170
- server.deliver("Archive", eml({ "Message-ID": header }), { uid: uid });
171
- return server;
172
- };
173
-
174
- const search = (server: FakeImapServer, header: string) =>
175
- searchMailboxForHighestMessageIdUid(server.connection(), "Archive", header);
176
-
177
- it("asks the folder by Message-ID rather than by an interpolated string", async () => {
178
- const server = archiveHolding([]);
179
- const hostile = '<a"b@example.com>\r\nUID 1';
180
-
181
- await search(server, hostile);
182
-
183
- assert.deepStrictEqual(server.commands("SEARCH"), [
184
- `SEARCH Archive ${JSON.stringify([["HEADER", "Message-ID", hostile]])}`,
185
- ]);
186
- });
187
-
188
- it("answers null, not another message's UID, when the folder holds no such message", async () => {
189
- const server = archiveHolding([
190
- [11, "<stranger-a@example.com>"],
191
- [12, "<stranger-b@example.com>"],
192
- ]);
193
-
194
- assert.strictEqual(await search(server, "<moved@example.com>"), null);
195
- });
196
-
197
- it("answers the UID of the message carrying that Message-ID", async () => {
198
- const server = archiveHolding([
199
- [11, "<stranger-a@example.com>"],
200
- [12, "<moved@example.com>"],
201
- ]);
202
-
203
- assert.strictEqual(await search(server, "<moved@example.com>"), 12);
204
- });
205
-
206
- // RFC 3501 does not order a SEARCH response, so the fresh copy is the
207
- // highest uid returned rather than the last one returned.
208
- it("takes the highest match whatever order the server lists them in", async () => {
209
- const server = archiveHolding([
210
- [40, "<moved@example.com>"],
211
- [12, "<moved@example.com>"],
212
- ]);
213
-
214
- assert.strictEqual(await search(server, "<moved@example.com>"), 40);
215
- });
216
- });