Browse Source

Merge branch 'search' into reverse

Conflicts:
	controller/search.js
	index.js
	query/search.js
pull/5/head
Harish Krishna 10 years ago
parent
commit
e387eac699
  1. 1
      .travis.yml
  2. 46
      app.js
  3. 24
      controller/index.js
  4. 57
      controller/search.js
  5. 52
      controller/suggest.js
  6. 26
      docs/404.md
  7. 8
      docs/cors.md
  8. 28
      docs/index.md
  9. 8
      docs/jsonp.md
  10. 22
      docs/suggest/success.md
  11. 44
      index.js
  12. 7
      middleware/reverse.js
  13. 19
      middleware/toobusy.js
  14. 7
      package.json
  15. 2
      query/reverse.js
  16. 4
      query/search.js
  17. 4
      query/suggest.js
  18. 11
      sanitiser/sanitise.js
  19. 11
      src/backend.js
  20. 14
      test/ciao/reverse/success.coffee
  21. 6
      test/unit/controller/index.js
  22. 64
      test/unit/controller/search.js
  23. 64
      test/unit/controller/suggest.js
  24. 42
      test/unit/mock/backend.js
  25. 10
      test/unit/mock/query.js
  26. 23
      test/unit/query/indeces.js
  27. 52
      test/unit/query/reverse.js
  28. 79
      test/unit/query/search.js
  29. 50
      test/unit/query/suggest.js
  30. 9
      test/unit/run.js
  31. 203
      test/unit/sanitiser/sanitise.js

1
.travis.yml

@ -1,5 +1,4 @@
language: node_js language: node_js
script: "npm run unit" script: "npm run unit"
node_js: node_js:
- "0.11"
- "0.10" - "0.10"

46
app.js

@ -0,0 +1,46 @@
var app = require('express')();
/** ----------------------- middleware ----------------------- **/
app.use( require('./middleware/toobusy') ); // should be first
app.use( require('./middleware/headers') );
app.use( require('./middleware/cors') );
app.use( require('./middleware/jsonp') );
var middlewares = {};
middlewares.search = require('./middleware/search');
/** ----------------------- sanitisers ----------------------- **/
var sanitisers = {};
sanitisers.sanitiser= require('./sanitiser/sanitise');
/** ----------------------- controllers ----------------------- **/
var controllers = {};
controllers.index = require('./controller/index');
controllers.suggest = require('./controller/suggest');
controllers.search = require('./controller/search');
/** ----------------------- routes ----------------------- **/
// api root
app.get( '/', controllers.index() );
// suggest API
app.get( '/suggest', sanitisers.sanitiser.middleware, controllers.suggest() );
// search API
app.get( '/search', sanitisers.sanitiser.middleware, middlewares.search.middleware, controllers.search() );
// reverse API
app.get( '/reverse', sanitisers.sanitiser.middleware, controllers.search(undefined, require('../query/reverse')) );
/** ----------------------- error middleware ----------------------- **/
app.use( require('./middleware/404') );
app.use( require('./middleware/500') );
module.exports = app;

24
controller/index.js

@ -1,16 +1,22 @@
var pkg = require('../package'); var pkg = require('../package');
function controller( req, res, next ){ function setup(){
// stats function controller( req, res, next ){
res.json({
name: pkg.name, // stats
version: { res.json({
number: pkg.version name: pkg.name,
} version: {
}); number: pkg.version
}
});
}
return controller;
} }
module.exports = controller; module.exports = setup;

57
controller/search.js

@ -1,37 +1,42 @@
var backend = require('../src/backend'); function setup( backend, query ){
function controller( req, res, next ){ // allow overriding of dependencies
var required_query = req.required_query ? req.required_query : 'search'; backend = backend || require('../src/backend');
var query = require('../query/' + required_query); query = query || require('../query/search');
// backend command
var cmd = {
index: 'pelias',
body: query( req.clean )
};
// query backend function controller( req, res, next ){
backend().client.search( cmd, function( err, data ){
var docs = []; // backend command
var cmd = {
index: 'pelias',
body: query( req.clean )
};
// handle backend errors // query backend
if( err ){ return next( err ); } backend().client.search( cmd, function( err, data ){
if( data && data.hits && data.hits.total){ var docs = [];
docs = data.hits.hits.map( function( hit ){
return hit._source; // handle backend errors
}); if( err ){ return next( err ); }
}
if( data && data.hits && data.hits.total){
docs = data.hits.hits.map( function( hit ){
return hit._source;
});
}
// respond // respond
return res.status(200).json({ return res.status(200).json({
date: new Date().getTime(), date: new Date().getTime(),
body: docs body: docs
});
}); });
});
}
return controller;
} }
module.exports = controller; module.exports = setup;

52
controller/suggest.js

@ -1,35 +1,41 @@
var query = require('../query/suggest'), function setup( backend, query ){
backend = require('../src/backend');
function controller( req, res, next ){ // allow overriding of dependencies
backend = backend || require('../src/backend');
query = query || require('../query/suggest');
// backend command function controller( req, res, next ){
var cmd = {
index: 'pelias',
body: query( req.clean )
};
// query backend // backend command
backend().client.suggest( cmd, function( err, data ){ var cmd = {
index: 'pelias',
body: query( req.clean )
};
var docs = []; // query backend
backend().client.suggest( cmd, function( err, data ){
// handle backend errors var docs = [];
if( err ){ return next( err ); }
// map response to a valid FeatureCollection // handle backend errors
if( data && Array.isArray( data.pelias ) && data.pelias.length ){ if( err ){ return next( err ); }
docs = data['pelias'][0].options || [];
}
// respond // map response to a valid FeatureCollection
return res.status(200).json({ if( data && Array.isArray( data.pelias ) && data.pelias.length ){
date: new Date().getTime(), docs = data['pelias'][0].options || [];
body: docs }
// respond
return res.status(200).json({
date: new Date().getTime(),
body: docs
});
}); });
});
}
return controller;
} }
module.exports = controller; module.exports = setup;

26
docs/404.md

