Skip to content

Commit 6166515

Browse files
committed
adding tests for externalclients and gcpservice
This commit aims to add tests for the ExternalClients module and the GcpService module to ensure their proper functionality and reliability. The tests target the remaining not tested apis. The deleteIfExists method is also added to the DummyService to simulate deletion scenarios in the tests. Issue: ARSN-524
1 parent 1f00381 commit 6166515

File tree

3 files changed

+252
-4
lines changed

3 files changed

+252
-4
lines changed

tests/unit/storage/data/DummyService.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,13 @@ class AzureDummyContainerClient {
8888
readableStreamBody: new DummyObjectStream(offset, length || OBJECT_SIZE),
8989
};
9090
}
91+
92+
async deleteIfExists() {
93+
if (this.key === 'externalBackendTestBucket/externalBackendMissingKey') {
94+
return { succeeded: false };
95+
}
96+
return { succeeded: true };
97+
}
9198
}
9299

93100
class DummyService {

tests/unit/storage/data/external/ExternalClients.spec.js

Lines changed: 116 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
const assert = require('assert');
2-
const async = require('async');
32
const stream = require('stream');
3+
const sinon = require('sinon');
44
const { promisify } = require('util');
55

66
const AwsClient = require('../../../../../lib/storage/data/external/AwsClient');
@@ -50,11 +50,21 @@ const backendClients = [
5050
},
5151
];
5252
const log = new DummyRequestLogger();
53+
let sandbox;
5354

