@remit/imap-worker 0.0.88 → 0.0.90
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +6 -6
- package/src/cli.ts +3 -2
- package/src/credential-less-sync-fence.test.ts +5 -9
- package/src/e2e-processor-shim.ts +2 -2
- package/src/emit.test.ts +25 -42
- package/src/emit.ts +94 -119
- package/src/events.ts +1 -19
- package/src/handlers/delete-account-objects.test.ts +27 -18
- package/src/handlers/delete-account-objects.ts +5 -12
- package/src/handlers/message-copy.sqlite.test.ts +3 -3
- package/src/handlers/message-copy.ts +5 -2
- package/src/handlers/message-delete.sqlite.test.ts +1 -1
- package/src/handlers/message-delete.ts +7 -4
- package/src/handlers/message-move.sqlite.test.ts +1 -1
- package/src/handlers/message-move.ts +9 -6
- package/src/handlers/placement-move-push.ts +9 -6
- package/src/handlers/placement-push.sqlite.test.ts +3 -2
- package/src/handlers/probe-message-id.sqlite.test.ts +2 -4
- package/src/handlers/sync-mailboxes.ts +8 -4
- package/src/handlers/sync-message-body.sqlite.test.ts +2 -2
- package/src/handlers/sync-message-body.ts +15 -18
- package/src/handlers/sync-messages.sqlite.test.ts +14 -2
- package/src/handlers/sync-messages.ts +20 -8
- package/src/index.ts +3 -1
- package/src/mail-sync-selection.sqlite.test.ts +7 -13
- package/src/pending-sync.sqlite.test.ts +26 -28
- package/src/processor.ts +10 -16
- package/src/queues.ts +40 -0
- package/src/scheduler/attachment-sweep.sqlite.test.ts +2 -2
- package/src/scheduler/half-deleted-accounts.sqlite.test.ts +20 -27
- package/src/scheduler/handler.ts +35 -28
- package/src/scheduler/run-tick.sqlite.test.ts +6 -14
- package/src/scheduler/run-tick.ts +10 -14
- package/src/scheduler/runner.ts +15 -12
- package/src/stranded-placement.sqlite.test.ts +9 -5
- package/src/test-helpers/test-world.ts +40 -13
- package/src/test-helpers/worker-queues.ts +11 -0
- package/src/processor.test.ts +0 -58
|
@@ -84,7 +84,7 @@ const placementMover = (w: World): PlacementMoveService =>
|
|
|
84
84
|
addressService: w.client.address,
|
|
85
85
|
mailboxSpecialUseService: w.client.mailboxSpecialUse,
|
|
86
86
|
mailboxService: w.client.mailbox,
|
|
87
|
-
|
|
87
|
+
queue: w.client.queues.messageManagement,
|
|
88
88
|
});
|
|
89
89
|
|
|
90
90
|
const placementOf = async (a: Arranged) => {
|
|
@@ -131,7 +131,8 @@ const queueRefuses = (w: World): void => {
|
|
|
131
131
|
};
|
|
132
132
|
|
|
133
133
|
const queueTakes = (w: World): void => {
|
|
134
|
-
w.queue.answer(async () => ({
|
|
134
|
+
w.queue.answer(async () => ({}));
|
|
135
|
+
w.queue.refuse([]);
|
|
135
136
|
};
|
|
136
137
|
|
|
137
138
|
const redriveLater = async (
|
|
@@ -255,9 +256,7 @@ describe("a move whose event dies leaves no row moving", () => {
|
|
|
255
256
|
|
|
256
257
|
it("hands back a move to Trash whose batch entry the queue refused", async () => {
|
|
257
258
|
const a = await arrange();
|
|
258
|
-
a.w.queue.
|
|
259
|
-
Failed: [{ Id: "0", Code: "InvalidParameterValue", SenderFault: true }],
|
|
260
|
-
}));
|
|
259
|
+
a.w.queue.refuse([0]);
|
|
261
260
|
|
|
262
261
|
await assert.rejects(
|
|
263
262
|
a.w.client.messageMove.deleteMessages(
|
|
@@ -448,6 +447,7 @@ describe("the reconcile re-drives a move or delete already stranded", () => {
|
|
|
448
447
|
await handleMessageMove(
|
|
449
448
|
stamped<MessageMoveEvent>(move),
|
|
450
449
|
a.w.log,
|
|
450
|
+
a.w.queues,
|
|
451
451
|
1,
|
|
452
452
|
a.w.scope,
|
|
453
453
|
);
|
|
@@ -470,6 +470,7 @@ describe("the reconcile re-drives a move or delete already stranded", () => {
|
|
|
470
470
|
await handlePlacementMovePush(
|
|
471
471
|
stamped<PlacementMovePushEvent>(push),
|
|
472
472
|
a.w.log,
|
|
473
|
+
a.w.queues,
|
|
473
474
|
1,
|
|
474
475
|
a.w.scope,
|
|
475
476
|
);
|
|
@@ -501,6 +502,7 @@ describe("the reconcile re-drives a move or delete already stranded", () => {
|
|
|
501
502
|
await handleMessageMove(
|
|
502
503
|
stamped<MessageMoveEvent>(emitted[0]),
|
|
503
504
|
a.w.log,
|
|
505
|
+
a.w.queues,
|
|
504
506
|
1,
|
|
505
507
|
a.w.scope,
|
|
506
508
|
);
|
|
@@ -521,6 +523,7 @@ describe("the reconcile re-drives a move or delete already stranded", () => {
|
|
|
521
523
|
messageId: a.message.messageId,
|
|
522
524
|
}),
|
|
523
525
|
a.w.log,
|
|
526
|
+
a.w.queues,
|
|
524
527
|
1,
|
|
525
528
|
a.w.scope,
|
|
526
529
|
);
|
|
@@ -646,6 +649,7 @@ describe("a uid-0 copy whose event is gone", () => {
|
|
|
646
649
|
await handleMessageCopy(
|
|
647
650
|
stamped<MessageCopyEvent>(copy),
|
|
648
651
|
a.w.log,
|
|
652
|
+
a.w.queues,
|
|
649
653
|
1,
|
|
650
654
|
a.w.scope,
|
|
651
655
|
);
|
|
@@ -5,10 +5,11 @@ import { tmpdir } from "node:os";
|
|
|
5
5
|
import { join } from "node:path";
|
|
6
6
|
import {
|
|
7
7
|
_resetForTest,
|
|
8
|
-
getClient,
|
|
9
8
|
type RemitClient,
|
|
10
9
|
setClient,
|
|
11
10
|
} from "@remit/backend/client";
|
|
11
|
+
import { createRemitClient } from "@remit/backend/create-remit-client";
|
|
12
|
+
import { buildSharedDeps } from "@remit/backend/shared-deps";
|
|
12
13
|
import type {
|
|
13
14
|
AccountItem,
|
|
14
15
|
MailboxItem,
|
|
@@ -16,11 +17,20 @@ import type {
|
|
|
16
17
|
MessageItem,
|
|
17
18
|
OutboxMessageItem,
|
|
18
19
|
} from "@remit/data-ports";
|
|
20
|
+
import {
|
|
21
|
+
type RecordingQueue,
|
|
22
|
+
recordingQueue,
|
|
23
|
+
} from "@remit/data-ports/recording-queue";
|
|
19
24
|
import {
|
|
20
25
|
AccountAuthType,
|
|
21
26
|
ConnectionState,
|
|
22
27
|
MailboxCursorState,
|
|
23
28
|
} from "@remit/domain-enums";
|
|
29
|
+
import {
|
|
30
|
+
createSqliteRepositories,
|
|
31
|
+
openSqliteDatabases,
|
|
32
|
+
sqliteDatabasePathsFromEnv,
|
|
33
|
+
} from "@remit/drizzle-service";
|
|
24
34
|
import { createShippedSqliteFiles } from "@remit/drizzle-service/test-sqlite";
|
|
25
35
|
import type { Logger } from "@remit/logger";
|
|
26
36
|
import type { ManagedConnectionFactory } from "@remit/mailbox-service";
|
|
@@ -28,13 +38,11 @@ import {
|
|
|
28
38
|
eml,
|
|
29
39
|
FakeImapServer,
|
|
30
40
|
} from "@remit/mailbox-service/test-helpers/fake-imap";
|
|
31
|
-
import {
|
|
32
|
-
captureQueue,
|
|
33
|
-
type QueueCapture,
|
|
34
|
-
} from "@remit/mailbox-service/test-helpers/test-queue";
|
|
35
41
|
import { serializeEncryptedPayload } from "@remit/secrets-service";
|
|
36
42
|
import type { createConnectionScopeWithCredentials } from "../connection-scope.js";
|
|
37
43
|
import { syncMessages } from "../handlers/sync-messages.js";
|
|
44
|
+
import type { WorkerQueues } from "../queues.js";
|
|
45
|
+
import { workerQueuesOn } from "./worker-queues.js";
|
|
38
46
|
|
|
39
47
|
export interface LogLine {
|
|
40
48
|
level: string;
|
|
@@ -84,8 +92,8 @@ export interface SeededMessage {
|
|
|
84
92
|
}
|
|
85
93
|
|
|
86
94
|
/**
|
|
87
|
-
* One SQLite store per test file, over the shipped migrations, composed
|
|
88
|
-
* backend's
|
|
95
|
+
* One SQLite store per test file, over the shipped migrations, composed from
|
|
96
|
+
* the backend's SQLite repositories over filesystem storage — every
|
|
89
97
|
* repository and service a handler reaches is the one a self-host process
|
|
90
98
|
* runs. Each `world()` is a fresh account on it, with its own in-memory mail
|
|
91
99
|
* server at the IMAP port, and a message reaches the store the way mail does:
|
|
@@ -103,12 +111,29 @@ export const openStore = async (): Promise<{
|
|
|
103
111
|
process.env.BETTER_AUTH_URL = "https://mail.example.com";
|
|
104
112
|
process.env.BETTER_AUTH_SECRET = "a-signing-secret-of-at-least-32-characters";
|
|
105
113
|
_resetForTest();
|
|
106
|
-
const
|
|
114
|
+
const repositories = createSqliteRepositories(
|
|
115
|
+
await openSqliteDatabases(sqliteDatabasePathsFromEnv()),
|
|
116
|
+
);
|
|
117
|
+
const queuesOn = (queue: RecordingQueue) => ({
|
|
118
|
+
sync: queue.fifo,
|
|
119
|
+
accountFanout: queue,
|
|
120
|
+
smtp: queue,
|
|
121
|
+
messageManagement: queue,
|
|
122
|
+
body: queue,
|
|
123
|
+
});
|
|
124
|
+
const shared = buildSharedDeps(queuesOn(recordingQueue()));
|
|
125
|
+
const clientOn = (queue: RecordingQueue): RemitClient =>
|
|
126
|
+
createRemitClient({ repositories, ...shared, queues: queuesOn(queue) });
|
|
127
|
+
const client = clientOn(recordingQueue());
|
|
107
128
|
setClient(client);
|
|
108
129
|
return {
|
|
109
130
|
client,
|
|
110
|
-
world: (accountConfigId = `cfg-${randomUUID()}`) =>
|
|
111
|
-
|
|
131
|
+
world: (accountConfigId = `cfg-${randomUUID()}`) => {
|
|
132
|
+
const queue = recordingQueue();
|
|
133
|
+
const worldClient = clientOn(queue);
|
|
134
|
+
setClient(worldClient);
|
|
135
|
+
return createWorld(worldClient, queue, accountConfigId);
|
|
136
|
+
},
|
|
112
137
|
close: () => {
|
|
113
138
|
_resetForTest();
|
|
114
139
|
rmSync(directory, { recursive: true, force: true });
|
|
@@ -135,7 +160,8 @@ export interface World {
|
|
|
135
160
|
) => Promise<Array<{ mailboxId: string; uid?: number; isDeleted: boolean }>>;
|
|
136
161
|
queued: () => Array<Record<string, unknown>>;
|
|
137
162
|
refuseSends: (error: Error | undefined) => void;
|
|
138
|
-
queue:
|
|
163
|
+
queue: RecordingQueue;
|
|
164
|
+
queues: WorkerQueues;
|
|
139
165
|
draft: (
|
|
140
166
|
attachment?: Buffer,
|
|
141
167
|
) => Promise<{ outbox: OutboxMessageItem; attachmentId?: string }>;
|
|
@@ -145,10 +171,9 @@ export interface World {
|
|
|
145
171
|
|
|
146
172
|
const createWorld = async (
|
|
147
173
|
client: RemitClient,
|
|
174
|
+
queue: RecordingQueue,
|
|
148
175
|
accountConfigId: string,
|
|
149
176
|
): Promise<World> => {
|
|
150
|
-
const queue = captureQueue();
|
|
151
|
-
|
|
152
177
|
const imap = new FakeImapServer();
|
|
153
178
|
const logs: LogLine[] = [];
|
|
154
179
|
const password = await client.secrets.encrypt("app-password");
|
|
@@ -268,6 +293,7 @@ const createWorld = async (
|
|
|
268
293
|
mailboxId: mailbox.mailboxId,
|
|
269
294
|
},
|
|
270
295
|
capturingLogger([]),
|
|
296
|
+
workerQueuesOn(queue),
|
|
271
297
|
connectionFactory,
|
|
272
298
|
);
|
|
273
299
|
imap.connections = baseline.connections;
|
|
@@ -391,6 +417,7 @@ const createWorld = async (
|
|
|
391
417
|
queued,
|
|
392
418
|
refuseSends,
|
|
393
419
|
queue,
|
|
420
|
+
queues: workerQueuesOn(queue),
|
|
394
421
|
draft,
|
|
395
422
|
scope,
|
|
396
423
|
connectionFactory,
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { RecordingQueue } from "@remit/data-ports/recording-queue";
|
|
2
|
+
import type { WorkerQueues } from "../queues.js";
|
|
3
|
+
|
|
4
|
+
export const workerQueuesOn = (queue: RecordingQueue): WorkerQueues => ({
|
|
5
|
+
mailboxes: queue.fifo,
|
|
6
|
+
messages: queue.fifo,
|
|
7
|
+
flags: queue.fifo,
|
|
8
|
+
body: queue,
|
|
9
|
+
mailboxManagement: queue,
|
|
10
|
+
messageManagement: queue,
|
|
11
|
+
});
|
package/src/processor.test.ts
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import { describe, it } from "node:test";
|
|
3
|
-
import type { Logger } from "@remit/logger";
|
|
4
|
-
import type { ImapWorkerStopEvent } from "./events.js";
|
|
5
|
-
import { processEvent } from "./processor.js";
|
|
6
|
-
|
|
7
|
-
interface CapturedLogEntry {
|
|
8
|
-
args: unknown[];
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
const createCapturingLogger = (): {
|
|
12
|
-
log: Logger;
|
|
13
|
-
infoCalls: CapturedLogEntry[];
|
|
14
|
-
} => {
|
|
15
|
-
const infoCalls: CapturedLogEntry[] = [];
|
|
16
|
-
const noop = () => {};
|
|
17
|
-
const log = {
|
|
18
|
-
info: (...args: unknown[]) => infoCalls.push({ args }),
|
|
19
|
-
warn: noop,
|
|
20
|
-
error: noop,
|
|
21
|
-
debug: noop,
|
|
22
|
-
fatal: noop,
|
|
23
|
-
trace: noop,
|
|
24
|
-
child: () => log,
|
|
25
|
-
} as unknown as Logger;
|
|
26
|
-
return { log, infoCalls };
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
describe("processEvent — IMAP_WORKER_STOP", () => {
|
|
30
|
-
it("returns undefined and logs the stop signal — tombstone fence on the account row already halts work", async () => {
|
|
31
|
-
const event: ImapWorkerStopEvent = {
|
|
32
|
-
type: "IMAP_WORKER_STOP",
|
|
33
|
-
accountConfigId: "acfg_alice_replay_safe_test_id",
|
|
34
|
-
accountId: "acct_alice_replay_safe_test_id",
|
|
35
|
-
};
|
|
36
|
-
const { log, infoCalls } = createCapturingLogger();
|
|
37
|
-
|
|
38
|
-
const result = await processEvent(event, log);
|
|
39
|
-
|
|
40
|
-
assert.equal(result, undefined);
|
|
41
|
-
const matched = infoCalls.find((c) => {
|
|
42
|
-
const [meta, msg] = c.args;
|
|
43
|
-
return (
|
|
44
|
-
typeof msg === "string" &&
|
|
45
|
-
msg === "Imap worker stop signal received" &&
|
|
46
|
-
typeof meta === "object" &&
|
|
47
|
-
meta !== null &&
|
|
48
|
-
(meta as { accountConfigId?: string }).accountConfigId ===
|
|
49
|
-
event.accountConfigId &&
|
|
50
|
-
(meta as { accountId?: string }).accountId === event.accountId
|
|
51
|
-
);
|
|
52
|
-
});
|
|
53
|
-
assert.ok(
|
|
54
|
-
matched,
|
|
55
|
-
"processor must log the cascade-contract stop signal with both ids",
|
|
56
|
-
);
|
|
57
|
-
});
|
|
58
|
-
});
|