UNPKG

mongoprime

Version:

In memory MongoDB fixture loader for parallel testing.

191 lines (148 loc) 4.96 kB
const { MongoClient, ObjectId } = require('mongodb') const net = require('net') const debug = require('debug')('mongoprime') const uniqueTempDir = require('unique-temp-dir') const MongodbPrebuilt = require('mongodb-prebuilt') const process = require('process') const getPort = require('get-port') const MongoWireProtocol = require('mongo-wire-protocol') const uuid = require('uuid') let options = { fixtures: {}, // Fixture collection host: '127.0.0.1', // Proxy host port: 27018, // Proxy port ignore: ['system', 'admin', 'local'], // Collections to ignore path: null, // Path for mongo metadata - defaults to randomly generated systm tmp dir mongo: null // Mongo port - randomly generated } let initialized = false let connections = {} let primed = [] const startProxy = async () => { const server = net.createServer() server.on('connection', (socket) => { debug('New connection received') var request = new MongoWireProtocol() socket.on('data', async (chunk) => { if (request.finished) { request = new MongoWireProtocol() } request.parse(chunk) if (!request.finished) { } const database = request.fullCollectionName.replace('.$cmd', '') await primeDatabase(database) forwardRequest(socket, chunk, database) }) socket.on('error', error => { console.error(error) }) socket.on('close', () => { debug('Connection closed') }) }) server.on('listening', () => { debug('Server started') }) server.listen({port: options.port, host: options.host}) } const generateURL = () => { return { host: `mongodb://${options.host}:${options.port}`, db: uuid() } } const forwardRequest = (socket, chunk, database) => { const serviceSocket = new net.Socket() serviceSocket.connect(options.mongo, options.host, function () { debug(`Forwarding data to ${database}`) serviceSocket.write(chunk) }) serviceSocket.on('end', function () { }) serviceSocket.on('data', function (data) { debug(`Receiving data from ${database}`) socket.write(data) // socket.end() }) } const primeDatabase = async (database) => { if (!!~options.ignore.indexOf(database) || !!~primed.indexOf(database)) return primed.push(database) debug(`Priming ${database}`) await clearCollections(database) await loadFixtures(database) } const initProxy = async (params) => { if (initialized) return options = Object.assign(options, params) options.mongo = await getPort() options.path = options.path || uniqueTempDir({ create: true }) await startServer() await startProxy() process.env.MONGO_PRIMER_DB_PORT = options.port process.env.MONGO_PRIMER_DB_HOST = options.host initialized = true } const getUri = (databaseName) => { return { host: 'mongodb://' + options.host + ':' + options.mongo, db: databaseName } } const clearCollections = async (database) => { const db = await getConnection(database) const names = await listCollections(database) const filtered = Object.keys(options.fixtures).filter(c => ~names.indexOf(c)) return Promise.all(filtered.map(name => { return db.collection(name).drop() })) } const startServer = async () => { const mongodHelper = new MongodbPrebuilt.MongodHelper(['--bind_ip', options.host, '--port', options.mongo, '--dbpath', options.path, '--storageEngine', 'ephemeralForTest']) await mongodHelper.run() } const getConnection = async (database) => { if (connections[database]) { debug(`Reusing ${database} connection`) return Promise.resolve(connections[database]) } else { const { host, db } = getUri(database) const con = await MongoClient.connect(host) connections[database] = con.db(db) return connections[database] } } const stopServer = async () => { Object.keys(connections).map(databaseName => { connections[databaseName].close() }) return new MongodbPrebuilt.MongoBins('mongo', ['--port', options.mongo, '--eval', "db.getSiblingDB('admin').shutdownServer()"]).run() } const closeAll = async (database) => { const db = await getConnection(database) db.stop() } const listCollections = async (database) => { const db = await getConnection(database) const names = await db.listCollections().toArray() return names.map(c => { return c.name }).filter(c => { return !c.match(options.ignore) }) } const loadFixtures = async (database) => { const db = await getConnection(database) const promises = Object.keys(options.fixtures).map(name => { const items = options.fixtures[name] // Ensure there is something else we get and Invalid Operation, no operations specified error if (items.length) { return db.collection(name).insert(items) } }) return Promise.all(promises) } exports.generateURL = generateURL exports.initProxy = initProxy exports.stopServer = stopServer exports.closeAll = closeAll exports.ObjectId = ObjectId