@remit/backend 0.0.60 → 0.0.62

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.60",
3
+ "version": "0.0.62",
4
4
  "description": "Remit Mail Inspector API backend",
5
5
  "license": "MIT",
6
6
  "author": "",
@@ -0,0 +1,132 @@
1
+ import assert from "node:assert/strict";
2
+ import { afterEach, describe, it } from "node:test";
3
+ import type { AddressItem, ResultList } from "@remit/data-ports";
4
+ import type { APIGatewayProxyEvent } from "aws-lambda";
5
+ import type { Context } from "openapi-backend";
6
+ import { deriveAccountConfigId } from "../auth.js";
7
+ import {
8
+ _resetForTest,
9
+ type RemitClient,
10
+ setClient,
11
+ } from "../service/data-client.js";
12
+ import { AddressOperations } from "./address.js";
13
+
14
+ const searchAddresses =
15
+ AddressOperations.AddressOperations_searchAddresses as unknown as (
16
+ context: Context,
17
+ event: APIGatewayProxyEvent,
18
+ ) => Promise<ResultList<AddressItem>>;
19
+
20
+ const SUB = "cognito-sub-704";
21
+ const ACCOUNT_CONFIG_ID = deriveAccountConfigId(SUB);
22
+
23
+ const eventFor = (sub: string): APIGatewayProxyEvent =>
24
+ ({
25
+ requestContext: { authorizer: { claims: { sub } } },
26
+ }) as unknown as APIGatewayProxyEvent;
27
+
28
+ const contextFor = (query: { q: string; limit?: number }): Context =>
29
+ ({ request: { query } }) as unknown as Context;
30
+
31
+ const address = (over: Partial<AddressItem>): AddressItem =>
32
+ ({
33
+ addressId: "addr-1",
34
+ accountConfigId: ACCOUNT_CONFIG_ID,
35
+ localPart: "amsterdam",
36
+ domain: "pocahondas.nl",
37
+ normalizedEmail: "amsterdam@pocahondas.nl",
38
+ normalizedCompound: "pocahondas locatie amsterdam amsterdam@pocahondas.nl",
39
+ displayName: "Pocahondas locatie amsterdam",
40
+ flags: {},
41
+ inboundCount: 4,
42
+ outboundCount: 1,
43
+ replyCount: 2,
44
+ lastInboundAt: 1_000,
45
+ lastReplyAt: 900,
46
+ createdAt: 100,
47
+ updatedAt: 200,
48
+ ...over,
49
+ }) as AddressItem;
50
+
51
+ interface Listing {
52
+ accountConfigId: string;
53
+ search?: string;
54
+ limit?: number;
55
+ }
56
+
57
+ const clientReturning = (items: AddressItem[], seen: Listing[]): RemitClient =>
58
+ ({
59
+ address: {
60
+ listByAccountConfig: async (
61
+ input: Listing,
62
+ ): Promise<ResultList<AddressItem>> => {
63
+ seen.push(input);
64
+ return { items, continuationToken: undefined };
65
+ },
66
+ },
67
+ }) as unknown as RemitClient;
68
+
69
+ afterEach(() => {
70
+ _resetForTest();
71
+ });
72
+
73
+ describe("AddressOperations_searchAddresses", () => {
74
+ it("looks a partial term up against the caller's own addresses (#704)", async () => {
75
+ const seen: Listing[] = [];
76
+ setClient(clientReturning([address({})], seen));
77
+
78
+ const response = await searchAddresses(
79
+ contextFor({ q: "Po", limit: 8 }),
80
+ eventFor(SUB),
81
+ );
82
+
83
+ assert.deepEqual(seen, [
84
+ { accountConfigId: ACCOUNT_CONFIG_ID, search: "po", limit: 8 },
85
+ ]);
86
+ assert.deepEqual(
87
+ response.items.map((item) => item.normalizedEmail),
88
+ ["amsterdam@pocahondas.nl"],
89
+ );
90
+ assert.equal(response.items[0].displayName, "Pocahondas locatie amsterdam");
91
+ });
92
+
93
+ it("hands the suggestion list back in the order it was ranked", async () => {
94
+ const seen: Listing[] = [];
95
+ setClient(
96
+ clientReturning(
97
+ [
98
+ address({
99
+ addressId: "addr-frequent",
100
+ normalizedEmail: "zoe@pocahondas.nl",
101
+ }),
102
+ address({
103
+ addressId: "addr-stranger",
104
+ normalizedEmail: "aaron@pocahondas.nl",
105
+ inboundCount: 0,
106
+ replyCount: 0,
107
+ }),
108
+ ],
109
+ seen,
110
+ ),
111
+ );
112
+
113
+ const response = await searchAddresses(
114
+ contextFor({ q: "po" }),
115
+ eventFor(SUB),
116
+ );
117
+
118
+ assert.deepEqual(
119
+ response.items.map((item) => item.addressId),
120
+ ["addr-frequent", "addr-stranger"],
121
+ );
122
+ });
123
+
124
+ it("asks for a suggestion-sized window when the caller names no limit", async () => {
125
+ const seen: Listing[] = [];
126
+ setClient(clientReturning([], seen));
127
+
128
+ await searchAddresses(contextFor({ q: "po" }), eventFor(SUB));
129
+
130
+ assert.equal(seen[0].limit, 10);
131
+ });
132
+ });
@@ -25,6 +25,7 @@ import {
25
25
  OutboxAttachmentRepo,
26
26
  OutboxMessageRepo,
27
27
  QuarantineRepo,
28
+ SenderSignerStandingRepo,
28
29
  } from "@remit/drizzle-service";
