blob: bbcf761dc705da4983bcc84bbc5d7dc67b2f9cf5 [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 util = require('util');
const events = require('events');
const Client = require('../../lib/client');
const clientOptions = require('../../lib/client-options');
const auth = require('../../lib/auth');
const types = require('../../lib/types');
const { dataTypes } = types;
const loadBalancing = require('../../lib/policies/load-balancing');
const retry = require('../../lib/policies/retry');
const speculativeExecution = require('../../lib/policies/speculative-execution');
const timestampGeneration = require('../../lib/policies/timestamp-generation');
const Encoder = require('../../lib/encoder');
const utils = require('../../lib/utils');
const writers = require('../../lib/writers');
const OperationState = require('../../lib/operation-state');
const helper = require('../test-helper');
const contactPoints = ['a'];
describe('types', function () {
describe('Long', function () {
const Long = types.Long;
it('should convert from and to Buffer', function () {
/* eslint-disable no-multi-spaces */
[
//int64 decimal value //hex value
['-123456789012345678', 'fe4964b459cf0cb2'],
['-800000000000000000', 'f4e5d43d13b00000'],
['-888888888888888888', 'f3aa0843dcfc71c8'],
['-555555555555555555', 'f84a452a6a1dc71d'],
['-789456', 'fffffffffff3f430'],
['-911111111111111144', 'f35b15458f4f8e18'],
['-9007199254740993', 'ffdfffffffffffff'],
['-1125899906842624', 'fffc000000000000'],
['555555555555555555', '07b5bad595e238e3'],
['789456' , '00000000000c0bd0'],
['888888888888888888', '0c55f7bc23038e38']
].forEach(function (item) {
const buffer = utils.allocBufferFromString(item[1], 'hex');
const value = Long.fromBuffer(buffer);
assert.strictEqual(value.toString(), item[0]);
assert.strictEqual(Long.toBuffer(value).toString('hex'), buffer.toString('hex'),
'Hexadecimal values should match for ' + item[1]);
});
/* eslint-enable no-multi-spaces */
});
it('should return a valid number for int greater than 2^53 and less than -2^53', function () {
[
new Long(0, 0x7FFFFFFF),
new Long(0xFFFFFFFF, 0x7FFFFFFF),
new Long(0xFFFFFFFF, 0x7FFFFF01)
].forEach(function (item) {
assert.ok(item.toNumber() > Math.pow(2, 53), util.format('Value should be greater than 2^53 for %s', item));
});
[
new Long(0, 0xF0000000),
new Long(0, 0xF0000001)
].forEach(function (item) {
assert.ok(item.toNumber() < Math.pow(2, 53), util.format('Value should be less than -2^53 for %s', item));
});
});
});
describe('Integer', function () {
const Integer = types.Integer;
/* eslint-disable no-multi-spaces */
const values = [
//hex value | string varint
['02000001', '33554433'],
['02000000', '33554432'],
['1111111111111111', '1229782938247303441'],
['01', '1'],
['0400', '1024'],
['7fffffff', '2147483647'],
['02000000000001', '562949953421313'],
['ff', '-1'],
['ff01', '-255'],
['faa8c4', '-350012'],
['eb233d9f', '-350012001'],
['f7d9c411c4', '-35001200188'],
['f0bdc0', '-1000000'],
['ff172b5aeff4', '-1000000000012'],
['9c', '-100'],
['c31e', '-15586'],
['00c31e', '49950'],
['0500e3c2cef9eaaab3', '92297829382473034419'],
['033171cbe0fac2d665b78d4e', '988229782938247303441911118'],
['fcce8e341f053d299a4872b2', '-988229782938247303441911118'],
['00b70cefb9c19c9c5112972fd01a4e676d', '243315893003967298069149506221212854125'],
['00ba0cef', '12193007'],
['00ffffffff', '4294967295']
];
/* eslint-enable no-multi-spaces */
it('should create from buffer', function () {
values.forEach(function (item) {
const buffer = utils.allocBufferFromString(item[0], 'hex');
const value = Integer.fromBuffer(buffer);
assert.strictEqual(value.toString(), item[1]);
});
});
it('should convert to buffer', function () {
values.forEach(function (item) {
const buffer = Integer.toBuffer(Integer.fromString(item[1]));
assert.strictEqual(buffer.toString('hex'), item[0]);
});
});
});
describe('Tuple', function () {
const Tuple = types.Tuple;
describe('#get()', function () {
it('should return the element at position', function () {
const t = new Tuple('first', 'second');
assert.strictEqual(t.get(0), 'first');
assert.strictEqual(t.get(1), 'second');
assert.strictEqual(t.get(2), undefined);
assert.strictEqual(t.length, 2);
});
});
describe('#toString()', function () {
it('should return the string of the elements surrounded by parenthesis', function () {
const id = types.Uuid.random();
const decimal = types.BigDecimal.fromString('1.2');
const t = new Tuple(id, decimal, 0);
assert.strictEqual(t.toString(), '(' + id.toString() + ',' + decimal.toString() + ',0)');
});
});
describe('#toJSON()', function () {
it('should return the string of the elements surrounded by square brackets', function () {
const id = types.TimeUuid.now();
const decimal = types.BigDecimal.fromString('-1');
const t = new Tuple(id, decimal, 1, {z: 1});
assert.strictEqual(JSON.stringify(t), '["' + id.toString() + '","' + decimal.toString() + '",1,{"z":1}]');
});
});
describe('#values()', function () {
it('should return the Array representation of the Tuple', function () {
const t = new Tuple('first2', 'second2', 'third2');
assert.strictEqual(t.length, 3);
const values = t.values();
assert.ok(Array.isArray(values));
assert.strictEqual(values.length, 3);
assert.strictEqual(values[0], 'first2');
assert.strictEqual(values[1], 'second2');
assert.strictEqual(values[2], 'third2');
});
it('when modifying the returned Array the Tuple should not change its values', function () {
const t = new Tuple('first3', 'second3', 'third3');
const values = t.values();
assert.strictEqual(values.length, 3);
values[0] = 'whatever';
values.shift();
assert.strictEqual(t.get(0), 'first3');
assert.strictEqual(t.get(1), 'second3');
assert.strictEqual(t.get(2), 'third3');
});
});
describe('fromArray()', () => {
it('should return a Tuple instance using the Array items as elements', () => {
[
[ 'a' ],
[ 'a', 'b' ],
[ 'a', 'b', 'c' ]
].forEach(items => {
const tuple = Tuple.fromArray(items);
// The Array instance should not be the same
assert.notStrictEqual(tuple.elements, items);
// The elements should be the same
assert.deepStrictEqual(tuple.elements, items);
});
});
});
});
describe('LocalDate', function () {
const LocalDate = types.LocalDate;
describe('new LocalDate', function (){
it('should refuse to create LocalDate from invalid values.', function () {
assert.throws(() => new types.LocalDate(), Error);
assert.throws(() => new types.LocalDate(undefined), Error);
// Outside of ES5 Date range.
assert.throws(() => new types.LocalDate(-271821, 4, 19), Error);
assert.throws(() => new types.LocalDate(275760, 9, 14), Error);
// Outside of LocalDate range.
assert.throws(() => new types.LocalDate(-2147483649), Error);
assert.throws(() => new types.LocalDate(2147483648), Error);
});
});
describe('#toString()', function () {
it('should return the string in the form of yyyy-mm-dd', function () {
assert.strictEqual(new LocalDate(2015, 2, 1).toString(), '2015-02-01');
assert.strictEqual(new LocalDate(2015, 12, 13).toString(), '2015-12-13');
assert.strictEqual(new LocalDate(101, 12, 14).toString(), '0101-12-14');
assert.strictEqual(new LocalDate(-100, 11, 6).toString(), '-0100-11-06');
});
});
describe('#fromBuffer() and #toBuffer()', function () {
it('should encode and decode a LocalDate', function () {
const value = new LocalDate(2010, 8, 5);
const encoded = value.toBuffer();
const decoded = LocalDate.fromBuffer(encoded);
assert.strictEqual(decoded.toString(), value.toString());
assert.ok(decoded.equals(value));
assert.ok(value.equals(decoded));
});
});
describe('#fromString()', function () {
it('should parse the string representation as yyyy-mm-dd', function () {
[
['1200-12-30', 1200, 12, 30],
['1-1-1', 1, 1, 1],
['21-2-1', 21, 2, 1],
['-21-2-1', -21, 2, 1],
['2010-4-29', 2010, 4, 29],
['-199-06-30', -199, 6, 30],
['1201-04-03', 1201, 4, 3],
['-1201-04-03', -1201, 4, 3],
['0-1-1', 0, 1, 1]
].forEach(function (item) {
const value = LocalDate.fromString(item[0]);
assert.strictEqual(value.year, item[1]);
assert.strictEqual(value.month, item[2]);
assert.strictEqual(value.day, item[3]);
});
});
it('should parse the string representation as since epoch days', function () {
[
['0', '1970-01-01'],
['1', '1970-01-02'],
['2147483647', '2147483647'],
['-2147483648', '-2147483648'],
['-719162', '0001-01-01']
].forEach(function (item) {
const value = LocalDate.fromString(item[0]);
assert.strictEqual(value.toString(), item[1]);
});
});
it('should throw when string representation is invalid', function () {
[
'',
'1880-1',
'1880-1-z',
undefined,
null,
' '
].forEach(function (value) {
assert.throws(function () {
LocalDate.fromString(value);
});
});
});
});
});
describe('LocalTime', function () {
const LocalTime = types.LocalTime;
const Long = types.Long;
/* eslint-disable no-multi-spaces */
const values = [
//Long value | string representation | hour/min/sec/nanos
['1000000001', '00:00:01.000000001', [0, 0, 1, 1]],
['0', '00:00:00', [0, 0, 0, 0]],
['3600000006001', '01:00:00.000006001', [1, 0, 0, 6001]],
['61000000000', '00:01:01', [0, 1, 1, 0]],
['610000030000', '00:10:10.00003', [0, 10, 10, 30000]],
['52171800000000', '14:29:31.8', [14, 29, 31, 800000000]],
['52171800600000', '14:29:31.8006', [14, 29, 31, 800600000]]
];
/* eslint-enable no-multi-spaces */
describe('new LocalTime', function () {
it('should refuse to create LocalTime from invalid values.', function () {
// Not a long.
assert.throws(() => new types.LocalTime(23.0), Error);
// < 0
assert.throws(() => new types.LocalTime(types.Long(-1)), Error);
// > maxNanos
assert.throws(() => new types.LocalTime(Long.fromString('86400000000000')), Error);
});
});
describe('#toString()', function () {
it('should return the string representation', function () {
values.forEach(function (item) {
const val = new LocalTime(Long.fromString(item[0]));
assert.strictEqual(val.toString(), item[1]);
});
});
});
describe('#toJSON()', function () {
it('should return the string representation', function () {
values.forEach(function (item) {
const val = new LocalTime(Long.fromString(item[0]));
assert.strictEqual(val.toString(), item[1]);
});
});
});
describe('#fromString()', function () {
it('should parse the string representation', function () {
values.forEach(function (item) {
const val = LocalTime.fromString(item[1]);
assert.ok(new LocalTime(Long.fromString(item[0])).equals(val));
assert.ok(new LocalTime(Long.fromString(item[0]))
.getTotalNanoseconds()
.equals(val.getTotalNanoseconds()));
});
});
});
describe('#toBuffer() and fromBuffer()', function () {
values.forEach(function (item) {
const val = new LocalTime(Long.fromString(item[0]));
const encoded = val.toBuffer();
const decoded = LocalTime.fromBuffer(encoded);
assert.ok(decoded.equals(val));
assert.strictEqual(val.toString(), decoded.toString());
});
});
describe('#hour #minute #second #nanosecond', function () {
it('should get the correct parts', function () {
values.forEach(function (item) {
const val = new LocalTime(Long.fromString(item[0]));
const parts = item[2];
assert.strictEqual(val.hour, parts[0]);
assert.strictEqual(val.minute, parts[1]);
assert.strictEqual(val.second, parts[2]);
assert.strictEqual(val.nanosecond, parts[3]);
});
});
});
describe('fromDate()', function () {
it('should use the local time', function () {
const date = new Date();
const time = LocalTime.fromDate(date, 1);
assert.strictEqual(time.hour, date.getHours());
assert.strictEqual(time.minute, date.getMinutes());
assert.strictEqual(time.second, date.getSeconds());
assert.strictEqual(time.nanosecond, date.getMilliseconds() * 1000000 + 1);
});
});
describe('fromMilliseconds', function () {
it('should default nanoseconds to 0 when not provided', function () {
const time = LocalTime.fromMilliseconds(1);
assert.ok(time.equals(LocalTime.fromMilliseconds(1, 0)));
});
});
});
describe('ResultStream', function () {
it('should be readable as soon as it has data', function (done) {
const buf = [];
const stream = new types.ResultStream();
stream.on('end', function streamEnd() {
assert.strictEqual(Buffer.concat(buf).toString(), 'Jimmy McNulty');
done();
});
stream.on('readable', function streamReadable() {
let item;
while ((item = stream.read())) {
buf.push(item);
}
});
stream.add(utils.allocBufferFromString('Jimmy'));
stream.add(utils.allocBufferFromString(' '));
stream.add(utils.allocBufferFromString('McNulty'));
stream.add(null);
});
it('should buffer until is read', function (done) {
const buf = [];
const stream = new types.ResultStream();
stream.add(utils.allocBufferFromString('Stringer'));
stream.add(utils.allocBufferFromString(' '));
stream.add(utils.allocBufferFromString('Bell'));
stream.add(null);
stream.on('end', function streamEnd() {
assert.equal(Buffer.concat(buf).toString(), 'Stringer Bell');
done();
});
stream.on('readable', function streamReadable() {
let item;
while ((item = stream.read())) {
buf.push(item);
}
});
});
it('should be readable until the end', function (done) {
const buf = [];
const stream = new types.ResultStream();
stream.add(utils.allocBufferFromString('Omar'));
stream.add(utils.allocBufferFromString(' '));
stream.on('end', function streamEnd() {
assert.equal(Buffer.concat(buf).toString(), 'Omar Little');
done();
});
stream.on('readable', function streamReadable() {
let item;
while ((item = stream.read())) {
buf.push(item);
}
});
stream.add(utils.allocBufferFromString('Little'));
stream.add(null);
});
it('should be readable on objectMode', function (done) {
const buf = [];
const stream = new types.ResultStream({objectMode: true});
//passing objects
stream.add({toString: function (){return 'One';}});
stream.add({toString: function (){return 'Two';}});
stream.add(null);
stream.on('end', function streamEnd() {
assert.equal(buf.join(' '), 'One Two');
done();
});
stream.on('readable', function streamReadable() {
let item;
while ((item = stream.read())) {
buf.push(item);
}
});
});
});
describe('Row', function () {
it('should get the value by column name or index', function () {
const columns = [{name: 'first', type: { code: dataTypes.varchar}}, {name: 'second', type: { code: dataTypes.varchar}}];
const row = new types.Row(columns);
row['first'] = 'hello';
row['second'] = 'world';
assert.ok(row.get, 'It should contain a get method');
assert.strictEqual(row['first'], 'hello');
assert.strictEqual(row.get('first'), row['first']);
assert.strictEqual(row.get(0), row['first']);
assert.strictEqual(row.get('second'), row['second']);
assert.strictEqual(row.get(1), row['second']);
});
it('should enumerate only columns defined', function () {
const columns = [{name: 'col1', type: { code: dataTypes.varchar}}, {name: 'col2', type: { code: dataTypes.varchar}}];
const row = new types.Row(columns);
row['col1'] = 'val1';
row['col2'] = 'val2';
assert.strictEqual(JSON.stringify(row), JSON.stringify({col1: 'val1', col2: 'val2'}));
});
it('should be serializable to json', function () {
let i;
let columns = [{name: 'col1', type: { code: dataTypes.varchar}}, {name: 'col2', type: { code: dataTypes.varchar}}];
let row = new types.Row(columns, [utils.allocBufferFromString('val1'), utils.allocBufferFromString('val2')]);
row['col1'] = 'val1';
row['col2'] = 'val2';
assert.strictEqual(JSON.stringify(row), JSON.stringify({col1: 'val1', col2: 'val2'}));
columns = [
{name: 'cid', type: { code: dataTypes.uuid}},
{name: 'ctid', type: { code: dataTypes.timeuuid}},
{name: 'clong', type: { code: dataTypes.bigint}},
{name: 'cvarint', type: { code: dataTypes.varint}}
];
let rowValues = [
types.Uuid.random(),
types.TimeUuid.now(),
types.Long.fromNumber(1000),
types.Integer.fromNumber(22)
];
row = new types.Row(columns);
for (i = 0; i < columns.length; i++) {
row[columns[i].name] = rowValues[i];
}
let expected = util.format('{"cid":"%s","ctid":"%s","clong":"1000","cvarint":"22"}',
rowValues[0].toString(), rowValues[1].toString());
assert.strictEqual(JSON.stringify(row), expected);
rowValues = [
types.BigDecimal.fromString("1.762"),
new types.InetAddress(utils.allocBufferFromArray([192, 168, 0, 1])),
null];
columns = [
{name: 'cdecimal', type: { code: dataTypes.decimal}},
{name: 'inet1', type: { code: dataTypes.inet}},
{name: 'inet2', type: { code: dataTypes.inet}}
];
row = new types.Row(columns);
for (i = 0; i < columns.length; i++) {
row[columns[i].name] = rowValues[i];
}
expected = '{"cdecimal":"1.762","inet1":"192.168.0.1","inet2":null}';
assert.strictEqual(JSON.stringify(row), expected);
});
it('should have values that can be inspected', function () {
const columns = [{name: 'col10', type: { code: dataTypes.varchar}}, {name: 'col2', type: { code: dataTypes.int}}];
const row = new types.Row(columns);
row['col10'] = 'val1';
row['col2'] = 2;
helper.assertContains(util.inspect(row), util.inspect({col10: 'val1', col2: 2}));
});
});
describe('uuid() backward-compatibility', function () {
it('should generate a random string uuid', function () {
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
const val = types.uuid();
assert.strictEqual(typeof val, 'string');
assert.strictEqual(val.length, 36);
assert.ok(uuidRegex.test(val));
assert.notEqual(val, types.uuid());
});
it('should fill in the values in a buffer', function () {
const buf = utils.allocBufferUnsafe(16);
const val = types.uuid(null, buf);
assert.strictEqual(val, buf);
});
});
describe('timeuuid() backward-compatibility', function () {
it('should generate a string uuid', function () {
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
const val = types.timeuuid();
assert.strictEqual(typeof val, 'string');
assert.strictEqual(val.length, 36);
assert.ok(uuidRegex.test(val));
assert.notEqual(val, types.timeuuid());
});
it('should fill in the values in a buffer', function () {
const buf = utils.allocBufferUnsafe(16);
const val = types.timeuuid(null, buf);
assert.strictEqual(val, buf);
});
});
describe('generateTimestamp()', function () {
it('should generate using date and microseconds parts', function () {
let date = new Date();
let value = types.generateTimestamp(date, 123);
assert.instanceOf(value, types.Long);
assert.strictEqual(value.toString(), types.Long
.fromNumber(date.getTime())
.multiply(types.Long.fromInt(1000))
.add(types.Long.fromInt(123))
.toString());
date = new Date('2010-04-29');
value = types.generateTimestamp(date, 898);
assert.instanceOf(value, types.Long);
assert.strictEqual(value.toString(), types.Long
.fromNumber(date.getTime())
.multiply(types.Long.fromInt(1000))
.add(types.Long.fromInt(898))
.toString());
});
});
});
describe('utils', function () {
describe('#extend()', function () {
it('should allow null sources', function () {
const originalObject = {};
const extended = utils.extend(originalObject, null);
assert.strictEqual(originalObject, extended);
});
});
describe('#funcCompare()', function () {
it('should return a compare function valid for Array#sort', function () {
const values = [
{id: 1, getValue : () => 100},
{id: 2, getValue : () => 3},
{id: 3, getValue : () => 1}
];
values.sort(utils.funcCompare('getValue'));
assert.strictEqual(values[0].id, 3);
assert.strictEqual(values[1].id, 2);
assert.strictEqual(values[2].id, 1);
});
});
describe('#binarySearch()', function () {
it('should return the key index if found, or the bitwise compliment of the first larger value', function () {
const compareFunc = function (a, b) {
if (a > b) {
return 1;
}
if (a < b) {
return -1;
}
return 0;
};
let val;
val = utils.binarySearch([0, 1, 2, 3, 4], 2, compareFunc);
assert.strictEqual(val, 2);
val = utils.binarySearch(['A', 'B', 'C', 'D', 'E'], 'D', compareFunc);
assert.strictEqual(val, 3);
val = utils.binarySearch(['A', 'B', 'C', 'D', 'Z'], 'M', compareFunc);
assert.strictEqual(val, ~4);
val = utils.binarySearch([0, 1, 2, 3, 4], 2.5, compareFunc);
assert.strictEqual(val, ~3);
});
});
describe('#deepExtend', function () {
it('should override only the most inner props', function () {
let value;
//single values
value = utils.deepExtend({}, {a: '1'});
assert.strictEqual(value.a, '1');
value = utils.deepExtend({a: '2'}, {a: '1'});
assert.strictEqual(value.a, '1');
value = utils.deepExtend({a: new Date()}, {a: new Date(100)});
assert.strictEqual(value.a.toString(), new Date(100).toString());
value = utils.deepExtend({a: 2}, {a: 1});
assert.strictEqual(value.a, 1);
//composed 1 level
value = utils.deepExtend({a: { a1: 1, a2: 2}, b: 1000}, {a: {a2: 15}});
assert.strictEqual(value.a.a2, 15);
assert.strictEqual(value.a.a1, 1);
assert.strictEqual(value.b, 1000);
//composed 2 level
value = utils.deepExtend({a: { a1: 1, a2: { a21: 10, a22: 20}}}, {a: {a2: {a21: 11}}, b: { b1: 100, b2: 200}});
assert.strictEqual(value.a.a2.a21, 11);
assert.strictEqual(value.a.a2.a22, 20);
assert.strictEqual(value.a.a1, 1);
assert.strictEqual(value.b.b1, 100);
assert.strictEqual(value.b.b2, 200);
//multiple sources
value = utils.deepExtend({z: 9}, {a: { a1: 1, a2: { a21: 10, a22: 20}}}, {a: {a2: {a21: 11}}, b: { b1: 100, b2: 200}});
assert.strictEqual(value.a.a2.a21, 11);
assert.strictEqual(value.a.a2.a22, 20);
assert.strictEqual(value.a.a1, 1);
assert.strictEqual(value.b.b1, 100);
assert.strictEqual(value.b.b2, 200);
assert.strictEqual(value.z, 9);
//!source
value = utils.deepExtend({z: 3}, null);
assert.strictEqual(value.z, 3);
//undefined
const o = undefined;
value = utils.deepExtend({z: 4}, o);
assert.strictEqual(value.z, 4);
});
});
});
describe('clientOptions', function () {
describe('#extend()', function () {
it('should require contactPoints', function () {
assert.doesNotThrow(function () {
clientOptions.extend({contactPoints: ['host1', 'host2']});
});
assert.throws(function () {
clientOptions.extend({contactPoints: {}});
});
assert.throws(function () {
clientOptions.extend({});
});
assert.throws(function () {
clientOptions.extend(null);
});
assert.throws(function () {
clientOptions.extend(undefined);
});
});
it('should create a new instance', function () {
const a = {contactPoints: ['host1']};
let options = clientOptions.extend(a);
assert.notStrictEqual(a, options);
assert.notStrictEqual(options, clientOptions.defaultOptions());
//it should use baseOptions as source
const b = {};
options = clientOptions.extend(b, a);
//B is the instance source
assert.strictEqual(b, options);
//A is the target
assert.notStrictEqual(a, options);
assert.notStrictEqual(options, clientOptions.defaultOptions());
});
it('should validate the policies', function () {
const policy1 = new loadBalancing.RoundRobinPolicy();
const policy2 = new retry.RetryPolicy();
const options = clientOptions.extend({
contactPoints: ['host1'],
policies: {
loadBalancing: policy1,
retry: policy2
}
});
assert.strictEqual(options.policies.loadBalancing, policy1);
assert.strictEqual(options.policies.retry, policy2);
assert.throws(function () {
clientOptions.extend({
contactPoints: ['host1'],
policies: {
loadBalancing: {}
}
});
});
assert.throws(function () {
clientOptions.extend({
contactPoints: ['host1'],
policies: {
//Use whatever object
loadBalancing: new (function C1() {})()
}
});
});
assert.throws(function () {
clientOptions.extend({
contactPoints: ['host1'],
policies: {
//Use whatever object
retry: new (function C2() {})()
}
});
});
});
it('should validate the encoding options', function () {
function DummyConstructor() {}
assert.doesNotThrow(function () {
clientOptions.extend({
contactPoints: ['host1'],
encoding: {}
});
});
assert.doesNotThrow(function () {
clientOptions.extend({
contactPoints: ['host1'],
encoding: { map: helper.Map}
});
});
assert.throws(function () {
clientOptions.extend({
contactPoints: ['host1'],
encoding: { map: 1}
});
});
assert.throws(function () {
clientOptions.extend({
contactPoints: ['host1'],
encoding: { map: DummyConstructor}
});
});
});
it('should validate protocolOptions.maxVersion', function () {
assert.throws(function () {
clientOptions.extend({
contactPoints: ['host1'],
protocolOptions: { maxVersion: '1' }
});
}, TypeError);
assert.throws(function () {
clientOptions.extend({
contactPoints: ['host1'],
protocolOptions: { maxVersion: 16 }
});
}, TypeError);
});
it('should validate credentials', () => {
const message = /credentials username and password must be a string/;
assert.throws(() => clientOptions.extend({ contactPoints, credentials: {} }), message);
assert.throws(() => clientOptions.extend({ contactPoints, credentials: { username: 'a' } }), message);
assert.throws(() => clientOptions.extend({ contactPoints, credentials: { password: 'b' } }), message);
assert.doesNotThrow(() => clientOptions.extend({
contactPoints, credentials: { username: 'a', password: 'b' }
}));
});
it('should validate authProvider', () => {
const message = /options\.authProvider must be an instance of AuthProvider/;
assert.throws(() => clientOptions.extend({ contactPoints, authProvider: {} }), message);
assert.throws(() => clientOptions.extend({ contactPoints, authProvider: true }), message);
assert.throws(() => clientOptions.extend({ contactPoints, authProvider: 'abc' }), message);
assert.doesNotThrow(() => clientOptions.extend({
contactPoints, authProvider: new auth.PlainTextAuthProvider('a', 'b')
}));
assert.doesNotThrow(() => clientOptions.extend({ contactPoints, authProvider: null }));
assert.doesNotThrow(() => clientOptions.extend({ contactPoints, authProvider: undefined }));
});
it('should use the authProvider when both credentials and authProvider are defined', () => {
const authProvider = new auth.PlainTextAuthProvider('a', 'b');
const options = clientOptions.extend({
contactPoints, authProvider, credentials: { username: 'c', password: 'd' }
});
assert.strictEqual(options.authProvider, authProvider);
});
});
describe('#defaultOptions()', function () {
const options = clientOptions.defaultOptions();
it('should set not set the default consistency level', function () {
assert.strictEqual(options.queryOptions.consistency, undefined);
});
it('should set True to warmup option', function () {
assert.strictEqual(options.pooling.warmup, true);
});
it('should set 12secs as default read timeout', function () {
assert.strictEqual(12000, options.socketOptions.readTimeout);
});
it('should set useUndefinedAsUnset as true', function () {
assert.strictEqual(true, options.encoding.useUndefinedAsUnset);
});
});
});
describe('writers', function () {
describe('WriteQueue', function () {
it('should buffer until threshold is passed', async () => {
const coalescingThreshold = 50;
const buffers = [];
const socketMock = {
write: function (buf, cb) {
buffers.push(buf);
setImmediate(cb);
return true;
},
on: utils.noop
};
const options = utils.extend({}, clientOptions.defaultOptions());
options.socketOptions.coalescingThreshold = coalescingThreshold;
const encoder = new Encoder(3, options);
const queue = new writers.WriteQueue(socketMock, encoder, options);
const request = {
write: () => utils.allocBufferUnsafe(10)
};
const itemCallback = sinon.spy(() => {});
for (let i = 0; i < 10; i++) {
queue.push(new OperationState(request, null, utils.noop), itemCallback);
}
await helper.wait.until(() => itemCallback.callCount === 10);
// 10 frames written
assert.strictEqual(itemCallback.callCount, 10);
// 10 frames coalesced into 2 buffers of 50b each
assert.strictEqual(buffers.length, 2);
buffers.forEach(b => assert.strictEqual(b.length, coalescingThreshold));
});
it('should stop writing and wait for the drain event when socket.write() returns false', async () => {
let writeReturnValue = true;
class SocketMock extends events.EventEmitter {
write(buffer, cb) {
setImmediate(cb);
return writeReturnValue;
}
}
const socket = sinon.spy(new SocketMock());
const options = clientOptions.defaultOptions();
const encoder = new Encoder(types.protocolVersion.v4, options);
const queue = new writers.WriteQueue(socket, encoder, options);
const request = {
write: () => utils.allocBufferUnsafe(10)
};
const itemCallback = sinon.spy(() => {});
queue.push(new OperationState(request, null, utils.noop), itemCallback);
await helper.wait.until(() => itemCallback.callCount === 1);
// Set socket.write() return value to false
writeReturnValue = false;
queue.push(new OperationState(request, null, utils.noop), itemCallback);
await helper.wait.until(() => itemCallback.callCount === 2);
assert.strictEqual(socket.write.callCount, 2);
// Next time an item is added to the queue, it will not be processed.
queue.push(new OperationState(request, null, utils.noop), itemCallback);
// After some time, write should not have been called
await helper.delayAsync(20);
assert.strictEqual(socket.write.callCount, 2);
assert.strictEqual(itemCallback.callCount, 2);
// It should wait for the drain event to be emitted
socket.emit('drain');
// After some ticks, socket.write() should be called again
await helper.wait.until(() => itemCallback.callCount === 3);
assert.strictEqual(socket.write.callCount, 3);
});
});
});
describe('exports', function () {
it('should contain API', function () {
//test that the exposed API is the one expected
//it looks like a dumb test and it is, but it is necessary!
/* eslint-disable global-require */
const api = require('../../index.js');
assert.strictEqual(api.Client, Client);
assert.ok(api.errors);
assert.strictEqual(typeof api.errors.DriverError, 'function');
assert.strictEqual(typeof api.ExecutionProfile, 'function');
assert.strictEqual(api.ExecutionProfile.name, 'ExecutionProfile');
assert.strictEqual(typeof api.ExecutionOptions, 'function');
assert.strictEqual(api.ExecutionOptions.name, 'ExecutionOptions');
assert.ok(api.types);
assert.ok(api.policies);
assert.ok(api.auth);
assert.ok(typeof api.auth.AuthProvider, 'function');
//policies modules
assert.strictEqual(api.policies.loadBalancing, loadBalancing);
assert.strictEqual(typeof api.policies.loadBalancing.LoadBalancingPolicy, 'function');
assert.instanceOf(api.policies.defaultLoadBalancingPolicy(), api.policies.loadBalancing.LoadBalancingPolicy);
assert.strictEqual(api.policies.retry, retry);
assert.strictEqual(typeof api.policies.retry.RetryPolicy, 'function');
assert.strictEqual(typeof api.policies.retry.IdempotenceAwareRetryPolicy, 'function');
assert.instanceOf(api.policies.defaultRetryPolicy(), api.policies.retry.RetryPolicy);
assert.strictEqual(api.policies.reconnection, require('../../lib/policies/reconnection'));
assert.strictEqual(typeof api.policies.reconnection.ReconnectionPolicy, 'function');
assert.instanceOf(api.policies.defaultReconnectionPolicy(), api.policies.reconnection.ReconnectionPolicy);
assert.strictEqual(api.policies.speculativeExecution, speculativeExecution);
assert.strictEqual(typeof speculativeExecution.NoSpeculativeExecutionPolicy, 'function');
assert.strictEqual(typeof speculativeExecution.ConstantSpeculativeExecutionPolicy, 'function');
assert.strictEqual(typeof speculativeExecution.SpeculativeExecutionPolicy, 'function');
assert.strictEqual(api.policies.timestampGeneration, timestampGeneration);
assert.strictEqual(typeof timestampGeneration.TimestampGenerator, 'function');
assert.strictEqual(typeof timestampGeneration.MonotonicTimestampGenerator, 'function');
assert.instanceOf(api.policies.defaultTimestampGenerator(), timestampGeneration.MonotonicTimestampGenerator);
assert.strictEqual(api.auth, require('../../lib/auth'));
// mapping module
assert.ok(api.mapping);
assertConstructorExposed(api.mapping, api.mapping.TableMappings);
assertConstructorExposed(api.mapping, api.mapping.DefaultTableMappings);
assertConstructorExposed(api.mapping, api.mapping.UnderscoreCqlToCamelCaseMappings);
assertConstructorExposed(api.mapping, api.mapping.Mapper);
assertConstructorExposed(api.mapping, api.mapping.ModelMapper);
assertConstructorExposed(api.mapping, api.mapping.ModelBatchItem);
assertConstructorExposed(api.mapping, api.mapping.ModelBatchMapper);
assertConstructorExposed(api.mapping, api.mapping.Result);
assert.ok(api.mapping.q);
assert.strictEqual(typeof api.mapping.q.in_, 'function');
//metadata module with classes
assert.ok(api.metadata);
assert.strictEqual(typeof api.metadata.Metadata, 'function');
assert.strictEqual(api.metadata.Metadata, require('../../lib/metadata'));
assert.ok(api.Encoder);
assert.strictEqual(typeof api.Encoder, 'function');
assert.strictEqual(api.Encoder, require('../../lib/encoder'));
assert.ok(api.defaultOptions());
assert.strictEqual(api.tracker, require('../../lib/tracker'));
assert.strictEqual(typeof api.tracker.RequestTracker, 'function');
assert.strictEqual(typeof api.tracker.RequestLogger, 'function');
assert.ok(api.metrics);
assert.strictEqual(typeof api.metrics.ClientMetrics, 'function');
assert.strictEqual(api.metrics.ClientMetrics.name, 'ClientMetrics');
assert.strictEqual(typeof api.metrics.DefaultMetrics, 'function');
assert.strictEqual(api.metrics.DefaultMetrics.name, 'DefaultMetrics');
assert.ok(api.concurrent);
assert.strictEqual(typeof api.concurrent.executeConcurrent, 'function');
/* eslint-enable global-require */
});
});
function assertConstructorExposed(obj, constructorRef) {
assert.ok(obj);
assert.strictEqual(typeof constructorRef, 'function');
// Verify that is exposed with the same name as the class
assert.strictEqual(obj[constructorRef.name], constructorRef);
}