@syncular/server 0.0.4-26 → 0.0.4-33
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/blobs/index.d.ts +0 -2
- package/dist/blobs/index.d.ts.map +1 -1
- package/dist/blobs/index.js +0 -2
- package/dist/blobs/index.js.map +1 -1
- package/dist/notify.js +2 -2
- package/dist/notify.js.map +1 -1
- package/dist/proxy/oplog.d.ts +1 -1
- package/dist/proxy/oplog.d.ts.map +1 -1
- package/dist/proxy/oplog.js +2 -8
- package/dist/proxy/oplog.js.map +1 -1
- package/dist/pull.d.ts.map +1 -1
- package/dist/pull.js +10 -58
- package/dist/pull.js.map +1 -1
- package/dist/snapshot-chunks/db-metadata.d.ts.map +1 -1
- package/dist/snapshot-chunks/db-metadata.js +6 -9
- package/dist/snapshot-chunks/db-metadata.js.map +1 -1
- package/package.json +2 -2
- package/src/blobs/index.ts +0 -2
- package/src/notify.ts +2 -2
- package/src/proxy/oplog.ts +2 -10
- package/src/pull.ts +15 -81
- package/src/snapshot-chunks/db-metadata.ts +10 -9
- package/dist/blobs/adapters/filesystem.d.ts +0 -31
- package/dist/blobs/adapters/filesystem.d.ts.map +0 -1
- package/dist/blobs/adapters/filesystem.js +0 -140
- package/dist/blobs/adapters/filesystem.js.map +0 -1
- package/dist/blobs/adapters/s3.d.ts +0 -83
- package/dist/blobs/adapters/s3.d.ts.map +0 -1
- package/dist/blobs/adapters/s3.js +0 -219
- package/dist/blobs/adapters/s3.js.map +0 -1
- package/src/blobs/adapters/filesystem.test.ts +0 -132
- package/src/blobs/adapters/filesystem.ts +0 -189
- package/src/blobs/adapters/s3.test.ts +0 -522
- package/src/blobs/adapters/s3.ts +0 -324
|
@@ -1,522 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from 'bun:test';
|
|
2
|
-
import {
|
|
3
|
-
createS3BlobStorageAdapter,
|
|
4
|
-
type GetSignedUrlFn,
|
|
5
|
-
type S3ClientLike,
|
|
6
|
-
type S3Commands,
|
|
7
|
-
} from './s3';
|
|
8
|
-
|
|
9
|
-
// ---------------------------------------------------------------------------
|
|
10
|
-
// Helpers
|
|
11
|
-
// ---------------------------------------------------------------------------
|
|
12
|
-
|
|
13
|
-
const TEST_BUCKET = 'test-bucket';
|
|
14
|
-
const TEST_HASH =
|
|
15
|
-
'sha256:abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890';
|
|
16
|
-
const TEST_HEX =
|
|
17
|
-
'abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890';
|
|
18
|
-
|
|
19
|
-
/** Compute the expected base64 of a hex hash (used in checksum headers). */
|
|
20
|
-
function hexToBase64(hex: string): string {
|
|
21
|
-
return Buffer.from(hex, 'hex').toString('base64');
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
// ---------------------------------------------------------------------------
|
|
25
|
-
// Tag types so the mock client can identify which command was sent
|
|
26
|
-
// ---------------------------------------------------------------------------
|
|
27
|
-
|
|
28
|
-
const PUT_TAG = Symbol('PutObjectCommand');
|
|
29
|
-
const GET_TAG = Symbol('GetObjectCommand');
|
|
30
|
-
const HEAD_TAG = Symbol('HeadObjectCommand');
|
|
31
|
-
const DELETE_TAG = Symbol('DeleteObjectCommand');
|
|
32
|
-
|
|
33
|
-
interface MockCommand {
|
|
34
|
-
__tag: symbol;
|
|
35
|
-
input: Record<string, unknown>;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function createMockCommands(): S3Commands {
|
|
39
|
-
return {
|
|
40
|
-
PutObjectCommand: class {
|
|
41
|
-
__tag = PUT_TAG;
|
|
42
|
-
input: Record<string, unknown>;
|
|
43
|
-
constructor(input: Record<string, unknown>) {
|
|
44
|
-
this.input = input;
|
|
45
|
-
}
|
|
46
|
-
} as unknown as S3Commands['PutObjectCommand'],
|
|
47
|
-
|
|
48
|
-
GetObjectCommand: class {
|
|
49
|
-
__tag = GET_TAG;
|
|
50
|
-
input: Record<string, unknown>;
|
|
51
|
-
constructor(input: Record<string, unknown>) {
|
|
52
|
-
this.input = input;
|
|
53
|
-
}
|
|
54
|
-
} as unknown as S3Commands['GetObjectCommand'],
|
|
55
|
-
|
|
56
|
-
HeadObjectCommand: class {
|
|
57
|
-
__tag = HEAD_TAG;
|
|
58
|
-
input: Record<string, unknown>;
|
|
59
|
-
constructor(input: Record<string, unknown>) {
|
|
60
|
-
this.input = input;
|
|
61
|
-
}
|
|
62
|
-
} as unknown as S3Commands['HeadObjectCommand'],
|
|
63
|
-
|
|
64
|
-
DeleteObjectCommand: class {
|
|
65
|
-
__tag = DELETE_TAG;
|
|
66
|
-
input: Record<string, unknown>;
|
|
67
|
-
constructor(input: Record<string, unknown>) {
|
|
68
|
-
this.input = input;
|
|
69
|
-
}
|
|
70
|
-
} as unknown as S3Commands['DeleteObjectCommand'],
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// ---------------------------------------------------------------------------
|
|
75
|
-
// Mock S3 client
|
|
76
|
-
// ---------------------------------------------------------------------------
|
|
77
|
-
|
|
78
|
-
interface SendCall {
|
|
79
|
-
tag: symbol;
|
|
80
|
-
input: Record<string, unknown>;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function createMockS3Client(options?: {
|
|
84
|
-
/** Value returned by send(). Can be a function of the command tag. */
|
|
85
|
-
response?:
|
|
86
|
-
| Record<string, unknown>
|
|
87
|
-
| ((tag: symbol) => Record<string, unknown>);
|
|
88
|
-
/** When true, send() rejects with a NotFound-style error. */
|
|
89
|
-
notFound?: boolean;
|
|
90
|
-
}) {
|
|
91
|
-
const calls: SendCall[] = [];
|
|
92
|
-
|
|
93
|
-
const client: S3ClientLike = {
|
|
94
|
-
async send(command: unknown) {
|
|
95
|
-
const cmd = command as MockCommand;
|
|
96
|
-
calls.push({ tag: cmd.__tag, input: cmd.input });
|
|
97
|
-
|
|
98
|
-
if (options?.notFound) {
|
|
99
|
-
const err = new Error('NotFound') as Error & {
|
|
100
|
-
name: string;
|
|
101
|
-
$metadata: { httpStatusCode: number };
|
|
102
|
-
};
|
|
103
|
-
err.name = 'NotFound';
|
|
104
|
-
err.$metadata = { httpStatusCode: 404 };
|
|
105
|
-
throw err;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
if (typeof options?.response === 'function') {
|
|
109
|
-
return options.response(cmd.__tag);
|
|
110
|
-
}
|
|
111
|
-
return options?.response ?? {};
|
|
112
|
-
},
|
|
113
|
-
};
|
|
114
|
-
|
|
115
|
-
return { client, calls };
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// ---------------------------------------------------------------------------
|
|
119
|
-
// Mock getSignedUrl
|
|
120
|
-
// ---------------------------------------------------------------------------
|
|
121
|
-
|
|
122
|
-
interface SignedUrlCall {
|
|
123
|
-
command: MockCommand;
|
|
124
|
-
options: { expiresIn: number };
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
function createMockGetSignedUrl(): {
|
|
128
|
-
fn: GetSignedUrlFn;
|
|
129
|
-
calls: SignedUrlCall[];
|
|
130
|
-
} {
|
|
131
|
-
const calls: SignedUrlCall[] = [];
|
|
132
|
-
const fn: GetSignedUrlFn = async (_client, command, options) => {
|
|
133
|
-
const cmd = command as MockCommand;
|
|
134
|
-
calls.push({ command: cmd, options });
|
|
135
|
-
return `https://s3.example.com/presigned/${cmd.input.Key as string}`;
|
|
136
|
-
};
|
|
137
|
-
return { fn, calls };
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// ---------------------------------------------------------------------------
|
|
141
|
-
// Tests
|
|
142
|
-
// ---------------------------------------------------------------------------
|
|
143
|
-
|
|
144
|
-
describe('createS3BlobStorageAdapter', () => {
|
|
145
|
-
// ---- signUpload ----
|
|
146
|
-
describe('signUpload', () => {
|
|
147
|
-
test('returns presigned URL with correct method and headers', async () => {
|
|
148
|
-
const commands = createMockCommands();
|
|
149
|
-
const { client } = createMockS3Client();
|
|
150
|
-
const { fn: getSignedUrl, calls: signCalls } = createMockGetSignedUrl();
|
|
151
|
-
|
|
152
|
-
const adapter = createS3BlobStorageAdapter({
|
|
153
|
-
client,
|
|
154
|
-
bucket: TEST_BUCKET,
|
|
155
|
-
commands,
|
|
156
|
-
getSignedUrl,
|
|
157
|
-
requireChecksum: false,
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
const result = await adapter.signUpload({
|
|
161
|
-
hash: TEST_HASH,
|
|
162
|
-
size: 1024,
|
|
163
|
-
mimeType: 'image/png',
|
|
164
|
-
expiresIn: 300,
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
expect(result.method).toBe('PUT');
|
|
168
|
-
expect(result.url).toContain(TEST_HEX);
|
|
169
|
-
expect(result.headers).toBeDefined();
|
|
170
|
-
expect(result.headers!['Content-Type']).toBe('image/png');
|
|
171
|
-
expect(result.headers!['Content-Length']).toBe('1024');
|
|
172
|
-
// No checksum header when requireChecksum=false
|
|
173
|
-
expect(result.headers!['x-amz-checksum-sha256']).toBeUndefined();
|
|
174
|
-
|
|
175
|
-
// Verify the presigner was called with the right expiresIn
|
|
176
|
-
expect(signCalls).toHaveLength(1);
|
|
177
|
-
expect(signCalls[0]!.options.expiresIn).toBe(300);
|
|
178
|
-
|
|
179
|
-
// Verify PutObjectCommand was constructed with correct bucket/key
|
|
180
|
-
const cmdInput = signCalls[0]!.command.input;
|
|
181
|
-
expect(cmdInput.Bucket).toBe(TEST_BUCKET);
|
|
182
|
-
expect(cmdInput.Key).toBe(TEST_HEX);
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
test('includes checksum header when requireChecksum=true', async () => {
|
|
186
|
-
const commands = createMockCommands();
|
|
187
|
-
const { client } = createMockS3Client();
|
|
188
|
-
const { fn: getSignedUrl } = createMockGetSignedUrl();
|
|
189
|
-
|
|
190
|
-
const adapter = createS3BlobStorageAdapter({
|
|
191
|
-
client,
|
|
192
|
-
bucket: TEST_BUCKET,
|
|
193
|
-
commands,
|
|
194
|
-
getSignedUrl,
|
|
195
|
-
requireChecksum: true,
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
const result = await adapter.signUpload({
|
|
199
|
-
hash: TEST_HASH,
|
|
200
|
-
size: 512,
|
|
201
|
-
mimeType: 'application/octet-stream',
|
|
202
|
-
expiresIn: 60,
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
const expectedBase64 = hexToBase64(TEST_HEX);
|
|
206
|
-
expect(result.headers!['x-amz-checksum-sha256']).toBe(expectedBase64);
|
|
207
|
-
});
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
// ---- signDownload ----
|
|
211
|
-
describe('signDownload', () => {
|
|
212
|
-
test('returns presigned URL', async () => {
|
|
213
|
-
const commands = createMockCommands();
|
|
214
|
-
const { client } = createMockS3Client();
|
|
215
|
-
const { fn: getSignedUrl, calls: signCalls } = createMockGetSignedUrl();
|
|
216
|
-
|
|
217
|
-
const adapter = createS3BlobStorageAdapter({
|
|
218
|
-
client,
|
|
219
|
-
bucket: TEST_BUCKET,
|
|
220
|
-
commands,
|
|
221
|
-
getSignedUrl,
|
|
222
|
-
});
|
|
223
|
-
|
|
224
|
-
const url = await adapter.signDownload({
|
|
225
|
-
hash: TEST_HASH,
|
|
226
|
-
expiresIn: 120,
|
|
227
|
-
});
|
|
228
|
-
|
|
229
|
-
expect(url).toContain(TEST_HEX);
|
|
230
|
-
expect(signCalls).toHaveLength(1);
|
|
231
|
-
expect(signCalls[0]!.options.expiresIn).toBe(120);
|
|
232
|
-
|
|
233
|
-
// Verify GetObjectCommand was used
|
|
234
|
-
expect(signCalls[0]!.command.__tag).toBe(GET_TAG);
|
|
235
|
-
expect(signCalls[0]!.command.input.Bucket).toBe(TEST_BUCKET);
|
|
236
|
-
expect(signCalls[0]!.command.input.Key).toBe(TEST_HEX);
|
|
237
|
-
});
|
|
238
|
-
});
|
|
239
|
-
|
|
240
|
-
// ---- exists ----
|
|
241
|
-
describe('exists', () => {
|
|
242
|
-
test('returns true when HeadObject succeeds', async () => {
|
|
243
|
-
const commands = createMockCommands();
|
|
244
|
-
const { client } = createMockS3Client();
|
|
245
|
-
const { fn: getSignedUrl } = createMockGetSignedUrl();
|
|
246
|
-
|
|
247
|
-
const adapter = createS3BlobStorageAdapter({
|
|
248
|
-
client,
|
|
249
|
-
bucket: TEST_BUCKET,
|
|
250
|
-
commands,
|
|
251
|
-
getSignedUrl,
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
expect(await adapter.exists(TEST_HASH)).toBe(true);
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
test('returns false on NotFound error', async () => {
|
|
258
|
-
const commands = createMockCommands();
|
|
259
|
-
const { client } = createMockS3Client({ notFound: true });
|
|
260
|
-
const { fn: getSignedUrl } = createMockGetSignedUrl();
|
|
261
|
-
|
|
262
|
-
const adapter = createS3BlobStorageAdapter({
|
|
263
|
-
client,
|
|
264
|
-
bucket: TEST_BUCKET,
|
|
265
|
-
commands,
|
|
266
|
-
getSignedUrl,
|
|
267
|
-
});
|
|
268
|
-
|
|
269
|
-
expect(await adapter.exists(TEST_HASH)).toBe(false);
|
|
270
|
-
});
|
|
271
|
-
});
|
|
272
|
-
|
|
273
|
-
// ---- delete ----
|
|
274
|
-
describe('delete', () => {
|
|
275
|
-
test('calls DeleteObjectCommand with correct bucket and key', async () => {
|
|
276
|
-
const commands = createMockCommands();
|
|
277
|
-
const { client, calls } = createMockS3Client();
|
|
278
|
-
const { fn: getSignedUrl } = createMockGetSignedUrl();
|
|
279
|
-
|
|
280
|
-
const adapter = createS3BlobStorageAdapter({
|
|
281
|
-
client,
|
|
282
|
-
bucket: TEST_BUCKET,
|
|
283
|
-
commands,
|
|
284
|
-
getSignedUrl,
|
|
285
|
-
});
|
|
286
|
-
|
|
287
|
-
await adapter.delete(TEST_HASH);
|
|
288
|
-
|
|
289
|
-
expect(calls).toHaveLength(1);
|
|
290
|
-
expect(calls[0]!.tag).toBe(DELETE_TAG);
|
|
291
|
-
expect(calls[0]!.input.Bucket).toBe(TEST_BUCKET);
|
|
292
|
-
expect(calls[0]!.input.Key).toBe(TEST_HEX);
|
|
293
|
-
});
|
|
294
|
-
});
|
|
295
|
-
|
|
296
|
-
// ---- getMetadata ----
|
|
297
|
-
describe('getMetadata', () => {
|
|
298
|
-
test('returns size and mimeType from HeadObject', async () => {
|
|
299
|
-
const commands = createMockCommands();
|
|
300
|
-
const { client } = createMockS3Client({
|
|
301
|
-
response: { ContentLength: 2048, ContentType: 'image/jpeg' },
|
|
302
|
-
});
|
|
303
|
-
const { fn: getSignedUrl } = createMockGetSignedUrl();
|
|
304
|
-
|
|
305
|
-
const adapter = createS3BlobStorageAdapter({
|
|
306
|
-
client,
|
|
307
|
-
bucket: TEST_BUCKET,
|
|
308
|
-
commands,
|
|
309
|
-
getSignedUrl,
|
|
310
|
-
});
|
|
311
|
-
|
|
312
|
-
const meta = await adapter.getMetadata!(TEST_HASH);
|
|
313
|
-
expect(meta).toEqual({ size: 2048, mimeType: 'image/jpeg' });
|
|
314
|
-
});
|
|
315
|
-
|
|
316
|
-
test('returns null on NotFound', async () => {
|
|
317
|
-
const commands = createMockCommands();
|
|
318
|
-
const { client } = createMockS3Client({ notFound: true });
|
|
319
|
-
const { fn: getSignedUrl } = createMockGetSignedUrl();
|
|
320
|
-
|
|
321
|
-
const adapter = createS3BlobStorageAdapter({
|
|
322
|
-
client,
|
|
323
|
-
bucket: TEST_BUCKET,
|
|
324
|
-
commands,
|
|
325
|
-
getSignedUrl,
|
|
326
|
-
});
|
|
327
|
-
|
|
328
|
-
const meta = await adapter.getMetadata!(TEST_HASH);
|
|
329
|
-
expect(meta).toBeNull();
|
|
330
|
-
});
|
|
331
|
-
});
|
|
332
|
-
|
|
333
|
-
// ---- put ----
|
|
334
|
-
describe('put', () => {
|
|
335
|
-
test('calls PutObjectCommand with Body and correct key', async () => {
|
|
336
|
-
const commands = createMockCommands();
|
|
337
|
-
const { client, calls } = createMockS3Client();
|
|
338
|
-
const { fn: getSignedUrl } = createMockGetSignedUrl();
|
|
339
|
-
|
|
340
|
-
const adapter = createS3BlobStorageAdapter({
|
|
341
|
-
client,
|
|
342
|
-
bucket: TEST_BUCKET,
|
|
343
|
-
commands,
|
|
344
|
-
getSignedUrl,
|
|
345
|
-
});
|
|
346
|
-
|
|
347
|
-
const data = new Uint8Array([1, 2, 3, 4]);
|
|
348
|
-
await adapter.put!(TEST_HASH, data);
|
|
349
|
-
|
|
350
|
-
expect(calls).toHaveLength(1);
|
|
351
|
-
expect(calls[0]!.tag).toBe(PUT_TAG);
|
|
352
|
-
expect(calls[0]!.input.Bucket).toBe(TEST_BUCKET);
|
|
353
|
-
expect(calls[0]!.input.Key).toBe(TEST_HEX);
|
|
354
|
-
expect(calls[0]!.input.Body).toBe(data);
|
|
355
|
-
expect(calls[0]!.input.ContentLength).toBe(4);
|
|
356
|
-
expect(calls[0]!.input.ContentType).toBe('application/octet-stream');
|
|
357
|
-
});
|
|
358
|
-
});
|
|
359
|
-
|
|
360
|
-
// ---- get ----
|
|
361
|
-
describe('get', () => {
|
|
362
|
-
test('returns Uint8Array from transformToByteArray', async () => {
|
|
363
|
-
const expectedBytes = new Uint8Array([10, 20, 30]);
|
|
364
|
-
const commands = createMockCommands();
|
|
365
|
-
const { client } = createMockS3Client({
|
|
366
|
-
response: {
|
|
367
|
-
Body: {
|
|
368
|
-
transformToByteArray: async () => expectedBytes,
|
|
369
|
-
},
|
|
370
|
-
},
|
|
371
|
-
});
|
|
372
|
-
const { fn: getSignedUrl } = createMockGetSignedUrl();
|
|
373
|
-
|
|
374
|
-
const adapter = createS3BlobStorageAdapter({
|
|
375
|
-
client,
|
|
376
|
-
bucket: TEST_BUCKET,
|
|
377
|
-
commands,
|
|
378
|
-
getSignedUrl,
|
|
379
|
-
});
|
|
380
|
-
|
|
381
|
-
const result = await adapter.get!(TEST_HASH);
|
|
382
|
-
expect(result).toBe(expectedBytes);
|
|
383
|
-
});
|
|
384
|
-
|
|
385
|
-
test('returns null on NotFound', async () => {
|
|
386
|
-
const commands = createMockCommands();
|
|
387
|
-
const { client } = createMockS3Client({ notFound: true });
|
|
388
|
-
const { fn: getSignedUrl } = createMockGetSignedUrl();
|
|
389
|
-
|
|
390
|
-
const adapter = createS3BlobStorageAdapter({
|
|
391
|
-
client,
|
|
392
|
-
bucket: TEST_BUCKET,
|
|
393
|
-
commands,
|
|
394
|
-
getSignedUrl,
|
|
395
|
-
});
|
|
396
|
-
|
|
397
|
-
const result = await adapter.get!(TEST_HASH);
|
|
398
|
-
expect(result).toBeNull();
|
|
399
|
-
});
|
|
400
|
-
});
|
|
401
|
-
|
|
402
|
-
// ---- getStream ----
|
|
403
|
-
describe('getStream', () => {
|
|
404
|
-
test('returns ReadableStream from transformToWebStream', async () => {
|
|
405
|
-
const mockStream = new ReadableStream<Uint8Array>({
|
|
406
|
-
start(controller) {
|
|
407
|
-
controller.enqueue(new Uint8Array([5, 6, 7]));
|
|
408
|
-
controller.close();
|
|
409
|
-
},
|
|
410
|
-
});
|
|
411
|
-
|
|
412
|
-
const commands = createMockCommands();
|
|
413
|
-
const { client } = createMockS3Client({
|
|
414
|
-
response: {
|
|
415
|
-
Body: {
|
|
416
|
-
transformToWebStream: () => mockStream,
|
|
417
|
-
},
|
|
418
|
-
},
|
|
419
|
-
});
|
|
420
|
-
const { fn: getSignedUrl } = createMockGetSignedUrl();
|
|
421
|
-
|
|
422
|
-
const adapter = createS3BlobStorageAdapter({
|
|
423
|
-
client,
|
|
424
|
-
bucket: TEST_BUCKET,
|
|
425
|
-
commands,
|
|
426
|
-
getSignedUrl,
|
|
427
|
-
});
|
|
428
|
-
|
|
429
|
-
const result = await adapter.getStream!(TEST_HASH);
|
|
430
|
-
expect(result).toBe(mockStream);
|
|
431
|
-
});
|
|
432
|
-
|
|
433
|
-
test('returns null on NotFound', async () => {
|
|
434
|
-
const commands = createMockCommands();
|
|
435
|
-
const { client } = createMockS3Client({ notFound: true });
|
|
436
|
-
const { fn: getSignedUrl } = createMockGetSignedUrl();
|
|
437
|
-
|
|
438
|
-
const adapter = createS3BlobStorageAdapter({
|
|
439
|
-
client,
|
|
440
|
-
bucket: TEST_BUCKET,
|
|
441
|
-
commands,
|
|
442
|
-
getSignedUrl,
|
|
443
|
-
});
|
|
444
|
-
|
|
445
|
-
const result = await adapter.getStream!(TEST_HASH);
|
|
446
|
-
expect(result).toBeNull();
|
|
447
|
-
});
|
|
448
|
-
});
|
|
449
|
-
|
|
450
|
-
// ---- key prefix ----
|
|
451
|
-
describe('key prefix', () => {
|
|
452
|
-
test('prepends keyPrefix to all keys', async () => {
|
|
453
|
-
const commands = createMockCommands();
|
|
454
|
-
const { client, calls } = createMockS3Client();
|
|
455
|
-
const { fn: getSignedUrl, calls: signCalls } = createMockGetSignedUrl();
|
|
456
|
-
|
|
457
|
-
const adapter = createS3BlobStorageAdapter({
|
|
458
|
-
client,
|
|
459
|
-
bucket: TEST_BUCKET,
|
|
460
|
-
keyPrefix: 'blobs/',
|
|
461
|
-
commands,
|
|
462
|
-
getSignedUrl,
|
|
463
|
-
});
|
|
464
|
-
|
|
465
|
-
// exists -> HeadObjectCommand
|
|
466
|
-
await adapter.exists(TEST_HASH);
|
|
467
|
-
expect(calls[0]!.input.Key).toBe(`blobs/${TEST_HEX}`);
|
|
468
|
-
|
|
469
|
-
// delete -> DeleteObjectCommand
|
|
470
|
-
await adapter.delete(TEST_HASH);
|
|
471
|
-
expect(calls[1]!.input.Key).toBe(`blobs/${TEST_HEX}`);
|
|
472
|
-
|
|
473
|
-
// signUpload -> PutObjectCommand via presigner
|
|
474
|
-
await adapter.signUpload({
|
|
475
|
-
hash: TEST_HASH,
|
|
476
|
-
size: 100,
|
|
477
|
-
mimeType: 'text/plain',
|
|
478
|
-
expiresIn: 60,
|
|
479
|
-
});
|
|
480
|
-
expect(signCalls[0]!.command.input.Key).toBe(`blobs/${TEST_HEX}`);
|
|
481
|
-
|
|
482
|
-
// signDownload -> GetObjectCommand via presigner
|
|
483
|
-
await adapter.signDownload({ hash: TEST_HASH, expiresIn: 60 });
|
|
484
|
-
expect(signCalls[1]!.command.input.Key).toBe(`blobs/${TEST_HEX}`);
|
|
485
|
-
});
|
|
486
|
-
});
|
|
487
|
-
|
|
488
|
-
// ---- hash stripping ----
|
|
489
|
-
describe('hash stripping', () => {
|
|
490
|
-
test('strips "sha256:" prefix from hash to form the key', async () => {
|
|
491
|
-
const commands = createMockCommands();
|
|
492
|
-
const { client, calls } = createMockS3Client();
|
|
493
|
-
const { fn: getSignedUrl } = createMockGetSignedUrl();
|
|
494
|
-
|
|
495
|
-
const adapter = createS3BlobStorageAdapter({
|
|
496
|
-
client,
|
|
497
|
-
bucket: TEST_BUCKET,
|
|
498
|
-
commands,
|
|
499
|
-
getSignedUrl,
|
|
500
|
-
});
|
|
501
|
-
|
|
502
|
-
await adapter.exists('sha256:deadbeef');
|
|
503
|
-
expect(calls[0]!.input.Key).toBe('deadbeef');
|
|
504
|
-
});
|
|
505
|
-
|
|
506
|
-
test('leaves hashes without "sha256:" prefix unchanged', async () => {
|
|
507
|
-
const commands = createMockCommands();
|
|
508
|
-
const { client, calls } = createMockS3Client();
|
|
509
|
-
const { fn: getSignedUrl } = createMockGetSignedUrl();
|
|
510
|
-
|
|
511
|
-
const adapter = createS3BlobStorageAdapter({
|
|
512
|
-
client,
|
|
513
|
-
bucket: TEST_BUCKET,
|
|
514
|
-
commands,
|
|
515
|
-
getSignedUrl,
|
|
516
|
-
});
|
|
517
|
-
|
|
518
|
-
await adapter.exists('deadbeef');
|
|
519
|
-
expect(calls[0]!.input.Key).toBe('deadbeef');
|
|
520
|
-
});
|
|
521
|
-
});
|
|
522
|
-
});
|