diff --git a/app.js b/app.js index d4cf0257..21b465f0 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_nearby = require('./controller/suggest_nearby'); 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/nearby', sanitisers.suggest.middleware, controllers.suggest_nearby() ); // search API app.get( '/search', sanitisers.search.middleware, controllers.search() ); diff --git a/controller/suggest.js b/controller/suggest.js index b0a30f70..12269fef 100644 --- a/controller/suggest.js +++ b/controller/suggest.js @@ -4,6 +4,7 @@ var service = { mget: require('../service/mget') }; var geojsonify = require('../helper/geojsonify').search; +var async = require('async'); function setup( backend, query ){ @@ -13,14 +14,36 @@ function setup( backend, query ){ function controller( req, res, next ){ - // backend command var cmd = { index: 'pelias', body: query( req.clean ) }; - // responder - function reply( docs ){ + var SIZE = req.clean.size || 10; + + var query_backend = function(cmd, callback) { + // query backend + service.suggest( backend, cmd, function( err, docs ){ + + // error handler + if( err ){ return next( err ); } + + callback(null, docs); + }); + }; + + var dedup = function(combined) { + var unique_ids = []; + return combined.filter(function(item, pos) { + if (unique_ids.indexOf(item.text) == -1) { + unique_ids.push(item.text); + return true; + } + return false; + }); + }; + + var reply = function(docs) { // convert docs to geojson var geojson = geojsonify( docs ); @@ -30,21 +53,17 @@ function setup( backend, query ){ // respond return res.status(200).json( geojson ); - } - - // query backend - service.suggest( backend, cmd, function( err, suggested ){ + }; - // error handler - if( err ){ return next( err ); } + var respond = function(data) { // no documents suggested, return empty array to avoid ActionRequestValidationException - if( !Array.isArray( suggested ) || !suggested.length ){ + if( !Array.isArray( data ) || !data.length ){ return reply([]); } // map suggester output to mget query - var query = suggested.map( function( doc ) { + var query = data.map( function( doc ) { var idParts = doc.text.split(':'); return { _index: 'pelias', @@ -62,8 +81,72 @@ function setup( backend, query ){ return reply( docs ); }); - }); + }; + + 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 = { + admin_3p: function(callback){ + cmd.body = query( req.admin, 3 ); + query_backend(cmd, callback); + }, + admin_1p: function(callback){ + cmd.body = query( req.admin, 1 ); + query_backend(cmd, callback); + }, + all_3p: function(callback) { + cmd.body = query( req.clean, 3 ); + query_backend(cmd, callback); + } + } + } else { + async_query = { + all_5p: function(callback){ + cmd.body = query( req.clean, 5); + query_backend(cmd, callback); + }, + all_3p: function(callback){ + cmd.body = query( req.clean, 3); + query_backend(cmd, callback); + }, + all_1p: function(callback){ + cmd.body = query( req.clean, 1 ); + query_backend(cmd, callback); + }, + admin_1p: function(callback){ + cmd.body = query( req.admin ); + query_backend(cmd, callback); + } + } + } + + async.parallel(async_query, function(err, results) { + // 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 = []; + results_keys.forEach(function(key){ + combined = combined.concat(results[key].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_nearby.js b/controller/suggest_nearby.js new file mode 100644 index 00000000..b0a30f70 --- /dev/null +++ b/controller/suggest_nearby.js @@ -0,0 +1,72 @@ + +var service = { + suggest: require('../service/suggest'), + mget: require('../service/mget') +}; +var geojsonify = require('../helper/geojsonify').search; + +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 ) + }; + + // responder + function reply( docs ){ + + // convert docs to geojson + var geojson = geojsonify( docs ); + + // response envelope + geojson.date = new Date().getTime(); + + // respond + return res.status(200).json( geojson ); + } + + // query backend + service.suggest( backend, cmd, function( err, suggested ){ + + // error handler + if( err ){ return next( err ); } + + // no documents suggested, return empty array to avoid ActionRequestValidationException + if( !Array.isArray( suggested ) || !suggested.length ){ + return reply([]); + } + + // map suggester output to mget query + var query = suggested.map( function( doc ) { + var idParts = doc.text.split(':'); + return { + _index: 'pelias', + _type: idParts[0], + _id: idParts[1] + }; + }); + + service.mget( backend, query, function( err, docs ){ + + // error handler + if( err ){ return next( err ); } + + // reply + return reply( docs ); + + }); + }); + + } + + return controller; +} + +module.exports = setup; \ No newline at end of file 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/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", diff --git a/query/suggest.js b/query/suggest.js index ba086f88..29f2f0b3 100644 --- a/query/suggest.js +++ b/query/suggest.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) } } } 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 diff --git a/test/unit/controller/doc.js b/test/unit/controller/doc.js index 7b481a9f..02090257 100644 --- a/test/unit/controller/doc.js +++ b/test/unit/controller/doc.js @@ -23,10 +23,14 @@ module.exports.tests.functional_success = function(test, common) { coordinates: [ -50.5, 100.1 ] }, properties: { + id: 'myid1', + type: 'mytype1', + layer: 'mytype1', name: 'test name1', admin0: 'country1', admin1: 'state1', - admin2: 'city1' + admin2: 'city1', + text: 'test name1, city1, state1' } }, { type: 'Feature', @@ -35,15 +39,19 @@ module.exports.tests.functional_success = function(test, common) { coordinates: [ -51.5, 100.2 ] }, properties: { + id: 'myid2', + type: 'mytype2', + layer: 'mytype2', name: 'test name2', admin0: 'country2', admin1: 'state2', - admin2: 'city2' + admin2: 'city2', + text: 'test name2, city2, state2' } }]; test('functional success', function(t) { - var backend = mockBackend( 'client/doc/ok/1', function( cmd ){ + var backend = mockBackend( 'client/mget/ok/1', function( cmd ){ t.deepEqual(cmd, { body: { docs: [ { _id: 123, _index: 'pelias', _type: 'a' } ] } }, 'correct backend command'); }); var controller = setup( backend ); @@ -68,7 +76,7 @@ module.exports.tests.functional_success = function(test, common) { // 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 ){ + var backend = mockBackend( 'client/mget/fail/1', function( cmd ){ t.deepEqual(cmd, { body: { docs: [ { _id: 123, _index: 'pelias', _type: 'b' } ] } }, 'correct backend command'); }); var controller = setup( backend ); diff --git a/test/unit/controller/suggest.js b/test/unit/controller/suggest.js index 283e78cc..f36a0fa5 100644 --- a/test/unit/controller/suggest.js +++ b/test/unit/controller/suggest.js @@ -52,13 +52,15 @@ module.exports.tests.functional_success = function(test, common) { }]; test('functional success', function(t) { - var i = 0; var backend = mockBackend( 'client/suggest/ok/1', function( cmd ){ - // the backend executes 2 commands, so we check them both - if( ++i === 1 ){ - t.deepEqual(cmd, { body: { a: 'b' }, index: 'pelias' }, 'correct suggest command'); + // the backend executes suggest (vanilla and admin-only) and mget, so we check them all based on cmd + if( cmd.body.docs ){ + t.deepEqual(cmd, { body: { docs: [ { _id: 'mockid1', _index: 'pelias', _type: 'mocktype' } , { _id: 'mockid2', _index: 'pelias', _type: 'mocktype' }] } }, 'correct mget command'); + } else 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 suggest/admin command'); } else { - t.deepEqual(cmd, { body: { docs: [ { _id: 'mockid', _index: 'pelias', _type: 'mocktype' }, { _id: 'mockid', _index: 'pelias', _type: 'mocktype' } ] } }, 'correct mget command'); + t.deepEqual(cmd, { body: { input: 'b' }, index: 'pelias' }, 'correct suggest command'); } }); var controller = setup( backend, mockQuery() ); @@ -68,9 +70,6 @@ module.exports.tests.functional_success = function(test, common) { return res; }, json: function( json ){ - - console.log( 'json', json ); - t.equal(typeof json, 'object', 'returns json'); t.equal(typeof json.date, 'number', 'date set'); t.equal(json.type, 'FeatureCollection', 'valid geojson'); @@ -79,7 +78,7 @@ module.exports.tests.functional_success = function(test, common) { t.end(); } }; - controller( { clean: { a: 'b' } }, res ); + controller( { clean: { input: 'b' } }, res ); }); }; @@ -87,7 +86,14 @@ module.exports.tests.functional_success = function(test, common) { 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'); + if( cmd.body.docs ){ + t.deepEqual(cmd, { body: { docs: [ { _id: 'mockid1', _index: 'pelias', _type: 'mocktype' } , { _id: 'mockid2', _index: 'pelias', _type: 'mocktype' }] } }, 'correct mget command'); + } else if (cmd.body.layers) { + // layers are set exclusively for admin: test for admin-only layers + t.deepEqual(cmd, { body: { a: 'b', layers: [ 'admin0', 'admin1', 'admin2' ] }, index: 'pelias' }, 'correct suggest/admin command'); + } else { + t.deepEqual(cmd, { body: { a: 'b' }, index: 'pelias' }, 'correct suggest command'); + } }); var controller = setup( backend, mockQuery() ); var next = function( message ){ diff --git a/test/unit/controller/suggest_nearby.js b/test/unit/controller/suggest_nearby.js new file mode 100644 index 00000000..9501e71b --- /dev/null +++ b/test/unit/controller/suggest_nearby.js @@ -0,0 +1,107 @@ + +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/mget/ok/1' fixture + var expected = [{ + type: 'Feature', + geometry: { + type: 'Point', + coordinates: [ -50.5, 100.1 ] + }, + properties: { + id: 'myid1', + type: 'mytype1', + layer: 'mytype1', + name: 'test name1', + admin0: 'country1', + admin1: 'state1', + admin2: 'city1', + text: 'test name1, city1, state1' + } + }, { + type: 'Feature', + geometry: { + type: 'Point', + coordinates: [ -51.5, 100.2 ] + }, + properties: { + id: 'myid2', + type: 'mytype2', + layer: 'mytype2', + name: 'test name2', + admin0: 'country2', + admin1: 'state2', + admin2: 'city2', + text: 'test name2, city2, state2' + } + }]; + + test('functional success', function(t) { + var i = 0; + var backend = mockBackend( 'client/suggest/ok/1', function( cmd ){ + // the backend executes 2 commands, so we check them both + if( ++i === 1 ){ + t.deepEqual(cmd, { body: { a: 'b' }, index: 'pelias' }, 'correct suggest command'); + } else { + t.deepEqual(cmd, { body: { docs: [ { _id: 'mockid1', _index: 'pelias', _type: 'mocktype' }, { _id: 'mockid2', _index: 'pelias', _type: 'mocktype' } ] } }, 'correct mget 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/nearby ' + 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 cbb5ab5e..597b139c 100644 --- a/test/unit/mock/backend.js +++ b/test/unit/mock/backend.js @@ -1,8 +1,7 @@ - var responses = {}; responses['client/suggest/ok/1'] = function( cmd, cb ){ - return cb( undefined, suggestEnvelope([ { score: 1, text: 'mocktype:mockid' }, { score: 2, text: 'mocktype:mockid' } ]) ); + return cb( undefined, suggestEnvelope([ { score: 1, text: 'mocktype:mockid1' }, { score: 2, text: 'mocktype:mockid2' } ]) ); }; responses['client/suggest/fail/1'] = function( cmd, cb ){ return cb( 'a backend error occurred' ); @@ -28,6 +27,10 @@ responses['client/search/ok/1'] = function( cmd, cb ){ } }])); }; +responses['client/search/fail/1'] = function( cmd, cb ){ + return cb( 'a backend error occurred' ); +}; + responses['client/mget/ok/1'] = function( cmd, cb ){ return cb( undefined, mgetEnvelope([{ _id: 'myid1', @@ -51,28 +54,7 @@ responses['client/mget/ok/1'] = function( cmd, cb ){ } }])); }; -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']; +responses['client/mget/fail/1'] = responses['client/search/fail/1']; function setup( key, cmdCb ){ function backend( a, b ){ @@ -80,7 +62,7 @@ function setup( key, cmdCb ){ client: { mget: function( cmd, cb ){ if( 'function' === typeof cmdCb ){ cmdCb( cmd ); } - return responses[key].apply( this, arguments ); + return responses[key.indexOf('mget') === -1 ? 'client/mget/ok/1' : key].apply( this, arguments ); }, suggest: function( cmd, cb ){ if( 'function' === typeof cmdCb ){ cmdCb( cmd ); } @@ -89,10 +71,6 @@ function setup( key, cmdCb ){ search: function( cmd, cb ){ if( 'function' === typeof cmdCb ){ cmdCb( cmd ); } return responses[key].apply( this, arguments ); - }, - mget: function( cmd, cb ){ - if( 'function' === typeof cmdCb ){ cmdCb( cmd ); } - return responses['client/mget/ok/1'].apply( this, arguments ); } } }; @@ -100,7 +78,7 @@ function setup( key, cmdCb ){ return backend; } -function docEnvelope( options ){ +function mgetEnvelope( options ){ return { docs: options }; } @@ -112,8 +90,4 @@ function searchEnvelope( options ){ return { hits: { total: options.length, hits: options } }; } -function mgetEnvelope( options ){ - return { docs: options }; -} - module.exports = setup; \ No newline at end of file diff --git a/test/unit/run.js b/test/unit/run.js index 1c0d7d4b..0bc64a72 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_nearby'), require('./controller/search'), require('./service/mget'), require('./service/search'), diff --git a/test/unit/service/mget.js b/test/unit/service/mget.js index 0b43f717..f62eeae0 100644 --- a/test/unit/service/mget.js +++ b/test/unit/service/mget.js @@ -16,12 +16,14 @@ module.exports.tests.functional_success = function(test, common) { var expected = [ { + _id: 'myid1', _type: 'mytype1', value: 1, center_point: { lat: 100.1, lon: -50.5 }, name: { default: 'test name1' }, admin0: 'country1', admin1: 'state1', admin2: 'city1' }, { + _id: 'myid2', _type: 'mytype2', value: 2, center_point: { lat: 100.2, lon: -51.5 }, name: { default: 'test name2' }, @@ -30,7 +32,7 @@ module.exports.tests.functional_success = function(test, common) { ]; test('valid query', function(t) { - var backend = mockBackend( 'client/doc/ok/1', function( cmd ){ + var backend = mockBackend( 'client/mget/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) { @@ -56,7 +58,7 @@ module.exports.tests.functional_failure = function(test, common) { { } ]; - var backend = mockBackend( 'client/doc/fail/1', function( cmd ){ + var backend = mockBackend( 'client/mget/fail/1', function( cmd ){ t.notDeepEqual(cmd, { body: { docs: [ { _id: 123, _index: 'pelias', _type: 'a' } ] } }, 'incorrect backend command'); }); invalid_queries.forEach(function(query) { diff --git a/test/unit/service/search.js b/test/unit/service/search.js index 2e1b3393..0768dff1 100644 --- a/test/unit/service/search.js +++ b/test/unit/service/search.js @@ -18,12 +18,14 @@ module.exports.tests.functional_success = function(test, common) { var expected = [ { + _id: 'myid1', _type: 'mytype1', value: 1, center_point: { lat: 100.1, lon: -50.5 }, name: { default: 'test name1' }, admin0: 'country1', admin1: 'state1', admin2: 'city1' }, { + _id: 'myid2', _type: 'mytype2', value: 2, center_point: { lat: 100.2, lon: -51.5 }, name: { default: 'test name2' }, diff --git a/test/unit/service/suggest.js b/test/unit/service/suggest.js index 0f57e02c..5ae120a0 100644 --- a/test/unit/service/suggest.js +++ b/test/unit/service/suggest.js @@ -16,14 +16,9 @@ module.exports.tests.interface = function(test, common) { // 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 } + { score: 1, text: 'mocktype:mockid1' }, + { score: 2, text: 'mocktype:mockid2' } ]; test('valid ES query', function(t) {