Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

highwind

Package Overview
Dependencies
Maintainers
1
Versions
8
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

highwind - npm Package Compare versions

Comparing version 1.0.1 to 1.0.3

lib/middleware/latency.js

219

lib/mock_api.js
'use strict';
var _entries = require('babel-runtime/core-js/object/entries');
var _entries2 = _interopRequireDefault(_entries);
var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };

@@ -25,2 +31,4 @@

var _url2 = _interopRequireDefault(_url);
var _fs = require('fs');

@@ -34,8 +42,13 @@

var PROD_ROOT_URL = void 0;
var FIXTURES_PATH = void 0;
var QUERY_STRING_IGNORE = void 0;
var QUIET_MODE = void 0;
var SERVERS = [];
var REQUIRED_CONFIG_OPTIONS = ['prodRootURL', 'fixturesPath'];
var DEFAULT_OPTIONS = {
encoding: 'utf8',
latency: 0,
ports: [4567],
queryStringIgnore: [],
quiet: false,
saveFixtures: true
};
var JSON_CONTENT_TYPE_REGEXP = /javascript|json/;

@@ -50,39 +63,28 @@ module.exports = {

var app = (0, _express2.default)();
var defaults = {
ports: [4567],
encoding: 'utf8',
queryStringIgnore: [],
quiet: false
};
var modOptions = _extends({}, defaults, options);
var prodRootURL = modOptions.prodRootURL;
var corsWhitelist = modOptions.corsWhitelist;
var fixturesPath = modOptions.fixturesPath;
var overrides = modOptions.overrides;
var queryStringIgnore = modOptions.queryStringIgnore;
var ports = modOptions.ports;
var encoding = modOptions.encoding;
var quiet = modOptions.quiet;
var settings = _extends({}, DEFAULT_OPTIONS, options);
var corsWhitelist = settings.corsWhitelist;
var encoding = settings.encoding;
var latency = settings.latency;
var overrides = settings.overrides;
var ports = settings.ports;
PROD_ROOT_URL = prodRootURL;
FIXTURES_PATH = fixturesPath;
QUERY_STRING_IGNORE = queryStringIgnore;
QUIET_MODE = quiet;
if (corsWhitelist) {
setCorsMiddleware(app, corsWhitelist);
}
if (isValidDuration(latency)) {
simulateLatency(app, latency);
}
if (overrides) {
delegateRouteOverrides(app, overrides, encoding);
delegateRouteOverrides(app, settings);
}
app.get('*', function (req, res) {
app.all('*', function (req, res) {
var path = getURLPathWithQueryString(req);
var fileName = getFileName(path);
var fileName = getFileName(path, settings);
_fs2.default.readFile(fileName, encoding, function (err, data) {
if (err) {
recordFromProd(req, res);
fetchResponse(req, res, _extends({}, settings, { fileName: fileName, path: path }));
} else {
serveLocalResponse(res, fileName, data, { quiet: QUIET_MODE });
serveResponse(res, _extends({}, settings, { fileName: fileName, data: data }));
}

@@ -96,2 +98,3 @@ });

};
return startListening(app, ports, function (err) {

@@ -126,2 +129,6 @@ return callback(err, result);

function isValidDuration(latency) {
return Number.isFinite(latency) && latency > 0;
}
function generateMissingParamsError(options, callback) {

@@ -132,7 +139,27 @@ if (typeof callback !== 'function') {

for (var i = 0; i < REQUIRED_CONFIG_OPTIONS.length; i++) {
var key = REQUIRED_CONFIG_OPTIONS[i];
if (typeof options[key] !== 'string') {
return new Error('Missing definition of ' + key + ' in config file');
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;
try {
for (var _iterator = REQUIRED_CONFIG_OPTIONS[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
var key = _step.value;
if (typeof options[key] !== 'string') {
return new Error('Missing definition of ' + key + ' in config file');
}
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return) {
_iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}

@@ -156,2 +183,10 @@

function simulateLatency(app, latency) {
var latencyMiddleware = function latencyMiddleware(_req, _res, next) {
return global.setTimeout(next, latency);
};
app.use(latencyMiddleware);
}
function startListening(app, ports, callback) {

@@ -187,9 +222,11 @@ var tasks = ports.map(function (port) {

function delegateRouteOverrides(app, overrides, encoding) {
function delegateRouteOverrides(app, options) {
var overrides = options.overrides;
var encoding = options.encoding;
var quiet = options.quiet;
var methods = ['get', 'post', 'put', 'delete', 'all'];
var defaults = {
status: 200,
headers: {
'Content-Type': 'application/json'
}
headers: { 'Content-Type': 'application/json' }
};

@@ -210,4 +247,6 @@ var jsonMiddleware = [_bodyParser2.default.json(), _bodyParser2.default.urlencoded({ extended: true })];

var mergeParams = routeParams.mergeParams;
var _routeParams$withQuer = routeParams.withQueryParams;
var queryParams = _routeParams$withQuer === undefined ? {} : _routeParams$withQuer;
var responseIsJson = /(javascript|json)/.test(headers['Content-Type']);
var responseIsJson = JSON_CONTENT_TYPE_REGEXP.test(headers['Content-Type']);

@@ -219,3 +258,3 @@ if (!route) {

if (!response) {
var fileName = getFileName(route);
var fileName = getFileName(route, options);
_fs2.default.readFile(fileName, encoding, function (err, data) {

@@ -234,6 +273,36 @@ if (err) {

app[method].call(app, route, jsonMiddleware, function (req, res) {
if (!QUIET_MODE) {
app[method].call(app, route, jsonMiddleware, function (req, res, next) {
if (!quiet) {
console.info('==> 📁 Serving local fixture for ' + method.toUpperCase() + ' -> \'' + route + '\'');
}
var _iteratorNormalCompletion2 = true;
var _didIteratorError2 = false;
var _iteratorError2 = undefined;
try {
for (var _iterator2 = (0, _entries2.default)(queryParams)[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
var _step2$value = _slicedToArray(_step2.value, 2);
var param = _step2$value[0];
var value = _step2$value[1];
if (req.query[param] !== value) {
return next();
}
}
} catch (err) {
_didIteratorError2 = true;
_iteratorError2 = err;
} finally {
try {
if (!_iteratorNormalCompletion2 && _iterator2.return) {
_iterator2.return();
}
} finally {
if (_didIteratorError2) {
throw _iteratorError2;
}
}
}
var payload = responseIsJson && typeof mergeParams === 'function' ? mergeParams(JSON.parse(fixture), req.body) : fixture;

@@ -246,10 +315,19 @@ res.status(status).set(headers).send(payload);

function recordFromProd(req, res) {
function fetchResponse(req, res, options) {
if (req.method !== 'GET') {
console.error('==> ⛔️ Couldn\'t complete fetch with non-GET method');
return res.status(500).end();
}
var responseIsJson = void 0;
var path = getURLPathWithQueryString(req);
var prodURL = getProdURL(path);
var prodRootURL = options.prodRootURL;
var saveFixtures = options.saveFixtures;
var path = options.path;
var fileName = options.fileName;
var prodURL = prodRootURL + path;
var responseIsJsonp = prodURL.match(/callback\=([^\&]+)/);
console.info('==> 📡 GET ' + PROD_ROOT_URL + ' -> ' + path);
(0, _nodeFetch2.default)(prodURL).then(function (response) {
console.info('==> 📡 GET ' + prodRootURL + ' -> ' + path);
(0, _nodeFetch2.default)(prodRootURL + path).then(function (response) {
if (response.ok) {

@@ -271,12 +349,6 @@ console.info('==> 📡 STATUS ' + response.status);

}).then(function (response) {
var fileName = getFileName(path);
var data = responseIsJson ? JSON.stringify(response) : response;
_fs2.default.writeFile(fileName, data, function (err) {
if (err) {
throw Error('Couldn\'t write response locally, received fs error: \'' + err + '\'');
}
console.info('==> 💾 Saved response to ' + fileName);
});
serveLocalResponse(res, fileName, data, { quiet: true });
if (saveFixtures) {
saveFixture(fileName, response, responseIsJson);
}
serveResponse(res, _extends({}, options, { newResponse: true }));
}).catch(function (err) {

@@ -288,7 +360,9 @@ console.error('==> ⛔️ ' + err);

function serveLocalResponse(res, fileName, data) {
var options = arguments.length <= 3 || arguments[3] === undefined ? { quiet: false } : arguments[3];
function serveResponse(res, options) {
var data = options.data;
var fileName = options.fileName;
var quiet = options.quiet;
var newResponse = options.newResponse;
if (quiet !== true) {
if (!quiet && !newResponse) {
console.info('==> 📁 Serving local response from ' + fileName);

@@ -307,15 +381,26 @@ }

function getFileName(path) {
var fileNameInDirectory = QUERY_STRING_IGNORE.reduce(function (fileName, regex) {
function saveFixture(fileName, response, responseIsJson) {
var data = responseIsJson ? JSON.stringify(response) : response;
_fs2.default.writeFile(fileName, data, function (err) {
if (err) {
throw Error('Couldn\'t write response locally, received fs error: \'' + err + '\'');
}
console.info('==> 💾 Saved response to ' + fileName);
});
}
function getFileName(path, options) {
var queryStringIgnore = options.queryStringIgnore;
var fixturesPath = options.fixturesPath;
var fileNameInDirectory = queryStringIgnore.reduce(function (fileName, regex) {
return fileName.replace(regex, '');
}, path).replace(/\//, '').replace(/\//g, ':');
return FIXTURES_PATH + '/' + fileNameInDirectory + '.json';
return fixturesPath + '/' + fileNameInDirectory + '.json';
}
function getProdURL(path) {
return PROD_ROOT_URL + path;
}
function getURLPathWithQueryString(req) {
var queryString = _url2.default.parse(req.url).query;
function getURLPathWithQueryString(req) {
var queryString = (0, _url.parse)(req.url).query;
if (queryString && queryString.length > 0) {

@@ -322,0 +407,0 @@ return req.path + '?' + queryString;

{
"name": "highwind",
"version": "1.0.1",
"version": "1.0.3",
"description": "Mock API express server",

@@ -25,5 +25,7 @@ "main": "lib/mock_api.js",

"babel-eslint": "^4.1.6",
"babel-plugin-transform-object-assign": "^6.3.13",
"babel-plugin-transform-object-entries": "^1.0.0",
"babel-plugin-transform-object-rest-spread": "^6.8.0",
"babel-preset-es2015": "^6.3.13",
"chai": "^3.4.1",
"eslint": "^1.10.3",
"mocha": "^2.3.4",

@@ -39,3 +41,2 @@ "nock": "^5.2.1",

"cors": "^2.7.1",
"eslint": "^1.10.3",
"express": "^4.13.3",

@@ -42,0 +43,0 @@ "node-fetch": "^1.3.3",

@@ -65,3 +65,3 @@ Highwind

* `overrides` *(object)*:
* HTTP methods for which specific routes should be overridden. Each property of this object should have a key specifying an HTTP method known to Express's Application object (`get`, `post`, `put`, `delete`, `all`). Examples follow below.
* HTTP methods for which specific routes should be overridden. Each property of this object should have a key specifying an HTTP method known to Express's `app` object (`get`, `post`, `put`, `delete`, `all`). Examples follow below.
* `queryStringIgnore` *(array of RegExp)*:

@@ -80,2 +80,8 @@ * **Default:** `[]`

* Silences console output when an API response is being served locally. One possible use case is feature tests, in which you'll (ideally) be serving everything locally, to minimize spec pollution.
* `saveFixtures`: *(boolean)*
* **Default:** `true`.
* Toggles persisting responses from the production API as local fixtures.
* `latency`: *(number)*
* **Default:** 0
* Number of milliseconds to delay responses in order to simulate latency.

@@ -138,2 +144,20 @@ ## HTTP Route Overrides

### Serving a JSON response with dynamic query params
```js
overrides: {
get: [
{
route: '/api/search',
withQueryParams: { query: 'foo' },
response: {
keyword: 'foo',
result_count: 15
},
status: 200
}
]
}
```
This serves the specified response _only_ when the query string matches the params specified in the `withQueryParams` object; in all other cases, it defers to the default response.
## Serving a JSONP response

@@ -144,2 +168,2 @@

## License
[MIT License](http://mit-license.org/) © Refinery29, Inc. 2016
[MIT License](http://mit-license.org/) © Refinery29, Inc. 2016-2017

@@ -52,7 +52,9 @@ import 'babel-polyfill';

expect(servers).to.have.length(1);
expect(servers[0].port).to.equal(4567);
expect(servers[0].server).to.be.a('object');
expect(servers[0].active).to.be.true;
close(result.servers, done);
const [ server ] = servers;
expect(server.port).to.equal(4567);
expect(server.server).to.be.a('object');
expect(server.active).to.be.true;
close(servers, done);
});

@@ -71,44 +73,98 @@ });

beforeEach(function(done) {
nock(PROD_ROOT_URL)
.get(route)
.query(true)
.reply(200, response);
start(DEFAULT_OPTIONS, (err, result) => {
mockAPI = result.app;
done();
describe('When the request method is GET', function() {
describe('And the saveFixtures setting is set to true', function() {
beforeEach(function(done) {
nock(PROD_ROOT_URL)
.get(route)
.query(true)
.reply(200, response);
start(DEFAULT_OPTIONS, (err, result) => {
mockAPI = result;
done();
});
});
afterEach(function() {
close(mockAPI.servers);
[responsePath, responsePathWithCallback].forEach(path => {
try {
fs.accessSync(path, fs.F_OK);
} catch (e) {
return;
}
fs.unlinkSync(path);
});
});
it('persists and responds with a response from the production API', function(done) {
request(mockAPI.app)
.get(route)
.expect('Content-Type', /application\/json/)
.expect(200, response, () => fs.access(responsePath, fs.F_OK, done));
});
it('truncates ignored query string expressions in the persisted response filename', function(done) {
request(mockAPI.app)
.get(route + IGNORED_QUERY_PARAMS)
.expect('Content-Type', /application\/json/)
.expect(200, response, () => fs.access(responsePath, fs.F_OK, done));
});
it('renders the endpoint as JSONP when a callback is specified in the query string', function(done) {
request(mockAPI.app)
.get(route + JSONP_CALLBACK)
.expect('Content-Type', /application\/javascript/)
.expect(200, response, () => fs.access(responsePathWithCallback, fs.F_OK, done));
});
});
});
afterEach(function() {
close(mockAPI.servers);
[responsePath, responsePathWithCallback].forEach(path => {
try {
fs.accessSync(path, fs.F_OK);
} catch (e) {
return;
}
fs.unlinkSync(path);
describe('And the saveFixtures setting is set to false', function() {
beforeEach(function(done) {
nock(PROD_ROOT_URL)
.get(route)
.query(true)
.reply(200, response);
start({ ...DEFAULT_OPTIONS, saveFixtures: false }, (err, result) => {
mockAPI = result;
done();
});
});
afterEach(function() {
close(mockAPI.servers);
});
it('responses with a response from the production API and does not persist the response', function(done) {
request(mockAPI.app)
.get(route)
.expect('Content-Type', /application\/json/)
.expect(200, response, () => fs.access(responsePath, fs.F_OK, (err) => {
if (err) {
done();
}
}));
});
});
});
it('persists and responds with a response from the production API', function(done) {
request(mockAPI)
.get(route)
.expect('Content-Type', /application\/json/)
.expect(200, response, () => fs.access(responsePath, fs.F_OK, done));
});
describe('When the request method is not GET', function() {
beforeEach(function(done) {
start(DEFAULT_OPTIONS, (err, result) => {
mockAPI = result;
done();
});
});
it('truncates ignored query string expressions in the persisted response filename', function(done) {
request(mockAPI)
.get(route + IGNORED_QUERY_PARAMS)
.expect('Content-Type', /application\/json/)
.expect(200, response, () => fs.access(responsePath, fs.F_OK, done));
});
afterEach(function() {
close(mockAPI.servers);
});
it('renders the endpoint as JSONP when a callback is specified in the query string', function(done) {
request(mockAPI)
.get(route + JSONP_CALLBACK)
.expect('Content-Type', /application\/javascript/)
.expect(200, response, () => fs.access(responsePathWithCallback, fs.F_OK, done));
it('does not call the production API and returns an error', function(done) {
request(mockAPI.app)
.put(route)
.expect(500, '', done);
});
});

@@ -130,4 +186,5 @@ });

.replyWithError('Fake API hit the production API');
start(DEFAULT_OPTIONS, (err, result) => {
mockAPI = result.app;
mockAPI = result;
done();

@@ -142,3 +199,3 @@ });

it('serves the locally persisted response as JSON and does not hit the production API', function(done) {
request(mockAPI)
request(mockAPI.app)
.get(route)

@@ -150,3 +207,3 @@ .expect('Content-Type', /application\/json/)

it('ignores truncated query string expressions when identifying the persisted response filename and does not hit the production API', function(done) {
request(mockAPI)
request(mockAPI.app)
.get(route + IGNORED_QUERY_PARAMS)

@@ -158,3 +215,3 @@ .expect('Content-Type', /application\/json/)

it('renders the endpoint as JSONP when a callback is specified in the query string and does not hit the production API', function(done) {
request(mockAPI)
request(mockAPI.app)
.get(route + JSONP_CALLBACK)

@@ -176,4 +233,5 @@ .expect('Content-Type', /application\/javascript/)

.replyWithError('Fake API hit the production API');
start(DEFAULT_OPTIONS, (err, result) => {
mockAPI = result.app;
mockAPI = result;
done();

@@ -188,3 +246,3 @@ });

it('responds with the locally persisted response as `text/html` and does not hit the production API', function(done) {
request(mockAPI)
request(mockAPI.app)
.get(route)

@@ -202,16 +260,15 @@ .expect('Content-Type', /text\/html/)

const response = 'overridden response';
const modOptions = Object.assign({}, DEFAULT_OPTIONS, {
const modOptions = {
...DEFAULT_OPTIONS,
overrides: {
get: [
{
route: route,
response: response,
route,
response,
status: 503,
headers: {
'Content-Type': 'text/plain'
}
headers: { 'Content-Type': 'text/plain' }
}
]
}
});
};

@@ -223,4 +280,5 @@ before(function(done) {

.replyWithError('Fake API hit the production API');
start(modOptions, (err, result) => {
mockAPI = result.app;
mockAPI = result;
done();

@@ -235,3 +293,3 @@ });

it('responds with the specified response, status, and headers and does not hit the production API', function(done) {
request(mockAPI)
request(mockAPI.app)
.get(route)

@@ -243,3 +301,3 @@ .expect('Content-Type', /text\/plain/)

it('responds with the specified headers even if the filename specifies a JSONP callback', function(done) {
request(mockAPI)
request(mockAPI.app)
.get(route + JSONP_CALLBACK)

@@ -254,12 +312,13 @@ .expect('Content-Type', /text\/plain/, done);

const response = { status: 'overridden response' };
const modOptions = Object.assign({}, DEFAULT_OPTIONS, {
const modOptions = {
...DEFAULT_OPTIONS,
overrides: {
get: [
{
route: route,
response: response
route,
response
}
]
}
});
};

@@ -270,4 +329,5 @@ before(function(done) {

.replyWithError('Fake API hit the production API');
start(modOptions, (err, result) => {
mockAPI = result.app;
mockAPI = result;
done();

@@ -282,3 +342,3 @@ });

it('responds with the specified response rendered as JSON, status 200, and does not hit the production API', function(done) {
request(mockAPI)
request(mockAPI.app)
.get(route)

@@ -290,2 +350,53 @@ .expect('Content-Type', /application\/json/)

describe('And there is a JSON response with query param expectations specified in the override', function() {
let mockAPI;
const route = '/overridden_route';
const response = { status: 'overridden response' };
const modOptions = {
...DEFAULT_OPTIONS,
overrides: {
get: [
{
route,
response,
withQueryParams: { foo: 'bar' }
}
]
}
};
before(function(done) {
nock(PROD_ROOT_URL)
.get(route)
.replyWithError('Fake API hit the production API');
start(modOptions, (err, result) => {
mockAPI = result;
done();
});
});
after(function() {
close(mockAPI.servers);
});
describe('when the query params are specified in the route', function() {
it('responds with the specified response, status 200, and does not hit the production API', function(done) {
request(mockAPI.app)
.get(route + '?foo=bar')
.expect('Content-Type', /application\/json/)
.expect(200, response, done);
});
});
describe('when the query params are not specified in the route', function() {
it('does not respond with the specified response, status 200, and does not hit the production API', function(done) {
request(mockAPI.app)
.get(route + '?foo=quux')
.expect(500, '', done);
});
});
});
describe('And there is a JSON response with a mergeParmas callback specified in the override', function() {

@@ -296,15 +407,14 @@ let mockAPI;

const response = { status: 'overridden response' };
const modOptions = Object.assign({}, DEFAULT_OPTIONS, {
const modOptions = {
...DEFAULT_OPTIONS,
overrides: {
post: [
{
route: route,
response: response,
mergeParams(response, params) {
return Object.assign({}, response, params);
}
route,
response,
mergeParams: (response, params) => ({ ...response, ...params })
}
]
}
});
};

@@ -315,4 +425,5 @@ before(function(done) {

.replyWithError('Fake API hit the production API');
start(modOptions, (err, result) => {
mockAPI = result.app;
mockAPI = result;
done();

@@ -327,7 +438,7 @@ });

it('responds with the specified response rendered as JSON, status 200, and does not hit the production API', function(done) {
request(mockAPI)
request(mockAPI.app)
.post(route)
.send(reqBody)
.expect('Content-Type', /application\/json/)
.expect(200, Object.assign({}, response, reqBody), done);
.expect(200, { ...response, ...reqBody }, done);
});

@@ -350,3 +461,3 @@ });

const ports = [5000, 5001, 5002];
const modOptions = Object.assign({}, DEFAULT_OPTIONS, { ports });
const modOptions = { ...DEFAULT_OPTIONS, ports };
start(modOptions, (err, result) => {

@@ -370,3 +481,3 @@ close(result.servers, done);

ports = [1111, 1112, 1113];
const modOptions = Object.assign({}, DEFAULT_OPTIONS, { ports: ports });
const modOptions = { ...DEFAULT_OPTIONS, ports };
start(modOptions, (err, result) => {

@@ -373,0 +484,0 @@ servers = result.servers;

@@ -6,10 +6,6 @@ import 'babel-polyfill';

import bodyParser from 'body-parser';
import { parse as urlParse } from 'url';
import url from 'url';
import fs from 'fs';
import { parallel } from 'async';
let PROD_ROOT_URL;
let FIXTURES_PATH;
let QUERY_STRING_IGNORE;
let QUIET_MODE;
const SERVERS = [];

@@ -20,2 +16,11 @@ const REQUIRED_CONFIG_OPTIONS = [

];
const DEFAULT_OPTIONS = {
encoding: 'utf8',
latency: 0,
ports: [4567],
queryStringIgnore: [],
quiet: false,
saveFixtures: true
};
const JSON_CONTENT_TYPE_REGEXP = /javascript|json/;

@@ -30,40 +35,23 @@ module.exports = {

const app = express();
const defaults = {
ports: [4567],
encoding: 'utf8',
queryStringIgnore: [],
quiet: false
};
const modOptions = Object.assign({}, defaults, options);
const {
prodRootURL,
corsWhitelist,
fixturesPath,
overrides,
queryStringIgnore,
ports,
encoding,
quiet
} = modOptions;
const settings = { ...DEFAULT_OPTIONS, ...options };
const { corsWhitelist, encoding, latency, overrides, ports } = settings;
PROD_ROOT_URL = prodRootURL;
FIXTURES_PATH = fixturesPath;
QUERY_STRING_IGNORE = queryStringIgnore;
QUIET_MODE = quiet;
if (corsWhitelist) {
setCorsMiddleware(app, corsWhitelist);
}
if (isValidDuration(latency)) {
simulateLatency(app, latency);
}
if (overrides) {
delegateRouteOverrides(app, overrides, encoding);
delegateRouteOverrides(app, settings);
}
app.get('*', (req, res) => {
app.all('*', (req, res) => {
const path = getURLPathWithQueryString(req);
const fileName = getFileName(path);
const fileName = getFileName(path, settings);
fs.readFile(fileName, encoding, (err, data) => {
if (err) {
recordFromProd(req, res);
fetchResponse(req, res, { ...settings, fileName, path });
} else {
serveLocalResponse(res, fileName, data, { quiet: QUIET_MODE });
serveResponse(res, { ...settings, fileName, data });
}

@@ -74,6 +62,7 @@ });

const result = {
app: app,
app,
servers: SERVERS
};
return startListening(app, ports, err => callback(err, result));
return startListening(app, ports, (err) => callback(err, result));
},

@@ -102,2 +91,6 @@

function isValidDuration(latency) {
return Number.isFinite(latency) && latency > 0;
}
function generateMissingParamsError(options, callback) {

@@ -108,4 +101,3 @@ if (typeof callback !== 'function') {

for (let i = 0; i < REQUIRED_CONFIG_OPTIONS.length; i++) {
const key = REQUIRED_CONFIG_OPTIONS[i];
for (const key of REQUIRED_CONFIG_OPTIONS) {
if (typeof options[key] !== 'string') {

@@ -131,2 +123,9 @@ return new Error(`Missing definition of ${key} in config file`);

function simulateLatency(app, latency) {
const latencyMiddleware = (_req, _res, next) =>
global.setTimeout(next, latency);
app.use(latencyMiddleware);
}
function startListening(app, ports, callback) {

@@ -158,9 +157,8 @@ const tasks = ports.map(port => {

function delegateRouteOverrides(app, overrides, encoding) {
function delegateRouteOverrides(app, options) {
const { overrides, encoding, quiet } = options;
const methods = ['get', 'post', 'put', 'delete', 'all'];
const defaults = {
status: 200,
headers: {
'Content-Type': 'application/json'
}
headers: { 'Content-Type': 'application/json' }
};

@@ -178,5 +176,12 @@ const jsonMiddleware = [

let fixture;
const routeParams = Object.assign({}, defaults, params);
const { route, status, response, headers, mergeParams } = routeParams;
const responseIsJson = /(javascript|json)/.test(headers['Content-Type']);
const routeParams = { ...defaults, ...params };
const {
route,
status,
response,
headers,
mergeParams,
withQueryParams: queryParams = {}
} = routeParams;
const responseIsJson = JSON_CONTENT_TYPE_REGEXP.test(headers['Content-Type']);

@@ -188,3 +193,3 @@ if (!route) {

if (!response) {
const fileName = getFileName(route);
const fileName = getFileName(route, options);
fs.readFile(fileName, encoding, (err, data) => {

@@ -203,6 +208,11 @@ if (err) {

app[method].call(app, route, jsonMiddleware, (req, res) => {
if (!QUIET_MODE) {
app[method].call(app, route, jsonMiddleware, (req, res, next) => {
if (!quiet) {
console.info(`==> 📁 Serving local fixture for ${method.toUpperCase()} -> '${route}'`);
}
for (const [param, value] of Object.entries(queryParams)) {
if (req.query[param] !== value) {
return next();
}
}
const payload = responseIsJson && typeof mergeParams === 'function'

@@ -220,10 +230,15 @@ ? mergeParams(JSON.parse(fixture), req.body)

function recordFromProd(req, res) {
function fetchResponse(req, res, options) {
if (req.method !== 'GET') {
console.error(`==> ⛔️ Couldn't complete fetch with non-GET method`);
return res.status(500).end();
}
let responseIsJson;
const path = getURLPathWithQueryString(req);
const prodURL = getProdURL(path);
const { prodRootURL, saveFixtures, path, fileName } = options;
const prodURL = prodRootURL + path;
const responseIsJsonp = prodURL.match(/callback\=([^\&]+)/);
console.info(`==> 📡 GET ${PROD_ROOT_URL} -> ${path}`);
fetch(prodURL)
console.info(`==> 📡 GET ${prodRootURL} -> ${path}`);
fetch(prodRootURL + path)
.then(response => {

@@ -247,14 +262,6 @@ if (response.ok) {

.then(response => {
const fileName = getFileName(path);
const data = responseIsJson
? JSON.stringify(response)
: response;
fs.writeFile(fileName, data, (err) => {
if (err) {
throw Error(`Couldn't write response locally, received fs error: '${err}'`)
}
console.info(`==> 💾 Saved response to ${fileName}`);
});
serveLocalResponse(res, fileName, data, { quiet: true });
if (saveFixtures) {
saveFixture(fileName, response, responseIsJson);
}
serveResponse(res, { ...options, newResponse: true });
})

@@ -267,5 +274,5 @@ .catch(err => {

function serveLocalResponse(res, fileName, data, options = { quiet: false }) {
const { quiet } = options;
if (quiet !== true) {
function serveResponse(res, options) {
const { data, fileName, quiet, newResponse } = options;
if (!quiet && !newResponse) {
console.info(`==> 📁 Serving local response from ${fileName}`);

@@ -288,16 +295,27 @@ }

function getFileName(path) {
const fileNameInDirectory = QUERY_STRING_IGNORE
function saveFixture(fileName, response, responseIsJson) {
const data = responseIsJson
? JSON.stringify(response)
: response;
fs.writeFile(fileName, data, (err) => {
if (err) {
throw Error(`Couldn't write response locally, received fs error: '${err}'`)
}
console.info(`==> 💾 Saved response to ${fileName}`);
});
}
function getFileName(path, options) {
const { queryStringIgnore, fixturesPath } = options;
const fileNameInDirectory = queryStringIgnore
.reduce((fileName, regex) => fileName.replace(regex, ''), path)
.replace(/\//, '')
.replace(/\//g, ':');
return `${FIXTURES_PATH}/${fileNameInDirectory}.json`;
return `${fixturesPath}/${fileNameInDirectory}.json`;
}
function getProdURL(path) {
return PROD_ROOT_URL + path;
}
function getURLPathWithQueryString(req) {
const queryString = url.parse(req.url).query;
function getURLPathWithQueryString(req) {
const queryString = urlParse(req.url).query;
if (queryString && queryString.length > 0) {

@@ -304,0 +322,0 @@ return req.path + '?' + queryString;

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc