@remit/imap-worker 0.0.81 → 0.0.82

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.81",
3
+ "version": "0.0.82",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "exports": {
@@ -711,6 +711,23 @@ describe("processMailboxManagement — a tagged NO the server means as success (
711
711
  assert.equal(called("mailbox.transition").length, 0);
712
712
  });
713
713
 
714
+ it("marks failed and acks when the server refuses the DELETE with NOPERM", async () => {
715
+ recordDeleteIntent();
716
+ h.connection.deleteMailbox = async () => {
717
+ throw Object.assign(new Error("Command failed"), {
718
+ serverResponseCode: "NOPERM",
719
+ responseText: "Permission denied",
720
+ });
721
+ };
722
+
723
+ await assert.doesNotReject(
724
+ processMailboxManagement(deleteEvent, noopLogger, deps()),
725
+ );
726
+ assert.equal(lastSettle().to, "failed");
727
+ assert.equal(lastSettle().set?.syncFailureReason, "Permission denied");
728
+ assert.equal(called("mailbox.deleteMailboxWithMail").length, 0);
729
+ });
730
+
714
731
  it("still marks failed and rethrows when the server fails for any other reason", async () => {
715
732
  recordDeleteIntent();
716
733
  h.connection.deleteMailbox = async () => {
@@ -747,6 +764,21 @@ describe("processMailboxManagement — a tagged NO the server means as success (
747
764
  assert.equal(lastSettle().to, "synced");
748
765
  });
749
766
 
767
+ it("marks failed with the server's reason and acks when a CREATE is refused", async () => {
768
+ h.connection.createMailbox = async () => {
769
+ throw Object.assign(new Error("Command failed"), {
770
+ serverResponseCode: "NOPERM",
771
+ responseText: "Permission denied",
772
+ });
773
+ };
774
+
775
+ await assert.doesNotReject(
776
+ processMailboxManagement(createEvent, noopLogger, deps()),
777
+ );
778
+ assert.equal(lastSettle().to, "failed");
779
+ assert.equal(lastSettle().set?.syncFailureReason, "Permission denied");
780
+ });
781
+
750
782
  it("still marks failed and rethrows when a CREATE fails for any other reason", async () => {
751
783
  h.connection.createMailbox = async () => {
752
784
  throw Object.assign(new Error("Command failed"), {
@@ -781,6 +813,25 @@ describe("processMailboxManagement — a tagged NO the server means as success (
781
813
  ]);
782
814
  });
783
815
 
816
+ it("marks failed with the server's reason and acks when a RENAME is refused", async () => {
817
+ recordRenameIntent();
818
+ h.connection.renameMailbox = async () => {
819
+ throw Object.assign(new Error("Command failed"), {
820
+ serverResponseCode: "CANNOT",
821
+ responseText: "Can't rename mailbox with its children",
822
+ });
823
+ };
824
+
825
+ await assert.doesNotReject(
826
+ processMailboxManagement(renameEvent, noopLogger, deps()),
827
+ );
828
+ assert.equal(lastSettle().to, "failed");
829
+ assert.equal(
830
+ lastSettle().set?.syncFailureReason,
831
+ "Can't rename mailbox with its children",
832
+ );
833
+ });
834
+
784
835
  it("still refuses the intent and rethrows when a RENAME fails for any other reason", async () => {
785
836
  recordRenameIntent();
786
837
  h.connection.renameMailbox = async () => {
@@ -9,7 +9,9 @@ import {
9
9
  FolderRenameSettleError,
10
10
  isMailboxAbsentUpstream,
11
11
  isMailboxPresentUpstream,
12
+ isMailboxRefusedUpstream,
12
13
  MailboxManagementService,
14
+ upstreamFailureReason,
13
15
  } from "@remit/mailbox-service";
14
16
  import { isAccountDeleted } from "../account-check.js";
15
17
  import { createConnectionScopeWithCredentials } from "../connection-scope.js";
@@ -47,12 +49,13 @@ const recordCreateOutcome = async (
47
49
  accountId: string,
48
50
  mailboxId: string,
49
51
  to: (typeof MailboxSyncStatus)[keyof typeof MailboxSyncStatus],
52
+ syncFailureReason = "",
50
53
  ): Promise<void> => {
51
54
  const written = await mailboxService.transition(accountId, mailboxId, {
52
55
  from: [MailboxSyncStatus.pending],
53
56
  wherePendingPath: null,
54
57
  to,
55
- set: {},
58
+ set: { syncFailureReason },
56
59
  });
57
60
  if (!written) throw new NotFoundError(`Mailbox not found: ${mailboxId}`);
58
61
  };
@@ -167,7 +170,15 @@ const handleCreate = async (
167
170
  accountId,
168
171
  mailboxId,
169
172
  MailboxSyncStatus.failed,
173
+ upstreamFailureReason(error),
170
174
  );
175
+ if (isMailboxRefusedUpstream(error)) {
176
+ log.error(
177
+ { accountId, mailboxId, path, intent: "create", error },
178
+ "Mail server refused the create",
179
+ );
180
+ return;
181
+ }
171
182
  throw error;
172
183
  }
173
184
  })
@@ -333,7 +344,22 @@ const handleRename = async (
333
344
  mailboxId,
334
345
  oldPath,
335
346
  newPath,
347
+ upstreamFailureReason(error),
336
348
  );
349
+ if (isMailboxRefusedUpstream(error)) {
350
+ log.error(
351
+ {
352
+ accountId,
353
+ mailboxId,
354
+ oldPath,
355
+ newPath,
356
+ intent: "rename",
357
+ error,
358
+ },
359
+ "Mail server refused the rename",
360
+ );
361
+ return;
362
+ }
337
363
  throw error;
338
364
  })
339
365
  .finally(() => scope.disconnect());
@@ -431,7 +457,11 @@ const handleDelete = async (
431
457
  // worker's backstop is unreachable from it; if something else
432
458
  // reaches it, the honest outcome is `failed`, not `synced` —
433
459
  // the folder was never deleted and nothing was undone.
434
- await managementService.failDelete(accountId, mailboxId);
460
+ await managementService.failDelete(
461
+ accountId,
462
+ mailboxId,
463
+ upstreamFailureReason(error),
464
+ );
435
465
  if (
436
466
  error instanceof Error &&
437
467
  error.message.includes("Cannot delete INBOX")
@@ -443,6 +473,13 @@ const handleDelete = async (
443
473
  // Don't rethrow — no retry can make this succeed.
444
474
  return;
445
475
  }
476
+ if (isMailboxRefusedUpstream(error)) {
477
+ log.error(
478
+ { accountId, mailboxId, path, intent: "delete", error },
479
+ "Mail server refused the delete",
480
+ );
481
+ return;
482
+ }
446
483
  throw error;
447
484
  }
448
485
  })