blob: 9e6c37c87d94ab05fd8abd6ff998fa268bd15eff [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';
const { assert } = require('chai');
const sinon = require('sinon');
const utils = require('../../lib/utils');
const helper = require('../test-helper');
const AddressResolver = utils.AddressResolver;
describe('utils', function () {
describe('timesLimit()', function () {
it('should handle sync and async functions', function (done) {
utils.timesLimit(5, 10, function (i, next) {
if (i === 0) {
return setImmediate(next);
}
next();
}, done);
});
});
describe('allocBuffer', function () {
it('should return a buffer filled with zeros', function () {
const b = utils.allocBuffer(256);
helper.assertInstanceOf(b, Buffer);
assert.strictEqual(b.length, 256);
for (let i = 0; i < b.length; i++) {
assert.strictEqual(b[i], 0);
}
});
});
describe('allocBufferUnsafe', function () {
it('should return a Buffer with the correct length', function () {
const b = utils.allocBuffer(256);
helper.assertInstanceOf(b, Buffer);
assert.strictEqual(b.length, 256);
});
});
describe('allocBufferFromString', function () {
it('should throw TypeError when the first parameter is not a string', function () {
if (typeof Buffer.from === 'undefined') {
// Our method validates for a string
assert.throws(function () {
utils.allocBufferFromString([]);
}, TypeError);
}
assert.throws(function () {
utils.allocBufferFromString(null);
}, TypeError);
assert.throws(function () {
utils.allocBufferFromString(undefined);
}, TypeError);
assert.throws(function () {
utils.allocBufferFromString(100);
}, TypeError);
});
it('should return a Buffer representing the string value', function () {
const text = 'Hello safe buffer';
const b = utils.allocBufferFromString(text);
helper.assertInstanceOf(b, Buffer);
assert.strictEqual(b.toString(), text);
});
});
describe('allocBufferFromArray', function () {
it('should throw TypeError when the first parameter is not a string', function () {
if (typeof Buffer.from === 'undefined') {
// Our method validates for an Array instance
assert.throws(function () {
utils.allocBufferFromArray('hello');
}, TypeError);
}
assert.throws(function () {
utils.allocBufferFromArray(null);
}, TypeError);
assert.throws(function () {
utils.allocBufferFromArray(undefined);
}, TypeError);
assert.throws(function () {
utils.allocBufferFromArray(100);
}, TypeError);
});
it('should return a Buffer representing the string value', function () {
const arr = [ 0xff, 0, 0x1a ];
const b = utils.allocBufferFromArray(arr);
helper.assertInstanceOf(b, Buffer);
assert.strictEqual(b.length, arr.length);
for (let i = 0; i < b.length; i++) {
assert.strictEqual(b[i], arr[i]);
}
});
});
describe('AddressResolver', () => {
describe('#getIp()', () => {
it('should return the resolved addresses in a round-robin fashion', async () => {
const addresses = ['10.10.10.1', '10.10.10.2'];
const resolver = new AddressResolver({ nameOrIp: 'dummy-host', dns: getDnsMock(addresses) });
await resolver.init();
const result = new Map();
for (let i = 0; i < 10; i++) {
const ip = resolver.getIp();
result.set(ip, (result.get(ip) || 0) + 1);
}
assert.deepStrictEqual(Array.from(result.keys()).sort(), addresses);
assert.strictEqual(result.get(addresses[0]), 5);
assert.strictEqual(result.get(addresses[1]), 5);
});
});
describe('#init()', () => {
it('should callback in error when resolution fails', async () => {
const error = new Error('dummy error');
const resolver = new AddressResolver({ nameOrIp: 'dummy-host', dns: getDnsMock(error) });
let err;
try {
await resolver.init();
} catch (e) {
err = e;
}
assert.strictEqual(err, error);
});
it('should callback in error when resolution returns empty', async () => {
const resolver = new AddressResolver({ nameOrIp: 'dummy-host', dns: getDnsMock([]) });
let err;
try {
await resolver.init();
} catch (e) {
err = e;
}
helper.assertInstanceOf(err, Error);
helper.assertContains(err.message, 'could not be resolved');
});
it('should support a IP address as parameter', async () => {
const address = '10.10.10.255';
const resolver = new AddressResolver({ nameOrIp: address, dns: getDnsMock(new Error('ip must be used')) });
await resolver.init();
for (let i = 0; i < 10; i++) {
assert.strictEqual(resolver.getIp(), address);
}
});
});
describe('#refresh()', () => {
it('should ignore failures', async () => {
let initialized = false;
const address = '10.10.10.1';
const dnsMock = {
resolve4: (name, cb) => {
if (!initialized) {
cb(null, [ address ]);
} else {
cb(new Error('this error should be ignored'));
}
}
};
const resolver = new AddressResolver({ nameOrIp: 'dummy-host', dns: dnsMock });
await resolver.init();
assert.strictEqual(resolver.getIp(), address);
initialized = true;
await resolver.refresh();
// getIp() should work as usual
assert.strictEqual(resolver.getIp(), address);
});
it('should update the ips returned by getIp() methods', async () => {
const addresses = ['10.10.10.1'];
const resolver = new AddressResolver({ nameOrIp: 'dummy-host', dns: getDnsMock(addresses) });
await resolver.init();
// Update the addresses that are going to be resolved
const initialAddresses = addresses.slice(0);
addresses.splice(0, 1);
addresses.push('10.10.10.1', '10.10.10.2');
// Validate that get ip uses a cached value
for (let i = 0; i < 10; i++) {
assert.strictEqual(resolver.getIp(), initialAddresses[0]);
}
await resolver.refresh();
const result = new Map();
// Should have resolved
for (let i = 0; i < 10; i++) {
const ip = resolver.getIp();
result.set(ip, (result.get(ip) || 0) + 1);
}
assert.deepStrictEqual(Array.from(result.keys()).sort(), addresses);
assert.strictEqual(result.get(addresses[0]), 5);
assert.strictEqual(result.get(addresses[1]), 5);
});
it('should resolve once when called in parallel', async () => {
const address = '10.10.10.1';
const dnsMock = {
resolve4: sinon.fake((name, cb) => {
cb(null, [ address ]);
})
};
const resolver = new AddressResolver({ nameOrIp: 'dummy-host', dns: dnsMock });
await resolver.init();
assert.strictEqual(resolver.getIp(), address);
assert.isTrue(dnsMock.resolve4.calledOnce);
await Promise.all(Array(10).fill(0).map(() => resolver.refresh()));
assert.strictEqual(dnsMock.resolve4.callCount, 2);
});
});
});
});
function getDnsMock(addressesOrErr) {
return {
resolve4: (name, cb) => {
if (Array.isArray(addressesOrErr)) {
// Use a copy of the addresses
process.nextTick(() => cb(null, Array.from(addressesOrErr)));
} else {
process.nextTick(() => cb(addressesOrErr));
}
}
};
}