UNPKG

tarantool-driver

Version:
750 lines (719 loc) 18.8 kB
/** * Created by klond on 05.04.15. */ /*eslint-env mocha */ /* global Promise */ var exec = require('child_process').exec; var expect = require('chai').expect; var sinon = require('sinon'); var spy = sinon.spy.bind(sinon); var stub = sinon.stub.bind(sinon); var fs = require('fs'); var assert = require('assert'); var TarantoolConnection = require('../lib/connection'); var SliderBuffer = require('../lib/sliderBuffer'); var mlite = require('msgpack-lite'); var conn; describe('constructor', function () { it('should parse options correctly', function () { stub(TarantoolConnection.prototype, 'connect').returns(Promise.resolve()); var option; try { option = getOption(6380); expect(option).to.have.property('port', 6380); expect(option).to.have.property('host', 'localhost'); option = getOption('6380'); expect(option).to.have.property('port', 6380); option = getOption(6381, '192.168.1.1'); expect(option).to.have.property('port', 6381); expect(option).to.have.property('host', '192.168.1.1'); option = getOption(6381, '192.168.1.1', { password: '123', username: 'userloser' }); expect(option).to.have.property('port', 6381); expect(option).to.have.property('host', '192.168.1.1'); expect(option).to.have.property('password', '123'); expect(option).to.have.property('username', 'userloser'); option = getOption('mail.ru:33013'); expect(option).to.have.property('port', 33013); expect(option).to.have.property('host', 'mail.ru'); option = getOption('notguest:sesame@mail.ru:3301'); expect(option).to.have.property('port', 3301); expect(option).to.have.property('host', 'mail.ru'); expect(option).to.have.property('username', 'notguest'); expect(option).to.have.property('password', 'sesame'); option = getOption('/var/run/tarantool/unix.sock'); expect(option).to.have.property('path', '/var/run/tarantool/unix.sock'); option = getOption({ port: 6380, host: '192.168.1.1' }); expect(option).to.have.property('port', 6380); expect(option).to.have.property('host', '192.168.1.1'); option = getOption({ port: 6380, host: '192.168.1.1', reserveHosts: ['notguest:sesame@mail.ru:3301', 'mail.ru:3301'] }); expect(option).to.have.property('port', 6380); expect(option).to.have.property('host', '192.168.1.1'); expect(option).to.have.property('reserveHosts'); expect(option.reserveHosts).to.deep.equal(['notguest:sesame@mail.ru:3301', 'mail.ru:3301']); option = new TarantoolConnection({ port: 6380, host: '192.168.1.1', reserveHosts: ['notguest:sesame@mail.ru:3301', 'mail.ru:3301'] }); expect(option.reserve).to.deep.include( { port: 6380, host: '192.168.1.1', path: null, username: null, password: null }, { port: 3301, host: 'mail.ru' }, { port: 3301, host: 'mail.ru', username: 'notguest', password: 'sesame' } ); option = getOption({ port: 6380, host: '192.168.1.1' }); expect(option).to.have.property('port', 6380); expect(option).to.have.property('host', '192.168.1.1'); option = getOption({ port: '6380' }); expect(option).to.have.property('port', 6380); option = getOption(6380, { host: '192.168.1.1' }); expect(option).to.have.property('port', 6380); expect(option).to.have.property('host', '192.168.1.1'); option = getOption('6380', { host: '192.168.1.1' }); expect(option).to.have.property('port', 6380); } catch (err) { TarantoolConnection.prototype.connect.restore(); throw err; } TarantoolConnection.prototype.connect.restore(); function getOption() { conn = TarantoolConnection.apply(null, arguments); return conn.options; } }); it('should throw when arguments is invalid', function () { expect(function () { new TarantoolConnection(function () {}); }).to.throw(Error); }); }); describe('reconnecting', function () { this.timeout(8000); it('should pass the correct retry times', function (done) { var t = 0; new TarantoolConnection({ port: 1, retryStrategy: function (times) { expect(times).to.eql(++t); if (times === 3) { done(); return; } return 0; } }); }); it('should skip reconnecting when retryStrategy doesn\'t return a number', function (done) { conn = new TarantoolConnection({ port: 1, retryStrategy: function () { process.nextTick(function () { expect(conn.state).to.eql(32); // states.END == 32 done(); }); return null; } }); }); it('should not try to reconnect when disconnected manually', function (done) { conn = new TarantoolConnection(33013, { lazyConnect: true }); conn.eval('return func_foo()') .then(function () { conn.disconnect(); return conn.eval('return func_foo()'); }) .catch(function (err) { expect(err.message).to.match(/Connection is closed/); done(); }); }); it('should try to reconnect and then connect eventially', function (done){ function timer(){ return conn.ping() .then(function(res){ assert.equal(res, true); done(); }) .catch(function(err){ done(err); }); } conn = new TarantoolConnection(33013, { lazyConnect: true }); conn.eval('return func_foo()') .then(function () { exec('docker kill tarantool', function(error, stdout, stderr){ if(error){ done(error); } conn.eval('return func_foo()') .catch(function(err){ expect(err.message).to.match(/connect ECONNREFUSED/); }); exec('docker start tarantool', function(e, stdo, stde){ if(error){ done(error); } setTimeout(timer, 1000); }); }); }); }); }); describe('multihost', function () { this.timeout(10000); // after(function() { // exec('docker start tarantool'); // }); var t; it('should try to connect to reserve hosts cyclically', function(done){ conn = new TarantoolConnection(33013, { reserveHosts: ['test:test@127.0.0.1:33014', '127.0.0.1:33015'], beforeReserve: 1, retryStrategy: function (times) { return Math.min(times * 500, 2000); } }); t = 0; conn.on('connect', function(){ switch (t){ case 1: conn.eval('return box.cfg') .then(function(res){ t++; expect(res[0].listen.toString()).to.eql('33014'); exec('docker kill reserve', function(error, stdout, stderr){ if(error){ done(error); } }); exec('docker start tarantool'); }) .catch(function(e){ done(e); }); break; case 2: conn.eval('return box.cfg') .then(function(res){ t++; expect(res[0].listen.toString()).to.eql('33015'); exec('docker kill reserve_2', function(error, stdout, stderr){ if(error){ done(error); } }); }) .catch(function(e){ done(e); }); break; case 3: conn.eval('return box.cfg') .then(function(res){ t++; expect(res[0].listen.toString()).to.eql('33013'); done(); }) .catch(function(e){ done(e); }); break; } }); conn.ping() .then(function(){ t++; exec('docker kill tarantool', function(error, stdout, stderr){ if(error){ done(error); } }); }) .catch(function(e){ done(e); }); }); }); describe('lazy connect', function(){ beforeEach(function(){ conn = new TarantoolConnection({port: 33013, lazyConnect: true, username: 'test', password: 'test'}); }); it('lazy connect', function(done){ conn.connect() .then(function(){ done(); }, function(e){ done(e); }); }); it('should be authenticated', function(done){ conn.connect().then(function(){ return conn.eval('return box.session.user()'); }) .then(function(res){ assert.equal(res[0], 'test'); done(); }) .catch(function(e){done(e);}); }); it('should disconnect when inited', function(done){ conn.disconnect(); expect(conn.state).to.eql(32); // states.END == 32 done(); }); it('should disconnect', function(done){ conn.connect() .then(function(res){ conn.disconnect(); assert.equal(conn.socket.writable, false); done(); }) .catch(function(e){done(e);}); }); }); describe('instant connection', function(){ beforeEach(function(){ conn = new TarantoolConnection({port: 33013, username: 'test', password: 'test'}); }); it('connect', function(done){ conn.eval('return func_arg(...)', 'connected!') .then(function(res){ try{ assert.equal(res, 'connected!'); } catch(e){console.error(e);} done(); }, function(e){ done(e); }); }); it('should reject when connected', function (done) { conn.connect().catch(function (err) { expect(err.message).to.match(/Tarantool is already connecting\/connected/); done(); }); }); it('should be authenticated', function(done){ conn.eval('return box.session.user()') .then(function(res){ assert.equal(res[0], 'test'); done(); }) .catch(function(e){done(e);}); }); it('should reject when auth failed', function (done) { conn = new TarantoolConnection({port: 33013, username: 'userloser', password: 'test'}); conn.eval('return func_foo()') .catch(function (err) { expect(err.message).to.include("not found"); conn.disconnect(); done(); }); }); it('should reject command when connection is closed', function (done) { conn = new TarantoolConnection(); conn.disconnect(); conn.eval('return func_foo()') .catch(function (err) { expect(err.message).to.match(/Connection is closed/); done(); }); }); }); describe('timeout', function(){ it('should close the connection when timeout', function (done) { conn = new TarantoolConnection(33013, '192.0.0.0', { timeout: 1, retryStrategy: null }); var pending = 2; conn.on('error', function (err) { expect(err.message).to.eql('connect ETIMEDOUT'); if (!--pending) { done(); } }); conn.ping() .catch(function (err) { expect(err.message).to.match(/Connection is closed/); if (!--pending) { done(); } }); }); it('should clear the timeout when connected', function (done) { conn = new TarantoolConnection(33013, { timeout: 10000 }); setImmediate(function () { stub(conn.socket, 'setTimeout') .callsFake(function (timeout) { expect(timeout).to.eql(0); conn.socket.setTimeout.restore(); done(); }); }); }); }); describe('requests', function(){ var insertTuple = [50, 10, 'my key', 30]; before(function(done){ console.log('before call'); try{ conn = new TarantoolConnection({port: 33013, username: 'test', password: 'test'}); Promise.all([conn.delete(514, 0, [1]),conn.delete(514, 0, [2]), conn.delete(514, 0, [3]),conn.delete(514, 0, [4]), conn.delete(512, 0, [999])]) .then(function(){ return conn.call('clearaddmore'); }) .then(function(){ done(); }) .catch(function(e){ done(e); }); } catch(e){ console.log(e); } }); it('replace', function(done){ conn.replace(512, insertTuple) .then(function(a){ assert.equal(a.length, 1); for (var i = 0; i<a[0].length; i++) assert.equal(a[0][i], insertTuple[i]); done(); }, function(e){done(e);}); }); it('simple select', function(done){ conn.select(512, 0, 1, 0, 'eq', [50]) .then(function(a){ assert.equal(a.length, 1); for (var i = 0; i<a[0].length; i++) assert.equal(a[0][i], insertTuple[i]); done(); }, function(e){done(e);}); }); it('simple select with callback', function(done){ conn.selectCb(512, 0, 1, 0, 'eq', [50], function(a){ assert.equal(a.length, 1); for (var i = 0; i<a[0].length; i++) assert.equal(a[0][i], insertTuple[i]); done(); }, function(e){done(e);}); }); it('composite select', function(done){ conn.select(512, 1, 1, 0, 'eq', [10, 'my key']) .then(function(a){ assert.equal(a.length, 1); for (var i = 0; i<a[0].length; i++) assert.equal(a[0][i], insertTuple[i]); done(); }).catch(function(e){ done(e); }); }); it('delete', function(done){ conn.delete(512, 0, [50]) .then(function(a){ assert.equal(a.length, 1); for (var i = 0; i<a[0].length; i++) assert.equal(a[0][i], insertTuple[i]); done(); }).catch(function(e){ done(e); }); }); it('insert', function(done){ conn.insert(512, insertTuple) .then(function(a){ assert.equal(a.length, 1); for (var i = 0; i<a[0].length; i++) assert.equal(a[0][i], insertTuple[i]); done(); }, function(e){done(e);}); }); it('dup error', function(done){ conn.insert(512, insertTuple) .then(function(a){ done(new Error('can insert')); }, function(e){ assert(e instanceof Error); done(); }); }); it('update', function(done){ conn.update(512, 0, [50], [['+',3,10]]) .then(function(a){ assert.equal(a.length, 1); assert.equal(a[0][3], insertTuple[3]+10); done(); }).catch(function(e){ done(e); }); }); it('a lot of insert', function(done){ var promises = []; for (var i = 0; i <= 5000; i++) { promises.push(conn.insert(515, ['key' + i, i])); } Promise.all(promises) .then(function(pr){ done(); }) .catch(function(e){ done(e); }); }); it('check errors', function(done){ conn.insert(512, ['key', 'key', 'key']) .then(function(){ done(new Error('Right when need error')); }) .catch(function(e){ done(); }); }); it('call print', function(done){ conn.call('myprint', ['test']) .then(function(){ done(); }) .catch(function(e){ console.log(e); done(e); }); }); it('call batch', function(done){ conn.call('batch', [[1], [2], [3]]) .then(function(){ done(); }) .catch(function(e){ console.log(e); done(e); }); }); it('call get', function(done){ conn.insert(514, [4]) .then(function() { return conn.call('myget', 4); }) .then(function(value){ done(); }) .catch(function(e){ console.log(e); done(e); }); }); it('get metadata space by name', function(done){ conn._getSpaceId('batched') .then(function(v){ assert.equal(v, 514); done(); }) .catch(function(e){ done(e); }); }); it('get metadata index by name', function(done){ conn._getIndexId(514, 'primary') .then(function(v){ assert.equal(v, 0); done(); }) .catch(function(e){ done(e); }); }); it('insert with space name', function(done){ conn.insert('test', [999, 999, 'fear']) .then(function(v){ done(); }) .catch(done); }); it('select with space name and index name', function(done){ conn.select('test', 'primary', 0, 0, 'all', [999]) .then(function(){ done(); }) .catch(done); }); it('select with space name and index number', function(done){ conn.select('test', 0, 0, 0, 'eq', [999]) .then(function(){ done(); }) .catch(done); }); it('select with space number and index name', function(done){ conn.select(512, 'primary', 0, 0, 'eq', [999]) .then(function(){ done(); }) .catch(done); }); it('delete with name', function(done){ conn.delete('test', 'primary', [999]) .then(function(){ done(); }) .catch(done); }); it('update with name', function(done){ conn.update('test', 'primary', [999], ['+', 1, 10]) .then(function(){ done(); }) .catch(done); }); it('evaluate expression', function(done){ conn.eval('return 2+2') .then(function(res){ assert.equal(res, 4); done(); }) .catch(function(e){ done(e); }); }); it('evaluate expression with args', function(done){ conn.eval('return func_sum(...)', 11, 22) .then(function(res){ assert.equal(res, 33); done(); }) .catch(function(e){ done(e); }); }); it('ping', function(done){ conn.ping() .then(function(res){ assert.equal(res, true); done(); }) .catch(function(e){ done(e); }); }); }); describe('upsert', function(){ before(function(done){ try{ conn = new TarantoolConnection({port: 33013,lazyConnect: true}); conn.connect().then(function(){ return conn._auth('test', 'test'); }, function(e){ done(e); }) .then(function(){ return Promise.all([ conn.delete('upstest', 'primary', 1), conn.delete('upstest', 'primary', 2) ]); }) .then(function(){ done(); }) .catch(function(e){ done(e); }); } catch(e){ console.log(e); } }); it('insert', function(done){ conn.upsert('upstest', [['+', 3, 3]], [1, 2, 3]) .then(function() { return conn.select('upstest', 'primary', 1, 0, 'eq', 1); }) .then(function(tuples){ assert.equal(tuples.length, 1); assert.deepEqual(tuples[0], [1, 2, 3]); done(); }) .catch(function(e){ done(e); }); }); it('update', function(done){ conn.upsert('upstest', [['+', 2, 2]], [2, 4, 3]) .then(function(){ return conn.upsert('upstest', [['+', 2, 2]], [2, 4, 3]); }) .then(function() { return conn.select('upstest', 'primary', 1, 0, 'eq', 2) ; }) .then(function(tuples){ assert.equal(tuples.length, 1); assert.deepEqual(tuples[0], [2, 4, 5]); done(); }) .catch(function(e){ done(e); }); }); }); describe('connection test with custom msgpack implementation', function(){ var customConn; beforeEach(function(){ customConn = TarantoolConnection( { port: 33013, msgpack: { encode: function(obj){ return mlite.encode(obj); }, decode: function(buf){ return mlite.decode(buf); } }, lazyConnect: true, username: 'test', password: 'test' } ); }); it('connect', function(done){ customConn.connect().then(function(){ done(); }, function(e){ throw 'not connected'; }); }); it('should be authenticated', function(done){ conn.eval('return box.session.user()') .then(function(res){ assert.equal(res[0], 'test'); done(); }) .catch(function(e){done(e);}); }); }); describe('slider buffer', function(){ })