@ -1,6 +1,6 @@
# invalid path # invalid path
*Generated: Fri Sep 12 2014 20:51:44 GMT+0100 (BST)* *Generated: Thu Sep 18 2014 14:53:39 GMT+0100 (BST)*
## Request ## Request
```javascript ```javascript
{ {
@ -18,16 +18,16 @@ Status: 404
{ {
"x-powered-by": "mapzen", "x-powered-by": "mapzen",
"charset": "utf8", "charset": "utf8",
"cache-control": "public,max-age=300",
"server": "Pelias/0.0.0",
"access-control-allow-origin": "*", "access-control-allow-origin": "*",
"access-control-allow-methods": "GET", "access-control-allow-methods": "GET",
"access-control-allow-headers": "X-Requested-With,content-type", "access-control-allow-headers": "X-Requested-With,content-type",
"access-control-allow-credentials": "true", "access-control-allow-credentials": "true",
"server": "Pelias/0.0.0",
"cache-control": "public,max-age=300",
"content-type": "application/json; charset=utf-8", "content-type": "application/json; charset=utf-8",
"content-length": "35", "content-length": "35",
"etag": "W/\"23-dfdfa185\"", "etag": "W/\"23-dfdfa185\"",
"date": "Fri, 12 Sep 2014 19:51:44 GMT", "date": "Thu, 18 Sep 2014 13:53:39 GMT",
"connection": "close" "connection": "close"
} }
``` ```
@ -39,19 +39,14 @@ Status: 404
## Tests ## Tests
### ✓ cache-control header correctly set ### ✓ not found
```javascript
response.should.have.header 'Cache-Control','public,max-age=300'
```
### ✓ content-type header correctly set
```javascript ```javascript
response.should.have.header 'Content-Type','application/json; charset=utf-8' response.statusCode.should.equal 404
``` ```
### ✓ not found ### ✓ cache-control header correctly set
```javascript ```javascript
response.statusCode.should.equal 404 response.should.have.header 'Cache-Control','public,max-age=300'
``` ```
### ✓ should respond in json with server info ### ✓ should respond in json with server info
@ -61,3 +56,8 @@ should.exist json.error
json.error.should.equal 'not found: invalid path' 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'
```

8
docs/cors.md

@ -1,6 +1,6 @@
# cross-origin resource sharing # cross-origin resource sharing
*Generated: Fri Sep 12 2014 20:51:44 GMT+0100 (BST)* *Generated: Thu Sep 18 2014 14:53:39 GMT+0100 (BST)*
## Request ## Request
```javascript ```javascript
{ {
@ -18,16 +18,16 @@ Status: 200
{ {
"x-powered-by": "mapzen", "x-powered-by": "mapzen",
"charset": "utf8", "charset": "utf8",
"cache-control": "public,max-age=60",
"server": "Pelias/0.0.0",
"access-control-allow-origin": "*", "access-control-allow-origin": "*",
"access-control-allow-methods": "GET", "access-control-allow-methods": "GET",
"access-control-allow-headers": "X-Requested-With,content-type", "access-control-allow-headers": "X-Requested-With,content-type",
"access-control-allow-credentials": "true", "access-control-allow-credentials": "true",
"server": "Pelias/0.0.0",
"cache-control": "public,max-age=60",
"content-type": "application/json; charset=utf-8", "content-type": "application/json; charset=utf-8",
"content-length": "50", "content-length": "50",
"etag": "W/\"32-85536434\"", "etag": "W/\"32-85536434\"",
"date": "Fri, 12 Sep 2014 19:51:44 GMT", "date": "Thu, 18 Sep 2014 13:53:39 GMT",
"connection": "close" "connection": "close"
} }
``` ```

28
docs/index.md

@ -1,6 +1,6 @@
# api root # api root
*Generated: Fri Sep 12 2014 20:51:45 GMT+0100 (BST)* *Generated: Thu Sep 18 2014 14:53:39 GMT+0100 (BST)*
## Request ## Request
```javascript ```javascript
{ {
@ -18,16 +18,16 @@ Status: 200
{ {
"x-powered-by": "mapzen", "x-powered-by": "mapzen",
"charset": "utf8", "charset": "utf8",
"cache-control": "public,max-age=60",
"server": "Pelias/0.0.0",
"access-control-allow-origin": "*", "access-control-allow-origin": "*",
"access-control-allow-methods": "GET", "access-control-allow-methods": "GET",
"access-control-allow-headers": "X-Requested-With,content-type", "access-control-allow-headers": "X-Requested-With,content-type",
"access-control-allow-credentials": "true", "access-control-allow-credentials": "true",
"server": "Pelias/0.0.0",
"cache-control": "public,max-age=60",
"content-type": "application/json; charset=utf-8", "content-type": "application/json; charset=utf-8",
"content-length": "50", "content-length": "50",
"etag": "W/\"32-85536434\"", "etag": "W/\"32-85536434\"",
"date": "Fri, 12 Sep 2014 19:51:44 GMT", "date": "Thu, 18 Sep 2014 13:53:39 GMT",
"connection": "close" "connection": "close"
} }
``` ```
@ -42,6 +42,12 @@ Status: 200
## Tests ## Tests
### ✓ server header correctly set
```javascript
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 ### ✓ content-type header correctly set
```javascript ```javascript
response.should.have.header 'Content-Type','application/json; charset=utf-8' response.should.have.header 'Content-Type','application/json; charset=utf-8'
@ -57,20 +63,14 @@ response.statusCode.should.equal 200
response.should.have.header 'Cache-Control','public,max-age=60' response.should.have.header 'Cache-Control','public,max-age=60'
``` ```
### ✓ charset header correctly set ### ✓ vanity header correctly set
```javascript
response.should.have.header 'Charset','utf8'
```
### ✓ server header correctly set
```javascript ```javascript
response.should.have.header 'Server' response.should.have.header 'X-Powered-By','mapzen'
response.headers.server.should.match /Pelias\/\d{1,2}\.\d{1,2}\.\d{1,2}/
``` ```
### ✓ vanity header correctly set ### ✓ charset header correctly set
```javascript ```javascript
response.should.have.header 'X-Powered-By','mapzen' response.should.have.header 'Charset','utf8'
``` ```
### ✓ should respond in json with server info ### ✓ should respond in json with server info

8
docs/jsonp.md

@ -1,6 +1,6 @@
# jsonp # jsonp
*Generated: Fri Sep 12 2014 20:51:45 GMT+0100 (BST)* *Generated: Thu Sep 18 2014 14:53:39 GMT+0100 (BST)*
## Request ## Request
```javascript ```javascript
{ {
@ -18,16 +18,16 @@ Status: 200
{ {
"x-powered-by": "mapzen", "x-powered-by": "mapzen",
"charset": "utf8", "charset": "utf8",
"cache-control": "public,max-age=60",
"server": "Pelias/0.0.0",
"access-control-allow-origin": "*", "access-control-allow-origin": "*",
"access-control-allow-methods": "GET", "access-control-allow-methods": "GET",
"access-control-allow-headers": "X-Requested-With,content-type", "access-control-allow-headers": "X-Requested-With,content-type",
"access-control-allow-credentials": "true", "access-control-allow-credentials": "true",
"server": "Pelias/0.0.0",
"cache-control": "public,max-age=60",
"content-type": "application/javascript; charset=utf-8", "content-type": "application/javascript; charset=utf-8",
"content-length": "57", "content-length": "57",
"etag": "W/\"39-b8a2aba1\"", "etag": "W/\"39-b8a2aba1\"",
"date": "Fri, 12 Sep 2014 19:51:44 GMT", "date": "Thu, 18 Sep 2014 13:53:39 GMT",
"connection": "close" "connection": "close"
} }
``` ```

22
docs/suggest/success.md

@ -1,6 +1,6 @@
# valid suggest query # valid suggest query
*Generated: Fri Sep 12 2014 20:51:45 GMT+0100 (BST)* *Generated: Thu Sep 18 2014 14:53:39 GMT+0100 (BST)*
## Request ## Request
```javascript ```javascript
{ {
@ -18,22 +18,22 @@ Status: 200
{ {
"x-powered-by": "mapzen", "x-powered-by": "mapzen",
"charset": "utf8", "charset": "utf8",
"cache-control": "public,max-age=60",
"server": "Pelias/0.0.0",
"access-control-allow-origin": "*", "access-control-allow-origin": "*",
"access-control-allow-methods": "GET", "access-control-allow-methods": "GET",
"access-control-allow-headers": "X-Requested-With,content-type", "access-control-allow-headers": "X-Requested-With,content-type",
"access-control-allow-credentials": "true", "access-control-allow-credentials": "true",
"server": "Pelias/0.0.0",
"cache-control": "public,max-age=60",
"content-type": "application/json; charset=utf-8", "content-type": "application/json; charset=utf-8",
"content-length": "1248", "content-length": "1248",
"etag": "W/\"o9NALcf9i0O3JoLO7pfqog==\"", "etag": "W/\"H7TGcVqWuu8sAb6aBhpd6A==\"",
"date": "Fri, 12 Sep 2014 19:51:44 GMT", "date": "Thu, 18 Sep 2014 13:53:39 GMT",
"connection": "close" "connection": "close"
} }
``` ```
```javascript ```javascript
{ {
"date": 1410551504928, "date": 1411048419796,
"body": [ "body": [
{ {
"text": "ACRELÂNDIA, Brazil", "text": "ACRELÂNDIA, Brazil",
@ -121,6 +121,11 @@ Status: 200
## Tests ## Tests
### ✓ 200 ok
```javascript
response.statusCode.should.equal 200
```
### ✓ valid response ### ✓ valid response
```javascript ```javascript
now = new Date().getTime() now = new Date().getTime()
@ -132,8 +137,3 @@ should.exist json.body
json.body.should.be.instanceof Array json.body.should.be.instanceof Array
``` ```
### ✓ 200 ok
```javascript
response.statusCode.should.equal 200
```

44
index.js

@ -1,29 +1,17 @@
var app = require('express')(); var cluster = require('cluster'),
app = require('./app'),
/** ----------------------- middleware ----------------------- **/ multicore = false,
port = ( process.env.PORT || 3100 );
app.use( require('./middleware/headers') );
app.use( require('./middleware/cors') ); /** cluster webserver across all cores **/
app.use( require('./middleware/jsonp') ); if( multicore ){
// @todo: not finished yet
/** ----------------------- routes ----------------------- **/ // cluster(app)
// .use(cluster.stats())
// api root // .listen( process.env.PORT || 3100 );
app.get( '/', require('./controller/index') ); }
else {
// suggest API console.log( 'listening on ' + port );
app.get( '/suggest', require('./sanitiser/sanitise'), require('./controller/suggest') ); app.listen( process.env.PORT || 3100 );
}
// search API
app.get( '/search', require('./sanitiser/sanitise'), require('./middleware/search'), require('./controller/search') );
// reverse API
app.get( '/reverse', require('./sanitiser/sanitise'), require('./middleware/reverse'), require('./controller/search') );
/** ----------------------- error middleware ----------------------- **/
app.use( require('./middleware/404') );
app.use( require('./middleware/500') );
app.listen( process.env.PORT || 3100 );

7
middleware/reverse.js

@ -1,7 +0,0 @@
// middleware
function middleware(req, res, next){
req.required_query = "reverse";
next();
}
module.exports = middleware;

19
middleware/toobusy.js

@ -0,0 +1,19 @@
// middleware which blocks requests when the eventloop is too busy
var toobusy = require('toobusy');
function middleware(req, res, next){
if( toobusy() ){
res.status(503); // Service Unavailable
return next('Server Overwhelmed');
}
return next();
}
// calling .shutdown allows your process to exit normally
process.on('SIGINT', function() {
toobusy.shutdown();
process.exit();
});
module.exports = middleware;

7
package.json

@ -11,6 +11,7 @@
"test": "npm run unit && npm run ciao", "test": "npm run unit && npm run ciao",
"unit": "node test/unit/run.js | tap-spec", "unit": "node test/unit/run.js | tap-spec",
"ciao": "node node_modules/ciao/bin/ciao -c test/ciao.json test/ciao", "ciao": "node node_modules/ciao/bin/ciao -c test/ciao.json test/ciao",
"audit": "npm shrinkwrap; node node_modules/nsp/bin/nspCLI.js audit-shrinkwrap; rm npm-shrinkwrap.json;",
"docs": "rm -r docs; cd test/ciao; node ../../node_modules/ciao/bin/ciao -c ../ciao.json . -d ../../docs" "docs": "rm -r docs; cd test/ciao; node ../../node_modules/ciao/bin/ciao -c ../ciao.json . -d ../../docs"
}, },
"repository": { "repository": {
@ -33,11 +34,13 @@
"dependencies": { "dependencies": {
"express": "^4.8.8", "express": "^4.8.8",
"geopipes-elasticsearch-backend": "0.0.7", "geopipes-elasticsearch-backend": "0.0.7",
"pelias-esclient": "0.0.25" "pelias-esclient": "0.0.25",
"toobusy": "^0.2.4"
}, },
"devDependencies": { "devDependencies": {
"ciao": "^0.3.4", "ciao": "^0.3.4",
"tape": "^2.13.4", "tape": "^2.13.4",
"tap-spec": "^0.2.0" "tap-spec": "^0.2.0",
"nsp": "^0.3.0"
} }
} }

2
query/reverse.js

@ -23,7 +23,7 @@ function generate( params ){
"size": 1 "size": 1
}; };
logger.log( 'cmd', JSON.stringify( cmd, null, 2 ) ); // logger.log( 'cmd', JSON.stringify( cmd, null, 2 ) );
return cmd; return cmd;
} }

4
query/search.js

@ -35,10 +35,10 @@ function generate( params ){
"unit": 'km' "unit": 'km'
} }
}], }],
"size": 30 "size": params.size
}; };
logger.log( 'cmd', JSON.stringify( cmd, null, 2 ) ); // logger.log( 'cmd', JSON.stringify( cmd, null, 2 ) );
return cmd; return cmd;
} }

4
query/suggest.js

@ -3,7 +3,7 @@ var logger = require('../src/logger');
// Build pelias suggest query // Build pelias suggest query
function generate( params ){ function generate( params ){
var cmd = { var cmd = {
'pelias' : { 'pelias' : {
'text' : params.input, 'text' : params.input,
@ -23,7 +23,7 @@ function generate( params ){
} }
}; };
logger.log( 'cmd', JSON.stringify( cmd, null, 2 ) ); // logger.log( 'cmd', JSON.stringify( cmd, null, 2 ) );
return cmd; return cmd;
} }

11
sanitiser/sanitise.js

@ -21,7 +21,7 @@ function sanitize( params, cb ){
// total results // total results
var size = parseInt( params.size, 10 ); var size = parseInt( params.size, 10 );
if( !isNaN( size ) ){ if( !isNaN( size ) ){
clean.size = Math.min( size, 40 ); // max clean.size = Math.min( Math.max( size, 1 ), 40 ); // max
} else { } else {
clean.size = 10; // default clean.size = 10; // default
} }
@ -33,7 +33,7 @@ function sanitize( params, cb ){
}); });
for( var x=0; x<layers.length; x++ ){ for( var x=0; x<layers.length; x++ ){
if( -1 === indeces.indexOf( layers[x] ) ){ if( -1 === indeces.indexOf( layers[x] ) ){
return cb( 'invalid param \'layer\': must be one or more of ' + layers.join(',') ); return cb( 'invalid param \'layer\': must be one or more of ' + indeces.join(',') );
} }
} }
clean.layers = layers; clean.layers = layers;
@ -59,7 +59,7 @@ function sanitize( params, cb ){
// zoom level // zoom level
var zoom = parseInt( params.zoom, 10 ); var zoom = parseInt( params.zoom, 10 );
if( !isNaN( zoom ) ){ if( !isNaN( zoom ) ){
clean.zoom = Math.min( zoom, 18 ); // max clean.zoom = Math.min( Math.max( zoom, 1 ), 18 ); // max
} else { } else {
clean.zoom = 10; // default clean.zoom = 10; // default
} }
@ -68,8 +68,11 @@ function sanitize( params, cb ){
} }
// export function
module.exports = sanitize;
// middleware // middleware
module.exports = function( req, res, next ){ module.exports.middleware = function( req, res, next ){
sanitize( req.query, function( err, clean ){ sanitize( req.query, function( err, clean ){
if( err ){ if( err ){
res.status(400); // 400 Bad Request res.status(400); // 400 Bad Request

11
src/backend.js

@ -1,14 +1,7 @@
var Backend = require('geopipes-elasticsearch-backend'), var Backend = require('geopipes-elasticsearch-backend'),
backends = {}, client = require('pelias-esclient')(),
client; backends = {};
// set env specific client
if( process.env.NODE_ENV === 'test' ){
client = require('./pelias-mockclient');
} else {
client = require('pelias-esclient')();
}
function getBackend( index, type ){ function getBackend( index, type ){
var key = ( index + ':' + type ); var key = ( index + ':' + type );

14
test/ciao/reverse/success.coffee

@ -0,0 +1,14 @@
#> valid reverse query
path: '/reverse?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
should.exist json.date
json.date.should.be.within now-2000, now+2000
should.exist json.body
json.body.should.be.instanceof Array

6
test/unit/controller/index.js

@ -1,17 +1,19 @@
var controller = require('../../../controller/index'); var setup = require('../../../controller/index');
module.exports.tests = {}; module.exports.tests = {};
module.exports.tests.interface = function(test, common) { module.exports.tests.interface = function(test, common) {
test('valid interface', function(t) { test('valid interface', function(t) {
t.equal(typeof controller, 'function', 'controller is a function'); t.equal(typeof setup, 'function', 'setup is a function');
t.equal(typeof setup(), 'function', 'setup returns a controller');
t.end(); t.end();
}); });
}; };
module.exports.tests.info = function(test, common) { module.exports.tests.info = function(test, common) {
test('returns server info', function(t) { test('returns server info', function(t) {
var controller = setup();
var res = { json: function( json ){ var res = { json: function( json ){
t.equal(typeof json, 'object', 'returns json'); t.equal(typeof json, 'object', 'returns json');
t.equal(typeof json.name, 'string', 'name'); t.equal(typeof json.name, 'string', 'name');

64
test/unit/controller/search.js

@ -0,0 +1,64 @@
var setup = require('../../../controller/search'),
mockBackend = require('../mock/backend'),
mockQuery = require('../mock/query');
module.exports.tests = {};
module.exports.tests.interface = function(test, common) {
test('valid interface', function(t) {
t.equal(typeof setup, 'function', 'setup is a function');
t.equal(typeof setup(), 'function', 'setup returns a controller');
t.end();
});
};
// functionally test controller (backend success)
module.exports.tests.functional_success = function(test, common) {
test('functional test', function(t) {
var backend = mockBackend( 'client/search/ok/1', function( cmd ){
t.deepEqual(cmd, { body: { a: 'b' }, index: 'pelias' }, 'correct backend command');
});
var controller = setup( backend, mockQuery() );
var res = {
status: function( code ){
t.equal(code, 200, 'status set');
return res;
},
json: function( json ){
t.equal(typeof json, 'object', 'returns json');
t.equal(typeof json.date, 'number', 'date set');
t.true(Array.isArray(json.body), 'body is array');
// t.deepEqual(json.body, [ { value: 1 }, { value: 2 } ], 'values correctly mapped');
t.end();
}
};
controller( { clean: { a: 'b' } }, res );
});
};
// functionally test controller (backend failure)
module.exports.tests.functional_failure = function(test, common) {
test('functional test', function(t) {
var backend = mockBackend( 'client/search/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 /search ' + name, testFunction);
}
for( var testCase in module.exports.tests ){
module.exports.tests[testCase](test, common);
}
};

64
test/unit/controller/suggest.js

@ -0,0 +1,64 @@
var setup = require('../../../controller/suggest'),
mockBackend = require('../mock/backend'),
mockQuery = require('../mock/query');
module.exports.tests = {};
module.exports.tests.interface = function(test, common) {
test('valid interface', function(t) {
t.equal(typeof setup, 'function', 'setup is a function');
t.equal(typeof setup(), 'function', 'setup returns a controller');
t.end();
});
};
// functionally test controller (backend success)
module.exports.tests.functional_success = function(test, common) {
test('functional test', function(t) {
var backend = mockBackend( 'client/suggest/ok/1', function( cmd ){
t.deepEqual(cmd, { body: { a: 'b' }, index: 'pelias' }, 'correct backend command');
});
var controller = setup( backend, mockQuery() );
var res = {
status: function( code ){
t.equal(code, 200, 'status set');
return res;
},
json: function( json ){
t.equal(typeof json, 'object', 'returns json');
t.equal(typeof json.date, 'number', 'date set');
t.true(Array.isArray(json.body), 'body is array');
t.deepEqual(json.body, [ { value: 1 }, { value: 2 } ], 'values correctly mapped');
t.end();
}
};
controller( { clean: { a: 'b' } }, res );
});
};
// functionally test controller (backend failure)
module.exports.tests.functional_failure = function(test, common) {
test('functional test', function(t) {
var backend = mockBackend( 'client/suggest/fail/1', function( cmd ){
t.deepEqual(cmd, { body: { a: 'b' }, index: 'pelias' }, 'correct backend command');
});
var controller = setup( backend, mockQuery() );
var next = function( message ){
t.equal(message,'a backend error occurred','error passed to errorHandler');
t.end();
};
controller( { clean: { a: 'b' } }, undefined, next );
});
};
module.exports.all = function (tape, common) {
function test(name, testFunction) {
return tape('GET /suggest ' + name, testFunction);
}
for( var testCase in module.exports.tests ){
module.exports.tests[testCase](test, common);
}
};

42
test/unit/mock/backend.js

@ -0,0 +1,42 @@
var responses = {};
responses['client/suggest/ok/1'] = function( cmd, cb ){
return cb( undefined, suggestEnvelope([ { value: 1 }, { value: 2 } ]) );
};
responses['client/suggest/fail/1'] = function( cmd, cb ){
return cb( 'a backend error occurred' );
};
responses['client/search/ok/1'] = function( cmd, cb ){
return cb( undefined, searchEnvelope([ { value: 1 }, { value: 2 } ]) );
};
responses['client/search/fail/1'] = function( cmd, cb ){
return cb( 'a backend error occurred' );
};
function setup( key, cmdCb ){
function backend( a, b ){
return {
client: {
suggest: function( cmd, cb ){
if( 'function' === typeof cmdCb ){ cmdCb( cmd ); }
return responses[key].apply( this, arguments );
},
search: function( cmd, cb ){
if( 'function' === typeof cmdCb ){ cmdCb( cmd ); }
return responses[key].apply( this, arguments );
}
}
};
}
return backend;
}
function suggestEnvelope( options ){
return { pelias: [{ options: options }]};
}
function searchEnvelope( options ){
return { pelias: [{ options: options }]};
}
module.exports = setup;

10
test/unit/mock/query.js

@ -0,0 +1,10 @@
function setup(){
return query;
}
function query( clean ){
return clean;
}
module.exports = setup;

23
test/unit/query/indeces.js

@ -0,0 +1,23 @@
var indeces = require('../../../query/indeces');
module.exports.tests = {};
module.exports.tests.interface = function(test, common) {
test('valid interface', function(t) {
t.true(Array.isArray(indeces), 'valid array');
t.equal(indeces.length, 7, 'valid array');
t.end();
});
};
module.exports.all = function (tape, common) {
function test(name, testFunction) {
return tape('indeces ' + name, testFunction);
}
for( var testCase in module.exports.tests ){
module.exports.tests[testCase](test, common);
}
};

52
test/unit/query/reverse.js

@ -0,0 +1,52 @@
var generate = require('../../../query/reverse');
module.exports.tests = {};
module.exports.tests.interface = function(test, common) {
test('valid interface', function(t) {
t.equal(typeof generate, 'function', 'valid function');
t.end();
});
};
module.exports.tests.query = function(test, common) {
test('valid query', function(t) {
var query = generate({
lat: 29.49136, lon: -82.50622
});
var expected = {
query:{
filtered : {
query : {
match_all : {}
},
filter : {
geo_distance : {
distance : '1km',
center_point : {
lat: 29.49136,
lon: -82.50622
}
}
}
}
},
size: 1
};
t.deepEqual(query, expected, 'valid reverse query');
t.end();
});
};
module.exports.all = function (tape, common) {
function test(name, testFunction) {
return tape('reverse query ' + name, testFunction);
}
for( var testCase in module.exports.tests ){
module.exports.tests[testCase](test, common);
}
};

79
test/unit/query/search.js

@ -0,0 +1,79 @@
var generate = require('../../../query/search');
module.exports.tests = {};
module.exports.tests.interface = function(test, common) {
test('valid interface', function(t) {
t.equal(typeof generate, 'function', 'valid function');
t.end();
});
};
module.exports.tests.query = function(test, common) {
test('valid query', function(t) {
var query = generate({
input: 'test', size: 10,
lat: 29.49136, lon: -82.50622,
bbox: {
top_left: {
lat: 11.51053655297385,
lon: -103.16362455862279
},
bottom_right: {
lat: 47.472183447026154,
lon: -61.84881544137721
}
},
layers: ['test']
});
var expected = {
query:{
query_string : {
query: 'test',
fields: ['name.default'],
default_operator: 'OR'
}
},
filter: {
geo_bounding_box: {
center_point: {
top_left: {
lat: 11.51053655297385,
lon: -103.16362455862279
},
bottom_right: {
lat: 47.472183447026154,
lon: -61.84881544137721
}
}
}
},
sort : [{
_geo_distance : {
center_point : {
lat: 29.49136,
lon: -82.50622
},
order: 'asc',
unit: 'km'
}
}],
size: 10
};
t.deepEqual(query, expected, 'valid search query');
t.end();
});
};
module.exports.all = function (tape, common) {
function test(name, testFunction) {
return tape('search query ' + name, testFunction);
}
for( var testCase in module.exports.tests ){
module.exports.tests[testCase](test, common);
}
};

50
test/unit/query/suggest.js

@ -0,0 +1,50 @@
var generate = require('../../../query/suggest');
module.exports.tests = {};
module.exports.tests.interface = function(test, common) {
test('valid interface', function(t) {
t.equal(typeof generate, 'function', 'valid function');
t.end();
});
};
module.exports.tests.query = function(test, common) {
test('valid query', function(t) {
var query = generate({
input: 'test', size: 10,
lat: 0, lon: 0,
layers: ['test']
});
var expected = {
pelias: {
text: 'test',
completion: {
field: 'suggest',
size: 10,
context: {
dataset: [ 'test' ],
location: {
precision: 2,
value: [ 0, 0 ]
}
}
}
}
};
t.deepEqual(query, expected, 'valid suggest query');
t.end();
});
};
module.exports.all = function (tape, common) {
function test(name, testFunction) {
return tape('suggest query ' + name, testFunction);
}
for( var testCase in module.exports.tests ){
module.exports.tests[testCase](test, common);
}
};

9
test/unit/run.js

@ -3,7 +3,14 @@ var tape = require('tape');
var common = {}; var common = {};
var tests = [ var tests = [
require('./controller/index') require('./controller/index'),
require('./controller/suggest'),
require('./controller/search'),
require('./sanitiser/sanitise'),
require('./query/indeces'),
require('./query/suggest'),
require('./query/search'),
require('./query/reverse')
]; ];
tests.map(function(t) { tests.map(function(t) {

203
test/unit/sanitiser/sanitise.js

@ -0,0 +1,203 @@
var sanitize = require('../../../sanitiser/sanitise'),
defaultError = 'invalid param \'input\': text length, must be >0',
defaultClean = { input: 'test', lat: 0, layers: [ 'geoname', 'osmnode', 'osmway', 'admin0', 'admin1', 'admin2', 'neighborhood' ], lon: 0, size: 10, zoom: 10 };
module.exports.tests = {};
module.exports.tests.interface = function(test, common) {
test('sanitize interface', function(t) {
t.equal(typeof sanitize, 'function', 'sanitize is a function');
t.equal(sanitize.length, 2, 'sanitize interface');
t.end();
});
test('middleware interface', function(t) {
t.equal(typeof sanitize.middleware, 'function', 'middleware is a function');
t.equal(sanitize.middleware.length, 3, 'sanitize is valid middleware');
t.end();
});
};
module.exports.tests.sanitize_input = function(test, common) {
var inputs = {
invalid: [ '', 100, null, undefined, new Date() ],
valid: [ 'a', 'aa', 'aaaaaaaa' ]
};
inputs.invalid.forEach( function( input ){
test('invalid input', function(t) {
sanitize({ input: input, lat: 0, lon: 0 }, function( err, clean ){
t.equal(err, 'invalid param \'input\': text length, must be >0', 'invalid input');
t.equal(clean, undefined, 'clean not set');
t.end();
});
});
});
inputs.valid.forEach( function( input ){
test('valid input', function(t) {
sanitize({ input: input, lat: 0, lon: 0 }, function( err, clean ){
var expected = JSON.parse(JSON.stringify( defaultClean ));
expected.input = input;
t.equal(err, undefined, 'no error');
t.deepEqual(clean, expected, 'clean set correctly');
t.end();
});
});
});
};
module.exports.tests.sanitize_lat = function(test, common) {
var lats = {
invalid: [ -1, -45, -90, 91, 120, 181 ],
valid: [ 0, 45, 90, -0, '0', '45', '90' ]
};
lats.invalid.forEach( function( lat ){
test('invalid lat', function(t) {
sanitize({ input: 'test', lat: lat, lon: 0 }, function( err, clean ){
t.equal(err, 'invalid param \'lat\': must be >0 and <90', 'invalid latitude');
t.equal(clean, undefined, 'clean not set');
t.end();
});
});
});
lats.valid.forEach( function( lat ){
test('valid lat', function(t) {
sanitize({ input: 'test', lat: lat, lon: 0 }, function( err, clean ){
var expected = JSON.parse(JSON.stringify( defaultClean ));
expected.lat = parseFloat( lat );
t.equal(err, undefined, 'no error');
t.deepEqual(clean, expected, 'clean set correctly');
t.end();
});
});
});
};
module.exports.tests.sanitize_lon = function(test, common) {
var lons = {
invalid: [ -360, -181, 181, 360 ],
valid: [ -180, -1, -0, 0, 45, 90, '-180', '0', '180' ]
};
lons.invalid.forEach( function( lon ){
test('invalid lon', function(t) {
sanitize({ input: 'test', lat: 0, lon: lon }, function( err, clean ){
t.equal(err, 'invalid param \'lon\': must be >-180 and <180', 'invalid longitude');
t.equal(clean, undefined, 'clean not set');
t.end();
});
});
});
lons.valid.forEach( function( lon ){
test('valid lon', function(t) {
sanitize({ input: 'test', lat: 0, lon: lon }, function( err, clean ){
var expected = JSON.parse(JSON.stringify( defaultClean ));
expected.lon = parseFloat( lon );
t.equal(err, undefined, 'no error');
t.deepEqual(clean, expected, 'clean set correctly');
t.end();
});
});
});
};
module.exports.tests.sanitize_zoom = function(test, common) {
test('invalid zoom value', function(t) {
sanitize({ zoom: 'a', input: 'test', lat: 0, lon: 0 }, function( err, clean ){
t.equal(clean.zoom, 10, 'default zoom set');
t.end();
});
});
test('below min zoom value', function(t) {
sanitize({ zoom: -100, input: 'test', lat: 0, lon: 0 }, function( err, clean ){
t.equal(clean.zoom, 1, 'min zoom set');
t.end();
});
});
test('above max zoom value', function(t) {
sanitize({ zoom: 9999, input: 'test', lat: 0, lon: 0 }, function( err, clean ){
t.equal(clean.zoom, 18, 'max zoom set');
t.end();
});
});
};
module.exports.tests.sanitize_size = function(test, common) {
test('invalid size value', function(t) {
sanitize({ size: 'a', input: 'test', lat: 0, lon: 0 }, function( err, clean ){
t.equal(clean.size, 10, 'default size set');
t.end();
});
});
test('below min size value', function(t) {
sanitize({ size: -100, input: 'test', lat: 0, lon: 0 }, function( err, clean ){
t.equal(clean.size, 1, 'min size set');
t.end();
});
});
test('above max size value', function(t) {
sanitize({ size: 9999, input: 'test', lat: 0, lon: 0 }, function( err, clean ){
t.equal(clean.size, 40, 'max size set');
t.end();
});
});
};
module.exports.tests.sanitize_layers = function(test, common) {
test('unspecified', function(t) {
sanitize({ layers: undefined, input: 'test', lat: 0, lon: 0 }, function( err, clean ){
t.deepEqual(clean.layers, defaultClean.layers, 'default layers set');
t.end();
});
});
test('invalid layer', function(t) {
sanitize({ layers: 'test_layer', input: 'test', lat: 0, lon: 0 }, function( err, clean ){
var msg = 'invalid param \'layer\': must be one or more of geoname,osmnode,osmway,admin0,admin1,admin2,neighborhood';
t.equal(err, msg, 'invalid layer requested');
t.end();
});
});
};
module.exports.tests.invalid_params = function(test, common) {
test('invalid input params', function(t) {
sanitize( undefined, function( err, clean ){
t.equal(err, defaultError, 'handle invalid params gracefully');
t.end();
});
});
};
module.exports.tests.middleware_failure = function(test, common) {
test('middleware failure', function(t) {
var res = { status: function( code ){
t.equal(code, 400, 'status set');
}};
var next = function( message ){
t.equal(message, defaultError);
t.end();
};
sanitize.middleware( {}, res, next );
});
};
module.exports.tests.middleware_success = function(test, common) {
test('middleware success', function(t) {
var req = { query: { input: 'test', lat: 0, lon: 0 }};
var next = function( message ){
t.equal(message, undefined, 'no error message set');
t.deepEqual(req.clean, defaultClean);
t.end();
};
sanitize.middleware( req, undefined, next );
});
};
module.exports.all = function (tape, common) {
function test(name, testFunction) {
return tape('SANTIZE /sanitise ' + name, testFunction);
}
for( var testCase in module.exports.tests ){
module.exports.tests[testCase](test, common);
}
};
Loading…
Cancel
Save