@remit/mailbox-service 0.0.105 → 0.0.107
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 +1 -1
- package/src/outbox-attachment-content.test.ts +8 -0
- package/src/outbox-attachment-content.ts +5 -0
- package/src/outbox-attachment.test.ts +17 -0
- package/src/outbox-attachment.ts +3 -0
- package/src/outbox-failed-recovery.test.ts +3 -1
- package/src/outbox-queue-settled-row.test.ts +3 -1
- package/src/outbox-queue.test.ts +50 -2
- package/src/outbox-queue.ts +17 -2
package/package.json
CHANGED
|
@@ -70,6 +70,14 @@ describe("loadOutboxAttachmentContents", () => {
|
|
|
70
70
|
);
|
|
71
71
|
});
|
|
72
72
|
|
|
73
|
+
it("gives each file the content-id its reservation handed out", async () => {
|
|
74
|
+
const deps = await setup([row("shot")], ["shot"]);
|
|
75
|
+
|
|
76
|
+
const [content] = await loadOutboxAttachmentContents(deps, OWNER, NOW);
|
|
77
|
+
|
|
78
|
+
assert.equal(content.cid, "shot@remit");
|
|
79
|
+
});
|
|
80
|
+
|
|
73
81
|
it("leaves out a reservation that lapsed without its bytes", async () => {
|
|
74
82
|
const deps = await setup(
|
|
75
83
|
[
|
|
@@ -6,8 +6,12 @@ export interface OutboxAttachmentContent {
|
|
|
6
6
|
filename: string;
|
|
7
7
|
contentType: string;
|
|
8
8
|
content: Buffer;
|
|
9
|
+
cid: string;
|
|
9
10
|
}
|
|
10
11
|
|
|
12
|
+
export const outboxAttachmentContentId = (outboxAttachmentId: string): string =>
|
|
13
|
+
`${outboxAttachmentId}@remit`;
|
|
14
|
+
|
|
11
15
|
/**
|
|
12
16
|
* A file on a message that cannot be put into it. Carries the file's name so
|
|
13
17
|
* whoever settles the message can tell the sender which file to fix.
|
|
@@ -93,6 +97,7 @@ export const loadOutboxAttachmentContents = async (
|
|
|
93
97
|
filename: item.filename,
|
|
94
98
|
contentType: item.contentType,
|
|
95
99
|
content,
|
|
100
|
+
cid: outboxAttachmentContentId(item.outboxAttachmentId),
|
|
96
101
|
};
|
|
97
102
|
}),
|
|
98
103
|
);
|
|
@@ -224,6 +224,23 @@ describe("reserving room on a draft", () => {
|
|
|
224
224
|
assert.ok(row?.storageKey.endsWith(result.reservation.outboxAttachmentId));
|
|
225
225
|
});
|
|
226
226
|
|
|
227
|
+
it("names the content-id a body can refer to the file by", async () => {
|
|
228
|
+
const { service } = build();
|
|
229
|
+
|
|
230
|
+
const result = await mint(service, {
|
|
231
|
+
filename: "shot.png",
|
|
232
|
+
contentType: "image/png",
|
|
233
|
+
sizeBytes: 64,
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
assert.equal(result.outcome, "Minted");
|
|
237
|
+
if (result.outcome !== "Minted") return;
|
|
238
|
+
assert.equal(
|
|
239
|
+
result.reservation.contentId,
|
|
240
|
+
`${result.reservation.outboxAttachmentId}@remit`,
|
|
241
|
+
);
|
|
242
|
+
});
|
|
243
|
+
|
|
227
244
|
it("refuses a declared size over the cap", async () => {
|
|
228
245
|
const { service } = build();
|
|
229
246
|
|
package/src/outbox-attachment.ts
CHANGED
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
import {
|
|
17
17
|
loadOutboxAttachmentContents,
|
|
18
18
|
type OutboxAttachmentContent,
|
|
19
|
+
outboxAttachmentContentId,
|
|
19
20
|
} from "./outbox-attachment-content.js";
|
|
20
21
|
import {
|
|
21
22
|
normalizeAttachmentContentType,
|
|
@@ -45,6 +46,7 @@ export interface OutboxAttachmentReservation {
|
|
|
45
46
|
filename: string;
|
|
46
47
|
contentType: string;
|
|
47
48
|
sizeBytes: number;
|
|
49
|
+
contentId: string;
|
|
48
50
|
uploadUrl: string;
|
|
49
51
|
uploadExpiresAt: number;
|
|
50
52
|
}
|
|
@@ -271,6 +273,7 @@ export class OutboxAttachmentService {
|
|
|
271
273
|
filename: reserved.item.filename,
|
|
272
274
|
contentType: reserved.item.contentType,
|
|
273
275
|
sizeBytes: reserved.item.sizeBytes,
|
|
276
|
+
contentId: outboxAttachmentContentId(reserved.item.outboxAttachmentId),
|
|
274
277
|
uploadUrl: target.uploadUrl,
|
|
275
278
|
uploadExpiresAt: reservationExpiresAt,
|
|
276
279
|
},
|
|
@@ -125,7 +125,9 @@ const createHarness = (
|
|
|
125
125
|
outboxAttachmentService: {
|
|
126
126
|
unfinishedUpload: async () => undefined,
|
|
127
127
|
} as unknown as OutboxAttachmentService,
|
|
128
|
-
accountService: {
|
|
128
|
+
accountService: {
|
|
129
|
+
get: async () => ({ syncedServices: ["Mail"] }),
|
|
130
|
+
} as unknown as IAccountRepository,
|
|
129
131
|
sqsSmtpQueueUrl: "http://localhost/queue",
|
|
130
132
|
sqsClient: {
|
|
131
133
|
send: async (command: { input: { MessageBody: string } }) => {
|
|
@@ -89,7 +89,9 @@ const createHarness = (stored: OutboxMessageItem): Harness => {
|
|
|
89
89
|
},
|
|
90
90
|
unfinishedUpload: async () => undefined,
|
|
91
91
|
} as unknown as OutboxAttachmentService,
|
|
92
|
-
accountService: {
|
|
92
|
+
accountService: {
|
|
93
|
+
get: async () => ({ syncedServices: ["Mail"] }),
|
|
94
|
+
} as unknown as IAccountRepository,
|
|
93
95
|
sqsSmtpQueueUrl: "http://localhost/queue",
|
|
94
96
|
sqsClient: {
|
|
95
97
|
send: async (command: { input: { MessageBody: string } }) => {
|
package/src/outbox-queue.test.ts
CHANGED
|
@@ -18,7 +18,7 @@ import type {
|
|
|
18
18
|
OutboxAttachmentItem,
|
|
19
19
|
OutboxMessageItem,
|
|
20
20
|
} from "@remit/data-ports";
|
|
21
|
-
import { BadRequestError } from "@remit/data-ports/errors";
|
|
21
|
+
import { BadRequestError, MailSyncOffError } from "@remit/data-ports/errors";
|
|
22
22
|
import { OutboxMessageStatus } from "@remit/domain-enums";
|
|
23
23
|
import type { OutboxAttachmentService } from "./outbox-attachment.js";
|
|
24
24
|
import { OutboxQueueService } from "./outbox-queue.js";
|
|
@@ -55,6 +55,7 @@ const createHarness = (
|
|
|
55
55
|
stored: OutboxMessageItem,
|
|
56
56
|
attachments: OutboxAttachmentItem[] = [],
|
|
57
57
|
lateAttachments: OutboxAttachmentItem[] = [],
|
|
58
|
+
syncedServices: string[] = ["Mail"],
|
|
58
59
|
): Harness => {
|
|
59
60
|
let unfinishedChecks = 0;
|
|
60
61
|
const harness: Harness = {
|
|
@@ -98,7 +99,9 @@ const createHarness = (
|
|
|
98
99
|
return held.find((item) => item.state !== "Stored");
|
|
99
100
|
},
|
|
100
101
|
} as unknown as OutboxAttachmentService,
|
|
101
|
-
accountService: {
|
|
102
|
+
accountService: {
|
|
103
|
+
get: async () => ({ syncedServices }),
|
|
104
|
+
} as unknown as IAccountRepository,
|
|
102
105
|
sqsSmtpQueueUrl: "http://localhost/queue",
|
|
103
106
|
sqsClient: {
|
|
104
107
|
send: async (command: { input: { MessageBody: string } }) => {
|
|
@@ -241,3 +244,48 @@ describe("OutboxQueueService and a message with nowhere to go", () => {
|
|
|
241
244
|
assert.equal(harness.enqueued.length, 1);
|
|
242
245
|
});
|
|
243
246
|
});
|
|
247
|
+
|
|
248
|
+
describe("OutboxQueueService and an account with mail off", () => {
|
|
249
|
+
const refusesWithTheFix = (error: unknown): boolean => {
|
|
250
|
+
assert.ok(error instanceof MailSyncOffError);
|
|
251
|
+
assert.equal(error.statusCode, 400);
|
|
252
|
+
assert.equal(error.publicApiError?.code, "mail_sync_off");
|
|
253
|
+
assert.match(error.message, /Turn Mail back on .* in Settings/);
|
|
254
|
+
return true;
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
it("refuses to queue a stored draft and leaves it a draft", async () => {
|
|
258
|
+
const harness = createHarness(draft({}), [], [], ["Calendar"]);
|
|
259
|
+
|
|
260
|
+
await assert.rejects(
|
|
261
|
+
() => harness.service.send(ACCOUNT_CONFIG_ID, OUTBOX_MESSAGE_ID),
|
|
262
|
+
refusesWithTheFix,
|
|
263
|
+
);
|
|
264
|
+
|
|
265
|
+
assert.deepEqual(harness.statusWrites, []);
|
|
266
|
+
assert.deepEqual(harness.enqueued, []);
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
it("refuses a send-immediately create before writing a row", async () => {
|
|
270
|
+
const harness = createHarness(draft({}), [], [], ["Calendar"]);
|
|
271
|
+
|
|
272
|
+
await assert.rejects(
|
|
273
|
+
() =>
|
|
274
|
+
harness.service.createAndSend(
|
|
275
|
+
sendInput({ toAddresses: ["them@example.com"] }),
|
|
276
|
+
),
|
|
277
|
+
refusesWithTheFix,
|
|
278
|
+
);
|
|
279
|
+
|
|
280
|
+
assert.equal(harness.created, 0);
|
|
281
|
+
assert.deepEqual(harness.enqueued, []);
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
it("sends from an account syncing mail and calendar", async () => {
|
|
285
|
+
const harness = createHarness(draft({}), [], [], ["Mail", "Calendar"]);
|
|
286
|
+
|
|
287
|
+
await harness.service.send(ACCOUNT_CONFIG_ID, OUTBOX_MESSAGE_ID);
|
|
288
|
+
|
|
289
|
+
assert.equal(harness.enqueued.length, 1);
|
|
290
|
+
});
|
|
291
|
+
});
|
package/src/outbox-queue.ts
CHANGED
|
@@ -5,8 +5,12 @@ import type {
|
|
|
5
5
|
IOutboxMessageRepository,
|
|
6
6
|
OutboxMessageItem,
|
|
7
7
|
} from "@remit/data-ports";
|
|
8
|
-
import {
|
|
9
|
-
|
|
8
|
+
import {
|
|
9
|
+
BadRequestError,
|
|
10
|
+
ConflictError,
|
|
11
|
+
MailSyncOffError,
|
|
12
|
+
} from "@remit/data-ports/errors";
|
|
13
|
+
import { AccountService, OutboxMessageStatus } from "@remit/domain-enums";
|
|
10
14
|
import { createQueueProducer } from "@remit/sqs-client/producer";
|
|
11
15
|
import type { OutboxAttachmentService } from "./outbox-attachment.js";
|
|
12
16
|
import { isOpenForWork } from "./outbox-status.js";
|
|
@@ -88,6 +92,9 @@ const hasNowhereToGo = (message: {
|
|
|
88
92
|
const NO_RECIPIENT_MESSAGE =
|
|
89
93
|
"This message has nobody to send to. Add a recipient before sending it.";
|
|
90
94
|
|
|
95
|
+
const MAIL_SYNC_OFF_SEND_MESSAGE =
|
|
96
|
+
"Mail is turned off for this account, so it cannot send. Turn Mail back on for this account in Settings to send from it.";
|
|
97
|
+
|
|
91
98
|
const ENQUEUE_FAILED_MESSAGE =
|
|
92
99
|
"This message could not be handed to the outgoing queue, so it was not sent. Send it again.";
|
|
93
100
|
|
|
@@ -250,6 +257,12 @@ export class OutboxQueueService {
|
|
|
250
257
|
return updated;
|
|
251
258
|
};
|
|
252
259
|
|
|
260
|
+
private refuseWithoutMail = async (accountId: string): Promise<void> => {
|
|
261
|
+
const account = await this.accountService.get(accountId);
|
|
262
|
+
if (account.syncedServices.includes(AccountService.Mail)) return;
|
|
263
|
+
throw new MailSyncOffError(MAIL_SYNC_OFF_SEND_MESSAGE, accountId);
|
|
264
|
+
};
|
|
265
|
+
|
|
253
266
|
send = async (
|
|
254
267
|
accountConfigId: string,
|
|
255
268
|
outboxMessageId: string,
|
|
@@ -268,6 +281,7 @@ export class OutboxQueueService {
|
|
|
268
281
|
if (hasNowhereToGo(existing)) {
|
|
269
282
|
throw new BadRequestError(NO_RECIPIENT_MESSAGE);
|
|
270
283
|
}
|
|
284
|
+
await this.refuseWithoutMail(existing.accountId);
|
|
271
285
|
|
|
272
286
|
const unfinished = await this.outboxAttachmentService.unfinishedUpload(
|
|
273
287
|
accountConfigId,
|
|
@@ -339,6 +353,7 @@ export class OutboxQueueService {
|
|
|
339
353
|
if (hasNowhereToGo(input)) {
|
|
340
354
|
throw new BadRequestError(NO_RECIPIENT_MESSAGE);
|
|
341
355
|
}
|
|
356
|
+
await this.refuseWithoutMail(input.accountId);
|
|
342
357
|
|
|
343
358
|
const domain = extractDomain(input.fromAddress);
|
|
344
359
|
const messageIdValue = generateMessageId(domain);
|