mocks v2
This commit is contained in:
31
bin/cli.js
31
bin/cli.js
@ -8,22 +8,15 @@ const loadConfig = require('config-master')
|
|||||||
const path = require('path')
|
const path = require('path')
|
||||||
const s = require('string-tools')
|
const s = require('string-tools')
|
||||||
const os = require('os')
|
const os = require('os')
|
||||||
|
const arrayify = require('array-back')
|
||||||
|
|
||||||
const cli = commandLineArgs(cliOptions.definitions)
|
const cli = commandLineArgs(cliOptions.definitions)
|
||||||
const usage = cli.getUsage(cliOptions.usageData)
|
const usage = cli.getUsage(cliOptions.usageData)
|
||||||
const stored = loadConfig('local-web-server')
|
const stored = loadConfig('local-web-server')
|
||||||
const options = collectOptions()
|
const options = collectOptions()
|
||||||
|
|
||||||
// TODO summary line on server launch
|
if (options.misc.help) stop(usage, 0)
|
||||||
|
if (options.misc.config) stop(JSON.stringify(options.server, null, ' '), 0)
|
||||||
if (options.misc.help) {
|
|
||||||
console.log(usage)
|
|
||||||
process.exit(0)
|
|
||||||
}
|
|
||||||
if (options.misc.config) {
|
|
||||||
console.log(JSON.stringify(options.server, null, ' '))
|
|
||||||
process.exit(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
const app = localWebServer({
|
const app = localWebServer({
|
||||||
static: {
|
static: {
|
||||||
@ -48,19 +41,15 @@ const app = localWebServer({
|
|||||||
spa: options.server.spa,
|
spa: options.server.spa,
|
||||||
'no-cache': options.server['no-cache'],
|
'no-cache': options.server['no-cache'],
|
||||||
rewrite: options.server.rewrite,
|
rewrite: options.server.rewrite,
|
||||||
verbose: options.server.verbose
|
verbose: options.server.verbose,
|
||||||
|
mocks: options.server.mocks
|
||||||
})
|
})
|
||||||
|
|
||||||
app
|
app.listen(options.server.port, onServerUp)
|
||||||
.on('verbose', (category, message) => {
|
|
||||||
console.error(ansi.format(s.padRight(category, 14), 'bold'), message)
|
|
||||||
})
|
|
||||||
.listen(options.server.port, onServerUp)
|
|
||||||
|
|
||||||
function halt (err) {
|
function stop (msgs, exitCode) {
|
||||||
console.log(ansi.format(`Error: ${err.message}`, 'red'))
|
arrayify(msgs).forEach(msg => console.error(ansi.format(msg)))
|
||||||
console.log(usage)
|
process.exit(exitCode)
|
||||||
process.exit(1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function onServerUp () {
|
function onServerUp () {
|
||||||
@ -85,7 +74,7 @@ function collectOptions () {
|
|||||||
try {
|
try {
|
||||||
options = cli.parse()
|
options = cli.parse()
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
halt(err)
|
stop([ `[red]{Error}: ${err.message}`, usage ], 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
const builtIn = {
|
const builtIn = {
|
||||||
|
@ -1,7 +1,43 @@
|
|||||||
{
|
{
|
||||||
"rewrite": [
|
"mocks": [
|
||||||
{ "from": "/trees", "to": "/mocks/trees.mock.js" },
|
{
|
||||||
{ "from": "/trees/:id", "to": "/mocks/tree.mock.js" },
|
"route": "/one",
|
||||||
{ "from": "/search", "to": "/mocks/search.mock.js" }
|
"response": {
|
||||||
|
"body": { "id": 1, "name": "whatever" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"route": "/two",
|
||||||
|
"request": { "accepts": "xml" },
|
||||||
|
"response": {
|
||||||
|
"body": "<result id='2' name='whatever' />"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"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"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
8
example/mock/mocks/five.js
Normal file
8
example/mock/mocks/five.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
module.exports = {
|
||||||
|
response: function (ctx, id, name) {
|
||||||
|
this.body = {
|
||||||
|
id: id,
|
||||||
|
name: name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
5
example/mock/mocks/four.js
Normal file
5
example/mock/mocks/four.js
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
module.exports = {
|
||||||
|
response: {
|
||||||
|
body: { id: 2, name: 'eucalyptus', maxHeight: 210 }
|
||||||
|
}
|
||||||
|
}
|
@ -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: '<tree id="2" name="eucalyptus" maxHeight="210"/>'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
@ -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 }
|
|
||||||
}
|
|
||||||
]
|
|
@ -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 }
|
|
||||||
]
|
|
@ -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
|
|
@ -44,7 +44,8 @@ function localWebServer (options) {
|
|||||||
mime: {},
|
mime: {},
|
||||||
forbid: [],
|
forbid: [],
|
||||||
rewrite: [],
|
rewrite: [],
|
||||||
verbose: false
|
verbose: false,
|
||||||
|
mocks: []
|
||||||
}, options)
|
}, options)
|
||||||
|
|
||||||
if (options.verbose) {
|
if (options.verbose) {
|
||||||
@ -106,7 +107,7 @@ function localWebServer (options) {
|
|||||||
app.use(mw.blacklist(options.forbid))
|
app.use(mw.blacklist(options.forbid))
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Cache */
|
/* cache */
|
||||||
if (!options['no-cache']) {
|
if (!options['no-cache']) {
|
||||||
const conditional = require('koa-conditional-get')
|
const conditional = require('koa-conditional-get')
|
||||||
const etag = require('koa-etag')
|
const etag = require('koa-etag')
|
||||||
@ -144,7 +145,21 @@ function localWebServer (options) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Mock Responses */
|
/* 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 */
|
/* serve static files */
|
||||||
if (options.static.root) {
|
if (options.static.root) {
|
||||||
|
@ -3,6 +3,7 @@ const path = require('path')
|
|||||||
const http = require('http')
|
const http = require('http')
|
||||||
const url = require('url')
|
const url = require('url')
|
||||||
const arrayify = require('array-back')
|
const arrayify = require('array-back')
|
||||||
|
const t = require('typical')
|
||||||
const pathToRegexp = require('path-to-regexp')
|
const pathToRegexp = require('path-to-regexp')
|
||||||
const debug = require('debug')('local-web-server')
|
const debug = require('debug')('local-web-server')
|
||||||
|
|
||||||
@ -63,43 +64,6 @@ 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)))
|
|
||||||
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, {
|
|
||||||
request: {
|
|
||||||
method: [ ctx.method, undefined ],
|
|
||||||
accepts: type => ctx.accepts(type)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
/* else take the first mock without a request (no request means 'all requests') */
|
|
||||||
if (!mock) {
|
|
||||||
mock = mocks.find(mock => !mock.request)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mock) {
|
|
||||||
let mockedResponse = mock.response
|
|
||||||
if (t.isFunction(mock.response)) {
|
|
||||||
mockedResponse = new mock.response(ctx)
|
|
||||||
}
|
|
||||||
debug('mock response: %j', mockedResponse)
|
|
||||||
Object.assign(ctx.response, mockedResponse)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return next()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function mime (mimeTypes) {
|
function mime (mimeTypes) {
|
||||||
return function mime (ctx, next) {
|
return function mime (ctx, next) {
|
||||||
return next().then(() => {
|
return next().then(() => {
|
||||||
@ -111,3 +75,42 @@ function mime (mimeTypes) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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')
|
||||||
|
|
||||||
|
/* find a mock with compatible method and accepts */
|
||||||
|
let target = targets.find(target => {
|
||||||
|
return testValue(target, {
|
||||||
|
request: {
|
||||||
|
method: [ ctx.method, undefined ],
|
||||||
|
accepts: type => ctx.accepts(type)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
/* else take the first target without a request (no request means 'all requests') */
|
||||||
|
if (!target) {
|
||||||
|
target = targets.find(target => !target.request)
|
||||||
|
}
|
||||||
|
|
||||||
|
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('target response: %j', mockedResponse)
|
||||||
|
Object.assign(ctx.response, mockedResponse)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user