@elastic.io/maester-client
Advanced tools
Comparing version 3.4.4-dev.2 to 4.0.0-dev.1
@@ -7,38 +7,51 @@ module.exports = { | ||
}, | ||
extends: ["airbnb-base"], | ||
extends: [ | ||
'airbnb-base', | ||
], | ||
globals: { | ||
Atomics: "readonly", | ||
SharedArrayBuffer: "readonly", | ||
Atomics: 'readonly', | ||
SharedArrayBuffer: 'readonly', | ||
}, | ||
parser: "@typescript-eslint/parser", | ||
parser: '@typescript-eslint/parser', | ||
parserOptions: { | ||
ecmaVersion: 2018, | ||
sourceType: "module", | ||
sourceType: 'module', | ||
}, | ||
plugins: ["@typescript-eslint"], | ||
plugins: [ | ||
'@typescript-eslint', | ||
], | ||
rules: { | ||
quotes: ["error", "single"], | ||
"no-plusplus": 0, | ||
"no-await-in-loop": 0, | ||
"prefer-default-export": 0, | ||
"import/extensions": 0, | ||
"import/prefer-default-export": 0, | ||
"class-methods-use-this": 0, | ||
"import/no-unresolved": 0, | ||
"no-unused-vars": 0, | ||
"max-len": ["error", { code: 180 }], | ||
"no-param-reassign": 0, | ||
"guard-for-in": "off", | ||
"no-return-assign": 0, | ||
"no-prototype-builtins": 0, | ||
"operator-linebreak": 0, | ||
'linebreak-style': 0, | ||
'import/no-extraneous-dependencies': ['error', { devDependencies: ['spec/**/*', 'spec-integration/**/*'] }], | ||
'no-plusplus': 0, | ||
'no-unused-vars': 0, | ||
'no-await-in-loop': 0, | ||
'prefer-default-export': 0, | ||
'import/prefer-default-export': 0, | ||
'class-methods-use-this': 1, | ||
'max-len': ['error', { code: 180 }], | ||
'no-param-reassign': 1, | ||
'no-return-assign': 1, | ||
'no-use-before-define': 0, | ||
'comma-dangle': 0, | ||
'object-curly-newline': 0, | ||
camelcase: 0, | ||
'import/extensions': [ | ||
'error', | ||
'ignorePackages', | ||
{ | ||
js: 'never', | ||
jsx: 'never', | ||
ts: 'never', | ||
tsx: 'never', | ||
}, | ||
], | ||
}, | ||
settings: { | ||
"import/extensions": [".js", ".jsx", ".ts", ".tsx"], | ||
"import/parsers": { | ||
"@typescript-eslint/parser": [".ts", ".tsx"], | ||
'import/parsers': { | ||
'@typescript-eslint/parser': ['.ts', '.tsx'], | ||
}, | ||
"import/resolver": { | ||
'import/resolver': { | ||
node: { | ||
extensions: [".js", ".jsx", ".ts", ".tsx"], | ||
extensions: ['.js', '.jsx', '.ts', '.tsx'], | ||
}, | ||
@@ -45,0 +58,0 @@ }, |
@@ -1,4 +0,7 @@ | ||
* | ||
# 4.0.0 (June 17, 2022) | ||
* new version of library | ||
# 3.4.3 (April 8, 2022) | ||
* Fix dependencies | ||
# 3.4.2 (July 27, 2021) | ||
@@ -5,0 +8,0 @@ * Update headers validation |
{ | ||
"name": "@elastic.io/maester-client", | ||
"version": "3.4.4-dev.2", | ||
"version": "4.0.0-dev.1", | ||
"description": "The official object-storage client", | ||
"main": "dist/src/index.js", | ||
"types": "dist/src/index.d.ts", | ||
"main": "dist/index.js", | ||
"types": "dist/index.d.ts", | ||
"scripts": { | ||
"tsc": "rm -fr dist && tsc", | ||
"lint": "eslint '*/**/*.ts' --quiet --fix", | ||
"test": "NODE_ENV=test mocha -r ts-node/register --recursive ./spec/**/*.ts", | ||
"pretest": "npm run lint && find src spec -name \"*.js\" -type f -delete", | ||
"lint": "eslint '*/**/*[^.d$].ts' --quiet --fix", | ||
"test": "NODE_ENV=test mocha -r ts-node/register --recursive spec/**/*.ts --timeout 10000", | ||
"integration-test": "npm run pretest && mocha --exit --r ts-node/register spec-integration/**/*.ts --timeout 1000000", | ||
"pretest": "eslint --ext .ts --quiet --fix && find src spec spec-integration -name \"*.js\" -type f -delete && find src spec spec-integration -name \"*.d.ts\" -type f -delete", | ||
"posttest": "npm run tsc", | ||
@@ -35,19 +36,27 @@ "build": "npm run tsc", | ||
"devDependencies": { | ||
"@elastic.io/component-logger": "0.0.1", | ||
"@types/chai": "4.2.11", | ||
"@types/chai-as-promised": "7.1.5", | ||
"@types/dicer": "0.2.0", | ||
"@types/jsonwebtoken": "8.3.9", | ||
"@types/lodash": "4.14.170", | ||
"@types/mocha": "9.1.0", | ||
"@types/mocha": "9.0.0", | ||
"@types/nock": "11.1.0", | ||
"@types/node": "16.9.6", | ||
"@types/qs": "6.9.1", | ||
"@typescript-eslint/eslint-plugin": "2.28.0", | ||
"@typescript-eslint/parser": "2.28.0", | ||
"chai": "4.2.0", | ||
"eslint": "6.8.0", | ||
"eslint-config-airbnb-base": "14.0.0", | ||
"eslint-plugin-import": "2.20.1", | ||
"mocha": "9.2.2", | ||
"chai": "4.3.4", | ||
"chai-as-promised": "7.1.1", | ||
"dotenv": "16.0.1", | ||
"eslint": "7.32.0", | ||
"eslint-config-airbnb-base": "14.2.1", | ||
"eslint-plugin-import": "2.24.2", | ||
"is-uuid": "1.0.2", | ||
"mocha": "10.0.0", | ||
"nock": "12.0.3", | ||
"ts-node": "8.8.2", | ||
"typescript": "3.8.3" | ||
"nyc": "15.1.0", | ||
"sinon": "11.1.2", | ||
"ts-node": "10.2.1", | ||
"typescript": "4.4.3" | ||
}, | ||
@@ -57,6 +66,9 @@ "dependencies": { | ||
"@types/sinon": "10.0.0", | ||
"aws-sdk": "2.1152.0", | ||
"axios": "0.26.1", | ||
"form-data": "4.0.0", | ||
"get-stream": "6.0.1", | ||
"jsonwebtoken": "8.5.1", | ||
"sinon": "10.0.0" | ||
} | ||
} |
@@ -0,5 +1,15 @@ | ||
/* eslint-disable import/first */ | ||
process.env.REQUEST_MAX_RETRY = '3'; | ||
process.env.REQUEST_RETRY_DELAY = '0'; | ||
import { Readable, Duplex } from 'stream'; | ||
import * as crypto from 'crypto'; | ||
import * as zlib from 'zlib'; | ||
import getLogger from '@elastic.io/component-logger'; | ||
import sinon from 'sinon'; | ||
export const getContext = () => ({ | ||
logger: getLogger(), | ||
emit: sinon.spy(), | ||
}); | ||
const MESSAGE_CRYPTO_PASSWORD = 'testCryptoPassword'; | ||
@@ -9,9 +19,2 @@ const MESSAGE_CRYPTO_IV = 'iv=any16_symbols'; | ||
export const streamResponse = (responseData: any) => () => { | ||
const stream = new Readable(); | ||
stream.push(JSON.stringify(responseData)); | ||
stream.push(null); | ||
return stream; | ||
}; | ||
export const encryptStream = (): Duplex => { | ||
@@ -30,1 +33,9 @@ const encodeKey = crypto.createHash('sha256').update(MESSAGE_CRYPTO_PASSWORD, 'utf8').digest(); | ||
export const unzip = (): Duplex => zlib.createGunzip(); | ||
export const streamFromObject = (data: object): Readable => { | ||
const dataString = JSON.stringify(data); | ||
const stream = new Readable(); | ||
stream.push(dataString); | ||
stream.push(null); | ||
return stream; | ||
}; |
/* eslint-disable no-unused-expressions */ | ||
import nock from 'nock'; | ||
import sinonjs, { SinonSandbox } from 'sinon'; | ||
import sinon from 'sinon'; | ||
import getStream from 'get-stream'; | ||
import { expect } from 'chai'; | ||
import { ObjectStorage, StorageClient } from '../src'; | ||
import { | ||
describe, beforeEach, afterEach, it, | ||
} from 'mocha'; | ||
import { Readable } from 'stream'; | ||
import { ObjectStorage } from '../src/ObjectStorage'; | ||
import logging from '../src/logger'; | ||
import { | ||
streamResponse, encryptStream, decryptStream, zip, unzip, | ||
encryptStream, decryptStream, zip, unzip, streamFromObject | ||
} from './helpers'; | ||
const formStream = (dataString: string): Readable => { | ||
const stream = new Readable(); | ||
stream.push(dataString); | ||
stream.push(null); | ||
return stream; | ||
}; | ||
describe('Object Storage', () => { | ||
@@ -28,5 +16,13 @@ const config = { | ||
}; | ||
const objectStorage = new ObjectStorage(config); | ||
const postData = { test: 'test' }; | ||
const createdObjWithQueryField = { | ||
contentType: 'application/json', | ||
createdAt: 1622811501107, | ||
objectId: '2bd48165-119f-489d-8842-8d07b2c7cc1b', | ||
metadata: {}, | ||
queriableFields: { | ||
demosearchfield: 'qwerty', | ||
}, | ||
}; | ||
const responseData = { | ||
@@ -40,106 +36,103 @@ contentLength: 'meta.contentLength', | ||
}; | ||
// eslint-disable-next-line max-len | ||
const responseString = '{"contentLength":"meta.contentLength","contentType":"meta.contentType","createdAt":"meta.createdAt","md5":"meta.md5Hash","objectId":"obj.id","metadata":"meta.userMetadata"}'; | ||
let sinon: SinonSandbox; | ||
beforeEach(async () => { | ||
sinon = sinonjs.createSandbox(); | ||
}); | ||
afterEach(() => { | ||
sinon.restore(); | ||
}); | ||
let finalReqCfg; | ||
afterEach(sinon.restore); | ||
describe('basic', () => { | ||
describe('data mode', () => { | ||
it('should getAllByParams', async () => { | ||
const objectStorage = new ObjectStorage(config); | ||
const objectStorageCalls = nock(config.uri) | ||
// @ts-ignore: Nock .d.ts are outdated. | ||
.matchHeader('authorization', `Bearer ${config.jwtSecret}`) | ||
.get('/objects?foo=bar') | ||
.reply(200, {}); | ||
await objectStorage.getAllByParams({ foo: 'bar' }); | ||
expect(objectStorageCalls.isDone()).to.be.true; | ||
describe('should getAllByParams', () => { | ||
beforeEach(async () => { | ||
finalReqCfg = sinon.stub(StorageClient.prototype, <any>'requestRetry').callsFake(async () => ( | ||
{ data: streamFromObject([createdObjWithQueryField, createdObjWithQueryField]) } | ||
)); | ||
}); | ||
it('should getAllByParams', async () => { | ||
const result = await objectStorage.getAllByParams({ foo: 'bar' }); | ||
expect(JSON.parse(result)).to.deep.equal([createdObjWithQueryField, createdObjWithQueryField]); | ||
const { firstArg, lastArg } = finalReqCfg.getCall(0); | ||
expect(lastArg).to.be.deep.equal({}); | ||
expect(firstArg.getFreshStream).to.be.equal(undefined); | ||
expect(firstArg.axiosReqConfig).to.deep.equal({ | ||
method: 'get', | ||
url: '/objects', | ||
responseType: 'stream', | ||
params: { foo: 'bar' }, | ||
headers: { Authorization: 'Bearer jwt' } | ||
}); | ||
}); | ||
}); | ||
it('should getById (stream)', async () => { | ||
const objectStorage = new ObjectStorage(config); | ||
const objectStorageCalls = nock(config.uri) | ||
// @ts-ignore: Nock .d.ts are outdated. | ||
.matchHeader('authorization', `Bearer ${config.jwtSecret}`) | ||
.get('/objects/objectId') | ||
.reply(200, formStream('i`m a stream')); | ||
const result = await objectStorage.getById('objectId', 'stream'); | ||
expect(result.toString('base64')).to.be.equal(formStream('i`m a stream').toString()); | ||
expect(objectStorageCalls.isDone()).to.be.true; | ||
describe('should getById (stream)', () => { | ||
beforeEach(async () => { | ||
finalReqCfg = sinon.stub(StorageClient.prototype, <any>'requestRetry').callsFake(async () => ({ data: streamFromObject({ q: 'i`m a stream' }) })); | ||
}); | ||
it('should getById (stream)', async () => { | ||
const result = await objectStorage.getOne('objectId', { responseType: 'stream' }); | ||
const streamAsJSON = await getStream(result); | ||
expect(JSON.parse(streamAsJSON)).to.be.deep.equal({ q: 'i`m a stream' }); | ||
const { firstArg, lastArg } = finalReqCfg.getCall(0); | ||
expect(lastArg).to.be.deep.equal({}); | ||
expect(firstArg.getFreshStream).to.be.equal(undefined); | ||
expect(firstArg.axiosReqConfig).to.deep.equal({ | ||
method: 'get', | ||
url: '/objects/objectId', | ||
responseType: 'stream', | ||
params: {}, | ||
headers: { Authorization: 'Bearer jwt' } | ||
}); | ||
}); | ||
}); | ||
it('should getById (json)', async () => { | ||
const objectStorage = new ObjectStorage(config); | ||
const objectStorageCalls = nock(config.uri) | ||
// @ts-ignore: Nock .d.ts are outdated. | ||
.matchHeader('authorization', `Bearer ${config.jwtSecret}`) | ||
.get('/objects/objectId') | ||
.reply(200, formStream('i`m a stream')); | ||
const result = await objectStorage.getById('objectId', 'json'); | ||
expect(result).to.be.deep.equal('i`m a stream'); | ||
expect(objectStorageCalls.isDone()).to.be.true; | ||
describe('should getById (json)', () => { | ||
beforeEach(async () => { | ||
finalReqCfg = sinon.stub(StorageClient.prototype, <any>'requestRetry').callsFake(async () => ({ data: streamFromObject({ q: 'i`m a stream' }) })); | ||
}); | ||
it('should getById (json)', async () => { | ||
const result = await objectStorage.getOne('objectId', { responseType: 'json' }); | ||
expect(result).to.be.equal(JSON.stringify({ q: 'i`m a stream' })); | ||
const { firstArg, lastArg } = finalReqCfg.getCall(0); | ||
expect(lastArg).to.be.deep.equal({}); | ||
expect(firstArg.getFreshStream).to.be.equal(undefined); | ||
expect(firstArg.axiosReqConfig).to.deep.equal({ | ||
method: 'get', | ||
url: '/objects/objectId', | ||
responseType: 'stream', | ||
params: {}, | ||
headers: { Authorization: 'Bearer jwt' } | ||
}); | ||
}); | ||
}); | ||
it('should getById (arraybuffer)', async () => { | ||
const objectStorage = new ObjectStorage(config); | ||
const objectStorageCalls = nock(config.uri) | ||
// @ts-ignore: Nock .d.ts are outdated. | ||
.matchHeader('authorization', `Bearer ${config.jwtSecret}`) | ||
.get('/objects/objectId') | ||
.reply(200, formStream('i`m a stream')); | ||
const result = await objectStorage.getById('objectId', 'arraybuffer'); | ||
const encodedResult = Buffer.from('i`m a stream', 'binary').toString('base64'); | ||
expect(result.toString('base64')).to.be.equal(encodedResult); | ||
expect(objectStorageCalls.isDone()).to.be.true; | ||
describe('should getById (arraybuffer)', () => { | ||
beforeEach(async () => { | ||
finalReqCfg = sinon.stub(StorageClient.prototype, <any>'requestRetry').callsFake(async () => ({ data: streamFromObject({ q: 'i`m a stream' }) })); | ||
}); | ||
it('should getById (arraybuffer)', async () => { | ||
const result = await objectStorage.getOne('objectId', { responseType: 'arraybuffer' }); | ||
const encodedResult = Buffer.from(JSON.stringify({ q: 'i`m a stream' }), 'binary').toString('base64'); | ||
expect(result.toString('base64')).to.be.equal(encodedResult); | ||
const { firstArg, lastArg } = finalReqCfg.getCall(0); | ||
expect(lastArg).to.be.deep.equal({}); | ||
expect(firstArg.getFreshStream).to.be.equal(undefined); | ||
expect(firstArg.axiosReqConfig).to.deep.equal({ | ||
method: 'get', | ||
url: '/objects/objectId', | ||
responseType: 'stream', | ||
params: {}, | ||
headers: { Authorization: 'Bearer jwt' } | ||
}); | ||
}); | ||
}); | ||
}); | ||
describe('stream mode', () => { | ||
it('should fail after 3 get retries', async () => { | ||
const log = sinon.stub(logging, 'warn'); | ||
const objectStorage = new ObjectStorage(config); | ||
const objectStorageCalls = nock(config.uri) | ||
// @ts-ignore: Nock .d.ts are outdated. | ||
.matchHeader('authorization', `Bearer ${config.jwtSecret}`) | ||
.get('/objects/1') | ||
.replyWithError({ code: 'ETIMEDOUT' }) | ||
.get('/objects/1') | ||
.reply(404) | ||
.get('/objects/1') | ||
.replyWithError({ code: 'ENOTFOUND' }); | ||
.times(3) | ||
.reply(500); | ||
let err; | ||
try { | ||
await objectStorage.getById('1'); | ||
} catch (e) { | ||
err = e; | ||
} | ||
await expect(objectStorage.getOne('1')).to.be.rejectedWith('Server error during request'); | ||
expect(objectStorageCalls.isDone()).to.be.true; | ||
expect(err.code).to.be.equal('ENOTFOUND'); | ||
expect(log.getCall(1).args[1].toString()).to.include('404'); | ||
expect(log.callCount).to.be.equal(2); | ||
}); | ||
it('should retry get request on errors', async () => { | ||
const objectStorage = new ObjectStorage(config); | ||
const objectStorageCalls = nock(config.uri) | ||
// @ts-ignore: Nock .d.ts are outdated. | ||
.matchHeader('authorization', `Bearer ${config.jwtSecret}`) | ||
@@ -149,62 +142,32 @@ .get('/objects/1') | ||
.get('/objects/1') | ||
.reply(200, streamResponse(responseData)); | ||
.reply(200, streamFromObject(responseData)); | ||
const response = await objectStorage.getById('1'); | ||
const response = await objectStorage.getOne('1', { responseType: 'json' }); | ||
expect(objectStorageCalls.isDone()).to.be.true; | ||
expect(response).to.be.deep.equal(responseString); | ||
expect(response).to.be.deep.equal(JSON.stringify(responseData)); | ||
}); | ||
it('should throw an error on post request connection error', async () => { | ||
const objectStorage = new ObjectStorage(config); | ||
const objectStorageCalls = nock(config.uri) | ||
// @ts-ignore: Nock .d.ts are outdated. | ||
.matchHeader('authorization', `Bearer ${config.jwtSecret}`) | ||
.post('/objects') | ||
.replyWithError({ code: 'ECONNREFUSED' }) | ||
.post('/objects') | ||
.replyWithError({ code: 'ECONNREFUSED' }) | ||
.post('/objects') | ||
.times(3) | ||
.replyWithError({ code: 'ECONNREFUSED' }); | ||
let err; | ||
try { | ||
await objectStorage.postObject(postData, {}); | ||
} catch (e) { | ||
err = e; | ||
} | ||
await expect(objectStorage.add(postData, {})).to.be.rejectedWith('Server error during request'); | ||
expect(objectStorageCalls.isDone()).to.be.true; | ||
expect(err.code).to.be.equal('ECONNREFUSED'); | ||
}); | ||
it('should throw an error on post request http error', async () => { | ||
const objectStorage = new ObjectStorage(config); | ||
it('should throw an error immediately on post request http error', async () => { | ||
const objectStorageCalls = nock(config.uri) | ||
// @ts-ignore: Nock .d.ts are outdated. | ||
.matchHeader('authorization', `Bearer ${config.jwtSecret}`) | ||
.post('/objects') | ||
.reply(409) | ||
.post('/objects') | ||
.reply(409) | ||
.post('/objects') | ||
.reply(409); | ||
let err; | ||
try { | ||
await objectStorage.postObject(postData, {}); | ||
} catch (e) { | ||
err = e; | ||
} | ||
await expect(objectStorage.add(postData, {})).to.be.rejectedWith('Request failed with status code 409'); | ||
expect(objectStorageCalls.isDone()).to.be.true; | ||
expect(err.toString()).to.include('409'); | ||
}); | ||
it('should post successfully', async () => { | ||
const objectStorage = new ObjectStorage(config); | ||
const objectStorageCalls = nock(config.uri) | ||
// @ts-ignore: Nock .d.ts are outdated. | ||
.matchHeader('authorization', `Bearer ${config.jwtSecret}`) | ||
@@ -214,10 +177,8 @@ .post('/objects') | ||
const response: any = await objectStorage.postObject(postData, {}); | ||
const objectId = await objectStorage.add(postData, {}); | ||
expect(objectStorageCalls.isDone()).to.be.true; | ||
expect(response.objectId).to.match(/^[0-9a-z-]+$/); | ||
expect(objectId).to.match(/^[0-9a-z-]+$/); | ||
}); | ||
}); | ||
}); | ||
describe('middlewares + zip/unzip and encrypt/decrypt', () => { | ||
@@ -229,26 +190,11 @@ describe('stream mode', () => { | ||
objectStorageWithMiddlewares.use(zip, unzip); | ||
const log = sinon.stub(logging, 'warn'); | ||
const objectStorageWithMiddlewaresCalls = nock(config.uri) | ||
// @ts-ignore: Nock .d.ts are outdated. | ||
.matchHeader('authorization', `Bearer ${config.jwtSecret}`) | ||
.get('/objects/1') | ||
.replyWithError({ code: 'ETIMEDOUT' }) | ||
.get('/objects/1') | ||
.reply(404) | ||
.get('/objects/1') | ||
.replyWithError({ code: 'ENOTFOUND' }); | ||
.times(3) | ||
.replyWithError({ code: 'ETIMEDOUT' }); | ||
let err; | ||
try { | ||
await objectStorageWithMiddlewares.getById('1'); | ||
} catch (e) { | ||
err = e; | ||
} | ||
await expect(objectStorageWithMiddlewares.getOne('1')).to.be.rejectedWith('Server error during request'); | ||
expect(objectStorageWithMiddlewaresCalls.isDone()).to.be.true; | ||
expect(err.code).to.be.equal('ENOTFOUND'); | ||
expect(log.getCall(1).args[1].toString()).to.include('404'); | ||
expect(log.callCount).to.be.equal(2); | ||
}); | ||
it('should retry get request on errors', async () => { | ||
@@ -258,4 +204,4 @@ const objectStorageWithMiddlewares = new ObjectStorage(config); | ||
objectStorageWithMiddlewares.use(zip, unzip); | ||
const responseStream = streamFromObject(responseData).pipe(encryptStream()).pipe(zip()); | ||
const objectStorageWithMiddlewaresCalls = nock(config.uri) | ||
// @ts-ignore: Nock .d.ts are outdated. | ||
.matchHeader('authorization', `Bearer ${config.jwtSecret}`) | ||
@@ -265,13 +211,9 @@ .get('/objects/1') | ||
.get('/objects/1') | ||
.reply(200, () => { | ||
const stream = streamResponse(responseData)(); | ||
return stream.pipe(encryptStream()).pipe(zip()); | ||
}); | ||
.reply(200, responseStream); | ||
const response = await objectStorageWithMiddlewares.getById('1'); | ||
const stream = await objectStorageWithMiddlewares.getOne('1', { responseType: 'stream' }); | ||
const result = await getStream(stream); | ||
expect(result).to.be.deep.equal(JSON.stringify(responseData)); | ||
expect(objectStorageWithMiddlewaresCalls.isDone()).to.be.true; | ||
expect(response).to.be.deep.equal(responseString); | ||
}); | ||
it('should throw an error on post request connection error', async () => { | ||
@@ -282,22 +224,10 @@ const objectStorageWithMiddlewares = new ObjectStorage(config); | ||
const objectStorageWithMiddlewaresCalls = nock(config.uri) | ||
// @ts-ignore: Nock .d.ts are outdated. | ||
.matchHeader('authorization', `Bearer ${config.jwtSecret}`) | ||
.post('/objects') | ||
.replyWithError({ code: 'ECONNREFUSED' }) | ||
.post('/objects') | ||
.replyWithError({ code: 'ECONNREFUSED' }) | ||
.post('/objects') | ||
.times(3) | ||
.replyWithError({ code: 'ECONNREFUSED' }); | ||
let err; | ||
try { | ||
await objectStorageWithMiddlewares.postObject(postData, {}); | ||
} catch (e) { | ||
err = e; | ||
} | ||
await expect(objectStorageWithMiddlewares.add(postData, {})).to.be.rejectedWith('Server error during request'); | ||
expect(objectStorageWithMiddlewaresCalls.isDone()).to.be.true; | ||
expect(err.code).to.be.equal('ECONNREFUSED'); | ||
}); | ||
it('should throw an error on post request http error', async () => { | ||
@@ -308,21 +238,9 @@ const objectStorageWithMiddlewares = new ObjectStorage(config); | ||
const objectStorageWithMiddlewaresCalls = nock(config.uri) | ||
// @ts-ignore: Nock .d.ts are outdated. | ||
.matchHeader('authorization', `Bearer ${config.jwtSecret}`) | ||
.post('/objects') | ||
.reply(409) | ||
.post('/objects') | ||
.reply(409) | ||
.post('/objects') | ||
.reply(409); | ||
let err; | ||
try { | ||
await objectStorageWithMiddlewares.postObject(postData, {}); | ||
} catch (e) { | ||
err = e; | ||
} | ||
await expect(objectStorageWithMiddlewares.add(postData, {})).to.be.rejectedWith('Request failed with status code 409'); | ||
expect(objectStorageWithMiddlewaresCalls.isDone()).to.be.true; | ||
expect(err.toString()).to.include('409'); | ||
}); | ||
it('should post successfully', async () => { | ||
@@ -333,72 +251,45 @@ const objectStorageWithMiddlewares = new ObjectStorage(config); | ||
const objectStorageWithMiddlewaresCalls = nock(config.uri) | ||
// @ts-ignore: Nock .d.ts are outdated. | ||
.matchHeader('authorization', `Bearer ${config.jwtSecret}`) | ||
.post('/objects') | ||
.reply(200, { objectId: '1' }); | ||
.reply(200, streamFromObject({ objectId: 'dfsf-2dasd3-dsf2l' })); | ||
const response:any = await objectStorageWithMiddlewares.postObject(postData, {}); | ||
const response = await objectStorageWithMiddlewares.add(postData, {}); | ||
expect(response).to.be.equal('dfsf-2dasd3-dsf2l'); | ||
expect(objectStorageWithMiddlewaresCalls.isDone()).to.be.true; | ||
expect(response.objectId).to.be.equal('1'); | ||
}); | ||
it('should add 2 objects successfully', async () => { | ||
const objectStorageWithMiddlewares = new ObjectStorage(config); | ||
objectStorageWithMiddlewares.use(encryptStream, decryptStream); | ||
objectStorageWithMiddlewares.use(zip, unzip); | ||
const objectStorageWithMiddlewaresCalls = nock(config.uri) | ||
// @ts-ignore: Nock .d.ts are outdated. | ||
.matchHeader('authorization', `Bearer ${config.jwtSecret}`) | ||
.post('/objects') | ||
.reply(200, { objectId: '1' }) | ||
.post('/objects') | ||
.reply(200, { objectId: '2' }); | ||
const response1: any = await objectStorageWithMiddlewares.postObject(postData, {}); | ||
const response2: any = await objectStorageWithMiddlewares.postObject(postData, {}); | ||
expect(objectStorageWithMiddlewaresCalls.isDone()).to.be.true; | ||
expect(response1.objectId).to.be.equal('1'); | ||
expect(response2.objectId).to.be.equal('2'); | ||
}); | ||
}); | ||
describe('configure ReqOptions', () => { | ||
describe('configure ReqOptions', () => { | ||
beforeEach(async () => { | ||
finalReqCfg = sinon.spy(StorageClient.prototype, <any>'requestRetry'); | ||
}); | ||
it('should get 2 objects successfully', async () => { | ||
const objectStorageWithMiddlewares = new ObjectStorage(config); | ||
objectStorageWithMiddlewares.use(encryptStream, decryptStream); | ||
objectStorageWithMiddlewares.use(zip, unzip); | ||
const objectStorageWithMiddlewaresCalls = nock(config.uri) | ||
// @ts-ignore: Nock .d.ts are outdated. | ||
it('configure ReqOptions', async () => { | ||
const objectStorageCalls = nock(config.uri) | ||
.matchHeader('authorization', `Bearer ${config.jwtSecret}`) | ||
.get('/objects/1') | ||
.reply(200, () => { | ||
const stream = streamResponse(responseData)(); | ||
return stream.pipe(encryptStream()).pipe(zip()); | ||
}) | ||
.get('/objects/2') | ||
.reply(200, () => { | ||
const stream = streamResponse(responseData)(); | ||
return stream.pipe(encryptStream()).pipe(zip()); | ||
}); | ||
.times(5) | ||
.replyWithError({ code: 'ETIMEDOUT' }); | ||
const outStreamFirst = await objectStorageWithMiddlewares.getById('1'); | ||
const outStreamSecond = await objectStorageWithMiddlewares.getById('2'); | ||
expect(objectStorageWithMiddlewaresCalls.isDone()).to.be.true; | ||
expect(outStreamFirst).to.be.deep.equal(responseString); | ||
expect(outStreamSecond).to.be.deep.equal(responseString); | ||
const retryOptions = { retriesCount: 5, requestTimeout: 1, retryDelay: 1 }; | ||
await expect(objectStorage.getOne('1', { retryOptions })).to.be.rejectedWith('Server error during request'); | ||
expect(objectStorageCalls.isDone()).to.be.true; | ||
const { lastArg } = finalReqCfg.getCall(0); | ||
expect(lastArg).to.be.deep.equal(retryOptions); | ||
}); | ||
it('should use valid jwt token', async () => { | ||
const objectStorageWithMiddlewares = new ObjectStorage(config); | ||
objectStorageWithMiddlewares.use(encryptStream, decryptStream); | ||
objectStorageWithMiddlewares.use(zip, unzip); | ||
const objectStorageWithMiddlewaresCalls = nock(config.uri) | ||
// @ts-ignore: Nock .d.ts are outdated. | ||
it('configure ReqOptions', async () => { | ||
const objectStorageCalls = nock(config.uri) | ||
.matchHeader('authorization', `Bearer ${config.jwtSecret}`) | ||
.post('/objects') | ||
.reply(200); | ||
.get('/objects/1') | ||
.times(4) | ||
.replyWithError({ code: 'ETIMEDOUT' }) | ||
.get('/objects/1') | ||
.reply(200, streamFromObject({ objectId: '234-sdf' })); | ||
const response:any = await objectStorageWithMiddlewares.postObject(postData, {}); | ||
expect(objectStorageWithMiddlewaresCalls.isDone()).to.be.true; | ||
expect(response.objectId).to.match(/^[0-9a-z-]+$/); | ||
const retryOptions = { retriesCount: 5, requestTimeout: 1, retryDelay: 1 }; | ||
const result = await objectStorage.getOne('1', { retryOptions }); | ||
expect(JSON.parse(result)).to.be.deep.equal({ objectId: '234-sdf' }); | ||
expect(objectStorageCalls.isDone()).to.be.true; | ||
const { lastArg } = finalReqCfg.getCall(0); | ||
expect(lastArg).to.be.deep.equal(retryOptions); | ||
}); | ||
@@ -405,0 +296,0 @@ }); |
/* eslint-disable max-len */ | ||
import 'mocha'; | ||
import chai from 'chai'; | ||
import nock from 'nock'; | ||
import chai, { expect } from 'chai'; | ||
import sinon from 'sinon'; | ||
import bunyan from '@elastic.io/bunyan-logger'; | ||
import { ObjectStorageWrapper, MAESTER_MAX_SUPPORTED_COUNT_OF_QUERY_HEADERS } from '../src/ObjectStorageWrapper'; | ||
import getStream from 'get-stream'; | ||
import chaiAsPromised from 'chai-as-promised'; | ||
import { getContext, streamFromObject } from './helpers'; | ||
import { ObjectStorageWrapper, StorageClient, MAESTER_MAX_SUPPORTED_COUNT_OF_QUERY_HEADERS } from '../src'; | ||
const { expect } = chai; | ||
chai.use(chaiAsPromised); | ||
process.env.ELASTICIO_OBJECT_STORAGE_TOKEN = | ||
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0ZW5hbnRJZCI6IjU2YzIwN2FkYjkxMjExODFlNjUwYzBlZiIsImNvbnRyYWN0SWQiOiI1YjVlZDFjZjI3MmNmODAwMTFhZTdiNmEiLCJ3b3Jrc3BhY2VJZCI6IjVhNzFiZmM1NjA3ZjFiMDAwNzI5OGEyYSIsImZsb3dJZCI6IioiLCJ1c2VySWQiOiI1YjE2NGRiMzRkNTlhODAwMDdiZDQ3OTMiLCJpYXQiOjE1ODg1ODg3NjZ9.3GlJAwHz__e2Y5tgkzD1t-JyhgXGJOSVFSLUBCqLh5Y'; | ||
process.env.ELASTICIO_WORKSPACE_ID = 'test'; | ||
process.env.ELASTICIO_FLOW_ID = 'test'; | ||
process.env.ELASTICIO_API_URI = 'https://api.hostname'; | ||
process.env.ELASTICIO_OBJECT_STORAGE_TOKEN = 'eyJhbGciOiJIUzI1NiIsInR5cCI6I'; | ||
process.env.ELASTICIO_OBJECT_STORAGE_URI = 'https://ma.estr'; | ||
process.env.ELASTICIO_STEP_ID = 'step_id'; | ||
let context: any; | ||
let objectStorageWrapper: any; | ||
describe('ObjectStorageWrapper', () => { | ||
const objectStorageWrapper = new ObjectStorageWrapper(getContext()); | ||
const genHeaders = (amount: number) => { | ||
@@ -37,8 +31,6 @@ const resultHeaders = []; | ||
}; | ||
const rawData = '{"foo":"bar"}'; | ||
const queryKey = 'baz'; | ||
const queryValue = 'bap'; | ||
const ttl = -1; | ||
const ttl = 10; | ||
const id = 'id123'; | ||
const maesterUri = 'https://ma.estr'; | ||
const createObjectWithQueriableField = { | ||
@@ -53,154 +45,117 @@ contentType: 'application/json', | ||
}; | ||
const anotherCreateObjectWithQueriableField = { | ||
contentType: 'application/json', | ||
createdAt: 1622811501108, | ||
objectId: '78asdas87-ss77-77ss-7888-8d07b2c7cc2a', | ||
metadata: {}, | ||
queriableFields: { | ||
demosearchfield: 'qwerty', | ||
}, | ||
}; | ||
before(async () => { | ||
context = { | ||
logger: bunyan.createLogger({ name: 'dummy' }), | ||
emit: sinon.spy(), | ||
}; | ||
objectStorageWrapper = new ObjectStorageWrapper(context); | ||
}); | ||
let finalReqCfg; | ||
beforeEach(async () => { | ||
context.emit.resetHistory(); | ||
}); | ||
afterEach(sinon.restore); | ||
after(() => { | ||
nock.restore(); | ||
nock.cleanAll(); | ||
nock.activate(); | ||
}); | ||
describe('Create object', () => { | ||
beforeEach(async () => { | ||
finalReqCfg = sinon.stub(StorageClient.prototype, <any>'requestRetry').callsFake(async () => ({ data: createObjectWithQueriableField })); | ||
}); | ||
describe('valid inputs', () => { | ||
describe('With queriable and meta fields', () => { | ||
it('Should save the data correctly', async () => { | ||
nock(maesterUri) | ||
.post('/objects') | ||
.matchHeader('x-query-key0', 'value0') | ||
.matchHeader('x-eio-ttl', '-1') | ||
.reply(201, createObjectWithQueriableField); | ||
await objectStorageWrapper.createObject(data, genHeaders(1), [], ttl); | ||
it('Should save the data correctly (1 q-header, ttl-value)', async () => { | ||
const result = await objectStorageWrapper.createObject(data, genHeaders(1), [], ttl); | ||
expect(result).to.be.equal(createObjectWithQueriableField.objectId); | ||
const { firstArg, lastArg } = finalReqCfg.getCall(0); | ||
expect(lastArg).to.be.deep.equal({}); | ||
const stream = await firstArg.getFreshStream(); | ||
expect(await getStream(stream)).to.be.equal(JSON.stringify(data)); | ||
expect(firstArg.axiosReqConfig).to.be.deep.equal({ | ||
method: 'post', | ||
url: '/objects', | ||
headers: { | ||
Authorization: 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6I', | ||
'content-type': 'application/json', | ||
'x-query-key0': 'value0', | ||
'x-eio-ttl': '10' | ||
} | ||
}); | ||
it('Should save the data correctly', async () => { | ||
nock(maesterUri) | ||
.post('/objects') | ||
.reply(201, createObjectWithQueriableField); | ||
await objectStorageWrapper.createObject(data); | ||
}); | ||
it('Should save the data correctly (2 q-header, 3 q-header)', async () => { | ||
const result = await objectStorageWrapper.createObject(data, genHeaders(2), genHeaders(3)); | ||
expect(result).to.be.equal(createObjectWithQueriableField.objectId); | ||
const { firstArg, lastArg } = finalReqCfg.getCall(0); | ||
expect(lastArg).to.be.deep.equal({}); | ||
const stream = await firstArg.getFreshStream(); | ||
expect(await getStream(stream)).to.be.equal(JSON.stringify(data)); | ||
expect(firstArg.axiosReqConfig).to.be.deep.equal({ | ||
method: 'post', | ||
url: '/objects', | ||
headers: { | ||
Authorization: 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6I', | ||
'content-type': 'application/json', | ||
'x-query-key0': 'value0', | ||
'x-query-key1': 'value1', | ||
'x-meta-key0': 'value0', | ||
'x-meta-key1': 'value1', | ||
'x-meta-key2': 'value2', | ||
} | ||
}); | ||
it('Should save the data correctly', async () => { | ||
nock(maesterUri) | ||
.post('/objects') | ||
.reply(201, createObjectWithQueriableField); | ||
await objectStorageWrapper.createObject(data, []); | ||
}); | ||
it('Should save the data correctly (no headers)', async () => { | ||
const result = await objectStorageWrapper.createObject(data); | ||
expect(result).to.be.equal(createObjectWithQueriableField.objectId); | ||
const { firstArg, lastArg } = finalReqCfg.getCall(0); | ||
expect(lastArg).to.be.deep.equal({}); | ||
const stream = await firstArg.getFreshStream(); | ||
expect(await getStream(stream)).to.be.equal(JSON.stringify(data)); | ||
expect(firstArg.axiosReqConfig).to.be.deep.equal({ | ||
method: 'post', | ||
url: '/objects', | ||
headers: { | ||
Authorization: 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6I', | ||
'content-type': 'application/json', | ||
} | ||
}); | ||
it('Should save the data correctly', async () => { | ||
nock(maesterUri) | ||
.post('/objects') | ||
.reply(201, createObjectWithQueriableField); | ||
await objectStorageWrapper.createObject(data, null); | ||
}); | ||
it('Should save the data correctly', async () => { | ||
nock(maesterUri) | ||
.post('/objects') | ||
.matchHeader('x-query-key0', 'value0') | ||
.matchHeader('x-query-key1', 'value1') | ||
.matchHeader('x-query-key2', 'value2') | ||
.matchHeader('x-query-key3', 'value3') | ||
.matchHeader('x-query-key4', 'value4') | ||
.matchHeader('x-meta-key0', 'value0') | ||
.matchHeader('x-meta-key1', 'value1') | ||
.matchHeader('x-eio-ttl', '-1') | ||
.reply(201, createObjectWithQueriableField); | ||
await objectStorageWrapper.createObject(data, genHeaders(MAESTER_MAX_SUPPORTED_COUNT_OF_QUERY_HEADERS), genHeaders(2), ttl); | ||
}); | ||
}); | ||
describe('Without queriable fields', () => { | ||
it('Should save the data correctly', async () => { | ||
nock(maesterUri).post('/objects').matchHeader('x-eio-ttl', '-1').reply(201, createObjectWithQueriableField); | ||
await objectStorageWrapper.createObject(data, genHeaders(1), [], ttl); | ||
it('Should save the data correctly (no headers)', async () => { | ||
const result = await objectStorageWrapper.createObject(data, [], []); | ||
expect(result).to.be.equal(createObjectWithQueriableField.objectId); | ||
const { firstArg, lastArg } = finalReqCfg.getCall(0); | ||
expect(lastArg).to.be.deep.equal({}); | ||
const stream = await firstArg.getFreshStream(); | ||
expect(await getStream(stream)).to.be.equal(JSON.stringify(data)); | ||
expect(firstArg.axiosReqConfig).to.be.deep.equal({ | ||
method: 'post', | ||
url: '/objects', | ||
headers: { | ||
Authorization: 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6I', | ||
'content-type': 'application/json', | ||
} | ||
}); | ||
it('Should save the data correctly', async () => { | ||
nock(maesterUri).post('/objects').reply(201, createObjectWithQueriableField); | ||
await objectStorageWrapper.createObject(data); | ||
}); | ||
}); | ||
}); | ||
describe('invalid inputs', () => { | ||
describe('Query key set, query value undefined', () => { | ||
it('Should throw error', async () => { | ||
await objectStorageWrapper | ||
.createObject(data, [{ key: 'key0', value: 'value0' }, { key: 'key1' }], [], ttl) | ||
.catch((error: { message: any }) => { | ||
expect(error.message).to.equal('header "value" is mandatory if header "key" passed'); | ||
}); | ||
}); | ||
it('Should throw error', async () => { | ||
await objectStorageWrapper | ||
.createObject(data, genHeaders(1), [{ key: 'key0', value: 'value0' }, { key: 'key1' }], ttl) | ||
.catch((error: { message: any }) => { | ||
expect(error.message).to.equal('header "value" is mandatory if header "key" passed'); | ||
}); | ||
}); | ||
it('Should throw error', async () => { | ||
await expect( | ||
// @ts-ignore | ||
objectStorageWrapper.createObject(data, [{ key: 'key0', value: 'value0' }, { key: 'key1' }], [], ttl) | ||
).to.be.rejectedWith('header "value" is mandatory if header "key" passed'); | ||
}); | ||
describe('Query value set, query key undefined', () => { | ||
it('Should throw error', async () => { | ||
await objectStorageWrapper.createObject(data, [{ value: 'value1' }], [], ttl).catch((error: { message: any }) => { | ||
expect(error.message).to.equal('header "key" is mandatory if header "value" passed'); | ||
}); | ||
}); | ||
it('Should throw error', async () => { | ||
await objectStorageWrapper.createObject(data, genHeaders(1), [{ value: 'value1' }], ttl).catch((error: { message: any }) => { | ||
expect(error.message).to.equal('header "key" is mandatory if header "value" passed'); | ||
}); | ||
}); | ||
it('Should throw error', async () => { | ||
await expect( | ||
// @ts-ignore | ||
objectStorageWrapper.createObject(data, [{ value: 'value1' }], [], ttl) | ||
).to.be.rejectedWith('header "key" is mandatory if header "value" passed'); | ||
}); | ||
describe(`Maester headers maximum amount is exceed (${MAESTER_MAX_SUPPORTED_COUNT_OF_QUERY_HEADERS} items)`, () => { | ||
it('Should throw error', async () => { | ||
await objectStorageWrapper | ||
.createObject(data, genHeaders(MAESTER_MAX_SUPPORTED_COUNT_OF_QUERY_HEADERS + 1), [], ttl) | ||
.catch((error: { message: any }) => { | ||
expect(error.message).to.equal(`maximum available amount of headers is ${MAESTER_MAX_SUPPORTED_COUNT_OF_QUERY_HEADERS}`); | ||
}); | ||
}); | ||
it(`Should throw error, Maester headers maximum amount is exceed (${MAESTER_MAX_SUPPORTED_COUNT_OF_QUERY_HEADERS} items)`, async () => { | ||
await expect( | ||
// @ts-ignore | ||
objectStorageWrapper.createObject(data, genHeaders(MAESTER_MAX_SUPPORTED_COUNT_OF_QUERY_HEADERS + 1), [], ttl) | ||
).to.be.rejectedWith(`maximum available amount of headers is ${MAESTER_MAX_SUPPORTED_COUNT_OF_QUERY_HEADERS}`); | ||
}); | ||
describe('Header used more than one time', () => { | ||
it('Should throw error', async () => { | ||
await objectStorageWrapper | ||
.createObject( | ||
data, | ||
[ | ||
{ key: 'key0', value: 'value0' }, | ||
{ key: 'key0', value: 'value0' }, | ||
], | ||
[], | ||
ttl, | ||
) | ||
.catch((error: { message: any }) => { | ||
expect(error.message).to.equal('header key "key0" was already added'); | ||
}); | ||
}); | ||
it('Should throw error', async () => { | ||
await objectStorageWrapper | ||
.createObject( | ||
data, | ||
genHeaders(1), | ||
[ | ||
{ key: 'key0', value: 'value0' }, | ||
{ key: 'key0', value: 'value0' }, | ||
], | ||
ttl, | ||
) | ||
.catch((error: { message: any }) => { | ||
expect(error.message).to.equal('header key "key0" was already added'); | ||
}); | ||
}); | ||
it('Should throw error, Header used more than one time', async () => { | ||
await expect( | ||
// @ts-ignore | ||
objectStorageWrapper.createObject( | ||
data, | ||
[ | ||
{ key: 'key0', value: 'value0' }, | ||
{ key: 'key1', value: 'value0' }, | ||
{ key: 'key0', value: 'value0' }, | ||
], | ||
[], | ||
ttl, | ||
) | ||
).to.be.rejectedWith('header key "key0" was already added'); | ||
}); | ||
@@ -210,155 +165,95 @@ }); | ||
describe('Lookup object by ID', () => { | ||
describe('Lookup with valid ID', () => { | ||
it('Should successfully return a JSON object', async () => { | ||
nock(maesterUri).get(`/objects/${id}`).reply(200, data); | ||
const result = await objectStorageWrapper.lookupObjectById(id); | ||
expect(result).to.deep.equal(rawData); | ||
beforeEach(async () => { | ||
finalReqCfg = sinon.stub(StorageClient.prototype, <any>'requestRetry').callsFake(async () => ({ data: streamFromObject(data) })); | ||
}); | ||
it('Should successfully return object', async () => { | ||
const result = await objectStorageWrapper.lookupObjectById(id); | ||
expect(JSON.parse(result)).to.be.deep.equal(data); | ||
const { firstArg, lastArg } = finalReqCfg.getCall(0); | ||
expect(lastArg).to.be.deep.equal({}); | ||
expect(firstArg.getFreshStream).to.be.equal(undefined); | ||
expect(firstArg.axiosReqConfig).to.deep.equal({ | ||
method: 'get', | ||
url: '/objects/id123', | ||
responseType: 'stream', | ||
params: {}, | ||
headers: { Authorization: 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6I' } | ||
}); | ||
it('Should successfully return a string', async () => { | ||
nock(maesterUri).get(`/objects/${id}`).reply(200, rawData); | ||
const result = await objectStorageWrapper.lookupObjectById(id); | ||
expect(result).to.deep.equal(rawData); | ||
}); | ||
}); | ||
}); | ||
describe('Lookup objects by query parameters', () => { | ||
describe('Objects not found in Maester', () => { | ||
it('Should successfully return an empty stringified array', async () => { | ||
nock(maesterUri).get(`/objects?query[${queryKey}]=${queryValue}`).reply(200, []); | ||
const result = await objectStorageWrapper.lookupObjectsByQueryParameters([{ key: queryKey, value: queryValue }]); | ||
expect(result).to.deep.equal([]); | ||
}); | ||
beforeEach(async () => { | ||
finalReqCfg = sinon.stub(StorageClient.prototype, <any>'requestRetry').callsFake(async () => ({ | ||
data: streamFromObject([createObjectWithQueriableField, createObjectWithQueriableField]) | ||
})); | ||
}); | ||
describe('Different amount of search params', () => { | ||
describe('valid input', () => { | ||
it('Should successfully return an empty stringified array', async () => { | ||
nock(maesterUri).get('/objects?query[key0]=value0').reply(200, []); | ||
const result = await objectStorageWrapper.lookupObjectsByQueryParameters(genHeaders(1)); | ||
expect(result).to.deep.equal([]); | ||
}); | ||
it('Should successfully return an empty stringified array', async () => { | ||
nock(maesterUri) | ||
.get('/objects?query[key0]=value0&query[key1]=value1&query[key2]=value2&query[key3]=value3&query[key4]=value4') | ||
.reply(200, []); | ||
const result = await objectStorageWrapper.lookupObjectsByQueryParameters(genHeaders(5)); | ||
expect(result).to.deep.equal([]); | ||
}); | ||
describe('One object found in Maester', () => { | ||
it('Should return a stringified array of 1 object', async () => { | ||
nock(maesterUri).get('/objects?query[key0]=value0&query[key1]=value1').reply(200, [createObjectWithQueriableField]); | ||
const result = await objectStorageWrapper.lookupObjectsByQueryParameters(genHeaders(2)); | ||
expect(result).to.deep.equal([createObjectWithQueriableField]); | ||
}); | ||
}); | ||
describe('Two objects found in Maester', () => { | ||
it('Should return a stringified array of 2 objects', async () => { | ||
nock(maesterUri) | ||
.get('/objects?query[key0]=value0&query[key1]=value1&query[key2]=value2') | ||
.reply(200, [createObjectWithQueriableField, anotherCreateObjectWithQueriableField]); | ||
const result = await objectStorageWrapper.lookupObjectsByQueryParameters(genHeaders(3)); | ||
expect(result).to.deep.equal([createObjectWithQueriableField, anotherCreateObjectWithQueriableField]); | ||
}); | ||
}); | ||
it('Should return 2 objects', async () => { | ||
const result = await objectStorageWrapper.lookupObjectsByQueryParameters([{ key: queryKey, value: queryValue }]); | ||
expect(JSON.parse(result)).to.deep.equal([createObjectWithQueriableField, createObjectWithQueriableField]); | ||
const { firstArg, lastArg } = finalReqCfg.getCall(0); | ||
expect(lastArg).to.be.deep.equal({}); | ||
expect(firstArg.getFreshStream).to.be.equal(undefined); | ||
expect(firstArg.axiosReqConfig).to.deep.equal({ | ||
method: 'get', | ||
url: '/objects', | ||
responseType: 'stream', | ||
params: { 'query[baz]': 'bap' }, | ||
headers: { Authorization: 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6I' } | ||
}); | ||
describe('invalid input', () => { | ||
describe('Query key set, query value undefined', () => { | ||
it('Should throw error', async () => { | ||
await objectStorageWrapper | ||
.lookupObjectsByQueryParameters([{ key: 'key0', value: 'value0' }, { key: 'key1' }]) | ||
.catch((error: { message: any }) => { | ||
expect(error.message).to.equal('header "value" is mandatory if header "key" passed'); | ||
}); | ||
}); | ||
}); | ||
describe('Query value set, query key undefined', () => { | ||
it('Should throw error', async () => { | ||
await objectStorageWrapper.lookupObjectsByQueryParameters([{ value: 'value1' }]).catch((error: { message: any }) => { | ||
expect(error.message).to.equal('header "key" is mandatory if header "value" passed'); | ||
}); | ||
}); | ||
}); | ||
describe(`Maester headers maximum amount is exceed (${MAESTER_MAX_SUPPORTED_COUNT_OF_QUERY_HEADERS} items)`, () => { | ||
it('Should throw error', async () => { | ||
await objectStorageWrapper | ||
.lookupObjectsByQueryParameters(genHeaders(MAESTER_MAX_SUPPORTED_COUNT_OF_QUERY_HEADERS + 1)) | ||
.catch((error: { message: any }) => { | ||
expect(error.message).to.equal(`maximum available amount of headers is ${MAESTER_MAX_SUPPORTED_COUNT_OF_QUERY_HEADERS}`); | ||
}); | ||
}); | ||
}); | ||
describe('Header used more than one time', () => { | ||
it('Should throw error', async () => { | ||
await objectStorageWrapper | ||
.lookupObjectsByQueryParameters( | ||
[ | ||
{ key: 'key0', value: 'value0' }, | ||
{ key: 'key0', value: 'value0' }, | ||
], | ||
) | ||
.catch((error: { message: any }) => { | ||
expect(error.message).to.equal('header key "key0" was already added'); | ||
}); | ||
}); | ||
}); | ||
it('At least one header must be present', async () => { | ||
try { | ||
await objectStorageWrapper.lookupObjectsByQueryParameters([]); | ||
} catch (error) { | ||
expect(error.message).to.be.equal('At least one query header must be present'); | ||
} | ||
}); | ||
}); | ||
}); | ||
it('Should throw error: query key set, query value undefined', async () => { | ||
await expect( | ||
// @ts-ignore | ||
objectStorageWrapper.lookupObjectsByQueryParameters([{ key: 'key0', value: 'value0' }, { key: 'key1' }]) | ||
).to.be.rejectedWith('header "value" is mandatory if header "key" passed'); | ||
}); | ||
}); | ||
describe('Update object', () => { | ||
beforeEach(async () => { | ||
finalReqCfg = sinon.stub(StorageClient.prototype, <any>'requestRetry').callsFake(async () => ({ data: createObjectWithQueriableField })); | ||
}); | ||
describe('Valid update request', () => { | ||
it('Should successfully update an object', async () => { | ||
nock(maesterUri).put(`/objects/${id}`).reply(200, updatedData); | ||
const result = await objectStorageWrapper.updateObject(id, updatedData); | ||
expect(result).to.deep.equal(updatedData); | ||
const result = await objectStorageWrapper.updateObjectById(id, updatedData); | ||
expect(result).to.deep.equal(createObjectWithQueriableField); | ||
const { firstArg, lastArg } = finalReqCfg.getCall(0); | ||
expect(lastArg).to.be.deep.equal({}); | ||
const stream = await firstArg.getFreshStream(); | ||
expect(await getStream(stream)).to.be.equal(JSON.stringify(updatedData)); | ||
expect(firstArg.axiosReqConfig).to.be.deep.equal({ | ||
method: 'put', | ||
url: '/objects/id123', | ||
headers: { | ||
Authorization: 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6I', | ||
'content-type': 'application/json' | ||
} | ||
}); | ||
}); | ||
it('Should successfully update an object', async () => { | ||
nock(maesterUri).put(`/objects/${id}`).reply(200, updatedData); | ||
const result = await objectStorageWrapper.updateObject(id, updatedData, []); | ||
expect(result).to.deep.equal(updatedData); | ||
}); | ||
it('Should successfully update an object', async () => { | ||
nock(maesterUri).put(`/objects/${id}`).reply(200, updatedData); | ||
const result = await objectStorageWrapper.updateObject(id, updatedData, null); | ||
expect(result).to.deep.equal(updatedData); | ||
}); | ||
it('Should successfully update an object with headers', async () => { | ||
nock(maesterUri) | ||
.put(`/objects/${id}`) | ||
.matchHeader('x-query-key0', 'value0') | ||
.reply(200, updatedData); | ||
const result = await objectStorageWrapper.updateObject(id, updatedData, genHeaders(1)); | ||
expect(result).to.deep.equal(updatedData); | ||
const result = await objectStorageWrapper.updateObjectById(id, updatedData, genHeaders(3), genHeaders(2)); | ||
expect(result).to.deep.equal(createObjectWithQueriableField); | ||
const { firstArg, lastArg } = finalReqCfg.getCall(0); | ||
expect(lastArg).to.be.deep.equal({}); | ||
const stream = await firstArg.getFreshStream(); | ||
expect(await getStream(stream)).to.be.equal(JSON.stringify(updatedData)); | ||
expect(firstArg.axiosReqConfig).to.be.deep.equal({ | ||
method: 'put', | ||
url: '/objects/id123', | ||
headers: { | ||
Authorization: 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6I', | ||
'content-type': 'application/json', | ||
'x-query-key0': 'value0', | ||
'x-query-key1': 'value1', | ||
'x-query-key2': 'value2', | ||
'x-meta-key0': 'value0', | ||
'x-meta-key1': 'value1' | ||
} | ||
}); | ||
}); | ||
it('Should successfully update an object with headers', async () => { | ||
nock(maesterUri).put(`/objects/${id}`) | ||
.matchHeader('x-query-key0', 'value0') | ||
.matchHeader('x-query-key1', 'value1') | ||
.matchHeader('x-query-key2', 'value2') | ||
.matchHeader('x-query-key3', 'value3') | ||
.matchHeader('x-query-key4', 'value4') | ||
.reply(200, updatedData); | ||
const result = await objectStorageWrapper.updateObject(id, updatedData, genHeaders(5)); | ||
expect(result).to.deep.equal(updatedData); | ||
}); | ||
it('Should successfully update an object with headers', async () => { | ||
nock(maesterUri).put(`/objects/${id}`) | ||
.matchHeader('x-query-key0', 'value0') | ||
.matchHeader('x-query-key1', 'value1') | ||
.matchHeader('x-meta-key0', 'value0') | ||
.matchHeader('x-meta-key1', 'value1') | ||
.reply(200, updatedData); | ||
const result = await objectStorageWrapper.updateObject(id, updatedData, genHeaders(5), genHeaders(2)); | ||
expect(result).to.deep.equal(updatedData); | ||
}); | ||
}); | ||
describe('Invalid update request', () => { | ||
it('Should throw an error', async () => { | ||
await objectStorageWrapper | ||
.updateObject( | ||
await expect( | ||
// @ts-ignore | ||
objectStorageWrapper.updateObjectById( | ||
id, | ||
@@ -371,5 +266,3 @@ updatedData, | ||
) | ||
.catch((error: { message: any }) => { | ||
expect(error.message).to.equal('header key "key0" was already added'); | ||
}); | ||
).to.be.rejectedWith('header key "key0" was already added'); | ||
}); | ||
@@ -379,6 +272,16 @@ }); | ||
describe('Delete object by ID', () => { | ||
describe('ID is valid', () => { | ||
it('Should delete an object', async () => { | ||
nock(maesterUri).delete(`/objects/${id}`).reply(204); | ||
await objectStorageWrapper.deleteObjectById(id); | ||
beforeEach(async () => { | ||
finalReqCfg = sinon.stub(StorageClient.prototype, <any>'requestRetry').callsFake(async () => ({ data: '' })); | ||
}); | ||
it('should delete object by ID', async () => { | ||
const result = await objectStorageWrapper.deleteObjectById(id); | ||
expect(result.data).to.be.equal(''); | ||
const { firstArg, lastArg } = finalReqCfg.getCall(0); | ||
expect(lastArg).to.be.deep.equal({}); | ||
expect(firstArg.getFreshStream).to.be.equal(undefined); | ||
expect(firstArg.axiosReqConfig).to.be.deep.equal({ | ||
method: 'delete', | ||
url: '/objects/id123', | ||
params: {}, | ||
headers: { Authorization: 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6I' } | ||
}); | ||
@@ -388,62 +291,20 @@ }); | ||
describe('Delete object by query parameter', () => { | ||
beforeEach(async () => { | ||
finalReqCfg = sinon.stub(StorageClient.prototype, <any>'requestRetry').callsFake(async () => ({ data: '' })); | ||
}); | ||
describe('Different amount of search params', () => { | ||
describe('valid input', () => { | ||
it('Should successfully delete objects (one param)', async () => { | ||
nock(maesterUri).delete('/objects?query[key0]=value0').reply(200); | ||
await objectStorageWrapper.deleteObjectsByQueryParameters(genHeaders(1)); | ||
}); | ||
it('Should successfully delete objects (two params)', async () => { | ||
nock(maesterUri) | ||
.delete('/objects?query[key0]=value0&query[key1]=value1&query[key2]=value2') | ||
.reply(200, [createObjectWithQueriableField, anotherCreateObjectWithQueriableField]); | ||
await objectStorageWrapper.deleteObjectsByQueryParameters(genHeaders(3)); | ||
}); | ||
it('Should successfully delete objects (five params)', async () => { | ||
nock(maesterUri) | ||
.delete('/objects?query[key0]=value0&query[key1]=value1&query[key2]=value2&query[key3]=value3&query[key4]=value4') | ||
.reply(200, []); | ||
await objectStorageWrapper.deleteObjectsByQueryParameters(genHeaders(5)); | ||
}); | ||
}); | ||
describe('invalid input', () => { | ||
describe('Query key set, query value undefined', () => { | ||
it('Should throw error', async () => { | ||
await objectStorageWrapper | ||
.deleteObjectsByQueryParameters([{ key: 'key0', value: 'value0' }, { key: 'key1' }], ttl) | ||
.catch((error: { message: any }) => { | ||
expect(error.message).to.equal('header "value" is mandatory if header "key" passed'); | ||
}); | ||
const result = await objectStorageWrapper.deleteObjectsByQueryParameters(genHeaders(2)); | ||
expect(result.data).to.be.equal(''); | ||
const { firstArg, lastArg } = finalReqCfg.getCall(0); | ||
expect(lastArg).to.be.deep.equal({}); | ||
expect(firstArg.getFreshStream).to.be.equal(undefined); | ||
expect(firstArg.axiosReqConfig).to.be.deep.equal({ | ||
method: 'delete', | ||
url: '/objects', | ||
params: { 'query[key0]': 'value0', 'query[key1]': 'value1' }, | ||
headers: { Authorization: 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6I' } | ||
}); | ||
}); | ||
describe('Query value set, query key undefined', () => { | ||
it('Should throw error', async () => { | ||
await objectStorageWrapper.deleteObjectsByQueryParameters([{ value: 'value1' }], ttl).catch((error: { message: any }) => { | ||
expect(error.message).to.equal('header "key" is mandatory if header "value" passed'); | ||
}); | ||
}); | ||
}); | ||
describe(`Maester headers maximum amount is exceed (${MAESTER_MAX_SUPPORTED_COUNT_OF_QUERY_HEADERS} items)`, () => { | ||
it('Should throw error', async () => { | ||
await objectStorageWrapper | ||
.deleteObjectsByQueryParameters(genHeaders(MAESTER_MAX_SUPPORTED_COUNT_OF_QUERY_HEADERS + 1), ttl) | ||
.catch((error: { message: any }) => { | ||
expect(error.message).to.equal(`maximum available amount of headers is ${MAESTER_MAX_SUPPORTED_COUNT_OF_QUERY_HEADERS}`); | ||
}); | ||
}); | ||
}); | ||
describe('Header used more than one time', () => { | ||
it('Should throw error', async () => { | ||
await objectStorageWrapper | ||
.deleteObjectsByQueryParameters( | ||
[ | ||
{ key: 'key0', value: 'value0' }, | ||
{ key: 'key0', value: 'value0' }, | ||
], | ||
ttl, | ||
) | ||
.catch((error: { message: any }) => { | ||
expect(error.message).to.equal('header key "key0" was already added'); | ||
}); | ||
}); | ||
}); | ||
}); | ||
@@ -450,0 +311,0 @@ }); |
/* eslint-disable no-unused-expressions */ | ||
import nock from 'nock'; | ||
import sinonjs, { SinonSandbox } from 'sinon'; | ||
import sinon from 'sinon'; | ||
import { expect } from 'chai'; | ||
import { | ||
describe, beforeEach, afterEach, it, | ||
} from 'mocha'; | ||
import { Readable } from 'stream'; | ||
import getStream from 'get-stream'; | ||
import { StorageClient } from '../src/StorageClient'; | ||
import logging from '../src/logger'; | ||
import { streamResponse } from './helpers'; | ||
import { streamFromObject } from './helpers'; | ||
@@ -19,5 +15,4 @@ describe('Storage Client', () => { | ||
}; | ||
const storageClient = new StorageClient(config); | ||
const data = { test: 'test' }; | ||
const responseData = { | ||
@@ -31,51 +26,14 @@ contentLength: 'meta.contentLength', | ||
}; | ||
let putStream: () => Readable; | ||
let sinon: SinonSandbox; | ||
beforeEach(async () => { | ||
sinon = sinonjs.createSandbox(); | ||
putStream = () => { | ||
const stream = new Readable(); | ||
stream.push(JSON.stringify(data)); | ||
stream.push(null); | ||
return stream; | ||
}; | ||
}); | ||
afterEach(() => { | ||
sinon.restore(); | ||
}); | ||
it('should fail after 3 retries', async () => { | ||
const log = sinon.stub(logging, 'warn'); | ||
const storageClient = new StorageClient(config); | ||
const storageClientCalls = nock(config.uri) | ||
// @ts-ignore: Nock .d.ts are outdated. | ||
.matchHeader('authorization', `Bearer ${config.jwtSecret}`) | ||
.get('/objects/1') | ||
.replyWithError({ code: 'ETIMEDOUT' }) | ||
.get('/objects/1') | ||
.reply(404) | ||
.get('/objects/1') | ||
.replyWithError({ code: 'ENOTFOUND' }); | ||
.times(3) | ||
.reply(520); | ||
let err; | ||
try { | ||
await storageClient.readStream('1'); | ||
} catch (e) { | ||
err = e; | ||
} | ||
await expect(storageClient.get('1', {})).to.be.rejectedWith('Server error during request'); | ||
expect(storageClientCalls.isDone()).to.be.true; | ||
expect(err.code).to.be.equal('ENOTFOUND'); | ||
expect(log.getCall(1).args[1].toString()).to.include('404'); | ||
expect(log.callCount).to.be.equal(2); | ||
}); | ||
it('should retry get request 3 times on errors', async () => { | ||
const storageClient = new StorageClient(config); | ||
const storageClientCalls = nock(config.uri) | ||
// @ts-ignore: Nock .d.ts are outdated. | ||
.matchHeader('authorization', `Bearer ${config.jwtSecret}`) | ||
@@ -87,17 +45,11 @@ .get('/objects/1') | ||
.get('/objects/1') | ||
.reply(200, streamResponse(data)); | ||
.reply(200, streamFromObject(data)); | ||
const response = await storageClient.readStream('1'); | ||
const { data: stream } = await storageClient.get('1', {}); | ||
const response = await getStream(stream); | ||
expect(JSON.parse(response)).to.be.deep.equal(data); | ||
expect(storageClientCalls.isDone()).to.be.true; | ||
expect(response.data).to.be.instanceOf(Readable); | ||
const resultData = JSON.parse(await getStream(response.data)); | ||
expect(resultData).to.be.deep.equal(data); | ||
}); | ||
it('should retry post request 3 times on errors', async () => { | ||
const storageClient = new StorageClient(config); | ||
const storageClientCalls = nock(config.uri) | ||
// @ts-ignore: Nock .d.ts are outdated. | ||
.matchHeader('authorization', `Bearer ${config.jwtSecret}`) | ||
@@ -107,41 +59,29 @@ .post('/objects') | ||
.post('/objects') | ||
.reply(400) | ||
.reply(505) | ||
.post('/objects') | ||
.reply(200, responseData); | ||
const response = await storageClient.writeStream(putStream, {}); | ||
const response = await storageClient.post(streamFromObject.bind({}, data)); | ||
expect(response.data).to.be.deep.equal(responseData); | ||
expect(storageClientCalls.isDone()).to.be.true; | ||
}); | ||
it('should accept jwt token on add', async () => { | ||
const storageClient = new StorageClient(config); | ||
it('INSURE IT`S A FRESH STREAM ON EACH RETRY', async () => { | ||
const log = sinon.stub(logging, 'debug'); | ||
const storageClientCalls = nock(config.uri) | ||
// @ts-ignore: Nock .d.ts are outdated. | ||
.matchHeader('authorization', `Bearer ${config.jwtSecret}`) | ||
.post('/objects') | ||
.times(2) | ||
.reply(500) | ||
.post('/objects') | ||
.reply(200, responseData); | ||
const response = await storageClient.writeStream(putStream, {}); | ||
expect(storageClientCalls.isDone()).to.be.true; | ||
const response = await storageClient.post(streamFromObject.bind({}, data), {}); | ||
expect(response.data).to.be.deep.equal(responseData); | ||
expect(storageClientCalls.isDone()).to.be.true; | ||
const firstStreamInstance = log.getCall(0).args[0]; | ||
const secStreamInstance = log.getCall(1).args[0]; | ||
const thirdStreamInstance = log.getCall(2).args[0]; | ||
expect(firstStreamInstance === secStreamInstance).to.be.equal(false); | ||
expect(secStreamInstance === thirdStreamInstance).to.be.equal(false); | ||
}); | ||
it('should accept jwt token on get', async () => { | ||
const storageClient = new StorageClient(config); | ||
const storageClientCalls = nock(config.uri) | ||
// @ts-ignore: Nock .d.ts are outdated. | ||
.matchHeader('authorization', `Bearer ${config.jwtSecret}`) | ||
.get('/objects/1') | ||
.reply(200, streamResponse(data)); | ||
const response = await storageClient.readStream('1'); | ||
expect(storageClientCalls.isDone()).to.be.true; | ||
expect(response.data).to.be.instanceOf(Readable); | ||
const resultData = JSON.parse(await getStream(response.data)); | ||
expect(resultData).to.be.deep.equal(data); | ||
}); | ||
}); |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 4 instances in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 2 instances in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
129849
32
2136
1
8
25
10
+ Addedaws-sdk@2.1152.0
+ Addedform-data@4.0.0
+ Addedjsonwebtoken@8.5.1
+ Addedasynckit@0.4.0(transitive)
+ Addedaws-sdk@2.1152.0(transitive)
+ Addedbase64-js@1.5.1(transitive)
+ Addedbuffer@4.9.2(transitive)
+ Addedbuffer-equal-constant-time@1.0.1(transitive)
+ Addedcombined-stream@1.0.8(transitive)
+ Addeddelayed-stream@1.0.0(transitive)
+ Addedecdsa-sig-formatter@1.0.11(transitive)
+ Addedevents@1.1.1(transitive)
+ Addedform-data@4.0.0(transitive)
+ Addedieee754@1.1.13(transitive)
+ Addedisarray@1.0.0(transitive)
+ Addedjmespath@0.16.0(transitive)
+ Addedjsonwebtoken@8.5.1(transitive)
+ Addedjwa@1.4.1(transitive)
+ Addedjws@3.2.2(transitive)
+ Addedlodash.includes@4.3.0(transitive)
+ Addedlodash.isboolean@3.0.3(transitive)
+ Addedlodash.isinteger@4.0.4(transitive)
+ Addedlodash.isnumber@3.0.3(transitive)
+ Addedlodash.isplainobject@4.0.6(transitive)
+ Addedlodash.isstring@4.0.1(transitive)
+ Addedlodash.once@4.1.1(transitive)
+ Addedmime-db@1.52.0(transitive)
+ Addedmime-types@2.1.35(transitive)
+ Addedms@2.1.3(transitive)
+ Addedpunycode@1.3.2(transitive)
+ Addedquerystring@0.2.0(transitive)
+ Addedsafe-buffer@5.2.1(transitive)
+ Addedsax@1.2.1(transitive)
+ Addedsemver@5.7.2(transitive)
+ Addedurl@0.10.3(transitive)
+ Addeduuid@8.0.0(transitive)
+ Addedxml2js@0.4.19(transitive)
+ Addedxmlbuilder@9.0.7(transitive)