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 0.1.2 to 1.0.0

114

lib/mock_api.js

@@ -29,14 +29,20 @@ 'use strict';

var _async = require('async');
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var PROD_ROOT_URL = undefined;
var FIXTURES_PATH = undefined;
var QUERY_STRING_IGNORE = undefined;
var QUIET_MODE = undefined;
var SERVERS = {};
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'];
module.exports = {
start: function start(options) {
throwIfMissingOptions(options);
start: function start(options, callback) {
var error = generateMissingParamsError(options, callback);
if (error) {
return callback(error);
}
var app = (0, _express2.default)();

@@ -59,2 +65,3 @@ var defaults = {

PROD_ROOT_URL = prodRootURL;

@@ -71,2 +78,3 @@ FIXTURES_PATH = fixturesPath;

}
app.get('*', function (req, res) {

@@ -83,28 +91,49 @@ var path = getURLPathWithQueryString(req);

});
startListening(app, ports);
return {
var result = {
app: app,
servers: SERVERS
};
return startListening(app, ports, function (err) {
return callback(err, result);
});
},
close: function close(clientServers) {
close: function close(clientServers, callback) {
var servers = clientServers || SERVERS;
var ports = Object.keys(servers);
if (!ports.length) {
throw Error('closeMockAPI invoked without arguments or open servers');
var activeServers = servers.filter(function (server) {
return server.active;
});
if (activeServers.length === 0) {
return callback(Error('close() invoked without arguments or open servers'));
}
ports.forEach(function (port) {
console.info('Closing mock API server on port ' + port);
servers[port].close();
delete servers[port];
var tasks = activeServers.map(function (serverEntry) {
var server = serverEntry.server;
var port = serverEntry.port;
return function (callback) {
console.info('Closing mock API server on port ' + port);
return server.close(function (err) {
serverEntry.active = false;
callback(err);
});
};
});
return (0, _async.parallel)(tasks, callback);
}
};
function throwIfMissingOptions(options) {
REQUIRED_CONFIG_OPTIONS.forEach(function (key) {
function generateMissingParamsError(options, callback) {
if (typeof callback !== 'function') {
return new Error('Missing callback');
}
for (var i = 0; i < REQUIRED_CONFIG_OPTIONS.length; i++) {
var key = REQUIRED_CONFIG_OPTIONS[i];
if (typeof options[key] !== 'string') {
throw Error('Missing definition of ' + key + ' in config file');
return new Error('Missing definition of ' + key + ' in config file');
}
});
}
return null;
}

@@ -125,17 +154,30 @@

function startListening(app, ports) {
ports.forEach(function (port) {
if (SERVERS.hasOwnProperty(port)) {
console.warn('Port ' + port + ' specified more than once in config file');
return;
}
var server = app.listen(port, function (err) {
if (err) {
console.error(err);
} else {
console.info('Mock API server listening on port ' + port);
function startListening(app, ports, callback) {
var tasks = ports.map(function (port) {
return function (callback) {
var activeServers = SERVERS.filter(function (server) {
return server.active;
});
if (activeServers.map(function (server) {
return server.port;
}).includes(port)) {
console.warn('Port ' + port + ' specified more than once in config file');
return;
}
});
SERVERS[port] = server;
var server = app.listen(port, function (err) {
if (err) {
callback(err);
} else {
console.info('Mock API server listening on port ' + port);
callback(null);
}
});
SERVERS.push({
port: port,
server: server,
active: true
});
};
});
return (0, _async.parallel)(tasks, callback);
}

@@ -158,3 +200,3 @@

overrides[method].forEach(function (params) {
var fixture = undefined;
var fixture = void 0;
var routeParams = _extends({}, defaults, params);

@@ -200,3 +242,3 @@ var route = routeParams.route;

function recordFromProd(req, res) {
var responseIsJson = undefined;
var responseIsJson = void 0;
var path = getURLPathWithQueryString(req);

@@ -203,0 +245,0 @@ var prodURL = getProdURL(path);

{
"name": "highwind",
"version": "0.1.2",
"version": "1.0.0",
"description": "Mock API express server",

@@ -9,3 +9,3 @@ "main": "lib/mock_api.js",

"test": "mocha spec/mock_api_spec.js --require babel-core/register",
"prepublish": "babel src --out-dir lib"
"prepublish": "./node_modules/.bin/babel src --out-dir lib"
},

@@ -35,2 +35,3 @@ "license": "MIT",

"dependencies": {
"async": "^1.5.2",
"babel-polyfill": "^6.3.14",

@@ -37,0 +38,0 @@ "body-parser": "^1.14.2",

@@ -25,6 +25,25 @@ Highwind

// booting your server
highwind.start(options);
highwind.start(options, (err, result) => {
// 'result.app' is the express object
//
// 'result.servers' represents all currently running Highwind servers,
// and is an array of server objects with signature:
// {
// port : number
// server : instance of the express object in 'result.app'
// active : bool
// }
//
// The important thing to note is that by the time the callback is called,
// result[n].server is currently listening for requests.
});
// closing your server
highwind.close();
// first argument is an optional array of servers with the same signature
// as highwind.start's `result.servers`. If this param is not passed in,
// highwind.close will close all currently running servers.
highwind.close(null, (err) => {
// do something after all currently running servers have been closed
});
```

@@ -31,0 +50,0 @@

@@ -7,6 +7,3 @@ import 'babel-polyfill';

import { spy as spyOn } from 'sinon';
import {
start as startMockAPI,
close as closeMockAPI
} from '../src/mock_api.js';
import { start, close } from '../src/mock_api.js';

@@ -20,27 +17,44 @@ const PROD_ROOT_URL = 'http://localhost:4444';

fixturesPath: RESPONSES_DIR,
queryStringIgnore: [ new RegExp(`\\${IGNORED_QUERY_PARAMS}$`) ],
queryStringIgnore: [
new RegExp(`\\${IGNORED_QUERY_PARAMS}$`)
],
quiet: true
};
describe('#start', function() {
describe('start()', function() {
describe('Initialization', function() {
it('throws an error when a prodRootURL or fixturesPath are not specified', function() {
expect(() => {
startMockAPI({
prodRootURL: 'http://www.refinery29.com'
});
}).to.throw(Error);
it('calls the passed in callback with an error when fixturesPath is not specified', function(done) {
start({
prodRootURL: 'http://www.refinery29.com'
}, (err) => {
expect(err).to.be.an('error');
done();
});
});
expect(() => {
startMockAPI({
fixturesPath: './fixtures'
});
}).to.throw(Error);
it('calls the passed in callback with an error when prodRootUrl is not specified', function(done) {
start({
fixturesPath: './fixtures'
}, (err) => {
expect(err).to.be.an('error');
done();
});
});
expect(() => {
startMockAPI({
prodRootURL: 'http://www.refinery29.com/',
fixturesPath: './fixtures'
});
}).to.not.throw(Error);
it('does not pass in an error, and populates result.app and result.servers when prodRootURL and fixturesPath are specified', function(done) {
start({
prodRootURL: 'http://www.refinery29.com',
fixturesPath: './fixtures'
}, (err, result) => {
expect(err).to.not.exist;
const { app, servers } = result;
expect(app).to.be.a('function');
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);
});
});

@@ -58,4 +72,3 @@ });

beforeEach(function() {
mockAPI = startMockAPI(DEFAULT_OPTIONS);
beforeEach(function(done) {
nock(PROD_ROOT_URL)

@@ -65,6 +78,10 @@ .get(route)

.reply(200, response);
start(DEFAULT_OPTIONS, (err, result) => {
mockAPI = result.app;
done();
});
});
afterEach(function() {
closeMockAPI(mockAPI.servers);
close(mockAPI.servers);
[responsePath, responsePathWithCallback].forEach(path => {

@@ -81,3 +98,3 @@ try {

it('persists and responds with a response from the production API', function(done) {
request(mockAPI.app)
request(mockAPI)
.get(route)

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

it('truncates ignored query string expressions in the persisted response filename', function(done) {
request(mockAPI.app)
request(mockAPI)
.get(route + IGNORED_QUERY_PARAMS)

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

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

@@ -113,4 +130,3 @@ .expect('Content-Type', /application\/javascript/)

beforeEach(function() {
mockAPI = startMockAPI(DEFAULT_OPTIONS);
beforeEach(function(done) {
nock(PROD_ROOT_URL)

@@ -120,10 +136,14 @@ .get(route)

.replyWithError('Fake API hit the production API');
start(DEFAULT_OPTIONS, (err, result) => {
mockAPI = result.app;
done();
});
});
afterEach(function() {
closeMockAPI(mockAPI.servers);
close(mockAPI.servers);
});
it('serves the locally persisted response as JSON and does not hit the production API', function(done) {
request(mockAPI.app)
request(mockAPI)
.get(route)

@@ -135,3 +155,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.app)
request(mockAPI)
.get(route + IGNORED_QUERY_PARAMS)

@@ -143,3 +163,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.app)
request(mockAPI)
.get(route + JSONP_CALLBACK)

@@ -157,15 +177,18 @@ .expect('Content-Type', /application\/javascript/)

before(function() {
mockAPI = startMockAPI(DEFAULT_OPTIONS);
before(function(done) {
nock(PROD_ROOT_URL)
.get(route)
.replyWithError('Fake API hit the production API');
start(DEFAULT_OPTIONS, (err, result) => {
mockAPI = result.app;
done();
});
});
after(function() {
closeMockAPI(mockAPI.servers);
close(mockAPI.servers);
});
it('responds with the locally persisted response as `text/html` and does not hit the production API', function(done) {
request(mockAPI.app)
request(mockAPI)
.get(route)

@@ -198,4 +221,3 @@ .expect('Content-Type', /text\/html/)

before(function() {
mockAPI = startMockAPI(modOptions);
before(function(done) {
nock(PROD_ROOT_URL)

@@ -205,10 +227,14 @@ .get(route)

.replyWithError('Fake API hit the production API');
start(modOptions, (err, result) => {
mockAPI = result.app;
done();
});
});
after(function() {
closeMockAPI(mockAPI.servers);
close(mockAPI.servers);
});
it('responds with the specified response, status, and headers and does not hit the production API', function(done) {
request(mockAPI.app)
request(mockAPI)
.get(route)

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

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

@@ -242,15 +268,18 @@ .expect('Content-Type', /text\/plain/, done);

before(function() {
mockAPI = startMockAPI(modOptions);
before(function(done) {
nock(PROD_ROOT_URL)
.get(route)
.replyWithError('Fake API hit the production API');
start(modOptions, (err, result) => {
mockAPI = result.app;
done();
});
});
after(function() {
closeMockAPI(mockAPI.servers);
close(mockAPI.servers);
});
it('responds with the specified response rendered as JSON, status 200, and does not hit the production API', function(done) {
request(mockAPI.app)
request(mockAPI)
.get(route)

@@ -281,15 +310,18 @@ .expect('Content-Type', /application\/json/)

before(function() {
mockAPI = startMockAPI(modOptions);
before(function(done) {
nock(PROD_ROOT_URL)
.post(route)
.replyWithError('Fake API hit the production API');
start(modOptions, (err, result) => {
mockAPI = result.app;
done();
});
});
after(function() {
closeMockAPI(mockAPI.servers);
close(mockAPI.servers);
});
it('responds with the specified response rendered as JSON, status 200, and does not hit the production API', function(done) {
request(mockAPI.app)
request(mockAPI)
.post(route)

@@ -305,38 +337,66 @@ .send(reqBody)

describe('#close', function() {
describe('without arguments', function() {
it('closes and garbage collects all servers instantiated by startMockAPI', function() {
const ports = [1000, 2000, 3000];
const modOptions = Object.assign({}, DEFAULT_OPTIONS, {
ports: ports
describe('close()', function() {
it('calls the callback with an error if there are no servers', function(done) {
close(null, (err) => {
expect(err).to.be.an('error');
done();
});
});
describe('when there are only inactive servers', function() {
before(function(done) {
const ports = [5000, 5001, 5002];
const modOptions = Object.assign({}, DEFAULT_OPTIONS, { ports });
start(modOptions, (err, result) => {
close(result.servers, done);
});
const { servers } = startMockAPI(modOptions);
const closedServers = [];
});
ports.forEach(port => servers[port].on('close', () => closedServers.push(port)));
closeMockAPI();
global.setInterval(1, () => {
expect(ports).to.deep.equal(closedServers);
expect(servers).to.deep.equal({});
it('calls the callback with an error if there are only inactive servers', function(done) {
close(null, (err) => {
expect(err).to.be.an('error');
done();
});
});
});
it('throws an error unless startMockAPI has been previously invoked', function() {
expect(closeMockAPI).to.throw(Error);
describe('without explicitly passing in servers', function() {
let ports, servers;
beforeEach(function(done) {
ports = [1111, 1112, 1113];
const modOptions = Object.assign({}, DEFAULT_OPTIONS, { ports: ports });
start(modOptions, (err, result) => {
servers = result.servers;
done();
});
});
it('marks all running servers as inactive', function(done) {
close(null, () => {
expect(servers.filter(server => server.active)).to.have.length(0);
done();
});
});
});
describe('with arguments', function() {
it('closes and garbage collects all servers passed to it', function() {
describe('explicitly passing in servers', function() {
it('marks all servers passed to it as inactive', function(done) {
const mockServer = {
close() {}
close(callback) {
return callback();
}
};
spyOn(mockServer, 'close');
const servers = {
4567: mockServer
};
expect(mockServer.close).to.have.been.called;
closeMockAPI(servers);
global.setInterval(1, () => {
expect(servers).to.deep.equal({});
const servers = [
{
port: 4567,
server: mockServer,
active: true
}
];
close(servers, () => {
expect(mockServer.close).to.have.been.called;
expect(servers.filter(server => server.active)).to.have.length(0);
done();
});

@@ -343,0 +403,0 @@ });

@@ -8,2 +8,3 @@ import 'babel-polyfill';

import fs from 'fs';
import { parallel } from 'async';

@@ -14,3 +15,3 @@ let PROD_ROOT_URL;

let QUIET_MODE;
const SERVERS = {};
const SERVERS = [];
const REQUIRED_CONFIG_OPTIONS = [

@@ -22,4 +23,8 @@ 'prodRootURL',

module.exports = {
start(options) {
throwIfMissingOptions(options);
start(options, callback) {
const error = generateMissingParamsError(options, callback);
if (error) {
return callback(error);
}
const app = express();

@@ -55,2 +60,3 @@ const defaults = {

}
app.get('*', (req, res) => {

@@ -67,29 +73,44 @@ const path = getURLPathWithQueryString(req);

});
startListening(app, ports);
return {
const result = {
app: app,
servers: SERVERS
};
return startListening(app, ports, err => callback(err, result));
},
close(clientServers) {
close(clientServers, callback) {
const servers = clientServers || SERVERS;
const ports = Object.keys(servers);
if (!ports.length) {
throw Error('closeMockAPI invoked without arguments or open servers');
const activeServers = servers.filter(server => server.active);
if (activeServers.length === 0) {
return callback(Error('close() invoked without arguments or open servers'));
}
ports.forEach(port => {
console.info(`Closing mock API server on port ${port}`);
servers[port].close();
delete servers[port];
const tasks = activeServers.map(serverEntry => {
const { server, port } = serverEntry;
return (callback) => {
console.info(`Closing mock API server on port ${port}`);
return server.close(err => {
serverEntry.active = false;
callback(err);
});
};
});
return parallel(tasks, callback);
}
}
function throwIfMissingOptions(options) {
REQUIRED_CONFIG_OPTIONS.forEach(key => {
function generateMissingParamsError(options, callback) {
if (typeof callback !== 'function') {
return new Error('Missing callback');
}
for (let i = 0; i < REQUIRED_CONFIG_OPTIONS.length; i++) {
const key = REQUIRED_CONFIG_OPTIONS[i];
if (typeof options[key] !== 'string') {
throw Error(`Missing definition of ${key} in config file`);
return new Error(`Missing definition of ${key} in config file`);
}
});
}
return null;
}

@@ -109,17 +130,26 @@

function startListening(app, ports) {
ports.forEach(port => {
if (SERVERS.hasOwnProperty(port)) {
console.warn(`Port ${port} specified more than once in config file`);
return;
function startListening(app, ports, callback) {
const tasks = ports.map(port => {
return (callback) => {
const activeServers = SERVERS.filter(server => server.active);
if (activeServers.map(server => server.port).includes(port)) {
console.warn(`Port ${port} specified more than once in config file`);
return;
}
const server = app.listen(port, (err) => {
if (err) {
callback(err);
} else {
console.info(`Mock API server listening on port ${port}`);
callback(null);
}
});
SERVERS.push({
port,
server,
active: true
});
}
const server = app.listen(port, (err) => {
if (err) {
console.error(err);
} else {
console.info(`Mock API server listening on port ${port}`);
}
});
SERVERS[port] = server;
});
return parallel(tasks, callback);
}

@@ -126,0 +156,0 @@

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