mongodb-memory-server-core
Version:
MongoDB Server for testing (core package, without autodownload). The server will allow you to connect your favourite ODM or client library to the MongoDB Server and run parallel integration tests isolated from each other.
251 lines • 9.66 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.mkdir = exports.checkBinaryPermissions = exports.ManagerAdvanced = exports.ManagerBase = exports.tryReleaseFile = exports.pathExists = exports.statPath = exports.authDefault = exports.ensureAsync = exports.isAlive = exports.killProcess = exports.assertion = exports.isNullOrUndefined = exports.uriTemplate = exports.getHost = exports.generateDbName = exports.errorWithCode = void 0;
const tslib_1 = require("tslib");
const debug_1 = (0, tslib_1.__importDefault)(require("debug"));
const fs_1 = require("fs");
const errors_1 = require("./errors");
const log = (0, debug_1.default)('MongoMS:utils');
/**
* This is here, because NodeJS does not have a FSError type
* @param err Value to check agains
* @returns `true` if it is a error with code, `false` if not
*/
function errorWithCode(err) {
return err instanceof Error && 'code' in err;
}
exports.errorWithCode = errorWithCode;
/**
* Return input or default database
* @param {string} dbName
*/
function generateDbName(dbName) {
// this is ""(empty) to make it compatible with mongodb's uri format and mongoose's uri format
// (in mongodb its the auth database, in mongoose its the default database for models)
return dbName || '';
}
exports.generateDbName = generateDbName;
/**
* Extracts the host and port information from a mongodb URI string.
* @param {string} uri mongodb URI
*/
function getHost(uri) {
// this will turn "mongodb://user:pass@localhost:port/authdb?queryoptions=1" to "localhost:port"
return uri.replace(/(?:^mongodb:\/{2})|(?:\/.*$)|(?:.*@)/gim, '');
}
exports.getHost = getHost;
/**
* Basic MongoDB Connection string
* @param host the host ip or an list of hosts
* @param port the host port or undefined if "host" is an list of hosts
* @param dbName the database to add to the uri (in mongodb its the auth database, in mongoose its the default database for models)
* @param query extra uri-query options (joined with "&")
*/
function uriTemplate(host, port, dbName, query) {
const hosts = !isNullOrUndefined(port) ? `${host}:${port}` : host;
return `mongodb://${hosts}/${dbName}` + (!isNullOrUndefined(query) ? `?${query.join('&')}` : '');
}
exports.uriTemplate = uriTemplate;
/**
* Because since node 4.0.0 the internal util.is* functions got deprecated
* @param val Any value to test if null or undefined
*/
function isNullOrUndefined(val) {
return val === null || val === undefined;
}
exports.isNullOrUndefined = isNullOrUndefined;
/**
* Assert an condition, if "false" throw error
* Note: it is not named "assert" to differentiate between node and jest types
* @param cond The Condition to throw
* @param error An Custom Error to throw
*/
function assertion(cond, error) {
if (!cond) {
throw error !== null && error !== void 0 ? error : new errors_1.AssertionFallbackError();
}
}
exports.assertion = assertion;
/**
* Kill an ChildProcess
* @param childprocess The Process to kill
* @param name the name used in the logs
* @param mongodPort the port for the mongod process (for easier logging)
*/
function killProcess(childprocess, name, mongodPort) {
return (0, tslib_1.__awaiter)(this, void 0, void 0, function* () {
function ilog(msg) {
log(`Mongo[${mongodPort || 'unknown'}] killProcess: ${msg}`);
}
// this case can somehow happen, see https://github.com/nodkz/mongodb-memory-server/issues/666
if (isNullOrUndefined(childprocess)) {
ilog('childprocess was somehow undefined');
return;
}
// check if the childProcess (via PID) is still alive (found thanks to https://github.com/nodkz/mongodb-memory-server/issues/411)
if (!isAlive(childprocess.pid)) {
ilog("given childProcess's PID was not alive anymore");
return;
}
/**
* Timeout before using SIGKILL
*/
const timeoutTime = 1000 * 10;
yield new Promise((res, rej) => {
let timeout = setTimeout(() => {
ilog('timeout triggered, trying SIGKILL');
if (!debug_1.default.enabled('MongoMS:utils')) {
console.warn('An Process didnt exit with signal "SIGINT" within 10 seconds, using "SIGKILL"!\n' +
'Enable debug logs for more information');
}
childprocess.kill('SIGKILL');
timeout = setTimeout(() => {
ilog('timeout triggered again, rejecting');
rej(new Error(`Process "${name}" didnt exit, enable debug for more information.`));
}, timeoutTime);
}, timeoutTime);
childprocess.once(`exit`, (code, signal) => {
ilog(`${name}: got exit signal, Code: ${code}, Signal: ${signal}`);
clearTimeout(timeout);
res();
});
ilog(`${name}: sending "SIGINT"`);
childprocess.kill('SIGINT');
});
});
}
exports.killProcess = killProcess;
/**
* Check if the given Process is still alive
* @param {number} pid The Process PID
*/
function isAlive(pid) {
// This test (and allow to be undefined) is here because somewhere between nodejs 12 and 16 the types for "childprocess.pid" changed to include "| undefined"
if (isNullOrUndefined(pid)) {
return false;
}
try {
process.kill(pid, 0); // code "0" dosnt actually kill anything (on all supported systems)
return true;
}
catch (err) {
return false;
}
}
exports.isAlive = isAlive;
/**
* Call "process.nextTick" to ensure an function is exectued directly after all code surrounding it
* look at the following link to get to know on why this needed: https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/#process-nexttick (read full documentation)
*/
function ensureAsync() {
return (0, tslib_1.__awaiter)(this, void 0, void 0, function* () {
return new Promise((res) => process.nextTick(res));
});
}
exports.ensureAsync = ensureAsync;
/**
* Convert Partitial input into full-defaulted output
* @param opts Partitial input options
*/
function authDefault(opts) {
return Object.assign({ force: false, disable: false, customRootName: 'mongodb-memory-server-root', customRootPwd: 'rootuser', extraUsers: [], keyfileContent: '0123456789' }, opts);
}
exports.authDefault = authDefault;
/**
* Run "fs.promises.stat", but return "undefined" if error is "ENOENT" or "EACCES"
* follows symlinks
* @param path The Path to Stat
* @throws if the error is not "ENOENT" or "EACCES"
*/
function statPath(path) {
return (0, tslib_1.__awaiter)(this, void 0, void 0, function* () {
return fs_1.promises.stat(path).catch((err) => {
// catch the error if the directory doesn't exist or permission is denied, without throwing an error
if (['ENOENT', 'EACCES'].includes(err.code)) {
return undefined;
}
throw err;
});
});
}
exports.statPath = statPath;
/**
* Like "fs.existsSync" but async
* uses "utils.statPath"
* follows symlinks
* @param path The Path to check for
*/
function pathExists(path) {
return (0, tslib_1.__awaiter)(this, void 0, void 0, function* () {
return !isNullOrUndefined(yield statPath(path));
});
}
exports.pathExists = pathExists;
/**
* Try to read an release file path and apply an parser to the output
* @param path The Path to read for an release file
* @param parser An function to parse the output of the file
*/
function tryReleaseFile(path, parser) {
return (0, tslib_1.__awaiter)(this, void 0, void 0, function* () {
try {
const output = yield fs_1.promises.readFile(path);
return parser(output.toString());
}
catch (err) {
if (errorWithCode(err) && !['ENOENT', 'EACCES'].includes(err.code)) {
throw err;
}
log(`tryReleaseFile: "${path}" does not exist`);
return undefined;
}
});
}
exports.tryReleaseFile = tryReleaseFile;
/**
* This Class is used to have unified types for base-manager functions
*/
class ManagerBase {
}
exports.ManagerBase = ManagerBase;
/**
* This Class is used to have unified types for advanced-manager functions
*/
class ManagerAdvanced extends ManagerBase {
}
exports.ManagerAdvanced = ManagerAdvanced;
/**
* Check that the Binary has sufficient Permissions to be executed
* @param path The Path to check
*/
function checkBinaryPermissions(path) {
return (0, tslib_1.__awaiter)(this, void 0, void 0, function* () {
try {
yield fs_1.promises.access(path, fs_1.constants.X_OK); // check if the provided path exists and has the execute bit for current user
}
catch (err) {
if (errorWithCode(err)) {
if (err.code === 'EACCES') {
throw new errors_1.InsufficientPermissionsError(path);
}
if (err.code === 'ENOENT') {
throw new errors_1.BinaryNotFoundError(path);
}
}
throw err;
}
});
}
exports.checkBinaryPermissions = checkBinaryPermissions;
/**
* Make Directory, wrapper for native mkdir with recursive true
* @param path The Path to create
* @returns Nothing
*/
function mkdir(path) {
return (0, tslib_1.__awaiter)(this, void 0, void 0, function* () {
yield fs_1.promises.mkdir(path, { recursive: true });
});
}
exports.mkdir = mkdir;
//# sourceMappingURL=utils.js.map