How to use nonProxyStubs method in mountebank

Best JavaScript code snippet using mountebank

filesystemBackedImpostersRepository.js

Source:filesystemBackedImpostersRepository.js Github

copy

Full Screen

1'use strict';2const fsExtra = require('fs-extra'),3 prometheus = require('prom-client'),4 properLockFile = require('proper-lockfile'),5 pathModule = require('path'),6 helpers = require('../util/helpers.js'),7 errors = require('../util/errors.js');8/**9 * An abstraction for loading imposters from the filesystem10 * The root of the database is provided on the command line as --datadir11 * The file layout looks like this:12 *13 * /{datadir}14 * /300015 * /imposter.json16 * {17 * "protocol": "http",18 * "port": 3000,19 * "stubs: [{20 * "predicates": [{ "equals": { "path": "/" } }],21 * "meta": {22 * "dir": "stubs/{epoch-pid-counter}"23 * }24 * }]25 * }26 *27 * /stubs28 * /{epoch-pid-counter}29 * /meta.json30 * {31 * "responseFiles": ["responses/{epoch-pid-counter}.json"],32 * // An array of indexes into responseFiles which handle repeat behavior33 * "orderWithRepeats": [0],34 * // The next index into orderWithRepeats; incremented with each call to nextResponse()35 * "nextIndex": 036 * }37 *38 * /responses39 * /{epoch-pid-counter}.json40 * {41 * "is": { "body": "Hello, world!" }42 * }43 *44 * /matches45 * /{epoch-pid-counter}.json46 * { ... }47 *48 * /requests49 * /{epoch-pid-counter}.json50 * { ... }51 *52 * This structure is designed to minimize the amount of file locking and to maximize parallelism and throughput.53 *54 * The imposters.json file needs to be locked during imposter-level activities (e.g. adding a stub).55 * Readers do not lock; they just get the data at the time they read it. Since this file doesn't56 * contain transient state, there's no harm from a stale read. Writes (which happen for57 * stub changing operations, either for proxy response recording or stub API calls) grab a file lock58 * for both the read and the write. Writes to this file should be infrequent, except perhaps during59 * proxy recording. Newly added stubs may change the index of existing stubs in the stubs array, but60 * will never change the stub meta.dir, so it is always safe to hang on to it for subsequent operations.61 *62 * The stub meta.json needs to be locked to add responses or trigger the next response, but is63 * separated from the imposter.json so we can have responses from multiple stubs in parallel with no64 * lock conflict. Again, readers (e.g. to generate imposter JSON) do not need a lock because the responseFiles65 * array is mostly read-only, and even when it's not (adding responses during proxyAlways recording), there's66 * no harm from a stale read. Writers (usually generating the next response for a stub) grab a lock for the67 * read and the write. This should be the most common write across files, which is why the meta.json file68 * is small.69 *70 * In both cases where a file needs to be locked, an exponential backoff retry strategy is used. Inconsistent71 * reads of partially written files (which can happen by default with the system calls - fs.writeFile is not72 * atomic) are avoided by writing first to a temp file (during which time reads can happen to the original file)73 * and then renaming to the original file.74 *75 * New directories and filenames use a timestamp-based filename to allow creating them without synchronizing76 * with a read. Since multiple files (esp. requests) can be added during the same millisecond, a pid and counter77 * is tacked on to the filename to improve uniqueness. It doesn't provide the * ironclad guarantee a GUID78 * does -- two different processes on two different machines could in theory have the same pid and create files79 * during the same timestamp with the same counter, but the odds of that happening * are so small that it's not80 * worth giving up the easy time-based sortability based on filenames alone.81 *82 * Keeping all imposter information under a directory (instead of having metadata outside the directory)83 * allows us to remove the imposter by simply removing the directory.84 *85 * There are some extra checks on filesystem operations (atomicWriteFile) due to antivirus software, solar flares,86 * gremlins, etc. graceful-fs solves some of these, but apparently not all.87 *88 * @module89 */90const91 metrics = {92 lockAcquireDuration: new prometheus.Histogram({93 name: 'mb_lock_acquire_duration_seconds',94 help: 'Time it takes to acquire a file lock',95 buckets: [0.1, 0.2, 0.5, 1, 3, 5, 10, 30],96 labelNames: ['caller']97 }),98 lockHoldDuration: new prometheus.Histogram({99 name: 'mb_lock_hold_duration_seconds',100 help: 'Time a file lock is held',101 buckets: [0.1, 0.2, 0.5, 1, 2],102 labelNames: ['caller']103 }),104 lockErrors: new prometheus.Counter({105 name: 'mb_lock_errors_total',106 help: 'Number of lock errors',107 labelNames: ['caller', 'code']108 })109 };110/**111 * Creates the repository112 * @param {Object} config - The database configuration113 * @param {String} config.datadir - The database directory114 * @param {Object} logger - The logger115 * @returns {Object}116 */117function create (config, logger) {118 const imposterFns = {};119 let counter = 0,120 locks = 0;121 async function ensureDir (filepath) {122 const dir = pathModule.dirname(filepath);123 await fsExtra.ensureDir(dir);124 }125 async function writeFile (filepath, obj) {126 await ensureDir(filepath);127 await fsExtra.writeFile(filepath, JSON.stringify(obj, null, 2));128 }129 function tryParse (maybeJSON, filepath) {130 try {131 return JSON.parse(maybeJSON);132 }133 catch (parseErr) {134 logger.error(`Corrupted database: invalid JSON for ${filepath}`);135 throw errors.DatabaseError(`invalid JSON in ${filepath}`, { details: parseErr.message });136 }137 }138 async function readFile (filepath, defaultContents) {139 try {140 const data = await fsExtra.readFile(filepath, 'utf8');141 return tryParse(data, filepath);142 }143 catch (err) {144 if (err.code === 'ENOENT') {145 if (typeof defaultContents === 'undefined') {146 logger.error(`Corrupted database: missing file ${filepath}`);147 throw errors.DatabaseError('file not found', { details: err.message });148 }149 else {150 return defaultContents;151 }152 }153 else {154 throw err;155 }156 }157 }158 function delay (duration) {159 return new Promise(resolve => {160 setTimeout(resolve, duration);161 });162 }163 async function atomicWriteFile (filepath, obj, attempts = 1) {164 const tmpfile = filepath + '.tmp';165 try {166 await writeFile(tmpfile, obj);167 await fsExtra.rename(tmpfile, filepath);168 }169 catch (err) {170 if (err.code === 'ENOENT' && attempts < 15) {171 logger.debug(`Attempt ${attempts} failed with ENOENT error on atomic write of ${filepath}. Retrying...`);172 await delay(10);173 await atomicWriteFile(filepath, obj, attempts + 1);174 }175 else {176 throw err;177 }178 }179 }180 async function lockFile (filepath) {181 const options = {182 realpath: false,183 retries: {184 retries: 20,185 minTimeout: 10,186 maxTimeout: 5000,187 randomize: true,188 factor: 1.5189 },190 stale: 30000191 };192 // with realpath = false, the file doesn't have to exist, but the directory does193 await ensureDir(filepath);194 return properLockFile.lock(filepath, options);195 }196 async function readAndWriteFile (filepath, caller, transformer, defaultContents) {197 const currentLockId = locks,198 observeLockAcquireDuration = metrics.lockAcquireDuration.startTimer({ caller });199 locks += 1;200 try {201 const release = await lockFile(filepath),202 acquireLockSeconds = observeLockAcquireDuration(),203 observeLockHoldDuration = metrics.lockHoldDuration.startTimer({ caller });204 logger.debug(`Acquired file lock on ${filepath} for ${caller}-${currentLockId} after ${acquireLockSeconds}s`);205 const original = await readFile(filepath, defaultContents),206 transformed = await transformer(original);207 await atomicWriteFile(filepath, transformed);208 await release();209 const lockHeld = observeLockHoldDuration();210 logger.debug(`Released file lock on ${filepath} for ${caller}-${currentLockId} after ${lockHeld}s`);211 }212 catch (err) {213 // Ignore lock already released errors214 if (err.code !== 'ERELEASED') {215 metrics.lockErrors.inc({ caller, code: err.code });216 properLockFile.unlock(filepath, { realpath: false }).catch(() => { /* ignore */ });217 throw err;218 }219 }220 }221 async function remove (path) {222 await fsExtra.remove(path);223 }224 function filenameFor (timestamp) {225 const epoch = timestamp.valueOf();226 counter += 1;227 return `${epoch}-${process.pid}-${counter}`;228 }229 function partsFrom (filename) {230 // format {epoch}-{pid}-{counter}231 const pattern = /^(\d+)-(\d+)-(\d+)\.json$/,232 parts = pattern.exec(filename);233 return {234 epoch: Number(parts[1]),235 pid: Number(parts[2]),236 counter: Number(parts[3])237 };238 }239 function timeSorter (first, second) {240 // format {epoch}-{pid}-{counter}241 // sort by epoch first, then pid, then counter to guarantee determinism for242 // files added during the same millisecond.243 const firstParts = partsFrom(first),244 secondParts = partsFrom(second);245 let result = firstParts.epoch - secondParts.epoch;246 if (result === 0) {247 result = firstParts.pid - secondParts.pid;248 }249 if (result === 0) {250 result = firstParts.counter - secondParts.counter;251 }252 return result;253 }254 function readdir (path) {255 return new Promise((resolve, reject) => {256 fsExtra.readdir(path, (err, files) => {257 if (err && err.code === 'ENOENT') {258 // Nothing saved yet259 resolve([]);260 }261 else if (err) {262 reject(err);263 }264 else {265 resolve(files);266 }267 });268 });269 }270 async function loadAllInDir (path) {271 const files = await readdir(path),272 reads = files273 .filter(file => file.indexOf('.json') > 0)274 .sort(timeSorter)275 .map(file => readFile(`${path}/${file}`));276 return Promise.all(reads);277 }278 function repeatsFor (response) {279 return response.repeat || 1;280 }281 async function saveStubMetaAndResponses (stub, baseDir) {282 const stubDefinition = {283 meta: { dir: `stubs/${filenameFor(new Date())}` }284 },285 meta = {286 responseFiles: [],287 orderWithRepeats: [],288 nextIndex: 0289 },290 responses = stub.responses || [],291 writes = [];292 if (stub.predicates) {293 stubDefinition.predicates = stub.predicates;294 }295 for (let i = 0; i < responses.length; i += 1) {296 const responseFile = `responses/${filenameFor(new Date())}.json`;297 meta.responseFiles.push(responseFile);298 for (let repeats = 0; repeats < repeatsFor(responses[i]); repeats += 1) {299 meta.orderWithRepeats.push(i);300 }301 writes.push(writeFile(`${baseDir}/${stubDefinition.meta.dir}/${responseFile}`, responses[i]));302 }303 writes.push(writeFile(`${baseDir}/${stubDefinition.meta.dir}/meta.json`, meta));304 await Promise.all(writes);305 return stubDefinition;306 }307 function stubRepository (baseDir) {308 const imposterFile = `${baseDir}/imposter.json`;309 function metaPath (stubDir) {310 return `${baseDir}/${stubDir}/meta.json`;311 }312 function responsePath (stubDir, responseFile) {313 return `${baseDir}/${stubDir}/${responseFile}`;314 }315 function requestPath (request) {316 return `${baseDir}/requests/${filenameFor(Date.parse(request.timestamp))}.json`;317 }318 function matchPath (stubDir, match) {319 return `${baseDir}/${stubDir}/matches/${filenameFor(Date.parse(match.timestamp))}.json`;320 }321 function readHeader () {322 return readFile(imposterFile, { stubs: [] });323 }324 function readAndWriteHeader (caller, transformer) {325 return readAndWriteFile(imposterFile, caller, transformer, { stubs: [] });326 }327 function wrap (stub) {328 const cloned = helpers.clone(stub || {}),329 stubDir = stub ? stub.meta.dir : '';330 if (typeof stub === 'undefined') {331 return {332 addResponse: () => Promise.resolve(),333 nextResponse: () => Promise.resolve({334 is: {},335 stubIndex: () => Promise.resolve(0)336 }),337 recordMatch: () => Promise.resolve()338 };339 }340 delete cloned.meta;341 /**342 * Adds a response to the stub343 * @memberOf module:models/filesystemBackedImpostersRepository#344 * @param {Object} response - the new response345 * @returns {Object} - the promise346 */347 cloned.addResponse = async response => {348 let responseFile;349 await readAndWriteFile(metaPath(stubDir), 'addResponse', async meta => {350 const responseIndex = meta.responseFiles.length;351 responseFile = `responses/${filenameFor(new Date())}.json`;352 meta.responseFiles.push(responseFile);353 for (let repeats = 0; repeats < repeatsFor(response); repeats += 1) {354 meta.orderWithRepeats.push(responseIndex);355 }356 await writeFile(responsePath(stubDir, responseFile), response);357 return meta;358 });359 };360 async function stubIndex () {361 const header = await readHeader();362 for (let i = 0; i < header.stubs.length; i += 1) {363 if (header.stubs[i].meta.dir === stubDir) {364 return i;365 }366 }367 return 0;368 }369 function createResponse (responseConfig) {370 const result = helpers.clone(responseConfig || { is: {} });371 result.stubIndex = stubIndex;372 return result;373 }374 /**375 * Returns the next response for the stub, taking into consideration repeat behavior and cycling back the beginning376 * @memberOf module:models/filesystemBackedImpostersRepository#377 * @returns {Object} - the promise378 */379 cloned.nextResponse = async () => {380 let responseFile;381 await readAndWriteFile(metaPath(stubDir), 'nextResponse', async meta => {382 const maxIndex = meta.orderWithRepeats.length,383 responseIndex = meta.orderWithRepeats[meta.nextIndex % maxIndex];384 responseFile = meta.responseFiles[responseIndex];385 meta.nextIndex = (meta.nextIndex + 1) % maxIndex;386 return meta;387 });388 // No need to read the response file while the lock is held389 const responseConfig = await readFile(responsePath(stubDir, responseFile));390 return createResponse(responseConfig);391 };392 /**393 * Records a match for debugging purposes394 * @memberOf module:models/filesystemBackedImpostersRepository#395 * @param {Object} request - the request396 * @param {Object} response - the response397 * @param {Object} responseConfig - the config that generated the response398 * @param {Number} processingTime - the time to match the predicate and generate the full response399 * @returns {Object} - the promise400 */401 cloned.recordMatch = async (request, response, responseConfig, processingTime) => {402 const match = {403 timestamp: new Date().toJSON(),404 request,405 response,406 responseConfig,407 processingTime408 };409 await writeFile(matchPath(stubDir, match), match);410 };411 return cloned;412 }413 /**414 * Returns the number of stubs for the imposter415 * @memberOf module:models/filesystemBackedImpostersRepository#416 * @returns {Object} - the promise417 */418 async function count () {419 const imposter = await readHeader();420 return imposter.stubs.length;421 }422 /**423 * Returns the first stub whose predicates matches the filter424 * @memberOf module:models/filesystemBackedImpostersRepository#425 * @param {Function} filter - the filter function426 * @param {Number} startIndex - the index to to start searching427 * @returns {Object} - the promise428 */429 async function first (filter, startIndex = 0) {430 const header = await readHeader();431 for (let i = startIndex; i < header.stubs.length; i += 1) {432 if (filter(header.stubs[i].predicates || [])) {433 return { success: true, stub: wrap(header.stubs[i]) };434 }435 }436 return { success: false, stub: wrap() };437 }438 /**439 * Adds a new stub to imposter440 * @memberOf module:models/filesystemBackedImpostersRepository#441 * @param {Object} stub - the stub to add442 * @returns {Object} - the promise443 */444 async function add (stub) { // eslint-disable-line no-shadow445 const stubDefinition = await saveStubMetaAndResponses(stub, baseDir);446 await readAndWriteHeader('addStub', async header => {447 header.stubs.push(stubDefinition);448 return header;449 });450 }451 /**452 * Inserts a new stub at the given index453 * @memberOf module:models/filesystemBackedImpostersRepository#454 * @param {Object} stub - the stub to add455 * @param {Number} index - the index to insert the new stub at456 * @returns {Object} - the promise457 */458 async function insertAtIndex (stub, index) {459 const stubDefinition = await saveStubMetaAndResponses(stub, baseDir);460 await readAndWriteHeader('insertStubAtIndex', async header => {461 header.stubs.splice(index, 0, stubDefinition);462 return header;463 });464 }465 /**466 * Deletes the stub at the given index467 * @memberOf module:models/filesystemBackedImpostersRepository#468 * @param {Number} index - the index of the stub to delete469 * @returns {Object} - the promise470 */471 async function deleteAtIndex (index) {472 let stubDir;473 await readAndWriteHeader('deleteStubAtIndex', async header => {474 if (typeof header.stubs[index] === 'undefined') {475 throw errors.MissingResourceError(`no stub at index ${index}`);476 }477 stubDir = header.stubs[index].meta.dir;478 header.stubs.splice(index, 1);479 return header;480 });481 await remove(`${baseDir}/${stubDir}`);482 }483 /**484 * Overwrites all stubs with a new list485 * @memberOf module:models/filesystemBackedImpostersRepository#486 * @param {Object} newStubs - the new list of stubs487 * @returns {Object} - the promise488 */489 async function overwriteAll (newStubs) {490 await readAndWriteHeader('overwriteAllStubs', async header => {491 header.stubs = [];492 await remove(`${baseDir}/stubs`);493 return header;494 });495 let addSequence = Promise.resolve();496 newStubs.forEach(stub => {497 addSequence = addSequence.then(() => add(stub));498 });499 await addSequence;500 }501 /**502 * Overwrites the stub at the given index503 * @memberOf module:models/filesystemBackedImpostersRepository#504 * @param {Object} stub - the new stub505 * @param {Number} index - the index of the stub to overwrite506 * @returns {Object} - the promise507 */508 async function overwriteAtIndex (stub, index) {509 await deleteAtIndex(index);510 await insertAtIndex(stub, index);511 }512 async function loadResponses (stub) {513 const meta = await readFile(metaPath(stub.meta.dir));514 return Promise.all(meta.responseFiles.map(responseFile =>515 readFile(responsePath(stub.meta.dir, responseFile))));516 }517 async function loadMatches (stub) {518 return loadAllInDir(`${baseDir}/${stub.meta.dir}/matches`);519 }520 /**521 * Returns a JSON-convertible representation522 * @memberOf module:models/filesystemBackedImpostersRepository#523 * @param {Object} options - The formatting options524 * @param {Boolean} options.debug - If true, includes debug information525 * @returns {Object} - the promise resolving to the JSON object526 */527 async function toJSON (options = {}) {528 const header = await readHeader(),529 responsePromises = header.stubs.map(loadResponses),530 stubResponses = await Promise.all(responsePromises),531 debugPromises = options.debug ? header.stubs.map(loadMatches) : [],532 matches = await Promise.all(debugPromises);533 header.stubs.forEach((stub, index) => {534 stub.responses = stubResponses[index];535 if (options.debug && matches[index].length > 0) {536 stub.matches = matches[index];537 }538 delete stub.meta;539 });540 return header.stubs;541 }542 function isRecordedResponse (response) {543 return response.is && typeof response.is._proxyResponseTime === 'number';544 }545 /**546 * Removes the saved proxy responses547 * @memberOf module:models/filesystemBackedImpostersRepository#548 * @returns {Object} - Promise549 */550 async function deleteSavedProxyResponses () {551 const allStubs = await toJSON();552 allStubs.forEach(stub => {553 stub.responses = stub.responses.filter(response => !isRecordedResponse(response));554 });555 const nonProxyStubs = allStubs.filter(stub => stub.responses.length > 0);556 return overwriteAll(nonProxyStubs);557 }558 /**559 * Adds a request for the imposter560 * @memberOf module:models/filesystemBackedImpostersRepository#561 * @param {Object} request - the request562 * @returns {Object} - the promise563 */564 async function addRequest (request) {565 const recordedRequest = helpers.clone(request);566 recordedRequest.timestamp = new Date().toJSON();567 await writeFile(requestPath(recordedRequest), recordedRequest);568 }569 /**570 * Returns the saved requests for the imposter571 * @memberOf module:models/filesystemBackedImpostersRepository#572 * @returns {Object} - the promise resolving to the array of requests573 */574 async function loadRequests () {575 return loadAllInDir(`${baseDir}/requests`);576 }577 /**578 * Deletes the requests directory for an imposter579 * @memberOf module:models/filesystemBackedImpostersRepository#580 * @returns {Object} - Promise581 */582 async function deleteSavedRequests () {583 await remove(`${baseDir}/requests`);584 }585 return {586 count,587 first,588 add,589 insertAtIndex,590 overwriteAll,591 overwriteAtIndex,592 deleteAtIndex,593 toJSON,594 deleteSavedProxyResponses,595 addRequest,596 loadRequests,597 deleteSavedRequests598 };599 }600 function imposterDir (id) {601 return `${config.datadir}/${id}`;602 }603 function headerFile (id) {604 return `${imposterDir(id)}/imposter.json`;605 }606 /**607 * Returns the stubs repository for the imposter608 * @memberOf module:models/filesystemBackedImpostersRepository#609 * @param {Number} id - the id of the imposter610 * @returns {Object} - the stubs repository611 */612 function stubsFor (id) {613 return stubRepository(imposterDir(id));614 }615 /**616 * Saves a reference to the imposter so that the functions617 * (which can't be persisted) can be rehydrated to a loaded imposter.618 * This means that any data in the function closures will be held in619 * memory.620 * @memberOf module:models/filesystemBackedImpostersRepository#621 * @param {Object} imposter - the imposter622 */623 function addReference (imposter) {624 const id = String(imposter.port);625 imposterFns[id] = {};626 Object.keys(imposter).forEach(key => {627 if (typeof imposter[key] === 'function') {628 imposterFns[id][key] = imposter[key];629 }630 });631 }632 function rehydrate (imposter) {633 const id = String(imposter.port);634 Object.keys(imposterFns[id]).forEach(key => {635 imposter[key] = imposterFns[id][key];636 });637 }638 /**639 * Adds a new imposter640 * @memberOf module:models/filesystemBackedImpostersRepository#641 * @param {Object} imposter - the imposter to add642 * @returns {Object} - the promise643 */644 async function add (imposter) {645 const imposterConfig = imposter.creationRequest,646 stubs = imposterConfig.stubs || [],647 saveStubs = stubs.map(stub => saveStubMetaAndResponses(stub, imposterDir(imposter.port))),648 stubDefinitions = await Promise.all(saveStubs);649 delete imposterConfig.requests;650 imposterConfig.port = imposter.port;651 imposterConfig.stubs = stubDefinitions;652 await writeFile(headerFile(imposter.port), imposterConfig);653 addReference(imposter);654 return imposter;655 }656 /**657 * Gets the imposter by id658 * @memberOf module:models/filesystemBackedImpostersRepository#659 * @param {Number} id - the id of the imposter (e.g. the port)660 * @returns {Object} - the promise resolving to the imposter661 */662 async function get (id) {663 const header = await readFile(headerFile(id), null);664 if (header === null) {665 return null;666 }667 header.stubs = await stubsFor(id).toJSON();668 rehydrate(header);669 return header;670 }671 /**672 * Gets all imposters673 * @memberOf module:models/filesystemBackedImpostersRepository#674 * @returns {Object} - all imposters keyed by port675 */676 async function all () {677 return Promise.all(Object.keys(imposterFns).map(get));678 }679 /**680 * Returns whether an imposter at the given id exists or not681 * @memberOf module:models/filesystemBackedImpostersRepository#682 * @param {Number} id - the id (e.g. the port)683 * @returns {boolean}684 */685 async function exists (id) {686 return Object.keys(imposterFns).indexOf(String(id)) >= 0;687 }688 async function shutdown (id) {689 if (typeof imposterFns[String(id)] === 'undefined') {690 return;691 }692 const stop = imposterFns[String(id)].stop;693 delete imposterFns[String(id)];694 if (stop) {695 await stop();696 }697 }698 /**699 * Deletes the imposter at the given id700 * @memberOf module:models/filesystemBackedImpostersRepository#701 * @param {Number} id - the id (e.g. the port)702 * @returns {Object} - the deletion promise703 */704 async function del (id) {705 const imposter = await get(id),706 cleanup = [shutdown(id)];707 if (imposter !== null) {708 cleanup.push(remove(imposterDir(id)));709 }710 await Promise.all(cleanup);711 return imposter;712 }713 /**714 * Deletes all imposters; used during testing715 * @memberOf module:models/filesystemBackedImpostersRepository#716 */717 async function stopAll () {718 const promises = Object.keys(imposterFns).map(shutdown);719 await Promise.all(promises);720 }721 /**722 * Deletes all imposters synchronously; used during shutdown723 * @memberOf module:models/filesystemBackedImpostersRepository#724 */725 function stopAllSync () {726 Object.keys(imposterFns).forEach(shutdown);727 }728 /**729 * Deletes all imposters730 * @memberOf module:models/filesystemBackedImpostersRepository#731 * @returns {Object} - the deletion promise732 */733 async function deleteAll () {734 const ids = Object.keys(imposterFns),735 dirs = ids.map(imposterDir),736 deleteImposter = ids.map(shutdown).concat(dirs.map(remove));737 // Remove only the directories for imposters we have a reference to738 await Promise.all(deleteImposter);739 const entries = await readdir(config.datadir);740 if (entries.length === 0) {741 await remove(config.datadir);742 }743 }744 async function loadImposterFrom (dir, protocols) {745 const imposterFilename = `${config.datadir}/${dir}/imposter.json`;746 if (!fsExtra.existsSync(imposterFilename)) {747 logger.warn(`Skipping ${dir} during loading; missing imposter.json`);748 return;749 }750 const imposterConfig = JSON.parse(fsExtra.readFileSync(imposterFilename)),751 protocol = protocols[imposterConfig.protocol];752 if (protocol) {753 logger.info(`Loading ${imposterConfig.protocol}:${dir} from datadir`);754 const imposter = await protocol.createImposterFrom(imposterConfig);755 addReference(imposter);756 }757 else {758 logger.error(`Cannot load imposter ${dir}; no protocol loaded for ${config.protocol}`);759 }760 }761 /**762 * Loads all saved imposters at startup763 * @memberOf module:models/filesystemBackedImpostersRepository#764 * @param {Object} protocols - The protocol map, used to instantiate a new instance765 * @returns {Object} - a promise766 */767 async function loadAll (protocols) {768 if (!config.datadir || !fsExtra.existsSync(config.datadir)) {769 return;770 }771 const dirs = fsExtra.readdirSync(config.datadir),772 promises = dirs.map(async dir => loadImposterFrom(dir, protocols));773 await Promise.all(promises);774 }775 return {776 add,777 addReference,778 get,779 all,780 exists,781 del,782 stopAll,783 stopAllSync,784 deleteAll,785 stubsFor,786 loadAll787 };788}...

Full Screen

Full Screen

inMemoryImpostersRepository.js

Source:inMemoryImpostersRepository.js Github

copy

Full Screen

1'use strict';2const helpers = require('../util/helpers.js'),3 errors = require('../util/errors.js');4/**5 * An abstraction for loading imposters from in-memory6 * @module7 */8function repeatsFor (response) {9 return response.repeat || 1;10}11function repeatTransform (responses) {12 const result = [];13 let response, repeats;14 for (let i = 0; i < responses.length; i += 1) {15 response = responses[i];16 repeats = repeatsFor(response);17 for (let j = 0; j < repeats; j += 1) {18 result.push(response);19 }20 }21 return result;22}23function createResponse (responseConfig, stubIndexFn) {24 const cloned = helpers.clone(responseConfig || { is: {} });25 cloned.stubIndex = stubIndexFn ? stubIndexFn : () => Promise.resolve(0);26 return cloned;27}28function wrap (stub = {}) {29 const cloned = helpers.clone(stub),30 statefulResponses = repeatTransform(cloned.responses || []);31 /**32 * Adds a new response to the stub (e.g. during proxying)33 * @memberOf module:models/inMemoryImpostersRepository#34 * @param {Object} response - the response to add35 * @returns {Object} - the promise36 */37 cloned.addResponse = async response => {38 cloned.responses = cloned.responses || [];39 cloned.responses.push(response);40 statefulResponses.push(response);41 return response;42 };43 /**44 * Selects the next response from the stub, including repeat behavior and circling back to the beginning45 * @memberOf module:models/inMemoryImpostersRepository#46 * @returns {Object} - the response47 * @returns {Object} - the promise48 */49 cloned.nextResponse = async () => {50 const responseConfig = statefulResponses.shift();51 if (responseConfig) {52 statefulResponses.push(responseConfig);53 return createResponse(responseConfig, cloned.stubIndex);54 }55 else {56 return createResponse();57 }58 };59 /**60 * Records a match for debugging purposes61 * @memberOf module:models/inMemoryImpostersRepository#62 * @param {Object} request - the request63 * @param {Object} response - the response64 * @param {Object} responseConfig - the config that generated the response65 * @param {Number} processingTime - the time to match the predicate and generate the full response66 * @returns {Object} - the promise67 */68 cloned.recordMatch = async (request, response, responseConfig, processingTime) => {69 cloned.matches = cloned.matches || [];70 cloned.matches.push({71 timestamp: new Date().toJSON(),72 request,73 response,74 responseConfig,75 processingTime76 });77 };78 return cloned;79}80/**81 * Creates the stubs repository for a single imposter82 * @returns {Object}83 */84function createStubsRepository () {85 const stubs = [];86 let requests = [];87 function reindex () {88 // stubIndex() is used to find the right spot to insert recorded89 // proxy responses. We reindex after every state change90 stubs.forEach((stub, index) => {91 stub.stubIndex = async () => index;92 });93 }94 /**95 * Returns the first stub whose predicates match the filter, or a default one if none match96 * @memberOf module:models/inMemoryImpostersRepository#97 * @param {Function} filter - the filter function98 * @param {Number} startIndex - the index to to start searching99 * @returns {Object}100 */101 async function first (filter, startIndex = 0) {102 for (let i = startIndex; i < stubs.length; i += 1) {103 if (filter(stubs[i].predicates || [])) {104 return { success: true, stub: stubs[i] };105 }106 }107 return { success: false, stub: wrap() };108 }109 /**110 * Adds a new stub111 * @memberOf module:models/inMemoryImpostersRepository#112 * @param {Object} stub - the stub to add113 * @returns {Object} - the promise114 */115 async function add (stub) {116 stubs.push(wrap(stub));117 reindex();118 }119 /**120 * Inserts a new stub at the given index121 * @memberOf module:models/inMemoryImpostersRepository#122 * @param {Object} stub - the stub to insert123 * @param {Number} index - the index to add the stub at124 * @returns {Object} - the promise125 */126 async function insertAtIndex (stub, index) {127 stubs.splice(index, 0, wrap(stub));128 reindex();129 }130 /**131 * Overwrites the list of stubs with a new list132 * @memberOf module:models/inMemoryImpostersRepository#133 * @param {Object} newStubs - the new list of stubs134 * @returns {Object} - the promise135 */136 async function overwriteAll (newStubs) {137 while (stubs.length > 0) {138 stubs.pop();139 }140 newStubs.forEach(stub => add(stub));141 reindex();142 }143 /**144 * Overwrites the stub at the given index with the new stub145 * @memberOf module:models/inMemoryImpostersRepository#146 * @param {Object} newStub - the new stub147 * @param {Number} index - the index of the old stuib148 * @returns {Object} - the promise149 */150 async function overwriteAtIndex (newStub, index) {151 if (typeof stubs[index] === 'undefined') {152 throw errors.MissingResourceError(`no stub at index ${index}`);153 }154 stubs[index] = wrap(newStub);155 reindex();156 }157 /**158 * Deletes the stub at the given index159 * @memberOf module:models/inMemoryImpostersRepository#160 * @param {Number} index - the index of the stub to delete161 * @returns {Object} - the promise162 */163 async function deleteAtIndex (index) {164 if (typeof stubs[index] === 'undefined') {165 throw errors.MissingResourceError(`no stub at index ${index}`);166 }167 stubs.splice(index, 1);168 reindex();169 }170 /**171 * Returns a JSON-convertible representation172 * @memberOf module:models/inMemoryImpostersRepository#173 * @param {Object} options - The formatting options174 * @param {Boolean} options.debug - If true, includes debug information175 * @returns {Object} - the promise resolving to the JSON object176 */177 async function toJSON (options = {}) {178 const cloned = helpers.clone(stubs);179 cloned.forEach(stub => {180 if (!options.debug) {181 delete stub.matches;182 }183 });184 return cloned;185 }186 function isRecordedResponse (response) {187 return response.is && typeof response.is._proxyResponseTime === 'number';188 }189 /**190 * Removes the saved proxy responses191 * @memberOf module:models/inMemoryImpostersRepository#192 * @returns {Object} - Promise193 */194 async function deleteSavedProxyResponses () {195 const allStubs = await toJSON();196 allStubs.forEach(stub => {197 stub.responses = stub.responses.filter(response => !isRecordedResponse(response));198 });199 const nonProxyStubs = allStubs.filter(stub => stub.responses.length > 0);200 await overwriteAll(nonProxyStubs);201 }202 /**203 * Adds a request for the imposter204 * @memberOf module:models/inMemoryImpostersRepository#205 * @param {Object} request - the request206 * @returns {Object} - the promise207 */208 async function addRequest (request) {209 const recordedRequest = helpers.clone(request);210 recordedRequest.timestamp = new Date().toJSON();211 requests.push(recordedRequest);212 }213 /**214 * Returns the saved requests for the imposter215 * @memberOf module:models/inMemoryImpostersRepository#216 * @returns {Object} - the promise resolving to the array of requests217 */218 async function loadRequests () {219 return requests;220 }221 /**222 * Clears the saved requests list223 * @memberOf module:models/inMemoryImpostersRepository#224 * @param {Object} request - the request225 * @returns {Object} - Promise226 */227 async function deleteSavedRequests () {228 requests = [];229 }230 return {231 count: () => stubs.length,232 first,233 add,234 insertAtIndex,235 overwriteAll,236 overwriteAtIndex,237 deleteAtIndex,238 toJSON,239 deleteSavedProxyResponses,240 addRequest,241 loadRequests,242 deleteSavedRequests243 };244}245/**246 * Creates the repository247 * @returns {Object}248 */249function create () {250 const imposters = {},251 stubRepos = {};252 /**253 * Adds a new imposter254 * @memberOf module:models/inMemoryImpostersRepository#255 * @param {Object} imposter - the imposter to add256 * @returns {Object} - the promise257 */258 async function add (imposter) {259 if (!imposter.stubs) {260 imposter.stubs = [];261 }262 imposters[String(imposter.port)] = imposter;263 const promises = (imposter.creationRequest.stubs || []).map(stubsFor(imposter.port).add);264 await Promise.all(promises);265 return imposter;266 }267 /**268 * Gets the imposter by id269 * @memberOf module:models/inMemoryImpostersRepository#270 * @param {Number} id - the id of the imposter (e.g. the port)271 * @returns {Object} - the imposter272 */273 async function get (id) {274 return imposters[String(id)] || null;275 }276 /**277 * Gets all imposters278 * @memberOf module:models/inMemoryImpostersRepository#279 * @returns {Object} - all imposters keyed by port280 */281 async function all () {282 return Promise.all(Object.keys(imposters).map(get));283 }284 /**285 * Returns whether an imposter at the given id exists or not286 * @memberOf module:models/inMemoryImpostersRepository#287 * @param {Number} id - the id (e.g. the port)288 * @returns {boolean}289 */290 async function exists (id) {291 return typeof imposters[String(id)] !== 'undefined';292 }293 /**294 * Deletes the imposter at the given id295 * @memberOf module:models/inMemoryImpostersRepository#296 * @param {Number} id - the id (e.g. the port)297 * @returns {Object} - the deletion promise298 */299 async function del (id) {300 const result = imposters[String(id)] || null;301 delete imposters[String(id)];302 delete stubRepos[String(id)];303 if (result) {304 await result.stop();305 }306 return result;307 }308 /**309 * Deletes all imposters synchronously; used during shutdown310 * @memberOf module:models/inMemoryImpostersRepository#311 */312 function stopAllSync () {313 Object.keys(imposters).forEach(id => {314 imposters[id].stop();315 delete imposters[id];316 delete stubRepos[id];317 });318 }319 /**320 * Deletes all imposters321 * @memberOf module:models/inMemoryImpostersRepository#322 * @returns {Object} - the deletion promise323 */324 async function deleteAll () {325 const ids = Object.keys(imposters),326 promises = ids.map(id => imposters[id].stop());327 ids.forEach(id => {328 delete imposters[id];329 delete stubRepos[id];330 });331 await Promise.all(promises);332 }333 /**334 * Returns the stub repository for the given id335 * @memberOf module:models/inMemoryImpostersRepository#336 * @param {Number} id - the imposter's id337 * @returns {Object} - the stub repository338 */339 function stubsFor (id) {340 // In practice, the stubsFor call occurs before the imposter is actually added...341 if (!stubRepos[String(id)]) {342 stubRepos[String(id)] = createStubsRepository();343 }344 return stubRepos[String(id)];345 }346 /**347 * Called at startup to load saved imposters.348 * Does nothing for in memory repository349 * @memberOf module:models/inMemoryImpostersRepository#350 * @returns {Object} - a promise351 */352 async function loadAll () {353 return Promise.resolve();354 }355 return {356 add,357 get,358 all,359 exists,360 del,361 stopAllSync,362 deleteAll,363 stubsFor,364 createStubsRepository,365 loadAll366 };367}...

Full Screen

Full Screen

Using AI Code Generation

copy

Full Screen

1var mb = require('mountebank');2var port = 2525;3 {4 {5 {6 is: {7 }8 }9 }10 },11 {12 {13 {14 is: {15 }16 }17 }18 }19];20mb.create({port: port, allowInjection: true}, function () {21 mb.startImposter(imposters[0], function () {22 mb.startImposter(imposters[1], function () {23 mb.nonProxyStubs(imposters[0].port, imposters[1].port, function () {24 console.log("nonProxyStubs called");25 });26 });27 });28});29var mb = require('mountebank');30var port = 2525;31 {32 {33 {34 is: {35 }36 }37 }38 },39 {40 {41 {42 is: {43 }44 }45 }46 }47];48mb.create({port: port, allowInjection: true}, function () {49 mb.startImposter(imposters[0], function () {50 mb.startImposter(imposters[1], function () {51 mb.proxy(imposters[0].port, imposters[1].port, function () {52 console.log("proxy called");53 });54 });55 });56});57var mb = require('mountebank');58var port = 2525;59 {

Full Screen

Using AI Code Generation

copy

Full Screen

1var mb = require('mountebank');2var server = mb.create({3});4server.then(function () {5 console.log('Server started');6 server.createStub({7 {8 equals: {9 }10 }11 {12 is: {13 }14 }15 }).then(function (stub) {16 console.log('Stub created', stub);17 });18 server.nonProxyStubs();19 server.addRoute({20 {21 equals: {22 }23 }24 {25 is: {26 }27 }28 }).then(function (route) {29 console.log('Route created', route);30 });31});32{33 {34 {35 "equals": {36 }37 }38 {39 "is": {40 "headers": {41 },42 "body": {

Full Screen

Using AI Code Generation

copy

Full Screen

1var mb = require('mountebank');2var port = 2525;3var imposterPort = 3000;4var protocol = 'http';5 {6 {7 equals: {8 }9 }10 {11 is: {12 headers: {13 },14 body: JSON.stringify({name: 'test'})15 }16 }17 }18];19mb.create({port: port, allowInjection: true, debug: true}, function (error, imposter) {20 imposter.addStub(stubs, function (error, response) {21 console.log(response.statusCode);22 });23});24var mb = require('mountebank');25var port = 2525;26var imposterPort = 3000;27var protocol = 'http';28 {29 {30 equals: {31 }32 }33 {34 is: {35 headers: {36 },37 body: JSON.stringify({name: 'test'})38 }39 }40 }41];42mb.create({port: port, allowInjection: true, debug: true}, function (error, imposter) {43 imposter.addStub(stubs, function (error, response) {44 console.log(response.statusCode);45 });46});47var mb = require('mountebank');48var port = 2525;49var imposterPort = 3000;50var protocol = 'http';51 {52 {53 equals: {54 }55 }56 {57 is: {58 headers: {59 },60 body: JSON.stringify({name: 'test'})61 }62 }63 }64];65mb.create({port: port, allowInjection: true, debug: true}, function (error, imposter)

Full Screen

Using AI Code Generation

copy

Full Screen

1var mb = require('mountebank');2var port = 2525;3var imposterPort = 3000;4mb.create(port, function (error, mbServer) {5 if (error) {6 console.error(error);7 } else {8 mbServer.post('/imposters', {9 stubs: [{10 responses: [{11 is: {12 }13 }]14 }]15 }, function (error, response) {16 if (error) {17 console.error(error);18 } else {19 console.log(response.body);20 }21 });22 }23});24var mb = require('mountebank');25var port = 2525;26var imposterPort = 3000;27var proxyPort = 3001;28mb.create(port, function (error, mbServer) {29 if (error) {30 console.error(error);31 } else {32 mbServer.post('/imposters', {33 stubs: [{34 responses: [{35 proxy: {36 }37 }]38 }]39 }, function (error, response) {40 if (error) {41 console.error(error);42 } else {43 console.log(response.body);44 }45 });46 }47});48var mb = require('mountebank');49var port = 2525;50var imposterPort = 3000;51var proxyPort = 3001;52mb.create(port, function (error, mbServer) {53 if (error) {54 console.error(error);55 } else {56 mbServer.post('/imposters', {57 stubs: [{58 responses: [{59 proxy: {60 }61 }]62 }]63 }, function (error, response) {64 if (error) {65 console.error(error);66 } else {67 console.log(response.body);68 }69 });70 }71});72var mb = require('mountebank');73var port = 2525;

Full Screen

Using AI Code Generation

copy

Full Screen

1var mb = require('mountebank');2var stubs = [ {3 predicates: [ { equals: { path: '/test' } } ],4 responses: [ { is: { body: 'Hello World!', statusCode: 200 } } ]5} ];6mb.create({ port: 2525, allowInjection: true }, function () {7 mb.nonProxyStubs(stubs, function (error) {8 console.log(error || 'stubs created');9 });10});11[ { protocol: 'http',12 [ { predicates: [Array],13 responses: [Array] } ],14 _links: { self: [Object] } } ]15[ { protocol: 'http',16 [ { predicates: [Array],17 responses: [Array] },18 { predicates: [Array],19 responses: [Array] } ],20 _links: { self: [Object] } } ]

Full Screen

Using AI Code Generation

copy

Full Screen

1var http = require('http');2var mb = require('mountebank');3var mbServer = mb.create({4});5var server = http.createServer(function (req, res) {6 res.writeHead(200, { 'Content-Type': 'text/plain' });7 res.end('Hello World\n');8});9server.listen(8080, function () {10 console.log('Server listening on port 8080');11});12mbServer.then(function (mb) {13 console.log('Mountebank server started');14 var stub = {15 {16 is: {17 headers: {18 },19 }20 }21 };22 var imposter = {23 };24 mb.post('/imposters', imposter).then(function (response) {25 console.log('Imposter created');26 var request = {27 };28 mb.get('/imposters/2525/requests', request).then(function (response) {29 console.log('Request sent');30 mb.del('/imposters/2525').then(function (response) {31 console.log('Imposter deleted');32 mb.stop().then(function (response) {33 console.log('Mountebank server stopped');34 server.close();35 });36 });37 });38 });39});

Full Screen

Using AI Code Generation

copy

Full Screen

1const mbHelper = require('./mbHelper.js');2 {3 {4 equals: {5 }6 }7 {8 is: {9 headers: {10 },11 body: {12 }13 }14 }15 }16];17const proxy = {18 {19 startsWith: {20 }21 }22 {23 proxy: {24 }25 }26};27 {28 },29 {30 }31];32const mbConfig = {33};34const serverConfig = {35};36const server = require('./server.js');37mbHelper.create(mbConfig, () => {38 console.log('mb server created');39 server.create(serverConfig, () => {40 console.log('server created');41 mbHelper.createImposters(imposters, () => {42 console.log('imposters created');43 });44 });45});46const http = require('http');47const express = require('express');48const bodyParser = require('body-parser');

Full Screen

Using AI Code Generation

copy

Full Screen

1const mb = require('mountebank');2const { imposters } = mb.create();3const { create } = mb.create();4const { protocol, port, name, stubs, defaultResponse } = imposters[0];5const { predicates, responses } = stubs[0];6const { equals } = predicates[0];7const { is } = responses[0];8const { statusCode } = defaultResponse;9const imposter = create({10 {11 {12 equals: {13 body: {14 },15 },16 },17 {18 is: {19 headers: {20 },21 body: {22 },23 },24 },25 },26 defaultResponse: {27 },28});29imposter.then((imposter) => {30 imposter.start();31});32imposter.then((imposter) => {33 imposter.stop();34});

Full Screen

Using AI Code Generation

copy

Full Screen

1var mb = require('mountebank');2var request = require('request');3var stub = {4 {5 is: {6 headers: {7 },8 body: JSON.stringify({9 {10 "attributes": {

Full Screen

Automation Testing Tutorials

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.

LambdaTest Learning Hubs:

YouTube

You could also refer to video tutorials over LambdaTest YouTube channel to get step by step demonstration from industry experts.

Run mountebank automation tests on LambdaTest cloud grid

Perform automation testing on 3000+ real desktop and mobile devices online.

Try LambdaTest Now !!

Get 100 minutes of automation test minutes FREE!!

Next-Gen App & Browser Testing Cloud

Was this article helpful?

Helpful

NotHelpful