| /* |
| * 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)); |
| } |
| } |
| }; |
| } |