Browse Source

Merge branch 'master' into suggest_plus_mget

Conflicts:
	controller/suggest.js
	test/unit/controller/suggest.js
	test/unit/mock/backend.js
pull/42/head
Harish Krishna 10 years ago
parent
commit
21042b8d5e
  1. 2
      app.js
  2. 107
      controller/suggest.js
  3. 72
      controller/suggest_nearby.js
  4. 12
      docs/404.md
  5. 4
      docs/cors.md
  6. 20
      docs/doc/msuccess.md
  7. 20
      docs/doc/success.md
  8. 28
      docs/index.md
  9. 14
      docs/jsonp.md
  10. 24
      docs/reverse/success.md
  11. 18
      docs/search/success.md
  12. 71
      docs/suggest/success.md
  13. 223
      docs/suggest/success_nearby.md
  14. 3
      package.json
  15. 4
      query/suggest.js
  16. 16
      test/ciao/suggest/success_nearby.coffee
  17. 100
      test/unit/controller/doc.js
  18. 26
      test/unit/controller/suggest.js
  19. 107
      test/unit/controller/suggest_nearby.js
  20. 27
      test/unit/mock/backend.js
  21. 5
      test/unit/run.js
  22. 84
      test/unit/service/mget.js
  23. 84
      test/unit/service/search.js
  24. 72
      test/unit/service/suggest.js

2
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() );

107
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;

72
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;

12
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

4
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"
}
```

20
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
```

20
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
```

28
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'
```

14
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(';
```

24
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
```

18
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
```

71
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
```

223
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
```

3
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",

4
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)
}
}
}

16
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

100
test/unit/controller/doc.js

@ -0,0 +1,100 @@
var setup = require('../../../controller/doc'),
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.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: {
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 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 );
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/mget/fail/1', function( cmd ){
t.deepEqual(cmd, { body: { docs: [ { _id: 123, _index: 'pelias', _type: 'b' } ] } }, 'correct backend command');
});
var controller = setup( backend );
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);
}
};

26
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 ){

107
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);
}
};

27
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,14 +54,16 @@ responses['client/mget/ok/1'] = function( cmd, cb ){
}
}]));
};
responses['client/search/fail/1'] = function( cmd, cb ){
return cb( 'a backend error occurred' );
};
responses['client/mget/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.indexOf('mget') === -1 ? 'client/mget/ok/1' : key].apply( this, arguments );
},
suggest: function( cmd, cb ){
if( 'function' === typeof cmdCb ){ cmdCb( cmd ); }
return responses[key].apply( this, arguments );
@ -66,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 );
}
}
};
@ -77,6 +78,10 @@ function setup( key, cmdCb ){
return backend;
}
function mgetEnvelope( options ){
return { docs: options };
}
function suggestEnvelope( options ){
return { pelias: [{ options: options }]};
}
@ -85,8 +90,4 @@ function searchEnvelope( options ){
return { hits: { total: options.length, hits: options } };
}
function mgetEnvelope( options ){
return { docs: options };
}
module.exports = setup;

5
test/unit/run.js

@ -4,8 +4,13 @@ var common = {};
var tests = [
require('./controller/index'),
require('./controller/doc'),
require('./controller/suggest'),
require('./controller/suggest_nearby'),
require('./controller/search'),
require('./service/mget'),
require('./service/search'),
require('./service/suggest'),
require('./sanitiser/suggest'),
require('./sanitiser/doc'),
require('./query/indeces'),

84
test/unit/service/mget.js

@ -0,0 +1,84 @@
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 = [
{
_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' },
admin0: 'country2', admin1: 'state2', admin2: 'city2'
}
];
test('valid query', function(t) {
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) {
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/mget/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);
}
};

84
test/unit/service/search.js

@ -0,0 +1,84 @@
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 = [
{
_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' },
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);
}
};

72
test/unit/service/suggest.js

@ -0,0 +1,72 @@
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 expected = [
{ score: 1, text: 'mocktype:mockid1' },
{ score: 2, text: 'mocktype:mockid2' }
];
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);
}
};
Loading…
Cancel
Save