@remit/backend 0.0.102 → 0.0.103
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
|
@@ -8,6 +8,8 @@ import type {
|
|
|
8
8
|
ThreadMessageItem,
|
|
9
9
|
} from "@remit/data-ports";
|
|
10
10
|
import { deriveAddressId } from "@remit/data-ports/id";
|
|
11
|
+
import { hasAbandonedDelete } from "@remit/data-ports/message-settlement";
|
|
12
|
+
import { MessageStatus, MessageSyncStatus } from "@remit/domain-enums";
|
|
11
13
|
import { type EnrichClient, enrichThreadRows } from "./enrichThreadRows.js";
|
|
12
14
|
|
|
13
15
|
const threadRow = (
|
|
@@ -250,3 +252,80 @@ describe("enrichThreadRows — muted", () => {
|
|
|
250
252
|
assert.equal(result?.muted, false);
|
|
251
253
|
});
|
|
252
254
|
});
|
|
255
|
+
|
|
256
|
+
describe("enrichThreadRows — the mutation pair", () => {
|
|
257
|
+
const withMessage = (message: Partial<MessageItem>): EnrichClient => ({
|
|
258
|
+
message: {
|
|
259
|
+
get: async () =>
|
|
260
|
+
[{ messageId: "msg-1", ...message }] as unknown as MessageItem[],
|
|
261
|
+
},
|
|
262
|
+
address: { getAddress: async () => [] },
|
|
263
|
+
messageLabel: { listByMessageIds: async () => [] },
|
|
264
|
+
label: { listByAccountConfig: async () => [] },
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
const project = async (message: Partial<MessageItem>) => {
|
|
268
|
+
const [result] = await enrichThreadRows(
|
|
269
|
+
[threadRow("tm-1", "msg-1")],
|
|
270
|
+
withMessage(message),
|
|
271
|
+
"acc-1",
|
|
272
|
+
);
|
|
273
|
+
return result;
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
test("projects the pair an abandoned delete leaves behind", async () => {
|
|
277
|
+
const result = await project({
|
|
278
|
+
status: MessageStatus.active,
|
|
279
|
+
syncStatus: MessageSyncStatus.failed,
|
|
280
|
+
});
|
|
281
|
+
assert.equal(result?.status, MessageStatus.active);
|
|
282
|
+
assert.equal(result?.syncStatus, MessageSyncStatus.failed);
|
|
283
|
+
assert.equal(
|
|
284
|
+
hasAbandonedDelete({
|
|
285
|
+
status: result?.status,
|
|
286
|
+
syncStatus: result?.syncStatus,
|
|
287
|
+
}),
|
|
288
|
+
true,
|
|
289
|
+
);
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
test("projects a move mid-retry without calling it a give-up", async () => {
|
|
293
|
+
const result = await project({
|
|
294
|
+
status: MessageStatus.moving,
|
|
295
|
+
syncStatus: MessageSyncStatus.failed,
|
|
296
|
+
});
|
|
297
|
+
assert.equal(result?.status, MessageStatus.moving);
|
|
298
|
+
assert.equal(result?.syncStatus, MessageSyncStatus.failed);
|
|
299
|
+
assert.equal(
|
|
300
|
+
hasAbandonedDelete({
|
|
301
|
+
status: result?.status,
|
|
302
|
+
syncStatus: result?.syncStatus,
|
|
303
|
+
}),
|
|
304
|
+
false,
|
|
305
|
+
);
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
test("projects an ordinary inbound row", async () => {
|
|
309
|
+
const result = await project({
|
|
310
|
+
status: MessageStatus.active,
|
|
311
|
+
syncStatus: MessageSyncStatus.pending,
|
|
312
|
+
});
|
|
313
|
+
assert.equal(result?.status, MessageStatus.active);
|
|
314
|
+
assert.equal(result?.syncStatus, MessageSyncStatus.pending);
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
test("a row whose Message row is missing claims no mutation", async () => {
|
|
318
|
+
const [result] = await enrichThreadRows(
|
|
319
|
+
[threadRow("tm-1", "msg-1")],
|
|
320
|
+
buildClient([], []),
|
|
321
|
+
"acc-1",
|
|
322
|
+
);
|
|
323
|
+
assert.equal(
|
|
324
|
+
hasAbandonedDelete({
|
|
325
|
+
status: result?.status,
|
|
326
|
+
syncStatus: result?.syncStatus,
|
|
327
|
+
}),
|
|
328
|
+
false,
|
|
329
|
+
);
|
|
330
|
+
});
|
|
331
|
+
});
|
|
@@ -7,7 +7,12 @@ import type {
|
|
|
7
7
|
ThreadMessageItem,
|
|
8
8
|
} from "@remit/data-ports";
|
|
9
9
|
import { deriveAddressId } from "@remit/data-ports/id";
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
MessageStatus,
|
|
12
|
+
MessageSyncStatus,
|
|
13
|
+
SenderTrust,
|
|
14
|
+
StarColor,
|
|
15
|
+
} from "@remit/domain-enums";
|
|
11
16
|
import { deriveAutoMoved } from "./autoMoved.js";
|
|
12
17
|
import { deriveMuted } from "./deriveMuted.js";
|
|
13
18
|
import { deriveSenderTrust } from "./senderTrust.js";
|
|
@@ -56,6 +61,8 @@ const toResponse = (item: ThreadMessageItem): ThreadMessageResponse => ({
|
|
|
56
61
|
updatedAt: item.updatedAt,
|
|
57
62
|
senderTrust: SenderTrust.Unknown,
|
|
58
63
|
muted: false,
|
|
64
|
+
status: MessageStatus.active,
|
|
65
|
+
syncStatus: MessageSyncStatus.pending,
|
|
59
66
|
});
|
|
60
67
|
|
|
61
68
|
/**
|
|
@@ -102,8 +109,13 @@ export const planBatchFetch = (rows: ThreadMessageItem[]): BatchPlan => {
|
|
|
102
109
|
/**
|
|
103
110
|
* Enrich a page of ThreadMessage rows with `senderTrust` and `muted` (both
|
|
104
111
|
* derived from the From Address's flags map), `authenticity`, `autoMoved` and
|
|
105
|
-
* `spamReport
|
|
106
|
-
*
|
|
112
|
+
* `spamReport`, and the `status`/`syncStatus` pair an IMAP mutation leaves
|
|
113
|
+
* behind (all projected straight from the Message row — no ThreadMessage
|
|
114
|
+
* column of their own, see `deriveAutoMoved`). `toResponse` seeds that pair
|
|
115
|
+
* with the ordinary inbound values, which the projection then overwrites; a
|
|
116
|
+
* ThreadMessage row whose Message row is missing from the batch is broken in a
|
|
117
|
+
* way this read path cannot report, and claiming a mutation it has no evidence
|
|
118
|
+
* of would be worse.
|
|
107
119
|
*
|
|
108
120
|
* `category` is not enriched: it is denormalized onto the ThreadMessage row
|
|
109
121
|
* (shared with `Message.category`'s write-once value, see body-sync.ts) and
|
|
@@ -174,6 +186,12 @@ export const enrichThreadRows = async (
|
|
|
174
186
|
const spamReportByMessageId = new Map(
|
|
175
187
|
messages.map((m) => [m.messageId, m.spamReport]),
|
|
176
188
|
);
|
|
189
|
+
const settlementByMessageId = new Map(
|
|
190
|
+
messages.map((m) => [
|
|
191
|
+
m.messageId,
|
|
192
|
+
{ status: m.status, syncStatus: m.syncStatus },
|
|
193
|
+
]),
|
|
194
|
+
);
|
|
177
195
|
const trustByAddressId = new Map(
|
|
178
196
|
addresses.map((a) => [a.addressId, deriveSenderTrust(a.flags)]),
|
|
179
197
|
);
|
|
@@ -194,8 +212,10 @@ export const enrichThreadRows = async (
|
|
|
194
212
|
? (mutedByAddressId.get(addressId) ?? false)
|
|
195
213
|
: false;
|
|
196
214
|
const labels = labelsByMessageId.get(row.messageId);
|
|
215
|
+
const settlement = settlementByMessageId.get(row.messageId);
|
|
197
216
|
return {
|
|
198
217
|
...base,
|
|
218
|
+
...(settlement ?? {}),
|
|
199
219
|
...(authenticity !== undefined ? { authenticity } : {}),
|
|
200
220
|
...(autoMoved !== undefined ? { autoMoved } : {}),
|
|
201
221
|
...(labels !== undefined ? { labels } : {}),
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
2
|
import { describe, it } from "node:test";
|
|
3
3
|
import type { ThreadMessageResponse } from "@remit/api-openapi-types";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
MessageCategory,
|
|
6
|
+
MessageStatus,
|
|
7
|
+
MessageSyncStatus,
|
|
8
|
+
SenderTrust,
|
|
9
|
+
StarColor,
|
|
10
|
+
} from "@remit/domain-enums";
|
|
5
11
|
import {
|
|
6
12
|
filterByOffRowCriteria,
|
|
7
13
|
hasOffRowCriteria,
|
|
@@ -30,6 +36,8 @@ const row = (
|
|
|
30
36
|
updatedAt: 0,
|
|
31
37
|
senderTrust: SenderTrust.Unknown,
|
|
32
38
|
muted: false,
|
|
39
|
+
status: MessageStatus.active,
|
|
40
|
+
syncStatus: MessageSyncStatus.pending,
|
|
33
41
|
...overrides,
|
|
34
42
|
});
|
|
35
43
|
|
package/src/handlers/message.ts
CHANGED
|
@@ -454,6 +454,8 @@ export const MessageOperations: Record<
|
|
|
454
454
|
internalDate: message.internalDate,
|
|
455
455
|
messageIdHeader: message.messageIdHeader,
|
|
456
456
|
authenticity: message.authenticity,
|
|
457
|
+
status: message.status,
|
|
458
|
+
syncStatus: message.syncStatus,
|
|
457
459
|
...(autoMoved ? { autoMoved } : {}),
|
|
458
460
|
...(labels.length > 0 ? { labels } : {}),
|
|
459
461
|
...(message.spamReport ? { spamReport: message.spamReport } : {}),
|