@remit/backend 0.0.64 → 0.0.66

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/backend",
3
- "version": "0.0.64",
3
+ "version": "0.0.66",
4
4
  "description": "Remit Mail Inspector API backend",
5
5
  "license": "MIT",
6
6
  "author": "",
@@ -1,6 +1,9 @@
1
1
  import assert from "node:assert/strict";
2
2
  import { describe, it } from "node:test";
3
- import { MoveNotSettledError } from "@remit/mailbox-service";
3
+ import {
4
+ MoveNotSettledError,
5
+ NoJunkMailboxError,
6
+ } from "@remit/mailbox-service";
4
7
  import { GENERIC_FAILURE_REASON, settleSpamReportBulk } from "./message.js";
5
8
 
6
9
  describe("settleSpamReportBulk", () => {
@@ -25,6 +28,19 @@ describe("settleSpamReportBulk", () => {
25
28
  ]);
26
29
  });
27
30
 
31
+ it("tells the user to create the missing Junk folder rather than to try again", async () => {
32
+ // Retrying cannot resolve this one, so the generic retry copy would be a
33
+ // dead end. The service's own text names the folder and the fix.
34
+ const outcome = await settleSpamReportBulk(["msg-1"], async () => {
35
+ throw new NoJunkMailboxError();
36
+ });
37
+
38
+ const reason = outcome.failures?.[0].reason ?? "";
39
+ assert.match(reason, /no Junk folder/);
40
+ assert.match(reason, /Create one/);
41
+ assert.notEqual(reason, GENERIC_FAILURE_REASON);
42
+ });
43
+
28
44
  it("omits failures entirely when every message succeeds", async () => {
29
45
  const outcome = await settleSpamReportBulk(
30
46
  ["msg-1", "msg-2"],
@@ -24,6 +24,7 @@ import {
24
24
  isMessageBodySyncBroken,
25
25
  MailboxCursorPausedError,
26
26
  MoveNotSettledError,
27
+ NoJunkMailboxError,
27
28
  } from "@remit/mailbox-service";
28
29
  import {
29
30
  isStorageNotFoundError as isStorageNotFoundErrorFromService,
@@ -339,22 +340,29 @@ export interface SpamReportBulkOutcome {
339
340
  }
340
341
 
341
342
  /**
342
- * The one designed, allowlisted failure reason returned to the client
343
- * verbatim everything else is flattened to `GENERIC_FAILURE_REASON`, same
344
- * policy as `error.ts`'s "Internal server error" flattening for an unhandled
345
- * error. Without this, an arbitrary thrown error's raw text — an internal
346
- * "No Junk mailbox found for account <id>", an AWS SDK message naming a
347
- * queue URL or `ECONNREFUSED host:port` would land straight in a 200
348
- * response body, since `SpamReportBulkResult.reason` is documented as shown
349
- * to the user as-is.
343
+ * The fallback for a failure nobody designed copy for — an AWS SDK message
344
+ * naming a queue URL, an `ECONNREFUSED host:port`, a programmer error.
345
+ * Everything reaching it is flattened, same policy as `error.ts`'s "Internal
346
+ * server error" flattening, because `SpamReportBulkResult.reason` is shown to
347
+ * the user as-is. It says "try again" because retrying is the only thing left
348
+ * to say about a cause we cannot name; an outcome the service DOES understand
349
+ * carries its own text through `DESIGNED_FAILURES` instead of landing here.
350
350
  */
351
351
  export const GENERIC_FAILURE_REASON =
352
- "This message could not be processed. Please try again.";
352
+ "This message could not be processed. Please try again. If it keeps failing, report it.";
353
+
354
+ /**
355
+ * The designed, user-facing outcomes whose own message is returned verbatim:
356
+ * each names what happened and what the user can do about it, which the
357
+ * generic text cannot.
358
+ */
359
+ const DESIGNED_FAILURES = [MoveNotSettledError, NoJunkMailboxError];
360
+
361
+ const isDesignedFailure = (reason: unknown): reason is Error =>
362
+ DESIGNED_FAILURES.some((designed) => reason instanceof designed);
353
363
 
354
364
  const failureReason = (reason: unknown): string =>
355
- reason instanceof MoveNotSettledError
356
- ? reason.message
357
- : GENERIC_FAILURE_REASON;
365
+ isDesignedFailure(reason) ? reason.message : GENERIC_FAILURE_REASON;
358
366
 
359
367
  /**
360
368
  * Drive one report-spam/not-spam operation per message, concurrently. Each
@@ -348,6 +348,24 @@ describe("an outbox entry that has left draft (#604)", () => {
348
348
  assert.equal(response.statusCode, 409);
349
349
  });
350
350
 
351
+ it("still discards a message that was sent but never filed", async () => {
352
+ installClient();
353
+ const outboxMessageId = await sentOutboxMessageId();
354
+ const client = await getClient();
355
+ await client.outboxMessage.update(ACCOUNT_CONFIG_ID, outboxMessageId, {
356
+ status: OutboxMessageStatus.unfiled,
357
+ });
358
+
359
+ const response = await respond(() =>
360
+ deleteDraft(
361
+ requestContext({ params: { outboxMessageId } }),
362
+ authorizedEvent(),
363
+ ),
364
+ );
365
+
366
+ assert.equal(response.statusCode, 204);
367
+ });
368
+
351
369
  it("still accepts an autosave PATCH while the entry is a draft", async () => {
352
370
  installClient();
353
371
  const draft = await createDraft(