Browse Source

Merge remote-tracking branch 'origin/master' into private-param-235

* merged master into branch
* removed `details`
* refactored `details` and `private` into a common _flag_bool sanitizer type
* cleaned up `private`
pull/241/head
Diana Shkolnikov 9 years ago
parent
commit
3ecd0435dc
  1. 3
      .gitignore
  2. 3
      controller/status.js
  3. 28
      helper/geojsonify.js
  4. 25
      helper/layers.js
  5. 10
      helper/query_parser.js
  6. 20
      helper/types.js
  7. 30
      middleware/_types.js
  8. 2
      middleware/geocodeJSON.js
  9. 3
      package.json
  10. 101
      public/apiDoc.md
  11. 15
      query/layers.js
  12. 8
      query/sources.js
  13. 9
      routes/v1.js
  14. 35
      sanitiser/_categories.js
  15. 31
      sanitiser/_details.js
  16. 50
      sanitiser/_flag_bool.js
  17. 11
      sanitiser/_geo_common.js
  18. 37
      sanitiser/_geo_reverse.js
  19. 37
      sanitiser/_geo_search.js
  20. 129
      sanitiser/_id.js
  21. 52
      sanitiser/_layers.js
  22. 31
      sanitiser/_private.js
  23. 17
      sanitiser/_sanitize.js
  24. 55
      sanitiser/_size.js
  25. 59
      sanitiser/_source.js
  26. 81
      sanitiser/_targets.js
  27. 41
      sanitiser/_text.js
  28. 9
      sanitiser/_truthy.js
  29. 9
      sanitiser/place.js
  30. 23
      sanitiser/reverse.js
  31. 26
      sanitiser/sanitizeAll.js
  32. 17
      sanitiser/search.js
  33. 57
      test/unit/controller/place.js
  34. 79
      test/unit/controller/search.js
  35. 95
      test/unit/helper/geojsonify.js
  36. 7
      test/unit/helper/query_parser.js
  37. 32
      test/unit/helper/types.js
  38. 3
      test/unit/query/search.js
  39. 7
      test/unit/run.js
  40. 53
      test/unit/sanitiser/_details.js
  41. 64
      test/unit/sanitiser/_flag_bool.js
  42. 120
      test/unit/sanitiser/_layers.js
  43. 51
      test/unit/sanitiser/_source.js
  44. 132
      test/unit/sanitiser/_sources.js
  45. 5
      test/unit/sanitiser/_text.js
  46. 36
      test/unit/sanitiser/place.js
  47. 129
      test/unit/sanitiser/reverse.js
  48. 108
      test/unit/sanitiser/search.js

3
.gitignore vendored

@ -2,4 +2,5 @@ node_modules
coverage
.idea
*.log
reports
reports
pids

3
controller/status.js

@ -0,0 +1,3 @@
module.exports = function controller( req, res, next) {
res.send('status: ok');
};

28
helper/geojsonify.js

