mirror of https://github.com/pelias/api.git
Peter Johnson
10 years ago
36 changed files with 1074 additions and 115 deletions
@ -0,0 +1,55 @@ |
|||||||
|
# API |
||||||
|
|
||||||
|
Pelias RESTful API |
||||||
|
|
||||||
|
## Documentation |
||||||
|
|
||||||
|
[API Documentation](https://github.com/pelias/api/tree/master/docs) |
||||||
|
|
||||||
|
## Install Dependencies |
||||||
|
|
||||||
|
```bash |
||||||
|
$ npm install |
||||||
|
``` |
||||||
|
|
||||||
|
## Contributing |
||||||
|
|
||||||
|
Please fork and pull request against upstream master on a feature branch. |
||||||
|
|
||||||
|
Pretty please; provide unit tests and script fixtures in the `test` directory. |
||||||
|
|
||||||
|
### Start Server |
||||||
|
|
||||||
|
```bash |
||||||
|
$ npm start |
||||||
|
``` |
||||||
|
|
||||||
|
### Running Unit Tests |
||||||
|
|
||||||
|
```bash |
||||||
|
$ npm run unit |
||||||
|
``` |
||||||
|
|
||||||
|
### Running Functional Tests |
||||||
|
|
||||||
|
```bash |
||||||
|
$ npm run ciao |
||||||
|
``` |
||||||
|
|
||||||
|
### Running All Tests |
||||||
|
|
||||||
|
```bash |
||||||
|
$ npm test |
||||||
|
``` |
||||||
|
|
||||||
|
### Generate API Documentation |
||||||
|
|
||||||
|
```bash |
||||||
|
$ npm run docs |
||||||
|
``` |
||||||
|
|
||||||
|
### Continuous Integration |
||||||
|
|
||||||
|
Travis tests every release against node version `0.10` |
||||||
|
|
||||||
|
[![Build Status](https://travis-ci.org/pelias/api.png?branch=master)](https://travis-ci.org/pelias/api) |
@ -0,0 +1,35 @@ |
|||||||
|
|
||||||
|
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') ); |
||||||
|
|
||||||
|
/** ----------------------- sanitisers ----------------------- **/ |
||||||
|
|
||||||
|
var sanitisers = {}; |
||||||
|
sanitisers.suggest = require('./sanitiser/suggest'); |
||||||
|
|
||||||
|
/** ----------------------- controllers ----------------------- **/ |
||||||
|
|
||||||
|
var controllers = {}; |
||||||
|
controllers.index = require('./controller/index'); |
||||||
|
controllers.suggest = require('./controller/suggest'); |
||||||
|
|
||||||
|
/** ----------------------- routes ----------------------- **/ |
||||||
|
|
||||||
|
// api root
|
||||||
|
app.get( '/', controllers.index() ); |
||||||
|
|
||||||
|
// suggest API
|
||||||
|
app.get( '/suggest', sanitisers.suggest.middleware, controllers.suggest() ); |
||||||
|
|
||||||
|
/** ----------------------- error middleware ----------------------- **/ |
||||||
|
|
||||||
|
app.use( require('./middleware/404') ); |
||||||
|
app.use( require('./middleware/500') ); |
||||||
|
|
||||||
|
module.exports = app; |
@ -1,13 +1,22 @@ |
|||||||
|
|
||||||
var pkg = require('../package'); |
var pkg = require('../package'); |
||||||
|
|
||||||
function controller( req, res ){ |
function setup(){ |
||||||
|
|
||||||
|
function controller( req, res, next ){ |
||||||
|
|
||||||
|
// stats
|
||||||
res.json({ |
res.json({ |
||||||
name: pkg.name, |
name: pkg.name, |
||||||
version: { |
version: { |
||||||
number: pkg.version |
number: pkg.version |
||||||
} |
} |
||||||
}); |
}); |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
return controller; |
||||||
|
|
||||||
} |
} |
||||||
|
|
||||||
module.exports = controller; |
module.exports = setup; |
@ -1,32 +1,41 @@ |
|||||||
|
|
||||||
var logger = require('../src/logger'), |
function setup( backend, query ){ |
||||||
responder = require('../src/responder'), |
|
||||||
query = require('../query/suggest'), |
|
||||||
backend = require('../src/backend'); |
|
||||||
|
|
||||||
module.exports = function( req, res, next ){ |
// allow overriding of dependencies
|
||||||
|
backend = backend || require('../src/backend'); |
||||||
|
query = query || require('../query/suggest'); |
||||||
|
|
||||||
var reply = { |
function controller( req, res, next ){ |
||||||
date: new Date().getTime(), |
|
||||||
body: [] |
|
||||||
}; |
|
||||||
|
|
||||||
|
// backend command
|
||||||
var cmd = { |
var cmd = { |
||||||
index: 'pelias', |
index: 'pelias', |
||||||
body: query( req.clean ) // generate query from clean params
|
body: query( req.clean ) |
||||||
}; |
}; |
||||||
|
|
||||||
// Proxy request to ES backend & map response to a valid FeatureCollection
|
// query backend
|
||||||
backend().client.suggest( cmd, function( err, data ){ |
backend().client.suggest( cmd, function( err, data ){ |
||||||
|
|
||||||
if( err ){ return responder.error( req, res, next, err ); } |
var docs = []; |
||||||
if( data && data.pelias && data.pelias.length ){ |
|
||||||
|
|
||||||
// map options to reply body
|
// handle backend errors
|
||||||
reply.body = data['pelias'][0].options; |
if( err ){ return next( err ); } |
||||||
|
|
||||||
|
// map response to a valid FeatureCollection
|
||||||
|
if( data && Array.isArray( data.pelias ) && data.pelias.length ){ |
||||||
|
docs = data['pelias'][0].options || []; |
||||||
} |
} |
||||||
|
|
||||||
return responder.cors( req, res, reply ); |
// respond
|
||||||
|
return res.status(200).json({ |
||||||
|
date: new Date().getTime(), |
||||||
|
body: docs |
||||||
}); |
}); |
||||||
|
}); |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
return controller; |
||||||
|
} |
||||||
|
|
||||||
}; |
module.exports = setup; |
@ -0,0 +1,63 @@ |
|||||||
|
# invalid path |
||||||
|
|
||||||
|
*Generated: Fri Sep 12 2014 20:51:44 GMT+0100 (BST)* |
||||||
|
## Request |
||||||
|
```javascript |
||||||
|
{ |
||||||
|
"protocol": "http:", |
||||||
|
"host": "localhost", |
||||||
|
"method": "GET", |
||||||
|
"port": 3100, |
||||||
|
"path": "/notexist" |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
## Response |
||||||
|
```javascript |
||||||
|
Status: 404 |
||||||
|
{ |
||||||
|
"x-powered-by": "mapzen", |
||||||
|
"charset": "utf8", |
||||||
|
"access-control-allow-origin": "*", |
||||||
|
"access-control-allow-methods": "GET", |
||||||
|
"access-control-allow-headers": "X-Requested-With,content-type", |
||||||
|
"access-control-allow-credentials": "true", |
||||||
|
"server": "Pelias/0.0.0", |
||||||
|
"cache-control": "public,max-age=300", |
||||||
|
"content-type": "application/json; charset=utf-8", |
||||||
|
"content-length": "35", |
||||||
|
"etag": "W/\"23-dfdfa185\"", |
||||||
|
"date": "Fri, 12 Sep 2014 19:51:44 GMT", |
||||||
|
"connection": "close" |
||||||
|
} |
||||||
|
``` |
||||||
|
```javascript |
||||||
|
{ |
||||||
|
"error": "not found: invalid path" |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
## Tests |
||||||
|
|
||||||
|
### ✓ cache-control header correctly set |
||||||
|
```javascript |
||||||
|
response.should.have.header 'Cache-Control','public,max-age=300' |
||||||
|
``` |
||||||
|
|
||||||
|
### ✓ content-type header correctly set |
||||||
|
```javascript |
||||||
|
response.should.have.header 'Content-Type','application/json; charset=utf-8' |
||||||
|
``` |
||||||
|
|
||||||
|
### ✓ not found |
||||||
|
```javascript |
||||||
|
response.statusCode.should.equal 404 |
||||||
|
``` |
||||||
|
|
||||||
|
### ✓ should respond in json with server info |
||||||
|
```javascript |
||||||
|
should.exist json |
||||||
|
should.exist json.error |
||||||
|
json.error.should.equal 'not found: invalid path' |
||||||
|
``` |
||||||
|
|
@ -0,0 +1,52 @@ |
|||||||
|
# cross-origin resource sharing |
||||||
|
|
||||||
|
*Generated: Fri Sep 12 2014 20:51:44 GMT+0100 (BST)* |
||||||
|
## Request |
||||||
|
```javascript |
||||||
|
{ |
||||||
|
"protocol": "http:", |
||||||
|
"host": "localhost", |
||||||
|
"method": "GET", |
||||||
|
"port": 3100, |
||||||
|
"path": "/" |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
## Response |
||||||
|
```javascript |
||||||
|
Status: 200 |
||||||
|
{ |
||||||
|
"x-powered-by": "mapzen", |
||||||
|
"charset": "utf8", |
||||||
|
"access-control-allow-origin": "*", |
||||||
|
"access-control-allow-methods": "GET", |
||||||
|
"access-control-allow-headers": "X-Requested-With,content-type", |
||||||
|
"access-control-allow-credentials": "true", |
||||||
|
"server": "Pelias/0.0.0", |
||||||
|
"cache-control": "public,max-age=60", |
||||||
|
"content-type": "application/json; charset=utf-8", |
||||||
|
"content-length": "50", |
||||||
|
"etag": "W/\"32-85536434\"", |
||||||
|
"date": "Fri, 12 Sep 2014 19:51:44 GMT", |
||||||
|
"connection": "close" |
||||||
|
} |
||||||
|
``` |
||||||
|
```javascript |
||||||
|
{ |
||||||
|
"name": "pelias-api", |
||||||
|
"version": { |
||||||
|
"number": "0.0.0" |
||||||
|
} |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
## Tests |
||||||
|
|
||||||
|
### ✓ access control headers correctly set |
||||||
|
```javascript |
||||||
|
response.should.have.header 'Access-Control-Allow-Origin','*' |
||||||
|
response.should.have.header 'Access-Control-Allow-Methods','GET' |
||||||
|
response.should.have.header 'Access-Control-Allow-Headers','X-Requested-With,content-type' |
||||||
|
response.should.have.header 'Access-Control-Allow-Credentials','true' |
||||||
|
``` |
||||||
|
|
@ -0,0 +1,82 @@ |
|||||||
|
# api root |
||||||
|
|
||||||
|
*Generated: Fri Sep 12 2014 20:51:45 GMT+0100 (BST)* |
||||||
|
## Request |
||||||
|
```javascript |
||||||
|
{ |
||||||
|
"protocol": "http:", |
||||||
|
"host": "localhost", |
||||||
|
"method": "GET", |
||||||
|
"port": 3100, |
||||||
|
"path": "/" |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
## Response |
||||||
|
```javascript |
||||||
|
Status: 200 |
||||||
|
{ |
||||||
|
"x-powered-by": "mapzen", |
||||||
|
"charset": "utf8", |
||||||
|
"access-control-allow-origin": "*", |
||||||
|
"access-control-allow-methods": "GET", |
||||||
|
"access-control-allow-headers": "X-Requested-With,content-type", |
||||||
|
"access-control-allow-credentials": "true", |
||||||
|
"server": "Pelias/0.0.0", |
||||||
|
"cache-control": "public,max-age=60", |
||||||
|
"content-type": "application/json; charset=utf-8", |
||||||
|
"content-length": "50", |
||||||
|
"etag": "W/\"32-85536434\"", |
||||||
|
"date": "Fri, 12 Sep 2014 19:51:44 GMT", |
||||||
|
"connection": "close" |
||||||
|
} |
||||||
|
``` |
||||||
|
```javascript |
||||||
|
{ |
||||||
|
"name": "pelias-api", |
||||||
|
"version": { |
||||||
|
"number": "0.0.0" |
||||||
|
} |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
## Tests |
||||||
|
|
||||||
|
### ✓ content-type header correctly set |
||||||
|
```javascript |
||||||
|
response.should.have.header 'Content-Type','application/json; charset=utf-8' |
||||||
|
``` |
||||||
|
|
||||||
|
### ✓ endpoint available |
||||||
|
```javascript |
||||||
|
response.statusCode.should.equal 200 |
||||||
|
``` |
||||||
|
|
||||||
|
### ✓ cache-control header correctly set |
||||||
|
```javascript |
||||||
|
response.should.have.header 'Cache-Control','public,max-age=60' |
||||||
|
``` |
||||||
|
|
||||||
|
### ✓ charset header correctly set |
||||||
|
```javascript |
||||||
|
response.should.have.header 'Charset','utf8' |
||||||
|
``` |
||||||
|
|
||||||
|
### ✓ 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}/ |
||||||
|
``` |
||||||
|
|
||||||
|
### ✓ vanity header correctly set |
||||||
|
```javascript |
||||||
|
response.should.have.header 'X-Powered-By','mapzen' |
||||||
|
``` |
||||||
|
|
||||||
|
### ✓ should respond in json with server info |
||||||
|
```javascript |
||||||
|
should.exist json |
||||||
|
should.exist json.name |
||||||
|
should.exist json.version |
||||||
|
``` |
||||||
|
|
@ -0,0 +1,50 @@ |
|||||||
|
# jsonp |
||||||
|
|
||||||
|
*Generated: Fri Sep 12 2014 20:51:45 GMT+0100 (BST)* |
||||||
|
## Request |
||||||
|
```javascript |
||||||
|
{ |
||||||
|
"protocol": "http:", |
||||||
|
"host": "localhost", |
||||||
|
"method": "GET", |
||||||
|
"port": 3100, |
||||||
|
"path": "/?callback=test" |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
## Response |
||||||
|
```javascript |
||||||
|
Status: 200 |
||||||
|
{ |
||||||
|
"x-powered-by": "mapzen", |
||||||
|
"charset": "utf8", |
||||||
|
"access-control-allow-origin": "*", |
||||||
|
"access-control-allow-methods": "GET", |
||||||
|
"access-control-allow-headers": "X-Requested-With,content-type", |
||||||
|
"access-control-allow-credentials": "true", |
||||||
|
"server": "Pelias/0.0.0", |
||||||
|
"cache-control": "public,max-age=60", |
||||||
|
"content-type": "application/javascript; charset=utf-8", |
||||||
|
"content-length": "57", |
||||||
|
"etag": "W/\"39-b8a2aba1\"", |
||||||
|
"date": "Fri, 12 Sep 2014 19:51:44 GMT", |
||||||
|
"connection": "close" |
||||||
|
} |
||||||
|
``` |
||||||
|
```html |
||||||
|
test({"name":"pelias-api","version":{"number":"0.0.0"}}); |
||||||
|
``` |
||||||
|
|
||||||
|
## Tests |
||||||
|
|
||||||
|
### ✓ should respond with jsonp |
||||||
|
```javascript |
||||||
|
should.exist response.body |
||||||
|
response.body.substr(0,5).should.equal 'test('; |
||||||
|
``` |
||||||
|
|
||||||
|
### ✓ content-type header correctly set |
||||||
|
```javascript |
||||||
|
response.should.have.header 'Content-Type','application/javascript; charset=utf-8' |
||||||
|
``` |
||||||
|
|
@ -1,14 +0,0 @@ |
|||||||
|
|
||||||
var express = require('express'), |
|
||||||
app = express(); |
|
||||||
|
|
||||||
// middleware modules
|
|
||||||
// app.use( require('cookie-parser')() );
|
|
||||||
|
|
||||||
// enable client-side caching of 60s by default
|
|
||||||
app.use(function(req, res, next){ |
|
||||||
res.header('Cache-Control','public,max-age=60'); |
|
||||||
next(); |
|
||||||
}); |
|
||||||
|
|
||||||
module.exports = app; |
|
@ -1,10 +1,17 @@ |
|||||||
|
|
||||||
var app = require('./express'); |
var cluster = require('cluster'), |
||||||
|
app = require('./app'), |
||||||
|
multicore = false, |
||||||
|
port = ( process.env.PORT || 3100 ); |
||||||
|
|
||||||
// api root
|
/** cluster webserver across all cores **/ |
||||||
app.get( '/', require('./controller/index') ); |
if( multicore ){ |
||||||
|
// @todo: not finished yet
|
||||||
// suggest API
|
// cluster(app)
|
||||||
app.get( '/suggest', require('./sanitiser/suggest'), require('./controller/suggest') ); |
// .use(cluster.stats())
|
||||||
|
// .listen( process.env.PORT || 3100 );
|
||||||
app.listen( process.env.PORT || 3100 ); |
} |
||||||
|
else { |
||||||
|
console.log( 'listening on ' + port ); |
||||||
|
app.listen( process.env.PORT || 3100 ); |
||||||
|
} |
@ -0,0 +1,8 @@ |
|||||||
|
|
||||||
|
// handle not found errors
|
||||||
|
function middleware(req, res) { |
||||||
|
res.header('Cache-Control','public,max-age=300'); // 5 minute cache
|
||||||
|
res.status(404).json({ error: 'not found: invalid path' }); |
||||||
|
} |
||||||
|
|
||||||
|
module.exports = middleware; |
@ -0,0 +1,9 @@ |
|||||||
|
|
||||||
|
// handle application errors
|
||||||
|
function middleware(err, req, res, next) { |
||||||
|
res.header('Cache-Control','no-cache'); |
||||||
|
if( res.statusCode < 400 ){ res.status(500); } |
||||||
|
res.json({ error: err }); |
||||||
|
} |
||||||
|
|
||||||
|
module.exports = middleware; |
@ -0,0 +1,10 @@ |
|||||||
|
|
||||||
|
function middleware(req, res, next){ |
||||||
|
res.header('Access-Control-Allow-Origin', '*'); |
||||||
|
res.header('Access-Control-Allow-Methods', 'GET'); |
||||||
|
res.header('Access-Control-Allow-Headers', 'X-Requested-With,content-type'); |
||||||
|
res.header('Access-Control-Allow-Credentials', true); |
||||||
|
next(); |
||||||
|
} |
||||||
|
|
||||||
|
module.exports = middleware; |
@ -0,0 +1,12 @@ |
|||||||
|
|
||||||
|
var pkg = require('../package'); |
||||||
|
|
||||||
|
function middleware(req, res, next){ |
||||||
|
res.header('Charset','utf8'); |
||||||
|
res.header('Cache-Control','public,max-age=60'); |
||||||
|
res.header('Server', 'Pelias/'+pkg.version); |
||||||
|
res.header('X-Powered-By', 'mapzen'); |
||||||
|
next(); |
||||||
|
} |
||||||
|
|
||||||
|
module.exports = middleware; |
@ -0,0 +1,24 @@ |
|||||||
|
|
||||||
|
function middleware(req, res, next){ |
||||||
|
|
||||||
|
// store old json function
|
||||||
|
var json = res.json.bind(res); |
||||||
|
|
||||||
|
// replace with jsonp aware function
|
||||||
|
res.json = function( data ){ |
||||||
|
|
||||||
|
// jsonp
|
||||||
|
if( req.query && req.query.callback ){ |
||||||
|
res.header('Content-type','application/javascript'); |
||||||
|
return res.send( req.query.callback + '('+ JSON.stringify( data ) + ');' ); |
||||||
|
} |
||||||
|
|
||||||
|
// regular json
|
||||||
|
res.header('Content-type','application/json'); |
||||||
|
return json( data ); |
||||||
|
}; |
||||||
|
|
||||||
|
next(); |
||||||
|
} |
||||||
|
|
||||||
|
module.exports = middleware; |
@ -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; |
@ -1,33 +0,0 @@ |
|||||||
|
|
||||||
// send a reply that is capable of JSON, CORS and JSONP
|
|
||||||
function cors( req, res, obj ){ |
|
||||||
res.header('Charset','utf8'); |
|
||||||
res.header('Cache-Control','public,max-age=60'); |
|
||||||
res.header('Access-Control-Allow-Origin', '*'); |
|
||||||
res.header('Access-Control-Allow-Methods', 'GET'); |
|
||||||
res.header('Access-Control-Allow-Headers', 'X-Requested-With,content-type'); |
|
||||||
res.header('Access-Control-Allow-Credentials', true); |
|
||||||
res.header('X-Powered-By', 'pelias'); |
|
||||||
|
|
||||||
// jsonp
|
|
||||||
if( req.query && req.query.callback ){ |
|
||||||
res.header('Content-type','application/javascript'); |
|
||||||
return res.send( req.query.callback + '('+ JSON.stringify( obj ) + ');' ); |
|
||||||
} |
|
||||||
|
|
||||||
// regular json
|
|
||||||
res.header('Content-type','application/json'); |
|
||||||
return res.json( obj ); |
|
||||||
} |
|
||||||
|
|
||||||
// send an error
|
|
||||||
function error( req, res, next, err ){ |
|
||||||
console.error( 'application error:', err ); |
|
||||||
// mask error from user (contains paths)
|
|
||||||
return cors( req, res, { error: 'application error' } ); |
|
||||||
} |
|
||||||
|
|
||||||
module.exports = { |
|
||||||
cors: cors, |
|
||||||
error: error |
|
||||||
}; |
|
@ -0,0 +1,17 @@ |
|||||||
|
|
||||||
|
#> invalid path |
||||||
|
path: '/notexist' |
||||||
|
|
||||||
|
#? not found |
||||||
|
response.statusCode.should.equal 404 |
||||||
|
|
||||||
|
#? content-type header correctly set |
||||||
|
response.should.have.header 'Content-Type','application/json; charset=utf-8' |
||||||
|
|
||||||
|
#? cache-control header correctly set |
||||||
|
response.should.have.header 'Cache-Control','public,max-age=300' |
||||||
|
|
||||||
|
#? should respond in json with server info |
||||||
|
should.exist json |
||||||
|
should.exist json.error |
||||||
|
json.error.should.equal 'not found: invalid path' |
@ -0,0 +1,9 @@ |
|||||||
|
|
||||||
|
#> cross-origin resource sharing |
||||||
|
path: '/' |
||||||
|
|
||||||
|
#? access control headers correctly set |
||||||
|
response.should.have.header 'Access-Control-Allow-Origin','*' |
||||||
|
response.should.have.header 'Access-Control-Allow-Methods','GET' |
||||||
|
response.should.have.header 'Access-Control-Allow-Headers','X-Requested-With,content-type' |
||||||
|
response.should.have.header 'Access-Control-Allow-Credentials','true' |
@ -0,0 +1,10 @@ |
|||||||
|
|
||||||
|
#> jsonp |
||||||
|
path: '/?callback=test' |
||||||
|
|
||||||
|
#? content-type header correctly set |
||||||
|
response.should.have.header 'Content-Type','application/javascript; charset=utf-8' |
||||||
|
|
||||||
|
#? should respond with jsonp |
||||||
|
should.exist response.body |
||||||
|
response.body.substr(0,5).should.equal 'test('; |
@ -0,0 +1,15 @@ |
|||||||
|
|
||||||
|
#> valid suggest query |
||||||
|
path: '/suggest?input=a&lat=0&lon=0' |
||||||
|
|
||||||
|
#? 200 ok |
||||||
|
response.statusCode.should.equal 200 |
||||||
|
|
||||||
|
#? valid response |
||||||
|
now = new Date().getTime() |
||||||
|
should.exist json |
||||||
|
should.not.exist json.error |
||||||
|
should.exist json.date |
||||||
|
json.date.should.be.within now-1000, now+1000 |
||||||
|
should.exist json.body |
||||||
|
json.body.should.be.instanceof Array |
@ -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); |
||||||
|
} |
||||||
|
}; |
@ -0,0 +1,28 @@ |
|||||||
|
|
||||||
|
var responses = {}; |
||||||
|
responses['client/suggest/ok/1'] = function( cmd, cb ){ |
||||||
|
return cb( undefined, suggestEnvelope([ { value: 1 }, { value: 2 } ]) ); |
||||||
|
}; |
||||||
|
responses['client/suggest/fail/1'] = function( cmd, cb ){ |
||||||
|
return cb( 'a backend error occurred' ); |
||||||
|
}; |
||||||
|
|
||||||
|
function setup( key, cmdCb ){ |
||||||
|
function backend( a, b ){ |
||||||
|
return { |
||||||
|
client: { |
||||||
|
suggest: function( cmd, cb ){ |
||||||
|
if( 'function' === typeof cmdCb ){ cmdCb( cmd ); } |
||||||
|
return responses[key].apply( this, arguments ); |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
} |
||||||
|
return backend; |
||||||
|
} |
||||||
|
|
||||||
|
function suggestEnvelope( options ){ |
||||||
|
return { pelias: [{ options: options }]}; |
||||||
|
} |
||||||
|
|
||||||
|
module.exports = setup; |
@ -0,0 +1,10 @@ |
|||||||
|
|
||||||
|
function setup(){ |
||||||
|
return query; |
||||||
|
} |
||||||
|
|
||||||
|
function query( clean ){ |
||||||
|
return clean; |
||||||
|
} |
||||||
|
|
||||||
|
module.exports = setup; |
@ -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); |
||||||
|
} |
||||||
|
}; |
@ -0,0 +1,29 @@ |
|||||||
|
|
||||||
|
var query = require('../../../query/suggest'); |
||||||
|
|
||||||
|
module.exports.tests = {}; |
||||||
|
|
||||||
|
module.exports.tests.interface = function(test, common) { |
||||||
|
test('valid interface', function(t) { |
||||||
|
t.equal(typeof query, 'function', 'valid function'); |
||||||
|
t.end(); |
||||||
|
}); |
||||||
|
}; |
||||||
|
|
||||||
|
module.exports.tests.interface = function(test, common) { |
||||||
|
test('valid interface', function(t) { |
||||||
|
t.equal(typeof query, 'function', 'valid function'); |
||||||
|
t.end(); |
||||||
|
}); |
||||||
|
}; |
||||||
|
|
||||||
|
module.exports.all = function (tape, common) { |
||||||
|
|
||||||
|
function test(name, testFunction) { |
||||||
|
return tape('suggest query ' + name, testFunction); |
||||||
|
} |
||||||
|
|
||||||
|
for( var testCase in module.exports.tests ){ |
||||||
|
module.exports.tests[testCase](test, common); |
||||||
|
} |
||||||
|
}; |
@ -0,0 +1,203 @@ |
|||||||
|
|
||||||
|
var sanitize = require('../../../sanitiser/suggest'), |
||||||
|
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 /suggest ' + name, testFunction); |
||||||
|
} |
||||||
|
|
||||||
|
for( var testCase in module.exports.tests ){ |
||||||
|
module.exports.tests[testCase](test, common); |
||||||
|
} |
||||||
|
}; |
Loading…
Reference in new issue