@remit/imap-worker 0.0.89 → 0.0.91
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-check.d.ts +27 -0
- package/dist/account-check.js +70 -0
- package/dist/body-sync-gate.d.ts +12 -0
- package/dist/body-sync-gate.js +53 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +193 -0
- package/dist/config-env.d.ts +5 -0
- package/dist/config-env.js +37 -0
- package/dist/connection-pool.d.ts +22 -0
- package/dist/connection-pool.js +97 -0
- package/dist/connection-scope.d.ts +8 -0
- package/dist/connection-scope.js +10 -0
- package/dist/context.d.ts +46 -0
- package/dist/context.js +15 -0
- package/dist/e2e-processor-shim.d.ts +2 -0
- package/dist/e2e-processor-shim.js +181 -0
- package/dist/emit.d.ts +15 -0
- package/dist/emit.js +76 -0
- package/dist/events.d.ts +203 -0
- package/dist/events.js +1 -0
- package/dist/failure-kind.d.ts +10 -0
- package/{src/failure-kind.ts → dist/failure-kind.js} +6 -6
- package/dist/handlers/append-sent-message.d.ts +3 -0
- package/dist/handlers/append-sent-message.js +152 -0
- package/dist/handlers/delete-account-objects.d.ts +3 -0
- package/dist/handlers/delete-account-objects.js +35 -0
- package/dist/handlers/empty-trash.d.ts +14 -0
- package/dist/handlers/empty-trash.js +217 -0
- package/dist/handlers/flag-push.d.ts +38 -0
- package/dist/handlers/flag-push.js +195 -0
- package/dist/handlers/imported-folder.d.ts +4 -0
- package/dist/handlers/imported-folder.js +23 -0
- package/dist/handlers/mailbox-management.d.ts +6 -0
- package/dist/handlers/mailbox-management.js +294 -0
- package/dist/handlers/mailbox-sync-order.d.ts +21 -0
- package/dist/handlers/mailbox-sync-order.js +50 -0
- package/dist/handlers/message-copy.d.ts +40 -0
- package/dist/handlers/message-copy.js +371 -0
- package/dist/handlers/message-delete-terminal.d.ts +60 -0
- package/dist/handlers/message-delete-terminal.js +77 -0
- package/dist/handlers/message-delete.d.ts +57 -0
- package/dist/handlers/message-delete.js +520 -0
- package/{src/handlers/message-move-terminal.ts → dist/handlers/message-move-terminal.d.ts} +18 -105
- package/dist/handlers/message-move-terminal.js +94 -0
- package/dist/handlers/message-move.d.ts +146 -0
- package/dist/handlers/message-move.js +490 -0
- package/dist/handlers/placement-move-push.d.ts +62 -0
- package/dist/handlers/placement-move-push.js +307 -0
- package/dist/handlers/probe-message-id.d.ts +2 -0
- package/dist/handlers/probe-message-id.js +6 -0
- package/dist/handlers/sync-mailboxes.d.ts +45 -0
- package/dist/handlers/sync-mailboxes.js +233 -0
- package/dist/handlers/sync-message-body.d.ts +23 -0
- package/dist/handlers/sync-message-body.js +182 -0
- package/dist/handlers/sync-messages.d.ts +29 -0
- package/dist/handlers/sync-messages.js +285 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +67 -0
- package/dist/lambda-context.d.ts +8 -0
- package/dist/lambda-context.js +33 -0
- package/dist/mailbox-row.d.ts +9 -0
- package/dist/mailbox-row.js +6 -0
- package/dist/poller.d.ts +1 -0
- package/dist/poller.js +51 -0
- package/dist/process.d.ts +6 -0
- package/dist/process.js +3 -0
- package/dist/processor.d.ts +3 -0
- package/dist/processor.js +44 -0
- package/dist/queues.d.ts +10 -0
- package/dist/queues.js +1 -0
- package/dist/s3-account-objects.d.ts +3 -0
- package/dist/s3-account-objects.js +26 -0
- package/dist/scheduler/attachment-sweep.d.ts +11 -0
- package/dist/scheduler/attachment-sweep.js +23 -0
- package/dist/scheduler/calendar-subscriptions.d.ts +26 -0
- package/dist/scheduler/calendar-subscriptions.js +35 -0
- package/dist/scheduler/config.d.ts +27 -0
- package/{src/scheduler/config.ts → dist/scheduler/config.js} +7 -26
- package/{src/scheduler/decide-due.ts → dist/scheduler/decide-due.d.ts} +3 -12
- package/dist/scheduler/decide-due.js +17 -0
- package/dist/scheduler/handler.d.ts +21 -0
- package/{src/scheduler/handler.ts → dist/scheduler/handler.js} +24 -36
- package/{src/scheduler/loop.ts → dist/scheduler/loop.d.ts} +7 -23
- package/dist/scheduler/loop.js +8 -0
- package/dist/scheduler/run-tick.d.ts +62 -0
- package/dist/scheduler/run-tick.js +164 -0
- package/dist/scheduler/runner.d.ts +2 -0
- package/{src/scheduler/runner.ts → dist/scheduler/runner.js} +58 -84
- package/dist/scheduler/stranded-placement-redrive.d.ts +16 -0
- package/dist/scheduler/stranded-placement-redrive.js +172 -0
- package/dist/sqs-queues.d.ts +4 -0
- package/dist/sqs-queues.js +22 -0
- package/dist/with-oauth-lifecycle-deps.d.ts +5 -0
- package/dist/with-oauth-lifecycle-deps.js +17 -0
- package/dist/with-oauth-lifecycle.d.ts +56 -0
- package/dist/with-oauth-lifecycle.js +92 -0
- package/{src/worker-version.ts → dist/worker-version.d.ts} +1 -2
- package/dist/worker-version.js +12 -0
- package/package.json +75 -69
- package/src/account-check.test.ts +0 -149
- package/src/account-check.ts +0 -101
- package/src/body-sync-gate.test.ts +0 -185
- package/src/body-sync-gate.ts +0 -86
- package/src/cli.ts +0 -224
- package/src/connection-scope.test.ts +0 -266
- package/src/connection-scope.ts +0 -314
- package/src/credential-less-sync-fence.test.ts +0 -152
- package/src/e2e-processor-shim.ts +0 -232
- package/src/emit.test.ts +0 -127
- package/src/emit.ts +0 -117
- package/src/events.ts +0 -252
- package/src/failure-kind.test.ts +0 -42
- package/src/handlers/append-sent-message.sqlite.test.ts +0 -176
- package/src/handlers/append-sent-message.ts +0 -264
- package/src/handlers/delete-account-objects.test.ts +0 -128
- package/src/handlers/delete-account-objects.ts +0 -94
- package/src/handlers/empty-trash.sqlite.test.ts +0 -209
- package/src/handlers/empty-trash.ts +0 -352
- package/src/handlers/flag-push.ts +0 -285
- package/src/handlers/imported-folder.test.ts +0 -63
- package/src/handlers/imported-folder.ts +0 -38
- package/src/handlers/mailbox-management.sqlite.test.ts +0 -265
- package/src/handlers/mailbox-management.ts +0 -486
- package/src/handlers/mailbox-sync-order.test.ts +0 -93
- package/src/handlers/mailbox-sync-order.ts +0 -65
- package/src/handlers/message-copy.sqlite.test.ts +0 -298
- package/src/handlers/message-copy.ts +0 -566
- package/src/handlers/message-delete-terminal.ts +0 -147
- package/src/handlers/message-delete.sqlite.test.ts +0 -291
- package/src/handlers/message-delete.ts +0 -885
- package/src/handlers/message-move.sqlite.test.ts +0 -271
- package/src/handlers/message-move.test.ts +0 -216
- package/src/handlers/message-move.ts +0 -752
- package/src/handlers/placement-move-push.test.ts +0 -137
- package/src/handlers/placement-move-push.ts +0 -475
- package/src/handlers/placement-push.sqlite.test.ts +0 -298
- package/src/handlers/probe-message-id.sqlite.test.ts +0 -192
- package/src/handlers/probe-message-id.ts +0 -15
- package/src/handlers/sync-mailboxes.test.ts +0 -183
- package/src/handlers/sync-mailboxes.ts +0 -421
- package/src/handlers/sync-message-body.sqlite.test.ts +0 -314
- package/src/handlers/sync-message-body.test.ts +0 -150
- package/src/handlers/sync-message-body.ts +0 -365
- package/src/handlers/sync-messages.sqlite.test.ts +0 -183
- package/src/handlers/sync-messages.ts +0 -525
- package/src/index.test.ts +0 -22
- package/src/index.ts +0 -86
- package/src/mail-sync-selection.sqlite.test.ts +0 -234
- package/src/mailbox-row.ts +0 -20
- package/src/pending-sync.sqlite.test.ts +0 -166
- package/src/poller.ts +0 -55
- package/src/processor.ts +0 -60
- package/src/queues.ts +0 -49
- package/src/scheduler/attachment-sweep.sqlite.test.ts +0 -75
- package/src/scheduler/attachment-sweep.ts +0 -46
- package/src/scheduler/calendar-subscriptions.sqlite.test.ts +0 -147
- package/src/scheduler/calendar-subscriptions.ts +0 -103
- package/src/scheduler/config.test.ts +0 -40
- package/src/scheduler/decide-due.test.ts +0 -44
- package/src/scheduler/half-deleted-accounts.sqlite.test.ts +0 -196
- package/src/scheduler/loop.test.ts +0 -143
- package/src/scheduler/run-tick.sqlite.test.ts +0 -191
- package/src/scheduler/run-tick.ts +0 -281
- package/src/scheduler/stranded-placement-redrive.ts +0 -265
- package/src/stranded-placement.sqlite.test.ts +0 -794
- package/src/test-helpers/test-world.ts +0 -427
- package/src/thread-join.sqlite.test.ts +0 -106
- package/src/with-oauth-lifecycle-deps.sqlite.test.ts +0 -269
- package/src/with-oauth-lifecycle-deps.ts +0 -71
- package/src/with-oauth-lifecycle.ts +0 -158
- package/tsconfig.json +0 -8
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
IMessageRepository,
|
|
3
|
-
IThreadMessageRepository,
|
|
4
|
-
MessageItem,
|
|
5
|
-
} from "@remit/data-ports";
|
|
6
|
-
import { MessageMutation, MessageSyncStatus } from "@remit/domain-enums";
|
|
7
|
-
import {
|
|
8
|
-
type IImapConnection,
|
|
9
|
-
isMessageGoneFromOpenMailbox,
|
|
10
|
-
restoreSourcePlacement,
|
|
11
|
-
} from "@remit/mailbox-service";
|
|
12
|
-
|
|
13
|
-
export interface MessageDeleteTerminalLogger {
|
|
14
|
-
info(obj: Record<string, unknown>, msg: string): void;
|
|
15
|
-
error(obj: Record<string, unknown>, msg: string): void;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export interface ResolveExhaustedMessageDeleteDeps {
|
|
19
|
-
messageService: Pick<
|
|
20
|
-
IMessageRepository,
|
|
21
|
-
"deleteIfPlacement" | "transitionPlacement"
|
|
22
|
-
>;
|
|
23
|
-
threadMessageService: Pick<
|
|
24
|
-
IThreadMessageRepository,
|
|
25
|
-
"findAllByMessageId" | "deleteMany" | "update"
|
|
26
|
-
>;
|
|
27
|
-
log: MessageDeleteTerminalLogger;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export interface ResolveExhaustedMessageDeleteInput {
|
|
31
|
-
/** Where the row stood when the caller read it; the delete holds only while it still does. */
|
|
32
|
-
placement: Pick<MessageItem, "status" | "mailboxId" | "uid">;
|
|
33
|
-
accountId: string;
|
|
34
|
-
accountConfigId: string;
|
|
35
|
-
messageId: string;
|
|
36
|
-
sourceMailboxId: string;
|
|
37
|
-
uid: number;
|
|
38
|
-
sourceMailboxPath: string;
|
|
39
|
-
getConnection: () => Promise<IImapConnection>;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export type MessageDeleteTerminalOutcome = "reconciled" | "broken";
|
|
43
|
-
|
|
44
|
-
export interface ResolveExhaustedMessageDeleteResult {
|
|
45
|
-
outcome: MessageDeleteTerminalOutcome;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Resolve a MESSAGE_DELETE failure that has exhausted its redelivery budget
|
|
50
|
-
* into exactly one of two terminal outcomes, mirroring
|
|
51
|
-
* `resolveExhaustedMessageMoveFailure` for the same failure taxonomy (issue
|
|
52
|
-
* #655) — no third, softer outcome.
|
|
53
|
-
*
|
|
54
|
-
* 1. RECONCILED (expected) — the message no longer exists at the delete's
|
|
55
|
-
* source on IMAP, confirmed by {@link isMessageGoneFromOpenMailbox} rather
|
|
56
|
-
* than by a FETCH coming back empty. The move to Trash landed server-side,
|
|
57
|
-
* or a foreign client moved or expunged the message; from here those are
|
|
58
|
-
* indistinguishable and have the same answer. The stale rows are deleted via
|
|
59
|
-
* `deleteIfPlacement` and the caller resyncs the affected folders,
|
|
60
|
-
* so whichever folder actually holds the message re-projects it with the
|
|
61
|
-
* server's own UID. Metric only, no alarm — routine.
|
|
62
|
-
* 2. BROKEN — the message is still at the source, so the delete never took
|
|
63
|
-
* effect, but it keeps failing: broken code or a broken account, not a
|
|
64
|
-
* transient blip. The row is put back where the server has just said the
|
|
65
|
-
* message is: `mailboxId` and `uid` return to the delete's source pair, and
|
|
66
|
-
* the thread rows lose the deletion mark the optimistic transition
|
|
67
|
-
* wrote (issue #1098).
|
|
68
|
-
*
|
|
69
|
-
* Settling `status` to `active` while leaving `mailboxId` on Trash is what
|
|
70
|
-
* this replaces. `bindsForeignUid` refuses a mismatched pair only while
|
|
71
|
-
* `status` is `moving`, so clearing it read as consistent, and the UI then
|
|
72
|
-
* offered "Delete permanently" on a Trash mailbox with the source folder's
|
|
73
|
-
* uid — an expunge naming a different message. Restoring the pair is not a
|
|
74
|
-
* revert on ambiguity (what PR #652 was pulled for): the presence probe
|
|
75
|
-
* above has just answered, so this writes a placement the server confirmed.
|
|
76
|
-
*
|
|
77
|
-
* A server that cannot be reached at exhaustion time never reaches either
|
|
78
|
-
* verdict: the probe throws and the record dead-letters with the row untouched.
|
|
79
|
-
* Absence is only ever concluded from an answer the server gave.
|
|
80
|
-
*/
|
|
81
|
-
export const resolveExhaustedMessageDeleteFailure = async (
|
|
82
|
-
deps: ResolveExhaustedMessageDeleteDeps,
|
|
83
|
-
input: ResolveExhaustedMessageDeleteInput,
|
|
84
|
-
): Promise<ResolveExhaustedMessageDeleteResult> => {
|
|
85
|
-
const {
|
|
86
|
-
accountId,
|
|
87
|
-
accountConfigId,
|
|
88
|
-
messageId,
|
|
89
|
-
sourceMailboxId,
|
|
90
|
-
uid,
|
|
91
|
-
sourceMailboxPath,
|
|
92
|
-
getConnection,
|
|
93
|
-
} = input;
|
|
94
|
-
|
|
95
|
-
const connection = await getConnection();
|
|
96
|
-
await connection.openBox(sourceMailboxPath, true);
|
|
97
|
-
|
|
98
|
-
if (await isMessageGoneFromOpenMailbox(connection, uid)) {
|
|
99
|
-
const reconciled = await deps.messageService.deleteIfPlacement(
|
|
100
|
-
messageId,
|
|
101
|
-
input.placement,
|
|
102
|
-
);
|
|
103
|
-
deps.log.info(
|
|
104
|
-
{
|
|
105
|
-
metric: "message_delete_stale_row_reconciled",
|
|
106
|
-
accountId,
|
|
107
|
-
accountConfigId,
|
|
108
|
-
messageId,
|
|
109
|
-
uid,
|
|
110
|
-
sourceMailboxPath,
|
|
111
|
-
reconciled,
|
|
112
|
-
},
|
|
113
|
-
"Message no longer at its delete source after retry exhaustion (the delete landed server-side, or an external delete or move); stale row reconciled",
|
|
114
|
-
);
|
|
115
|
-
return { outcome: "reconciled" };
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
deps.log.error(
|
|
119
|
-
{
|
|
120
|
-
alert: "message_delete_failed",
|
|
121
|
-
accountId,
|
|
122
|
-
accountConfigId,
|
|
123
|
-
messageId,
|
|
124
|
-
uid,
|
|
125
|
-
sourceMailboxId,
|
|
126
|
-
sourceMailboxPath,
|
|
127
|
-
},
|
|
128
|
-
"Delete could not be pushed to IMAP after retry exhaustion; the message is still at its source — row moved back to the source folder",
|
|
129
|
-
);
|
|
130
|
-
|
|
131
|
-
await restoreSourcePlacement(deps, {
|
|
132
|
-
accountConfigId,
|
|
133
|
-
messageId,
|
|
134
|
-
sourceMailboxId,
|
|
135
|
-
uid,
|
|
136
|
-
// The probe above just confirmed the pair, so the row is a faithful
|
|
137
|
-
// projection of the source again — and the delete the user asked for did
|
|
138
|
-
// not happen, with nothing left to try it. `abandoned` is the value that
|
|
139
|
-
// says so without claiming a retry is coming (imap-mutations R3); the
|
|
140
|
-
// alert stays for the operator, and the mutation names itself so the
|
|
141
|
-
// reading pane offers the right way out (issue #1229).
|
|
142
|
-
syncStatus: MessageSyncStatus.abandoned,
|
|
143
|
-
abandonedMutation: MessageMutation.delete,
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
return { outcome: "broken" };
|
|
147
|
-
};
|
|
@@ -1,291 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import { after, before, describe, it } from "node:test";
|
|
3
|
-
import type { RemitClient } from "@remit/backend/client";
|
|
4
|
-
import {
|
|
5
|
-
MailboxCursorState,
|
|
6
|
-
MailboxSpecialUse,
|
|
7
|
-
MessageMutation,
|
|
8
|
-
MessageStatus,
|
|
9
|
-
MessageSyncStatus,
|
|
10
|
-
} from "@remit/domain-enums";
|
|
11
|
-
import type { MessageDeleteEvent } from "../events.js";
|
|
12
|
-
import { mail, openStore, type World } from "../test-helpers/test-world.js";
|
|
13
|
-
import { handleMessageDelete } from "./message-delete.js";
|
|
14
|
-
|
|
15
|
-
let store: Awaited<ReturnType<typeof openStore>>;
|
|
16
|
-
|
|
17
|
-
before(async () => {
|
|
18
|
-
store = await openStore();
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
after(() => store.close());
|
|
22
|
-
|
|
23
|
-
interface Arranged {
|
|
24
|
-
w: World;
|
|
25
|
-
messageId: string;
|
|
26
|
-
uid: number;
|
|
27
|
-
messageIdHeader: string;
|
|
28
|
-
inboxId: string;
|
|
29
|
-
trashId: string;
|
|
30
|
-
event: MessageDeleteEvent;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* A message in INBOX, deleted through the service the API calls, so the row
|
|
35
|
-
* the handler finds and the event it gets are the ones a person's delete
|
|
36
|
-
* leaves behind.
|
|
37
|
-
*/
|
|
38
|
-
const arrange = async (
|
|
39
|
-
options: { permanent?: boolean; olderCopyInTrash?: boolean } = {},
|
|
40
|
-
): Promise<Arranged> => {
|
|
41
|
-
const w = await store.world();
|
|
42
|
-
const inbox = await w.folder("INBOX");
|
|
43
|
-
const trash = await w.folder("Trash", {
|
|
44
|
-
specialUse: MailboxSpecialUse.Trash,
|
|
45
|
-
});
|
|
46
|
-
const seeded = await w.message(inbox);
|
|
47
|
-
if (options.olderCopyInTrash)
|
|
48
|
-
w.imap.deliver("Trash", mail(seeded.messageIdHeader));
|
|
49
|
-
|
|
50
|
-
await w.client.messageMove.deleteMessage(
|
|
51
|
-
w.account.accountConfigId,
|
|
52
|
-
seeded.messageId,
|
|
53
|
-
w.account.accountId,
|
|
54
|
-
options.permanent ? { permanent: true } : { toTrash: true },
|
|
55
|
-
);
|
|
56
|
-
const event = w
|
|
57
|
-
.queued()
|
|
58
|
-
.find(
|
|
59
|
-
(body) => body.type === "MESSAGE_DELETE",
|
|
60
|
-
) as unknown as MessageDeleteEvent;
|
|
61
|
-
assert.ok(event, "the delete enqueued a MESSAGE_DELETE");
|
|
62
|
-
|
|
63
|
-
return {
|
|
64
|
-
w,
|
|
65
|
-
messageId: seeded.messageId,
|
|
66
|
-
uid: seeded.uid,
|
|
67
|
-
messageIdHeader: seeded.messageIdHeader,
|
|
68
|
-
inboxId: inbox.mailboxId,
|
|
69
|
-
trashId: trash.mailboxId,
|
|
70
|
-
event,
|
|
71
|
-
};
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
const run = (a: Arranged, receiveCount = 1, event = a.event) =>
|
|
75
|
-
handleMessageDelete(event, a.w.log, receiveCount, a.w.scope);
|
|
76
|
-
|
|
77
|
-
const resyncs = (w: World): unknown[] =>
|
|
78
|
-
w
|
|
79
|
-
.queued()
|
|
80
|
-
.filter((body) => body.type === "SYNC_MESSAGES")
|
|
81
|
-
.map((body) => body.mailboxId);
|
|
82
|
-
|
|
83
|
-
const client = (): RemitClient => store.client;
|
|
84
|
-
|
|
85
|
-
describe("a move to Trash", () => {
|
|
86
|
-
it("lands in Trash and settles the row on the uid the server gave it", async () => {
|
|
87
|
-
const a = await arrange();
|
|
88
|
-
|
|
89
|
-
await run(a);
|
|
90
|
-
|
|
91
|
-
assert.deepEqual(a.w.imap.uids("INBOX"), []);
|
|
92
|
-
const [trashUid] = a.w.imap.uids("Trash", a.messageIdHeader);
|
|
93
|
-
const row = await a.w.row(a.messageId);
|
|
94
|
-
assert.equal(row?.mailboxId, a.trashId);
|
|
95
|
-
assert.equal(row?.uid, trashUid);
|
|
96
|
-
assert.equal(row?.status, MessageStatus.active);
|
|
97
|
-
assert.deepEqual(await a.w.threadRows(a.messageId), [
|
|
98
|
-
{ mailboxId: a.trashId, uid: trashUid, isDeleted: true },
|
|
99
|
-
]);
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
// Issue #1122. Without UIDPLUS the destination is asked by Message-ID, and
|
|
103
|
-
// Trash already holds an older copy: binding the row to it lets Empty Trash
|
|
104
|
-
// expunge the wrong one and leaves the fresh copy unreachable.
|
|
105
|
-
it("settles on the copy this move delivered when the server sends no COPYUID", async () => {
|
|
106
|
-
const a = await arrange({ olderCopyInTrash: true });
|
|
107
|
-
a.w.imap.uidplus = false;
|
|
108
|
-
|
|
109
|
-
await run(a);
|
|
110
|
-
|
|
111
|
-
const copies = a.w.imap.uids("Trash", a.messageIdHeader);
|
|
112
|
-
assert.equal(copies.length, 2);
|
|
113
|
-
assert.equal((await a.w.row(a.messageId))?.uid, Math.max(...copies));
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
// One Message-ID can have several server copies. A source that still holds
|
|
117
|
-
// the uid proves the MOVE never ran, so the older copy in Trash is not this
|
|
118
|
-
// message and must never settle the row.
|
|
119
|
-
it("never settles on an older copy in Trash while the source still holds the message", async () => {
|
|
120
|
-
const a = await arrange({ olderCopyInTrash: true });
|
|
121
|
-
a.w.imap.uidplus = false;
|
|
122
|
-
a.w.imap.ignoreNext.add("MOVE");
|
|
123
|
-
|
|
124
|
-
await assert.rejects(run(a), /unconfirmed/);
|
|
125
|
-
|
|
126
|
-
const row = await a.w.row(a.messageId);
|
|
127
|
-
assert.equal(row?.uid, a.uid);
|
|
128
|
-
assert.equal(row?.status, MessageStatus.moving);
|
|
129
|
-
assert.equal(row?.syncStatus, MessageSyncStatus.failed);
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
it("issues nothing when a redelivery finds the delete already settled", async () => {
|
|
133
|
-
const a = await arrange();
|
|
134
|
-
await run(a);
|
|
135
|
-
const settled = await a.w.row(a.messageId);
|
|
136
|
-
const commands = a.w.imap.log.length;
|
|
137
|
-
|
|
138
|
-
await run(a);
|
|
139
|
-
|
|
140
|
-
assert.equal(
|
|
141
|
-
a.w.imap.log.length,
|
|
142
|
-
commands,
|
|
143
|
-
"no IMAP command on redelivery",
|
|
144
|
-
);
|
|
145
|
-
assert.deepEqual(await a.w.row(a.messageId), settled);
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
it("marks the row failed and redelivers when the connection drops mid-move", async () => {
|
|
149
|
-
const a = await arrange();
|
|
150
|
-
a.w.imap.failNext.MOVE = new Error("Connection reset");
|
|
151
|
-
|
|
152
|
-
await assert.rejects(run(a), /Connection reset/);
|
|
153
|
-
|
|
154
|
-
const row = await a.w.row(a.messageId);
|
|
155
|
-
assert.equal(row?.status, MessageStatus.moving);
|
|
156
|
-
assert.equal(row?.syncStatus, MessageSyncStatus.failed);
|
|
157
|
-
assert.deepEqual(a.w.imap.uids("INBOX"), [a.uid]);
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
// Issue #980. At the ceiling the server is asked where the message is, and
|
|
161
|
-
// a message that never left INBOX puts the row back there.
|
|
162
|
-
it("puts the row back on its source at the ceiling when the message never left", async () => {
|
|
163
|
-
const a = await arrange();
|
|
164
|
-
a.w.imap.failNext.MOVE = new Error("Connection reset");
|
|
165
|
-
|
|
166
|
-
await run(a, 3);
|
|
167
|
-
|
|
168
|
-
const row = await a.w.row(a.messageId);
|
|
169
|
-
assert.equal(row?.mailboxId, a.inboxId);
|
|
170
|
-
assert.equal(row?.uid, a.uid);
|
|
171
|
-
assert.notEqual(row?.status, MessageStatus.moving);
|
|
172
|
-
assert.deepEqual(resyncs(a.w), [a.inboxId, a.trashId]);
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
it("hands the row back without creating a Trash the server does not have", async () => {
|
|
176
|
-
const a = await arrange();
|
|
177
|
-
a.w.imap.removeFolder("Trash");
|
|
178
|
-
|
|
179
|
-
await run(a);
|
|
180
|
-
|
|
181
|
-
assert.deepEqual(a.w.imap.commands("CREATE"), []);
|
|
182
|
-
const row = await a.w.row(a.messageId);
|
|
183
|
-
assert.equal(row?.mailboxId, a.inboxId);
|
|
184
|
-
assert.equal(row?.uid, a.uid);
|
|
185
|
-
assert.equal(row?.syncStatus, MessageSyncStatus.abandoned);
|
|
186
|
-
assert.equal(row?.abandonedMutation, MessageMutation.delete);
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
it("hands the row back without connecting while the source cursor is rebuilding", async () => {
|
|
190
|
-
const a = await arrange();
|
|
191
|
-
await client().mailbox.update(a.w.account.accountId, a.inboxId, {
|
|
192
|
-
cursorState: MailboxCursorState.rebuilding,
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
await run(a);
|
|
196
|
-
|
|
197
|
-
assert.equal(a.w.imap.connections, 0);
|
|
198
|
-
const row = await a.w.row(a.messageId);
|
|
199
|
-
assert.equal(row?.mailboxId, a.inboxId);
|
|
200
|
-
assert.equal(row?.status, MessageStatus.active);
|
|
201
|
-
assert.equal(
|
|
202
|
-
a.w.logs.filter(
|
|
203
|
-
(line) => line.fields.metric === "message_delete_dropped_on_pause",
|
|
204
|
-
).length,
|
|
205
|
-
1,
|
|
206
|
-
);
|
|
207
|
-
assert.deepEqual(resyncs(a.w), [a.inboxId, a.trashId]);
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
// The folder was deleted and recreated on the server: uid 1 now names
|
|
211
|
-
// whatever the server put there. Nothing may be moved by it.
|
|
212
|
-
it("moves nothing when the source's UIDVALIDITY changed under it", async () => {
|
|
213
|
-
const a = await arrange();
|
|
214
|
-
a.w.imap.setUidValidity("INBOX", 2);
|
|
215
|
-
|
|
216
|
-
await run(a);
|
|
217
|
-
|
|
218
|
-
assert.deepEqual(a.w.imap.commands("MOVE"), []);
|
|
219
|
-
assert.equal((await a.w.row(a.messageId))?.mailboxId, a.inboxId);
|
|
220
|
-
const inbox = await client().mailbox.get(a.w.account.accountId, a.inboxId);
|
|
221
|
-
assert.notEqual(inbox.cursorState, MailboxCursorState.normal);
|
|
222
|
-
});
|
|
223
|
-
|
|
224
|
-
it("moves from the folder's current path when it was renamed after enqueue", async () => {
|
|
225
|
-
const a = await arrange();
|
|
226
|
-
a.w.imap.renameFolder("INBOX", "Old/Inbox");
|
|
227
|
-
await client().mailbox.update(a.w.account.accountId, a.inboxId, {
|
|
228
|
-
fullPath: "Old/Inbox",
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
await run(a);
|
|
232
|
-
|
|
233
|
-
assert.deepEqual(a.w.imap.uids("Old/Inbox"), []);
|
|
234
|
-
assert.equal(a.w.imap.uids("Trash", a.messageIdHeader).length, 1);
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
it("acks without connecting when the source mailbox was deleted", async () => {
|
|
238
|
-
const a = await arrange();
|
|
239
|
-
await client().mailbox.delete(a.w.account.accountId, a.inboxId);
|
|
240
|
-
|
|
241
|
-
await run(a);
|
|
242
|
-
|
|
243
|
-
assert.equal(a.w.imap.connections, 0);
|
|
244
|
-
});
|
|
245
|
-
|
|
246
|
-
it("refuses an event minted under an unknown contract and hands the row back", async () => {
|
|
247
|
-
const a = await arrange();
|
|
248
|
-
|
|
249
|
-
await run(a, 1, {
|
|
250
|
-
...a.event,
|
|
251
|
-
schemaVersion: 99,
|
|
252
|
-
} as unknown as MessageDeleteEvent);
|
|
253
|
-
|
|
254
|
-
assert.equal(a.w.imap.connections, 0);
|
|
255
|
-
const row = await a.w.row(a.messageId);
|
|
256
|
-
assert.equal(row?.mailboxId, a.inboxId);
|
|
257
|
-
assert.equal(row?.syncStatus, MessageSyncStatus.abandoned);
|
|
258
|
-
});
|
|
259
|
-
});
|
|
260
|
-
|
|
261
|
-
describe("a permanent delete", () => {
|
|
262
|
-
it("expunges the message and removes every local row", async () => {
|
|
263
|
-
const a = await arrange({ permanent: true });
|
|
264
|
-
|
|
265
|
-
await run(a);
|
|
266
|
-
|
|
267
|
-
assert.deepEqual(a.w.imap.uids("INBOX"), []);
|
|
268
|
-
assert.equal(await a.w.row(a.messageId), undefined);
|
|
269
|
-
assert.deepEqual(await a.w.threadRows(a.messageId), []);
|
|
270
|
-
});
|
|
271
|
-
|
|
272
|
-
it("cleans up locally when another client already expunged it", async () => {
|
|
273
|
-
const a = await arrange({ permanent: true });
|
|
274
|
-
a.w.imap.empty("INBOX");
|
|
275
|
-
a.w.imap.failNext.EXPUNGE = new Error("Message not found (NONEXISTENT)");
|
|
276
|
-
|
|
277
|
-
await run(a);
|
|
278
|
-
|
|
279
|
-
assert.equal(await a.w.row(a.messageId), undefined);
|
|
280
|
-
});
|
|
281
|
-
|
|
282
|
-
it("keeps the row when the server still holds the message after a failed expunge", async () => {
|
|
283
|
-
const a = await arrange({ permanent: true });
|
|
284
|
-
a.w.imap.failNext.EXPUNGE = new Error("Message not found (NONEXISTENT)");
|
|
285
|
-
|
|
286
|
-
await assert.rejects(run(a), /NONEXISTENT/);
|
|
287
|
-
|
|
288
|
-
assert.equal((await a.w.row(a.messageId))?.status, MessageStatus.deleting);
|
|
289
|
-
assert.deepEqual(a.w.imap.uids("INBOX"), [a.uid]);
|
|
290
|
-
});
|
|
291
|
-
});
|