@ -5,7 +5,7 @@ var outputGenerator = require('./outputGenerator');
var logger = require('pelias-logger').get('api');
// Properties to be copied when details=true
// Properties to be copied
var DETAILS_PROPS = [
'housenumber',
'street',
@ -79,14 +79,11 @@ function lookupLayer(src) {
return src._type;
}
function geojsonifyPlaces( docs, params ){
var details = params ? params.details : {};
details = details === true || details === 1;
function geojsonifyPlaces( docs ){
// flatten & expand data for geojson conversion
var geodata = docs
.map(geojsonifyPlace.bind(null, details))
.map(geojsonifyPlace)
.filter( function( doc ){
return !!doc;
});
@ -100,7 +97,7 @@ function geojsonifyPlaces( docs, params ){
return geojson;
}
function geojsonifyPlace(details, place) {
function geojsonifyPlace(place) {
// something went very wrong
if( !place || !place.hasOwnProperty( 'center_point' ) ) {
@ -110,7 +107,7 @@ function geojsonifyPlace(details, place) {
var geocoding = {};
addMetaData(place, geocoding);
addDetails(details, place, geocoding);
addDetails(place, geocoding);
addLabel(place, geocoding);
var output = {};
@ -125,20 +122,17 @@ function geojsonifyPlace(details, place) {
}
/**
* Add details properties when needed
* Add details properties
*
* @param {boolean} details
* @param {object} src
* @param {object} dst
*/
function addDetails(details, src, dst) {
if (details) {
// map name
if( !src.name || !src.name.default ) { return warning(src); }
dst.name = src.name.default;
function addDetails(src, dst) {
// map name
if( !src.name || !src.name.default ) { return warning(src); }
dst.name = src.name.default;
copyProperties(src, DETAILS_PROPS, dst);
}
copyProperties(src, DETAILS_PROPS, dst);
}
/**

25
helper/layers.js

@ -1,25 +0,0 @@
module.exports = function(alias_layers) {
// make a copy of the array so, you are not modifying original ref
var layers = alias_layers.slice(0);
// expand aliases
var expand_aliases = function(alias, layers, layer_indeces) {
var alias_index = layers.indexOf(alias);
if (alias_index !== -1 ) {
layers.splice(alias_index, 1);
layers = layers.concat(layer_indeces);
}
return layers;
};
layers = expand_aliases('poi', layers, ['geoname','osmnode','osmway']);
layers = expand_aliases('admin', layers, ['admin0','admin1','admin2','neighborhood','locality','local_admin']);
layers = expand_aliases('address', layers, ['osmaddress','openaddresses']);
// de-dupe
layers = layers.filter(function(item, pos) {
return layers.indexOf(item) === pos;
});
return layers;
};

10
helper/query_parser.js

@ -1,26 +1,20 @@
var parser = require('addressit');
var extend = require('extend');
var get_layers_helper = require('../helper/layers');
var layers_map = require('../query/layers');
var delim = ',';
module.exports = {};
module.exports.get_layers = function get_layers(query) {
var tokenized = query.split(/[ ,]+/);
var hasNumber = /\d/.test(query);
if (query.length <= 3 ) {
// no address parsing required
return get_layers_helper(['admin']);
return layers_map.coarse;
}
};
module.exports.get_parsed_address = function get_parsed_address(query) {
var tokenized = query.split(/[ ,]+/);
var hasNumber = /\d/.test(query);
var getAdminPartsBySplittingOnDelim = function(query) {
// naive approach - for admin matching during query time
// split 'flatiron, new york, ny' into 'flatiron' and 'new york, ny'

20
helper/types.js

@ -9,23 +9,29 @@ var intersection = function intersection(set1, set2) {
});
};
/**
* Combine all types and determine the unique subset
*
* @param {Array} clean_types
* @returns {Array}
*/
module.exports = function calculate_types(clean_types) {
if (!clean_types) {
return undefined;
if (!clean_types || !(clean_types.from_layers || clean_types.from_sources || clean_types.from_address_parser)) {
throw new Error('clean_types should not be null or undefined');
}
/* the layers and source parameters are cumulative:
* perform a set insersection of their specified types
* perform a set intersection of their specified types
*/
if (clean_types.from_layers || clean_types.from_source) {
if (clean_types.from_layers || clean_types.from_sources) {
var types = valid_types;
if (clean_types.from_layers) {
types = intersection(types, clean_types.from_layers);
}
if (clean_types.from_source) {
types = intersection(types, clean_types.from_source);
if (clean_types.from_sources) {
types = intersection(types, clean_types.from_sources);
}
return types;
@ -38,4 +44,6 @@ module.exports = function calculate_types(clean_types) {
if (clean_types.from_address_parser) {
return clean_types.from_address_parser;
}
throw new Error('no types specified');
};

30
middleware/_types.js

@ -8,17 +8,27 @@ var types_helper = require( '../helper/types' );
* message instead of searching at all.
*/
function middleware(req, res, next) {
var types = types_helper(req.clean.types);
req.clean = req.clean || {};
if (types !== undefined && types.length !== undefined) {
if (types.length === 0) {
var err = 'You have specified both the `source` and `layers` ' +
'parameters in a combination that will return no results.';
res.status(400); // 400 Bad Request
return next(err);
} else {
req.clean.type = types;
}
if (req.clean.hasOwnProperty('types') === false) {
return next();
}
try {
var types = types_helper(req.clean.types);
if ((types instanceof Array) && types.length === 0) {
var err = 'You have specified both the `sources` and `layers` ' +
'parameters in a combination that will return no results.';
res.status(400); // 400 Bad Request
return next(err);
}
req.clean.type = types;
}
catch (err) {
// this means there were no types specified
delete req.clean.types;
}
next();

2
middleware/geocodeJSON.js

@ -49,7 +49,7 @@ function convertToGeocodeJSON(peliasConfig, req, next) {
req.results.geojson.geocoding.timestamp = new Date().getTime();
// convert docs to geojson and merge with geocoding block
extend(req.results.geojson, geojsonify(req.results.data, req.clean));
extend(req.results.geojson, geojsonify(req.results.data));
next();
}

3
package.json

@ -35,6 +35,7 @@
"dependencies": {
"addressit": "1.3.0",
"async": "^0.9.0",
"check-types": "^3.3.1",
"cluster2": "git://github.com/missinglink/cluster2.git#node_zero_twelve",
"express": "^4.8.8",
"express-http-proxy": "^0.6.0",
@ -43,7 +44,7 @@
"geojson-extent": "^0.3.1",
"geolib": "^2.0.18",
"geopipes-elasticsearch-backend": "^0.2.0",
"is-object": "^1.0.1",
"lodash": "^3.10.1",
"markdown": "0.5.0",
"microtime": "1.4.0",
"morgan": "1.5.2",

101
public/apiDoc.md

@ -1,100 +1 @@
## /search
The full text search endpoint that matches the name of a place to points on the planet.
#### Required Parameters
* `text`: the string to search for (eg `new york city` or `london`)
#### Optional Parameters
* `lat`, `lon`: the latitude/longitude coordinates to bias search results towards (may increase relevancy)
* `size` (default: `10`): the number of results to return
* `layers` (default: `poi,admin,address`): the comma-separated names of datasets you wish to query. Valid values are:
* aliases for multiple datasets like `poi`, `admin` or `address`
* `poi` expands internally to `geoname`, `osmnode`, `osmway`
* `admin` expands to `admin0`, `admin1`, `admin2`, `neighborhood`, `locality`, `local_admin`
* `address` expands to `osmaddress`, `openaddresses`
* the name of one particular dataset, like `geoname` or `osmaddress`
* `bbox`: the bounding box from which you want all your results to come
* can be one of the following comma separated string values
* "southwest_lng,southwest_lat,northeast_lng,northeast_lat" `L.latLngBounds(southwestLatLng, northeastLatLng).toBBoxString()`
* bottom left lon, bottom left lat, top right lon, top right lat
* left, bottom, right, top
* min longitude, min latitude, max longitude, max latitude
* `details` (default: `true`): indicates if results should contain detailed. Valid values:
* `false`: results will only contain `id`, `layer`, and `text` properties
* `true`: all available properties will be included in results
## /search/coarse
Like the `/search` endpoint, but performs a "coarse" search, meaning that it only searches administrative regions
(countries, states, counties, neighborhoods, etc.).
#### Required Parameters
Same as `/search`.
#### Optional Parameters
Same as `/search`.
## /suggest
The autocompletion endpoint that offers very fast response times; ideal for completing partial user input. Mixes
results from around the provided lat/lon coordinates and also from precision level 1 and 3.
#### Required Parameters
* `text`: query string
* `lat`, `lon`: The latitude/longitude coordinates to bias results towards.
* `lat`/`lon` are currently **required** because of this [open issue](https://github.com/elasticsearch/elasticsearch/issues/6444)
#### Optional Parameters
* `size` (default: `10`): number of results requested
* `layers` (default: `poi,admin,address`): datasets you wish to query
* `details` (default: `true`)
## /suggest/coarse
Only queries the admin layers.
#### Required Parameters
Same as `/suggest`.
#### Optional Parameters
Same as `/suggest`.
## /suggest/nearby
Works as autocomplete for only the places located near a latitude/longitude; this endpoint is the same as `/suggest`
but the results are all from within 50 kilometers of the specified point. Unlike `/suggest`, `/suggest/nearby` does
not mix results from different precision levels (500km, 1000km etc from lat/lon).
#### Required Parameters
Same as `/suggest`.
#### Optional Parameters
Same as `/suggest`.
## /reverse
The reverse geocoding endpoint; matches a point on the planet to the name of that place.
#### Required Parameters
* `lat`, `lon`: The coordinates of the point.
#### Optional Parameters
* `layers` (default: `poi,admin,address`)
* `details` (default: `true`)
## /place
The endpoint for retrieving one or more places with specific ids. These correspond
directly with Elasticsearch documents.
#### Required Parameters
* one of `id` or `ids`
* `id`:
* unique id of the places to be retrieved
* should be in the form of type:id, for example: `geoname:4163334`
* `ids`:
* if multiple places are to be fetched in bulk, an array of ids
## DETAILED DOCUMENTATION COMING SOON!

15
query/layers.js

@ -0,0 +1,15 @@
/*
* Mapping from data layers to type values
*/
module.exports = {
'venue': ['geoname','osmnode','osmway'],
'address': ['osmaddress','openaddresses'],
'country': ['admin0'],
'region': ['admin1'],
'county': ['admin2'],
'locality': ['locality'],
'localadmin': ['local_admin'],
'neighbourhood': ['neighborhood'],
'coarse': ['admin0','admin1','admin2','neighborhood','locality','local_admin'],
};

8
query/sources.js

@ -3,8 +3,12 @@
*/
module.exports = {
'geonames': ['geoname'],
'gn' : ['geoname'],
'geonames' : ['geoname'],
'oa' : ['openaddresses'],
'openaddresses' : ['openaddresses'],
'quattroshapes': ['admin0', 'admin1', 'admin2', 'neighborhood', 'locality', 'local_admin'],
'qs' : ['admin0', 'admin1', 'admin2', 'neighborhood', 'locality', 'local_admin'],
'quattroshapes' : ['admin0', 'admin1', 'admin2', 'neighborhood', 'locality', 'local_admin'],
'osm' : ['osmaddress', 'osmnode', 'osmway'],
'openstreetmap' : ['osmaddress', 'osmnode', 'osmway']
};

9
routes/v1.js

@ -19,7 +19,8 @@ var middleware = {
var controllers = {
mdToHTML: require('../controller/markdownToHtml'),
place: require('../controller/place'),
search: require('../controller/search')
search: require('../controller/search'),
status: require('../controller/status')
};
/** ----------------------- controllers ----------------------- **/
@ -69,6 +70,7 @@ function addRoutes(app, peliasConfig) {
]),
reverse: createRouter([
sanitisers.reverse.middleware,
middleware.types,
controllers.search(undefined, reverseQuery),
// TODO: add confidence scores
postProc.distances(),
@ -82,6 +84,9 @@ function addRoutes(app, peliasConfig) {
postProc.renamePlacenames(),
postProc.geocodeJSON(peliasConfig),
postProc.sendJSON
]),
status: createRouter([
controllers.status
])
};
@ -96,6 +101,8 @@ function addRoutes(app, peliasConfig) {
app.get ( base + 'search', routers.search );
app.post( base + 'search', routers.search );
app.get ( base + 'reverse', routers.reverse );
app.get ( '/status', routers.status );
}
/**

35
sanitiser/_categories.js

@ -1,36 +1,33 @@
var isObject = require('is-object');
var check = require('check-types');
// validate inputs, convert types and apply defaults
function sanitize( req ){
function sanitize( raw, clean ){
var clean = req.clean || {};
var params= req.query;
// ensure the input params are a valid object
if( !isObject( params ) ){
params = {};
}
// error & warning messages
var messages = { errors: [], warnings: [] };
// default case (no categories specified in GET params)
if('string' !== typeof params.categories || !params.categories.length){
clean.categories = [];
}
else {
// parse GET params
clean.categories = params.categories.split(',')
clean.categories = [];
// if categories string has been set
if( check.unemptyString( raw.categories ) ){
// map input categories to valid format
clean.categories = raw.categories.split(',')
.map(function (cat) {
return cat.toLowerCase().trim(); // lowercase inputs
})
.filter( function( cat ) {
return ( cat.length > 0 );
});
}
// pass validated params to next middleware
req.clean = clean;
if( !clean.categories.length ){
messages.warnings.push( 'invalid \'categories\': no valid category strings found');
}
}
return { 'error': false };
return messages;
}

31
sanitiser/_details.js

@ -1,31 +0,0 @@
var isObject = require('is-object');
var isTruthy = require('./_truthy');
// validate inputs, convert types and apply defaults
function sanitize( req, default_value ){
req.clean = req.clean || {};
var params= req.query;
if (default_value === undefined) {
default_value = true;
}
default_value = !!default_value;
// ensure the input params are a valid object
if( !isObject( params ) ){
params = {};
}
if (params.details === undefined) {
req.clean.details = default_value;
} else {
req.clean.details = isTruthy(params.details);
}
return {'error':false};
}
module.exports = sanitize;

50
sanitiser/_flag_bool.js

@ -0,0 +1,50 @@
var _ = require('lodash');
/**
* Returns sanitizer function for boolean flag parameters
*
* @param {string} paramName name of parameter being sanitized
* @param {boolean} defaultValue value to set variable to if none specified
* @returns {Function}
*/
function setup( paramName, defaultValue ) {
return function( raw, clean ){
return sanitize( raw, clean, {
paramName: paramName,
defaultValue: defaultValue
});
};
}
/**
* Validate inputs, convert types and apply defaults
*
* @param {object} raw
* @param {object} clean
* @param {object} opts
* @returns {{errors: Array, warnings: Array}}
*/
function sanitize( raw, clean, opts ){
// error & warning messages`1
var messages = { errors: [], warnings: [] };
if( !_.isUndefined( raw[opts.paramName] ) ){
clean[opts.paramName] = isTruthy( raw[opts.paramName] );
}
else {
clean[opts.paramName] = opts.defaultValue;
}
return messages;
}
/**
* Determine if param value is "truthy"
* @param {*} val
* @returns {boolean}
*/
function isTruthy(val) {
return _.contains( ['true', '1', 1, true], val );
}
module.exports = setup;

11
sanitiser/_geo_common.js

@ -1,7 +1,8 @@
/**
* helper sanitiser methods for geo parameters
*/
var util = require( 'util' );
var util = require('util'),
check = require('check-types');
/**
* Parse and validate bbox parameter
@ -9,15 +10,15 @@ var util = require( 'util' );
* bbox = left, bottom, right, top
* bbox = min Longitude, min Latitude, max Longitude, max Latitude
*
* @param {object} raw
* @param {object} clean
* @param {string} param
*/
function sanitize_bbox( clean, param ) {
if( !param ) {
function sanitize_bbox( raw, clean ) {
if( !check.unemptyString( raw.bbox ) ) {
return;
}
var bboxArr = param.split( ',' );
var bboxArr = raw.bbox.split( ',' );
if( Array.isArray( bboxArr ) && bboxArr.length === 4 ) {
var bbox = bboxArr.map(parseFloat);

37
sanitiser/_geo_reverse.js

@ -1,39 +1,26 @@
var isObject = require('is-object');
var geo_common = require ('./_geo_common');
var LAT_LON_IS_REQUIRED = true,
CIRCLE_IS_REQUIRED = false,
CIRCLE_MUST_BE_COMPLETE = false;
// validate inputs, convert types and apply defaults
module.exports = function sanitize( req ){
var clean = req.clean || {};
var params = req.query;
var latlon_is_required = true;
var circle_is_required = false;
var circle_must_be_complete = false;
module.exports = function sanitize( raw, clean ){
// ensure the input params are a valid object
if( !isObject( params ) ){
params = {};
}
if( !isObject( params.point ) ){
params.point = {};
}
// error & warning messages
var messages = { errors: [], warnings: [] };
try {
geo_common.sanitize_coord( 'lat', clean, params['point.lat'], latlon_is_required );
geo_common.sanitize_coord( 'lon', clean, params['point.lon'], latlon_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 );
// boundary.circle.* is not mandatory, and only specifying radius is fine,
// as point.lat/lon will be used to fill those values by default
geo_common.sanitize_boundary_circle( clean, params, circle_is_required, circle_must_be_complete);
geo_common.sanitize_boundary_circle( clean, raw, CIRCLE_IS_REQUIRED, CIRCLE_MUST_BE_COMPLETE);
}
catch (err) {
return {
'error': true,
'message': err.message
};
messages.errors.push( err.message );
}
req.clean = clean;
return { 'error': false };
return messages;
};

37
sanitiser/_geo_search.js

@ -1,38 +1,21 @@
var isObject = require('is-object');
var geo_common = require ('./_geo_common');
var LAT_LON_IS_REQUIRED = false;
// validate inputs, convert types and apply defaults
module.exports = function sanitize( req ){
var clean = req.clean || {};
var params = req.query;
var latlon_is_required = false;
// ensure the input params are a valid object
if( !isObject( params ) ){
params = {};
}
module.exports = function sanitize( raw, clean ){
if( !isObject( params.focus ) ){
params.focus = {};
}
if( !isObject( params.focus.point ) ){
params.focus.point = {};
}
// error & warning messages
var messages = { errors: [], warnings: [] };
try {
geo_common.sanitize_coord( 'lat', clean, params['focus.point.lat'], latlon_is_required );
geo_common.sanitize_coord( 'lon', clean, params['focus.point.lon'], latlon_is_required );
geo_common.sanitize_bbox(clean, params.bbox);
geo_common.sanitize_coord( 'lat', clean, raw['focus.point.lat'], LAT_LON_IS_REQUIRED );
geo_common.sanitize_coord( 'lon', clean, raw['focus.point.lon'], LAT_LON_IS_REQUIRED );
geo_common.sanitize_bbox(raw, clean);
}
catch (err) {
return {
'error': true,
'message': err.message
};
messages.errors.push( err.message );
}
req.clean = clean;
return { 'error': false };
return messages;
};

129
sanitiser/_id.js

@ -1,72 +1,79 @@
var isObject = require('is-object');
var check = require('check-types'),
types = require('../query/types');
var ID_DELIM = ':';
// validate inputs, convert types and apply defaults
// id generally looks like 'geoname:4163334' (type:id)
// so, both type and id are required fields.
function sanitize( req ){
req.clean = req.clean || {};
var params = req.query;
var types = require('../query/types');
var delim = ':';
// ensure params is a valid object
if( !isObject( params ) ){
params = {};
}
var errormessage = function(fieldname, message) {
return {
'error': true,
'message': message || ('invalid param \''+ fieldname + '\': text length, must be >0')
};
};
if(('string' === typeof params.id && !params.id.length) || params.id === undefined){
return errormessage('id');
}
if( params && params.id && params.id.length ){
req.clean.ids = [];
params.ids = Array.isArray(params.id) ? params.id : [params.id];
// de-dupe
params.ids = params.ids.filter(function(item, pos) {
return params.ids.indexOf(item) === pos;
});
function errorMessage(fieldname, message) {
return message || 'invalid param \''+ fieldname + '\': text length, must be >0';
}
function sanitize( raw, clean ){
for (var i=0; i<params.ids.length; i++) {
var thisparam = params.ids[i];
// basic format/ presence of ':'
if(thisparam.indexOf(delim) === -1) {
return errormessage(null, 'invalid: must be of the format type:id for ex: \'geoname:4163334\'');
}
var param_index = thisparam.indexOf(delim);
var type = thisparam.substring(0, param_index );
var id = thisparam.substring(param_index + 1);
// id text
if('string' !== typeof id || !id.length){
return errormessage(thisparam);
}
// type text
if('string' !== typeof type || !type.length){
return errormessage(thisparam);
}
// type text must be one of the types
if(types.indexOf(type) === -1){
return errormessage('type', type + ' is invalid. It must be one of these values - [' + types.join(', ') + ']');
}
req.clean.ids.push({
id: id,
type: type
});
// error & warning messages
var messages = { errors: [], warnings: [] };
// 'raw.id' can be an array!?
var rawIds = check.array( raw.id ) ? raw.id : [ raw.id ];
// de-dupe ids
rawIds = rawIds.filter(function(item, pos) {
return rawIds.indexOf( item ) === pos;
});
// ensure all elements are valid non-empty strings
rawIds = rawIds.filter( function( uc ){
if( !check.unemptyString( uc ) ){
messages.errors.push( errorMessage('id') );
return false;
}
}
return true;
});
// init 'clean.ids'
clean.ids = [];
// cycle through raw ids and set those which are valid
rawIds.forEach( function( rawId ){
var param_index = rawId.indexOf(ID_DELIM);
var type = rawId.substring(0, param_index );
var id = rawId.substring(param_index + 1);
// basic format/ presence of ':'
if(param_index === -1) {
messages.errors.push(
errorMessage(null, 'invalid: must be of the format type:id for ex: \'geoname:4163334\'')
);
}
// id text
if( !check.unemptyString( id ) ){
messages.errors.push( errorMessage( rawId ) );
}
// type text
if( !check.unemptyString( type ) ){
messages.errors.push( errorMessage( rawId ) );
}
// type text must be one of the types
if( types.indexOf( type ) === -1 ){
messages.errors.push(
errorMessage('type', type + ' is invalid. It must be one of these values - [' + types.join(', ') + ']')
);
}
// add valid id to 'clean.ids' array
clean.ids.push({
id: id,
type: type
});
});
return { 'error': false };
return messages;
}
// export function

52
sanitiser/_layers.js

@ -1,52 +0,0 @@
var isObject = require('is-object'),
types = require('../query/types'),
get_layers = require('../helper/layers');
// validate inputs, convert types and apply defaults
function sanitize( req ){
var clean = req.clean || {};
var params= req.query;
clean.types = clean.types || {};
// ensure the input params are a valid object
if( !isObject( params ) ){
params = {};
}
// default case (no layers specified in GET params)
// don't even set the from_layers key in this case
if('string' !== typeof params.layers || !params.layers.length){
return { 'error': false };
}
// decide which layers can be queried
var alias_layers = ['poi', 'admin', 'address'];
var alias_types = types.concat(alias_layers);
// parse GET params
var layers = params.layers.split(',').map( function( layer ){
return layer.toLowerCase(); // lowercase inputs
});
// validate layer names
for( var x=0; x<layers.length; x++ ){
if( -1 === alias_types.indexOf( layers[x] ) ){
return {
'error': true,
'message': 'invalid param \'layers\': must be one or more of ' + alias_types.join(',')
};
}
}
// pass validated params to next middleware
clean.types.from_layers = get_layers(layers);
req.clean = clean;
return { 'error': false };
}
// export function
module.exports = sanitize;

31
sanitiser/_private.js

@ -1,31 +0,0 @@
var isObject = require('is-object');
var isTruthy = require('./_truthy');
// validate inputs, convert types and apply defaults
function sanitize( req, default_value ){
req.clean = req.clean || {};
var params= req.query;
if (default_value === undefined) {
default_value = true;
}
default_value = !!default_value;
// ensure the input params are a valid object
if( !isObject( params ) ){
params = {};
}
if (params.private === undefined) {
req.clean.private = default_value;
} else {
req.clean.private = isTruthy(params.private);
}
return {'error':false};
}
module.exports = sanitize;

17
sanitiser/_sanitize.js

@ -1,17 +0,0 @@
function sanitize( req, sanitiser, cb ){
req.clean = req.clean || {};
for (var s in sanitiser) {
var sanity = sanitiser[s](req);
if (sanity.error) {
return cb(sanity.message);
}
}
return cb( undefined, req.clean );
}
// export function
module.exports = sanitize;

55
sanitiser/_size.js

@ -1,30 +1,47 @@
var isObject = require('is-object');
var MIN_SIZE = 1,
MAX_SIZE = 40,
DEFAULT_SIZE = 10;
// validate inputs, convert types and apply defaults
function sanitize( req, default_size){
var clean = req.clean || {};
var params= req.query;
function sanitize( raw, clean ){
default_size = default_size || 10;
// error & warning messages
var messages = { errors: [], warnings: [] };
// ensure the input params are a valid object
if( !isObject( params ) ){
params = {};
}
// coercions
var _size = parseInt( raw.size, 10 );
// invalid numeric input
// @todo: this can be removed now as queries have default sizes?
if( isNaN( _size ) ){
// total results
var size = parseInt( params.size, 10 );
if( !isNaN( size ) ){
clean.size = Math.min( Math.max( size, 1 ), 40 ); // max
} else {
clean.size = default_size; // default
// set the default size
messages.warnings.push('invalid integer \'size\', using DEFAULT_SIZE');
clean.size = DEFAULT_SIZE;
}
// valid numeric input
else {
req.clean = clean;
return {'error':false};
// ensure size falls within defined range
if( _size > MAX_SIZE ){
// set the max size
messages.warnings.push('out-of-range integer \'size\', using MAX_SIZE');
clean.size = MAX_SIZE;
}
else if( _size < MIN_SIZE ){
// set the min size
messages.warnings.push('out-of-range integer \'size\', using MIN_SIZE');
clean.size = MIN_SIZE;
}
else {
// set the input size
clean.size = _size;
}
}
return messages;
}
// export function

59
sanitiser/_source.js

@ -1,44 +1,45 @@
var isObject = require('is-object');
var sources_map = require( '../query/sources' );
var all_sources = Object.keys(sources_map);
function sanitize( req ) {
req.clean = req.clean || {};
var params = req.query;
var check = require('check-types'),
sources_map = require( '../query/sources' );
req.clean.types = req.clean.types || {};
var ALL_SOURCES = Object.keys(sources_map),
ALL_SOURCES_JOINED = ALL_SOURCES.join(',');
// ensure the input params are a valid object
if( !isObject( params ) ){
params = {};
}
function sanitize( raw, clean ) {
// error & warning messages
var messages = { errors: [], warnings: [] };
// init clean.types (if not already init)
clean.types = clean.types || {};
// default case (no layers specified in GET params)
// don't even set the from_layers key in this case
if('string' !== typeof params.source || !params.source.length){
return { error: false };
}
if( check.unemptyString( raw.source ) ){
var sources = params.source.split(',');
var sources = raw.source.split(',');
var invalid_sources = sources.filter(function(source) {
return all_sources.indexOf(source) === -1;
});
var invalid_sources = sources.filter(function(source) {
return ALL_SOURCES.indexOf(source) === -1;
});
if (invalid_sources.length > 0) {
return {
error: true,
msg: '`' + invalid_sources[0] + '` is an invalid source parameter. Valid options: ' + all_sources.join(', ')
};
}
if( invalid_sources.length > 0 ){
invalid_sources.forEach( function( invalid ){
messages.errors.push('\'' + invalid + '\' is an invalid source parameter. Valid options: ' + ALL_SOURCES_JOINED);
});
}
else {
var types = sources.reduce(function(acc, source) {
return acc.concat(sources_map[source]);
}, []);
var types = sources.reduce(function(acc, source) {
return acc.concat(sources_map[source]);
}, []);
clean.types.from_source = types;
}
req.clean.types.from_source = types;
}
return { error: false };
return messages;
}
module.exports = sanitize;

81
sanitiser/_targets.js

@ -0,0 +1,81 @@
var _ = require('lodash'),
check = require('check-types');
function setup( paramName, targetMap ) {
return function( raw, clean ){
return sanitize( raw, clean, {
paramName: paramName,
targetMap: targetMap,
targetMapKeysString: Object.keys(targetMap).join(',')
});
};
}
function sanitize( raw, clean, opts ) {
// error & warning messages
var messages = { errors: [], warnings: [] };
// init clean.types
clean.types = clean.types || {};
// the string of targets (comma delimeted)
var targetsString = raw[opts.paramName];
// trim whitespace
if( check.unemptyString( targetsString ) ){
targetsString = targetsString.trim();
// param must be a valid non-empty string
if( !check.unemptyString( targetsString ) ){
messages.errors.push(
opts.paramName + ' parameter cannot be an empty string. Valid options: ' + opts.targetMapKeysString
);
}
else {
// split string in to array and lowercase each target string
var targets = targetsString.split(',').map( function( target ){
return target.toLowerCase(); // lowercase inputs
});
// emit an error for each target *not* present in the targetMap
targets.filter( function( target ){
return !opts.targetMap.hasOwnProperty(target);
}).forEach( function( target ){
messages.errors.push(
'\'' + target + '\' is an invalid ' + opts.paramName + ' parameter. Valid options: ' + opts.targetMapKeysString
);
});
// only set types value when no error occured
if( !messages.errors.length ){
// store the values under a new key as 'clean.types.from_*'
var typesKey = 'from_' + opts.paramName;
// ?
clean.types[typesKey] = targets.reduce(function(acc, target) {
return acc.concat(opts.targetMap[target]);
}, []);
// dedupe in case aliases expanded to common things or user typed in duplicates
clean.types[typesKey] = _.unique(clean.types[typesKey]);
}
}
}
// string is empty
else if( check.string( targetsString ) ){
messages.errors.push(
opts.paramName + ' parameter cannot be an empty string. Valid options: ' + opts.targetMapKeysString
);
}
return messages;
}
module.exports = setup;

41
sanitiser/_text.js

@ -1,32 +1,33 @@
var isObject = require('is-object');
var query_parser = require('../helper/query_parser');
var check = require('check-types'),
query_parser = require('../helper/query_parser');
// validate texts, convert types and apply defaults
function sanitize( req ){
req.clean = req.clean || {};
var params= req.query;
function sanitize( raw, clean ){
// ensure the text params are a valid object
if( !isObject( params ) ){
params = {};
}
// error & warning messages
var messages = { errors: [], warnings: [] };
// text text
if('string' !== typeof params.text || !params.text.length){
return {
'error': true,
'message': 'invalid param \'text\': text length, must be >0'
};
// invalid input 'text'
if( !check.unemptyString( raw.text ) ){
messages.errors.push('invalid param \'text\': text length, must be >0');
}
req.clean.text = params.text;
// valid input 'text'
else {
req.clean.parsed_text = query_parser.get_parsed_address(params.text);
// valid text
clean.text = raw.text;
req.clean.types = req.clean.layers || {};
req.clean.types.from_address_parsing = query_parser.get_layers(params.text);
// parse text with query parser
clean.parsed_text = query_parser.get_parsed_address(clean.text);
// try to set layers from query parser results
clean.types = clean.layers || {};
clean.types.from_address_parsing = query_parser.get_layers(clean.text);
}
return { 'error': false };
return messages;
}
// export function

9
sanitiser/_truthy.js

@ -1,9 +0,0 @@
function isTruthy(val) {
if (typeof val === 'string') {
return ['true', '1', 'yes', 'y'].indexOf(val) !== -1;
}
return val === 1 || val === true;
}
module.exports = isTruthy;

9
sanitiser/place.js

@ -1,13 +1,15 @@
var _sanitize = require('../sanitiser/_sanitize'),
var sanitizeAll = require('../sanitiser/sanitizeAll'),
sanitizers = {
id: require('../sanitiser/_id'),
details: require('../sanitiser/_details')
private: require('../sanitiser/_flag_bool')('private', false)
};
var sanitize = function(req, cb) { _sanitize(req, sanitizers, cb); };
var sanitize = function(req, cb) { sanitizeAll(req, sanitizers, cb); };
// export sanitize for testing
module.exports.sanitize = sanitize;
module.exports.sanitiser_list = sanitizers;
// middleware
module.exports.middleware = function( req, res, next ){
@ -16,7 +18,6 @@ module.exports.middleware = function( req, res, next ){
res.status(400); // 400 Bad Request
return next(err);
}
req.clean = clean;
next();
});
};

23
sanitiser/reverse.js

@ -1,20 +1,19 @@
var _sanitize = require('../sanitiser/_sanitize'),
sanitiser = {
latlonzoom: require('../sanitiser/_geo_reverse'),
layers: require('../sanitiser/_layers'),
suorce: require('../sanitiser/_source'),
details: require('../sanitiser/_details'),
var sanitizeAll = require('../sanitiser/sanitizeAll'),
sanitizers = {
layers: require('../sanitiser/_targets')('layers', require('../query/layers')),
sources: require('../sanitiser/_targets')('sources', require('../query/sources')),
size: require('../sanitiser/_size'),
categories: function ( req ) {
var categories = require('../sanitiser/_categories');
return categories(req);
}
private: require('../sanitiser/_flag_bool')('private', false),
geo_reverse: require('../sanitiser/_geo_reverse'),
categories: require('../sanitiser/_categories')
};
var sanitize = function(req, cb) { _sanitize(req, sanitiser, cb); };
var sanitize = function(req, cb) { sanitizeAll(req, sanitizers, cb); };
// export sanitize for testing
module.exports.sanitize = sanitize;
module.exports.sanitiser_list = sanitizers;
// middleware
module.exports.middleware = function( req, res, next ){
@ -23,7 +22,7 @@ module.exports.middleware = function( req, res, next ){
res.status(400); // 400 Bad Request
return next(err);
}
req.clean = clean;
next();
});
};

26
sanitiser/sanitizeAll.js

@ -0,0 +1,26 @@
function sanitize( req, sanitizers, cb ){
// init an object to store clean
// (sanitized) input parameters
req.clean = {};
// source of input parameters
// (in this case from the GET querystring params)
var params = req.query || {};
for (var s in sanitizers) {
var sanity = sanitizers[s]( params, req.clean );
// errors
if( sanity.errors.length ){
return cb( sanity.errors[0] );
}
}
return cb( undefined, req.clean );
}
// export function
module.exports = sanitize;

17
sanitiser/search.js

@ -1,19 +1,20 @@
var _sanitize = require('../sanitiser/_sanitize'),
var sanitizeAll = require('../sanitiser/sanitizeAll'),
sanitizers = {
text: require('../sanitiser/_text'),
size: require('../sanitiser/_size'),
layers: require('../sanitiser/_layers'),
source: require('../sanitiser/_source'),
details: require('../sanitiser/_details'),
private: require('../sanitiser/_private'),
latlonzoom: require('../sanitiser/_geo_search')
layers: require('../sanitiser/_targets')('layers', require( '../query/layers' )),
sources: require('../sanitiser/_targets')('sources', require( '../query/sources' )),
private: require('../sanitiser/_flag_bool')('private', false),
geo_search: require('../sanitiser/_geo_search'),
categories: require('../sanitiser/_categories')
};
var sanitize = function(req, cb) { _sanitize(req, sanitizers, cb); };
var sanitize = function(req, cb) { sanitizeAll(req, sanitizers, cb); };
// export sanitize for testing
module.exports.sanitize = sanitize;
module.exports.sanitiser_list = sanitizers;
// middleware
module.exports.middleware = function( req, res, next ){
@ -22,7 +23,7 @@ module.exports.middleware = function( req, res, next ){
res.status(400); // 400 Bad Request
return next(err);
}
req.clean = clean;
next();
});
};

57
test/unit/controller/place.js

@ -63,63 +63,6 @@ module.exports.tests.functional_success = function(test, common) {
};
controller( { clean: { ids: [ {'id' : 123, 'type': 'a' } ] } }, res, next );
});
var detailed_expectation = [{
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [ -50.5, 100.1 ]
},
properties: {
id: 'myid1',
layer: 'mytype1',
name: 'test name1',
admin0: 'country1',
admin1: 'state1',
admin2: 'city1',
text: 'test name1, city1, state1'
}
}, {
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [ -51.5, 100.2 ]
},
properties: {
id: 'myid2',
layer: 'mytype2',
name: 'test name2',
admin0: 'country2',
admin1: 'state2',
admin2: 'city2',
text: 'test name2, city2, state2'
}
}];
test('functional success (with details)', function(t) {
var backend = mockBackend( 'client/mget/ok/1', function( cmd ){
t.deepEqual(cmd, { body: { docs: [ { _id: 123, _index: 'pelias', _type: 'a' } ] } }, 'correct backend command');
});
var controller = setup( backend );
var res = {
status: function( code ){
t.equal(code, 200, 'status set');
return res;
},
json: function( json ){
t.equal(typeof json, 'object', 'returns json');
t.equal(typeof json.date, 'number', 'date set');
t.equal(json.type, 'FeatureCollection', 'valid geojson');
t.true(Array.isArray(json.features), 'features is array');
t.deepEqual(json.features, detailed_expectation, 'values correctly mapped along with details');
}
};
var next = function next() {
t.equal(arguments.length, 0, 'next was called without error');
t.end();
};
controller( { clean: { ids: [ {'id' : 123, 'type': 'a' } ], details: true } }, res, next );
});
};
// functionally test controller (backend failure)

79
test/unit/controller/search.js

@ -21,7 +21,7 @@ module.exports.tests.functional_success = function(test, common) {
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [ -50.5, 100.1 ]
coordinates: [-50.5, 100.1]
},
properties: {
id: 'myid1',
@ -32,7 +32,7 @@ module.exports.tests.functional_success = function(test, common) {
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [ -51.5, 100.2 ]
coordinates: [-51.5, 100.2]
},
properties: {
id: 'myid2',
@ -41,17 +41,21 @@ module.exports.tests.functional_success = function(test, common) {
}
}];
test('functional success', function(t) {
var backend = mockBackend( 'client/search/ok/1', function( cmd ){
t.deepEqual(cmd, { body: { a: 'b' }, index: 'pelias', searchType: 'dfs_query_then_fetch' }, 'correct backend command');
test('functional success', function (t) {
var backend = mockBackend('client/search/ok/1', function (cmd) {
t.deepEqual(cmd, {
body: {a: 'b'},
index: 'pelias',
searchType: 'dfs_query_then_fetch'
}, 'correct backend command');
});
var controller = setup( backend, mockQuery() );
var controller = setup(backend, mockQuery());
var res = {
status: function( code ){
status: function (code) {
t.equal(code, 200, 'status set');
return res;
},
json: function( json ){
json: function (json) {
t.equal(typeof json, 'object', 'returns json');
t.equal(typeof json.date, 'number', 'date set');
t.equal(json.type, 'FeatureCollection', 'valid geojson');
@ -63,64 +67,7 @@ module.exports.tests.functional_success = function(test, common) {
t.equal(arguments.length, 0, 'next was called without error');
t.end();
};
controller( { clean: { a: 'b' } }, res, next );
});
var detailed_expectation = [{
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [ -50.5, 100.1 ]
},
properties: {
id: 'myid1',
layer: 'mytype1',
name: 'test name1',
admin0: 'country1',
admin1: 'state1',
admin2: 'city1',
text: 'test name1, city1, state1'
}
}, {
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [ -51.5, 100.2 ]
},
properties: {
id: 'myid2',
layer: 'mytype2',
name: 'test name2',
admin0: 'country2',
admin1: 'state2',
admin2: 'city2',
text: 'test name2, city2, state2'
}
}];
test('functional success (with details)', function(t) {
var backend = mockBackend( 'client/search/ok/1', function( cmd ){
t.deepEqual(cmd, { body: { a: 'b', details: true }, index: 'pelias', searchType: 'dfs_query_then_fetch' }, '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.equal(json.type, 'FeatureCollection', 'valid geojson');
t.true(Array.isArray(json.features), 'features is array');
t.deepEqual(json.features, detailed_expectation, 'values with details correctly mapped');
}
};
var next = function next() {
t.equal(arguments.length, 0, 'next was called without error');
t.end();
};
controller( { clean: { a: 'b', details: true } }, res, next );
controller({clean: {a: 'b'}}, res, next);
});
};

95
test/unit/helper/geojsonify.js

@ -6,7 +6,7 @@ module.exports.tests = {};
module.exports.tests.interface = function(test, common) {
test('valid interface .search()', function(t) {
t.equal(typeof geojsonify.search, 'function', 'search is a function');
t.equal(geojsonify.search.length, 2, 'accepts x arguments');
t.equal(geojsonify.search.length, 1, 'accepts x arguments');
t.end();
});
};
@ -28,7 +28,7 @@ module.exports.tests.earth = function(test, common) {
test('earth', function(t) {
t.doesNotThrow(function(){
geojsonify.search( earth, { details: true } );
geojsonify.search( earth );
});
t.end();
});
@ -219,98 +219,11 @@ module.exports.tests.search = function(test, common) {
]
};
var truthy_params = [true, 1];
test('geojsonify.search(doc, true) with details', function(t) {
var json = geojsonify.search( input, { details: true } );
t.deepEqual(json, expected, 'all docs (with details) mapped');
t.end();
});
truthy_params.forEach(function(details) {
test('geojsonify.search(doc, '+ details +') with details', function(t) {
var json = geojsonify.search( input, { details: details } );
t.deepEqual(json, expected, 'all docs (with details) mapped');
t.end();
});
});
var no_details_expected = {
'type': 'FeatureCollection',
'bbox': [ -73.985656, 40.748432, -0.101795, 51.5337144 ],
'features': [
{
'type': 'Feature',
'geometry': {
'type': 'Point',
'coordinates': [
-0.1069716,
51.5337144
]
},
'properties': {
'geocoding': {
'id': 'id1',
'layer': 'type1',
'source': 'type1',
'label': '\'Round Midnight Jazz and Blues Bar, test3, Angel'
}
}
},
{
'type': 'Feature',
'geometry': {
'type': 'Point',
'coordinates': [
-0.101795,
51.517806
]
},
'properties': {
'geocoding': {
'id': 'id2',
'layer': 'type2',
'source': 'type2',
'label': 'Blues Cafe, test3, Smithfield'
}
}
},
{
'type': 'Feature',
'geometry': {
'type': 'Point',
'coordinates': [
-73.985656,
40.748432
]
},
'properties': {
'geocoding': {
'id': '34633854',
'layer': 'venue',
'source': 'osm',
'label': 'Empire State Building, Manhattan, NY'
}
}
}
]
};
test('geojsonify.search(doc) with no details (default)', function(t) {
test('geojsonify.search(doc)', function(t) {
var json = geojsonify.search( input );
t.deepEqual(json, no_details_expected, 'all docs (with no details) mapped');
t.deepEqual(json, expected, 'all docs mapped');
t.end();
});
var falsy_params = [false, undefined, null, 0, -1, 123, 'abc'];
falsy_params.forEach(function(details) {
test('geojsonify.search(doc, '+ details +') with no details', function(t) {
var json = geojsonify.search( input, { details: details } );
t.deepEqual(json, no_details_expected, 'all docs (with no details) mapped');
t.end();
});
});
};
module.exports.all = function (tape, common) {

7
test/unit/helper/query_parser.js

@ -1,6 +1,6 @@
var parser = require('../../../helper/query_parser');
var get_layers = require('../../../helper/layers');
var layers_map = require('../../../query/layers');
module.exports.tests = {};
@ -43,7 +43,7 @@ module.exports.tests.parse_three_chars_or_less = function(test, common) {
var testParse = function(query) {
test('query length < 3 (' + query + ')', function(t) {
var address = parser.get_parsed_address(query);
var target_layer = get_layers(['admin']);
var target_layer = layers_map.coarse;
var layers = parser.get_layers(query);
t.equal(typeof address, 'object', 'valid object');
@ -67,7 +67,7 @@ module.exports.tests.parse_one_or_more_tokens = function(test, common) {
var testParse = function(query, parse_address) {
test('query with one or more tokens (' + query + ')', function(t) {
var address = parser.get_parsed_address(query);
var target_layer = get_layers(['admin', 'poi']);
var target_layer = layers_map.coarse.concat(layers_map.venue);
var layers = parser.get_layers(query);
t.equal(typeof address, 'object', 'valid object');
@ -119,7 +119,6 @@ module.exports.tests.parse_address = function(test, common) {
query_string = query_string.substring(1);
var address = parser.get_parsed_address(query_string);
var non_address_layer = get_layers(['admin', 'poi']);
t.equal(typeof address, 'object', 'valid object for the address ('+query_string+')');

32
test/unit/helper/types.js

@ -1,20 +1,32 @@
var types = require('../../../helper/types');
var valid_types = require( '../../../query/types' );
module.exports.tests = {};
module.exports.tests.no_cleaned_types = function(test, common) {
test('no cleaned types', function(t) {
var actual = types(undefined);
t.equal(actual, undefined, 'all valid types returned for empty input');
t.end();
try {
types();
t.fail('exception should be thrown');
}
catch (err) {
t.equal(err.message, 'clean_types should not be null or undefined', 'no input should result in exception');
}
finally {
t.end();
}
});
test('no cleaned types', function(t) {
var cleaned_types = {};
var actual = types(cleaned_types);
t.equal(actual, undefined, 'all valid types returned for empty input');
t.end();
try {
types({});
t.fail('exception should be thrown');
}
catch (err) {
t.equal(err.message, 'clean_types should not be null or undefined', 'no input should result in exception');
}
finally {
t.end();
}
});
};
@ -58,7 +70,7 @@ module.exports.tests.layers_parameter_and_address_parser = function(test, common
module.exports.tests.source_parameter = function(test, common) {
test('source parameter specified', function(t) {
var cleaned_types = {
from_source: ['openaddresses']
from_sources: ['openaddresses']
};
var actual = types(cleaned_types);
@ -72,7 +84,7 @@ module.exports.tests.source_parameter = function(test, common) {
module.exports.tests.source_and_layers_parameters = function(test, common) {
test('source and layers parameter both specified', function(t) {
var cleaned_types = {
from_source: ['openaddresses'],
from_sources: ['openaddresses'],
from_layers: ['osmaddress', 'openaddresses']
};

3
test/unit/query/search.js

@ -89,7 +89,6 @@ module.exports.tests.query = function(test, common) {
layers: [ 'geoname', 'osmnode', 'osmway', 'admin0', 'admin1', 'admin2', 'neighborhood',
'locality', 'local_admin', 'osmaddress', 'openaddresses' ],
size: 10,
details: true,
parsed_text: parser.get_parsed_address(address),
});
@ -107,7 +106,6 @@ module.exports.tests.query = function(test, common) {
layers: [ 'geoname', 'osmnode', 'osmway', 'admin0', 'admin1', 'admin2', 'neighborhood',
'locality', 'local_admin', 'osmaddress', 'openaddresses' ],
size: 10,
details: true,
parsed_text: parser.get_parsed_address(partial_address),
});
@ -125,7 +123,6 @@ module.exports.tests.query = function(test, common) {
layers: [ 'geoname', 'osmnode', 'osmway', 'admin0', 'admin1', 'admin2', 'neighborhood',
'locality', 'local_admin', 'osmaddress', 'openaddresses' ],
size: 10,
details: true,
parsed_text: parser.get_parsed_address(partial_address),
});

7
test/unit/run.js

@ -8,11 +8,10 @@ var tests = [
require('./controller/search'),
require('./service/mget'),
require('./service/search'),
require('./sanitiser/_details'),
require('./sanitiser/_private'),
require('./sanitiser/_source'),
require('./sanitiser/_truthy'),
require('./sanitiser/_flag_bool'),
require('./sanitiser/_sources'),
require('./sanitiser/search'),
require('./sanitiser/_layers'),
require('./sanitiser/reverse'),
require('./sanitiser/place'),
require('./query/types'),

53
test/unit/sanitiser/_details.js

@ -1,53 +0,0 @@
var sanitize = require('../../../sanitiser/_details');
module.exports.tests = {};
module.exports.tests.sanitize_details = function(test, common) {
var invalid_values = [null, -1, 123, NaN, 'abc'];
invalid_values.forEach(function(detailsValue) {
test('invalid details param ' + detailsValue, function(t) {
var req = {query: { details: detailsValue }};
sanitize(req);
t.equal(req.clean.details, false, 'default details set (to false)');
t.end();
});
});
var valid_values = ['true', true, 1, '1', 'yes', 'y'];
valid_values.forEach(function(detailsValue) {
test('valid details param ' + detailsValue, function(t) {
var req = {query: { details: detailsValue }};
sanitize(req);
t.equal(req.clean.details, true, 'details set to true');
t.end();
});
});
var valid_false_values = ['false', false, 0, '0', 'no', 'n'];
valid_false_values.forEach(function(detailsValue) {
test('test setting false explicitly ' + detailsValue, function(t) {
var req = {query: { details: detailsValue }};
sanitize(req);
t.equal(req.clean.details, false, 'details set to false');
t.end();
});
});
test('test default behavior', function(t) {
var req = {query: {}};
sanitize(req);
t.equal(req.clean.details, true, 'details set to true');
t.end();
});
};
module.exports.all = function (tape, common) {
function test(name, testFunction) {
return tape('SANTIZE _details ' + name, testFunction);
}
for( var testCase in module.exports.tests ){
module.exports.tests[testCase](test, common);
}
};

64
test/unit/sanitiser/_flag_bool.js

@ -0,0 +1,64 @@
var sanitizer = require('../../../sanitiser/_flag_bool');
var sanitize = sanitizer('dirty_param', true);
module.exports.tests = {};
module.exports.tests.sanitize_private = function(test, common) {
var invalid_values = [null, -1, 123, NaN, 'abc'];
invalid_values.forEach(function (value) {
test('invalid dirty_param ' + value, function (t) {
var raw = {dirty_param: value};
var clean = {};
sanitize(raw, clean);
t.equal(clean.dirty_param, false, 'default clean value set (to false)');
t.end();
});
});
var valid_values = ['true', true, 1, '1'];
valid_values.forEach(function (value) {
test('valid dirty_param ' + value, function (t) {
var raw = {dirty_param: value};
var clean = {};
sanitize(raw, clean);
t.equal(clean.dirty_param, true, 'clean value set to true');
t.end();
});
});
var valid_false_values = ['false', false, 0, '0'];
valid_false_values.forEach(function (value) {
test('test setting false explicitly ' + value, function (t) {
var raw = {dirty_param: value};
var clean = {};
sanitize(raw, clean);
t.equal(clean.dirty_param, false, 'clean value set to false');
t.end();
});
});
};
module.exports.tests.validate_default_behavior = function(test, common) {
var default_values = [true, false, 'foo'];
default_values.forEach(function (defaultValue) {
test('test default behavior: ' + defaultValue, function (t) {
var sanitize_true = sanitizer('foo_bar', defaultValue);
var raw = {};
var clean = {};
sanitize_true(raw, clean);
t.equal(clean.foo_bar, defaultValue, 'foo_bar set to ' + defaultValue);
t.end();
});
});
};
module.exports.all = function (tape, common) {
function test(name, testFunction) {
return tape('SANTIZE _flag_bool: ' + name, testFunction);
}
for( var testCase in module.exports.tests ){
module.exports.tests[testCase](test, common);
}
};

120
test/unit/sanitiser/_layers.js

@ -0,0 +1,120 @@
var sanitize = require('../../../sanitiser/_targets')('layers', require('../../../query/layers'));
module.exports.tests = {};
module.exports.tests.sanitize_layers = function(test, common) {
test('unspecified', function(t) {
var messages = sanitize({ layers: undefined }, {});
t.equal(messages.errors.length, 0, 'no errors');
t.end();
});
test('invalid layer', function(t) {
var raw = { layers: 'test_layer' };
var clean = {};
var messages = sanitize(raw, clean);
var msg = ' is an invalid layers parameter. Valid options: ';
t.equal(messages.errors.length, 1, 'errors set');
t.true(messages.errors[0].match(msg), 'invalid layer requested');
t.true(messages.errors[0].length > msg.length, 'invalid error message');
t.end();
});
test('venue (alias) layer', function(t) {
var venue_layers = ['geoname','osmnode','osmway'];
var raw = { layers: 'venue' };
var clean = {};
sanitize(raw, clean);
t.deepEqual(clean.types.from_layers, venue_layers, 'venue layers set');
t.end();
});
test('coarse (alias) layer', function(t) {
var admin_layers = ['admin0','admin1','admin2','neighborhood','locality','local_admin'];
var raw = { layers: 'coarse' };
var clean = {};
sanitize(raw, clean);
t.deepEqual(clean.types.from_layers, admin_layers, 'coarse layers set');
t.end();
});
test('address (alias) layer', function(t) {
var address_layers = ['osmaddress','openaddresses'];
var raw = { layers: 'address' };
var clean = {};
sanitize(raw, clean);
t.deepEqual(clean.types.from_layers, address_layers, 'address layers set');
t.end();
});
test('venue alias layer plus regular layers', function(t) {
var venue_layers = ['geoname','osmnode','osmway'];
var reg_layers = ['admin0', 'admin1'];
var raw = { layers: 'venue,country,region' };
var clean = {};
sanitize(raw, clean);
t.deepEqual(clean.types.from_layers, venue_layers.concat(reg_layers), 'venue + regular layers');
t.end();
});
test('coarse alias layer plus regular layers', function(t) {
var admin_layers = ['admin0','admin1','admin2','neighborhood','locality','local_admin'];
var reg_layers = ['geoname', 'osmnode', 'osmway'];
var raw = { layers: 'coarse,venue,country' };
var clean = {};
sanitize(raw, clean);
t.deepEqual(clean.types.from_layers, admin_layers.concat(reg_layers), 'coarse + regular layers set');
t.end();
});
test('address alias layer plus regular layers', function(t) {
var address_layers = ['osmaddress','openaddresses'];
var reg_layers = ['admin0', 'locality'];
var raw = { layers: 'address,country,locality' };
var clean = {};
sanitize(raw, clean);
t.deepEqual(clean.types.from_layers, address_layers.concat(reg_layers), 'address + regular layers set');
t.end();
});
test('alias layer plus regular layers (no duplicates)', function(t) {
var venue_layers = ['geoname','osmnode','osmway','admin0'];
var raw = { layers: 'venue,country' };
var clean = {};
sanitize(raw, clean);
t.deepEqual(clean.types.from_layers, venue_layers, 'venue layers found (no duplicates)');
t.end();
});
test('multiple alias layers (no duplicates)', function(t) {
var alias_layers = ['geoname','osmnode','osmway','admin0','admin1','admin2','neighborhood','locality','local_admin'];
var raw = { layers: 'venue,coarse' };
var clean = {};
sanitize(raw, clean);
t.deepEqual(clean.types.from_layers, alias_layers, 'all layers found (no duplicates)');
t.end();
});
};
module.exports.all = function (tape, common) {
function test(name, testFunction) {
return tape('SANTIZE _layers ' + name, testFunction);
}
for( var testCase in module.exports.tests ){
module.exports.tests[testCase](test, common);
}
};

51
test/unit/sanitiser/_source.js

@ -7,13 +7,15 @@ module.exports.tests = {};
module.exports.tests.no_sources = function(test, common) {
test('source is not set', function(t) {
var req = {
query: { }
query: { },
clean: { }
};
var response = sanitize(req);
var response = sanitize(req.query, req.clean);
t.deepEqual(req.clean.types, {}, 'clean.types should be empty object');
t.deepEqual(response, success_response, 'no error returned');
t.deepEqual(response.errors, [], 'no error returned');
t.deepEqual(response.warnings, [], 'no warnings returned');
t.end();
});
@ -21,13 +23,15 @@ module.exports.tests.no_sources = function(test, common) {
var req = {
query: {
source: ''
}
},
clean: { }
};
var response = sanitize(req);
var response = sanitize(req.query, req.clean);
t.deepEqual(req.clean.types, {}, 'clean.types should be empty object');
t.deepEqual(response, success_response, 'no error returned');
t.deepEqual(response.errors, [], 'no error returned');
t.deepEqual(response.warnings, [], 'no warnings returned');
t.end();
});
};
@ -37,13 +41,15 @@ module.exports.tests.valid_sources = function(test, common) {
var req = {
query: {
source: 'geonames'
}
},
clean: { }
};
var response = sanitize(req);
var response = sanitize(req.query, req.clean);
t.deepEqual(req.clean.types, { from_source: ['geoname'] }, 'clean.types should contain from_source entry with geonames');
t.deepEqual(response, success_response, 'no error returned');
t.deepEqual(response.errors, [], 'no error returned');
t.deepEqual(response.warnings, [], 'no warnings returned');
t.end();
});
@ -51,16 +57,18 @@ module.exports.tests.valid_sources = function(test, common) {
var req = {
query: {
source: 'openstreetmap'
}
},
clean: { }
};
var expected_types = {
from_source: ['osmaddress', 'osmnode', 'osmway']
};
var response = sanitize(req);
var response = sanitize(req.query, req.clean);
t.deepEqual(req.clean.types, expected_types, 'clean.types should contain from_source entry with multiple entries for openstreetmap');
t.deepEqual(response, success_response, 'no error returned');
t.deepEqual(response.errors, [], 'no error returned');
t.deepEqual(response.warnings, [], 'no warnings returned');
t.end();
});
@ -68,17 +76,19 @@ module.exports.tests.valid_sources = function(test, common) {
var req = {
query: {
source: 'openstreetmap,openaddresses'
}
},
clean: { }
};
var expected_types = {
from_source: ['osmaddress', 'osmnode', 'osmway', 'openaddresses']
};
var response = sanitize(req);
var response = sanitize(req.query, req.clean);
t.deepEqual(req.clean.types, expected_types,
'clean.types should contain from_source entry with multiple entries for openstreetmap and openadresses');
t.deepEqual(response, success_response, 'no error returned');
t.deepEqual(response.errors, [], 'no error returned');
t.deepEqual(response.warnings, [], 'no warnings returned');
t.end();
});
};
@ -88,14 +98,17 @@ module.exports.tests.invalid_sources = function(test, common) {
var req = {
query: {
source: 'notasource'
}
},
clean: { }
};
var expected_response = {
error: true,
msg: '`notasource` is an invalid source parameter. Valid options: geonames, openaddresses, quattroshapes, openstreetmap'
errors: [
'\'notasource\' is an invalid source parameter. Valid options: geonames,openaddresses,quattroshapes,openstreetmap'
],
warnings: []
};
var response = sanitize(req);
var response = sanitize(req.query, req.clean);
t.deepEqual(response, expected_response, 'error with message returned');
t.deepEqual(req.clean.types, { }, 'clean.types should remain empty');

132
test/unit/sanitiser/_sources.js

@ -0,0 +1,132 @@
var sanitize = require( '../../../sanitiser/_targets' )('sources', require('../../../query/sources'));
var success_messages = { error: false };
module.exports.tests = {};
module.exports.tests.no_sources = function(test, common) {
test('sources is not set', function(t) {
var req = {
query: { },
clean: { }
};
var messages = sanitize(req.query, req.clean);
t.deepEqual(req.clean.types, {}, 'clean.types should be empty object');
t.deepEqual(messages.errors, [], 'no error returned');
t.deepEqual(messages.warnings, [], 'no warnings returned');
t.end();
});
test('source is empty string', function(t) {
var req = {
query: {
sources: ''
},
clean: { }
};
var expected_error = 'sources parameter cannot be an empty string. ' +
'Valid options: gn,geonames,oa,openaddresses,qs,quattroshapes,osm,openstreetmap';
var messages = sanitize(req.query, req.clean);
t.deepEqual(req.clean.types, {}, 'clean.types should be empty object');
t.deepEqual(messages.errors.length, 1, 'error returned');
t.deepEqual(messages.errors[0], expected_error, 'error returned');
t.deepEqual(messages.warnings, [], 'no warnings returned');
t.end();
});
};
module.exports.tests.valid_sources = function(test, common) {
test('geonames source', function(t) {
var req = {
query: {
sources: 'geonames'
},
clean: { }
};
var messages = sanitize(req.query, req.clean);
t.deepEqual(req.clean.types, { from_sources: ['geoname'] }, 'clean.types should contain from_source entry with geonames');
t.deepEqual(messages.errors, [], 'no error returned');
t.deepEqual(messages.warnings, [], 'no warnings returned');
t.end();
});
test('openstreetmap source', function(t) {
var req = {
query: {
sources: 'openstreetmap'
},
clean: { }
};
var expected_types = {
from_sources: ['osmaddress', 'osmnode', 'osmway']
};
var messages = sanitize(req.query, req.clean);
t.deepEqual(req.clean.types, expected_types, 'clean.types should contain from_source entry with multiple entries for openstreetmap');
t.deepEqual(messages.errors, [], 'no error returned');
t.deepEqual(messages.warnings, [], 'no warnings returned');
t.end();
});
test('multiple sources', function(t) {
var req = {
query: {
sources: 'openstreetmap,openaddresses'
},
clean: { }
};
var expected_types = {
from_sources: ['osmaddress', 'osmnode', 'osmway', 'openaddresses']
};
var messages = sanitize(req.query, req.clean);
t.deepEqual(req.clean.types, expected_types,
'clean.types should contain from_source entry with multiple entries for openstreetmap and openadresses');
t.deepEqual(messages.errors, [], 'no error returned');
t.deepEqual(messages.warnings, [], 'no warnings returned');
t.end();
});
};
module.exports.tests.invalid_sources = function(test, common) {
test('geonames source', function(t) {
var req = {
query: {
sources: 'notasource'
},
clean: { }
};
var expected_messages = {
errors: [
'\'notasource\' is an invalid sources parameter. Valid options: gn,geonames,oa,openaddresses,qs,quattroshapes,osm,openstreetmap'
],
warnings: []
};
var messages = sanitize(req.query, req.clean);
t.deepEqual(messages, expected_messages, 'error with message returned');
t.deepEqual(req.clean.types, { }, 'clean.types should remain empty');
t.end();
});
};
module.exports.all = function (tape, common) {
function test(name, testFunction) {
return tape('SANTIZE _sources ' + name, testFunction);
}
for( var testCase in module.exports.tests ){
module.exports.tests[testCase](test, common);
}
};

5
test/unit/sanitiser/_text.js

@ -1,17 +1,12 @@
var text = require('../../../sanitiser/_text'),
parser = require('../../../helper/query_parser'),
delim = ',',
defaultError = 'invalid param \'text\': text length, must be >0',
allLayers = [ 'geoname', 'osmnode', 'osmway', 'admin0', 'admin1', 'admin2', 'neighborhood',
'locality', 'local_admin', 'osmaddress', 'openaddresses' ],
nonAddressLayers = [ 'geoname', 'osmnode', 'osmway', 'admin0', 'admin1', 'admin2', 'neighborhood',
'locality', 'local_admin' ],
defaultParsed= { },
defaultClean = { text: 'test',
layers: allLayers,
size: 10,
details: true,
parsed_text: defaultParsed,
lat:0,
lon:0

36
test/unit/sanitiser/place.js

@ -10,7 +10,7 @@ var place = require('../../../sanitiser/place'),
defaultMissingTypeError = function(input) {
var type = input.split(delimiter)[0];
return type + ' is invalid. It must be one of these values - [' + types.join(', ') + ']'; },
defaultClean = { ids: [ { id: '123', type: 'geoname' } ], details: true },
defaultClean = { ids: [ { id: '123', type: 'geoname' } ], private: false },
sanitize = function(query, cb) { _sanitize({'query':query}, cb); },
inputs = {
valid: [ 'geoname:1', 'osmnode:2', 'admin0:53', 'osmway:44', 'geoname:5' ],
@ -56,7 +56,7 @@ module.exports.tests.sanitize_id = function(test, common) {
test('valid input', function(t) {
inputs.valid.forEach( function( input ){
var input_parts = input.split(delimiter);
var expected = { ids: [ { id: input_parts[1], type: input_parts[0] } ], details: true };
var expected = { ids: [ { id: input_parts[1], type: input_parts[0] } ], private: false };
sanitize({ id: input }, function( err, clean ){
t.equal(err, undefined, 'no error (' + input + ')' );
t.deepEqual(clean, expected, 'clean set correctly (' + input + ')');
@ -94,7 +94,7 @@ module.exports.tests.sanitize_ids = function(test, common) {
var input_parts = input.split(delimiter);
expected.ids.push({ id: input_parts[1], type: input_parts[0] });
});
expected.details = true;
expected.private = false;
sanitize({ id: inputs.valid }, function( err, clean ){
t.equal(err, undefined, 'no error' );
t.deepEqual(clean, expected, 'clean set correctly');
@ -103,32 +103,32 @@ module.exports.tests.sanitize_ids = function(test, common) {
});
};
module.exports.tests.sanitize_details = function(test, common) {
module.exports.tests.sanitize_private = function(test, common) {
var invalid_values = [null, -1, 123, NaN, 'abc'];
invalid_values.forEach(function(details) {
test('invalid details param ' + details, function(t) {
sanitize({ id:'geoname:123', details: details }, function( err, clean ){
t.equal(clean.details, false, 'default details set (to false)');
invalid_values.forEach(function(value) {
test('invalid private param ' + value, function(t) {
sanitize({ id:'geoname:123', 'private': value}, function( err, clean ){
t.equal(clean.private, false, 'default private set (to false)');
t.end();
});
});
});
var valid_values = ['true', true, 1];
valid_values.forEach(function(details) {
test('valid details param ' + details, function(t) {
sanitize({ id:'geoname:123', details: details }, function( err, clean ){
t.equal(clean.details, true, 'details set to true');
valid_values.forEach(function(value) {
test('valid private param ' + value, function(t) {
sanitize({ id:'geoname:123', 'private': value }, function( err, clean ){
t.equal(clean.private, true, 'private set to true');
t.end();
});
});
});
var valid_false_values = ['false', false, 0];
valid_false_values.forEach(function(details) {
test('test setting false explicitly ' + details, function(t) {
sanitize({ id:'geoname:123', details: details }, function( err, clean ){
t.equal(clean.details, false, 'details set to false');
valid_false_values.forEach(function(value) {
test('test setting false explicitly ' + value, function(t) {
sanitize({ id:'geoname:123', 'private': value }, function( err, clean ){
t.equal(clean.private, false, 'private set to false');
t.end();
});
});
@ -136,14 +136,14 @@ module.exports.tests.sanitize_details = function(test, common) {
test('test default behavior', function(t) {
sanitize({ id:'geoname:123' }, function( err, clean ){
t.equal(clean.details, true, 'details set to true');
t.equal(clean.private, false, 'private set to false');
t.end();
});
});
};
module.exports.tests.de_dupe = function(test, common) {
var expected = { ids: [ { id: '1', type: 'geoname' }, { id: '2', type: 'osmnode' } ], details: true };
var expected = { ids: [ { id: '1', type: 'geoname' }, { id: '2', type: 'osmnode' } ], private: false };
test('duplicate ids', function(t) {
sanitize( { id: ['geoname:1', 'osmnode:2', 'geoname:1'] }, function( err, clean ){
t.equal(err, undefined, 'no error' );

129
test/unit/sanitiser/reverse.js

@ -1,15 +1,14 @@
var suggest = require('../../../sanitiser/reverse'),
_sanitize = suggest.sanitize,
middleware = suggest.middleware,
delim = ',',
var reverse = require('../../../sanitiser/reverse'),
_sanitize = reverse.sanitize,
middleware = reverse.middleware,
defaultError = 'missing param \'lat\'',
defaultClean = { lat:0,
types: {
},
lon: 0,
size: 10,
details: true,
private: false,
categories: []
},
sanitize = function(query, cb) { _sanitize({'query':query}, cb); };
@ -29,6 +28,14 @@ module.exports.tests.interface = function(test, common) {
});
};
module.exports.tests.sanitisers = function(test, common) {
test('check sanitiser list', function (t) {
var expected = ['layers', 'sources', 'size', 'private', 'geo_reverse', 'categories'];
t.deepEqual(Object.keys(reverse.sanitiser_list), expected);
t.end();
});
};
module.exports.tests.sanitize_lat = function(test, common) {
var lats = {
invalid: [],
@ -115,116 +122,40 @@ module.exports.tests.sanitize_size = function(test, common) {
});
};
module.exports.tests.sanitize_details = function(test, common) {
module.exports.tests.sanitize_private = function(test, common) {
var invalid_values = [null, -1, 123, NaN, 'abc'];
invalid_values.forEach(function(details) {
test('invalid details param ' + details, function(t) {
sanitize({ 'point.lat': 0, 'point.lon': 0, details: details }, function( err, clean ){
t.equal(clean.details, false, 'details set to false');
invalid_values.forEach(function(value) {
test('invalid private param ' + value, function(t) {
sanitize({ 'point.lat': 0, 'point.lon': 0, 'private': value}, function( err, clean ){
t.equal(clean.private, false, 'default private set (to false)');
t.end();
});
});
});
var valid_values = [true, 'true', 1, '1', 'yes', 'y'];
valid_values.forEach(function(details) {
test('valid details param ' + details, function(t) {
sanitize({ 'point.lat': 0, 'point.lon': 0, details: details }, function( err, clean ){
t.equal(clean.details, true, 'details set to true');
var valid_values = ['true', true, 1];
valid_values.forEach(function(value) {
test('valid private param ' + value, function(t) {
sanitize({ 'point.lat': 0, 'point.lon': 0, 'private': value }, function( err, clean ){
t.equal(clean.private, true, 'private set to true');
t.end();
});
});
});
test('test default behavior', function(t) {
sanitize({ 'point.lat': 0, 'point.lon': 0 }, function( err, clean ){
t.equal(clean.details, true, 'details set to true');
t.end();
});
});
var valid_false_values = ['false', false, 0, '0', 'no', 'n'];
valid_false_values.forEach(function(details) {
test('test setting false explicitly ' + details, function(t) {
sanitize({ 'point.lat': 0, 'point.lon': 0, details: details }, function( err, clean ){
t.equal(clean.details, false, 'details set to false');
var valid_false_values = ['false', false, 0];
valid_false_values.forEach(function(value) {
test('test setting false explicitly ' + value, function(t) {
sanitize({ 'point.lat': 0, 'point.lon': 0, 'private': value }, function( err, clean ){
t.equal(clean.private, false, 'private set to false');
t.end();
});
});
});
};
module.exports.tests.sanitize_layers = function(test, common) {
test('unspecified', function(t) {
sanitize({ layers: undefined, 'point.lat': 0, 'point.lon': 0 }, function( err, clean ){
t.deepEqual(clean.types.from_layers, defaultClean.types.from_layers, 'default layers set');
t.end();
});
});
test('invalid layer', function(t) {
sanitize({ layers: 'test_layer', 'point.lat': 0, 'point.lon': 0 }, function( err, clean ){
var msg = 'invalid param \'layers\': must be one or more of ';
t.true(err.match(msg), 'invalid layer requested');
t.true(err.length > msg.length, 'invalid error message');
t.end();
});
});
test('poi (alias) layer', function(t) {
var poi_layers = ['geoname','osmnode','osmway'];
sanitize({ layers: 'poi', 'point.lat': 0, 'point.lon': 0 }, function( err, clean ){
t.deepEqual(clean.types.from_layers, poi_layers, 'poi layers set');
t.end();
});
});
test('admin (alias) layer', function(t) {
var admin_layers = ['admin0','admin1','admin2','neighborhood','locality','local_admin'];
sanitize({ layers: 'admin', 'point.lat': 0, 'point.lon': 0 }, function( err, clean ){
t.deepEqual(clean.types.from_layers, admin_layers, 'admin layers set');
t.end();
});
});
test('address (alias) layer', function(t) {
var address_layers = ['osmaddress','openaddresses'];
sanitize({ layers: 'address', 'point.lat': 0, 'point.lon': 0 }, function( err, clean ){
t.deepEqual(clean.types.from_layers, address_layers, 'address layers set');
t.end();
});
});
test('poi alias layer plus regular layers', function(t) {
var poi_layers = ['geoname','osmnode','osmway'];
var reg_layers = ['admin0', 'admin1'];
sanitize({ layers: 'poi,admin0,admin1', 'point.lat': 0, 'point.lon': 0 }, function( err, clean ){
t.deepEqual(clean.types.from_layers, reg_layers.concat(poi_layers), 'poi + regular layers');
t.end();
});
});
test('admin alias layer plus regular layers', function(t) {
var admin_layers = ['admin0','admin1','admin2','neighborhood','locality','local_admin'];
var reg_layers = ['geoname', 'osmway'];
sanitize({ layers: 'admin,geoname,osmway', 'point.lat': 0, 'point.lon': 0 }, function( err, clean ){
t.deepEqual(clean.types.from_layers, reg_layers.concat(admin_layers), 'admin + regular layers set');
t.end();
});
});
test('address alias layer plus regular layers', function(t) {
var address_layers = ['osmaddress','openaddresses'];
var reg_layers = ['geoname', 'osmway'];
sanitize({ layers: 'address,geoname,osmway', 'point.lat': 0, 'point.lon': 0 }, function( err, clean ){
t.deepEqual(clean.types.from_layers, reg_layers.concat(address_layers), 'address + regular layers set');
t.end();
});
});
test('alias layer plus regular layers (no duplicates)', function(t) {
var poi_layers = ['geoname','osmnode','osmway'];
sanitize({ layers: 'poi,geoname,osmnode', 'point.lat': 0, 'point.lon': 0 }, function( err, clean ){
t.deepEqual(clean.types.from_layers, poi_layers, 'poi layers found (no duplicates)');
t.end();
});
});
test('multiple alias layers (no duplicates)', function(t) {
var alias_layers = ['geoname','osmnode','osmway','admin0','admin1','admin2','neighborhood','locality','local_admin'];
sanitize({ layers: 'poi,admin', 'point.lat': 0, 'point.lon': 0 }, function( err, clean ){
t.deepEqual(clean.types.from_layers, alias_layers, 'all layers found (no duplicates)');
test('test default behavior', function(t) {
sanitize({ 'point.lat': 0, 'point.lon': 0 }, function( err, clean ){
t.equal(clean.private, false, 'private set to false');
t.end();
});
});

108
test/unit/sanitiser/search.js

@ -5,13 +5,11 @@ var search = require('../../../sanitiser/search'),
defaultParsed = _text.defaultParsed,
_sanitize = search.sanitize,
middleware = search.middleware,
delim = ',',
defaultError = 'invalid param \'text\': text length, must be >0',
defaultClean = { text: 'test',
types: {
},
size: 10,
details: true,
parsed_text: defaultParsed,
},
sanitize = function(query, cb) { _sanitize({'query':query}, cb); };
@ -26,7 +24,15 @@ module.exports.tests.interface = function(test, common) {
});
test('middleware interface', function(t) {
t.equal(typeof middleware, 'function', 'middleware is a function');
t.equal(middleware.length, 3, 'sanitizee has a valid middleware');
t.equal(middleware.length, 3, 'sanitize has a valid middleware');
t.end();
});
};
module.exports.tests.sanitisers = function(test, common) {
test('check sanitiser list', function (t) {
var expected = ['text', 'size', 'layers', 'sources', 'private', 'geo_search', 'categories' ];
t.deepEqual(Object.keys(search.sanitiser_list), expected);
t.end();
});
};
@ -89,7 +95,7 @@ module.exports.tests.sanitize_text_with_delim = function(test, common) {
module.exports.tests.sanitize_private_no_value = function(test, common) {
test('default private should be set to true', function(t) {
sanitize({ text: 'test' }, function( err, clean ){
t.equal(clean.private, true, 'private set to true');
t.equal(clean.private, false, 'private set to false');
});
t.end();
});
@ -261,78 +267,40 @@ module.exports.tests.sanitize_size = function(test, common) {
});
};
module.exports.tests.sanitize_layers = function(test, common) {
test('unspecified', function(t) {
sanitize({ layers: undefined, text: 'test' }, function( err, clean ){
t.deepEqual(clean.types.from_layers, defaultClean.types.from_layers, 'default layers set');
t.end();
});
});
test('invalid layer', function(t) {
sanitize({ layers: 'test_layer', text: 'test' }, function( err, clean ){
var msg = 'invalid param \'layers\': must be one or more of ';
t.true(err.match(msg), 'invalid layer requested');
t.true(err.length > msg.length, 'invalid error message');
t.end();
});
});
test('poi (alias) layer', function(t) {
var poi_layers = ['geoname','osmnode','osmway'];
sanitize({ layers: 'poi', text: 'test' }, function( err, clean ){
t.deepEqual(clean.types.from_layers, poi_layers, 'poi layers set');
t.end();
});
});
test('admin (alias) layer', function(t) {
var admin_layers = ['admin0','admin1','admin2','neighborhood','locality','local_admin'];
sanitize({ layers: 'admin', text: 'test' }, function( err, clean ){
t.deepEqual(clean.types.from_layers, admin_layers, 'admin layers set');
t.end();
});
});
test('address (alias) layer', function(t) {
var address_layers = ['osmaddress','openaddresses'];
sanitize({ layers: 'address', text: 'test' }, function( err, clean ){
t.deepEqual(clean.types.from_layers, address_layers, 'types from layers set');
t.deepEqual(clean.types.from_address_parser, _text.allLayers, 'address parser uses default layers');
t.end();
});
});
test('poi alias layer plus regular layers', function(t) {
var poi_layers = ['geoname','osmnode','osmway'];
var reg_layers = ['admin0', 'admin1'];
sanitize({ layers: 'poi,admin0,admin1', text: 'test' }, function( err, clean ){
t.deepEqual(clean.types.from_layers, reg_layers.concat(poi_layers), 'poi + regular layers');
t.end();
});
});
test('admin alias layer plus regular layers', function(t) {
var admin_layers = ['admin0','admin1','admin2','neighborhood','locality','local_admin'];
var reg_layers = ['geoname', 'osmway'];
sanitize({ layers: 'admin,geoname,osmway', text: 'test' }, function( err, clean ){
t.deepEqual(clean.types.from_layers, reg_layers.concat(admin_layers), 'admin + regular layers set');
t.end();
module.exports.tests.sanitize_private = function(test, common) {
var invalid_values = [null, -1, 123, NaN, 'abc'];
invalid_values.forEach(function(value) {
test('invalid private param ' + value, function(t) {
sanitize({ text: 'test', lat: 0, lon: 0, 'private': value }, function( err, clean ){
t.equal(clean.private, false, 'default private set (to false)');
t.end();
});
});
});
test('address alias layer plus regular layers', function(t) {
var address_layers = ['osmaddress','openaddresses'];
var reg_layers = ['geoname', 'osmway'];
sanitize({ layers: 'address,geoname,osmway', text: 'test' }, function( err, clean ){
t.deepEqual(clean.types.from_layers, reg_layers.concat(address_layers), 'address + regular layers set');
t.end();
var valid_values = ['true', true, 1, '1'];
valid_values.forEach(function(value) {
test('valid private ' + value, function(t) {
sanitize({ text: 'test', 'private': value}, function( err, clean ){
t.equal(clean.private, true, 'private set to true');
t.end();
});
});
});
test('alias layer plus regular layers (no duplicates)', function(t) {
var poi_layers = ['geoname','osmnode','osmway'];
sanitize({ layers: 'poi,geoname,osmnode', text: 'test' }, function( err, clean ){
t.deepEqual(clean.types.from_layers, poi_layers, 'poi layers found (no duplicates)');
t.end();
var valid_false_values = ['false', false, 0, '0'];
valid_false_values.forEach(function(value) {
test('test setting false explicitly ' + value, function(t) {
sanitize({ text: 'test', 'private': value }, function( err, clean ){
t.equal(clean.private, false, 'private set to false');
t.end();
});
});
});
test('multiple alias layers (no duplicates)', function(t) {
var alias_layers = ['geoname','osmnode','osmway','admin0','admin1','admin2','neighborhood','locality','local_admin'];
sanitize({ layers: 'poi,admin', text: 'test' }, function( err, clean ){
t.deepEqual(clean.types.from_layers, alias_layers, 'all layers found (no duplicates)');
test('test default behavior', function(t) {
sanitize({ text: 'test' }, function( err, clean ){
t.equal(clean.private, false, 'private set to false');
t.end();
});
});

Loading…
Cancel
Save