Comparing version 0.1.2 to 1.0.0
@@ -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 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
39614
929
1
142
9
+ Addedasync@^1.5.2
+ Addedasync@1.5.2(transitive)