@remit/backend 0.0.143 → 0.0.145

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.
Files changed (279) hide show
  1. package/dist/dev-server/content-handler.d.ts +38 -0
  2. package/dist/dev-server/content-handler.js +46 -0
  3. package/dist/src/api.d.ts +5 -0
  4. package/dist/src/api.js +73 -0
  5. package/dist/src/auth.d.ts +24 -0
  6. package/dist/src/auth.js +81 -0
  7. package/dist/src/config/api-config.d.ts +20 -0
  8. package/dist/src/config/api-config.js +1 -0
  9. package/dist/src/config/from-env.d.ts +2 -0
  10. package/dist/src/config/from-env.js +65 -0
  11. package/dist/src/config/msoauth-secrets-manager.d.ts +2 -0
  12. package/dist/src/config/msoauth-secrets-manager.js +28 -0
  13. package/dist/src/config/msoauth.d.ts +19 -0
  14. package/dist/src/config/msoauth.js +6 -0
  15. package/{src/derive/autoMoved.ts → dist/src/derive/autoMoved.d.ts} +1 -29
  16. package/dist/src/derive/autoMoved.js +49 -0
  17. package/dist/src/derive/contentSignature.d.ts +40 -0
  18. package/{src/derive/contentSignature.ts → dist/src/derive/contentSignature.js} +23 -77
  19. package/dist/src/derive/contentUrl.d.ts +33 -0
  20. package/dist/src/derive/contentUrl.js +27 -0
  21. package/{src/derive/deriveMuted.ts → dist/src/derive/deriveMuted.d.ts} +1 -3
  22. package/dist/src/derive/deriveMuted.js +7 -0
  23. package/dist/src/derive/enrichThreadRows.d.ts +118 -0
  24. package/dist/src/derive/enrichThreadRows.js +151 -0
  25. package/dist/src/derive/filterThreadCriteria.d.ts +23 -0
  26. package/dist/src/derive/filterThreadCriteria.js +24 -0
  27. package/{src/derive/pendingMoveCounts.ts → dist/src/derive/pendingMoveCounts.d.ts} +5 -53
  28. package/dist/src/derive/pendingMoveCounts.js +53 -0
  29. package/{src/derive/senderTrust.ts → dist/src/derive/senderTrust.d.ts} +1 -9
  30. package/dist/src/derive/senderTrust.js +19 -0
  31. package/dist/src/error.d.ts +21 -0
  32. package/dist/src/error.js +85 -0
  33. package/dist/src/handlers/account-guards.d.ts +36 -0
  34. package/dist/src/handlers/account-guards.js +107 -0
  35. package/dist/src/handlers/account-oauth.d.ts +15 -0
  36. package/dist/src/handlers/account-oauth.js +334 -0
  37. package/dist/src/handlers/account-overrides.d.ts +53 -0
  38. package/dist/src/handlers/account-overrides.js +233 -0
  39. package/dist/src/handlers/account-signature.d.ts +28 -0
  40. package/dist/src/handlers/account-signature.js +85 -0
  41. package/dist/src/handlers/account.d.ts +7 -0
  42. package/dist/src/handlers/account.js +337 -0
  43. package/dist/src/handlers/address.d.ts +35 -0
  44. package/dist/src/handlers/address.js +133 -0
  45. package/dist/src/handlers/calendar-event.d.ts +70 -0
  46. package/dist/src/handlers/calendar-event.js +417 -0
  47. package/dist/src/handlers/calendar-feed.d.ts +47 -0
  48. package/dist/src/handlers/calendar-feed.js +184 -0
  49. package/dist/src/handlers/calendar-suggestion.d.ts +47 -0
  50. package/dist/src/handlers/calendar-suggestion.js +204 -0
  51. package/dist/src/handlers/calendar.d.ts +116 -0
  52. package/dist/src/handlers/calendar.js +323 -0
  53. package/dist/src/handlers/config.d.ts +43 -0
  54. package/dist/src/handlers/config.js +164 -0
  55. package/dist/src/handlers/ensure-account-config.d.ts +9 -0
  56. package/dist/src/handlers/ensure-account-config.js +27 -0
  57. package/dist/src/handlers/filter.d.ts +3 -0
  58. package/dist/src/handlers/filter.js +88 -0
  59. package/dist/src/handlers/folder-role-appointments.d.ts +68 -0
  60. package/dist/src/handlers/folder-role-appointments.js +168 -0
  61. package/dist/src/handlers/folder-role-labels.d.ts +26 -0
  62. package/dist/src/handlers/folder-role-labels.js +40 -0
  63. package/dist/src/handlers/folder-role.d.ts +9 -0
  64. package/dist/src/handlers/folder-role.js +37 -0
  65. package/dist/src/handlers/index.d.ts +88 -0
  66. package/dist/src/handlers/index.js +62 -0
  67. package/dist/src/handlers/label.d.ts +43 -0
  68. package/dist/src/handlers/label.js +107 -0
  69. package/dist/src/handlers/mailbox-bindings.d.ts +41 -0
  70. package/dist/src/handlers/mailbox-bindings.js +61 -0
  71. package/dist/src/handlers/mailbox.d.ts +23 -0
  72. package/dist/src/handlers/mailbox.js +211 -0
  73. package/dist/src/handlers/me.d.ts +2 -0
  74. package/dist/src/handlers/me.js +117 -0
  75. package/dist/src/handlers/message.d.ts +82 -0
  76. package/dist/src/handlers/message.js +595 -0
  77. package/dist/src/handlers/organize.d.ts +10 -0
  78. package/dist/src/handlers/organize.js +103 -0
  79. package/dist/src/handlers/outbox-attachment.d.ts +25 -0
  80. package/dist/src/handlers/outbox-attachment.js +77 -0
  81. package/dist/src/handlers/outbox.d.ts +4 -0
  82. package/dist/src/handlers/outbox.js +136 -0
  83. package/dist/src/handlers/sync-progress.d.ts +46 -0
  84. package/dist/src/handlers/sync-progress.js +63 -0
  85. package/dist/src/handlers/sync.d.ts +28 -0
  86. package/dist/src/handlers/sync.js +73 -0
  87. package/dist/src/handlers/system-update.d.ts +2 -0
  88. package/dist/src/handlers/system-update.js +159 -0
  89. package/dist/src/handlers/thread.d.ts +148 -0
  90. package/dist/src/handlers/thread.js +228 -0
  91. package/dist/src/handlers/unified-threads.d.ts +247 -0
  92. package/dist/src/handlers/unified-threads.js +406 -0
  93. package/{src/handlers/vip-suggestions.ts → dist/src/handlers/vip-suggestions.d.ts} +1 -13
  94. package/dist/src/handlers/vip-suggestions.js +16 -0
  95. package/dist/src/index.d.ts +5 -0
  96. package/dist/src/index.js +53 -0
  97. package/{src/json.ts → dist/src/json.d.ts} +1 -4
  98. package/dist/src/json.js +10 -0
  99. package/dist/src/jwt-auth.d.ts +15 -0
  100. package/dist/src/jwt-auth.js +45 -0
  101. package/dist/src/lambda.d.ts +13 -0
  102. package/dist/src/lambda.js +33 -0
  103. package/dist/src/request.d.ts +9 -0
  104. package/dist/src/request.js +17 -0
  105. package/dist/src/response.d.ts +44 -0
  106. package/dist/src/response.js +80 -0
  107. package/dist/src/serve.d.ts +31 -0
  108. package/dist/src/serve.js +117 -0
  109. package/dist/src/service/account-deletion.d.ts +20 -0
  110. package/dist/src/service/account-deletion.js +33 -0
  111. package/dist/src/service/compose-sqlite.d.ts +3 -0
  112. package/dist/src/service/compose-sqlite.js +13 -0
  113. package/dist/src/service/create-remit-client.d.ts +109 -0
  114. package/dist/src/service/create-remit-client.js +188 -0
  115. package/dist/src/service/data-client.d.ts +12 -0
  116. package/dist/src/service/data-client.js +42 -0
  117. package/dist/src/service/export-identity.d.ts +4 -0
  118. package/dist/src/service/export-identity.js +33 -0
  119. package/dist/src/service/fire-and-forget.d.ts +54 -0
  120. package/dist/src/service/fire-and-forget.js +37 -0
  121. package/dist/src/service/organize.d.ts +181 -0
  122. package/dist/src/service/organize.js +367 -0
  123. package/dist/src/service/queues.d.ts +12 -0
  124. package/dist/src/service/queues.js +1 -0
  125. package/dist/src/service/refusal.d.ts +4 -0
  126. package/dist/src/service/refusal.js +20 -0
  127. package/dist/src/service/sender-category.d.ts +51 -0
  128. package/{src/service/sender-category.ts → dist/src/service/sender-category.js} +40 -81
  129. package/dist/src/service/shared-deps.d.ts +6 -0
  130. package/dist/src/service/shared-deps.js +24 -0
  131. package/dist/src/service/trigger-sync.d.ts +57 -0
  132. package/dist/src/service/trigger-sync.js +31 -0
  133. package/dist/src/types.d.ts +72 -0
  134. package/dist/src/types.js +1 -0
  135. package/package.json +125 -98
  136. package/.env.test +0 -4
  137. package/dev-server/content-auth.test.ts +0 -113
  138. package/dev-server/content-auth.ts +0 -54
  139. package/dev-server/content-handler.test.ts +0 -105
  140. package/dev-server/content-handler.ts +0 -79
  141. package/dev-server/content-path.test.ts +0 -37
  142. package/dev-server/content-path.ts +0 -27
  143. package/dev-server/cors.test.ts +0 -48
  144. package/dev-server/cors.ts +0 -25
  145. package/dev-server/keep-alive.test.ts +0 -24
  146. package/dev-server/lambda-helpers.ts +0 -69
  147. package/dev-server/log-output.test.ts +0 -173
  148. package/dev-server/oauth-callback-route.test.ts +0 -66
  149. package/dev-server/relational-health.test.ts +0 -83
  150. package/dev-server/relational-health.ts +0 -39
  151. package/dev-server/server.ts +0 -384
  152. package/dev-server/sync-age.test.ts +0 -91
  153. package/dev-server/sync-age.ts +0 -55
  154. package/dev-server/upload-handler.ts +0 -132
  155. package/dev-server/upload-route.test.ts +0 -196
  156. package/scripts/backfill-classification.ts +0 -91
  157. package/scripts/backfill-list-id.ts +0 -83
  158. package/scripts/config-save.ts +0 -128
  159. package/src/auth.test.ts +0 -12
  160. package/src/auth.ts +0 -92
  161. package/src/config/msoauth.test.ts +0 -48
  162. package/src/config/msoauth.ts +0 -48
  163. package/src/data-backend.test.ts +0 -23
  164. package/src/data-backend.ts +0 -14
  165. package/src/derive/autoMoved.test.ts +0 -104
  166. package/src/derive/contentSignature.test.ts +0 -161
  167. package/src/derive/contentUrl.test.ts +0 -172
  168. package/src/derive/contentUrl.ts +0 -86
  169. package/src/derive/deriveMuted.test.ts +0 -34
  170. package/src/derive/enrichThreadRows.test.ts +0 -360
  171. package/src/derive/enrichThreadRows.ts +0 -234
  172. package/src/derive/filterThreadCriteria.test.ts +0 -126
  173. package/src/derive/filterThreadCriteria.ts +0 -51
  174. package/src/derive/senderTrust.test.ts +0 -67
  175. package/src/error.test.ts +0 -201
  176. package/src/error.ts +0 -123
  177. package/src/handlers/account-guards.test.ts +0 -67
  178. package/src/handlers/account-guards.ts +0 -169
  179. package/src/handlers/account-oauth-callback.test.ts +0 -155
  180. package/src/handlers/account-oauth-consent.test.ts +0 -336
  181. package/src/handlers/account-oauth-start.test.ts +0 -191
  182. package/src/handlers/account-oauth.test.ts +0 -439
  183. package/src/handlers/account-oauth.ts +0 -635
  184. package/src/handlers/account-overrides.test.ts +0 -88
  185. package/src/handlers/account-overrides.ts +0 -323
  186. package/src/handlers/account-ownership.ts +0 -25
  187. package/src/handlers/account-signature.ts +0 -117
  188. package/src/handlers/account-synced-services.test.ts +0 -245
  189. package/src/handlers/account.ts +0 -552
  190. package/src/handlers/address.test.ts +0 -381
  191. package/src/handlers/address.ts +0 -200
  192. package/src/handlers/calendar-event.test.ts +0 -685
  193. package/src/handlers/calendar-event.ts +0 -738
  194. package/src/handlers/calendar-feed-validators.test.ts +0 -228
  195. package/src/handlers/calendar-feed.test.ts +0 -439
  196. package/src/handlers/calendar-feed.ts +0 -291
  197. package/src/handlers/calendar-sqlite-fixture.ts +0 -36
  198. package/src/handlers/calendar-suggestion.test.ts +0 -1269
  199. package/src/handlers/calendar-suggestion.ts +0 -425
  200. package/src/handlers/calendar.test.ts +0 -1080
  201. package/src/handlers/calendar.ts +0 -517
  202. package/src/handlers/config.test.ts +0 -245
  203. package/src/handlers/config.ts +0 -332
  204. package/src/handlers/ensure-account-config.ts +0 -30
  205. package/src/handlers/filter.test.ts +0 -644
  206. package/src/handlers/filter.ts +0 -518
  207. package/src/handlers/folder-role-appointments.test.ts +0 -438
  208. package/src/handlers/folder-role-appointments.ts +0 -249
  209. package/src/handlers/folder-role-labels.ts +0 -63
  210. package/src/handlers/folder-role.test.ts +0 -209
  211. package/src/handlers/folder-role.ts +0 -80
  212. package/src/handlers/index.ts +0 -83
  213. package/src/handlers/label.test.ts +0 -112
  214. package/src/handlers/label.ts +0 -210
  215. package/src/handlers/mailbox-bindings.ts +0 -111
  216. package/src/handlers/mailbox-rename-refusal.test.ts +0 -247
  217. package/src/handlers/mailbox-settled-target.test.ts +0 -251
  218. package/src/handlers/mailbox.test.ts +0 -280
  219. package/src/handlers/mailbox.ts +0 -581
  220. package/src/handlers/me.test.ts +0 -81
  221. package/src/handlers/me.ts +0 -203
  222. package/src/handlers/message-read-backfill.test.ts +0 -103
  223. package/src/handlers/message.test.ts +0 -131
  224. package/src/handlers/message.ts +0 -1131
  225. package/src/handlers/organize.test.ts +0 -277
  226. package/src/handlers/organize.ts +0 -197
  227. package/src/handlers/outbox-attachment.test.ts +0 -765
  228. package/src/handlers/outbox-attachment.ts +0 -126
  229. package/src/handlers/outbox-list.test.ts +0 -179
  230. package/src/handlers/outbox.test.ts +0 -772
  231. package/src/handlers/outbox.ts +0 -258
  232. package/src/handlers/search.test.ts +0 -161
  233. package/src/handlers/search.ts +0 -105
  234. package/src/handlers/sync-progress.test.ts +0 -205
  235. package/src/handlers/sync-progress.ts +0 -99
  236. package/src/handlers/sync.test.ts +0 -168
  237. package/src/handlers/sync.ts +0 -118
  238. package/src/handlers/system-update.test.ts +0 -497
  239. package/src/handlers/system-update.ts +0 -230
  240. package/src/handlers/thread.test.ts +0 -396
  241. package/src/handlers/thread.ts +0 -427
  242. package/src/handlers/unified-threads-mail-sources.sqlite.test.ts +0 -197
  243. package/src/handlers/unified-threads.test.ts +0 -785
  244. package/src/handlers/unified-threads.ts +0 -669
  245. package/src/index.test.ts +0 -83
  246. package/src/index.ts +0 -228
  247. package/src/jwt-auth.test.ts +0 -152
  248. package/src/jwt-auth.ts +0 -132
  249. package/src/log-redaction.test.ts +0 -144
  250. package/src/openapi-spec.test.ts +0 -118
  251. package/src/request-context.test.ts +0 -61
  252. package/src/request-context.ts +0 -33
  253. package/src/request-pipeline.test.ts +0 -88
  254. package/src/request.ts +0 -10
  255. package/src/response.test.ts +0 -130
  256. package/src/response.ts +0 -144
  257. package/src/service/compose-sqlite.ts +0 -98
  258. package/src/service/config-import.ts +0 -39
  259. package/src/service/create-remit-client.ts +0 -522
  260. package/src/service/data-client.test.ts +0 -97
  261. package/src/service/data-client.ts +0 -58
  262. package/src/service/export-identity.ts +0 -39
  263. package/src/service/filter.ts +0 -40
  264. package/src/service/fire-and-forget.test.ts +0 -126
  265. package/src/service/fire-and-forget.ts +0 -79
  266. package/src/service/localhost-env-config.test.ts +0 -36
  267. package/src/service/organize-queue.ts +0 -98
  268. package/src/service/organize.test.ts +0 -1451
  269. package/src/service/organize.ts +0 -1025
  270. package/src/service/read-path-filter-config.test.ts +0 -243
  271. package/src/service/semantic-capability.test.ts +0 -78
  272. package/src/service/semantic-capability.ts +0 -95
  273. package/src/service/sender-category.test.ts +0 -235
  274. package/src/service/sqs.ts +0 -4
  275. package/src/service/trigger-sync.test.ts +0 -205
  276. package/src/service/trigger-sync.ts +0 -100
  277. package/src/stdout-capture-fixture.ts +0 -40
  278. package/src/types.ts +0 -271
  279. package/tsconfig.json +0 -7
