@remit/mailbox-service 0.0.71 → 0.0.72
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
CHANGED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A row the SMTP worker settled has to be a row the user can still act on.
|
|
3
|
+
*
|
|
4
|
+
* `sending` is not: `send` takes draft, failed and blocked, and `deleteDraft`
|
|
5
|
+
* those three plus `unfiled`, so a send that never reached the server left a
|
|
6
|
+
* row that 409s on both buttons forever (#951). The worker now settles that
|
|
7
|
+
* row instead of dead-lettering it, and these are the two moves the settled
|
|
8
|
+
* status has to accept for the settle to be worth anything.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import assert from "node:assert/strict";
|
|
12
|
+
import { describe, it } from "node:test";
|
|
13
|
+
import type {
|
|
14
|
+
IAccountRepository,
|
|
15
|
+
IOutboxMessageRepository,
|
|
16
|
+
OutboxMessageItem,
|
|
17
|
+
} from "@remit/data-ports";
|
|
18
|
+
import { OutboxMessageStatus } from "@remit/domain-enums";
|
|
19
|
+
import type { OutboxAttachmentService } from "./outbox-attachment.js";
|
|
20
|
+
import { OutboxQueueService } from "./outbox-queue.js";
|
|
21
|
+
|
|
22
|
+
const ACCOUNT_CONFIG_ID = "cfg-1";
|
|
23
|
+
const ACCOUNT_ID = "acc-1";
|
|
24
|
+
const OUTBOX_MESSAGE_ID = "ob-1";
|
|
25
|
+
|
|
26
|
+
const row = (status: OutboxMessageItem["status"]): OutboxMessageItem =>
|
|
27
|
+
({
|
|
28
|
+
outboxMessageId: OUTBOX_MESSAGE_ID,
|
|
29
|
+
accountId: ACCOUNT_ID,
|
|
30
|
+
accountConfigId: ACCOUNT_CONFIG_ID,
|
|
31
|
+
fromAddress: "me@example.com",
|
|
32
|
+
toAddresses: ["them@example.com"],
|
|
33
|
+
ccAddresses: [],
|
|
34
|
+
bccAddresses: [],
|
|
35
|
+
references: [],
|
|
36
|
+
messageIdValue: "<m1@example.com>",
|
|
37
|
+
lastError: "SMTP connection failed: ECONNREFUSED",
|
|
38
|
+
status,
|
|
39
|
+
createdAt: 0,
|
|
40
|
+
updatedAt: 0,
|
|
41
|
+
}) as unknown as OutboxMessageItem;
|
|
42
|
+
|
|
43
|
+
interface Harness {
|
|
44
|
+
service: OutboxQueueService;
|
|
45
|
+
enqueued: string[];
|
|
46
|
+
statusWrites: string[];
|
|
47
|
+
deleted: string[];
|
|
48
|
+
discarded: string[];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const createHarness = (stored: OutboxMessageItem): Harness => {
|
|
52
|
+
const harness: Harness = {
|
|
53
|
+
service: undefined as unknown as OutboxQueueService,
|
|
54
|
+
enqueued: [],
|
|
55
|
+
statusWrites: [],
|
|
56
|
+
deleted: [],
|
|
57
|
+
discarded: [],
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const outboxMessageService = {
|
|
61
|
+
get: async () => stored,
|
|
62
|
+
updateStatus: async (
|
|
63
|
+
_configId: string,
|
|
64
|
+
_id: string,
|
|
65
|
+
status: OutboxMessageItem["status"],
|
|
66
|
+
) => {
|
|
67
|
+
harness.statusWrites.push(status);
|
|
68
|
+
return { ...stored, status };
|
|
69
|
+
},
|
|
70
|
+
delete: async (_configId: string, id: string) => {
|
|
71
|
+
harness.deleted.push(id);
|
|
72
|
+
},
|
|
73
|
+
} as unknown as IOutboxMessageRepository;
|
|
74
|
+
|
|
75
|
+
harness.service = new OutboxQueueService({
|
|
76
|
+
outboxMessageService,
|
|
77
|
+
outboxAttachmentService: {
|
|
78
|
+
discardAll: async (_configId: string, _accountId: string, id: string) => {
|
|
79
|
+
harness.discarded.push(id);
|
|
80
|
+
},
|
|
81
|
+
} as unknown as OutboxAttachmentService,
|
|
82
|
+
accountService: {} as unknown as IAccountRepository,
|
|
83
|
+
sqsSmtpQueueUrl: "http://localhost/queue",
|
|
84
|
+
sqsClient: {
|
|
85
|
+
send: async (command: { input: { MessageBody: string } }) => {
|
|
86
|
+
harness.enqueued.push(command.input.MessageBody);
|
|
87
|
+
return {};
|
|
88
|
+
},
|
|
89
|
+
} as never,
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
return harness;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
describe("a row the worker settled at `failed`", () => {
|
|
96
|
+
it("is sendable again — Retry is the whole point of settling there", async () => {
|
|
97
|
+
const harness = createHarness(row(OutboxMessageStatus.failed));
|
|
98
|
+
|
|
99
|
+
await harness.service.send(ACCOUNT_CONFIG_ID, OUTBOX_MESSAGE_ID);
|
|
100
|
+
|
|
101
|
+
assert.deepEqual(harness.statusWrites, [OutboxMessageStatus.queued]);
|
|
102
|
+
assert.equal(harness.enqueued.length, 1);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it("is discardable — the other way out of the Outbox", async () => {
|
|
106
|
+
const harness = createHarness(row(OutboxMessageStatus.failed));
|
|
107
|
+
|
|
108
|
+
await harness.service.deleteDraft(ACCOUNT_CONFIG_ID, OUTBOX_MESSAGE_ID);
|
|
109
|
+
|
|
110
|
+
assert.deepEqual(harness.deleted, [OUTBOX_MESSAGE_ID]);
|
|
111
|
+
assert.deepEqual(harness.discarded, [OUTBOX_MESSAGE_ID]);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
describe("a row the worker settled at `unfiled`", () => {
|
|
116
|
+
it("is discardable but never sendable — the server may already hold it", async () => {
|
|
117
|
+
const harness = createHarness(row(OutboxMessageStatus.unfiled));
|
|
118
|
+
|
|
119
|
+
await assert.rejects(
|
|
120
|
+
() => harness.service.send(ACCOUNT_CONFIG_ID, OUTBOX_MESSAGE_ID),
|
|
121
|
+
/cannot be sent again/,
|
|
122
|
+
);
|
|
123
|
+
assert.deepEqual(harness.enqueued, [], "nothing reached the SMTP queue");
|
|
124
|
+
|
|
125
|
+
await harness.service.deleteDraft(ACCOUNT_CONFIG_ID, OUTBOX_MESSAGE_ID);
|
|
126
|
+
assert.deepEqual(harness.deleted, [OUTBOX_MESSAGE_ID]);
|
|
127
|
+
});
|
|
128
|
+
});
|