lundi 25 décembre 2017

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: 'myemail@gmail.com', 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: 'myemail@gmail.com', 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);
        });
    });
});

Aucun commentaire:

Enregistrer un commentaire