@remit/imap-worker 0.0.30 → 0.0.32

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remit/imap-worker",
3
- "version": "0.0.30",
3
+ "version": "0.0.32",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "exports": {
@@ -1,6 +1,6 @@
1
1
  import assert from "node:assert/strict";
2
- import { afterEach, describe, it, mock } from "node:test";
3
- import { getClient } from "@remit/backend/client";
2
+ import { afterEach, before, describe, it, mock } from "node:test";
3
+ import { getClient, type RemitClient, setClient } from "@remit/backend/client";
4
4
  import type { Logger } from "@remit/logger-lambda";
5
5
  import type { FlagPushEvent } from "../events.js";
6
6
  import {
@@ -58,6 +58,21 @@ describe("handleFlagPush — deleted mailbox is terminal (#287/#289)", () => {
58
58
  flagName,
59
59
  } as FlagPushEvent;
60
60
 
61
+ // The client is supplied by injection (`setClient`), so these tests register
62
+ // the repositories they then mock rather than reaching a composition.
63
+ before(() => {
64
+ setClient({
65
+ account: { get: async () => undefined },
66
+ message: { get: async () => undefined },
67
+ mailbox: { get: async () => undefined },
68
+ flagPush: {
69
+ find: async () => undefined,
70
+ updateState: async () => undefined,
71
+ delete: async () => undefined,
72
+ },
73
+ } as unknown as RemitClient);
74
+ });
75
+
61
76
  afterEach(() => mock.restoreAll());
62
77
 
63
78
  it("drops the orphaned marker and acks without pushing when the mailbox is gone", async () => {
@@ -106,6 +106,10 @@ const deps = (): MailboxManagementDeps =>
106
106
  h.calls.push({ method: "mailbox.update", args });
107
107
  if (h.mailboxUpdateError) throw h.mailboxUpdateError;
108
108
  },
109
+ findByPathPrefix: async (...args: unknown[]) => {
110
+ h.calls.push({ method: "mailbox.findByPathPrefix", args });
111
+ return [];
112
+ },
109
113
  delete: record("mailbox.delete"),
110
114
  },
111
115
  secrets: {},
@@ -70,15 +70,18 @@ const isMailboxPresentUpstream = (error: unknown): boolean => {
70
70
  * Pinned invariant for the whole-chain terminal guards below.
71
71
  *
72
72
  * Each handler wraps its `MailboxManagementService.sync*` chain in a try/catch
73
- * that treats a NotFoundError as terminal (ack with a WARN, issue #289). That
74
- * is only sound because `syncCreate` / `syncRename` / `syncDelete` and their
75
- * error-recovery branches here touch nothing but the single target mailbox
76
- * row via `mailboxService.get/update/delete(accountId, mailboxId)`. So the only
77
- * row a NotFoundError can refer to is that target, and its absence can only mean
78
- * the user deleted the folder mid-sync never an unrelated missing entity that
79
- * should have been retried. If a sync method ever reads or writes a second
80
- * entity, narrow these catches (match the mailboxId / re-check existence) before
81
- * a NotFoundError from elsewhere is silently acked.
73
+ * that treats a NotFoundError as terminal (ack with a WARN, issue #289). That is
74
+ * only sound while the sole row a NotFoundError can refer to is the target
75
+ * mailbox, so its absence can only mean the user deleted the folder mid-sync —
76
+ * never an unrelated missing entity that should have been retried.
77
+ *
78
+ * `syncCreate` and `syncDelete` hold to that by touching nothing but the target
79
+ * row. `syncRename` also writes the renamed subtree's descendant rows, and keeps
80
+ * the invariant by absorbing their NotFoundErrors itself: a descendant deleted
81
+ * mid-settle never reaches these catches. Any sync method that reads or writes a
82
+ * second entity owes the same, or these catches must be narrowed (match the
83
+ * mailboxId / re-check existence) before a NotFoundError from elsewhere is
84
+ * silently acked.
82
85
  */
83
86
 
84
87
  /**
@@ -1,6 +1,6 @@
1
1
  import assert from "node:assert";
2
- import { afterEach, describe, it, mock } from "node:test";
3
- import { getClient } from "@remit/backend/client";
2
+ import { afterEach, before, describe, it, mock } from "node:test";
3
+ import { getClient, type RemitClient, setClient } from "@remit/backend/client";
4
4
  import type { AccountItem, ThreadMessageItem } from "@remit/data-ports";
5
5
  import type { Logger } from "@remit/logger-lambda";
6
6
  import type { MessageMoveEvent } from "../events.js";
@@ -219,6 +219,17 @@ describe("handleMessageMove — deleted mailbox is terminal (#287/#289)", () =>
219
219
  timestamp: 1700000000000,
220
220
  } as MessageMoveEvent;
221
221
 
222
+ // The client is supplied by injection (`setClient`), so these tests register
223
+ // the repositories they then mock rather than reaching a composition.
224
+ before(() => {
225
+ setClient({
226
+ account: { get: async () => undefined },
227
+ mailbox: { get: async () => undefined },
228
+ message: { updateUid: async () => undefined },
229
+ secrets: { decrypt: async () => undefined },
230
+ } as unknown as RemitClient);
231
+ });
232
+
222
233
  afterEach(() => mock.restoreAll());
223
234
 
224
235
  it("acks without connecting when the source mailbox was deleted", async () => {
@@ -1,6 +1,6 @@
1
1
  import assert from "node:assert/strict";
2
- import { afterEach, describe, it, mock } from "node:test";
3
- import { getClient } from "@remit/backend/client";
2
+ import { afterEach, before, describe, it, mock } from "node:test";
3
+ import { getClient, type RemitClient, setClient } from "@remit/backend/client";
4
4
  import type { Logger } from "@remit/logger-lambda";
5
5
  import type { IImapConnection } from "@remit/mailbox-service";
6
6
  import type { PlacementMovePushEvent } from "../events.js";
@@ -295,6 +295,21 @@ describe("handlePlacementMovePush — deleted mailbox is terminal (#287/#289)",
295
295
  timestamp: 1700000000000,
296
296
  };
297
297
 
298
+ // The client is supplied by injection (`setClient`), so these tests register
299
+ // the repositories they then mock rather than reaching a composition.
300
+ before(() => {
301
+ setClient({
302
+ account: { get: async () => undefined },
303
+ message: { get: async () => undefined },
304
+ mailbox: { get: async () => undefined },
305
+ placementMove: {
306
+ find: async () => undefined,
307
+ updateState: async () => undefined,
308
+ delete: async () => undefined,
309
+ },
310
+ } as unknown as RemitClient);
311
+ });
312
+
298
313
  afterEach(() => mock.restoreAll());
299
314
 
300
315
  it("drops the marker and acks without pushing when a mailbox is gone", async () => {
@@ -221,10 +221,8 @@ describe("syncMessageBody — DLQ propagation (integrated, #1270)", () => {
221
221
  const cappedMailbox = (): MailboxItem =>
222
222
  ({ fullPath: "INBOX" }) as unknown as MailboxItem;
223
223
 
224
- // On the DynamoDB path (main repo, DATA_BACKEND unset) the client is injected
225
- // by the composition root; register a client whose repositories these tests
226
- // then mock. On the relational path (reader, DATA_BACKEND=postgres) getClient
227
- // builds its own client and this injection is inert.
224
+ // The client is supplied by injection (`setClient`), so these tests register
225
+ // the repositories they then mock rather than reaching a composition.
228
226
  before(() => {
229
227
  setClient({
230
228
  account: { get: async () => cappedAccount() },