@directus/storage-driver-s3 10.0.0 → 10.0.2
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/index.d.ts +34 -0
- package/package.json +7 -8
- package/dist/index.test.js +0 -525
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/// <reference types="node" resolution-mode="require"/>
|
|
2
|
+
import type { Driver, Range } from '@directus/storage';
|
|
3
|
+
import type { Readable } from 'node:stream';
|
|
4
|
+
export type DriverS3Config = {
|
|
5
|
+
root?: string;
|
|
6
|
+
key?: string;
|
|
7
|
+
secret?: string;
|
|
8
|
+
bucket: string;
|
|
9
|
+
acl?: string;
|
|
10
|
+
serverSideEncryption?: string;
|
|
11
|
+
endpoint?: string;
|
|
12
|
+
region?: string;
|
|
13
|
+
forcePathStyle?: boolean;
|
|
14
|
+
};
|
|
15
|
+
export declare class DriverS3 implements Driver {
|
|
16
|
+
private config;
|
|
17
|
+
private client;
|
|
18
|
+
private root;
|
|
19
|
+
constructor(config: DriverS3Config);
|
|
20
|
+
private getClient;
|
|
21
|
+
private fullPath;
|
|
22
|
+
read(filepath: string, range?: Range): Promise<Readable>;
|
|
23
|
+
stat(filepath: string): Promise<{
|
|
24
|
+
size: number;
|
|
25
|
+
modified: Date;
|
|
26
|
+
}>;
|
|
27
|
+
exists(filepath: string): Promise<boolean>;
|
|
28
|
+
move(src: string, dest: string): Promise<void>;
|
|
29
|
+
copy(src: string, dest: string): Promise<void>;
|
|
30
|
+
write(filepath: string, content: Readable, type?: string): Promise<void>;
|
|
31
|
+
delete(filepath: string): Promise<void>;
|
|
32
|
+
list(prefix?: string): AsyncGenerator<string, void, unknown>;
|
|
33
|
+
}
|
|
34
|
+
export default DriverS3;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@directus/storage-driver-s3",
|
|
3
|
-
"version": "10.0.
|
|
3
|
+
"version": "10.0.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "S3 file storage abstraction for `@directus/storage`",
|
|
6
6
|
"repository": {
|
|
@@ -17,8 +17,7 @@
|
|
|
17
17
|
},
|
|
18
18
|
"main": "dist/index.js",
|
|
19
19
|
"files": [
|
|
20
|
-
"dist"
|
|
21
|
-
"!**/*.d.ts?(.map)"
|
|
20
|
+
"dist"
|
|
22
21
|
],
|
|
23
22
|
"publishConfig": {
|
|
24
23
|
"access": "public"
|
|
@@ -27,18 +26,18 @@
|
|
|
27
26
|
"@aws-sdk/abort-controller": "3.310.0",
|
|
28
27
|
"@aws-sdk/client-s3": "3.317.0",
|
|
29
28
|
"@aws-sdk/lib-storage": "3.317.0",
|
|
30
|
-
"@directus/storage": "10.0.
|
|
31
|
-
"@directus/utils": "10.0.
|
|
29
|
+
"@directus/storage": "10.0.1",
|
|
30
|
+
"@directus/utils": "10.0.2"
|
|
32
31
|
},
|
|
33
32
|
"devDependencies": {
|
|
34
33
|
"@directus/tsconfig": "0.0.7",
|
|
35
34
|
"@ngneat/falso": "6.4.0",
|
|
36
|
-
"@vitest/coverage-c8": "0.
|
|
35
|
+
"@vitest/coverage-c8": "0.31.0",
|
|
37
36
|
"typescript": "5.0.4",
|
|
38
|
-
"vitest": "0.
|
|
37
|
+
"vitest": "0.31.0"
|
|
39
38
|
},
|
|
40
39
|
"scripts": {
|
|
41
|
-
"build": "tsc --
|
|
40
|
+
"build": "tsc --project tsconfig.prod.json",
|
|
42
41
|
"dev": "tsc --watch",
|
|
43
42
|
"test": "vitest --watch=false"
|
|
44
43
|
}
|
package/dist/index.test.js
DELETED
|
@@ -1,525 +0,0 @@
|
|
|
1
|
-
import { CopyObjectCommand, DeleteObjectCommand, GetObjectCommand, HeadObjectCommand, ListObjectsV2Command, S3Client, } from '@aws-sdk/client-s3';
|
|
2
|
-
import { Upload } from '@aws-sdk/lib-storage';
|
|
3
|
-
import { normalizePath } from '@directus/utils';
|
|
4
|
-
import { isReadableStream } from '@directus/utils/node';
|
|
5
|
-
import { randAlphaNumeric, randBoolean, randDirectoryPath, randDomainName, randFilePath, randFileType, randGitBranch as randBucket, randGitShortSha as randUnique, randNumber, randPastDate, randText, randWord, } from '@ngneat/falso';
|
|
6
|
-
import { join } from 'node:path';
|
|
7
|
-
import { PassThrough, Readable } from 'node:stream';
|
|
8
|
-
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
|
|
9
|
-
import { DriverS3 } from './index.js';
|
|
10
|
-
vi.mock('@directus/utils/node');
|
|
11
|
-
vi.mock('@directus/utils');
|
|
12
|
-
vi.mock('@aws-sdk/client-s3');
|
|
13
|
-
vi.mock('@aws-sdk/lib-storage');
|
|
14
|
-
vi.mock('node:path');
|
|
15
|
-
let sample;
|
|
16
|
-
let driver;
|
|
17
|
-
beforeEach(() => {
|
|
18
|
-
sample = {
|
|
19
|
-
config: {
|
|
20
|
-
key: randAlphaNumeric({ length: 20 }).join(''),
|
|
21
|
-
secret: randAlphaNumeric({ length: 40 }).join(''),
|
|
22
|
-
bucket: randBucket(),
|
|
23
|
-
acl: randWord(),
|
|
24
|
-
serverSideEncryption: randWord(),
|
|
25
|
-
root: randDirectoryPath(),
|
|
26
|
-
endpoint: randDomainName(),
|
|
27
|
-
region: randWord(),
|
|
28
|
-
forcePathStyle: randBoolean(),
|
|
29
|
-
},
|
|
30
|
-
path: {
|
|
31
|
-
input: randUnique() + randFilePath(),
|
|
32
|
-
inputFull: randUnique() + randFilePath(),
|
|
33
|
-
src: randUnique() + randFilePath(),
|
|
34
|
-
srcFull: randUnique() + randFilePath(),
|
|
35
|
-
dest: randUnique() + randFilePath(),
|
|
36
|
-
destFull: randUnique() + randFilePath(),
|
|
37
|
-
},
|
|
38
|
-
range: {
|
|
39
|
-
start: randNumber(),
|
|
40
|
-
end: randNumber(),
|
|
41
|
-
},
|
|
42
|
-
stream: new PassThrough(),
|
|
43
|
-
text: randText(),
|
|
44
|
-
file: {
|
|
45
|
-
type: randFileType(),
|
|
46
|
-
size: randNumber(),
|
|
47
|
-
modified: randPastDate(),
|
|
48
|
-
},
|
|
49
|
-
};
|
|
50
|
-
driver = new DriverS3({
|
|
51
|
-
key: sample.config.key,
|
|
52
|
-
secret: sample.config.secret,
|
|
53
|
-
bucket: sample.config.bucket,
|
|
54
|
-
});
|
|
55
|
-
driver['fullPath'] = vi.fn().mockImplementation((input) => {
|
|
56
|
-
if (input === sample.path.src)
|
|
57
|
-
return sample.path.srcFull;
|
|
58
|
-
if (input === sample.path.dest)
|
|
59
|
-
return sample.path.destFull;
|
|
60
|
-
if (input === sample.path.input)
|
|
61
|
-
return sample.path.inputFull;
|
|
62
|
-
return '';
|
|
63
|
-
});
|
|
64
|
-
});
|
|
65
|
-
afterEach(() => {
|
|
66
|
-
vi.resetAllMocks();
|
|
67
|
-
});
|
|
68
|
-
describe('#constructor', () => {
|
|
69
|
-
let getClientBackup;
|
|
70
|
-
let sampleClient;
|
|
71
|
-
beforeEach(() => {
|
|
72
|
-
getClientBackup = DriverS3.prototype['getClient'];
|
|
73
|
-
sampleClient = {};
|
|
74
|
-
DriverS3.prototype['getClient'] = vi.fn().mockReturnValue(sampleClient);
|
|
75
|
-
});
|
|
76
|
-
afterEach(() => {
|
|
77
|
-
DriverS3.prototype['getClient'] = getClientBackup;
|
|
78
|
-
});
|
|
79
|
-
test('Saves passed config to local property', () => {
|
|
80
|
-
const driver = new DriverS3(sample.config);
|
|
81
|
-
expect(driver['config']).toBe(sample.config);
|
|
82
|
-
});
|
|
83
|
-
test('Creates shared client', () => {
|
|
84
|
-
const driver = new DriverS3(sample.config);
|
|
85
|
-
expect(driver['getClient']).toHaveBeenCalledOnce();
|
|
86
|
-
expect(driver['client']).toBe(sampleClient);
|
|
87
|
-
});
|
|
88
|
-
test('Defaults root to empty string', () => {
|
|
89
|
-
expect(driver['root']).toBe('');
|
|
90
|
-
});
|
|
91
|
-
test('Normalizes config path when root is given', () => {
|
|
92
|
-
const mockRoot = randDirectoryPath();
|
|
93
|
-
vi.mocked(normalizePath).mockReturnValue(mockRoot);
|
|
94
|
-
const driver = new DriverS3({
|
|
95
|
-
key: sample.config.key,
|
|
96
|
-
secret: sample.config.secret,
|
|
97
|
-
bucket: sample.config.bucket,
|
|
98
|
-
root: sample.config.root,
|
|
99
|
-
});
|
|
100
|
-
expect(normalizePath).toHaveBeenCalledWith(sample.config.root, { removeLeading: true });
|
|
101
|
-
expect(driver['root']).toBe(mockRoot);
|
|
102
|
-
});
|
|
103
|
-
});
|
|
104
|
-
describe('#getClient', () => {
|
|
105
|
-
// The constructor calls getClient(), so we don't have to call it separately
|
|
106
|
-
test('Throws error if key defined but secret missing', () => {
|
|
107
|
-
try {
|
|
108
|
-
new DriverS3({ key: 'key', bucket: 'bucket' });
|
|
109
|
-
}
|
|
110
|
-
catch (err) {
|
|
111
|
-
expect(err).toBeInstanceOf(Error);
|
|
112
|
-
expect(err.message).toBe('Both `key` and `secret` are required when defined');
|
|
113
|
-
}
|
|
114
|
-
});
|
|
115
|
-
test('Throws error if secret defined but key missing', () => {
|
|
116
|
-
try {
|
|
117
|
-
new DriverS3({ secret: 'secret', bucket: 'bucket' });
|
|
118
|
-
}
|
|
119
|
-
catch (err) {
|
|
120
|
-
expect(err).toBeInstanceOf(Error);
|
|
121
|
-
expect(err.message).toBe('Both `key` and `secret` are required when defined');
|
|
122
|
-
}
|
|
123
|
-
});
|
|
124
|
-
test('Creates S3Client without key / secret (based on machine config)', () => {
|
|
125
|
-
const driver = new DriverS3({ bucket: 'bucket' });
|
|
126
|
-
expect(S3Client).toHaveBeenCalledWith({});
|
|
127
|
-
expect(driver['client']).toBeInstanceOf(S3Client);
|
|
128
|
-
});
|
|
129
|
-
test('Creates S3Client with key / secret configuration', () => {
|
|
130
|
-
expect(S3Client).toHaveBeenCalledWith({
|
|
131
|
-
credentials: {
|
|
132
|
-
accessKeyId: sample.config.key,
|
|
133
|
-
secretAccessKey: sample.config.secret,
|
|
134
|
-
},
|
|
135
|
-
});
|
|
136
|
-
expect(driver['client']).toBeInstanceOf(S3Client);
|
|
137
|
-
});
|
|
138
|
-
test('Sets http endpoints', () => {
|
|
139
|
-
const sampleDomain = randDomainName();
|
|
140
|
-
const sampleHttpEndpoint = `http://${sampleDomain}`;
|
|
141
|
-
new DriverS3({
|
|
142
|
-
key: sample.config.key,
|
|
143
|
-
secret: sample.config.secret,
|
|
144
|
-
bucket: sample.config.bucket,
|
|
145
|
-
endpoint: sampleHttpEndpoint,
|
|
146
|
-
});
|
|
147
|
-
expect(S3Client).toHaveBeenCalledWith({
|
|
148
|
-
endpoint: {
|
|
149
|
-
hostname: sampleDomain,
|
|
150
|
-
protocol: 'http:',
|
|
151
|
-
path: '/',
|
|
152
|
-
},
|
|
153
|
-
credentials: {
|
|
154
|
-
accessKeyId: sample.config.key,
|
|
155
|
-
secretAccessKey: sample.config.secret,
|
|
156
|
-
},
|
|
157
|
-
});
|
|
158
|
-
});
|
|
159
|
-
test('Sets https endpoints', () => {
|
|
160
|
-
const sampleDomain = randDomainName();
|
|
161
|
-
const sampleHttpEndpoint = `https://${sampleDomain}`;
|
|
162
|
-
new DriverS3({
|
|
163
|
-
key: sample.config.key,
|
|
164
|
-
secret: sample.config.secret,
|
|
165
|
-
bucket: sample.config.bucket,
|
|
166
|
-
endpoint: sampleHttpEndpoint,
|
|
167
|
-
});
|
|
168
|
-
expect(S3Client).toHaveBeenCalledWith({
|
|
169
|
-
endpoint: {
|
|
170
|
-
hostname: sampleDomain,
|
|
171
|
-
protocol: 'https:',
|
|
172
|
-
path: '/',
|
|
173
|
-
},
|
|
174
|
-
credentials: {
|
|
175
|
-
accessKeyId: sample.config.key,
|
|
176
|
-
secretAccessKey: sample.config.secret,
|
|
177
|
-
},
|
|
178
|
-
});
|
|
179
|
-
});
|
|
180
|
-
test('Sets region', () => {
|
|
181
|
-
new DriverS3({
|
|
182
|
-
key: sample.config.key,
|
|
183
|
-
secret: sample.config.secret,
|
|
184
|
-
bucket: sample.config.bucket,
|
|
185
|
-
region: sample.config.region,
|
|
186
|
-
});
|
|
187
|
-
expect(S3Client).toHaveBeenCalledWith({
|
|
188
|
-
region: sample.config.region,
|
|
189
|
-
credentials: {
|
|
190
|
-
accessKeyId: sample.config.key,
|
|
191
|
-
secretAccessKey: sample.config.secret,
|
|
192
|
-
},
|
|
193
|
-
});
|
|
194
|
-
});
|
|
195
|
-
test('Sets force path style', () => {
|
|
196
|
-
new DriverS3({
|
|
197
|
-
key: sample.config.key,
|
|
198
|
-
secret: sample.config.secret,
|
|
199
|
-
bucket: sample.config.bucket,
|
|
200
|
-
forcePathStyle: sample.config.forcePathStyle,
|
|
201
|
-
});
|
|
202
|
-
expect(S3Client).toHaveBeenCalledWith({
|
|
203
|
-
forcePathStyle: sample.config.forcePathStyle,
|
|
204
|
-
credentials: {
|
|
205
|
-
accessKeyId: sample.config.key,
|
|
206
|
-
secretAccessKey: sample.config.secret,
|
|
207
|
-
},
|
|
208
|
-
});
|
|
209
|
-
});
|
|
210
|
-
});
|
|
211
|
-
describe('#fullPath', () => {
|
|
212
|
-
test('Returns normalized joined path', () => {
|
|
213
|
-
const driver = new DriverS3({
|
|
214
|
-
key: sample.config.key,
|
|
215
|
-
secret: sample.config.secret,
|
|
216
|
-
bucket: sample.config.bucket,
|
|
217
|
-
});
|
|
218
|
-
vi.mocked(join).mockReturnValue(sample.path.inputFull);
|
|
219
|
-
vi.mocked(normalizePath).mockReturnValue(sample.path.inputFull);
|
|
220
|
-
driver['root'] = sample.config.root;
|
|
221
|
-
const result = driver['fullPath'](sample.path.input);
|
|
222
|
-
expect(join).toHaveBeenCalledWith(sample.config.root, sample.path.input);
|
|
223
|
-
expect(normalizePath).toHaveBeenCalledWith(sample.path.inputFull);
|
|
224
|
-
expect(result).toBe(sample.path.inputFull);
|
|
225
|
-
});
|
|
226
|
-
});
|
|
227
|
-
describe('#read', () => {
|
|
228
|
-
beforeEach(() => {
|
|
229
|
-
vi.mocked(driver['client'].send).mockReturnValue({ Body: new Readable() });
|
|
230
|
-
vi.mocked(isReadableStream).mockReturnValue(true);
|
|
231
|
-
});
|
|
232
|
-
test('Uses fullPath key / bucket in command input', async () => {
|
|
233
|
-
await driver.read(sample.path.input);
|
|
234
|
-
expect(driver['fullPath']).toHaveBeenCalledWith(sample.path.input);
|
|
235
|
-
expect(GetObjectCommand).toHaveBeenCalledWith({
|
|
236
|
-
Key: sample.path.inputFull,
|
|
237
|
-
Bucket: sample.config.bucket,
|
|
238
|
-
});
|
|
239
|
-
});
|
|
240
|
-
test('Optionally allows setting start range offset', async () => {
|
|
241
|
-
await driver.read(sample.path.input, { start: sample.range.start });
|
|
242
|
-
expect(GetObjectCommand).toHaveBeenCalledWith({
|
|
243
|
-
Key: sample.path.inputFull,
|
|
244
|
-
Bucket: sample.config.bucket,
|
|
245
|
-
Range: `bytes=${sample.range.start}-`,
|
|
246
|
-
});
|
|
247
|
-
});
|
|
248
|
-
test('Optionally allows setting end range offset', async () => {
|
|
249
|
-
await driver.read(sample.path.input, { end: sample.range.end });
|
|
250
|
-
expect(GetObjectCommand).toHaveBeenCalledWith({
|
|
251
|
-
Key: sample.path.inputFull,
|
|
252
|
-
Bucket: sample.config.bucket,
|
|
253
|
-
Range: `bytes=-${sample.range.end}`,
|
|
254
|
-
});
|
|
255
|
-
});
|
|
256
|
-
test('Optionally allows setting start and end range offset', async () => {
|
|
257
|
-
await driver.read(sample.path.input, sample.range);
|
|
258
|
-
expect(GetObjectCommand).toHaveBeenCalledWith({
|
|
259
|
-
Key: sample.path.inputFull,
|
|
260
|
-
Bucket: sample.config.bucket,
|
|
261
|
-
Range: `bytes=${sample.range.start}-${sample.range.end}`,
|
|
262
|
-
});
|
|
263
|
-
});
|
|
264
|
-
test('Throws an error when no stream is returned', async () => {
|
|
265
|
-
vi.mocked(driver['client'].send).mockReturnValue({ Body: undefined });
|
|
266
|
-
try {
|
|
267
|
-
await driver.read(sample.path.input, sample.range);
|
|
268
|
-
}
|
|
269
|
-
catch (err) {
|
|
270
|
-
expect(err).toBeInstanceOf(Error);
|
|
271
|
-
expect(err.message).toBe(`No stream returned for file "${sample.path.input}"`);
|
|
272
|
-
}
|
|
273
|
-
});
|
|
274
|
-
test('Throws an error when returned stream is not a readable stream', async () => {
|
|
275
|
-
vi.mocked(isReadableStream).mockReturnValue(false);
|
|
276
|
-
expect(driver.read(sample.path.input, sample.range)).rejects.toThrowError(new Error(`No stream returned for file "${sample.path.input}"`));
|
|
277
|
-
});
|
|
278
|
-
test('Returns stream from S3 client', async () => {
|
|
279
|
-
const mockGetObjectCommand = {};
|
|
280
|
-
vi.mocked(driver['client'].send).mockReturnValue({ Body: sample.stream });
|
|
281
|
-
vi.mocked(GetObjectCommand).mockReturnValue(mockGetObjectCommand);
|
|
282
|
-
const stream = await driver.read(sample.path.input, sample.range);
|
|
283
|
-
expect(driver['client'].send).toHaveBeenCalledWith(mockGetObjectCommand);
|
|
284
|
-
expect(stream).toBe(sample.stream);
|
|
285
|
-
});
|
|
286
|
-
});
|
|
287
|
-
describe('#stat', () => {
|
|
288
|
-
beforeEach(() => {
|
|
289
|
-
vi.mocked(driver['client'].send).mockResolvedValue({
|
|
290
|
-
ContentLength: sample.file.size,
|
|
291
|
-
LastModified: sample.file.modified,
|
|
292
|
-
});
|
|
293
|
-
});
|
|
294
|
-
test('Uses HeadObjectCommand with fullPath', async () => {
|
|
295
|
-
await driver.stat(sample.path.input);
|
|
296
|
-
expect(driver['fullPath']).toHaveBeenCalledWith(sample.path.input);
|
|
297
|
-
expect(HeadObjectCommand).toHaveBeenCalledWith({
|
|
298
|
-
Key: sample.path.inputFull,
|
|
299
|
-
Bucket: sample.config.bucket,
|
|
300
|
-
});
|
|
301
|
-
});
|
|
302
|
-
test('Calls #send with HeadObjectCommand', async () => {
|
|
303
|
-
const mockHeadObjectCommand = {};
|
|
304
|
-
vi.mocked(HeadObjectCommand).mockReturnValue(mockHeadObjectCommand);
|
|
305
|
-
await driver.stat(sample.path.input);
|
|
306
|
-
expect(driver['client'].send).toHaveBeenCalledWith(mockHeadObjectCommand);
|
|
307
|
-
});
|
|
308
|
-
test('Returns size/modified from returned send data', async () => {
|
|
309
|
-
const result = await driver.stat(sample.path.input);
|
|
310
|
-
expect(result).toStrictEqual({
|
|
311
|
-
size: sample.file.size,
|
|
312
|
-
modified: sample.file.modified,
|
|
313
|
-
});
|
|
314
|
-
});
|
|
315
|
-
});
|
|
316
|
-
describe('#exists', () => {
|
|
317
|
-
beforeEach(() => {
|
|
318
|
-
driver.stat = vi.fn();
|
|
319
|
-
});
|
|
320
|
-
test('Returns true if stat returns the stats', async () => {
|
|
321
|
-
vi.mocked(driver.stat).mockResolvedValue({ size: sample.file.size, modified: sample.file.modified });
|
|
322
|
-
const exists = await driver.exists(sample.path.input);
|
|
323
|
-
expect(exists).toBe(true);
|
|
324
|
-
});
|
|
325
|
-
test('Returns false if stat throws an error', async () => {
|
|
326
|
-
vi.mocked(driver.stat).mockRejectedValue(new Error());
|
|
327
|
-
const exists = await driver.exists(sample.path.input);
|
|
328
|
-
expect(exists).toBe(false);
|
|
329
|
-
});
|
|
330
|
-
});
|
|
331
|
-
describe('#move', () => {
|
|
332
|
-
beforeEach(async () => {
|
|
333
|
-
driver.copy = vi.fn();
|
|
334
|
-
driver.delete = vi.fn();
|
|
335
|
-
await driver.move(sample.path.src, sample.path.dest);
|
|
336
|
-
});
|
|
337
|
-
test('Calls copy with given src and dest', async () => {
|
|
338
|
-
expect(driver.copy).toHaveBeenCalledWith(sample.path.src, sample.path.dest);
|
|
339
|
-
});
|
|
340
|
-
test('Calls delete on successful copy', async () => {
|
|
341
|
-
expect(driver.delete).toHaveBeenCalledWith(sample.path.src);
|
|
342
|
-
});
|
|
343
|
-
});
|
|
344
|
-
describe('#copy', () => {
|
|
345
|
-
test('Constructs params object based on config', async () => {
|
|
346
|
-
await driver.copy(sample.path.src, sample.path.dest);
|
|
347
|
-
expect(CopyObjectCommand).toHaveBeenCalledWith({
|
|
348
|
-
Key: sample.path.destFull,
|
|
349
|
-
Bucket: sample.config.bucket,
|
|
350
|
-
CopySource: `/${sample.config.bucket}/${sample.path.srcFull}`,
|
|
351
|
-
});
|
|
352
|
-
});
|
|
353
|
-
test('Optionally sets ServerSideEncryption', async () => {
|
|
354
|
-
driver['config'].serverSideEncryption = sample.config.serverSideEncryption;
|
|
355
|
-
await driver.copy(sample.path.src, sample.path.dest);
|
|
356
|
-
expect(CopyObjectCommand).toHaveBeenCalledWith({
|
|
357
|
-
Key: sample.path.destFull,
|
|
358
|
-
Bucket: sample.config.bucket,
|
|
359
|
-
CopySource: `/${sample.config.bucket}/${sample.path.srcFull}`,
|
|
360
|
-
ServerSideEncryption: sample.config.serverSideEncryption,
|
|
361
|
-
});
|
|
362
|
-
});
|
|
363
|
-
test('Optionally sets ACL', async () => {
|
|
364
|
-
driver['config'].acl = sample.config.acl;
|
|
365
|
-
await driver.copy(sample.path.src, sample.path.dest);
|
|
366
|
-
expect(CopyObjectCommand).toHaveBeenCalledWith({
|
|
367
|
-
Key: sample.path.destFull,
|
|
368
|
-
Bucket: sample.config.bucket,
|
|
369
|
-
CopySource: `/${sample.config.bucket}/${sample.path.srcFull}`,
|
|
370
|
-
ACL: sample.config.acl,
|
|
371
|
-
});
|
|
372
|
-
});
|
|
373
|
-
test('Executes CopyObjectCommand', async () => {
|
|
374
|
-
const mockCommand = {};
|
|
375
|
-
vi.mocked(CopyObjectCommand).mockReturnValue(mockCommand);
|
|
376
|
-
await driver.copy(sample.path.src, sample.path.dest);
|
|
377
|
-
expect(driver['client'].send).toHaveBeenCalledWith(mockCommand);
|
|
378
|
-
});
|
|
379
|
-
});
|
|
380
|
-
describe('#write', () => {
|
|
381
|
-
test('Passes streams to body as is', async () => {
|
|
382
|
-
await driver.write(sample.path.input, sample.stream);
|
|
383
|
-
expect(Upload).toHaveBeenCalledWith({
|
|
384
|
-
client: driver['client'],
|
|
385
|
-
params: {
|
|
386
|
-
Key: sample.path.inputFull,
|
|
387
|
-
Bucket: sample.config.bucket,
|
|
388
|
-
Body: sample.stream,
|
|
389
|
-
},
|
|
390
|
-
});
|
|
391
|
-
});
|
|
392
|
-
test('Optionally sets ContentType', async () => {
|
|
393
|
-
await driver.write(sample.path.input, sample.stream, sample.file.type);
|
|
394
|
-
expect(Upload).toHaveBeenCalledWith({
|
|
395
|
-
client: driver['client'],
|
|
396
|
-
params: {
|
|
397
|
-
Key: sample.path.inputFull,
|
|
398
|
-
Bucket: sample.config.bucket,
|
|
399
|
-
Body: sample.stream,
|
|
400
|
-
ContentType: sample.file.type,
|
|
401
|
-
},
|
|
402
|
-
});
|
|
403
|
-
});
|
|
404
|
-
test('Optionally sets ServerSideEncryption', async () => {
|
|
405
|
-
driver['config'].serverSideEncryption = sample.config.serverSideEncryption;
|
|
406
|
-
await driver.write(sample.path.input, sample.stream);
|
|
407
|
-
expect(Upload).toHaveBeenCalledWith({
|
|
408
|
-
client: driver['client'],
|
|
409
|
-
params: {
|
|
410
|
-
Key: sample.path.inputFull,
|
|
411
|
-
Bucket: sample.config.bucket,
|
|
412
|
-
Body: sample.stream,
|
|
413
|
-
ServerSideEncryption: sample.config.serverSideEncryption,
|
|
414
|
-
},
|
|
415
|
-
});
|
|
416
|
-
});
|
|
417
|
-
test('Optionally sets ACL', async () => {
|
|
418
|
-
driver['config'].acl = sample.config.acl;
|
|
419
|
-
await driver.write(sample.path.input, sample.stream);
|
|
420
|
-
expect(Upload).toHaveBeenCalledWith({
|
|
421
|
-
client: driver['client'],
|
|
422
|
-
params: {
|
|
423
|
-
Key: sample.path.inputFull,
|
|
424
|
-
Bucket: sample.config.bucket,
|
|
425
|
-
Body: sample.stream,
|
|
426
|
-
ACL: sample.config.acl,
|
|
427
|
-
},
|
|
428
|
-
});
|
|
429
|
-
});
|
|
430
|
-
test('Waits for upload to be done', async () => {
|
|
431
|
-
const mockUpload = { done: vi.fn() };
|
|
432
|
-
vi.mocked(Upload).mockReturnValue(mockUpload);
|
|
433
|
-
await driver.write(sample.path.input, sample.stream);
|
|
434
|
-
expect(mockUpload.done).toHaveBeenCalledOnce();
|
|
435
|
-
});
|
|
436
|
-
});
|
|
437
|
-
describe('#delete', () => {
|
|
438
|
-
test('Constructs params based on input', async () => {
|
|
439
|
-
await driver.delete(sample.path.input);
|
|
440
|
-
expect(DeleteObjectCommand).toHaveBeenCalledWith({
|
|
441
|
-
Key: sample.path.inputFull,
|
|
442
|
-
Bucket: sample.config.bucket,
|
|
443
|
-
});
|
|
444
|
-
});
|
|
445
|
-
test('Executes DeleteObjectCommand', async () => {
|
|
446
|
-
const mockDeleteObjectCommand = {};
|
|
447
|
-
vi.mocked(DeleteObjectCommand).mockReturnValue(mockDeleteObjectCommand);
|
|
448
|
-
await driver.delete(sample.path.input);
|
|
449
|
-
expect(driver['client'].send).toHaveBeenCalledWith(mockDeleteObjectCommand);
|
|
450
|
-
});
|
|
451
|
-
});
|
|
452
|
-
describe('#list', () => {
|
|
453
|
-
test('Constructs list objects params based on input prefix', async () => {
|
|
454
|
-
vi.mocked(driver['client'].send).mockResolvedValue({});
|
|
455
|
-
await driver.list(sample.path.input).next();
|
|
456
|
-
expect(ListObjectsV2Command).toHaveBeenCalledWith({
|
|
457
|
-
Bucket: sample.config.bucket,
|
|
458
|
-
Prefix: sample.path.inputFull,
|
|
459
|
-
MaxKeys: 1000,
|
|
460
|
-
});
|
|
461
|
-
});
|
|
462
|
-
test('Calls send with the command', async () => {
|
|
463
|
-
const mockListObjectsV2Command = {};
|
|
464
|
-
vi.mocked(ListObjectsV2Command).mockReturnValue(mockListObjectsV2Command);
|
|
465
|
-
vi.mocked(driver['client'].send).mockResolvedValue({});
|
|
466
|
-
await driver.list(sample.path.input).next();
|
|
467
|
-
expect(driver['client'].send).toHaveBeenCalledWith(mockListObjectsV2Command);
|
|
468
|
-
});
|
|
469
|
-
test('Yields file Key omitting root', async () => {
|
|
470
|
-
const sampleRoot = randDirectoryPath();
|
|
471
|
-
const sampleFile = randFilePath();
|
|
472
|
-
const sampleFull = `${sampleRoot}${sampleFile}`;
|
|
473
|
-
vi.mocked(driver['client'].send).mockResolvedValue({
|
|
474
|
-
Contents: [
|
|
475
|
-
{
|
|
476
|
-
Key: sampleFull,
|
|
477
|
-
},
|
|
478
|
-
],
|
|
479
|
-
});
|
|
480
|
-
driver['root'] = sampleRoot;
|
|
481
|
-
const iterator = driver.list(sample.path.input);
|
|
482
|
-
const output = [];
|
|
483
|
-
for await (const filepath of iterator) {
|
|
484
|
-
output.push(filepath);
|
|
485
|
-
}
|
|
486
|
-
expect(output).toStrictEqual([sampleFile]);
|
|
487
|
-
});
|
|
488
|
-
test('Continuously fetches until all pages are returned', async () => {
|
|
489
|
-
vi.mocked(driver['client'].send)
|
|
490
|
-
.mockResolvedValueOnce({
|
|
491
|
-
NextContinuationToken: randWord(),
|
|
492
|
-
Contents: [
|
|
493
|
-
{
|
|
494
|
-
Key: randFilePath(),
|
|
495
|
-
},
|
|
496
|
-
{
|
|
497
|
-
Key: randFilePath(),
|
|
498
|
-
},
|
|
499
|
-
],
|
|
500
|
-
})
|
|
501
|
-
.mockResolvedValueOnce({
|
|
502
|
-
NextContinuationToken: randWord(),
|
|
503
|
-
Contents: [
|
|
504
|
-
{
|
|
505
|
-
Key: randFilePath(),
|
|
506
|
-
},
|
|
507
|
-
],
|
|
508
|
-
})
|
|
509
|
-
.mockResolvedValueOnce({
|
|
510
|
-
NextContinuationToken: undefined,
|
|
511
|
-
Contents: [
|
|
512
|
-
{
|
|
513
|
-
Key: randFilePath(),
|
|
514
|
-
},
|
|
515
|
-
],
|
|
516
|
-
});
|
|
517
|
-
const iterator = driver.list(sample.path.input);
|
|
518
|
-
const output = [];
|
|
519
|
-
for await (const filepath of iterator) {
|
|
520
|
-
output.push(filepath);
|
|
521
|
-
}
|
|
522
|
-
expect(driver['client'].send).toHaveBeenCalledTimes(3);
|
|
523
|
-
expect(output.length).toBe(4);
|
|
524
|
-
});
|
|
525
|
-
});
|