29
30
  import { env } from "expect-env";
30
31
  import {
@@ -73,6 +74,7 @@ export const buildSqliteClient = async (): Promise<RemitClient> => {
73
74
  filterAnchorTransaction: new DrizzleFilterAnchorTransaction(genericDb),
74
75
  label: new LabelRepo(genericDb),
75
76
  messageLabel: new MessageLabelRepo(genericDb),
77
+ senderSignerStanding: new SenderSignerStandingRepo(genericDb),
76
78
  unitOfWork: new DrizzleUnitOfWork(messageDataDb),
77
79
  };
78
80
 
@@ -21,6 +21,7 @@ import type {
21
21
  IOutboxAttachmentRepository,
22
22
  IOutboxMessageRepository,
23
23
  IQuarantineRepository,
24
+ ISenderSignerStandingRepository,
24
25
  IThreadMessageRepository,
25
26
  IUnitOfWork,
26
27
  } from "@remit/data-ports";
@@ -102,6 +103,12 @@ export interface RemitClient {
102
103
  label: ILabelRepository;
103
104
  messageLabel: IMessageLabelRepository;
104
105
 
106
+ // How often a sender has been seen arriving under one DKIM signing identity,
107
+ // per account. Read by the authenticity derivation to tell a familiar
108
+ // third-party signer from a first sighting; written once per message by
109
+ // body-sync, which is its only writer.
110
+ senderSignerStanding: ISenderSignerStandingRepository;
111
+
105
112
  // Atomic write set for a message save. Present on the relational backend (real
106
113
  // transaction); absent on DynamoDB, where callers fall back to per-repo
107
114
  // writes with that backend's own (non-transactional) guarantees.
@@ -182,6 +189,7 @@ export interface RemitClientRepositories {
182
189
  filterAnchorTransaction: IFilterAnchorTransaction;
183
190
  label: ILabelRepository;
184
191
  messageLabel: IMessageLabelRepository;
192
+ senderSignerStanding: ISenderSignerStandingRepository;
185
193
  unitOfWork?: IUnitOfWork;
186
194
  }
187
195
 
@@ -403,6 +411,7 @@ export const createRemitClient = (deps: RemitClientDeps): RemitClient => {
403
411
  filterAnchorTransaction: repositories.filterAnchorTransaction,
404
412
  label: repositories.label,
405
413
  messageLabel: repositories.messageLabel,
414
+ senderSignerStanding: repositories.senderSignerStanding,
406
415
  unitOfWork: repositories.unitOfWork,
407
416
 
408
417
  storage,