@remit/imap-worker 0.0.90 → 0.0.92

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 (176) 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 +22 -0
  76. package/dist/scheduler/calendar-subscriptions.js +35 -0
  77. package/dist/scheduler/config.d.ts +29 -0
  78. package/{src/scheduler/config.ts → dist/scheduler/config.js} +9 -28
  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/dist/scheduler/handler.js +50 -0
  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 +66 -0
  86. package/dist/scheduler/run-tick.js +164 -0
  87. package/dist/scheduler/runner.d.ts +2 -0
  88. package/dist/scheduler/runner.js +102 -0
  89. package/dist/scheduler/stranded-placement-redrive.d.ts +16 -0
  90. package/dist/scheduler/stranded-placement-redrive.js +172 -0
  91. package/dist/scheduler/tick.d.ts +5 -0
  92. package/dist/scheduler/tick.js +5 -0
  93. package/dist/sqs-queues.d.ts +4 -0
  94. package/dist/sqs-queues.js +22 -0
  95. package/dist/with-oauth-lifecycle-deps.d.ts +5 -0
  96. package/dist/with-oauth-lifecycle-deps.js +17 -0
  97. package/dist/with-oauth-lifecycle.d.ts +56 -0
  98. package/dist/with-oauth-lifecycle.js +92 -0
  99. package/{src/worker-version.ts → dist/worker-version.d.ts} +1 -2
  100. package/dist/worker-version.js +12 -0
  101. package/package.json +79 -69
  102. package/src/account-check.test.ts +0 -149
  103. package/src/account-check.ts +0 -101
  104. package/src/body-sync-gate.test.ts +0 -185
  105. package/src/body-sync-gate.ts +0 -86
  106. package/src/cli.ts +0 -218
  107. package/src/connection-scope.test.ts +0 -266
  108. package/src/connection-scope.ts +0 -314
  109. package/src/credential-less-sync-fence.test.ts +0 -152
  110. package/src/e2e-processor-shim.ts +0 -232
  111. package/src/emit.test.ts +0 -145
  112. package/src/emit.ts +0 -115
  113. package/src/events.ts +0 -252
  114. package/src/failure-kind.test.ts +0 -42
  115. package/src/handlers/append-sent-message.sqlite.test.ts +0 -176
  116. package/src/handlers/append-sent-message.ts +0 -264
  117. package/src/handlers/delete-account-objects.test.ts +0 -126
  118. package/src/handlers/delete-account-objects.ts +0 -96
  119. package/src/handlers/empty-trash.sqlite.test.ts +0 -209
  120. package/src/handlers/empty-trash.ts +0 -352
  121. package/src/handlers/flag-push.ts +0 -285
  122. package/src/handlers/imported-folder.test.ts +0 -63
  123. package/src/handlers/imported-folder.ts +0 -38
  124. package/src/handlers/mailbox-management.sqlite.test.ts +0 -265
  125. package/src/handlers/mailbox-management.ts +0 -486
  126. package/src/handlers/mailbox-sync-order.test.ts +0 -93
  127. package/src/handlers/mailbox-sync-order.ts +0 -65
  128. package/src/handlers/message-copy.sqlite.test.ts +0 -298
  129. package/src/handlers/message-copy.ts +0 -569
  130. package/src/handlers/message-delete-terminal.ts +0 -147
  131. package/src/handlers/message-delete.sqlite.test.ts +0 -291
  132. package/src/handlers/message-delete.ts +0 -888
  133. package/src/handlers/message-move.sqlite.test.ts +0 -271
  134. package/src/handlers/message-move.test.ts +0 -216
  135. package/src/handlers/message-move.ts +0 -755
  136. package/src/handlers/placement-move-push.test.ts +0 -137
  137. package/src/handlers/placement-move-push.ts +0 -478
  138. package/src/handlers/placement-push.sqlite.test.ts +0 -299
  139. package/src/handlers/probe-message-id.sqlite.test.ts +0 -192
  140. package/src/handlers/probe-message-id.ts +0 -15
  141. package/src/handlers/sync-mailboxes.test.ts +0 -183
  142. package/src/handlers/sync-mailboxes.ts +0 -425
  143. package/src/handlers/sync-message-body.sqlite.test.ts +0 -314
  144. package/src/handlers/sync-message-body.test.ts +0 -150
  145. package/src/handlers/sync-message-body.ts +0 -366
  146. package/src/handlers/sync-messages.sqlite.test.ts +0 -195
  147. package/src/handlers/sync-messages.ts +0 -537
  148. package/src/index.test.ts +0 -22
  149. package/src/index.ts +0 -82
  150. package/src/mail-sync-selection.sqlite.test.ts +0 -227
  151. package/src/mailbox-row.ts +0 -20
  152. package/src/pending-sync.sqlite.test.ts +0 -165
  153. package/src/poller.ts +0 -55
  154. package/src/processor.ts +0 -62
  155. package/src/queues.ts +0 -40
  156. package/src/scheduler/attachment-sweep.sqlite.test.ts +0 -74
  157. package/src/scheduler/attachment-sweep.ts +0 -46
  158. package/src/scheduler/calendar-subscriptions.sqlite.test.ts +0 -147
  159. package/src/scheduler/calendar-subscriptions.ts +0 -103
  160. package/src/scheduler/config.test.ts +0 -40
  161. package/src/scheduler/decide-due.test.ts +0 -44
  162. package/src/scheduler/half-deleted-accounts.sqlite.test.ts +0 -196
  163. package/src/scheduler/handler.ts +0 -69
  164. package/src/scheduler/loop.test.ts +0 -143
  165. package/src/scheduler/run-tick.sqlite.test.ts +0 -191
  166. package/src/scheduler/run-tick.ts +0 -281
  167. package/src/scheduler/runner.ts +0 -125
  168. package/src/scheduler/stranded-placement-redrive.ts +0 -265
  169. package/src/stranded-placement.sqlite.test.ts +0 -799
  170. package/src/test-helpers/test-world.ts +0 -425
  171. package/src/test-helpers/worker-queues.ts +0 -11
  172. package/src/thread-join.sqlite.test.ts +0 -106
  173. package/src/with-oauth-lifecycle-deps.sqlite.test.ts +0 -269
  174. package/src/with-oauth-lifecycle-deps.ts +0 -71
  175. package/src/with-oauth-lifecycle.ts +0 -158
  176. package/tsconfig.json +0 -8
