@remit/storage-service 0.0.12 → 0.0.13
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/backends/filesystem.d.ts +12 -0
- package/dist/backends/filesystem.js +316 -0
- package/dist/backends/s3.d.ts +3 -0
- package/dist/backends/s3.js +413 -0
- package/dist/filesystem.js +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/outbox-sweep.d.ts +33 -0
- package/dist/outbox-sweep.js +37 -0
- package/dist/outbox-upload-url.d.ts +44 -0
- package/{src/outbox-upload-url.ts → dist/outbox-upload-url.js} +20 -50
- package/dist/s3.d.ts +3 -0
- package/dist/s3.js +28 -0
- package/dist/signed-path.d.ts +14 -0
- package/dist/signed-path.js +46 -0
- package/dist/storage.d.ts +255 -0
- package/dist/storage.js +228 -0
- package/dist/uri.d.ts +9 -0
- package/dist/uri.js +28 -0
- package/package.json +51 -46
- package/src/backends/filesystem.ts +0 -517
- package/src/backends/s3.test.ts +0 -447
- package/src/backends/s3.ts +0 -624
- package/src/index.ts +0 -53
- package/src/outbox-attachment-retrieve.test.ts +0 -56
- package/src/outbox-sweep.test.ts +0 -196
- package/src/outbox-sweep.ts +0 -103
- package/src/outbox-upload-url.test.ts +0 -290
- package/src/s3.ts +0 -36
- package/src/signed-path.ts +0 -86
- package/src/storage.test.ts +0 -646
- package/src/storage.ts +0 -699
- package/src/uri.ts +0 -47
- package/tsconfig.json +0 -8
- /package/{src/filesystem.ts → dist/filesystem.d.ts} +0 -0
package/src/storage.test.ts
DELETED
|
@@ -1,646 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert";
|
|
2
|
-
import { createHash } from "node:crypto";
|
|
3
|
-
import { rm } from "node:fs/promises";
|
|
4
|
-
import { tmpdir } from "node:os";
|
|
5
|
-
import { join } from "node:path";
|
|
6
|
-
import { Readable } from "node:stream";
|
|
7
|
-
import { describe, test } from "node:test";
|
|
8
|
-
import { ContentEncoding, StorageType } from "@remit/domain-enums";
|
|
9
|
-
import { createFilesystemStorageService } from "./backends/filesystem.js";
|
|
10
|
-
import {
|
|
11
|
-
buildBodyPartKey,
|
|
12
|
-
buildDeduplicatedKey,
|
|
13
|
-
buildMessageBodyKey,
|
|
14
|
-
buildOutboxAttachmentKey,
|
|
15
|
-
buildOutboxAttachmentPrefix,
|
|
16
|
-
buildParsedBodyKey,
|
|
17
|
-
computeChecksum,
|
|
18
|
-
createMockStorageService,
|
|
19
|
-
isStorageNotFoundError,
|
|
20
|
-
parseContentStorageKey,
|
|
21
|
-
} from "./storage.js";
|
|
22
|
-
import { buildStorageUri, parseStorageUri } from "./uri.js";
|
|
23
|
-
|
|
24
|
-
describe("parseStorageUri", () => {
|
|
25
|
-
test("parses S3 URIs", () => {
|
|
26
|
-
const result = parseStorageUri("s3://my-bucket/path/to/object");
|
|
27
|
-
assert.strictEqual(result.storageType, StorageType.S3);
|
|
28
|
-
assert.strictEqual(result.storageLocation, "my-bucket");
|
|
29
|
-
assert.strictEqual(result.storageKey, "path/to/object");
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
test("parses file URIs", () => {
|
|
33
|
-
const result = parseStorageUri("file:///var/data/remit/path/to/file");
|
|
34
|
-
assert.strictEqual(result.storageType, StorageType.Filesystem);
|
|
35
|
-
assert.strictEqual(result.storageKey, "/var/data/remit/path/to/file");
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
test("throws for unsupported schemes", () => {
|
|
39
|
-
assert.throws(
|
|
40
|
-
() => parseStorageUri("http://example.com/file"),
|
|
41
|
-
/Unsupported storage URI scheme/,
|
|
42
|
-
);
|
|
43
|
-
});
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
describe("buildStorageUri", () => {
|
|
47
|
-
test("builds S3 URIs", () => {
|
|
48
|
-
const uri = buildStorageUri(StorageType.S3, "my-bucket", "path/to/object");
|
|
49
|
-
assert.strictEqual(uri, "s3://my-bucket/path/to/object");
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
test("builds file URIs", () => {
|
|
53
|
-
const uri = buildStorageUri(
|
|
54
|
-
StorageType.Filesystem,
|
|
55
|
-
"/var/data",
|
|
56
|
-
"path/to/file",
|
|
57
|
-
);
|
|
58
|
-
assert.strictEqual(uri, "file:///var/data/path/to/file");
|
|
59
|
-
});
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
describe("path builders", () => {
|
|
63
|
-
test("buildMessageBodyKey nests accountId under accountConfigId for tenant-scoped CloudFront caching", () => {
|
|
64
|
-
const key = buildMessageBodyKey("cfg1", "acc123", "msg456");
|
|
65
|
-
assert.strictEqual(key, "accounts/cfg1/acc123/messages/msg456/body.eml");
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
test("buildBodyPartKey nests accountId under accountConfigId", () => {
|
|
69
|
-
const key = buildBodyPartKey("cfg1", "acc123", "msg456", "1.2");
|
|
70
|
-
assert.strictEqual(key, "accounts/cfg1/acc123/messages/msg456/parts/1.2");
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
test("buildDeduplicatedKey nests accountId under accountConfigId", () => {
|
|
74
|
-
const hash = "abcdef1234567890";
|
|
75
|
-
const key = buildDeduplicatedKey("cfg1", "acc123", hash);
|
|
76
|
-
assert.strictEqual(key, "accounts/cfg1/acc123/dedup/ab/abcdef1234567890");
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
test("buildParsedBodyKey nests accountId under accountConfigId", () => {
|
|
80
|
-
const key = buildParsedBodyKey("cfg1", "acc123", "msg456");
|
|
81
|
-
assert.strictEqual(
|
|
82
|
-
key,
|
|
83
|
-
"accounts/cfg1/acc123/messages/msg456/parsed.json.gz",
|
|
84
|
-
);
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
test("buildOutboxAttachmentKey hangs the file off its own draft, under the same tenant prefix", () => {
|
|
88
|
-
const key = buildOutboxAttachmentKey("cfg1", "acc123", "draft9", "att7");
|
|
89
|
-
assert.strictEqual(
|
|
90
|
-
key,
|
|
91
|
-
"accounts/cfg1/acc123/outbox/draft9/attachments/att7",
|
|
92
|
-
);
|
|
93
|
-
assert.ok(
|
|
94
|
-
key.startsWith(buildOutboxAttachmentPrefix("cfg1", "acc123", "draft9")),
|
|
95
|
-
);
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
test("parseContentStorageKey does not recognise an outbox attachment key", () => {
|
|
99
|
-
assert.strictEqual(
|
|
100
|
-
parseContentStorageKey(
|
|
101
|
-
buildOutboxAttachmentKey("cfg1", "acc123", "draft9", "att7"),
|
|
102
|
-
),
|
|
103
|
-
null,
|
|
104
|
-
);
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
test("computeChecksum returns SHA-256 hex", () => {
|
|
108
|
-
const content = Buffer.from("test content");
|
|
109
|
-
const expected = createHash("sha256").update(content).digest("hex");
|
|
110
|
-
assert.strictEqual(computeChecksum(content), expected);
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
test("path order is accountConfigId before accountId so /content/accounts/{configId}/* matches at the edge", () => {
|
|
114
|
-
const key = buildMessageBodyKey("CFG", "ACC", "MSG");
|
|
115
|
-
const segments = key.split("/");
|
|
116
|
-
assert.strictEqual(segments[0], "accounts");
|
|
117
|
-
assert.strictEqual(segments[1], "CFG");
|
|
118
|
-
assert.strictEqual(segments[2], "ACC");
|
|
119
|
-
});
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
describe("parseContentStorageKey", () => {
|
|
123
|
-
test("parses accountConfigId/accountId/messageId out of a body key", () => {
|
|
124
|
-
const key = buildMessageBodyKey("cfg1", "acc123", "msg456");
|
|
125
|
-
assert.deepEqual(parseContentStorageKey(key), {
|
|
126
|
-
accountConfigId: "cfg1",
|
|
127
|
-
accountId: "acc123",
|
|
128
|
-
messageId: "msg456",
|
|
129
|
-
});
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
test("parses a part key the same way as a body key", () => {
|
|
133
|
-
const key = buildBodyPartKey("cfg1", "acc123", "msg456", "1.2");
|
|
134
|
-
assert.deepEqual(parseContentStorageKey(key), {
|
|
135
|
-
accountConfigId: "cfg1",
|
|
136
|
-
accountId: "acc123",
|
|
137
|
-
messageId: "msg456",
|
|
138
|
-
});
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
test("returns null for a key that doesn't match the accounts/.../messages/... layout", () => {
|
|
142
|
-
assert.strictEqual(parseContentStorageKey("nope/whatever"), null);
|
|
143
|
-
});
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
describe("isStorageNotFoundError", () => {
|
|
147
|
-
test("matches S3 NoSuchKey via name", () => {
|
|
148
|
-
assert.equal(
|
|
149
|
-
isStorageNotFoundError(
|
|
150
|
-
Object.assign(new Error("missing"), { name: "NoSuchKey" }),
|
|
151
|
-
),
|
|
152
|
-
true,
|
|
153
|
-
);
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
test("matches S3 NoSuchKey via Code", () => {
|
|
157
|
-
assert.equal(
|
|
158
|
-
isStorageNotFoundError({ Code: "NoSuchKey", message: "missing" }),
|
|
159
|
-
true,
|
|
160
|
-
);
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
test("matches filesystem ENOENT", () => {
|
|
164
|
-
assert.equal(
|
|
165
|
-
isStorageNotFoundError(
|
|
166
|
-
Object.assign(new Error("nope"), { code: "ENOENT" }),
|
|
167
|
-
),
|
|
168
|
-
true,
|
|
169
|
-
);
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
test("does not match generic errors", () => {
|
|
173
|
-
assert.equal(isStorageNotFoundError(new Error("boom")), false);
|
|
174
|
-
assert.equal(isStorageNotFoundError(null), false);
|
|
175
|
-
assert.equal(isStorageNotFoundError("oops"), false);
|
|
176
|
-
});
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
describe("createMockStorageService", () => {
|
|
180
|
-
test("storeMessageBody stores and retrieves content with nested accountConfigId/accountId path", async () => {
|
|
181
|
-
const storage = createMockStorageService();
|
|
182
|
-
const content = Buffer.from("Hello, world!");
|
|
183
|
-
|
|
184
|
-
const ref = await storage.storeMessageBody({
|
|
185
|
-
accountConfigId: "cfg1",
|
|
186
|
-
accountId: "acc123",
|
|
187
|
-
messageId: "msg456",
|
|
188
|
-
content,
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
assert.strictEqual(
|
|
192
|
-
ref.storageKey,
|
|
193
|
-
"accounts/cfg1/acc123/messages/msg456/body.eml",
|
|
194
|
-
);
|
|
195
|
-
assert.strictEqual(ref.sizeBytes, content.length);
|
|
196
|
-
assert.strictEqual(ref.contentEncoding, ContentEncoding.None);
|
|
197
|
-
|
|
198
|
-
const retrieved = await storage.retrieve(ref.uri);
|
|
199
|
-
assert.deepStrictEqual(retrieved, content);
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
test("storeBodyPart stores with part path", async () => {
|
|
203
|
-
const storage = createMockStorageService();
|
|
204
|
-
const content = Buffer.from("attachment content");
|
|
205
|
-
|
|
206
|
-
const ref = await storage.storeBodyPart({
|
|
207
|
-
accountConfigId: "cfg1",
|
|
208
|
-
accountId: "acc123",
|
|
209
|
-
messageId: "msg456",
|
|
210
|
-
partPath: "1.2",
|
|
211
|
-
content,
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
assert.strictEqual(
|
|
215
|
-
ref.storageKey,
|
|
216
|
-
"accounts/cfg1/acc123/messages/msg456/parts/1.2",
|
|
217
|
-
);
|
|
218
|
-
|
|
219
|
-
const retrieved = await storage.retrieve(ref.uri);
|
|
220
|
-
assert.deepStrictEqual(retrieved, content);
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
test("storeDeduplicated uses content hash", async () => {
|
|
224
|
-
const storage = createMockStorageService();
|
|
225
|
-
const content = Buffer.from("deduplicate me");
|
|
226
|
-
const hash = computeChecksum(content);
|
|
227
|
-
|
|
228
|
-
const ref = await storage.storeDeduplicated({
|
|
229
|
-
accountConfigId: "cfg1",
|
|
230
|
-
accountId: "acc123",
|
|
231
|
-
content,
|
|
232
|
-
});
|
|
233
|
-
|
|
234
|
-
assert.strictEqual(
|
|
235
|
-
ref.storageKey,
|
|
236
|
-
`accounts/cfg1/acc123/dedup/${hash.slice(0, 2)}/${hash}`,
|
|
237
|
-
);
|
|
238
|
-
assert.strictEqual(ref.checksumSha256, hash);
|
|
239
|
-
});
|
|
240
|
-
|
|
241
|
-
test("checks existence", async () => {
|
|
242
|
-
const storage = createMockStorageService();
|
|
243
|
-
const content = Buffer.from("test");
|
|
244
|
-
|
|
245
|
-
const ref = await storage.storeMessageBody({
|
|
246
|
-
accountConfigId: "cfg1",
|
|
247
|
-
accountId: "acc123",
|
|
248
|
-
messageId: "msg456",
|
|
249
|
-
content,
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
assert.strictEqual(await storage.exists(ref.uri), true);
|
|
253
|
-
assert.strictEqual(await storage.exists("mock://does-not-exist"), false);
|
|
254
|
-
});
|
|
255
|
-
|
|
256
|
-
test("deletes content", async () => {
|
|
257
|
-
const storage = createMockStorageService();
|
|
258
|
-
const content = Buffer.from("to delete");
|
|
259
|
-
|
|
260
|
-
const ref = await storage.storeMessageBody({
|
|
261
|
-
accountConfigId: "cfg1",
|
|
262
|
-
accountId: "acc123",
|
|
263
|
-
messageId: "msg456",
|
|
264
|
-
content,
|
|
265
|
-
});
|
|
266
|
-
assert.strictEqual(await storage.exists(ref.uri), true);
|
|
267
|
-
|
|
268
|
-
await storage.delete(ref.uri);
|
|
269
|
-
assert.strictEqual(await storage.exists(ref.uri), false);
|
|
270
|
-
});
|
|
271
|
-
|
|
272
|
-
test("storeParsedBody / retrieveParsedBody round-trip", async () => {
|
|
273
|
-
const storage = createMockStorageService();
|
|
274
|
-
const parsed = {
|
|
275
|
-
text: "hello",
|
|
276
|
-
html: "<p>hello</p>",
|
|
277
|
-
attachments: [
|
|
278
|
-
{
|
|
279
|
-
filename: "a.pdf",
|
|
280
|
-
contentType: "application/pdf",
|
|
281
|
-
contentDisposition: "attachment",
|
|
282
|
-
contentId: null,
|
|
283
|
-
size: 1234,
|
|
284
|
-
},
|
|
285
|
-
],
|
|
286
|
-
};
|
|
287
|
-
|
|
288
|
-
const ref = await storage.storeParsedBody({
|
|
289
|
-
accountConfigId: "cfg1",
|
|
290
|
-
accountId: "acc123",
|
|
291
|
-
messageId: "msg456",
|
|
292
|
-
parsed,
|
|
293
|
-
});
|
|
294
|
-
|
|
295
|
-
assert.strictEqual(
|
|
296
|
-
ref.storageKey,
|
|
297
|
-
"accounts/cfg1/acc123/messages/msg456/parsed.json.gz",
|
|
298
|
-
);
|
|
299
|
-
|
|
300
|
-
const retrieved = await storage.retrieveParsedBody(
|
|
301
|
-
"cfg1",
|
|
302
|
-
"acc123",
|
|
303
|
-
"msg456",
|
|
304
|
-
);
|
|
305
|
-
assert.deepStrictEqual(retrieved, parsed);
|
|
306
|
-
});
|
|
307
|
-
|
|
308
|
-
test("retrieveParsedBody returns null on miss", async () => {
|
|
309
|
-
const storage = createMockStorageService();
|
|
310
|
-
const result = await storage.retrieveParsedBody("cfg1", "acc123", "nope");
|
|
311
|
-
assert.strictEqual(result, null);
|
|
312
|
-
});
|
|
313
|
-
|
|
314
|
-
test("retrieveBodyPart round-trips stored content and returns null on miss", async () => {
|
|
315
|
-
const storage = createMockStorageService();
|
|
316
|
-
const content = Buffer.from("attachment bytes");
|
|
317
|
-
|
|
318
|
-
await storage.storeBodyPart({
|
|
319
|
-
accountConfigId: "cfg1",
|
|
320
|
-
accountId: "acc123",
|
|
321
|
-
messageId: "msg456",
|
|
322
|
-
partPath: "1.2",
|
|
323
|
-
content,
|
|
324
|
-
});
|
|
325
|
-
|
|
326
|
-
const retrieved = await storage.retrieveBodyPart(
|
|
327
|
-
"cfg1",
|
|
328
|
-
"acc123",
|
|
329
|
-
"msg456",
|
|
330
|
-
"1.2",
|
|
331
|
-
);
|
|
332
|
-
assert.deepStrictEqual(retrieved, content);
|
|
333
|
-
|
|
334
|
-
const miss = await storage.retrieveBodyPart(
|
|
335
|
-
"cfg1",
|
|
336
|
-
"acc123",
|
|
337
|
-
"msg456",
|
|
338
|
-
"9",
|
|
339
|
-
);
|
|
340
|
-
assert.strictEqual(miss, null);
|
|
341
|
-
});
|
|
342
|
-
});
|
|
343
|
-
|
|
344
|
-
describe("createFilesystemStorageService", () => {
|
|
345
|
-
const testBasePath = join(tmpdir(), `remit-storage-test-${Date.now()}`);
|
|
346
|
-
|
|
347
|
-
test("storeMessageBody stores and retrieves content", async () => {
|
|
348
|
-
const storage = createFilesystemStorageService(testBasePath);
|
|
349
|
-
const content = Buffer.from("Filesystem test content");
|
|
350
|
-
|
|
351
|
-
const ref = await storage.storeMessageBody({
|
|
352
|
-
accountConfigId: "cfg1",
|
|
353
|
-
accountId: "acc123",
|
|
354
|
-
messageId: "msg456",
|
|
355
|
-
content,
|
|
356
|
-
});
|
|
357
|
-
|
|
358
|
-
assert.strictEqual(ref.storageType, StorageType.Filesystem);
|
|
359
|
-
assert.strictEqual(ref.storageLocation, testBasePath);
|
|
360
|
-
assert.strictEqual(
|
|
361
|
-
ref.storageKey,
|
|
362
|
-
"accounts/cfg1/acc123/messages/msg456/body.eml",
|
|
363
|
-
);
|
|
364
|
-
assert.ok(ref.uri.startsWith("file://"));
|
|
365
|
-
|
|
366
|
-
const retrieved = await storage.retrieve(ref.uri);
|
|
367
|
-
assert.deepStrictEqual(retrieved, content);
|
|
368
|
-
});
|
|
369
|
-
|
|
370
|
-
test("storeMessageBodyStream streams content to disk and round-trips via retrieve", async () => {
|
|
371
|
-
const storage = createFilesystemStorageService(testBasePath);
|
|
372
|
-
const content = Buffer.from("streamed filesystem body");
|
|
373
|
-
|
|
374
|
-
const ref = await storage.storeMessageBodyStream({
|
|
375
|
-
accountConfigId: "cfg1",
|
|
376
|
-
accountId: "acc123",
|
|
377
|
-
messageId: "msg-stream",
|
|
378
|
-
content: Readable.from(content),
|
|
379
|
-
});
|
|
380
|
-
|
|
381
|
-
assert.strictEqual(ref.storageType, StorageType.Filesystem);
|
|
382
|
-
assert.strictEqual(ref.contentEncoding, ContentEncoding.Gzip);
|
|
383
|
-
assert.strictEqual(ref.checksumSha256, computeChecksum(content));
|
|
384
|
-
|
|
385
|
-
const retrieved = await storage.retrieve(ref.uri);
|
|
386
|
-
assert.deepStrictEqual(retrieved, content);
|
|
387
|
-
});
|
|
388
|
-
|
|
389
|
-
test("storeBodyPart stores with part path", async () => {
|
|
390
|
-
const storage = createFilesystemStorageService(testBasePath);
|
|
391
|
-
const content = Buffer.from("attachment content");
|
|
392
|
-
|
|
393
|
-
const ref = await storage.storeBodyPart({
|
|
394
|
-
accountConfigId: "cfg1",
|
|
395
|
-
accountId: "acc123",
|
|
396
|
-
messageId: "msg456",
|
|
397
|
-
partPath: "1.2",
|
|
398
|
-
content,
|
|
399
|
-
});
|
|
400
|
-
|
|
401
|
-
assert.strictEqual(
|
|
402
|
-
ref.storageKey,
|
|
403
|
-
"accounts/cfg1/acc123/messages/msg456/parts/1.2",
|
|
404
|
-
);
|
|
405
|
-
|
|
406
|
-
const retrieved = await storage.retrieve(ref.uri);
|
|
407
|
-
assert.deepStrictEqual(retrieved, content);
|
|
408
|
-
});
|
|
409
|
-
|
|
410
|
-
test("storeDeduplicated uses content hash", async () => {
|
|
411
|
-
const storage = createFilesystemStorageService(testBasePath);
|
|
412
|
-
const content = Buffer.from("dedupe fs content");
|
|
413
|
-
const hash = computeChecksum(content);
|
|
414
|
-
|
|
415
|
-
const ref = await storage.storeDeduplicated({
|
|
416
|
-
accountConfigId: "cfg1",
|
|
417
|
-
accountId: "acc123",
|
|
418
|
-
content,
|
|
419
|
-
});
|
|
420
|
-
|
|
421
|
-
assert.strictEqual(
|
|
422
|
-
ref.storageKey,
|
|
423
|
-
`accounts/cfg1/acc123/dedup/${hash.slice(0, 2)}/${hash}`,
|
|
424
|
-
);
|
|
425
|
-
assert.strictEqual(ref.checksumSha256, hash);
|
|
426
|
-
|
|
427
|
-
const retrieved = await storage.retrieve(ref.uri);
|
|
428
|
-
assert.deepStrictEqual(retrieved, content);
|
|
429
|
-
});
|
|
430
|
-
|
|
431
|
-
test("checks existence", async () => {
|
|
432
|
-
const storage = createFilesystemStorageService(testBasePath);
|
|
433
|
-
const content = Buffer.from("exists test");
|
|
434
|
-
|
|
435
|
-
const ref = await storage.storeMessageBody({
|
|
436
|
-
accountConfigId: "cfg1",
|
|
437
|
-
accountId: "acc123",
|
|
438
|
-
messageId: "msg789",
|
|
439
|
-
content,
|
|
440
|
-
});
|
|
441
|
-
|
|
442
|
-
assert.strictEqual(await storage.exists(ref.uri), true);
|
|
443
|
-
assert.strictEqual(await storage.exists("file:///nonexistent/path"), false);
|
|
444
|
-
});
|
|
445
|
-
|
|
446
|
-
test("deletes content", async () => {
|
|
447
|
-
const storage = createFilesystemStorageService(testBasePath);
|
|
448
|
-
const content = Buffer.from("to be deleted");
|
|
449
|
-
|
|
450
|
-
const ref = await storage.storeMessageBody({
|
|
451
|
-
accountConfigId: "cfg1",
|
|
452
|
-
accountId: "acc123",
|
|
453
|
-
messageId: "msg-delete",
|
|
454
|
-
content,
|
|
455
|
-
});
|
|
456
|
-
assert.strictEqual(await storage.exists(ref.uri), true);
|
|
457
|
-
|
|
458
|
-
await storage.delete(ref.uri);
|
|
459
|
-
assert.strictEqual(await storage.exists(ref.uri), false);
|
|
460
|
-
});
|
|
461
|
-
|
|
462
|
-
test("storeParsedBody / retrieveParsedBody round-trip with gzip", async () => {
|
|
463
|
-
const storage = createFilesystemStorageService(testBasePath);
|
|
464
|
-
const parsed = {
|
|
465
|
-
text: "hello fs",
|
|
466
|
-
html: "<p>hello fs</p>",
|
|
467
|
-
attachments: [],
|
|
468
|
-
};
|
|
469
|
-
|
|
470
|
-
const ref = await storage.storeParsedBody({
|
|
471
|
-
accountConfigId: "cfg1",
|
|
472
|
-
accountId: "acc123",
|
|
473
|
-
messageId: "msg-parsed",
|
|
474
|
-
parsed,
|
|
475
|
-
});
|
|
476
|
-
|
|
477
|
-
assert.strictEqual(
|
|
478
|
-
ref.storageKey,
|
|
479
|
-
"accounts/cfg1/acc123/messages/msg-parsed/parsed.json.gz",
|
|
480
|
-
);
|
|
481
|
-
|
|
482
|
-
const retrieved = await storage.retrieveParsedBody(
|
|
483
|
-
"cfg1",
|
|
484
|
-
"acc123",
|
|
485
|
-
"msg-parsed",
|
|
486
|
-
);
|
|
487
|
-
assert.deepStrictEqual(retrieved, parsed);
|
|
488
|
-
});
|
|
489
|
-
|
|
490
|
-
test("retrieveParsedBody returns null on miss (ENOENT)", async () => {
|
|
491
|
-
const storage = createFilesystemStorageService(testBasePath);
|
|
492
|
-
const result = await storage.retrieveParsedBody(
|
|
493
|
-
"cfg1",
|
|
494
|
-
"acc123",
|
|
495
|
-
"missing",
|
|
496
|
-
);
|
|
497
|
-
assert.strictEqual(result, null);
|
|
498
|
-
});
|
|
499
|
-
|
|
500
|
-
test("retrieveBodyPart round-trips gzipped content and returns null on miss (ENOENT)", async () => {
|
|
501
|
-
const storage = createFilesystemStorageService(testBasePath);
|
|
502
|
-
const content = Buffer.from("fs attachment bytes");
|
|
503
|
-
|
|
504
|
-
await storage.storeBodyPart({
|
|
505
|
-
accountConfigId: "cfg1",
|
|
506
|
-
accountId: "acc123",
|
|
507
|
-
messageId: "msg-part-fs",
|
|
508
|
-
partPath: "1.2",
|
|
509
|
-
content,
|
|
510
|
-
});
|
|
511
|
-
|
|
512
|
-
const retrieved = await storage.retrieveBodyPart(
|
|
513
|
-
"cfg1",
|
|
514
|
-
"acc123",
|
|
515
|
-
"msg-part-fs",
|
|
516
|
-
"1.2",
|
|
517
|
-
);
|
|
518
|
-
assert.deepStrictEqual(retrieved, content);
|
|
519
|
-
|
|
520
|
-
const miss = await storage.retrieveBodyPart(
|
|
521
|
-
"cfg1",
|
|
522
|
-
"acc123",
|
|
523
|
-
"msg-part-fs",
|
|
524
|
-
"nope",
|
|
525
|
-
);
|
|
526
|
-
assert.strictEqual(miss, null);
|
|
527
|
-
});
|
|
528
|
-
|
|
529
|
-
test("storeOutboxAttachment writes the bytes uncompressed so the size on disk is the size the cap counts", async () => {
|
|
530
|
-
const storage = createFilesystemStorageService(testBasePath);
|
|
531
|
-
// Highly compressible: gzip would make the stored size disagree with the
|
|
532
|
-
// decoded size the per-message cap is expressed in.
|
|
533
|
-
const content = Buffer.alloc(4096, 0x61);
|
|
534
|
-
|
|
535
|
-
const ref = await storage.storeOutboxAttachment({
|
|
536
|
-
accountConfigId: "cfg1",
|
|
537
|
-
accountId: "acc123",
|
|
538
|
-
outboxMessageId: "draft1",
|
|
539
|
-
outboxAttachmentId: "att1",
|
|
540
|
-
content,
|
|
541
|
-
});
|
|
542
|
-
|
|
543
|
-
assert.strictEqual(ref.contentEncoding, ContentEncoding.None);
|
|
544
|
-
assert.strictEqual(ref.sizeBytes, content.length);
|
|
545
|
-
assert.strictEqual(ref.checksumSha256, computeChecksum(content));
|
|
546
|
-
assert.deepStrictEqual(await storage.retrieve(ref.uri), content);
|
|
547
|
-
|
|
548
|
-
const [item] = await storage.listOutboxAttachments(
|
|
549
|
-
"cfg1",
|
|
550
|
-
"acc123",
|
|
551
|
-
"draft1",
|
|
552
|
-
);
|
|
553
|
-
assert.strictEqual(item.sizeBytes, content.length);
|
|
554
|
-
});
|
|
555
|
-
|
|
556
|
-
test("listOutboxAttachments returns only the named draft, and every object in it", async () => {
|
|
557
|
-
const storage = createFilesystemStorageService(testBasePath);
|
|
558
|
-
for (const id of ["a", "b", "c"]) {
|
|
559
|
-
await storage.storeOutboxAttachment({
|
|
560
|
-
accountConfigId: "cfg1",
|
|
561
|
-
accountId: "acc123",
|
|
562
|
-
outboxMessageId: "draft-many",
|
|
563
|
-
outboxAttachmentId: id,
|
|
564
|
-
content: Buffer.from(`file-${id}`),
|
|
565
|
-
});
|
|
566
|
-
}
|
|
567
|
-
await storage.storeOutboxAttachment({
|
|
568
|
-
accountConfigId: "cfg1",
|
|
569
|
-
accountId: "acc123",
|
|
570
|
-
outboxMessageId: "draft-other",
|
|
571
|
-
outboxAttachmentId: "d",
|
|
572
|
-
content: Buffer.from("elsewhere"),
|
|
573
|
-
});
|
|
574
|
-
|
|
575
|
-
const all = await storage.listOutboxAttachments(
|
|
576
|
-
"cfg1",
|
|
577
|
-
"acc123",
|
|
578
|
-
"draft-many",
|
|
579
|
-
);
|
|
580
|
-
assert.deepStrictEqual(all.map((item) => item.outboxAttachmentId).sort(), [
|
|
581
|
-
"a",
|
|
582
|
-
"b",
|
|
583
|
-
"c",
|
|
584
|
-
]);
|
|
585
|
-
});
|
|
586
|
-
|
|
587
|
-
test("listOutboxAttachments returns an empty array for a draft that has none", async () => {
|
|
588
|
-
const storage = createFilesystemStorageService(testBasePath);
|
|
589
|
-
assert.deepStrictEqual(
|
|
590
|
-
await storage.listOutboxAttachments("cfg1", "acc123", "draft-empty"),
|
|
591
|
-
[],
|
|
592
|
-
);
|
|
593
|
-
});
|
|
594
|
-
|
|
595
|
-
test("deleting a draft's attachments empties the prefix and the ledger with it", async () => {
|
|
596
|
-
const storage = createFilesystemStorageService(testBasePath);
|
|
597
|
-
await storage.storeOutboxAttachment({
|
|
598
|
-
accountConfigId: "cfg1",
|
|
599
|
-
accountId: "acc123",
|
|
600
|
-
outboxMessageId: "draft-gone",
|
|
601
|
-
outboxAttachmentId: "att1",
|
|
602
|
-
content: Buffer.alloc(4),
|
|
603
|
-
});
|
|
604
|
-
await storage.deleteOutboxAttachments("cfg1", "acc123", "draft-gone");
|
|
605
|
-
|
|
606
|
-
assert.deepStrictEqual(
|
|
607
|
-
await storage.listOutboxAttachments("cfg1", "acc123", "draft-gone"),
|
|
608
|
-
[],
|
|
609
|
-
);
|
|
610
|
-
});
|
|
611
|
-
|
|
612
|
-
test("listOutboxDraftsWithAttachments finds prefixes whose row may be long gone", async () => {
|
|
613
|
-
const storage = createFilesystemStorageService(testBasePath);
|
|
614
|
-
for (const draft of ["draft-x", "draft-y"]) {
|
|
615
|
-
await storage.storeOutboxAttachment({
|
|
616
|
-
accountConfigId: "cfg-sweep",
|
|
617
|
-
accountId: "acc-sweep",
|
|
618
|
-
outboxMessageId: draft,
|
|
619
|
-
outboxAttachmentId: "att1",
|
|
620
|
-
content: Buffer.alloc(4),
|
|
621
|
-
});
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
assert.deepStrictEqual(
|
|
625
|
-
(
|
|
626
|
-
await storage.listOutboxDraftsWithAttachments("cfg-sweep", "acc-sweep")
|
|
627
|
-
).sort(),
|
|
628
|
-
["draft-x", "draft-y"],
|
|
629
|
-
);
|
|
630
|
-
});
|
|
631
|
-
|
|
632
|
-
test("deleting attachments on a draft that never had any is quiet", async () => {
|
|
633
|
-
const storage = createFilesystemStorageService(testBasePath);
|
|
634
|
-
await storage.deleteOutboxAttachments("cfg1", "acc123", "draft-never");
|
|
635
|
-
await storage.deleteOutboxAttachment(
|
|
636
|
-
"cfg1",
|
|
637
|
-
"acc123",
|
|
638
|
-
"draft-never",
|
|
639
|
-
"att1",
|
|
640
|
-
);
|
|
641
|
-
});
|
|
642
|
-
|
|
643
|
-
test("cleanup test directory", async () => {
|
|
644
|
-
await rm(testBasePath, { recursive: true, force: true });
|
|
645
|
-
});
|
|
646
|
-
});
|