Best JavaScript code snippet using mountebank
filesystemBackedImpostersRepository.js
Source:filesystemBackedImpostersRepository.js  
1'use strict';2/**3 * An abstraction for loading imposters from the filesystem4 * The root of the database is provided on the command line as --datadir5 * The file layout looks like this:6 *7 * /{datadir}8 *   /30009 *     /imposter.json10 *       {11 *         "protocol": "http",12 *         "port": 3000,13 *         "stubs: [{14 *           "predicates": [{ "equals": { "path": "/" } }],15 *           "meta": {16 *             "dir": "stubs/{epoch-pid-counter}"17 *           }18 *         }]19 *       }20 *21 *     /stubs22 *       /{epoch-pid-counter}23 *         /meta.json24 *           {25 *             "responseFiles": ["responses/{epoch-pid-counter}.json"],26 *               // An array of indexes into responseFiles which handle repeat behavior27 *             "orderWithRepeats": [0],28 *               // The next index into orderWithRepeats; incremented with each call to nextResponse()29 *             "nextIndex": 030 *           }31 *32 *         /responses33 *           /{epoch-pid-counter}.json34 *             {35 *               "is": { "body": "Hello, world!" }36 *             }37 *38 *         /matches39 *             /{epoch-pid-counter}.json40 *                 { ... }41 *42 *     /requests43 *       /{epoch-pid-counter}.json44 *         { ... }45 *46 * This structure is designed to minimize the amount of file locking and to maximize parallelism and throughput.47 *48 * The imposters.json file needs to be locked during imposter-level activities (e.g. adding a stub).49 * Readers do not lock; they just get the data at the time they read it. Since this file doesn't50 * contain transient state, there's no harm from a stale read. Writes (which happen for51 * stub changing operations, either for proxy response recording or stub API calls) grab a file lock52 * for both the read and the write. Writes to this file should be infrequent, except perhaps during53 * proxy recording. Newly added stubs may change the index of existing stubs in the stubs array, but54 * will never change the stub meta.dir, so it is always safe to hang on to it for subsequent operations.55 *56 * The stub meta.json needs to be locked to add responses or trigger the next response, but is57 * separated from the imposter.json so we can have responses from multiple stubs in parallel with no58 * lock conflict. Again, readers (e.g. to generate imposter JSON) do not need a lock because the responseFiles59 * array is mostly read-only, and even when it's not (adding responses during proxyAlways recording), there's60 * no harm from a stale read. Writers (usually generating the next response for a stub) grab a lock for the61 * read and the write. This should be the most common write across files, which is why the meta.json file62 * is small.63 *64 * In both cases where a file needs to be locked, an exponential backoff retry strategy is used. Inconsistent65 * reads of partially written files (which can happen by default with the system calls - fs.writeFile is not66 * atomic) are avoided by writing first to a temp file (during which time reads can happen to the original file)67 * and then renaming to the original file.68 *69 * New directories and filenames use a timestamp-based filename to allow creating them without synchronizing70 * with a read. Since multiple files (esp. requests) can be added during the same millisecond, a pid and counter71 * is tacked on to the filename to improve uniqueness. It doesn't provide the * ironclad guarantee a GUID72 * does -- two different processes on two different machines could in theory have the same pid and create files73 * during the same timestamp with the same counter, but the odds of that happening * are so small that it's not74 * worth giving up the easy time-based sortability based on filenames alone.75 *76 * Keeping all imposter information under a directory (instead of having metadata outside the directory)77 * allows us to remove the imposter by simply removing the directory.78 *79 * There are some extra checks on filesystem operations (atomicWriteFile) due to antivirus software, solar flares,80 * gremlins, etc. graceful-fs solves some of these, but apparently not all.81 *82 * @module83 */84const prometheus = require('prom-client'),85    metrics = {86        lockAcquireDuration: new prometheus.Histogram({87            name: 'mb_lock_acquire_duration_seconds',88            help: 'Time it takes to acquire a file lock',89            buckets: [0.1, 0.2, 0.5, 1, 3, 5, 10, 30],90            labelNames: ['caller']91        }),92        lockHoldDuration: new prometheus.Histogram({93            name: 'mb_lock_hold_duration_seconds',94            help: 'Time a file lock is held',95            buckets: [0.1, 0.2, 0.5, 1, 2],96            labelNames: ['caller']97        }),98        lockErrors: new prometheus.Counter({99            name: 'mb_lock_errors_total',100            help: 'Number of lock errors',101            labelNames: ['caller', 'code']102        })103    };104/**105 * Creates the repository106 * @param {Object} config - The database configuration107 * @param {String} config.datadir - The database directory108 * @param {Object} logger - The logger109 * @returns {Object}110 */111function create (config, logger) {112    let counter = 0,113        locks = 0;114    const Q = require('q'),115        imposterFns = {};116    function prettyError (err, filepath) {117        const errors = require('../util/errors');118        // I started getting EROFS after upgrading to OSX Catalina 10.15.6119        if (err.code === 'EACCES' || err.code === 'EROFS') {120            return errors.InsufficientAccessError({ path: filepath });121        }122        else {123            return err;124        }125    }126    function writeFile (filepath, obj) {127        const fs = require('fs-extra'),128            path = require('path'),129            dir = path.dirname(filepath),130            deferred = Q.defer();131        fs.ensureDir(dir, mkdirErr => {132            if (mkdirErr) {133                deferred.reject(prettyError(mkdirErr, dir));134            }135            else {136                fs.writeFile(filepath, JSON.stringify(obj, null, 2), err => {137                    if (err) {138                        deferred.reject(prettyError(err, filepath));139                    }140                    else {141                        deferred.resolve(filepath);142                    }143                });144            }145        });146        return deferred.promise;147    }148    function readFile (filepath, defaultContents) {149        const deferred = Q.defer(),150            fs = require('fs-extra'),151            errors = require('../util/errors');152        fs.readFile(filepath, 'utf8', (err, data) => {153            if (err && err.code === 'ENOENT') {154                if (typeof defaultContents === 'undefined') {155                    logger.error(`Corrupted database: missing file ${filepath}`);156                    deferred.reject(errors.DatabaseError('file not found', { details: err.message }));157                }158                else {159                    deferred.resolve(defaultContents);160                }161            }162            else if (err) {163                deferred.reject(prettyError(err, filepath));164            }165            else {166                try {167                    deferred.resolve(JSON.parse(data));168                }169                catch (parseErr) {170                    logger.error(`Corrupted database: invalid JSON for ${filepath}`);171                    deferred.reject(errors.DatabaseError(`invalid JSON in ${filepath}`, { details: parseErr.message }));172                }173            }174        });175        return deferred.promise;176    }177    function rename (oldPath, newPath) {178        const deferred = Q.defer(),179            fs = require('fs-extra');180        fs.rename(oldPath, newPath, err => {181            if (err) {182                deferred.reject(prettyError(err, oldPath));183            }184            else {185                deferred.resolve(newPath);186            }187        });188        return deferred.promise;189    }190    function ensureDir (filepath) {191        const fs = require('fs-extra'),192            path = require('path'),193            dir = path.dirname(filepath),194            deferred = Q.defer();195        fs.ensureDir(dir, err => {196            if (err) {197                deferred.reject(prettyError(err, dir));198            }199            else {200                deferred.resolve(dir);201            }202        });203        return deferred.promise;204    }205    function atomicWriteFile (filepath, obj, attempts = 1) {206        const tmpfile = filepath + '.tmp';207        return writeFile(tmpfile, obj)208            .then(() => rename(tmpfile, filepath))209            .catch(err => {210                if (err.code === 'ENOENT' && attempts < 15) {211                    logger.debug(`Attempt ${attempts} failed with ENOENT error on atomic write of  ${filepath}. Retrying...`);212                    return Q.delay(10).then(() => atomicWriteFile(filepath, obj, attempts + 1));213                }214                else {215                    return Q.reject(err);216                }217            });218    }219    function lockFile (filepath) {220        const locker = require('proper-lockfile'),221            options = {222                realpath: false,223                retries: {224                    retries: 20,225                    minTimeout: 10,226                    maxTimeout: 5000,227                    randomize: true,228                    factor: 1.5229                },230                stale: 30000231            };232        // with realpath = false, the file doesn't have to exist, but the directory does233        return ensureDir(filepath)234            .then(() => locker.lock(filepath, options));235    }236    function readAndWriteFile (filepath, caller, transformer, defaultContents) {237        const locker = require('proper-lockfile'),238            currentLockId = locks,239            observeLockAcquireDuration = metrics.lockAcquireDuration.startTimer({ caller });240        locks += 1;241        return lockFile(filepath)242            .then(release => {243                const acquireLockSeconds = observeLockAcquireDuration(),244                    observeLockHoldDuration = metrics.lockHoldDuration.startTimer({ caller });245                logger.debug(`Acquired file lock on ${filepath} for ${caller}-${currentLockId} after ${acquireLockSeconds}s`);246                return readFile(filepath, defaultContents)247                    .then(original => transformer(original))248                    .then(transformed => atomicWriteFile(filepath, transformed))249                    .then(() => {250                        const lockHeld = observeLockHoldDuration();251                        logger.debug(`Released file lock on ${filepath} for ${caller}-${currentLockId} after ${lockHeld}s`);252                        return release()253                            .catch(err => {254                                // Ignore lock already released errors255                                if (err.code !== 'ERELEASED') {256                                    throw err;257                                }258                            });259                    });260            })261            .catch(err => {262                metrics.lockErrors.inc({ caller, code: err.code });263                locker.unlock(filepath, { realpath: false }).catch(() => {});264                return Q.reject(err);265            });266    }267    function remove (path) {268        const deferred = Q.defer(),269            fs = require('fs-extra');270        fs.remove(path, err => {271            if (err) {272                deferred.reject(err);273            }274            else {275                deferred.resolve({});276            }277        });278        return deferred.promise;279    }280    function filenameFor (timestamp) {281        const epoch = timestamp.valueOf();282        counter += 1;283        return `${epoch}-${process.pid}-${counter}`;284    }285    function partsFrom (filename) {286        // format {epoch}-{pid}-{counter}287        const pattern = /^(\d+)-(\d+)-(\d+)\.json$/,288            parts = pattern.exec(filename);289        return {290            epoch: Number(parts[1]),291            pid: Number(parts[2]),292            counter: Number(parts[3])293        };294    }295    function timeSorter (first, second) {296        // format {epoch}-{pid}-{counter}297        // sort by epoch first, then pid, then counter to guarantee determinism for298        // files added during the same millisecond.299        const firstParts = partsFrom(first),300            secondParts = partsFrom(second);301        let result = firstParts.epoch - secondParts.epoch;302        if (result === 0) {303            result = firstParts.pid - secondParts.pid;304        }305        if (result === 0) {306            result = firstParts.counter - secondParts.counter;307        }308        return result;309    }310    function readdir (path) {311        const deferred = Q.defer(),312            fs = require('fs-extra');313        fs.readdir(path, (err, files) => {314            if (err && err.code === 'ENOENT') {315                // Nothing saved yet316                deferred.resolve([]);317            }318            else if (err) {319                deferred.reject(err);320            }321            else {322                deferred.resolve(files);323            }324        });325        return deferred.promise;326    }327    function loadAllInDir (path) {328        return readdir(path).then(files => {329            const promises = files330                .filter(file => file.indexOf('.json') > 0)331                .sort(timeSorter)332                .map(file => readFile(`${path}/${file}`));333            return Q.all(promises);334        });335    }336    function repeatsFor (response) {337        return response.repeat || 1;338    }339    function saveStubMetaAndResponses (stub, baseDir) {340        const stubDefinition = {341                meta: { dir: `stubs/${filenameFor(new Date())}` }342            },343            meta = {344                responseFiles: [],345                orderWithRepeats: [],346                nextIndex: 0347            },348            responses = stub.responses || [],349            promises = [];350        if (stub.predicates) {351            stubDefinition.predicates = stub.predicates;352        }353        for (let i = 0; i < responses.length; i += 1) {354            const responseFile = `responses/${filenameFor(new Date())}.json`;355            meta.responseFiles.push(responseFile);356            for (let repeats = 0; repeats < repeatsFor(responses[i]); repeats += 1) {357                meta.orderWithRepeats.push(i);358            }359            promises.push(writeFile(`${baseDir}/${stubDefinition.meta.dir}/${responseFile}`, responses[i]));360        }361        promises.push(writeFile(`${baseDir}/${stubDefinition.meta.dir}/meta.json`, meta));362        return Q.all(promises).then(() => stubDefinition);363    }364    function stubRepository (baseDir) {365        const imposterFile = `${baseDir}/imposter.json`;366        function metaPath (stubDir) {367            return `${baseDir}/${stubDir}/meta.json`;368        }369        function responsePath (stubDir, responseFile) {370            return `${baseDir}/${stubDir}/${responseFile}`;371        }372        function requestPath (request) {373            return `${baseDir}/requests/${filenameFor(Date.parse(request.timestamp))}.json`;374        }375        function matchPath (stubDir, match) {376            return `${baseDir}/${stubDir}/matches/${filenameFor(Date.parse(match.timestamp))}.json`;377        }378        function readHeader () {379            return readFile(imposterFile, { stubs: [] });380        }381        function readAndWriteHeader (caller, transformer) {382            return readAndWriteFile(imposterFile, caller, transformer, { stubs: [] });383        }384        function wrap (stub) {385            const helpers = require('../util/helpers'),386                cloned = helpers.clone(stub || {}),387                stubDir = stub ? stub.meta.dir : '';388            if (typeof stub === 'undefined') {389                return {390                    addResponse: () => Q(),391                    nextResponse: () => Q({392                        is: {},393                        stubIndex: () => Q(0)394                    }),395                    recordMatch: () => Q()396                };397            }398            delete cloned.meta;399            /**400             * Adds a response to the stub401             * @memberOf module:models/filesystemBackedImpostersRepository#402             * @param {Object} response - the new response403             * @returns {Object} - the promise404             */405            cloned.addResponse = response => {406                let responseFile;407                return readAndWriteFile(metaPath(stubDir), 'addResponse', meta => {408                    const responseIndex = meta.responseFiles.length;409                    responseFile = `responses/${filenameFor(new Date())}.json`;410                    meta.responseFiles.push(responseFile);411                    for (let repeats = 0; repeats < repeatsFor(response); repeats += 1) {412                        meta.orderWithRepeats.push(responseIndex);413                    }414                    return writeFile(responsePath(stubDir, responseFile), response).then(() => meta);415                });416            };417            function stubIndex () {418                return readHeader().then(header => {419                    for (let i = 0; i < header.stubs.length; i += 1) {420                        if (header.stubs[i].meta.dir === stubDir) {421                            return i;422                        }423                    }424                    return 0;425                });426            }427            function createResponse (responseConfig) {428                const result = helpers.clone(responseConfig || { is: {} });429                result.stubIndex = stubIndex;430                return result;431            }432            /**433             * Returns the next response for the stub, taking into consideration repeat behavior and cycling back the beginning434             * @memberOf module:models/filesystemBackedImpostersRepository#435             * @returns {Object} - the promise436             */437            cloned.nextResponse = () => {438                let responseFile;439                return readAndWriteFile(metaPath(stubDir), 'nextResponse', meta => {440                    const maxIndex = meta.orderWithRepeats.length,441                        responseIndex = meta.orderWithRepeats[meta.nextIndex % maxIndex];442                    responseFile = meta.responseFiles[responseIndex];443                    meta.nextIndex = (meta.nextIndex + 1) % maxIndex;444                    return Q(meta);445                }).then(() => readFile(responsePath(stubDir, responseFile)))446                    .then(responseConfig => createResponse(responseConfig));447            };448            /**449             * Records a match for debugging purposes450             * @memberOf module:models/filesystemBackedImpostersRepository#451             * @param {Object} request - the request452             * @param {Object} response - the response453             * @param {Object} responseConfig - the config that generated the response454             * @param {Number} processingTime - the time to match the predicate and generate the full response455             * @returns {Object} - the promise456             */457            cloned.recordMatch = (request, response, responseConfig, processingTime) => {458                const match = {459                    timestamp: new Date().toJSON(),460                    request,461                    response,462                    responseConfig,463                    processingTime464                };465                return writeFile(matchPath(stubDir, match), match);466            };467            return cloned;468        }469        /**470         * Returns the number of stubs for the imposter471         * @memberOf module:models/filesystemBackedImpostersRepository#472         * @returns {Object} - the promise473         */474        function count () {475            return readHeader().then(imposter => imposter.stubs.length);476        }477        /**478         * Returns the first stub whose predicates matches the filter479         * @memberOf module:models/filesystemBackedImpostersRepository#480         * @param {Function} filter - the filter function481         * @param {Number} startIndex - the index to to start searching482         * @returns {Object} - the promise483         */484        function first (filter, startIndex = 0) {485            return readHeader().then(header => {486                for (let i = startIndex; i < header.stubs.length; i += 1) {487                    if (filter(header.stubs[i].predicates || [])) {488                        return { success: true, stub: wrap(header.stubs[i]) };489                    }490                }491                return { success: false, stub: wrap() };492            });493        }494        /**495         * Adds a new stub to imposter496         * @memberOf module:models/filesystemBackedImpostersRepository#497         * @param {Object} stub - the stub to add498         * @returns {Object} - the promise499         */500        function add (stub) { // eslint-disable-line no-shadow501            return saveStubMetaAndResponses(stub, baseDir).then(stubDefinition => {502                return readAndWriteHeader('addStub', header => {503                    header.stubs.push(stubDefinition);504                    return header;505                });506            });507        }508        /**509         * Inserts a new stub at the given index510         * @memberOf module:models/filesystemBackedImpostersRepository#511         * @param {Object} stub - the stub to add512         * @param {Number} index - the index to insert the new stub at513         * @returns {Object} - the promise514         */515        function insertAtIndex (stub, index) {516            return saveStubMetaAndResponses(stub, baseDir).then(stubDefinition => {517                return readAndWriteHeader('insertStubAtIndex', header => {518                    header.stubs.splice(index, 0, stubDefinition);519                    return header;520                });521            });522        }523        /**524         * Deletes the stub at the given index525         * @memberOf module:models/filesystemBackedImpostersRepository#526         * @param {Number} index - the index of the stub to delete527         * @returns {Object} - the promise528         */529        function deleteAtIndex (index) {530            let stubDir;531            return readAndWriteHeader('deleteStubAtIndex', header => {532                const errors = require('../util/errors');533                if (typeof header.stubs[index] === 'undefined') {534                    return Q.reject(errors.MissingResourceError(`no stub at index ${index}`));535                }536                stubDir = header.stubs[index].meta.dir;537                header.stubs.splice(index, 1);538                return header;539            }).then(() => remove(`${baseDir}/${stubDir}`));540        }541        /**542         * Overwrites all stubs with a new list543         * @memberOf module:models/filesystemBackedImpostersRepository#544         * @param {Object} newStubs - the new list of stubs545         * @returns {Object} - the promise546         */547        function overwriteAll (newStubs) {548            return readAndWriteHeader('overwriteAllStubs', header => {549                header.stubs = [];550                return remove(`${baseDir}/stubs`).then(() => header);551            }).then(() => {552                let addSequence = Q(true);553                newStubs.forEach(stub => {554                    addSequence = addSequence.then(() => add(stub));555                });556                return addSequence;557            });558        }559        /**560         * Overwrites the stub at the given index561         * @memberOf module:models/filesystemBackedImpostersRepository#562         * @param {Object} stub - the new stub563         * @param {Number} index - the index of the stub to overwrite564         * @returns {Object} - the promise565         */566        function overwriteAtIndex (stub, index) {567            return deleteAtIndex(index).then(() => insertAtIndex(stub, index));568        }569        function loadResponses (stub) {570            return readFile(metaPath(stub.meta.dir))571                .then(meta => Q.all(meta.responseFiles.map(responseFile =>572                    readFile(responsePath(stub.meta.dir, responseFile)))));573        }574        function loadMatches (stub) {575            return loadAllInDir(`${baseDir}/${stub.meta.dir}/matches`);576        }577        /**578         * Returns a JSON-convertible representation579         * @memberOf module:models/filesystemBackedImpostersRepository#580         * @param {Object} options - The formatting options581         * @param {Boolean} options.debug - If true, includes debug information582         * @returns {Object} - the promise resolving to the JSON object583         */584        function toJSON (options = {}) {585            return readHeader().then(header => {586                const responsePromises = header.stubs.map(loadResponses),587                    debugPromises = options.debug ? header.stubs.map(loadMatches) : [];588                return Q.all(responsePromises).then(stubResponses => {589                    return Q.all(debugPromises).then(matches => {590                        header.stubs.forEach((stub, index) => {591                            stub.responses = stubResponses[index];592                            if (options.debug && matches[index].length > 0) {593                                stub.matches = matches[index];594                            }595                            delete stub.meta;596                        });597                        return header.stubs;598                    });599                });600            });601        }602        function isRecordedResponse (response) {603            return response.is && typeof response.is._proxyResponseTime === 'number';604        }605        /**606         * Removes the saved proxy responses607         * @memberOf module:models/filesystemBackedImpostersRepository#608         * @returns {Object} - Promise609         */610        function deleteSavedProxyResponses () {611            return toJSON().then(allStubs => {612                allStubs.forEach(stub => {613                    stub.responses = stub.responses.filter(response => !isRecordedResponse(response));614                });615                allStubs = allStubs.filter(stub => stub.responses.length > 0);616                return overwriteAll(allStubs);617            });618        }619        /**620         * Adds a request for the imposter621         * @memberOf module:models/filesystemBackedImpostersRepository#622         * @param {Object} request - the request623         * @returns {Object} - the promise624         */625        function addRequest (request) {626            const helpers = require('../util/helpers');627            const recordedRequest = helpers.clone(request);628            recordedRequest.timestamp = new Date().toJSON();629            return writeFile(requestPath(recordedRequest), recordedRequest);630        }631        /**632         * Returns the saved requests for the imposter633         * @memberOf module:models/filesystemBackedImpostersRepository#634         * @returns {Object} - the promise resolving to the array of requests635         */636        function loadRequests () {637            return loadAllInDir(`${baseDir}/requests`);638        }639        /**640         * Deletes the requests directory for an imposter641         * @memberOf module:models/filesystemBackedImpostersRepository#642         * @returns {Object} - Promise643         */644        function deleteSavedRequests () {645            return remove(`${baseDir}/requests`);646        }647        return {648            count,649            first,650            add,651            insertAtIndex,652            overwriteAll,653            overwriteAtIndex,654            deleteAtIndex,655            toJSON,656            deleteSavedProxyResponses,657            addRequest,658            loadRequests,659            deleteSavedRequests660        };661    }662    function imposterDir (id) {663        return `${config.datadir}/${id}`;664    }665    function headerFile (id) {666        return `${imposterDir(id)}/imposter.json`;667    }668    /**669     * Returns the stubs repository for the imposter670     * @memberOf module:models/filesystemBackedImpostersRepository#671     * @param {Number} id - the id of the imposter672     * @returns {Object} - the stubs repository673     */674    function stubsFor (id) {675        return stubRepository(imposterDir(id));676    }677    /**678     * Saves a reference to the imposter so that the functions679     * (which can't be persisted) can be rehydrated to a loaded imposter.680     * This means that any data in the function closures will be held in681     * memory.682     * @memberOf module:models/filesystemBackedImpostersRepository#683     * @param {Object} imposter - the imposter684     */685    function addReference (imposter) {686        const id = String(imposter.port);687        imposterFns[id] = {};688        Object.keys(imposter).forEach(key => {689            if (typeof imposter[key] === 'function') {690                imposterFns[id][key] = imposter[key];691            }692        });693    }694    function rehydrate (imposter) {695        const id = String(imposter.port);696        Object.keys(imposterFns[id]).forEach(key => {697            imposter[key] = imposterFns[id][key];698        });699    }700    /**701     * Adds a new imposter702     * @memberOf module:models/filesystemBackedImpostersRepository#703     * @param {Object} imposter - the imposter to add704     * @returns {Object} - the promise705     */706    function add (imposter) {707        const imposterConfig = imposter.creationRequest,708            stubs = imposterConfig.stubs || [],709            promises = stubs.map(stub => saveStubMetaAndResponses(stub, imposterDir(imposter.port)));710        delete imposterConfig.requests;711        return Q.all(promises).then(stubDefinitions => {712            imposterConfig.port = imposter.port;713            imposterConfig.stubs = stubDefinitions;714            return writeFile(headerFile(imposter.port), imposterConfig);715        }).then(() => {716            addReference(imposter);717            return imposter;718        });719    }720    /**721     * Gets the imposter by id722     * @memberOf module:models/filesystemBackedImpostersRepository#723     * @param {Number} id - the id of the imposter (e.g. the port)724     * @returns {Object} - the promise resolving to the imposter725     */726    function get (id) {727        return readFile(headerFile(id), null).then(header => {728            if (header === null) {729                return Q(null);730            }731            return stubsFor(id).toJSON().then(stubs => {732                header.stubs = stubs;733                rehydrate(header);734                return header;735            });736        });737    }738    /**739     * Gets all imposters740     * @memberOf module:models/filesystemBackedImpostersRepository#741     * @returns {Object} - all imposters keyed by port742     */743    function all () {744        return Q.all(Object.keys(imposterFns).map(get));745    }746    /**747     * Returns whether an imposter at the given id exists or not748     * @memberOf module:models/filesystemBackedImpostersRepository#749     * @param {Number} id - the id (e.g. the port)750     * @returns {boolean}751     */752    function exists (id) {753        return Q(Object.keys(imposterFns).indexOf(String(id)) >= 0);754    }755    function shutdown (id) {756        if (typeof imposterFns[String(id)] === 'undefined') {757            return Q();758        }759        const fn = imposterFns[String(id)].stop;760        delete imposterFns[String(id)];761        return fn ? fn() : Q();762    }763    /**764     * Deletes the imposter at the given id765     * @memberOf module:models/filesystemBackedImpostersRepository#766     * @param {Number} id - the id (e.g. the port)767     * @returns {Object} - the deletion promise768     */769    function del (id) {770        return get(id).then(imposter => {771            const promises = [shutdown(id)];772            if (imposter !== null) {773                promises.push(remove(imposterDir(id)));774            }775            return Q.all(promises).then(() => imposter);776        });777    }778    /**779     * Deletes all imposters synchronously; used during shutdown780     * @memberOf module:models/filesystemBackedImpostersRepository#781     */782    function stopAllSync () {783        Object.keys(imposterFns).forEach(shutdown);784    }785    /**786     * Deletes all imposters787     * @memberOf module:models/filesystemBackedImpostersRepository#788     * @returns {Object} - the deletion promise789     */790    function deleteAll () {791        const ids = Object.keys(imposterFns),792            dirs = ids.map(imposterDir),793            promises = ids.map(shutdown).concat(dirs.map(remove));794        // Remove only the directories for imposters we have a reference to795        return Q.all(promises)796            .then(() => readdir(config.datadir))797            .then(entries => {798                if (entries.length === 0) {799                    return remove(config.datadir);800                }801                else {802                    return Q();803                }804            });805    }806    /**807     * Loads all saved imposters at startup808     * @memberOf module:models/filesystemBackedImpostersRepository#809     * @param {Object} protocols - The protocol map, used to instantiate a new instance810     * @returns {Object} - a promise811     */812    function loadAll (protocols) {813        const fs = require('fs-extra');814        if (!config.datadir || !fs.existsSync(config.datadir)) {815            return Q();816        }817        const dirs = fs.readdirSync(config.datadir),818            promises = dirs.map(dir => {819                const imposterFilename = `${config.datadir}/${dir}/imposter.json`;820                if (!fs.existsSync(imposterFilename)) {821                    logger.warn(`Skipping ${dir} during loading; missing imposter.json`);822                    return Q();823                }824                const imposterConfig = JSON.parse(fs.readFileSync(imposterFilename)),825                    protocol = protocols[imposterConfig.protocol];826                if (protocol) {827                    logger.info(`Loading ${imposterConfig.protocol}:${dir} from datadir`);828                    return protocol.createImposterFrom(imposterConfig)829                        .then(imposter => addReference(imposter));830                }831                else {832                    logger.error(`Cannot load imposter ${dir}; no protocol loaded for ${config.protocol}`);833                    return Q();834                }835            });836        return Q.all(promises);837    }838    return {839        add,840        addReference,841        get,842        all,843        exists,844        del,845        stopAllSync,846        deleteAll,847        stubsFor,848        loadAll849    };850}...mountebank-helper.js
Source:mountebank-helper.js  
1const request = require('supertest')2let mountebankServer = null3function setUrl(url) {4    mountebankServer = request(url)5}6function createImposter(imposterConfig) {7    return mountebankServer8        .delete(`/imposters/${imposterConfig.port}`)9        .then(() => {10            return mountebankServer11                .post('/imposters')12                .set('Content-Type', 'application/json')13                .send(JSON.stringify(imposterConfig))14        })15}16function getImposterPort(imposterConfig) {17    return imposterConfig.port18}19function getRequests(port) {20    return mountebankServer.get(`/imposters/${port}`).then(response => {21        return {22            numberOfRequests: response.body.numberOfRequests,23            requests: response.body.requests24        }25    })26}27module.exports = {28    setUrl,29    createImposter,30    getImposterPort,31    getRequests...Using AI Code Generation
1var mb = require('mountebank');2var imposter = mb.create({3        {4                {5                    is: {6                        headers: {7                        },8                    }9                }10        }11});12imposter.then(function (imposter) {13    console.log('imposter created');14});15var mb = require('mountebank');16var imposter = mb.create({17        {18                {19                    is: {20                        headers: {21                        },22                    }23                }24        }25});26imposter.then(function (imposter) {27    console.log('imposter created');28});29var frisby = require('frisby');30var mb = require('mountebank');31frisby.create('test imposter')32    .expectStatus(200)33    .expectHeader('Content-Type', 'text/html')34    .expectBodyContains('Hello World!')35    .afterJSON(function (json) {36        mb.stop(imposter);37    })38    .toss();39  ✓ should be OK (5ms)401 passing (17ms)41var frisby = require('frisby');42var mb = require('mountebank');43frisby.create('test imposter')44    .expectStatus(200)45    .expectHeader('Content-TypeUsing AI Code Generation
1var imposterConfig = require('mountebank').imposterConfig;2var config = imposterConfig({3    stubs: [{4        predicates: [{5            equals: {6            }7        }],8        responses: [{9            is: {10                headers: {11                },12            }13        }]14    }]15});16config.create(function (error) {17    if (error) {18        console.error(error);19    }20    else {21        console.log('Imposter created');22    }23});24var imposterConfig = require('mountebank').imposterConfig;25var config = imposterConfig({26    stubs: [{27        predicates: [{28            equals: {29            }30        }],31        responses: [{32            is: {33                headers: {34                },35            }36        }]37    }]38});39config.create(function (error) {40    if (error) {41        console.error(error);42    }43    else {44        console.log('Imposter created');45    }46});47var config = imposterConfig({48    stubs: [{49        predicates: [{50            equals: {51            }52        }],53        responses: [{54            is: {55                headers: {56                },57            }58        }]59    }]60});61config.create(function (error) {62    if (error) {63        console.error(error);64    }65    else {66        console.log('Imposter created');67    }68});69var config = imposterConfig({70    stubs: [{71        predicates: [{72            equals: {73            }74        }],75        responses: [{76            is: {Using AI Code Generation
1const mb = require('mountebank'),2        {3                {4                    is: {5                        headers: {6                        },7                        body: JSON.stringify({8                        })9                    }10                }11        }12    ];13mb.create(14    imposterConfig(protocol, port, stubs),15    function (error, imposter) {16        console.log(`Created imposter on port ${imposter.port}`);17    }18);19const mb = require('mountebank'),20        {21                {22                    is: {23                        headers: {24                        },25                        body: JSON.stringify({26                        })27                    }28                }29        }30    ];31mb.create(32    imposterConfig(protocol, port, stubs),33    function (error, imposter) {34        console.log(`Created imposter on port ${imposter.port}`);35    }36);37const mb = require('mountebank'),38        {39                {40                    is: {41                        headers: {42                        },43                        body: JSON.stringify({44                        })45                    }46                }47        }48    ];49mb.create(50    imposterConfig(protocol, port, stubs),51    function (error, imposter) {52        console.log(`Created imposter on port ${imposter.port}`);53    }54);55const mb = require('mountebank'),Using AI Code Generation
1var imposterConfig = require('imposterConfig');2var options = {3        {4                { equals: { method: 'GET', path: '/test' } }5                { is: { statusCode: 200, body: 'Hello' } }6        }7};8imposterConfig(options, function (error, response) {9    if (error) {10        console.error(error);11    } else {12        console.log(response);13    }14});15var request = require('request');16module.exports = function (options, callback) {17    request({18    }, callback);19};20var imposterConfig = require('imposterConfig');21var options = {22        {23                { equals: { method: 'GET', path: '/test' } }24                { is: { statusCode: 200, body: 'Hello' } }25        }26};27imposterConfig(options, function (error, response) {28    if (error) {29        console.error(error);30    } else {31        console.log(response);32    }33});34var request = require('request');35module.exports = function (options, callback) {36    request({37    }, callback);38};39var imposterConfig = require('imposterConfig');40var options = {41        {42                { equals: { method: 'GET', path: '/test' } }43                { is: { statusCode: 200, body: 'Hello' } }Using AI Code Generation
1var mb = require('mountebank');2var fs = require('fs');3var imposterConfig = {4    stubs: [{5        responses: [{6            is: {7            }8        }]9    }]10};11mb.create(imposterConfig)12    .then(function (imposter) {13        console.log(imposter.port);14        setTimeout(function () {15            imposter.stop();16        }, 10000);17    })18    .catch(function (error) {19        console.error(error);20    });21The imposter object returned by mb.create() has a few useful methods:22imposter.stop() - stops the imposter23imposter.get() - returns a promise for the current state of the imposter24imposter.addStub() - adds a new stub to the imposter25imposter.deleteStub() - deletes a stub from the imposter26imposter.addProxy() - adds a new proxy to the imposter27imposter.deleteProxy() - deletes a proxy from the imposter28imposter.addPredicate() - adds a new predicate to the imposter29imposter.deletePredicate() - deletes a predicate from the imposter30imposter.addResponse() - adds a new response to the imposter31imposter.deleteResponse() - deletes a response from the imposter32imposter.addBehavior() - adds a new behavior to the imposter33imposter.deleteBehavior() - deletes a behavior from the imposter34imposter.addDecorator() - adds a new decorator to the imposter35imposter.deleteDecorator() - deletes a decorator from the imposter36imposter.addRequest() - adds a new request to the imposter37imposter.deleteRequest() - deletes a request from the imposter38imposter.addResponse() - adds a new response to the imposter39imposter.deleteResponse() - deletes a response from the imposter40imposter.addResponse() - adds a new response to the imposterUsing AI Code Generation
1var http = require('http');2var fs = require('fs');3var path = require('path');4var imposterConfig = require('mountebank').imposterConfig;5var imposter = imposterConfig({6  stubs: [{7    responses: [{8      is: {9      }10    }]11  }]12});13imposter.create(function (error, result) {14  if (error) {15    console.error(error);16    process.exit(1);17  }18  console.log('Imposter created, with port ' + result.port);19  console.log('To stop the imposter, run "mb stop --port ' + result.port + '"');20  console.log('To view the imposter, run "mb view --port ' + result.port + '"');21  console.log('To delete the imposter, run "mb delete --port ' + result.port + '"');22  console.log('To test the imposter, run "node test.js"');23    var body = '';24    response.on('data', function (data) {25      body += data;26    });27    response.on('end', function () {28      console.log('Imposter responded with: ' + body);29    });30  });31});Using AI Code Generation
1<% if (name) { %>2<% } else { %>3<% } %>4{5}6module.exports = {7};8export const port = 3000;9export const protocol = 'http';10export const name = 'Test';Using AI Code Generation
1const mb = require('mountebank');2const config = require('./config.json');3const port = 4545;4mb.create({port: port, pidfile: 'mb.pid', logfile: 'mb.log'})5    .then(() => mb.imposterConfig({port: port, protocol: 'http', config: config}))6    .then(() => mb.imposterConfig({port: port, protocol: 'http', delete: true}))7    .then(() => mb.stop({pidfile: 'mb.pid'}))8    .catch((error) => {9        console.error(error);10        mb.stop({pidfile: 'mb.pid'});11    });12{13        {14                {15                    "is": {16                        "headers": {17                        },18                    }19                }20        }21}Using AI Code Generation
1const mb = require('mountebank');2mb.create({ port: 2525, protocol: 'http' }).then(function (imposter) {3    const stub = {4            {5                is: {6                    headers: {7                    },8                    body: JSON.stringify({ message: 'Hello World!' })9                }10            }11    };12    imposter.addStub(stub).then(function () {13        return imposter.get('/', { json: true });14    }).then(function (response) {15        console.log(response.body);16    }).finally(function () {17        imposter.stop();18    });19});20const mb = require('mountebank');21mb.create({ port: 2525, protocol: 'http' }).then(function (imposter) {22    const stub = {23            {24                is: {25                    headers: {26                    },27                    body: JSON.stringify({ message: 'Hello World!' })28                }29            }30    };31    imposter.addStub(stub).then(function () {32        return imposter.get('/', { json: true });33    }).then(function (response) {34        console.log(response.body);35    }).finally(function () {36        imposter.stop();37    });38});Learn to execute automation testing from scratch with LambdaTest Learning Hub. Right from setting up the prerequisites to run your first automation test, to following best practices and diving deeper into advanced test scenarios. LambdaTest Learning Hubs compile a list of step-by-step guides to help you be proficient with different test automation frameworks i.e. Selenium, Cypress, TestNG etc.
You could also refer to video tutorials over LambdaTest YouTube channel to get step by step demonstration from industry experts.
Get 100 minutes of automation test minutes FREE!!
