Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Node.js Mocha Sequelize Error ConnectionManager.getConnection was called after the connection manager was closed

There are 2 mocha test files:

  1. Creates a server and pings it using chai just to check if it's
    working
  2. Creates a server and tests user insertion into database (sequelize postgres)

Both of these servers initialize a database connection.

When ran independently both of them pass, when ran together the second one fails with the following error:

Error ConnectionManager.getConnection was called after the connection manager was closed

Looking at the console, connection with the database is established 2 times for each test, but still acts as a single pool.

# db/index.js

global.TABLE_USERS = 'users';

const Promise = require('bluebird');
const Sequelize = require('sequelize');
const config = require('./../config');
const User = require('./User');

/**
 * @return {Promise}
 */
const connect = () => {
    return new Promise((resolve, reject) => {
        let sequelize = new Sequelize(config.postgres.database, config.postgres.user, config.postgres.password, {
            host: config.postgres.host,
            dialect: 'postgres',
            pool: {
                max: 5,
                min: 0,
                acquire: 30000,
                idle: 10000
            },
            define: {
                underscored: false,
                freezeTableName: false,
                charset: 'utf8',
                dialectOptions: {
                    collate: 'utf8_general_ci'
                }
            },
        });

        let user = User(sequelize);

        sequelize
            .authenticate()
            .then(() => {
                resolve({
                    User: user,
                    sequelize: sequelize
                })
            })
            .catch(err => {
                console.error('Couldn\'t authenticate');
                reject(err)
            })
    });
};

module.exports.connect = connect;

Main server module:

const express = require('express');
const bodyParser = require('body-parser');
global.Promise = require('bluebird');
let routing = require('./routing');
const config = require('./config');
const middleware = require('./middleware');
let database = require('./db');
let Repositories = require('./repositories');
let Services = require('./services');
let Controllers = require('./controllers');
const Promise = require('bluebird');

/**
 * @property {http.Server} this.app
 */
class Server {

    constructor() {
        this.app = express();
    }

    /**
     * @param {Function} beforeHook
     *
     */
    init(beforeHook = null) {
        return this._initDatabaseConnection()
            .then(() => {
                this._initContainer(beforeHook);
                this._initRoutes();
                return this._initServer()
            });
    }

    /**
     *
     * @param {Function} beforeHook
     * @private
     */
    _initContainer(beforeHook) {
        this.container = {};
        // Modify for testing before starting
        if (typeof beforeHook === 'function') beforeHook(this);
        this.container = Repositories(this.database);
        this.container = Services(this.container);
        this.controllers = Controllers(this.container);
    }

    /**
     *
     * @private
     */
    _initRoutes() {
        this.app.use(bodyParser.json());
        middleware.handleCors(this.app);
        this.app.use(routing({...this.controllers, ...this.services}));
        middleware.handleErrors(this.app);
    }

    /**
     *
     * @private
     *
     * @return {Promise}
     */
    _initServer() {
        return new Promise((resolve, reject) => {
            this.server = this.app.listen(config.app.port, () => {
                console.log(`Server started listening in ${config.app.env} on port ${config.app.port}`);
                resolve(this)
            });
        });
    }

    /**
     *
     * @return {Promise}
     * @private
     */
    _initDatabaseConnection() {
        return database.connect()
            .then(connection => {
                this.database = connection;
                console.log('Connected to the database');

                return Promise.resolve()
            })
    }

    /**
     * @return {Promise}
     */
    close() {
        this.server.close();
        return this.database.sequelize.close();
    }
}

module.exports = Server;

First test case

const assert = require('assert');
const chai = require('chai'),
    expect = chai.expect,
    chaiHttp = require('chai-http');

chai.use(chaiHttp);

const Server = require('../../src/Server');

describe('Server app test', () => {

    let server;

    before(async () => {
        server = await (new Server()).init();
    });

    after(async () => {
        await server.close();
    });

    it('should say respond it\'s name', async () => {
        let pingServer = () => {
            return new Promise((resolve, reject) => {
                chai.request(server.server)
                    .get('/')
                    .end((err, res) => {
                        expect(err).to.be.null;
                        expect(res).to.have.status(200);
                        resolve(res.body)
                    });
            });
        };

        let res = await pingServer();
        assert.equal(res.msg, 'API server');
    });
});

Second test case, UserControllerTest

const assert = require('assert');
const chai = require('chai'),
    expect = chai.expect,
    chaiHttp = require('chai-http');

chai.use(chaiHttp);

const sinon = require('sinon');
const Promise = require('bluebird');
const Response = require('./../../src/lib/RequestHelper');
const UserValidation = require('./../../src/validation/UserValidation');
const Server = require('./../../src/Server');
const ReCaptchaService = require('./../../src/services/ReCaptchaService');
const ValidationError = require('./../../src/errors/ValidationError');


describe('/users/signup', () => {

    describe('valid reCaptcha scenario', () => {
        let server, reCaptchaServiceStub;

        before(async () => {
            reCaptchaServiceStub = sinon.stub(ReCaptchaService.prototype, 'authenticate').returns(true);

            function setReCaptchaServiceStub(server) {
                server.services = {ReCaptchaService: new reCaptchaServiceStub()};
            }

            server = await (new Server()).init(setReCaptchaServiceStub);
        });

        after(async () => {
            reCaptchaServiceStub.restore();
            await server.database.User.destroy({where: {}});
            await server.close();
        });

        beforeEach(async () => {
            await server.database.User.destroy({where: {}});
        });

        it('should allow user to register', async () => {

            let data = {email: '[email protected]', password: '1234'};
            data[UserValidation.CAPTCHA_RESPONSE] = 'captcha_token';

            let signUp = (data) => {
                return new Promise((resolve, reject) => {
                    chai.request(server.server)
                        .post('/users/signup')
                        .send(data)
                        .end((err, res) => {
                            console.log(res.body)
                            expect(err).to.be.null;
                            expect(res).to.have.status(Response.STATUS_OK);
                            resolve(res.body)
                        });
                });
            };

            let res = await signUp(data);
            expect(res.token).to.be.a('string');
        });
    });

    describe('invalid reCaptcha scenario', () => {
        let server, reCaptchaServiceStub;

        before(async () => {
            reCaptchaServiceStub = sinon.stub(ReCaptchaService.prototype, 'authenticate')
                .onCall()
                .throws(new ValidationError('some err'));

            function setReCaptchaServiceStub(server) {
                server.container.ReCaptchaService = new reCaptchaServiceStub()
            }

            server = await (new Server()).init(setReCaptchaServiceStub);
        });

        after(async () => {
            reCaptchaServiceStub.restore();
            await server.close();
        });

        beforeEach(async () => {
            await server.database.User.destroy({where: {}});
        });

        it('should send a bad request on invalid reCaptcha', async () => {

            let data = {email: '[email protected]', password: '1234'};
            data[UserValidation.CAPTCHA_RESPONSE] = 'random_token';

            let signUp = (data) => {
                return new Promise((resolve, reject) => {
                    chai.request(server.server)
                        .post('/users/signup')
                        .send(data)
                        .end((err, res) => {
                            expect(err).to.not.be.null;
                            expect(res).to.have.status(Response.STATUS_BAD_REQUEST);
                            resolve(res.body);
                        });
                });
            };

            let res = await signUp(data);
            expect(res.err).to.equal(UserValidation.ERR_INVALID_RECAPTCHA);
        });
    });
});
like image 373
Marko Kovačević Avatar asked Dec 25 '17 15:12

Marko Kovačević


1 Answers

After doing more research into this, this is the following behaviour that caused the issue.

When mocha is ran to test the files recursively it is ran as a single process, this causes conflicts when closing the connection with sequelize.

To avoid this issue you should NOT close the connection with sequelize, but instead set an extra option with mocha --exit which terminates any additional cycles in the event loop after tests complete, thus closing the sequelize connection by itself.

like image 176
Marko Kovačević Avatar answered Oct 21 '22 11:10

Marko Kovačević