From 4323b0030becbb33f1498149b74eb568565d575a Mon Sep 17 00:00:00 2001 From: Peter Johnson Date: Fri, 12 Sep 2014 19:14:03 +0100 Subject: [PATCH 01/15] progress commit --- controller/index.js | 5 +++- controller/suggest.js | 37 ++++++++++++----------- express.js | 14 --------- index.js | 50 +++++++++++++++++++++++++++++++- middleware/404.js | 8 +++++ middleware/500.js | 9 ++++++ package.json | 3 +- sanitiser/suggest.js | 14 +++++---- src/responder.js | 33 --------------------- test/ciao/404.coffee | 17 +++++++++++ test/ciao/cors.coffee | 9 ++++++ test/ciao/index.coffee | 6 ++++ test/ciao/jsonp.coffee | 10 +++++++ test/ciao/suggest/success.coffee | 15 ++++++++++ 14 files changed, 158 insertions(+), 72 deletions(-) delete mode 100644 express.js create mode 100644 middleware/404.js create mode 100644 middleware/500.js delete mode 100644 src/responder.js create mode 100644 test/ciao/404.coffee create mode 100644 test/ciao/cors.coffee create mode 100644 test/ciao/jsonp.coffee create mode 100644 test/ciao/suggest/success.coffee diff --git a/controller/index.js b/controller/index.js index c3267192..6936a982 100644 --- a/controller/index.js +++ b/controller/index.js @@ -1,13 +1,16 @@ var pkg = require('../package'); -function controller( req, res ){ +function controller( req, res, next ){ + + // stats res.json({ name: pkg.name, version: { number: pkg.version } }); + } module.exports = controller; \ No newline at end of file diff --git a/controller/suggest.js b/controller/suggest.js index f2a2230c..c6967186 100644 --- a/controller/suggest.js +++ b/controller/suggest.js @@ -1,32 +1,35 @@ -var logger = require('../src/logger'), - responder = require('../src/responder'), - query = require('../query/suggest'), +var query = require('../query/suggest'), backend = require('../src/backend'); -module.exports = function( req, res, next ){ - - var reply = { - date: new Date().getTime(), - body: [] - }; +function controller( req, res, next ){ + // backend command var cmd = { index: 'pelias', - body: query( req.clean ) // generate query from clean params + body: query( req.clean ) }; - // Proxy request to ES backend & map response to a valid FeatureCollection + // query backend backend().client.suggest( cmd, function( err, data ){ - if( err ){ return responder.error( req, res, next, err ); } - if( data && data.pelias && data.pelias.length ){ + var docs = []; - // map options to reply body - reply.body = data['pelias'][0].options; + // handle backend errors + if( err ){ return next( err ); } + + // map response to a valid FeatureCollection + if( data && Array.isArray( data.pelias ) && data.pelias.length ){ + docs = data['pelias'][0].options || []; } - return responder.cors( req, res, reply ); + // respond + return res.status(200).json({ + date: new Date().getTime(), + body: docs + }); }); -}; \ No newline at end of file +} + +module.exports = controller; \ No newline at end of file diff --git a/express.js b/express.js deleted file mode 100644 index b48e0560..00000000 --- a/express.js +++ /dev/null @@ -1,14 +0,0 @@ - -var express = require('express'), - app = express(); - -// middleware modules -// app.use( require('cookie-parser')() ); - -// enable client-side caching of 60s by default -app.use(function(req, res, next){ - res.header('Cache-Control','public,max-age=60'); - next(); -}); - -module.exports = app; \ No newline at end of file diff --git a/index.js b/index.js index 15543369..ea0ad29a 100644 --- a/index.js +++ b/index.js @@ -1,5 +1,47 @@ -var app = require('./express'); +var app = require('express')(); + +/** ----------------------- middleware ----------------------- **/ + +// generic headers +app.use(function(req, res, next){ + res.header('Charset','utf8'); + res.header('Access-Control-Allow-Origin', '*'); + res.header('Access-Control-Allow-Methods', 'GET'); + res.header('Access-Control-Allow-Headers', 'X-Requested-With,content-type'); + res.header('Access-Control-Allow-Credentials', true); + res.header('X-Powered-By', 'pelias'); + next(); +}); + +// jsonp middleware +// override json() to handle jsonp +app.use(function(req, res, next){ + + res._json = res.json; + res.json = function( data ){ + + // jsonp + if( req.query && req.query.callback ){ + res.header('Content-type','application/javascript'); + return res.send( req.query.callback + '('+ JSON.stringify( data ) + ');' ); + } + + // regular json + res.header('Content-type','application/json'); + return res._json( data ); + }; + + next(); +}); + +// enable client-side caching of 60s by default +app.use(function(req, res, next){ + res.header('Cache-Control','public,max-age=60'); + next(); +}); + +/** ----------------------- Routes ----------------------- **/ // api root app.get( '/', require('./controller/index') ); @@ -7,4 +49,10 @@ app.get( '/', require('./controller/index') ); // suggest API app.get( '/suggest', require('./sanitiser/suggest'), require('./controller/suggest') ); +/** ----------------------- error middleware ----------------------- **/ + +// handle application errors +app.use( require('./middleware/404') ); +app.use( require('./middleware/500') ); + app.listen( process.env.PORT || 3100 ); \ No newline at end of file diff --git a/middleware/404.js b/middleware/404.js new file mode 100644 index 00000000..e90ed3c3 --- /dev/null +++ b/middleware/404.js @@ -0,0 +1,8 @@ + +// handle not found errors +function middleware(req, res) { + res.header('Cache-Control','public,max-age=300'); // 5 minute cache + res.status(404).json({ error: 'not found: invalid path' }); +} + +module.exports = middleware; \ No newline at end of file diff --git a/middleware/500.js b/middleware/500.js new file mode 100644 index 00000000..d73c5684 --- /dev/null +++ b/middleware/500.js @@ -0,0 +1,9 @@ + +// handle application errors +function middleware(err, req, res, next) { + res.header('Cache-Control','no-cache'); + if( res.statusCode < 400 ){ res.status(500); } + res.json({ error: err }); +} + +module.exports = middleware; \ No newline at end of file diff --git a/package.json b/package.json index c9fb2fc1..559e7aed 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,8 @@ "start": "node index.js", "test": "npm run unit && npm run ciao", "unit": "node test/unit/run.js | tap-spec", - "ciao": "node node_modules/ciao/bin/ciao -c test/ciao.json test/ciao" + "ciao": "node node_modules/ciao/bin/ciao -c test/ciao.json test/ciao", + "docs": "cd test/ciao; node ../../node_modules/ciao/bin/ciao -c ../ciao.json . -d ../../docs" }, "repository": { "type": "git", diff --git a/sanitiser/suggest.js b/sanitiser/suggest.js index ea5296db..d7dfa60a 100644 --- a/sanitiser/suggest.js +++ b/sanitiser/suggest.js @@ -14,7 +14,7 @@ function sanitize( params, cb ){ // input text if('string' !== typeof params.input || !params.input.length){ - return cb( 'invalid input text length, must be >0' ); + return cb( 'invalid param \'input\': text length, must be >0' ); } clean.input = params.input; @@ -33,7 +33,7 @@ function sanitize( params, cb ){ }); for( var x=0; x 90 ){ - return cb( 'invalid lat, must be >0 and <90' ); + return cb( 'invalid param \'lat\': must be >0 and <90' ); } clean.lat = lat; // lon var lon = parseFloat( params.lon, 10 ); if( isNaN( lon ) || lon < -180 || lon > 180 ){ - return cb( 'invalid lon, must be >-180 and <180' ); + return cb( 'invalid param \'lon\': must be >-180 and <180' ); } clean.lon = lon; @@ -68,9 +68,13 @@ function sanitize( params, cb ){ } +// middleware module.exports = function( req, res, next ){ sanitize( req.query, function( err, clean ){ - if( err ){ next( err ); } + if( err ){ + res.status(400); // 400 Bad Request + return next(err); + } req.clean = clean; next(); }); diff --git a/src/responder.js b/src/responder.js deleted file mode 100644 index 80f8d880..00000000 --- a/src/responder.js +++ /dev/null @@ -1,33 +0,0 @@ - -// send a reply that is capable of JSON, CORS and JSONP -function cors( req, res, obj ){ - res.header('Charset','utf8'); - res.header('Cache-Control','public,max-age=60'); - res.header('Access-Control-Allow-Origin', '*'); - res.header('Access-Control-Allow-Methods', 'GET'); - res.header('Access-Control-Allow-Headers', 'X-Requested-With,content-type'); - res.header('Access-Control-Allow-Credentials', true); - res.header('X-Powered-By', 'pelias'); - - // jsonp - if( req.query && req.query.callback ){ - res.header('Content-type','application/javascript'); - return res.send( req.query.callback + '('+ JSON.stringify( obj ) + ');' ); - } - - // regular json - res.header('Content-type','application/json'); - return res.json( obj ); -} - -// send an error -function error( req, res, next, err ){ - console.error( 'application error:', err ); - // mask error from user (contains paths) - return cors( req, res, { error: 'application error' } ); -} - -module.exports = { - cors: cors, - error: error -}; \ No newline at end of file diff --git a/test/ciao/404.coffee b/test/ciao/404.coffee new file mode 100644 index 00000000..764bc8d5 --- /dev/null +++ b/test/ciao/404.coffee @@ -0,0 +1,17 @@ + +#> invalid path +path: '/notexist' + +#? not found +response.statusCode.should.equal 404 + +#? content-type header correctly set +response.should.have.header 'Content-Type','application/json; charset=utf-8' + +#? cache-control header correctly set +response.should.have.header 'Cache-Control','public,max-age=300' + +#? should respond in json with server info +should.exist json +should.exist json.error +json.error.should.equal 'not found: invalid path' \ No newline at end of file diff --git a/test/ciao/cors.coffee b/test/ciao/cors.coffee new file mode 100644 index 00000000..bd97c5de --- /dev/null +++ b/test/ciao/cors.coffee @@ -0,0 +1,9 @@ + +#> cross-origin resource sharing +path: '/' + +#? access control headers correctly set +response.should.have.header 'Access-Control-Allow-Origin','*' +response.should.have.header 'Access-Control-Allow-Methods','GET' +response.should.have.header 'Access-Control-Allow-Headers','X-Requested-With,content-type' +response.should.have.header 'Access-Control-Allow-Credentials','true' \ No newline at end of file diff --git a/test/ciao/index.coffee b/test/ciao/index.coffee index 960f6551..2a9dba88 100644 --- a/test/ciao/index.coffee +++ b/test/ciao/index.coffee @@ -8,9 +8,15 @@ response.statusCode.should.equal 200 #? content-type header correctly set response.should.have.header 'Content-Type','application/json; charset=utf-8' +#? charset header correctly set +response.should.have.header 'Charset','utf8' + #? cache-control header correctly set response.should.have.header 'Cache-Control','public,max-age=60' +#? vanity header correctly set +response.should.have.header 'X-Powered-By','pelias' + #? should respond in json with server info should.exist json should.exist json.name diff --git a/test/ciao/jsonp.coffee b/test/ciao/jsonp.coffee new file mode 100644 index 00000000..d83f0eaa --- /dev/null +++ b/test/ciao/jsonp.coffee @@ -0,0 +1,10 @@ + +#> jsonp +path: '/?callback=test' + +#? content-type header correctly set +response.should.have.header 'Content-Type','application/javascript; charset=utf-8' + +#? should respond with jsonp +should.exist response.body +response.body.substr(0,5).should.equal 'test('; \ No newline at end of file diff --git a/test/ciao/suggest/success.coffee b/test/ciao/suggest/success.coffee new file mode 100644 index 00000000..b5a58c44 --- /dev/null +++ b/test/ciao/suggest/success.coffee @@ -0,0 +1,15 @@ + +#> valid suggest query +path: '/suggest?input=a&lat=0&lon=0' + +#? 200 ok +response.statusCode.should.equal 200 + +#? valid response +now = new Date().getTime() +should.exist json +should.not.exist json.error +should.exist json.date +json.date.should.be.within now-1000, now+1000 +should.exist json.body +json.body.should.be.instanceof Array \ No newline at end of file From 05fba480e9cb08b861f2ae02a707d8741ef6c4d2 Mon Sep 17 00:00:00 2001 From: Peter Johnson Date: Fri, 12 Sep 2014 19:14:33 +0100 Subject: [PATCH 02/15] add docs --- docs/404.md | 65 ++++++++++++++++++ docs/cors.md | 54 +++++++++++++++ docs/index.md | 78 ++++++++++++++++++++++ docs/jsonp.md | 52 +++++++++++++++ docs/suggest/success.md | 141 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 390 insertions(+) create mode 100644 docs/404.md create mode 100644 docs/cors.md create mode 100644 docs/index.md create mode 100644 docs/jsonp.md create mode 100644 docs/suggest/success.md diff --git a/docs/404.md b/docs/404.md new file mode 100644 index 00000000..4694f1a2 --- /dev/null +++ b/docs/404.md @@ -0,0 +1,65 @@ +# invalid path + +*Generated: Fri Sep 12 2014 19:14:09 GMT+0100 (BST)* +## Request +```javascript +{ + "protocol": "http:", + "host": "localhost", + "method": "GET", + "port": 3100, + "path": "/notexist", + "headers": { + "User-Agent": "Ciao/Client 1.0" + } +} +``` + +## Response +```javascript +Status: 404 +{ + "x-powered-by": "pelias", + "charset": "utf8", + "access-control-allow-origin": "*", + "access-control-allow-methods": "GET", + "access-control-allow-headers": "X-Requested-With,content-type", + "access-control-allow-credentials": "true", + "cache-control": "public,max-age=300", + "content-type": "application/json; charset=utf-8", + "content-length": "35", + "etag": "W/\"23-dfdfa185\"", + "date": "Fri, 12 Sep 2014 18:14:09 GMT", + "connection": "close" +} +``` +```javascript +{ + "error": "not found: invalid path" +} +``` + +## Tests + +### ✓ content-type header correctly set +```javascript +response.should.have.header 'Content-Type','application/json; charset=utf-8' +``` + +### ✓ should respond in json with server info +```javascript +should.exist json +should.exist json.error +json.error.should.equal 'not found: invalid path' +``` + +### ✓ cache-control header correctly set +```javascript +response.should.have.header 'Cache-Control','public,max-age=300' +``` + +### ✓ not found +```javascript +response.statusCode.should.equal 404 +``` + diff --git a/docs/cors.md b/docs/cors.md new file mode 100644 index 00000000..77d10ea7 --- /dev/null +++ b/docs/cors.md @@ -0,0 +1,54 @@ +# cross-origin resource sharing + +*Generated: Fri Sep 12 2014 19:14:09 GMT+0100 (BST)* +## Request +```javascript +{ + "protocol": "http:", + "host": "localhost", + "method": "GET", + "port": 3100, + "path": "/", + "headers": { + "User-Agent": "Ciao/Client 1.0" + } +} +``` + +## Response +```javascript +Status: 200 +{ + "x-powered-by": "pelias", + "charset": "utf8", + "access-control-allow-origin": "*", + "access-control-allow-methods": "GET", + "access-control-allow-headers": "X-Requested-With,content-type", + "access-control-allow-credentials": "true", + "cache-control": "public,max-age=60", + "content-type": "application/json; charset=utf-8", + "content-length": "50", + "etag": "W/\"32-85536434\"", + "date": "Fri, 12 Sep 2014 18:14:09 GMT", + "connection": "close" +} +``` +```javascript +{ + "name": "pelias-api", + "version": { + "number": "0.0.0" + } +} +``` + +## Tests + +### ✓ access control headers correctly set +```javascript +response.should.have.header 'Access-Control-Allow-Origin','*' +response.should.have.header 'Access-Control-Allow-Methods','GET' +response.should.have.header 'Access-Control-Allow-Headers','X-Requested-With,content-type' +response.should.have.header 'Access-Control-Allow-Credentials','true' +``` + diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 00000000..917f36f8 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,78 @@ +# api root + +*Generated: Fri Sep 12 2014 19:14:09 GMT+0100 (BST)* +## Request +```javascript +{ + "protocol": "http:", + "host": "localhost", + "method": "GET", + "port": 3100, + "path": "/", + "headers": { + "User-Agent": "Ciao/Client 1.0" + } +} +``` + +## Response +```javascript +Status: 200 +{ + "x-powered-by": "pelias", + "charset": "utf8", + "access-control-allow-origin": "*", + "access-control-allow-methods": "GET", + "access-control-allow-headers": "X-Requested-With,content-type", + "access-control-allow-credentials": "true", + "cache-control": "public,max-age=60", + "content-type": "application/json; charset=utf-8", + "content-length": "50", + "etag": "W/\"32-85536434\"", + "date": "Fri, 12 Sep 2014 18:14:09 GMT", + "connection": "close" +} +``` +```javascript +{ + "name": "pelias-api", + "version": { + "number": "0.0.0" + } +} +``` + +## Tests + +### ✓ endpoint available +```javascript +response.statusCode.should.equal 200 +``` + +### ✓ vanity header correctly set +```javascript +response.should.have.header 'X-Powered-By','pelias' +``` + +### ✓ cache-control header correctly set +```javascript +response.should.have.header 'Cache-Control','public,max-age=60' +``` + +### ✓ should respond in json with server info +```javascript +should.exist json +should.exist json.name +should.exist json.version +``` + +### ✓ content-type header correctly set +```javascript +response.should.have.header 'Content-Type','application/json; charset=utf-8' +``` + +### ✓ charset header correctly set +```javascript +response.should.have.header 'Charset','utf8' +``` + diff --git a/docs/jsonp.md b/docs/jsonp.md new file mode 100644 index 00000000..861785cb --- /dev/null +++ b/docs/jsonp.md @@ -0,0 +1,52 @@ +# jsonp + +*Generated: Fri Sep 12 2014 19:14:09 GMT+0100 (BST)* +## Request +```javascript +{ + "protocol": "http:", + "host": "localhost", + "method": "GET", + "port": 3100, + "path": "/?callback=test", + "headers": { + "User-Agent": "Ciao/Client 1.0" + } +} +``` + +## Response +```javascript +Status: 200 +{ + "x-powered-by": "pelias", + "charset": "utf8", + "access-control-allow-origin": "*", + "access-control-allow-methods": "GET", + "access-control-allow-headers": "X-Requested-With,content-type", + "access-control-allow-credentials": "true", + "cache-control": "public,max-age=60", + "content-type": "application/javascript; charset=utf-8", + "content-length": "57", + "etag": "W/\"39-b8a2aba1\"", + "date": "Fri, 12 Sep 2014 18:14:09 GMT", + "connection": "close" +} +``` +```html +test({"name":"pelias-api","version":{"number":"0.0.0"}}); +``` + +## Tests + +### ✓ content-type header correctly set +```javascript +response.should.have.header 'Content-Type','application/javascript; charset=utf-8' +``` + +### ✓ should respond with jsonp +```javascript +should.exist response.body +response.body.substr(0,5).should.equal 'test('; +``` + diff --git a/docs/suggest/success.md b/docs/suggest/success.md new file mode 100644 index 00000000..0019f600 --- /dev/null +++ b/docs/suggest/success.md @@ -0,0 +1,141 @@ +# valid suggest query + +*Generated: Fri Sep 12 2014 19:14:09 GMT+0100 (BST)* +## Request +```javascript +{ + "protocol": "http:", + "host": "localhost", + "method": "GET", + "port": 3100, + "path": "/suggest?input=a&lat=0&lon=0", + "headers": { + "User-Agent": "Ciao/Client 1.0" + } +} +``` + +## Response +```javascript +Status: 200 +{ + "x-powered-by": "pelias", + "charset": "utf8", + "access-control-allow-origin": "*", + "access-control-allow-methods": "GET", + "access-control-allow-headers": "X-Requested-With,content-type", + "access-control-allow-credentials": "true", + "cache-control": "public,max-age=60", + "content-type": "application/json; charset=utf-8", + "content-length": "1248", + "etag": "W/\"jtfnMCXDw5frK6L5eD1thg==\"", + "date": "Fri, 12 Sep 2014 18:14:09 GMT", + "connection": "close" +} +``` +```javascript +{ + "date": 1410545649156, + "body": [ + { + "text": "ACRELÂNDIA, Brazil", + "score": 1, + "payload": { + "id": "admin2/708:adm2:br:bra:acrel__ndia", + "geo": "-66.908143,-9.954353" + } + }, + { + "text": "ALTA FLORESTA, Brazil", + "score": 1, + "payload": { + "id": "admin2/2986:adm2:br:bra:alta_floresta", + "geo": "-56.404593,-10.042071" + } + }, + { + "text": "ALTO ALEGRE, Brazil", + "score": 1, + "payload": { + "id": "admin2/4611:adm2:br:bra:alto_alegre", + "geo": "-62.627879,3.103540" + } + }, + { + "text": "ALTO PARAÍSO, Brazil", + "score": 1, + "payload": { + "id": "admin2/4584:adm2:br:bra:alto_para__so", + "geo": "-63.418743,-9.697774" + } + }, + { + "text": "ALVARÃES, Brazil", + "score": 1, + "payload": { + "id": "admin2/832:adm2:br:bra:alvar__es", + "geo": "-65.296384,-3.674615" + } + }, + { + "text": "AMAJARI, Brazil", + "score": 1, + "payload": { + "id": "admin2/4610:adm2:br:bra:amajari", + "geo": "-62.710104,3.724864" + } + }, + { + "text": "AMAZONAS, Brazil", + "score": 1, + "payload": { + "id": "admin1/3232:adm1:br:bra:amazonas", + "geo": "-64.949558,-3.785708" + } + }, + { + "text": "ANAMÃ, Brazil", + "score": 1, + "payload": { + "id": "admin2/834:adm2:br:bra:anam__", + "geo": "-61.683670,-3.473836" + } + }, + { + "text": "ANORI, Brazil", + "score": 1, + "payload": { + "id": "admin2/835:adm2:br:bra:anori", + "geo": "-62.182138,-4.154809" + } + }, + { + "text": "APIACÁS, Brazil", + "score": 1, + "payload": { + "id": "admin2/2992:adm2:br:bra:apiac__s", + "geo": "-57.803447,-8.583036" + } + } + ] +} +``` + +## Tests + +### ✓ 200 ok +```javascript +response.statusCode.should.equal 200 +``` + +### ✓ valid response +```javascript +now = new Date().getTime() +should.exist json +should.not.exist json.error +should.exist json.date +json.date.should.be.within now-1000, now+1000 +should.exist json.body +json.body.should.be.instanceof Array +``` + From 4353d3d7ceb01ebe21a99c0064b2d0da1ee53bae Mon Sep 17 00:00:00 2001 From: Peter Johnson Date: Fri, 12 Sep 2014 19:16:27 +0100 Subject: [PATCH 03/15] add docs --- docs/404.md | 19 ++++++++----------- docs/cors.md | 9 +++------ docs/index.md | 27 ++++++++++++--------------- docs/jsonp.md | 9 +++------ docs/suggest/success.md | 23 ++++++++++------------- 5 files changed, 36 insertions(+), 51 deletions(-) diff --git a/docs/404.md b/docs/404.md index 4694f1a2..3760d337 100644 --- a/docs/404.md +++ b/docs/404.md @@ -1,6 +1,6 @@ # invalid path -*Generated: Fri Sep 12 2014 19:14:09 GMT+0100 (BST)* +*Generated: Fri Sep 12 2014 19:16:14 GMT+0100 (BST)* ## Request ```javascript { @@ -8,10 +8,7 @@ "host": "localhost", "method": "GET", "port": 3100, - "path": "/notexist", - "headers": { - "User-Agent": "Ciao/Client 1.0" - } + "path": "/notexist" } ``` @@ -29,7 +26,7 @@ Status: 404 "content-type": "application/json; charset=utf-8", "content-length": "35", "etag": "W/\"23-dfdfa185\"", - "date": "Fri, 12 Sep 2014 18:14:09 GMT", + "date": "Fri, 12 Sep 2014 18:16:14 GMT", "connection": "close" } ``` @@ -41,11 +38,6 @@ Status: 404 ## Tests -### ✓ content-type header correctly set -```javascript -response.should.have.header 'Content-Type','application/json; charset=utf-8' -``` - ### ✓ should respond in json with server info ```javascript should.exist json @@ -53,6 +45,11 @@ should.exist json.error json.error.should.equal 'not found: invalid path' ``` +### ✓ content-type header correctly set +```javascript +response.should.have.header 'Content-Type','application/json; charset=utf-8' +``` + ### ✓ cache-control header correctly set ```javascript response.should.have.header 'Cache-Control','public,max-age=300' diff --git a/docs/cors.md b/docs/cors.md index 77d10ea7..14f1a4ae 100644 --- a/docs/cors.md +++ b/docs/cors.md @@ -1,6 +1,6 @@ # cross-origin resource sharing -*Generated: Fri Sep 12 2014 19:14:09 GMT+0100 (BST)* +*Generated: Fri Sep 12 2014 19:16:14 GMT+0100 (BST)* ## Request ```javascript { @@ -8,10 +8,7 @@ "host": "localhost", "method": "GET", "port": 3100, - "path": "/", - "headers": { - "User-Agent": "Ciao/Client 1.0" - } + "path": "/" } ``` @@ -29,7 +26,7 @@ Status: 200 "content-type": "application/json; charset=utf-8", "content-length": "50", "etag": "W/\"32-85536434\"", - "date": "Fri, 12 Sep 2014 18:14:09 GMT", + "date": "Fri, 12 Sep 2014 18:16:14 GMT", "connection": "close" } ``` diff --git a/docs/index.md b/docs/index.md index 917f36f8..3ea5e22b 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,6 +1,6 @@ # api root -*Generated: Fri Sep 12 2014 19:14:09 GMT+0100 (BST)* +*Generated: Fri Sep 12 2014 19:16:14 GMT+0100 (BST)* ## Request ```javascript { @@ -8,10 +8,7 @@ "host": "localhost", "method": "GET", "port": 3100, - "path": "/", - "headers": { - "User-Agent": "Ciao/Client 1.0" - } + "path": "/" } ``` @@ -29,7 +26,7 @@ Status: 200 "content-type": "application/json; charset=utf-8", "content-length": "50", "etag": "W/\"32-85536434\"", - "date": "Fri, 12 Sep 2014 18:14:09 GMT", + "date": "Fri, 12 Sep 2014 18:16:14 GMT", "connection": "close" } ``` @@ -44,11 +41,6 @@ Status: 200 ## Tests -### ✓ endpoint available -```javascript -response.statusCode.should.equal 200 -``` - ### ✓ vanity header correctly set ```javascript response.should.have.header 'X-Powered-By','pelias' @@ -59,11 +51,9 @@ response.should.have.header 'X-Powered-By','pelias' response.should.have.header 'Cache-Control','public,max-age=60' ``` -### ✓ should respond in json with server info +### ✓ endpoint available ```javascript -should.exist json -should.exist json.name -should.exist json.version +response.statusCode.should.equal 200 ``` ### ✓ content-type header correctly set @@ -76,3 +66,10 @@ response.should.have.header 'Content-Type','application/json; charset=utf-8' response.should.have.header 'Charset','utf8' ``` +### ✓ should respond in json with server info +```javascript +should.exist json +should.exist json.name +should.exist json.version +``` + diff --git a/docs/jsonp.md b/docs/jsonp.md index 861785cb..a6fe0ead 100644 --- a/docs/jsonp.md +++ b/docs/jsonp.md @@ -1,6 +1,6 @@ # jsonp -*Generated: Fri Sep 12 2014 19:14:09 GMT+0100 (BST)* +*Generated: Fri Sep 12 2014 19:16:14 GMT+0100 (BST)* ## Request ```javascript { @@ -8,10 +8,7 @@ "host": "localhost", "method": "GET", "port": 3100, - "path": "/?callback=test", - "headers": { - "User-Agent": "Ciao/Client 1.0" - } + "path": "/?callback=test" } ``` @@ -29,7 +26,7 @@ Status: 200 "content-type": "application/javascript; charset=utf-8", "content-length": "57", "etag": "W/\"39-b8a2aba1\"", - "date": "Fri, 12 Sep 2014 18:14:09 GMT", + "date": "Fri, 12 Sep 2014 18:16:14 GMT", "connection": "close" } ``` diff --git a/docs/suggest/success.md b/docs/suggest/success.md index 0019f600..762e52e4 100644 --- a/docs/suggest/success.md +++ b/docs/suggest/success.md @@ -1,6 +1,6 @@ # valid suggest query -*Generated: Fri Sep 12 2014 19:14:09 GMT+0100 (BST)* +*Generated: Fri Sep 12 2014 19:16:14 GMT+0100 (BST)* ## Request ```javascript { @@ -8,10 +8,7 @@ "host": "localhost", "method": "GET", "port": 3100, - "path": "/suggest?input=a&lat=0&lon=0", - "headers": { - "User-Agent": "Ciao/Client 1.0" - } + "path": "/suggest?input=a&lat=0&lon=0" } ``` @@ -28,14 +25,14 @@ Status: 200 "cache-control": "public,max-age=60", "content-type": "application/json; charset=utf-8", "content-length": "1248", - "etag": "W/\"jtfnMCXDw5frK6L5eD1thg==\"", - "date": "Fri, 12 Sep 2014 18:14:09 GMT", + "etag": "W/\"htT1UWW77Ibdm7ncnD9KgA==\"", + "date": "Fri, 12 Sep 2014 18:16:14 GMT", "connection": "close" } ``` ```javascript { - "date": 1410545649156, + "date": 1410545774257, "body": [ { "text": "ACRELÂNDIA, Brazil", @@ -123,11 +120,6 @@ Status: 200 ## Tests -### ✓ 200 ok -```javascript -response.statusCode.should.equal 200 -``` - ### ✓ valid response ```javascript now = new Date().getTime() @@ -139,3 +131,8 @@ should.exist json.body json.body.should.be.instanceof Array ``` +### ✓ 200 ok +```javascript +response.statusCode.should.equal 200 +``` + From aca492c054fd7a2868218a273a3936e8d145f363 Mon Sep 17 00:00:00 2001 From: Peter Johnson Date: Fri, 12 Sep 2014 19:16:39 +0100 Subject: [PATCH 04/15] ciao config --- package.json | 2 +- test/ciao.json | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 559e7aed..46c15a5d 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "test": "npm run unit && npm run ciao", "unit": "node test/unit/run.js | tap-spec", "ciao": "node node_modules/ciao/bin/ciao -c test/ciao.json test/ciao", - "docs": "cd test/ciao; node ../../node_modules/ciao/bin/ciao -c ../ciao.json . -d ../../docs" + "docs": "rm -r docs; cd test/ciao; node ../../node_modules/ciao/bin/ciao -c ../ciao.json . -d ../../docs" }, "repository": { "type": "git", diff --git a/test/ciao.json b/test/ciao.json index 11b7e0c9..ddd811ef 100644 --- a/test/ciao.json +++ b/test/ciao.json @@ -2,10 +2,7 @@ "defaults": { "protocol": "http", "host": "localhost", - "port": 3100, - "headers": { - "User-Agent": "Ciao/Client 1.0" - } + "port": 3100 }, "config": {} } \ No newline at end of file From d3cf9523241f0bd8c8f594c80ff229fbf05a2a8a Mon Sep 17 00:00:00 2001 From: Peter Johnson Date: Fri, 12 Sep 2014 20:00:39 +0100 Subject: [PATCH 05/15] readme --- README.md | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 00000000..9b648860 --- /dev/null +++ b/README.md @@ -0,0 +1,55 @@ +# API + +Pelias RESTful API + +## Install Dependencies + +```bash +$ npm install +``` + +## Documentation + +Documentation is provided as [generated markdown](https://github.com/pelias/api/tree/master/docs): + +## Contributing + +Please fork and pull request against upstream master on a feature branch. + +Pretty please; provide unit tests and script fixtures in the `test` directory. + +### Start Server + +```bash +$ npm start +``` + +### Running Unit Tests + +```bash +$ npm run unit +``` + +### Running Functional Tests + +```bash +$ npm run ciao +``` + +### Running All Tests + +```bash +$ npm test +``` + +### Generate API Documentation + +```bash +$ npm run docs +``` + +### Continuous Integration + +Travis tests every release against node version `0.10` + +[![Build Status](https://travis-ci.org/pelias/api.png?branch=master)](https://travis-ci.org/pelias/api) \ No newline at end of file From 004ad6f5f7dd5d46d8ef787752e065ca4ead2182 Mon Sep 17 00:00:00 2001 From: Peter Johnson Date: Fri, 12 Sep 2014 20:03:03 +0100 Subject: [PATCH 06/15] readme --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 9b648860..885c9b7b 100644 --- a/README.md +++ b/README.md @@ -2,16 +2,16 @@ Pelias RESTful API +## Documentation + +[API Documentation](https://github.com/pelias/api/tree/master/docs) + ## Install Dependencies ```bash $ npm install ``` -## Documentation - -Documentation is provided as [generated markdown](https://github.com/pelias/api/tree/master/docs): - ## Contributing Please fork and pull request against upstream master on a feature branch. From a25946cbb6b050df9c071c04c47a73d531ca2432 Mon Sep 17 00:00:00 2001 From: Peter Johnson Date: Fri, 12 Sep 2014 20:51:37 +0100 Subject: [PATCH 07/15] vanity headers --- index.js | 6 ++++-- test/ciao/index.coffee | 6 +++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/index.js b/index.js index ea0ad29a..4a1bc1b2 100644 --- a/index.js +++ b/index.js @@ -1,5 +1,6 @@ -var app = require('express')(); +var pkg = require('./package'), + app = require('express')(); /** ----------------------- middleware ----------------------- **/ @@ -10,7 +11,8 @@ app.use(function(req, res, next){ res.header('Access-Control-Allow-Methods', 'GET'); res.header('Access-Control-Allow-Headers', 'X-Requested-With,content-type'); res.header('Access-Control-Allow-Credentials', true); - res.header('X-Powered-By', 'pelias'); + res.header('Server', 'Pelias/'+pkg.version); + res.header('X-Powered-By', 'mapzen'); next(); }); diff --git a/test/ciao/index.coffee b/test/ciao/index.coffee index 2a9dba88..76797848 100644 --- a/test/ciao/index.coffee +++ b/test/ciao/index.coffee @@ -14,8 +14,12 @@ response.should.have.header 'Charset','utf8' #? cache-control header correctly set response.should.have.header 'Cache-Control','public,max-age=60' +#? server header correctly set +response.should.have.header 'Server' +response.headers.server.should.match /Pelias\/\d{1,2}\.\d{1,2}\.\d{1,2}/ + #? vanity header correctly set -response.should.have.header 'X-Powered-By','pelias' +response.should.have.header 'X-Powered-By','mapzen' #? should respond in json with server info should.exist json From ae7e88979af9a8ec668568b05c240a8f06ed697a Mon Sep 17 00:00:00 2001 From: Peter Johnson Date: Fri, 12 Sep 2014 20:51:51 +0100 Subject: [PATCH 08/15] docs --- docs/404.md | 23 ++++++++++++----------- docs/cors.md | 7 ++++--- docs/index.md | 29 ++++++++++++++++++----------- docs/jsonp.md | 17 +++++++++-------- docs/suggest/success.md | 11 ++++++----- 5 files changed, 49 insertions(+), 38 deletions(-) diff --git a/docs/404.md b/docs/404.md index 3760d337..e683e480 100644 --- a/docs/404.md +++ b/docs/404.md @@ -1,6 +1,6 @@ # invalid path -*Generated: Fri Sep 12 2014 19:16:14 GMT+0100 (BST)* +*Generated: Fri Sep 12 2014 20:51:44 GMT+0100 (BST)* ## Request ```javascript { @@ -16,17 +16,18 @@ ```javascript Status: 404 { - "x-powered-by": "pelias", + "x-powered-by": "mapzen", "charset": "utf8", "access-control-allow-origin": "*", "access-control-allow-methods": "GET", "access-control-allow-headers": "X-Requested-With,content-type", "access-control-allow-credentials": "true", + "server": "Pelias/0.0.0", "cache-control": "public,max-age=300", "content-type": "application/json; charset=utf-8", "content-length": "35", "etag": "W/\"23-dfdfa185\"", - "date": "Fri, 12 Sep 2014 18:16:14 GMT", + "date": "Fri, 12 Sep 2014 19:51:44 GMT", "connection": "close" } ``` @@ -38,11 +39,9 @@ Status: 404 ## Tests -### ✓ should respond in json with server info +### ✓ cache-control header correctly set ```javascript -should.exist json -should.exist json.error -json.error.should.equal 'not found: invalid path' +response.should.have.header 'Cache-Control','public,max-age=300' ``` ### ✓ content-type header correctly set @@ -50,13 +49,15 @@ json.error.should.equal 'not found: invalid path' response.should.have.header 'Content-Type','application/json; charset=utf-8' ``` -### ✓ cache-control header correctly set +### ✓ not found ```javascript -response.should.have.header 'Cache-Control','public,max-age=300' +response.statusCode.should.equal 404 ``` -### ✓ not found +### ✓ should respond in json with server info ```javascript -response.statusCode.should.equal 404 +should.exist json +should.exist json.error +json.error.should.equal 'not found: invalid path' ``` diff --git a/docs/cors.md b/docs/cors.md index 14f1a4ae..0df8dc00 100644 --- a/docs/cors.md +++ b/docs/cors.md @@ -1,6 +1,6 @@ # cross-origin resource sharing -*Generated: Fri Sep 12 2014 19:16:14 GMT+0100 (BST)* +*Generated: Fri Sep 12 2014 20:51:44 GMT+0100 (BST)* ## Request ```javascript { @@ -16,17 +16,18 @@ ```javascript Status: 200 { - "x-powered-by": "pelias", + "x-powered-by": "mapzen", "charset": "utf8", "access-control-allow-origin": "*", "access-control-allow-methods": "GET", "access-control-allow-headers": "X-Requested-With,content-type", "access-control-allow-credentials": "true", + "server": "Pelias/0.0.0", "cache-control": "public,max-age=60", "content-type": "application/json; charset=utf-8", "content-length": "50", "etag": "W/\"32-85536434\"", - "date": "Fri, 12 Sep 2014 18:16:14 GMT", + "date": "Fri, 12 Sep 2014 19:51:44 GMT", "connection": "close" } ``` diff --git a/docs/index.md b/docs/index.md index 3ea5e22b..963ab240 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,6 +1,6 @@ # api root -*Generated: Fri Sep 12 2014 19:16:14 GMT+0100 (BST)* +*Generated: Fri Sep 12 2014 20:51:45 GMT+0100 (BST)* ## Request ```javascript { @@ -16,17 +16,18 @@ ```javascript Status: 200 { - "x-powered-by": "pelias", + "x-powered-by": "mapzen", "charset": "utf8", "access-control-allow-origin": "*", "access-control-allow-methods": "GET", "access-control-allow-headers": "X-Requested-With,content-type", "access-control-allow-credentials": "true", + "server": "Pelias/0.0.0", "cache-control": "public,max-age=60", "content-type": "application/json; charset=utf-8", "content-length": "50", "etag": "W/\"32-85536434\"", - "date": "Fri, 12 Sep 2014 18:16:14 GMT", + "date": "Fri, 12 Sep 2014 19:51:44 GMT", "connection": "close" } ``` @@ -41,9 +42,14 @@ Status: 200 ## Tests -### ✓ vanity header correctly set +### ✓ content-type header correctly set ```javascript -response.should.have.header 'X-Powered-By','pelias' +response.should.have.header 'Content-Type','application/json; charset=utf-8' +``` + +### ✓ endpoint available +```javascript +response.statusCode.should.equal 200 ``` ### ✓ cache-control header correctly set @@ -51,19 +57,20 @@ response.should.have.header 'X-Powered-By','pelias' response.should.have.header 'Cache-Control','public,max-age=60' ``` -### ✓ endpoint available +### ✓ charset header correctly set ```javascript -response.statusCode.should.equal 200 +response.should.have.header 'Charset','utf8' ``` -### ✓ content-type header correctly set +### ✓ server header correctly set ```javascript -response.should.have.header 'Content-Type','application/json; charset=utf-8' +response.should.have.header 'Server' +response.headers.server.should.match /Pelias\/\d{1,2}\.\d{1,2}\.\d{1,2}/ ``` -### ✓ charset header correctly set +### ✓ vanity header correctly set ```javascript -response.should.have.header 'Charset','utf8' +response.should.have.header 'X-Powered-By','mapzen' ``` ### ✓ should respond in json with server info diff --git a/docs/jsonp.md b/docs/jsonp.md index a6fe0ead..a67e5c40 100644 --- a/docs/jsonp.md +++ b/docs/jsonp.md @@ -1,6 +1,6 @@ # jsonp -*Generated: Fri Sep 12 2014 19:16:14 GMT+0100 (BST)* +*Generated: Fri Sep 12 2014 20:51:45 GMT+0100 (BST)* ## Request ```javascript { @@ -16,17 +16,18 @@ ```javascript Status: 200 { - "x-powered-by": "pelias", + "x-powered-by": "mapzen", "charset": "utf8", "access-control-allow-origin": "*", "access-control-allow-methods": "GET", "access-control-allow-headers": "X-Requested-With,content-type", "access-control-allow-credentials": "true", + "server": "Pelias/0.0.0", "cache-control": "public,max-age=60", "content-type": "application/javascript; charset=utf-8", "content-length": "57", "etag": "W/\"39-b8a2aba1\"", - "date": "Fri, 12 Sep 2014 18:16:14 GMT", + "date": "Fri, 12 Sep 2014 19:51:44 GMT", "connection": "close" } ``` @@ -36,14 +37,14 @@ test({"name":"pelias-api","version":{"number":"0.0.0"}}); ## Tests -### ✓ content-type header correctly set -```javascript -response.should.have.header 'Content-Type','application/javascript; charset=utf-8' -``` - ### ✓ should respond with jsonp ```javascript should.exist response.body response.body.substr(0,5).should.equal 'test('; ``` +### ✓ content-type header correctly set +```javascript +response.should.have.header 'Content-Type','application/javascript; charset=utf-8' +``` + diff --git a/docs/suggest/success.md b/docs/suggest/success.md index 762e52e4..abcc5db4 100644 --- a/docs/suggest/success.md +++ b/docs/suggest/success.md @@ -1,6 +1,6 @@ # valid suggest query -*Generated: Fri Sep 12 2014 19:16:14 GMT+0100 (BST)* +*Generated: Fri Sep 12 2014 20:51:45 GMT+0100 (BST)* ## Request ```javascript { @@ -16,23 +16,24 @@ ```javascript Status: 200 { - "x-powered-by": "pelias", + "x-powered-by": "mapzen", "charset": "utf8", "access-control-allow-origin": "*", "access-control-allow-methods": "GET", "access-control-allow-headers": "X-Requested-With,content-type", "access-control-allow-credentials": "true", + "server": "Pelias/0.0.0", "cache-control": "public,max-age=60", "content-type": "application/json; charset=utf-8", "content-length": "1248", - "etag": "W/\"htT1UWW77Ibdm7ncnD9KgA==\"", - "date": "Fri, 12 Sep 2014 18:16:14 GMT", + "etag": "W/\"o9NALcf9i0O3JoLO7pfqog==\"", + "date": "Fri, 12 Sep 2014 19:51:44 GMT", "connection": "close" } ``` ```javascript { - "date": 1410545774257, + "date": 1410551504928, "body": [ { "text": "ACRELÂNDIA, Brazil", From 972d3a2a84c1886345a6982634b8590644f0392d Mon Sep 17 00:00:00 2001 From: Peter Johnson Date: Mon, 15 Sep 2014 16:41:16 +0100 Subject: [PATCH 09/15] refactor --- index.js | 49 ++++++------------------------------------- middleware/cors.js | 10 +++++++++ middleware/headers.js | 12 +++++++++++ middleware/jsonp.js | 24 +++++++++++++++++++++ 4 files changed, 52 insertions(+), 43 deletions(-) create mode 100644 middleware/cors.js create mode 100644 middleware/headers.js create mode 100644 middleware/jsonp.js diff --git a/index.js b/index.js index 4a1bc1b2..e74fed10 100644 --- a/index.js +++ b/index.js @@ -1,49 +1,13 @@ -var pkg = require('./package'), - app = require('express')(); +var app = require('express')(); /** ----------------------- middleware ----------------------- **/ -// generic headers -app.use(function(req, res, next){ - res.header('Charset','utf8'); - res.header('Access-Control-Allow-Origin', '*'); - res.header('Access-Control-Allow-Methods', 'GET'); - res.header('Access-Control-Allow-Headers', 'X-Requested-With,content-type'); - res.header('Access-Control-Allow-Credentials', true); - res.header('Server', 'Pelias/'+pkg.version); - res.header('X-Powered-By', 'mapzen'); - next(); -}); - -// jsonp middleware -// override json() to handle jsonp -app.use(function(req, res, next){ - - res._json = res.json; - res.json = function( data ){ - - // jsonp - if( req.query && req.query.callback ){ - res.header('Content-type','application/javascript'); - return res.send( req.query.callback + '('+ JSON.stringify( data ) + ');' ); - } - - // regular json - res.header('Content-type','application/json'); - return res._json( data ); - }; - - next(); -}); - -// enable client-side caching of 60s by default -app.use(function(req, res, next){ - res.header('Cache-Control','public,max-age=60'); - next(); -}); - -/** ----------------------- Routes ----------------------- **/ +app.use( require('./middleware/headers') ); +app.use( require('./middleware/cors') ); +app.use( require('./middleware/jsonp') ); + +/** ----------------------- routes ----------------------- **/ // api root app.get( '/', require('./controller/index') ); @@ -53,7 +17,6 @@ app.get( '/suggest', require('./sanitiser/suggest'), require('./controller/sugge /** ----------------------- error middleware ----------------------- **/ -// handle application errors app.use( require('./middleware/404') ); app.use( require('./middleware/500') ); diff --git a/middleware/cors.js b/middleware/cors.js new file mode 100644 index 00000000..257bef4b --- /dev/null +++ b/middleware/cors.js @@ -0,0 +1,10 @@ + +function middleware(req, res, next){ + res.header('Access-Control-Allow-Origin', '*'); + res.header('Access-Control-Allow-Methods', 'GET'); + res.header('Access-Control-Allow-Headers', 'X-Requested-With,content-type'); + res.header('Access-Control-Allow-Credentials', true); + next(); +} + +module.exports = middleware; \ No newline at end of file diff --git a/middleware/headers.js b/middleware/headers.js new file mode 100644 index 00000000..4b40dd21 --- /dev/null +++ b/middleware/headers.js @@ -0,0 +1,12 @@ + +var pkg = require('../package'); + +function middleware(req, res, next){ + res.header('Charset','utf8'); + res.header('Cache-Control','public,max-age=60'); + res.header('Server', 'Pelias/'+pkg.version); + res.header('X-Powered-By', 'mapzen'); + next(); +} + +module.exports = middleware; \ No newline at end of file diff --git a/middleware/jsonp.js b/middleware/jsonp.js new file mode 100644 index 00000000..1a54716e --- /dev/null +++ b/middleware/jsonp.js @@ -0,0 +1,24 @@ + +function middleware(req, res, next){ + + // store old json function + var json = res.json.bind(res); + + // replace with jsonp aware function + res.json = function( data ){ + + // jsonp + if( req.query && req.query.callback ){ + res.header('Content-type','application/javascript'); + return res.send( req.query.callback + '('+ JSON.stringify( data ) + ');' ); + } + + // regular json + res.header('Content-type','application/json'); + return json( data ); + }; + + next(); +} + +module.exports = middleware; \ No newline at end of file From 794db085a26181ee85ea5ef06b022ce91a19b76b Mon Sep 17 00:00:00 2001 From: Peter Johnson Date: Thu, 18 Sep 2014 12:18:56 +0100 Subject: [PATCH 10/15] split app from server, add toobusy --- app.js | 24 ++++++++++++++++++++++++ index.js | 26 ++++++-------------------- middleware/toobusy.js | 19 +++++++++++++++++++ package.json | 3 ++- 4 files changed, 51 insertions(+), 21 deletions(-) create mode 100644 app.js create mode 100644 middleware/toobusy.js diff --git a/app.js b/app.js new file mode 100644 index 00000000..30761954 --- /dev/null +++ b/app.js @@ -0,0 +1,24 @@ + +var app = require('express')(); + +/** ----------------------- middleware ----------------------- **/ + +app.use( require('./middleware/toobusy') ); // should be first +app.use( require('./middleware/headers') ); +app.use( require('./middleware/cors') ); +app.use( require('./middleware/jsonp') ); + +/** ----------------------- routes ----------------------- **/ + +// api root +app.get( '/', require('./controller/index') ); + +// suggest API +app.get( '/suggest', require('./sanitiser/suggest'), require('./controller/suggest') ); + +/** ----------------------- error middleware ----------------------- **/ + +app.use( require('./middleware/404') ); +app.use( require('./middleware/500') ); + +app.listen( process.env.PORT || 3100 ); \ No newline at end of file diff --git a/index.js b/index.js index e74fed10..e788c86b 100644 --- a/index.js +++ b/index.js @@ -1,23 +1,9 @@ -var app = require('express')(); +/** cluster webserver across all cores **/ -/** ----------------------- middleware ----------------------- **/ +var cluster = require('cluster'), + app = require('./app'); -app.use( require('./middleware/headers') ); -app.use( require('./middleware/cors') ); -app.use( require('./middleware/jsonp') ); - -/** ----------------------- routes ----------------------- **/ - -// api root -app.get( '/', require('./controller/index') ); - -// suggest API -app.get( '/suggest', require('./sanitiser/suggest'), require('./controller/suggest') ); - -/** ----------------------- error middleware ----------------------- **/ - -app.use( require('./middleware/404') ); -app.use( require('./middleware/500') ); - -app.listen( process.env.PORT || 3100 ); \ No newline at end of file +cluster(app) + .use(cluster.stats()) + .listen( process.env.PORT || 3100 ); \ No newline at end of file diff --git a/middleware/toobusy.js b/middleware/toobusy.js new file mode 100644 index 00000000..bbe6784f --- /dev/null +++ b/middleware/toobusy.js @@ -0,0 +1,19 @@ + +// middleware which blocks requests when the eventloop is too busy +var toobusy = require('toobusy'); + +function middleware(req, res, next){ + if( toobusy() ){ + res.status(503); // Service Unavailable + return next('Server Overwhelmed'); + } + return next(); +} + +// calling .shutdown allows your process to exit normally +process.on('SIGINT', function() { + toobusy.shutdown(); + process.exit(); +}); + +module.exports = middleware; \ No newline at end of file diff --git a/package.json b/package.json index 46c15a5d..32e98b8a 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,8 @@ "dependencies": { "express": "^4.8.8", "geopipes-elasticsearch-backend": "0.0.7", - "pelias-esclient": "0.0.25" + "pelias-esclient": "0.0.25", + "toobusy": "^0.2.4" }, "devDependencies": { "ciao": "^0.3.4", From 18f4f6b25642257f76428a25b2e6a82f89a9172b Mon Sep 17 00:00:00 2001 From: Peter Johnson Date: Thu, 18 Sep 2014 13:30:21 +0100 Subject: [PATCH 11/15] refactor/ add more tests --- app.js | 6 ++-- controller/index.js | 24 ++++++++----- controller/suggest.js | 52 +++++++++++++++------------ index.js | 20 +++++++---- src/backend.js | 11 ++---- test/unit/controller/index.js | 6 ++-- test/unit/controller/suggest.js | 64 +++++++++++++++++++++++++++++++++ test/unit/mock/backend.js | 28 +++++++++++++++ test/unit/mock/query.js | 10 ++++++ test/unit/run.js | 3 +- 10 files changed, 171 insertions(+), 53 deletions(-) create mode 100644 test/unit/controller/suggest.js create mode 100644 test/unit/mock/backend.js create mode 100644 test/unit/mock/query.js diff --git a/app.js b/app.js index 30761954..a951f06b 100644 --- a/app.js +++ b/app.js @@ -11,14 +11,14 @@ app.use( require('./middleware/jsonp') ); /** ----------------------- routes ----------------------- **/ // api root -app.get( '/', require('./controller/index') ); +app.get( '/', require('./controller/index')() ); // suggest API -app.get( '/suggest', require('./sanitiser/suggest'), require('./controller/suggest') ); +app.get( '/suggest', require('./sanitiser/suggest'), require('./controller/suggest')() ); /** ----------------------- error middleware ----------------------- **/ app.use( require('./middleware/404') ); app.use( require('./middleware/500') ); -app.listen( process.env.PORT || 3100 ); \ No newline at end of file +module.exports = app; \ No newline at end of file diff --git a/controller/index.js b/controller/index.js index 6936a982..10f2d9a4 100644 --- a/controller/index.js +++ b/controller/index.js @@ -1,16 +1,22 @@ var pkg = require('../package'); -function controller( req, res, next ){ +function setup(){ - // stats - res.json({ - name: pkg.name, - version: { - number: pkg.version - } - }); + function controller( req, res, next ){ + + // stats + res.json({ + name: pkg.name, + version: { + number: pkg.version + } + }); + + } + + return controller; } -module.exports = controller; \ No newline at end of file +module.exports = setup; \ No newline at end of file diff --git a/controller/suggest.js b/controller/suggest.js index c6967186..b62b2fe6 100644 --- a/controller/suggest.js +++ b/controller/suggest.js @@ -1,35 +1,41 @@ -var query = require('../query/suggest'), - backend = require('../src/backend'); +function setup( backend, query ){ -function controller( req, res, next ){ + // allow overriding of dependencies + backend = backend || require('../src/backend'); + query = query || require('../query/suggest'); - // backend command - var cmd = { - index: 'pelias', - body: query( req.clean ) - }; + function controller( req, res, next ){ - // query backend - backend().client.suggest( cmd, function( err, data ){ + // backend command + var cmd = { + index: 'pelias', + body: query( req.clean ) + }; - var docs = []; + // query backend + backend().client.suggest( cmd, function( err, data ){ - // handle backend errors - if( err ){ return next( err ); } + var docs = []; - // map response to a valid FeatureCollection - if( data && Array.isArray( data.pelias ) && data.pelias.length ){ - docs = data['pelias'][0].options || []; - } + // handle backend errors + if( err ){ return next( err ); } - // respond - return res.status(200).json({ - date: new Date().getTime(), - body: docs + // map response to a valid FeatureCollection + if( data && Array.isArray( data.pelias ) && data.pelias.length ){ + docs = data['pelias'][0].options || []; + } + + // respond + return res.status(200).json({ + date: new Date().getTime(), + body: docs + }); }); - }); + } + + return controller; } -module.exports = controller; \ No newline at end of file +module.exports = setup; \ No newline at end of file diff --git a/index.js b/index.js index e788c86b..3003c068 100644 --- a/index.js +++ b/index.js @@ -1,9 +1,17 @@ -/** cluster webserver across all cores **/ - var cluster = require('cluster'), - app = require('./app'); + app = require('./app'), + multicore = false, + port = ( process.env.PORT || 3100 ); -cluster(app) - .use(cluster.stats()) - .listen( process.env.PORT || 3100 ); \ No newline at end of file +/** cluster webserver across all cores **/ +if( multicore ){ + // @todo: not finished yet + // cluster(app) + // .use(cluster.stats()) + // .listen( process.env.PORT || 3100 ); +} +else { + console.log( 'listening on ' + port ); + app.listen( process.env.PORT || 3100 ); +} \ No newline at end of file diff --git a/src/backend.js b/src/backend.js index 2a9d60ae..d0388966 100644 --- a/src/backend.js +++ b/src/backend.js @@ -1,14 +1,7 @@ var Backend = require('geopipes-elasticsearch-backend'), - backends = {}, - client; - -// set env specific client -if( process.env.NODE_ENV === 'test' ){ - client = require('./pelias-mockclient'); -} else { - client = require('pelias-esclient')(); -} + client = require('pelias-esclient')(), + backends = {}; function getBackend( index, type ){ var key = ( index + ':' + type ); diff --git a/test/unit/controller/index.js b/test/unit/controller/index.js index ef1d139d..e9752511 100644 --- a/test/unit/controller/index.js +++ b/test/unit/controller/index.js @@ -1,17 +1,19 @@ -var controller = require('../../../controller/index'); +var setup = require('../../../controller/index'); module.exports.tests = {}; module.exports.tests.interface = function(test, common) { test('valid interface', function(t) { - t.equal(typeof controller, 'function', 'controller is a function'); + t.equal(typeof setup, 'function', 'setup is a function'); + t.equal(typeof setup(), 'function', 'setup returns a controller'); t.end(); }); }; module.exports.tests.info = function(test, common) { test('returns server info', function(t) { + var controller = setup(); var res = { json: function( json ){ t.equal(typeof json, 'object', 'returns json'); t.equal(typeof json.name, 'string', 'name'); diff --git a/test/unit/controller/suggest.js b/test/unit/controller/suggest.js new file mode 100644 index 00000000..02c40b43 --- /dev/null +++ b/test/unit/controller/suggest.js @@ -0,0 +1,64 @@ + +var setup = require('../../../controller/suggest'), + mockBackend = require('../mock/backend'), + mockQuery = require('../mock/query'); + +module.exports.tests = {}; + +module.exports.tests.interface = function(test, common) { + test('valid interface', function(t) { + t.equal(typeof setup, 'function', 'setup is a function'); + t.equal(typeof setup(), 'function', 'setup returns a controller'); + t.end(); + }); +}; + +// functionally test controller (backend success) +module.exports.tests.functional_success = function(test, common) { + test('functional test', function(t) { + var backend = mockBackend( 'client/suggest/ok/1', function( cmd ){ + t.deepEqual(cmd, { body: { a: 'b' }, index: 'pelias' }, 'correct backend command'); + }); + var controller = setup( backend, mockQuery() ); + var res = { + status: function( code ){ + t.equal(code, 200, 'status set'); + return res; + }, + json: function( json ){ + t.equal(typeof json, 'object', 'returns json'); + t.equal(typeof json.date, 'number', 'date set'); + t.true(Array.isArray(json.body), 'body is array'); + t.deepEqual(json.body, [ { value: 1 }, { value: 2 } ], 'values correctly mapped'); + t.end(); + } + }; + controller( { clean: { a: 'b' } }, res ); + }); +}; + +// functionally test controller (backend failure) +module.exports.tests.functional_failure = function(test, common) { + test('functional test', function(t) { + var backend = mockBackend( 'client/suggest/fail/1', function( cmd ){ + t.deepEqual(cmd, { body: { a: 'b' }, index: 'pelias' }, 'correct backend command'); + }); + var controller = setup( backend, mockQuery() ); + var next = function( message ){ + t.equal(message,'a backend error occurred','error passed to errorHandler'); + t.end(); + }; + controller( { clean: { a: 'b' } }, undefined, next ); + }); +}; + +module.exports.all = function (tape, common) { + + function test(name, testFunction) { + return tape('GET /suggest ' + name, testFunction); + } + + for( var testCase in module.exports.tests ){ + module.exports.tests[testCase](test, common); + } +}; \ No newline at end of file diff --git a/test/unit/mock/backend.js b/test/unit/mock/backend.js new file mode 100644 index 00000000..8d87fedc --- /dev/null +++ b/test/unit/mock/backend.js @@ -0,0 +1,28 @@ + +var responses = {}; +responses['client/suggest/ok/1'] = function( cmd, cb ){ + return cb( undefined, suggestEnvelope([ { value: 1 }, { value: 2 } ]) ); +}; +responses['client/suggest/fail/1'] = function( cmd, cb ){ + return cb( 'a backend error occurred' ); +}; + +function setup( key, cmdCb ){ + function backend( a, b ){ + return { + client: { + suggest: function( cmd, cb ){ + if( 'function' === typeof cmdCb ){ cmdCb( cmd ); } + return responses[key].apply( this, arguments ); + } + } + }; + } + return backend; +} + +function suggestEnvelope( options ){ + return { pelias: [{ options: options }]}; +} + +module.exports = setup; \ No newline at end of file diff --git a/test/unit/mock/query.js b/test/unit/mock/query.js new file mode 100644 index 00000000..2a5f21fd --- /dev/null +++ b/test/unit/mock/query.js @@ -0,0 +1,10 @@ + +function setup(){ + return query; +} + +function query( clean ){ + return clean; +} + +module.exports = setup; \ No newline at end of file diff --git a/test/unit/run.js b/test/unit/run.js index 36045ca9..50bfccfd 100644 --- a/test/unit/run.js +++ b/test/unit/run.js @@ -3,7 +3,8 @@ var tape = require('tape'); var common = {}; var tests = [ - require('./controller/index') + require('./controller/index'), + require('./controller/suggest') ]; tests.map(function(t) { From 42a2387003f8ab8ecd519339753de8be60420aec Mon Sep 17 00:00:00 2001 From: Peter Johnson Date: Thu, 18 Sep 2014 13:43:59 +0100 Subject: [PATCH 12/15] tests --- app.js | 2 +- sanitiser/suggest.js | 5 ++- test/unit/run.js | 3 +- test/unit/sanitiser/suggest.js | 57 ++++++++++++++++++++++++++++++++++ 4 files changed, 64 insertions(+), 3 deletions(-) create mode 100644 test/unit/sanitiser/suggest.js diff --git a/app.js b/app.js index a951f06b..842cb385 100644 --- a/app.js +++ b/app.js @@ -14,7 +14,7 @@ app.use( require('./middleware/jsonp') ); app.get( '/', require('./controller/index')() ); // suggest API -app.get( '/suggest', require('./sanitiser/suggest'), require('./controller/suggest')() ); +app.get( '/suggest', require('./sanitiser/suggest').middleware, require('./controller/suggest')() ); /** ----------------------- error middleware ----------------------- **/ diff --git a/sanitiser/suggest.js b/sanitiser/suggest.js index d7dfa60a..a2112965 100644 --- a/sanitiser/suggest.js +++ b/sanitiser/suggest.js @@ -68,8 +68,11 @@ function sanitize( params, cb ){ } +// export function +module.exports = sanitize; + // middleware -module.exports = function( req, res, next ){ +module.exports.middleware = function( req, res, next ){ sanitize( req.query, function( err, clean ){ if( err ){ res.status(400); // 400 Bad Request diff --git a/test/unit/run.js b/test/unit/run.js index 50bfccfd..ffc77c68 100644 --- a/test/unit/run.js +++ b/test/unit/run.js @@ -4,7 +4,8 @@ var common = {}; var tests = [ require('./controller/index'), - require('./controller/suggest') + require('./controller/suggest'), + require('./sanitiser/suggest') ]; tests.map(function(t) { diff --git a/test/unit/sanitiser/suggest.js b/test/unit/sanitiser/suggest.js new file mode 100644 index 00000000..6c76a605 --- /dev/null +++ b/test/unit/sanitiser/suggest.js @@ -0,0 +1,57 @@ + +var sanitize = require('../../../sanitiser/suggest'); + +module.exports.tests = {}; + +module.exports.tests.interface = function(test, common) { + test('sanitize interface', function(t) { + t.equal(typeof sanitize, 'function', 'sanitize is a function'); + t.equal(sanitize.length, 2, 'sanitize interface'); + t.end(); + }); + test('middleware interface', function(t) { + t.equal(typeof sanitize.middleware, 'function', 'middleware is a function'); + t.equal(sanitize.middleware.length, 3, 'sanitize is valid middleware'); + t.end(); + }); +}; + +module.exports.tests.middleware_failure = function(test, common) { + test('middleware failure', function(t) { + var res = { status: function( code ){ + t.equal(code, 400, 'status set'); + }}; + var next = function( message ){ + t.equal(message,'invalid param \'input\': text length, must be >0'); + t.end(); + }; + sanitize.middleware( {}, res, next ); + }); +}; + +module.exports.tests.middleware_success = function(test, common) { + test('middleware success', function(t) { + var req = { query: { input: 'test', lat: 0, lon: 0 }}; + var next = function( message ){ + t.equal(message, undefined, 'no error message set'); + t.deepEqual(req.clean, { + input: 'test', lat: 0, lon: 0, + layers: [ 'geoname', 'osmnode', 'osmway', 'admin0', 'admin1', 'admin2', 'neighborhood' ], + size: 10, zoom: 10 + }); + t.end(); + }; + sanitize.middleware( req, undefined, next ); + }); +}; + +module.exports.all = function (tape, common) { + + function test(name, testFunction) { + return tape('SANTIZE /suggest ' + name, testFunction); + } + + for( var testCase in module.exports.tests ){ + module.exports.tests[testCase](test, common); + } +}; \ No newline at end of file From ffda37adffd7bfee3beec9dc8c30d6235bfd3501 Mon Sep 17 00:00:00 2001 From: Peter Johnson Date: Thu, 18 Sep 2014 14:18:06 +0100 Subject: [PATCH 13/15] tests --- sanitiser/suggest.js | 6 +- test/unit/sanitiser/suggest.js | 160 +++++++++++++++++++++++++++++++-- 2 files changed, 156 insertions(+), 10 deletions(-) diff --git a/sanitiser/suggest.js b/sanitiser/suggest.js index a2112965..85f972f6 100644 --- a/sanitiser/suggest.js +++ b/sanitiser/suggest.js @@ -21,7 +21,7 @@ function sanitize( params, cb ){ // total results var size = parseInt( params.size, 10 ); if( !isNaN( size ) ){ - clean.size = Math.min( size, 40 ); // max + clean.size = Math.min( Math.max( size, 1 ), 40 ); // max } else { clean.size = 10; // default } @@ -33,7 +33,7 @@ function sanitize( params, cb ){ }); for( var x=0; x0', + defaultClean = { input: 'test', lat: 0, layers: [ 'geoname', 'osmnode', 'osmway', 'admin0', 'admin1', 'admin2', 'neighborhood' ], lon: 0, size: 10, zoom: 10 }; module.exports.tests = {}; @@ -16,13 +18,161 @@ module.exports.tests.interface = function(test, common) { }); }; +module.exports.tests.sanitize_input = function(test, common) { + var inputs = { + invalid: [ '', 100, null, undefined, new Date() ], + valid: [ 'a', 'aa', 'aaaaaaaa' ] + }; + inputs.invalid.forEach( function( input ){ + test('invalid input', function(t) { + sanitize({ input: input, lat: 0, lon: 0 }, function( err, clean ){ + t.equal(err, 'invalid param \'input\': text length, must be >0', 'invalid input'); + t.equal(clean, undefined, 'clean not set'); + t.end(); + }); + }); + }); + inputs.valid.forEach( function( input ){ + test('valid input', function(t) { + sanitize({ input: input, lat: 0, lon: 0 }, function( err, clean ){ + var expected = JSON.parse(JSON.stringify( defaultClean )); + expected.input = input; + t.equal(err, undefined, 'no error'); + t.deepEqual(clean, expected, 'clean set correctly'); + t.end(); + }); + }); + }); +}; + +module.exports.tests.sanitize_lat = function(test, common) { + var lats = { + invalid: [ -1, -45, -90, 91, 120, 181 ], + valid: [ 0, 45, 90, -0, '0', '45', '90' ] + }; + lats.invalid.forEach( function( lat ){ + test('invalid lat', function(t) { + sanitize({ input: 'test', lat: lat, lon: 0 }, function( err, clean ){ + t.equal(err, 'invalid param \'lat\': must be >0 and <90', 'invalid latitude'); + t.equal(clean, undefined, 'clean not set'); + t.end(); + }); + }); + }); + lats.valid.forEach( function( lat ){ + test('valid lat', function(t) { + sanitize({ input: 'test', lat: lat, lon: 0 }, function( err, clean ){ + var expected = JSON.parse(JSON.stringify( defaultClean )); + expected.lat = parseFloat( lat ); + t.equal(err, undefined, 'no error'); + t.deepEqual(clean, expected, 'clean set correctly'); + t.end(); + }); + }); + }); +}; + +module.exports.tests.sanitize_lon = function(test, common) { + var lons = { + invalid: [ -360, -181, 181, 360 ], + valid: [ -180, -1, -0, 0, 45, 90, '-180', '0', '180' ] + }; + lons.invalid.forEach( function( lon ){ + test('invalid lon', function(t) { + sanitize({ input: 'test', lat: 0, lon: lon }, function( err, clean ){ + t.equal(err, 'invalid param \'lon\': must be >-180 and <180', 'invalid longitude'); + t.equal(clean, undefined, 'clean not set'); + t.end(); + }); + }); + }); + lons.valid.forEach( function( lon ){ + test('valid lon', function(t) { + sanitize({ input: 'test', lat: 0, lon: lon }, function( err, clean ){ + var expected = JSON.parse(JSON.stringify( defaultClean )); + expected.lon = parseFloat( lon ); + t.equal(err, undefined, 'no error'); + t.deepEqual(clean, expected, 'clean set correctly'); + t.end(); + }); + }); + }); +}; + +module.exports.tests.sanitize_zoom = function(test, common) { + test('invalid zoom value', function(t) { + sanitize({ zoom: 'a', input: 'test', lat: 0, lon: 0 }, function( err, clean ){ + t.equal(clean.zoom, 10, 'default zoom set'); + t.end(); + }); + }); + test('below min zoom value', function(t) { + sanitize({ zoom: -100, input: 'test', lat: 0, lon: 0 }, function( err, clean ){ + t.equal(clean.zoom, 1, 'min zoom set'); + t.end(); + }); + }); + test('above max zoom value', function(t) { + sanitize({ zoom: 9999, input: 'test', lat: 0, lon: 0 }, function( err, clean ){ + t.equal(clean.zoom, 18, 'max zoom set'); + t.end(); + }); + }); +}; + +module.exports.tests.sanitize_size = function(test, common) { + test('invalid size value', function(t) { + sanitize({ size: 'a', input: 'test', lat: 0, lon: 0 }, function( err, clean ){ + t.equal(clean.size, 10, 'default size set'); + t.end(); + }); + }); + test('below min size value', function(t) { + sanitize({ size: -100, input: 'test', lat: 0, lon: 0 }, function( err, clean ){ + t.equal(clean.size, 1, 'min size set'); + t.end(); + }); + }); + test('above max size value', function(t) { + sanitize({ size: 9999, input: 'test', lat: 0, lon: 0 }, function( err, clean ){ + t.equal(clean.size, 40, 'max size set'); + t.end(); + }); + }); +}; + +module.exports.tests.sanitize_layers = function(test, common) { + test('unspecified', function(t) { + sanitize({ layers: undefined, input: 'test', lat: 0, lon: 0 }, function( err, clean ){ + t.deepEqual(clean.layers, defaultClean.layers, 'default layers set'); + t.end(); + }); + }); + test('invalid layer', function(t) { + sanitize({ layers: 'test_layer', input: 'test', lat: 0, lon: 0 }, function( err, clean ){ + var msg = 'invalid param \'layer\': must be one or more of geoname,osmnode,osmway,admin0,admin1,admin2,neighborhood'; + t.equal(err, msg, 'invalid layer requested'); + t.end(); + }); + }); +}; + +module.exports.tests.invalid_params = function(test, common) { + test('invalid input params', function(t) { + sanitize( undefined, function( err, clean ){ + t.equal(err, defaultError, 'handle invalid params gracefully'); + t.end(); + }); + }); +}; + module.exports.tests.middleware_failure = function(test, common) { test('middleware failure', function(t) { var res = { status: function( code ){ t.equal(code, 400, 'status set'); }}; var next = function( message ){ - t.equal(message,'invalid param \'input\': text length, must be >0'); + t.equal(message, defaultError); t.end(); }; sanitize.middleware( {}, res, next ); @@ -34,11 +184,7 @@ module.exports.tests.middleware_success = function(test, common) { var req = { query: { input: 'test', lat: 0, lon: 0 }}; var next = function( message ){ t.equal(message, undefined, 'no error message set'); - t.deepEqual(req.clean, { - input: 'test', lat: 0, lon: 0, - layers: [ 'geoname', 'osmnode', 'osmway', 'admin0', 'admin1', 'admin2', 'neighborhood' ], - size: 10, zoom: 10 - }); + t.deepEqual(req.clean, defaultClean); t.end(); }; sanitize.middleware( req, undefined, next ); From 3cd93315ea5763b9912e6357b95ef071bbdd5bb1 Mon Sep 17 00:00:00 2001 From: Peter Johnson Date: Thu, 18 Sep 2014 14:21:38 +0100 Subject: [PATCH 14/15] refactor --- app.js | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/app.js b/app.js index 842cb385..d9659b17 100644 --- a/app.js +++ b/app.js @@ -8,13 +8,24 @@ app.use( require('./middleware/headers') ); app.use( require('./middleware/cors') ); app.use( require('./middleware/jsonp') ); +/** ----------------------- sanitisers ----------------------- **/ + +var sanitisers = {}; +sanitisers.suggest = require('./sanitiser/suggest'); + +/** ----------------------- controllers ----------------------- **/ + +var controllers = {}; +controllers.index = require('./controller/index'); +controllers.suggest = require('./controller/suggest'); + /** ----------------------- routes ----------------------- **/ // api root -app.get( '/', require('./controller/index')() ); +app.get( '/', controllers.index() ); // suggest API -app.get( '/suggest', require('./sanitiser/suggest').middleware, require('./controller/suggest')() ); +app.get( '/suggest', sanitisers.suggest.middleware, controllers.suggest() ); /** ----------------------- error middleware ----------------------- **/ From 2fd67fcc58ad03352e007acf5050ef08c84f6c1b Mon Sep 17 00:00:00 2001 From: Peter Johnson Date: Thu, 18 Sep 2014 14:50:10 +0100 Subject: [PATCH 15/15] refactor; add tests --- query/suggest.js | 4 ++-- test/unit/query/indeces.js | 23 +++++++++++++++++++++++ test/unit/query/suggest.js | 29 +++++++++++++++++++++++++++++ test/unit/run.js | 4 +++- 4 files changed, 57 insertions(+), 3 deletions(-) create mode 100644 test/unit/query/indeces.js create mode 100644 test/unit/query/suggest.js diff --git a/query/suggest.js b/query/suggest.js index b54e2518..9ba2e5c8 100644 --- a/query/suggest.js +++ b/query/suggest.js @@ -3,7 +3,7 @@ var logger = require('../src/logger'); // Build pelias suggest query function generate( params ){ - + var cmd = { 'pelias' : { 'text' : params.input, @@ -23,7 +23,7 @@ function generate( params ){ } }; - logger.log( 'cmd', JSON.stringify( cmd, null, 2 ) ); + // logger.log( 'cmd', JSON.stringify( cmd, null, 2 ) ); return cmd; } diff --git a/test/unit/query/indeces.js b/test/unit/query/indeces.js new file mode 100644 index 00000000..d074f620 --- /dev/null +++ b/test/unit/query/indeces.js @@ -0,0 +1,23 @@ + +var indeces = require('../../../query/indeces'); + +module.exports.tests = {}; + +module.exports.tests.interface = function(test, common) { + test('valid interface', function(t) { + t.true(Array.isArray(indeces), 'valid array'); + t.equal(indeces.length, 7, 'valid array'); + t.end(); + }); +}; + +module.exports.all = function (tape, common) { + + function test(name, testFunction) { + return tape('indeces ' + name, testFunction); + } + + for( var testCase in module.exports.tests ){ + module.exports.tests[testCase](test, common); + } +}; \ No newline at end of file diff --git a/test/unit/query/suggest.js b/test/unit/query/suggest.js new file mode 100644 index 00000000..ce7f8861 --- /dev/null +++ b/test/unit/query/suggest.js @@ -0,0 +1,29 @@ + +var query = require('../../../query/suggest'); + +module.exports.tests = {}; + +module.exports.tests.interface = function(test, common) { + test('valid interface', function(t) { + t.equal(typeof query, 'function', 'valid function'); + t.end(); + }); +}; + +module.exports.tests.interface = function(test, common) { + test('valid interface', function(t) { + t.equal(typeof query, 'function', 'valid function'); + t.end(); + }); +}; + +module.exports.all = function (tape, common) { + + function test(name, testFunction) { + return tape('suggest query ' + name, testFunction); + } + + for( var testCase in module.exports.tests ){ + module.exports.tests[testCase](test, common); + } +}; \ No newline at end of file diff --git a/test/unit/run.js b/test/unit/run.js index ffc77c68..d9b2041e 100644 --- a/test/unit/run.js +++ b/test/unit/run.js @@ -5,7 +5,9 @@ var common = {}; var tests = [ require('./controller/index'), require('./controller/suggest'), - require('./sanitiser/suggest') + require('./sanitiser/suggest'), + require('./query/indeces'), + require('./query/suggest') ]; tests.map(function(t) {