5455
describe('external backend clients', () => {
56+
beforeEach(() => {
57+
sandbox = sinon.createSandbox();
58+
});
59+
60+
afterEach(() => {
61+
sandbox.restore();
62+
});
63+
5564
backendClients.forEach(backend => {
5665
let testClient;
57-
let headAsync, getAsync, objectPutTaggingAsync, objectDeleteTaggingAsync;
66+
let headAsync, getAsync, deleteAsync, objectPutTaggingAsync, objectDeleteTaggingAsync,
67+
createMPUAsync, uploadPartAsync, abortMPUAsync, listPartsAsync;
5868

5969
beforeAll(() => {
6070
testClient = new backend.Class(backend.config);
@@ -63,10 +73,17 @@ describe('external backend clients', () => {
6373
// Promisify the client methods
6474
headAsync = promisify(testClient.head.bind(testClient));
6575
getAsync = promisify(testClient.get.bind(testClient));
76+
deleteAsync = promisify(testClient.delete.bind(testClient));
6677
if (backend.config.type !== 'azure') {
78+
createMPUAsync = promisify(testClient.createMPU.bind(testClient));
79+
uploadPartAsync = promisify(testClient.uploadPart.bind(testClient));
80+
abortMPUAsync = promisify(testClient.abortMPU.bind(testClient));
6781
objectPutTaggingAsync = promisify(testClient.objectPutTagging.bind(testClient));
6882
objectDeleteTaggingAsync = promisify(testClient.objectDeleteTagging.bind(testClient));
6983
}
84+
if (backend.config.type === 'aws') {
85+
listPartsAsync = promisify(testClient.listParts.bind(testClient));
86+
}
7087
});
7188

7289
if (backend.config.type !== 'azure') {
@@ -174,6 +191,16 @@ describe('external backend clients', () => {
174191
assert.strictEqual(errorHandled, true);
175192
});
176193

194+
it(`${backend.name} delete() should delete the requested key without error`, async () => {
195+
const key = 'externalBackendTestKey';
196+
const bucketName = 'externalBackendTestBucket';
197+
const objectInfo = Object.assign({
198+
deleteVersion: false,
199+
}, testClient.toObjectGetInfo(key, bucketName));
200+
const result = await deleteAsync(objectInfo, '');
201+
assert.strictEqual(result, undefined);
202+
});
203+
177204
if (backend.config.type !== 'azure') {
178205
it(`${backend.name} should set tags and then delete it`, async () => {
179206
const key = 'externalBackendTestKey';
@@ -228,7 +255,93 @@ describe('external backend clients', () => {
228255
assert(err.is.ServiceUnavailable);
229256
}
230257
});
258+
259+
it(`${backend.name} uploadPart() should return sanitized data retrieval info`, async () => {
260+
const key = 'externalBackendTestKey';
261+
const bucketName = 'externalBackendTestBucket';
262+
const result = await uploadPartAsync(null, null,
263+
stream.Readable.from(['part data']),
264+
9, key, 'uploadId-123', 1, bucketName, log);
265+
266+
assert.strictEqual(result.key, `${bucketName}/${key}`);
267+
assert.strictEqual(result.dataStoreName, backend.config.dataStoreName);
268+
assert(result.dataStoreETag);
269+
assert.strictEqual(result.dataStoreETag.includes('"'), false);
270+
});
271+
272+
it(`${backend.name} abortMPU() should resolve without error`, async () => {
273+
const key = 'externalBackendTestKey';
274+
const bucketName = 'externalBackendTestBucket';
275+
276+
const result = await abortMPUAsync(key, 'uploadId-123', bucketName, log);
277+
assert.strictEqual(result, undefined);
278+
});
279+
280+
if (backend.config.type === 'aws') {
281+
it(`${backend.name} listParts() should map result parts`, async () => {
282+
const key = 'externalBackendTestKey';
283+
const bucketName = 'externalBackendTestBucket';
284+
285+
const storedParts = await listPartsAsync(key, 'uploadId-123', bucketName, 0, 1000, log);
286+
287+
assert(Array.isArray(storedParts.Contents));
288+
assert(storedParts.Contents.length > 0);
289+
const firstPart = storedParts.Contents[0];
290+
assert.strictEqual(typeof firstPart.partNumber, 'number');
291+
assert(firstPart.value);
292+
assert.strictEqual(firstPart.value.ETag.includes('"'), false);
293+
});
294+
}
295+
296+
it(`${backend.name} createMPU() should trim metadata and forward tagging`, async () => {
297+
const key = 'externalBackendTestKey';
298+
const bucketName = 'externalBackendTestBucket';
299+
const metaHeaders = {
300+
'x-amz-meta-custom-key': 'customValue',
301+
'x-amz-meta-second-key': 'secondValue',
302+
ignored: 'shouldBeDropped',
303+
};
304+
const args = [
305+
key,
306+
metaHeaders,
307+
bucketName,
308+
'http://redirect',
309+
'text/plain',
310+
'max-age=3600',
311+
'attachment',
312+
'gzip',
313+
'k1=v1&k2=v2',
314+
log,
315+
];
316+
317+
if (backend.config.type === 'aws') {
318+
const sendSpy = sandbox.spy(testClient._client, 'send');
319+
const result = await createMPUAsync(...args);
320+
assert(result);
321+
assert(result.UploadId);
322+
assert(sendSpy.calledOnce);
323+
const command = sendSpy.firstCall.args[0];
324+
assert.strictEqual(command.constructor.name, 'CreateMultipartUploadCommand');
325+
assert.deepStrictEqual(command.input.Metadata, {
326+
'custom-key': 'customValue',
327+
'second-key': 'secondValue',
328+
});
329+
assert.strictEqual(command.input.Tagging, 'k1=v1&k2=v2');
330+
} else {
331+
const createSpy = sandbox.spy(testClient._client, 'createMultipartUpload');
332+
const result = await createMPUAsync(...args);
333+
assert(result);
334+
assert(result.UploadId);
335+
assert(createSpy.calledOnce);
336+
const capturedParams = createSpy.firstCall.args[0];
337+
assert.strictEqual(capturedParams.Bucket, backend.config.mpuBucket);
338+
assert.deepStrictEqual(capturedParams.Metadata, {
339+
'custom-key': 'customValue',
340+
'second-key': 'secondValue',
341+
});
342+
assert.strictEqual(capturedParams.Tagging, 'k1=v1&k2=v2');
343+
}
344+
});
231345
}
232-
// To-Do: test the other external client methods (delete, createMPU ...)
233346
});
234347
});

tests/unit/storage/data/external/GcpService.spec.js

Lines changed: 129 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ const invalidDnsBucketNames = [
108108

109109
function invalidDnsBucketNameHandler() {
110110
return (req, res) => {
111-
assert(req.headers.host, host);
111+
assert(req.headers.host, host);
112112
const bucketFromUrl = req.url.split('/')[1];
113113
assert.strictEqual(typeof bucketFromUrl, 'string');
114114
assert(invalidDnsBucketNames.includes(bucketFromUrl));
@@ -329,3 +329,131 @@ describe('GcpService dnsStyle tests', () => {
329329
client[test.op](test.params, err => done(err));
330330
}));
331331
});
332+
333+
describe('GcpService helper behavior', () => {
334+
let client;
335+
336+
beforeEach(() => {
337+
client = new GCP({
338+
s3Params: {
339+
endpoint: 'http://localhost',
340+
maxAttempts: 1,
341+
forcePathStyle: true,
342+
region: 'us-east-1',
343+
credentials: {
344+
accessKeyId: 'access',
345+
secretAccessKey: 'secret',
346+
},
347+
},
348+
bucketName: 'unit-bucket',
349+
dataStoreName: 'unit-location',
350+
});
351+
});
352+
353+
afterEach(() => {
354+
jest.restoreAllMocks();
355+
});
356+
357+
it('putObjectTagging should merge tags into metadata', done => {
358+
jest.spyOn(client, 'headObject')
359+
.mockImplementation((params, cb) => cb(null, { Metadata: { existing: 'alpha' } }));
360+
const copySpy = jest.spyOn(client, 'copyObject')
361+
.mockImplementation((params, cb) => cb(null, { CopyObjectResult: {} }));
362+
363+
client.putObjectTagging({
364+
Bucket: 'unit-bucket',
365+
Key: 'tagged-key',
366+
Tagging: {
367+
TagSet: [
368+
{ Key: 'team', Value: 'storage' },
369+
{ Key: 'env', Value: 'prod' },
370+
],
371+
},
372+
}, err => {
373+
assert.ifError(err);
374+
expect(copySpy).toHaveBeenCalledTimes(1);
375+
const metadata = copySpy.mock.calls[0][0].Metadata;
376+
assert.strictEqual(metadata.existing, 'alpha');
377+
assert.strictEqual(metadata['aws-tag-team'], 'storage');
378+
assert.strictEqual(metadata['aws-tag-env'], 'prod');
379+
done();
380+
});
381+
});
382+
383+
it('deleteObjectTagging should strip tag metadata and add sentinel', done => {
384+
jest.spyOn(client, 'headObject')
385+
.mockImplementation((params, cb) => cb(null, {
386+
Metadata: {
387+
'aws-tag-project': 'zenko',
388+
},
389+
}));
390+
const copySpy = jest.spyOn(client, 'copyObject')
391+
.mockImplementation((params, cb) => cb(null, { CopyObjectResult: {} }));
392+
393+
client.deleteObjectTagging({
394+
Bucket: 'unit-bucket',
395+
Key: 'tagged-key',
396+
}, err => {
397+
assert.ifError(err);
398+
const metadata = copySpy.mock.calls[0][0].Metadata;
399+
assert.strictEqual(metadata['aws-tag-project'], undefined);
400+
assert.strictEqual(metadata['scal-tags-removed'], 'true');
401+
done();
402+
});
403+
});
404+
405+
it('getObjectTagging should return TagSet derived from metadata', done => {
406+
jest.spyOn(client, 'headObject')
407+
.mockImplementation((params, cb) => cb(null, {
408+
Metadata: {
409+
'aws-tag-owner': 'arsenal',
410+
'aws-tag-color': 'blue',
411+
misc: 'ignored',
412+
},
413+
}));
414+
415+
client.getObjectTagging({
416+
Bucket: 'unit-bucket',
417+
Key: 'tagged-key',
418+
}, (err, res) => {
419+
assert.ifError(err);
420+
assert.deepStrictEqual(res.TagSet, [
421+
{ Key: 'owner', Value: 'arsenal' },
422+
{ Key: 'color', Value: 'blue' },
423+
]);
424+
done();
425+
});
426+
});
427+
428+
it('createMultipartUpload should reject missing parameters', done => {
429+
client.createMultipartUpload({ Bucket: 'unit-bucket' }, err => {
430+
assert(err);
431+
assert(err.is.InvalidRequest);
432+
done();
433+
});
434+
});
435+
436+
it('uploadPart should reject invalid part number', done => {
437+
client.uploadPart({
438+
Bucket: 'unit-bucket',
439+
Key: 'object',
440+
UploadId: 'upload',
441+
PartNumber: 'NaN',
442+
}, err => {
443+
assert(err);
444+
assert(err.is.InvalidArgument);
445+
done();
446+
});
447+
});
448+
449+
it('uploadPartCopy should reject missing parameters', done => {
450+
client.uploadPartCopy({
451+
Bucket: 'unit-bucket',
452+
Key: 'object',
453+
}, err => {
454+
assert(err);
455+
assert(err.is.InvalidRequest);
456+
done();
457+
});
458+
});
459+
});

0 commit comments

Comments
 (0)