@remit/mailbox-service 0.0.112 → 0.0.114
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/dist/account-credentials.d.ts +83 -0
- package/dist/account-credentials.js +104 -0
- package/dist/attribute-mapper.d.ts +36 -0
- package/dist/attribute-mapper.js +119 -0
- package/dist/body-parse.d.ts +28 -0
- package/{src/body-parse.ts → dist/body-parse.js} +18 -26
- package/dist/body-part-mapper.d.ts +92 -0
- package/dist/body-part-mapper.js +301 -0
- package/dist/body-sync-queue.d.ts +59 -0
- package/dist/body-sync-queue.js +56 -0
- package/dist/body-sync-terminal.d.ts +58 -0
- package/dist/body-sync-terminal.js +95 -0
- package/dist/body-sync.d.ts +351 -0
- package/dist/body-sync.js +1204 -0
- package/dist/calendar-parts.d.ts +23 -0
- package/dist/calendar-parts.js +18 -0
- package/dist/classification-backfill.d.ts +91 -0
- package/dist/classification-backfill.js +180 -0
- package/dist/classify-message.d.ts +31 -0
- package/dist/classify-message.js +89 -0
- package/dist/connection-factory.d.ts +54 -0
- package/dist/connection-factory.js +54 -0
- package/dist/connection-test.d.ts +41 -0
- package/dist/connection-test.js +152 -0
- package/dist/external-move.d.ts +94 -0
- package/dist/external-move.js +173 -0
- package/dist/filter-operations.d.ts +42 -0
- package/dist/filter-operations.js +211 -0
- package/dist/filters/filter-config.d.ts +14 -0
- package/dist/filters/filter-config.js +10 -0
- package/{src/filters/list-id.ts → dist/filters/list-id.d.ts} +2 -16
- package/dist/filters/list-id.js +26 -0
- package/dist/filters/match.d.ts +69 -0
- package/dist/filters/match.js +165 -0
- package/dist/filters/pipeline.d.ts +64 -0
- package/dist/filters/pipeline.js +100 -0
- package/{src/flag-push-terminal.ts → dist/flag-push-terminal.d.ts} +16 -72
- package/dist/flag-push-terminal.js +63 -0
- package/dist/flag-push.d.ts +69 -0
- package/dist/flag-push.js +111 -0
- package/dist/flag-queue.d.ts +175 -0
- package/dist/flag-queue.js +291 -0
- package/dist/folder-operations.d.ts +39 -0
- package/dist/folder-operations.js +112 -0
- package/dist/folder-placement.d.ts +13 -0
- package/dist/folder-placement.js +23 -0
- package/dist/heuristics/authenticationResults.d.ts +10 -0
- package/dist/heuristics/authenticationResults.js +187 -0
- package/dist/heuristics/classifyByHeaders.d.ts +82 -0
- package/dist/heuristics/classifyByHeaders.js +365 -0
- package/dist/heuristics/classifyPlacement.d.ts +67 -0
- package/dist/heuristics/classifyPlacement.js +153 -0
- package/dist/heuristics/machineSenders.d.ts +21 -0
- package/{src/heuristics/machineSenders.ts → dist/heuristics/machineSenders.js} +34 -40
- package/dist/heuristics/senderMismatch.d.ts +81 -0
- package/dist/heuristics/senderMismatch.js +192 -0
- package/dist/heuristics/socialDomains.d.ts +14 -0
- package/{src/heuristics/socialDomains.ts → dist/heuristics/socialDomains.js} +9 -9
- package/dist/heuristics/transactionalDomains.d.ts +18 -0
- package/{src/heuristics/transactionalDomains.ts → dist/heuristics/transactionalDomains.js} +8 -8
- package/dist/imapflow-connection.d.ts +377 -0
- package/dist/imapflow-connection.js +1147 -0
- package/dist/index.d.ts +63 -0
- package/dist/index.js +65 -0
- package/dist/list-id-backfill.d.ts +83 -0
- package/dist/list-id-backfill.js +132 -0
- package/dist/mailbox-cursor-rebuild.d.ts +74 -0
- package/dist/mailbox-cursor-rebuild.js +71 -0
- package/{src/mailbox-cursor.ts → dist/mailbox-cursor.d.ts} +20 -98
- package/dist/mailbox-cursor.js +102 -0
- package/dist/mailbox-intent.d.ts +41 -0
- package/{src/mailbox-intent.ts → dist/mailbox-intent.js} +38 -64
- package/dist/mailbox-management.d.ts +240 -0
- package/dist/mailbox-management.js +576 -0
- package/dist/mailbox-presence.d.ts +52 -0
- package/{src/mailbox-presence.ts → dist/mailbox-presence.js} +19 -30
- package/dist/mailbox-queue.d.ts +129 -0
- package/dist/mailbox-queue.js +216 -0
- package/dist/mailbox-sync.d.ts +93 -0
- package/dist/mailbox-sync.js +490 -0
- package/dist/mailbox-upstream.d.ts +38 -0
- package/{src/mailbox-upstream.ts → dist/mailbox-upstream.js} +37 -47
- package/dist/message-category.d.ts +51 -0
- package/{src/message-category.ts → dist/message-category.js} +28 -71
- package/dist/message-move.d.ts +285 -0
- package/dist/message-move.js +858 -0
- package/dist/message-parser.d.ts +14 -0
- package/dist/message-parser.js +17 -0
- package/{src/message-presence.ts → dist/message-presence.d.ts} +1 -13
- package/dist/message-presence.js +29 -0
- package/dist/message-read.d.ts +51 -0
- package/dist/message-read.js +31 -0
- package/dist/message-sync.d.ts +345 -0
- package/dist/message-sync.js +1358 -0
- package/dist/mime-walker.d.ts +102 -0
- package/dist/mime-walker.js +198 -0
- package/dist/organize-job.d.ts +20 -0
- package/dist/organize-job.js +37 -0
- package/dist/outbox-attachment-content.d.ts +37 -0
- package/dist/outbox-attachment-content.js +50 -0
- package/dist/outbox-attachment-filename.d.ts +19 -0
- package/dist/outbox-attachment-filename.js +64 -0
- package/dist/outbox-attachment.d.ts +148 -0
- package/dist/outbox-attachment.js +273 -0
- package/dist/outbox-queue.d.ts +73 -0
- package/dist/outbox-queue.js +280 -0
- package/{src/outbox-status.ts → dist/outbox-status.d.ts} +1 -6
- package/dist/outbox-status.js +18 -0
- package/dist/ownership.d.ts +24 -0
- package/dist/ownership.js +83 -0
- package/dist/placement-move-terminal.d.ts +67 -0
- package/dist/placement-move-terminal.js +88 -0
- package/dist/placement-move.d.ts +83 -0
- package/dist/placement-move.js +216 -0
- package/{src/placement-settled.ts → dist/placement-settled.d.ts} +13 -68
- package/dist/placement-settled.js +75 -0
- package/dist/quarantine.d.ts +95 -0
- package/dist/quarantine.js +131 -0
- package/dist/refusal.d.ts +31 -0
- package/dist/refusal.js +3 -0
- package/{src/restore-source-placement.ts → dist/restore-source-placement.d.ts} +29 -97
- package/dist/restore-source-placement.js +79 -0
- package/dist/search-text-backfill.d.ts +15 -0
- package/dist/search-text-backfill.js +33 -0
- package/dist/snippet.d.ts +61 -0
- package/dist/snippet.js +129 -0
- package/dist/spam-report.d.ts +86 -0
- package/dist/spam-report.js +241 -0
- package/dist/sync-warning.d.ts +21 -0
- package/dist/sync-warning.js +26 -0
- package/dist/sync-watermarks.d.ts +138 -0
- package/dist/sync-watermarks.js +205 -0
- package/dist/test-helpers/fake-imap.d.ts +151 -0
- package/dist/test-helpers/fake-imap.js +508 -0
- package/dist/test-helpers/folder-roles.d.ts +34 -0
- package/dist/test-helpers/folder-roles.js +47 -0
- package/dist/test-helpers/mime-tree-generator.d.ts +82 -0
- package/dist/test-helpers/mime-tree-generator.js +329 -0
- package/dist/test-helpers/test-store.d.ts +69 -0
- package/dist/test-helpers/test-store.js +193 -0
- package/dist/text/normalizer.d.ts +20 -0
- package/dist/text/normalizer.js +102 -0
- package/dist/thread-message-rows.d.ts +69 -0
- package/dist/thread-message-rows.js +40 -0
- package/dist/thread-root.d.ts +7 -0
- package/dist/thread-root.js +25 -0
- package/dist/types.d.ts +343 -0
- package/dist/types.js +19 -0
- package/package.json +86 -88
- package/src/account-credentials.ts +0 -210
- package/src/attribute-mapper.test.ts +0 -104
- package/src/attribute-mapper.ts +0 -148
- package/src/body-parse.test.ts +0 -55
- package/src/body-part-mapper.property.test.ts +0 -354
- package/src/body-part-mapper.ts +0 -410
- package/src/body-sync-queue.test.ts +0 -64
- package/src/body-sync-queue.ts +0 -131
- package/src/body-sync-terminal.test.ts +0 -148
- package/src/body-sync-terminal.ts +0 -174
- package/src/body-sync.test.ts +0 -925
- package/src/body-sync.ts +0 -1891
- package/src/calendar-parts.ts +0 -41
- package/src/classification-backfill.test.ts +0 -285
- package/src/classification-backfill.ts +0 -363
- package/src/classify-message.test.ts +0 -142
- package/src/classify-message.ts +0 -116
- package/src/connection-factory.ts +0 -102
- package/src/connection-test.ts +0 -208
- package/src/credentials.test.ts +0 -181
- package/src/external-move.test.ts +0 -262
- package/src/external-move.ts +0 -285
- package/src/filter-operations.test.ts +0 -319
- package/src/filter-operations.ts +0 -377
- package/src/filter-update.test.ts +0 -313
- package/src/filters/filter-config.test.ts +0 -87
- package/src/filters/filter-config.ts +0 -34
- package/src/filters/list-id.test.ts +0 -50
- package/src/filters/match.test.ts +0 -392
- package/src/filters/match.ts +0 -225
- package/src/filters/pipeline.test.ts +0 -199
- package/src/filters/pipeline.ts +0 -170
- package/src/flag-push.test.ts +0 -112
- package/src/flag-push.ts +0 -198
- package/src/flag-queue.test.ts +0 -197
- package/src/flag-queue.ts +0 -509
- package/src/folder-operations.test.ts +0 -273
- package/src/folder-operations.ts +0 -217
- package/src/folder-placement.ts +0 -56
- package/src/forwarded-message.test.ts +0 -122
- package/src/heuristics/authenticationResults.test.ts +0 -189
- package/src/heuristics/authenticationResults.ts +0 -212
- package/src/heuristics/classifyByHeaders.test.ts +0 -889
- package/src/heuristics/classifyByHeaders.ts +0 -467
- package/src/heuristics/classifyPlacement.test.ts +0 -366
- package/src/heuristics/classifyPlacement.ts +0 -211
- package/src/heuristics/classifyRealMail.test.ts +0 -242
- package/src/heuristics/machineSenders.test.ts +0 -101
- package/src/heuristics/senderMismatch.test.ts +0 -346
- package/src/heuristics/senderMismatch.ts +0 -259
- package/src/imapflow-connect-retry.test.ts +0 -97
- package/src/imapflow-connection.test.ts +0 -503
- package/src/imapflow-connection.ts +0 -1476
- package/src/index.ts +0 -397
- package/src/list-id-backfill.test.ts +0 -207
- package/src/list-id-backfill.ts +0 -274
- package/src/mailbox-cursor-rebuild.test.ts +0 -171
- package/src/mailbox-cursor-rebuild.ts +0 -146
- package/src/mailbox-cursor.test.ts +0 -141
- package/src/mailbox-delete-intent.test.ts +0 -119
- package/src/mailbox-management.test.ts +0 -243
- package/src/mailbox-management.ts +0 -838
- package/src/mailbox-presence.test.ts +0 -74
- package/src/mailbox-queue.ts +0 -397
- package/src/mailbox-rename-intent.test.ts +0 -220
- package/src/mailbox-rename-subtree.test.ts +0 -287
- package/src/mailbox-sync.test.ts +0 -286
- package/src/mailbox-sync.ts +0 -724
- package/src/message-move.test.ts +0 -726
- package/src/message-move.ts +0 -1518
- package/src/message-parser.ts +0 -26
- package/src/message-presence.test.ts +0 -50
- package/src/message-read.ts +0 -87
- package/src/message-save-retry.test.ts +0 -90
- package/src/message-sync-flags.test.ts +0 -167
- package/src/message-sync.test.ts +0 -762
- package/src/message-sync.ts +0 -2133
- package/src/mime-walker.test.ts +0 -311
- package/src/mime-walker.ts +0 -306
- package/src/organize-job.ts +0 -93
- package/src/outbox-attachment-content.test.ts +0 -153
- package/src/outbox-attachment-content.ts +0 -104
- package/src/outbox-attachment-filename.test.ts +0 -132
- package/src/outbox-attachment-filename.ts +0 -76
- package/src/outbox-attachment.test.ts +0 -626
- package/src/outbox-attachment.ts +0 -536
- package/src/outbox-failed-recovery.test.ts +0 -299
- package/src/outbox-queue.test.ts +0 -315
- package/src/outbox-queue.ts +0 -525
- package/src/ownership.ts +0 -140
- package/src/placement-move-terminal.ts +0 -145
- package/src/placement-move.ts +0 -363
- package/src/placement-settled.test.ts +0 -147
- package/src/placement.test.ts +0 -404
- package/src/quarantine.test.ts +0 -133
- package/src/quarantine.ts +0 -229
- package/src/refusal.ts +0 -29
- package/src/snippet.test.ts +0 -213
- package/src/snippet.ts +0 -152
- package/src/spam-report.test.ts +0 -329
- package/src/spam-report.ts +0 -357
- package/src/sync-warning.test.ts +0 -213
- package/src/sync-warning.ts +0 -56
- package/src/sync-watermarks.test.ts +0 -409
- package/src/sync-watermarks.ts +0 -319
- package/src/system-flag-wire-format.test.ts +0 -77
- package/src/test-helpers/fake-imap.ts +0 -690
- package/src/test-helpers/folder-roles.ts +0 -66
- package/src/test-helpers/mime-tree-generator.ts +0 -461
- package/src/test-helpers/test-queue.ts +0 -56
- package/src/test-helpers/test-store.ts +0 -299
- package/src/text/normalizer.test.ts +0 -223
- package/src/text/normalizer.ts +0 -140
- package/src/thread-message-rows.ts +0 -77
- package/src/thread-root.test.ts +0 -29
- package/src/thread-root.ts +0 -51
- package/src/types/mailparser-augment.d.ts +0 -23
- package/src/types/natural-porter-stemmer.d.ts +0 -50
- package/src/types/stopword.d.ts +0 -69
- package/src/types.ts +0 -371
- package/tsconfig.json +0 -8
|
@@ -1,273 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import { afterEach, beforeEach, describe, it } from "node:test";
|
|
3
|
-
import type { AccountItem, MailboxItem } from "@remit/data-ports";
|
|
4
|
-
import { MailboxSyncStatus } from "@remit/domain-enums";
|
|
5
|
-
import {
|
|
6
|
-
changeFolderPath,
|
|
7
|
-
createFolder,
|
|
8
|
-
type FolderPathOutcome,
|
|
9
|
-
} from "./folder-operations.js";
|
|
10
|
-
import { MailboxQueueService } from "./mailbox-queue.js";
|
|
11
|
-
import { folderInAccount } from "./ownership.js";
|
|
12
|
-
import type { Refusable } from "./refusal.js";
|
|
13
|
-
import {
|
|
14
|
-
captureQueue,
|
|
15
|
-
openStore,
|
|
16
|
-
type QueueCapture,
|
|
17
|
-
type Store,
|
|
18
|
-
seedAccount,
|
|
19
|
-
seedMailbox,
|
|
20
|
-
} from "./test-helpers/test-store.js";
|
|
21
|
-
|
|
22
|
-
let store: Store;
|
|
23
|
-
let capture: QueueCapture;
|
|
24
|
-
let account: AccountItem;
|
|
25
|
-
let mailboxQueue: MailboxQueueService;
|
|
26
|
-
|
|
27
|
-
beforeEach(async () => {
|
|
28
|
-
capture = captureQueue();
|
|
29
|
-
store = await openStore();
|
|
30
|
-
account = await seedAccount(store);
|
|
31
|
-
mailboxQueue = new MailboxQueueService({
|
|
32
|
-
mailboxService: store.mailbox,
|
|
33
|
-
sqsQueueUrl: "http://localhost:4566/000000000000/mailbox",
|
|
34
|
-
});
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
afterEach(async () => {
|
|
38
|
-
capture.restore();
|
|
39
|
-
await store.close();
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
const rename = (
|
|
43
|
-
mailbox: MailboxItem,
|
|
44
|
-
fullPath: string,
|
|
45
|
-
): Promise<Refusable<FolderPathOutcome>> =>
|
|
46
|
-
changeFolderPath(
|
|
47
|
-
{ account: store.account, mailbox: store.mailbox, mailboxQueue },
|
|
48
|
-
account.accountConfigId,
|
|
49
|
-
{
|
|
50
|
-
accountId: mailbox.accountId,
|
|
51
|
-
mailboxId: mailbox.mailboxId,
|
|
52
|
-
fullPath,
|
|
53
|
-
},
|
|
54
|
-
);
|
|
55
|
-
|
|
56
|
-
const refusalOf = <T>(outcome: Refusable<T>) => {
|
|
57
|
-
assert.equal(outcome.ok, false, "expected a refusal");
|
|
58
|
-
return outcome.ok ? undefined : outcome.refusal;
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
const invalid = (message: string) => ({ kind: "Invalid", message });
|
|
62
|
-
|
|
63
|
-
describe("changeFolderPath refuses a rename no retry could satisfy", () => {
|
|
64
|
-
it("keeps INBOX where it is", async () => {
|
|
65
|
-
const inbox = await seedMailbox(store, account, "INBOX");
|
|
66
|
-
|
|
67
|
-
assert.deepEqual(
|
|
68
|
-
refusalOf(await rename(inbox, "Old inbox")),
|
|
69
|
-
invalid("The inbox can't be renamed."),
|
|
70
|
-
);
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
it("keeps the INBOX name for the inbox", async () => {
|
|
74
|
-
const folder = await seedMailbox(store, account, "Receipts");
|
|
75
|
-
|
|
76
|
-
assert.deepEqual(
|
|
77
|
-
refusalOf(await rename(folder, "inbox")),
|
|
78
|
-
invalid("“INBOX” is reserved for the inbox."),
|
|
79
|
-
);
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
it("needs a name", async () => {
|
|
83
|
-
const folder = await seedMailbox(store, account, "Receipts");
|
|
84
|
-
|
|
85
|
-
assert.deepEqual(
|
|
86
|
-
refusalOf(await rename(folder, " ")),
|
|
87
|
-
invalid("A folder needs a name."),
|
|
88
|
-
);
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
it("keeps a system folder's conventional name free", async () => {
|
|
92
|
-
const folder = await seedMailbox(store, account, "Receipts");
|
|
93
|
-
|
|
94
|
-
assert.deepEqual(
|
|
95
|
-
refusalOf(await rename(folder, "Projects/Trash")),
|
|
96
|
-
invalid("“Trash” is reserved for a system folder. Pick another name."),
|
|
97
|
-
);
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
it("refuses a path another folder holds", async () => {
|
|
101
|
-
await seedMailbox(store, account, "Archive 2024");
|
|
102
|
-
const folder = await seedMailbox(store, account, "Receipts");
|
|
103
|
-
|
|
104
|
-
assert.deepEqual(
|
|
105
|
-
refusalOf(await rename(folder, "Archive 2024")),
|
|
106
|
-
invalid("A folder named “Archive 2024” is already there."),
|
|
107
|
-
);
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
it("refuses a path a pending rename is heading for", async () => {
|
|
111
|
-
const first = await seedMailbox(store, account, "Bills");
|
|
112
|
-
const second = await seedMailbox(store, account, "Receipts");
|
|
113
|
-
assert.equal((await rename(first, "Paperwork")).ok, true);
|
|
114
|
-
|
|
115
|
-
assert.deepEqual(
|
|
116
|
-
refusalOf(await rename(second, "Paperwork")),
|
|
117
|
-
invalid("“Bills” is already being renamed to “Paperwork”."),
|
|
118
|
-
);
|
|
119
|
-
});
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
describe("changeFolderPath records a pending rename the mail server settles", () => {
|
|
123
|
-
it("keeps the confirmed path, records the target and queues the rename", async () => {
|
|
124
|
-
const folder = await seedMailbox(store, account, "Receipts");
|
|
125
|
-
|
|
126
|
-
const outcome = await rename(folder, "Paperwork");
|
|
127
|
-
|
|
128
|
-
assert.equal(outcome.ok, true);
|
|
129
|
-
const row = await store.mailbox.get(account.accountId, folder.mailboxId);
|
|
130
|
-
assert.equal(row.fullPath, "Receipts");
|
|
131
|
-
assert.equal(row.pendingPath, "Paperwork");
|
|
132
|
-
assert.equal(row.syncStatus, MailboxSyncStatus.pending);
|
|
133
|
-
assert.deepEqual(
|
|
134
|
-
capture.sent.map((event) => event.body.type),
|
|
135
|
-
["MAILBOX_RENAME"],
|
|
136
|
-
);
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
it("dismisses a failed rename when asked for the path the folder has", async () => {
|
|
140
|
-
const folder = await seedMailbox(store, account, "Receipts");
|
|
141
|
-
await store.mailbox.transition(account.accountId, folder.mailboxId, {
|
|
142
|
-
from: [MailboxSyncStatus.synced],
|
|
143
|
-
to: MailboxSyncStatus.failed,
|
|
144
|
-
set: { pendingPath: "Paperwork" },
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
const outcome = await rename(folder, "Receipts");
|
|
148
|
-
|
|
149
|
-
assert.equal(outcome.ok, true);
|
|
150
|
-
const row = await store.mailbox.get(account.accountId, folder.mailboxId);
|
|
151
|
-
assert.equal(row.syncStatus, MailboxSyncStatus.synced);
|
|
152
|
-
assert.equal(row.pendingPath, undefined);
|
|
153
|
-
assert.deepEqual(capture.sent, []);
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
it("returns the recorded intent when asked for the target it is already heading for", async () => {
|
|
157
|
-
const folder = await seedMailbox(store, account, "Receipts");
|
|
158
|
-
assert.equal((await rename(folder, "Paperwork")).ok, true);
|
|
159
|
-
|
|
160
|
-
const again = await rename(folder, "Paperwork");
|
|
161
|
-
|
|
162
|
-
assert.equal(again.ok && again.value.outcome, "AlreadyRecorded");
|
|
163
|
-
assert.equal(again.ok && again.value.mailbox.pendingPath, "Paperwork");
|
|
164
|
-
assert.equal(capture.sent.length, 1);
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
it("refuses a new target while another change is in flight", async () => {
|
|
168
|
-
const folder = await seedMailbox(store, account, "Receipts");
|
|
169
|
-
assert.equal((await rename(folder, "Paperwork")).ok, true);
|
|
170
|
-
|
|
171
|
-
assert.deepEqual(refusalOf(await rename(folder, "Bills")), {
|
|
172
|
-
kind: "Conflict",
|
|
173
|
-
message: `A rename is in progress for "Receipts".`,
|
|
174
|
-
});
|
|
175
|
-
assert.equal(capture.sent.length, 1);
|
|
176
|
-
});
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
describe("createFolder", () => {
|
|
180
|
-
it("records a pending folder and queues its create", async () => {
|
|
181
|
-
const outcome = await createFolder(
|
|
182
|
-
{ account: store.account, mailbox: store.mailbox, mailboxQueue },
|
|
183
|
-
account.accountConfigId,
|
|
184
|
-
{
|
|
185
|
-
accountId: account.accountId,
|
|
186
|
-
namespaceType: "personal",
|
|
187
|
-
fullPath: "Receipts",
|
|
188
|
-
},
|
|
189
|
-
);
|
|
190
|
-
|
|
191
|
-
assert.equal(outcome.ok, true);
|
|
192
|
-
assert.equal(outcome.ok && outcome.value.outcome, "Created");
|
|
193
|
-
assert.equal(outcome.ok && outcome.value.mailbox.syncStatus, "pending");
|
|
194
|
-
assert.deepEqual(
|
|
195
|
-
capture.sent.map((event) => event.body.type),
|
|
196
|
-
["MAILBOX_CREATE"],
|
|
197
|
-
);
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
it("returns the folder already at that path", async () => {
|
|
201
|
-
const existing = await seedMailbox(store, account, "Receipts");
|
|
202
|
-
|
|
203
|
-
const outcome = await createFolder(
|
|
204
|
-
{ account: store.account, mailbox: store.mailbox, mailboxQueue },
|
|
205
|
-
account.accountConfigId,
|
|
206
|
-
{
|
|
207
|
-
accountId: account.accountId,
|
|
208
|
-
namespaceType: "personal",
|
|
209
|
-
fullPath: "Receipts",
|
|
210
|
-
},
|
|
211
|
-
);
|
|
212
|
-
|
|
213
|
-
assert.equal(outcome.ok && outcome.value.outcome, "Existing");
|
|
214
|
-
assert.equal(
|
|
215
|
-
outcome.ok && outcome.value.mailbox.mailboxId,
|
|
216
|
-
existing.mailboxId,
|
|
217
|
-
);
|
|
218
|
-
assert.deepEqual(capture.sent, []);
|
|
219
|
-
});
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
describe("createFolder for another tenant", () => {
|
|
223
|
-
it("refuses a caller who does not own the account, recording nothing", async () => {
|
|
224
|
-
const outcome = await createFolder(
|
|
225
|
-
{ account: store.account, mailbox: store.mailbox, mailboxQueue },
|
|
226
|
-
"cfg-x",
|
|
227
|
-
{
|
|
228
|
-
accountId: account.accountId,
|
|
229
|
-
namespaceType: "personal",
|
|
230
|
-
fullPath: "Receipts",
|
|
231
|
-
},
|
|
232
|
-
);
|
|
233
|
-
|
|
234
|
-
assert.deepEqual(refusalOf(outcome), {
|
|
235
|
-
kind: "Forbidden",
|
|
236
|
-
message: `Account ${account.accountId} not in account config`,
|
|
237
|
-
});
|
|
238
|
-
assert.deepEqual(capture.sent, []);
|
|
239
|
-
});
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
describe("changeFolderPath for another tenant", () => {
|
|
243
|
-
it("refuses a caller who does not own the account, recording nothing", async () => {
|
|
244
|
-
const folder = await seedMailbox(store, account, "Receipts");
|
|
245
|
-
const outcome = await changeFolderPath(
|
|
246
|
-
{ account: store.account, mailbox: store.mailbox, mailboxQueue },
|
|
247
|
-
"cfg-x",
|
|
248
|
-
{
|
|
249
|
-
accountId: account.accountId,
|
|
250
|
-
mailboxId: folder.mailboxId,
|
|
251
|
-
fullPath: "Paperwork",
|
|
252
|
-
},
|
|
253
|
-
);
|
|
254
|
-
|
|
255
|
-
assert.deepEqual(refusalOf(outcome), {
|
|
256
|
-
kind: "Forbidden",
|
|
257
|
-
message: `Account ${account.accountId} not in account config`,
|
|
258
|
-
});
|
|
259
|
-
assert.deepEqual(capture.sent, []);
|
|
260
|
-
});
|
|
261
|
-
});
|
|
262
|
-
|
|
263
|
-
describe("folderInAccount", () => {
|
|
264
|
-
it("refuses another account's folder as not found on a read", async () => {
|
|
265
|
-
const other = await seedAccount(store, { accountConfigId: "cfg-other" });
|
|
266
|
-
const theirs = await seedMailbox(store, other, "Receipts");
|
|
267
|
-
|
|
268
|
-
assert.deepEqual(
|
|
269
|
-
refusalOf(folderInAccount(theirs, account.accountId, "read")),
|
|
270
|
-
{ kind: "NotFound", message: `Mailbox not found: ${theirs.mailboxId}` },
|
|
271
|
-
);
|
|
272
|
-
});
|
|
273
|
-
});
|
package/src/folder-operations.ts
DELETED
|
@@ -1,217 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
AccountItem,
|
|
3
|
-
IAccountRepository,
|
|
4
|
-
IMailboxRepository,
|
|
5
|
-
MailboxItem,
|
|
6
|
-
} from "@remit/data-ports";
|
|
7
|
-
import { isReservedFolderName } from "@remit/data-ports/folder-role";
|
|
8
|
-
import { mailboxLeafName } from "@remit/data-ports/mailbox-name";
|
|
9
|
-
import { MailboxSyncStatus } from "@remit/domain-enums";
|
|
10
|
-
import { carriesIntent, folderInFlight } from "./mailbox-intent.js";
|
|
11
|
-
import type { MailboxQueueService } from "./mailbox-queue.js";
|
|
12
|
-
import { folderInAccount, ownedAccount } from "./ownership.js";
|
|
13
|
-
import { accept, invalid, type Refusable, refuse } from "./refusal.js";
|
|
14
|
-
|
|
15
|
-
export interface FolderPathDeps {
|
|
16
|
-
account: { get(accountId: string): Promise<AccountItem> };
|
|
17
|
-
mailbox: {
|
|
18
|
-
get(accountId: string, mailboxId: string): Promise<MailboxItem>;
|
|
19
|
-
listAllByAccount(accountId: string): Promise<MailboxItem[]>;
|
|
20
|
-
};
|
|
21
|
-
mailboxQueue: Pick<
|
|
22
|
-
MailboxQueueService,
|
|
23
|
-
"renameMailbox" | "dismissMailboxIntent"
|
|
24
|
-
>;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export interface CreateFolderDeps {
|
|
28
|
-
account: Pick<IAccountRepository, "get">;
|
|
29
|
-
mailbox: Pick<IMailboxRepository, "findByPath">;
|
|
30
|
-
mailboxQueue: Pick<MailboxQueueService, "createMailbox">;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export interface NewFolder {
|
|
34
|
-
accountId: string;
|
|
35
|
-
namespaceType: MailboxItem["namespaceType"];
|
|
36
|
-
fullPath: string;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// AlreadyRecorded: the folder is already heading for the requested path, so
|
|
40
|
-
// nothing new was recorded or queued.
|
|
41
|
-
export interface FolderPathOutcome {
|
|
42
|
-
outcome: "Recorded" | "AlreadyRecorded";
|
|
43
|
-
mailbox: MailboxItem;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export interface NewFolderOutcome {
|
|
47
|
-
outcome: "Created" | "Existing";
|
|
48
|
-
mailbox: MailboxItem;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export interface FolderPathChange {
|
|
52
|
-
accountId: string;
|
|
53
|
-
mailboxId: string;
|
|
54
|
-
fullPath: string;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const renameTargetRefusal = async (
|
|
58
|
-
deps: Pick<FolderPathDeps, "mailbox">,
|
|
59
|
-
accountId: string,
|
|
60
|
-
before: MailboxItem,
|
|
61
|
-
target: string,
|
|
62
|
-
): Promise<Refusable<string>> => {
|
|
63
|
-
// Renaming INBOX leaves an empty INBOX behind, and INBOX is the one name
|
|
64
|
-
// RFC 3501 reserves.
|
|
65
|
-
if (before.fullPath.toUpperCase() === "INBOX") {
|
|
66
|
-
return invalid("The inbox can't be renamed.");
|
|
67
|
-
}
|
|
68
|
-
if (target.trim().length === 0) {
|
|
69
|
-
return invalid("A folder needs a name.");
|
|
70
|
-
}
|
|
71
|
-
if (target.toUpperCase() === "INBOX") {
|
|
72
|
-
return invalid("“INBOX” is reserved for the inbox.");
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// The mailbox sweep reads an unflagged folder under a role's conventional
|
|
76
|
-
// name as a duplicate and reaps it, with its mail, once another folder holds
|
|
77
|
-
// the flag.
|
|
78
|
-
const leaf = mailboxLeafName({
|
|
79
|
-
fullPath: target,
|
|
80
|
-
hierarchyDelimiter: before.hierarchyDelimiter,
|
|
81
|
-
});
|
|
82
|
-
if (isReservedFolderName(leaf)) {
|
|
83
|
-
return invalid(
|
|
84
|
-
`“${leaf}” is reserved for a system folder. Pick another name.`,
|
|
85
|
-
);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// A path a pending rename is heading for is as taken as one a folder sits
|
|
89
|
-
// at. A failed rename keeps its target only so the client can offer a
|
|
90
|
-
// retry; nothing is coming for it, so it claims nothing.
|
|
91
|
-
const folders = await deps.mailbox.listAllByAccount(accountId);
|
|
92
|
-
for (const folder of folders) {
|
|
93
|
-
if (folder.mailboxId === before.mailboxId) continue;
|
|
94
|
-
if (folder.fullPath === target) {
|
|
95
|
-
return invalid(`A folder named “${target}” is already there.`);
|
|
96
|
-
}
|
|
97
|
-
if (
|
|
98
|
-
folder.syncStatus === MailboxSyncStatus.pending &&
|
|
99
|
-
folder.pendingPath === target
|
|
100
|
-
) {
|
|
101
|
-
return invalid(
|
|
102
|
-
`“${folder.fullPath}” is already being renamed to “${target}”.`,
|
|
103
|
-
);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
return accept(target);
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
// A fullPath equal to the confirmed one is not a rename: it dismisses a failed
|
|
110
|
-
// intent, the one route out of `failed` that enqueues nothing. The row keeps
|
|
111
|
-
// the path the server holds until the rename settles.
|
|
112
|
-
export const ownedFolder = async (
|
|
113
|
-
deps: Pick<FolderPathDeps, "account" | "mailbox">,
|
|
114
|
-
accountConfigId: string,
|
|
115
|
-
accountId: string,
|
|
116
|
-
mailboxId: string,
|
|
117
|
-
): Promise<Refusable<MailboxItem>> => {
|
|
118
|
-
const owned = ownedAccount(
|
|
119
|
-
await deps.account.get(accountId),
|
|
120
|
-
accountConfigId,
|
|
121
|
-
"act",
|
|
122
|
-
);
|
|
123
|
-
if (!owned.ok) return owned;
|
|
124
|
-
return folderInAccount(
|
|
125
|
-
await deps.mailbox.get(accountId, mailboxId),
|
|
126
|
-
accountId,
|
|
127
|
-
"act",
|
|
128
|
-
);
|
|
129
|
-
};
|
|
130
|
-
|
|
131
|
-
export const changeFolderPath = async (
|
|
132
|
-
deps: FolderPathDeps,
|
|
133
|
-
accountConfigId: string,
|
|
134
|
-
change: FolderPathChange,
|
|
135
|
-
): Promise<Refusable<FolderPathOutcome>> => {
|
|
136
|
-
const { accountId, mailboxId, fullPath } = change;
|
|
137
|
-
const folder = await ownedFolder(deps, accountConfigId, accountId, mailboxId);
|
|
138
|
-
if (!folder.ok) return folder;
|
|
139
|
-
const before = folder.value;
|
|
140
|
-
if (fullPath === before.fullPath) {
|
|
141
|
-
return accept({
|
|
142
|
-
outcome: "Recorded",
|
|
143
|
-
mailbox: await deps.mailboxQueue.dismissMailboxIntent(
|
|
144
|
-
mailboxId,
|
|
145
|
-
accountId,
|
|
146
|
-
),
|
|
147
|
-
});
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
const target = await renameTargetRefusal(deps, accountId, before, fullPath);
|
|
151
|
-
if (!target.ok) return target;
|
|
152
|
-
|
|
153
|
-
if (!carriesIntent(before)) {
|
|
154
|
-
if (
|
|
155
|
-
before.syncStatus === MailboxSyncStatus.pending &&
|
|
156
|
-
before.pendingPath === fullPath
|
|
157
|
-
) {
|
|
158
|
-
return accept({ outcome: "AlreadyRecorded", mailbox: before });
|
|
159
|
-
}
|
|
160
|
-
return refuse({ kind: "Conflict", message: folderInFlight(before) });
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
return accept({
|
|
164
|
-
outcome: "Recorded",
|
|
165
|
-
mailbox: await deps.mailboxQueue.renameMailbox(
|
|
166
|
-
mailboxId,
|
|
167
|
-
fullPath,
|
|
168
|
-
accountId,
|
|
169
|
-
),
|
|
170
|
-
});
|
|
171
|
-
};
|
|
172
|
-
|
|
173
|
-
export const createFolder = async (
|
|
174
|
-
deps: CreateFolderDeps,
|
|
175
|
-
accountConfigId: string,
|
|
176
|
-
folder: NewFolder,
|
|
177
|
-
): Promise<Refusable<NewFolderOutcome>> => {
|
|
178
|
-
const { accountId, namespaceType, fullPath } = folder;
|
|
179
|
-
const owned = ownedAccount(
|
|
180
|
-
await deps.account.get(accountId),
|
|
181
|
-
accountConfigId,
|
|
182
|
-
"act",
|
|
183
|
-
);
|
|
184
|
-
if (!owned.ok) return owned;
|
|
185
|
-
|
|
186
|
-
const created = await deps.mailboxQueue.createMailbox(
|
|
187
|
-
{
|
|
188
|
-
accountId,
|
|
189
|
-
namespaceType,
|
|
190
|
-
namespacePrefix: "",
|
|
191
|
-
hierarchyDelimiter: "/",
|
|
192
|
-
fullPath,
|
|
193
|
-
uidValidity: 0,
|
|
194
|
-
uidNext: 1,
|
|
195
|
-
highestModseq: "0",
|
|
196
|
-
messageCount: 0,
|
|
197
|
-
unseenCount: 0,
|
|
198
|
-
deletedCount: 0,
|
|
199
|
-
totalSize: 0,
|
|
200
|
-
lastSyncUid: 0,
|
|
201
|
-
highWaterMarkUid: 0,
|
|
202
|
-
lastMessageSyncAt: 0,
|
|
203
|
-
},
|
|
204
|
-
accountId,
|
|
205
|
-
true,
|
|
206
|
-
);
|
|
207
|
-
if (created.outcome === "PathTaken") {
|
|
208
|
-
const existing = await deps.mailbox.findByPath(accountId, fullPath);
|
|
209
|
-
if (!existing) {
|
|
210
|
-
throw new Error(
|
|
211
|
-
`No folder at "${fullPath}" after its create reported the path taken`,
|
|
212
|
-
);
|
|
213
|
-
}
|
|
214
|
-
return accept({ outcome: "Existing", mailbox: existing });
|
|
215
|
-
}
|
|
216
|
-
return accept({ outcome: "Created", mailbox: created.mailbox });
|
|
217
|
-
};
|
package/src/folder-placement.ts
DELETED
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import type { IMailboxSpecialUseRepository } from "@remit/data-ports";
|
|
2
|
-
import type { FolderPlacement } from "./heuristics/classifyPlacement.js";
|
|
3
|
-
|
|
4
|
-
export type FolderPlacementLookup = Pick<
|
|
5
|
-
IMailboxSpecialUseRepository,
|
|
6
|
-
"findJunkMailbox" | "findInboxMailbox"
|
|
7
|
-
>;
|
|
8
|
-
|
|
9
|
-
type RoleMailbox = Awaited<
|
|
10
|
-
ReturnType<IMailboxSpecialUseRepository["findJunkMailbox"]>
|
|
11
|
-
>;
|
|
12
|
-
|
|
13
|
-
export interface FolderRoleMailboxes {
|
|
14
|
-
junkMailbox: RoleMailbox;
|
|
15
|
-
inboxMailbox: RoleMailbox;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export const resolveFolderRoleMailboxes = async (
|
|
19
|
-
lookup: FolderPlacementLookup,
|
|
20
|
-
accountId: string,
|
|
21
|
-
): Promise<FolderRoleMailboxes> => ({
|
|
22
|
-
junkMailbox: await lookup.findJunkMailbox(accountId),
|
|
23
|
-
inboxMailbox: await lookup.findInboxMailbox(accountId),
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
export const placementOf = (
|
|
27
|
-
mailboxId: string,
|
|
28
|
-
{ junkMailbox, inboxMailbox }: FolderRoleMailboxes,
|
|
29
|
-
): FolderPlacement => {
|
|
30
|
-
if (junkMailbox?.mailboxId === mailboxId) return "junk";
|
|
31
|
-
if (inboxMailbox?.mailboxId === mailboxId) return "inbox";
|
|
32
|
-
return "other";
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
export const resolveFolderPlacement = async (
|
|
36
|
-
lookup: FolderPlacementLookup,
|
|
37
|
-
accountId: string,
|
|
38
|
-
mailboxId: string,
|
|
39
|
-
): Promise<FolderPlacement> =>
|
|
40
|
-
placementOf(mailboxId, await resolveFolderRoleMailboxes(lookup, accountId));
|
|
41
|
-
|
|
42
|
-
export const resolveAccountFolderPlacements = async (
|
|
43
|
-
lookup: FolderPlacementLookup,
|
|
44
|
-
accountIds: readonly string[],
|
|
45
|
-
): Promise<ReadonlyMap<string, FolderPlacement>> => {
|
|
46
|
-
const placements = new Map<string, FolderPlacement>();
|
|
47
|
-
for (const accountId of accountIds) {
|
|
48
|
-
const { junkMailbox, inboxMailbox } = await resolveFolderRoleMailboxes(
|
|
49
|
-
lookup,
|
|
50
|
-
accountId,
|
|
51
|
-
);
|
|
52
|
-
if (inboxMailbox) placements.set(inboxMailbox.mailboxId, "inbox");
|
|
53
|
-
if (junkMailbox) placements.set(junkMailbox.mailboxId, "junk");
|
|
54
|
-
}
|
|
55
|
-
return placements;
|
|
56
|
-
};
|
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import { readFileSync } from "node:fs";
|
|
3
|
-
import { dirname, resolve } from "node:path";
|
|
4
|
-
import { describe, it } from "node:test";
|
|
5
|
-
import { fileURLToPath } from "node:url";
|
|
6
|
-
import {
|
|
7
|
-
mapMessagePartsToContent,
|
|
8
|
-
parsePartSource,
|
|
9
|
-
} from "./body-part-mapper.js";
|
|
10
|
-
import { type MimeNode, walkMimeStructure } from "./mime-walker.js";
|
|
11
|
-
|
|
12
|
-
const FIXTURE = readFileSync(
|
|
13
|
-
resolve(
|
|
14
|
-
dirname(fileURLToPath(import.meta.url)),
|
|
15
|
-
"../../e2e/fixtures/mime-adversarial/04-forwarded-rfc822-with-attachments.eml",
|
|
16
|
-
),
|
|
17
|
-
"utf8",
|
|
18
|
-
).replace(/\r?\n/g, "\r\n");
|
|
19
|
-
|
|
20
|
-
const ATTACHED = 'Content-Disposition: attachment; filename="forwarded.eml"';
|
|
21
|
-
const INLINE = 'Content-Disposition: inline; filename="forwarded.eml"';
|
|
22
|
-
|
|
23
|
-
/** Fixture 04 as imapflow hands it over: the forwarded message's root reuses part "2". */
|
|
24
|
-
const structure = (disposition: string): MimeNode => ({
|
|
25
|
-
type: "multipart/mixed",
|
|
26
|
-
childNodes: [
|
|
27
|
-
{ part: "1", type: "text/plain", encoding: "7bit", size: 115 },
|
|
28
|
-
{
|
|
29
|
-
part: "2",
|
|
30
|
-
type: "message/rfc822",
|
|
31
|
-
encoding: "7bit",
|
|
32
|
-
size: 638,
|
|
33
|
-
disposition,
|
|
34
|
-
dispositionParameters: { filename: "forwarded.eml" },
|
|
35
|
-
childNodes: [
|
|
36
|
-
{
|
|
37
|
-
part: "2",
|
|
38
|
-
type: "multipart/mixed",
|
|
39
|
-
childNodes: [
|
|
40
|
-
{ part: "2.1", type: "text/plain", encoding: "7bit", size: 40 },
|
|
41
|
-
{
|
|
42
|
-
part: "2.2",
|
|
43
|
-
type: "application/pdf",
|
|
44
|
-
encoding: "base64",
|
|
45
|
-
size: 44,
|
|
46
|
-
disposition: "attachment",
|
|
47
|
-
dispositionParameters: { filename: "original.pdf" },
|
|
48
|
-
},
|
|
49
|
-
],
|
|
50
|
-
},
|
|
51
|
-
],
|
|
52
|
-
},
|
|
53
|
-
],
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
const forwardedSource = (): string => {
|
|
57
|
-
const start = FIXTURE.indexOf("From: carol@example.com");
|
|
58
|
-
const end = FIXTURE.indexOf("\r\n--=_adv04_outer_=--");
|
|
59
|
-
return FIXTURE.slice(start, end);
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
const storedContent = async (
|
|
63
|
-
source: string,
|
|
64
|
-
disposition: string,
|
|
65
|
-
): Promise<Map<string, string>> => {
|
|
66
|
-
const pairs = await mapMessagePartsToContent(
|
|
67
|
-
walkMimeStructure(structure(disposition)),
|
|
68
|
-
await parsePartSource(Buffer.from(source)),
|
|
69
|
-
);
|
|
70
|
-
return new Map(
|
|
71
|
-
pairs.map((pair) => [pair.partPath, pair.content.toString("utf8")]),
|
|
72
|
-
);
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
describe("a forwarded message/rfc822 part", () => {
|
|
76
|
-
it("is stored as its own part, apart from the message it carries", () => {
|
|
77
|
-
const records = walkMimeStructure(structure("attachment"));
|
|
78
|
-
|
|
79
|
-
assert.deepEqual(
|
|
80
|
-
records.map((record) => [
|
|
81
|
-
record.partPath,
|
|
82
|
-
record.parentPartPath,
|
|
83
|
-
`${record.mediaType}/${record.mediaSubtype}`,
|
|
84
|
-
]),
|
|
85
|
-
[
|
|
86
|
-
["0", null, "MULTIPART/mixed"],
|
|
87
|
-
["1", "0", "TEXT/plain"],
|
|
88
|
-
["2", "0", "MESSAGE/rfc822"],
|
|
89
|
-
["2.0", "2", "MULTIPART/mixed"],
|
|
90
|
-
["2.1", "2.0", "TEXT/plain"],
|
|
91
|
-
["2.2", "2.0", "APPLICATION/pdf"],
|
|
92
|
-
],
|
|
93
|
-
);
|
|
94
|
-
const forwarded = records.find((record) => record.partPath === "2");
|
|
95
|
-
assert.equal(forwarded?.isMultipart, false);
|
|
96
|
-
assert.equal(forwarded?.dispositionFilename, "forwarded.eml");
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
for (const [disposition, header] of [
|
|
100
|
-
["attachment", ATTACHED],
|
|
101
|
-
["inline", INLINE],
|
|
102
|
-
] as const) {
|
|
103
|
-
it(`stores the raw forwarded message and its own parts' bytes when it is ${disposition}`, async () => {
|
|
104
|
-
const content = await storedContent(
|
|
105
|
-
FIXTURE.replace(ATTACHED, header),
|
|
106
|
-
disposition,
|
|
107
|
-
);
|
|
108
|
-
|
|
109
|
-
assert.deepEqual([...content.keys()], ["1", "2", "2.1", "2.2"]);
|
|
110
|
-
assert.match(content.get("1") ?? "", /Forwarding the message below/);
|
|
111
|
-
assert.equal(
|
|
112
|
-
(content.get("2") ?? "").trimEnd(),
|
|
113
|
-
forwardedSource().trimEnd(),
|
|
114
|
-
);
|
|
115
|
-
assert.equal(
|
|
116
|
-
(content.get("2.1") ?? "").trimEnd(),
|
|
117
|
-
"Original message body before forwarding.",
|
|
118
|
-
);
|
|
119
|
-
assert.equal(content.get("2.2"), "%PDF-1.4\n%adv 04 inner pdf\n%EOF\n");
|
|
120
|
-
});
|
|
121
|
-
}
|
|
122
|
-
});
|