@remit/backend 0.0.115 → 0.0.116

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.115",
3
+ "version": "0.0.116",
4
4
  "description": "Remit Mail Inspector API backend",
5
5
  "license": "MIT",
6
6
  "author": "",
@@ -467,6 +467,7 @@ describe("executeUnifiedThreadListing", () => {
467
467
  : [
468
468
  mailbox("m2-inbox", "a2", "INBOX"),
469
469
  mailbox("m2-archive", "a2", "Archive"),
470
+ mailbox("m2-trash", "a2", "Trash", ["Trash"]),
470
471
  ],
471
472
  },
472
473
  accountSetting: { listByAccountConfig: async () => [] },
@@ -730,4 +731,52 @@ describe("executeUnifiedThreadListing", () => {
730
731
  assert.deepEqual(response.items, []);
731
732
  assert.equal(response.count, 0);
732
733
  });
734
+
735
+ // #313: the Spam offer stated the junk share of the page the client had
736
+ // loaded, offered as a folder total. A count of one folder under the search's
737
+ // own criteria is what makes that number the folder's.
738
+ test("a folder scope narrows the search and the count to that folder", async () => {
739
+ const calls: Call[] = [];
740
+ const response = await executeUnifiedThreadListing(
741
+ buildListingClient(calls, 12),
742
+ CONFIG_ID,
743
+ { ...params({ mailboxId: "m2-archive", count: true }), searchText: "x" },
744
+ );
745
+
746
+ const searching = calls.find((call) => call.mode === "searchByDate");
747
+ const counting = calls.find((call) => call.mode === "count");
748
+ assert.deepEqual([...(searching?.mailboxIds ?? [])], ["m2-archive"]);
749
+ assert.deepEqual([...(counting?.mailboxIds ?? [])], ["m2-archive"]);
750
+ assert.equal(response.count, 12);
751
+ });
752
+
753
+ test("a folder scope intersects the account scope rather than replacing it", async () => {
754
+ const calls: Call[] = [];
755
+ const response = await executeUnifiedThreadListing(
756
+ buildListingClient(calls, 99),
757
+ CONFIG_ID,
758
+ {
759
+ ...params({ accountId: "a1", mailboxId: "m2-archive", count: true }),
760
+ searchText: "x",
761
+ },
762
+ );
763
+
764
+ assert.deepEqual(calls, []);
765
+ assert.equal(response.count, 0);
766
+ });
767
+
768
+ // Trash is the one folder the unscoped search never reaches, so a caller
769
+ // asking what it holds of a search is asking about mail the search does not
770
+ // see. Answering with the folder's own contents would widen the scope.
771
+ test("a folder the search scope excludes matches nothing", async () => {
772
+ const calls: Call[] = [];
773
+ const response = await executeUnifiedThreadListing(
774
+ buildListingClient(calls, 99),
775
+ CONFIG_ID,
776
+ { ...params({ mailboxId: "m2-trash", count: true }), searchText: "x" },
777
+ );
778
+
779
+ assert.deepEqual(calls, []);
780
+ assert.equal(response.count, 0);
781
+ });
733
782
  });
@@ -391,6 +391,29 @@ export const scopeToAccount = (
391
391
  );
392
392
  };
393
393
 
394
+ /**
395
+ * Narrow a mailbox scope to one folder.
396
+ *
397
+ * Intersects rather than replaces: a folder the mode's own scope does not hold —
398
+ * Trash, a muted folder, one of a muted account, one of another account when
399
+ * `accountId` narrowed first — leaves the empty set and matches nothing. A
400
+ * caller asking what one folder holds of a search must not be handed a folder
401
+ * the search itself does not reach.
402
+ *
403
+ * This is what makes a per-folder count of a cross-account search exact. The
404
+ * spam offer states how much of the search sits in a junk folder; before the
405
+ * parameter existed the only number available was the junk share of the page
406
+ * the client had loaded, which is a page length presented as a folder total
407
+ * (#313).
408
+ */
409
+ export const scopeToMailbox = (
410
+ mailboxIds: Set<string>,
411
+ mailboxId: string | undefined,
412
+ ): Set<string> => {
413
+ if (mailboxId === undefined) return mailboxIds;
414
+ return new Set(mailboxIds.has(mailboxId) ? [mailboxId] : []);
415
+ };
416
+
394
417
  /**
395
418
  * Minimal client surface `executeUnifiedThreadListing` needs, declared
396
419
  * structurally (like `ThreadSearchClient`) so the mode selection, the row
@@ -428,6 +451,7 @@ export type UnifiedThreadParams = {
428
451
  from?: string;
429
452
  subject?: string;
430
453
  accountId?: string;
454
+ mailboxId?: string;
431
455
  category?: MessageCategory[];
432
456
  unread?: boolean;
433
457
  attachments?: boolean;
@@ -462,10 +486,15 @@ export const executeUnifiedThreadListing = async (
462
486
  virtualCopyMailboxIds,
463
487
  } = await buildInboxMailboxMap(accountConfigId, client);
464
488
 
465
- // The account scope applies before the mode picks a set, so the listing, the
466
- // search and the count all narrow by the same rule.
489
+ // The account and folder scopes apply before the mode picks a set, so the
490
+ // listing, the search and the count all narrow by the same rule. Account
491
+ // first, folder second: the folder intersects what the account left, so a
492
+ // folder of another account narrows to nothing rather than reinstating one.
467
493
  const narrow = (mailboxIds: Set<string>): Set<string> =>
468
- scopeToAccount(mailboxIds, mailboxIdToAccountId, params.accountId);
494
+ scopeToMailbox(
495
+ scopeToAccount(mailboxIds, mailboxIdToAccountId, params.accountId),
496
+ params.mailboxId,
497
+ );
469
498
  const inboxMailboxIds = narrow(everyInboxMailboxId);
470
499
  const starredMailboxIds = narrow(everyStarredMailboxId);
471
500
  const searchMailboxIds = narrow(everySearchMailboxId);
@@ -581,6 +610,7 @@ export const UnifiedThreadOperations: Record<
581
610
  from,
582
611
  subject,
583
612
  accountId,
613
+ mailboxId,
584
614
  category,
585
615
  unread,
586
616
  attachments,
@@ -596,6 +626,7 @@ export const UnifiedThreadOperations: Record<
596
626
  from?: string;
597
627
  subject?: string;
598
628
  accountId?: string;
629
+ mailboxId?: string;
599
630
  category?: MessageCategory | MessageCategory[];
600
631
  unread?: boolean | string;
601
632
  attachments?: boolean | string;
@@ -624,6 +655,7 @@ export const UnifiedThreadOperations: Record<
624
655
  from: fromText || undefined,
625
656
  subject: subjectText || undefined,
626
657
  accountId: accountId || undefined,
658
+ mailboxId: mailboxId || undefined,
627
659
  category: toArray(category),
628
660
  unread: toBoolean(unread),
629
661
  attachments: toBoolean(attachments),