@@ -1,425 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import { randomUUID } from "node:crypto";
3
- import { mkdtempSync, rmSync } from "node:fs";
4
- import { tmpdir } from "node:os";
5
- import { join } from "node:path";
6
- import {
7
- _resetForTest,
8
- type RemitClient,
9
- setClient,
10
- } from "@remit/backend/client";
11
- import { createRemitClient } from "@remit/backend/create-remit-client";
12
- import { buildSharedDeps } from "@remit/backend/shared-deps";
13
- import type {
14
- AccountItem,
15
- MailboxItem,
16
- MailboxSpecialUseValue,
17
- MessageItem,
18
- OutboxMessageItem,
19
- } from "@remit/data-ports";
20
- import {
21
- type RecordingQueue,
22
- recordingQueue,
23
- } from "@remit/data-ports/recording-queue";
24
- import {
25
- AccountAuthType,
26
- ConnectionState,
27
- MailboxCursorState,
28
- } from "@remit/domain-enums";
29
- import {
30
- createSqliteRepositories,
31
- openSqliteDatabases,
32
- sqliteDatabasePathsFromEnv,
33
- } from "@remit/drizzle-service";
34
- import { createShippedSqliteFiles } from "@remit/drizzle-service/test-sqlite";
35
- import type { Logger } from "@remit/logger";
36
- import type { ManagedConnectionFactory } from "@remit/mailbox-service";
37
- import {
38
- eml,
39
- FakeImapServer,
40
- } from "@remit/mailbox-service/test-helpers/fake-imap";
41
- import { serializeEncryptedPayload } from "@remit/secrets-service";
42
- import type { createConnectionScopeWithCredentials } from "../connection-scope.js";
43
- import { syncMessages } from "../handlers/sync-messages.js";
44
- import type { WorkerQueues } from "../queues.js";
45
- import { workerQueuesOn } from "./worker-queues.js";
46
-
47
- export interface LogLine {
48
- level: string;
49
- fields: Record<string, unknown>;
50
- }
51
-
52
- export const capturingLogger = (lines: LogLine[]): Logger => {
53
- const at =
54
- (level: string) =>
55
- (fields: Record<string, unknown>): void => {
56
- lines.push({ level, fields });
57
- };
58
- const log = {
59
- info: at("info"),
60
- warn: at("warn"),
61
- error: at("error"),
62
- debug: at("debug"),
63
- fatal: at("fatal"),
64
- trace: at("trace"),
65
- child: () => log,
66
- } as unknown as Logger;
67
- return log;
68
- };
69
-
70
- /** A message the way a correspondent sends one, naming it by its Message-ID. */
71
- export const mail = (messageIdHeader: string): Buffer =>
72
- eml({
73
- "Message-ID": messageIdHeader,
74
- From: "Friend <friend@example.com>",
75
- To: "Person <person@example.com>",
76
- Date: "Tue, 14 Nov 2023 22:13:20 +0000",
77
- Subject: "test",
78
- "Content-Type": "text/plain; charset=utf-8",
79
- });
80
-
81
- export interface FolderOptions {
82
- specialUse?: MailboxSpecialUseValue;
83
- uidValidity?: number;
84
- cursorState?: MailboxItem["cursorState"];
85
- }
86
-
87
- export interface SeededMessage {
88
- messageId: string;
89
- uid: number;
90
- messageIdHeader: string;
91
- threadMessageId: string;
92
- }
93
-
94
- /**
95
- * One SQLite store per test file, over the shipped migrations, composed from
96
- * the backend's SQLite repositories over filesystem storage — every
97
- * repository and service a handler reaches is the one a self-host process
98
- * runs. Each `world()` is a fresh account on it, with its own in-memory mail
99
- * server at the IMAP port, and a message reaches the store the way mail does:
100
- * delivered to the server, then synced.
101
- */
102
- export const openStore = async (): Promise<{
103
- client: RemitClient;
104
- world: (accountConfigId?: string) => Promise<World>;
105
- close: () => void;
106
- }> => {
107
- const directory = mkdtempSync(join(tmpdir(), "imap-worker-world-"));
108
- createShippedSqliteFiles(directory);
109
- delete process.env.S3_BUCKET_NAME;
110
- process.env.STORAGE_LOCAL_PATH = join(directory, "storage");
111
- process.env.BETTER_AUTH_URL = "https://mail.example.com";
112
- process.env.BETTER_AUTH_SECRET = "a-signing-secret-of-at-least-32-characters";
113
- _resetForTest();
114
- const repositories = createSqliteRepositories(
115
- await openSqliteDatabases(sqliteDatabasePathsFromEnv()),
116
- );
117
- const queuesOn = (queue: RecordingQueue) => ({
118
- sync: queue.fifo,
119
- accountFanout: queue,
120
- smtp: queue,
121
- messageManagement: queue,
122
- body: queue,
123
- });
124
- const shared = buildSharedDeps(queuesOn(recordingQueue()));
125
- const clientOn = (queue: RecordingQueue): RemitClient =>
126
- createRemitClient({ repositories, ...shared, queues: queuesOn(queue) });
127
- const client = clientOn(recordingQueue());
128
- setClient(client);
129
- return {
130
- client,
131
- world: (accountConfigId = `cfg-${randomUUID()}`) => {
132
- const queue = recordingQueue();
133
- const worldClient = clientOn(queue);
134
- setClient(worldClient);
135
- return createWorld(worldClient, queue, accountConfigId);
136
- },
137
- close: () => {
138
- _resetForTest();
139
- rmSync(directory, { recursive: true, force: true });
140
- },
141
- };
142
- };
143
-
144
- export interface World {
145
- client: RemitClient;
146
- imap: FakeImapServer;
147
- account: AccountItem;
148
- logs: LogLine[];
149
- log: Logger;
150
- folder: (path: string, options?: FolderOptions) => Promise<MailboxItem>;
151
- /** A folder of a second account on this world's account config. */
152
- siblingFolder: (path: string) => Promise<MailboxItem>;
153
- message: (
154
- mailbox: MailboxItem,
155
- options?: { messageIdHeader?: string },
156
- ) => Promise<SeededMessage>;
157
- row: (messageId: string) => Promise<MessageItem | undefined>;
158
- threadRows: (
159
- messageId: string,
160
- ) => Promise<Array<{ mailboxId: string; uid?: number; isDeleted: boolean }>>;
161
- queued: () => Array<Record<string, unknown>>;
162
- refuseSends: (error: Error | undefined) => void;
163
- queue: RecordingQueue;
164
- queues: WorkerQueues;
165
- draft: (
166
- attachment?: Buffer,
167
- ) => Promise<{ outbox: OutboxMessageItem; attachmentId?: string }>;
168
- scope: typeof createConnectionScopeWithCredentials;
169
- connectionFactory: () => ManagedConnectionFactory;
170
- }
171
-
172
- const createWorld = async (
173
- client: RemitClient,
174
- queue: RecordingQueue,
175
- accountConfigId: string,
176
- ): Promise<World> => {
177
- const imap = new FakeImapServer();
178
- const logs: LogLine[] = [];
179
- const password = await client.secrets.encrypt("app-password");
180
- const account = await client.account.create({
181
- accountConfigId,
182
- username: "person@example.com",
183
- email: "person@example.com",
184
- authType: AccountAuthType.Password,
185
- passwordHash: JSON.stringify(serializeEncryptedPayload(password)),
186
- imapHost: "imap.example.com",
187
- imapPort: 993,
188
- imapTls: true,
189
- imapStartTls: false,
190
- isActive: true,
191
- connectionState: ConnectionState.Authenticated,
192
- });
193
-
194
- const folder = async (
195
- path: string,
196
- options: FolderOptions = {},
197
- ): Promise<MailboxItem> => {
198
- const uidValidity = options.uidValidity ?? 1;
199
- imap.addFolder(path, { uidValidity });
200
- const created = await client.mailbox.create({
201
- accountId: account.accountId,
202
- namespacePrefix: "",
203
- hierarchyDelimiter: "/",
204
- fullPath: path,
205
- uidValidity,
206
- uidNext: 1,
207
- highestModseq: "0",
208
- messageCount: 0,
209
- unseenCount: 0,
210
- deletedCount: 0,
211
- totalSize: 0,
212
- lastSyncUid: 0,
213
- highWaterMarkUid: 0,
214
- lastMessageSyncAt: 0,
215
- syncStatus: "synced",
216
- });
217
- if (created.outcome !== "Created") {
218
- throw new Error(`${path} could not be created: ${created.outcome}`);
219
- }
220
- const mailbox = created.mailbox;
221
- if (options.specialUse) {
222
- await client.mailboxSpecialUse.create(
223
- mailbox.mailboxId,
224
- options.specialUse,
225
- );
226
- }
227
- if (
228
- options.cursorState &&
229
- options.cursorState !== MailboxCursorState.normal
230
- ) {
231
- await client.mailbox.update(account.accountId, mailbox.mailboxId, {
232
- cursorState: options.cursorState,
233
- });
234
- return client.mailbox.get(account.accountId, mailbox.mailboxId);
235
- }
236
- return mailbox;
237
- };
238
-
239
- const siblingFolder = async (path: string): Promise<MailboxItem> => {
240
- const sibling = await client.account.create({
241
- accountConfigId,
242
- username: "person-too@example.com",
243
- email: "person-too@example.com",
244
- authType: AccountAuthType.Password,
245
- passwordHash: JSON.stringify(serializeEncryptedPayload(password)),
246
- imapHost: "imap.example.com",
247
- imapPort: 993,
248
- imapTls: true,
249
- imapStartTls: false,
250
- isActive: true,
251
- connectionState: ConnectionState.Authenticated,
252
- });
253
- const created = await client.mailbox.create({
254
- accountId: sibling.accountId,
255
- namespacePrefix: "",
256
- hierarchyDelimiter: "/",
257
- fullPath: path,
258
- uidValidity: 1,
259
- uidNext: 1,
260
- highestModseq: "0",
261
- messageCount: 0,
262
- unseenCount: 0,
263
- deletedCount: 0,
264
- totalSize: 0,
265
- lastSyncUid: 0,
266
- highWaterMarkUid: 0,
267
- lastMessageSyncAt: 0,
268
- syncStatus: "synced",
269
- });
270
- if (created.outcome !== "Created") {
271
- throw new Error(`${path} could not be created: ${created.outcome}`);
272
- }
273
- return created.mailbox;
274
- };
275
-
276
- const message = async (
277
- mailbox: MailboxItem,
278
- options: { messageIdHeader?: string } = {},
279
- ): Promise<SeededMessage> => {
280
- const messageIdHeader =
281
- options.messageIdHeader ?? `<${randomUUID()}@example.com>`;
282
- const uid = imap.deliver(mailbox.fullPath, mail(messageIdHeader));
283
- const baseline = {
284
- connections: imap.connections,
285
- commands: imap.log.length,
286
- };
287
- await syncMessages(
288
- {
289
- type: "SYNC_MESSAGES",
290
- eventId: randomUUID(),
291
- timestamp: Date.now(),
292
- accountId: account.accountId,
293
- mailboxId: mailbox.mailboxId,
294
- },
295
- capturingLogger([]),
296
- workerQueuesOn(queue),
297
- connectionFactory,
298
- );
299
- imap.connections = baseline.connections;
300
- imap.log.length = baseline.commands;
301
- const synced = (
302
- await client.message.listAllByMailbox(mailbox.mailboxId)
303
- ).find((row) => row.uid === uid);
304
- if (!synced)
305
- throw new Error(`uid ${uid} in ${mailbox.fullPath} did not sync`);
306
- const [threadMessage] = await client.threadMessage.findAllByMessageId(
307
- accountConfigId,
308
- synced.messageId,
309
- );
310
- if (!threadMessage)
311
- throw new Error(`uid ${uid} synced without a listing row`);
312
- return {
313
- messageId: synced.messageId,
314
- uid,
315
- messageIdHeader,
316
- threadMessageId: threadMessage.threadMessageId,
317
- };
318
- };
319
-
320
- const row = async (messageId: string): Promise<MessageItem | undefined> =>
321
- (await client.message.get([messageId]))[0];
322
-
323
- const threadRows = async (messageId: string) =>
324
- (
325
- await client.threadMessage.findAllByMessageId(accountConfigId, messageId)
326
- ).map((tm) => ({
327
- mailboxId: tm.mailboxId,
328
- uid: tm.uid,
329
- isDeleted: tm.isDeleted,
330
- }));
331
-
332
- const queued = (): Array<Record<string, unknown>> =>
333
- queue.sent.map((event) => event.body);
334
-
335
- const refuseSends = (error: Error | undefined): void => {
336
- queue.answer(async () => {
337
- if (error) throw error;
338
- return {};
339
- });
340
- };
341
-
342
- const draft = async (
343
- attachment?: Buffer,
344
- ): Promise<{ outbox: OutboxMessageItem; attachmentId?: string }> => {
345
- const outbox = await client.outboxQueue.createDraft({
346
- accountId: account.accountId,
347
- accountConfigId,
348
- fromAddress: "person@example.com",
349
- fromName: "Person",
350
- toAddresses: ["friend@example.com"],
351
- subject: "Quarterly numbers",
352
- textBody: "See you Thursday.",
353
- });
354
- if (!attachment) return { outbox };
355
- const minted = await client.outboxAttachment.mint({
356
- accountConfigId,
357
- outboxMessageId: outbox.outboxMessageId,
358
- filename: "numbers.csv",
359
- contentType: "text/csv",
360
- sizeBytes: attachment.length,
361
- });
362
- if (minted.outcome !== "Minted") throw new Error("the upload was refused");
363
- const attachmentId = minted.reservation.outboxAttachmentId;
364
- await client.storage.storeOutboxAttachment({
365
- accountConfigId,
366
- accountId: account.accountId,
367
- outboxMessageId: outbox.outboxMessageId,
368
- outboxAttachmentId: attachmentId,
369
- content: attachment,
370
- });
371
- const completed = await client.outboxAttachment.complete({
372
- accountConfigId,
373
- outboxMessageId: outbox.outboxMessageId,
374
- outboxAttachmentId: attachmentId,
375
- });
376
- assert.equal(completed.outcome, "Completed");
377
- return { outbox, attachmentId };
378
- };
379
-
380
- const scope = () => {
381
- let open: ReturnType<FakeImapServer["connection"]> | undefined;
382
- return {
383
- getConnection: async () => {
384
- open ??= imap.connection();
385
- return open;
386
- },
387
- disconnect: async () => {
388
- open = undefined;
389
- },
390
- };
391
- };
392
-
393
- const connectionFactory = (): ManagedConnectionFactory => {
394
- let open: ReturnType<FakeImapServer["connection"]> | undefined;
395
- return {
396
- getConnection: () => {
397
- open ??= imap.connection();
398
- return open;
399
- },
400
- close: async () => {
401
- open = undefined;
402
- },
403
- };
404
- };
405
-
406
- return {
407
- client,
408
- imap,
409
- account,
410
- logs,
411
- log: capturingLogger(logs),
412
- folder,
413
- siblingFolder,
414
- message,
415
- row,
416
- threadRows,
417
- queued,
418
- refuseSends,
419
- queue,
420
- queues: workerQueuesOn(queue),
421
- draft,
422
- scope,
423
- connectionFactory,
424
- };
425
- };
@@ -1,11 +0,0 @@
1
- import type { RecordingQueue } from "@remit/data-ports/recording-queue";
2
- import type { WorkerQueues } from "../queues.js";
3
-
4
- export const workerQueuesOn = (queue: RecordingQueue): WorkerQueues => ({
5
- mailboxes: queue.fifo,
6
- messages: queue.fifo,
7
- flags: queue.fifo,
8
- body: queue,
9
- mailboxManagement: queue,
10
- messageManagement: queue,
11
- });
@@ -1,106 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import { describe, it } from "node:test";
3
- import type {
4
- IMailboxRepository,
5
- IMailboxSpecialUseRepository,
6
- MailboxItem,
7
- } from "@remit/data-ports";
8
- import { deriveThreadId } from "@remit/data-ports/id";
9
- import { createSqliteRepositories } from "@remit/drizzle-service";
10
- import { createShippedSqliteDb } from "@remit/drizzle-service/test-sqlite";
11
- import { MessageSyncService, SyncWarningService } from "@remit/mailbox-service";
12
- import type { ImapMessage } from "@remit/mailbox-service/types";
13
-
14
- const ACCOUNT = "acct-1";
15
- const CONFIG = "cfg-1";
16
- const MAILBOX = { mailboxId: "mbx-1", fullPath: "INBOX" } as MailboxItem;
17
- const ROLES = {
18
- junkMailboxId: null,
19
- trashMailboxId: null,
20
- configJunkRoles: { junkMailboxIds: [], trashMailboxIds: [] },
21
- };
22
-
23
- const message = (
24
- uid: number,
25
- messageId: string,
26
- inReplyTo: string,
27
- references?: string[],
28
- ): ImapMessage =>
29
- ({
30
- uid,
31
- seq: uid,
32
- size: 100,
33
- internalDate: new Date(uid * 1000),
34
- flags: [],
35
- references,
36
- envelope: {
37
- date: new Date(uid * 1000).toISOString(),
38
- messageId,
39
- subject: `Subject ${uid}`,
40
- from: [{ name: "Sender", mailbox: "sender", host: "example.com" }],
41
- sender: [],
42
- replyTo: [],
43
- to: [],
44
- cc: [],
45
- bcc: [],
46
- inReplyTo,
47
- },
48
- }) as unknown as ImapMessage;
49
-
50
- const PARENT = message(1, "<p@example.com>", "<a@example.com>", [
51
- "<a@example.com>",
52
- ]);
53
- const REPLY = message(2, "<r@example.com>", "<p@example.com>");
54
-
55
- const syncInOrder = async (
56
- messages: ImapMessage[],
57
- ): Promise<{ threadIds: (messageId: string) => string[] }> => {
58
- const { db, sqlite } = createShippedSqliteDb();
59
- const repos = createSqliteRepositories({ app: db, mail: db, search: db });
60
- const service = new MessageSyncService(
61
- {} as never,
62
- {} as IMailboxRepository,
63
- {} as IMailboxSpecialUseRepository,
64
- repos.message,
65
- repos.envelope,
66
- repos.address,
67
- repos.threadMessage,
68
- new SyncWarningService(repos.syncWarning),
69
- );
70
- const save = (
71
- service as unknown as {
72
- saveMessage: (...args: unknown[]) => Promise<unknown>;
73
- }
74
- ).saveMessage.bind(service);
75
- for (const msg of messages) {
76
- await save(MAILBOX, ACCOUNT, CONFIG, msg, ROLES, {
77
- departed: new Set(),
78
- settled: new Set(),
79
- });
80
- }
81
- return {
82
- threadIds: (messageIdHeader) =>
83
- (
84
- sqlite
85
- .prepare(
86
- "SELECT thread_id FROM thread_message WHERE message_id_header = ?",
87
- )
88
- .all(messageIdHeader) as { thread_id: string }[]
89
- ).map((row) => row.thread_id),
90
- };
91
- };
92
-
93
- describe("a reply naming only its parent, which is itself a reply", () => {
94
- const rootThread = deriveThreadId(CONFIG, "<a@example.com>");
95
-
96
- it("joins the root's thread when the parent synced first", async () => {
97
- const { threadIds } = await syncInOrder([PARENT, REPLY]);
98
- assert.deepEqual(threadIds("<r@example.com>"), [rootThread]);
99
- });
100
-
101
- it("joins the root's thread when it synced before its parent, and stays there on a re-save", async () => {
102
- const { threadIds } = await syncInOrder([REPLY, PARENT, REPLY]);
103
- assert.deepEqual(threadIds("<r@example.com>"), [rootThread]);
104
- assert.deepEqual(threadIds("<p@example.com>"), [rootThread]);
105
- });
106
- });