diff --git a/bin/cli.js b/bin/cli.js
index 1716c69..dff27aa 100755
--- a/bin/cli.js
+++ b/bin/cli.js
@@ -8,22 +8,15 @@ const loadConfig = require('config-master')
const path = require('path')
const s = require('string-tools')
const os = require('os')
+const arrayify = require('array-back')
const cli = commandLineArgs(cliOptions.definitions)
const usage = cli.getUsage(cliOptions.usageData)
const stored = loadConfig('local-web-server')
const options = collectOptions()
-// TODO summary line on server launch
-
-if (options.misc.help) {
- console.log(usage)
- process.exit(0)
-}
-if (options.misc.config) {
- console.log(JSON.stringify(options.server, null, ' '))
- process.exit(0)
-}
+if (options.misc.help) stop(usage, 0)
+if (options.misc.config) stop(JSON.stringify(options.server, null, ' '), 0)
const app = localWebServer({
static: {
@@ -48,19 +41,15 @@ const app = localWebServer({
spa: options.server.spa,
'no-cache': options.server['no-cache'],
rewrite: options.server.rewrite,
- verbose: options.server.verbose
+ verbose: options.server.verbose,
+ mocks: options.server.mocks
})
-app
- .on('verbose', (category, message) => {
- console.error(ansi.format(s.padRight(category, 14), 'bold'), message)
- })
- .listen(options.server.port, onServerUp)
+app.listen(options.server.port, onServerUp)
-function halt (err) {
- console.log(ansi.format(`Error: ${err.message}`, 'red'))
- console.log(usage)
- process.exit(1)
+function stop (msgs, exitCode) {
+ arrayify(msgs).forEach(msg => console.error(ansi.format(msg)))
+ process.exit(exitCode)
}
function onServerUp () {
@@ -85,7 +74,7 @@ function collectOptions () {
try {
options = cli.parse()
} catch (err) {
- halt(err)
+ stop([ `[red]{Error}: ${err.message}`, usage ], 1)
}
const builtIn = {
diff --git a/example/mock/.local-web-server.json b/example/mock/.local-web-server.json
index 4259315..5b49c4f 100644
--- a/example/mock/.local-web-server.json
+++ b/example/mock/.local-web-server.json
@@ -1,7 +1,43 @@
{
- "rewrite": [
- { "from": "/trees", "to": "/mocks/trees.mock.js" },
- { "from": "/trees/:id", "to": "/mocks/tree.mock.js" },
- { "from": "/search", "to": "/mocks/search.mock.js" }
+ "mocks": [
+ {
+ "route": "/one",
+ "response": {
+ "body": { "id": 1, "name": "whatever" }
+ }
+ },
+ {
+ "route": "/two",
+ "request": { "accepts": "xml" },
+ "response": {
+ "body": ""
+ }
+ },
+ {
+ "route": "/three",
+ "targets": [
+ {
+ "request": { "method": "GET" },
+ "response": {
+ "body": { "id": 1, "name": "whatever" }
+ }
+ },
+ {
+ "request": { "method": "POST" },
+ "response": {
+ "status": 400,
+ "body": { "message": "That method is not allowed." }
+ }
+ }
+ ]
+ },
+ {
+ "route": "/four",
+ "module": "/mocks/four.js"
+ },
+ {
+ "route": "/five/:id\\?name=:name",
+ "module": "/mocks/five.js"
+ }
]
}
diff --git a/example/mock/mocks/five.js b/example/mock/mocks/five.js
new file mode 100644
index 0000000..168708d
--- /dev/null
+++ b/example/mock/mocks/five.js
@@ -0,0 +1,8 @@
+module.exports = {
+ response: function (ctx, id, name) {
+ this.body = {
+ id: id,
+ name: name
+ }
+ }
+}
diff --git a/example/mock/mocks/four.js b/example/mock/mocks/four.js
new file mode 100644
index 0000000..4beeb30
--- /dev/null
+++ b/example/mock/mocks/four.js
@@ -0,0 +1,5 @@
+module.exports = {
+ response: {
+ body: { id: 2, name: 'eucalyptus', maxHeight: 210 }
+ }
+}
diff --git a/example/mock/mocks/search.mock.js b/example/mock/mocks/search.mock.js
deleted file mode 100644
index 2fc9a72..0000000
--- a/example/mock/mocks/search.mock.js
+++ /dev/null
@@ -1,20 +0,0 @@
-module.exports = [
- {
- request: {
- accepts: 'json'
- },
- response: {
- status: 200,
- body: { id: 2, name: 'eucalyptus', maxHeight: 210 }
- }
- },
- {
- request: {
- accepts: 'xml'
- },
- response: {
- status: 200,
- body: ''
- }
- }
-]
diff --git a/example/mock/mocks/tree.mock.js b/example/mock/mocks/tree.mock.js
deleted file mode 100644
index f8ac1e2..0000000
--- a/example/mock/mocks/tree.mock.js
+++ /dev/null
@@ -1,25 +0,0 @@
-module.exports = [
- /* CREATE (409 CONFLICT - should be called on the collection) */
- {
- request: { method: 'POST' },
- response: { status: 409 }
- },
- /* READ */
- {
- request: { method: 'GET' },
- response: {
- status: 200,
- body: { id: 2, name: 'eucalyptus', maxHeight: 210 }
- }
- },
- /* UPDATE */
- {
- request: { method: 'PUT' },
- response: { status: 204 }
- },
- /* DELETE */
- {
- request: { method: 'DELETE' },
- response: { status: 204 }
- }
-]
diff --git a/example/mock/mocks/trees.json b/example/mock/mocks/trees.json
deleted file mode 100644
index 5d27582..0000000
--- a/example/mock/mocks/trees.json
+++ /dev/null
@@ -1,7 +0,0 @@
-[
- { "id": 1, "name": "Conifer", "maxHeight": 115 },
- { "id": 2, "name": "Eucalyptus", "maxHeight": 210 },
- { "id": 3, "name": "Ash", "maxHeight": 40 },
- { "id": 4, "name": "Elder", "maxHeight": 5 },
- { "id": 5, "name": "Holly", "maxHeight": 10 }
-]
diff --git a/example/mock/mocks/trees.mock.js b/example/mock/mocks/trees.mock.js
deleted file mode 100644
index 70a1f98..0000000
--- a/example/mock/mocks/trees.mock.js
+++ /dev/null
@@ -1,34 +0,0 @@
-const data = require('./trees')
-
-module.exports = [
- /* CREATE */
- {
- request: { method: 'POST' },
- response: function (ctx) {
- data.push(ctx.request.body)
- this.status = 201
- this.location = '/tree/1'
- }
- },
- /* READ */
- {
- request: { method: 'GET' },
- response: function (ctx) {
- this.status = 200
- this.body = data.filter(tree => tree.maxHeight > Number(ctx.query.tallerThan || 0))
- }
- },
- /* UPDATE (forbidden on collection)*/
- {
- request: { method: 'PUT' },
- response: { status: 404 }
- },
- /* DELETE (forbidden on collection) */
- {
- request: { method: 'DELETE' },
- response: { status: 404 }
- }
-]
-
-// curl -i http://localhost:8000/trees -H 'content-type: application/json' -d '{"id":6, "name":"Oak", "maxHeight": 100 }'
-// curl -i http://localhost:8000/trees
diff --git a/lib/local-web-server.js b/lib/local-web-server.js
index 6f45413..7176aee 100644
--- a/lib/local-web-server.js
+++ b/lib/local-web-server.js
@@ -44,7 +44,8 @@ function localWebServer (options) {
mime: {},
forbid: [],
rewrite: [],
- verbose: false
+ verbose: false,
+ mocks: []
}, options)
if (options.verbose) {
@@ -106,7 +107,7 @@ function localWebServer (options) {
app.use(mw.blacklist(options.forbid))
}
- /* Cache */
+ /* cache */
if (!options['no-cache']) {
const conditional = require('koa-conditional-get')
const etag = require('koa-etag')
@@ -144,7 +145,21 @@ function localWebServer (options) {
}
/* Mock Responses */
- app.use(mw.mockResponses({ root: options.static.root }))
+ options.mocks.forEach(mock => {
+ if (mock.module) {
+ mock.targets = require(path.join(options.static.root, mock.module))
+ }
+
+ if (mock.targets) {
+ app.use(mw.mockResponses(mock.route, mock.targets))
+ } else if (mock.response) {
+ mock.target = {
+ request: mock.request,
+ response: mock.response
+ }
+ app.use(mw.mockResponses(mock.route, mock.target))
+ }
+ })
/* serve static files */
if (options.static.root) {
diff --git a/lib/middleware.js b/lib/middleware.js
index 357b332..44eadbc 100644
--- a/lib/middleware.js
+++ b/lib/middleware.js
@@ -3,6 +3,7 @@ const path = require('path')
const http = require('http')
const url = require('url')
const arrayify = require('array-back')
+const t = require('typical')
const pathToRegexp = require('path-to-regexp')
const debug = require('debug')('local-web-server')
@@ -63,17 +64,30 @@ function blacklist (forbid) {
}
}
-function mockResponses (options) {
- options = options || { root: process.cwd() }
- return function mockResponses (ctx, next) {
- if (/\.mock.js$/.test(ctx.path)) {
- const mocks = arrayify(require(path.join(options.root, ctx.path)))
+function mime (mimeTypes) {
+ return function mime (ctx, next) {
+ return next().then(() => {
+ const reqPathExtension = path.extname(ctx.path).slice(1)
+ Object.keys(mimeTypes).forEach(mimeType => {
+ const extsToOverride = mimeTypes[mimeType]
+ if (extsToOverride.indexOf(reqPathExtension) > -1) ctx.type = mimeType
+ })
+ })
+ }
+}
+
+function mockResponses (route, targets) {
+ targets = arrayify(targets)
+ debug('mock route: %s, targets: %j', route, targets);
+ const pathRe = pathToRegexp(route)
+
+ return function mockResponse (ctx, next) {
+ if (pathRe.test(ctx.url)) {
const testValue = require('test-value')
- const t = require('typical')
/* find a mock with compatible method and accepts */
- let mock = mocks.find(mock => {
- return testValue(mock, {
+ let target = targets.find(target => {
+ return testValue(target, {
request: {
method: [ ctx.method, undefined ],
accepts: type => ctx.accepts(type)
@@ -81,33 +95,22 @@ function mockResponses (options) {
})
})
- /* else take the first mock without a request (no request means 'all requests') */
- if (!mock) {
- mock = mocks.find(mock => !mock.request)
+ /* else take the first target without a request (no request means 'all requests') */
+ if (!target) {
+ target = targets.find(target => !target.request)
}
- if (mock) {
- let mockedResponse = mock.response
- if (t.isFunction(mock.response)) {
- mockedResponse = new mock.response(ctx)
+ if (target) {
+ let mockedResponse = target.response
+ if (t.isFunction(target.response)) {
+ const pathMatches = ctx.url.match(pathRe).slice(1)
+ const ctor = target.response.bind(null, ...[ctx].concat(pathMatches))
+ mockedResponse = new ctor()
}
- debug('mock response: %j', mockedResponse)
+ debug('target response: %j', mockedResponse)
Object.assign(ctx.response, mockedResponse)
}
- } else {
- return next()
}
- }
-}
-
-function mime (mimeTypes) {
- return function mime (ctx, next) {
- return next().then(() => {
- const reqPathExtension = path.extname(ctx.path).slice(1)
- Object.keys(mimeTypes).forEach(mimeType => {
- const extsToOverride = mimeTypes[mimeType]
- if (extsToOverride.indexOf(reqPathExtension) > -1) ctx.type = mimeType
- })
- })
+ return next()
}
}