@@ -1,86 +0,0 @@
1
- /**
2
- * Build the public CloudFront URL that fetches a single body part for a
3
- * message. The path layout is what the Lambda@Edge JWT verifier expects
4
- * (PR 1 of #224): `/content/accounts/{accountConfigId}/{accountId}/messages/
5
- * {messageId}/parts/{partPath}`.
6
- *
7
- * The function is intentionally pure — domain comes from configuration so
8
- * tests can pin the output without env vars. `partPath` is passed through
9
- * `encodeURIComponent` because IMAP section paths are dot-separated digits
10
- * (e.g. `1`, `1.2`, `2.1.3`) but a defensive encode keeps the builder safe
11
- * if the path ever carries unexpected characters.
12
- */
13
- import type { ContentSigner } from "./contentSignature.js";
14
-
15
- export interface BuildContentUrlInput {
16
- domain: string;
17
- accountConfigId: string;
18
- accountId: string;
19
- messageId: string;
20
- partPath: string;
21
- /**
22
- * When present (self-host stack), append an `exp`/`sig` signature scoped to
23
- * this account's storage path. Absent on AWS, where Lambda@Edge guards the
24
- * URL and the path stays unsigned. See `contentSignature.ts`.
25
- */
26
- sign?: ContentSigner;
27
- }
28
-
29
- export interface BuildStorageContentUrlInput {
30
- domain: string;
31
- storageKey: string;
32
- sign?: ContentSigner;
33
- }
34
-
35
- export const buildStorageContentUrl = (
36
- input: BuildStorageContentUrlInput,
37
- ): string => {
38
- const { domain, storageKey, sign } = input;
39
- const normalizedDomain = domain.replace(/\/+$/, "");
40
- const base = normalizedDomain.startsWith("http")
41
- ? normalizedDomain
42
- : `https://${normalizedDomain}`;
43
- // The signed message is the decoded, account-scoped storage path — the same
44
- // value the `/content` route recomputes from `req.path`. Keep it in lockstep
45
- // with the verifier's `req.path.replace(/^\/content\//, "")`.
46
- const safeKey = storageKey
47
- .split("/")
48
- .map((segment) => encodeURIComponent(segment))
49
- .join("/");
50
- const url = `${base}/content/${safeKey}`;
51
- if (!sign) return url;
52
- const { exp, sig } = sign(storageKey);
53
- return `${url}?exp=${exp}&sig=${encodeURIComponent(sig)}`;
54
- };
55
-
56
- export const buildContentUrl = (input: BuildContentUrlInput): string => {
57
- const { domain, accountConfigId, accountId, messageId, partPath, sign } =
58
- input;
59
- return buildStorageContentUrl({
60
- domain,
61
- storageKey: `accounts/${accountConfigId}/${accountId}/messages/${messageId}/parts/${partPath}`,
62
- sign,
63
- });
64
- };
65
-
66
- /**
67
- * Read the CloudFront distribution domain from the environment. The CDK
68
- * wires `CONTENT_DELIVERY_DOMAIN` onto the API Lambda from the
69
- * ContentDelivery stack's published SSM parameter (`/{stage}/Remit/frontendUrl`).
70
- *
71
- * Throws when the env var is missing or empty. `BodyPartResponse.contentUrl`
72
- * is required + URL-formatted in the API contract, so silently emitting a
73
- * placeholder string would ship a contract lie to clients. Failing loud here
74
- * surfaces the missing wire-up as a 500 the deploy team can act on (#299).
75
- * Production synth hard-codes a syntactically-valid placeholder URL via
76
- * `infra/bin/infra.ts` so this only fires when the env is actually unset.
77
- */
78
- export const getContentDeliveryDomain = (): string => {
79
- const value = process.env.CONTENT_DELIVERY_DOMAIN;
80
- if (!value || value.length === 0) {
81
- throw new Error(
82
- "CONTENT_DELIVERY_DOMAIN is not set; BodyPartResponse.contentUrl cannot be built. Wire the CloudFront distribution domain via the API Lambda env (see infra/bin/infra.ts).",
83
- );
84
- }
85
- return value;
86
- };
@@ -1,34 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import { describe, it } from "node:test";
3
- import type { AddressFlags } from "@remit/api-openapi-types";
4
- import { deriveMuted } from "./deriveMuted.js";
5
-
6
- const SET_AT = 1_700_000_000_000;
7
-
8
- describe("deriveMuted", () => {
9
- it("returns false when flags is undefined", () => {
10
- assert.equal(deriveMuted(undefined), false);
11
- });
12
-
13
- it("returns false for an empty flags object", () => {
14
- assert.equal(deriveMuted({}), false);
15
- });
16
-
17
- it("returns false when muted.value is false", () => {
18
- const flags: AddressFlags = { muted: { value: false, setAt: SET_AT } };
19
- assert.equal(deriveMuted(flags), false);
20
- });
21
-
22
- it("returns true when muted.value is true", () => {
23
- const flags: AddressFlags = { muted: { value: true, setAt: SET_AT } };
24
- assert.equal(deriveMuted(flags), true);
25
- });
26
-
27
- it("ignores other flags (orthogonal axis)", () => {
28
- const flags: AddressFlags = {
29
- vip: { value: true, setAt: SET_AT },
30
- wellknown: { value: true, setAt: SET_AT },
31
- };
32
- assert.equal(deriveMuted(flags), false);
33
- });
34
- });
@@ -1,360 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import { describe, test } from "node:test";
3
- import type {
4
- AddressItem,
5
- LabelItem,
6
- MessageItem,
7
- MessageLabelItem,
8
- ThreadMessageItem,
9
- } from "@remit/data-ports";
10
- import { deriveAddressId } from "@remit/data-ports/id";
11
- import { hasAbandonedDelete } from "@remit/data-ports/message-settlement";
12
- import {
13
- MessageMutation,
14
- MessageStatus,
15
- MessageSyncStatus,
16
- } from "@remit/domain-enums";
17
- import { type EnrichClient, enrichThreadRows } from "./enrichThreadRows.js";
18
-
19
- const threadRow = (
20
- threadMessageId: string,
21
- messageId: string,
22
- fromEmail?: string,
23
- ): ThreadMessageItem =>
24
- ({
25
- threadMessageId,
26
- threadId: "th-1",
27
- messageId,
28
- accountConfigId: "acc-1",
29
- mailboxId: "mbx-1",
30
- fromEmail,
31
- sentDate: 1,
32
- isRead: true,
33
- hasAttachment: false,
34
- hasStars: false,
35
- isDeleted: false,
36
- createdAt: 1,
37
- updatedAt: 1,
38
- }) as unknown as ThreadMessageItem;
39
-
40
- const buildClient = (
41
- messageLabels: MessageLabelItem[],
42
- labels: LabelItem[],
43
- addresses: AddressItem[] = [],
44
- ): EnrichClient => ({
45
- message: { get: async () => [] as MessageItem[] },
46
- address: { getAddress: async () => addresses },
47
- messageLabel: {
48
- listByMessageIds: async (messageIds: string[]) =>
49
- messageLabels.filter((row) => messageIds.includes(row.messageId)),
50
- },
51
- label: {
52
- listByAccountConfig: async () => labels,
53
- },
54
- });
55
-
56
- describe("enrichThreadRows — labels", () => {
57
- test("attaches every label applied to a message", async () => {
58
- const rows = [threadRow("tm-1", "msg-1")];
59
- const messageLabels = [
60
- {
61
- messageLabelId: "ml-1",
62
- messageId: "msg-1",
63
- labelId: "lbl-1",
64
- accountConfigId: "acc-1",
65
- createdAt: 1,
66
- updatedAt: 1,
67
- },
68
- {
69
- messageLabelId: "ml-2",
70
- messageId: "msg-1",
71
- labelId: "lbl-2",
72
- accountConfigId: "acc-1",
73
- createdAt: 1,
74
- updatedAt: 1,
75
- },
76
- ] as unknown as MessageLabelItem[];
77
- const labels = [
78
- {
79
- labelId: "lbl-1",
80
- accountConfigId: "acc-1",
81
- name: "Receipts",
82
- normalizedName: "receipts",
83
- color: "Blue",
84
- createdAt: 1,
85
- updatedAt: 1,
86
- },
87
- {
88
- labelId: "lbl-2",
89
- accountConfigId: "acc-1",
90
- name: "Travel",
91
- normalizedName: "travel",
92
- color: "Green",
93
- createdAt: 1,
94
- updatedAt: 1,
95
- },
96
- ] as unknown as LabelItem[];
97
-
98
- const [result] = await enrichThreadRows(
99
- rows,
100
- buildClient(messageLabels, labels),
101
- "acc-1",
102
- );
103
-
104
- assert.deepEqual(result?.labels?.map((l) => l.labelId).sort(), [
105
- "lbl-1",
106
- "lbl-2",
107
- ]);
108
- assert.deepEqual(
109
- result?.labels?.find((l) => l.labelId === "lbl-1"),
110
- { labelId: "lbl-1", name: "Receipts", color: "Blue" },
111
- );
112
- });
113
-
114
- test("omits labels entirely when the message has none", async () => {
115
- const rows = [threadRow("tm-1", "msg-1")];
116
- const [result] = await enrichThreadRows(rows, buildClient([], []), "acc-1");
117
- assert.equal(result?.labels, undefined);
118
- });
119
-
120
- test("skips a MessageLabel row whose Label was deleted out from under it", async () => {
121
- const rows = [threadRow("tm-1", "msg-1")];
122
- const messageLabels = [
123
- {
124
- messageLabelId: "ml-1",
125
- messageId: "msg-1",
126
- labelId: "gone",
127
- accountConfigId: "acc-1",
128
- createdAt: 1,
129
- updatedAt: 1,
130
- },
131
- ] as unknown as MessageLabelItem[];
132
-
133
- const [result] = await enrichThreadRows(
134
- rows,
135
- buildClient(messageLabels, []),
136
- "acc-1",
137
- );
138
- assert.equal(result?.labels, undefined);
139
- });
140
-
141
- test("never cross-wires labels between rows for different messages", async () => {
142
- const rows = [threadRow("tm-1", "msg-1"), threadRow("tm-2", "msg-2")];
143
- const messageLabels = [
144
- {
145
- messageLabelId: "ml-1",
146
- messageId: "msg-1",
147
- labelId: "lbl-1",
148
- accountConfigId: "acc-1",
149
- createdAt: 1,
150
- updatedAt: 1,
151
- },
152
- ] as unknown as MessageLabelItem[];
153
- const labels = [
154
- {
155
- labelId: "lbl-1",
156
- accountConfigId: "acc-1",
157
- name: "Receipts",
158
- normalizedName: "receipts",
159
- color: "Blue",
160
- createdAt: 1,
161
- updatedAt: 1,
162
- },
163
- ] as unknown as LabelItem[];
164
-
165
- const [first, second] = await enrichThreadRows(
166
- rows,
167
- buildClient(messageLabels, labels),
168
- "acc-1",
169
- );
170
- assert.equal(first?.labels?.length, 1);
171
- assert.equal(second?.labels, undefined);
172
- });
173
- });
174
-
175
- describe("enrichThreadRows — spamReport", () => {
176
- test("projects spamReport straight from the Message row", async () => {
177
- const rows = [threadRow("tm-1", "msg-1")];
178
- const client: EnrichClient = {
179
- message: {
180
- get: async () =>
181
- [
182
- {
183
- messageId: "msg-1",
184
- spamReport: { reportedAt: 1_700_000_000_000 },
185
- },
186
- ] as unknown as MessageItem[],
187
- },
188
- address: { getAddress: async () => [] },
189
- messageLabel: { listByMessageIds: async () => [] },
190
- label: { listByAccountConfig: async () => [] },
191
- };
192
-
193
- const [result] = await enrichThreadRows(rows, client, "acc-1");
194
- assert.deepEqual(result?.spamReport, { reportedAt: 1_700_000_000_000 });
195
- });
196
-
197
- test("omits spamReport when the Message has never been reported", async () => {
198
- const rows = [threadRow("tm-1", "msg-1")];
199
- const [result] = await enrichThreadRows(rows, buildClient([], []), "acc-1");
200
- assert.equal(result?.spamReport, undefined);
201
- });
202
- });
203
-
204
- describe("enrichThreadRows — muted", () => {
205
- const SET_AT = 1_700_000_000_000;
206
-
207
- test("sets muted true from the batch-fetched Address's flags, no extra query", async () => {
208
- const fromEmail = "muted@example.com";
209
- const addressId = deriveAddressId("acc-1", fromEmail);
210
- const rows = [threadRow("tm-1", "msg-1", fromEmail)];
211
- const addresses = [
212
- {
213
- addressId,
214
- accountConfigId: "acc-1",
215
- flags: { muted: { value: true, setAt: SET_AT } },
216
- },
217
- ] as unknown as AddressItem[];
218
-
219
- let addressCalls = 0;
220
- const client: EnrichClient = {
221
- message: { get: async () => [] as MessageItem[] },
222
- address: {
223
- getAddress: async () => {
224
- addressCalls += 1;
225
- return addresses;
226
- },
227
- },
228
- messageLabel: { listByMessageIds: async () => [] },
229
- label: { listByAccountConfig: async () => [] },
230
- };
231
-
232
- const [result] = await enrichThreadRows(rows, client, "acc-1");
233
- assert.equal(result?.muted, true);
234
- assert.equal(addressCalls, 1);
235
- });
236
-
237
- test("defaults muted to false when the Address has no muted flag", async () => {
238
- const fromEmail = "not-muted@example.com";
239
- const addressId = deriveAddressId("acc-1", fromEmail);
240
- const rows = [threadRow("tm-1", "msg-1", fromEmail)];
241
- const addresses = [
242
- { addressId, accountConfigId: "acc-1", flags: {} },
243
- ] as unknown as AddressItem[];
244
-
245
- const [result] = await enrichThreadRows(
246
- rows,
247
- buildClient([], [], addresses),
248
- "acc-1",
249
- );
250
- assert.equal(result?.muted, false);
251
- });
252
-
253
- test("defaults muted to false when no Address row resolves", async () => {
254
- const rows = [threadRow("tm-1", "msg-1")];
255
- const [result] = await enrichThreadRows(rows, buildClient([], []), "acc-1");
256
- assert.equal(result?.muted, false);
257
- });
258
- });
259
-
260
- describe("enrichThreadRows — the mutation pair", () => {
261
- const withMessage = (message: Partial<MessageItem>): EnrichClient => ({
262
- message: {
263
- get: async () =>
264
- [{ messageId: "msg-1", ...message }] as unknown as MessageItem[],
265
- },
266
- address: { getAddress: async () => [] },
267
- messageLabel: { listByMessageIds: async () => [] },
268
- label: { listByAccountConfig: async () => [] },
269
- });
270
-
271
- const project = async (message: Partial<MessageItem>) => {
272
- const [result] = await enrichThreadRows(
273
- [threadRow("tm-1", "msg-1")],
274
- withMessage(message),
275
- "acc-1",
276
- );
277
- return result;
278
- };
279
-
280
- test("projects the pair an abandoned delete leaves behind, and what it gave up on", async () => {
281
- const result = await project({
282
- status: MessageStatus.active,
283
- syncStatus: MessageSyncStatus.abandoned,
284
- abandonedMutation: MessageMutation.delete,
285
- });
286
- assert.equal(result?.status, MessageStatus.active);
287
- assert.equal(result?.syncStatus, MessageSyncStatus.abandoned);
288
- assert.equal(result?.abandonedMutation, MessageMutation.delete);
289
- assert.equal(
290
- hasAbandonedDelete({
291
- status: result?.status,
292
- syncStatus: result?.syncStatus,
293
- abandonedMutation: result?.abandonedMutation,
294
- }),
295
- true,
296
- );
297
- });
298
-
299
- // Issue #1229. Both give-ups leave the same `status`/`syncStatus` pair, so
300
- // the projection has to carry the third field or the client cannot tell a
301
- // move that handed back from a delete that was refused.
302
- test("projects a move that gave up as a move, never as a delete", async () => {
303
- const result = await project({
304
- status: MessageStatus.active,
305
- syncStatus: MessageSyncStatus.abandoned,
306
- abandonedMutation: MessageMutation.move,
307
- });
308
- assert.equal(result?.abandonedMutation, MessageMutation.move);
309
- assert.equal(
310
- hasAbandonedDelete({
311
- status: result?.status,
312
- syncStatus: result?.syncStatus,
313
- abandonedMutation: result?.abandonedMutation,
314
- }),
315
- false,
316
- );
317
- });
318
-
319
- test("projects a move mid-retry without calling it a give-up", async () => {
320
- const result = await project({
321
- status: MessageStatus.moving,
322
- syncStatus: MessageSyncStatus.failed,
323
- });
324
- assert.equal(result?.status, MessageStatus.moving);
325
- assert.equal(result?.syncStatus, MessageSyncStatus.failed);
326
- assert.equal(
327
- hasAbandonedDelete({
328
- status: result?.status,
329
- syncStatus: result?.syncStatus,
330
- abandonedMutation: result?.abandonedMutation,
331
- }),
332
- false,
333
- );
334
- });
335
-
336
- test("projects an ordinary inbound row", async () => {
337
- const result = await project({
338
- status: MessageStatus.active,
339
- syncStatus: MessageSyncStatus.pending,
340
- });
341
- assert.equal(result?.status, MessageStatus.active);
342
- assert.equal(result?.syncStatus, MessageSyncStatus.pending);
343
- });
344
-
345
- test("a row whose Message row is missing claims no mutation", async () => {
346
- const [result] = await enrichThreadRows(
347
- [threadRow("tm-1", "msg-1")],
348
- buildClient([], []),
349
- "acc-1",
350
- );
351
- assert.equal(
352
- hasAbandonedDelete({
353
- status: result?.status,
354
- syncStatus: result?.syncStatus,
355
- abandonedMutation: result?.abandonedMutation,
356
- }),
357
- false,
358
- );
359
- });
360
- });