'use strict' const net = require('net') const co = require('co') const expect = require('expect.js') const describe = require('mocha').describe const it = require('mocha').it const Pool = require('../') describe('pool error handling', function () { it('Should complete these queries without dying', function (done) { const pool = new Pool() let errors = 0 let shouldGet = 0 function runErrorQuery() { shouldGet++ return new Promise(function (resolve, reject) { pool .query("SELECT 'asd'+1 ") .then(function (res) { reject(res) // this should always error }) .catch(function (err) { errors++ resolve(err) }) }) } const ps = [] for (let i = 0; i < 5; i++) { ps.push(runErrorQuery()) } Promise.all(ps).then(function () { expect(shouldGet).to.eql(errors) pool.end(done) }) }) describe('calling release more than once', () => { it( 'should throw each time', co.wrap(function* () { const pool = new Pool() const client = yield pool.connect() client.release() expect(() => client.release()).to.throwError() expect(() => client.release()).to.throwError() return yield pool.end() }) ) it('should throw each time with callbacks', function (done) { const pool = new Pool() pool.connect(function (err, client, clientDone) { expect(err).not.to.be.an(Error) clientDone() expect(() => clientDone()).to.throwError() expect(() => clientDone()).to.throwError() pool.end(done) }) }) }) describe('calling connect after end', () => { it('should return an error', function* () { const pool = new Pool() const res = yield pool.query('SELECT $1::text as name', ['hi']) expect(res.rows[0].name).to.equal('hi') const wait = pool.end() pool.query('select now()') yield wait expect(() => pool.query('select now()')).to.reject() }) }) describe('using an ended pool', () => { it('rejects all additional promises', (done) => { const pool = new Pool() const promises = [] pool.end().then(() => { const squash = (promise) => promise.catch((e) => 'okay!') promises.push(squash(pool.connect())) promises.push(squash(pool.query('SELECT NOW()'))) promises.push(squash(pool.end())) Promise.all(promises).then((res) => { expect(res).to.eql(['okay!', 'okay!', 'okay!']) done() }) }) }) it('returns an error on all additional callbacks', (done) => { const pool = new Pool() pool.end(() => { pool.query('SELECT *', (err) => { expect(err).to.be.an(Error) pool.connect((err) => { expect(err).to.be.an(Error) pool.end((err) => { expect(err).to.be.an(Error) done() }) }) }) }) }) }) describe('error from idle client', () => { it( 'removes client from pool', co.wrap(function* () { const pool = new Pool() const client = yield pool.connect() expect(pool.totalCount).to.equal(1) expect(pool.waitingCount).to.equal(0) expect(pool.idleCount).to.equal(0) client.release() yield new Promise((resolve, reject) => { process.nextTick(() => { let poolError pool.once('error', (err) => { poolError = err }) let clientError client.once('error', (err) => { clientError = err }) client.emit('error', new Error('expected')) expect(clientError.message).to.equal('expected') expect(poolError.message).to.equal('expected') expect(pool.idleCount).to.equal(0) expect(pool.totalCount).to.equal(0) pool.end().then(resolve, reject) }) }) }) ) }) describe('error from in-use client', () => { it( 'keeps the client in the pool', co.wrap(function* () { const pool = new Pool() const client = yield pool.connect() expect(pool.totalCount).to.equal(1) expect(pool.waitingCount).to.equal(0) expect(pool.idleCount).to.equal(0) yield new Promise((resolve, reject) => { process.nextTick(() => { let poolError pool.once('error', (err) => { poolError = err }) let clientError client.once('error', (err) => { clientError = err }) client.emit('error', new Error('expected')) expect(clientError.message).to.equal('expected') expect(poolError).not.to.be.ok() expect(pool.idleCount).to.equal(0) expect(pool.totalCount).to.equal(1) client.release() pool.end().then(resolve, reject) }) }) }) ) }) describe('passing a function to pool.query', () => { it('calls back with error', (done) => { const pool = new Pool() console.log('passing fn to query') pool.query((err) => { expect(err).to.be.an(Error) pool.end(done) }) }) }) describe('pool with lots of errors', () => { it( 'continues to work and provide new clients', co.wrap(function* () { const pool = new Pool({ max: 1 }) const errors = [] for (var i = 0; i < 20; i++) { try { yield pool.query('invalid sql') } catch (err) { errors.push(err) } } expect(errors).to.have.length(20) expect(pool.idleCount).to.equal(0) expect(pool.query).to.be.a(Function) const res = yield pool.query('SELECT $1::text as name', ['brianc']) expect(res.rows).to.have.length(1) expect(res.rows[0].name).to.equal('brianc') return pool.end() }) ) }) it('should continue with queued items after a connection failure', (done) => { const closeServer = net .createServer((socket) => { socket.destroy() }) .unref() closeServer.listen(() => { const pool = new Pool({ max: 1, port: closeServer.address().port, host: 'localhost' }) pool.connect((err) => { expect(err).to.be.an(Error) if (err.code) { expect(err.code).to.be('ECONNRESET') } }) pool.connect((err) => { expect(err).to.be.an(Error) if (err.code) { expect(err.code).to.be('ECONNRESET') } closeServer.close(() => { pool.end(done) }) }) }) }) it('handles post-checkout client failures in pool.query', (done) => { const pool = new Pool({ max: 1 }) pool.on('error', () => { // We double close the connection in this test, prevent exception caused by that }) pool.query('SELECT pg_sleep(5)', [], (err) => { expect(err).to.be.an(Error) done() }) setTimeout(() => { pool._clients[0].end() }, 1000) }) })