node-postgres/test/index.js
Charmander fd802a385c Don’t create promises when callbacks are provided (#31)
* Revert "When connection fail, emit the error. (#28)"

This reverts commit 6a7edabc22e36db7386c97ee93f08f957364f37d.

The callback passed to `Pool.prototype.connect` should be responsible for handling connection errors. The `error` event is documented to be:

> Emitted whenever an idle client in the pool encounters an error.

This isn’t the case of an idle client in the pool; it never makes it into the pool.

It also breaks tests on pg’s master because of nonspecific dependencies.

* Don’t create promises when callbacks are provided

It’s incorrect to do so. One consequence is that a rejected promise will be unhandled, which is currently annoying, but also dangerous in the future:

> DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

The way callbacks are used currently also causes #24 (hiding of errors thrown synchronously from the callback). One fix for that would be to call them asynchronously from inside the `new Promise()` executor:

    process.nextTick(cb, error);

I don’t think it’s worth implementing, though, since it would still be backwards-incompatible – just less obvious about it.

Also fixes a bug where the `Pool.prototype.connect` callback would be called twice if there was an error.

* Use Node-0.10-compatible `process.nextTick`
2016-12-04 17:20:24 -06:00

203 lines
6.0 KiB
JavaScript

var expect = require('expect.js')
var _ = require('lodash')
var describe = require('mocha').describe
var it = require('mocha').it
var Promise = require('bluebird')
var Pool = require('../')
if (typeof global.Promise === 'undefined') {
global.Promise = Promise
}
describe('pool', function () {
it('can be used as a factory function', function () {
var pool = Pool()
expect(pool instanceof Pool).to.be.ok()
expect(typeof pool.connect).to.be('function')
})
describe('with callbacks', function () {
it('works totally unconfigured', function (done) {
var pool = new Pool()
pool.connect(function (err, client, release) {
if (err) return done(err)
client.query('SELECT NOW()', function (err, res) {
release()
if (err) return done(err)
expect(res.rows).to.have.length(1)
pool.end(done)
})
})
})
it('passes props to clients', function (done) {
var pool = new Pool({ binary: true })
pool.connect(function (err, client, release) {
release()
if (err) return done(err)
expect(client.binary).to.eql(true)
pool.end(done)
})
})
it('can run a query with a callback without parameters', function (done) {
var pool = new Pool()
pool.query('SELECT 1 as num', function (err, res) {
expect(res.rows[0]).to.eql({ num: 1 })
pool.end(function () {
done(err)
})
})
})
it('can run a query with a callback', function (done) {
var pool = new Pool()
pool.query('SELECT $1::text as name', ['brianc'], function (err, res) {
expect(res.rows[0]).to.eql({ name: 'brianc' })
pool.end(function () {
done(err)
})
})
})
it('passes connection errors to callback', function (done) {
var pool = new Pool({host: 'no-postgres-server-here.com'})
pool.query('SELECT $1::text as name', ['brianc'], function (err, res) {
expect(res).to.be(undefined)
expect(err).to.be.an(Error)
pool.end(function (err) {
done(err)
})
})
})
it('removes client if it errors in background', function (done) {
var pool = new Pool()
pool.connect(function (err, client, release) {
release()
if (err) return done(err)
client.testString = 'foo'
setTimeout(function () {
client.emit('error', new Error('on purpose'))
}, 10)
})
pool.on('error', function (err) {
expect(err.message).to.be('on purpose')
expect(err.client).to.not.be(undefined)
expect(err.client.testString).to.be('foo')
err.client.connection.stream.on('end', function () {
pool.end(done)
})
})
})
it('should not change given options', function (done) {
var options = { max: 10 }
var pool = new Pool(options)
pool.connect(function (err, client, release) {
release()
if (err) return done(err)
expect(options).to.eql({ max: 10 })
pool.end(done)
})
})
it('does not create promises when connecting', function (done) {
var pool = new Pool()
var returnValue = pool.connect(function (err, client, release) {
release()
if (err) return done(err)
pool.end(done)
})
expect(returnValue).to.be(undefined)
})
it('does not create promises when querying', function (done) {
var pool = new Pool()
var returnValue = pool.query('SELECT 1 as num', function (err) {
pool.end(function () {
done(err)
})
})
expect(returnValue).to.be(undefined)
})
it('does not create promises when ending', function (done) {
var pool = new Pool()
var returnValue = pool.end(done)
expect(returnValue).to.be(undefined)
})
})
describe('with promises', function () {
it('connects and disconnects', function () {
var pool = new Pool()
return pool.connect().then(function (client) {
expect(pool.pool.availableObjectsCount()).to.be(0)
return client.query('select $1::text as name', ['hi']).then(function (res) {
expect(res.rows).to.eql([{ name: 'hi' }])
client.release()
expect(pool.pool.getPoolSize()).to.be(1)
expect(pool.pool.availableObjectsCount()).to.be(1)
return pool.end()
})
})
})
it('properly pools clients', function () {
var pool = new Pool({ poolSize: 9 })
return Promise.map(_.times(30), function () {
return pool.connect().then(function (client) {
return client.query('select $1::text as name', ['hi']).then(function (res) {
client.release()
return res
})
})
}).then(function (res) {
expect(res).to.have.length(30)
expect(pool.pool.getPoolSize()).to.be(9)
return pool.end()
})
})
it('supports just running queries', function () {
var pool = new Pool({ poolSize: 9 })
return Promise.map(_.times(30), function () {
return pool.query('SELECT $1::text as name', ['hi'])
}).then(function (queries) {
expect(queries).to.have.length(30)
expect(pool.pool.getPoolSize()).to.be(9)
expect(pool.pool.availableObjectsCount()).to.be(9)
return pool.end()
})
})
it('recovers from all errors', function () {
var pool = new Pool()
var errors = []
return Promise.mapSeries(_.times(30), function () {
return pool.query('SELECT asldkfjasldkf')
.catch(function (e) {
errors.push(e)
})
}).then(function () {
return pool.query('SELECT $1::text as name', ['hi']).then(function (res) {
expect(errors).to.have.length(30)
expect(res.rows).to.eql([{ name: 'hi' }])
return pool.end()
})
})
})
})
})
process.on('unhandledRejection', function (e) {
console.error(e.message, e.stack)
setImmediate(function () {
throw e
})
})