mongodb-memory-server
Version:
In-memory MongoDB Server. Designed with testing in mind, the server will allow you to connect your favourite ODM or client library to the MongoDB Server and run integration tests isolated from each other.
307 lines (231 loc) • 8.75 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _events = _interopRequireDefault(require("events"));
var _mongodb = require("mongodb");
var _MongoMemoryServer = _interopRequireDefault(require("./MongoMemoryServer"));
var _db_util = require("./util/db_util");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } }
function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; }
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
class MongoMemoryReplSet extends _events.default.EventEmitter {
constructor(opts = {}) {
super();
_defineProperty(this, "servers", void 0);
_defineProperty(this, "opts", void 0);
_defineProperty(this, "debug", void 0);
_defineProperty(this, "_state", void 0);
const replSetDefaults = {
auth: false,
args: [],
name: 'testset',
count: 1,
dbName: (0, _db_util.generateDbName)(),
ip: '127.0.0.1',
oplogSize: 1,
spawn: {},
storageEngine: 'ephemeralForTest'
};
this._state = 'stopped';
this.opts = {
binary: opts.binary || {},
debug: !!opts.debug,
instanceOpts: opts.instanceOpts || [],
replSet: Object.assign(replSetDefaults, opts.replSet)
};
this.opts.replSet.args.push('--oplogSize', `${this.opts.replSet.oplogSize}`);
this.debug = (...args) => {
if (!this.opts.debug) return;
console.log(...args);
}; // auto start by default
if (opts.autoStart || !('autoStart' in opts)) {
this.debug('Autostarting MongoMemoryReplSet.');
setTimeout(() => this.start(), 0);
}
process.on('beforeExit', () => this.stop());
}
getConnectionString(otherDb) {
var _this = this;
return _asyncToGenerator(function* () {
return _this.getUri(otherDb);
})();
}
/**
* Returns database name.
*/
getDbName() {
var _this2 = this;
return _asyncToGenerator(function* () {
// this function is only async for consistency with MongoMemoryServer
// I don't see much point to either of them being async but don't
// care enough to change it and introduce a breaking change.
return _this2.opts.replSet.dbName;
})();
}
/**
* Returns instance options suitable for a MongoMemoryServer.
* @param {MongoMemoryInstancePropBaseT} baseOpts
*/
getInstanceOpts(baseOpts = {}) {
const rsOpts = this.opts.replSet;
const opts = {
auth: !!rsOpts.auth,
args: rsOpts.args,
dbName: rsOpts.dbName,
ip: rsOpts.ip,
replSet: rsOpts.name
};
if (baseOpts.args) opts.args = rsOpts.args.concat(baseOpts.args);
if (baseOpts.port) opts.port = baseOpts.port;
if (baseOpts.dbPath) opts.dbPath = baseOpts.dbPath;
if (baseOpts.storageEngine) opts.storageEngine = baseOpts.storageEngine;
this.debug(' instance opts:', opts);
return opts;
}
/**
* Returns a mongodb: URI to connect to a given database.
*/
getUri(otherDb) {
var _this3 = this;
return _asyncToGenerator(function* () {
if (_this3._state === 'init') {
yield _this3._waitForPrimary();
}
if (_this3._state !== 'running') {
throw new Error('Replica Set is not running. Use opts.debug for more info.');
}
let dbName;
if (otherDb) {
dbName = typeof otherDb === 'string' ? otherDb : (0, _db_util.generateDbName)();
} else {
dbName = _this3.opts.replSet.dbName;
}
const ports = yield Promise.all(_this3.servers.map(s => s.getPort()));
const hosts = ports.map(port => `127.0.0.1:${port}`).join(',');
return `mongodb://${hosts}/${dbName}`;
})();
}
/**
* Start underlying `mongod` instances.
*/
start() {
var _this4 = this;
return _asyncToGenerator(function* () {
_this4.debug('start');
if (_this4._state !== 'stopped') {
throw new Error(`Already in 'init' or 'running' state. Use opts.debug = true for more info.`);
}
_this4.emit(_this4._state = 'init');
_this4.debug('init'); // Any servers defined within `opts.instanceOpts` should be started first as
// the user could have specified a `dbPath` in which case we would want to perform
// the `replSetInitiate` command against that server.
const servers = _this4.opts.instanceOpts.map(opts => {
_this4.debug(' starting server from instanceOpts:', opts, '...');
return _this4._startServer(_this4.getInstanceOpts(opts));
});
while (servers.length < _this4.opts.replSet.count) {
_this4.debug(' starting a server due to count...');
const server = _this4._startServer(_this4.getInstanceOpts({}));
servers.push(server);
}
_this4.servers = servers; // Brief delay to wait for servers to start up.
yield new Promise(resolve => setTimeout(resolve, 1000));
yield _this4._initReplSet();
})();
}
/**
* Stop the underlying `mongod` instance(s).
*/
stop() {
var _this5 = this;
return _asyncToGenerator(function* () {
if (_this5._state === 'stopped') return false;
const servers = _this5.servers;
_this5.servers = [];
return Promise.all(servers.map(s => s.stop())).then(() => {
_this5.emit(_this5._state = 'stopped');
return true;
}).catch(err => {
_this5.debug(err);
_this5.emit(_this5._state = 'stopped', err);
return false;
});
})();
}
waitUntilRunning() {
var _this6 = this;
return _asyncToGenerator(function* () {
if (_this6._state === 'running') return;
yield new Promise(resolve => _this6.once('running', () => resolve()));
})();
}
/**
* Connects to the first server from the list of servers and issues the `replSetInitiate`
* command passing in a new replica set configuration object.
*/
_initReplSet() {
var _this7 = this;
return _asyncToGenerator(function* () {
if (_this7._state !== 'init') {
throw new Error('Not in init phase.');
}
_this7.debug('Initializing replica set.');
if (!_this7.servers.length) {
throw new Error('One or more server is required.');
}
const uris = yield Promise.all(_this7.servers.map(server => server.getUri()));
const conn = yield _mongodb.MongoClient.connect(uris[0], {
useNewUrlParser: true
});
try {
const db = yield conn.db(_this7.opts.replSet.dbName);
const admin = db.admin();
const members = uris.map((uri, idx) => ({
_id: idx,
host: (0, _db_util.getHost)(uri)
}));
const rsConfig = {
_id: _this7.opts.replSet.name,
members
};
yield admin.command({
replSetInitiate: rsConfig
});
_this7.debug('Waiting for replica set to have a PRIMARY member.');
yield _this7._waitForPrimary(admin);
_this7.emit(_this7._state = 'running');
_this7.debug('running');
} finally {
yield conn.close();
}
})();
}
_startServer(instanceOpts) {
const serverOpts = {
autoStart: true,
debug: this.opts.debug,
binary: this.opts.binary,
instance: instanceOpts,
spawn: this.opts.replSet.spawn
};
const server = new _MongoMemoryServer.default(serverOpts);
return server;
}
_waitForPrimary(db) {
var _this8 = this;
return _asyncToGenerator(function* () {
const replStatus = yield (0, _db_util.getReplStatus)(db);
_this8.debug(' replStatus:', replStatus);
const hasPrimary = replStatus.members.some(m => m.stateStr === 'PRIMARY');
if (!hasPrimary) {
_this8.debug('No PRIMARY yet. Waiting...');
return new Promise(resolve => setTimeout(() => resolve(_this8._waitForPrimary(db)), 1000));
}
return true;
})();
}
}
exports.default = MongoMemoryReplSet;