From 6558cc9289400586d214335218c2627bcbd75f42 Mon Sep 17 00:00:00 2001 From: Peter Johnson Date: Fri, 10 Oct 2014 16:40:01 +0100 Subject: [PATCH 01/46] progress commit --- app.js | 4 +++ controller/suggest.js | 52 +++++++++++++++++++++------- controller/suggest_admin.js | 50 ++++++++++++++++++++++++++ controller/suggest_poi.js | 46 ++++++++++++++++++++++++ query/suggest_admin.js | 29 ++++++++++++++++ query/{suggest.js => suggest_poi.js} | 0 6 files changed, 169 insertions(+), 12 deletions(-) create mode 100644 controller/suggest_admin.js create mode 100644 controller/suggest_poi.js create mode 100644 query/suggest_admin.js rename query/{suggest.js => suggest_poi.js} (100%) diff --git a/app.js b/app.js index 987b5bcc..e563723b 100644 --- a/app.js +++ b/app.js @@ -20,6 +20,8 @@ sanitisers.reverse = require('./sanitiser/reverse'); var controllers = {}; controllers.index = require('./controller/index'); controllers.suggest = require('./controller/suggest'); +controllers.suggest_poi = require('./controller/suggest_poi'); +controllers.suggest_admin = require('./controller/suggest_admin'); controllers.search = require('./controller/search'); /** ----------------------- routes ----------------------- **/ @@ -29,6 +31,8 @@ app.get( '/', controllers.index() ); // suggest API app.get( '/suggest', sanitisers.suggest.middleware, controllers.suggest() ); +app.get( '/suggest/poi', sanitisers.suggest.middleware, controllers.suggest_poi() ); +app.get( '/suggest/admin', sanitisers.suggest.middleware, controllers.suggest_admin() ); // search API app.get( '/search', sanitisers.search.middleware, controllers.search() ); diff --git a/controller/suggest.js b/controller/suggest.js index 36c3cc98..27da5946 100644 --- a/controller/suggest.js +++ b/controller/suggest.js @@ -3,16 +3,19 @@ var geojsonify = require('../helper/geojsonify').suggest; function setup( backend, query ){ - // allow overriding of dependencies - backend = backend || require('../src/backend'); - query = query || require('../query/suggest'); - function controller( req, res, next ){ - // backend command + // combine the 2 queries + + // allow overriding of dependencies + backend = backend || require('../src/backend'); + var query_admin = require('../query/suggest_admin'); + var query_poi = require('../query/suggest_admin'); + + // **query_poi** command var cmd = { index: 'pelias', - body: query( req.clean ) + body: query_poi( req.clean ) }; // query backend @@ -28,14 +31,39 @@ function setup( backend, query ){ docs = data['pelias'][0].options || []; } - // convert docs to geojson - var geojson = geojsonify( docs ); + // **query_admin** command + var cmd = { + index: 'pelias', + body: query_admin( req.clean ) + }; + + // query backend + backend().client.suggest( cmd, function( err, data ){ + + var docs2 = []; + + // handle backend errors + if( err ){ return next( err ); } + + // map response to a valid FeatureCollection + if( data && Array.isArray( data.pelias ) && data.pelias.length ){ + docs2 = data['pelias'][0].options || []; + } + + /** --- combine 2 doc sets --- **/ + var combined = docs2.slice(0, 3).concat(docs); + + // convert docs to geojson + var geojson = geojsonify( combined ); + + // response envelope + geojson.date = new Date().getTime(); + + // respond + return res.status(200).json( geojson ); - // response envelope - geojson.date = new Date().getTime(); + }); - // respond - return res.status(200).json( geojson ); }); } diff --git a/controller/suggest_admin.js b/controller/suggest_admin.js new file mode 100644 index 00000000..743865a5 --- /dev/null +++ b/controller/suggest_admin.js @@ -0,0 +1,50 @@ + +/** + README: http://www.elasticsearch.org/guide/en/elasticsearch/client/javascript-api/current/api-reference.html#api-suggest +**/ + +var geojsonify = require('../helper/geojsonify').suggest; + +function setup( backend, query ){ + + // allow overriding of dependencies + backend = backend || require('../src/backend'); + query = query || require('../query/suggest_admin'); + + function controller( req, res, next ){ + + // backend command + var cmd = { + index: 'pelias', + body: query( req.clean ) + }; + + // query backend + backend().client.suggest( cmd, function( err, data ){ + + var docs = []; + + // 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 || []; + } + + // convert docs to geojson + var geojson = geojsonify( docs ); + + // response envelope + geojson.date = new Date().getTime(); + + // respond + return res.status(200).json( geojson ); + }); + + } + + return controller; +} + +module.exports = setup; \ No newline at end of file diff --git a/controller/suggest_poi.js b/controller/suggest_poi.js new file mode 100644 index 00000000..9e30b41a --- /dev/null +++ b/controller/suggest_poi.js @@ -0,0 +1,46 @@ + +var geojsonify = require('../helper/geojsonify').suggest; + +function setup( backend, query ){ + + // allow overriding of dependencies + backend = backend || require('../src/backend'); + query = query || require('../query/suggest_poi'); + + function controller( req, res, next ){ + + // backend command + var cmd = { + index: 'pelias', + body: query( req.clean ) + }; + + // query backend + backend().client.suggest( cmd, function( err, data ){ + + var docs = []; + + // 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 || []; + } + + // convert docs to geojson + var geojson = geojsonify( docs ); + + // response envelope + geojson.date = new Date().getTime(); + + // respond + return res.status(200).json( geojson ); + }); + + } + + return controller; +} + +module.exports = setup; \ No newline at end of file diff --git a/query/suggest_admin.js b/query/suggest_admin.js new file mode 100644 index 00000000..bb004fd8 --- /dev/null +++ b/query/suggest_admin.js @@ -0,0 +1,29 @@ + +var logger = require('../src/logger'); + +// Build pelias suggest query +function generate( params ){ + + var cmd = { + 'pelias' : { + 'text' : params.input, + 'completion' : { + 'size' : params.size, + 'field' : 'suggest', + 'context': { + 'dataset': ['admin0','admin1','admin2'], + 'location': { + 'value': [ params.lon, params.lat ], + 'precision': 1 + } + } + } + } + }; + + // logger.log( 'cmd', JSON.stringify( cmd, null, 2 ) ); + return cmd; + +} + +module.exports = generate; \ No newline at end of file diff --git a/query/suggest.js b/query/suggest_poi.js similarity index 100% rename from query/suggest.js rename to query/suggest_poi.js From 8771fc04daa58ad20f6ebd2be777eb729329d493 Mon Sep 17 00:00:00 2001 From: Harish Krishna Date: Fri, 10 Oct 2014 13:04:28 -0400 Subject: [PATCH 02/46] query_poi should require suggest_poi --- controller/suggest.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/controller/suggest.js b/controller/suggest.js index 27da5946..1fc58812 100644 --- a/controller/suggest.js +++ b/controller/suggest.js @@ -10,7 +10,7 @@ function setup( backend, query ){ // allow overriding of dependencies backend = backend || require('../src/backend'); var query_admin = require('../query/suggest_admin'); - var query_poi = require('../query/suggest_admin'); + var query_poi = require('../query/suggest_poi'); // **query_poi** command var cmd = { From 5f8c3da14e849ec3fc3b5d91c104562aa0daa012 Mon Sep 17 00:00:00 2001 From: Harish Krishna Date: Fri, 10 Oct 2014 13:59:43 -0400 Subject: [PATCH 03/46] using async (parallel) --- controller/suggest.js | 63 +++++++++++++++++++------------------------ package.json | 3 ++- 2 files changed, 29 insertions(+), 37 deletions(-) diff --git a/controller/suggest.js b/controller/suggest.js index 1fc58812..8e792ff8 100644 --- a/controller/suggest.js +++ b/controller/suggest.js @@ -1,5 +1,6 @@ var geojsonify = require('../helper/geojsonify').suggest; +var async = require('async'); function setup( backend, query ){ @@ -11,59 +12,49 @@ function setup( backend, query ){ backend = backend || require('../src/backend'); var query_admin = require('../query/suggest_admin'); var query_poi = require('../query/suggest_poi'); - - // **query_poi** command var cmd = { - index: 'pelias', - body: query_poi( req.clean ) + index: 'pelias' }; - - // query backend - backend().client.suggest( cmd, function( err, data ){ - - var docs = []; - - // 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 || []; - } - - // **query_admin** command - var cmd = { - index: 'pelias', - body: query_admin( req.clean ) - }; - + var query_backend = function(cmd, callback) { // query backend backend().client.suggest( cmd, function( err, data ){ - var docs2 = []; + var docs = []; // handle backend errors if( err ){ return next( err ); } // map response to a valid FeatureCollection if( data && Array.isArray( data.pelias ) && data.pelias.length ){ - docs2 = data['pelias'][0].options || []; + docs = data['pelias'][0].options || []; } - /** --- combine 2 doc sets --- **/ - var combined = docs2.slice(0, 3).concat(docs); - - // convert docs to geojson - var geojson = geojsonify( combined ); + callback(null, docs); + }); + }; - // response envelope - geojson.date = new Date().getTime(); + async.parallel({ + admin: function(callback){ + cmd.body = query_admin( req.clean ); + query_backend(cmd, callback); + }, + poi: function(callback){ + cmd.body = query_poi( req.clean ); + query_backend(cmd, callback); + } + }, + function(err, results) { + // results is now equals to: {admin: docs, poi: docs} + var combined = results.poi.slice(0, 3).concat(results.admin); - // respond - return res.status(200).json( geojson ); + // convert docs to geojson + var geojson = geojsonify( combined ); - }); + // response envelope + geojson.date = new Date().getTime(); + // respond + return res.status(200).json( geojson ); }); } diff --git a/package.json b/package.json index 4d0bfce9..aacccf89 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,8 @@ "geojson": "^0.2.0", "geopipes-elasticsearch-backend": "0.0.8", "pelias-esclient": "0.0.25", - "toobusy": "^0.2.4" + "toobusy": "^0.2.4", + "async": "^0.9.0" }, "devDependencies": { "ciao": "^0.3.4", From 81a62482e7b7ec863a8dfd13587df3579cdff38c Mon Sep 17 00:00:00 2001 From: Harish Krishna Date: Fri, 10 Oct 2014 14:11:00 -0400 Subject: [PATCH 04/46] query/suggest does not exist anymore. temp fix - using suggest_poi. changing controller/suggest to controller/suggest_poi as well --- test/unit/controller/suggest.js | 2 +- test/unit/query/suggest.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/unit/controller/suggest.js b/test/unit/controller/suggest.js index f67f5da7..bb3f2cc4 100644 --- a/test/unit/controller/suggest.js +++ b/test/unit/controller/suggest.js @@ -1,5 +1,5 @@ -var setup = require('../../../controller/suggest'), +var setup = require('../../../controller/suggest_poi'), mockBackend = require('../mock/backend'), mockQuery = require('../mock/query'); diff --git a/test/unit/query/suggest.js b/test/unit/query/suggest.js index 74e138ff..bd5bc3a6 100644 --- a/test/unit/query/suggest.js +++ b/test/unit/query/suggest.js @@ -1,5 +1,5 @@ -var generate = require('../../../query/suggest'); +var generate = require('../../../query/suggest_poi'); module.exports.tests = {}; From 6b29c5bbfe2b6372a92c61ca619d24b65c45be3c Mon Sep 17 00:00:00 2001 From: Harish Krishna Date: Mon, 13 Oct 2014 12:14:14 -0400 Subject: [PATCH 05/46] adding 'aliases' - 'poi' & 'admin' for the layers param. --- sanitiser/_layers.js | 25 ++++++++++++++++-- test/unit/sanitiser/sanitise.js | 46 ++++++++++++++++++++++++++++++++- 2 files changed, 68 insertions(+), 3 deletions(-) diff --git a/sanitiser/_layers.js b/sanitiser/_layers.js index c4f5d6eb..20e83f78 100644 --- a/sanitiser/_layers.js +++ b/sanitiser/_layers.js @@ -13,17 +13,38 @@ function sanitize( req ){ // which layers to query if('string' === typeof params.layers && params.layers.length){ + + var alias_layers = ['poi', 'admin']; + var alias_indeces = indeces.concat(alias_layers); + var layers = params.layers.split(',').map( function( layer ){ return layer.toLowerCase(); // lowercase inputs }); for( var x=0; x Date: Mon, 13 Oct 2014 12:33:20 -0400 Subject: [PATCH 06/46] adding type --- controller/search.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/controller/search.js b/controller/search.js index fcb12831..1edb3b7a 100644 --- a/controller/search.js +++ b/controller/search.js @@ -12,7 +12,8 @@ function setup( backend, query ){ // backend command var cmd = { index: 'pelias', - body: query( req.clean ) + body: query( req.clean ), + type: req.clean.layers }; // query backend From 0a58bc9fea61381738ea8ea7f0152a47acdf9f26 Mon Sep 17 00:00:00 2001 From: Harish Krishna Date: Mon, 13 Oct 2014 13:40:26 -0400 Subject: [PATCH 07/46] adding type only if clean.layers are not undefined --- controller/search.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/controller/search.js b/controller/search.js index 1edb3b7a..2554de2d 100644 --- a/controller/search.js +++ b/controller/search.js @@ -12,10 +12,12 @@ function setup( backend, query ){ // backend command var cmd = { index: 'pelias', - body: query( req.clean ), - type: req.clean.layers + body: query( req.clean ) }; + if (req.clean.layers) { + cmd.type = req.clean.layers; + } // query backend backend().client.search( cmd, function( err, data ){ From 1d54574d51eb488fdd216f75cf63bf5a8b760774 Mon Sep 17 00:00:00 2001 From: Harish Krishna Date: Mon, 13 Oct 2014 13:44:44 -0400 Subject: [PATCH 08/46] valid precision - tests grouped together --- test/unit/query/suggest.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/test/unit/query/suggest.js b/test/unit/query/suggest.js index 74e138ff..77c3f099 100644 --- a/test/unit/query/suggest.js +++ b/test/unit/query/suggest.js @@ -63,9 +63,8 @@ module.exports.tests.precision = function(test, common) { {zoom:null, precision:1}, {zoom:undefined, precision:1} ]; - - test_cases.forEach( function( test_case ){ - test('valid precision where zoom = ' + test_case.zoom, function(t) { + test('valid precision', function(t) { + test_cases.forEach( function( test_case ){ var query = generate({ input: 'test', size: 10, lat: 0, lon: 0, zoom:test_case.zoom, @@ -87,9 +86,9 @@ module.exports.tests.precision = function(test, common) { } } }; - t.deepEqual(query, expected, 'valid suggest query'); - t.end(); + t.deepEqual(query, expected, 'valid suggest query for zoom = ' + test_case.zoom); }); + t.end(); }); }; From 51f23c4410ddd6cae3f7e956171e1218dcadf9cc Mon Sep 17 00:00:00 2001 From: Harish Krishna Date: Mon, 13 Oct 2014 14:28:22 -0400 Subject: [PATCH 09/46] expand/ reverse size --- query/reverse.js | 2 +- sanitiser/_size.js | 6 ++++-- sanitiser/reverse.js | 8 ++++++-- test/unit/query/reverse.js | 10 ++++++++++ 4 files changed, 21 insertions(+), 5 deletions(-) diff --git a/query/reverse.js b/query/reverse.js index 21b755da..3ada51be 100644 --- a/query/reverse.js +++ b/query/reverse.js @@ -9,7 +9,7 @@ function generate( params ){ lon: params.lon }; - return queries.distance( centroid, { size: 1 } ); + return queries.distance( centroid, { size: params.size || 1 } ); } module.exports = generate; \ No newline at end of file diff --git a/sanitiser/_size.js b/sanitiser/_size.js index b3e1041e..4bc1ec8e 100644 --- a/sanitiser/_size.js +++ b/sanitiser/_size.js @@ -1,9 +1,11 @@ // validate inputs, convert types and apply defaults -function sanitize( req ){ +function sanitize( req, default_size){ var clean = req.clean || {}; var params= req.query; + var default_size = default_size || 10; + // ensure the input params are a valid object if( Object.prototype.toString.call( params ) !== '[object Object]' ){ params = {}; @@ -14,7 +16,7 @@ function sanitize( req ){ if( !isNaN( size ) ){ clean.size = Math.min( Math.max( size, 1 ), 40 ); // max } else { - clean.size = 10; // default + clean.size = default_size; // default } req.clean = clean; diff --git a/sanitiser/reverse.js b/sanitiser/reverse.js index ae7c1af6..cb3e141f 100644 --- a/sanitiser/reverse.js +++ b/sanitiser/reverse.js @@ -2,8 +2,12 @@ var logger = require('../src/logger'), _sanitize = require('../sanitiser/_sanitize'), sanitiser = { - latlonzoom: require('../sanitiser/_latlonzoom') - }; + latlonzoom: require('../sanitiser/_latlonzoom'), + size: function( req ) { + var size = require('../sanitiser/_size'); + return size(req, 1); + } + }; var sanitize = function(req, cb) { _sanitize(req, sanitiser, cb); } diff --git a/test/unit/query/reverse.js b/test/unit/query/reverse.js index 4b54b646..85a0720c 100644 --- a/test/unit/query/reverse.js +++ b/test/unit/query/reverse.js @@ -58,6 +58,16 @@ module.exports.tests.query = function(test, common) { }; t.deepEqual(query, expected, 'valid reverse query'); + + // test different sizes + var sizes = [1,2,10,undefined,null]; + sizes.forEach( function(size) { + query = generate({ + lat: 29.49136, lon: -82.50622, size: size + }); + expected.size = size ? size : 1; + t.deepEqual(query, expected, 'valid reverse query for size: '+ size); + }); t.end(); }); }; From ceecf5837d33459f4fc0e3297baa874a65f1b7db Mon Sep 17 00:00:00 2001 From: Harish Krishna Date: Mon, 13 Oct 2014 14:35:04 -0400 Subject: [PATCH 10/46] fix indentation --- sanitiser/reverse.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sanitiser/reverse.js b/sanitiser/reverse.js index cb3e141f..b0e7b275 100644 --- a/sanitiser/reverse.js +++ b/sanitiser/reverse.js @@ -2,12 +2,12 @@ var logger = require('../src/logger'), _sanitize = require('../sanitiser/_sanitize'), sanitiser = { - latlonzoom: require('../sanitiser/_latlonzoom'), + latlonzoom: require('../sanitiser/_latlonzoom'), size: function( req ) { var size = require('../sanitiser/_size'); return size(req, 1); } - }; + }; var sanitize = function(req, cb) { _sanitize(req, sanitiser, cb); } @@ -24,4 +24,4 @@ module.exports.middleware = function( req, res, next ){ req.clean = clean; next(); }); -}; \ No newline at end of file +}; From a824d209aaa6ff1ad777842c7531f237bb04b354 Mon Sep 17 00:00:00 2001 From: Harish Krishna Date: Mon, 13 Oct 2014 16:41:25 -0400 Subject: [PATCH 11/46] play with precisions --- controller/suggest.js | 12 ++++++++++-- query/suggest_poi.js | 4 ++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/controller/suggest.js b/controller/suggest.js index 8e792ff8..e163789c 100644 --- a/controller/suggest.js +++ b/controller/suggest.js @@ -41,11 +41,19 @@ function setup( backend, query ){ poi: function(callback){ cmd.body = query_poi( req.clean ); query_backend(cmd, callback); + }, + poi3: function(callback){ + cmd.body = query_poi( req.clean, 3 ); + query_backend(cmd, callback); + }, + poi1: function(callback){ + cmd.body = query_poi( req.clean, 1 ); + query_backend(cmd, callback); } }, function(err, results) { - // results is now equals to: {admin: docs, poi: docs} - var combined = results.poi.slice(0, 3).concat(results.admin); + // results is now equals to: {admin: docs, poi: docs, poi1: docs, poi3: docs} + var combined = results.poi.concat(results.admin).concat(results.poi1).concat(results.poi3); // convert docs to geojson var geojson = geojsonify( combined ); diff --git a/query/suggest_poi.js b/query/suggest_poi.js index ba086f88..29f2f0b3 100644 --- a/query/suggest_poi.js +++ b/query/suggest_poi.js @@ -2,7 +2,7 @@ var logger = require('../src/logger'); // Build pelias suggest query -function generate( params ){ +function generate( params, precision ){ var getPrecision = function(zoom) { switch (true) { @@ -29,7 +29,7 @@ function generate( params ){ 'dataset': params.layers, 'location': { 'value': [ params.lon, params.lat ], - 'precision': getPrecision(params.zoom) + 'precision': precision || getPrecision(params.zoom) } } } From 6b192d598d9fca4147bdbc1b9e86decaf4c737fa Mon Sep 17 00:00:00 2001 From: Harish Krishna Date: Mon, 13 Oct 2014 17:04:17 -0400 Subject: [PATCH 12/46] removing poi3 (mostly redundant) - deduping the results --- controller/suggest.js | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/controller/suggest.js b/controller/suggest.js index e163789c..a97a05e7 100644 --- a/controller/suggest.js +++ b/controller/suggest.js @@ -42,10 +42,6 @@ function setup( backend, query ){ cmd.body = query_poi( req.clean ); query_backend(cmd, callback); }, - poi3: function(callback){ - cmd.body = query_poi( req.clean, 3 ); - query_backend(cmd, callback); - }, poi1: function(callback){ cmd.body = query_poi( req.clean, 1 ); query_backend(cmd, callback); @@ -53,8 +49,18 @@ function setup( backend, query ){ }, function(err, results) { // results is now equals to: {admin: docs, poi: docs, poi1: docs, poi3: docs} - var combined = results.poi.concat(results.admin).concat(results.poi1).concat(results.poi3); - + var combined = results.poi.splice(0,3).concat(results.admin.splice(0,4)).concat(results.poi1.splice(0,4)); + + //dedup + var unique_ids = []; + combined = combined.filter(function(item, pos) { + if (unique_ids.indexOf(item.payload.id) == -1) { + unique_ids.push(item.payload.id); + return true; + } + return false; + }); + // convert docs to geojson var geojson = geojsonify( combined ); From 81c064308f01d406e28312e20771fed7f1d09a71 Mon Sep 17 00:00:00 2001 From: Harish Krishna Date: Mon, 13 Oct 2014 18:02:53 -0400 Subject: [PATCH 13/46] equal distribution (mostly equal to the size requested or less) --- controller/suggest.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/controller/suggest.js b/controller/suggest.js index a97a05e7..e46c8071 100644 --- a/controller/suggest.js +++ b/controller/suggest.js @@ -48,9 +48,10 @@ function setup( backend, query ){ } }, function(err, results) { - // results is now equals to: {admin: docs, poi: docs, poi1: docs, poi3: docs} - var combined = results.poi.splice(0,3).concat(results.admin.splice(0,4)).concat(results.poi1.splice(0,4)); - + var splice_length = req.clean.size / 3; + // results is now equals to: {admin: docs, poi: docs, poi1: docs} + var combined = results.poi.splice(0,splice_length).concat(results.admin.splice(0,splice_length)).concat(results.poi1.splice(0,splice_length)); + //dedup var unique_ids = []; combined = combined.filter(function(item, pos) { From a8642cd97dc9b99b2381274949c7df27ec845b70 Mon Sep 17 00:00:00 2001 From: Harish Krishna Date: Mon, 13 Oct 2014 18:54:32 -0400 Subject: [PATCH 14/46] optional precision param --- query/suggest_admin.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/query/suggest_admin.js b/query/suggest_admin.js index bb004fd8..46883744 100644 --- a/query/suggest_admin.js +++ b/query/suggest_admin.js @@ -2,7 +2,7 @@ var logger = require('../src/logger'); // Build pelias suggest query -function generate( params ){ +function generate( params, precision ){ var cmd = { 'pelias' : { @@ -14,7 +14,7 @@ function generate( params ){ 'dataset': ['admin0','admin1','admin2'], 'location': { 'value': [ params.lon, params.lat ], - 'precision': 1 + 'precision': precision || 1 } } } From e75aac5570415fa1611700858ba85c4aaa56800e Mon Sep 17 00:00:00 2001 From: Harish Krishna Date: Mon, 13 Oct 2014 18:54:49 -0400 Subject: [PATCH 15/46] when query is eual to or less than three letters - give admin* preference (with precision 1,3) and then fallback to pois. --- controller/suggest.js | 72 +++++++++++++++++++++++++++++-------------- 1 file changed, 49 insertions(+), 23 deletions(-) diff --git a/controller/suggest.js b/controller/suggest.js index e46c8071..31ea58e1 100644 --- a/controller/suggest.js +++ b/controller/suggest.js @@ -33,43 +33,69 @@ function setup( backend, query ){ }); }; - async.parallel({ - admin: function(callback){ - cmd.body = query_admin( req.clean ); - query_backend(cmd, callback); - }, - poi: function(callback){ - cmd.body = query_poi( req.clean ); - query_backend(cmd, callback); - }, - poi1: function(callback){ - cmd.body = query_poi( req.clean, 1 ); - query_backend(cmd, callback); - } - }, - function(err, results) { - var splice_length = req.clean.size / 3; - // results is now equals to: {admin: docs, poi: docs, poi1: docs} - var combined = results.poi.splice(0,splice_length).concat(results.admin.splice(0,splice_length)).concat(results.poi1.splice(0,splice_length)); - - //dedup + var dedup = function(combined) { var unique_ids = []; - combined = combined.filter(function(item, pos) { + return combined.filter(function(item, pos) { if (unique_ids.indexOf(item.payload.id) == -1) { unique_ids.push(item.payload.id); return true; } return false; }); - + }; + + var respond = function(data) { // convert docs to geojson - var geojson = geojsonify( combined ); + var geojson = geojsonify( data ); // response envelope geojson.date = new Date().getTime(); // respond return res.status(200).json( geojson ); + }; + + var async_query; + + if (req.clean.input.length < 4) { + async_query = { + a: function(callback){ + cmd.body = query_admin( req.clean, [3,1] ); + query_backend(cmd, callback); + }, + b: function(callback) { + cmd.body = query_poi( req.clean ); + query_backend(cmd, callback); + } + } + } else { + async_query = { + a: function(callback){ + cmd.body = query_poi( req.clean ); + query_backend(cmd, callback); + }, + b: function(callback){ + cmd.body = query_admin( req.clean ); + query_backend(cmd, callback); + }, + c: function(callback){ + cmd.body = query_poi( req.clean, 1 ); + query_backend(cmd, callback); + } + } + } + + async.parallel(async_query, function(err, results) { + var splice_length = parseInt((req.clean.size / Object.keys(results).length), 10); + + // results is equal to: {one: docs, two: docs, three: docs} + var combined = []; + for (keys in results) { + combined = combined.concat(results[keys].splice(0,splice_length)); + } + + combined = dedup(combined); + respond(combined); }); } From db29e85a3c1e5909bde87e2195f07d3fd9af2a84 Mon Sep 17 00:00:00 2001 From: Harish Krishna Date: Mon, 13 Oct 2014 19:20:45 -0400 Subject: [PATCH 16/46] precision as an array [3,1] resulted in mostly precision:1 values. so, splitting it up --- controller/suggest.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/controller/suggest.js b/controller/suggest.js index 31ea58e1..645c0d73 100644 --- a/controller/suggest.js +++ b/controller/suggest.js @@ -60,13 +60,17 @@ function setup( backend, query ){ if (req.clean.input.length < 4) { async_query = { a: function(callback){ - cmd.body = query_admin( req.clean, [3,1] ); + cmd.body = query_admin( req.clean, 4 ); query_backend(cmd, callback); }, b: function(callback) { cmd.body = query_poi( req.clean ); query_backend(cmd, callback); - } + }, + c: function(callback){ + cmd.body = query_admin( req.clean ); + query_backend(cmd, callback); + }, } } else { async_query = { @@ -88,7 +92,7 @@ function setup( backend, query ){ async.parallel(async_query, function(err, results) { var splice_length = parseInt((req.clean.size / Object.keys(results).length), 10); - // results is equal to: {one: docs, two: docs, three: docs} + // results is equal to: {a: docs, b: docs, c: docs} var combined = []; for (keys in results) { combined = combined.concat(results[keys].splice(0,splice_length)); From b6b0c80f30e3be0d37614b1f0360fb794e336462 Mon Sep 17 00:00:00 2001 From: Harish Krishna Date: Tue, 14 Oct 2014 15:19:41 -0400 Subject: [PATCH 17/46] 5/3/1 precisions for suggest that start with a number or if more than 3 characters. if its less than 3 characters give preference to admins --- controller/suggest.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/controller/suggest.js b/controller/suggest.js index 645c0d73..1dbe985c 100644 --- a/controller/suggest.js +++ b/controller/suggest.js @@ -57,10 +57,10 @@ function setup( backend, query ){ var async_query; - if (req.clean.input.length < 4) { + if (req.clean.input.length < 4 && isNaN(parseInt(req.clean.input, 10))) { async_query = { a: function(callback){ - cmd.body = query_admin( req.clean, 4 ); + cmd.body = query_admin( req.clean, 3 ); query_backend(cmd, callback); }, b: function(callback) { @@ -75,14 +75,18 @@ function setup( backend, query ){ } else { async_query = { a: function(callback){ - cmd.body = query_poi( req.clean ); + cmd.body = query_poi( req.clean, 5); query_backend(cmd, callback); }, b: function(callback){ - cmd.body = query_admin( req.clean ); + cmd.body = query_poi( req.clean, 3); query_backend(cmd, callback); }, c: function(callback){ + cmd.body = query_admin( req.clean ); + query_backend(cmd, callback); + }, + d: function(callback){ cmd.body = query_poi( req.clean, 1 ); query_backend(cmd, callback); } From b4f7366ba164acd9b22aaa40160be497e9eb34a0 Mon Sep 17 00:00:00 2001 From: Harish Krishna Date: Wed, 15 Oct 2014 10:37:20 -0400 Subject: [PATCH 18/46] fine tuning 5-3-1 and admin logic --- controller/suggest.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/controller/suggest.js b/controller/suggest.js index 1dbe985c..8ce97791 100644 --- a/controller/suggest.js +++ b/controller/suggest.js @@ -63,14 +63,14 @@ function setup( backend, query ){ cmd.body = query_admin( req.clean, 3 ); query_backend(cmd, callback); }, - b: function(callback) { - cmd.body = query_poi( req.clean ); + d: function(callback){ + cmd.body = query_admin( req.clean, 1 ); query_backend(cmd, callback); }, - c: function(callback){ - cmd.body = query_admin( req.clean ); + c: function(callback) { + cmd.body = query_poi( req.clean, 3 ); query_backend(cmd, callback); - }, + } } } else { async_query = { @@ -83,11 +83,11 @@ function setup( backend, query ){ query_backend(cmd, callback); }, c: function(callback){ - cmd.body = query_admin( req.clean ); + cmd.body = query_poi( req.clean, 1 ); query_backend(cmd, callback); }, d: function(callback){ - cmd.body = query_poi( req.clean, 1 ); + cmd.body = query_admin( req.clean ); query_backend(cmd, callback); } } From fb6947e3fe6d1a86e3faa67fd7b30718b4840035 Mon Sep 17 00:00:00 2001 From: Harish Krishna Date: Wed, 15 Oct 2014 13:57:50 -0400 Subject: [PATCH 19/46] /get endpoint - controller and sanitizer --- app.js | 5 +++++ controller/get.js | 44 ++++++++++++++++++++++++++++++++++++++++++++ sanitiser/_id.js | 39 +++++++++++++++++++++++++++++++++++++++ sanitiser/get.js | 23 +++++++++++++++++++++++ 4 files changed, 111 insertions(+) create mode 100644 controller/get.js create mode 100644 sanitiser/_id.js create mode 100644 sanitiser/get.js diff --git a/app.js b/app.js index 987b5bcc..a80a4f82 100644 --- a/app.js +++ b/app.js @@ -11,6 +11,7 @@ app.use( require('./middleware/jsonp') ); /** ----------------------- sanitisers ----------------------- **/ var sanitisers = {}; +sanitisers.get = require('./sanitiser/get'); sanitisers.suggest = require('./sanitiser/suggest'); sanitisers.search = sanitisers.suggest; sanitisers.reverse = require('./sanitiser/reverse'); @@ -19,6 +20,7 @@ sanitisers.reverse = require('./sanitiser/reverse'); var controllers = {}; controllers.index = require('./controller/index'); +controllers.get = require('./controller/get'); controllers.suggest = require('./controller/suggest'); controllers.search = require('./controller/search'); @@ -27,6 +29,9 @@ controllers.search = require('./controller/search'); // api root app.get( '/', controllers.index() ); +// get doc API +app.get( '/get', sanitisers.get.middleware, controllers.get() ); + // suggest API app.get( '/suggest', sanitisers.suggest.middleware, controllers.suggest() ); diff --git a/controller/get.js b/controller/get.js new file mode 100644 index 00000000..57842b93 --- /dev/null +++ b/controller/get.js @@ -0,0 +1,44 @@ + +var geojsonify = require('../helper/geojsonify').search; + +function setup( backend ){ + + // allow overriding of dependencies + backend = backend || require('../src/backend'); + + function controller( req, res, next ){ + + // backend command + var cmd = { + index: 'pelias', + type: req.clean.type, + id: req.clean.id + }; + + // query backend + backend().client.get( cmd, function( err, data ){ + + var docs = []; + // handle backend errors + if( err ){ return next( err ); } + + if( data && data.found && data._source ){ + docs.push(data._source); + } + + // convert docs to geojson + var geojson = geojsonify( docs ); + + // response envelope + geojson.date = new Date().getTime(); + + // respond + return res.status(200).json( geojson ); + }); + + } + + return controller; +} + +module.exports = setup; diff --git a/sanitiser/_id.js b/sanitiser/_id.js new file mode 100644 index 00000000..d2fee937 --- /dev/null +++ b/sanitiser/_id.js @@ -0,0 +1,39 @@ +// validate inputs, convert types and apply defaults +// id generally looks like 'geoname/4163334' (type/id) +// so, both type and id are required fields. + +function sanitize( req ){ + + req.clean = req.clean || {}; + var params= req.query; + + // ensure params is a valid object + if( Object.prototype.toString.call( params ) !== '[object Object]' ){ + params = {}; + } + + var errormessage = function(fieldname) { + return { + 'error': true, + 'message': 'invalid param \''+ fieldname + '\': text length, must be >0' + } + }; + + // id text + if('string' !== typeof params.id || !params.id.length){ + return errormessage('id'); + } + req.clean.id = params.id; + + // type text + if('string' !== typeof params.type || !params.type.length){ + return errormessage('type'); + } + req.clean.type = params.type; + + return { 'error': false }; + +} + +// export function +module.exports = sanitize; \ No newline at end of file diff --git a/sanitiser/get.js b/sanitiser/get.js new file mode 100644 index 00000000..64db6edc --- /dev/null +++ b/sanitiser/get.js @@ -0,0 +1,23 @@ + +var logger = require('../src/logger'), + _sanitize = require('../sanitiser/_sanitize'), + sanitizers = { + id: require('../sanitiser/_id') + }; + +var sanitize = function(req, cb) { _sanitize(req, sanitizers, cb); } + +// export sanitize for testing +module.exports.sanitize = sanitize; + +// middleware +module.exports.middleware = function( req, res, next ){ + sanitize( req, function( err, clean ){ + if( err ){ + res.status(400); // 400 Bad Request + return next(err); + } + req.clean = clean; + next(); + }); +}; \ No newline at end of file From 32e9a13d64ce05d3066e6d7b7138c915f5e55e7a Mon Sep 17 00:00:00 2001 From: Harish Krishna Date: Wed, 15 Oct 2014 14:12:30 -0400 Subject: [PATCH 20/46] type should be one of the indeces --- sanitiser/_id.js | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/sanitiser/_id.js b/sanitiser/_id.js index d2fee937..6620a280 100644 --- a/sanitiser/_id.js +++ b/sanitiser/_id.js @@ -4,21 +4,22 @@ function sanitize( req ){ - req.clean = req.clean || {}; - var params= req.query; + req.clean = req.clean || {}; + var params = req.query; + var indeces = require('../query/indeces'); // ensure params is a valid object if( Object.prototype.toString.call( params ) !== '[object Object]' ){ params = {}; } - var errormessage = function(fieldname) { + var errormessage = function(fieldname, message) { return { 'error': true, - 'message': 'invalid param \''+ fieldname + '\': text length, must be >0' + 'message': message || ('invalid param \''+ fieldname + '\': text length, must be >0') } }; - + // id text if('string' !== typeof params.id || !params.id.length){ return errormessage('id'); @@ -31,6 +32,12 @@ function sanitize( req ){ } req.clean.type = params.type; + // type text must be one of the indeces + if(indeces.indexOf(params.type) == -1){ + return errormessage('type', 'type must be one of these values - [' + indeces.join(", ") + ']'); + } + req.clean.type = params.type; + return { 'error': false }; } From 018ee3b945bfecb7c399324db1fd1d1a1edb625c Mon Sep 17 00:00:00 2001 From: Harish Krishna Date: Wed, 15 Oct 2014 14:55:58 -0400 Subject: [PATCH 21/46] tests - adding get sanitizer tests, renaming sanitiser to suggest because that is what it is really. --- test/unit/run.js | 3 +- test/unit/sanitiser/get.js | 118 ++++++++++++++++++ .../sanitiser/{sanitise.js => suggest.js} | 2 +- 3 files changed, 121 insertions(+), 2 deletions(-) create mode 100644 test/unit/sanitiser/get.js rename test/unit/sanitiser/{sanitise.js => suggest.js} (99%) diff --git a/test/unit/run.js b/test/unit/run.js index 96e9c95a..34b5a92e 100644 --- a/test/unit/run.js +++ b/test/unit/run.js @@ -6,7 +6,8 @@ var tests = [ require('./controller/index'), require('./controller/suggest'), require('./controller/search'), - require('./sanitiser/sanitise'), + require('./sanitiser/suggest'), + require('./sanitiser/get'), require('./query/indeces'), require('./query/suggest'), require('./query/search'), diff --git a/test/unit/sanitiser/get.js b/test/unit/sanitiser/get.js new file mode 100644 index 00000000..d00133c3 --- /dev/null +++ b/test/unit/sanitiser/get.js @@ -0,0 +1,118 @@ + +var get = require('../../../sanitiser/get'), + _sanitize = get.sanitize, + middleware = get.middleware, + indeces = require('../../../query/indeces'), + defaultIdError = 'invalid param \'id\': text length, must be >0', + defaultTypeError = 'invalid param \'type\': text length, must be >0', + defaultError = defaultIdError, + defaultMissingTypeError = 'type must be one of these values - [' + indeces.join(", ") + ']', + defaultClean = { id: '123', type: 'geoname' }, + sanitize = function(query, cb) { _sanitize({'query':query}, cb); } + +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 middleware, 'function', 'middleware is a function'); + t.equal(middleware.length, 3, 'sanitizee has a valid middleware'); + t.end(); + }); +}; + +module.exports.tests.sanitize_id_and_type = function(test, common) { + var inputs = { + valid: [ + {id:'1', type:'geoname'}, + {id:'2', type:'osmnode'}, + {id:'3', type:'geoname'}, + {id:'4', type:'osmway'}, + {id:'5', type:'admin0'} + ], + invalid: [ + {id:undefined, type:undefined}, + {id:null, type:null}, + {id:'', type:''}, + {id:'4', type:''}, + {id:'5', type:'gibberish'} + ] + }; + + inputs.invalid.forEach( function( input ){ + test('invalid id and/or type', function(t) { + sanitize({ id: input.id, type: input.type }, function( err, clean ){ + switch (err) { + case defaultIdError: + t.equal(err, defaultIdError, 'missing id'); break; + case defaultTypeError: + t.equal(err, defaultTypeError, 'missing type'); break; + case defaultMissingTypeError: + t.equal(err, defaultMissingTypeError, 'unknown type'); break; + default: break; + } + t.equal(clean, undefined, 'clean not set'); + t.end(); + }); + }); + }); + inputs.valid.forEach( function( input ){ + test('valid id and/or type', function(t) { + var expected = { id: input.id, type: input.type }; + sanitize({ id: input.id, type: input.type, }, function( err, clean ){ + t.equal(err, undefined, 'no error'); + t.deepEqual(clean, expected, 'clean set correctly'); + t.end(); + }); + }); + }); +}; + +module.exports.tests.invalid_params = function(test, common) { + test('invalid 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, defaultError); + t.end(); + }; + middleware( {}, res, next ); + }); +}; + +module.exports.tests.middleware_success = function(test, common) { + test('middleware success', function(t) { + var req = { query: { id: '123', type: 'geoname' }}; + var next = function( message ){ + t.equal(message, undefined, 'no error message set'); + t.deepEqual(req.clean, defaultClean); + t.end(); + }; + middleware( req, undefined, next ); + }); +}; + +module.exports.all = function (tape, common) { + + function test(name, testFunction) { + return tape('SANTIZE /get ' + 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/sanitiser/sanitise.js b/test/unit/sanitiser/suggest.js similarity index 99% rename from test/unit/sanitiser/sanitise.js rename to test/unit/sanitiser/suggest.js index cae2d8b1..66055b68 100644 --- a/test/unit/sanitiser/sanitise.js +++ b/test/unit/sanitiser/suggest.js @@ -241,7 +241,7 @@ module.exports.tests.middleware_success = function(test, common) { module.exports.all = function (tape, common) { function test(name, testFunction) { - return tape('SANTIZE /sanitise ' + name, testFunction); + return tape('SANTIZE /suggest ' + name, testFunction); } for( var testCase in module.exports.tests ){ From ed5a1923d3adceb982e5bc750a78efc2c47d4d46 Mon Sep 17 00:00:00 2001 From: Harish Krishna Date: Wed, 15 Oct 2014 15:09:49 -0400 Subject: [PATCH 22/46] grouping test cases when appropriate --- test/unit/sanitiser/get.js | 23 ++++++++-------- test/unit/sanitiser/suggest.js | 49 +++++++++++++++++----------------- 2 files changed, 37 insertions(+), 35 deletions(-) diff --git a/test/unit/sanitiser/get.js b/test/unit/sanitiser/get.js index d00133c3..1ed34637 100644 --- a/test/unit/sanitiser/get.js +++ b/test/unit/sanitiser/get.js @@ -43,32 +43,33 @@ module.exports.tests.sanitize_id_and_type = function(test, common) { ] }; - inputs.invalid.forEach( function( input ){ - test('invalid id and/or type', function(t) { + test('invalid id and/or type', function(t) { + inputs.invalid.forEach( function( input ){ sanitize({ id: input.id, type: input.type }, function( err, clean ){ switch (err) { case defaultIdError: - t.equal(err, defaultIdError, 'missing id'); break; + t.equal(err, defaultIdError, input.id + ' is invalid (missing id)'); break; case defaultTypeError: - t.equal(err, defaultTypeError, 'missing type'); break; + t.equal(err, defaultTypeError, input.type + ' is invalid (missing type)'); break; case defaultMissingTypeError: - t.equal(err, defaultMissingTypeError, 'unknown type'); break; + t.equal(err, defaultMissingTypeError, input.type + ' is an unknown type'); break; default: break; } t.equal(clean, undefined, 'clean not set'); - t.end(); }); }); + t.end(); }); - inputs.valid.forEach( function( input ){ - test('valid id and/or type', function(t) { + + test('valid id and/or type', function(t) { + inputs.valid.forEach( function( input ){ var expected = { id: input.id, type: input.type }; sanitize({ id: input.id, type: input.type, }, function( err, clean ){ - t.equal(err, undefined, 'no error'); - t.deepEqual(clean, expected, 'clean set correctly'); - t.end(); + t.equal(err, undefined, 'no error (' + input.id + ', ' + input.type + ')' ); + t.deepEqual(clean, expected, 'clean set correctly (' + input.id + ', ' + input.type + ')'); }); }); + t.end(); }); }; diff --git a/test/unit/sanitiser/suggest.js b/test/unit/sanitiser/suggest.js index 66055b68..52096be2 100644 --- a/test/unit/sanitiser/suggest.js +++ b/test/unit/sanitiser/suggest.js @@ -26,25 +26,25 @@ module.exports.tests.sanitize_input = function(test, common) { invalid: [ '', 100, null, undefined, new Date() ], valid: [ 'a', 'aa', 'aaaaaaaa' ] }; - inputs.invalid.forEach( function( input ){ - test('invalid input', function(t) { + test('invalid input', function(t) { + inputs.invalid.forEach( function( input ){ 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(err, 'invalid param \'input\': text length, must be >0', input + ' is an invalid input'); t.equal(clean, undefined, 'clean not set'); - t.end(); }); }); + t.end(); }); - inputs.valid.forEach( function( input ){ - test('valid input', function(t) { + test('valid input', function(t) { + inputs.valid.forEach( function( input ){ 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(); + t.deepEqual(clean, expected, 'clean set correctly (' + input + ')'); }); }); + t.end(); }); }; @@ -53,25 +53,25 @@ module.exports.tests.sanitize_lat = function(test, common) { invalid: [ -181, -120, -91, 91, 120, 181 ], valid: [ 0, 45, 90, -0, '0', '45', '90' ] }; - lats.invalid.forEach( function( lat ){ - test('invalid lat', function(t) { + test('invalid lat', function(t) { + lats.invalid.forEach( function( lat ){ sanitize({ input: 'test', lat: lat, lon: 0 }, function( err, clean ){ - t.equal(err, 'invalid param \'lat\': must be >-90 and <90', 'invalid latitude'); + t.equal(err, 'invalid param \'lat\': must be >-90 and <90', lat + ' is an invalid latitude'); t.equal(clean, undefined, 'clean not set'); - t.end(); }); }); + t.end(); }); - lats.valid.forEach( function( lat ){ - test('valid lat', function(t) { + test('valid lat', function(t) { + lats.valid.forEach( function( lat ){ 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(); + t.deepEqual(clean, expected, 'clean set correctly (' + lat + ')'); }); }); + t.end(); }); }; @@ -80,25 +80,26 @@ module.exports.tests.sanitize_lon = function(test, common) { 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) { + test('invalid lon', function(t) { + lons.invalid.forEach( function( lon ){ 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(err, 'invalid param \'lon\': must be >-180 and <180', lon + ' is an invalid longitude'); t.equal(clean, undefined, 'clean not set'); - t.end(); + }); }); + t.end(); }); - lons.valid.forEach( function( lon ){ - test('valid lon', function(t) { + test('valid lon', function(t) { + lons.valid.forEach( function( lon ){ 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(); + t.deepEqual(clean, expected, 'clean set correctly (' + lon + ')'); }); }); + t.end(); }); }; From 516722f8dcf4a7b0d6f4ba6b0f271a91da52f726 Mon Sep 17 00:00:00 2001 From: Harish Krishna Date: Wed, 15 Oct 2014 16:07:14 -0400 Subject: [PATCH 23/46] dont set type till its a valid type --- sanitiser/_id.js | 1 - 1 file changed, 1 deletion(-) diff --git a/sanitiser/_id.js b/sanitiser/_id.js index 6620a280..7c466110 100644 --- a/sanitiser/_id.js +++ b/sanitiser/_id.js @@ -30,7 +30,6 @@ function sanitize( req ){ if('string' !== typeof params.type || !params.type.length){ return errormessage('type'); } - req.clean.type = params.type; // type text must be one of the indeces if(indeces.indexOf(params.type) == -1){ From 23e6cfbef782935d52e8990a84c6c2a49d57593e Mon Sep 17 00:00:00 2001 From: Harish Krishna Date: Wed, 15 Oct 2014 17:14:54 -0400 Subject: [PATCH 24/46] mget for real - first pass (experiencing problems with the backend client) --- app.js | 3 +++ controller/mget.js | 55 +++++++++++++++++++++++++++++++++++++++++++++ sanitiser/_ids.js | 56 ++++++++++++++++++++++++++++++++++++++++++++++ sanitiser/mget.js | 23 +++++++++++++++++++ 4 files changed, 137 insertions(+) create mode 100644 controller/mget.js create mode 100644 sanitiser/_ids.js create mode 100644 sanitiser/mget.js diff --git a/app.js b/app.js index a80a4f82..1c071cb1 100644 --- a/app.js +++ b/app.js @@ -12,6 +12,7 @@ app.use( require('./middleware/jsonp') ); var sanitisers = {}; sanitisers.get = require('./sanitiser/get'); +sanitisers.mget = require('./sanitiser/mget'); sanitisers.suggest = require('./sanitiser/suggest'); sanitisers.search = sanitisers.suggest; sanitisers.reverse = require('./sanitiser/reverse'); @@ -21,6 +22,7 @@ sanitisers.reverse = require('./sanitiser/reverse'); var controllers = {}; controllers.index = require('./controller/index'); controllers.get = require('./controller/get'); +controllers.mget = require('./controller/mget'); controllers.suggest = require('./controller/suggest'); controllers.search = require('./controller/search'); @@ -31,6 +33,7 @@ app.get( '/', controllers.index() ); // get doc API app.get( '/get', sanitisers.get.middleware, controllers.get() ); +app.get( '/mget', sanitisers.mget.middleware, controllers.mget() ); // suggest API app.get( '/suggest', sanitisers.suggest.middleware, controllers.suggest() ); diff --git a/controller/mget.js b/controller/mget.js new file mode 100644 index 00000000..fadc4ede --- /dev/null +++ b/controller/mget.js @@ -0,0 +1,55 @@ + +var geojsonify = require('../helper/geojsonify').search; + +function setup( backend ){ + + // allow overriding of dependencies + backend = backend || require('../src/backend'); + + function controller( req, res, next ){ + + // backend command + var cmd = req.clean.ids.map( function(id) { + return { + index: 'pelias', + type: id.type, + id: id.id + }; + }); + cmd = { + index: 'pelias', + type: 'geoname', + ids: cmd.map(function(c){ return c.id }) + } + console.log('cmd:') + console.log(cmd) + // query backend + backend().client.mget( cmd, function( err, data ){ + console.log('error:') + console.log(err) + var docs = []; + // handle backend errors + if( err ){ return next( err ); } + + if( data && data.docs && Array.isArray(data.docs) && data.docs.length ){ + docs = data.docs.map( function( doc ){ + return doc._source; + }); + } + + // convert docs to geojson + var geojson = geojsonify( docs ); + + // response envelope + geojson.date = new Date().getTime(); + + // respond + return res.status(200).json( geojson ); + }); + + } + + return controller; +} + +module.exports = setup; diff --git a/sanitiser/_ids.js b/sanitiser/_ids.js new file mode 100644 index 00000000..8354de9b --- /dev/null +++ b/sanitiser/_ids.js @@ -0,0 +1,56 @@ +// validate inputs, convert types and apply defaults +// id generally looks like 'geoname/4163334' (type/id) +// so, both type and id are required fields. + +function sanitize( req ){ + + req.clean = req.clean || {}; + var params = req.query; + var indeces = require('../query/indeces'); + + // ensure params is a valid object + if( Object.prototype.toString.call( params ) !== '[object Object]' ){ + params = {}; + } + console.log(params) + + var errormessage = function(fieldname, message) { + return { + 'error': true, + 'message': message || ('invalid param \''+ fieldname + '\': text length, must be >0') + } + }; + + if( params && params.ids && params.ids.length ){ + req.clean.ids = []; + console.log(params.ids) + params.ids.split(',').forEach( function(param) { + param = param.split('/'); + var type= param[0]; + var id = param[1]; + console.log(param) + // id text + if('string' !== typeof id || !id.length){ + return errormessage('id'); + } + // type text + if('string' !== typeof type || !type.length){ + return errormessage('type'); + } + // type text must be one of the indeces + if(indeces.indexOf(type) == -1){ + return errormessage('type', 'type must be one of these values - [' + indeces.join(", ") + ']'); + } + req.clean.ids.push({ + id: id, + type: type + }); + }); + } + console.log(req.clean) + return { 'error': false }; + +} + +// export function +module.exports = sanitize; \ No newline at end of file diff --git a/sanitiser/mget.js b/sanitiser/mget.js new file mode 100644 index 00000000..b1f6d40f --- /dev/null +++ b/sanitiser/mget.js @@ -0,0 +1,23 @@ + +var logger = require('../src/logger'), + _sanitize = require('../sanitiser/_sanitize'), + sanitizers = { + id: require('../sanitiser/_ids') + }; + +var sanitize = function(req, cb) { _sanitize(req, sanitizers, cb); } + +// export sanitize for testing +module.exports.sanitize = sanitize; + +// middleware +module.exports.middleware = function( req, res, next ){ + sanitize( req, function( err, clean ){ + if( err ){ + res.status(400); // 400 Bad Request + return next(err); + } + req.clean = clean; + next(); + }); +}; \ No newline at end of file From 0dd495a3789b5835a8f623e696f7c1dd394e8d7b Mon Sep 17 00:00:00 2001 From: Harish Krishna Date: Wed, 15 Oct 2014 17:39:36 -0400 Subject: [PATCH 25/46] going from ?id=123&type=geoname to ?id=geoname/123 tests updated --- sanitiser/_id.js | 21 +++++++++++++--- test/unit/sanitiser/get.js | 49 +++++++++++++++++++++----------------- 2 files changed, 45 insertions(+), 25 deletions(-) diff --git a/sanitiser/_id.js b/sanitiser/_id.js index 7c466110..52397128 100644 --- a/sanitiser/_id.js +++ b/sanitiser/_id.js @@ -24,18 +24,33 @@ function sanitize( req ){ if('string' !== typeof params.id || !params.id.length){ return errormessage('id'); } + + // id format + if(params.id.indexOf('/') === -1) { + return errormessage('id', 'invalid: must be of the format type/id for ex: \'geoname/4163334\''); + } req.clean.id = params.id; + var param = params.id.split('/'); + var param_type = param[0]; + var param_id = param[1]; + + // id text + if('string' !== typeof param_id || !param_id.length){ + return errormessage('id'); + } + // type text - if('string' !== typeof params.type || !params.type.length){ + if('string' !== typeof param_type || !param_type.length){ return errormessage('type'); } // type text must be one of the indeces - if(indeces.indexOf(params.type) == -1){ + if(indeces.indexOf(param_type) == -1){ return errormessage('type', 'type must be one of these values - [' + indeces.join(", ") + ']'); } - req.clean.type = params.type; + req.clean.id = param_id; + req.clean.type = param_type; return { 'error': false }; diff --git a/test/unit/sanitiser/get.js b/test/unit/sanitiser/get.js index 1ed34637..36e777d1 100644 --- a/test/unit/sanitiser/get.js +++ b/test/unit/sanitiser/get.js @@ -5,6 +5,7 @@ var get = require('../../../sanitiser/get'), indeces = require('../../../query/indeces'), defaultIdError = 'invalid param \'id\': text length, must be >0', defaultTypeError = 'invalid param \'type\': text length, must be >0', + defaultFormatError = 'invalid: must be of the format type/id for ex: \'geoname/4163334\'', defaultError = defaultIdError, defaultMissingTypeError = 'type must be one of these values - [' + indeces.join(", ") + ']', defaultClean = { id: '123', type: 'geoname' }, @@ -28,31 +29,34 @@ module.exports.tests.interface = function(test, common) { module.exports.tests.sanitize_id_and_type = function(test, common) { var inputs = { valid: [ - {id:'1', type:'geoname'}, - {id:'2', type:'osmnode'}, - {id:'3', type:'geoname'}, - {id:'4', type:'osmway'}, - {id:'5', type:'admin0'} + 'geoname/1', + 'osmnode/2', + 'admin0/53', + 'osmway/44', + 'geoname/5' ], - invalid: [ - {id:undefined, type:undefined}, - {id:null, type:null}, - {id:'', type:''}, - {id:'4', type:''}, - {id:'5', type:'gibberish'} + invalid: [ + '/', + '', + '//', + 'geoname/', + '/234', + 'gibberish/23' ] }; - test('invalid id and/or type', function(t) { + test('invalid input', function(t) { inputs.invalid.forEach( function( input ){ - sanitize({ id: input.id, type: input.type }, function( err, clean ){ + sanitize({ id: input }, function( err, clean ){ switch (err) { case defaultIdError: - t.equal(err, defaultIdError, input.id + ' is invalid (missing id)'); break; + t.equal(err, defaultIdError, input + ' is invalid (missing id)'); break; case defaultTypeError: - t.equal(err, defaultTypeError, input.type + ' is invalid (missing type)'); break; + t.equal(err, defaultTypeError, input + ' is invalid (missing type)'); break; + case defaultFormatError: + t.equal(err, defaultFormatError, input + ' is invalid (invalid format)'); break; case defaultMissingTypeError: - t.equal(err, defaultMissingTypeError, input.type + ' is an unknown type'); break; + t.equal(err, defaultMissingTypeError, input + ' is an unknown type'); break; default: break; } t.equal(clean, undefined, 'clean not set'); @@ -61,12 +65,13 @@ module.exports.tests.sanitize_id_and_type = function(test, common) { t.end(); }); - test('valid id and/or type', function(t) { + test('valid input', function(t) { inputs.valid.forEach( function( input ){ - var expected = { id: input.id, type: input.type }; - sanitize({ id: input.id, type: input.type, }, function( err, clean ){ - t.equal(err, undefined, 'no error (' + input.id + ', ' + input.type + ')' ); - t.deepEqual(clean, expected, 'clean set correctly (' + input.id + ', ' + input.type + ')'); + var input_parts = input.split('/'); + var expected = { id: input_parts[1], type: input_parts[0] }; + sanitize({ id: input }, function( err, clean ){ + t.equal(err, undefined, 'no error (' + input + ')' ); + t.deepEqual(clean, expected, 'clean set correctly (' + input + ')'); }); }); t.end(); @@ -97,7 +102,7 @@ module.exports.tests.middleware_failure = function(test, common) { module.exports.tests.middleware_success = function(test, common) { test('middleware success', function(t) { - var req = { query: { id: '123', type: 'geoname' }}; + var req = { query: { id: 'geoname/123' }}; var next = function( message ){ t.equal(message, undefined, 'no error message set'); t.deepEqual(req.clean, defaultClean); From b828a05b01b80eaa42d79653d439418d444710f6 Mon Sep 17 00:00:00 2001 From: Harish Krishna Date: Thu, 16 Oct 2014 16:10:25 -0400 Subject: [PATCH 26/46] simplifying things, DRY - one endpoint 'GET' handles single/multiple requests. plus test coverage --- app.js | 3 -- controller/get.js | 29 ++++++++---- controller/mget.js | 55 ---------------------- sanitiser/_id.js | 62 ++++++++++++++----------- sanitiser/_ids.js | 56 ----------------------- sanitiser/mget.js | 23 ---------- test/unit/sanitiser/get.js | 94 ++++++++++++++++++++++++-------------- 7 files changed, 114 insertions(+), 208 deletions(-) delete mode 100644 controller/mget.js delete mode 100644 sanitiser/_ids.js delete mode 100644 sanitiser/mget.js diff --git a/app.js b/app.js index 1c071cb1..a80a4f82 100644 --- a/app.js +++ b/app.js @@ -12,7 +12,6 @@ app.use( require('./middleware/jsonp') ); var sanitisers = {}; sanitisers.get = require('./sanitiser/get'); -sanitisers.mget = require('./sanitiser/mget'); sanitisers.suggest = require('./sanitiser/suggest'); sanitisers.search = sanitisers.suggest; sanitisers.reverse = require('./sanitiser/reverse'); @@ -22,7 +21,6 @@ sanitisers.reverse = require('./sanitiser/reverse'); var controllers = {}; controllers.index = require('./controller/index'); controllers.get = require('./controller/get'); -controllers.mget = require('./controller/mget'); controllers.suggest = require('./controller/suggest'); controllers.search = require('./controller/search'); @@ -33,7 +31,6 @@ app.get( '/', controllers.index() ); // get doc API app.get( '/get', sanitisers.get.middleware, controllers.get() ); -app.get( '/mget', sanitisers.mget.middleware, controllers.mget() ); // suggest API app.get( '/suggest', sanitisers.suggest.middleware, controllers.suggest() ); diff --git a/controller/get.js b/controller/get.js index 57842b93..b48e6f64 100644 --- a/controller/get.js +++ b/controller/get.js @@ -5,25 +5,36 @@ function setup( backend ){ // allow overriding of dependencies backend = backend || require('../src/backend'); - + backend = new backend(); + function controller( req, res, next ){ + var docs = req.clean.ids.map( function(id) { + return { + _index: 'pelias', + _type: id.type, + _id: id.id + }; + }); + // backend command var cmd = { - index: 'pelias', - type: req.clean.type, - id: req.clean.id + body: { + docs: docs + } }; - // query backend - backend().client.get( cmd, function( err, data ){ - + // query new backend + backend.client.mget( cmd, function( err, data ){ + var docs = []; // handle backend errors if( err ){ return next( err ); } - if( data && data.found && data._source ){ - docs.push(data._source); + if( data && data.docs && Array.isArray(data.docs) && data.docs.length ){ + docs = data.docs.map( function( doc ){ + return doc._source; + }); } // convert docs to geojson diff --git a/controller/mget.js b/controller/mget.js deleted file mode 100644 index fadc4ede..00000000 --- a/controller/mget.js +++ /dev/null @@ -1,55 +0,0 @@ - -var geojsonify = require('../helper/geojsonify').search; - -function setup( backend ){ - - // allow overriding of dependencies - backend = backend || require('../src/backend'); - - function controller( req, res, next ){ - - // backend command - var cmd = req.clean.ids.map( function(id) { - return { - index: 'pelias', - type: id.type, - id: id.id - }; - }); - cmd = { - index: 'pelias', - type: 'geoname', - ids: cmd.map(function(c){ return c.id }) - } - console.log('cmd:') - console.log(cmd) - // query backend - backend().client.mget( cmd, function( err, data ){ - console.log('error:') - console.log(err) - var docs = []; - // handle backend errors - if( err ){ return next( err ); } - - if( data && data.docs && Array.isArray(data.docs) && data.docs.length ){ - docs = data.docs.map( function( doc ){ - return doc._source; - }); - } - - // convert docs to geojson - var geojson = geojsonify( docs ); - - // response envelope - geojson.date = new Date().getTime(); - - // respond - return res.status(200).json( geojson ); - }); - - } - - return controller; -} - -module.exports = setup; diff --git a/sanitiser/_id.js b/sanitiser/_id.js index 52397128..62860243 100644 --- a/sanitiser/_id.js +++ b/sanitiser/_id.js @@ -1,5 +1,5 @@ // validate inputs, convert types and apply defaults -// id generally looks like 'geoname/4163334' (type/id) +// id generally looks like 'geoname:4163334' (type:id) // so, both type and id are required fields. function sanitize( req ){ @@ -7,6 +7,7 @@ function sanitize( req ){ req.clean = req.clean || {}; var params = req.query; var indeces = require('../query/indeces'); + var delim = ':'; // ensure params is a valid object if( Object.prototype.toString.call( params ) !== '[object Object]' ){ @@ -20,38 +21,45 @@ function sanitize( req ){ } }; - // id text - if('string' !== typeof params.id || !params.id.length){ + if(('string' === typeof params.id && !params.id.length) || params.id === undefined){ return errormessage('id'); } - // id format - if(params.id.indexOf('/') === -1) { - return errormessage('id', 'invalid: must be of the format type/id for ex: \'geoname/4163334\''); - } - req.clean.id = params.id; + if( params && params.id && params.id.length ){ + req.clean.ids = []; + params.ids = Array.isArray(params.id) ? params.id : [params.id]; + + for (var i=0; i0') - } - }; - - if( params && params.ids && params.ids.length ){ - req.clean.ids = []; - console.log(params.ids) - params.ids.split(',').forEach( function(param) { - param = param.split('/'); - var type= param[0]; - var id = param[1]; - console.log(param) - // id text - if('string' !== typeof id || !id.length){ - return errormessage('id'); - } - // type text - if('string' !== typeof type || !type.length){ - return errormessage('type'); - } - // type text must be one of the indeces - if(indeces.indexOf(type) == -1){ - return errormessage('type', 'type must be one of these values - [' + indeces.join(", ") + ']'); - } - req.clean.ids.push({ - id: id, - type: type - }); - }); - } - console.log(req.clean) - return { 'error': false }; - -} - -// export function -module.exports = sanitize; \ No newline at end of file diff --git a/sanitiser/mget.js b/sanitiser/mget.js deleted file mode 100644 index b1f6d40f..00000000 --- a/sanitiser/mget.js +++ /dev/null @@ -1,23 +0,0 @@ - -var logger = require('../src/logger'), - _sanitize = require('../sanitiser/_sanitize'), - sanitizers = { - id: require('../sanitiser/_ids') - }; - -var sanitize = function(req, cb) { _sanitize(req, sanitizers, cb); } - -// export sanitize for testing -module.exports.sanitize = sanitize; - -// middleware -module.exports.middleware = function( req, res, next ){ - sanitize( req, function( err, clean ){ - if( err ){ - res.status(400); // 400 Bad Request - return next(err); - } - req.clean = clean; - next(); - }); -}; \ No newline at end of file diff --git a/test/unit/sanitiser/get.js b/test/unit/sanitiser/get.js index 36e777d1..3de73397 100644 --- a/test/unit/sanitiser/get.js +++ b/test/unit/sanitiser/get.js @@ -3,13 +3,19 @@ var get = require('../../../sanitiser/get'), _sanitize = get.sanitize, middleware = get.middleware, indeces = require('../../../query/indeces'), - defaultIdError = 'invalid param \'id\': text length, must be >0', - defaultTypeError = 'invalid param \'type\': text length, must be >0', - defaultFormatError = 'invalid: must be of the format type/id for ex: \'geoname/4163334\'', - defaultError = defaultIdError, - defaultMissingTypeError = 'type must be one of these values - [' + indeces.join(", ") + ']', - defaultClean = { id: '123', type: 'geoname' }, - sanitize = function(query, cb) { _sanitize({'query':query}, cb); } + delimiter = ':', + defaultLengthError = function(input) { return 'invalid param \''+ input + '\': text length, must be >0' }, + defaultFormatError = 'invalid: must be of the format type:id for ex: \'geoname:4163334\'', + defaultError = 'invalid param \'id\': text length, must be >0', + defaultMissingTypeError = function(input) { + var type = input.split(delimiter)[0]; + return type + ' is invalid. It must be one of these values - [' + indeces.join(", ") + ']'}, + defaultClean = { ids: [ { id: '123', type: 'geoname' } ] }, + sanitize = function(query, cb) { _sanitize({'query':query}, cb); }, + inputs = { + valid: [ 'geoname:1', 'osmnode:2', 'admin0:53', 'osmway:44', 'geoname:5' ], + invalid: [ ':', '', '::', 'geoname:', ':234', 'gibberish:23' ] + }; module.exports.tests = {}; @@ -26,37 +32,19 @@ module.exports.tests.interface = function(test, common) { }); }; -module.exports.tests.sanitize_id_and_type = function(test, common) { - var inputs = { - valid: [ - 'geoname/1', - 'osmnode/2', - 'admin0/53', - 'osmway/44', - 'geoname/5' - ], - invalid: [ - '/', - '', - '//', - 'geoname/', - '/234', - 'gibberish/23' - ] - }; - +module.exports.tests.sanitize_id = function(test, common) { test('invalid input', function(t) { inputs.invalid.forEach( function( input ){ sanitize({ id: input }, function( err, clean ){ switch (err) { - case defaultIdError: - t.equal(err, defaultIdError, input + ' is invalid (missing id)'); break; - case defaultTypeError: - t.equal(err, defaultTypeError, input + ' is invalid (missing type)'); break; + case defaultError: + t.equal(err, defaultError, input + ' is invalid input'); break; + case defaultLengthError(input): + t.equal(err, defaultLengthError(input), input + ' is invalid (missing id/type)'); break; case defaultFormatError: t.equal(err, defaultFormatError, input + ' is invalid (invalid format)'); break; - case defaultMissingTypeError: - t.equal(err, defaultMissingTypeError, input + ' is an unknown type'); break; + case defaultMissingTypeError(input): + t.equal(err, defaultMissingTypeError(input), input + ' is an unknown type'); break; default: break; } t.equal(clean, undefined, 'clean not set'); @@ -67,8 +55,8 @@ module.exports.tests.sanitize_id_and_type = function(test, common) { test('valid input', function(t) { inputs.valid.forEach( function( input ){ - var input_parts = input.split('/'); - var expected = { id: input_parts[1], type: input_parts[0] }; + var input_parts = input.split(delimiter); + var expected = { ids: [ { id: input_parts[1], type: input_parts[0] } ] }; sanitize({ id: input }, function( err, clean ){ t.equal(err, undefined, 'no error (' + input + ')' ); t.deepEqual(clean, expected, 'clean set correctly (' + input + ')'); @@ -78,6 +66,42 @@ module.exports.tests.sanitize_id_and_type = function(test, common) { }); }; + +module.exports.tests.sanitize_ids = function(test, common) { + test('invalid input', function(t) { + sanitize({ id: inputs.invalid }, function( err, clean ){ + var input = inputs.invalid[0]; // since it breaks on the first invalid element + switch (err) { + case defaultError: + t.equal(err, defaultError, input + ' is invalid input'); break; + case defaultLengthError(input): + t.equal(err, defaultLengthError(input), input + ' is invalid (missing id/type)'); break; + case defaultFormatError: + t.equal(err, defaultFormatError, input + ' is invalid (invalid format)'); break; + case defaultMissingTypeError(input): + t.equal(err, defaultMissingTypeError(input), input + ' is an unknown type'); break; + default: break; + } + t.equal(clean, undefined, 'clean not set'); + }); + t.end(); + }); + + test('valid input', function(t) { + var expected={}; + expected.ids = []; + inputs.valid.forEach( function( input ){ + var input_parts = input.split(delimiter); + expected.ids.push({ id: input_parts[1], type: input_parts[0] }); + }); + sanitize({ id: inputs.valid }, function( err, clean ){ + t.equal(err, undefined, 'no error' ); + t.deepEqual(clean, expected, 'clean set correctly'); + }); + t.end(); + }); +}; + module.exports.tests.invalid_params = function(test, common) { test('invalid params', function(t) { sanitize( undefined, function( err, clean ){ @@ -102,7 +126,7 @@ module.exports.tests.middleware_failure = function(test, common) { module.exports.tests.middleware_success = function(test, common) { test('middleware success', function(t) { - var req = { query: { id: 'geoname/123' }}; + var req = { query: { id: 'geoname' + delimiter + '123' }}; var next = function( message ){ t.equal(message, undefined, 'no error message set'); t.deepEqual(req.clean, defaultClean); From 5e42b4ac0c0ccfbbd54140621b535a263de9886f Mon Sep 17 00:00:00 2001 From: Harish Krishna Date: Thu, 16 Oct 2014 16:32:17 -0400 Subject: [PATCH 27/46] de-dupe as part of sanitizing --- sanitiser/_id.js | 5 +++++ test/unit/sanitiser/get.js | 11 +++++++++++ 2 files changed, 16 insertions(+) diff --git a/sanitiser/_id.js b/sanitiser/_id.js index 62860243..8f165f22 100644 --- a/sanitiser/_id.js +++ b/sanitiser/_id.js @@ -29,6 +29,11 @@ function sanitize( req ){ req.clean.ids = []; params.ids = Array.isArray(params.id) ? params.id : [params.id]; + // de-dupe + params.ids = params.ids.filter(function(item, pos) { + return params.ids.indexOf(item) == pos; + }); + for (var i=0; i Date: Thu, 16 Oct 2014 16:55:30 -0400 Subject: [PATCH 28/46] ciao --- test/ciao/get/success.coffee | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 test/ciao/get/success.coffee diff --git a/test/ciao/get/success.coffee b/test/ciao/get/success.coffee new file mode 100644 index 00000000..bd294d79 --- /dev/null +++ b/test/ciao/get/success.coffee @@ -0,0 +1,16 @@ + +#> valid get query +path: '/get?id=geoname:4221195' + +#? 200 ok +response.statusCode.should.equal 200 + +#? valid response +now = new Date().getTime() +should.exist json +should.not.exist json.error +json.date.should.be.within now-5000, now+5000 + +#? valid geojson +json.type.should.equal 'FeatureCollection' +json.features.should.be.instanceof Array \ No newline at end of file From ca10661457634856e4d915369b0ea4ae7be2b8ca Mon Sep 17 00:00:00 2001 From: Harish Krishna Date: Thu, 16 Oct 2014 17:03:36 -0400 Subject: [PATCH 29/46] adding a mget ciao test --- test/ciao/get/mget.coffee | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 test/ciao/get/mget.coffee diff --git a/test/ciao/get/mget.coffee b/test/ciao/get/mget.coffee new file mode 100644 index 00000000..ab5004ec --- /dev/null +++ b/test/ciao/get/mget.coffee @@ -0,0 +1,16 @@ + +#> valid get query +path: '/get?id=geoname:4221195&id=geoname:4193595' + +#? 200 ok +response.statusCode.should.equal 200 + +#? valid response +now = new Date().getTime() +should.exist json +should.not.exist json.error +json.date.should.be.within now-5000, now+5000 + +#? valid geojson +json.type.should.equal 'FeatureCollection' +json.features.should.be.instanceof Array \ No newline at end of file From e6376942376332dbc48e253da9f7afdb24c54dbf Mon Sep 17 00:00:00 2001 From: Harish Krishna Date: Thu, 16 Oct 2014 17:04:04 -0400 Subject: [PATCH 30/46] doc - get --- docs/get/mget.md | 97 +++++++++++++++++++++++++++++++++++++++++++++ docs/get/success.md | 80 +++++++++++++++++++++++++++++++++++++ 2 files changed, 177 insertions(+) create mode 100644 docs/get/mget.md create mode 100644 docs/get/success.md diff --git a/docs/get/mget.md b/docs/get/mget.md new file mode 100644 index 00000000..744c9aa1 --- /dev/null +++ b/docs/get/mget.md @@ -0,0 +1,97 @@ +# valid get query + +*Generated: Thu Oct 16 2014 17:02:52 GMT-0400 (EDT)* +## Request +```javascript +{ + "protocol": "http:", + "host": "localhost", + "method": "GET", + "port": 3100, + "path": "/get?id=geoname:4221195&id=geoname:4193595" +} +``` + +## Response +```javascript +Status: 200 +{ + "x-powered-by": "mapzen", + "charset": "utf8", + "cache-control": "public,max-age=60", + "server": "Pelias/0.0.0", + "access-control-allow-origin": "*", + "access-control-allow-methods": "GET", + "access-control-allow-headers": "X-Requested-With,content-type", + "access-control-allow-credentials": "true", + "content-type": "application/json; charset=utf-8", + "content-length": "567", + "etag": "W/\"237-e0425e36\"", + "date": "Thu, 16 Oct 2014 21:02:52 GMT", + "connection": "close" +} +``` +```javascript +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -82.9207, + 34.36094 + ] + }, + "properties": { + "name": "Sanders Grove Cemetery", + "admin0": "United States", + "admin1": "Georgia", + "admin2": "Hart County", + "text": "Sanders Grove Cemetery, Hart County, United States" + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -83.94213, + 33.32262 + ] + }, + "properties": { + "name": "Etheredge Cemetery", + "admin0": "United States", + "admin1": "Georgia", + "admin2": "Butts County", + "text": "Etheredge Cemetery, Butts County, United States" + } + } + ], + "date": 1413493372681 +} +``` + +## Tests + +### ✓ valid response +```javascript +now = new Date().getTime() +should.exist json +should.not.exist json.error +json.date.should.be.within now-5000, now+5000 +``` + +### ✓ valid geojson +```javascript +json.type.should.equal 'FeatureCollection' +json.features.should.be.instanceof Array +``` + +### ✓ 200 ok +```javascript +response.statusCode.should.equal 200 +``` + diff --git a/docs/get/success.md b/docs/get/success.md new file mode 100644 index 00000000..5ec7689f --- /dev/null +++ b/docs/get/success.md @@ -0,0 +1,80 @@ +# valid get query + +*Generated: Thu Oct 16 2014 17:02:52 GMT-0400 (EDT)* +## Request +```javascript +{ + "protocol": "http:", + "host": "localhost", + "method": "GET", + "port": 3100, + "path": "/get?id=geoname:4221195" +} +``` + +## Response +```javascript +Status: 200 +{ + "x-powered-by": "mapzen", + "charset": "utf8", + "cache-control": "public,max-age=60", + "server": "Pelias/0.0.0", + "access-control-allow-origin": "*", + "access-control-allow-methods": "GET", + "access-control-allow-headers": "X-Requested-With,content-type", + "access-control-allow-credentials": "true", + "content-type": "application/json; charset=utf-8", + "content-length": "317", + "etag": "W/\"13d-bc388729\"", + "date": "Thu, 16 Oct 2014 21:02:52 GMT", + "connection": "close" +} +``` +```javascript +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -82.9207, + 34.36094 + ] + }, + "properties": { + "name": "Sanders Grove Cemetery", + "admin0": "United States", + "admin1": "Georgia", + "admin2": "Hart County", + "text": "Sanders Grove Cemetery, Hart County, United States" + } + } + ], + "date": 1413493372842 +} +``` + +## Tests + +### ✓ valid geojson +```javascript +json.type.should.equal 'FeatureCollection' +json.features.should.be.instanceof Array +``` + +### ✓ 200 ok +```javascript +response.statusCode.should.equal 200 +``` + +### ✓ valid response +```javascript +now = new Date().getTime() +should.exist json +should.not.exist json.error +json.date.should.be.within now-5000, now+5000 +``` + From 81b3668b304989fe5a2971c0c9b6b29ff3055ae8 Mon Sep 17 00:00:00 2001 From: Harish Krishna Date: Wed, 22 Oct 2014 17:06:12 -0400 Subject: [PATCH 31/46] get to doc --- app.js | 8 ++++---- controller/{get.js => doc.js} | 0 sanitiser/{get.js => doc.js} | 0 test/ciao/{get/mget.coffee => doc/msuccess.coffee} | 4 ++-- test/ciao/{get => doc}/success.coffee | 4 ++-- test/unit/run.js | 2 +- test/unit/sanitiser/{get.js => doc.js} | 8 ++++---- 7 files changed, 13 insertions(+), 13 deletions(-) rename controller/{get.js => doc.js} (100%) rename sanitiser/{get.js => doc.js} (100%) rename test/ciao/{get/mget.coffee => doc/msuccess.coffee} (69%) rename test/ciao/{get => doc}/success.coffee (73%) rename test/unit/sanitiser/{get.js => doc.js} (97%) diff --git a/app.js b/app.js index a80a4f82..d4cf0257 100644 --- a/app.js +++ b/app.js @@ -11,7 +11,7 @@ app.use( require('./middleware/jsonp') ); /** ----------------------- sanitisers ----------------------- **/ var sanitisers = {}; -sanitisers.get = require('./sanitiser/get'); +sanitisers.doc = require('./sanitiser/doc'); sanitisers.suggest = require('./sanitiser/suggest'); sanitisers.search = sanitisers.suggest; sanitisers.reverse = require('./sanitiser/reverse'); @@ -20,7 +20,7 @@ sanitisers.reverse = require('./sanitiser/reverse'); var controllers = {}; controllers.index = require('./controller/index'); -controllers.get = require('./controller/get'); +controllers.doc = require('./controller/doc'); controllers.suggest = require('./controller/suggest'); controllers.search = require('./controller/search'); @@ -29,8 +29,8 @@ controllers.search = require('./controller/search'); // api root app.get( '/', controllers.index() ); -// get doc API -app.get( '/get', sanitisers.get.middleware, controllers.get() ); +// doc API +app.get( '/doc', sanitisers.doc.middleware, controllers.doc() ); // suggest API app.get( '/suggest', sanitisers.suggest.middleware, controllers.suggest() ); diff --git a/controller/get.js b/controller/doc.js similarity index 100% rename from controller/get.js rename to controller/doc.js diff --git a/sanitiser/get.js b/sanitiser/doc.js similarity index 100% rename from sanitiser/get.js rename to sanitiser/doc.js diff --git a/test/ciao/get/mget.coffee b/test/ciao/doc/msuccess.coffee similarity index 69% rename from test/ciao/get/mget.coffee rename to test/ciao/doc/msuccess.coffee index ab5004ec..56808be4 100644 --- a/test/ciao/get/mget.coffee +++ b/test/ciao/doc/msuccess.coffee @@ -1,6 +1,6 @@ -#> valid get query -path: '/get?id=geoname:4221195&id=geoname:4193595' +#> valid doc query +path: '/doc?id=geoname:4221195&id=geoname:4193595' #? 200 ok response.statusCode.should.equal 200 diff --git a/test/ciao/get/success.coffee b/test/ciao/doc/success.coffee similarity index 73% rename from test/ciao/get/success.coffee rename to test/ciao/doc/success.coffee index bd294d79..3818aca6 100644 --- a/test/ciao/get/success.coffee +++ b/test/ciao/doc/success.coffee @@ -1,6 +1,6 @@ -#> valid get query -path: '/get?id=geoname:4221195' +#> valid doc query +path: '/doc?id=geoname:4221195' #? 200 ok response.statusCode.should.equal 200 diff --git a/test/unit/run.js b/test/unit/run.js index 34b5a92e..bad988db 100644 --- a/test/unit/run.js +++ b/test/unit/run.js @@ -7,7 +7,7 @@ var tests = [ require('./controller/suggest'), require('./controller/search'), require('./sanitiser/suggest'), - require('./sanitiser/get'), + require('./sanitiser/doc'), require('./query/indeces'), require('./query/suggest'), require('./query/search'), diff --git a/test/unit/sanitiser/get.js b/test/unit/sanitiser/doc.js similarity index 97% rename from test/unit/sanitiser/get.js rename to test/unit/sanitiser/doc.js index 8c7dd33d..dcd15cbb 100644 --- a/test/unit/sanitiser/get.js +++ b/test/unit/sanitiser/doc.js @@ -1,7 +1,7 @@ -var get = require('../../../sanitiser/get'), - _sanitize = get.sanitize, - middleware = get.middleware, +var doc = require('../../../sanitiser/doc'), + _sanitize = doc.sanitize, + middleware = doc.middleware, indeces = require('../../../query/indeces'), delimiter = ':', defaultLengthError = function(input) { return 'invalid param \''+ input + '\': text length, must be >0' }, @@ -150,7 +150,7 @@ module.exports.tests.middleware_success = function(test, common) { module.exports.all = function (tape, common) { function test(name, testFunction) { - return tape('SANTIZE /get ' + name, testFunction); + return tape('SANTIZE /doc ' + name, testFunction); } for( var testCase in module.exports.tests ){ From 49f954f561c06131a9a2cdf399c4c9e220c48d72 Mon Sep 17 00:00:00 2001 From: Harish Krishna Date: Thu, 23 Oct 2014 11:59:54 -0400 Subject: [PATCH 32/46] updated docs --- docs/404.md | 22 ++-- docs/cors.md | 4 +- docs/{get/mget.md => doc/msuccess.md} | 28 ++--- docs/{get => doc}/success.md | 32 ++--- docs/index.md | 16 +-- docs/jsonp.md | 4 +- docs/reverse/success.md | 33 ++--- docs/search/success.md | 173 ++++++++++++++++++++++++-- docs/suggest/success.md | 173 +------------------------- 9 files changed, 239 insertions(+), 246 deletions(-) rename docs/{get/mget.md => doc/msuccess.md} (81%) rename docs/{get => doc}/success.md (81%) diff --git a/docs/404.md b/docs/404.md index 79c6241a..66a3567d 100644 --- a/docs/404.md +++ b/docs/404.md @@ -1,6 +1,6 @@ # invalid path -*Generated: Thu Sep 25 2014 19:25:20 GMT+0100 (BST)* +*Generated: Thu Oct 23 2014 11:58:14 GMT-0400 (EDT)* ## Request ```javascript { @@ -27,7 +27,7 @@ Status: 404 "content-type": "application/json; charset=utf-8", "content-length": "35", "etag": "W/\"23-dfdfa185\"", - "date": "Thu, 25 Sep 2014 18:25:20 GMT", + "date": "Thu, 23 Oct 2014 15:58:14 GMT", "connection": "close" } ``` @@ -39,14 +39,19 @@ Status: 404 ## Tests -### ✓ not found +### ✓ cache-control header correctly set ```javascript -response.statusCode.should.equal 404 +response.should.have.header 'Cache-Control','public,max-age=300' ``` -### ✓ cache-control header correctly set +### ✓ content-type header correctly set ```javascript -response.should.have.header 'Cache-Control','public,max-age=300' +response.should.have.header 'Content-Type','application/json; charset=utf-8' +``` + +### ✓ not found +```javascript +response.statusCode.should.equal 404 ``` ### ✓ should respond in json with server info @@ -56,8 +61,3 @@ 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' -``` - diff --git a/docs/cors.md b/docs/cors.md index a186bc55..1f6e7e46 100644 --- a/docs/cors.md +++ b/docs/cors.md @@ -1,6 +1,6 @@ # cross-origin resource sharing -*Generated: Thu Sep 25 2014 19:25:20 GMT+0100 (BST)* +*Generated: Thu Oct 23 2014 11:58:14 GMT-0400 (EDT)* ## Request ```javascript { @@ -27,7 +27,7 @@ Status: 200 "content-type": "application/json; charset=utf-8", "content-length": "50", "etag": "W/\"32-85536434\"", - "date": "Thu, 25 Sep 2014 18:25:20 GMT", + "date": "Thu, 23 Oct 2014 15:58:14 GMT", "connection": "close" } ``` diff --git a/docs/get/mget.md b/docs/doc/msuccess.md similarity index 81% rename from docs/get/mget.md rename to docs/doc/msuccess.md index 744c9aa1..c9f3d8c9 100644 --- a/docs/get/mget.md +++ b/docs/doc/msuccess.md @@ -1,6 +1,6 @@ -# valid get query +# valid doc query -*Generated: Thu Oct 16 2014 17:02:52 GMT-0400 (EDT)* +*Generated: Thu Oct 23 2014 11:58:14 GMT-0400 (EDT)* ## Request ```javascript { @@ -8,7 +8,7 @@ "host": "localhost", "method": "GET", "port": 3100, - "path": "/get?id=geoname:4221195&id=geoname:4193595" + "path": "/doc?id=geoname:4221195&id=geoname:4193595" } ``` @@ -25,9 +25,9 @@ Status: 200 "access-control-allow-headers": "X-Requested-With,content-type", "access-control-allow-credentials": "true", "content-type": "application/json; charset=utf-8", - "content-length": "567", - "etag": "W/\"237-e0425e36\"", - "date": "Thu, 16 Oct 2014 21:02:52 GMT", + "content-length": "555", + "etag": "W/\"22b-6aa14642\"", + "date": "Thu, 23 Oct 2014 15:58:14 GMT", "connection": "close" } ``` @@ -49,7 +49,7 @@ Status: 200 "admin0": "United States", "admin1": "Georgia", "admin2": "Hart County", - "text": "Sanders Grove Cemetery, Hart County, United States" + "text": "Sanders Grove Cemetery, Hart County, Georgia" } }, { @@ -66,16 +66,21 @@ Status: 200 "admin0": "United States", "admin1": "Georgia", "admin2": "Butts County", - "text": "Etheredge Cemetery, Butts County, United States" + "text": "Etheredge Cemetery, Butts County, Georgia" } } ], - "date": 1413493372681 + "date": 1414079894512 } ``` ## Tests +### ✓ 200 ok +```javascript +response.statusCode.should.equal 200 +``` + ### ✓ valid response ```javascript now = new Date().getTime() @@ -90,8 +95,3 @@ json.type.should.equal 'FeatureCollection' json.features.should.be.instanceof Array ``` -### ✓ 200 ok -```javascript -response.statusCode.should.equal 200 -``` - diff --git a/docs/get/success.md b/docs/doc/success.md similarity index 81% rename from docs/get/success.md rename to docs/doc/success.md index 5ec7689f..34179af0 100644 --- a/docs/get/success.md +++ b/docs/doc/success.md @@ -1,6 +1,6 @@ -# valid get query +# valid doc query -*Generated: Thu Oct 16 2014 17:02:52 GMT-0400 (EDT)* +*Generated: Thu Oct 23 2014 11:58:14 GMT-0400 (EDT)* ## Request ```javascript { @@ -8,7 +8,7 @@ "host": "localhost", "method": "GET", "port": 3100, - "path": "/get?id=geoname:4221195" + "path": "/doc?id=geoname:4221195" } ``` @@ -25,9 +25,9 @@ Status: 200 "access-control-allow-headers": "X-Requested-With,content-type", "access-control-allow-credentials": "true", "content-type": "application/json; charset=utf-8", - "content-length": "317", - "etag": "W/\"13d-bc388729\"", - "date": "Thu, 16 Oct 2014 21:02:52 GMT", + "content-length": "311", + "etag": "W/\"137-ab9138f7\"", + "date": "Thu, 23 Oct 2014 15:58:14 GMT", "connection": "close" } ``` @@ -49,20 +49,22 @@ Status: 200 "admin0": "United States", "admin1": "Georgia", "admin2": "Hart County", - "text": "Sanders Grove Cemetery, Hart County, United States" + "text": "Sanders Grove Cemetery, Hart County, Georgia" } } ], - "date": 1413493372842 + "date": 1414079894512 } ``` ## Tests -### ✓ valid geojson +### ✓ valid response ```javascript -json.type.should.equal 'FeatureCollection' -json.features.should.be.instanceof Array +now = new Date().getTime() +should.exist json +should.not.exist json.error +json.date.should.be.within now-5000, now+5000 ``` ### ✓ 200 ok @@ -70,11 +72,9 @@ json.features.should.be.instanceof Array response.statusCode.should.equal 200 ``` -### ✓ valid response +### ✓ valid geojson ```javascript -now = new Date().getTime() -should.exist json -should.not.exist json.error -json.date.should.be.within now-5000, now+5000 +json.type.should.equal 'FeatureCollection' +json.features.should.be.instanceof Array ``` diff --git a/docs/index.md b/docs/index.md index 485f343b..a386d38f 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,6 +1,6 @@ # api root -*Generated: Thu Sep 25 2014 19:25:20 GMT+0100 (BST)* +*Generated: Thu Oct 23 2014 11:58:14 GMT-0400 (EDT)* ## Request ```javascript { @@ -27,7 +27,7 @@ Status: 200 "content-type": "application/json; charset=utf-8", "content-length": "50", "etag": "W/\"32-85536434\"", - "date": "Thu, 25 Sep 2014 18:25:20 GMT", + "date": "Thu, 23 Oct 2014 15:58:14 GMT", "connection": "close" } ``` @@ -42,9 +42,9 @@ Status: 200 ## Tests -### ✓ vanity header correctly set +### ✓ endpoint available ```javascript -response.should.have.header 'X-Powered-By','mapzen' +response.statusCode.should.equal 200 ``` ### ✓ server header correctly set @@ -53,9 +53,9 @@ response.should.have.header 'Server' response.headers.server.should.match /Pelias\/\d{1,2}\.\d{1,2}\.\d{1,2}/ ``` -### ✓ content-type header correctly set +### ✓ vanity header correctly set ```javascript -response.should.have.header 'Content-Type','application/json; charset=utf-8' +response.should.have.header 'X-Powered-By','mapzen' ``` ### ✓ cache-control header correctly set @@ -70,9 +70,9 @@ should.exist json.name should.exist json.version ``` -### ✓ endpoint available +### ✓ content-type header correctly set ```javascript -response.statusCode.should.equal 200 +response.should.have.header 'Content-Type','application/json; charset=utf-8' ``` ### ✓ charset header correctly set diff --git a/docs/jsonp.md b/docs/jsonp.md index 8526fd96..f4ba0eca 100644 --- a/docs/jsonp.md +++ b/docs/jsonp.md @@ -1,6 +1,6 @@ # jsonp -*Generated: Thu Sep 25 2014 19:25:20 GMT+0100 (BST)* +*Generated: Thu Oct 23 2014 11:58:14 GMT-0400 (EDT)* ## Request ```javascript { @@ -27,7 +27,7 @@ Status: 200 "content-type": "application/javascript; charset=utf-8", "content-length": "57", "etag": "W/\"39-b8a2aba1\"", - "date": "Thu, 25 Sep 2014 18:25:20 GMT", + "date": "Thu, 23 Oct 2014 15:58:14 GMT", "connection": "close" } ``` diff --git a/docs/reverse/success.md b/docs/reverse/success.md index 52fa3afb..1813aab7 100644 --- a/docs/reverse/success.md +++ b/docs/reverse/success.md @@ -1,6 +1,6 @@ # valid reverse query -*Generated: Thu Sep 25 2014 19:25:20 GMT+0100 (BST)* +*Generated: Thu Oct 23 2014 11:58:15 GMT-0400 (EDT)* ## Request ```javascript { @@ -25,9 +25,9 @@ Status: 200 "access-control-allow-headers": "X-Requested-With,content-type", "access-control-allow-credentials": "true", "content-type": "application/json; charset=utf-8", - "content-length": "263", - "etag": "W/\"107-75b55c25\"", - "date": "Thu, 25 Sep 2014 18:25:20 GMT", + "content-length": "282", + "etag": "W/\"11a-95fc1500\"", + "date": "Thu, 23 Oct 2014 15:58:15 GMT", "connection": "close" } ``` @@ -40,19 +40,20 @@ Status: 200 "geometry": { "type": "Point", "coordinates": [ - -82.506198, - 29.542519 + -82.50622, + 29.49136 ] }, "properties": { - "name": "Archer", + "name": "Adam", "admin0": "United States", - "admin1": "*florida", - "text": "Archer, *florida, United States" + "admin1": "Florida", + "admin2": "Alachua County", + "text": "Adam, Alachua County, Florida" } } ], - "date": 1411669520735 + "date": 1414079895606 } ``` @@ -63,6 +64,12 @@ Status: 200 response.statusCode.should.equal 200 ``` +### ✓ valid geojson +```javascript +json.type.should.equal 'FeatureCollection' +json.features.should.be.instanceof Array +``` + ### ✓ valid response ```javascript now = new Date().getTime() @@ -71,9 +78,3 @@ should.not.exist json.error json.date.should.be.within now-5000, now+5000 ``` -### ✓ valid geojson -```javascript -json.type.should.equal 'FeatureCollection' -json.features.should.be.instanceof Array -``` - diff --git a/docs/search/success.md b/docs/search/success.md index 5e6159e1..6b64c548 100644 --- a/docs/search/success.md +++ b/docs/search/success.md @@ -1,6 +1,6 @@ # valid search query -*Generated: Thu Sep 25 2014 19:25:21 GMT+0100 (BST)* +*Generated: Thu Oct 23 2014 11:58:15 GMT-0400 (EDT)* ## Request ```javascript { @@ -25,9 +25,9 @@ Status: 200 "access-control-allow-headers": "X-Requested-With,content-type", "access-control-allow-credentials": "true", "content-type": "application/json; charset=utf-8", - "content-length": "289", - "etag": "W/\"121-69343a38\"", - "date": "Thu, 25 Sep 2014 18:25:20 GMT", + "content-length": "2398", + "etag": "W/\"0tqT2h50EMVuqDtvmB5nAQ==\"", + "date": "Thu, 23 Oct 2014 15:58:15 GMT", "connection": "close" } ``` @@ -40,20 +40,173 @@ Status: 200 "geometry": { "type": "Point", "coordinates": [ - -82.357442, - 29.72089 + -82.5052, + 29.50312 ] }, "properties": { - "name": "Hidden Lake", + "name": "Blue Pete Lake", "admin0": "United States", "admin1": "Florida", - "admin2": "Alachua", - "text": "Hidden Lake, Alachua, United States" + "admin2": "Alachua County", + "text": "Blue Pete Lake, Alachua County, Florida" + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -82.52097, + 29.47185 + ] + }, + "properties": { + "name": "Sawgrass Lake", + "admin0": "United States", + "admin1": "Florida", + "admin2": "Levy County", + "text": "Sawgrass Lake, Levy County, Florida" + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -82.39141, + 29.4468 + ] + }, + "properties": { + "name": "Johnson Lake", + "admin0": "United States", + "admin1": "Florida", + "admin2": "Marion County", + "text": "Johnson Lake, Marion County, Florida" + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -82.35435, + 29.49526 + ] + }, + "properties": { + "name": "Ledwith Lake", + "admin0": "United States", + "admin1": "Florida", + "admin2": "Alachua County", + "text": "Ledwith Lake, Alachua County, Florida" + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -82.35316, + 29.52469 + ] + }, + "properties": { + "name": "Levy Lake", + "admin0": "United States", + "admin1": "Florida", + "admin2": "Alachua County", + "text": "Levy Lake, Alachua County, Florida" + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -82.66311, + 29.54036 + ] + }, + "properties": { + "name": "Fox Lake", + "admin0": "United States", + "admin1": "Florida", + "admin2": "Levy County", + "text": "Fox Lake, Levy County, Florida" + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -82.40502, + 29.61705 + ] + }, + "properties": { + "name": "Lake Kanapaha", + "admin0": "United States", + "admin1": "Florida", + "admin2": "Alachua County", + "text": "Lake Kanapaha, Alachua County, Florida" + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -82.70856, + 29.53293 + ] + }, + "properties": { + "name": "Doorshutter Lake", + "admin0": "United States", + "admin1": "Florida", + "admin2": "Levy County", + "text": "Doorshutter Lake, Levy County, Florida" + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -82.30215, + 29.52978 + ] + }, + "properties": { + "name": "Wauberg Lake", + "admin0": "United States", + "admin1": "Florida", + "admin2": "Alachua County", + "text": "Wauberg Lake, Alachua County, Florida" + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -82.47914, + 29.30795 + ] + }, + "properties": { + "name": "Lake Stafford", + "admin0": "United States", + "admin1": "Florida", + "admin2": "Levy County", + "text": "Lake Stafford, Levy County, Florida" } } ], - "date": 1411669520989 + "date": 1414079895605 } ``` diff --git a/docs/suggest/success.md b/docs/suggest/success.md index 6798f863..bc120b2c 100644 --- a/docs/suggest/success.md +++ b/docs/suggest/success.md @@ -1,6 +1,6 @@ # valid suggest query -*Generated: Thu Sep 25 2014 19:25:20 GMT+0100 (BST)* +*Generated: Thu Oct 23 2014 11:58:14 GMT-0400 (EDT)* ## Request ```javascript { @@ -25,178 +25,17 @@ Status: 200 "access-control-allow-headers": "X-Requested-With,content-type", "access-control-allow-credentials": "true", "content-type": "application/json; charset=utf-8", - "content-length": "1933", - "etag": "W/\"I89q+0HZNmXyHsTfLSP5Ww==\"", - "date": "Thu, 25 Sep 2014 18:25:20 GMT", + "content-length": "63", + "etag": "W/\"3f-200731a6\"", + "date": "Thu, 23 Oct 2014 15:58:14 GMT", "connection": "close" } ``` ```javascript { "type": "FeatureCollection", - "features": [ - { - "type": "Feature", - "geometry": { - "type": "Point", - "coordinates": [ - 7.56019, - 5.419786 - ] - }, - "properties": { - "text": "Abia, Nigeria", - "score": 1, - "type": "admin1", - "id": "1775:adm1:ng:nga:abia" - } - }, - { - "type": "Feature", - "geometry": { - "type": "Point", - "coordinates": [ - -66.908143, - -9.954353 - ] - }, - "properties": { - "text": "Acrelândia, Brazil", - "score": 1, - "type": "admin2", - "id": "708:adm2:br:bra:acrel_ndia" - } - }, - { - "type": "Feature", - "geometry": { - "type": "Point", - "coordinates": [ - -60.005461, - -3.099378 - ] - }, - "properties": { - "text": "Adrianópolis, Manaus, Brasil", - "score": 1, - "type": "neighborhood", - "id": "799:_:_:_:adrian_polis" - } - }, - { - "type": "Feature", - "geometry": { - "type": "Point", - "coordinates": [ - 7.909644, - 5.013733 - ] - }, - "properties": { - "text": "Akwa Ibom, Nigeria", - "score": 1, - "type": "admin1", - "id": "1776:adm1:ng:nga:akwa_ibom" - } - }, - { - "type": "Feature", - "geometry": { - "type": "Point", - "coordinates": [ - 9.691808, - 4.050576 - ] - }, - "properties": { - "text": "Akwa, Littoral, Cameroun", - "score": 1, - "type": "neighborhood", - "id": "1863:_:_:_:akwa" - } - }, - { - "type": "Feature", - "geometry": { - "type": "Point", - "coordinates": [ - -56.404593, - -10.042071 - ] - }, - "properties": { - "text": "Alta Floresta, Brazil", - "score": 1, - "type": "admin2", - "id": "2986:adm2:br:bra:alta_floresta" - } - }, - { - "type": "Feature", - "geometry": { - "type": "Point", - "coordinates": [ - -62.627879, - 3.10354 - ] - }, - "properties": { - "text": "Alto Alegre, Brazil", - "score": 1, - "type": "admin2", - "id": "4611:adm2:br:bra:alto_alegre" - } - }, - { - "type": "Feature", - "geometry": { - "type": "Point", - "coordinates": [ - -63.418743, - -9.697774 - ] - }, - "properties": { - "text": "Alto Paraíso, Brazil", - "score": 1, - "type": "admin2", - "id": "4584:adm2:br:bra:alto_para_so" - } - }, - { - "type": "Feature", - "geometry": { - "type": "Point", - "coordinates": [ - -65.296384, - -3.674615 - ] - }, - "properties": { - "text": "Alvarães, Brazil", - "score": 1, - "type": "admin2", - "id": "832:adm2:br:bra:alvar_es" - } - }, - { - "type": "Feature", - "geometry": { - "type": "Point", - "coordinates": [ - -62.710104, - 3.724864 - ] - }, - "properties": { - "text": "Amajari, Brazil", - "score": 1, - "type": "admin2", - "id": "4610:adm2:br:bra:amajari" - } - } - ], - "date": 1411669520909 + "features": [], + "date": 1414079894479 } ``` From 6ac839f37bde47083151dd43297652aaf03f648e Mon Sep 17 00:00:00 2001 From: Harish Krishna Date: Mon, 27 Oct 2014 11:01:39 -0400 Subject: [PATCH 33/46] take the first occurence of delim and compute id and type accordingly. This works with the case when the id has the delim character in it. --- sanitiser/_id.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sanitiser/_id.js b/sanitiser/_id.js index 8f165f22..073e1824 100644 --- a/sanitiser/_id.js +++ b/sanitiser/_id.js @@ -42,9 +42,9 @@ function sanitize( req ){ return errormessage(null, 'invalid: must be of the format type:id for ex: \'geoname:4163334\''); } - var param = thisparam.split(delim); - var type= param[0]; - var id = param[1]; + var param_index = thisparam.indexOf(delim); + var type = thisparam.substring(0, param_index ); + var id = thisparam.substring(param_index + 1); // id text if('string' !== typeof id || !id.length){ From 7f2206d75b0fbcb6b274f0bc9c2f8c58287dfb85 Mon Sep 17 00:00:00 2001 From: Harish Krishna Date: Tue, 28 Oct 2014 13:57:10 -0400 Subject: [PATCH 34/46] moving things around --- controller/suggest.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/controller/suggest.js b/controller/suggest.js index 8ce97791..b4a5267e 100644 --- a/controller/suggest.js +++ b/controller/suggest.js @@ -63,7 +63,7 @@ function setup( backend, query ){ cmd.body = query_admin( req.clean, 3 ); query_backend(cmd, callback); }, - d: function(callback){ + b: function(callback){ cmd.body = query_admin( req.clean, 1 ); query_backend(cmd, callback); }, From 2ee32f18921ca18ce96b83ddff9133db1cb219d7 Mon Sep 17 00:00:00 2001 From: Peter Johnson Date: Mon, 3 Nov 2014 14:12:35 +0000 Subject: [PATCH 35/46] mget service --- controller/doc.js | 26 ++++++-------------------- service/mget.js | 43 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 20 deletions(-) create mode 100644 service/mget.js diff --git a/controller/doc.js b/controller/doc.js index b48e6f64..c945aadb 100644 --- a/controller/doc.js +++ b/controller/doc.js @@ -1,15 +1,15 @@ +var service = { mget: require('../service/mget') }; var geojsonify = require('../helper/geojsonify').search; function setup( backend ){ // allow overriding of dependencies backend = backend || require('../src/backend'); - backend = new backend(); function controller( req, res, next ){ - var docs = req.clean.ids.map( function(id) { + var query = req.clean.ids.map( function(id) { return { _index: 'pelias', _type: id.type, @@ -17,25 +17,10 @@ function setup( backend ){ }; }); - // backend command - var cmd = { - body: { - docs: docs - } - }; - - // query new backend - backend.client.mget( cmd, function( err, data ){ - - var docs = []; - // handle backend errors - if( err ){ return next( err ); } + service.mget( backend, query, function( err, docs ){ - if( data && data.docs && Array.isArray(data.docs) && data.docs.length ){ - docs = data.docs.map( function( doc ){ - return doc._source; - }); - } + // error handler + if( err ){ return next( err ); } // convert docs to geojson var geojson = geojsonify( docs ); @@ -45,6 +30,7 @@ function setup( backend ){ // respond return res.status(200).json( geojson ); + }); } diff --git a/service/mget.js b/service/mget.js new file mode 100644 index 00000000..a5713f03 --- /dev/null +++ b/service/mget.js @@ -0,0 +1,43 @@ + +/** + + query must be an array of hashes, structured like so: + + { + _index: 'myindex', + _type: 'mytype', + _id: 'myid' + } + +**/ + +function service( backend, query, cb ){ + + // backend command + var cmd = { + body: { + docs: query + } + }; + + // query new backend + backend().client.mget( cmd, function( err, data ){ + + // handle backend errors + if( err ){ return cb( err ); } + + // map returned documents + var docs = []; + if( data && Array.isArray(data.docs) ){ + docs = data.docs.map( function( doc ){ + return doc._source; + }); + } + + // fire callback + return cb( null, docs ); + }); + +} + +module.exports = service; \ No newline at end of file From 0301fe8e74da9c48ad02464d8cba55d59facc601 Mon Sep 17 00:00:00 2001 From: Peter Johnson Date: Mon, 3 Nov 2014 14:16:32 +0000 Subject: [PATCH 36/46] search service --- controller/search.js | 14 ++++---------- service/search.js | 30 ++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 10 deletions(-) create mode 100644 service/search.js diff --git a/controller/search.js b/controller/search.js index 2554de2d..36f6c3f1 100644 --- a/controller/search.js +++ b/controller/search.js @@ -1,4 +1,5 @@ +var service = { search: require('../service/search') }; var geojsonify = require('../helper/geojsonify').search; function setup( backend, query ){ @@ -18,20 +19,13 @@ function setup( backend, query ){ if (req.clean.layers) { cmd.type = req.clean.layers; } - // query backend - backend().client.search( cmd, function( err, data ){ - var docs = []; + // query backend + service.search( backend, cmd, function( err, docs ){ - // handle backend errors + // error handler if( err ){ return next( err ); } - if( data && data.hits && data.hits.total && Array.isArray(data.hits.hits)){ - docs = data.hits.hits.map( function( hit ){ - return hit._source; - }); - } - // convert docs to geojson var geojson = geojsonify( docs ); diff --git a/service/search.js b/service/search.js new file mode 100644 index 00000000..ff413382 --- /dev/null +++ b/service/search.js @@ -0,0 +1,30 @@ + +/** + + query can be any valid ES query + +**/ + +function service( backend, cmd, cb ){ + + // query new backend + backend().client.search( cmd, function( err, data ){ + + // handle backend errors + if( err ){ return cb( err ); } + + // map returned documents + var docs = []; + if( data && data.hits && data.hits.total && Array.isArray(data.hits.hits)){ + docs = data.hits.hits.map( function( hit ){ + return hit._source; + }); + } + + // fire callback + return cb( null, docs ); + }); + +} + +module.exports = service; \ No newline at end of file From 3ea90dcf4d95646bf721e04b345ac3fe79b1c174 Mon Sep 17 00:00:00 2001 From: Peter Johnson Date: Mon, 3 Nov 2014 14:17:19 +0000 Subject: [PATCH 37/46] typo --- service/search.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/service/search.js b/service/search.js index ff413382..6f525123 100644 --- a/service/search.js +++ b/service/search.js @@ -1,7 +1,7 @@ /** - query can be any valid ES query + cmd can be any valid ES query command **/ From e24a467e96c6bdecd78799df4092541f0c9df14f Mon Sep 17 00:00:00 2001 From: Peter Johnson Date: Mon, 3 Nov 2014 14:27:10 +0000 Subject: [PATCH 38/46] suggest service --- controller/suggest.js | 12 +++--------- service/suggest.js | 28 ++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 9 deletions(-) create mode 100644 service/suggest.js diff --git a/controller/suggest.js b/controller/suggest.js index 36c3cc98..956363b8 100644 --- a/controller/suggest.js +++ b/controller/suggest.js @@ -1,4 +1,5 @@ +var service = { suggest: require('../service/suggest') }; var geojsonify = require('../helper/geojsonify').suggest; function setup( backend, query ){ @@ -16,18 +17,11 @@ function setup( backend, query ){ }; // query backend - backend().client.suggest( cmd, function( err, data ){ + service.suggest( backend, cmd, function( err, docs ){ - var docs = []; - - // handle backend errors + // error handler 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 || []; - } - // convert docs to geojson var geojson = geojsonify( docs ); diff --git a/service/suggest.js b/service/suggest.js new file mode 100644 index 00000000..b12087aa --- /dev/null +++ b/service/suggest.js @@ -0,0 +1,28 @@ + +/** + + cmd can be any valid ES suggest command + +**/ + +function service( backend, cmd, cb ){ + + // query new backend + backend().client.suggest( cmd, function( err, data ){ + + // handle backend errors + if( err ){ return cb( err ); } + + // map returned documents + var docs = []; + if( data && Array.isArray( data.pelias ) && data.pelias.length ){ + docs = data['pelias'][0].options || []; + } + + // fire callback + return cb( null, docs ); + }); + +} + +module.exports = service; \ No newline at end of file From f1d13c13370994cf2fe1b69ef3d2c3379fe96e11 Mon Sep 17 00:00:00 2001 From: Harish Krishna Date: Mon, 3 Nov 2014 17:39:51 -0500 Subject: [PATCH 39/46] consolidating suggest_poi and suggest_admin into one suggest endpoint. fixing tests (more tests to come) --- app.js | 4 - controller/suggest.js | 120 +++++++++++++++------------ controller/suggest_admin.js | 50 ----------- controller/suggest_poi.js | 46 ---------- query/{suggest_poi.js => suggest.js} | 0 query/suggest_admin.js | 29 ------- test/unit/controller/suggest.js | 15 ++-- test/unit/mock/backend.js | 10 ++- test/unit/query/suggest.js | 2 +- 9 files changed, 83 insertions(+), 193 deletions(-) delete mode 100644 controller/suggest_admin.js delete mode 100644 controller/suggest_poi.js rename query/{suggest_poi.js => suggest.js} (100%) delete mode 100644 query/suggest_admin.js diff --git a/app.js b/app.js index 7bfe52ed..d4cf0257 100644 --- a/app.js +++ b/app.js @@ -22,8 +22,6 @@ var controllers = {}; controllers.index = require('./controller/index'); controllers.doc = require('./controller/doc'); controllers.suggest = require('./controller/suggest'); -controllers.suggest_poi = require('./controller/suggest_poi'); -controllers.suggest_admin = require('./controller/suggest_admin'); controllers.search = require('./controller/search'); /** ----------------------- routes ----------------------- **/ @@ -36,8 +34,6 @@ app.get( '/doc', sanitisers.doc.middleware, controllers.doc() ); // suggest API app.get( '/suggest', sanitisers.suggest.middleware, controllers.suggest() ); -app.get( '/suggest/poi', sanitisers.suggest.middleware, controllers.suggest_poi() ); -app.get( '/suggest/admin', sanitisers.suggest.middleware, controllers.suggest_admin() ); // search API app.get( '/search', sanitisers.search.middleware, controllers.search() ); diff --git a/controller/suggest.js b/controller/suggest.js index b4a5267e..9af8c9ae 100644 --- a/controller/suggest.js +++ b/controller/suggest.js @@ -4,17 +4,19 @@ var async = require('async'); function setup( backend, query ){ - function controller( req, res, next ){ + // allow overriding of dependencies + backend = backend || require('../src/backend'); + query = query || require('../query/suggest'); - // combine the 2 queries + function controller( req, res, next ){ - // allow overriding of dependencies - backend = backend || require('../src/backend'); - var query_admin = require('../query/suggest_admin'); - var query_poi = require('../query/suggest_poi'); var cmd = { - index: 'pelias' + index: 'pelias', + body: query( req.clean ) }; + + var SIZE = req.clean.size || 10; + var query_backend = function(cmd, callback) { // query backend backend().client.suggest( cmd, function( err, data ){ @@ -55,57 +57,67 @@ function setup( backend, query ){ return res.status(200).json( geojson ); }; - var async_query; - - if (req.clean.input.length < 4 && isNaN(parseInt(req.clean.input, 10))) { - async_query = { - a: function(callback){ - cmd.body = query_admin( req.clean, 3 ); - query_backend(cmd, callback); - }, - b: function(callback){ - cmd.body = query_admin( req.clean, 1 ); - query_backend(cmd, callback); - }, - c: function(callback) { - cmd.body = query_poi( req.clean, 3 ); - query_backend(cmd, callback); + if (req.clean.input) { + var async_query; + + // admin only + req.admin = {}; + for (k in req.clean) { req.admin[k] = req.clean[k] } + req.admin.layers = ['admin0','admin1','admin2']; + + if (req.clean.input.length < 4 && isNaN(parseInt(req.clean.input, 10))) { + async_query = { + a: function(callback){ + cmd.body = query( req.admin, 3 ); + query_backend(cmd, callback); + }, + b: function(callback){ + cmd.body = query( req.admin, 1 ); + query_backend(cmd, callback); + }, + c: function(callback) { + cmd.body = query( req.clean, 3 ); + query_backend(cmd, callback); + } } - } - } else { - async_query = { - a: function(callback){ - cmd.body = query_poi( req.clean, 5); - query_backend(cmd, callback); - }, - b: function(callback){ - cmd.body = query_poi( req.clean, 3); - query_backend(cmd, callback); - }, - c: function(callback){ - cmd.body = query_poi( req.clean, 1 ); - query_backend(cmd, callback); - }, - d: function(callback){ - cmd.body = query_admin( req.clean ); - query_backend(cmd, callback); + } else { + async_query = { + a: function(callback){ + cmd.body = query( req.clean, 5); + query_backend(cmd, callback); + }, + b: function(callback){ + cmd.body = query( req.clean, 3); + query_backend(cmd, callback); + }, + c: function(callback){ + cmd.body = query( req.clean, 1 ); + query_backend(cmd, callback); + }, + d: function(callback){ + cmd.body = query( req.admin ); + query_backend(cmd, callback); + } } } - } - - async.parallel(async_query, function(err, results) { - var splice_length = parseInt((req.clean.size / Object.keys(results).length), 10); - - // results is equal to: {a: docs, b: docs, c: docs} - var combined = []; - for (keys in results) { - combined = combined.concat(results[keys].splice(0,splice_length)); - } - combined = dedup(combined); - respond(combined); - }); - + async.parallel(async_query, function(err, results) { + var splice_length = parseInt((SIZE / Object.keys(results).length), 10); + // results is equal to: {a: docs, b: docs, c: docs} + var combined = []; + for (keys in results) { + combined = combined.concat(results[keys].splice(0,splice_length)); + } + + combined = dedup(combined); + respond(combined); + }); + } else { + query_backend(cmd, function(err, results) { + respond(results); + }); + } + } return controller; diff --git a/controller/suggest_admin.js b/controller/suggest_admin.js deleted file mode 100644 index 743865a5..00000000 --- a/controller/suggest_admin.js +++ /dev/null @@ -1,50 +0,0 @@ - -/** - README: http://www.elasticsearch.org/guide/en/elasticsearch/client/javascript-api/current/api-reference.html#api-suggest -**/ - -var geojsonify = require('../helper/geojsonify').suggest; - -function setup( backend, query ){ - - // allow overriding of dependencies - backend = backend || require('../src/backend'); - query = query || require('../query/suggest_admin'); - - function controller( req, res, next ){ - - // backend command - var cmd = { - index: 'pelias', - body: query( req.clean ) - }; - - // query backend - backend().client.suggest( cmd, function( err, data ){ - - var docs = []; - - // 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 || []; - } - - // convert docs to geojson - var geojson = geojsonify( docs ); - - // response envelope - geojson.date = new Date().getTime(); - - // respond - return res.status(200).json( geojson ); - }); - - } - - return controller; -} - -module.exports = setup; \ No newline at end of file diff --git a/controller/suggest_poi.js b/controller/suggest_poi.js deleted file mode 100644 index 9e30b41a..00000000 --- a/controller/suggest_poi.js +++ /dev/null @@ -1,46 +0,0 @@ - -var geojsonify = require('../helper/geojsonify').suggest; - -function setup( backend, query ){ - - // allow overriding of dependencies - backend = backend || require('../src/backend'); - query = query || require('../query/suggest_poi'); - - function controller( req, res, next ){ - - // backend command - var cmd = { - index: 'pelias', - body: query( req.clean ) - }; - - // query backend - backend().client.suggest( cmd, function( err, data ){ - - var docs = []; - - // 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 || []; - } - - // convert docs to geojson - var geojson = geojsonify( docs ); - - // response envelope - geojson.date = new Date().getTime(); - - // respond - return res.status(200).json( geojson ); - }); - - } - - return controller; -} - -module.exports = setup; \ No newline at end of file diff --git a/query/suggest_poi.js b/query/suggest.js similarity index 100% rename from query/suggest_poi.js rename to query/suggest.js diff --git a/query/suggest_admin.js b/query/suggest_admin.js deleted file mode 100644 index 46883744..00000000 --- a/query/suggest_admin.js +++ /dev/null @@ -1,29 +0,0 @@ - -var logger = require('../src/logger'); - -// Build pelias suggest query -function generate( params, precision ){ - - var cmd = { - 'pelias' : { - 'text' : params.input, - 'completion' : { - 'size' : params.size, - 'field' : 'suggest', - 'context': { - 'dataset': ['admin0','admin1','admin2'], - 'location': { - 'value': [ params.lon, params.lat ], - 'precision': precision || 1 - } - } - } - } - }; - - // logger.log( 'cmd', JSON.stringify( cmd, null, 2 ) ); - return cmd; - -} - -module.exports = generate; \ No newline at end of file diff --git a/test/unit/controller/suggest.js b/test/unit/controller/suggest.js index bb3f2cc4..57051964 100644 --- a/test/unit/controller/suggest.js +++ b/test/unit/controller/suggest.js @@ -1,5 +1,5 @@ -var setup = require('../../../controller/suggest_poi'), +var setup = require('../../../controller/suggest'), mockBackend = require('../mock/backend'), mockQuery = require('../mock/query'); @@ -24,7 +24,7 @@ module.exports.tests.functional_success = function(test, common) { coordinates: [ 101, -10.1 ] }, properties: { - id: 'mockid', + id: 'mockid1', type: 'mocktype', value: 1 } @@ -35,7 +35,7 @@ module.exports.tests.functional_success = function(test, common) { coordinates: [ 101, -10.1 ] }, properties: { - id: 'mockid', + id: 'mockid2', type: 'mocktype', value: 2 } @@ -43,7 +43,12 @@ module.exports.tests.functional_success = function(test, common) { test('functional success', function(t) { var backend = mockBackend( 'client/suggest/ok/1', function( cmd ){ - t.deepEqual(cmd, { body: { a: 'b' }, index: 'pelias' }, 'correct backend command'); + if (cmd.body.layers) { + // layers are set exclusively for admin: test for admin-only layers + t.deepEqual(cmd, { body: { input: 'b', layers: [ 'admin0', 'admin1', 'admin2' ] }, index: 'pelias' }, 'correct backend command'); + } else { + t.deepEqual(cmd, { body: { input: 'b' }, index: 'pelias' }, 'correct backend command'); + } }); var controller = setup( backend, mockQuery() ); var res = { @@ -60,7 +65,7 @@ module.exports.tests.functional_success = function(test, common) { t.end(); } }; - controller( { clean: { a: 'b' } }, res ); + controller( { clean: { input: 'b' } }, res ); }); }; diff --git a/test/unit/mock/backend.js b/test/unit/mock/backend.js index 63378423..91560a35 100644 --- a/test/unit/mock/backend.js +++ b/test/unit/mock/backend.js @@ -1,12 +1,14 @@ -var mockPayload = { - id: 'mocktype/mockid', - geo: '101,-10.1' +var mockPayload = function(id){ + return { + id: 'mocktype/mockid'+id, + geo: '101,-10.1' + } }; var responses = {}; responses['client/suggest/ok/1'] = function( cmd, cb ){ - return cb( undefined, suggestEnvelope([ { value: 1, payload: mockPayload }, { value: 2, payload: mockPayload } ]) ); + return cb( undefined, suggestEnvelope([ { value: 1, payload: mockPayload(1) }, { value: 2, payload: mockPayload(2) } ]) ); }; responses['client/suggest/fail/1'] = function( cmd, cb ){ return cb( 'a backend error occurred' ); diff --git a/test/unit/query/suggest.js b/test/unit/query/suggest.js index c072d224..77c3f099 100644 --- a/test/unit/query/suggest.js +++ b/test/unit/query/suggest.js @@ -1,5 +1,5 @@ -var generate = require('../../../query/suggest_poi'); +var generate = require('../../../query/suggest'); module.exports.tests = {}; From be39fb5a8f45e5a1f818e78814fa4043f1da59c9 Mon Sep 17 00:00:00 2001 From: Harish Krishna Date: Tue, 4 Nov 2014 13:51:59 -0500 Subject: [PATCH 40/46] adding tests for the doc controller --- test/unit/controller/doc.js | 93 +++++++++++++++++++++++++++++++++++++ test/unit/mock/backend.js | 27 +++++++++++ test/unit/run.js | 1 + 3 files changed, 121 insertions(+) create mode 100644 test/unit/controller/doc.js diff --git a/test/unit/controller/doc.js b/test/unit/controller/doc.js new file mode 100644 index 00000000..98b5a174 --- /dev/null +++ b/test/unit/controller/doc.js @@ -0,0 +1,93 @@ + +var setup = require('../../../controller/doc'), + 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) { + + // expected geojson features for 'client/doc/ok/1' fixture + var expected = [{ + type: 'Feature', + geometry: { + type: 'Point', + coordinates: [ -50.5, 100.1 ] + }, + properties: { + name: 'test name1', + admin0: 'country1', + admin1: 'state1', + admin2: 'city1' + } + }, { + type: 'Feature', + geometry: { + type: 'Point', + coordinates: [ -51.5, 100.2 ] + }, + properties: { + name: 'test name2', + admin0: 'country2', + admin1: 'state2', + admin2: 'city2' + } + }]; + + test('functional success', function(t) { + var backend = mockBackend( 'client/doc/ok/1', function( cmd ){ + t.deepEqual(cmd, { body: { docs: [ { _id: 123, _index: 'pelias', _type: 'a' } ] } }, '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.equal(json.type, 'FeatureCollection', 'valid geojson'); + t.true(Array.isArray(json.features), 'features is array'); + t.deepEqual(json.features, expected, 'values correctly mapped'); + t.end(); + } + }; + controller( { clean: { ids: [ {'id' : 123, 'type': 'a' } ] } }, res ); + }); +}; + +// functionally test controller (backend failure) +module.exports.tests.functional_failure = function(test, common) { + test('functional failure', function(t) { + var backend = mockBackend( 'client/doc/fail/1', function( cmd ){ + t.deepEqual(cmd, { body: { docs: [ { _id: 123, _index: 'pelias', _type: 'b' } ] } }, '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: { ids: [ {'id' : 123, 'type': 'b' } ] } }, undefined, next ); + }); +}; + +module.exports.all = function (tape, common) { + + function test(name, testFunction) { + return tape('GET /doc ' + 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 index 63378423..1d0c3aaf 100644 --- a/test/unit/mock/backend.js +++ b/test/unit/mock/backend.js @@ -32,10 +32,33 @@ responses['client/search/fail/1'] = function( cmd, cb ){ return cb( 'a backend error occurred' ); }; +responses['client/doc/ok/1'] = function( cmd, cb ){ + return cb( undefined, docEnvelope([{ + _source: { + value: 1, + center_point: { lat: 100.1, lon: -50.5 }, + name: { default: 'test name1' }, + admin0: 'country1', admin1: 'state1', admin2: 'city1' + } + }, { + _source: { + value: 2, + center_point: { lat: 100.2, lon: -51.5 }, + name: { default: 'test name2' }, + admin0: 'country2', admin1: 'state2', admin2: 'city2' + } + }])); +}; +responses['client/doc/fail/1'] = responses['client/search/fail/1']; + function setup( key, cmdCb ){ function backend( a, b ){ return { client: { + mget: function( cmd, cb ){ + if( 'function' === typeof cmdCb ){ cmdCb( cmd ); } + return responses[key].apply( this, arguments ); + }, suggest: function( cmd, cb ){ if( 'function' === typeof cmdCb ){ cmdCb( cmd ); } return responses[key].apply( this, arguments ); @@ -50,6 +73,10 @@ function setup( key, cmdCb ){ return backend; } +function docEnvelope( options ){ + return { docs: options }; +} + function suggestEnvelope( options ){ return { pelias: [{ options: options }]}; } diff --git a/test/unit/run.js b/test/unit/run.js index bad988db..6b1398f9 100644 --- a/test/unit/run.js +++ b/test/unit/run.js @@ -4,6 +4,7 @@ var common = {}; var tests = [ require('./controller/index'), + require('./controller/doc'), require('./controller/suggest'), require('./controller/search'), require('./sanitiser/suggest'), From a53a6fd6d8e4a9c4e2877c32b3952ae8f78164d8 Mon Sep 17 00:00:00 2001 From: Harish Krishna Date: Tue, 4 Nov 2014 14:34:03 -0500 Subject: [PATCH 41/46] no query needed --- test/unit/controller/doc.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/test/unit/controller/doc.js b/test/unit/controller/doc.js index 98b5a174..7b481a9f 100644 --- a/test/unit/controller/doc.js +++ b/test/unit/controller/doc.js @@ -1,7 +1,6 @@ var setup = require('../../../controller/doc'), - mockBackend = require('../mock/backend'), - mockQuery = require('../mock/query'); + mockBackend = require('../mock/backend'); module.exports.tests = {}; @@ -47,7 +46,7 @@ module.exports.tests.functional_success = function(test, common) { var backend = mockBackend( 'client/doc/ok/1', function( cmd ){ t.deepEqual(cmd, { body: { docs: [ { _id: 123, _index: 'pelias', _type: 'a' } ] } }, 'correct backend command'); }); - var controller = setup( backend, mockQuery() ); + var controller = setup( backend ); var res = { status: function( code ){ t.equal(code, 200, 'status set'); @@ -72,7 +71,7 @@ module.exports.tests.functional_failure = function(test, common) { var backend = mockBackend( 'client/doc/fail/1', function( cmd ){ t.deepEqual(cmd, { body: { docs: [ { _id: 123, _index: 'pelias', _type: 'b' } ] } }, 'correct backend command'); }); - var controller = setup( backend, mockQuery() ); + var controller = setup( backend ); var next = function( message ){ t.equal(message,'a backend error occurred','error passed to errorHandler'); t.end(); From b1424797cda6d2aeb8eb2578f2ac7a9389fec231 Mon Sep 17 00:00:00 2001 From: Harish Krishna Date: Tue, 4 Nov 2014 15:42:52 -0500 Subject: [PATCH 42/46] adding tests for service wrapper --- test/unit/run.js | 3 ++ test/unit/service/mget.js | 82 ++++++++++++++++++++++++++++++++++++ test/unit/service/search.js | 82 ++++++++++++++++++++++++++++++++++++ test/unit/service/suggest.js | 77 +++++++++++++++++++++++++++++++++ 4 files changed, 244 insertions(+) create mode 100644 test/unit/service/mget.js create mode 100644 test/unit/service/search.js create mode 100644 test/unit/service/suggest.js diff --git a/test/unit/run.js b/test/unit/run.js index 6b1398f9..ec1309be 100644 --- a/test/unit/run.js +++ b/test/unit/run.js @@ -7,6 +7,9 @@ var tests = [ require('./controller/doc'), require('./controller/suggest'), require('./controller/search'), + require('./service/mget'), + require('./service/search'), + require('./service/suggest'), require('./sanitiser/suggest'), require('./sanitiser/doc'), require('./query/indeces'), diff --git a/test/unit/service/mget.js b/test/unit/service/mget.js new file mode 100644 index 00000000..0b43f717 --- /dev/null +++ b/test/unit/service/mget.js @@ -0,0 +1,82 @@ + +var setup = require('../../../service/mget'), + mockBackend = require('../mock/backend'); + +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.end(); + }); +}; + +// functionally test service +module.exports.tests.functional_success = function(test, common) { + + var expected = [ + { + value: 1, + center_point: { lat: 100.1, lon: -50.5 }, + name: { default: 'test name1' }, + admin0: 'country1', admin1: 'state1', admin2: 'city1' + }, + { + value: 2, + center_point: { lat: 100.2, lon: -51.5 }, + name: { default: 'test name2' }, + admin0: 'country2', admin1: 'state2', admin2: 'city2' + } + ]; + + test('valid query', function(t) { + var backend = mockBackend( 'client/doc/ok/1', function( cmd ){ + t.deepEqual(cmd, { body: { docs: [ { _id: 123, _index: 'pelias', _type: 'a' } ] } }, 'correct backend command'); + }); + setup( backend, [ { _id: 123, _index: 'pelias', _type: 'a' } ], function(err, data) { + t.true(Array.isArray(data), 'returns an array'); + data.forEach(function(d) { + t.true(typeof d === 'object', 'valid object'); + }); + t.deepEqual(data, expected, 'values correctly mapped') + t.end(); + }); + }); + +}; + +// functionally test service +module.exports.tests.functional_failure = function(test, common) { + + test('invalid query', function(t) { + var invalid_queries = [ + { _id: 123, _index: 'pelias' }, + { _id: 123, _type: 'a' }, + { _index: 'pelias', _type: 'a' }, + { } + ]; + + var backend = mockBackend( 'client/doc/fail/1', function( cmd ){ + t.notDeepEqual(cmd, { body: { docs: [ { _id: 123, _index: 'pelias', _type: 'a' } ] } }, 'incorrect backend command'); + }); + invalid_queries.forEach(function(query) { + setup( backend, [ query ], function(err, data) { + t.equal(err, 'a backend error occurred','error passed to errorHandler'); + t.equal(data, undefined, 'data is undefined'); + }); + }); + t.end(); + }); + +}; + +module.exports.all = function (tape, common) { + + function test(name, testFunction) { + return tape('SERVICE /mget ' + 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/service/search.js b/test/unit/service/search.js new file mode 100644 index 00000000..2e1b3393 --- /dev/null +++ b/test/unit/service/search.js @@ -0,0 +1,82 @@ + +var setup = require('../../../service/search'), + mockBackend = require('../mock/backend'); + +var example_valid_es_query = { body: { a: 'b' }, index: 'pelias' }; + +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.end(); + }); +}; + +// functionally test service +module.exports.tests.functional_success = function(test, common) { + + var expected = [ + { + value: 1, + center_point: { lat: 100.1, lon: -50.5 }, + name: { default: 'test name1' }, + admin0: 'country1', admin1: 'state1', admin2: 'city1' + }, + { + value: 2, + center_point: { lat: 100.2, lon: -51.5 }, + name: { default: 'test name2' }, + admin0: 'country2', admin1: 'state2', admin2: 'city2' + } + ]; + + test('valid ES query', function(t) { + var backend = mockBackend( 'client/search/ok/1', function( cmd ){ + t.deepEqual(cmd, example_valid_es_query, 'no change to the command'); + }); + setup( backend, example_valid_es_query, function(err, data) { + t.true(Array.isArray(data), 'returns an array'); + data.forEach(function(d) { + t.true(typeof d === 'object', 'valid object'); + }); + t.deepEqual(data, expected, 'values correctly mapped') + t.end(); + }); + }); + +}; + +// functionally test service +module.exports.tests.functional_failure = function(test, common) { + + test('invalid ES query', function(t) { + var invalid_queries = [ + { }, + { foo: 'bar' } + ]; + + var backend = mockBackend( 'client/search/fail/1', function( cmd ){ + t.notDeepEqual(cmd, example_valid_es_query, 'incorrect backend command'); + }); + invalid_queries.forEach(function(query) { + setup( backend, [ query ], function(err, data) { + t.equal(err, 'a backend error occurred','error passed to errorHandler'); + t.equal(data, undefined, 'data is undefined'); + }); + }); + t.end(); + }); + +}; + +module.exports.all = function (tape, common) { + + function test(name, testFunction) { + return tape('SERVICE /search ' + 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/service/suggest.js b/test/unit/service/suggest.js new file mode 100644 index 00000000..0f57e02c --- /dev/null +++ b/test/unit/service/suggest.js @@ -0,0 +1,77 @@ + +var setup = require('../../../service/suggest'), + mockBackend = require('../mock/backend'); + +var example_valid_es_query = { body: { a: 'b' }, index: 'pelias' }; + +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.end(); + }); +}; + +// functionally test service +module.exports.tests.functional_success = function(test, common) { + + var mockPayload = { + id: 'mocktype/mockid', + geo: '101,-10.1' + }; + + var expected = [ + { value: 1, payload: mockPayload }, + { value: 2, payload: mockPayload } + ]; + + test('valid ES query', function(t) { + var backend = mockBackend( 'client/suggest/ok/1', function( cmd ){ + t.deepEqual(cmd, example_valid_es_query, 'no change to the command'); + }); + setup( backend, example_valid_es_query, function(err, data) { + t.true(Array.isArray(data), 'returns an array'); + data.forEach(function(d) { + t.true(typeof d === 'object', 'valid object'); + }); + t.deepEqual(data, expected, 'values correctly mapped') + t.end(); + }); + }); + +}; + +// functionally test service +module.exports.tests.functional_failure = function(test, common) { + + test('invalid ES query', function(t) { + var invalid_queries = [ + { }, + { foo: 'bar' } + ]; + + var backend = mockBackend( 'client/suggest/fail/1', function( cmd ){ + t.notDeepEqual(cmd, example_valid_es_query, 'incorrect backend command'); + }); + invalid_queries.forEach(function(query) { + setup( backend, [ query ], function(err, data) { + t.equal(err, 'a backend error occurred','error passed to errorHandler'); + t.equal(data, undefined, 'data is undefined'); + }); + }); + t.end(); + }); + +}; + +module.exports.all = function (tape, common) { + + function test(name, testFunction) { + return tape('SERVICE /suggest ' + name, testFunction); + } + + for( var testCase in module.exports.tests ){ + module.exports.tests[testCase](test, common); + } +}; \ No newline at end of file From dbd800b97f633cf920d99317a36d212dfe693cbe Mon Sep 17 00:00:00 2001 From: Harish Krishna Date: Tue, 4 Nov 2014 16:33:38 -0500 Subject: [PATCH 43/46] maintain the order --- controller/suggest.js | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/controller/suggest.js b/controller/suggest.js index e5f87084..425b7415 100644 --- a/controller/suggest.js +++ b/controller/suggest.js @@ -62,34 +62,34 @@ function setup( backend, query ){ if (req.clean.input.length < 4 && isNaN(parseInt(req.clean.input, 10))) { async_query = { - a: function(callback){ + admin_3p: function(callback){ cmd.body = query( req.admin, 3 ); query_backend(cmd, callback); }, - b: function(callback){ + admin_1p: function(callback){ cmd.body = query( req.admin, 1 ); query_backend(cmd, callback); }, - c: function(callback) { + all_3p: function(callback) { cmd.body = query( req.clean, 3 ); query_backend(cmd, callback); } } } else { async_query = { - a: function(callback){ + all_5p: function(callback){ cmd.body = query( req.clean, 5); query_backend(cmd, callback); }, - b: function(callback){ + all_3p: function(callback){ cmd.body = query( req.clean, 3); query_backend(cmd, callback); }, - c: function(callback){ + all_1p: function(callback){ cmd.body = query( req.clean, 1 ); query_backend(cmd, callback); }, - d: function(callback){ + admin_1p: function(callback){ cmd.body = query( req.admin ); query_backend(cmd, callback); } @@ -97,12 +97,14 @@ function setup( backend, query ){ } async.parallel(async_query, function(err, results) { - var splice_length = parseInt((SIZE / Object.keys(results).length), 10); // results is equal to: {a: docs, b: docs, c: docs} + var splice_length = parseInt((SIZE / Object.keys(results).length), 10); + var results_keys = Object.keys(async_query); + var combined = []; - for (keys in results) { - combined = combined.concat(results[keys].splice(0,splice_length)); - } + results_keys.forEach(function(key){ + combined = combined.concat(results[key].splice(0,splice_length)); + }); combined = dedup(combined); respond(combined); From 886aa498f5743049f763be761c74162a80db4301 Mon Sep 17 00:00:00 2001 From: Harish Krishna Date: Wed, 5 Nov 2014 12:35:06 -0500 Subject: [PATCH 44/46] bringing back vanilla suggest (suggestions based on lat/lon/zoom only) - calling it suggest_near_me and adding a new endpoint (plus tests) --- app.js | 2 + controller/suggest_near_me.js | 40 +++++++++++ test/unit/controller/suggest_near_me.js | 91 +++++++++++++++++++++++++ test/unit/run.js | 1 + 4 files changed, 134 insertions(+) create mode 100644 controller/suggest_near_me.js create mode 100644 test/unit/controller/suggest_near_me.js diff --git a/app.js b/app.js index d4cf0257..51eada83 100644 --- a/app.js +++ b/app.js @@ -22,6 +22,7 @@ var controllers = {}; controllers.index = require('./controller/index'); controllers.doc = require('./controller/doc'); controllers.suggest = require('./controller/suggest'); +controllers.suggest_near_me = require('./controller/suggest_near_me'); controllers.search = require('./controller/search'); /** ----------------------- routes ----------------------- **/ @@ -34,6 +35,7 @@ app.get( '/doc', sanitisers.doc.middleware, controllers.doc() ); // suggest API app.get( '/suggest', sanitisers.suggest.middleware, controllers.suggest() ); +app.get( '/suggest_near_me', sanitisers.suggest.middleware, controllers.suggest_near_me() ); // search API app.get( '/search', sanitisers.search.middleware, controllers.search() ); diff --git a/controller/suggest_near_me.js b/controller/suggest_near_me.js new file mode 100644 index 00000000..956363b8 --- /dev/null +++ b/controller/suggest_near_me.js @@ -0,0 +1,40 @@ + +var service = { suggest: require('../service/suggest') }; +var geojsonify = require('../helper/geojsonify').suggest; + +function setup( backend, query ){ + + // allow overriding of dependencies + backend = backend || require('../src/backend'); + query = query || require('../query/suggest'); + + function controller( req, res, next ){ + + // backend command + var cmd = { + index: 'pelias', + body: query( req.clean ) + }; + + // query backend + service.suggest( backend, cmd, function( err, docs ){ + + // error handler + if( err ){ return next( err ); } + + // convert docs to geojson + var geojson = geojsonify( docs ); + + // response envelope + geojson.date = new Date().getTime(); + + // respond + return res.status(200).json( geojson ); + }); + + } + + return controller; +} + +module.exports = setup; \ No newline at end of file diff --git a/test/unit/controller/suggest_near_me.js b/test/unit/controller/suggest_near_me.js new file mode 100644 index 00000000..bfb96195 --- /dev/null +++ b/test/unit/controller/suggest_near_me.js @@ -0,0 +1,91 @@ + +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) { + + // expected geojson features for 'client/suggest/ok/1' fixture + var expected = [{ + type: 'Feature', + geometry: { + type: 'Point', + coordinates: [ 101, -10.1 ] + }, + properties: { + id: 'mockid1', + type: 'mocktype', + value: 1 + } + }, { + type: 'Feature', + geometry: { + type: 'Point', + coordinates: [ 101, -10.1 ] + }, + properties: { + id: 'mockid2', + type: 'mocktype', + value: 2 + } + }]; + + test('functional success', 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.equal(json.type, 'FeatureCollection', 'valid geojson'); + t.true(Array.isArray(json.features), 'features is array'); + t.deepEqual(json.features, expected, '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 failure', 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/run.js b/test/unit/run.js index ec1309be..f828c6c5 100644 --- a/test/unit/run.js +++ b/test/unit/run.js @@ -6,6 +6,7 @@ var tests = [ require('./controller/index'), require('./controller/doc'), require('./controller/suggest'), + require('./controller/suggest_near_me'), require('./controller/search'), require('./service/mget'), require('./service/search'), From 407c32e782099627970cba7abbb73878a842caa5 Mon Sep 17 00:00:00 2001 From: Harish Krishna Date: Wed, 5 Nov 2014 16:43:19 -0500 Subject: [PATCH 45/46] suggest_near_me to /suggest/nearby and suggest_near_me.js to suggest_nearby.js --- app.js | 4 ++-- controller/{suggest_near_me.js => suggest_nearby.js} | 0 .../unit/controller/{suggest_near_me.js => suggest_nearby.js} | 0 test/unit/run.js | 2 +- 4 files changed, 3 insertions(+), 3 deletions(-) rename controller/{suggest_near_me.js => suggest_nearby.js} (100%) rename test/unit/controller/{suggest_near_me.js => suggest_nearby.js} (100%) diff --git a/app.js b/app.js index 51eada83..21b465f0 100644 --- a/app.js +++ b/app.js @@ -22,7 +22,7 @@ var controllers = {}; controllers.index = require('./controller/index'); controllers.doc = require('./controller/doc'); controllers.suggest = require('./controller/suggest'); -controllers.suggest_near_me = require('./controller/suggest_near_me'); +controllers.suggest_nearby = require('./controller/suggest_nearby'); controllers.search = require('./controller/search'); /** ----------------------- routes ----------------------- **/ @@ -35,7 +35,7 @@ app.get( '/doc', sanitisers.doc.middleware, controllers.doc() ); // suggest API app.get( '/suggest', sanitisers.suggest.middleware, controllers.suggest() ); -app.get( '/suggest_near_me', sanitisers.suggest.middleware, controllers.suggest_near_me() ); +app.get( '/suggest/nearby', sanitisers.suggest.middleware, controllers.suggest_nearby() ); // search API app.get( '/search', sanitisers.search.middleware, controllers.search() ); diff --git a/controller/suggest_near_me.js b/controller/suggest_nearby.js similarity index 100% rename from controller/suggest_near_me.js rename to controller/suggest_nearby.js diff --git a/test/unit/controller/suggest_near_me.js b/test/unit/controller/suggest_nearby.js similarity index 100% rename from test/unit/controller/suggest_near_me.js rename to test/unit/controller/suggest_nearby.js diff --git a/test/unit/run.js b/test/unit/run.js index f828c6c5..4428903c 100644 --- a/test/unit/run.js +++ b/test/unit/run.js @@ -6,7 +6,7 @@ var tests = [ require('./controller/index'), require('./controller/doc'), require('./controller/suggest'), - require('./controller/suggest_near_me'), + require('./controller/suggest_nearby'), require('./controller/search'), require('./service/mget'), require('./service/search'), From 49a63f1861e5cea2b09ce1d363bf8cb7934f0284 Mon Sep 17 00:00:00 2001 From: Harish Krishna Date: Thu, 6 Nov 2014 11:46:06 -0500 Subject: [PATCH 46/46] Documentation and some ciao tests --- docs/404.md | 12 +- docs/cors.md | 4 +- docs/doc/msuccess.md | 20 +-- docs/doc/success.md | 20 +-- docs/index.md | 28 +-- docs/jsonp.md | 14 +- docs/reverse/success.md | 24 +-- docs/search/success.md | 18 +- docs/suggest/success.md | 71 ++++++-- docs/suggest/success_nearby.md | 223 ++++++++++++++++++++++++ test/ciao/suggest/success_nearby.coffee | 16 ++ 11 files changed, 369 insertions(+), 81 deletions(-) create mode 100644 docs/suggest/success_nearby.md create mode 100644 test/ciao/suggest/success_nearby.coffee diff --git a/docs/404.md b/docs/404.md index 66a3567d..917c5ad4 100644 --- a/docs/404.md +++ b/docs/404.md @@ -1,6 +1,6 @@ # invalid path -*Generated: Thu Oct 23 2014 11:58:14 GMT-0400 (EDT)* +*Generated: Thu Nov 06 2014 11:44:19 GMT-0500 (EST)* ## Request ```javascript { @@ -27,7 +27,7 @@ Status: 404 "content-type": "application/json; charset=utf-8", "content-length": "35", "etag": "W/\"23-dfdfa185\"", - "date": "Thu, 23 Oct 2014 15:58:14 GMT", + "date": "Thu, 06 Nov 2014 16:44:19 GMT", "connection": "close" } ``` @@ -39,9 +39,9 @@ Status: 404 ## Tests -### ✓ cache-control header correctly set +### ✓ not found ```javascript -response.should.have.header 'Cache-Control','public,max-age=300' +response.statusCode.should.equal 404 ``` ### ✓ content-type header correctly set @@ -49,9 +49,9 @@ response.should.have.header 'Cache-Control','public,max-age=300' response.should.have.header 'Content-Type','application/json; charset=utf-8' ``` -### ✓ not found +### ✓ cache-control header correctly set ```javascript -response.statusCode.should.equal 404 +response.should.have.header 'Cache-Control','public,max-age=300' ``` ### ✓ should respond in json with server info diff --git a/docs/cors.md b/docs/cors.md index 1f6e7e46..17ddda39 100644 --- a/docs/cors.md +++ b/docs/cors.md @@ -1,6 +1,6 @@ # cross-origin resource sharing -*Generated: Thu Oct 23 2014 11:58:14 GMT-0400 (EDT)* +*Generated: Thu Nov 06 2014 11:44:19 GMT-0500 (EST)* ## Request ```javascript { @@ -27,7 +27,7 @@ Status: 200 "content-type": "application/json; charset=utf-8", "content-length": "50", "etag": "W/\"32-85536434\"", - "date": "Thu, 23 Oct 2014 15:58:14 GMT", + "date": "Thu, 06 Nov 2014 16:44:19 GMT", "connection": "close" } ``` diff --git a/docs/doc/msuccess.md b/docs/doc/msuccess.md index c9f3d8c9..1f510a42 100644 --- a/docs/doc/msuccess.md +++ b/docs/doc/msuccess.md @@ -1,6 +1,6 @@ # valid doc query -*Generated: Thu Oct 23 2014 11:58:14 GMT-0400 (EDT)* +*Generated: Thu Nov 06 2014 11:44:19 GMT-0500 (EST)* ## Request ```javascript { @@ -26,8 +26,8 @@ Status: 200 "access-control-allow-credentials": "true", "content-type": "application/json; charset=utf-8", "content-length": "555", - "etag": "W/\"22b-6aa14642\"", - "date": "Thu, 23 Oct 2014 15:58:14 GMT", + "etag": "W/\"22b-dd736629\"", + "date": "Thu, 06 Nov 2014 16:44:19 GMT", "connection": "close" } ``` @@ -70,7 +70,7 @@ Status: 200 } } ], - "date": 1414079894512 + "date": 1415292259726 } ``` @@ -81,6 +81,12 @@ Status: 200 response.statusCode.should.equal 200 ``` +### ✓ valid geojson +```javascript +json.type.should.equal 'FeatureCollection' +json.features.should.be.instanceof Array +``` + ### ✓ valid response ```javascript now = new Date().getTime() @@ -89,9 +95,3 @@ should.not.exist json.error json.date.should.be.within now-5000, now+5000 ``` -### ✓ valid geojson -```javascript -json.type.should.equal 'FeatureCollection' -json.features.should.be.instanceof Array -``` - diff --git a/docs/doc/success.md b/docs/doc/success.md index 34179af0..a6dd6c00 100644 --- a/docs/doc/success.md +++ b/docs/doc/success.md @@ -1,6 +1,6 @@ # valid doc query -*Generated: Thu Oct 23 2014 11:58:14 GMT-0400 (EDT)* +*Generated: Thu Nov 06 2014 11:44:20 GMT-0500 (EST)* ## Request ```javascript { @@ -26,8 +26,8 @@ Status: 200 "access-control-allow-credentials": "true", "content-type": "application/json; charset=utf-8", "content-length": "311", - "etag": "W/\"137-ab9138f7\"", - "date": "Thu, 23 Oct 2014 15:58:14 GMT", + "etag": "W/\"137-1644173e\"", + "date": "Thu, 06 Nov 2014 16:44:20 GMT", "connection": "close" } ``` @@ -53,12 +53,18 @@ Status: 200 } } ], - "date": 1414079894512 + "date": 1415292260057 } ``` ## Tests +### ✓ valid geojson +```javascript +json.type.should.equal 'FeatureCollection' +json.features.should.be.instanceof Array +``` + ### ✓ valid response ```javascript now = new Date().getTime() @@ -72,9 +78,3 @@ json.date.should.be.within now-5000, now+5000 response.statusCode.should.equal 200 ``` -### ✓ valid geojson -```javascript -json.type.should.equal 'FeatureCollection' -json.features.should.be.instanceof Array -``` - diff --git a/docs/index.md b/docs/index.md index a386d38f..cde683b5 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,6 +1,6 @@ # api root -*Generated: Thu Oct 23 2014 11:58:14 GMT-0400 (EDT)* +*Generated: Thu Nov 06 2014 11:44:19 GMT-0500 (EST)* ## Request ```javascript { @@ -27,7 +27,7 @@ Status: 200 "content-type": "application/json; charset=utf-8", "content-length": "50", "etag": "W/\"32-85536434\"", - "date": "Thu, 23 Oct 2014 15:58:14 GMT", + "date": "Thu, 06 Nov 2014 16:44:19 GMT", "connection": "close" } ``` @@ -42,6 +42,11 @@ Status: 200 ## Tests +### ✓ charset header correctly set +```javascript +response.should.have.header 'Charset','utf8' +``` + ### ✓ endpoint available ```javascript response.statusCode.should.equal 200 @@ -53,16 +58,6 @@ response.should.have.header 'Server' response.headers.server.should.match /Pelias\/\d{1,2}\.\d{1,2}\.\d{1,2}/ ``` -### ✓ vanity header correctly set -```javascript -response.should.have.header 'X-Powered-By','mapzen' -``` - -### ✓ 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 @@ -70,13 +65,18 @@ should.exist json.name should.exist json.version ``` +### ✓ vanity header correctly set +```javascript +response.should.have.header 'X-Powered-By','mapzen' +``` + ### ✓ content-type header correctly set ```javascript response.should.have.header 'Content-Type','application/json; charset=utf-8' ``` -### ✓ charset header correctly set +### ✓ cache-control header correctly set ```javascript -response.should.have.header 'Charset','utf8' +response.should.have.header 'Cache-Control','public,max-age=60' ``` diff --git a/docs/jsonp.md b/docs/jsonp.md index f4ba0eca..b8c08577 100644 --- a/docs/jsonp.md +++ b/docs/jsonp.md @@ -1,6 +1,6 @@ # jsonp -*Generated: Thu Oct 23 2014 11:58:14 GMT-0400 (EDT)* +*Generated: Thu Nov 06 2014 11:44:19 GMT-0500 (EST)* ## Request ```javascript { @@ -27,7 +27,7 @@ Status: 200 "content-type": "application/javascript; charset=utf-8", "content-length": "57", "etag": "W/\"39-b8a2aba1\"", - "date": "Thu, 23 Oct 2014 15:58:14 GMT", + "date": "Thu, 06 Nov 2014 16:44:19 GMT", "connection": "close" } ``` @@ -37,14 +37,14 @@ test({"name":"pelias-api","version":{"number":"0.0.0"}}); ## Tests -### ✓ should respond with jsonp +### ✓ content-type header correctly set ```javascript -should.exist response.body -response.body.substr(0,5).should.equal 'test('; +response.should.have.header 'Content-Type','application/javascript; charset=utf-8' ``` -### ✓ content-type header correctly set +### ✓ should respond with jsonp ```javascript -response.should.have.header 'Content-Type','application/javascript; charset=utf-8' +should.exist response.body +response.body.substr(0,5).should.equal 'test('; ``` diff --git a/docs/reverse/success.md b/docs/reverse/success.md index 1813aab7..cb7e84e1 100644 --- a/docs/reverse/success.md +++ b/docs/reverse/success.md @@ -1,6 +1,6 @@ # valid reverse query -*Generated: Thu Oct 23 2014 11:58:15 GMT-0400 (EDT)* +*Generated: Thu Nov 06 2014 11:44:19 GMT-0500 (EST)* ## Request ```javascript { @@ -26,8 +26,8 @@ Status: 200 "access-control-allow-credentials": "true", "content-type": "application/json; charset=utf-8", "content-length": "282", - "etag": "W/\"11a-95fc1500\"", - "date": "Thu, 23 Oct 2014 15:58:15 GMT", + "etag": "W/\"11a-efcd00c9\"", + "date": "Thu, 06 Nov 2014 16:44:19 GMT", "connection": "close" } ``` @@ -53,12 +53,20 @@ Status: 200 } } ], - "date": 1414079895606 + "date": 1415292259729 } ``` ## Tests +### ✓ valid response +```javascript +now = new Date().getTime() +should.exist json +should.not.exist json.error +json.date.should.be.within now-5000, now+5000 +``` + ### ✓ 200 ok ```javascript response.statusCode.should.equal 200 @@ -70,11 +78,3 @@ json.type.should.equal 'FeatureCollection' json.features.should.be.instanceof Array ``` -### ✓ valid response -```javascript -now = new Date().getTime() -should.exist json -should.not.exist json.error -json.date.should.be.within now-5000, now+5000 -``` - diff --git a/docs/search/success.md b/docs/search/success.md index 6b64c548..50789c87 100644 --- a/docs/search/success.md +++ b/docs/search/success.md @@ -1,6 +1,6 @@ # valid search query -*Generated: Thu Oct 23 2014 11:58:15 GMT-0400 (EDT)* +*Generated: Thu Nov 06 2014 11:44:19 GMT-0500 (EST)* ## Request ```javascript { @@ -26,8 +26,8 @@ Status: 200 "access-control-allow-credentials": "true", "content-type": "application/json; charset=utf-8", "content-length": "2398", - "etag": "W/\"0tqT2h50EMVuqDtvmB5nAQ==\"", - "date": "Thu, 23 Oct 2014 15:58:15 GMT", + "etag": "W/\"NldeHivz2maJ3rqa73a+2w==\"", + "date": "Thu, 06 Nov 2014 16:44:19 GMT", "connection": "close" } ``` @@ -206,7 +206,7 @@ Status: 200 } } ], - "date": 1414079895605 + "date": 1415292259730 } ``` @@ -220,14 +220,14 @@ should.not.exist json.error json.date.should.be.within now-5000, now+5000 ``` -### ✓ valid geojson +### ✓ 200 ok ```javascript -json.type.should.equal 'FeatureCollection' -json.features.should.be.instanceof Array +response.statusCode.should.equal 200 ``` -### ✓ 200 ok +### ✓ valid geojson ```javascript -response.statusCode.should.equal 200 +json.type.should.equal 'FeatureCollection' +json.features.should.be.instanceof Array ``` diff --git a/docs/suggest/success.md b/docs/suggest/success.md index bc120b2c..8c430b53 100644 --- a/docs/suggest/success.md +++ b/docs/suggest/success.md @@ -1,6 +1,6 @@ # valid suggest query -*Generated: Thu Oct 23 2014 11:58:14 GMT-0400 (EDT)* +*Generated: Thu Nov 06 2014 11:44:19 GMT-0500 (EST)* ## Request ```javascript { @@ -25,27 +25,71 @@ Status: 200 "access-control-allow-headers": "X-Requested-With,content-type", "access-control-allow-credentials": "true", "content-type": "application/json; charset=utf-8", - "content-length": "63", - "etag": "W/\"3f-200731a6\"", - "date": "Thu, 23 Oct 2014 15:58:14 GMT", + "content-length": "571", + "etag": "W/\"23b-5d6e3dd3\"", + "date": "Thu, 06 Nov 2014 16:44:19 GMT", "connection": "close" } ``` ```javascript { "type": "FeatureCollection", - "features": [], - "date": 1414079894479 + "features": [ + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -8.481618, + 43.125692 + ] + }, + "properties": { + "text": "A Coruña", + "score": 14, + "type": "admin1", + "id": "3374:adm1:es:esp:a_coru_a" + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 7.56019, + 5.419786 + ] + }, + "properties": { + "text": "Abia", + "score": 14, + "type": "admin1", + "id": "1775:adm1:ng:nga:abia" + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 33.772337, + 2.826081 + ] + }, + "properties": { + "text": "Abim", + "score": 14, + "type": "admin1", + "id": "2848:adm1:ug:uga:abim" + } + } + ], + "date": 1415292259700 } ``` ## Tests -### ✓ 200 ok -```javascript -response.statusCode.should.equal 200 -``` - ### ✓ valid geojson ```javascript json.type.should.equal 'FeatureCollection' @@ -60,3 +104,8 @@ should.not.exist json.error json.date.should.be.within now-5000, now+5000 ``` +### ✓ 200 ok +```javascript +response.statusCode.should.equal 200 +``` + diff --git a/docs/suggest/success_nearby.md b/docs/suggest/success_nearby.md new file mode 100644 index 00000000..c29643c2 --- /dev/null +++ b/docs/suggest/success_nearby.md @@ -0,0 +1,223 @@ +# valid suggest query + +*Generated: Thu Nov 06 2014 11:44:20 GMT-0500 (EST)* +## Request +```javascript +{ + "protocol": "http:", + "host": "localhost", + "method": "GET", + "port": 3100, + "path": "/suggest/nearby?input=a&lat=29.49136&lon=-82.50622" +} +``` + +## Response +```javascript +Status: 200 +{ + "x-powered-by": "mapzen", + "charset": "utf8", + "cache-control": "public,max-age=60", + "server": "Pelias/0.0.0", + "access-control-allow-origin": "*", + "access-control-allow-methods": "GET", + "access-control-allow-headers": "X-Requested-With,content-type", + "access-control-allow-credentials": "true", + "content-type": "application/json; charset=utf-8", + "content-length": "2034", + "etag": "W/\"Do9VJ5hCbynTxDjtm5fNlg==\"", + "date": "Thu, 06 Nov 2014 16:44:19 GMT", + "connection": "close" +} +``` +```javascript +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -82.05231, + 29.17998 + ] + }, + "properties": { + "text": "Abiding Hope E V Lutheran Church, Marion County, Florida", + "score": 1, + "type": "geoname", + "id": "4145572" + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -82.10231, + 29.21942 + ] + }, + "properties": { + "text": "Abundant Harvest Ministries, Marion County, Florida", + "score": 1, + "type": "geoname", + "id": "4145578" + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -82.50622, + 29.49136 + ] + }, + "properties": { + "text": "Adam, Alachua County, Florida", + "score": 1, + "type": "geoname", + "id": "4145612" + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -82.75374, + 35.17789 + ] + }, + "properties": { + "text": "Adams Branch, Transylvania County, North Carolina", + "score": 1, + "type": "geoname", + "id": "4452189" + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -82.83012, + 29.4783 + ] + }, + "properties": { + "text": "Adamsville Cemetery, Levy County, Florida", + "score": 1, + "type": "geoname", + "id": "4145634" + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -82.01511, + 35.17289 + ] + }, + "properties": { + "text": "Africa School (historical), Spartanburg County, South Carolina", + "score": 1, + "type": "geoname", + "id": "4569065" + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -82.20426, + 29.25192 + ] + }, + "properties": { + "text": "Agape Baptist Church, Marion County, Florida", + "score": 1, + "type": "geoname", + "id": "4145673" + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -82.14954, + 29.19248 + ] + }, + "properties": { + "text": "Agnew Cemetery, Marion County, Florida", + "score": 1, + "type": "geoname", + "id": "4145677" + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -82.75429, + 35.16928 + ] + }, + "properties": { + "text": "Aiken Mountain, Transylvania County, North Carolina", + "score": 1, + "type": "geoname", + "id": "4452268" + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -82.15912, + 29.47877 + ] + }, + "properties": { + "text": "Alachua County Fire Rescue Station 31, Alachua County, Florida", + "score": 1, + "type": "geoname", + "id": "4152402" + } + } + ], + "date": 1415292259785 +} +``` + +## Tests + +### ✓ 200 ok +```javascript +response.statusCode.should.equal 200 +``` + +### ✓ valid response +```javascript +now = new Date().getTime() +should.exist json +should.not.exist json.error +json.date.should.be.within now-5000, now+5000 +``` + +### ✓ valid geojson +```javascript +json.type.should.equal 'FeatureCollection' +json.features.should.be.instanceof Array +``` + diff --git a/test/ciao/suggest/success_nearby.coffee b/test/ciao/suggest/success_nearby.coffee new file mode 100644 index 00000000..ca958720 --- /dev/null +++ b/test/ciao/suggest/success_nearby.coffee @@ -0,0 +1,16 @@ + +#> valid suggest query +path: '/suggest/nearby?input=a&lat=29.49136&lon=-82.50622' + +#? 200 ok +response.statusCode.should.equal 200 + +#? valid response +now = new Date().getTime() +should.exist json +should.not.exist json.error +json.date.should.be.within now-5000, now+5000 + +#? valid geojson +json.type.should.equal 'FeatureCollection' +json.features.should.be.instanceof Array \ No newline at end of file