@remit/imap-worker 0.0.85 → 0.0.87
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/README.md +3 -1
- package/package.json +10 -2
- package/src/account-check.ts +1 -1
- package/src/body-sync-gate.test.ts +1 -1
- package/src/body-sync-gate.ts +1 -1
- package/src/e2e-processor-shim.ts +6 -21
- package/src/emit.test.ts +32 -4
- package/src/emit.ts +58 -23
- package/src/failure-kind.ts +1 -1
- package/src/handlers/append-sent-message.sqlite.test.ts +176 -0
- package/src/handlers/append-sent-message.ts +3 -24
- package/src/handlers/delete-account-objects.test.ts +1 -1
- package/src/handlers/delete-account-objects.ts +1 -1
- package/src/handlers/empty-trash.sqlite.test.ts +209 -0
- package/src/handlers/empty-trash.ts +3 -24
- package/src/handlers/flag-push.ts +11 -18
- package/src/handlers/mailbox-management.sqlite.test.ts +265 -0
- package/src/handlers/mailbox-management.ts +12 -48
- package/src/handlers/message-copy.sqlite.test.ts +298 -0
- package/src/handlers/message-copy.ts +31 -49
- package/src/handlers/message-delete-terminal.ts +12 -10
- package/src/handlers/message-delete.sqlite.test.ts +291 -0
- package/src/handlers/message-delete.ts +26 -32
- package/src/handlers/message-move-terminal.ts +12 -10
- package/src/handlers/message-move.sqlite.test.ts +271 -0
- package/src/handlers/message-move.test.ts +76 -863
- package/src/handlers/message-move.ts +49 -39
- package/src/handlers/placement-move-push.test.ts +96 -329
- package/src/handlers/placement-move-push.ts +75 -29
- package/src/handlers/placement-push.sqlite.test.ts +298 -0
- package/src/handlers/probe-message-id.sqlite.test.ts +197 -0
- package/src/handlers/probe-message-id.ts +15 -0
- package/src/handlers/sync-mailboxes.test.ts +0 -1
- package/src/handlers/sync-mailboxes.ts +1 -1
- package/src/handlers/sync-message-body.sqlite.test.ts +314 -0
- package/src/handlers/sync-message-body.test.ts +3 -299
- package/src/handlers/sync-message-body.ts +21 -11
- package/src/handlers/sync-messages.sqlite.test.ts +183 -0
- package/src/handlers/sync-messages.ts +35 -34
- package/src/index.ts +5 -3
- package/src/mail-sync-selection.sqlite.test.ts +20 -18
- package/src/pending-sync.sqlite.test.ts +167 -0
- package/src/poller.ts +5 -4
- package/src/processor.test.ts +1 -1
- package/src/processor.ts +1 -1
- package/src/scheduler/attachment-sweep.sqlite.test.ts +74 -0
- package/src/scheduler/attachment-sweep.ts +1 -1
- package/src/scheduler/{calendar-subscriptions.test.ts → calendar-subscriptions.sqlite.test.ts} +43 -55
- package/src/scheduler/calendar-subscriptions.ts +6 -6
- package/src/scheduler/half-deleted-accounts.sqlite.test.ts +203 -0
- package/src/scheduler/handler.ts +12 -1
- package/src/scheduler/loop.test.ts +2 -0
- package/src/scheduler/run-tick.sqlite.test.ts +192 -0
- package/src/scheduler/run-tick.ts +39 -1
- package/src/scheduler/runner.ts +21 -1
- package/src/scheduler/stranded-placement-redrive.ts +265 -0
- package/src/stranded-placement.sqlite.test.ts +795 -0
- package/src/test-helpers/test-world.ts +398 -0
- package/src/thread-join.sqlite.test.ts +115 -0
- package/src/with-oauth-lifecycle-deps.sqlite.test.ts +138 -14
- package/src/with-oauth-lifecycle.ts +1 -1
- package/src/handlers/append-sent-message.test.ts +0 -529
- package/src/handlers/empty-trash.test.ts +0 -728
- package/src/handlers/flag-push-placement-race.test.ts +0 -385
- package/src/handlers/flag-push.test.ts +0 -325
- package/src/handlers/mailbox-management.test.ts +0 -850
- package/src/handlers/message-copy.test.ts +0 -654
- package/src/handlers/message-delete.test.ts +0 -1869
- package/src/handlers/message-move-terminal.test.ts +0 -412
- package/src/handlers/sync-messages.test.ts +0 -567
- package/src/scheduler/attachment-sweep.test.ts +0 -169
- package/src/scheduler/run-tick.test.ts +0 -478
- package/src/with-oauth-lifecycle.test.ts +0 -318
|
@@ -1,412 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import { describe, it } from "node:test";
|
|
3
|
-
import type {
|
|
4
|
-
IMessageRepository,
|
|
5
|
-
IThreadMessageRepository,
|
|
6
|
-
} from "@remit/data-ports";
|
|
7
|
-
import {
|
|
8
|
-
hasAbandonedDelete,
|
|
9
|
-
hasAbandonedMove,
|
|
10
|
-
} from "@remit/data-ports/message-settlement";
|
|
11
|
-
import {
|
|
12
|
-
type IImapConnection,
|
|
13
|
-
placementBindingOf,
|
|
14
|
-
} from "@remit/mailbox-service";
|
|
15
|
-
import {
|
|
16
|
-
type MessageMoveTerminalLogger,
|
|
17
|
-
type ResolveExhaustedMessageMoveDeps,
|
|
18
|
-
resolveExhaustedMessageMoveFailure,
|
|
19
|
-
} from "./message-move-terminal.js";
|
|
20
|
-
|
|
21
|
-
interface LogEntry {
|
|
22
|
-
obj: Record<string, unknown>;
|
|
23
|
-
msg: string;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const buildLogger = (): {
|
|
27
|
-
log: MessageMoveTerminalLogger;
|
|
28
|
-
infos: LogEntry[];
|
|
29
|
-
errors: LogEntry[];
|
|
30
|
-
} => {
|
|
31
|
-
const infos: LogEntry[] = [];
|
|
32
|
-
const errors: LogEntry[] = [];
|
|
33
|
-
return {
|
|
34
|
-
log: {
|
|
35
|
-
info: (obj, msg) => infos.push({ obj, msg }),
|
|
36
|
-
error: (obj, msg) => errors.push({ obj, msg }),
|
|
37
|
-
},
|
|
38
|
-
infos,
|
|
39
|
-
errors,
|
|
40
|
-
};
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
const buildConnection = (
|
|
44
|
-
present: Set<number>,
|
|
45
|
-
fetchDrops: Set<number> = new Set(),
|
|
46
|
-
): IImapConnection =>
|
|
47
|
-
({
|
|
48
|
-
openBox: async () => ({}) as never,
|
|
49
|
-
fetchMessages: async (uids: number[]) =>
|
|
50
|
-
uids
|
|
51
|
-
.filter((uid) => present.has(uid) && !fetchDrops.has(uid))
|
|
52
|
-
.map((uid) => ({ uid }) as unknown as never),
|
|
53
|
-
search: async (criteria: unknown[]) => {
|
|
54
|
-
const [, value] = (criteria as Array<[string, string]>)[0];
|
|
55
|
-
const uid = Number(value);
|
|
56
|
-
return present.has(uid) ? [uid] : [];
|
|
57
|
-
},
|
|
58
|
-
}) as unknown as IImapConnection;
|
|
59
|
-
|
|
60
|
-
interface MessageRow {
|
|
61
|
-
messageId: string;
|
|
62
|
-
mailboxId: string;
|
|
63
|
-
uid: number;
|
|
64
|
-
status: string;
|
|
65
|
-
syncStatus: string;
|
|
66
|
-
originalMailboxId: string;
|
|
67
|
-
originalUid: number;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
interface ThreadRow {
|
|
71
|
-
accountConfigId: string;
|
|
72
|
-
threadMessageId: string;
|
|
73
|
-
mailboxId: string;
|
|
74
|
-
uid: number;
|
|
75
|
-
isDeleted: boolean;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* The move's pending state IS the Message row, so these fakes hold real rows
|
|
80
|
-
* and the assertions read the rows back — a resolver that settled the row on
|
|
81
|
-
* anything but the server's own answer shows up here as a changed row, not as
|
|
82
|
-
* an uncalled mock. `transitionPlacement` honours its predicate the way the
|
|
83
|
-
* repository does: the write lands only while the row is still in one of the
|
|
84
|
-
* from-states the caller named (imap-mutations R3), and answers `undefined`
|
|
85
|
-
* otherwise.
|
|
86
|
-
*/
|
|
87
|
-
const buildRepositories = (row: MessageRow) => {
|
|
88
|
-
const messages = new Map<string, MessageRow>([[row.messageId, row]]);
|
|
89
|
-
const threadMessages = new Map<string, ThreadRow>([
|
|
90
|
-
[
|
|
91
|
-
`tm-${row.messageId}`,
|
|
92
|
-
{
|
|
93
|
-
accountConfigId: "cfg-1",
|
|
94
|
-
threadMessageId: `tm-${row.messageId}`,
|
|
95
|
-
mailboxId: row.mailboxId,
|
|
96
|
-
uid: row.uid,
|
|
97
|
-
isDeleted: false,
|
|
98
|
-
},
|
|
99
|
-
],
|
|
100
|
-
]);
|
|
101
|
-
|
|
102
|
-
return {
|
|
103
|
-
messages,
|
|
104
|
-
threadMessages,
|
|
105
|
-
messageService: {
|
|
106
|
-
delete: async (messageId: string) => {
|
|
107
|
-
messages.delete(messageId);
|
|
108
|
-
},
|
|
109
|
-
update: async (messageId: string, input: Partial<MessageRow>) => {
|
|
110
|
-
const current = messages.get(messageId);
|
|
111
|
-
if (current) messages.set(messageId, { ...current, ...input });
|
|
112
|
-
},
|
|
113
|
-
transitionPlacement: async (
|
|
114
|
-
messageId: string,
|
|
115
|
-
expected: { status?: string[] },
|
|
116
|
-
next: Partial<MessageRow>,
|
|
117
|
-
) => {
|
|
118
|
-
const current = messages.get(messageId);
|
|
119
|
-
if (!current) return undefined;
|
|
120
|
-
if (expected.status && !expected.status.includes(current.status)) {
|
|
121
|
-
return undefined;
|
|
122
|
-
}
|
|
123
|
-
const written = { ...current, ...next };
|
|
124
|
-
messages.set(messageId, written);
|
|
125
|
-
return written;
|
|
126
|
-
},
|
|
127
|
-
} as unknown as Pick<IMessageRepository, "delete" | "transitionPlacement">,
|
|
128
|
-
threadMessageService: {
|
|
129
|
-
findAllByMessageId: async () => [...threadMessages.values()],
|
|
130
|
-
deleteMany: async (
|
|
131
|
-
keys: Array<{ accountConfigId: string; threadMessageId: string }>,
|
|
132
|
-
) => {
|
|
133
|
-
for (const key of keys) threadMessages.delete(key.threadMessageId);
|
|
134
|
-
},
|
|
135
|
-
update: async (
|
|
136
|
-
_accountConfigId: string,
|
|
137
|
-
threadMessageId: string,
|
|
138
|
-
set: Partial<ThreadRow>,
|
|
139
|
-
) => {
|
|
140
|
-
const current = threadMessages.get(threadMessageId);
|
|
141
|
-
if (current)
|
|
142
|
-
threadMessages.set(threadMessageId, { ...current, ...set });
|
|
143
|
-
},
|
|
144
|
-
} as unknown as Pick<
|
|
145
|
-
IThreadMessageRepository,
|
|
146
|
-
"findAllByMessageId" | "deleteMany" | "update"
|
|
147
|
-
>,
|
|
148
|
-
};
|
|
149
|
-
};
|
|
150
|
-
|
|
151
|
-
const pendingMoveRow = (): MessageRow => ({
|
|
152
|
-
messageId: "msg-1",
|
|
153
|
-
mailboxId: "mbx-archive",
|
|
154
|
-
uid: 101,
|
|
155
|
-
status: "moving",
|
|
156
|
-
syncStatus: "failed",
|
|
157
|
-
originalMailboxId: "mbx-inbox",
|
|
158
|
-
originalUid: 101,
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
const input = {
|
|
162
|
-
accountId: "acc-1",
|
|
163
|
-
accountConfigId: "cfg-1",
|
|
164
|
-
messageId: "msg-1",
|
|
165
|
-
sourceMailboxId: "mbx-inbox",
|
|
166
|
-
uid: 101,
|
|
167
|
-
sourceMailboxPath: "INBOX",
|
|
168
|
-
};
|
|
169
|
-
|
|
170
|
-
describe("resolveExhaustedMessageMoveFailure — the two terminal outcomes (issue #655)", () => {
|
|
171
|
-
it("RECONCILED: the message is gone from the move's source — stale row reconciled, no alarm", async () => {
|
|
172
|
-
const repos = buildRepositories(pendingMoveRow());
|
|
173
|
-
const { log, infos, errors } = buildLogger();
|
|
174
|
-
const deps: ResolveExhaustedMessageMoveDeps = {
|
|
175
|
-
messageService: repos.messageService,
|
|
176
|
-
threadMessageService: repos.threadMessageService,
|
|
177
|
-
log,
|
|
178
|
-
};
|
|
179
|
-
|
|
180
|
-
const result = await resolveExhaustedMessageMoveFailure(deps, {
|
|
181
|
-
...input,
|
|
182
|
-
getConnection: async () => buildConnection(new Set()),
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
assert.equal(result.outcome, "reconciled");
|
|
186
|
-
assert.equal(
|
|
187
|
-
repos.messages.get("msg-1"),
|
|
188
|
-
undefined,
|
|
189
|
-
"the stale Message row is deleted so a resync can re-project it",
|
|
190
|
-
);
|
|
191
|
-
assert.equal(repos.threadMessages.size, 0);
|
|
192
|
-
assert.equal(errors.length, 0, "no alarm for the expected/routine outcome");
|
|
193
|
-
assert.ok(
|
|
194
|
-
infos.some((e) => e.obj.metric === "message_move_stale_row_reconciled"),
|
|
195
|
-
);
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
it("BROKEN: the message is still at the source — the row is put back on the pair the server confirmed, alarm logged, never re-thrown", async () => {
|
|
199
|
-
const repos = buildRepositories(pendingMoveRow());
|
|
200
|
-
const { log, errors } = buildLogger();
|
|
201
|
-
const deps: ResolveExhaustedMessageMoveDeps = {
|
|
202
|
-
messageService: repos.messageService,
|
|
203
|
-
threadMessageService: repos.threadMessageService,
|
|
204
|
-
log,
|
|
205
|
-
};
|
|
206
|
-
|
|
207
|
-
const result = await resolveExhaustedMessageMoveFailure(deps, {
|
|
208
|
-
...input,
|
|
209
|
-
getConnection: async () => buildConnection(new Set([101])),
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
assert.equal(result.outcome, "broken");
|
|
213
|
-
assert.equal(repos.messages.get("msg-1")?.mailboxId, "mbx-inbox");
|
|
214
|
-
assert.equal(repos.messages.get("msg-1")?.uid, 101);
|
|
215
|
-
assert.equal(
|
|
216
|
-
repos.threadMessages.get("tm-msg-1")?.mailboxId,
|
|
217
|
-
"mbx-inbox",
|
|
218
|
-
"the listing row follows the message back to the folder it never left",
|
|
219
|
-
);
|
|
220
|
-
assert.ok(errors.some((e) => e.obj.alert === "message_move_failed"));
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
// Issue #1153: before the give-up had a value of its own, this row settled
|
|
224
|
-
// on `synced` and read exactly like a move that never happened — nothing on
|
|
225
|
-
// it said "the move you asked for is not coming". #1229 is why it also has
|
|
226
|
-
// to say WHICH mutation: a hand-back clears `status`, and `status` was the
|
|
227
|
-
// only field naming one.
|
|
228
|
-
it("BROKEN: the row says a move gave up, and says it was a move", async () => {
|
|
229
|
-
const repos = buildRepositories(pendingMoveRow());
|
|
230
|
-
const { log } = buildLogger();
|
|
231
|
-
const deps: ResolveExhaustedMessageMoveDeps = {
|
|
232
|
-
messageService: repos.messageService,
|
|
233
|
-
threadMessageService: repos.threadMessageService,
|
|
234
|
-
log,
|
|
235
|
-
};
|
|
236
|
-
|
|
237
|
-
await resolveExhaustedMessageMoveFailure(deps, {
|
|
238
|
-
...input,
|
|
239
|
-
getConnection: async () => buildConnection(new Set([101])),
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
const settled = repos.messages.get("msg-1") as unknown as {
|
|
243
|
-
syncStatus: string;
|
|
244
|
-
abandonedMutation: string;
|
|
245
|
-
};
|
|
246
|
-
assert.equal(settled.syncStatus, "abandoned");
|
|
247
|
-
assert.equal(settled.abandonedMutation, "move");
|
|
248
|
-
assert.equal(
|
|
249
|
-
hasAbandonedDelete(settled as never),
|
|
250
|
-
false,
|
|
251
|
-
"and never reads as a delete that gave up",
|
|
252
|
-
);
|
|
253
|
-
assert.equal(hasAbandonedMove(settled as never), true);
|
|
254
|
-
});
|
|
255
|
-
|
|
256
|
-
// Issue #1005: a give-up that never writes `status` leaves the row naming
|
|
257
|
-
// the destination with the source's uid — a pair every dependent mutation
|
|
258
|
-
// refuses, which made the message undeletable and unmovable for good.
|
|
259
|
-
it("BROKEN: the settled row is no longer refused by the placement guard (#1005)", async () => {
|
|
260
|
-
const repos = buildRepositories(pendingMoveRow());
|
|
261
|
-
const { log } = buildLogger();
|
|
262
|
-
const deps: ResolveExhaustedMessageMoveDeps = {
|
|
263
|
-
messageService: repos.messageService,
|
|
264
|
-
threadMessageService: repos.threadMessageService,
|
|
265
|
-
log,
|
|
266
|
-
};
|
|
267
|
-
|
|
268
|
-
assert.equal(
|
|
269
|
-
placementBindingOf(pendingMoveRow() as never),
|
|
270
|
-
"in_flight",
|
|
271
|
-
"the row starts on a pair the delete route will not act on",
|
|
272
|
-
);
|
|
273
|
-
|
|
274
|
-
await resolveExhaustedMessageMoveFailure(deps, {
|
|
275
|
-
...input,
|
|
276
|
-
getConnection: async () => buildConnection(new Set([101])),
|
|
277
|
-
});
|
|
278
|
-
|
|
279
|
-
const settled = repos.messages.get("msg-1");
|
|
280
|
-
assert.ok(settled);
|
|
281
|
-
assert.equal(placementBindingOf(settled as never), "consistent");
|
|
282
|
-
assert.equal(settled.status, "active");
|
|
283
|
-
});
|
|
284
|
-
|
|
285
|
-
it("BROKEN: an empty FETCH the SEARCH contradicts never counts as gone", async () => {
|
|
286
|
-
const repos = buildRepositories(pendingMoveRow());
|
|
287
|
-
const { log } = buildLogger();
|
|
288
|
-
const deps: ResolveExhaustedMessageMoveDeps = {
|
|
289
|
-
messageService: repos.messageService,
|
|
290
|
-
threadMessageService: repos.threadMessageService,
|
|
291
|
-
log,
|
|
292
|
-
};
|
|
293
|
-
|
|
294
|
-
const result = await resolveExhaustedMessageMoveFailure(deps, {
|
|
295
|
-
...input,
|
|
296
|
-
getConnection: async () =>
|
|
297
|
-
buildConnection(new Set([101]), new Set([101])),
|
|
298
|
-
});
|
|
299
|
-
|
|
300
|
-
assert.equal(result.outcome, "broken");
|
|
301
|
-
assert.equal(
|
|
302
|
-
repos.messages.get("msg-1")?.mailboxId,
|
|
303
|
-
"mbx-inbox",
|
|
304
|
-
"a message the SEARCH still lists is at the source, and the row says so",
|
|
305
|
-
);
|
|
306
|
-
});
|
|
307
|
-
|
|
308
|
-
// The resolver's contract is that it is never re-thrown, so the caller can
|
|
309
|
-
// ack the record and emit its resync. A row another path deleted or settled
|
|
310
|
-
// while the probe was in flight has nothing left to restore, and it loses
|
|
311
|
-
// the transition's predicate rather than raising; a NotFound still wraps an
|
|
312
|
-
// ElectroDB composites miss on the listing row.
|
|
313
|
-
it("BROKEN: a row settled underneath the probe loses the predicate instead of throwing", async () => {
|
|
314
|
-
const repos = buildRepositories(pendingMoveRow());
|
|
315
|
-
const { log } = buildLogger();
|
|
316
|
-
const deps: ResolveExhaustedMessageMoveDeps = {
|
|
317
|
-
messageService: {
|
|
318
|
-
...repos.messageService,
|
|
319
|
-
transitionPlacement: async () => undefined,
|
|
320
|
-
} as unknown as ResolveExhaustedMessageMoveDeps["messageService"],
|
|
321
|
-
threadMessageService: repos.threadMessageService,
|
|
322
|
-
log,
|
|
323
|
-
};
|
|
324
|
-
|
|
325
|
-
const result = await resolveExhaustedMessageMoveFailure(deps, {
|
|
326
|
-
...input,
|
|
327
|
-
getConnection: async () => buildConnection(new Set([101])),
|
|
328
|
-
});
|
|
329
|
-
|
|
330
|
-
assert.equal(result.outcome, "broken");
|
|
331
|
-
});
|
|
332
|
-
|
|
333
|
-
it("BROKEN: a listing row whose composites moved on does not fail the settle", async () => {
|
|
334
|
-
const repos = buildRepositories(pendingMoveRow());
|
|
335
|
-
const { log } = buildLogger();
|
|
336
|
-
const deps: ResolveExhaustedMessageMoveDeps = {
|
|
337
|
-
messageService: repos.messageService,
|
|
338
|
-
threadMessageService: {
|
|
339
|
-
...repos.threadMessageService,
|
|
340
|
-
update: async () => {
|
|
341
|
-
throw Object.assign(new Error("Thread message not found"), {
|
|
342
|
-
name: "NotFoundError",
|
|
343
|
-
});
|
|
344
|
-
},
|
|
345
|
-
} as unknown as ResolveExhaustedMessageMoveDeps["threadMessageService"],
|
|
346
|
-
log,
|
|
347
|
-
};
|
|
348
|
-
|
|
349
|
-
const result = await resolveExhaustedMessageMoveFailure(deps, {
|
|
350
|
-
...input,
|
|
351
|
-
getConnection: async () => buildConnection(new Set([101])),
|
|
352
|
-
});
|
|
353
|
-
|
|
354
|
-
assert.equal(result.outcome, "broken");
|
|
355
|
-
assert.equal(
|
|
356
|
-
repos.messages.get("msg-1")?.mailboxId,
|
|
357
|
-
"mbx-inbox",
|
|
358
|
-
"the Message row still lands on the pair the server confirmed",
|
|
359
|
-
);
|
|
360
|
-
});
|
|
361
|
-
|
|
362
|
-
it("a repository fault that is not a missing row still propagates", async () => {
|
|
363
|
-
const repos = buildRepositories(pendingMoveRow());
|
|
364
|
-
const { log } = buildLogger();
|
|
365
|
-
const deps: ResolveExhaustedMessageMoveDeps = {
|
|
366
|
-
messageService: {
|
|
367
|
-
...repos.messageService,
|
|
368
|
-
transitionPlacement: async () => {
|
|
369
|
-
throw new Error("ProvisionedThroughputExceeded");
|
|
370
|
-
},
|
|
371
|
-
} as unknown as ResolveExhaustedMessageMoveDeps["messageService"],
|
|
372
|
-
threadMessageService: repos.threadMessageService,
|
|
373
|
-
log,
|
|
374
|
-
};
|
|
375
|
-
|
|
376
|
-
await assert.rejects(
|
|
377
|
-
() =>
|
|
378
|
-
resolveExhaustedMessageMoveFailure(deps, {
|
|
379
|
-
...input,
|
|
380
|
-
getConnection: async () => buildConnection(new Set([101])),
|
|
381
|
-
}),
|
|
382
|
-
/ProvisionedThroughputExceeded/,
|
|
383
|
-
);
|
|
384
|
-
});
|
|
385
|
-
|
|
386
|
-
it("an unreachable server reaches no verdict at all — the probe propagates and the row is untouched", async () => {
|
|
387
|
-
const repos = buildRepositories(pendingMoveRow());
|
|
388
|
-
const { log } = buildLogger();
|
|
389
|
-
const deps: ResolveExhaustedMessageMoveDeps = {
|
|
390
|
-
messageService: repos.messageService,
|
|
391
|
-
threadMessageService: repos.threadMessageService,
|
|
392
|
-
log,
|
|
393
|
-
};
|
|
394
|
-
|
|
395
|
-
await assert.rejects(
|
|
396
|
-
() =>
|
|
397
|
-
resolveExhaustedMessageMoveFailure(deps, {
|
|
398
|
-
...input,
|
|
399
|
-
getConnection: async () => {
|
|
400
|
-
throw new Error("ECONNRESET");
|
|
401
|
-
},
|
|
402
|
-
}),
|
|
403
|
-
/ECONNRESET/,
|
|
404
|
-
);
|
|
405
|
-
|
|
406
|
-
assert.deepEqual(
|
|
407
|
-
repos.messages.get("msg-1"),
|
|
408
|
-
pendingMoveRow(),
|
|
409
|
-
"absence is only ever concluded from an answer the server gave",
|
|
410
|
-
);
|
|
411
|
-
});
|
|
412
|
-
});
|