Browse Source

Merge remote-tracking branch 'origin' into 243-reverse-confidence-scores

pull/274/head
Diana Shkolnikov 9 years ago
parent
commit
1752155e0d
  1. 2
      README.md
  2. 13
      controller/place.js
  3. 13
      controller/search.js
  4. 1
      helper/geojsonify.js
  5. 6
      middleware/confidenceScore.js
  6. 4
      middleware/distance.js
  7. 70
      middleware/geocodeJSON.js
  8. 4
      middleware/renamePlacenames.js
  9. 4
      middleware/sendJSON.js
  10. 2
      package.json
  11. 19
      routes/v1.js
  12. 7
      sanitiser/_geo_reverse.js
  13. 24
      sanitiser/_id.js
  14. 22
      sanitiser/_size.js
  15. 4
      sanitiser/place.js
  16. 5
      sanitiser/reverse.js
  17. 19
      sanitiser/sanitizeAll.js
  18. 5
      sanitiser/search.js
  19. 4
      test/ciao/404.coffee
  20. 2
      test/ciao/CORS/headers_GET.coffee
  21. 10
      test/ciao/CORS/headers_OPTIONS.coffee
  22. 33
      test/ciao/autocomplete/basic_autocomplete.coffee
  23. 34
      test/ciao/autocomplete/null_island.coffee
  24. 2
      test/ciao/index.coffee
  25. 33
      test/ciao/place/basic_place.coffee
  26. 34
      test/ciao/place/missing_id.coffee
  27. 16
      test/ciao/place/msuccess.coffee
  28. 16
      test/ciao/place/success.coffee
  29. 34
      test/ciao/reverse/basic_reverse.coffee
  30. 34
      test/ciao/reverse/null_island.coffee
  31. 15
      test/ciao/reverse/success.coffee
  32. 41
      test/ciao/search/address_parsing.coffee
  33. 33
      test/ciao/search/basic_search.coffee
  34. 33
      test/ciao/search/no_params.coffee
  35. 35
      test/ciao/search/null_island.coffee
  36. 15
      test/ciao/search/success.coffee
  37. 6
      test/ciao/suggest/nearby_nobias.coffee
  38. 16
      test/ciao/suggest/success.coffee
  39. 16
      test/ciao/suggest/success_nearby.coffee
  40. 4
      test/unit/helper/geojsonify.js
  41. 10
      test/unit/middleware/distance.js
  42. 1
      test/unit/run.js
  43. 61
      test/unit/sanitiser/_size.js
  44. 135
      test/unit/sanitiser/place.js
  45. 144
      test/unit/sanitiser/reverse.js
  46. 170
      test/unit/sanitiser/search.js

2
README.md

