@remit/imap-worker 0.0.88 → 0.0.90

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 (38) hide show
  1. package/package.json +6 -6
  2. package/src/cli.ts +3 -2
  3. package/src/credential-less-sync-fence.test.ts +5 -9
  4. package/src/e2e-processor-shim.ts +2 -2
  5. package/src/emit.test.ts +25 -42
  6. package/src/emit.ts +94 -119
  7. package/src/events.ts +1 -19
  8. package/src/handlers/delete-account-objects.test.ts +27 -18
  9. package/src/handlers/delete-account-objects.ts +5 -12
  10. package/src/handlers/message-copy.sqlite.test.ts +3 -3
  11. package/src/handlers/message-copy.ts +5 -2
  12. package/src/handlers/message-delete.sqlite.test.ts +1 -1
  13. package/src/handlers/message-delete.ts +7 -4
  14. package/src/handlers/message-move.sqlite.test.ts +1 -1
  15. package/src/handlers/message-move.ts +9 -6
  16. package/src/handlers/placement-move-push.ts +9 -6
  17. package/src/handlers/placement-push.sqlite.test.ts +3 -2
  18. package/src/handlers/probe-message-id.sqlite.test.ts +2 -4
  19. package/src/handlers/sync-mailboxes.ts +8 -4
  20. package/src/handlers/sync-message-body.sqlite.test.ts +2 -2
  21. package/src/handlers/sync-message-body.ts +15 -18
  22. package/src/handlers/sync-messages.sqlite.test.ts +14 -2
  23. package/src/handlers/sync-messages.ts +20 -8
  24. package/src/index.ts +3 -1
  25. package/src/mail-sync-selection.sqlite.test.ts +7 -13
  26. package/src/pending-sync.sqlite.test.ts +26 -28
  27. package/src/processor.ts +10 -16
  28. package/src/queues.ts +40 -0
  29. package/src/scheduler/attachment-sweep.sqlite.test.ts +2 -2
  30. package/src/scheduler/half-deleted-accounts.sqlite.test.ts +20 -27
  31. package/src/scheduler/handler.ts +35 -28
  32. package/src/scheduler/run-tick.sqlite.test.ts +6 -14
  33. package/src/scheduler/run-tick.ts +10 -14
  34. package/src/scheduler/runner.ts +15 -12
  35. package/src/stranded-placement.sqlite.test.ts +9 -5
  36. package/src/test-helpers/test-world.ts +40 -13
  37. package/src/test-helpers/worker-queues.ts +11 -0
  38. package/src/processor.test.ts +0 -58
@@ -84,7 +84,7 @@ const placementMover = (w: World): PlacementMoveService =>
84
84
  addressService: w.client.address,
85
85
  mailboxSpecialUseService: w.client.mailboxSpecialUse,
86
86
  mailboxService: w.client.mailbox,
87
- sqsQueueUrl: process.env.SQS_QUEUE_URL_MESSAGE_MGMT ?? "",
87
+ queue: w.client.queues.messageManagement,
88
88
  });
89
89
 
