Source: main.js

/**
 * ### Test helpers
 *
 * This module hosts everything needed for tests.
 * Abstraction over the expection engine, abstraction over the basic functions,
 * this module is the Test toolbelt.
 * Little to no test framework logic should be in unit tests to keep them
 * focused on code documentation.
 *
 * @module Library/Test-Helpers
 */
"use strict";

const lodash = require("lodash/fp");
const {
    defaultsDeep,
    has,
    flow,
    keys,
    map
} = lodash;

const BPromise = require("bluebird");

const tape = require("blue-tape");
const sinon = require("sinon");
const proxyquire = require("proxyquire").noPreserveCache().noCallThru();

/**
 * Return a function that will execute a test with a given description
 * @param {Function} testFn - Test engine
 * @param {String} testDescription - Unit test description
 * @param {Function} cb - Callback called with test engine object
 * @return {Function} Unit test handler
 */
exports.unitTest = (testFn, testDescription) => (cb) =>
    testFn(`${testDescription}`, cb);

/**
 * Returns a function that will perform several tests, each with a concatenated
 * description.
 * Descriptions will be concatenated to form a test statement that will have this
 * structure: "Module - Function(): When XXXXXXXXX, it should YYYYYYYYY
 * @curried
 * @param {String} mode - Execution mode(null = normal, skip, or only)
 * @param {String} moduleName - Tested module description
 * @param {Array<Object>} assertions - Assertion array to perform
 * @return {Array} Assertions execution results
 */
exports.executeTestsInternal = (mode) => (moduleName, functionBlocks) =>
    map((fnObj) =>
        map(function(assert) {
            const description = `${moduleName} - ${fnObj.name}: when ${assert.when}, it should ${assert.should}`;
            const newTest = exports.unitTest(tape, description);
            newTest.skip = exports.unitTest(tape.skip, description);
            newTest.only = exports.unitTest(tape.only, description);
            // istanbul ignore if
            if (mode === "skip" || fnObj.skip) {
                return assert.test(newTest.skip);
            }
            return assert.test(newTest);
        }, fnObj.assertions), functionBlocks);
exports.executeTests = exports.executeTestsInternal();
exports.executeTests.skip = exports.executeTestsInternal("skip");

const utilityFunctions = {};
/**
 * Identity function
 * @param {Mixed} input - Value to be returned
 * @return {Mixed} Same as input
 */
utilityFunctions.idFn = (input) => input;
/**
 * Return the identity function
 * @return {Function} Identity function
 */
utilityFunctions.idFnHO = () => utilityFunctions.idFn;
/**
 * Returns null
 * @return {Null} Null
 */
utilityFunctions.nullFn = () => null;

/**
 * Returns a function that returns null
 * @return {Function} Function that returns null
 */
utilityFunctions.nullFnHO = () => utilityFunctions.nullFn;
/**
 * Throws an Error
 * @param {String} message - Error message
 * @return {Error} Error thrown
 */
utilityFunctions.throwFn = function(message) {
    throw new Error(message);
};
/**
 * Returns a function that throws an Error
 * @param {String} message - Error message
 * @return {Function} Function that throws an error
 */
utilityFunctions.throwFnHO = (message) => () => utilityFunctions.throwFn(message);
/**
 * Returns a resolved Promise
 * @param {Mixed} value - Resolved value
 * @return {Promise<Mixed>} Promise resolved with given value
 */
utilityFunctions.resolveFn = (value) => BPromise.resolve(value);
/**
 * Returns a function that return a resolved Promise with given value
 * @param  {Mixed} value - Resolved value
 * @return {Function} Function that returns a resolved Promise with given value
 */
utilityFunctions.resolveFnHO = (value) => () => utilityFunctions.resolveFn(value);
/**
 * Returns a rejected Promise
 * @param  {String} reason - Rejection reason
 * @return {Promise<String>} Promise rejected with given reason
 */
utilityFunctions.rejectFn = (reason) => BPromise.reject(new Error(reason));
/*
 * Returns a function that return a rejected Promise with given reason
 * @param  {String} reason - Rejection reason
 * @return {Function} Function that return a rejected Promise with given reason
 */
utilityFunctions.rejectFnHO = (reason) => (otherReason) => (otherReason) ?
    utilityFunctions.rejectFn(otherReason) :
    utilityFunctions.rejectFn(reason);
// Abstracts Sinon.js in tests
utilityFunctions.stub = sinon.stub;
utilityFunctions.spy = sinon.spy;
/**
 * Create an Express-like response mock
 * @return {Object} Response mock
 */
utilityFunctions.createResponseMock = function() {
    const response = {};
    response.status = function(status) {
        response.status = status;
        return response;
    };
    response.json = function(body) {
        response.body = body;
        return response;
    };
    return response;
};
exports.utilityFunctions = utilityFunctions;

// Proxyquire configuration object for stubs (to prevent all calls to the
// original module)
//const noCallThru = {
//"@noCallThru": true
//};

/**
 * Convert an object into another object with computed properties by using the given
 * object properties names as keys declared in the given dependency
 * dictionnary.
 * If they are not defined, this function will assume that they refer to node
 * modules instead of project-related ones during the computed property resolution.
 * Usage of the dictionnary is optional, but helpful since you can then use
 * aliases for modules names in your stubs declaration, instead of having to
 * resolve all modules path names (since proxyquire need them)
 * @example:
 * Given { "foo": "bar" } as the dictionnary
 * and { "fs": ..., "foo": ..., "baz": ... } as the object,
 * result will be { "fs": ..., "bar": ..., "baz": ... }
 * @param  {Object} dictionnary - Dictionnary
 * @param  {Object} object - Input object
 * @return {Object} Converted object
 */
exports.convertIntoComputedProperties = function(dictionnary, object) {
    const newObj = {};
    const objKeys = keys(object);
    map(function(property) {
            if (has(property, dictionnary)) {
                newObj[dictionnary[property]] = object[property];
            } else {
                newObj[property] = object[property];
            }
        },
        objKeys
    );
    return newObj;
};

/**
 * Require a module with stubbed dependencies
 * @param  {Object} dictionnary - Dictionnary
 * @param {Object} defaultStubs - Default stubs (internal dependencies) of the
 * tested module
 * @param {String} moduleName - Module name to proxiquire
 * @param {Object} customStubs - Custom stubs for the proxified module
 * @return {Mixed} Proxified module
 */
exports.requireWithStubs = function(dictionnary, defaultStubs, moduleName, customStubs) {
    const stubs = flow(
        defaultsDeep(exports.convertIntoComputedProperties(dictionnary, customStubs)),
        defaultsDeep(exports.convertIntoComputedProperties(dictionnary, defaultStubs))
    )({});
    return proxyquire(
        moduleName,
        stubs
    );
};

/**
 * Prepare a module for tests by requiring it with stubs
 * Module name is based on the given test file name that
 * will consume the prepared module.
 * @param  {Object} dictionnary - Dictionnary
 * @param {Object} defaultStubs - Default stubs (internal dependencies) of the
 * tested module
 * @param {String} testFileName - Test file name
 * @param {Object} customStubs - Custom stubs for the proxified module
 * @return {Mixed} Proxified module
 */
exports.prepareForTests = (dictionnary, defaultStubs) => (testFileName) => (customStubs) =>
    exports.requireWithStubs(
        dictionnary,
        defaultStubs,
        testFileName.replace(".spec", ""),
        customStubs
    );