@mysql/xdevapi
Version:
MySQL Connector/Node.js - A Node.js driver for MySQL using the X Protocol and X DevAPI.
288 lines (230 loc) • 10.9 kB
JavaScript
/*
* Copyright (c) 2018, 2022, Oracle and/or its affiliates.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License, version 2.0, as
* published by the Free Software Foundation.
*
* This program is also distributed with certain software (including
* but not limited to OpenSSL) that is licensed under separate terms,
* as designated in a particular file or component or in included license
* documentation. The authors of MySQL hereby grant you an
* additional permission to link the program and your derivative works
* with the separately licensed software that they have included with
* MySQL.
*
* Without limiting anything contained in the foregoing, this file,
* which is part of MySQL Connector/Node.js, is also subject to the
* Universal FOSS Exception, version 1.0, a copy of which can be found at
* http://oss.oracle.com/licenses/universal-foss-exception.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License, version 2.0, for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
;
/* eslint-env node, mocha */
const expect = require('chai').expect;
const errors = require('../../../lib/constants/errors');
const td = require('testdouble');
const util = require('util');
// subject under test needs to be reloaded with replacement fakes
let client = require('../../../lib/DevAPI/Client');
describe('DevAPI Client', () => {
afterEach('reset fakes', () => {
td.reset();
});
context('getSession()', () => {
let connection, pool, session;
beforeEach('create fakes', () => {
connection = td.function();
pool = td.function();
session = td.function();
td.replace('../../../lib/DevAPI/Connection', connection);
td.replace('../../../lib/DevAPI/ConnectionPool', pool);
td.replace('../../../lib/DevAPI/Session', session);
client = require('../../../lib/DevAPI/Client');
});
it('creates a session over a standalone connection if pooling is not enabled', () => {
const options = { foo: 'bar', pooling: { enabled: false } };
const open = td.function();
td.when(connection(options)).thenReturn({ open });
td.when(open()).thenResolve('baz');
td.when(session('baz')).thenReturn('qux');
return client(options).getSession()
.then(res => {
expect(res).to.equal('qux');
});
});
it('creates a session over a connection retrieved from a connection pool if pooling is enabled', () => {
const options = { foo: 'bar', pooling: { enabled: true } };
const create = td.function();
const getConnection = td.function();
td.when(pool(options)).thenReturn({ create });
td.when(create()).thenReturn({ getConnection });
td.when(getConnection()).thenResolve('baz');
td.when(session('baz')).thenReturn('qux');
return client(options).getSession()
.then(res => {
expect(res).to.equal('qux');
});
});
it('does not create a session if the pool is not able to return a valid connection', () => {
const options = { foo: 'bar', pooling: { enabled: true } };
const create = td.function();
const getConnection = td.function();
td.when(pool(options)).thenReturn({ create });
td.when(create()).thenReturn({ getConnection });
td.when(getConnection()).thenResolve('baz');
return client(options).getSession()
.then(res => {
return expect(res).to.not.exist;
});
});
});
context('close()', () => {
let connection, pool, session;
beforeEach('create fakes', () => {
connection = td.function();
pool = td.function();
session = td.function();
td.replace('../../../lib/DevAPI/Connection', connection);
td.replace('../../../lib/DevAPI/ConnectionPool', pool);
td.replace('../../../lib/DevAPI/Session', session);
client = require('../../../lib/DevAPI/Client');
});
it('destroys an existing connection pool', () => {
const destroy = td.function();
const create = td.function();
const getConnection = td.function();
td.when(pool(), { ignoreExtraArgs: true }).thenReturn({ create });
td.when(create()).thenReturn({ destroy, getConnection });
td.when(getConnection()).thenResolve('foo');
td.when(session('foo')).thenReturn();
td.when(destroy()).thenResolve();
const cli = client({ pooling: { enabled: true } });
return cli.getSession()
.then(() => {
return cli.close();
})
.then(() => {
expect(td.explain(destroy).callCount).to.equal(1);
});
});
it('fails if an existing connection pool has already been destroyed', () => {
const destroy = td.function();
const create = td.function();
const getConnection = td.function();
td.when(pool(), { ignoreExtraArgs: true }).thenReturn({ create });
td.when(create()).thenReturn({ destroy, getConnection });
td.when(getConnection()).thenResolve('foo');
td.when(session('foo')).thenReturn();
td.when(destroy()).thenResolve();
const cli = client({ pooling: { enabled: true } });
return cli.getSession()
.then(() => {
return cli.close();
})
.then(() => {
return cli.close();
})
.then(() => {
return expect.fail();
})
.catch(err => {
return expect(err.message).to.deep.equal(errors.MESSAGES.ER_DEVAPI_POOL_CLOSED);
});
});
it('fails if an existing connection pool is not available', () => {
return client({ pooling: { enabled: true } }).close()
.then(() => {
return expect.fail();
})
.catch(err => {
return expect(err.message).to.deep.equal(errors.MESSAGES.ER_DEVAPI_POOL_CLOSED);
});
});
it('destroys an existing standalone connection', () => {
const destroy = td.function();
const open = td.function();
td.when(connection(), { ignoreExtraArgs: true }).thenReturn({ destroy, open });
td.when(open()).thenResolve('foo');
td.when(session('foo')).thenReturn();
td.when(destroy()).thenResolve();
const cli = client({ pooling: { enabled: false } });
return cli.getSession()
.then(() => {
return cli.close();
})
.then(() => {
expect(td.explain(destroy).callCount).to.equal(1);
});
});
it('does nothing if an existing standalone connection has already been closed', () => {
const destroy = td.function();
const open = td.function();
td.when(connection(), { ignoreExtraArgs: true }).thenReturn({ destroy, open });
td.when(open()).thenResolve('foo');
td.when(session('foo')).thenReturn();
td.when(destroy()).thenResolve();
const cli = client({ pooling: { enabled: false } });
return cli.getSession()
.then(() => {
return cli.close();
})
.then(() => {
return cli.close();
})
.then(() => {
expect(td.explain(destroy).callCount).to.equal(1);
});
});
it('does nothing if an existing standalone connection is not available', () => {
const destroy = td.function();
td.when(connection(), { ignoreExtraArgs: true }).thenReturn(null);
td.when(destroy()).thenResolve();
return client({ pooling: { enabled: false } }).close()
.then(() => {
expect(td.explain(destroy).callCount).to.equal(0);
});
});
});
context('Client.validate()', () => {
let connection, pool;
beforeEach('create fakes', () => {
connection = td.replace('../../../lib/DevAPI/Connection');
pool = td.replace('../../../lib/DevAPI/ConnectionPool');
client = require('../../../lib/DevAPI/Client');
});
it('fails when a connection option is badly specified', () => {
const options = { foo: 'bar' };
const error = new Error('foobar');
td.when(connection.validate(options)).thenThrow(error);
return expect(() => client.validate(options)).to.throw(error);
});
it('fails when an unknown client option is provided', () => {
const options = { foo: 'bar' };
td.when(connection.validate(), { ignoreExtraArgs: true }).thenReturn(true);
return expect(() => client.validate(options)).to.throw(util.format(errors.MESSAGES.ER_DEVAPI_BAD_CLIENT_OPTION, 'foo'));
});
it('fails when the pooling option is badly specified', () => {
const options = { pooling: 'foo' };
td.when(connection.validate(), { ignoreExtraArgs: true }).thenReturn(true);
return expect(() => client.validate(options)).to.throw(util.format(errors.MESSAGES.ER_DEVAPI_BAD_CLIENT_OPTION_VALUE, 'pooling', 'foo'));
});
it('fails when a pooling option value is badly specified', () => {
const options = { pooling: { foo: 'bar' } };
const error = new Error('foobar');
td.when(connection.validate(), { ignoreExtraArgs: true }).thenReturn(true);
// In this case, the object will also contain default values for
// the valid pooling options.
td.when(pool.validate(td.matchers.contains(options.pooling))).thenThrow(error);
return expect(() => client.validate(options)).to.throw(error);
});
});
});