最新消息: 电脑我帮您提供丰富的电脑知识,编程学习,软件下载,win7系统下载。

Node.js Mocha Sequelize错误连接管理器关闭后调用ConnectionManager.getConnection

IT培训 admin 6浏览 0评论

Node.js Mocha Sequelize错误连接管理器关闭后调用ConnectionManager.getConnection

有2个mocha测试文件:

  1. 创建一个服务器并使用chai ping它,以检查它是否是 工作的
  2. 创建服务器并测试用户插入数据库(sequelize postgres)

这两个服务器都初始化数据库连接。

独立运行时,两个都通过,当一起运行时,第二个失败并出现以下错误:

连接管理器关闭后调用错误ConnectionManager.getConnection

查看控制台,每次测试都会建立与数据库的连接2次,但仍然充当单个池。

# 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;

主服务器模块:

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;

第一个测试用例

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');
    });
});

第二个测试用例,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);
        });
    });
});
回答如下:

在对此进行更多研究之后,以下是导致该问题的行为。

当运行mocha以递归方式测试文件时,它将作为单个进程运行,这会在使用sequelize关闭连接时导致冲突。

为避免此问题,您不应该使用sequelize关闭连接,而是使用mocha --exit设置一个额外选项,它会在测试完成后终止事件循环中的任何其他周期,从而自行关闭sequelize连接。

Node.js Mocha Sequelize错误连接管理器关闭后调用ConnectionManager.getConnection

有2个mocha测试文件:

  1. 创建一个服务器并使用chai ping它,以检查它是否是 工作的
  2. 创建服务器并测试用户插入数据库(sequelize postgres)

这两个服务器都初始化数据库连接。

独立运行时,两个都通过,当一起运行时,第二个失败并出现以下错误:

连接管理器关闭后调用错误ConnectionManager.getConnection

查看控制台,每次测试都会建立与数据库的连接2次,但仍然充当单个池。

# 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;

主服务器模块:

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;

第一个测试用例

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');
    });
});

第二个测试用例,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);
        });
    });
});
回答如下:

在对此进行更多研究之后,以下是导致该问题的行为。

当运行mocha以递归方式测试文件时,它将作为单个进程运行,这会在使用sequelize关闭连接时导致冲突。

为避免此问题,您不应该使用sequelize关闭连接,而是使用mocha --exit设置一个额外选项,它会在测试完成后终止事件循环中的任何其他周期,从而自行关闭sequelize连接。

发布评论

评论列表 (0)

  1. 暂无评论