@ -5,7 +5,7 @@
## Documentation ## Documentation
See our [API Documentation](https://github.com/pelias/api/blob/master/DOCS.md). See our [API Documentation](https://github.com/pelias/api/blob/master/public/apiDoc.md).
## Install Dependencies ## Install Dependencies

13
controller/place.js

@ -1,10 +1,18 @@
var service = { mget: require('../service/mget') }; var service = { mget: require('../service/mget') };
function setup( backend ){ function setup( backend ){
// allow overriding of dependencies // allow overriding of dependencies
backend = backend || require('../src/backend'); backend = backend || require('../src/backend');
function controller( req, res, next ){ function controller( req, res, next ){
// do not run controller when a request
// validation error has occurred.
if( req.errors && req.errors.length ){
return next();
}
var query = req.clean.ids.map( function(id) { var query = req.clean.ids.map( function(id) {
return { return {
_index: 'pelias', _index: 'pelias',
@ -17,9 +25,8 @@ function setup( backend ){
// error handler // error handler
if( err ){ return next( err ); } if( err ){ return next( err ); }
req.results = { // set response data
data: docs res.data = docs;
};
next(); next();
}); });

13
controller/search.js

@ -8,6 +8,12 @@ function setup( backend, query ){
function controller( req, res, next ){ function controller( req, res, next ){
// do not run controller when a request
// validation error has occurred.
if( req.errors && req.errors.length ){
return next();
}
// backend command // backend command
var cmd = { var cmd = {
index: 'pelias', index: 'pelias',
@ -26,10 +32,9 @@ function setup( backend, query ){
// error handler // error handler
if( err ){ return next( err ); } if( err ){ return next( err ); }
req.results = { // set response data
data: docs, res.data = docs;
meta: meta res.meta = meta;
};
next(); next();
}); });

1
helper/geojsonify.js

@ -8,7 +8,6 @@ var GeoJSON = require('geojson'),
var DETAILS_PROPS = [ var DETAILS_PROPS = [
'housenumber', 'housenumber',
'street', 'street',
'category',
'postalcode', 'postalcode',
'country_a', 'country_a',
'country', 'country',

6
middleware/confidenceScore.js

@ -23,17 +23,17 @@ function setup(peliasConfig) {
function computeScores(req, res, next) { function computeScores(req, res, next) {
// do nothing if no result data set // do nothing if no result data set
if (!req.results || !req.results.data || !req.results.meta) { if (!res || !res.data || !res.meta) {
return next(); return next();
} }
// compute standard deviation and mean from all scores // compute standard deviation and mean from all scores
var scores = req.results.meta.scores; var scores = res.meta.scores;
var stdev = computeStandardDeviation(scores); var stdev = computeStandardDeviation(scores);
var mean = stats.mean(scores); var mean = stats.mean(scores);
// loop through data items and determine confidence scores // loop through data items and determine confidence scores
req.results.data = req.results.data.map(computeConfidenceScore.bind(null, req, mean, stdev)); res.data = res.data.map(computeConfidenceScore.bind(null, req, mean, stdev));
next(); next();
} }

4
middleware/distance.js

@ -9,7 +9,7 @@ function setup() {
function computeDistances(req, res, next) { function computeDistances(req, res, next) {
// do nothing if no result data set // do nothing if no result data set
if (!req.results || !req.results.data) { if (!res || !res.data) {
return next(); return next();
} }
@ -17,7 +17,7 @@ function computeDistances(req, res, next) {
return next(); return next();
} }
req.results.data.forEach(function (place) { res.data.forEach(function (place) {
// the result of getDistance is in meters, so convert to kilometers // the result of getDistance is in meters, so convert to kilometers
place.distance = geolib.getDistance( place.distance = geolib.getDistance(
{ latitude: req.clean.lat, longitude: req.clean.lon }, { latitude: req.clean.lat, longitude: req.clean.lon },

70
middleware/geocodeJSON.js

@ -1,71 +1,89 @@
var url = require('url');
var extend = require('extend'); var extend = require('extend');
var geojsonify = require('../helper/geojsonify').search; var geojsonify = require('../helper/geojsonify').search;
function setup(peliasConfig) { /**
* Returns a middleware function that converts elasticsearch
peliasConfig = peliasConfig || require( 'pelias-config' ).generate().api; * results into geocodeJSON format.
*
* @param {object} [peliasConfig] api portion of pelias config
* @param {string} [basePath]
* @returns {middleware}
*/
function setup(peliasConfig, basePath) {
var opts = {
config: peliasConfig || require('pelias-config').generate().api,
basePath: basePath || '/'
};
function middleware(req, res, next) { function middleware(req, res, next) {
return convertToGeocodeJSON(peliasConfig, req, next); return convertToGeocodeJSON(req, res, next, opts);
} }
return middleware; return middleware;
} }
function convertToGeocodeJSON(peliasConfig, req, next) { /**
* Converts elasticsearch results into geocodeJSON format
// do nothing if no result data set *
if (!req.results || !req.results.data) { * @param {object} req
return next(); * @param {object} res
} * @param {object} next
* @param {object} opts
req.results.geojson = { geocoding: {} }; * @param {string} opts.basePath e.g. '/v1/'
* @param {string} opts.config.host e.g. 'pelias.mapzen.com'
* @param {string} opts.config.version e.g. 1.0
* @returns {*}
*/
function convertToGeocodeJSON(req, res, next, opts) {
res.body = { geocoding: {} };
// REQUIRED. A semver.org compliant version number. Describes the version of // REQUIRED. A semver.org compliant version number. Describes the version of
// the GeocodeJSON spec that is implemented by this instance. // the GeocodeJSON spec that is implemented by this instance.
req.results.geojson.geocoding.version = '0.1'; res.body.geocoding.version = '0.1';
// OPTIONAL. Default: null. The attribution of the data. In case of multiple sources, // OPTIONAL. Default: null. The attribution of the data. In case of multiple sources,
// and then multiple attributions, can be an object with one key by source. // and then multiple attributions, can be an object with one key by source.
// Can be a URI on the server, which outlines attribution details. // Can be a URI on the server, which outlines attribution details.
req.results.geojson.geocoding.attribution = peliasConfig.host + 'attribution'; res.body.geocoding.attribution = url.resolve(opts.config.host, opts.basePath + 'attribution');
// OPTIONAL. Default: null. The query that has been issued to trigger the // OPTIONAL. Default: null. The query that has been issued to trigger the
// search. // search.
// Freeform object. // Freeform object.
// This is the equivalent of how the engine interpreted the incoming request. // This is the equivalent of how the engine interpreted the incoming request.
// Helpful for debugging and understanding how the input impacts results. // Helpful for debugging and understanding how the input impacts results.
req.results.geojson.geocoding.query = req.clean; res.body.geocoding.query = req.clean;
// OPTIONAL. Warnings and errors. // OPTIONAL. Warnings and errors.
addMessages(req.results, 'warnings', req.results.geojson.geocoding); addMessages(req, 'warnings', res.body.geocoding);
addMessages(req.results, 'errors', req.results.geojson.geocoding); addMessages(req, 'errors', res.body.geocoding);
// OPTIONAL // OPTIONAL
// Freeform // Freeform
addEngine(peliasConfig, req.results.geojson.geocoding); addEngine(opts.config.version, res.body.geocoding);
// response envelope // response envelope
req.results.geojson.geocoding.timestamp = new Date().getTime(); res.body.geocoding.timestamp = new Date().getTime();
// convert docs to geojson and merge with geocoding block // convert docs to geojson and merge with geocoding block
extend(req.results.geojson, geojsonify(req.results.data)); extend(res.body, geojsonify(res.data || []));
next(); next();
} }
function addMessages(results, msgType, geocoding) { function addMessages(req, msgType, geocoding) {
if (results.hasOwnProperty(msgType)) { if (req.hasOwnProperty(msgType) && req[msgType].length) {
geocoding.messages = geocoding.messages || {}; geocoding[msgType] = req[msgType];
geocoding.messages[msgType] = results[msgType];
} }
} }
function addEngine(peliasConfig, geocoding) { function addEngine(version, geocoding) {
geocoding.engine = { geocoding.engine = {
name: 'Pelias', name: 'Pelias',
author: 'Mapzen', author: 'Mapzen',
version: peliasConfig.version version: version
}; };
} }

4
middleware/renamePlacenames.js

@ -32,12 +32,12 @@ function setup() {
function renamePlacenames(req, res, next) { function renamePlacenames(req, res, next) {
// do nothing if no result data set // do nothing if no result data set
if (!req.results || !req.results.data) { if (!res || !res.data) {
return next(); return next();
} }
// loop through data items and remap placenames // loop through data items and remap placenames
req.results.data = req.results.data.map(renameProperties); res.data = res.data.map(renameProperties);
next(); next();
} }

4
middleware/sendJSON.js

@ -1,12 +1,12 @@
function sendJSONResponse(req, res, next) { function sendJSONResponse(req, res, next) {
// do nothing if no result data set // do nothing if no result data set
if (!req.results || !req.results.geojson) { if (!res || !res.body) {
return next(); return next();
} }
// respond // respond
return res.status(200).json(req.results.geojson); return res.status(200).json(res.body);
} }
module.exports = sendJSONResponse; module.exports = sendJSONResponse;

2
package.json

@ -59,7 +59,7 @@
"through2": "0.6.5" "through2": "0.6.5"
}, },
"devDependencies": { "devDependencies": {
"ciao": "^0.3.4", "ciao": "^0.6.0",
"istanbul": "^0.3.13", "istanbul": "^0.3.13",
"jshint": "^2.5.6", "jshint": "^2.5.6",
"nsp": "^0.3.0", "nsp": "^0.3.0",

19
routes/v1.js

@ -43,6 +43,8 @@ var postProc = {
*/ */
function addRoutes(app, peliasConfig) { function addRoutes(app, peliasConfig) {
var base = '/v1/';
/** ------------------------- routers ------------------------- **/ /** ------------------------- routers ------------------------- **/
var routers = { var routers = {
@ -58,7 +60,7 @@ function addRoutes(app, peliasConfig) {
controllers.search(), controllers.search(),
postProc.confidenceScores(peliasConfig), postProc.confidenceScores(peliasConfig),
postProc.renamePlacenames(), postProc.renamePlacenames(),
postProc.geocodeJSON(peliasConfig), postProc.geocodeJSON(peliasConfig, base),
postProc.sendJSON postProc.sendJSON
]), ]),
autocomplete: createRouter([ autocomplete: createRouter([
@ -67,7 +69,7 @@ function addRoutes(app, peliasConfig) {
controllers.search(null, require('../query/autocomplete')), controllers.search(null, require('../query/autocomplete')),
postProc.confidenceScores(peliasConfig), postProc.confidenceScores(peliasConfig),
postProc.renamePlacenames(), postProc.renamePlacenames(),
postProc.geocodeJSON(peliasConfig), postProc.geocodeJSON(peliasConfig, base),
postProc.sendJSON postProc.sendJSON
]), ]),
reverse: createRouter([ reverse: createRouter([
@ -79,14 +81,14 @@ function addRoutes(app, peliasConfig) {
// so it must be calculated first // so it must be calculated first
postProc.confidenceScoresReverse(), postProc.confidenceScoresReverse(),
postProc.renamePlacenames(), postProc.renamePlacenames(),
postProc.geocodeJSON(peliasConfig), postProc.geocodeJSON(peliasConfig, base),
postProc.sendJSON postProc.sendJSON
]), ]),
place: createRouter([ place: createRouter([
sanitisers.place.middleware, sanitisers.place.middleware,
controllers.place(), controllers.place(),
postProc.renamePlacenames(), postProc.renamePlacenames(),
postProc.geocodeJSON(peliasConfig), postProc.geocodeJSON(peliasConfig, base),
postProc.sendJSON postProc.sendJSON
]), ]),
status: createRouter([ status: createRouter([
@ -95,18 +97,19 @@ function addRoutes(app, peliasConfig) {
}; };
var base = '/v1/'; // static data endpoints
// api root
app.get ( base, routers.index ); app.get ( base, routers.index );
app.get ( base + 'attribution', routers.attribution ); app.get ( base + 'attribution', routers.attribution );
app.get ( '/attribution', routers.attribution );
app.get ( '/status', routers.status );
// backend dependent endpoints
app.get ( base + 'place', routers.place ); app.get ( base + 'place', routers.place );
app.get ( base + 'autocomplete', routers.autocomplete ); app.get ( base + 'autocomplete', routers.autocomplete );
app.get ( base + 'search', routers.search ); app.get ( base + 'search', routers.search );
app.post( base + 'search', routers.search ); app.post( base + 'search', routers.search );
app.get ( base + 'reverse', routers.reverse ); app.get ( base + 'reverse', routers.reverse );
app.get ( '/status', routers.status );
} }
/** /**

7
sanitiser/_geo_reverse.js

@ -14,6 +14,13 @@ module.exports = function sanitize( raw, clean ){
geo_common.sanitize_coord( 'lat', clean, raw['point.lat'], LAT_LON_IS_REQUIRED ); geo_common.sanitize_coord( 'lat', clean, raw['point.lat'], LAT_LON_IS_REQUIRED );
geo_common.sanitize_coord( 'lon', clean, raw['point.lon'], LAT_LON_IS_REQUIRED ); geo_common.sanitize_coord( 'lon', clean, raw['point.lon'], LAT_LON_IS_REQUIRED );
// remove both if only one is set
// @todo: clean this up!
if( !clean.hasOwnProperty('lat') || !clean.hasOwnProperty('lon') ){
delete clean.lat;
delete clean.lon;
}
// boundary.circle.* is not mandatory, and only specifying radius is fine, // boundary.circle.* is not mandatory, and only specifying radius is fine,
// as point.lat/lon will be used to fill those values by default // as point.lat/lon will be used to fill those values by default
geo_common.sanitize_boundary_circle( clean, raw, CIRCLE_IS_REQUIRED, CIRCLE_MUST_BE_COMPLETE); geo_common.sanitize_boundary_circle( clean, raw, CIRCLE_IS_REQUIRED, CIRCLE_MUST_BE_COMPLETE);

24
sanitiser/_id.js

@ -18,8 +18,18 @@ function sanitize( raw, clean ){
// error & warning messages // error & warning messages
var messages = { errors: [], warnings: [] }; var messages = { errors: [], warnings: [] };
// 'raw.id' can be an array!? // 'raw.id' can be an array
var rawIds = check.array( raw.id ) ? _.unique( raw.id ) : [ raw.id ]; var rawIds = check.array( raw.id ) ? _.unique( raw.id ) : [];
// 'raw.id' can be a string
if( check.unemptyString( raw.id ) ){
rawIds.push( raw.id );
}
// no ids provided
if( !rawIds.length ){
messages.errors.push( errorMessage('id') );
}
// ensure all elements are valid non-empty strings // ensure all elements are valid non-empty strings
rawIds = rawIds.filter( function( uc ){ rawIds = rawIds.filter( function( uc ){
@ -48,25 +58,27 @@ function sanitize( raw, clean ){
} }
// id text // id text
if( !check.unemptyString( id ) ){ else if( !check.unemptyString( id ) ){
messages.errors.push( errorMessage( rawId ) ); messages.errors.push( errorMessage( rawId ) );
} }
// type text // type text
if( !check.unemptyString( type ) ){ else if( !check.unemptyString( type ) ){
messages.errors.push( errorMessage( rawId ) ); messages.errors.push( errorMessage( rawId ) );
} }
// type text must be one of the types // type text must be one of the types
if( !_.contains( types, type ) ){ else if( !_.contains( types, type ) ){
messages.errors.push( messages.errors.push(
errorMessage('type', type + ' is invalid. It must be one of these values - [' + types.join(', ') + ']') errorMessage('type', type + ' is invalid. It must be one of these values - [' + types.join(', ') + ']')
); );
} }
// add valid id to 'clean.ids' array // add valid id to 'clean.ids' array
else {
clean.ids.push({ clean.ids.push({
id: id, id: id,
type: type type: type
}); });
}
}); });
return messages; return messages;

22
sanitiser/_size.js

@ -1,3 +1,4 @@
var check = require('check-types');
var MIN_SIZE = 1, var MIN_SIZE = 1,
MAX_SIZE = 40, MAX_SIZE = 40,
@ -10,36 +11,23 @@ function sanitize( raw, clean ){
var messages = { errors: [], warnings: [] }; var messages = { errors: [], warnings: [] };
// coercions // coercions
var _size = parseInt( raw.size, 10 ); clean.size = parseInt( raw.size, 10 );
// invalid numeric input // invalid numeric input
// @todo: this can be removed now as queries have default sizes? if( isNaN( clean.size ) ){
if( isNaN( _size ) ){
// set the default size
messages.warnings.push('invalid integer \'size\', using DEFAULT_SIZE');
clean.size = DEFAULT_SIZE; clean.size = DEFAULT_SIZE;
} }
// valid numeric input
else {
// ensure size falls within defined range // ensure size falls within defined range
if( _size > MAX_SIZE ){ else if( clean.size > MAX_SIZE ){
// set the max size // set the max size
messages.warnings.push('out-of-range integer \'size\', using MAX_SIZE'); messages.warnings.push('out-of-range integer \'size\', using MAX_SIZE');
clean.size = MAX_SIZE; clean.size = MAX_SIZE;
} }
else if( _size < MIN_SIZE ){ else if( clean.size < MIN_SIZE ){
// set the min size // set the min size
messages.warnings.push('out-of-range integer \'size\', using MIN_SIZE'); messages.warnings.push('out-of-range integer \'size\', using MIN_SIZE');
clean.size = MIN_SIZE; clean.size = MIN_SIZE;
} }
else {
// set the input size
clean.size = _size;
}
}
return messages; return messages;
} }

4
sanitiser/place.js

@ -14,10 +14,6 @@ module.exports.sanitiser_list = sanitizers;
// middleware // middleware
module.exports.middleware = function( req, res, next ){ module.exports.middleware = function( req, res, next ){
sanitize( req, function( err, clean ){ sanitize( req, function( err, clean ){
if( err ){
res.status(400); // 400 Bad Request
return next(err);
}
next(); next();
}); });
}; };

5
sanitiser/reverse.js

@ -6,7 +6,6 @@ var sanitizeAll = require('../sanitiser/sanitizeAll'),
size: require('../sanitiser/_size'), size: require('../sanitiser/_size'),
private: require('../sanitiser/_flag_bool')('private', false), private: require('../sanitiser/_flag_bool')('private', false),
geo_reverse: require('../sanitiser/_geo_reverse'), geo_reverse: require('../sanitiser/_geo_reverse'),
categories: require('../sanitiser/_categories'),
boundary_country: require('../sanitiser/_boundary_country'), boundary_country: require('../sanitiser/_boundary_country'),
}; };
@ -19,10 +18,6 @@ module.exports.sanitiser_list = sanitizers;
// middleware // middleware
module.exports.middleware = function( req, res, next ){ module.exports.middleware = function( req, res, next ){
sanitize( req, function( err, clean ){ sanitize( req, function( err, clean ){
if( err ){
res.status(400); // 400 Bad Request
return next(err);
}
next(); next();
}); });
}; };

19
sanitiser/sanitizeAll.js

@ -1,10 +1,16 @@
var check = require('check-types');
function sanitize( req, sanitizers, cb ){ function sanitize( req, sanitizers, cb ){
// init an object to store clean // init an object to store clean
// (sanitized) input parameters // (sanitized) input parameters
req.clean = {}; req.clean = {};
// init erros and warnings arrays
req.errors = [];
req.warnings = [];
// source of input parameters // source of input parameters
// (in this case from the GET querystring params) // (in this case from the GET querystring params)
var params = req.query || {}; var params = req.query || {};
@ -12,14 +18,21 @@ function sanitize( req, sanitizers, cb ){
for (var s in sanitizers) { for (var s in sanitizers) {
var sanity = sanitizers[s]( params, req.clean ); var sanity = sanitizers[s]( params, req.clean );
// errors // if errors occurred then set them
// on the req object.
if( sanity.errors.length ){ if( sanity.errors.length ){
return cb( sanity.errors[0] ); req.errors = req.errors.concat( sanity.errors );
}
// if warnings occurred then set them
// on the req object.
if( sanity.warnings.length ){
req.warnings = req.warnings.concat( sanity.warnings );
} }
} }
// @todo remove these args, they do not need to be passed out
return cb( undefined, req.clean ); return cb( undefined, req.clean );
} }
// export function // export function

5
sanitiser/search.js

@ -7,7 +7,6 @@ var sanitizeAll = require('../sanitiser/sanitizeAll'),
sources: require('../sanitiser/_targets')('sources', require( '../query/sources' )), sources: require('../sanitiser/_targets')('sources', require( '../query/sources' )),
private: require('../sanitiser/_flag_bool')('private', false), private: require('../sanitiser/_flag_bool')('private', false),
geo_search: require('../sanitiser/_geo_search'), geo_search: require('../sanitiser/_geo_search'),
categories: require('../sanitiser/_categories'),
boundary_country: require('../sanitiser/_boundary_country'), boundary_country: require('../sanitiser/_boundary_country'),
}; };
@ -20,10 +19,6 @@ module.exports.sanitiser_list = sanitizers;
// middleware // middleware
module.exports.middleware = function( req, res, next ){ module.exports.middleware = function( req, res, next ){
sanitize( req, function( err, clean ){ sanitize( req, function( err, clean ){
if( err ){
res.status(400); // 400 Bad Request
return next(err);
}
next(); next();
}); });
}; };

4
test/ciao/404.coffee

@ -3,7 +3,7 @@
path: '/notexist' path: '/notexist'
#? not found #? not found
response.statusCode.should.equal 404 response.statusCode.should.be.equal 404
#? content-type header correctly set #? content-type header correctly set
response.should.have.header 'Content-Type','application/json; charset=utf-8' response.should.have.header 'Content-Type','application/json; charset=utf-8'
@ -14,4 +14,4 @@ response.should.have.header 'Cache-Control','public,max-age=300'
#? should respond in json with server info #? should respond in json with server info
should.exist json should.exist json
should.exist json.error should.exist json.error
json.error.should.equal 'not found: invalid path' json.error.should.be.equal 'not found: invalid path'

2
test/ciao/cors.coffee → test/ciao/CORS/headers_GET.coffee

@ -4,6 +4,6 @@ path: '/'
#? access control headers correctly set #? access control headers correctly set
response.should.have.header 'Access-Control-Allow-Origin','*' response.should.have.header 'Access-Control-Allow-Origin','*'
response.should.have.header 'Access-Control-Allow-Methods','GET' response.should.have.header 'Access-Control-Allow-Methods','GET, OPTIONS'
response.should.have.header 'Access-Control-Allow-Headers','X-Requested-With,content-type' response.should.have.header 'Access-Control-Allow-Headers','X-Requested-With,content-type'
response.should.have.header 'Access-Control-Allow-Credentials','true' response.should.have.header 'Access-Control-Allow-Credentials','true'

10
test/ciao/CORS/headers_OPTIONS.coffee

@ -0,0 +1,10 @@
#> cross-origin resource sharing
path: '/'
method: 'OPTIONS'
#? access control headers correctly set
response.should.have.header 'Access-Control-Allow-Origin','*'
response.should.have.header 'Access-Control-Allow-Methods','GET, OPTIONS'
response.should.have.header 'Access-Control-Allow-Headers','X-Requested-With,content-type'
response.should.have.header 'Access-Control-Allow-Credentials','true'

33
test/ciao/autocomplete/basic_autocomplete.coffee

@ -0,0 +1,33 @@
#> basic autocomplete
path: '/v1/autocomplete?text=a'
#? 200 ok
response.statusCode.should.be.equal 200
response.should.have.header 'charset', 'utf8'
response.should.have.header 'content-type', 'application/json; charset=utf-8'
#? valid geocoding block
should.exist json.geocoding
should.exist json.geocoding.version
should.exist json.geocoding.attribution
should.exist json.geocoding.query
should.exist json.geocoding.engine
should.exist json.geocoding.engine.name
should.exist json.geocoding.engine.author
should.exist json.geocoding.engine.version
should.exist json.geocoding.timestamp
#? valid geojson
json.type.should.be.equal 'FeatureCollection'
json.features.should.be.instanceof Array
#? expected errors
should.not.exist json.geocoding.errors
#? expected warnings
should.not.exist json.geocoding.warnings
#? inputs
json.geocoding.query['text'].should.eql 'a'
json.geocoding.query['size'].should.eql 10

34
test/ciao/autocomplete/null_island.coffee

@ -0,0 +1,34 @@
#> null island
path: '/v1/autocomplete?text=a&focus.point.lat=0&focus.point.lon=0'
#? 200 ok
response.statusCode.should.be.equal 200
response.should.have.header 'charset', 'utf8'
response.should.have.header 'content-type', 'application/json; charset=utf-8'
#? valid geocoding block
should.exist json.geocoding
should.exist json.geocoding.version
should.exist json.geocoding.attribution
should.exist json.geocoding.query
should.exist json.geocoding.engine
should.exist json.geocoding.engine.name
should.exist json.geocoding.engine.author
should.exist json.geocoding.engine.version
should.exist json.geocoding.timestamp
#? valid geojson
json.type.should.be.equal 'FeatureCollection'
json.features.should.be.instanceof Array
#? expected errors
should.not.exist json.geocoding.errors
#? expected warnings
should.not.exist json.geocoding.warnings
#? inputs
json.geocoding.query['lat'].should.eql 0
json.geocoding.query['lon'].should.eql 0
json.geocoding.query['size'].should.eql 10

2
test/ciao/index.coffee

@ -3,7 +3,7 @@
path: '/v1/' path: '/v1/'
#? endpoint available #? endpoint available
response.statusCode.should.equal 200 response.statusCode.should.be.equal 200
#? content-type header correctly set #? content-type header correctly set
response.should.have.header 'Content-Type','text/html; charset=utf-8' response.should.have.header 'Content-Type','text/html; charset=utf-8'

33
test/ciao/place/basic_place.coffee

@ -0,0 +1,33 @@
#> basic place
path: '/v1/place?id=geoname:1'
#? 200 ok
response.statusCode.should.be.equal 200
response.should.have.header 'charset', 'utf8'
response.should.have.header 'content-type', 'application/json; charset=utf-8'
#? valid geocoding block
should.exist json.geocoding
should.exist json.geocoding.version
should.exist json.geocoding.attribution
should.exist json.geocoding.query
should.exist json.geocoding.engine
should.exist json.geocoding.engine.name
should.exist json.geocoding.engine.author
should.exist json.geocoding.engine.version
should.exist json.geocoding.timestamp
#? valid geojson
json.type.should.be.equal 'FeatureCollection'
json.features.should.be.instanceof Array
#? expected errors
should.not.exist json.geocoding.errors
#? expected warnings
should.not.exist json.geocoding.warnings
#? inputs
json.geocoding.query['ids'].should.eql [{ id: '1', type: 'geoname' }]
should.not.exist json.geocoding.query['size']

34
test/ciao/place/missing_id.coffee

@ -0,0 +1,34 @@
#> missing id
path: '/v1/place'
#? 200 ok
response.statusCode.should.be.equal 200
response.should.have.header 'charset', 'utf8'
response.should.have.header 'content-type', 'application/json; charset=utf-8'
#? valid geocoding block
should.exist json.geocoding
should.exist json.geocoding.version
should.exist json.geocoding.attribution
should.exist json.geocoding.query
should.exist json.geocoding.engine
should.exist json.geocoding.engine.name
should.exist json.geocoding.engine.author
should.exist json.geocoding.engine.version
should.exist json.geocoding.timestamp
#? valid geojson
json.type.should.be.equal 'FeatureCollection'
json.features.should.be.instanceof Array
#? expected errors
should.exist json.geocoding.errors
json.geocoding.errors.should.eql [ 'invalid param \'id\': text length, must be >0' ]
#? expected warnings
should.not.exist json.geocoding.warnings
#? inputs
json.geocoding.query['ids'].should.eql []
should.not.exist json.geocoding.query['size']

16
test/ciao/place/msuccess.coffee

@ -1,16 +0,0 @@
#> valid place query
path: '/v1/place?id=geoname:4221195&id=geoname:4193595'
#? 200 ok
response.statusCode.should.equal 200
#? valid response
now = new Date().getTime()
should.exist json
should.not.exist json.error
json.date.should.be.within now-5000, now+5000
#? valid geojson
json.type.should.equal 'FeatureCollection'
json.features.should.be.instanceof Array

16
test/ciao/place/success.coffee

@ -1,16 +0,0 @@
#> valid place query
path: '/v1/place?id=geoname:4221195'
#? 200 ok
response.statusCode.should.equal 200
#? valid response
now = new Date().getTime()
should.exist json
should.not.exist json.error
json.date.should.be.within now-5000, now+5000
#? valid geojson
json.type.should.equal 'FeatureCollection'
json.features.should.be.instanceof Array

34
test/ciao/reverse/basic_reverse.coffee

@ -0,0 +1,34 @@
#> basic reverse
path: '/v1/reverse?point.lat=1&point.lon=2'
#? 200 ok
response.statusCode.should.be.equal 200
response.should.have.header 'charset', 'utf8'
response.should.have.header 'content-type', 'application/json; charset=utf-8'
#? valid geocoding block
should.exist json.geocoding
should.exist json.geocoding.version
should.exist json.geocoding.attribution
should.exist json.geocoding.query
should.exist json.geocoding.engine
should.exist json.geocoding.engine.name
should.exist json.geocoding.engine.author
should.exist json.geocoding.engine.version
should.exist json.geocoding.timestamp
#? valid geojson
json.type.should.be.equal 'FeatureCollection'
json.features.should.be.instanceof Array
#? expected errors
should.not.exist json.geocoding.errors
#? expected warnings
should.not.exist json.geocoding.warnings
#? inputs
json.geocoding.query['lat'].should.eql 1
json.geocoding.query['lon'].should.eql 2
json.geocoding.query['size'].should.eql 10

34
test/ciao/reverse/null_island.coffee

@ -0,0 +1,34 @@
#> null island
path: '/v1/reverse?point.lat=0&point.lon=0'
#? 200 ok
response.statusCode.should.be.equal 200
response.should.have.header 'charset', 'utf8'
response.should.have.header 'content-type', 'application/json; charset=utf-8'
#? valid geocoding block
should.exist json.geocoding
should.exist json.geocoding.version
should.exist json.geocoding.attribution
should.exist json.geocoding.query
should.exist json.geocoding.engine
should.exist json.geocoding.engine.name
should.exist json.geocoding.engine.author
should.exist json.geocoding.engine.version
should.exist json.geocoding.timestamp
#? valid geojson
json.type.should.be.equal 'FeatureCollection'
json.features.should.be.instanceof Array
#? expected errors
should.not.exist json.geocoding.errors
#? expected warnings
should.not.exist json.geocoding.warnings
#? inputs
json.geocoding.query['lat'].should.eql 0
json.geocoding.query['lon'].should.eql 0
json.geocoding.query['size'].should.eql 10

15
test/ciao/reverse/success.coffee

@ -1,15 +0,0 @@
#> valid reverse query
path: '/v1/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
json.date.should.be.within now-5000, now+5000
#? valid geojson
json.type.should.equal 'FeatureCollection'
json.features.should.be.instanceof Array

41
test/ciao/search/address_parsing.coffee

@ -0,0 +1,41 @@
#> address parsing
path: '/v1/search?text=30%20w%2026th%20st%2C%20ny'
#? 200 ok
response.statusCode.should.be.equal 200
response.should.have.header 'charset', 'utf8'
response.should.have.header 'content-type', 'application/json; charset=utf-8'
#? valid geocoding block
should.exist json.geocoding
should.exist json.geocoding.version
should.exist json.geocoding.attribution
should.exist json.geocoding.query
should.exist json.geocoding.engine
should.exist json.geocoding.engine.name
should.exist json.geocoding.engine.author
should.exist json.geocoding.engine.version
should.exist json.geocoding.timestamp
#? valid geojson
json.type.should.be.equal 'FeatureCollection'
json.features.should.be.instanceof Array
#? expected errors
should.not.exist json.geocoding.errors
#? expected warnings
should.not.exist json.geocoding.warnings
#? inputs
json.geocoding.query['text'].should.eql '30 w 26th st, ny'
json.geocoding.query['size'].should.eql 10
#? address parsing
json.geocoding.query.parsed_text['name'].should.eql '30 w 26th st'
json.geocoding.query.parsed_text['number'].should.eql 30
json.geocoding.query.parsed_text['street'].should.eql 'w 26th st'
json.geocoding.query.parsed_text['state'].should.eql 'NY'
json.geocoding.query.parsed_text['regions'].should.eql []
json.geocoding.query.parsed_text['admin_parts'].should.eql "ny"

33
test/ciao/search/basic_search.coffee

@ -0,0 +1,33 @@
#> basic search
path: '/v1/search?text=a'
#? 200 ok
response.statusCode.should.be.equal 200
response.should.have.header 'charset', 'utf8'
response.should.have.header 'content-type', 'application/json; charset=utf-8'
#? valid geocoding block
should.exist json.geocoding
should.exist json.geocoding.version
should.exist json.geocoding.attribution
should.exist json.geocoding.query
should.exist json.geocoding.engine
should.exist json.geocoding.engine.name
should.exist json.geocoding.engine.author
should.exist json.geocoding.engine.version
should.exist json.geocoding.timestamp
#? valid geojson
json.type.should.be.equal 'FeatureCollection'
json.features.should.be.instanceof Array
#? expected errors
should.not.exist json.geocoding.errors
#? expected warnings
should.not.exist json.geocoding.warnings
#? inputs
json.geocoding.query['text'].should.eql 'a'
json.geocoding.query['size'].should.eql 10

33
test/ciao/search/no_params.coffee

@ -0,0 +1,33 @@
#> no params specified
path: '/v1/search'
#? 200 ok
response.statusCode.should.be.equal 200
response.should.have.header 'charset', 'utf8'
response.should.have.header 'content-type', 'application/json; charset=utf-8'
#? valid geocoding block
should.exist json.geocoding
should.exist json.geocoding.version
should.exist json.geocoding.attribution
should.exist json.geocoding.query
should.exist json.geocoding.engine
should.exist json.geocoding.engine.name
should.exist json.geocoding.engine.author
should.exist json.geocoding.engine.version
should.exist json.geocoding.timestamp
#? valid geojson
json.type.should.be.equal 'FeatureCollection'
json.features.should.be.instanceof Array
#? expected errors
should.exist json.geocoding.errors
json.geocoding.errors.should.eql [ 'invalid param \'text\': text length, must be >0' ]
#? expected warnings
should.not.exist json.geocoding.warnings
#? inputs
json.geocoding.query['size'].should.eql 10

35
test/ciao/search/null_island.coffee

@ -0,0 +1,35 @@
#> null island
path: '/v1/search?text=a&focus.point.lat=0&focus.point.lon=0'
#? 200 ok
response.statusCode.should.be.equal 200
response.should.have.header 'charset', 'utf8'
response.should.have.header 'content-type', 'application/json; charset=utf-8'
#? valid geocoding block
should.exist json.geocoding
should.exist json.geocoding.version
should.exist json.geocoding.attribution
should.exist json.geocoding.query
should.exist json.geocoding.engine
should.exist json.geocoding.engine.name
should.exist json.geocoding.engine.author
should.exist json.geocoding.engine.version
should.exist json.geocoding.timestamp
#? valid geojson
json.type.should.be.equal 'FeatureCollection'
json.features.should.be.instanceof Array
#? expected errors
should.not.exist json.geocoding.errors
#? expected warnings
should.not.exist json.geocoding.warnings
#? inputs
json.geocoding.query['text'].should.eql 'a'
json.geocoding.query['size'].should.eql 10
json.geocoding.query['lat'].should.eql 0
json.geocoding.query['lon'].should.eql 0

15
test/ciao/search/success.coffee

@ -1,15 +0,0 @@
#> valid search query
path: '/v1/search?text=lake&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

6
test/ciao/suggest/nearby_nobias.coffee

@ -1,6 +0,0 @@
#> suggest without geo bias
path: '/v1/suggest/nearby?text=a'
#? 400 bad request
response.statusCode.should.equal 400

16
test/ciao/suggest/success.coffee

@ -1,16 +0,0 @@
#> valid suggest query
path: '/v1/suggest?text=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
json.date.should.be.within now-5000, now+5000
#? valid geojson
json.type.should.equal 'FeatureCollection'
json.features.should.be.instanceof Array

16
test/ciao/suggest/success_nearby.coffee

@ -1,16 +0,0 @@
#> valid suggest query
path: '/v1/suggest/nearby?text=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

4
test/unit/helper/geojsonify.js

@ -153,7 +153,6 @@ module.exports.tests.search = function(test, common) {
'localadmin': 'test1', 'localadmin': 'test1',
'locality': 'test2', 'locality': 'test2',
'neighbourhood': 'test3', 'neighbourhood': 'test3',
'category': ['food', 'nightlife'],
'housenumber': '13', 'housenumber': '13',
'street': 'Liverpool Road', 'street': 'Liverpool Road',
'postalcode': 'N1 0RW' 'postalcode': 'N1 0RW'
@ -206,8 +205,7 @@ module.exports.tests.search = function(test, common) {
'county': 'New York', 'county': 'New York',
'localadmin': 'Manhattan', 'localadmin': 'Manhattan',
'locality': 'New York', 'locality': 'New York',
'neighbourhood': 'Koreatown', 'neighbourhood': 'Koreatown'
'category': ['tourism', 'transport']
} }
} }
] ]

10
test/unit/middleware/distance.js

@ -8,8 +8,9 @@ module.exports.tests.computeDistance = function(test, common) {
clean: { clean: {
lat: 45, lat: 45,
lon: -77 lon: -77
}, }
results: { };
var res = {
data: [ data: [
{ {
center_point: { center_point: {
@ -18,12 +19,11 @@ module.exports.tests.computeDistance = function(test, common) {
} }
} }
] ]
}
}; };
var expected = 742.348; var expected = 742.348;
distance(req, null, function () { distance(req, res, function () {
t.equal(req.results.data[0].distance, expected, 'correct distance computed'); t.equal(res.data[0].distance, expected, 'correct distance computed');
t.end(); t.end();
}); });
}); });

1
test/unit/run.js

@ -28,6 +28,7 @@ var tests = [
require('./sanitiser/_geo_common'), require('./sanitiser/_geo_common'),
require('./middleware/distance'), require('./middleware/distance'),
require('./middleware/confidenceScoreReverse'), require('./middleware/confidenceScoreReverse'),
require('./sanitiser/_size'),
]; ];
tests.map(function(t) { tests.map(function(t) {

61
test/unit/sanitiser/_size.js

@ -0,0 +1,61 @@
var sanitize = require('../../../sanitiser/_size');
module.exports.tests = {};
module.exports.tests.sanitize_size = function(test, common) {
test('size=0', function(t) {
var raw = { size: 0 };
var clean = {};
var res = sanitize(raw, clean);
t.equal(res.errors.length, 0, 'should return no errors');
t.equal(res.warnings.length, 1, 'should return warning');
t.equal(res.warnings[0], 'out-of-range integer \'size\', using MIN_SIZE', 'check warning text');
t.equal(clean.size, 1, 'default to 1');
t.end();
});
test('size=10000', function(t) {
var raw = { size: 10000 };
var clean = {};
var res = sanitize(raw, clean);
t.equal(res.errors.length, 0, 'should return no errors');
t.equal(res.warnings.length, 1, 'should return warning');
t.equal(res.warnings[0], 'out-of-range integer \'size\', using MAX_SIZE', 'check warning text');
t.equal(clean.size, 40, 'default to 40');
t.end();
});
test('size not set', function(t) {
var raw = {};
var clean = {};
var res = sanitize(raw, clean);
t.equal(res.errors.length, 0, 'should return no errors');
t.equal(res.warnings.length, 0, 'should return no warning');
t.equal(clean.size, 10, 'default to 10');
t.end();
});
var valid_sizes = [5, '5', 5.5, '5.5'];
valid_sizes.forEach(function (size) {
test('size=' + size, function (t) {
var raw = {size: size};
var clean = {};
var res = sanitize(raw, clean);
t.equal(res.errors.length, 0, 'should return no errors');
t.equal(res.warnings.length, 0, 'should return warning');
t.equal(clean.size, 5, 'set to correct integer');
t.end();
});
});
};
module.exports.all = function (tape, common) {
function test(name, testFunction) {
return tape('SANTIZE _size ' + name, testFunction);
}
for( var testCase in module.exports.tests ){
module.exports.tests[testCase](test, common);
}
};

135
test/unit/sanitiser/place.js

@ -1,6 +1,8 @@
// @todo: refactor this test, it's pretty messy, brittle and hard to follow
var place = require('../../../sanitiser/place'), var place = require('../../../sanitiser/place'),
_sanitize = place.sanitize, sanitize = place.sanitize,
middleware = place.middleware, middleware = place.middleware,
types = require('../../../query/types'), types = require('../../../query/types'),
delimiter = ':', delimiter = ':',
@ -11,12 +13,14 @@ var place = require('../../../sanitiser/place'),
var type = input.split(delimiter)[0]; var type = input.split(delimiter)[0];
return type + ' is invalid. It must be one of these values - [' + types.join(', ') + ']'; }, return type + ' is invalid. It must be one of these values - [' + types.join(', ') + ']'; },
defaultClean = { ids: [ { id: '123', type: 'geoname' } ], private: false }, defaultClean = { ids: [ { id: '123', type: 'geoname' } ], private: false },
sanitize = function(query, cb) { _sanitize({'query':query}, cb); },
inputs = { inputs = {
valid: [ 'geoname:1', 'osmnode:2', 'admin0:53', 'osmway:44', 'geoname:5' ], valid: [ 'geoname:1', 'osmnode:2', 'admin0:53', 'osmway:44', 'geoname:5' ],
invalid: [ ':', '', '::', 'geoname:', ':234', 'gibberish:23' ] invalid: [ ':', '', '::', 'geoname:', ':234', 'gibberish:23' ]
}; };
// these are the default values you would expect when no input params are specified.
var emptyClean = { ids: [], private: false };
module.exports.tests = {}; module.exports.tests = {};
module.exports.tests.interface = function(test, common) { module.exports.tests.interface = function(test, common) {
@ -33,33 +37,35 @@ module.exports.tests.interface = function(test, common) {
}; };
module.exports.tests.sanitize_id = function(test, common) { module.exports.tests.sanitize_id = function(test, common) {
test('invalid input', function(t) { test('id: invalid input', function(t) {
inputs.invalid.forEach( function( input ){ inputs.invalid.forEach( function( input ){
sanitize({ id: input }, function( err, clean ){ var req = { query: { id: input } };
switch (err) { sanitize(req, function( ){
switch (req.errors[0]) {
case defaultError: case defaultError:
t.equal(err, defaultError, input + ' is invalid input'); break; t.equal(req.errors[0], defaultError, input + ' is invalid input'); break;
case defaultLengthError(input): case defaultLengthError(input):
t.equal(err, defaultLengthError(input), input + ' is invalid (missing id/type)'); break; t.equal(req.errors[0], defaultLengthError(input), input + ' is invalid (missing id/type)'); break;
case defaultFormatError: case defaultFormatError:
t.equal(err, defaultFormatError, input + ' is invalid (invalid format)'); break; t.equal(req.errors[0], defaultFormatError, input + ' is invalid (invalid format)'); break;
case defaultMissingTypeError(input): case defaultMissingTypeError(input):
t.equal(err, defaultMissingTypeError(input), input + ' is an unknown type'); break; t.equal(req.errors[0], defaultMissingTypeError(input), input + ' is an unknown type'); break;
default: break; default: break;
} }
t.equal(clean, undefined, 'clean not set'); t.deepEqual(req.clean, emptyClean, 'clean only has default values set');
}); });
}); });
t.end(); t.end();
}); });
test('valid input', function(t) { test('id: valid input', function(t) {
inputs.valid.forEach( function( input ){ inputs.valid.forEach( function( input ){
var input_parts = input.split(delimiter); var input_parts = input.split(delimiter);
var expected = { ids: [ { id: input_parts[1], type: input_parts[0] } ], private: false }; var expected = { ids: [ { id: input_parts[1], type: input_parts[0] } ], private: false };
sanitize({ id: input }, function( err, clean ){ var req = { query: { id: input } };
t.equal(err, undefined, 'no error (' + input + ')' ); sanitize(req, function(){
t.deepEqual(clean, expected, 'clean set correctly (' + input + ')'); t.deepEqual( req.errors, [], 'no error (' + input + ')' );
t.deepEqual( req.clean, expected, 'clean set correctly (' + input + ')');
}); });
}); });
t.end(); t.end();
@ -68,26 +74,26 @@ module.exports.tests.sanitize_id = function(test, common) {
module.exports.tests.sanitize_ids = function(test, common) { module.exports.tests.sanitize_ids = function(test, common) {
test('invalid input', function(t) { test('ids: invalid input', function(t) {
sanitize({ id: inputs.invalid }, function( err, clean ){ var req = { query: { id: inputs.invalid } };
var input = inputs.invalid[0]; // since it breaks on the first invalid element var expected = [
switch (err) { 'invalid param \'id\': text length, must be >0',
case defaultError: 'invalid param \':\': text length, must be >0',
t.equal(err, defaultError, input + ' is invalid input'); break; 'invalid param \'::\': text length, must be >0',
case defaultLengthError(input): 'invalid param \'geoname:\': text length, must be >0',
t.equal(err, defaultLengthError(input), input + ' is invalid (missing id/type)'); break; 'invalid param \':234\': text length, must be >0',
case defaultFormatError: 'gibberish is invalid. It must be one of these values - ' +
t.equal(err, defaultFormatError, input + ' is invalid (invalid format)'); break; '[geoname, osmnode, osmway, admin0, admin1, admin2, neighborhood, ' +
case defaultMissingTypeError(input): 'locality, local_admin, osmaddress, openaddresses]'
t.equal(err, defaultMissingTypeError(input), input + ' is an unknown type'); break; ];
default: break; sanitize(req, function(){
} t.deepEqual(req.errors, expected);
t.equal(clean, undefined, 'clean not set'); t.deepEqual(req.clean, emptyClean, 'clean only has default values set');
}); });
t.end(); t.end();
}); });
test('valid input', function(t) { test('ids: valid input', function(t) {
var expected={}; var expected={};
expected.ids = []; expected.ids = [];
inputs.valid.forEach( function( input ){ inputs.valid.forEach( function( input ){
@ -95,9 +101,10 @@ module.exports.tests.sanitize_ids = function(test, common) {
expected.ids.push({ id: input_parts[1], type: input_parts[0] }); expected.ids.push({ id: input_parts[1], type: input_parts[0] });
}); });
expected.private = false; expected.private = false;
sanitize({ id: inputs.valid }, function( err, clean ){ var req = { query: { id: inputs.valid } };
t.equal(err, undefined, 'no error' ); sanitize(req, function(){
t.deepEqual(clean, expected, 'clean set correctly'); t.deepEqual( req.errors, [], 'no errors' );
t.deepEqual( req.clean, expected, 'clean set correctly' );
}); });
t.end(); t.end();
}); });
@ -107,8 +114,11 @@ module.exports.tests.sanitize_private = function(test, common) {
var invalid_values = [null, -1, 123, NaN, 'abc']; var invalid_values = [null, -1, 123, NaN, 'abc'];
invalid_values.forEach(function(value) { invalid_values.forEach(function(value) {
test('invalid private param ' + value, function(t) { test('invalid private param ' + value, function(t) {
sanitize({ id:'geoname:123', 'private': value}, function( err, clean ){ var req = { query: { id:'geoname:123', 'private': value } };
t.equal(clean.private, false, 'default private set (to false)'); sanitize(req, function(){
t.deepEqual( req.errors, [], 'no errors' );
t.deepEqual( req.warnings, [], 'no warnings' );
t.equal(req.clean.private, false, 'default private set (to false)');
t.end(); t.end();
}); });
}); });
@ -117,8 +127,11 @@ module.exports.tests.sanitize_private = function(test, common) {
var valid_values = ['true', true, 1]; var valid_values = ['true', true, 1];
valid_values.forEach(function(value) { valid_values.forEach(function(value) {
test('valid private param ' + value, function(t) { test('valid private param ' + value, function(t) {
sanitize({ id:'geoname:123', 'private': value }, function( err, clean ){ var req = { query: { id:'geoname:123', 'private': value } };
t.equal(clean.private, true, 'private set to true'); sanitize(req, function(){
t.deepEqual( req.errors, [], 'no errors' );
t.deepEqual( req.warnings, [], 'no warnings' );
t.equal(req.clean.private, true, 'private set to true');
t.end(); t.end();
}); });
}); });
@ -127,16 +140,22 @@ module.exports.tests.sanitize_private = function(test, common) {
var valid_false_values = ['false', false, 0]; var valid_false_values = ['false', false, 0];
valid_false_values.forEach(function(value) { valid_false_values.forEach(function(value) {
test('test setting false explicitly ' + value, function(t) { test('test setting false explicitly ' + value, function(t) {
sanitize({ id:'geoname:123', 'private': value }, function( err, clean ){ var req = { query: { id:'geoname:123', 'private': value } };
t.equal(clean.private, false, 'private set to false'); sanitize(req, function(){
t.deepEqual( req.errors, [], 'no errors' );
t.deepEqual( req.warnings, [], 'no warnings' );
t.equal(req.clean.private, false, 'private set to false');
t.end(); t.end();
}); });
}); });
}); });
test('test default behavior', function(t) { test('test default behavior', function(t) {
sanitize({ id:'geoname:123' }, function( err, clean ){ var req = { query: { id:'geoname:123' } };
t.equal(clean.private, false, 'private set to false'); sanitize(req, function(){
t.deepEqual( req.errors, [], 'no errors' );
t.deepEqual( req.warnings, [], 'no warnings' );
t.equal(req.clean.private, false, 'private set to false');
t.end(); t.end();
}); });
}); });
@ -144,10 +163,12 @@ module.exports.tests.sanitize_private = function(test, common) {
module.exports.tests.de_dupe = function(test, common) { module.exports.tests.de_dupe = function(test, common) {
var expected = { ids: [ { id: '1', type: 'geoname' }, { id: '2', type: 'osmnode' } ], private: false }; var expected = { ids: [ { id: '1', type: 'geoname' }, { id: '2', type: 'osmnode' } ], private: false };
var req = { query: { id: ['geoname:1', 'osmnode:2', 'geoname:1'] } };
test('duplicate ids', function(t) { test('duplicate ids', function(t) {
sanitize( { id: ['geoname:1', 'osmnode:2', 'geoname:1'] }, function( err, clean ){ sanitize( req, function(){
t.equal(err, undefined, 'no error' ); t.deepEqual( req.errors, [], 'no errors' );
t.deepEqual(clean, expected, 'clean set correctly'); t.deepEqual( req.warnings, [], 'no warnings' );
t.deepEqual(req.clean, expected, 'clean set correctly');
t.end(); t.end();
}); });
}); });
@ -155,31 +176,21 @@ module.exports.tests.de_dupe = function(test, common) {
module.exports.tests.invalid_params = function(test, common) { module.exports.tests.invalid_params = function(test, common) {
test('invalid params', function(t) { test('invalid params', function(t) {
sanitize( undefined, function( err, clean ){ var req = { query: {} };
t.equal(err, defaultError, 'handle invalid params gracefully'); sanitize( req, function(){
t.equal( req.errors[0], defaultError, 'handle invalid params gracefully');
t.deepEqual( req.warnings, [], 'no warnings' );
t.end(); t.end();
}); });
}); });
}; };
module.exports.tests.middleware_failure = function(test, common) {
test('middleware failure', function(t) {
var res = { status: function( code ){
t.equal(code, 400, 'status set');
}};
var next = function( message ){
t.equal(message, defaultError);
t.end();
};
middleware( {}, res, next );
});
};
module.exports.tests.middleware_success = function(test, common) { module.exports.tests.middleware_success = function(test, common) {
test('middleware success', function(t) { test('middleware success', function(t) {
var req = { query: { id: 'geoname' + delimiter + '123' }}; var req = { query: { id: 'geoname:123' }};
var next = function( message ){ var next = function(){
t.equal(message, undefined, 'no error message set'); t.deepEqual( req.errors, [], 'no errors' );
t.deepEqual( req.warnings, [], 'no warnings' );
t.deepEqual(req.clean, defaultClean); t.deepEqual(req.clean, defaultClean);
t.end(); t.end();
}; };

144
test/unit/sanitiser/reverse.js

@ -1,6 +1,8 @@
// @todo: refactor this test, it's pretty messy, brittle and hard to follow
var reverse = require('../../../sanitiser/reverse'), var reverse = require('../../../sanitiser/reverse'),
_sanitize = reverse.sanitize, sanitize = reverse.sanitize,
middleware = reverse.middleware, middleware = reverse.middleware,
defaultError = 'missing param \'lat\'', defaultError = 'missing param \'lat\'',
defaultClean = { lat:0, defaultClean = { lat:0,
@ -9,10 +11,12 @@ var reverse = require('../../../sanitiser/reverse'),
lon: 0, lon: 0,
size: 10, size: 10,
private: false, private: false,
categories: [],
boundary: { } boundary: { }
}, };
sanitize = function(query, cb) { _sanitize({'query':query}, cb); };
// these are the default values you would expect when no input params are specified.
// @todo: why is this different from $defaultClean?
var emptyClean = { boundary: {}, private: false, size: 10, types: {} };
module.exports.tests = {}; module.exports.tests = {};
@ -31,7 +35,7 @@ module.exports.tests.interface = function(test, common) {
module.exports.tests.sanitisers = function(test, common) { module.exports.tests.sanitisers = function(test, common) {
test('check sanitiser list', function (t) { test('check sanitiser list', function (t) {
var expected = ['layers', 'sources', 'size', 'private', 'geo_reverse', 'categories', 'boundary_country']; var expected = ['layers', 'sources', 'size', 'private', 'geo_reverse', 'boundary_country'];
t.deepEqual(Object.keys(reverse.sanitiser_list), expected); t.deepEqual(Object.keys(reverse.sanitiser_list), expected);
t.end(); t.end();
}); });
@ -45,29 +49,32 @@ module.exports.tests.sanitize_lat = function(test, common) {
}; };
test('invalid lat', function(t) { test('invalid lat', function(t) {
lats.invalid.forEach( function( lat ){ lats.invalid.forEach( function( lat ){
sanitize({ 'point.lat': lat, 'point.lon': 0 }, function( err, clean ){ var req = { query: { 'point.lat': lat, 'point.lon': 0 } };
t.equal(err, 'invalid param \'lat\': must be >-90 and <90', lat + ' is an invalid latitude'); sanitize(req, function(){
t.equal(clean, undefined, 'clean not set'); t.equal(req.errors[0], 'invalid param \'lat\': must be >-90 and <90', lat + ' is an invalid latitude');
t.deepEqual(req.clean, emptyClean, 'clean only has default values set');
}); });
}); });
t.end(); t.end();
}); });
test('valid lat', function(t) { test('valid lat', function(t) {
lats.valid.forEach( function( lat ){ lats.valid.forEach( function( lat ){
sanitize({ 'point.lat': lat, 'point.lon': 0 }, function( err, clean ){ var req = { query: { 'point.lat': lat, 'point.lon': 0 } };
sanitize(req, function(){
var expected = JSON.parse(JSON.stringify( defaultClean )); var expected = JSON.parse(JSON.stringify( defaultClean ));
expected.lat = parseFloat( lat ); expected.lat = parseFloat( lat );
t.equal(err, undefined, 'no error'); t.deepEqual(req.errors, [], 'no errors');
t.equal(clean.lat, parseFloat(lat), 'clean set correctly (' + lat + ')'); t.equal(req.clean.lat, parseFloat(lat), 'clean set correctly (' + lat + ')');
}); });
}); });
t.end(); t.end();
}); });
test('missing lat', function(t) { test('missing lat', function(t) {
lats.missing.forEach( function( lat ){ lats.missing.forEach( function( lat ){
sanitize({ 'point.lat': lat, 'point.lon': 0 }, function( err, clean ){ var req = { query: { 'point.lat': lat, 'point.lon': 0 } };
t.equal(err, 'missing param \'lat\'', 'latitude is a required field'); sanitize(req, function(){
t.equal(clean, undefined, 'clean not set'); t.equal(req.errors[0], 'missing param \'lat\'', 'latitude is a required field');
t.deepEqual(req.clean, emptyClean, 'clean only has default values set');
}); });
}); });
t.end(); t.end();
@ -81,43 +88,50 @@ module.exports.tests.sanitize_lon = function(test, common) {
}; };
test('valid lon', function(t) { test('valid lon', function(t) {
lons.valid.forEach( function( lon ){ lons.valid.forEach( function( lon ){
sanitize({ 'point.lat': 0, 'point.lon': lon }, function( err, clean ){ var req = { query: { 'point.lat': 0, 'point.lon': lon } };
sanitize(req, function(){
var expected = JSON.parse(JSON.stringify( defaultClean )); var expected = JSON.parse(JSON.stringify( defaultClean ));
expected.lon = parseFloat( lon ); expected.lon = parseFloat( lon );
t.equal(err, undefined, 'no error'); t.deepEqual(req.errors, [], 'no errors');
t.equal(clean.lon, parseFloat(lon), 'clean set correctly (' + lon + ')'); t.equal(req.clean.lon, parseFloat(lon), 'clean set correctly (' + lon + ')');
}); });
}); });
t.end(); t.end();
}); });
test('missing lon', function(t) { test('missing lon', function(t) {
lons.missing.forEach( function( lon ){ lons.missing.forEach( function( lon ){
sanitize({ 'point.lat': 0, 'point.lon': lon }, function( err, clean ){ var req = { query: { 'point.lat': 0, 'point.lon': lon } };
t.equal(err, 'missing param \'lon\'', 'longitude is a required field');
t.equal(clean, undefined, 'clean not set'); // @todo: why is lat set?
var expected = { boundary: {}, lat: 0, private: false, size: 10, types: {} };
sanitize(req, function(){
t.equal(req.errors[0], 'missing param \'lon\'', 'longitude is a required field');
t.deepEqual(req.clean, expected, 'clean only has default values set');
}); });
}); });
t.end(); t.end();
}); });
}; };
module.exports.tests.sanitize_size = function(test, common) { module.exports.tests.sanitize_size = function(test, common) {
test('invalid size value', function(t) { test('invalid size value', function(t) {
sanitize({ size: 'a', 'point.lat': 0, 'point.lon': 0 }, function( err, clean ){ var req = { query: { size: 'a', 'point.lat': 0, 'point.lon': 0 } };
t.equal(clean.size, 10, 'default size set'); sanitize(req, function(){
t.equal(req.clean.size, 10, 'default size set');
t.end(); t.end();
}); });
}); });
test('below min size value', function(t) { test('below min size value', function(t) {
sanitize({ size: -100, 'point.lat': 0, 'point.lon': 0 }, function( err, clean ){ var req = { query: { size: -100, 'point.lat': 0, 'point.lon': 0 } };
t.equal(clean.size, 1, 'min size set'); sanitize(req, function(){
t.equal(req.clean.size, 1, 'min size set');
t.end(); t.end();
}); });
}); });
test('above max size value', function(t) { test('above max size value', function(t) {
sanitize({ size: 9999, 'point.lat': 0, 'point.lon': 0 }, function( err, clean ){ var req = { query: { size: 9999, 'point.lat': 0, 'point.lon': 0 } };
t.equal(clean.size, 40, 'max size set'); sanitize(req, function(){
t.equal(req.clean.size, 40, 'max size set');
t.end(); t.end();
}); });
}); });
@ -127,8 +141,9 @@ module.exports.tests.sanitize_private = function(test, common) {
var invalid_values = [null, -1, 123, NaN, 'abc']; var invalid_values = [null, -1, 123, NaN, 'abc'];
invalid_values.forEach(function(value) { invalid_values.forEach(function(value) {
test('invalid private param ' + value, function(t) { test('invalid private param ' + value, function(t) {
sanitize({ 'point.lat': 0, 'point.lon': 0, 'private': value}, function( err, clean ){ var req = { query: { 'point.lat': 0, 'point.lon': 0, 'private': value } };
t.equal(clean.private, false, 'default private set (to false)'); sanitize(req, function(){
t.equal(req.clean.private, false, 'default private set (to false)');
t.end(); t.end();
}); });
}); });
@ -137,8 +152,9 @@ module.exports.tests.sanitize_private = function(test, common) {
var valid_values = ['true', true, 1, '1']; var valid_values = ['true', true, 1, '1'];
valid_values.forEach(function(value) { valid_values.forEach(function(value) {
test('valid private param ' + value, function(t) { test('valid private param ' + value, function(t) {
sanitize({ 'point.lat': 0, 'point.lon': 0, 'private': value }, function( err, clean ){ var req = { query: { 'point.lat': 0, 'point.lon': 0, 'private': value } };
t.equal(clean.private, true, 'private set to true'); sanitize(req, function(){
t.equal(req.clean.private, true, 'private set to true');
t.end(); t.end();
}); });
}); });
@ -147,78 +163,28 @@ module.exports.tests.sanitize_private = function(test, common) {
var valid_false_values = ['false', false, 0]; var valid_false_values = ['false', false, 0];
valid_false_values.forEach(function(value) { valid_false_values.forEach(function(value) {
test('test setting false explicitly ' + value, function(t) { test('test setting false explicitly ' + value, function(t) {
sanitize({ 'point.lat': 0, 'point.lon': 0, 'private': value }, function( err, clean ){ var req = { query: { 'point.lat': 0, 'point.lon': 0, 'private': value } };
t.equal(clean.private, false, 'private set to false'); sanitize(req, function(){
t.equal(req.clean.private, false, 'private set to false');
t.end(); t.end();
}); });
}); });
}); });
test('test default behavior', function(t) { test('test default behavior', function(t) {
sanitize({ 'point.lat': 0, 'point.lon': 0 }, function( err, clean ){ var req = { query: { 'point.lat': 0, 'point.lon': 0 } };
t.equal(clean.private, false, 'private set to false'); sanitize(req, function(){
t.end(); t.equal(req.clean.private, false, 'private set to false');
});
});
};
module.exports.tests.sanitize_categories = function(test, common) {
var queryParams = { 'point.lat': 0, 'point.lon': 0 };
test('unspecified', function(t) {
queryParams.categories = undefined;
sanitize(queryParams, function( err, clean ){
t.deepEqual(clean.categories, defaultClean.categories, 'default to empty categories array');
t.end();
});
});
test('single category', function(t) {
queryParams.categories = 'food';
sanitize(queryParams, function( err, clean ){
t.deepEqual(clean.categories, ['food'], 'category set');
t.end();
});
});
test('multiple categories', function(t) {
queryParams.categories = 'food,education,nightlife';
sanitize(queryParams, function( err, clean ){
t.deepEqual(clean.categories, ['food', 'education', 'nightlife'], 'categories set');
t.end();
});
});
test('whitespace and empty strings', function(t) {
queryParams.categories = 'food, , nightlife ,';
sanitize(queryParams, function( err, clean ){
t.deepEqual(clean.categories, ['food', 'nightlife'], 'categories set');
t.end();
});
});
test('all empty strings', function(t) {
queryParams.categories = ', , ,';
sanitize(queryParams, function( err, clean ){
t.deepEqual(clean.categories, defaultClean.categories, 'empty strings filtered out');
t.end(); 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.equals(message, defaultError);
t.end();
};
middleware( {}, res, next );
});
};
module.exports.tests.middleware_success = function(test, common) { module.exports.tests.middleware_success = function(test, common) {
test('middleware success', function(t) { test('middleware success', function(t) {
var req = { query: { 'point.lat': 0, 'point.lon': 0 }}; var req = { query: { 'point.lat': 0, 'point.lon': 0 }};
var next = function( message ){ var next = function(){
t.equal(message, undefined, 'no error message set'); t.deepEqual(req.errors, [], 'no error message set');
t.deepEqual(req.clean, defaultClean); t.deepEqual(req.clean, defaultClean);
t.end(); t.end();
}; };

170
test/unit/sanitiser/search.js

@ -3,7 +3,7 @@ var search = require('../../../sanitiser/search'),
_text = require('../sanitiser/_text'), _text = require('../sanitiser/_text'),
parser = require('../../../helper/query_parser'), parser = require('../../../helper/query_parser'),
defaultParsed = _text.defaultParsed, defaultParsed = _text.defaultParsed,
_sanitize = search.sanitize, sanitize = search.sanitize,
middleware = search.middleware, middleware = search.middleware,
defaultError = 'invalid param \'text\': text length, must be >0', defaultError = 'invalid param \'text\': text length, must be >0',
defaultClean = { text: 'test', defaultClean = { text: 'test',
@ -11,8 +11,11 @@ var search = require('../../../sanitiser/search'),
}, },
size: 10, size: 10,
parsed_text: defaultParsed, parsed_text: defaultParsed,
}, };
sanitize = function(query, cb) { _sanitize({'query':query}, cb); };
// these are the default values you would expect when no input params are specified.
// @todo: why is this different from $defaultClean?
var emptyClean = { boundary: {}, private: false, size: 10, types: {} };
module.exports.tests = {}; module.exports.tests = {};
@ -31,7 +34,7 @@ module.exports.tests.interface = function(test, common) {
module.exports.tests.sanitisers = function(test, common) { module.exports.tests.sanitisers = function(test, common) {
test('check sanitiser list', function (t) { test('check sanitiser list', function (t) {
var expected = ['text', 'size', 'layers', 'sources', 'private', 'geo_search', 'categories', 'boundary_country' ]; var expected = ['text', 'size', 'layers', 'sources', 'private', 'geo_search', 'boundary_country' ];
t.deepEqual(Object.keys(search.sanitiser_list), expected); t.deepEqual(Object.keys(search.sanitiser_list), expected);
t.end(); t.end();
}); });
@ -41,9 +44,10 @@ module.exports.tests.sanitize_invalid_text = function(test, common) {
test('invalid text', function(t) { test('invalid text', function(t) {
var invalid = [ '', 100, null, undefined, new Date() ]; var invalid = [ '', 100, null, undefined, new Date() ];
invalid.forEach( function( text ){ invalid.forEach( function( text ){
sanitize({ text: text }, function( err, clean ){ var req = { query: { text: text } };
t.equal(err, 'invalid param \'text\': text length, must be >0', text + ' is an invalid text'); sanitize(req, function(){
t.equal(clean, undefined, 'clean not set'); t.equal(req.errors[0], 'invalid param \'text\': text length, must be >0', text + ' is an invalid text');
t.deepEqual(req.clean, emptyClean, 'clean only has default values set');
}); });
}); });
t.end(); t.end();
@ -52,22 +56,25 @@ module.exports.tests.sanitize_invalid_text = function(test, common) {
module.exports.tests.sanitise_valid_text = function(test, common) { module.exports.tests.sanitise_valid_text = function(test, common) {
test('valid short text', function(t) { test('valid short text', function(t) {
sanitize({ text: 'a' }, function( err, clean ){ var req = { query: { text: 'a' } };
t.equal(err, undefined, 'no error'); sanitize(req, function(){
t.equal(req.errors[0], undefined, 'no error');
}); });
t.end(); t.end();
}); });
test('valid not-quite-as-short text', function(t) { test('valid not-quite-as-short text', function(t) {
sanitize({ text: 'aa' }, function( err, clean ){ var req = { query: { text: 'aa' } };
t.equal(err, undefined, 'no error'); sanitize(req, function(){
t.equal(req.errors[0], undefined, 'no error');
}); });
t.end(); t.end();
}); });
test('valid longer text', function(t) { test('valid longer text', function(t) {
sanitize({ text: 'aaaaaaaa' }, function( err, clean ){ var req = { query: { text: 'aaaaaaaa' } };
t.equal(err, undefined, 'no error'); sanitize(req, function(){
t.equal(req.errors[0], undefined, 'no error');
}); });
t.end(); t.end();
}); });
@ -78,13 +85,14 @@ module.exports.tests.sanitize_text_with_delim = function(test, common) {
test('valid texts with a comma', function(t) { test('valid texts with a comma', function(t) {
texts.forEach( function( text ){ texts.forEach( function( text ){
sanitize({ text: text }, function( err, clean ){ var req = { query: { text: text } };
sanitize(req, function(){
var expected = JSON.parse(JSON.stringify( defaultClean )); var expected = JSON.parse(JSON.stringify( defaultClean ));
expected.text = text; expected.text = text;
expected.parsed_text = parser.get_parsed_address(text); expected.parsed_text = parser.get_parsed_address(text);
t.equal(err, undefined, 'no error'); t.equal(req.errors[0], undefined, 'no error');
t.equal(clean.parsed_text.name, expected.parsed_text.name, 'clean name set correctly'); t.equal(req.clean.parsed_text.name, expected.parsed_text.name, 'clean name set correctly');
}); });
}); });
@ -94,8 +102,9 @@ module.exports.tests.sanitize_text_with_delim = function(test, common) {
module.exports.tests.sanitize_private_no_value = function(test, common) { module.exports.tests.sanitize_private_no_value = function(test, common) {
test('default private should be set to true', function(t) { test('default private should be set to true', function(t) {
sanitize({ text: 'test' }, function( err, clean ){ var req = { query: { text: 'test' } };
t.equal(clean.private, false, 'private set to false'); sanitize(req, function(){
t.equal(req.clean.private, false, 'private set to false');
}); });
t.end(); t.end();
}); });
@ -103,8 +112,9 @@ module.exports.tests.sanitize_private_no_value = function(test, common) {
module.exports.tests.sanitize_private_explicit_true_value = function(test, common) { module.exports.tests.sanitize_private_explicit_true_value = function(test, common) {
test('explicit private should be set to true', function(t) { test('explicit private should be set to true', function(t) {
sanitize({ text: 'test', private: true }, function( err, clean ){ var req = { query: { text: 'test', private: true } };
t.equal(clean.private, true, 'private set to true'); sanitize(req, function(){
t.equal(req.clean.private, true, 'private set to true');
}); });
t.end(); t.end();
}); });
@ -112,8 +122,9 @@ module.exports.tests.sanitize_private_explicit_true_value = function(test, commo
module.exports.tests.sanitize_private_explicit_false_value = function(test, common) { module.exports.tests.sanitize_private_explicit_false_value = function(test, common) {
test('explicit private should be set to false', function(t) { test('explicit private should be set to false', function(t) {
sanitize({ text: 'test', private: false }, function( err, clean ){ var req = { query: { text: 'test', private: false } };
t.equal(clean.private, false, 'private set to false'); sanitize(req, function(){
t.equal(req.clean.private, false, 'private set to false');
}); });
t.end(); t.end();
}); });
@ -126,19 +137,21 @@ module.exports.tests.sanitize_lat = function(test, common) {
}; };
test('invalid lat', function(t) { test('invalid lat', function(t) {
lats.invalid.forEach( function( lat ){ lats.invalid.forEach( function( lat ){
sanitize({ text: 'test', 'focus.point.lat': lat, 'focus.point.lon': 0 }, function( err, clean ){ var req = { query: { text: 'test', 'focus.point.lat': lat, 'focus.point.lon': 0 } };
t.equal(err, 'invalid param \'lat\': must be >-90 and <90', lat + ' is an invalid latitude'); sanitize(req, function(){
t.equal(clean, undefined, 'clean not set'); t.equal(req.errors[0], 'invalid param \'lat\': must be >-90 and <90', lat + ' is an invalid latitude');
t.deepEqual(req.clean, emptyClean, 'clean only has default values set');
}); });
}); });
t.end(); t.end();
}); });
test('valid lat', function(t) { test('valid lat', function(t) {
lats.valid.forEach( function( lat ){ lats.valid.forEach( function( lat ){
sanitize({ text: 'test', 'focus.point.lat': lat, 'focus.point.lon': 0 }, function( err, clean ){ var req = { query: { text: 'test', 'focus.point.lat': lat, 'focus.point.lon': 0 } };
sanitize(req, function(){
var expected_lat = parseFloat( lat ); var expected_lat = parseFloat( lat );
t.equal(err, undefined, 'no error'); t.equal(req.errors[0], undefined, 'no error');
t.deepEqual(clean.lat, expected_lat, 'clean lat set correctly (' + lat + ')'); t.equal(req.clean.lat, expected_lat, 'clean lat set correctly (' + lat + ')');
}); });
}); });
t.end(); t.end();
@ -151,11 +164,12 @@ module.exports.tests.sanitize_lon = function(test, common) {
}; };
test('valid lon', function(t) { test('valid lon', function(t) {
lons.valid.forEach( function( lon ){ lons.valid.forEach( function( lon ){
sanitize({ text: 'test', 'focus.point.lat': 0, 'focus.point.lon': lon }, function( err, clean ){ var req = { query: { text: 'test', 'focus.point.lat': 0, 'focus.point.lon': lon } };
sanitize(req, function(){
var expected = JSON.parse(JSON.stringify( defaultClean )); var expected = JSON.parse(JSON.stringify( defaultClean ));
expected.lon = parseFloat( lon ); expected.lon = parseFloat( lon );
t.equal(err, undefined, 'no error'); t.equal(req.errors[0], undefined, 'no error');
t.deepEqual(clean.lon, expected.lon, 'clean set correctly (' + lon + ')'); t.equal(req.clean.lon, expected.lon, 'clean set correctly (' + lon + ')');
}); });
}); });
t.end(); t.end();
@ -164,26 +178,29 @@ module.exports.tests.sanitize_lon = function(test, common) {
module.exports.tests.sanitize_optional_geo = function(test, common) { module.exports.tests.sanitize_optional_geo = function(test, common) {
test('no lat/lon', function(t) { test('no lat/lon', function(t) {
sanitize({ text: 'test' }, function( err, clean ){ var req = { query: { text: 'test' } };
t.equal(err, undefined, 'no error'); sanitize(req, function(){
t.equal(clean.lat, undefined, 'clean set without lat'); t.equal(req.errors[0], undefined, 'no error');
t.equal(clean.lon, undefined, 'clean set without lon'); t.equal(req.clean.lat, undefined, 'clean set without lat');
t.equal(req.clean.lon, undefined, 'clean set without lon');
}); });
t.end(); t.end();
}); });
test('no lat', function(t) { test('no lat', function(t) {
sanitize({ text: 'test', 'focus.point.lon': 0 }, function( err, clean ){ var req = { query: { text: 'test', 'focus.point.lon': 0 } };
sanitize(req, function(){
var expected_lon = 0; var expected_lon = 0;
t.equal(err, undefined, 'no error'); t.equal(req.errors[0], undefined, 'no error');
t.deepEqual(clean.lon, expected_lon, 'clean set correctly (without any lat)'); t.deepEqual(req.clean.lon, expected_lon, 'clean set correctly (without any lat)');
}); });
t.end(); t.end();
}); });
test('no lon', function(t) { test('no lon', function(t) {
sanitize({ text: 'test', 'focus.point.lat': 0 }, function( err, clean ){ var req = { query: { text: 'test', 'focus.point.lat': 0 } };
sanitize(req, function(){
var expected_lat = 0; var expected_lat = 0;
t.equal(err, undefined, 'no error'); t.equal(req.errors[0], undefined, 'no error');
t.deepEqual(clean.lat, expected_lat, 'clean set correctly (without any lon)'); t.deepEqual(req.clean.lat, expected_lat, 'clean set correctly (without any lon)');
}); });
t.end(); t.end();
}); });
@ -219,18 +236,20 @@ module.exports.tests.sanitize_bbox = function(test, common) {
}; };
test('invalid bbox', function(t) { test('invalid bbox', function(t) {
bboxes.invalid.forEach( function( bbox ){ bboxes.invalid.forEach( function( bbox ){
sanitize({ text: 'test', bbox: bbox }, function( err, clean ){ var req = { query: { text: 'test', bbox: bbox } };
t.equal(err, undefined, 'no error'); sanitize(req, function(){
t.equal(clean.bbox, undefined, 'falling back on 50km distance from centroid'); t.equal(req.errors[0], undefined, 'no error');
t.equal(req.clean.bbox, undefined, 'falling back on 50km distance from centroid');
}); });
}); });
t.end(); t.end();
}); });
test('valid bbox', function(t) { test('valid bbox', function(t) {
bboxes.valid.forEach( function( bbox ){ bboxes.valid.forEach( function( bbox ){
sanitize({ text: 'test', bbox: bbox }, function( err, clean ){ var req = { query: { text: 'test', bbox: bbox } };
sanitize(req, function(){
var bboxArray = bbox.split(',').map(function(i) { var bboxArray = bbox.split(',').map(function(i) {
return parseInt(i); return parseInt(i, 10);
}); });
var expected_bbox = { var expected_bbox = {
right: Math.max(bboxArray[0], bboxArray[2]), right: Math.max(bboxArray[0], bboxArray[2]),
@ -238,8 +257,8 @@ module.exports.tests.sanitize_bbox = function(test, common) {
left: Math.min(bboxArray[0], bboxArray[2]), left: Math.min(bboxArray[0], bboxArray[2]),
bottom: Math.min(bboxArray[1], bboxArray[3]) bottom: Math.min(bboxArray[1], bboxArray[3])
}; };
t.equal(err, undefined, 'no error'); t.equal(req.errors[0], undefined, 'no error');
t.deepEqual(clean.bbox, expected_bbox, 'clean set correctly (' + bbox + ')'); t.deepEqual(req.clean.bbox, expected_bbox, 'clean set correctly (' + bbox + ')');
}); });
}); });
t.end(); t.end();
@ -248,20 +267,23 @@ module.exports.tests.sanitize_bbox = function(test, common) {
module.exports.tests.sanitize_size = function(test, common) { module.exports.tests.sanitize_size = function(test, common) {
test('invalid size value', function(t) { test('invalid size value', function(t) {
sanitize({ size: 'a', text: 'test', lat: 0, lon: 0 }, function( err, clean ){ var req = { query: { size: 'a', text: 'test', lat: 0, lon: 0 } };
t.equal(clean.size, 10, 'default size set'); sanitize(req, function(){
t.equal(req.clean.size, 10, 'default size set');
t.end(); t.end();
}); });
}); });
test('below min size value', function(t) { test('below min size value', function(t) {
sanitize({ size: -100, text: 'test', lat: 0, lon: 0 }, function( err, clean ){ var req = { query: { size: -100, text: 'test', lat: 0, lon: 0 } };
t.equal(clean.size, 1, 'min size set'); sanitize(req, function(){
t.equal(req.clean.size, 1, 'min size set');
t.end(); t.end();
}); });
}); });
test('above max size value', function(t) { test('above max size value', function(t) {
sanitize({ size: 9999, text: 'test', lat: 0, lon: 0 }, function( err, clean ){ var req = { query: { size: 9999, text: 'test', lat: 0, lon: 0 } };
t.equal(clean.size, 40, 'max size set'); sanitize(req, function(){
t.equal(req.clean.size, 40, 'max size set');
t.end(); t.end();
}); });
}); });
@ -271,8 +293,9 @@ module.exports.tests.sanitize_private = function(test, common) {
var invalid_values = [null, -1, 123, NaN, 'abc']; var invalid_values = [null, -1, 123, NaN, 'abc'];
invalid_values.forEach(function(value) { invalid_values.forEach(function(value) {
test('invalid private param ' + value, function(t) { test('invalid private param ' + value, function(t) {
sanitize({ text: 'test', lat: 0, lon: 0, 'private': value }, function( err, clean ){ var req = { query: { text: 'test', lat: 0, lon: 0, 'private': value } };
t.equal(clean.private, false, 'default private set (to false)'); sanitize(req, function(){
t.equal(req.clean.private, false, 'default private set (to false)');
t.end(); t.end();
}); });
}); });
@ -281,8 +304,9 @@ module.exports.tests.sanitize_private = function(test, common) {
var valid_values = ['true', true, 1, '1']; var valid_values = ['true', true, 1, '1'];
valid_values.forEach(function(value) { valid_values.forEach(function(value) {
test('valid private ' + value, function(t) { test('valid private ' + value, function(t) {
sanitize({ text: 'test', 'private': value}, function( err, clean ){ var req = { query: { text: 'test', 'private': value } };
t.equal(clean.private, true, 'private set to true'); sanitize(req, function(){
t.equal(req.clean.private, true, 'private set to true');
t.end(); t.end();
}); });
}); });
@ -291,16 +315,18 @@ module.exports.tests.sanitize_private = function(test, common) {
var valid_false_values = ['false', false, 0, '0']; var valid_false_values = ['false', false, 0, '0'];
valid_false_values.forEach(function(value) { valid_false_values.forEach(function(value) {
test('test setting false explicitly ' + value, function(t) { test('test setting false explicitly ' + value, function(t) {
sanitize({ text: 'test', 'private': value }, function( err, clean ){ var req = { query: { text: 'test', 'private': value } };
t.equal(clean.private, false, 'private set to false'); sanitize(req, function(){
t.equal(req.clean.private, false, 'private set to false');
t.end(); t.end();
}); });
}); });
}); });
test('test default behavior', function(t) { test('test default behavior', function(t) {
sanitize({ text: 'test' }, function( err, clean ){ var req = { query: { text: 'test' } };
t.equal(clean.private, false, 'private set to false'); sanitize(req, function(){
t.equal(req.clean.private, false, 'private set to false');
t.end(); t.end();
}); });
}); });
@ -308,31 +334,19 @@ module.exports.tests.sanitize_private = function(test, common) {
module.exports.tests.invalid_params = function(test, common) { module.exports.tests.invalid_params = function(test, common) {
test('invalid text params', function(t) { test('invalid text params', function(t) {
sanitize( undefined, function( err, clean ){ var req = { query: {} };
t.equal(err, defaultError, 'handle invalid params gracefully'); sanitize( req, function(){
t.equal(req.errors[0], defaultError, 'handle invalid params gracefully');
t.end(); t.end();
}); });
}); });
}; };
module.exports.tests.middleware_failure = function(test, common) {
test('middleware failure', function(t) {
var res = { status: function( code ){
t.equal(code, 400, 'status set');
}};
var next = function( message ){
t.equal(message, defaultError);
t.end();
};
middleware( {}, res, next );
});
};
module.exports.tests.middleware_success = function(test, common) { module.exports.tests.middleware_success = function(test, common) {
test('middleware success', function(t) { test('middleware success', function(t) {
var req = { query: { text: 'test' }}; var req = { query: { text: 'test' }};
var next = function( message ){ var next = function( message ){
t.equal(message, undefined, 'no error message set'); t.deepEqual(req.errors, [], 'no error messages set');
t.end(); t.end();
}; };
middleware( req, undefined, next ); middleware( req, undefined, next );

Loading…
Cancel
Save