Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@elastic.io/object-storage-client

Package Overview
Dependencies
Maintainers
15
Versions
23
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@elastic.io/object-storage-client - npm Package Compare versions

Comparing version 0.0.2-dev to 0.0.3-dev1

19

dist/object-storage.d.ts
/// <reference types="node" />
import { Readable, Duplex } from 'stream';
interface JWTPayload {
[index: string]: string;
}
import { StorageClientConfig, JWTPayload } from './storage-client';
export interface ObjectDTO {

@@ -15,13 +13,10 @@ stream?: Readable;

private reverses;
constructor(config: {
uri: string;
jwtSecret?: string;
});
constructor(config: StorageClientConfig);
use(forward: TransformMiddleware, reverse: TransformMiddleware): ObjectStorage;
getAsJSON(objectId: string, jwtPayloadOrToken?: JWTPayload | string): Promise<any>;
getAsStream(objectId: string, jwtPayloadOrToken?: JWTPayload | string): Promise<ObjectDTO>;
addAsStream(stream: Readable, jwtPayloadOrToken?: JWTPayload | string): Promise<string>;
getAsJSON(objectId: string, jwtPayload?: JWTPayload): Promise<any>;
getAsStream(objectId: string, jwtPayload?: JWTPayload): Promise<ObjectDTO>;
deleteOne(objectId: string, jwtPayload?: JWTPayload): Promise<void>;
addAsStream(stream: Readable, jwtPayload?: JWTPayload): Promise<string>;
private applyMiddlewares;
addAsJSON(data: any, jwtPayloadOrToken: JWTPayload | string): Promise<string>;
addAsJSON(data: any, jwtPayload?: JWTPayload): Promise<string>;
}
export {};

@@ -10,6 +10,5 @@ "use strict";

