diff --git a/.gitignore b/.gitignore index 419c501c..9a05b31b 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ node_modules coverage .idea *.log -reports \ No newline at end of file +reports +pids diff --git a/controller/status.js b/controller/status.js new file mode 100644 index 00000000..7c87af6d --- /dev/null +++ b/controller/status.js @@ -0,0 +1,3 @@ +module.exports = function controller( req, res, next) { + res.send('status: ok'); +}; diff --git a/helper/geojsonify.js b/helper/geojsonify.js index cf1f8744..dcd96168 100644 --- a/helper/geojsonify.js +++ b/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); } /** diff --git a/helper/layers.js b/helper/layers.js deleted file mode 100644 index a5a57d7f..00000000 --- a/helper/layers.js +++ /dev/null @@ -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; -}; diff --git a/helper/query_parser.js b/helper/query_parser.js index 20e4c332..b60ebcd8 100644 --- a/helper/query_parser.js +++ b/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' diff --git a/helper/types.js b/helper/types.js index 7455476e..249c27a0 100644 --- a/helper/types.js +++ b/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'); }; \ No newline at end of file diff --git a/middleware/_types.js b/middleware/_types.js index 8b12719f..baad127e 100644 --- a/middleware/_types.js +++ b/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(); diff --git a/middleware/geocodeJSON.js b/middleware/geocodeJSON.js index d2f58b46..4f7943e4 100644 --- a/middleware/geocodeJSON.js +++ b/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(); } diff --git a/package.json b/package.json index d2853d8c..8018919c 100644 --- a/package.json +++ b/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", diff --git a/public/apiDoc.md b/public/apiDoc.md index 76e7335f..54579ac3 100644 --- a/public/apiDoc.md +++ b/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! \ No newline at end of file diff --git a/query/layers.js b/query/layers.js new file mode 100644 index 00000000..7f16d1fb --- /dev/null +++ b/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'], +}; diff --git a/query/sources.js b/query/sources.js index 77be7fb9..3715be86 100644 --- a/query/sources.js +++ b/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'] }; diff --git a/routes/v1.js b/routes/v1.js index 592e6ce3..49fb3021 100644 --- a/routes/v1.js +++ b/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 ); } /** diff --git a/sanitiser/_categories.js b/sanitiser/_categories.js index f29c9fd0..e76e8767 100644 --- a/sanitiser/_categories.js +++ b/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; } diff --git a/sanitiser/_details.js b/sanitiser/_details.js deleted file mode 100644 index b1ef9471..00000000 --- a/sanitiser/_details.js +++ /dev/null @@ -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; diff --git a/sanitiser/_flag_bool.js b/sanitiser/_flag_bool.js new file mode 100644 index 00000000..68e9ce1c --- /dev/null +++ b/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; diff --git a/sanitiser/_geo_common.js b/sanitiser/_geo_common.js index 2bf8c6a5..71b728cb 100644 --- a/sanitiser/_geo_common.js +++ b/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); diff --git a/sanitiser/_geo_reverse.js b/sanitiser/_geo_reverse.js index f613e54f..4b259199 100644 --- a/sanitiser/_geo_reverse.js +++ b/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; }; diff --git a/sanitiser/_geo_search.js b/sanitiser/_geo_search.js index 1f1fbeb2..cc2fafcb 100644 --- a/sanitiser/_geo_search.js +++ b/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; }; diff --git a/sanitiser/_id.js b/sanitiser/_id.js index 78ff6b1f..77d21fa8 100644 --- a/sanitiser/_id.js +++ b/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 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 diff --git a/sanitiser/_source.js b/sanitiser/_source.js index f8f89a68..88d620fc 100644 --- a/sanitiser/_source.js +++ b/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; diff --git a/sanitiser/_targets.js b/sanitiser/_targets.js new file mode 100644 index 00000000..81e345b6 --- /dev/null +++ b/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; diff --git a/sanitiser/_text.js b/sanitiser/_text.js index 41f38d6c..2f1f96af 100644 --- a/sanitiser/_text.js +++ b/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 diff --git a/sanitiser/_truthy.js b/sanitiser/_truthy.js deleted file mode 100644 index c5d08f05..00000000 --- a/sanitiser/_truthy.js +++ /dev/null @@ -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; diff --git a/sanitiser/place.js b/sanitiser/place.js index 352777a6..8b5e6034 100644 --- a/sanitiser/place.js +++ b/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(); }); }; diff --git a/sanitiser/reverse.js b/sanitiser/reverse.js index edb8da7e..172a6840 100644 --- a/sanitiser/reverse.js +++ b/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(); }); }; + diff --git a/sanitiser/sanitizeAll.js b/sanitiser/sanitizeAll.js new file mode 100644 index 00000000..886a64c3 --- /dev/null +++ b/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; \ No newline at end of file diff --git a/sanitiser/search.js b/sanitiser/search.js index ccf82865..993c3730 100644 --- a/sanitiser/search.js +++ b/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(); }); }; + diff --git a/test/unit/controller/place.js b/test/unit/controller/place.js index 9c93e2d8..ba72de9e 100644 --- a/test/unit/controller/place.js +++ b/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) diff --git a/test/unit/controller/search.js b/test/unit/controller/search.js index 135393d9..94873027 100644 --- a/test/unit/controller/search.js +++ b/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); }); }; diff --git a/test/unit/helper/geojsonify.js b/test/unit/helper/geojsonify.js index 141c2783..dca0d3fb 100644 --- a/test/unit/helper/geojsonify.js +++ b/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) { diff --git a/test/unit/helper/query_parser.js b/test/unit/helper/query_parser.js index 1021d37c..ad669ece 100644 --- a/test/unit/helper/query_parser.js +++ b/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+')'); diff --git a/test/unit/helper/types.js b/test/unit/helper/types.js index 2398c6bb..4638ff12 100644 --- a/test/unit/helper/types.js +++ b/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'] }; diff --git a/test/unit/query/search.js b/test/unit/query/search.js index 399fd20a..a0f56175 100644 --- a/test/unit/query/search.js +++ b/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), }); diff --git a/test/unit/run.js b/test/unit/run.js index baa49f1f..bc166c4b 100644 --- a/test/unit/run.js +++ b/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'), diff --git a/test/unit/sanitiser/_details.js b/test/unit/sanitiser/_details.js deleted file mode 100644 index 26dc444d..00000000 --- a/test/unit/sanitiser/_details.js +++ /dev/null @@ -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); - } -}; diff --git a/test/unit/sanitiser/_flag_bool.js b/test/unit/sanitiser/_flag_bool.js new file mode 100644 index 00000000..9e4252c9 --- /dev/null +++ b/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); + } +}; diff --git a/test/unit/sanitiser/_layers.js b/test/unit/sanitiser/_layers.js new file mode 100644 index 00000000..12d2cfd3 --- /dev/null +++ b/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); + } +}; diff --git a/test/unit/sanitiser/_source.js b/test/unit/sanitiser/_source.js index 235e18c6..991accd5 100644 --- a/test/unit/sanitiser/_source.js +++ b/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'); diff --git a/test/unit/sanitiser/_sources.js b/test/unit/sanitiser/_sources.js new file mode 100644 index 00000000..05507a36 --- /dev/null +++ b/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); + } +}; diff --git a/test/unit/sanitiser/_text.js b/test/unit/sanitiser/_text.js index dfb11389..370a4061 100644 --- a/test/unit/sanitiser/_text.js +++ b/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 diff --git a/test/unit/sanitiser/place.js b/test/unit/sanitiser/place.js index 4ef07623..812744a8 100644 --- a/test/unit/sanitiser/place.js +++ b/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' ); diff --git a/test/unit/sanitiser/reverse.js b/test/unit/sanitiser/reverse.js index bf3659b5..3a8bacde 100644 --- a/test/unit/sanitiser/reverse.js +++ b/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(); }); }); diff --git a/test/unit/sanitiser/search.js b/test/unit/sanitiser/search.js index b8dacb7f..c93f51e5 100644 --- a/test/unit/sanitiser/search.js +++ b/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(); }); });