90
90
  const placementOf = async (a: Arranged) => {
@@ -131,7 +131,8 @@ const queueRefuses = (w: World): void => {
131
131
  };
132
132
 
133
133
  const queueTakes = (w: World): void => {
134
- w.queue.answer(async () => ({ Failed: [] }));
134
+ w.queue.answer(async () => ({}));
135
+ w.queue.refuse([]);
135
136
  };
136
137
 
137
138
  const redriveLater = async (
@@ -255,9 +256,7 @@ describe("a move whose event dies leaves no row moving", () => {
255
256
 
256
257
  it("hands back a move to Trash whose batch entry the queue refused", async () => {
257
258
  const a = await arrange();
258
- a.w.queue.answer(async () => ({
259
- Failed: [{ Id: "0", Code: "InvalidParameterValue", SenderFault: true }],
260
- }));
259
+ a.w.queue.refuse([0]);
261
260
 
262
261
  await assert.rejects(
263
262
  a.w.client.messageMove.deleteMessages(
@@ -448,6 +447,7 @@ describe("the reconcile re-drives a move or delete already stranded", () => {
448
447
  await handleMessageMove(
449
448
  stamped<MessageMoveEvent>(move),
450
449
  a.w.log,
450
+ a.w.queues,
451
451
  1,
452
452
  a.w.scope,
453
453
  );
@@ -470,6 +470,7 @@ describe("the reconcile re-drives a move or delete already stranded", () => {
470
470
  await handlePlacementMovePush(
471
471
  stamped<PlacementMovePushEvent>(push),
472
472
  a.w.log,
473
+ a.w.queues,
473
474
  1,
474
475
  a.w.scope,
475
476
  );
@@ -501,6 +502,7 @@ describe("the reconcile re-drives a move or delete already stranded", () => {
501
502
  await handleMessageMove(
502
503
  stamped<MessageMoveEvent>(emitted[0]),
503
504
  a.w.log,
505
+ a.w.queues,
504
506
  1,
505
507
  a.w.scope,
506
508
  );
@@ -521,6 +523,7 @@ describe("the reconcile re-drives a move or delete already stranded", () => {
521
523
  messageId: a.message.messageId,
522
524
  }),
523
525
  a.w.log,
526
+ a.w.queues,
524
527
  1,
525
528
  a.w.scope,
526
529
  );
@@ -646,6 +649,7 @@ describe("a uid-0 copy whose event is gone", () => {
646
649
  await handleMessageCopy(
647
650
  stamped<MessageCopyEvent>(copy),
648
651
  a.w.log,
652
+ a.w.queues,
649
653
  1,
650
654
  a.w.scope,
651
655
  );
@@ -5,10 +5,11 @@ import { tmpdir } from "node:os";
5
5
  import { join } from "node:path";
6
6
  import {
7
7
  _resetForTest,
8
- getClient,
9
8
  type RemitClient,
10
9
  setClient,
11
10
  } from "@remit/backend/client";
11
+ import { createRemitClient } from "@remit/backend/create-remit-client";
12
+ import { buildSharedDeps } from "@remit/backend/shared-deps";
12
13
  import type {
13
14
  AccountItem,
14
15
  MailboxItem,
@@ -16,11 +17,20 @@ import type {
16
17
  MessageItem,
17
18
  OutboxMessageItem,
18
19
  } from "@remit/data-ports";
20
+ import {
21
+ type RecordingQueue,
22
+ recordingQueue,
23
+ } from "@remit/data-ports/recording-queue";
19
24
  import {
20
25
  AccountAuthType,
21
26
  ConnectionState,
22
27
  MailboxCursorState,
23
28
  } from "@remit/domain-enums";
29
+ import {
30
+ createSqliteRepositories,
31
+ openSqliteDatabases,
32
+ sqliteDatabasePathsFromEnv,
33
+ } from "@remit/drizzle-service";
24
34
  import { createShippedSqliteFiles } from "@remit/drizzle-service/test-sqlite";
25
35
  import type { Logger } from "@remit/logger";
26
36
  import type { ManagedConnectionFactory } from "@remit/mailbox-service";
@@ -28,13 +38,11 @@ import {
28
38
  eml,
29
39
  FakeImapServer,
30
40
  } from "@remit/mailbox-service/test-helpers/fake-imap";
31
- import {
32
- captureQueue,
33
- type QueueCapture,
34
- } from "@remit/mailbox-service/test-helpers/test-queue";
35
41
  import { serializeEncryptedPayload } from "@remit/secrets-service";
36
42
  import type { createConnectionScopeWithCredentials } from "../connection-scope.js";
37
43
  import { syncMessages } from "../handlers/sync-messages.js";
44
+ import type { WorkerQueues } from "../queues.js";
45
+ import { workerQueuesOn } from "./worker-queues.js";
38
46
 
39
47
  export interface LogLine {
40
48
  level: string;
@@ -84,8 +92,8 @@ export interface SeededMessage {
84
92
  }
85
93
 
86
94
  /**
87
- * One SQLite store per test file, over the shipped migrations, composed by the
88
- * backend's own SQLite composition root over filesystem storage — every
95
+ * One SQLite store per test file, over the shipped migrations, composed from
96
+ * the backend's SQLite repositories over filesystem storage — every
89
97
  * repository and service a handler reaches is the one a self-host process
90
98
  * runs. Each `world()` is a fresh account on it, with its own in-memory mail
91
99
  * server at the IMAP port, and a message reaches the store the way mail does:
@@ -103,12 +111,29 @@ export const openStore = async (): Promise<{
103
111
  process.env.BETTER_AUTH_URL = "https://mail.example.com";
104
112
  process.env.BETTER_AUTH_SECRET = "a-signing-secret-of-at-least-32-characters";
105
113
  _resetForTest();
106
- const client = await getClient();
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());
107
128
  setClient(client);
108
129
  return {
109
130
  client,
110
- world: (accountConfigId = `cfg-${randomUUID()}`) =>
111
- createWorld(client, accountConfigId),
131
+ world: (accountConfigId = `cfg-${randomUUID()}`) => {
132
+ const queue = recordingQueue();
133
+ const worldClient = clientOn(queue);
134
+ setClient(worldClient);
135
+ return createWorld(worldClient, queue, accountConfigId);
136
+ },
112
137
  close: () => {
113
138
  _resetForTest();
114
139
  rmSync(directory, { recursive: true, force: true });
@@ -135,7 +160,8 @@ export interface World {
135
160
  ) => Promise<Array<{ mailboxId: string; uid?: number; isDeleted: boolean }>>;
136
161
  queued: () => Array<Record<string, unknown>>;
137
162
  refuseSends: (error: Error | undefined) => void;
138
- queue: QueueCapture;
163
+ queue: RecordingQueue;
164
+ queues: WorkerQueues;
139
165
  draft: (
140
166
  attachment?: Buffer,
141
167
  ) => Promise<{ outbox: OutboxMessageItem; attachmentId?: string }>;
@@ -145,10 +171,9 @@ export interface World {
145
171
 
146
172
  const createWorld = async (
147
173
  client: RemitClient,
174
+ queue: RecordingQueue,
148
175
  accountConfigId: string,
149
176
  ): Promise<World> => {
150
- const queue = captureQueue();
151
-
152
177
  const imap = new FakeImapServer();
153
178
  const logs: LogLine[] = [];
154
179
  const password = await client.secrets.encrypt("app-password");
@@ -268,6 +293,7 @@ const createWorld = async (
268
293
  mailboxId: mailbox.mailboxId,
269
294
  },
270
295
  capturingLogger([]),
296
+ workerQueuesOn(queue),
271
297
  connectionFactory,
272
298
  );
273
299
  imap.connections = baseline.connections;
@@ -391,6 +417,7 @@ const createWorld = async (
391
417
  queued,
392
418
  refuseSends,
393
419
  queue,
420
+ queues: workerQueuesOn(queue),
394
421
  draft,
395
422
  scope,
396
423
  connectionFactory,
@@ -0,0 +1,11 @@
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,58 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import { describe, it } from "node:test";
3
- import type { Logger } from "@remit/logger";
4
- import type { ImapWorkerStopEvent } from "./events.js";
5
- import { processEvent } from "./processor.js";
6
-
7
- interface CapturedLogEntry {
8
- args: unknown[];
9
- }
10
-
11
- const createCapturingLogger = (): {
12
- log: Logger;
13
- infoCalls: CapturedLogEntry[];
14
- } => {
15
- const infoCalls: CapturedLogEntry[] = [];
16
- const noop = () => {};
17
- const log = {
18
- info: (...args: unknown[]) => infoCalls.push({ args }),
19
- warn: noop,
20
- error: noop,
21
- debug: noop,
22
- fatal: noop,
23
- trace: noop,
24
- child: () => log,
25
- } as unknown as Logger;
26
- return { log, infoCalls };
27
- };
28
-
29
- describe("processEvent — IMAP_WORKER_STOP", () => {
30
- it("returns undefined and logs the stop signal — tombstone fence on the account row already halts work", async () => {
31
- const event: ImapWorkerStopEvent = {
32
- type: "IMAP_WORKER_STOP",
33
- accountConfigId: "acfg_alice_replay_safe_test_id",
34
- accountId: "acct_alice_replay_safe_test_id",
35
- };
36
- const { log, infoCalls } = createCapturingLogger();
37
-
38
- const result = await processEvent(event, log);
39
-
40
- assert.equal(result, undefined);
41
- const matched = infoCalls.find((c) => {
42
- const [meta, msg] = c.args;
43
- return (
44
- typeof msg === "string" &&
45
- msg === "Imap worker stop signal received" &&
46
- typeof meta === "object" &&
47
- meta !== null &&
48
- (meta as { accountConfigId?: string }).accountConfigId ===
49
- event.accountConfigId &&
50
- (meta as { accountId?: string }).accountId === event.accountId
51
- );
52
- });
53
- assert.ok(
54
- matched,
55
- "processor must log the cascade-contract stop signal with both ids",
56
- );
57
- });
58
- });