;
;
class ObjectStorage {
constructor(config) {
this.client = new storage_client_1.default({ uri: config.uri, jwtSecret: config.jwtSecret });
this.client = new storage_client_1.default(config);
this.forwards = [];

@@ -24,15 +23,18 @@ this.reverses = [];

// eslint-disable-next-line @typescript-eslint/no-explicit-any
async getAsJSON(objectId, jwtPayloadOrToken) {
const objectDTO = await this.getAsStream(objectId, jwtPayloadOrToken);
async getAsJSON(objectId, jwtPayload) {
const objectDTO = await this.getAsStream(objectId, jwtPayload);
const data = await get_stream_1.default(objectDTO.stream);
return JSON.parse(data);
}
async getAsStream(objectId, jwtPayloadOrToken) {
const res = await this.client.readStream(objectId, jwtPayloadOrToken);
async getAsStream(objectId, jwtPayload) {
const res = await this.client.readStream(objectId, { jwtPayload });
const resultStream = this.applyMiddlewares(res.data, this.reverses);
return { stream: resultStream, headers: res.headers };
}
async addAsStream(stream, jwtPayloadOrToken) {
async deleteOne(objectId, jwtPayload) {
await this.client.deleteOne(objectId, { jwtPayload });
}
async addAsStream(stream, jwtPayload) {
const resultStream = this.applyMiddlewares(stream, this.forwards);
const res = await this.client.writeStream(resultStream, jwtPayloadOrToken);
const res = await this.client.writeStream(resultStream, { jwtPayload });
return res.data.objectId;

@@ -46,3 +48,3 @@ }

// eslint-disable-next-line @typescript-eslint/no-explicit-any
async addAsJSON(data, jwtPayloadOrToken) {
async addAsJSON(data, jwtPayload) {
const dataString = JSON.stringify(data);

@@ -52,5 +54,5 @@ const dataStream = new stream_1.Readable();

dataStream.push(null);
return this.addAsStream(dataStream, jwtPayloadOrToken);
return this.addAsStream(dataStream, jwtPayload);
}
}
exports.default = ObjectStorage;
/// <reference types="node" />
import { AxiosResponse } from 'axios';
import { Readable } from 'stream';
interface JWTPayload {
declare const AXIOS_CONFIG_NAMESPACE = "object-storage-client";
export interface JWTPayload {
[index: string]: string;
}
export interface RequestOptions {
maxAttempts?: number;
delay?: number;
onResponse?: (err: Error, res: AxiosResponse) => boolean;
export interface ObjectOptions {
ttl: number;
}
export interface StorageClientRequestConfig {
jwtPayload?: JWTPayload;
objectOptions?: ObjectOptions;
retryDelay?: number;
retryCount?: number;
}
export interface StorageClientConfig {
uri: string;
jwtSecret?: string;
jwtToken?: string;
defaultRequestConfig?: StorageClientRequestConfig;
}
declare module 'axios' {
interface AxiosRequestConfig {
[AXIOS_CONFIG_NAMESPACE]?: StorageClientRequestConfig;
}
}
export default class StorageClient {
private api;
private readonly jwtSecret?;
private readonly api;
private static readonly DEFAULT_RETRY_DELAY;
private static readonly DEFAULT_RETRY_COUNT;
private static httpAgent;
private static httpsAgent;
constructor(config: {
uri: string;
jwtSecret?: string;
});
private requestRetry;
private getHeaders;
readStream(objectId: string, jwtPayloadOrToken?: JWTPayload | string): Promise<AxiosResponse>;
writeStream(stream: Readable, jwtPayloadOrToken?: JWTPayload | string): Promise<AxiosResponse>;
constructor(config: StorageClientConfig);
readStream(objectId: string, config?: StorageClientRequestConfig): Promise<AxiosResponse>;
writeStream(stream: Readable, config?: StorageClientRequestConfig): Promise<AxiosResponse>;
deleteOne(objectId: string, config?: StorageClientRequestConfig): Promise<AxiosResponse>;
private static retryCondition;
private static createApiClient;
}
export {};

@@ -5,69 +5,119 @@ "use strict";

};
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
result["default"] = mod;
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
const axios_1 = __importDefault(require("axios"));
const axios_retry_1 = __importStar(require("axios-retry"));
const http_1 = __importDefault(require("http"));
const https_1 = __importDefault(require("https"));
const logger_1 = __importDefault(require("./logger"));
const jsonwebtoken_1 = require("jsonwebtoken");
const util_1 = require("util");
const logger_1 = __importDefault(require("./logger"));
const AXIOS_CONFIG_NAMESPACE = 'object-storage-client';
const AXIOS_RETRY_CONFIG_NAMESPACE = 'axios-retry';
var ObjectHeaders;
(function (ObjectHeaders) {
ObjectHeaders["ttl"] = "x-eio-ttl";
})(ObjectHeaders || (ObjectHeaders = {}));
;
;
class StorageClient {
constructor(config) {
this.api = axios_1.default.create({
baseURL: `${config.uri}/`,
const { uri, jwtSecret, defaultRequestConfig, jwtToken } = config;
this.api = StorageClient.createApiClient(jwtSecret, jwtToken, uri, defaultRequestConfig);
}
async readStream(objectId, config) {
const res = await this.api.get(`/objects/${objectId}`, {
responseType: 'stream',
[AXIOS_CONFIG_NAMESPACE]: config
});
return res;
}
async writeStream(stream, config) {
const res = await this.api.post(`/objects`, stream, {
headers: {
'content-type': 'application/octet-stream'
},
[AXIOS_CONFIG_NAMESPACE]: config
});
return res;
}
async deleteOne(objectId, config) {
const res = await this.api.delete(`/objects/${objectId}`, {
[AXIOS_CONFIG_NAMESPACE]: config
});
return res;
}
static retryCondition(err) {
if (axios_retry_1.isNetworkError(err)) {
logger_1.default.warn('Error during object request: %s', err);
return true;
}
const { response } = err;
if (!response) {
return false;
}
if (response.status === 429 || (response.status >= 500 && response.status <= 599)) {
logger_1.default.warn('Error during object request: %s', `${response.status} (${response.statusText})`);
return true;
}
return false;
}
static createApiClient(jwtSecret, jwtToken, uri, requestConfig) {
if (!jwtSecret && !jwtToken) {
throw new Error('Neither JWT token passed, nor JWT secret provided.');
}
const api = axios_1.default.create({
baseURL: `${uri}/`,
httpAgent: StorageClient.httpAgent,
httpsAgent: StorageClient.httpsAgent,
validateStatus: null,
maxContentLength: Infinity,
maxRedirects: 0
maxContentLength: Infinity
});
this.jwtSecret = config.jwtSecret;
}
async requestRetry(request, { maxAttempts = 3, delay = 100, onResponse } = {}) {
let attempts = 0;
let res;
let err;
while (attempts < maxAttempts) {
err = null;
res = null;
attempts++;
try {
res = await request();
if (jwtToken) {
api.defaults.headers.common.Authorization = `Bearer ${jwtToken}`;
}
// pls do not move axios interceptors init order,
// since middleware below sets values for retry meddileware, which sould be next
api.interceptors.request.use(async (config) => {
const { [AXIOS_CONFIG_NAMESPACE]: cfg } = config;
if (!cfg) {
return config;
}
catch (e) {
err = e;
let newConfig = Object.assign({}, config, { [AXIOS_RETRY_CONFIG_NAMESPACE]: config[AXIOS_RETRY_CONFIG_NAMESPACE] || {} });
const { jwtPayload, retryDelay, retryCount, objectOptions } = cfg;
if (jwtPayload) {
if (!jwtSecret) {
throw new Error('JWT secret is not provided during initialization, can not encode JWT payload');
}
const token = await util_1.promisify(jsonwebtoken_1.sign)(jwtPayload, jwtSecret);
newConfig.headers.Authorization = `Bearer ${token}`;
}
if (onResponse && onResponse(err, res)) {
continue;
;
if (typeof retryDelay === 'number') {
newConfig['axios-retry'].retryDelay = () => retryDelay;
}
// last attempt error should not be logged
if ((err || res.status >= 400) && attempts < maxAttempts) {
logger_1.default.warn('Error during object request: %s', err || `${res.status} (${res.statusText})`);
await new Promise((resolve) => setTimeout(resolve, delay));
continue;
if (typeof retryCount === 'number') {
newConfig['axios-retry'].retries = retryCount;
}
break;
}
if (err || res.status >= 400) {
throw err || new Error(`HTTP error during object request: ${res.status} (${res.statusText})`);
}
return res;
if (objectOptions) {
newConfig.headers[ObjectHeaders.ttl] = objectOptions.ttl;
}
return newConfig;
});
axios_retry_1.default(api, {
retries: requestConfig && typeof requestConfig.retryCount === 'number'
? requestConfig.retryCount
: this.DEFAULT_RETRY_COUNT,
retryDelay: () => requestConfig && typeof requestConfig.retryDelay === 'number'
? requestConfig.retryDelay
: this.DEFAULT_RETRY_DELAY,
retryCondition: StorageClient.retryCondition
});
return api;
}
async getHeaders(jwtPayloadOrToken, override) {
if (typeof jwtPayloadOrToken !== 'string' && !this.jwtSecret) {
throw new Error('Neither JWT token passed, nor JWT secret provided during initialization');
}
const token = typeof jwtPayloadOrToken === 'string'
? jwtPayloadOrToken
: await util_1.promisify(jsonwebtoken_1.sign)(jwtPayloadOrToken, this.jwtSecret);
return Object.assign({ Authorization: `Bearer ${token}` }, override);
}
async readStream(objectId, jwtPayloadOrToken) {
const res = await this.requestRetry(async () => this.api.get(`/objects/${objectId}`, { responseType: 'stream', headers: await this.getHeaders(jwtPayloadOrToken) }));
return res;
}
async writeStream(stream, jwtPayloadOrToken) {
const res = await this.requestRetry(async () => this.api.post(`/objects`, stream, { headers: await this.getHeaders(jwtPayloadOrToken, { 'content-type': 'application/octet-stream' }) }));
return res;
}
}

@@ -74,0 +124,0 @@ StorageClient.httpAgent = new http_1.default.Agent({ keepAlive: true });

{
"name": "@elastic.io/object-storage-client",
"version": "0.0.2-dev",
"version": "0.0.3-dev1",
"description": "Elastic.io Message Store Client",
"main": "dist/index.js",
"engines": {
"node": ">= 10"
},
"scripts": {

@@ -18,3 +15,4 @@ "lint": "eslint --ext .ts .",

"@elastic.io/bunyan-logger": "1.0.5",
"axios": "0.19.0",
"axios": "0.21.0",
"axios-retry": "3.1.9",
"get-stream": "5.1.0",

@@ -21,0 +19,0 @@ "jsonwebtoken": "8.5.1",

@@ -11,9 +11,5 @@ import nock from 'nock';

import { streamResponse, encryptStream, decryptStream, zip, unzip } from './helpers';
import { JWTPayload } from '../src/storage-client';
describe('Object Storage', () => {
const config = {
uri: 'https://ma.es.ter',
jwtSecret: 'jwt'
};
const postData = { test: 'test' };

@@ -44,247 +40,664 @@

function authHeaderMatch (jwtPayload?: { [index: string]: string }) {
return (val: string) => {
const decoded = verify(val.split(' ')[1], config.jwtSecret);
if (jwtPayload) {
expect(decoded).to.deep.include(jwtPayload);
}
return decoded;
};
}
it('should throw exception if neither jwt secret, nor jwt token provided', async () => {
let err;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
let client: ObjectStorage; // eslint-disable-line no-unused-vars
try {
client = new ObjectStorage({ uri: '' });
} catch (e) {
err = e;
}
describe('basic', () => {
describe('data mode', () => {
it('should fail after 3 retries', async () => {
const log = sinon.stub(logging, 'warn');
const objectStorage = new ObjectStorage(config);
expect(err.toString()).to.include('JWT');
});
const objectStorageCalls = nock(config.uri)
// @ts-ignore: Nock .d.ts are outdated.
.matchHeader('authorization', authHeaderMatch())
.get('/objects/1')
.replyWithError({ code: 'ETIMEDOUT' })
.get('/objects/1')
.reply(404)
.get('/objects/1')
.replyWithError({ code: 'ENOTFOUND' });
describe('when jwt payload provided', () => {
const config = {
uri: 'https://ma.es.ter',
jwtSecret: 'jwt'
};
let err;
try {
await objectStorage.getAsJSON('1', {});
} catch (e) {
err = e;
function authHeaderMatch(jwtPayload?: JWTPayload) {
return (val: string) => {
const decoded = verify(val.split(' ')[1], config.jwtSecret);
if (jwtPayload) {
expect(decoded).to.deep.include(jwtPayload);
}
return decoded;
};
}
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);
});
describe('basic', () => {
describe('data mode', () => {
it('should fail after 3 retries', async () => {
const log = sinon.stub(logging, 'warn');
const objectStorage = new ObjectStorage(config);
it('should retry get request 3 times on errors', async () => {
const objectStorage = new ObjectStorage(config);
const objectStorageCalls = nock(config.uri)
// @ts-ignore: Nock .d.ts are outdated.
.matchHeader('authorization', authHeaderMatch())
.get('/objects/1')
.replyWithError({ code: 'ETIMEDOUT' })
.get('/objects/1')
.reply(429)
.get('/objects/1')
.replyWithError({ code: 'ECONNRESET' })
.get('/objects/1')
.replyWithError({ code: 'EHOSTUNREACH' });
const objectStorageCalls = nock(config.uri)
// @ts-ignore: Nock .d.ts are outdated.
.matchHeader('authorization', authHeaderMatch())
.get('/objects/1')
.reply(500)
.get('/objects/1')
.replyWithError({ code: 'ECONNRESET' })
.get('/objects/1')
.reply(200, streamResponse(responseData));
let err;
try {
await objectStorage.getAsJSON('1', {});
} catch (e) {
err = e;
}
const out = await objectStorage.getAsJSON('1', {});
expect(objectStorageCalls.isDone()).to.be.true;
expect(err.code).to.be.equal('EHOSTUNREACH');
expect(log.getCall(1).args[1].toString()).to.include('429');
expect(log.callCount).to.be.equal(4);
});
expect(objectStorageCalls.isDone()).to.be.true;
expect(out).to.be.deep.equal(responseData);
});
it('should retry get request 3 times on errors', async () => {
const objectStorage = new ObjectStorage(config);
it('should retry post request 3 times on errors', async () => {
const objectStorage = new ObjectStorage(config);
const objectStorageCalls = nock(config.uri)
// @ts-ignore: Nock .d.ts are outdated.
.matchHeader('authorization', authHeaderMatch())
.get('/objects/1')
.reply(500)
.get('/objects/1')
.replyWithError({ code: 'ECONNRESET' })
.get('/objects/1')
.replyWithError({ code: 'EHOSTUNREACH' })
.get('/objects/1')
.reply(200, streamResponse(responseData));
const objectStorageCalls = nock(config.uri)
// @ts-ignore: Nock .d.ts are outdated.
.matchHeader('authorization', authHeaderMatch())
.post('/objects')
.replyWithError({ code: 'ECONNREFUSED' })
.post('/objects')
.reply(400)
.post('/objects')
.reply(200, responseData);
const out = await objectStorage.getAsJSON('1', {});
await objectStorage.addAsJSON(postData, {});
expect(objectStorageCalls.isDone()).to.be.true;
expect(out).to.be.deep.equal(responseData);
});
expect(objectStorageCalls.isDone()).to.be.true;
});
it('should retry post request 3 times on errors', async () => {
const objectStorage = new ObjectStorage(config);
it('should accept jwt token on add', async () => {
const objectStorage = new ObjectStorage({ uri: config.uri });
const objectStorageCalls = nock(config.uri)
// @ts-ignore: Nock .d.ts are outdated.
.matchHeader('authorization', authHeaderMatch())
.post('/objects')
.replyWithError({ code: 'ECONNREFUSED' })
.post('/objects')
.reply(429)
.post('/objects')
.replyWithError({ code: 'EHOSTUNREACH' })
.post('/objects')
.reply(200, responseData);
const jwtPayload = { tenantId: '12', contractId: '1' };
const objectStorageCalls = nock(config.uri)
// @ts-ignore: Nock .d.ts are outdated.
.matchHeader('authorization', authHeaderMatch(jwtPayload))
.post('/objects')
.reply(200);
await objectStorage.addAsJSON(postData, {});
const objectId = await objectStorage.addAsJSON(postData, sign(jwtPayload, config.jwtSecret));
expect(objectStorageCalls.isDone()).to.be.true;
});
expect(objectStorageCalls.isDone()).to.be.true;
expect(objectId).to.match(/^[0-9a-z-]+$/);
});
it('should accept jwt token on add', async () => {
const objectStorage = new ObjectStorage(config);
it('should accept jwt token on get', async () => {
const objectStorage = new ObjectStorage({ uri: config.uri });
const jwtPayload = { tenantId: '12', contractId: '1' };
const objectStorageCalls = nock(config.uri)
// @ts-ignore: Nock .d.ts are outdated.
.matchHeader('authorization', authHeaderMatch(jwtPayload))
.post('/objects')
.reply(200);
const jwtPayload = { tenantId: '12', contractId: '1' };
const objectStorageCalls = nock(config.uri)
// @ts-ignore: Nock .d.ts are outdated.
.matchHeader('authorization', authHeaderMatch(jwtPayload))
.get('/objects/1')
.reply(200, streamResponse(responseData));
const objectId = await objectStorage.addAsJSON(postData, jwtPayload);
const out = await objectStorage.getAsJSON('1', sign(jwtPayload, config.jwtSecret));
expect(objectStorageCalls.isDone()).to.be.true;
expect(objectId).to.match(/^[0-9a-z-]+$/);
});
expect(objectStorageCalls.isDone()).to.be.true;
expect(out).to.be.deep.equal(responseData);
it('should accept jwt token on get', async () => {
const objectStorage = new ObjectStorage(config);
const jwtPayload = { tenantId: '12', contractId: '1' };
const objectStorageCalls = nock(config.uri)
// @ts-ignore: Nock .d.ts are outdated.
.matchHeader('authorization', authHeaderMatch(jwtPayload))
.get('/objects/1')
.reply(200, streamResponse(responseData));
const out = await objectStorage.getAsJSON('1', jwtPayload);
expect(objectStorageCalls.isDone()).to.be.true;
expect(out).to.be.deep.equal(responseData);
});
it('should accept jwt token on delete', async () => {
const objectStorage = new ObjectStorage(config);
const jwtPayload = { tenantId: '12', contractId: '1' };
const objectStorageCalls = nock(config.uri)
// @ts-ignore: Nock .d.ts are outdated.
.matchHeader('authorization', authHeaderMatch(jwtPayload))
.delete('/objects/1')
.reply(204);
await objectStorage.deleteOne('1', jwtPayload);
expect(objectStorageCalls.isDone()).to.be.true;
});
it('should throw exception if no jwt secret provided but with JWT payload', async () => {
const objectStorage = new ObjectStorage({ uri: config.uri, jwtToken: 'token' });
let err;
try {
await objectStorage.getAsJSON('1', {});
} catch (e) {
err = e;
}
expect(err.toString()).to.include('JWT');
});
});
it('should throw exception if neither jwt secret, nor jwt token provided', async () => {
const objectStorage = new ObjectStorage({ uri: config.uri });
describe('stream mode', () => {
it('should fail after 3 get retries', async () => {
const log = sinon.stub(logging, 'warn');
const objectStorage = new ObjectStorage(config);
let err;
try {
await objectStorage.getAsJSON('1', {});
} catch (e) {
err = e;
}
const objectStorageCalls = nock(config.uri)
// @ts-ignore: Nock .d.ts are outdated.
.matchHeader('authorization', authHeaderMatch())
.get('/objects/1')
.replyWithError({ code: 'ETIMEDOUT' })
.get('/objects/1')
.reply(429)
.get('/objects/1')
.replyWithError({ code: 'ECONNRESET' })
.get('/objects/1')
.replyWithError({ code: 'EHOSTUNREACH' });
expect(err.toString()).to.include('JWT');
let err;
try {
await objectStorage.getAsStream('1', {});
} catch (e) {
err = e;
}
expect(objectStorageCalls.isDone()).to.be.true;
expect(err.code).to.be.equal('EHOSTUNREACH');
expect(log.getCall(1).args[1].toString()).to.include('429');
expect(log.callCount).to.be.equal(4);
});
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', authHeaderMatch())
.get('/objects/1')
.reply(500)
.get('/objects/1')
.reply(200, streamResponse(responseData));
const response = await objectStorage.getAsStream('1', {});
const out = JSON.parse(await getStream(response.stream));
expect(objectStorageCalls.isDone()).to.be.true;
expect(out).to.be.deep.equal(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', authHeaderMatch())
.post('/objects')
.replyWithError({ code: 'ECONNREFUSED' })
.post('/objects')
.replyWithError({ code: 'ECONNRESET' })
.post('/objects')
.replyWithError({ code: 'ETIMEDOUT' })
.post('/objects')
.replyWithError({ code: 'EHOSTUNREACH' });
let err;
try {
await objectStorage.addAsStream(postStream, {});
} catch (e) {
err = e;
}
expect(objectStorageCalls.isDone()).to.be.true;
expect(err.code).to.be.equal('EHOSTUNREACH');
});
it('should throw an error on post request http error', async () => {
const objectStorage = new ObjectStorage(config);
const objectStorageCalls = nock(config.uri)
// @ts-ignore: Nock .d.ts are outdated.
.matchHeader('authorization', authHeaderMatch())
.post('/objects')
.reply(500)
.post('/objects')
.reply(503)
.post('/objects')
.reply(502)
.post('/objects')
.reply(429);
let err;
try {
await objectStorage.addAsStream(postStream, {});
} catch (e) {
err = e;
}
expect(objectStorageCalls.isDone()).to.be.true;
expect(err.toString()).to.include('429');
});
it('should post successfully', async () => {
const objectStorage = new ObjectStorage(config);
const objectStorageCalls = nock(config.uri)
// @ts-ignore: Nock .d.ts are outdated.
.matchHeader('authorization', authHeaderMatch())
.post('/objects')
.reply(200);
const objectId = await objectStorage.addAsStream(postStream, {});
expect(objectStorageCalls.isDone()).to.be.true;
expect(objectId).to.match(/^[0-9a-z-]+$/);
});
it('should use valid jwt token', async () => {
const objectStorage = new ObjectStorage(config);
const jwtPayload = { tenantId: '12', contractId: '1' };
const objectStorageCalls = nock(config.uri)
// @ts-ignore: Nock .d.ts are outdated.
.matchHeader('authorization', authHeaderMatch(jwtPayload))
.post('/objects')
.reply(200);
const objectId = await objectStorage.addAsStream(postStream, jwtPayload);
expect(objectStorageCalls.isDone()).to.be.true;
expect(objectId).to.match(/^[0-9a-z-]+$/);
});
});
});
describe('stream mode', () => {
it('should fail after 3 get retries', async () => {
const log = sinon.stub(logging, 'warn');
const objectStorage = new ObjectStorage(config);
describe('middlewares + zip/unzip and encrypt/decrypt', () => {
describe('data mode', () => {
it('should fail after 3 retries', async () => {
const objectStorageWithMiddlewares = new ObjectStorage(config);
objectStorageWithMiddlewares.use(encryptStream, decryptStream);
objectStorageWithMiddlewares.use(zip, unzip);
const log = sinon.stub(logging, 'warn');
const objectStorageWithMiddlewaresCalls = nock(config.uri)
// @ts-ignore: Nock .d.ts are outdated.
.matchHeader('authorization', authHeaderMatch())
.get('/objects/1')
.replyWithError({ code: 'ETIMEDOUT' })
.get('/objects/1')
.reply(429)
.get('/objects/1')
.replyWithError({ code: 'EHOSTUNREACH' })
.get('/objects/1')
.replyWithError({ code: 'ECONNREFUSED' });
const objectStorageCalls = nock(config.uri)
// @ts-ignore: Nock .d.ts are outdated.
.matchHeader('authorization', authHeaderMatch())
.get('/objects/1')
.replyWithError({ code: 'ETIMEDOUT' })
.get('/objects/1')
.reply(404)
.get('/objects/1')
.replyWithError({ code: 'ENOTFOUND' });
let err;
try {
await objectStorageWithMiddlewares.getAsJSON('1', {});
} catch (e) {
err = e;
}
let err;
try {
await objectStorage.getAsStream('1', {});
} catch (e) {
err = e;
}
expect(objectStorageWithMiddlewaresCalls.isDone()).to.be.true;
expect(err.code).to.be.equal('ECONNREFUSED');
expect(log.getCall(1).args[1].toString()).to.include('429');
expect(log.callCount).to.be.equal(4);
});
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 3 times on errors', 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', authHeaderMatch())
.get('/objects/1')
.reply(500)
.get('/objects/1')
.replyWithError({ code: 'ECONNRESET' })
.get('/objects/1')
.replyWithError({ code: 'ECONNREFUSED' })
.get('/objects/1')
.reply(200, () => {
const stream = streamResponse(responseData)();
return stream.pipe(encryptStream()).pipe(zip());
});
it('should retry get request on errors', async () => {
const objectStorage = new ObjectStorage(config);
const out = await objectStorageWithMiddlewares.getAsJSON('1', {});
const objectStorageCalls = nock(config.uri)
// @ts-ignore: Nock .d.ts are outdated.
.matchHeader('authorization', authHeaderMatch())
.get('/objects/1')
.reply(500)
.get('/objects/1')
.reply(200, streamResponse(responseData));
expect(objectStorageWithMiddlewaresCalls.isDone()).to.be.true;
expect(out).to.be.deep.equal(responseData);
});
const response = await objectStorage.getAsStream('1', {});
it('should retry post request 3 times on errors', 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', authHeaderMatch())
.post('/objects')
.replyWithError({ code: 'ECONNREFUSED' })
.post('/objects')
.replyWithError({ code: 'ECONNRESET' })
.post('/objects')
.reply(429)
.post('/objects')
.reply(200, responseData);
const out = JSON.parse(await getStream(response.stream));
await objectStorageWithMiddlewares.addAsJSON(postData, {});
expect(objectStorageCalls.isDone()).to.be.true;
expect(out).to.be.deep.equal(responseData);
});
expect(objectStorageWithMiddlewaresCalls.isDone()).to.be.true;
});
it('should throw an error on put request connection error', async () => {
const objectStorage = new ObjectStorage(config);
it('should accept jwt token on add', async () => {
const objectStorageWithMiddlewares = new ObjectStorage(config);
objectStorageWithMiddlewares.use(encryptStream, decryptStream);
objectStorageWithMiddlewares.use(zip, unzip);
const jwtPayload = { tenantId: '12', contractId: '1' };
const objectStorageWithMiddlewaresCalls = nock(config.uri)
// @ts-ignore: Nock .d.ts are outdated.
.matchHeader('authorization', authHeaderMatch(jwtPayload))
.post('/objects')
.reply(200);
const objectStorageCalls = nock(config.uri)
// @ts-ignore: Nock .d.ts are outdated.
.matchHeader('authorization', authHeaderMatch())
.post('/objects')
.replyWithError({ code: 'ECONNREFUSED' })
.post('/objects')
.replyWithError({ code: 'ECONNREFUSED' })
.post('/objects')
.replyWithError({ code: 'ECONNREFUSED' });
const objectId = await objectStorageWithMiddlewares.addAsJSON(postData, jwtPayload);
let err;
try {
await objectStorage.addAsStream(postStream, {});
} catch (e) {
err = e;
}
expect(objectStorageWithMiddlewaresCalls.isDone()).to.be.true;
expect(objectId).to.match(/^[0-9a-z-]+$/);
});
expect(objectStorageCalls.isDone()).to.be.true;
expect(err.code).to.be.equal('ECONNREFUSED');
});
it('should accept jwt token on get', async () => {
const objectStorageWithMiddlewares = new ObjectStorage(config);
objectStorageWithMiddlewares.use(encryptStream, decryptStream);
objectStorageWithMiddlewares.use(zip, unzip);
it('should throw an error on put request http error', async () => {
const objectStorage = new ObjectStorage(config);
const jwtPayload = { tenantId: '12', contractId: '1' };
const objectStorageWithMiddlewaresCalls = nock(config.uri)
// @ts-ignore: Nock .d.ts are outdated.
.matchHeader('authorization', authHeaderMatch(jwtPayload))
.get('/objects/1')
.reply(200, () => {
const stream = streamResponse(responseData)();
return stream.pipe(encryptStream()).pipe(zip());
});
const objectStorageCalls = nock(config.uri)
// @ts-ignore: Nock .d.ts are outdated.
.matchHeader('authorization', authHeaderMatch())
.post('/objects')
.reply(409)
.post('/objects')
.reply(409)
.post('/objects')
.reply(409);
const out = await objectStorageWithMiddlewares.getAsJSON('1', jwtPayload);
let err;
try {
await objectStorage.addAsStream(postStream, {});
} catch (e) {
err = e;
}
expect(objectStorageCalls.isDone()).to.be.true;
expect(err.toString()).to.include('409');
});
expect(objectStorageWithMiddlewaresCalls.isDone()).to.be.true;
expect(out).to.be.deep.equal(responseData);
});
it('should put successfully', async () => {
const objectStorage = new ObjectStorage(config);
it('should add 2 objects successfully', async () => {
const objectStorageWithMiddlewares = new ObjectStorage(config);
objectStorageWithMiddlewares.use(encryptStream, decryptStream);
objectStorageWithMiddlewares.use(zip, unzip);
const jwtPayload = { tenantId: '12', contractId: '1' };
const objectStorageWithMiddlewaresCalls = nock(config.uri)
// @ts-ignore: Nock .d.ts are outdated.
.matchHeader('authorization', authHeaderMatch(jwtPayload))
.post('/objects')
.reply(200, { objectId: '1' })
.post('/objects')
.reply(200, { objectId: '2' });
const objectStorageCalls = nock(config.uri)
// @ts-ignore: Nock .d.ts are outdated.
.matchHeader('authorization', authHeaderMatch())
.post('/objects')
.reply(200);
const objectIdFirst = await objectStorageWithMiddlewares.addAsJSON(postData, jwtPayload);
const objectIdSecond = await objectStorageWithMiddlewares.addAsJSON(postData, jwtPayload);
expect(objectStorageWithMiddlewaresCalls.isDone()).to.be.true;
expect(objectIdFirst).to.be.equal('1');
expect(objectIdSecond).to.be.equal('2');
});
const objectId = await objectStorage.addAsStream(postStream, {});
it('should get 2 objects successfully', async () => {
const objectStorageWithMiddlewares = new ObjectStorage(config);
objectStorageWithMiddlewares.use(encryptStream, decryptStream);
objectStorageWithMiddlewares.use(zip, unzip);
expect(objectStorageCalls.isDone()).to.be.true;
expect(objectId).to.match(/^[0-9a-z-]+$/);
const jwtPayload = { tenantId: '12', contractId: '1' };
const objectStorageWithMiddlewaresCalls = nock(config.uri)
// @ts-ignore: Nock .d.ts are outdated.
.matchHeader('authorization', authHeaderMatch(jwtPayload))
.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());
});
const outFirst = await objectStorageWithMiddlewares.getAsJSON('1', jwtPayload);
const outSecond = await objectStorageWithMiddlewares.getAsJSON('2', jwtPayload);
expect(objectStorageWithMiddlewaresCalls.isDone()).to.be.true;
expect(outFirst).to.be.deep.equal(responseData);
expect(outSecond).to.be.deep.equal(responseData);
});
it('should throw exception if no jwt secret provided but with JWT payload', async () => {
const objectStorageWithMiddlewares = new ObjectStorage({ uri: config.uri, jwtToken: 'token' });
let err;
try {
await objectStorageWithMiddlewares.getAsJSON('1', {});
} catch (e) {
err = e;
}
expect(err.toString()).to.include('JWT');
});
});
it('should use valid jwt token', async () => {
const objectStorage = new ObjectStorage(config);
describe('stream mode', () => {
it('should fail after 3 get retries', async () => {
const objectStorageWithMiddlewares = new ObjectStorage(config);
objectStorageWithMiddlewares.use(encryptStream, decryptStream);
objectStorageWithMiddlewares.use(zip, unzip);
const log = sinon.stub(logging, 'warn');
const objectStorageWithMiddlewaresCalls = nock(config.uri)
// @ts-ignore: Nock .d.ts are outdated.
.matchHeader('authorization', authHeaderMatch())
.get('/objects/1')
.replyWithError({ code: 'ETIMEDOUT' })
.get('/objects/1')
.reply(429)
.get('/objects/1')
.reply(500)
.get('/objects/1')
.replyWithError({ code: 'EHOSTUNREACH' });
const jwtPayload = { tenantId: '12', contractId: '1' };
const objectStorageCalls = nock(config.uri)
// @ts-ignore: Nock .d.ts are outdated.
.matchHeader('authorization', authHeaderMatch(jwtPayload))
.post('/objects')
.reply(200);
let err;
try {
await objectStorageWithMiddlewares.getAsStream('1', {});
} catch (e) {
err = e;
}
const objectId = await objectStorage.addAsStream(postStream, jwtPayload);
expect(objectStorageWithMiddlewaresCalls.isDone()).to.be.true;
expect(err.code).to.be.equal('EHOSTUNREACH');
expect(log.getCall(1).args[1].toString()).to.include('429');
expect(log.callCount).to.be.equal(4);
});
expect(objectStorageCalls.isDone()).to.be.true;
expect(objectId).to.match(/^[0-9a-z-]+$/);
it('should retry get request on errors', 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', authHeaderMatch())
.get('/objects/1')
.reply(500)
.get('/objects/1')
.reply(429)
.get('/objects/1')
.reply(200, () => {
const stream = streamResponse(responseData)();
return stream.pipe(encryptStream()).pipe(zip());
});
const response = await objectStorageWithMiddlewares.getAsStream('1', {});
const out = JSON.parse(await getStream(response.stream));
expect(objectStorageWithMiddlewaresCalls.isDone()).to.be.true;
expect(out).to.be.deep.equal(responseData);
});
it('should throw an error on post request connection error', 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', authHeaderMatch())
.post('/objects')
.replyWithError({ code: 'ECONNREFUSED' })
.post('/objects')
.replyWithError({ code: 'ETIMEDOUT' })
.post('/objects')
.replyWithError({ code: 'ECONNRESET' })
.post('/objects')
.replyWithError({ code: 'ECONNREFUSED' });
let err;
try {
await objectStorageWithMiddlewares.addAsStream(postStream, {});
} catch (e) {
err = e;
}
expect(objectStorageWithMiddlewaresCalls.isDone()).to.be.true;
expect(err.code).to.be.equal('ECONNREFUSED');
});
it('should throw an error on post request http error', 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', authHeaderMatch())
.post('/objects')
.reply(502)
.post('/objects')
.reply(503)
.post('/objects')
.reply(500)
.post('/objects')
.reply(429);
let err;
try {
await objectStorageWithMiddlewares.addAsStream(postStream, {});
} catch (e) {
err = e;
}
expect(objectStorageWithMiddlewaresCalls.isDone()).to.be.true;
expect(err.toString()).to.include('429');
});
it('should post 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', authHeaderMatch())
.post('/objects')
.reply(200, { objectId: '1' });
const objectId = await objectStorageWithMiddlewares.addAsStream(postStream, {});
expect(objectStorageWithMiddlewaresCalls.isDone()).to.be.true;
expect(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 jwtPayload = { tenantId: '12', contractId: '1' };
const objectStorageWithMiddlewaresCalls = nock(config.uri)
// @ts-ignore: Nock .d.ts are outdated.
.matchHeader('authorization', authHeaderMatch(jwtPayload))
.post('/objects')
.reply(200, { objectId: '1' })
.post('/objects')
.reply(200, { objectId: '2' });
const objectIdFirst = await objectStorageWithMiddlewares.addAsStream(postStream, jwtPayload);
const objectIdSecond = await objectStorageWithMiddlewares.addAsStream(postStream, jwtPayload);
expect(objectStorageWithMiddlewaresCalls.isDone()).to.be.true;
expect(objectIdFirst).to.be.equal('1');
expect(objectIdSecond).to.be.equal('2');
});
it('should get 2 objects successfully', async () => {
const objectStorageWithMiddlewares = new ObjectStorage(config);
objectStorageWithMiddlewares.use(encryptStream, decryptStream);
objectStorageWithMiddlewares.use(zip, unzip);
const jwtPayload = { tenantId: '12', contractId: '1' };
const objectStorageWithMiddlewaresCalls = nock(config.uri)
// @ts-ignore: Nock .d.ts are outdated.
.matchHeader('authorization', authHeaderMatch(jwtPayload))
.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());
});
const outStreamFirst = await objectStorageWithMiddlewares.getAsStream('1', jwtPayload);
const outFirst = JSON.parse(await getStream(outStreamFirst.stream));
const outStreamSecond = await objectStorageWithMiddlewares.getAsStream('2', jwtPayload);
const outSecond = JSON.parse(await getStream(outStreamSecond.stream));
expect(objectStorageWithMiddlewaresCalls.isDone()).to.be.true;
expect(outFirst).to.be.deep.equal(responseData);
expect(outSecond).to.be.deep.equal(responseData);
});
it('should use valid jwt token', async () => {
const objectStorageWithMiddlewares = new ObjectStorage(config);
objectStorageWithMiddlewares.use(encryptStream, decryptStream);
objectStorageWithMiddlewares.use(zip, unzip);
const jwtPayload = { tenantId: '12', contractId: '1' };
const objectStorageWithMiddlewaresCalls = nock(config.uri)
// @ts-ignore: Nock .d.ts are outdated.
.matchHeader('authorization', authHeaderMatch(jwtPayload))
.post('/objects')
.reply(200);
const objectId = await objectStorageWithMiddlewares.addAsStream(postStream, jwtPayload);
expect(objectStorageWithMiddlewaresCalls.isDone()).to.be.true;
expect(objectId).to.match(/^[0-9a-z-]+$/);
});
});

@@ -294,354 +707,607 @@ });

describe('middlewares + zip/unzip and encrypt/decrypt', () => {
describe('data mode', () => {
it('should fail after 3 retries', async () => {
const objectStorageWithMiddlewares = new ObjectStorage(config);
objectStorageWithMiddlewares.use(encryptStream, decryptStream);
objectStorageWithMiddlewares.use(zip, unzip);
const log = sinon.stub(logging, 'warn');
const objectStorageWithMiddlewaresCalls = nock(config.uri)
// @ts-ignore: Nock .d.ts are outdated.
.matchHeader('authorization', authHeaderMatch())
.get('/objects/1')
.replyWithError({ code: 'ETIMEDOUT' })
.get('/objects/1')
.reply(404)
.get('/objects/1')
.replyWithError({ code: 'ENOTFOUND' });
describe('when jwt token provided', () => {
const jwtSecret = 'jwt';
const jwtPayload = { tenantId: '12', contractId: '1' };
const token = sign(jwtPayload, jwtSecret);
const config = {
uri: 'https://ma.es.ter',
jwtToken: token
};
let err;
try {
await objectStorageWithMiddlewares.getAsJSON('1', {});
} catch (e) {
err = e;
}
describe('basic', () => {
describe('data mode', () => {
it('should fail after 3 retries', async () => {
const log = sinon.stub(logging, 'warn');
const objectStorage = new ObjectStorage(config);
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);
});
const objectStorageCalls = nock(config.uri)
// @ts-ignore: Nock .d.ts are outdated.
.matchHeader('authorization', `Bearer ${token}`)
.get('/objects/1')
.replyWithError({ code: 'ETIMEDOUT' })
.get('/objects/1')
.reply(429)
.get('/objects/1')
.replyWithError({ code: 'ECONNRESET' })
.get('/objects/1')
.replyWithError({ code: 'EHOSTUNREACH' });
it('should retry get request 3 times on errors', 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', authHeaderMatch())
.get('/objects/1')
.reply(500)
.get('/objects/1')
.replyWithError({ code: 'ECONNRESET' })
.get('/objects/1')
.reply(200, () => {
const stream = streamResponse(responseData)();
return stream.pipe(encryptStream()).pipe(zip());
});
let err;
try {
await objectStorage.getAsJSON('1');
} catch (e) {
err = e;
}
const out = await objectStorageWithMiddlewares.getAsJSON('1', {});
expect(objectStorageCalls.isDone()).to.be.true;
expect(err.code).to.be.equal('EHOSTUNREACH');
expect(log.getCall(1).args[1].toString()).to.include('429');
expect(log.callCount).to.be.equal(4);
});
expect(objectStorageWithMiddlewaresCalls.isDone()).to.be.true;
expect(out).to.be.deep.equal(responseData);
});
it('should retry get request 3 times on errors', async () => {
const objectStorage = new ObjectStorage(config);
it('should retry post request 3 times on errors', 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', authHeaderMatch())
.post('/objects')
.replyWithError({ code: 'ECONNREFUSED' })
.post('/objects')
.reply(400)
.post('/objects')
.reply(200, responseData);
const objectStorageCalls = nock(config.uri)
// @ts-ignore: Nock .d.ts are outdated.
.matchHeader('authorization', `Bearer ${token}`)
.get('/objects/1')
.reply(500)
.get('/objects/1')
.replyWithError({ code: 'ECONNRESET' })
.get('/objects/1')
.replyWithError({ code: 'EHOSTUNREACH' })
.get('/objects/1')
.reply(200, streamResponse(responseData));
await objectStorageWithMiddlewares.addAsJSON(postData, {});
const out = await objectStorage.getAsJSON('1');
expect(objectStorageWithMiddlewaresCalls.isDone()).to.be.true;
});
expect(objectStorageCalls.isDone()).to.be.true;
expect(out).to.be.deep.equal(responseData);
});
it('should accept jwt token on add', async () => {
const objectStorageWithMiddlewares = new ObjectStorage(config);
objectStorageWithMiddlewares.use(encryptStream, decryptStream);
objectStorageWithMiddlewares.use(zip, unzip);
const jwtPayload = { tenantId: '12', contractId: '1' };
const objectStorageWithMiddlewaresCalls = nock(config.uri)
// @ts-ignore: Nock .d.ts are outdated.
.matchHeader('authorization', authHeaderMatch(jwtPayload))
.post('/objects')
.reply(200);
it('should retry post request 3 times on errors', async () => {
const objectStorage = new ObjectStorage(config);
const objectId = await objectStorageWithMiddlewares.addAsJSON(postData, sign(jwtPayload, config.jwtSecret));
const objectStorageCalls = nock(config.uri)
// @ts-ignore: Nock .d.ts are outdated.
.matchHeader('authorization', `Bearer ${token}`)
.post('/objects')
.replyWithError({ code: 'ECONNREFUSED' })
.post('/objects')
.reply(429)
.post('/objects')
.replyWithError({ code: 'EHOSTUNREACH' })
.post('/objects')
.reply(200, responseData);
expect(objectStorageWithMiddlewaresCalls.isDone()).to.be.true;
expect(objectId).to.match(/^[0-9a-z-]+$/);
});
await objectStorage.addAsJSON(postData);
it('should accept jwt token on get', async () => {
const objectStorageWithMiddlewares = new ObjectStorage(config);
objectStorageWithMiddlewares.use(encryptStream, decryptStream);
objectStorageWithMiddlewares.use(zip, unzip);
expect(objectStorageCalls.isDone()).to.be.true;
});
const jwtPayload = { tenantId: '12', contractId: '1' };
const objectStorageWithMiddlewaresCalls = nock(config.uri)
// @ts-ignore: Nock .d.ts are outdated.
.matchHeader('authorization', authHeaderMatch(jwtPayload))
.get('/objects/1')
.reply(200, () => {
const stream = streamResponse(responseData)();
return stream.pipe(encryptStream()).pipe(zip());
});
it('should accept jwt token on add', async () => {
const objectStorage = new ObjectStorage(config);
const out = await objectStorageWithMiddlewares.getAsJSON('1', sign(jwtPayload, config.jwtSecret));
const objectStorageCalls = nock(config.uri)
// @ts-ignore: Nock .d.ts are outdated.
.matchHeader('authorization', `Bearer ${token}`)
.post('/objects')
.reply(200);
expect(objectStorageWithMiddlewaresCalls.isDone()).to.be.true;
expect(out).to.be.deep.equal(responseData);
});
const objectId = await objectStorage.addAsJSON(postData);
it('should add 2 objects successfully', async () => {
const objectStorageWithMiddlewares = new ObjectStorage(config);
objectStorageWithMiddlewares.use(encryptStream, decryptStream);
objectStorageWithMiddlewares.use(zip, unzip);
const jwtPayload = { tenantId: '12', contractId: '1' };
const objectStorageWithMiddlewaresCalls = nock(config.uri)
// @ts-ignore: Nock .d.ts are outdated.
.matchHeader('authorization', authHeaderMatch(jwtPayload))
.post('/objects')
.reply(200, { objectId: '1' })
.post('/objects')
.reply(200, { objectId: '2' });
expect(objectStorageCalls.isDone()).to.be.true;
expect(objectId).to.match(/^[0-9a-z-]+$/);
});
const objectIdFirst = await objectStorageWithMiddlewares.addAsJSON(postData, sign(jwtPayload, config.jwtSecret));
const objectIdSecond = await objectStorageWithMiddlewares.addAsJSON(postData, sign(jwtPayload, config.jwtSecret));
expect(objectStorageWithMiddlewaresCalls.isDone()).to.be.true;
expect(objectIdFirst).to.be.equal('1');
expect(objectIdSecond).to.be.equal('2');
});
it('should accept jwt token on get', async () => {
const objectStorage = new ObjectStorage(config);
it('should get 2 objects successfully', async () => {
const objectStorageWithMiddlewares = new ObjectStorage(config);
objectStorageWithMiddlewares.use(encryptStream, decryptStream);
objectStorageWithMiddlewares.use(zip, unzip);
const objectStorageCalls = nock(config.uri)
// @ts-ignore: Nock .d.ts are outdated.
.matchHeader('authorization', `Bearer ${token}`)
.get('/objects/1')
.reply(200, streamResponse(responseData));
const jwtPayload = { tenantId: '12', contractId: '1' };
const objectStorageWithMiddlewaresCalls = nock(config.uri)
// @ts-ignore: Nock .d.ts are outdated.
.matchHeader('authorization', authHeaderMatch(jwtPayload))
.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());
});
const out = await objectStorage.getAsJSON('1');
const outFirst = await objectStorageWithMiddlewares.getAsJSON('1', sign(jwtPayload, config.jwtSecret));
const outSecond = await objectStorageWithMiddlewares.getAsJSON('2', sign(jwtPayload, config.jwtSecret));
expect(objectStorageCalls.isDone()).to.be.true;
expect(out).to.be.deep.equal(responseData);
});
expect(objectStorageWithMiddlewaresCalls.isDone()).to.be.true;
expect(outFirst).to.be.deep.equal(responseData);
expect(outSecond).to.be.deep.equal(responseData);
it('should accept jwt token on delete', async () => {
const objectStorage = new ObjectStorage(config);
const objectStorageCalls = nock(config.uri)
// @ts-ignore: Nock .d.ts are outdated.
.matchHeader('authorization', `Bearer ${token}`)
.delete('/objects/1')
.reply(204);
await objectStorage.deleteOne('1');
expect(objectStorageCalls.isDone()).to.be.true;
});
});
it('should throw exception if neither jwt secret, nor jwt token provided', async () => {
const objectStorageWithMiddlewares = new ObjectStorage({ uri: config.uri });
describe('stream mode', () => {
it('should fail after 3 get retries', async () => {
const log = sinon.stub(logging, 'warn');
const objectStorage = new ObjectStorage(config);
let err;
try {
await objectStorageWithMiddlewares.getAsJSON('1', {});
} catch (e) {
err = e;
}
const objectStorageCalls = nock(config.uri)
// @ts-ignore: Nock .d.ts are outdated.
.matchHeader('authorization', `Bearer ${token}`)
.get('/objects/1')
.replyWithError({ code: 'ETIMEDOUT' })
.get('/objects/1')
.reply(429)
.get('/objects/1')
.replyWithError({ code: 'ECONNRESET' })
.get('/objects/1')
.replyWithError({ code: 'EHOSTUNREACH' });
expect(err.toString()).to.include('JWT');
let err;
try {
await objectStorage.getAsStream('1');
} catch (e) {
err = e;
}
expect(objectStorageCalls.isDone()).to.be.true;
expect(err.code).to.be.equal('EHOSTUNREACH');
expect(log.getCall(1).args[1].toString()).to.include('429');
expect(log.callCount).to.be.equal(4);
});
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 ${token}`)
.get('/objects/1')
.reply(500)
.get('/objects/1')
.reply(200, streamResponse(responseData));
const response = await objectStorage.getAsStream('1');
const out = JSON.parse(await getStream(response.stream));
expect(objectStorageCalls.isDone()).to.be.true;
expect(out).to.be.deep.equal(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 ${token}`)
.post('/objects')
.replyWithError({ code: 'ECONNREFUSED' })
.post('/objects')
.replyWithError({ code: 'ECONNRESET' })
.post('/objects')
.replyWithError({ code: 'ETIMEDOUT' })
.post('/objects')
.replyWithError({ code: 'EHOSTUNREACH' });
let err;
try {
await objectStorage.addAsStream(postStream);
} catch (e) {
err = e;
}
expect(objectStorageCalls.isDone()).to.be.true;
expect(err.code).to.be.equal('EHOSTUNREACH');
});
it('should throw an error on post request http error', async () => {
const objectStorage = new ObjectStorage(config);
const objectStorageCalls = nock(config.uri)
// @ts-ignore: Nock .d.ts are outdated.
.matchHeader('authorization', `Bearer ${token}`)
.post('/objects')
.reply(500)
.post('/objects')
.reply(503)
.post('/objects')
.reply(502)
.post('/objects')
.reply(429);
let err;
try {
await objectStorage.addAsStream(postStream);
} catch (e) {
err = e;
}
expect(objectStorageCalls.isDone()).to.be.true;
expect(err.toString()).to.include('429');
});
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 ${token}`)
.post('/objects')
.reply(200);
const objectId = await objectStorage.addAsStream(postStream);
expect(objectStorageCalls.isDone()).to.be.true;
expect(objectId).to.match(/^[0-9a-z-]+$/);
});
it('should use valid jwt token', async () => {
const objectStorage = new ObjectStorage(config);
const objectStorageCalls = nock(config.uri)
// @ts-ignore: Nock .d.ts are outdated.
.matchHeader('authorization', `Bearer ${token}`)
.post('/objects')
.reply(200);
const objectId = await objectStorage.addAsStream(postStream);
expect(objectStorageCalls.isDone()).to.be.true;
expect(objectId).to.match(/^[0-9a-z-]+$/);
});
});
});
describe('stream mode', () => {
it('should fail after 3 get retries', async () => {
const objectStorageWithMiddlewares = new ObjectStorage(config);
objectStorageWithMiddlewares.use(encryptStream, decryptStream);
objectStorageWithMiddlewares.use(zip, unzip);
const log = sinon.stub(logging, 'warn');
const objectStorageWithMiddlewaresCalls = nock(config.uri)
// @ts-ignore: Nock .d.ts are outdated.
.matchHeader('authorization', authHeaderMatch())
.get('/objects/1')
.replyWithError({ code: 'ETIMEDOUT' })
.get('/objects/1')
.reply(404)
.get('/objects/1')
.replyWithError({ code: 'ENOTFOUND' });
describe('middlewares + zip/unzip and encrypt/decrypt', () => {
describe('data mode', () => {
it('should fail after 3 retries', async () => {
const objectStorageWithMiddlewares = new ObjectStorage(config);
objectStorageWithMiddlewares.use(encryptStream, decryptStream);
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 ${token}`)
.get('/objects/1')
.replyWithError({ code: 'ETIMEDOUT' })
.get('/objects/1')
.reply(429)
.get('/objects/1')
.replyWithError({ code: 'EHOSTUNREACH' })
.get('/objects/1')
.replyWithError({ code: 'ECONNREFUSED' });
let err;
try {
await objectStorageWithMiddlewares.getAsStream('1', {});
} catch (e) {
err = e;
}
let err;
try {
await objectStorageWithMiddlewares.getAsJSON('1');
} catch (e) {
err = e;
}
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);
});
expect(objectStorageWithMiddlewaresCalls.isDone()).to.be.true;
expect(err.code).to.be.equal('ECONNREFUSED');
expect(log.getCall(1).args[1].toString()).to.include('429');
expect(log.callCount).to.be.equal(4);
});
it('should retry get request on errors', 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', authHeaderMatch())
.get('/objects/1')
.reply(500)
.get('/objects/1')
.reply(200, () => {
const stream = streamResponse(responseData)();
return stream.pipe(encryptStream()).pipe(zip());
});
it('should retry get request 3 times on errors', 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 ${token}`)
.get('/objects/1')
.reply(500)
.get('/objects/1')
.replyWithError({ code: 'ECONNRESET' })
.get('/objects/1')
.replyWithError({ code: 'ECONNREFUSED' })
.get('/objects/1')
.reply(200, () => {
const stream = streamResponse(responseData)();
return stream.pipe(encryptStream()).pipe(zip());
});
const response = await objectStorageWithMiddlewares.getAsStream('1', {});
const out = await objectStorageWithMiddlewares.getAsJSON('1');
const out = JSON.parse(await getStream(response.stream));
expect(objectStorageWithMiddlewaresCalls.isDone()).to.be.true;
expect(out).to.be.deep.equal(responseData);
});
expect(objectStorageWithMiddlewaresCalls.isDone()).to.be.true;
expect(out).to.be.deep.equal(responseData);
});
it('should throw an error on put request connection error', 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', authHeaderMatch())
.post('/objects')
.replyWithError({ code: 'ECONNREFUSED' })
.post('/objects')
.replyWithError({ code: 'ECONNREFUSED' })
.post('/objects')
.replyWithError({ code: 'ECONNREFUSED' });
it('should retry post request 3 times on errors', 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 ${token}`)
.post('/objects')
.replyWithError({ code: 'ECONNREFUSED' })
.post('/objects')
.replyWithError({ code: 'ECONNRESET' })
.post('/objects')
.reply(429)
.post('/objects')
.reply(200, responseData);
let err;
try {
await objectStorageWithMiddlewares.addAsStream(postStream, {});
} catch (e) {
err = e;
}
await objectStorageWithMiddlewares.addAsJSON(postData);
expect(objectStorageWithMiddlewaresCalls.isDone()).to.be.true;
expect(err.code).to.be.equal('ECONNREFUSED');
});
expect(objectStorageWithMiddlewaresCalls.isDone()).to.be.true;
});
it('should throw an error on put request http error', 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', authHeaderMatch())
.post('/objects')
.reply(409)
.post('/objects')
.reply(409)
.post('/objects')
.reply(409);
it('should accept jwt token on add', 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 ${token}`)
.post('/objects')
.reply(200);
let err;
try {
await objectStorageWithMiddlewares.addAsStream(postStream, {});
} catch (e) {
err = e;
}
expect(objectStorageWithMiddlewaresCalls.isDone()).to.be.true;
expect(err.toString()).to.include('409');
});
const objectId = await objectStorageWithMiddlewares.addAsJSON(postData);
it('should post 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', authHeaderMatch())
.post('/objects')
.reply(200, { objectId: '1' });
expect(objectStorageWithMiddlewaresCalls.isDone()).to.be.true;
expect(objectId).to.match(/^[0-9a-z-]+$/);
});
const objectId = await objectStorageWithMiddlewares.addAsStream(postStream, {});
it('should accept jwt token on get', async () => {
const objectStorageWithMiddlewares = new ObjectStorage(config);
objectStorageWithMiddlewares.use(encryptStream, decryptStream);
objectStorageWithMiddlewares.use(zip, unzip);
expect(objectStorageWithMiddlewaresCalls.isDone()).to.be.true;
expect(objectId).to.be.equal('1');
});
const objectStorageWithMiddlewaresCalls = nock(config.uri)
// @ts-ignore: Nock .d.ts are outdated.
.matchHeader('authorization', `Bearer ${token}`)
.get('/objects/1')
.reply(200, () => {
const stream = streamResponse(responseData)();
return stream.pipe(encryptStream()).pipe(zip());
});
it('should add 2 objects successfully', async () => {
const objectStorageWithMiddlewares = new ObjectStorage(config);
objectStorageWithMiddlewares.use(encryptStream, decryptStream);
objectStorageWithMiddlewares.use(zip, unzip);
const jwtPayload = { tenantId: '12', contractId: '1' };
const objectStorageWithMiddlewaresCalls = nock(config.uri)
// @ts-ignore: Nock .d.ts are outdated.
.matchHeader('authorization', authHeaderMatch(jwtPayload))
.post('/objects')
.reply(200, { objectId: '1' })
.post('/objects')
.reply(200, { objectId: '2' });
const out = await objectStorageWithMiddlewares.getAsJSON('1');
const objectIdFirst = await objectStorageWithMiddlewares.addAsStream(postStream, sign(jwtPayload, config.jwtSecret));
const objectIdSecond = await objectStorageWithMiddlewares.addAsStream(postStream, sign(jwtPayload, config.jwtSecret));
expect(objectStorageWithMiddlewaresCalls.isDone()).to.be.true;
expect(objectIdFirst).to.be.equal('1');
expect(objectIdSecond).to.be.equal('2');
});
expect(objectStorageWithMiddlewaresCalls.isDone()).to.be.true;
expect(out).to.be.deep.equal(responseData);
});
it('should get 2 objects successfully', async () => {
const objectStorageWithMiddlewares = new ObjectStorage(config);
objectStorageWithMiddlewares.use(encryptStream, decryptStream);
objectStorageWithMiddlewares.use(zip, unzip);
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 ${token}`)
.post('/objects')
.reply(200, { objectId: '1' })
.post('/objects')
.reply(200, { objectId: '2' });
const jwtPayload = { tenantId: '12', contractId: '1' };
const objectStorageWithMiddlewaresCalls = nock(config.uri)
// @ts-ignore: Nock .d.ts are outdated.
.matchHeader('authorization', authHeaderMatch(jwtPayload))
.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());
});
const objectIdFirst = await objectStorageWithMiddlewares.addAsJSON(postData);
const objectIdSecond = await objectStorageWithMiddlewares.addAsJSON(postData);
expect(objectStorageWithMiddlewaresCalls.isDone()).to.be.true;
expect(objectIdFirst).to.be.equal('1');
expect(objectIdSecond).to.be.equal('2');
});
const outStreamFirst = await objectStorageWithMiddlewares.getAsStream('1', sign(jwtPayload, config.jwtSecret));
const outFirst = JSON.parse(await getStream(outStreamFirst.stream));
const outStreamSecond = await objectStorageWithMiddlewares.getAsStream('2', sign(jwtPayload, config.jwtSecret));
const outSecond = JSON.parse(await getStream(outStreamSecond.stream));
expect(objectStorageWithMiddlewaresCalls.isDone()).to.be.true;
expect(outFirst).to.be.deep.equal(responseData);
expect(outSecond).to.be.deep.equal(responseData);
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.
.matchHeader('authorization', `Bearer ${token}`)
.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());
});
const outFirst = await objectStorageWithMiddlewares.getAsJSON('1');
const outSecond = await objectStorageWithMiddlewares.getAsJSON('2');
expect(objectStorageWithMiddlewaresCalls.isDone()).to.be.true;
expect(outFirst).to.be.deep.equal(responseData);
expect(outSecond).to.be.deep.equal(responseData);
});
});
it('should use valid jwt token', async () => {
const objectStorageWithMiddlewares = new ObjectStorage(config);
objectStorageWithMiddlewares.use(encryptStream, decryptStream);
objectStorageWithMiddlewares.use(zip, unzip);
const jwtPayload = { tenantId: '12', contractId: '1' };
const objectStorageWithMiddlewaresCalls = nock(config.uri)
// @ts-ignore: Nock .d.ts are outdated.
.matchHeader('authorization', authHeaderMatch(jwtPayload))
.post('/objects')
.reply(200);
describe('stream mode', () => {
it('should fail after 3 get retries', async () => {
const objectStorageWithMiddlewares = new ObjectStorage(config);
objectStorageWithMiddlewares.use(encryptStream, decryptStream);
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 ${token}`)
.get('/objects/1')
.replyWithError({ code: 'ETIMEDOUT' })
.get('/objects/1')
.reply(429)
.get('/objects/1')
.reply(500)
.get('/objects/1')
.replyWithError({ code: 'EHOSTUNREACH' });
const objectId = await objectStorageWithMiddlewares.addAsStream(postStream, jwtPayload);
let err;
try {
await objectStorageWithMiddlewares.getAsStream('1');
} catch (e) {
err = e;
}
expect(objectStorageWithMiddlewaresCalls.isDone()).to.be.true;
expect(objectId).to.match(/^[0-9a-z-]+$/);
expect(objectStorageWithMiddlewaresCalls.isDone()).to.be.true;
expect(err.code).to.be.equal('EHOSTUNREACH');
expect(log.getCall(1).args[1].toString()).to.include('429');
expect(log.callCount).to.be.equal(4);
});
it('should retry get request on errors', 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 ${token}`)
.get('/objects/1')
.reply(500)
.get('/objects/1')
.reply(429)
.get('/objects/1')
.reply(200, () => {
const stream = streamResponse(responseData)();
return stream.pipe(encryptStream()).pipe(zip());
});
const response = await objectStorageWithMiddlewares.getAsStream('1');
const out = JSON.parse(await getStream(response.stream));
expect(objectStorageWithMiddlewaresCalls.isDone()).to.be.true;
expect(out).to.be.deep.equal(responseData);
});
it('should throw an error on post request connection error', 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 ${token}`)
.post('/objects')
.replyWithError({ code: 'ECONNREFUSED' })
.post('/objects')
.replyWithError({ code: 'ETIMEDOUT' })
.post('/objects')
.replyWithError({ code: 'ECONNRESET' })
.post('/objects')
.replyWithError({ code: 'ECONNREFUSED' });
let err;
try {
await objectStorageWithMiddlewares.addAsStream(postStream);
} catch (e) {
err = e;
}
expect(objectStorageWithMiddlewaresCalls.isDone()).to.be.true;
expect(err.code).to.be.equal('ECONNREFUSED');
});
it('should throw an error on post request http error', 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 ${token}`)
.post('/objects')
.reply(502)
.post('/objects')
.reply(503)
.post('/objects')
.reply(500)
.post('/objects')
.reply(429);
let err;
try {
await objectStorageWithMiddlewares.addAsStream(postStream);
} catch (e) {
err = e;
}
expect(objectStorageWithMiddlewaresCalls.isDone()).to.be.true;
expect(err.toString()).to.include('429');
});
it('should post 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 ${token}`)
.post('/objects')
.reply(200, { objectId: '1' });
const objectId = await objectStorageWithMiddlewares.addAsStream(postStream);
expect(objectStorageWithMiddlewaresCalls.isDone()).to.be.true;
expect(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 ${token}`)
.post('/objects')
.reply(200, { objectId: '1' })
.post('/objects')
.reply(200, { objectId: '2' });
const objectIdFirst = await objectStorageWithMiddlewares.addAsStream(postStream);
const objectIdSecond = await objectStorageWithMiddlewares.addAsStream(postStream);
expect(objectStorageWithMiddlewaresCalls.isDone()).to.be.true;
expect(objectIdFirst).to.be.equal('1');
expect(objectIdSecond).to.be.equal('2');
});
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.
.matchHeader('authorization', `Bearer ${token}`)
.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());
});
const outStreamFirst = await objectStorageWithMiddlewares.getAsStream('1');
const outFirst = JSON.parse(await getStream(outStreamFirst.stream));
const outStreamSecond = await objectStorageWithMiddlewares.getAsStream('2');
const outSecond = JSON.parse(await getStream(outStreamSecond.stream));
expect(objectStorageWithMiddlewaresCalls.isDone()).to.be.true;
expect(outFirst).to.be.deep.equal(responseData);
expect(outSecond).to.be.deep.equal(responseData);
});
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.
.matchHeader('authorization', `Bearer ${token}`)
.post('/objects')
.reply(200);
const objectId = await objectStorageWithMiddlewares.addAsStream(postStream);
expect(objectStorageWithMiddlewaresCalls.isDone()).to.be.true;
expect(objectId).to.match(/^[0-9a-z-]+$/);
});
});

@@ -648,0 +1314,0 @@ });

@@ -63,9 +63,11 @@ import nock from 'nock';

.get('/objects/1')
.reply(404)
.reply(429)
.get('/objects/1')
.replyWithError({ code: 'ENOTFOUND' });
.reply(500)
.get('/objects/1')
.replyWithError({ code: 'EHOSTUNREACH' });
let err;
try {
await storageClient.readStream('1', {});
await storageClient.readStream('1', { jwtPayload: {} });
} catch (e) {

@@ -75,5 +77,5 @@ err = e;

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);
expect(err.code).to.be.equal('EHOSTUNREACH');
expect(log.getCall(1).args[1].toString()).to.include('429');
expect(log.callCount).to.be.equal(4);
});

@@ -92,5 +94,7 @@

.get('/objects/1')
.reply(429)
.get('/objects/1')
.reply(200, streamResponse(data));
const response = await storageClient.readStream('1', {});
const response = await storageClient.readStream('1', { jwtPayload: {} });

@@ -112,7 +116,9 @@ expect(storageClientCalls.isDone()).to.be.true;

.post('/objects')
.reply(400)
.reply(429)
.post('/objects')
.reply(500)
.post('/objects')
.reply(200, responseData);
const response = await storageClient.writeStream(putStream, {});
const response = await storageClient.writeStream(putStream, { jwtPayload: {} });
expect(response.data).to.be.deep.equal(responseData);

@@ -122,4 +128,4 @@ expect(storageClientCalls.isDone()).to.be.true;

it('should accept jwt token on add', async () => {
const storageClient = new StorageClient({ uri: config.uri });
it('should accept jwt payload on add', async () => {
const storageClient = new StorageClient(config);

@@ -133,3 +139,3 @@ const jwtPayload = { tenantId: '12', contractId: '1' };

const response = await storageClient.writeStream(putStream, sign(jwtPayload, config.jwtSecret));
const response = await storageClient.writeStream(putStream, { jwtPayload });
expect(storageClientCalls.isDone()).to.be.true;

@@ -140,5 +146,22 @@ expect(response.data).to.be.deep.equal(responseData);

it('should accept jwt token on get', async () => {
const storageClient = new StorageClient({ uri: config.uri });
it('should use default token on add', async () => {
const jwtPayload = { tenantId: '12', contractId: '1' };
const jwtToken = sign(jwtPayload, config.jwtSecret);
const storageClient = new StorageClient({ ...config, jwtToken: jwtToken });
const storageClientCalls = nock(config.uri)
// @ts-ignore: Nock .d.ts are outdated.
.matchHeader('authorization', authHeaderMatch(jwtPayload))
.post('/objects')
.reply(200, responseData);
const response = await storageClient.writeStream(putStream);
expect(storageClientCalls.isDone()).to.be.true;
expect(response.data).to.be.deep.equal(responseData);
expect(storageClientCalls.isDone()).to.be.true;
});
it('should accept jwt payload on get', async () => {
const storageClient = new StorageClient(config);
const jwtPayload = { tenantId: '12', contractId: '1' };

@@ -151,3 +174,3 @@ const storageClientCalls = nock(config.uri)

const response = await storageClient.readStream('1', sign(jwtPayload, config.jwtSecret));
const response = await storageClient.readStream('1', { jwtPayload });
expect(storageClientCalls.isDone()).to.be.true;

@@ -159,8 +182,56 @@ expect(response.data).to.be.instanceOf(Readable);

it('should throw exception if neither jwt secret, nor jwt token provided', async () => {
const storageClient = new StorageClient({ uri: config.uri });
it('should use default token on get', async () => {
const jwtPayload = { tenantId: '12', contractId: '1' };
const jwtToken = sign(jwtPayload, config.jwtSecret);
const storageClient = new StorageClient({ ...config, jwtToken: jwtToken });
const storageClientCalls = nock(config.uri)
// @ts-ignore: Nock .d.ts are outdated.
.matchHeader('authorization', authHeaderMatch(jwtPayload))
.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);
});
it('should accept jwt payload on delete', async () => {
const storageClient = new StorageClient(config);
const jwtPayload = { tenantId: '12', contractId: '1' };
const storageClientCalls = nock(config.uri)
// @ts-ignore: Nock .d.ts are outdated.
.matchHeader('authorization', authHeaderMatch(jwtPayload))
.delete('/objects/1')
.reply(200, streamResponse(data));
const response = await storageClient.deleteOne('1', { jwtPayload });
expect(storageClientCalls.isDone()).to.be.true;
expect(response.data).to.be.deep.equal(data);
});
it('should use default token on delete', async () => {
const jwtPayload = { tenantId: '12', contractId: '1' };
const jwtToken = sign(jwtPayload, config.jwtSecret);
const storageClient = new StorageClient({ ...config, jwtToken: jwtToken });
const storageClientCalls = nock(config.uri)
// @ts-ignore: Nock .d.ts are outdated.
.matchHeader('authorization', authHeaderMatch(jwtPayload))
.delete('/objects/1')
.reply(200, streamResponse(data));
const response = await storageClient.deleteOne('1');
expect(storageClientCalls.isDone()).to.be.true;
expect(response.data).to.be.deep.equal(data);
});
it('should throw exception if no jwt secret, nor jwt token provided', async () => {
let err;
try {
await storageClient.readStream('1', {});
// eslint-disable-next-line no-new
new StorageClient({ uri: config.uri });
} catch (e) {

@@ -172,2 +243,41 @@ err = e;

});
it('should throw exception if no jwt secret, but JWT payload provided on get', async () => {
const client = new StorageClient({ uri: config.uri, jwtToken: 'token' });
let err;
try {
// eslint-disable-next-line no-new
await client.readStream('1', { jwtPayload: {} });
} catch (e) {
err = e;
}
expect(err.toString()).to.include('JWT');
});
it('should throw exception if no jwt secret, but JWT payload provided on write', async () => {
const client = new StorageClient({ uri: config.uri, jwtToken: 'token' });
let err;
try {
// eslint-disable-next-line no-new
await client.writeStream(putStream, { jwtPayload: {} });
} catch (e) {
err = e;
}
expect(err.toString()).to.include('JWT');
});
it('should throw exception if no jwt secret, but JWT payload provided on delete', async () => {
const client = new StorageClient({ uri: config.uri, jwtToken: 'token' });
let err;
try {
// eslint-disable-next-line no-new
await client.deleteOne('1', { jwtPayload: {} });
} catch (e) {
err = e;
}
expect(err.toString()).to.include('JWT');
});
});
import { Readable, Duplex } from 'stream';
import getStream from 'get-stream';
import StorageClient from './storage-client';
import StorageClient, { StorageClientConfig, JWTPayload } from './storage-client';
interface JWTPayload { [index: string]: string };
export interface ObjectDTO {

@@ -20,4 +18,4 @@ stream?: Readable;

public constructor(config: {uri: string; jwtSecret?: string }) {
this.client = new StorageClient({ uri: config.uri, jwtSecret: config.jwtSecret });
public constructor(config: StorageClientConfig) {
this.client = new StorageClient(config);
this.forwards = [];

@@ -34,4 +32,4 @@ this.reverses = [];

// eslint-disable-next-line @typescript-eslint/no-explicit-any
public async getAsJSON(objectId: string, jwtPayloadOrToken?: JWTPayload | string): Promise<any> {
const objectDTO = await this.getAsStream(objectId, jwtPayloadOrToken);
public async getAsJSON(objectId: string, jwtPayload?: JWTPayload): Promise<any> {
const objectDTO = await this.getAsStream(objectId, jwtPayload);
const data = await getStream(objectDTO.stream);

@@ -41,4 +39,4 @@ return JSON.parse(data);

public async getAsStream(objectId: string, jwtPayloadOrToken?: JWTPayload | string): Promise<ObjectDTO> {
const res = await this.client.readStream(objectId, jwtPayloadOrToken);
public async getAsStream(objectId: string, jwtPayload?: JWTPayload): Promise<ObjectDTO> {
const res = await this.client.readStream(objectId, { jwtPayload });
const resultStream = this.applyMiddlewares(res.data, this.reverses);

@@ -48,5 +46,9 @@ return { stream: resultStream, headers: res.headers };

public async addAsStream(stream: Readable, jwtPayloadOrToken?: JWTPayload | string): Promise<string> {
public async deleteOne(objectId: string, jwtPayload?: JWTPayload): Promise<void> {
await this.client.deleteOne(objectId, { jwtPayload });
}
public async addAsStream(stream: Readable, jwtPayload?: JWTPayload): Promise<string> {
const resultStream = this.applyMiddlewares(stream, this.forwards);
const res = await this.client.writeStream(resultStream, jwtPayloadOrToken);
const res = await this.client.writeStream(resultStream, { jwtPayload });
return res.data.objectId;

@@ -62,3 +64,3 @@ }

// eslint-disable-next-line @typescript-eslint/no-explicit-any
public async addAsJSON(data: any, jwtPayloadOrToken: JWTPayload | string): Promise<string> {
public async addAsJSON(data: any, jwtPayload?: JWTPayload): Promise<string> {
const dataString = JSON.stringify(data);

@@ -68,4 +70,4 @@ const dataStream = new Readable();

dataStream.push(null);
return this.addAsStream(dataStream, jwtPayloadOrToken);
return this.addAsStream(dataStream, jwtPayload);
}
}

@@ -1,89 +0,189 @@

import axios, { AxiosInstance, AxiosResponse } from 'axios';
import axios, { AxiosInstance, AxiosResponse, AxiosRequestConfig, AxiosError } from 'axios';
import axiosRetry, { isNetworkError } from 'axios-retry';
import http from 'http';
import https from 'https';
import log from './logger';
import { sign } from 'jsonwebtoken';
import { promisify } from 'util';
import { Readable } from 'stream';
import logger from './logger';
interface JWTPayload { [index: string]: string };
const AXIOS_CONFIG_NAMESPACE = 'object-storage-client';
const AXIOS_RETRY_CONFIG_NAMESPACE = 'axios-retry';
export interface RequestOptions {
maxAttempts?: number;
delay?: number;
onResponse?: (err: Error, res: AxiosResponse) => boolean;
enum ObjectHeaders {
ttl = 'x-eio-ttl'
}
interface RequestHeaders { [index: string]: string | number }
export interface JWTPayload { [index: string]: string };
export interface ObjectOptions {
ttl: number;
};
export interface StorageClientRequestConfig {
jwtPayload?: JWTPayload;
objectOptions?: ObjectOptions;
retryDelay?: number;
retryCount?: number;
}
export interface StorageClientConfig {
uri: string;
jwtSecret?: string;
jwtToken?: string;
defaultRequestConfig?: StorageClientRequestConfig;
}
declare module 'axios' {
interface AxiosRequestConfig {
[AXIOS_CONFIG_NAMESPACE]?: StorageClientRequestConfig;
}
}
export default class StorageClient {
private api: AxiosInstance;
private readonly jwtSecret?: string;
private readonly api: AxiosInstance;
private static readonly DEFAULT_RETRY_DELAY: 100;
private static readonly DEFAULT_RETRY_COUNT: 3;
private static httpAgent = new http.Agent({ keepAlive: true });
private static httpsAgent = new https.Agent({ keepAlive: true });
public constructor(config: { uri: string; jwtSecret?: string }) {
this.api = axios.create({
baseURL: `${config.uri}/`,
public constructor(config: StorageClientConfig) {
const { uri, jwtSecret, defaultRequestConfig, jwtToken } = config;
this.api = StorageClient.createApiClient(jwtSecret, jwtToken, uri, defaultRequestConfig);
}
public async readStream(
objectId: string,
config?: StorageClientRequestConfig
): Promise<AxiosResponse> {
const res = await this.api.get(
`/objects/${objectId}`,
{
responseType: 'stream',
[AXIOS_CONFIG_NAMESPACE]: config
});
return res;
}
public async writeStream(
stream: Readable,
config?: StorageClientRequestConfig
): Promise<AxiosResponse> {
const res = await this.api.post(
`/objects`,
stream,
{
headers: {
'content-type': 'application/octet-stream'
},
[AXIOS_CONFIG_NAMESPACE]: config
});
return res;
}
public async deleteOne(
objectId: string,
config?: StorageClientRequestConfig
): Promise<AxiosResponse> {
const res = await this.api.delete(
`/objects/${objectId}`,
{
[AXIOS_CONFIG_NAMESPACE]: config
});
return res;
}
private static retryCondition(err: AxiosError) {
if (isNetworkError(err)) {
logger.warn('Error during object request: %s', err);
return true;
}
const { response } = err;
if (!response) {
return false;
}
if (response.status === 429 || (response.status >= 500 && response.status <= 599)) {
logger.warn('Error during object request: %s', `${response.status} (${response.statusText})`);
return true;
}
return false;
}
private static createApiClient(
jwtSecret: string,
jwtToken: string,
uri: string,
requestConfig: StorageClientRequestConfig
): AxiosInstance {
if (!jwtSecret && !jwtToken) {
throw new Error('Neither JWT token passed, nor JWT secret provided.');
}
const api = axios.create({
baseURL: `${uri}/`,
httpAgent: StorageClient.httpAgent,
httpsAgent: StorageClient.httpsAgent,
validateStatus: null,
maxContentLength: Infinity,
maxRedirects: 0
maxContentLength: Infinity
});
this.jwtSecret = config.jwtSecret;
}
private async requestRetry(request: () => Promise<AxiosResponse>, { maxAttempts = 3, delay = 100, onResponse }: RequestOptions = {}): Promise<AxiosResponse> {
let attempts = 0;
let res;
let err;
while (attempts < maxAttempts) {
err = null;
res = null;
attempts++;
try {
res = await request();
} catch (e) {
err = e;
if (jwtToken) {
api.defaults.headers.common.Authorization = `Bearer ${jwtToken}`;
}
// pls do not move axios interceptors init order,
// since middleware below sets values for retry meddileware, which sould be next
api.interceptors.request.use(async (config) => {
const { [AXIOS_CONFIG_NAMESPACE]: cfg } = config;
if (!cfg) {
return config;
}
if (onResponse && onResponse(err, res)) {
continue;
let newConfig = {
...config,
[AXIOS_RETRY_CONFIG_NAMESPACE]: config[AXIOS_RETRY_CONFIG_NAMESPACE] || {}
};
const { jwtPayload, retryDelay, retryCount, objectOptions } = cfg;
if (jwtPayload) {
if (!jwtSecret) {
throw new Error('JWT secret is not provided during initialization, can not encode JWT payload');
}
const token = await promisify(sign)(jwtPayload, jwtSecret);
newConfig.headers.Authorization = `Bearer ${token}`;
};
if (typeof retryDelay === 'number') {
newConfig['axios-retry'].retryDelay = () => retryDelay;
}
// last attempt error should not be logged
if ((err || res.status >= 400) && attempts < maxAttempts) {
log.warn('Error during object request: %s', err || `${res.status} (${res.statusText})`);
await new Promise((resolve): NodeJS.Timeout => setTimeout(resolve, delay));
continue;
if (typeof retryCount === 'number') {
newConfig['axios-retry'].retries = retryCount;
}
break;
}
if (err || res.status >= 400) {
throw err || new Error(`HTTP error during object request: ${res.status} (${res.statusText})`);
}
return res;
}
private async getHeaders(jwtPayloadOrToken: JWTPayload | string, override?: { [index: string]: string }) {
if (typeof jwtPayloadOrToken !== 'string' && !this.jwtSecret) {
throw new Error('Neither JWT token passed, nor JWT secret provided during initialization');
}
const token = typeof jwtPayloadOrToken === 'string'
? jwtPayloadOrToken
: await promisify(sign)(jwtPayloadOrToken, this.jwtSecret);
return { Authorization: `Bearer ${token}`, ...override };
}
if (objectOptions) {
newConfig.headers[ObjectHeaders.ttl] = objectOptions.ttl;
}
public async readStream(objectId: string, jwtPayloadOrToken?: JWTPayload | string): Promise<AxiosResponse> {
const res = await this.requestRetry(
async (): Promise<AxiosResponse> => this.api.get(`/objects/${objectId}`, { responseType: 'stream', headers: await this.getHeaders(jwtPayloadOrToken) })
return newConfig;
});
axiosRetry(
api,
{
retries: requestConfig && typeof requestConfig.retryCount === 'number'
? requestConfig.retryCount
: this.DEFAULT_RETRY_COUNT,
retryDelay: () => requestConfig && typeof requestConfig.retryDelay === 'number'
? requestConfig.retryDelay
: this.DEFAULT_RETRY_DELAY,
retryCondition: StorageClient.retryCondition
}
);
return res;
}
public async writeStream(stream: Readable, jwtPayloadOrToken?: JWTPayload | string): Promise<AxiosResponse> {
const res = await this.requestRetry(
async (): Promise<AxiosResponse> => this.api.post(`/objects`, stream, { headers: await this.getHeaders(jwtPayloadOrToken, { 'content-type': 'application/octet-stream' }) })
);
return res;
return api;
}
}
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc