diff --git a/CHANGELOG.md b/CHANGELOG.md index c2bce6d..b14cb71 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ +### v0.2.0 +- Add function `setFileMetadata` to create or replace object metadata +- Add function `getFileMetadata` to get an object metadata +- Update package `debug` to `4.3.4` ### v0.1.9 -- Update `simple-get` node dep to `4.0.1` +- Update package `simple-get` to `4.0.1` ### v0.1.8 - Fix the `downloadFile` callback function, the third argument is now always returning the response header of the request. diff --git a/README.md b/README.md index ea091b2..61ab9f2 100644 --- a/README.md +++ b/README.md @@ -179,6 +179,46 @@ storage.listFiles('templates', { queries: { prefix: 'prefixName' }, headers: { A }); ``` +### Get file metadata + +```js +storage.getFileMetadata('templates', 'filename.jpg', (err, headers) => { + if (err) { + // handle error + } + /** + * Returned headers: { + * Content-Length: 14 + * Accept-Ranges: bytes + * Last-Modified: Thu, 16 Jan 2014 21:12:31 GMT + * Etag: 451e372e48e0f6b1114fa0724aa79fa1 + * X-Timestamp: 1389906751.73463 + * X-Object-Meta-Book: GoodbyeColumbus + * Content-Type: application/octet-stream + * X-Trans-Id: tx37ea34dcd1ed48ca9bc7d-0052d84b6f + * X-Openstack-Request-Id: tx37ea34dcd1ed48ca9bc7d-0052d84b6f + * Date: Thu, 16 Jan 2014 21:13:19 GMT + * X-Object-Meta-Custom-Metadata-1: Value + * X-Object-Meta-Custom-Metadata-2: Value + * } + * // Details: https://docs.openstack.org/api-ref/object-store/?expanded=show-object-metadata-detail#show-object-metadata + */ +}); +``` + +### Set file metadata + +```js +storage.setFileMetadata('templates', 'filename.jpg', { headers: { 'Content-Type': 'image/jpeg', 'X-Object-Meta-LocationOrigin': 'Paris/France', 'X-Delete-At': 1440619048 }} (err, headers) => { + if (err) { + // handle error + } + // success + // console.log(headers['X-Object-Meta-name']) + // list of headers: https://docs.openstack.org/api-ref/object-store/?expanded=show-object-metadata-detail#show-object-metadata +}); +``` + ### Log The package uses debug to print logs into the terminal. To activate logs, you must pass the `DEBUG=*` environment variable. diff --git a/index.js b/index.js index 5c5de5a..a3ee7ef 100644 --- a/index.js +++ b/index.js @@ -308,6 +308,114 @@ function deleteFile (container, filename, callback) { }); } +/** + * @description Get object metadata + * + * @param {string} container Container name + * @param {string} filename filename to store + * @param {function} callback function(err, headers):void = The `err` argument is null by default, return an object if an error occurs. + * @returns {void} + */ +function getFileMetadata(container, filename, callback) { + const arrayArguments = [...arguments, { originStorage : _config.actifStorage }]; + + get.concat({ + url : `${_config.endpoints.url}/${container}/${filename}`, + method : 'HEAD', + headers : { + 'X-Auth-Token' : _config.token, + Accept : 'application/json' + }, + timeout: _config.timeout + }, (err, res) => { + + /** Manage special errors: timeouts, too many redirects or any unexpected behavior */ + res = res || {}; + res.error = err && err.toString().length > 0 ? err.toString() : null; + + checkIsConnected(res, 'getFileMetadata', arrayArguments, (error) => { + if (error) { + return callback(error); + } + + if (res && res.statusCode === 404) { + return callback(new Error('File does not exist')); + } + + err = err || checkResponseError(res); + + /** TODO: remove? it should never happen as every error switch to another storage */ + if (err) { + return callback(err); + } + + return callback(null, res.headers); + }); + }); + } + + /** + * @description Create or update object metadata. + * @description To create or update custom metadata + * @description use the X-Object-Meta-name header, + * @description where name is the name of the metadata item. + * + * @param {string} container Container name + * @param {string} filename file to store + * @param {string|Buffer} localPathOrBuffer absolute path to the file + * @param {Object} options { headers: {}, queries: {} } List of query parameters and headers: https://docs.openstack.org/api-ref/object-store/?expanded=create-or-update-object-metadata-detail#create-or-update-object-metadata + * @param {function} callback function(err, headers):void = The `err` is null by default, return an object if an error occurs. + * @returns {void} + */ +function setFileMetadata (container, filename, options, callback) { + + const arrayArguments = [...arguments]; + + if (callback === undefined) { + callback = options; + arrayArguments.push(options); + options = { headers: {}, queries: {} }; + arrayArguments[3] = options; + } + + arrayArguments.push({ originStorage : _config.actifStorage }) + + const { headers, queries } = getHeaderAndQueryParameters(options); + get.concat({ + url : `${_config.endpoints.url}/${container}/${filename}${queries}`, + method : 'POST', + headers : { + 'X-Auth-Token' : _config.token, + Accept : 'application/json', + ...headers + }, + timeout: _config.timeout + }, (err, res) => { + + /** Manage special errors: timeouts, too many redirects or any unexpected behavior */ + res = res || {}; + res.error = err && err.toString().length > 0 ? err.toString() : null; + + checkIsConnected(res, 'setFileMetadata', arrayArguments, (error) => { + if (error) { + return callback(error); + } + + if (res && res.statusCode === 404) { + return callback(new Error('File does not exist')); + } + + err = err || checkResponseError(res); + + /** TODO: remove? it should never happen as every error switch to another storage */ + if (err) { + return callback(err); + } + return callback(null, res.headers); + }); + }); +} + /** * @description Check the response status code and return an Error. * @@ -374,6 +482,12 @@ function checkIsConnected (response, from, args, callback) { case 'listFiles': listFiles.apply(null, args); break; + case 'getFileMetadata': + getFileMetadata.apply(null, args); + break; + case 'setFileMetadata': + setFileMetadata.apply(null, args); + break; default: /** TODO: remove? it should never happen */ callback(null); @@ -474,6 +588,8 @@ module.exports = (config) => { downloadFile, deleteFile, listFiles, + getFileMetadata, + setFileMetadata, setTimeout, setStorages, getStorages, diff --git a/package-lock.json b/package-lock.json index 8bf4d7a..5e8c634 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,15 @@ { "name": "high-availability-object-storage", - "version": "0.1.8", + "version": "0.1.9", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "high-availability-object-storage", - "version": "0.1.8", + "version": "0.1.9", "license": "Apache-2.0", "dependencies": { - "debug": "=4.3.3", + "debug": "=4.3.4", "simple-get": "=4.0.1" }, "devDependencies": { @@ -316,9 +316,9 @@ } }, "node_modules/debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dependencies": { "ms": "2.1.2" }, @@ -1939,9 +1939,9 @@ } }, "debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "requires": { "ms": "2.1.2" } diff --git a/package.json b/package.json index 61b5174..15745e3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "high-availability-object-storage", - "version": "0.1.9", + "version": "0.2.0", "description": "High available, performant, and tiny Node SDK for OpenStack Swift Object Storage", "main": "index.js", "scripts": { @@ -22,7 +22,7 @@ "author": "Steevepay", "license": "Apache-2.0", "dependencies": { - "debug": "=4.3.3", + "debug": "=4.3.4", "simple-get": "=4.0.1" }, "devDependencies": { diff --git a/tests/storage.test.js b/tests/storage.test.js index 4ab91d7..b04c98f 100644 --- a/tests/storage.test.js +++ b/tests/storage.test.js @@ -2537,6 +2537,1167 @@ describe('Ovh Object Storage High Availability', function () { }); }); }); + + describe('getFileMetadata', function () { + + describe('SINGLE STORAGE', function () { + it('should get file metadata (headers)', function (done) { + const firstNock = nock(publicUrlGRA) + .defaultReplyHeaders({ + 'content-length': '1492', + 'accept-ranges': 'bytes', + 'last-modified': 'Wed, 03 Nov 2021 13:02:39 GMT', + 'content-type': 'application/json', + etag: 'a30776a059eaf26eebf27756a849097d', + 'x-openstack-request-id': 'tx136c028c478a4b40a7014-0061829c9f', + date: 'Wed, 03 Nov 2021 14:28:48 GMT', + 'x-iplb-request-id': '25A66014:1D97_3626E64B:01BB_61829C9E_3C28BD:960D' + }) + .intercept("/templates/test.odt", "HEAD") + .reply(200,"OK"); + + storage.getFileMetadata('templates', 'test.odt', (err, headers) => { + assert.strictEqual(err, null); + assert.strictEqual(firstNock.pendingMocks().length, 0); + assert.strictEqual(headers['etag'].length > 0, true); + assert.strictEqual(headers['x-openstack-request-id'].length > 0, true); + assert.strictEqual(headers['content-length'].length > 0, true); + assert.strictEqual(headers['date'].length > 0, true); + done(); + }); + }); + + it('should reconnect automatically to object storage and retry', function (done) { + let firstNock = nock(publicUrlGRA) + .defaultReplyHeaders({ + 'content-length': '1492', + 'accept-ranges': 'bytes', + 'last-modified': 'Wed, 03 Nov 2021 13:02:39 GMT', + 'content-type': 'application/json', + etag: 'a30776a059eaf26eebf27756a849097d', + 'x-openstack-request-id': 'tx136c028c478a4b40a7014-0061829c9f', + date: 'Wed, 03 Nov 2021 14:28:48 GMT', + 'x-iplb-request-id': '25A66014:1D97_3626E64B:01BB_61829C9E_3C28BD:960D' + }) + .intercept("/templates/test.odt", "HEAD") + .reply(401, 'Unauthorized') + .intercept("/templates/test.odt", "HEAD") + .reply(200,"OK"); + + let secondNock = nock(authURL) + .post('/auth/tokens') + .reply(200, connectionResultSuccessV3, { "X-Subject-Token": tokenAuth }); + + storage.getFileMetadata('templates', 'test.odt', (err, headers) => { + assert.strictEqual(err, null); + assert.strictEqual(firstNock.pendingMocks().length, 0); + assert.strictEqual(secondNock.pendingMocks().length, 0); + assert.strictEqual(headers['etag'].length > 0, true); + assert.strictEqual(headers['x-openstack-request-id'].length > 0, true); + assert.strictEqual(headers['content-length'].length > 0, true); + assert.strictEqual(headers['date'].length > 0, true); + done(); + }); + }); + + it('should return an error if the single storage timout', function (done) { + storage.setTimeout(200); + const firstNock = nock(publicUrlGRA) + .intercept("/templates/test.odt", "HEAD") + .delayConnection(500) + .reply(200, {}) + + storage.getFileMetadata('templates', 'test.odt', (err, headers) => { + assert.notStrictEqual(err, null); + assert.strictEqual(err.message, 'Object Storages are not available'); + assert.strictEqual(headers, undefined); + assert.strictEqual(firstNock.pendingMocks().length, 0); + done(); + }); + }); + + it('should return an error if the single storage return any kind of errors', function (done) { + const firstNock = nock(publicUrlGRA) + .intercept("/templates/test.odt", "HEAD") + .replyWithError('Error Message 1234'); + + storage.getFileMetadata('templates', 'test.odt', (err, headers) => { + assert.notStrictEqual(err, null); + assert.strictEqual(err.message, 'Object Storages are not available'); + assert.strictEqual(headers, undefined); + assert.strictEqual(firstNock.pendingMocks().length, 0); + done(); + }); + }); + + it('should return an error if the file does not exist', function (done) { + const firstNock = nock(publicUrlGRA) + .intercept("/templates/test.odt", "HEAD") + .reply(404); + + storage.getFileMetadata('templates', 'test.odt', (err, headers) => { + assert.notStrictEqual(err, null); + assert.strictEqual(err.message, 'File does not exist'); + assert.strictEqual(headers, undefined); + assert.strictEqual(firstNock.pendingMocks().length, 0); + done(); + }); + }); + }); + + describe('MULTIPLE STORAGES', function () { + beforeEach(function (done) { + const firstNock = nock(authURL) + .post('/auth/tokens') + .reply(200, connectionResultSuccessV3, { "X-Subject-Token": tokenAuth }); + + storage.setTimeout(5000); + storage.setStorages([{ + username : 'storage-1-user', + password : 'storage-1-password', + authUrl : authURL, + tenantName : 'storage-1-tenant', + region : 'GRA' + }, + { + username : 'storage-2-user', + password : 'storage-2-password', + authUrl : authURL, + tenantName : 'storage-2-tenant', + region : 'SBG' + }]); + storage.connection((err) => { + assert.strictEqual(err, null) + assert.strictEqual(firstNock.pendingMocks().length, 0); + done(); + }) + }) + + it('should reconnect automatically to the second object storage if the first storage authentication fail and should retry the request', function(done){ + let firstNock = nock(publicUrlGRA) + /** 1 */ + .intercept("/templates/test.odt", "HEAD") + .reply(401, 'Unauthorized'); + + let secondNock = nock(authURL) + /** 2 */ + .post('/auth/tokens') + .reply(500, {}) + /** 3 */ + .post('/auth/tokens') + .reply(200, connectionResultSuccessV3, { "X-Subject-Token": tokenAuth }); + + let thirdNock = nock(publicUrlSBG) + /** 4 */ + .defaultReplyHeaders({ + 'content-length': '1492', + 'accept-ranges': 'bytes', + 'last-modified': 'Wed, 03 Nov 2021 13:02:39 GMT', + 'content-type': 'application/json', + etag: 'a30776a059eaf26eebf27756a849097d', + 'x-openstack-request-id': 'tx136c028c478a4b40a7014-0061829c9f', + date: 'Wed, 03 Nov 2021 14:28:48 GMT', + 'x-iplb-request-id': '25A66014:1D97_3626E64B:01BB_61829C9E_3C28BD:960D' + }) + .intercept("/templates/test.odt", "HEAD") + .reply(200, () => { + return fs.createReadStream(path.join(__dirname, 'assets', 'file.txt')); + }); + + storage.getFileMetadata('templates', 'test.odt', (err, headers) => { + assert.strictEqual(err, null); + assert.strictEqual(headers['etag'].length > 0, true); + assert.strictEqual(headers['x-openstack-request-id'].length > 0, true); + assert.strictEqual(headers['content-length'].length > 0, true); + assert.strictEqual(headers['date'].length > 0, true); + assert.strictEqual(firstNock.pendingMocks().length, 0); + assert.strictEqual(secondNock.pendingMocks().length, 0); + assert.strictEqual(thirdNock.pendingMocks().length, 0); + assert.deepStrictEqual(storage.getConfig().actifStorage, 1); + done(); + }); + + + }) + + it('should retry the request with the second object storage if the first object storage return a 500 error', function(done){ + let firstNock = nock(publicUrlGRA) + .intercept("/templates/test2.odt", "HEAD") + .reply(500, () => { + return ''; + }); + + let secondNock = nock(authURL) + .post('/auth/tokens') + .reply(200, connectionResultSuccessV3, { "X-Subject-Token": tokenAuth }); + + let thirdNock = nock(publicUrlSBG) + .defaultReplyHeaders({ + 'content-length': '1492', + 'accept-ranges': 'bytes', + 'last-modified': 'Wed, 03 Nov 2021 13:02:39 GMT', + 'content-type': 'application/json', + etag: 'a30776a059eaf26eebf27756a849097d', + 'x-openstack-request-id': 'tx136c028c478a4b40a7014-0061829c9f', + date: 'Wed, 03 Nov 2021 14:28:48 GMT', + 'x-iplb-request-id': '25A66014:1D97_3626E64B:01BB_61829C9E_3C28BD:960D' + }) + .intercept("/templates/test2.odt", "HEAD") + .reply(200, () => { + return fs.createReadStream(path.join(__dirname, 'assets', 'file.txt')); + }); + + storage.getFileMetadata('templates', 'test2.odt', (err, headers) => { + assert.strictEqual(err, null); + assert.strictEqual(headers['etag'].length > 0, true); + assert.strictEqual(headers['x-openstack-request-id'].length > 0, true); + assert.strictEqual(headers['content-length'].length > 0, true); + assert.strictEqual(headers['date'].length > 0, true); + assert.strictEqual(firstNock.pendingMocks().length, 0); + assert.strictEqual(secondNock.pendingMocks().length, 0); + assert.strictEqual(thirdNock.pendingMocks().length, 0); + assert.deepStrictEqual(storage.getConfig().actifStorage, 1); + done(); + }); + }) + + it('should retry the request with the second object storage if the first object storage timeout', function(done){ + storage.setTimeout(200); + let firstNock = nock(publicUrlGRA) + .intercept("/templates/test.odt", "HEAD") + .delayConnection(500) + .reply(200, {}); + + let secondNock = nock(authURL) + .post('/auth/tokens') + .reply(200, connectionResultSuccessV3, { "X-Subject-Token": tokenAuth }); + + let thirdNock = nock(publicUrlSBG) + .defaultReplyHeaders({ + 'content-length': '1492', + 'accept-ranges': 'bytes', + 'last-modified': 'Wed, 03 Nov 2021 13:02:39 GMT', + 'content-type': 'application/json', + etag: 'a30776a059eaf26eebf27756a849097d', + 'x-openstack-request-id': 'tx136c028c478a4b40a7014-0061829c9f', + date: 'Wed, 03 Nov 2021 14:28:48 GMT', + 'x-iplb-request-id': '25A66014:1D97_3626E64B:01BB_61829C9E_3C28BD:960D' + }) + .intercept("/templates/test.odt", "HEAD") + .reply(200, () => { + return fs.createReadStream(path.join(__dirname, 'assets', 'file.txt')); + }); + + + storage.getFileMetadata('templates', 'test.odt', (err, headers) => { + assert.strictEqual(err, null); + assert.strictEqual(headers['etag'].length > 0, true); + assert.strictEqual(headers['x-openstack-request-id'].length > 0, true); + assert.strictEqual(headers['content-length'].length > 0, true); + assert.strictEqual(headers['date'].length > 0, true); + assert.strictEqual(firstNock.pendingMocks().length, 0); + assert.strictEqual(secondNock.pendingMocks().length, 0); + assert.deepStrictEqual(storage.getConfig().actifStorage, 1); + assert.strictEqual(thirdNock.pendingMocks().length, 0); + done(); + }); + }) + + it('should retry the request with the second storage if the first storage return any kind of errors', function (done) { + let firstNock = nock(publicUrlGRA) + .intercept("/templates/test.odt", "HEAD") + .replyWithError('Error Message 1234'); + + let secondNock = nock(authURL) + .post('/auth/tokens') + .reply(200, connectionResultSuccessV3, { "X-Subject-Token": tokenAuth }); + + let thirdNock = nock(publicUrlSBG) + .defaultReplyHeaders({ + 'content-length': '1492', + 'accept-ranges': 'bytes', + 'last-modified': 'Wed, 03 Nov 2021 13:02:39 GMT', + 'content-type': 'application/json', + etag: 'a30776a059eaf26eebf27756a849097d', + 'x-openstack-request-id': 'tx136c028c478a4b40a7014-0061829c9f', + date: 'Wed, 03 Nov 2021 14:28:48 GMT', + 'x-iplb-request-id': '25A66014:1D97_3626E64B:01BB_61829C9E_3C28BD:960D' + }) + .intercept("/templates/test.odt", "HEAD") + .reply(200, () => { + return fs.createReadStream(path.join(__dirname, 'assets', 'file.txt')); + }); + + + storage.getFileMetadata('templates', 'test.odt', (err, headers) => { + assert.strictEqual(err, null); + assert.strictEqual(headers['etag'].length > 0, true); + assert.strictEqual(headers['x-openstack-request-id'].length > 0, true); + assert.strictEqual(headers['content-length'].length > 0, true); + assert.strictEqual(headers['date'].length > 0, true); + assert.strictEqual(firstNock.pendingMocks().length, 0); + assert.strictEqual(secondNock.pendingMocks().length, 0); + assert.strictEqual(thirdNock.pendingMocks().length, 0); + assert.deepStrictEqual(storage.getConfig().actifStorage, 1); + done(); + }); + }); + + describe("PARALLEL REQUESTS", function () { + + function getFileMetadataPromise() { + return new Promise((resolve, reject) => { + try { + storage.getFileMetadata('templates', 'test.odt', (err, headers) => { + if (err) { + return reject(err); + } + return resolve(headers); + }); + } catch(err) { + return reject(err); + } + }); + } + + it('should request the object storage in parallel and fallback to SBG if the main storage return any kind of errors', function (done) { + + let firstNock = nock(publicUrlGRA) + .intercept("/templates/test.odt", "HEAD") + .replyWithError('Error Message 1234') + .intercept("/templates/test.odt", "HEAD") + .replyWithError('Error Message 1234'); + + let secondNock = nock(authURL) + .post('/auth/tokens') + .reply(200, connectionResultSuccessV3, { "X-Subject-Token": tokenAuth }) + .post('/auth/tokens') + .reply(200, connectionResultSuccessV3, { "X-Subject-Token": tokenAuth }); + + let thirdNock = nock(publicUrlSBG) + .defaultReplyHeaders({ + 'content-length': '1492', + 'accept-ranges': 'bytes', + 'last-modified': 'Wed, 03 Nov 2021 13:02:39 GMT', + 'content-type': 'application/json', + etag: 'a30776a059eaf26eebf27756a849097d', + 'x-openstack-request-id': 'tx136c028c478a4b40a7014-0061829c9f', + date: 'Wed, 03 Nov 2021 14:28:48 GMT', + 'x-iplb-request-id': '25A66014:1D97_3626E64B:01BB_61829C9E_3C28BD:960D' + }) + .intercept("/templates/test.odt", "HEAD") + .reply(200, () => { + return fs.createReadStream(path.join(__dirname, 'assets', 'file.txt')); + }) + .intercept("/templates/test.odt", "HEAD") + .reply(200, () => { + return fs.createReadStream(path.join(__dirname, 'assets', 'file.txt')); + }); + + let promise1 = getFileMetadataPromise() + let promise2 = getFileMetadataPromise() + + Promise.all([promise1, promise2]).then(results => { + assert.strictEqual(results.length, 2) + assert.strictEqual(results[0]['etag'].length > 0, true); + assert.strictEqual(results[0]['x-openstack-request-id'].length > 0, true); + assert.strictEqual(results[0]['content-length'].length > 0, true); + assert.strictEqual(results[0]['date'].length > 0, true); + assert.strictEqual(results[1]['etag'].length > 0, true); + assert.strictEqual(results[1]['x-openstack-request-id'].length > 0, true); + assert.strictEqual(results[1]['content-length'].length > 0, true); + assert.strictEqual(results[1]['date'].length > 0, true); + assert.strictEqual(firstNock.pendingMocks().length, 0); + assert.strictEqual(secondNock.pendingMocks().length, 0); + assert.strictEqual(thirdNock.pendingMocks().length, 0); + assert.deepStrictEqual(storage.getConfig().actifStorage, 1); + done(); + }).catch(err => { + assert.strictEqual(err, null); + done(); + }); + }); + + it('should request the object storage in parallel and fallback to SBG if the authentication of the main storage return an error', function (done) { + + let firstNock = nock(publicUrlGRA) + .intercept("/templates/test.odt", "HEAD") + .reply(401, 'Unauthorized') + .intercept("/templates/test.odt", "HEAD") + .reply(401, 'Unauthorized'); + + let secondNock = nock(authURL) + .post('/auth/tokens') + .reply(500, {}) + .post('/auth/tokens') + .reply(500, {}) + .post('/auth/tokens') + .reply(200, connectionResultSuccessV3, { "X-Subject-Token": tokenAuth }) + .post('/auth/tokens') + .reply(200, connectionResultSuccessV3, { "X-Subject-Token": tokenAuth }); + + let thirdNock = nock(publicUrlSBG) + .defaultReplyHeaders({ + 'content-length': '1492', + 'accept-ranges': 'bytes', + 'last-modified': 'Wed, 03 Nov 2021 13:02:39 GMT', + 'content-type': 'application/json', + etag: 'a30776a059eaf26eebf27756a849097d', + 'x-openstack-request-id': 'tx136c028c478a4b40a7014-0061829c9f', + date: 'Wed, 03 Nov 2021 14:28:48 GMT', + 'x-iplb-request-id': '25A66014:1D97_3626E64B:01BB_61829C9E_3C28BD:960D' + }) + .intercept("/templates/test.odt", "HEAD") + .reply(200, () => { + return fs.createReadStream(path.join(__dirname, 'assets', 'file.txt')); + }) + .intercept("/templates/test.odt", "HEAD") + .reply(200, () => { + return fs.createReadStream(path.join(__dirname, 'assets', 'file.txt')); + }); + + let promise1 = getFileMetadataPromise() + let promise2 = getFileMetadataPromise() + + Promise.all([promise1, promise2]).then(async results => { + assert.strictEqual(results.length, 2); + assert.strictEqual(results[0]['etag'].length > 0, true); + assert.strictEqual(results[0]['x-openstack-request-id'].length > 0, true); + assert.strictEqual(results[0]['content-length'].length > 0, true); + assert.strictEqual(results[0]['date'].length > 0, true); + assert.strictEqual(results[1]['etag'].length > 0, true); + assert.strictEqual(results[1]['x-openstack-request-id'].length > 0, true); + assert.strictEqual(results[1]['content-length'].length > 0, true); + assert.strictEqual(results[1]['date'].length > 0, true); + assert.deepStrictEqual(storage.getConfig().actifStorage, 1); + assert.strictEqual(firstNock.pendingMocks().length, 0); + assert.strictEqual(secondNock.pendingMocks().length, 0); + assert.strictEqual(thirdNock.pendingMocks().length, 0); + done(); + }); + }); + + it('should request the object storage in parallel and fallback to SBG if the main storage timeout', function (done) { + + storage.setTimeout(200); + + let firstNock = nock(publicUrlGRA) + .intercept("/templates/test.odt", "HEAD") + .delayConnection(500) + .reply(200, {}) + .intercept("/templates/test.odt", "HEAD") + .delayConnection(500) + .reply(200, {}); + + let secondNock = nock(authURL) + .post('/auth/tokens') + .reply(200, connectionResultSuccessV3, { "X-Subject-Token": tokenAuth }) + .post('/auth/tokens') + .reply(200, connectionResultSuccessV3, { "X-Subject-Token": tokenAuth }); + + let thirdNock = nock(publicUrlSBG) + .defaultReplyHeaders({ + 'content-length': '1492', + 'accept-ranges': 'bytes', + 'last-modified': 'Wed, 03 Nov 2021 13:02:39 GMT', + 'content-type': 'application/json', + etag: 'a30776a059eaf26eebf27756a849097d', + 'x-openstack-request-id': 'tx136c028c478a4b40a7014-0061829c9f', + date: 'Wed, 03 Nov 2021 14:28:48 GMT', + 'x-iplb-request-id': '25A66014:1D97_3626E64B:01BB_61829C9E_3C28BD:960D' + }) + .intercept("/templates/test.odt", "HEAD") + .reply(200, () => { + return fs.createReadStream(path.join(__dirname, 'assets', 'file.txt')); + }) + .intercept("/templates/test.odt", "HEAD") + .reply(200, () => { + return fs.createReadStream(path.join(__dirname, 'assets', 'file.txt')); + }); + + let promise1 = getFileMetadataPromise() + let promise2 = getFileMetadataPromise() + + + Promise.all([promise1, promise2]).then(results => { + assert.strictEqual(results.length, 2); + assert.strictEqual(results[0]['etag'].length > 0, true); + assert.strictEqual(results[0]['x-openstack-request-id'].length > 0, true); + assert.strictEqual(results[0]['content-length'].length > 0, true); + assert.strictEqual(results[0]['date'].length > 0, true); + assert.strictEqual(results[1]['etag'].length > 0, true); + assert.strictEqual(results[1]['x-openstack-request-id'].length > 0, true); + assert.strictEqual(results[1]['content-length'].length > 0, true); + assert.strictEqual(results[1]['date'].length > 0, true); + assert.deepStrictEqual(storage.getConfig().actifStorage, 1); + assert.strictEqual(firstNock.pendingMocks().length, 0); + assert.strictEqual(secondNock.pendingMocks().length, 0); + assert.strictEqual(thirdNock.pendingMocks().length, 0); + done(); + }).catch(err => { + assert.strictEqual(err, null); + done(); + }); + }); + + it('should request the object storage in parallel and fallback to SBG if the main storage return a 500 error', function (done) { + + let firstNock = nock(publicUrlGRA) + .intercept("/templates/test.odt", "HEAD") + .reply(500, {}) + .intercept("/templates/test.odt", "HEAD") + .reply(500, {}); + + let secondNock = nock(authURL) + .post('/auth/tokens') + .reply(200, connectionResultSuccessV3, { "X-Subject-Token": tokenAuth }) + .post('/auth/tokens') + .reply(200, connectionResultSuccessV3, { "X-Subject-Token": tokenAuth }); + + let thirdNock = nock(publicUrlSBG) + .defaultReplyHeaders({ + 'content-length': '1492', + 'accept-ranges': 'bytes', + 'last-modified': 'Wed, 03 Nov 2021 13:02:39 GMT', + 'content-type': 'application/json', + etag: 'a30776a059eaf26eebf27756a849097d', + 'x-openstack-request-id': 'tx136c028c478a4b40a7014-0061829c9f', + date: 'Wed, 03 Nov 2021 14:28:48 GMT', + 'x-iplb-request-id': '25A66014:1D97_3626E64B:01BB_61829C9E_3C28BD:960D' + }) + .intercept("/templates/test.odt", "HEAD") + .reply(200, () => { + return fs.createReadStream(path.join(__dirname, 'assets', 'file.txt')); + }) + .intercept("/templates/test.odt", "HEAD") + .reply(200, () => { + return fs.createReadStream(path.join(__dirname, 'assets', 'file.txt')); + }); + + let promise1 = getFileMetadataPromise() + let promise2 = getFileMetadataPromise() + + + Promise.all([promise1, promise2]).then(results => { + assert.strictEqual(results.length, 2); + assert.strictEqual(results[0]['etag'].length > 0, true); + assert.strictEqual(results[0]['x-openstack-request-id'].length > 0, true); + assert.strictEqual(results[0]['content-length'].length > 0, true); + assert.strictEqual(results[0]['date'].length > 0, true); + assert.strictEqual(results[1]['etag'].length > 0, true); + assert.strictEqual(results[1]['x-openstack-request-id'].length > 0, true); + assert.strictEqual(results[1]['content-length'].length > 0, true); + assert.strictEqual(results[1]['date'].length > 0, true); + assert.deepStrictEqual(storage.getConfig().actifStorage, 1); + assert.strictEqual(firstNock.pendingMocks().length, 0); + assert.strictEqual(secondNock.pendingMocks().length, 0); + assert.strictEqual(thirdNock.pendingMocks().length, 0); + done(); + }).catch(err => { + assert.strictEqual(err, null); + done(); + }); + }); + }); + }); + }); + + describe('setFileMetadata', function () { + + const _headers = { + 'Content-Type': 'image/jpeg', + 'X-Object-Meta-LocationOrigin': 'Paris/France', + 'X-Delete-At': 1440619048 + } + + describe("SINGLE STORAGE", function () { + it('should set file metadata', function (done) { + const firstNock = nock(publicUrlGRA) + .matchHeader('x-object-meta-locationorigin', 'Paris/France') + .matchHeader('Content-Type', 'image/jpeg') + .matchHeader('X-Delete-At', 1440619048) + .defaultReplyHeaders({ + 'content-length': '0', + 'date': 'Wed, 03 Nov 2021 14:28:48 GMT', + 'x-trans-id': 'tx37ea34dcd1ed48ca9bc7d-0052d84b6f', + 'x-openstack-request-id': 'tx136c028c478a4b40a7014-0061829c9f', + }) + .post('/templates/test.odt') + .reply(200) + + storage.setFileMetadata('templates', 'test.odt', { headers: _headers }, (err, headers) => { + assert.strictEqual(err, null); + assert.strictEqual(firstNock.pendingMocks().length, 0); + assert.strictEqual(headers['x-trans-id'].length > 0, true); + assert.strictEqual(headers['x-openstack-request-id'].length > 0, true); + assert.strictEqual(headers['content-length'] === '0', true); + assert.strictEqual(headers['date'].length > 0, true); + done(); + }); + }); + + it('should reconnect automatically if the token is invalid and retry', function (done) { + let firstNock = nock(publicUrlGRA) + .matchHeader('x-object-meta-locationorigin', 'Paris/France') + .matchHeader('Content-Type', 'image/jpeg') + .matchHeader('X-Delete-At', 1440619048) + .defaultReplyHeaders({ + 'content-length': '0', + 'date': 'Wed, 03 Nov 2021 14:28:48 GMT', + 'x-trans-id': 'tx37ea34dcd1ed48ca9bc7d-0052d84b6f', + 'x-openstack-request-id': 'tx136c028c478a4b40a7014-0061829c9f', + }) + .post('/templates/test.odt') + .reply(401, 'Unauthorized') + .post('/templates/test.odt') + .reply(200) + + let secondNock = nock(authURL) + .post('/auth/tokens') + .reply(200, connectionResultSuccessV3, { "X-Subject-Token": tokenAuth }); + + storage.setFileMetadata('templates', 'test.odt', { headers: _headers }, (err, headers) => { + assert.strictEqual(err, null); + assert.strictEqual(firstNock.pendingMocks().length, 0); + assert.strictEqual(secondNock.pendingMocks().length, 0); + assert.strictEqual(headers['x-trans-id'].length > 0, true); + assert.strictEqual(headers['x-openstack-request-id'].length > 0, true); + assert.strictEqual(headers['content-length'] === '0', true); + assert.strictEqual(headers['date'].length > 0, true); + done(); + }); + }); + + it('should return an error if the single storage timout', function (done) { + storage.setTimeout(200); + const firstNock = nock(publicUrlGRA) + .matchHeader('x-object-meta-locationorigin', 'Paris/France') + .matchHeader('Content-Type', 'image/jpeg') + .matchHeader('X-Delete-At', 1440619048) + .post('/templates/test.odt') + .delayConnection(500) + .reply(200, {}) + + storage.setFileMetadata('templates', 'test.odt', { headers: _headers }, (err, headers) => { + assert.notStrictEqual(err, null); + assert.strictEqual(err.message, 'Object Storages are not available'); + assert.strictEqual(headers, undefined); + assert.strictEqual(firstNock.pendingMocks().length, 0); + done(); + }); + }); + + it('should return an error if containers or the file does not exists', function (done) { + const firstNock = nock(publicUrlGRA) + .matchHeader('x-object-meta-locationorigin', 'Paris/France') + .matchHeader('Content-Type', 'image/jpeg') + .matchHeader('X-Delete-At', 1440619048) + .post('/templates/test.odt') + .reply(404, '

Not Found

The resource could not be found.

'); + + storage.setFileMetadata('templates', 'test.odt', { headers: _headers }, (err, headers) => { + assert.notStrictEqual(err, null); + assert.strictEqual(err.message, 'File does not exist'); + assert.strictEqual(headers, undefined); + assert.strictEqual(firstNock.pendingMocks().length, 0); + done(); + }); + }); + + it('should return an error if the single storage return any kind of errors', function (done) { + const firstNock = nock(publicUrlGRA) + .matchHeader('x-object-meta-locationorigin', 'Paris/France') + .matchHeader('Content-Type', 'image/jpeg') + .matchHeader('X-Delete-At', 1440619048) + .post('/templates/test.odt') + .replyWithError('Error Message 1234'); + + storage.setFileMetadata('templates', 'test.odt', { headers: _headers }, (err, headers) => { + assert.notStrictEqual(err, null); + assert.strictEqual(err.message, 'Object Storages are not available'); + assert.strictEqual(headers, undefined); + assert.strictEqual(firstNock.pendingMocks().length, 0); + done(); + }); + }); + }) + + describe("MULTIPLE STORAGES", function () { + + beforeEach(function (done) { + const firstNock = nock(authURL) + .post('/auth/tokens') + .reply(200, connectionResultSuccessV3, { "X-Subject-Token": tokenAuth }); + + storage.setTimeout(5000); + storage.setStorages([{ + username : 'storage-1-user', + password : 'storage-1-password', + authUrl : authURL, + tenantName : 'storage-1-tenant', + region : 'GRA' + }, + { + username : 'storage-2-user', + password : 'storage-2-password', + authUrl : authURL, + tenantName : 'storage-2-tenant', + region : 'SBG' + }]); + storage.connection((err) => { + assert.strictEqual(err, null) + assert.strictEqual(firstNock.pendingMocks().length, 0); + done(); + }) + }) + + it('should reconnect automatically to the second object storage if the first storage authentication fail and should retry the request', function(done){ + + let firstNock = nock(publicUrlGRA) + .matchHeader('x-object-meta-locationorigin', 'Paris/France') + .matchHeader('Content-Type', 'image/jpeg') + .matchHeader('X-Delete-At', 1440619048) + /** 1 */ + .post('/templates/test.odt') + .reply(401, 'Unauthorized') + + + let secondNock = nock(authURL) + /** 2 */ + .post('/auth/tokens') + .reply(500, {}) + /** 3 */ + .post('/auth/tokens') + .reply(200, connectionResultSuccessV3, { "X-Subject-Token": tokenAuth }); + + let thirdNock = nock(publicUrlSBG) + .matchHeader('x-object-meta-locationorigin', 'Paris/France') + .matchHeader('Content-Type', 'image/jpeg') + .matchHeader('X-Delete-At', 1440619048) + .defaultReplyHeaders({ + 'content-length': '0', + 'date': 'Wed, 03 Nov 2021 14:28:48 GMT', + 'x-trans-id': 'tx37ea34dcd1ed48ca9bc7d-0052d84b6f', + 'x-openstack-request-id': 'tx136c028c478a4b40a7014-0061829c9f', + }) + /** 4 */ + .post('/templates/test.odt') + .reply(201, ''); + + storage.setFileMetadata('templates', 'test.odt', { headers: _headers }, (err, headers) => { + assert.strictEqual(err, null); + assert.strictEqual(firstNock.pendingMocks().length, 0); + assert.strictEqual(secondNock.pendingMocks().length, 0); + assert.strictEqual(thirdNock.pendingMocks().length, 0); + assert.strictEqual(headers['x-trans-id'].length > 0, true); + assert.strictEqual(headers['x-openstack-request-id'].length > 0, true); + assert.strictEqual(headers['content-length'] === '0', true); + assert.strictEqual(headers['date'].length > 0, true); + done(); + }); + }) + + it('should retry the request with the second object storage if the first object storage return a 500 error', function(done){ + + let firstNock = nock(publicUrlGRA) + .matchHeader('x-object-meta-locationorigin', 'Paris/France') + .matchHeader('Content-Type', 'image/jpeg') + .matchHeader('X-Delete-At', 1440619048) + .post('/templates/test.odt') + .reply(500, {}); + + let secondNock = nock(authURL) + .post('/auth/tokens') + .reply(200, connectionResultSuccessV3, { "X-Subject-Token": tokenAuth }); + + let thirdNock = nock(publicUrlSBG) + .matchHeader('x-object-meta-locationorigin', 'Paris/France') + .matchHeader('Content-Type', 'image/jpeg') + .matchHeader('X-Delete-At', 1440619048) + .defaultReplyHeaders({ + 'content-length': '0', + 'date': 'Wed, 03 Nov 2021 14:28:48 GMT', + 'x-trans-id': 'tx37ea34dcd1ed48ca9bc7d-0052d84b6f', + 'x-openstack-request-id': 'tx136c028c478a4b40a7014-0061829c9f', + }) + .post('/templates/test.odt') + .reply(201, ''); + + storage.setFileMetadata('templates', 'test.odt', { headers: _headers }, (err, headers) => { + assert.strictEqual(err, null); + assert.strictEqual(firstNock.pendingMocks().length, 0); + assert.strictEqual(secondNock.pendingMocks().length, 0); + assert.strictEqual(thirdNock.pendingMocks().length, 0); + assert.strictEqual(headers['x-trans-id'].length > 0, true); + assert.strictEqual(headers['x-openstack-request-id'].length > 0, true); + assert.strictEqual(headers['content-length'] === '0', true); + assert.strictEqual(headers['date'].length > 0, true); + done(); + }); + }) + + + + it('should retry the request with the second object storage if the first object storage timeout', function(done){ + storage.setTimeout(200); + let firstNock = nock(publicUrlGRA) + .matchHeader('x-object-meta-locationorigin', 'Paris/France') + .matchHeader('Content-Type', 'image/jpeg') + .matchHeader('X-Delete-At', 1440619048) + .post('/templates/test.odt') + .delayConnection(500) + .reply(200, {}); + + let secondNock = nock(authURL) + .post('/auth/tokens') + .reply(200, connectionResultSuccessV3, { "X-Subject-Token": tokenAuth }); + + let thirdNock = nock(publicUrlSBG) + .matchHeader('x-object-meta-locationorigin', 'Paris/France') + .matchHeader('Content-Type', 'image/jpeg') + .matchHeader('X-Delete-At', 1440619048) + .defaultReplyHeaders({ + 'content-length': '0', + 'date': 'Wed, 03 Nov 2021 14:28:48 GMT', + 'x-trans-id': 'tx37ea34dcd1ed48ca9bc7d-0052d84b6f', + 'x-openstack-request-id': 'tx136c028c478a4b40a7014-0061829c9f', + }) + .post('/templates/test.odt') + .reply(201, ''); + + storage.setFileMetadata('templates', 'test.odt', { headers: _headers }, (err, headers) => { + assert.strictEqual(err, null); + assert.strictEqual(firstNock.pendingMocks().length, 0); + assert.strictEqual(secondNock.pendingMocks().length, 0); + assert.strictEqual(thirdNock.pendingMocks().length, 0); + assert.strictEqual(headers['x-trans-id'].length > 0, true); + assert.strictEqual(headers['x-openstack-request-id'].length > 0, true); + assert.strictEqual(headers['content-length'] === '0', true); + assert.strictEqual(headers['date'].length > 0, true); + done(); + }); + }) + + it('should retry the request with the second storage if the first storage return any kind of errors', function (done) { + + let firstNock = nock(publicUrlGRA) + .matchHeader('x-object-meta-locationorigin', 'Paris/France') + .matchHeader('Content-Type', 'image/jpeg') + .matchHeader('X-Delete-At', 1440619048) + .post('/templates/test.odt') + .replyWithError('Error Message 1234'); + + + let secondNock = nock(authURL) + .post('/auth/tokens') + .reply(200, connectionResultSuccessV3, { "X-Subject-Token": tokenAuth }); + + let thirdNock = nock(publicUrlSBG) + .matchHeader('x-object-meta-locationorigin', 'Paris/France') + .matchHeader('Content-Type', 'image/jpeg') + .matchHeader('X-Delete-At', 1440619048) + .defaultReplyHeaders({ + 'content-length': '0', + 'date': 'Wed, 03 Nov 2021 14:28:48 GMT', + 'x-trans-id': 'tx37ea34dcd1ed48ca9bc7d-0052d84b6f', + 'x-openstack-request-id': 'tx136c028c478a4b40a7014-0061829c9f', + }) + .post('/templates/test.odt') + .reply(200); + + + storage.setFileMetadata('templates', 'test.odt', { headers: _headers }, (err, headers) => { + assert.strictEqual(err, null); + assert.strictEqual(firstNock.pendingMocks().length, 0); + assert.strictEqual(secondNock.pendingMocks().length, 0); + assert.strictEqual(thirdNock.pendingMocks().length, 0); + assert.strictEqual(headers['x-trans-id'].length > 0, true); + assert.strictEqual(headers['x-openstack-request-id'].length > 0, true); + assert.strictEqual(headers['content-length'] === '0', true); + assert.strictEqual(headers['date'].length > 0, true); + done(); + }); + }); + + describe("PARALLEL REQUESTS", function () { + + function setFileMetadataPromise() { + return new Promise((resolve, reject) => { + try { + storage.setFileMetadata('templates', 'test.odt', { headers: _headers }, (err, headers) => { + if (err) { + return reject(err); + } + return resolve(headers); + }); + } catch(err) { + return reject(err); + } + }); + } + + it('should request the object storage in parallel and fallback to SBG if the main storage return any kind of errors', function (done) { + + let firstNock = nock(publicUrlGRA) + .matchHeader('x-object-meta-locationorigin', 'Paris/France') + .matchHeader('Content-Type', 'image/jpeg') + .matchHeader('X-Delete-At', 1440619048) + .post('/templates/test.odt') + .replyWithError('Error Message 1234') + .post('/templates/test.odt') + .replyWithError('Error Message 1234'); + + let secondNock = nock(authURL) + .post('/auth/tokens') + .reply(200, connectionResultSuccessV3, { "X-Subject-Token": tokenAuth }) + .post('/auth/tokens') + .reply(200, connectionResultSuccessV3, { "X-Subject-Token": tokenAuth }); + + + let thirdNock = nock(publicUrlSBG) + .matchHeader('x-object-meta-locationorigin', 'Paris/France') + .matchHeader('Content-Type', 'image/jpeg') + .matchHeader('X-Delete-At', 1440619048) + .defaultReplyHeaders({ + 'content-length': '0', + 'date': 'Wed, 03 Nov 2021 14:28:48 GMT', + 'x-trans-id': 'tx37ea34dcd1ed48ca9bc7d-0052d84b6f', + 'x-openstack-request-id': 'tx136c028c478a4b40a7014-0061829c9f', + }) + .post('/templates/test.odt') + .reply(200) + .post('/templates/test.odt') + .reply(200) + + let promise1 = setFileMetadataPromise() + let promise2 = setFileMetadataPromise() + + + Promise.all([promise1, promise2]).then(results => { + assert.strictEqual(results.length, 2); + assert.strictEqual(results[0]['x-trans-id'].length > 0, true); + assert.strictEqual(results[0]['x-openstack-request-id'].length > 0, true); + assert.strictEqual(results[0]['content-length'] === '0', true); + assert.strictEqual(results[0]['date'].length > 0, true); + assert.strictEqual(results[1]['x-trans-id'].length > 0, true); + assert.strictEqual(results[1]['x-openstack-request-id'].length > 0, true); + assert.strictEqual(results[1]['content-length'] === '0', true); + assert.strictEqual(results[1]['date'].length > 0, true); + assert.deepStrictEqual(storage.getConfig().actifStorage, 1); + assert.strictEqual(firstNock.pendingMocks().length, 0); + assert.strictEqual(secondNock.pendingMocks().length, 0); + assert.strictEqual(thirdNock.pendingMocks().length, 0); + done(); + }).catch(err => { + assert.strictEqual(err, null); + done(); + }); + }); + + it('should request the object storage in parallel and fallback to SBG if the authentication of the main storage return an error', function (done) { + + let firstNock = nock(publicUrlGRA) + .matchHeader('x-object-meta-locationorigin', 'Paris/France') + .matchHeader('Content-Type', 'image/jpeg') + .matchHeader('X-Delete-At', 1440619048) + .post('/templates/test.odt') + .reply(401, 'Unauthorized') + .post('/templates/test.odt') + .reply(401, 'Unauthorized') + .post('/templates/test.odt') + .reply(401, 'Unauthorized') + + let secondNock = nock(authURL) + .post('/auth/tokens') + .reply(500, {}) + .post('/auth/tokens') + .reply(500, {}) + .post('/auth/tokens') + .reply(500, {}) + .post('/auth/tokens') + .reply(200, connectionResultSuccessV3, { "X-Subject-Token": tokenAuth }) + .post('/auth/tokens') + .reply(200, connectionResultSuccessV3, { "X-Subject-Token": tokenAuth }) + .post('/auth/tokens') + .reply(200, connectionResultSuccessV3, { "X-Subject-Token": tokenAuth }); + + let thirdNock = nock(publicUrlSBG) + .matchHeader('x-object-meta-locationorigin', 'Paris/France') + .matchHeader('Content-Type', 'image/jpeg') + .matchHeader('X-Delete-At', 1440619048) + .defaultReplyHeaders({ + 'content-length': '0', + 'date': 'Wed, 03 Nov 2021 14:28:48 GMT', + 'x-trans-id': 'tx37ea34dcd1ed48ca9bc7d-0052d84b6f', + 'x-openstack-request-id': 'tx136c028c478a4b40a7014-0061829c9f', + }) + .post('/templates/test.odt') + .reply(200) + .post('/templates/test.odt') + .reply(200) + .post('/templates/test.odt') + .reply(200) + .post('/templates/test.odt') + .reply(200) + + let promise1 = setFileMetadataPromise() + let promise2 = setFileMetadataPromise() + let promise3 = setFileMetadataPromise() + + Promise.all([promise1, promise2, promise3]).then(async results => { + + assert.strictEqual(results.length, 3); + assert.strictEqual(results[0]['x-trans-id'].length > 0, true); + assert.strictEqual(results[0]['x-openstack-request-id'].length > 0, true); + assert.strictEqual(results[0]['content-length'] === '0', true); + assert.strictEqual(results[0]['date'].length > 0, true); + assert.strictEqual(results[1]['x-trans-id'].length > 0, true); + assert.strictEqual(results[1]['x-openstack-request-id'].length > 0, true); + assert.strictEqual(results[1]['content-length'] === '0', true); + assert.strictEqual(results[1]['date'].length > 0, true); + assert.strictEqual(results[2]['x-trans-id'].length > 0, true); + assert.strictEqual(results[2]['x-openstack-request-id'].length > 0, true); + assert.strictEqual(results[2]['content-length'] === '0', true); + assert.strictEqual(results[2]['date'].length > 0, true); + + + let _result3 = await setFileMetadataPromise(); + assert.strictEqual(_result3['x-trans-id'].length > 0, true); + assert.strictEqual(_result3['x-openstack-request-id'].length > 0, true); + assert.strictEqual(_result3['content-length'] === '0', true); + assert.strictEqual(_result3['date'].length > 0, true); + + assert.deepStrictEqual(storage.getConfig().actifStorage, 1); + assert.strictEqual(firstNock.pendingMocks().length, 0); + assert.strictEqual(secondNock.pendingMocks().length, 0); + assert.strictEqual(thirdNock.pendingMocks().length, 0); + done(); + }); + }); + + it('should request the object storage in parallel and fallback to SBG if the main storage timeout', function (done) { + storage.setTimeout(200); + + let firstNock = nock(publicUrlGRA) + .matchHeader('x-object-meta-locationorigin', 'Paris/France') + .matchHeader('Content-Type', 'image/jpeg') + .matchHeader('X-Delete-At', 1440619048) + .post('/templates/test.odt') + .delayConnection(500) + .reply(200, {}) + .post('/templates/test.odt') + .delayConnection(500) + .reply(200, {}); + + let secondNock = nock(authURL) + .post('/auth/tokens') + .reply(200, connectionResultSuccessV3, { "X-Subject-Token": tokenAuth }) + .post('/auth/tokens') + .reply(200, connectionResultSuccessV3, { "X-Subject-Token": tokenAuth }); + + let thirdNock = nock(publicUrlSBG) + .matchHeader('x-object-meta-locationorigin', 'Paris/France') + .matchHeader('Content-Type', 'image/jpeg') + .matchHeader('X-Delete-At', 1440619048) + .defaultReplyHeaders({ + 'content-length': '0', + 'date': 'Wed, 03 Nov 2021 14:28:48 GMT', + 'x-trans-id': 'tx37ea34dcd1ed48ca9bc7d-0052d84b6f', + 'x-openstack-request-id': 'tx136c028c478a4b40a7014-0061829c9f', + }) + .post('/templates/test.odt') + .reply(200) + .post('/templates/test.odt') + .reply(200) + + + let promise1 = setFileMetadataPromise() + let promise2 = setFileMetadataPromise() + + + Promise.all([promise1, promise2]).then(results => { + assert.strictEqual(results.length, 2); + assert.strictEqual(results[0]['x-trans-id'].length > 0, true); + assert.strictEqual(results[0]['x-openstack-request-id'].length > 0, true); + assert.strictEqual(results[0]['content-length'] === '0', true); + assert.strictEqual(results[0]['date'].length > 0, true); + assert.strictEqual(results[1]['x-trans-id'].length > 0, true); + assert.strictEqual(results[1]['x-openstack-request-id'].length > 0, true); + assert.strictEqual(results[1]['content-length'] === '0', true); + assert.strictEqual(results[1]['date'].length > 0, true); + + assert.deepStrictEqual(storage.getConfig().actifStorage, 1); + assert.strictEqual(firstNock.pendingMocks().length, 0); + assert.strictEqual(secondNock.pendingMocks().length, 0); + assert.strictEqual(thirdNock.pendingMocks().length, 0); + done(); + }).catch(err => { + assert.strictEqual(err, null); + done(); + }); + }); + + it('should request the object storage in parallel and fallback to SBG if the main storage return a 500 error', function (done) { + + let firstNock = nock(publicUrlGRA) + .matchHeader('x-object-meta-locationorigin', 'Paris/France') + .matchHeader('Content-Type', 'image/jpeg') + .matchHeader('X-Delete-At', 1440619048) + .post('/templates/test.odt') + .reply(500, {}) + .post('/templates/test.odt') + .reply(500, {}); + + let secondNock = nock(authURL) + .post('/auth/tokens') + .reply(200, connectionResultSuccessV3, { "X-Subject-Token": tokenAuth }) + .post('/auth/tokens') + .reply(200, connectionResultSuccessV3, { "X-Subject-Token": tokenAuth }); + + let thirdNock = nock(publicUrlSBG) + .matchHeader('x-object-meta-locationorigin', 'Paris/France') + .matchHeader('Content-Type', 'image/jpeg') + .matchHeader('X-Delete-At', 1440619048) + .defaultReplyHeaders({ + 'content-length': '0', + 'date': 'Wed, 03 Nov 2021 14:28:48 GMT', + 'x-trans-id': 'tx37ea34dcd1ed48ca9bc7d-0052d84b6f', + 'x-openstack-request-id': 'tx136c028c478a4b40a7014-0061829c9f', + }) + .post('/templates/test.odt') + .reply(200) + .post('/templates/test.odt') + .reply(200) + + let promise1 = setFileMetadataPromise() + let promise2 = setFileMetadataPromise() + + + Promise.all([promise1, promise2]).then(results => { + assert.strictEqual(results.length, 2); + assert.strictEqual(results[0]['x-trans-id'].length > 0, true); + assert.strictEqual(results[0]['x-openstack-request-id'].length > 0, true); + assert.strictEqual(results[0]['content-length'] === '0', true); + assert.strictEqual(results[0]['date'].length > 0, true); + assert.strictEqual(results[1]['x-trans-id'].length > 0, true); + assert.strictEqual(results[1]['x-openstack-request-id'].length > 0, true); + assert.strictEqual(results[1]['content-length'] === '0', true); + assert.strictEqual(results[1]['date'].length > 0, true); + + assert.deepStrictEqual(storage.getConfig().actifStorage, 1); + assert.strictEqual(firstNock.pendingMocks().length, 0); + assert.strictEqual(secondNock.pendingMocks().length, 0); + assert.strictEqual(thirdNock.pendingMocks().length, 0); + done(); + }).catch(err => { + assert.strictEqual(err, null); + done(); + }); + }); + + }); + }); + }); }); let connectionResultSuccessV3 = {