Browse Source

Merge branch 'master' of https://github.com/pelias/api into pelias-intersections

pull/1058/head
Alex Loyko 7 years ago
parent
commit
f6f5f47f03
  1. 11
      .travis.yml
  2. 12
      README.md
  3. 17
      circle.yml
  4. 77
      controller/coarse_reverse.js
  5. 1
      controller/libpostal.js
  6. 2
      controller/place.js
  7. 111
      helper/geojsonify.js
  8. 42
      helper/geojsonify_meta_data.js
  9. 76
      helper/geojsonify_place_details.js
  10. 4
      helper/placeTypes.js
  11. 15
      helper/type_mapping.js
  12. 13
      index.js
  13. 7
      middleware/confidenceScore.js
  14. 1
      middleware/cors.js
  15. 3
      middleware/geocodeJSON.js
  16. 36
      middleware/interpolate.js
  17. 35
      package.json
  18. 14
      query/structured_geocoding.js
  19. 12
      sanitizer/_geo_common.js
  20. 21
      sanitizer/_geonames_deprecation.js
  21. 9
      sanitizer/_text_addressit.js
  22. 10
      service/configurations/PointInPolygon.js
  23. 1
      test/ciao/CORS/headers_GET.coffee
  24. 1
      test/ciao/CORS/headers_OPTIONS.coffee
  25. 157
      test/unit/controller/coarse_reverse.js
  26. 4
      test/unit/controller/libpostal.js
  27. 4
      test/unit/controller/place.js
  28. 4
      test/unit/controller/predicates/is_coarse_reverse.js
  29. 2
      test/unit/controller/search_with_ids.js
  30. 39
      test/unit/fixture/reverse_null_island.js
  31. 39
      test/unit/fixture/reverse_standard.js
  32. 49
      test/unit/fixture/reverse_with_boundary_country.js
  33. 41
      test/unit/fixture/reverse_with_layer_filtering.js
  34. 41
      test/unit/fixture/reverse_with_layer_filtering_non_coarse_subset.js
  35. 46
      test/unit/fixture/reverse_with_source_filtering.js
  36. 71
      test/unit/fixture/search_linguistic_viewport.js
  37. 71
      test/unit/fixture/search_linguistic_viewport_min_diagonal.js
  38. 59
      test/unit/fixture/structured_geocoding/boundary_country.json
  39. 859
      test/unit/fixture/structured_geocoding/fallback.json
  40. 62
      test/unit/fixture/structured_geocoding/linguistic_bbox.json
  41. 65
      test/unit/fixture/structured_geocoding/linguistic_focus.json
  42. 76
      test/unit/fixture/structured_geocoding/linguistic_focus_bbox.json
  43. 65
      test/unit/fixture/structured_geocoding/linguistic_focus_null_island.json
  44. 51
      test/unit/fixture/structured_geocoding/linguistic_only.json
  45. 51
      test/unit/fixture/structured_geocoding/linguistic_viewport.json
  46. 51
      test/unit/fixture/structured_geocoding/linguistic_viewport_min_diagonal.json
  47. 60
      test/unit/fixture/structured_geocoding/postalcode_only.js
  48. 52
      test/unit/fixture/structured_geocoding/with_source_filtering.json
  49. 910
      test/unit/helper/geojsonify.js
  50. 590
      test/unit/helper/geojsonify_place_details.js
  51. 5
      test/unit/helper/type_mapping.js
  52. 32
      test/unit/middleware/confidenceScore.js
  53. 298
      test/unit/middleware/interpolate.js
  54. 7
      test/unit/query/MockQuery.js
  55. 573
      test/unit/query/reverse.js
  56. 48
      test/unit/query/search.js
  57. 663
      test/unit/query/structured_geocoding.js
  58. 1
      test/unit/run.js
  59. 19
      test/unit/sanitizer/_geo_common.js
  60. 46
      test/unit/sanitizer/_geonames_deprecation.js
  61. 16
      test/unit/sanitizer/_layers.js
  62. 56
      test/unit/service/configurations/PointInPolygon.js

11
.travis.yml

@ -9,20 +9,15 @@ matrix:
fast_finish: true fast_finish: true
env: env:
global: global:
- CXX=g++-4.8 - BUILD_LEADER_ID=2
script: npm run travis script: npm run travis
addons:
apt:
sources:
- ubuntu-toolchain-r-test
packages:
- g++-4.8
before_install: before_install:
- npm i -g npm@^3.0.0 - npm i -g npm@^3.0.0
before_script: before_script:
- npm prune - npm prune
after_success: after_success:
- npm run semantic-release - npm install -g npx
- npx -p node@8 npm run semantic-release
branches: branches:
except: except:
- /^v\d+\.\d+\.\d+$/ - /^v\d+\.\d+\.\d+$/

12
README.md

@ -44,7 +44,7 @@ The API recognizes the following properties under the top-level `api` key in you
|`indexName`|*no*|*pelias*|name of the Elasticsearch index to be used when building queries| |`indexName`|*no*|*pelias*|name of the Elasticsearch index to be used when building queries|
|`relativeScores`|*no*|true|if set to true, confidence scores will be normalized, realistically at this point setting this to false is not tested or desirable |`relativeScores`|*no*|true|if set to true, confidence scores will be normalized, realistically at this point setting this to false is not tested or desirable
|`accessLog`|*no*||name of the format to use for access logs; may be any one of the [predefined values](https://github.com/expressjs/morgan#predefined-formats) in the `morgan` package. Defaults to `"common"`; if set to `false`, or an otherwise falsy value, disables access-logging entirely.| |`accessLog`|*no*||name of the format to use for access logs; may be any one of the [predefined values](https://github.com/expressjs/morgan#predefined-formats) in the `morgan` package. Defaults to `"common"`; if set to `false`, or an otherwise falsy value, disables access-logging entirely.|
|`services`|*no*||service definitions for [point-in-polygon](https://github.com/pelias/pip-service) and [placholder](https://github.com/pelias/placeholder) services. If missing (which is not recommended), the point-in-polygon and placeholder services will not be called.| |`services`|*no*||service definitions for [point-in-polygon](https://github.com/pelias/pip-service), and [placeholder](https://github.com/pelias/placeholder), and [interpolation](https://github.com/pelias/interpolation) services. If missing (which is not recommended), the services will not be called.|
|`defaultParameters.focus.point.lon` <br> `defaultParameters.focus.point.lat`|no | |default coordinates for focus point |`defaultParameters.focus.point.lon` <br> `defaultParameters.focus.point.lat`|no | |default coordinates for focus point
Example configuration file would look something like this: Example configuration file would look something like this:
@ -73,6 +73,10 @@ Example configuration file would look something like this:
}, },
"placeholder": { "placeholder": {
"url": "http://myplaceholderservice.com:5000" "url": "http://myplaceholderservice.com:5000"
},
"interpolation": {
"url": "http://myinterpolationservice.com:3000",
"timeout": 2500
} }
} }
"defaultParameters": { "defaultParameters": {
@ -80,12 +84,6 @@ Example configuration file would look something like this:
"focus.point.lon": 21.212121 "focus.point.lon": 21.212121
} }
}, },
"interpolation": {
"client": {
"adapter": "http",
"host": "internal-pelias-interpolation-dev-130430937.us-east-1.elb.amazonaws.com"
}
},
"logger": { "logger": {
"level": "debug" "level": "debug"
} }

17
circle.yml

@ -1,17 +0,0 @@
machine:
ruby:
version: 2.1.2
node:
version: 0.12.2
deployment:
dev:
branch: master
commands:
- git clone git@github.com:mapzen/pelias-deploy.git && cd pelias-deploy && bundle install
- cd pelias-deploy && bundle exec rake deploy:api dev
prod_build:
branch: staging
commands:
- git clone git@github.com:mapzen/pelias-deploy.git && cd pelias-deploy && bundle install
- cd pelias-deploy && bundle exec rake deploy:api prod_build

77
controller/coarse_reverse.js

@ -15,7 +15,11 @@ const coarse_granularities = [
'region', 'region',
'macroregion', 'macroregion',
'dependency', 'dependency',
'country' 'country',
'empire',
'continent',
'ocean',
'marinearea'
]; ];
// remove non-coarse layers and return what's left (or all if empty) // remove non-coarse layers and return what's left (or all if empty)
@ -62,40 +66,50 @@ function synthesizeDoc(results) {
const most_granular_layer = getMostGranularLayerOfResult(_.keys(results)); const most_granular_layer = getMostGranularLayerOfResult(_.keys(results));
const id = results[most_granular_layer][0].id; const id = results[most_granular_layer][0].id;
const doc = new Document('whosonfirst', most_granular_layer, id.toString()); try {
doc.setName('default', results[most_granular_layer][0].name); const doc = new Document('whosonfirst', most_granular_layer, id.toString());
doc.setName('default', results[most_granular_layer][0].name);
// assign the administrative hierarchy // assign the administrative hierarchy
_.keys(results).forEach((layer) => { _.keys(results).forEach((layer) => {
doc.addParent(layer, results[layer][0].name, results[layer][0].id.toString(), results[layer][0].abbr); doc.addParent(layer, results[layer][0].name, results[layer][0].id.toString(), results[layer][0].abbr || undefined);
}); });
// set centroid if available // set centroid if available
if (_.has(results[most_granular_layer][0], 'centroid')) { if (_.has(results[most_granular_layer][0], 'centroid')) {
doc.setCentroid( results[most_granular_layer][0].centroid ); doc.setCentroid( results[most_granular_layer][0].centroid );
} }
// set bounding box if available // set bounding box if available
if (_.has(results[most_granular_layer][0], 'bounding_box')) { if (_.has(results[most_granular_layer][0], 'bounding_box')) {
const parsed_bounding_box = results[most_granular_layer][0].bounding_box.split(',').map(parseFloat); const parsed_bounding_box = results[most_granular_layer][0].bounding_box.split(',').map(parseFloat);
doc.setBoundingBox({ doc.setBoundingBox({
upperLeft: { upperLeft: {
lat: parsed_bounding_box[3], lat: parsed_bounding_box[3],
lon: parsed_bounding_box[0] lon: parsed_bounding_box[0]
}, },
lowerRight: { lowerRight: {
lat: parsed_bounding_box[1], lat: parsed_bounding_box[1],
lon: parsed_bounding_box[2] lon: parsed_bounding_box[2]
} }
}); });
} }
const esDoc = doc.toESDocument();
esDoc.data._id = esDoc._id;
esDoc.data._type = esDoc._type;
return esDoc.data;
const esDoc = doc.toESDocument(); } catch( e ) {
esDoc.data._id = esDoc._id;
esDoc.data._type = esDoc._type;
return esDoc.data;
// an error occurred when generating a new Document
logger.info(`[controller:coarse_reverse][error]`);
logger.error(e);
logger.info(results);
return null;
}
} }
function setup(service, should_execute) { function setup(service, should_execute) {
@ -141,7 +155,10 @@ function setup(service, should_execute) {
// if there's a result at the requested layer(s), synthesize a doc from results // if there's a result at the requested layer(s), synthesize a doc from results
if (hasResultsAtRequestedLayers(applicable_results, effective_layers)) { if (hasResultsAtRequestedLayers(applicable_results, effective_layers)) {
res.data.push(synthesizeDoc(applicable_results)); const doc = synthesizeDoc(applicable_results);
if (doc){
res.data.push(doc);
}
} }
debugLog.stopTimer(req, initialTime); debugLog.stopTimer(req, initialTime);
return next(); return next();

1
controller/libpostal.js

@ -20,6 +20,7 @@ function setup(should_execute) {
parsed_text.country = iso3166.to3(_.toUpper(parsed_text.country)); parsed_text.country = iso3166.to3(_.toUpper(parsed_text.country));
} }
req.clean.parser = 'libpostal';
req.clean.parsed_text = parsed_text; req.clean.parsed_text = parsed_text;
debugLog.push(req, {parsed_text: req.clean.parsed_text}); debugLog.push(req, {parsed_text: req.clean.parsed_text});
} }

2
controller/place.js

@ -38,7 +38,7 @@ function setup( apiConfig, esclient ){
const cmd = req.clean.ids.map( function(id) { const cmd = req.clean.ids.map( function(id) {
return { return {
_index: apiConfig.indexName, _index: apiConfig.indexName,
_type: id.layers, _type: id.layer,
_id: id.id _id: id.id
}; };
}); });

111
helper/geojsonify.js

@ -1,28 +1,32 @@
var GeoJSON = require('geojson'); const GeoJSON = require('geojson');
var extent = require('@mapbox/geojson-extent'); const extent = require('@mapbox/geojson-extent');
var logger = require('pelias-logger').get('api'); const logger = require('pelias-logger').get('geojsonify');
var type_mapping = require('./type_mapping'); const collectDetails = require('./geojsonify_place_details');
var _ = require('lodash'); const _ = require('lodash');
var addDetails = require('./geojsonify_place_details'); const Document = require('pelias-model').Document;
var addMetaData = require('./geojsonify_meta_data');
function geojsonifyPlaces( params, docs ){ function geojsonifyPlaces( params, docs ){
// flatten & expand data for geojson conversion // flatten & expand data for geojson conversion
var geodata = docs const geodata = docs
.map(geojsonifyPlace.bind(null, params)) .filter(doc => {
.filter( function( doc ){ if (!_.has(doc, 'center_point')) {
return !!doc; logger.warn('No doc or center_point property');
}); return false;
} else {
return true;
}
})
.map(geojsonifyPlace.bind(null, params));
// get all the bounding_box corners as well as single points // get all the bounding_box corners as well as single points
// to be used for computing the overall bounding_box for the FeatureCollection // to be used for computing the overall bounding_box for the FeatureCollection
var extentPoints = extractExtentPoints(geodata); const extentPoints = extractExtentPoints(geodata);
// convert to geojson // convert to geojson
var geojson = GeoJSON.parse( geodata, { Point: ['lat', 'lng'] }); const geojson = GeoJSON.parse( geodata, { Point: ['lat', 'lng'] });
var geojsonExtentPoints = GeoJSON.parse( extentPoints, { Point: ['lat', 'lng'] }); const geojsonExtentPoints = GeoJSON.parse( extentPoints, { Point: ['lat', 'lng'] });
// to insert the bbox property at the top level of each feature, it must be done separately after // to insert the bbox property at the top level of each feature, it must be done separately after
// initial geojson construction is finished // initial geojson construction is finished
@ -35,36 +39,29 @@ function geojsonifyPlaces( params, docs ){
} }
function geojsonifyPlace(params, place) { function geojsonifyPlace(params, place) {
// setup the base doc
// something went very wrong const doc = {
if( !place || !place.hasOwnProperty( 'center_point' ) ) { id: place._id,
return warning('No doc or center_point property'); gid: new Document(place.source, place.layer, place._id).getGid(),
layer: place.layer,
source: place.source,
source_id: place.source_id,
bounding_box: place.bounding_box,
lat: parseFloat(place.center_point.lat),
lng: parseFloat(place.center_point.lon)
};
// assign name, logging a warning if it doesn't exist
if (_.has(place, 'name.default')) {
doc.name = place.name.default;
} else {
logger.warn(`doc ${doc.gid} does not contain name.default`);
} }
var output = {}; // assign all the details info into the doc
Object.assign(doc, collectDetails(params, place));
addMetaData(place, output);
addName(place, output);
addDetails(params, place, output);
// map center_point for GeoJSON to work properly
// these should not show up in the final feature properties
output.lat = parseFloat(place.center_point.lat);
output.lng = parseFloat(place.center_point.lon);
return output; return doc;
}
/**
* Validate and add name property
*
* @param {object} src
* @param {object} dst
*/
function addName(src, dst) {
// map name
if( !src.name || !src.name.default ) { return warning(src); }
dst.name = src.name.default;
} }
/** /**
@ -73,12 +70,7 @@ function addName(src, dst) {
* @param {object} geojson * @param {object} geojson
*/ */
function addBBoxPerFeature(geojson) { function addBBoxPerFeature(geojson) {
geojson.features.forEach(function (feature) { geojson.features.forEach(feature => {
if (!feature.properties.hasOwnProperty('bounding_box')) {
return;
}
if (feature.properties.bounding_box) { if (feature.properties.bounding_box) {
feature.bbox = [ feature.bbox = [
feature.properties.bounding_box.min_lon, feature.properties.bounding_box.min_lon,
@ -101,8 +93,8 @@ function addBBoxPerFeature(geojson) {
* @returns {Array} * @returns {Array}
*/ */
function extractExtentPoints(geodata) { function extractExtentPoints(geodata) {
var extentPoints = []; return geodata.reduce((extentPoints, place) => {
geodata.forEach(function (place) { // if there's a bounding_box, use the LL/UR for the extent
if (place.bounding_box) { if (place.bounding_box) {
extentPoints.push({ extentPoints.push({
lng: place.bounding_box.min_lon, lng: place.bounding_box.min_lon,
@ -112,16 +104,20 @@ function extractExtentPoints(geodata) {
lng: place.bounding_box.max_lon, lng: place.bounding_box.max_lon,
lat: place.bounding_box.max_lat lat: place.bounding_box.max_lat
}); });
} }
else { else {
// otherwise, use the point for the extent
extentPoints.push({ extentPoints.push({
lng: place.lng, lng: place.lng,
lat: place.lat lat: place.lat
}); });
} }
}); return extentPoints;
}, []);
return extentPoints;
} }
/** /**
@ -144,15 +140,4 @@ function computeBBox(geojson, geojsonExtentPoints) {
} }
} }
/**
* emit a warning if the doc format is invalid
*
* @note: if you see this error, fix it ASAP!
*/
function warning( doc ) {
console.error( 'error: invalid doc', __filename, doc);
return false; // remove offending doc from results
}
module.exports = geojsonifyPlaces; module.exports = geojsonifyPlaces;

42
helper/geojsonify_meta_data.js

@ -1,42 +0,0 @@
var Document = require('pelias-model').Document;
/**
* Determine and set place id, type, and source
*
* @param {object} src
* @param {object} dst
*/
function addMetaData(src, dst) {
dst.id = src._id;
dst.gid = makeGid(src);
dst.layer = lookupLayer(src);
dst.source = lookupSource(src);
dst.source_id = lookupSourceId(src);
if (src.hasOwnProperty('bounding_box')) {
dst.bounding_box = src.bounding_box;
}
}
/**
* Create a gid from a document
*
* @param {object} src
*/
function makeGid(src) {
var doc = new Document(lookupSource(src), lookupLayer(src), src._id);
return doc.getGid();
}
function lookupSource(src) {
return src.source;
}
function lookupSourceId(src) {
return src.source_id;
}
function lookupLayer(src) {
return src.layer;
}
module.exports = addMetaData;

76
helper/geojsonify_place_details.js

@ -1,9 +1,11 @@
var _ = require('lodash'); 'use strict';
const _ = require('lodash');
// Properties to be copied // Properties to be copied
// If a property is identified as a single string, assume it should be presented as a string in response // If a property is identified as a single string, assume it should be presented as a string in response
// If something other than string is desired, use the following structure: { name: 'category', type: 'array' } // If something other than string is desired, use the following structure: { name: 'category', type: 'array' }
var DETAILS_PROPS = [ const DETAILS_PROPS = [
{ name: 'housenumber', type: 'string' }, { name: 'housenumber', type: 'string' },
{ name: 'street', type: 'string' }, { name: 'street', type: 'string' },
{ name: 'street1', type: 'string' }, { name: 'street1', type: 'string' },
@ -43,68 +45,71 @@ var DETAILS_PROPS = [
{ name: 'borough_a', type: 'string' }, { name: 'borough_a', type: 'string' },
{ name: 'neighbourhood', type: 'string' }, { name: 'neighbourhood', type: 'string' },
{ name: 'neighbourhood_gid', type: 'string' }, { name: 'neighbourhood_gid', type: 'string' },
{ name: 'continent', type: 'string' },
{ name: 'continent_gid', type: 'string' },
{ name: 'continent_a', type: 'string' },
{ name: 'empire', type: 'string', condition: _.negate(hasCountry) },
{ name: 'empire_gid', type: 'string', condition: _.negate(hasCountry) },
{ name: 'empire_a', type: 'string', condition: _.negate(hasCountry) },
{ name: 'ocean', type: 'string' },
{ name: 'ocean_gid', type: 'string' },
{ name: 'ocean_a', type: 'string' },
{ name: 'marinearea', type: 'string' },
{ name: 'marinearea_gid', type: 'string' },
{ name: 'marinearea_a', type: 'string' },
{ name: 'bounding_box', type: 'default' }, { name: 'bounding_box', type: 'default' },
{ name: 'label', type: 'string' }, { name: 'label', type: 'string' },
{ name: 'category', type: 'array', condition: checkCategoryParam } { name: 'category', type: 'array', condition: checkCategoryParam }
]; ];
function checkCategoryParam(params) { // returns true IFF source a country_gid property
return _.isObject(params) && params.hasOwnProperty('categories'); function hasCountry(params, source) {
return source.hasOwnProperty('country_gid');
} }
/** function checkCategoryParam(params) {
* Add details properties return _.isObject(params) && params.hasOwnProperty('categories');
*
* @param {object} params clean query params
* @param {object} src
* @param {object} dst
*/
function addDetails(params, src, dst) {
copyProperties(params, src, DETAILS_PROPS, dst);
} }
/** /**
* Copy specified properties from source to dest. * Collect the specified properties from source into an object and return it
* Ignore missing properties. * Ignore missing properties.
* *
* @param {object} params clean query params * @param {object} params clean query params
* @param {object} source * @param {object} source
* @param {[]} props
* @param {object} dst * @param {object} dst
*/ */
function copyProperties( params, source, props, dst ) { function collectProperties( params, source ) {
props.forEach( function ( prop ) { return DETAILS_PROPS.reduce((result, prop) => {
// if condition isn't met, don't set the property
// if condition isn't met, just return without setting the property if (_.isFunction(prop.condition) && !prop.condition(params, source)) {
if (_.isFunction(prop.condition) && !prop.condition(params)) { return result;
return;
} }
var property = { if ( source.hasOwnProperty( prop.name ) ) {
name: prop.name || prop, let value = null;
type: prop.type || 'default'
};
var value = null;
if ( source.hasOwnProperty( property.name ) ) {
switch (property.type) { switch (prop.type) {
case 'string': case 'string':
value = getStringValue(source[property.name]); value = getStringValue(source[prop.name]);
break; break;
case 'array': case 'array':
value = getArrayValue(source[property.name]); value = getArrayValue(source[prop.name]);
break; break;
// default behavior is to copy property exactly as is // default behavior is to copy property exactly as is
default: default:
value = source[property.name]; value = source[prop.name];
} }
if (_.isNumber(value) || (value && !_.isEmpty(value))) { if (_.isNumber(value) || (value && !_.isEmpty(value))) {
dst[property.name] = value; result[prop.name] = value;
} }
} }
});
return result;
}, {});
} }
function getStringValue(property) { function getStringValue(property) {
@ -125,7 +130,6 @@ function getStringValue(property) {
return _.toString(property); return _.toString(property);
} }
function getArrayValue(property) { function getArrayValue(property) {
// isEmpty check works for all types of values: strings, arrays, objects // isEmpty check works for all types of values: strings, arrays, objects
if (_.isEmpty(property)) { if (_.isEmpty(property)) {
@ -139,4 +143,4 @@ function getArrayValue(property) {
return [property]; return [property];
} }
module.exports = addDetails; module.exports = collectProperties;

4
helper/placeTypes.js

@ -1,4 +1,8 @@
module.exports = [ module.exports = [
'ocean',
'marinearea',
'continent',
'empire',
'country', 'country',
'dependency', 'dependency',
'macroregion', 'macroregion',

15
helper/type_mapping.js

@ -1,5 +1,4 @@
var extend = require('extend'), const _ = require('lodash');
_ = require('lodash');
function addStandardTargetsToAliases(standard, aliases) { function addStandardTargetsToAliases(standard, aliases) {
var combined = _.extend({}, aliases); var combined = _.extend({}, aliases);
@ -49,9 +48,10 @@ var LAYERS_BY_SOURCE = {
openaddresses: [ 'address' ], openaddresses: [ 'address' ],
geonames: [ 'country','macroregion', 'region', 'county','localadmin', geonames: [ 'country','macroregion', 'region', 'county','localadmin',
'locality','borough', 'neighbourhood', 'venue' ], 'locality','borough', 'neighbourhood', 'venue' ],
whosonfirst: [ 'continent', 'country', 'dependency', 'macroregion', 'region', whosonfirst: [ 'continent', 'empire', 'country', 'dependency', 'macroregion', 'region',
'locality', 'localadmin', 'macrocounty', 'county', 'macrohood', 'borough', 'locality', 'localadmin', 'macrocounty', 'county', 'macrohood', 'borough',
'neighbourhood', 'microhood', 'disputed', 'venue', 'postalcode'] 'neighbourhood', 'microhood', 'disputed', 'venue', 'postalcode',
'continent', 'ocean', 'marinearea']
}; };
/* /*
@ -60,9 +60,10 @@ var LAYERS_BY_SOURCE = {
* may have layers that mean the same thing but have a different name * may have layers that mean the same thing but have a different name
*/ */
var LAYER_ALIASES = { var LAYER_ALIASES = {
'coarse': [ 'continent', 'country', 'dependency', 'macroregion', 'region', 'coarse': [ 'continent', 'empire', 'country', 'dependency', 'macroregion',
'locality', 'localadmin', 'macrocounty', 'county', 'macrohood', 'borough', 'region', 'locality', 'localadmin', 'macrocounty', 'county', 'macrohood',
'neighbourhood', 'microhood', 'disputed', 'postalcode' ] 'borough', 'neighbourhood', 'microhood', 'disputed', 'postalcode',
'continent', 'ocean', 'marinearea']
}; };
// create a list of all layers by combining each entry from LAYERS_BY_SOURCE // create a list of all layers by combining each entry from LAYERS_BY_SOURCE

13
index.js

@ -1,7 +1,10 @@
var app = require('./app'), const app = require('./app'),
port = ( process.env.PORT || 3100 ); port = ( process.env.PORT || 3100 ),
host = ( process.env.HOST || undefined );
/** run server on the default setup (single core) **/ const server = app.listen( port, host, () => {
console.log( 'pelias is now running on port ' + port ); // ask server for the actual address and port its listening on
app.listen( port ); const listenAddress = server.address();
console.log( `pelias is now running on ${listenAddress.address}:${listenAddress.port}` );
});

7
middleware/confidenceScore.js

@ -94,7 +94,8 @@ function checkForDealBreakers(req, hit) {
return false; return false;
} }
if (check.assigned(req.clean.parsed_text.state) && hit.parent.region_a && req.clean.parsed_text.state !== hit.parent.region_a[0]) { if (check.assigned(req.clean.parsed_text.state) && check.assigned(hit.parent) &&
hit.parent.region_a && req.clean.parsed_text.state !== hit.parent.region_a[0]) {
logger.debug('[confidence][deal-breaker]: state !== region_a'); logger.debug('[confidence][deal-breaker]: state !== region_a');
return true; return true;
} }
@ -220,8 +221,8 @@ function checkAddress(text, hit) {
res += propMatch(text.number, (hit.address_parts ? hit.address_parts.number : null), false); res += propMatch(text.number, (hit.address_parts ? hit.address_parts.number : null), false);
res += propMatch(text.street, (hit.address_parts ? hit.address_parts.street : null), false); res += propMatch(text.street, (hit.address_parts ? hit.address_parts.street : null), false);
res += propMatch(text.postalcode, (hit.address_parts ? hit.address_parts.zip: null), true); res += propMatch(text.postalcode, (hit.address_parts ? hit.address_parts.zip: null), true);
res += propMatch(text.state, (hit.parent.region_a ? hit.parent.region_a[0] : null), true); res += propMatch(text.state, ((hit.parent && hit.parent.region_a) ? hit.parent.region_a[0] : null), true);
res += propMatch(text.country, (hit.parent.country_a ? hit.parent.country_a[0] :null), true); res += propMatch(text.country, ((hit.parent && hit.parent.country_a) ? hit.parent.country_a[0] :null), true);
res /= checkCount; res /= checkCount;
} }

1
middleware/cors.js

@ -3,7 +3,6 @@ function middleware(req, res, next){
res.header('Access-Control-Allow-Origin', '*'); res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Methods', 'GET, OPTIONS'); res.header('Access-Control-Allow-Methods', 'GET, OPTIONS');
res.header('Access-Control-Allow-Headers', 'X-Requested-With,content-type'); res.header('Access-Control-Allow-Headers', 'X-Requested-With,content-type');
res.header('Access-Control-Allow-Credentials', true);
next(); next();
} }

3
middleware/geocodeJSON.js

@ -1,5 +1,4 @@
var url = require('url'); var url = require('url');
var extend = require('extend');
var geojsonify = require('../helper/geojsonify'); var geojsonify = require('../helper/geojsonify');
var _ = require('lodash'); var _ = require('lodash');
@ -74,7 +73,7 @@ function convertToGeocodeJSON(req, res, next, opts) {
res.body.geocoding.timestamp = new Date().getTime(); res.body.geocoding.timestamp = new Date().getTime();
// convert docs to geojson and merge with geocoding block // convert docs to geojson and merge with geocoding block
extend(res.body, geojsonify(req.clean, res.data || [])); _.extend(res.body, geojsonify(req.clean, res.data || []));
next(); next();
} }

36
middleware/interpolate.js

@ -21,15 +21,34 @@ example response from interpolation web service:
} }
**/ **/
// The interpolation middleware layer uses async.map to iterate over the results
// since the interpolation service only operates on single inputs. The problem
// with async.map is that if a single error is returned then the entire batch
// exits early. This function wraps the service call to intercept the error so
// that async.map never returns an error.
function error_intercepting_service(service, req) {
return (street_result, next) => {
service(req, street_result, (err, interpolation_result) => {
if (err) {
logger.error(`[middleware:interpolation] ${_.defaultTo(err.message, err)}`);
// now that the error has been caught and reported, act as if there was no error
return next(null, null);
}
// no error occurred, so pass along the result
return next(null, interpolation_result);
});
};
}
function setup(service, should_execute) { function setup(service, should_execute) {
return function controller(req, res, next) { return function controller(req, res, next) {
// bail early if the service shouldn't execute
if (!should_execute(req, res)) { if (!should_execute(req, res)) {
return next(); return next();
} }
// bind the service to the req which doesn't change
const req_bound_service = _.partial(service, req);
// only interpolate the street-layer results // only interpolate the street-layer results
// save this off into a separate array so that when docs are annotated // save this off into a separate array so that when docs are annotated
// after the interpolate results are returned, no complicated bookkeeping is needed // after the interpolate results are returned, no complicated bookkeeping is needed
@ -37,12 +56,13 @@ function setup(service, should_execute) {
// perform interpolations asynchronously for all relevant hits // perform interpolations asynchronously for all relevant hits
const start = (new Date()).getTime(); const start = (new Date()).getTime();
async.map(street_results, req_bound_service, (err, interpolation_results) => {
if (err) {
logger.error(`[middleware:interpolation] ${_.defaultTo(err.message, err)}`);
return next();
}
logger.info(`[interpolation] [street_results] count=${street_results.length}`);
// call the interpolation service asynchronously on every street result
async.map(street_results, error_intercepting_service(service, req), (err, interpolation_results) => {
// iterate the interpolation results, mapping back into the source results
interpolation_results.forEach((interpolation_result, idx) => { interpolation_results.forEach((interpolation_result, idx) => {
const source_result = street_results[idx]; const source_result = street_results[idx];

35
package.json

@ -7,7 +7,7 @@
"license": "MIT", "license": "MIT",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
"audit": "npm shrinkwrap; node node_modules/nsp/bin/nspCLI.js audit-shrinkwrap; rm npm-shrinkwrap.json;", "audit": "npm shrinkwrap; node node_modules/nsp/bin/nsp check; rm npm-shrinkwrap.json;",
"ciao": "node node_modules/ciao/bin/ciao -c test/ciao.json test/ciao", "ciao": "node node_modules/ciao/bin/ciao -c test/ciao.json test/ciao",
"coverage": "node_modules/.bin/istanbul cover test/unit/run.js", "coverage": "node_modules/.bin/istanbul cover test/unit/run.js",
"docs": "./bin/generate-docs", "docs": "./bin/generate-docs",
@ -19,7 +19,8 @@
"validate": "npm ls", "validate": "npm ls",
"semantic-release": "semantic-release pre && npm publish && semantic-release post", "semantic-release": "semantic-release pre && npm publish && semantic-release post",
"config": "node -e \"console.log(JSON.stringify(require( 'pelias-config' ).generate(require('./schema')), null, 2))\"", "config": "node -e \"console.log(JSON.stringify(require( 'pelias-config' ).generate(require('./schema')), null, 2))\"",
"check-dependencies": "node_modules/.bin/npm-check --production --ignore pelias-interpolation" "check-dependencies": "node_modules/.bin/npm-check --production --ignore pelias-interpolation",
"prune": "npm prune"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@ -43,27 +44,26 @@
"elasticsearch": "^13.0.0", "elasticsearch": "^13.0.0",
"elasticsearch-exceptions": "0.0.4", "elasticsearch-exceptions": "0.0.4",
"express": "^4.8.8", "express": "^4.8.8",
"extend": "^3.0.1",
"geojson": "^0.5.0", "geojson": "^0.5.0",
"@mapbox/geojson-extent": "^0.3.1", "@mapbox/geojson-extent": "^0.3.1",
"geolib": "^2.0.18", "geolib": "^2.0.18",
"iso-639-3": "^1.0.0", "iso-639-3": "^1.0.0",
"iso3166-1": "^0.3.0", "iso3166-1": "^0.3.0",
"joi": "^11.0.1", "joi": "^12.0.0",
"locale": "^0.1.0", "locale": "^0.1.0",
"lodash": "^4.17.4", "lodash": "^4.17.4",
"markdown": "0.5.0", "markdown": "0.5.0",
"morgan": "^1.8.2", "morgan": "^1.8.2",
"pelias-categories": "1.2.0", "pelias-categories": "1.2.0",
"pelias-config": "2.12.0", "pelias-config": "2.13.0",
"pelias-labels": "1.6.0", "pelias-labels": "1.7.0",
"pelias-logger": "0.2.0", "pelias-logger": "0.3.0",
"pelias-microservice-wrapper": "1.2.0", "pelias-microservice-wrapper": "1.3.0",
"pelias-model": "5.0.1", "pelias-model": "5.2.0",
"pelias-query": "9.1.0", "pelias-query": "9.1.1",
"pelias-sorting": "1.0.1", "pelias-sorting": "1.1.0",
"pelias-text-analyzer": "1.9.1", "pelias-text-analyzer": "1.10.2",
"predicates": "^1.0.1", "predicates": "^2.0.0",
"retry": "^0.10.1", "retry": "^0.10.1",
"stats-lite": "^2.0.4", "stats-lite": "^2.0.4",
"through2": "^2.0.3" "through2": "^2.0.3"
@ -74,12 +74,12 @@
"istanbul": "^0.4.2", "istanbul": "^0.4.2",
"jshint": "^2.5.6", "jshint": "^2.5.6",
"npm-check": "git://github.com/orangejulius/npm-check.git#disable-update-check", "npm-check": "git://github.com/orangejulius/npm-check.git#disable-update-check",
"nsp": "^2.2.0", "nsp": "^3.0.0",
"pelias-mock-logger": "1.1.1", "pelias-mock-logger": "1.2.0",
"precommit-hook": "^3.0.0", "precommit-hook": "^3.0.0",
"proxyquire": "^1.7.10", "proxyquire": "^1.7.10",
"semantic-release": "^7.0.1", "semantic-release": "^8.0.0",
"source-map": "^0.5.6", "source-map": "^0.6.0",
"tap-dot": "1.0.5", "tap-dot": "1.0.5",
"tape": "^4.5.1", "tape": "^4.5.1",
"tmp": "0.0.33", "tmp": "0.0.33",
@ -87,6 +87,7 @@
}, },
"pre-commit": [ "pre-commit": [
"lint", "lint",
"prune",
"validate", "validate",
"test", "test",
"check-dependencies" "check-dependencies"

14
query/structured_geocoding.js

@ -1,12 +1,12 @@
var peliasQuery = require('pelias-query'), const peliasQuery = require('pelias-query'),
defaults = require('./search_defaults'), defaults = require('./search_defaults'),
textParser = require('./text_parser'), textParser = require('./text_parser'),
check = require('check-types'); check = require('check-types');
//------------------------------ //------------------------------
// general-purpose search query // general-purpose search query
//------------------------------ //------------------------------
var structuredQuery = new peliasQuery.layout.StructuredFallbackQuery(); const structuredQuery = new peliasQuery.layout.StructuredFallbackQuery();
// scoring boost // scoring boost
structuredQuery.score( peliasQuery.view.focus_only_function( peliasQuery.view.phrase ) ); structuredQuery.score( peliasQuery.view.focus_only_function( peliasQuery.view.phrase ) );
@ -29,7 +29,7 @@ structuredQuery.filter( peliasQuery.view.categories );
**/ **/
function generateQuery( clean ){ function generateQuery( clean ){
var vs = new peliasQuery.Vars( defaults ); const vs = new peliasQuery.Vars( defaults );
// input text // input text
vs.var( 'input:name', clean.text ); vs.var( 'input:name', clean.text );
@ -95,7 +95,7 @@ function generateQuery( clean ){
textParser( clean.parsed_text, vs ); textParser( clean.parsed_text, vs );
} }
var q = getQuery(vs); const q = getQuery(vs);
// console.log(JSON.stringify(q.body, null, 2)); // console.log(JSON.stringify(q.body, null, 2));

12
sanitizer/_geo_common.js

@ -39,6 +39,18 @@ function sanitize_rect( key_prefix, clean, raw, bbox_is_required ) {
properties.forEach(function(prop) { properties.forEach(function(prop) {
sanitize_coord(prop, clean, raw, true); sanitize_coord(prop, clean, raw, true);
}); });
var min_lat = parseFloat( raw[key_prefix + '.' + 'min_lat'] );
var max_lat = parseFloat( raw[key_prefix + '.' + 'max_lat'] );
if (min_lat > max_lat) {
throw new Error( util.format( 'min_lat is larger than max_lat in \'%s\'', key_prefix ) );
}
var min_lon = parseFloat( raw[key_prefix + '.' + 'min_lon'] );
var max_lon = parseFloat( raw[key_prefix + '.' + 'max_lon'] );
if (min_lon > max_lon) {
throw new Error( util.format( 'min_lon is larger than max_lon in \'%s\'', key_prefix ) );
}
} }
/** /**

21
sanitizer/_geonames_deprecation.js

@ -1,21 +1,32 @@
const _ = require('lodash'); const _ = require('lodash');
/** /**
with the release of coarse reverse as a separate thing and ES reverse only * Now that we have the pip-service, we have stopped supporting returning Geonames for coarse reverse.
handling venues, addresses, and streets, geonames make no sense in the reverse context *
* However, until the `/nearby` endpoint is finalized, we still want to support Geonames for
* _non-coarse_ reverse.
**/ **/
const coarse_reverse_message ='coarse /reverse does not support geonames. See https://github.com/pelias/pelias/issues/675 for more info';
function _sanitize( raw, clean, opts ) { function _sanitize( raw, clean, opts ) {
// error & warning messages // error & warning messages
const messages = { errors: [], warnings: [] }; const messages = { errors: [], warnings: [] };
// return taking no action unless this is a coarse-only reverse request
const non_coarse_layers = ['address', 'street', 'venue'];
const is_coarse_reverse = !_.isEmpty(clean.layers) &&
_.isEmpty(_.intersection(clean.layers, non_coarse_layers));
if (!is_coarse_reverse) {
return messages;
}
if (_.isEqual(clean.sources, ['geonames']) || _.isEqual(clean.sources, ['gn'])) { if (_.isEqual(clean.sources, ['geonames']) || _.isEqual(clean.sources, ['gn'])) {
messages.errors.push('/reverse does not support geonames'); messages.errors.push(coarse_reverse_message);
} else if (_.includes(clean.sources, 'geonames') || _.includes(clean.sources, 'gn')) { } else if (_.includes(clean.sources, 'geonames') || _.includes(clean.sources, 'gn')) {
clean.sources = _.without(clean.sources, 'geonames', 'gn'); clean.sources = _.without(clean.sources, 'geonames', 'gn');
messages.warnings.push('/reverse does not support geonames'); messages.warnings.push(coarse_reverse_message);
} }
return messages; return messages;

9
sanitizer/_text_addressit.js

@ -1,6 +1,5 @@
var check = require('check-types'); var check = require('check-types');
var parser = require('addressit'); var parser = require('addressit');
var extend = require('extend');
var _ = require('lodash'); var _ = require('lodash');
var logger = require('pelias-logger').get('api'); var logger = require('pelias-logger').get('api');
@ -82,8 +81,8 @@ function parse(query) {
var addressWithAdminParts = getAdminPartsBySplittingOnDelim(queryParts); var addressWithAdminParts = getAdminPartsBySplittingOnDelim(queryParts);
var addressWithAddressParts= getAddressParts(queryParts.join(DELIM + ' ')); var addressWithAddressParts= getAddressParts(queryParts.join(DELIM + ' '));
var parsedAddress = extend(addressWithAdminParts, // combine the 2 objects
addressWithAddressParts); _.extend(addressWithAdminParts, addressWithAddressParts);
var address_parts = [ 'name', var address_parts = [ 'name',
'number', 'number',
@ -99,8 +98,8 @@ function parse(query) {
var parsed_text = {}; var parsed_text = {};
address_parts.forEach(function(part){ address_parts.forEach(function(part){
if (parsedAddress[part]) { if (addressWithAdminParts[part]) {
parsed_text[part] = parsedAddress[part]; parsed_text[part] = addressWithAdminParts[part];
} }
}); });

10
service/configurations/PointInPolygon.js

@ -11,6 +11,16 @@ class PointInPolygon extends ServiceConfiguration {
super('pip', o); super('pip', o);
} }
getParameters(req) {
if (_.has(req, 'clean.layers')) {
return {
layers: _.join(req.clean.layers, ',')
};
}
return {};
}
getUrl(req) { getUrl(req) {
// use resolve to eliminate possibility of duplicate /'s in URL // use resolve to eliminate possibility of duplicate /'s in URL
return url.resolve(this.baseUrl, `${req.clean['point.lon']}/${req.clean['point.lat']}`); return url.resolve(this.baseUrl, `${req.clean['point.lon']}/${req.clean['point.lat']}`);

1
test/ciao/CORS/headers_GET.coffee

@ -6,4 +6,3 @@ path: '/'
response.should.have.header 'Access-Control-Allow-Origin','*' response.should.have.header 'Access-Control-Allow-Origin','*'
response.should.have.header 'Access-Control-Allow-Methods','GET, OPTIONS' response.should.have.header 'Access-Control-Allow-Methods','GET, OPTIONS'
response.should.have.header 'Access-Control-Allow-Headers','X-Requested-With,content-type' response.should.have.header 'Access-Control-Allow-Headers','X-Requested-With,content-type'
response.should.have.header 'Access-Control-Allow-Credentials','true'

1
test/ciao/CORS/headers_OPTIONS.coffee

@ -7,4 +7,3 @@ method: 'OPTIONS'
response.should.have.header 'Access-Control-Allow-Origin','*' response.should.have.header 'Access-Control-Allow-Origin','*'
response.should.have.header 'Access-Control-Allow-Methods','GET, OPTIONS' response.should.have.header 'Access-Control-Allow-Methods','GET, OPTIONS'
response.should.have.header 'Access-Control-Allow-Headers','X-Requested-With,content-type' response.should.have.header 'Access-Control-Allow-Headers','X-Requested-With,content-type'
response.should.have.header 'Access-Control-Allow-Credentials','true'

157
test/unit/controller/coarse_reverse.js

@ -210,6 +210,22 @@ module.exports.tests.success_conditions = (test, common) => {
country: [ country: [
{ id: 100, name: 'country name', abbr: 'xyz'}, { id: 100, name: 'country name', abbr: 'xyz'},
{ id: 101, name: 'country name 2'} { id: 101, name: 'country name 2'}
],
empire: [
{ id: 110, name: 'empire name', abbr: 'empire abbr'},
{ id: 111, name: 'empire name 2'}
],
continent: [
{ id: 120, name: 'continent name', abbr: 'continent abbr'},
{ id: 121, name: 'continent name 2'}
],
ocean: [
{ id: 130, name: 'ocean name', abbr: 'ocean abbr'},
{ id: 131, name: 'ocean name 2'}
],
marinearea: [
{ id: 140, name: 'marinearea name', abbr: 'marinearea abbr'},
{ id: 141, name: 'marinearea name 2'}
] ]
}; };
@ -282,7 +298,19 @@ module.exports.tests.success_conditions = (test, common) => {
dependency_a: ['dependency abbr'], dependency_a: ['dependency abbr'],
country: ['country name'], country: ['country name'],
country_id: ['100'], country_id: ['100'],
country_a: ['xyz'] country_a: ['xyz'],
empire: ['empire name'],
empire_id: ['110'],
empire_a: ['empire abbr'],
continent: ['continent name'],
continent_id: ['120'],
continent_a: ['continent abbr'],
ocean: ['ocean name'],
ocean_id: ['130'],
ocean_a: ['ocean abbr'],
marinearea: ['marinearea name'],
marinearea_id: ['140'],
marinearea_a: ['marinearea abbr'],
}, },
center_point: { center_point: {
lat: 12.121212, lat: 12.121212,
@ -822,6 +850,22 @@ module.exports.tests.failure_conditions = (test, common) => {
country: [ country: [
{ id: 100, name: 'country name', abbr: 'xyz'}, { id: 100, name: 'country name', abbr: 'xyz'},
{ id: 101, name: 'country name 2'} { id: 101, name: 'country name 2'}
],
empire: [
{ id: 110, name: 'empire name', abbr: 'empire abbr'},
{ id: 111, name: 'empire name 2'}
],
continent: [
{ id: 120, name: 'continent name', abbr: 'continent abbr'},
{ id: 121, name: 'continent name 2'}
],
ocean: [
{ id: 130, name: 'ocean name', abbr: 'ocean abbr'},
{ id: 131, name: 'ocean name 2'}
],
marinearea: [
{ id: 140, name: 'marinearea name', abbr: 'marinearea abbr'},
{ id: 141, name: 'marinearea name 2'}
] ]
}; };
@ -860,6 +904,117 @@ module.exports.tests.failure_conditions = (test, common) => {
}); });
test('service returns 0 length name', (t) => {
t.plan(6);
const service = (req, callback) => {
t.deepEquals(req, { clean: { layers: ['neighbourhood'] } } );
const results = {
neighbourhood: [
{ id: 20, name: '' }
]
};
callback(undefined, results);
};
const logger = require('pelias-mock-logger')();
const controller = proxyquire('../../../controller/coarse_reverse', {
'pelias-logger': logger
})(service, _.constant(true));
const req = {
clean: {
layers: ['neighbourhood']
}
};
const res = { };
// verify that next was called
const next = () => {
t.pass('next() was called');
};
controller(req, res, next);
const expected = {
meta: {},
data: []
};
t.deepEquals(res, expected);
// logger messages
t.true(logger.hasMessages('info'), '[controller:coarse_reverse][error]');
t.true(logger.hasMessages('error'), 'invalid document type, expecting: truthy, got: ');
t.true(logger.hasMessages('info'), '{ neighbourhood: [ { id: 20, name: \'\' } ] }');
t.end();
});
test('service returns 0 length abbr', (t) => {
t.plan(4);
const service = (req, callback) => {
t.deepEquals(req, { clean: { layers: ['neighbourhood'] } } );
const results = {
neighbourhood: [
{ id: 20, name: 'Example', abbr: '' }
]
};
callback(undefined, results);
};
const logger = require('pelias-mock-logger')();
const controller = proxyquire('../../../controller/coarse_reverse', {
'pelias-logger': logger
})(service, _.constant(true));
const req = {
clean: {
layers: ['neighbourhood']
}
};
const res = { };
// verify that next was called
const next = () => {
t.pass('next() was called');
};
controller(req, res, next);
const expected = {
meta: {},
data: [{
name: { default: 'Example' },
phrase: { default: 'Example' },
parent: {
neighbourhood: [ 'Example' ],
neighbourhood_id: [ '20' ],
neighbourhood_a: [ null ]
},
source: 'whosonfirst',
layer: 'neighbourhood',
source_id: '20',
_id: '20',
_type: 'neighbourhood'
}]
};
t.deepEquals(res, expected);
t.notOk(logger.hasErrorMessages());
t.end();
});
}; };
module.exports.all = (tape, common) => { module.exports.all = (tape, common) => {

4
test/unit/controller/libpostal.js

@ -149,6 +149,7 @@ module.exports.tests.parse_is_called = (test, common) => {
controller(req, res, () => { controller(req, res, () => {
t.deepEquals(req, { t.deepEquals(req, {
clean: { clean: {
parser: 'libpostal',
parsed_text: 'replacement parsed_text' parsed_text: 'replacement parsed_text'
} }
}); });
@ -184,6 +185,7 @@ module.exports.tests.iso2_conversion = (test, common) => {
controller(req, res, () => { controller(req, res, () => {
t.deepEquals(req, { t.deepEquals(req, {
clean: { clean: {
parser: 'libpostal',
parsed_text: { parsed_text: {
locality: 'this is the locality' locality: 'this is the locality'
} }
@ -223,6 +225,7 @@ module.exports.tests.iso2_conversion = (test, common) => {
controller(req, res, () => { controller(req, res, () => {
t.deepEquals(req, { t.deepEquals(req, {
clean: { clean: {
parser: 'libpostal',
parsed_text: { parsed_text: {
country: 'unknown country code' country: 'unknown country code'
} }
@ -265,6 +268,7 @@ module.exports.tests.iso2_conversion = (test, common) => {
controller(req, res, () => { controller(req, res, () => {
t.deepEquals(req, { t.deepEquals(req, {
clean: { clean: {
parser: 'libpostal',
parsed_text: { parsed_text: {
country: 'ISO3 COUNTRY CODE' country: 'ISO3 COUNTRY CODE'
} }

4
test/unit/controller/place.js

@ -51,11 +51,11 @@ module.exports.tests.success = (test, common) => {
ids: [ ids: [
{ {
id: 'id1', id: 'id1',
layers: 'layer1' layer: 'layer1'
}, },
{ {
id: 'id2', id: 'id2',
layers: 'layer2' layer: 'layer2'
} }
] ]
}, },

4
test/unit/controller/predicates/is_coarse_reverse.js

@ -17,7 +17,9 @@ const coarse_layers = [
'borough', 'borough',
'neighbourhood', 'neighbourhood',
'microhood', 'microhood',
'disputed' 'disputed',
'ocean',
'marinearea'
]; ];
module.exports.tests = {}; module.exports.tests = {};

2
test/unit/controller/search_with_ids.js

@ -388,7 +388,7 @@ module.exports.tests.service_errors = (test, common) => {
const next = () => { const next = () => {
t.deepEqual(logger.getInfoMessages(), [ t.deepEqual(logger.getInfoMessages(), [
'[req]', '[req] endpoint=undefined {}',
'request timed out on attempt 1, retrying', 'request timed out on attempt 1, retrying',
'request timed out on attempt 2, retrying', 'request timed out on attempt 2, retrying',
'request timed out on attempt 3, retrying' 'request timed out on attempt 3, retrying'

39
test/unit/fixture/reverse_null_island.js

@ -1,39 +0,0 @@
var vs = require('../../../query/reverse_defaults');
module.exports = {
'query': {
'bool': {
'filter': [{
'geo_distance': {
'distance': '3km',
'distance_type': 'plane',
'optimize_bbox': 'indexed',
'center_point': {
'lat': 0,
'lon': 0
}
}
},
{
'terms': {
'layer': ['venue', 'address', 'street']
}
}]
}
},
'sort': [
'_score',
{
'_geo_distance': {
'center_point': {
'lat': 0,
'lon': 0
},
'order': 'asc',
'distance_type': 'plane'
}
}
],
'size': vs.size,
'track_scores': true
};

39
test/unit/fixture/reverse_standard.js

@ -1,39 +0,0 @@
var vs = require('../../../query/reverse_defaults');
module.exports = {
'query': {
'bool': {
'filter': [{
'geo_distance': {
'distance': '3km',
'distance_type': 'plane',
'optimize_bbox': 'indexed',
'center_point': {
'lat': 29.49136,
'lon': -82.50622
}
}
},
{
'terms': {
'layer': ['venue', 'address', 'street']
}
}]
}
},
'sort': [
'_score',
{
'_geo_distance': {
'center_point': {
'lat': 29.49136,
'lon': -82.50622
},
'order': 'asc',
'distance_type': 'plane'
}
}
],
'size': vs.size,
'track_scores': true
};

49
test/unit/fixture/reverse_with_boundary_country.js

@ -1,49 +0,0 @@
var vs = require('../../../query/reverse_defaults');
module.exports = {
'query': {
'bool': {
'must': [
{
'match': {
'parent.country_a': {
'analyzer': 'standard',
'query': 'ABC'
}
}
}
],
'filter': [{
'geo_distance': {
'distance': '3km',
'distance_type': 'plane',
'optimize_bbox': 'indexed',
'center_point': {
'lat': 29.49136,
'lon': -82.50622
}
}
},
{
'terms': {
'layer': ['venue', 'address', 'street']
}
}]
}
},
'sort': [
'_score',
{
'_geo_distance': {
'center_point': {
'lat': 29.49136,
'lon': -82.50622
},
'order': 'asc',
'distance_type': 'plane'
}
}
],
'size': vs.size,
'track_scores': true
};

41
test/unit/fixture/reverse_with_layer_filtering.js

@ -1,41 +0,0 @@
var vs = require('../../../query/reverse_defaults');
module.exports = {
'query': {
'bool': {
'filter': [
{
'geo_distance': {
'distance': '3km',
'distance_type': 'plane',
'optimize_bbox': 'indexed',
'center_point': {
'lat': 29.49136,
'lon': -82.50622
}
}
},
{
'terms': {
'layer': ['venue', 'address', 'street']
}
}
]
}
},
'sort': [
'_score',
{
'_geo_distance': {
'center_point': {
'lat': 29.49136,
'lon': -82.50622
},
'order': 'asc',
'distance_type': 'plane'
}
}
],
'size': vs.size,
'track_scores': true
};

41
test/unit/fixture/reverse_with_layer_filtering_non_coarse_subset.js

@ -1,41 +0,0 @@
var vs = require('../../../query/reverse_defaults');
module.exports = {
'query': {
'bool': {
'filter': [
{
'geo_distance': {
'distance': '3km',
'distance_type': 'plane',
'optimize_bbox': 'indexed',
'center_point': {
'lat': 29.49136,
'lon': -82.50622
}
}
},
{
'terms': {
'layer': ['venue', 'street']
}
}
]
}
},
'sort': [
'_score',
{
'_geo_distance': {
'center_point': {
'lat': 29.49136,
'lon': -82.50622
},
'order': 'asc',
'distance_type': 'plane'
}
}
],
'size': vs.size,
'track_scores': true
};

46
test/unit/fixture/reverse_with_source_filtering.js

@ -1,46 +0,0 @@
var vs = require('../../../query/reverse_defaults');
module.exports = {
'query': {
'bool': {
'filter': [
{
'geo_distance': {
'distance': '3km',
'distance_type': 'plane',
'optimize_bbox': 'indexed',
'center_point': {
'lat': 29.49136,
'lon': -82.50622
}
}
},
{
'terms': {
'source': ['test']
}
},
{
'terms': {
'layer': ['venue', 'address', 'street']
}
}
]
}
},
'sort': [
'_score',
{
'_geo_distance': {
'center_point': {
'lat': 29.49136,
'lon': -82.50622
},
'order': 'asc',
'distance_type': 'plane'
}
}
],
'size': vs.size,
'track_scores': true
};

71
test/unit/fixture/search_linguistic_viewport.js

@ -1,71 +0,0 @@
module.exports = {
'query': {
'function_score': {
'query': {
'bool': {
'minimum_should_match': 1,
'should': [
{
'bool': {
'_name': 'fallback.street',
'boost': 5,
'must': [
{
'match_phrase': {
'address_parts.street': 'street value'
}
}
],
'should': [],
'filter': {
'term': {
'layer': 'street'
}
}
}
}
],
'filter': {
'bool': {
'must': [
{
'terms': {
'layer': [
'test'
]
}
}
]
}
}
}
},
'max_boost': 20,
'functions': [
{
'field_value_factor': {
'modifier': 'log1p',
'field': 'popularity',
'missing': 1
},
'weight': 1
},
{
'field_value_factor': {
'modifier': 'log1p',
'field': 'population',
'missing': 1
},
'weight': 2
}
],
'score_mode': 'avg',
'boost_mode': 'multiply'
}
},
'size': 10,
'track_scores': true,
'sort': [
'_score'
]
};

71
test/unit/fixture/search_linguistic_viewport_min_diagonal.js

@ -1,71 +0,0 @@
module.exports = {
'query': {
'function_score': {
'query': {
'bool': {
'minimum_should_match': 1,
'should': [
{
'bool': {
'_name': 'fallback.street',
'boost': 5,
'must': [
{
'match_phrase': {
'address_parts.street': 'street value'
}
}
],
'should': [],
'filter': {
'term': {
'layer': 'street'
}
}
}
}
],
'filter': {
'bool': {
'must': [
{
'terms': {
'layer': [
'test'
]
}
}
]
}
}
}
},
'max_boost': 20,
'functions': [
{
'field_value_factor': {
'modifier': 'log1p',
'field': 'popularity',
'missing': 1
},
'weight': 1
},
{
'field_value_factor': {
'modifier': 'log1p',
'field': 'population',
'missing': 1
},
'weight': 2
}
],
'score_mode': 'avg',
'boost_mode': 'multiply'
}
},
'size': 10,
'track_scores': true,
'sort': [
'_score'
]
};

59
test/unit/fixture/structured_geocoding/boundary_country.json

@ -1,59 +0,0 @@
{
"query": {
"function_score": {
"query": {
"bool": {
"minimum_should_match": 1,
"should": [],
"filter": {
"bool": {
"must": [
{
"match": {
"parent.country_a": {
"analyzer": "standard",
"query": "ABC"
}
}
},
{
"terms": {
"layer": [
"test"
]
}
}
]
}
}
}
},
"max_boost": 20,
"functions": [
{
"field_value_factor": {
"modifier": "log1p",
"field": "popularity",
"missing": 1
},
"weight": 1
},
{
"field_value_factor": {
"modifier": "log1p",
"field": "population",
"missing": 1
},
"weight": 2
}
],
"score_mode": "avg",
"boost_mode": "multiply"
}
},
"sort": [
"_score"
],
"size": 10,
"track_scores": true
}

859
test/unit/fixture/structured_geocoding/fallback.json

@ -1,859 +0,0 @@
{
"query": {
"function_score": {
"query": {
"bool": {
"minimum_should_match": 1,
"should": [
{
"bool": {
"_name": "fallback.venue",
"must": [
{
"multi_match": {
"query": "query value",
"type": "phrase",
"fields": [
"phrase.default",
"category"
]
}
},
{
"multi_match": {
"query": "neighbourhood value",
"type": "phrase",
"fields": [
"parent.neighbourhood",
"parent.neighbourhood_a"
]
}
},
{
"multi_match": {
"query": "borough value",
"type": "phrase",
"fields": [
"parent.borough",
"parent.borough_a"
]
}
},
{
"multi_match": {
"query": "city value",
"type": "phrase",
"fields": [
"parent.locality",
"parent.locality_a",
"parent.localadmin",
"parent.localadmin_a"
]
}
},
{
"multi_match": {
"query": "county value",
"type": "phrase",
"fields": [
"parent.county",
"parent.county_a",
"parent.macrocounty",
"parent.macrocounty_a"
]
}
},
{
"multi_match": {
"query": "state value",
"type": "phrase",
"fields": [
"parent.region",
"parent.region_a",
"parent.macroregion",
"parent.macroregion_a"
]
}
},
{
"multi_match": {
"query": "country value",
"type": "phrase",
"fields": [
"parent.country",
"parent.country_a",
"parent.dependency",
"parent.dependency_a"
]
}
}
],
"filter": {
"term": {
"layer": "venue"
}
}
}
},
{
"bool": {
"_name": "fallback.address",
"must": [
{
"match_phrase": {
"address_parts.number": "number value"
}
},
{
"match_phrase": {
"address_parts.street": "street value"
}
},
{
"multi_match": {
"query": "neighbourhood value",
"type": "phrase",
"fields": [
"parent.neighbourhood",
"parent.neighbourhood_a"
]
}
},
{
"multi_match": {
"query": "borough value",
"type": "phrase",
"fields": [
"parent.borough",
"parent.borough_a"
]
}
},
{
"multi_match": {
"query": "city value",
"type": "phrase",
"fields": [
"parent.locality",
"parent.locality_a",
"parent.localadmin",
"parent.localadmin_a"
]
}
},
{
"multi_match": {
"query": "county value",
"type": "phrase",
"fields": [
"parent.county",
"parent.county_a",
"parent.macrocounty",
"parent.macrocounty_a"
]
}
},
{
"multi_match": {
"query": "state value",
"type": "phrase",
"fields": [
"parent.region",
"parent.region_a",
"parent.macroregion",
"parent.macroregion_a"
]
}
},
{
"multi_match": {
"query": "country value",
"type": "phrase",
"fields": [
"parent.country",
"parent.country_a",
"parent.dependency",
"parent.dependency_a"
]
}
}
],
"should": [
{
"match_phrase": {
"address_parts.zip": "postalcode value"
}
}
],
"filter": {
"term": {
"layer": "address"
}
},
"boost": 10
}
},
{
"bool": {
"_name": "fallback.street",
"must": [
{
"match_phrase": {
"address_parts.street": "street value"
}
},
{
"multi_match": {
"query": "neighbourhood value",
"type": "phrase",
"fields": [
"parent.neighbourhood",
"parent.neighbourhood_a"
]
}
},
{
"multi_match": {
"query": "borough value",
"type": "phrase",
"fields": [
"parent.borough",
"parent.borough_a"
]
}
},
{
"multi_match": {
"query": "city value",
"type": "phrase",
"fields": [
"parent.locality",
"parent.locality_a",
"parent.localadmin",
"parent.localadmin_a"
]
}
},
{
"multi_match": {
"query": "county value",
"type": "phrase",
"fields": [
"parent.county",
"parent.county_a",
"parent.macrocounty",
"parent.macrocounty_a"
]
}
},
{
"multi_match": {
"query": "state value",
"type": "phrase",
"fields": [
"parent.region",
"parent.region_a",
"parent.macroregion",
"parent.macroregion_a"
]
}
},
{
"multi_match": {
"query": "country value",
"type": "phrase",
"fields": [
"parent.country",
"parent.country_a",
"parent.dependency",
"parent.dependency_a"
]
}
}
],
"should": [
{
"match_phrase": {
"address_parts.zip": "postalcode value"
}
}
],
"filter": {
"term": {
"layer": "street"
}
},
"boost": 5
}
},
{
"bool": {
"_name": "fallback.postalcode",
"must": [
{
"multi_match": {
"query": "postalcode value",
"type": "phrase",
"fields": [
"parent.postalcode"
]
}
},
{
"multi_match": {
"query": "city value",
"type": "phrase",
"fields": [
"parent.locality",
"parent.locality_a",
"parent.localadmin",
"parent.localadmin_a"
]
}
},
{
"multi_match": {
"query": "county value",
"type": "phrase",
"fields": [
"parent.county",
"parent.county_a",
"parent.macrocounty",
"parent.macrocounty_a"
]
}
},
{
"multi_match": {
"query": "state value",
"type": "phrase",
"fields": [
"parent.region",
"parent.region_a",
"parent.macroregion",
"parent.macroregion_a"
]
}
},
{
"multi_match": {
"query": "country value",
"type": "phrase",
"fields": [
"parent.country",
"parent.country_a",
"parent.dependency",
"parent.dependency_a"
]
}
}
],
"filter": {
"term": {
"layer": "postalcode"
}
}
}
},
{
"bool": {
"_name": "fallback.neighbourhood",
"must": [
{
"multi_match": {
"query": "neighbourhood value",
"type": "phrase",
"fields": [
"parent.neighbourhood",
"parent.neighbourhood_a"
]
}
},
{
"multi_match": {
"query": "borough value",
"type": "phrase",
"fields": [
"parent.borough",
"parent.borough_a"
]
}
},
{
"multi_match": {
"query": "city value",
"type": "phrase",
"fields": [
"parent.locality",
"parent.locality_a",
"parent.localadmin",
"parent.localadmin_a"
]
}
},
{
"multi_match": {
"query": "county value",
"type": "phrase",
"fields": [
"parent.county",
"parent.county_a",
"parent.macrocounty",
"parent.macrocounty_a"
]
}
},
{
"multi_match": {
"query": "state value",
"type": "phrase",
"fields": [
"parent.region",
"parent.region_a",
"parent.macroregion",
"parent.macroregion_a"
]
}
},
{
"multi_match": {
"query": "country value",
"type": "phrase",
"fields": [
"parent.country",
"parent.country_a",
"parent.dependency",
"parent.dependency_a"
]
}
}
],
"filter": {
"term": {
"layer": "neighbourhood"
}
}
}
},
{
"bool": {
"_name": "fallback.borough",
"must": [
{
"multi_match": {
"query": "borough value",
"type": "phrase",
"fields": [
"parent.borough",
"parent.borough_a"
]
}
},
{
"multi_match": {
"query": "city value",
"type": "phrase",
"fields": [
"parent.locality",
"parent.locality_a",
"parent.localadmin",
"parent.localadmin_a"
]
}
},
{
"multi_match": {
"query": "county value",
"type": "phrase",
"fields": [
"parent.county",
"parent.county_a",
"parent.macrocounty",
"parent.macrocounty_a"
]
}
},
{
"multi_match": {
"query": "state value",
"type": "phrase",
"fields": [
"parent.region",
"parent.region_a",
"parent.macroregion",
"parent.macroregion_a"
]
}
},
{
"multi_match": {
"query": "country value",
"type": "phrase",
"fields": [
"parent.country",
"parent.country_a",
"parent.dependency",
"parent.dependency_a"
]
}
}
],
"filter": {
"term": {
"layer": "borough"
}
}
}
},
{
"bool": {
"_name": "fallback.locality",
"must": [
{
"multi_match": {
"query": "city value",
"type": "phrase",
"fields": [
"parent.locality",
"parent.locality_a"
]
}
},
{
"multi_match": {
"query": "county value",
"type": "phrase",
"fields": [
"parent.county",
"parent.county_a",
"parent.macrocounty",
"parent.macrocounty_a"
]
}
},
{
"multi_match": {
"query": "state value",
"type": "phrase",
"fields": [
"parent.region",
"parent.region_a",
"parent.macroregion",
"parent.macroregion_a"
]
}
},
{
"multi_match": {
"query": "country value",
"type": "phrase",
"fields": [
"parent.country",
"parent.country_a",
"parent.dependency",
"parent.dependency_a"
]
}
}
],
"filter": {
"term": {
"layer": "locality"
}
}
}
},
{
"bool": {
"_name": "fallback.localadmin",
"must": [
{
"multi_match": {
"query": "city value",
"type": "phrase",
"fields": [
"parent.localadmin",
"parent.localadmin_a"
]
}
},
{
"multi_match": {
"query": "county value",
"type": "phrase",
"fields": [
"parent.county",
"parent.county_a",
"parent.macrocounty",
"parent.macrocounty_a"
]
}
},
{
"multi_match": {
"query": "state value",
"type": "phrase",
"fields": [
"parent.region",
"parent.region_a",
"parent.macroregion",
"parent.macroregion_a"
]
}
},
{
"multi_match": {
"query": "country value",
"type": "phrase",
"fields": [
"parent.country",
"parent.country_a",
"parent.dependency",
"parent.dependency_a"
]
}
}
],
"filter": {
"term": {
"layer": "localadmin"
}
}
}
},
{
"bool": {
"_name": "fallback.county",
"must": [
{
"multi_match": {
"query": "county value",
"type": "phrase",
"fields": [
"parent.county",
"parent.county_a"
]
}
},
{
"multi_match": {
"query": "state value",
"type": "phrase",
"fields": [
"parent.region",
"parent.region_a",
"parent.macroregion",
"parent.macroregion_a"
]
}
},
{
"multi_match": {
"query": "country value",
"type": "phrase",
"fields": [
"parent.country",
"parent.country_a",
"parent.dependency",
"parent.dependency_a"
]
}
}
],
"filter": {
"term": {
"layer": "county"
}
}
}
},
{
"bool": {
"_name": "fallback.macrocounty",
"must": [
{
"multi_match": {
"query": "county value",
"type": "phrase",
"fields": [
"parent.macrocounty",
"parent.macrocounty_a"
]
}
},
{
"multi_match": {
"query": "state value",
"type": "phrase",
"fields": [
"parent.region",
"parent.region_a",
"parent.macroregion",
"parent.macroregion_a"
]
}
},
{
"multi_match": {
"query": "country value",
"type": "phrase",
"fields": [
"parent.country",
"parent.country_a",
"parent.dependency",
"parent.dependency_a"
]
}
}
],
"filter": {
"term": {
"layer": "macrocounty"
}
}
}
},
{
"bool": {
"_name": "fallback.region",
"must": [
{
"multi_match": {
"query": "state value",
"type": "phrase",
"fields": [
"parent.region",
"parent.region_a"
]
}
},
{
"multi_match": {
"query": "country value",
"type": "phrase",
"fields": [
"parent.country",
"parent.country_a",
"parent.dependency",
"parent.dependency_a"
]
}
}
],
"filter": {
"term": {
"layer": "region"
}
}
}
},
{
"bool": {
"_name": "fallback.macroregion",
"must": [
{
"multi_match": {
"query": "state value",
"type": "phrase",
"fields": [
"parent.macroregion",
"parent.macroregion_a"
]
}
},
{
"multi_match": {
"query": "country value",
"type": "phrase",
"fields": [
"parent.country",
"parent.country_a",
"parent.dependency",
"parent.dependency_a"
]
}
}
],
"filter": {
"term": {
"layer": "macroregion"
}
}
}
},
{
"bool": {
"_name": "fallback.dependency",
"must": [
{
"multi_match": {
"query": "country value",
"type": "phrase",
"fields": [
"parent.dependency",
"parent.dependency_a"
]
}
}
],
"filter": {
"term": {
"layer": "dependency"
}
}
}
},
{
"bool": {
"_name": "fallback.country",
"must": [
{
"multi_match": {
"query": "country value",
"type": "phrase",
"fields": [
"parent.country",
"parent.country_a"
]
}
}
],
"filter": {
"term": {
"layer": "country"
}
}
}
}
]
}
},
"max_boost": 20,
"functions": [
{
"field_value_factor": {
"modifier": "log1p",
"field": "popularity",
"missing": 1
},
"weight": 1
},
{
"field_value_factor": {
"modifier": "log1p",
"field": "population",
"missing": 1
},
"weight": 2
}
],
"score_mode": "avg",
"boost_mode": "multiply"
}
},
"sort": [
"_score"
],
"size": 20,
"track_scores": true
}

62
test/unit/fixture/structured_geocoding/linguistic_bbox.json

@ -1,62 +0,0 @@
{
"query": {
"function_score": {
"query": {
"bool": {
"minimum_should_match": 1,
"should": [],
"filter": {
"bool": {
"must": [
{
"geo_bounding_box": {
"type": "indexed",
"center_point": {
"top": 11.51,
"right": -61.84,
"bottom": 47.47,
"left": -103.16
}
}
},
{
"terms": {
"layer": [
"test"
]
}
}
]
}
}
}
},
"max_boost": 20,
"functions": [
{
"field_value_factor": {
"modifier": "log1p",
"field": "popularity",
"missing": 1
},
"weight": 1
},
{
"field_value_factor": {
"modifier": "log1p",
"field": "population",
"missing": 1
},
"weight": 2
}
],
"score_mode": "avg",
"boost_mode": "multiply"
}
},
"sort": [
"_score"
],
"size": 10,
"track_scores": true
}

65
test/unit/fixture/structured_geocoding/linguistic_focus.json

@ -1,65 +0,0 @@
{
"query": {
"function_score": {
"query": {
"bool": {
"minimum_should_match": 1,
"should": [],
"filter": {
"bool": {
"must": [
{
"terms": {
"layer": [
"test"
]
}
}
]
}
}
}
},
"max_boost": 20,
"functions": [
{
"weight": 2,
"linear": {
"center_point": {
"origin": {
"lat": 29.49136,
"lon": -82.50622
},
"offset": "0km",
"scale": "50km",
"decay": 0.5
}
}
},
{
"field_value_factor": {
"modifier": "log1p",
"field": "popularity",
"missing": 1
},
"weight": 1
},
{
"field_value_factor": {
"modifier": "log1p",
"field": "population",
"missing": 1
},
"weight": 2
}
],
"score_mode": "avg",
"boost_mode": "multiply"
}
},
"sort": [
"_score"
],
"size": 10,
"track_scores": true
}

76
test/unit/fixture/structured_geocoding/linguistic_focus_bbox.json

@ -1,76 +0,0 @@
{
"query": {
"function_score": {
"query": {
"bool": {
"minimum_should_match": 1,
"should": [],
"filter": {
"bool": {
"must": [
{
"geo_bounding_box": {
"type": "indexed",
"center_point": {
"top": 11.51,
"right": -61.84,
"bottom": 47.47,
"left": -103.16
}
}
},
{
"terms": {
"layer": [
"test"
]
}
}
]
}
}
}
},
"max_boost": 20,
"functions": [
{
"weight": 2,
"linear": {
"center_point": {
"origin": {
"lat": 29.49136,
"lon": -82.50622
},
"offset": "0km",
"scale": "50km",
"decay": 0.5
}
}
},
{
"field_value_factor": {
"modifier": "log1p",
"field": "popularity",
"missing": 1
},
"weight": 1
},
{
"field_value_factor": {
"modifier": "log1p",
"field": "population",
"missing": 1
},
"weight": 2
}
],
"score_mode": "avg",
"boost_mode": "multiply"
}
},
"sort": [
"_score"
],
"size": 10,
"track_scores": true
}

65
test/unit/fixture/structured_geocoding/linguistic_focus_null_island.json

@ -1,65 +0,0 @@
{
"query": {
"function_score": {
"query": {
"bool": {
"minimum_should_match": 1,
"should": [],
"filter": {
"bool": {
"must": [
{
"terms": {
"layer": [
"test"
]
}
}
]
}
}
}
},
"max_boost": 20,
"functions": [
{
"weight": 2,
"linear": {
"center_point": {
"origin": {
"lat": 0,
"lon": 0
},
"offset": "0km",
"scale": "50km",
"decay": 0.5
}
}
},
{
"field_value_factor": {
"modifier": "log1p",
"field": "popularity",
"missing": 1
},
"weight": 1
},
{
"field_value_factor": {
"modifier": "log1p",
"field": "population",
"missing": 1
},
"weight": 2
}
],
"score_mode": "avg",
"boost_mode": "multiply"
}
},
"sort": [
"_score"
],
"size": 10,
"track_scores": true
}

51
test/unit/fixture/structured_geocoding/linguistic_only.json

@ -1,51 +0,0 @@
{
"query": {
"function_score": {
"query": {
"bool": {
"minimum_should_match": 1,
"should": [],
"filter": {
"bool": {
"must": [
{
"terms": {
"layer": [
"test"
]
}
}
]
}
}
}
},
"max_boost": 20,
"functions": [
{
"field_value_factor": {
"modifier": "log1p",
"field": "popularity",
"missing": 1
},
"weight": 1
},
{
"field_value_factor": {
"modifier": "log1p",
"field": "population",
"missing": 1
},
"weight": 2
}
],
"score_mode": "avg",
"boost_mode": "multiply"
}
},
"sort": [
"_score"
],
"size": 10,
"track_scores": true
}

51
test/unit/fixture/structured_geocoding/linguistic_viewport.json

@ -1,51 +0,0 @@
{
"query": {
"function_score": {
"query": {
"bool": {
"minimum_should_match": 1,
"should": [],
"filter": {
"bool": {
"must": [
{
"terms": {
"layer": [
"test"
]
}
}
]
}
}
}
},
"max_boost": 20,
"functions": [
{
"field_value_factor": {
"modifier": "log1p",
"field": "popularity",
"missing": 1
},
"weight": 1
},
{
"field_value_factor": {
"modifier": "log1p",
"field": "population",
"missing": 1
},
"weight": 2
}
],
"score_mode": "avg",
"boost_mode": "multiply"
}
},
"sort": [
"_score"
],
"size": 10,
"track_scores": true
}

51
test/unit/fixture/structured_geocoding/linguistic_viewport_min_diagonal.json

@ -1,51 +0,0 @@
{
"query": {
"function_score": {
"query": {
"bool": {
"minimum_should_match": 1,
"should": [],
"filter": {
"bool": {
"must": [
{
"terms": {
"layer": [
"test"
]
}
}
]
}
}
}
},
"max_boost": 20,
"functions": [
{
"field_value_factor": {
"modifier": "log1p",
"field": "popularity",
"missing": 1
},
"weight": 1
},
{
"field_value_factor": {
"modifier": "log1p",
"field": "population",
"missing": 1
},
"weight": 2
}
],
"score_mode": "avg",
"boost_mode": "multiply"
}
},
"sort": [
"_score"
],
"size": 10,
"track_scores": true
}

60
test/unit/fixture/structured_geocoding/postalcode_only.js

@ -1,60 +0,0 @@
module.exports = {
'query': {
'function_score': {
'query': {
'bool': {
'minimum_should_match': 1,
'should': [
{
'bool': {
'_name': 'fallback.postalcode',
'must': [
{
'multi_match': {
'query': 'postalcode value',
'type': 'phrase',
'fields': [
'parent.postalcode'
]
}
}
],
'filter': {
'term': {
'layer': 'postalcode'
}
}
}
}
]
}
},
'max_boost': 20,
'functions': [
{
'field_value_factor': {
'modifier': 'log1p',
'field': 'popularity',
'missing': 1
},
'weight': 1
},
{
'field_value_factor': {
'modifier': 'log1p',
'field': 'population',
'missing': 1
},
'weight': 2
}
],
'score_mode': 'avg',
'boost_mode': 'multiply'
}
},
'size': 20,
'track_scores': true,
'sort': [
'_score'
]
};

52
test/unit/fixture/structured_geocoding/with_source_filtering.json

@ -1,52 +0,0 @@
{
"query": {
"function_score": {
"query": {
"bool": {
"minimum_should_match": 1,
"should": [
],
"filter": {
"bool": {
"must": [
{
"terms": {
"source": [
"test_source"
]
}
}
]
}
}
}
},
"max_boost": 20,
"functions": [
{
"field_value_factor": {
"modifier": "log1p",
"field": "popularity",
"missing": 1
},
"weight": 1
},
{
"field_value_factor": {
"modifier": "log1p",
"field": "population",
"missing": 1
},
"weight": 2
}
],
"score_mode": "avg",
"boost_mode": "multiply"
}
},
"sort": [
"_score"
],
"size": 20,
"track_scores": true
}

910
test/unit/helper/geojsonify.js

@ -1,5 +1,6 @@
const geojsonify = require('../../../helper/geojsonify');
var geojsonify = require('../../../helper/geojsonify'); const proxyquire = require('proxyquire').noCallThru();
module.exports.tests = {}; module.exports.tests = {};
@ -36,433 +37,610 @@ module.exports.tests.earth = function(test, common) {
}; };
module.exports.tests.geojsonify = function(test, common) { module.exports.tests.all = (test, common) => {
test('bounding_box should be calculated using points when avaiable', t => {
const input = [
{
_id: 'id 1',
source: 'source 1',
source_id: 'source_id 1',
layer: 'layer 1',
name: {
default: 'name 1',
},
center_point: {
lat: 12.121212,
lon: 21.212121
}
},
{
_id: 'id 2',
source: 'source 2',
source_id: 'source_id 2',
layer: 'layer 2',
name: {
default: 'name 2',
},
center_point: {
lat: 13.131313,
lon: 31.313131
}
}
];
const geojsonify = proxyquire('../../../helper/geojsonify', {
'./geojsonify_place_details': (params, source, dst) => {
if (source._id === 'id 1') {
return {
property1: 'property 1',
property2: 'property 2'
};
} else if (source._id === 'id 2') {
return {
property3: 'property 3',
property4: 'property 4'
};
}
}
});
const actual = geojsonify({}, input);
const expected = {
type: 'FeatureCollection',
features: [
{
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [ 21.212121, 12.121212 ]
},
properties: {
id: 'id 1',
gid: 'source 1:layer 1:id 1',
layer: 'layer 1',
source: 'source 1',
source_id: 'source_id 1',
name: 'name 1',
property1: 'property 1',
property2: 'property 2'
}
},
{
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [ 31.313131, 13.131313 ]
},
properties: {
id: 'id 2',
gid: 'source 2:layer 2:id 2',
layer: 'layer 2',
source: 'source 2',
source_id: 'source_id 2',
name: 'name 2',
property3: 'property 3',
property4: 'property 4'
}
}
],
bbox: [21.212121, 12.121212, 31.313131, 13.131313]
};
t.deepEquals(actual, expected);
t.end();
test('geojsonify(doc)', function(t) { });
var input = [
test('bounding_box should be calculated using polygons when avaiable', t => {
const input = [
{ {
'_id': 'id1', _id: 'id 1',
'_type': 'layer1', source: 'source 1',
'source': 'source1', source_id: 'source_id 1',
'source_id': 'source_id_1', layer: 'layer 1',
'layer': 'layer1', name: {
'center_point': { default: 'name 1',
'lat': 51.5337144,
'lon': -0.1069716
}, },
'name': { bounding_box: {
'default': '\'Round Midnight Jazz and Blues Bar' min_lon: 1,
min_lat: 1,
max_lon: 2,
max_lat: 2
}, },
'housenumber': '13', center_point: {
'street': 'Liverpool Road', lat: 12.121212,
'postalcode': 'N1 0RW', lon: 21.212121
'country_a': 'GBR', }
'country': 'United Kingdom',
'dependency': 'dependency name',
'region': 'Islington',
'region_a': 'ISL',
'macroregion': 'England',
'county': 'Angel',
'localadmin': 'test1',
'locality': 'test2',
'neighbourhood': 'test3',
'category': [
'food',
'nightlife'
],
'label': 'label for id id1'
}, },
{ {
'_id': 'id2', _id: 'id 2',
'_type': 'layer2', source: 'source 2',
'source': 'source2', source_id: 'source_id 2',
'source_id': 'source_id_2', layer: 'layer 2',
'layer': 'layer2', name: {
'name': { default: 'name 2',
'default': 'Blues Cafe'
}, },
'center_point': { bounding_box: {
'lat': '51.517806', min_lon: -3,
'lon': '-0.101795' min_lat: -3,
max_lon: -1,
max_lat: -1
}, },
'country_a': 'GBR', center_point: {
'country': 'United Kingdom', lat: 13.131313,
'dependency': 'dependency name', lon: 31.313131
'region': 'City And County Of The City Of London', }
'region_a': 'COL', }
'macroregion': 'England', ];
'county': 'Smithfield',
'localadmin': 'test1', const geojsonify = proxyquire('../../../helper/geojsonify', {
'locality': 'test2', './geojsonify_place_details': (params, source, dst) => {
'neighbourhood': 'test3', if (source._id === 'id 1') {
'label': 'label for id id2' return {
property1: 'property 1',
property2: 'property 2'
};
} else if (source._id === 'id 2') {
return {
property3: 'property 3',
property4: 'property 4'
};
}
}
});
const actual = geojsonify({}, input);
const expected = {
type: 'FeatureCollection',
features: [
{
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [ 21.212121, 12.121212 ]
},
properties: {
id: 'id 1',
gid: 'source 1:layer 1:id 1',
layer: 'layer 1',
source: 'source 1',
source_id: 'source_id 1',
name: 'name 1',
property1: 'property 1',
property2: 'property 2'
},
bbox: [ 1, 1, 2, 2 ]
},
{
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [ 31.313131, 13.131313 ]
},
properties: {
id: 'id 2',
gid: 'source 2:layer 2:id 2',
layer: 'layer 2',
source: 'source 2',
source_id: 'source_id 2',
name: 'name 2',
property3: 'property 3',
property4: 'property 4'
},
bbox: [ -3, -3, -1, -1 ]
}
],
bbox: [ -3, -3, 2, 2 ]
};
t.deepEquals(actual, expected);
t.end();
});
test('bounding_box should be calculated using polygons AND points when avaiable', t => {
const input = [
{
_id: 'id 1',
source: 'source 1',
source_id: 'source_id 1',
layer: 'layer 1',
name: {
default: 'name 1',
},
center_point: {
lat: 12.121212,
lon: 21.212121
}
}, },
{ {
'_id': 'node:34633854', _id: 'id 2',
'_type': 'venue', source: 'source 2',
'source': 'openstreetmap', source_id: 'source_id 2',
'source_id': 'source_id_3', layer: 'layer 2',
'layer': 'venue', name: {
'name': { default: 'name 2',
'default': 'Empire State Building'
}, },
'center_point': { bounding_box: {
'lat': '40.748432', min_lon: -3,
'lon': '-73.985656' min_lat: -3,
max_lon: -1,
max_lat: -1
}, },
'country_a': 'USA', center_point: {
'country': 'United States', lat: 13.131313,
'dependency': 'dependency name', lon: 31.313131
'region': 'New York', }
'region_a': 'NY',
'county': 'New York',
'borough': 'Manhattan',
'locality': 'New York',
'neighbourhood': 'Koreatown',
'category': [
'tourism',
'transport'
],
'label': 'label for id node:34633854'
} }
]; ];
var expected = { const geojsonify = proxyquire('../../../helper/geojsonify', {
'type': 'FeatureCollection', './geojsonify_place_details': (params, source, dst) => {
'bbox': [ -73.985656, 40.748432, -0.101795, 51.5337144 ], if (source._id === 'id 1') {
'features': [ return {
property1: 'property 1',
property2: 'property 2'
};
} else if (source._id === 'id 2') {
return {
property3: 'property 3',
property4: 'property 4'
};
}
}
});
const actual = geojsonify({}, input);
const expected = {
type: 'FeatureCollection',
features: [
{ {
'type': 'Feature', type: 'Feature',
'geometry': { geometry: {
'type': 'Point', type: 'Point',
'coordinates': [ coordinates: [ 21.212121, 12.121212 ]
-0.1069716,
51.5337144
]
}, },
'properties': { properties: {
'id': 'id1', id: 'id 1',
'gid': 'source1:layer1:id1', gid: 'source 1:layer 1:id 1',
'layer': 'layer1', layer: 'layer 1',
'source': 'source1', source: 'source 1',
'source_id': 'source_id_1', source_id: 'source_id 1',
'name': '\'Round Midnight Jazz and Blues Bar', name: 'name 1',
'country_a': 'GBR', property1: 'property 1',
'country': 'United Kingdom', property2: 'property 2'
'dependency': 'dependency name',
'macroregion': 'England',
'region': 'Islington',
'region_a': 'ISL',
'county': 'Angel',
'localadmin': 'test1',
'locality': 'test2',
'neighbourhood': 'test3',
'housenumber': '13',
'street': 'Liverpool Road',
'postalcode': 'N1 0RW',
'category': [
'food',
'nightlife'
],
'label': 'label for id id1'
} }
}, },
{ {
'type': 'Feature', type: 'Feature',
'geometry': { geometry: {
'type': 'Point', type: 'Point',
'coordinates': [ coordinates: [ 31.313131, 13.131313 ]
-0.101795,
51.517806
]
}, },
'properties': { properties: {
'id': 'id2', id: 'id 2',
'gid': 'source2:layer2:id2', gid: 'source 2:layer 2:id 2',
'layer': 'layer2', layer: 'layer 2',
'source': 'source2', source: 'source 2',
'source_id': 'source_id_2', source_id: 'source_id 2',
'name': 'Blues Cafe', name: 'name 2',
'country_a': 'GBR', property3: 'property 3',
'country': 'United Kingdom', property4: 'property 4'
'dependency': 'dependency name', },
'macroregion': 'England', bbox: [ -3, -3, -1, -1 ]
'region': 'City And County Of The City Of London', }
'region_a': 'COL', ],
'county': 'Smithfield', bbox: [ -3, -3, 21.212121, 12.121212 ]
'localadmin': 'test1', };
'locality': 'test2',
'neighbourhood': 'test3', t.deepEquals(actual, expected);
'label': 'label for id id2' t.end();
}
});
};
module.exports.tests.non_optimal_conditions = (test, common) => {
test('null/undefined places should log warnings and be ignored', t => {
const logger = require('pelias-mock-logger')();
const input = [
null,
undefined,
{
_id: 'id 1',
source: 'source 1',
source_id: 'source_id 1',
layer: 'layer 1',
name: {
default: 'name 1',
}, },
center_point: {
lat: 12.121212,
lon: 21.212121
}
}
];
const geojsonify = proxyquire('../../../helper/geojsonify', {
'./geojsonify_place_details': (params, source, dst) => {
if (source._id === 'id 1') {
return {
property1: 'property 1',
property2: 'property 2'
};
}
},
'pelias-logger': logger
});
const actual = geojsonify({}, input);
const expected = {
type: 'FeatureCollection',
features: [
{ {
'type': 'Feature', type: 'Feature',
'geometry': { geometry: {
'type': 'Point', type: 'Point',
'coordinates': [ coordinates: [ 21.212121, 12.121212 ]
-73.985656,
40.748432
]
}, },
'properties': { properties: {
'id': 'node:34633854', id: 'id 1',
'gid': 'openstreetmap:venue:node:34633854', gid: 'source 1:layer 1:id 1',
'layer': 'venue', layer: 'layer 1',
'source': 'openstreetmap', source: 'source 1',
'source_id': 'source_id_3', source_id: 'source_id 1',
'name': 'Empire State Building', name: 'name 1',
'country_a': 'USA', property1: 'property 1',
'country': 'United States', property2: 'property 2'
'dependency': 'dependency name',
'region': 'New York',
'region_a': 'NY',
'county': 'New York',
'borough': 'Manhattan',
'locality': 'New York',
'neighbourhood': 'Koreatown',
'category': [
'tourism',
'transport'
],
'label': 'label for id node:34633854'
} }
} }
] ],
bbox: [21.212121, 12.121212, 21.212121, 12.121212]
}; };
var json = geojsonify( {categories: 'foo'}, input ); t.deepEquals(actual, expected);
t.ok(logger.isWarnMessage('No doc or center_point property'));
t.deepEqual(json, expected, 'all docs mapped');
t.end(); t.end();
}); });
test('filtering out empty items', function (t) { test('places w/o center_point should log warnings and be ignored', t => {
var input = [ const logger = require('pelias-mock-logger')();
const input = [
{ {
'bounding_box': { _id: 'id 1',
'min_lat': 40.6514712164, source: 'source 1',
'max_lat': 40.6737320588, source_id: 'source_id 1',
'min_lon': -73.8967895508, layer: 'layer 1',
'max_lon': -73.8665771484 name: {
}, default: 'name 1',
'locality': [ }
'New York' },
], {
'source': 'whosonfirst', _id: 'id 2',
'layer': 'neighbourhood', source: 'source 2',
'population': 173198, source_id: 'source_id 2',
'popularity': 495, layer: 'layer 2',
'center_point': { name: {
'lon': -73.881319, default: 'name 2',
'lat': 40.663303
},
'name': {
'default': 'East New York'
}, },
'source_id': '85816607', center_point: {
'category': ['government'], lat: 13.131313,
'_id': '85816607', lon: 31.313131
'_type': 'neighbourhood', }
'_score': 21.434,
'confidence': 0.888,
'country': [
'United States'
],
'country_gid': [
'85633793'
],
'country_a': [
'USA'
],
'dependency': [
'dependency name'
],
'dependency_gid': [
'dependency id'
],
'dependency_a': [
'dependency abbrevation'
],
'macroregion': [
'MacroRegion Name'
],
'macroregion_gid': [
'MacroRegion Id'
],
'macroregion_a': [
'MacroRegion Abbreviation'
],
'region': [
'New York'
],
'region_gid': [
'85688543'
],
'region_a': [
'NY'
],
'macrocounty': [
'MacroCounty Name'
],
'macrocounty_gid': [
'MacroCounty Id'
],
'macrocounty_a': [
'MacroCounty Abbreviation'
],
'county': [
'Kings County'
],
'county_gid': [
'102082361'
],
'county_a': [
null
],
'borough': [
'Brooklyn'
],
'localadmin_gid': [
'404521211'
],
'borough_a': [
null
],
'locality_gid': [
'85977539'
],
'locality_a': [
null
],
'neighbourhood': [],
'neighbourhood_gid': [],
'label': 'label for id 85816607'
} }
]; ];
var expected = { const geojsonify = proxyquire('../../../helper/geojsonify', {
'type': 'FeatureCollection', './geojsonify_place_details': (params, source, dst) => {
'bbox': [-73.8967895508, 40.6514712164, -73.8665771484, 40.6737320588], if (source._id === 'id 2') {
'features': [ return {
property3: 'property 3',
property4: 'property 4'
};
}
},
'pelias-logger': logger
});
const actual = geojsonify({}, input);
const expected = {
type: 'FeatureCollection',
features: [
{ {
'type': 'Feature', type: 'Feature',
'properties': { geometry: {
'id': '85816607', type: 'Point',
'gid': 'whosonfirst:neighbourhood:85816607', coordinates: [ 31.313131, 13.131313 ]
'layer': 'neighbourhood',
'source': 'whosonfirst',
'source_id': '85816607',
'name': 'East New York',
'category': ['government'],
'confidence': 0.888,
'country': 'United States',
'country_gid': '85633793',
'country_a': 'USA',
'dependency': 'dependency name',
'dependency_gid': 'dependency id',
'dependency_a': 'dependency abbrevation',
'macroregion': 'MacroRegion Name',
'macroregion_gid': 'MacroRegion Id',
'macroregion_a': 'MacroRegion Abbreviation',
'region': 'New York',
'region_gid': '85688543',
'region_a': 'NY',
'macrocounty': 'MacroCounty Name',
'macrocounty_gid': 'MacroCounty Id',
'macrocounty_a': 'MacroCounty Abbreviation',
'county': 'Kings County',
'borough': 'Brooklyn',
'county_gid': '102082361',
'localadmin_gid': '404521211',
'locality': 'New York',
'locality_gid': '85977539',
'label': 'label for id 85816607'
}, },
'bbox': [-73.8967895508,40.6514712164,-73.8665771484,40.6737320588], properties: {
'geometry': { id: 'id 2',
'type': 'Point', gid: 'source 2:layer 2:id 2',
'coordinates': [ layer: 'layer 2',
-73.881319, source: 'source 2',
40.663303 source_id: 'source_id 2',
] name: 'name 2',
property3: 'property 3',
property4: 'property 4'
} }
} }
] ],
bbox: [31.313131, 13.131313, 31.313131, 13.131313]
}; };
var json = geojsonify( {categories: 'foo'}, input ); t.deepEquals(actual, expected);
t.ok(logger.isWarnMessage('No doc or center_point property'));
t.deepEqual(json, expected, 'all wanted properties exposed');
t.end(); t.end();
}); });
};
module.exports.tests.categories = function (test, common) { test('places w/o names should log warnings and be ignored', t => {
test('only set category if categories filter was used', function (t) { const logger = require('pelias-mock-logger')();
var input = [
const input = [
{ {
'_id': '85816607', _id: 'id 1',
'bounding_box': { source: 'source 1',
'min_lat': 40.6514712164, source_id: 'source_id 1',
'max_lat': 40.6737320588, layer: 'layer 1',
'min_lon': -73.8967895508, center_point: {
'max_lon': -73.8665771484 lat: 12.121212,
}, lon: 21.212121
'source': 'whosonfirst', }
'layer': 'neighbourhood', },
'center_point': { {
'lon': -73.881319, _id: 'id 2',
'lat': 40.663303 source: 'source 2',
}, source_id: 'source_id 2',
'name': { layer: 'layer 2',
'default': 'East New York' name: {},
center_point: {
lat: 13.131313,
lon: 31.313131
}
},
{
_id: 'id 3',
source: 'source 3',
source_id: 'source_id 3',
layer: 'layer 3',
name: {
default: 'name 3',
}, },
'source_id': '85816607', center_point: {
'category': ['government'], lat: 14.141414,
'label': 'label for id 85816607' lon: 41.414141
}
} }
]; ];
var expected = { const geojsonify = proxyquire('../../../helper/geojsonify', {
'type': 'FeatureCollection', './geojsonify_place_details': (params, source, dst) => {
'bbox': [-73.8967895508, 40.6514712164, -73.8665771484, 40.6737320588], if (source._id === 'id 1') {
'features': [ return {
property1: 'property 1',
property2: 'property 2'
};
} else if (source._id === 'id 2') {
return {
property3: 'property 3',
property4: 'property 4'
};
} else if (source._id === 'id 3') {
return {
property5: 'property 5',
property6: 'property 6'
};
}
},
'pelias-logger': logger
});
const actual = geojsonify({}, input);
const expected = {
type: 'FeatureCollection',
features: [
{ {
'type': 'Feature', type: 'Feature',
'properties': { geometry: {
'id': '85816607', type: 'Point',
'gid': 'whosonfirst:neighbourhood:85816607', coordinates: [ 21.212121, 12.121212 ]
'layer': 'neighbourhood',
'source': 'whosonfirst',
'source_id': '85816607',
'name': 'East New York',
'category': ['government'],
'label': 'label for id 85816607'
}, },
'bbox': [-73.8967895508,40.6514712164,-73.8665771484,40.6737320588], properties: {
'geometry': { id: 'id 1',
'type': 'Point', gid: 'source 1:layer 1:id 1',
'coordinates': [ layer: 'layer 1',
-73.881319, source: 'source 1',
40.663303 source_id: 'source_id 1',
] property1: 'property 1',
property2: 'property 2'
}
},
{
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [ 31.313131, 13.131313 ]
},
properties: {
id: 'id 2',
gid: 'source 2:layer 2:id 2',
layer: 'layer 2',
source: 'source 2',
source_id: 'source_id 2',
property3: 'property 3',
property4: 'property 4'
}
},
{
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [ 41.414141, 14.141414 ]
},
properties: {
id: 'id 3',
gid: 'source 3:layer 3:id 3',
layer: 'layer 3',
source: 'source 3',
source_id: 'source_id 3',
name: 'name 3',
property5: 'property 5',
property6: 'property 6'
} }
} }
] ],
bbox: [21.212121, 12.121212, 41.414141, 14.141414]
}; };
var json = geojsonify( {categories: 'foo'}, input ); t.deepEquals(actual, expected);
t.ok(logger.isWarnMessage('doc source 1:layer 1:id 1 does not contain name.default'));
t.ok(logger.isWarnMessage('doc source 2:layer 2:id 2 does not contain name.default'));
t.end();
t.deepEqual(json, expected, 'all wanted properties exposed'); });
test('no points', t => {
const logger = require('pelias-mock-logger')();
const input = [];
const geojsonify = proxyquire('../../../helper/geojsonify', {
'./geojsonify_place_details': (params, source, dst) => {
t.fail('should not have bee called');
},
'pelias-logger': logger
});
const actual = geojsonify({}, input);
const expected = {
type: 'FeatureCollection',
features: []
};
t.deepEquals(actual, expected);
t.end(); t.end();
}); });
};
module.exports.all = function (tape, common) { };
module.exports.all = (tape, common) => {
function test(name, testFunction) { function test(name, testFunction) {
return tape('geojsonify: ' + name, testFunction); return tape(`geojsonify: ${name}`, testFunction);
} }
for( var testCase in module.exports.tests ){ for( var testCase in module.exports.tests ){

590
test/unit/helper/geojsonify_place_details.js

@ -0,0 +1,590 @@
const geojsonify = require('../../../helper/geojsonify_place_details');
module.exports.tests = {};
module.exports.tests.geojsonify_place_details = (test, common) => {
test('plain old string values should be copied verbatim, replacing old values', t => {
const source = {
housenumber: 'housenumber value',
street: 'street value',
postalcode: 'postalcode value',
postalcode_gid: 'postalcode_gid value',
match_type: 'match_type value',
accuracy: 'accuracy value',
country: 'country value',
country_gid: 'country_gid value',
country_a: 'country_a value',
dependency: 'dependency value',
dependency_gid: 'dependency_gid value',
dependency_a: 'dependency_a value',
macroregion: 'macroregion value',
macroregion_gid: 'macroregion_gid value',
macroregion_a: 'macroregion_a value',
region: 'region value',
region_gid: 'region_gid value',
region_a: 'region_a value',
macrocounty: 'macrocounty value',
macrocounty_gid: 'macrocounty_gid value',
macrocounty_a: 'macrocounty_a value',
county: 'county value',
county_gid: 'county_gid value',
county_a: 'county_a value',
localadmin: 'localadmin value',
localadmin_gid: 'localadmin_gid value',
localadmin_a: 'localadmin_a value',
locality: 'locality value',
locality_gid: 'locality_gid value',
locality_a: 'locality_a value',
borough: 'borough value',
borough_gid: 'borough_gid value',
borough_a: 'borough_a value',
neighbourhood: 'neighbourhood value',
neighbourhood_gid: 'neighbourhood_gid value',
continent: 'continent value',
continent_gid: 'continent_gid value',
continent_a: 'continent_a value',
ocean: 'ocean value',
ocean_gid: 'ocean_gid value',
ocean_a: 'ocean_a value',
marinearea: 'marinearea value',
marinearea_gid: 'marinearea_gid value',
marinearea_a: 'marinearea_a value',
label: 'label value'
};
const expected = {
housenumber: 'housenumber value',
street: 'street value',
postalcode: 'postalcode value',
postalcode_gid: 'postalcode_gid value',
match_type: 'match_type value',
accuracy: 'accuracy value',
country: 'country value',
country_gid: 'country_gid value',
country_a: 'country_a value',
dependency: 'dependency value',
dependency_gid: 'dependency_gid value',
dependency_a: 'dependency_a value',
macroregion: 'macroregion value',
macroregion_gid: 'macroregion_gid value',
macroregion_a: 'macroregion_a value',
region: 'region value',
region_gid: 'region_gid value',
region_a: 'region_a value',
macrocounty: 'macrocounty value',
macrocounty_gid: 'macrocounty_gid value',
macrocounty_a: 'macrocounty_a value',
county: 'county value',
county_gid: 'county_gid value',
county_a: 'county_a value',
localadmin: 'localadmin value',
localadmin_gid: 'localadmin_gid value',
localadmin_a: 'localadmin_a value',
locality: 'locality value',
locality_gid: 'locality_gid value',
locality_a: 'locality_a value',
borough: 'borough value',
borough_gid: 'borough_gid value',
borough_a: 'borough_a value',
neighbourhood: 'neighbourhood value',
neighbourhood_gid: 'neighbourhood_gid value',
continent: 'continent value',
continent_gid: 'continent_gid value',
continent_a: 'continent_a value',
ocean: 'ocean value',
ocean_gid: 'ocean_gid value',
ocean_a: 'ocean_a value',
marinearea: 'marinearea value',
marinearea_gid: 'marinearea_gid value',
marinearea_a: 'marinearea_a value',
label: 'label value'
};
const actual = geojsonify({}, source);
t.deepEqual(actual, expected);
t.end();
});
test('\'empty\' string-type values should be output as \'\'', t => {
[ [], {}, '', 17, true, null, undefined ].forEach(empty_value => {
const source = {
housenumber: empty_value,
street: empty_value,
postalcode: empty_value,
postalcode_gid: empty_value,
match_type: empty_value,
accuracy: empty_value,
country: empty_value,
country_gid: empty_value,
country_a: empty_value,
dependency: empty_value,
dependency_gid: empty_value,
dependency_a: empty_value,
macroregion: empty_value,
macroregion_gid: empty_value,
macroregion_a: empty_value,
region: empty_value,
region_gid: empty_value,
region_a: empty_value,
macrocounty: empty_value,
macrocounty_gid: empty_value,
macrocounty_a: empty_value,
county: empty_value,
county_gid: empty_value,
county_a: empty_value,
localadmin: empty_value,
localadmin_gid: empty_value,
localadmin_a: empty_value,
locality: empty_value,
locality_gid: empty_value,
locality_a: empty_value,
borough: empty_value,
borough_gid: empty_value,
borough_a: empty_value,
neighbourhood: empty_value,
neighbourhood_gid: empty_value,
continent: empty_value,
continent_gid: empty_value,
continent_a: empty_value,
ocean: empty_value,
ocean_gid: empty_value,
ocean_a: empty_value,
marinearea: empty_value,
marinearea_gid: empty_value,
marinearea_a: empty_value,
label: empty_value
};
const expected = {};
const actual = geojsonify({}, source);
t.deepEqual(actual, expected);
});
t.end();
});
test('source arrays should be copy first value', t => {
const source = {
housenumber: ['housenumber value 1', 'housenumber value 2'],
street: ['street value 1', 'street value 2'],
postalcode: ['postalcode value 1', 'postalcode value 2'],
postalcode_gid: ['postalcode_gid value 1', 'postalcode_gid value 2'],
match_type: ['match_type value 1', 'match_type value 2'],
accuracy: ['accuracy value 1', 'accuracy value 2'],
country: ['country value 1', 'country value 2'],
country_gid: ['country_gid value 1', 'country_gid value 2'],
country_a: ['country_a value 1', 'country_a value 2'],
dependency: ['dependency value 1', 'dependency value 2'],
dependency_gid: ['dependency_gid value 1', 'dependency_gid value 2'],
dependency_a: ['dependency_a value 1', 'dependency_a value 2'],
macroregion: ['macroregion value 1', 'macroregion value 2'],
macroregion_gid: ['macroregion_gid value 1', 'macroregion_gid value 2'],
macroregion_a: ['macroregion_a value 1', 'macroregion_a value 2'],
region: ['region value 1', 'region value 2'],
region_gid: ['region_gid value 1', 'region_gid value 2'],
region_a: ['region_a value 1', 'region_a value 2'],
macrocounty: ['macrocounty value 1', 'macrocounty value 2'],
macrocounty_gid: ['macrocounty_gid value 1', 'macrocounty_gid value 2'],
macrocounty_a: ['macrocounty_a value 1', 'macrocounty_a value 2'],
county: ['county value 1', 'county value 2'],
county_gid: ['county_gid value 1', 'county_gid value 2'],
county_a: ['county_a value 1', 'county_a value 2'],
localadmin: ['localadmin value 1', 'localadmin value 2'],
localadmin_gid: ['localadmin_gid value 1', 'localadmin_gid value 2'],
localadmin_a: ['localadmin_a value 1', 'localadmin_a value 2'],
locality: ['locality value 1', 'locality value 2'],
locality_gid: ['locality_gid value 1', 'locality_gid value 2'],
locality_a: ['locality_a value 1', 'locality_a value 2'],
borough: ['borough value 1', 'borough value 2'],
borough_gid: ['borough_gid value 1', 'borough_gid value 2'],
borough_a: ['borough_a value 1', 'borough_a value 2'],
neighbourhood: ['neighbourhood value 1', 'neighbourhood value 2'],
neighbourhood_gid: ['neighbourhood_gid value 1', 'neighbourhood_gid value 2'],
continent: ['continent value 1', 'continent value 2'],
continent_gid: ['continent_gid value 1', 'continent_gid value 2'],
continent_a: ['continent_a value 1', 'continent_a value 2'],
ocean: ['ocean value 1', 'ocean value 2'],
ocean_gid: ['ocean_gid value 1', 'ocean_gid value 2'],
ocean_a: ['ocean_a value 1', 'ocean_a value 2'],
marinearea: ['marinearea value 1', 'marinearea value 2'],
marinearea_gid: ['marinearea_gid value 1', 'marinearea_gid value 2'],
marinearea_a: ['marinearea_a value 1','marinearea_a value 2'],
label: ['label value 1', 'label value 2']
};
const expected = {
housenumber: 'housenumber value 1',
street: 'street value 1',
postalcode: 'postalcode value 1',
postalcode_gid: 'postalcode_gid value 1',
match_type: 'match_type value 1',
accuracy: 'accuracy value 1',
country: 'country value 1',
country_gid: 'country_gid value 1',
country_a: 'country_a value 1',
dependency: 'dependency value 1',
dependency_gid: 'dependency_gid value 1',
dependency_a: 'dependency_a value 1',
macroregion: 'macroregion value 1',
macroregion_gid: 'macroregion_gid value 1',
macroregion_a: 'macroregion_a value 1',
region: 'region value 1',
region_gid: 'region_gid value 1',
region_a: 'region_a value 1',
macrocounty: 'macrocounty value 1',
macrocounty_gid: 'macrocounty_gid value 1',
macrocounty_a: 'macrocounty_a value 1',
county: 'county value 1',
county_gid: 'county_gid value 1',
county_a: 'county_a value 1',
localadmin: 'localadmin value 1',
localadmin_gid: 'localadmin_gid value 1',
localadmin_a: 'localadmin_a value 1',
locality: 'locality value 1',
locality_gid: 'locality_gid value 1',
locality_a: 'locality_a value 1',
borough: 'borough value 1',
borough_gid: 'borough_gid value 1',
borough_a: 'borough_a value 1',
neighbourhood: 'neighbourhood value 1',
neighbourhood_gid: 'neighbourhood_gid value 1',
continent: 'continent value 1',
continent_gid: 'continent_gid value 1',
continent_a: 'continent_a value 1',
ocean: 'ocean value 1',
ocean_gid: 'ocean_gid value 1',
ocean_a: 'ocean_a value 1',
marinearea: 'marinearea value 1',
marinearea_gid: 'marinearea_gid value 1',
marinearea_a: 'marinearea_a value 1',
label: 'label value 1'
};
const actual = geojsonify({}, source);
t.deepEqual(actual, expected);
t.end();
});
test('non-empty objects should be converted to strings', t => {
// THIS TEST SHOWS THAT THE CODE DOES NOT DO WHAT IT EXPECTED
const source = {
housenumber: { housenumber: 'housenumber value'},
street: { street: 'street value'},
postalcode: { postalcode: 'postalcode value'},
postalcode_gid: { postalcode_gid: 'postalcode_gid value'},
match_type: { match_type: 'match_type value'},
accuracy: { accuracy: 'accuracy value'},
country: { country: 'country value'},
country_gid: { country_gid: 'country_gid value'},
country_a: { country_a: 'country_a value'},
dependency: { dependency: 'dependency value'},
dependency_gid: { dependency_gid: 'dependency_gid value'},
dependency_a: { dependency_a: 'dependency_a value'},
macroregion: { macroregion: 'macroregion value'},
macroregion_gid: { macroregion_gid: 'macroregion_gid value'},
macroregion_a: { macroregion_a: 'macroregion_a value'},
region: { region: 'region value'},
region_gid: { region_gid: 'region_gid value'},
region_a: { region_a: 'region_a value'},
macrocounty: { macrocounty: 'macrocounty value'},
macrocounty_gid: { macrocounty_gid: 'macrocounty_gid value'},
macrocounty_a: { macrocounty_a: 'macrocounty_a value'},
county: { county: 'county value'},
county_gid: { county_gid: 'county_gid value'},
county_a: { county_a: 'county_a value'},
localadmin: { localadmin: 'localadmin value'},
localadmin_gid: { localadmin_gid: 'localadmin_gid value'},
localadmin_a: { localadmin_a: 'localadmin_a value'},
locality: { locality: 'locality value'},
locality_gid: { locality_gid: 'locality_gid value'},
locality_a: { locality_a: 'locality_a value'},
borough: { borough: 'borough value'},
borough_gid: { borough_gid: 'borough_gid value'},
borough_a: { borough_a: 'borough_a value'},
neighbourhood: { neighbourhood: 'neighbourhood value'},
neighbourhood_gid: { neighbourhood_gid: 'neighbourhood_gid value'},
continent: { continent: 'continent value'} ,
continent_gid: { continent: 'continent_gid value'},
continent_a: { continent: 'continent_a value'},
ocean: { ocean: 'ocean value'},
ocean_gid: { ocean_gid: 'ocean_gid value'},
ocean_a: { ocean_a: 'ocean_a value'},
marinearea: { marinearea: 'marinearea value'},
marinearea_gid: { marinearea_gid: 'marinearea_gid value'},
marinearea_a: { marinearea_a: 'marinearea_a value'},
label: { label: 'label value'}
};
const expected = {
housenumber: '[object Object]',
street: '[object Object]',
postalcode: '[object Object]',
postalcode_gid: '[object Object]',
match_type: '[object Object]',
accuracy: '[object Object]',
country: '[object Object]',
country_gid: '[object Object]',
country_a: '[object Object]',
dependency: '[object Object]',
dependency_gid: '[object Object]',
dependency_a: '[object Object]',
macroregion: '[object Object]',
macroregion_gid: '[object Object]',
macroregion_a: '[object Object]',
region: '[object Object]',
region_gid: '[object Object]',
region_a: '[object Object]',
macrocounty: '[object Object]',
macrocounty_gid: '[object Object]',
macrocounty_a: '[object Object]',
county: '[object Object]',
county_gid: '[object Object]',
county_a: '[object Object]',
localadmin: '[object Object]',
localadmin_gid: '[object Object]',
localadmin_a: '[object Object]',
locality: '[object Object]',
locality_gid: '[object Object]',
locality_a: '[object Object]',
borough: '[object Object]',
borough_gid: '[object Object]',
borough_a: '[object Object]',
neighbourhood: '[object Object]',
neighbourhood_gid: '[object Object]',
continent: '[object Object]',
continent_gid: '[object Object]',
continent_a: '[object Object]',
ocean: '[object Object]',
ocean_gid: '[object Object]',
ocean_a: '[object Object]',
marinearea: '[object Object]',
marinearea_gid: '[object Object]',
marinearea_a: '[object Object]',
label: '[object Object]'
};
const actual = geojsonify({}, source);
t.deepEqual(actual, expected);
t.end();
});
test('\'default\'-type properties should be copied without type conversion and overwrite old values', t => {
[ 'this is a string', 17.3, { a: 1 }, [1, 2, 3] ].forEach(value => {
const source = {
confidence: value,
distance: value,
bounding_box: value
};
const expected = {
confidence: value,
distance: value,
bounding_box: value
};
const actual = geojsonify({}, source);
t.deepEqual(actual, expected);
});
t.end();
});
test('\'default\'-type properties that are numbers should be output as numbers', t => {
[ 17, 17.3 ].forEach(value => {
const source = {
confidence: value,
distance: value,
bounding_box: value
};
const expected = {
confidence: value,
distance: value,
bounding_box: value
};
const actual = geojsonify({}, source);
t.deepEqual(actual, expected);
});
t.end();
});
test('\'empty\' values for default-type properties should not be output', t => {
[ undefined, null, true, {}, [] ].forEach(value => {
const source = {
confidence: value,
distance: value,
bounding_box: value
};
const expected = {};
const actual = geojsonify({}, source);
t.deepEqual(actual, expected);
});
t.end();
});
test('array-type properties should not be output when empty', t => {
const source = {
category: []
};
const expected = {};
const actual = geojsonify({}, source);
t.deepEqual(actual, expected);
t.end();
});
test('array-type properties with array values should be output as arrays', t => {
const source = {
category: [ 1, 2 ]
};
const expected = {
category: [ 1, 2 ]
};
const clean = {
categories: true
};
const actual = geojsonify(clean, source);
t.deepEqual(actual, expected);
t.end();
});
test('category property should be output when params contains \'category\' property', t => {
[ {a: 1}, 'this is a string'].forEach(value => {
const source = {
category: value
};
const expected = {
category: [ value ]
};
const clean = {
categories: true
};
const actual = geojsonify(clean, source);
t.deepEqual(actual, expected);
});
t.end();
});
test('category property should not be output when params does not contain \'category\' property', t => {
const source = {
category: [ 1, 2 ]
};
const expected = {
};
const clean = {};
const actual = geojsonify(clean, source);
t.deepEqual(actual, expected);
t.end();
});
test('category property should not be output when params is not an object', t => {
const source = {
category: [ 1, 2 ]
};
const expected = {
};
const clean = 'this is not an object';
const actual = geojsonify(clean, source);
t.deepEqual(actual, expected);
t.end();
});
};
module.exports.tests.empire_specific = (test, common) => {
test('empire* properties should not be output when country_gid property is available', t => {
const source = {
country_gid: 'country_gid value',
empire: 'empire value',
empire_gid: 'empire_gid value',
empire_a: 'empire_a value',
label: 'label value'
};
const expected = {
country_gid: 'country_gid value',
label: 'label value'
};
const actual = geojsonify({}, source);
t.deepEqual(actual, expected);
t.end();
});
test('empire* properties should be output when country_gid property is not available', t => {
const source = {
empire: 'empire value',
empire_gid: 'empire_gid value',
empire_a: 'empire_a value',
label: 'label value'
};
const expected = {
empire: 'empire value',
empire_gid: 'empire_gid value',
empire_a: 'empire_a value',
label: 'label value'
};
const actual = geojsonify({}, source);
t.deepEqual(actual, expected);
t.end();
});
};
module.exports.all = (tape, common) => {
function test(name, testFunction) {
return tape(`geojsonify: ${name}`, testFunction);
}
for( var testCase in module.exports.tests ){
module.exports.tests[testCase](test, common);
}
};

5
test/unit/helper/type_mapping.js

@ -12,9 +12,10 @@ module.exports.tests.interfaces = function(test, common) {
test('alias layer mapping', function(t) { test('alias layer mapping', function(t) {
t.deepEquals(type_mapping.layer_mapping.coarse, t.deepEquals(type_mapping.layer_mapping.coarse,
[ 'continent', 'country', 'dependency', 'macroregion', [ 'continent', 'empire', 'country', 'dependency', 'macroregion',
'region', 'locality', 'localadmin', 'macrocounty', 'county', 'macrohood', 'region', 'locality', 'localadmin', 'macrocounty', 'county', 'macrohood',
'borough', 'neighbourhood', 'microhood', 'disputed', 'postalcode' ]); 'borough', 'neighbourhood', 'microhood', 'disputed', 'postalcode',
'continent', 'ocean', 'marinearea']);
t.end(); t.end();
}); });

32
test/unit/middleware/confidenceScore.js

@ -169,6 +169,38 @@ module.exports.tests.confidenceScore = function(test, common) {
t.false(res.data[0].hasOwnProperty('confidence'), 'score was not set'); t.false(res.data[0].hasOwnProperty('confidence'), 'score was not set');
t.end(); t.end();
}); });
test('missing parent object should not throw an exception', function(t) {
var req = {
clean: {
text: '123 Main St, City, NM',
parsed_text: {
number: 123,
street: 'Main St',
state: 'NM'
}
}
};
var res = {
data: [{
_score: 10,
found: true,
value: 1,
center_point: { lat: 100.1, lon: -50.5 },
name: { default: 'test name1' },
}],
meta: {
scores: [10],
query_type: 'original'
}
};
t.doesNotThrow(() => {
confidenceScore(req, res, () => {});
});
t.equal(res.data[0].confidence, 0.28, 'score was set');
t.end();
});
}; };
module.exports.all = function (tape, common) { module.exports.all = function (tape, common) {

298
test/unit/middleware/interpolate.js

@ -38,129 +38,6 @@ module.exports.tests.early_exit_conditions = (test, common) => {
}; };
module.exports.tests.error_conditions = (test, common) => {
test('service error string should log and not modify any results', t => {
t.plan(2);
const service = (req, res, callback) => {
callback('this is an error', {
properties: {
number: 17,
source: 'OSM',
source_id: 'openstreetmap source id',
lat: 12.121212,
lon: 21.212121
}
});
};
const logger = require('pelias-mock-logger')();
const controller = proxyquire('../../../middleware/interpolate', {
'pelias-logger': logger
})(service, () => true);
const req = { a: 1 };
const res = {
data: [
{
id: 1,
layer: 'street',
name: {
default: 'street name 1'
},
address_parts: {},
// bounding_box should be removed
bounding_box: {}
}
]
};
controller(req, res, () => {
t.ok(logger.isErrorMessage('[middleware:interpolation] this is an error'));
t.deepEquals(res, {
data: [
{
id: 1,
layer: 'street',
name: {
default: 'street name 1'
},
address_parts: {},
// bounding_box should be removed
bounding_box: {}
}
]
}, 'res should not have been modified');
});
});
test('service error object should log message and not modify any results', t => {
t.plan(2);
const service = (req, res, callback) => {
callback({ message: 'this is an error' }, {
properties: {
number: 17,
source: 'OSM',
source_id: 'openstreetmap source id',
lat: 12.121212,
lon: 21.212121
}
});
};
const logger = require('pelias-mock-logger')();
const controller = proxyquire('../../../middleware/interpolate', {
'pelias-logger': logger
})(service, () => true);
const req = { a: 1 };
const res = {
data: [
{
id: 1,
layer: 'street',
name: {
default: 'street name 1'
},
address_parts: {},
// bounding_box should be removed
bounding_box: {}
}
]
};
controller(req, res, () => {
t.ok(logger.isErrorMessage('[middleware:interpolation] this is an error'));
t.deepEquals(res, {
data: [
{
id: 1,
layer: 'street',
name: {
default: 'street name 1'
},
address_parts: {},
// bounding_box should be removed
bounding_box: {}
}
]
}, 'res should not have been modified');
});
});
};
module.exports.tests.success_conditions = (test, common) => { module.exports.tests.success_conditions = (test, common) => {
test('undefined res should not cause errors', t => { test('undefined res should not cause errors', t => {
const service = (req, res, callback) => { const service = (req, res, callback) => {
@ -182,6 +59,7 @@ module.exports.tests.success_conditions = (test, common) => {
controller(req, undefined, () => { controller(req, undefined, () => {
t.notOk(logger.hasErrorMessages(), 'there shouldn\'t be any error messages'); t.notOk(logger.hasErrorMessages(), 'there shouldn\'t be any error messages');
t.ok(logger.isInfoMessage(/\[interpolation\] \[took\] \d+ ms/)); t.ok(logger.isInfoMessage(/\[interpolation\] \[took\] \d+ ms/));
t.ok(logger.isInfoMessage(/\[interpolation\] \[street_results\] count=0/));
t.end(); t.end();
}); });
@ -210,6 +88,7 @@ module.exports.tests.success_conditions = (test, common) => {
controller(req, res, () => { controller(req, res, () => {
t.notOk(logger.hasErrorMessages(), 'there shouldn\'t be any error messages'); t.notOk(logger.hasErrorMessages(), 'there shouldn\'t be any error messages');
t.ok(logger.isInfoMessage(/\[interpolation\] \[took\] \d+ ms/)); t.ok(logger.isInfoMessage(/\[interpolation\] \[took\] \d+ ms/));
t.ok(logger.isInfoMessage(/\[interpolation\] \[street_results\] count=0/));
t.deepEquals(res, {}); t.deepEquals(res, {});
@ -219,7 +98,7 @@ module.exports.tests.success_conditions = (test, common) => {
}); });
test('interpolated results should be mapped in', t => { test('only \'street\' layer results should attempt interpolation', t => {
const service = (req, res, callback) => { const service = (req, res, callback) => {
if (res.id === 1) { if (res.id === 1) {
callback(null, { callback(null, {
@ -294,6 +173,7 @@ module.exports.tests.success_conditions = (test, common) => {
bounding_box: {} bounding_box: {}
}, },
{ {
// this is not a street result and should not attempt interpolation
id: 2, id: 2,
layer: 'not street', layer: 'not street',
name: { name: {
@ -323,6 +203,7 @@ module.exports.tests.success_conditions = (test, common) => {
controller(req, res, () => { controller(req, res, () => {
t.notOk(logger.hasErrorMessages(), 'there shouldn\'t be any error messages'); t.notOk(logger.hasErrorMessages(), 'there shouldn\'t be any error messages');
t.ok(logger.isInfoMessage(/\[interpolation\] \[took\] \d+ ms/), 'timing should be info-logged'); t.ok(logger.isInfoMessage(/\[interpolation\] \[took\] \d+ ms/), 'timing should be info-logged');
t.ok(logger.isInfoMessage(/\[interpolation\] \[street_results\] count=3/), 'street count should be info-logged');
// test debug messages very vaguely to avoid brittle tests // test debug messages very vaguely to avoid brittle tests
t.ok(logger.isDebugMessage(/^\[interpolation\] \[hit\] this is req.clean.parsed_text \{.+?\}$/), t.ok(logger.isDebugMessage(/^\[interpolation\] \[hit\] this is req.clean.parsed_text \{.+?\}$/),
@ -398,6 +279,172 @@ module.exports.tests.success_conditions = (test, common) => {
}); });
test('service call returning error should not map in interpolated results for non-errors', t => {
const service = (req, res, callback) => {
if (res.id === 1) {
callback(null, {
properties: {
number: 17,
source: 'Source Abbr 1',
source_id: 'source 1 source id',
lat: 12.121212,
lon: 21.212121
}
});
} else if (res.id === 2) {
callback('id 2 produced an error string', {});
} else if (res.id === 3) {
callback({ message: 'id 3 produced an error object' }, {});
} else if (res.id === 4) {
callback(null, {
properties: {
number: 18,
source: 'Source Abbr 4',
source_id: 'source 4 source id',
lat: 13.131313,
lon: 31.313131
}
});
} else {
console.error(res.id);
t.fail(`unexpected id ${res.id}`);
}
};
const logger = require('pelias-mock-logger')();
const controller = proxyquire('../../../middleware/interpolate', {
'pelias-logger': logger,
'../helper/type_mapping': {
source_mapping: {
'source abbr 1': ['full source name 1'],
'source abbr 4': ['full source name 4']
}
}
})(service, () => true);
const req = {
clean: {
parsed_text: 'this is req.clean.parsed_text'
}
};
const res = {
data: [
{
id: 1,
layer: 'street',
name: {
default: 'street name 1'
},
address_parts: {}
},
{
id: 2,
layer: 'street',
name: {
default: 'street name 2'
},
address_parts: {}
},
{
id: 3,
layer: 'street',
name: {
default: 'street name 3'
},
address_parts: {}
},
{
id: 4,
layer: 'street',
name: {
default: 'street name 4'
},
address_parts: {}
}
]
};
controller(req, res, () => {
t.deepEquals(logger.getErrorMessages(), [
'[middleware:interpolation] id 2 produced an error string',
'[middleware:interpolation] id 3 produced an error object'
]);
t.ok(logger.isInfoMessage(/\[interpolation\] \[took\] \d+ ms/), 'timing should be info-logged');
t.ok(logger.isInfoMessage(/\[interpolation\] \[street_results\] count=4/), 'street count should be info-logged');
// test debug messages very vaguely to avoid brittle tests
t.ok(logger.isDebugMessage(/^\[interpolation\] \[hit\] this is req.clean.parsed_text \{.+?\}$/),
'hits should be debug-logged');
t.deepEquals(res, {
data: [
{
id: 1,
layer: 'address',
match_type: 'interpolated',
name: {
default: '17 street name 1'
},
source: 'full source name 1',
source_id: 'source 1 source id',
address_parts: {
number: 17
},
center_point: {
lat: 12.121212,
lon: 21.212121
}
},
{
id: 4,
layer: 'address',
match_type: 'interpolated',
name: {
default: '18 street name 4'
},
source: 'full source name 4',
source_id: 'source 4 source id',
address_parts: {
number: 18
},
center_point: {
lat: 13.131313,
lon: 31.313131
}
},
{
id: 2,
layer: 'street',
name: {
default: 'street name 2'
},
address_parts: {}
},
{
id: 3,
layer: 'street',
name: {
default: 'street name 3'
},
address_parts: {}
}
]
}, 'hits should be mapped in and res.data sorted with addresses first and non-addresses last');
t.end();
});
});
test('interpolation result without source_id should remove all together', t => { test('interpolation result without source_id should remove all together', t => {
const service = (req, res, callback) => { const service = (req, res, callback) => {
if (res.id === 1) { if (res.id === 1) {
@ -448,6 +495,7 @@ module.exports.tests.success_conditions = (test, common) => {
controller(req, res, () => { controller(req, res, () => {
t.notOk(logger.hasErrorMessages(), 'there shouldn\'t be any error messages'); t.notOk(logger.hasErrorMessages(), 'there shouldn\'t be any error messages');
t.ok(logger.isInfoMessage(/\[interpolation\] \[took\] \d+ ms/)); t.ok(logger.isInfoMessage(/\[interpolation\] \[took\] \d+ ms/));
t.ok(logger.isInfoMessage(/\[interpolation\] \[street_results\] count=1/), 'street count should be info-logged');
t.deepEquals(res, { t.deepEquals(res, {
data: [ data: [
@ -536,6 +584,7 @@ module.exports.tests.success_conditions = (test, common) => {
controller(req, res, () => { controller(req, res, () => {
t.notOk(logger.hasErrorMessages(), 'there shouldn\'t be any error messages'); t.notOk(logger.hasErrorMessages(), 'there shouldn\'t be any error messages');
t.ok(logger.isInfoMessage(/\[interpolation\] \[took\] \d+ ms/)); t.ok(logger.isInfoMessage(/\[interpolation\] \[took\] \d+ ms/));
t.ok(logger.isInfoMessage(/\[interpolation\] \[street_results\] count=2/), 'street count should be info-logged');
// test debug messages very vaguely to avoid brittle tests // test debug messages very vaguely to avoid brittle tests
t.ok(logger.isDebugMessage('[interpolation] [miss] this is req.clean.parsed_text')); t.ok(logger.isDebugMessage('[interpolation] [miss] this is req.clean.parsed_text'));
@ -636,6 +685,7 @@ module.exports.tests.success_conditions = (test, common) => {
controller(req, res, () => { controller(req, res, () => {
t.notOk(logger.hasErrorMessages(), 'there shouldn\'t be any error messages'); t.notOk(logger.hasErrorMessages(), 'there shouldn\'t be any error messages');
t.ok(logger.isInfoMessage(/\[interpolation\] \[took\] \d+ ms/)); t.ok(logger.isInfoMessage(/\[interpolation\] \[took\] \d+ ms/));
t.ok(logger.isInfoMessage(/\[interpolation\] \[street_results\] count=2/), 'street count should be info-logged');
// test debug messages very vaguely to avoid brittle tests // test debug messages very vaguely to avoid brittle tests
t.ok(logger.isDebugMessage('[interpolation] [miss] this is req.clean.parsed_text')); t.ok(logger.isDebugMessage('[interpolation] [miss] this is req.clean.parsed_text'));

7
test/unit/query/MockQuery.js

@ -3,6 +3,7 @@
module.exports = class MockQuery { module.exports = class MockQuery {
constructor() { constructor() {
this._score_functions = []; this._score_functions = [];
this._sort_functions = [];
this._filter_functions = []; this._filter_functions = [];
} }
@ -10,6 +11,7 @@ module.exports = class MockQuery {
return { return {
vs: vs, vs: vs,
score_functions: this._score_functions, score_functions: this._score_functions,
sort_functions: this._sort_functions,
filter_functions: this._filter_functions filter_functions: this._filter_functions
}; };
} }
@ -19,6 +21,11 @@ module.exports = class MockQuery {
return this; return this;
} }
sort(view) {
this._sort_functions.push(view);
return this;
}
filter(view) { filter(view) {
this._filter_functions.push(view); this._filter_functions.push(view);
return this; return this;

573
test/unit/query/reverse.js

@ -1,190 +1,503 @@
var generate = require('../../../query/reverse'); const generate = require('../../../query/reverse');
const _ = require('lodash');
const proxyquire = require('proxyquire').noCallThru();
const MockQuery = require('./MockQuery');
const all_layers = require('../../../helper/type_mapping').layers;
// helper for canned views
const views = {
boundary_country: 'boundary_country view',
boundary_circle: 'boundary_circle view',
sources: 'sources view',
layers: 'layers view',
categories: 'categories view',
sort_distance: 'sort_distance view'
};
module.exports.tests = {}; module.exports.tests = {};
module.exports.tests.interface = function(test, common) { module.exports.tests.interface = (test, common) => {
test('valid interface', function(t) { test('valid interface', t => {
t.equal(typeof generate, 'function', 'valid function'); t.equal(typeof generate, 'function', 'valid function');
t.end(); t.end();
}); });
}; };
module.exports.tests.query = function(test, common) { module.exports.tests.query = (test, common) => {
test('valid query', function(t) { test('base no frills', t => {
var query = generate({ const clean = {};
'point.lat': 29.49136,
'point.lon': -82.50622, const query = proxyquire('../../../query/reverse', {
'boundary.circle.lat': 29.49136, 'pelias-query': {
'boundary.circle.lon': -82.50622, layout: {
'boundary.circle.radius': 3 FilteredBooleanQuery: MockQuery
}); },
view: views,
Vars: require('pelias-query').Vars
},
'./reverse_defaults': {
default_parameter_1: 'first default parameter',
default_parameter_2: 'second default parameter'
}
})(clean);
t.equals(query.type, 'reverse', 'query type set');
t.deepEquals(query.body.vs.var('default_parameter_1').toString(), 'first default parameter');
t.deepEquals(query.body.vs.var('default_parameter_2').toString(), 'second default parameter');
t.notOk(query.body.vs.isset('size'));
t.notOk(query.body.vs.isset('sources'));
t.notOk(query.body.vs.isset('layers'));
t.notOk(query.body.vs.isset('focus:point:lat'));
t.notOk(query.body.vs.isset('focus:point:lon'));
t.notOk(query.body.vs.isset('boundary:circle:lat'));
t.notOk(query.body.vs.isset('boundary:circle:lon'));
t.notOk(query.body.vs.isset('boundary:circle:radius'));
t.notOk(query.body.vs.isset('boundary:country'));
t.notOk(query.body.vs.isset('input:categories'));
t.deepEquals(query.body.score_functions, [
'boundary_country view'
]);
t.deepEquals(query.body.filter_functions, [
'boundary_circle view',
'sources view',
'layers view',
'categories view'
]);
t.deepEquals(query.body.sort_functions, [
'sort_distance view'
]);
t.end();
var compiled = JSON.parse( JSON.stringify( query ) ); });
var expected = require('../fixture/reverse_standard');
t.deepEqual(compiled.type, 'reverse', 'query type set'); test('clean.querySize should set size parameter', t => {
t.deepEqual(compiled.body, expected, 'reverse_standard'); const clean = {
querySize: 17
};
const query = proxyquire('../../../query/reverse', {
'pelias-query': {
layout: {
FilteredBooleanQuery: MockQuery
},
view: views,
Vars: require('pelias-query').Vars
},
'./reverse_defaults': {}
})(clean);
t.deepEquals(query.body.vs.var('size').toString(), 17);
t.end(); t.end();
}); });
test('valid query - null island', function(t) { };
var query = generate({
'point.lat': 0,
'point.lon': 0,
'boundary.circle.lat': 0,
'boundary.circle.lon': 0,
'boundary.circle.radius': 3
});
var compiled = JSON.parse( JSON.stringify( query ) ); module.exports.tests.sources = (test, common) => {
var expected = require('../fixture/reverse_null_island'); test('non-array clean.sources should not set sources in vs', t => {
const clean = {
sources: 'this is not an array'
};
t.deepEqual(compiled.type, 'reverse', 'query type set'); const query = proxyquire('../../../query/reverse', {
t.deepEqual(compiled.body, expected, 'reverse_null_island'); 'pelias-query': {
layout: {
FilteredBooleanQuery: MockQuery
},
view: views,
Vars: require('pelias-query').Vars
},
'./reverse_defaults': {}
})(clean);
t.notOk(query.body.vs.isset('sources'));
t.end(); t.end();
}); });
test('valid query with radius', function(t) { test('empty array clean.sources should not set sources in vs', t => {
var query = generate({ const clean = {
'point.lat': 29.49136, sources: []
'point.lon': -82.50622, };
'boundary.circle.lat': 29.49136,
'boundary.circle.lon': -82.50622, const query = proxyquire('../../../query/reverse', {
'boundary.circle.radius': 123 'pelias-query': {
}); layout: {
FilteredBooleanQuery: MockQuery
},
view: views,
Vars: require('pelias-query').Vars
},
'./reverse_defaults': {}
})(clean);
t.notOk(query.body.vs.isset('sources'));
t.end();
var compiled = JSON.parse( JSON.stringify( query ) ); });
var expected = '123km';
test('non-empty array clean.sources should set sources in vs', t => {
const clean = {
sources: ['source 1', 'source 2']
};
t.deepEqual(compiled.type, 'reverse', 'query type set'); const query = proxyquire('../../../query/reverse', {
t.deepEqual(compiled.body.query.bool.filter[0].geo_distance.distance, expected, 'distance set to boundary circle radius'); 'pelias-query': {
layout: {
FilteredBooleanQuery: MockQuery
},
view: views,
Vars: require('pelias-query').Vars
},
'./reverse_defaults': {}
})(clean);
t.deepEquals(query.body.vs.var('sources').toString(), ['source 1', 'source 2']);
t.end(); t.end();
}); });
// for coarse reverse cases where boundary circle radius isn't used };
test('undefined radius set to default radius', function(t) {
var query = generate({
'point.lat': 12.12121,
'point.lon': 21.21212,
'boundary.circle.lat': 12.12121,
'boundary.circle.lon': 21.21212
});
var compiled = JSON.parse( JSON.stringify( query ) ); module.exports.tests.layers = (test, common) => {
var expected = '1km'; test('non-array clean.layers should not set sources in vs', t => {
const clean = {
layers: 'this is not an array'
};
t.deepEqual(compiled.type, 'reverse', 'query type set'); const query = proxyquire('../../../query/reverse', {
t.deepEqual(compiled.body.query.bool.filter[0].geo_distance.distance, expected, 'distance set to default boundary circle radius'); 'pelias-query': {
layout: {
FilteredBooleanQuery: MockQuery
},
view: views,
Vars: require('pelias-query').Vars
},
'./reverse_defaults': {}
})(clean);
t.notOk(query.body.vs.isset('layers'));
t.end(); t.end();
}); });
test('boundary.circle lat/lon/radius - overrides point.lat/lon when set', function(t) { test('empty array clean.layers should not set sources in vs', t => {
var clean = { const clean = {
'point.lat': 29.49136, layers: []
'point.lon': -82.50622,
'boundary.circle.lat': 111,
'boundary.circle.lon': 333,
'boundary.circle.radius': 3
}; };
var query = generate(clean);
var compiled = JSON.parse( JSON.stringify( query ) );
// this should not equal `point.lat` and `point.lon` as it was explitely specified const query = proxyquire('../../../query/reverse', {
var expected = { lat: clean['boundary.circle.lat'], lon: clean['boundary.circle.lon'] }; 'pelias-query': {
var centroid = compiled.body.query.bool.filter[0].geo_distance.center_point; layout: {
FilteredBooleanQuery: MockQuery
},
view: views,
Vars: require('pelias-query').Vars
},
'./reverse_defaults': {}
})(clean);
t.notOk(query.body.vs.isset('layers'));
t.end();
});
test('non-empty array clean.layers should only set non-coarse layers in vs', t => {
const clean = {
layers: all_layers
};
t.deepEqual(compiled.type, 'reverse', 'query type set'); const query = proxyquire('../../../query/reverse', {
t.deepEqual(centroid, expected, 'reverse: boundary.circle/lon overrides point.lat/lon'); 'pelias-query': {
layout: {
FilteredBooleanQuery: MockQuery
},
view: views,
Vars: require('pelias-query').Vars
},
'./reverse_defaults': {}
})(clean);
t.deepEquals(query.body.vs.var('layers').toString(), ['address', 'venue', 'street']);
t.end(); t.end();
}); });
test('size fuzz test', function(t) { };
// test different sizes
var sizes = [1,4,20,undefined,null]; module.exports.tests.focus_point = (test, common) => {
var expected = [1,4,20,1,1]; test('numeric point.lat and non-numeric point.lon should not add focus:point:* fields', t => {
sizes.forEach( function( size, index ){ const clean = {
var query = generate({ 'point.lat': 12.121212,
'point.lat': 29.49136, 'point.lon': -82.50622, querySize: size 'point.lon': 'this is non-numeric'
}); };
var compiled = JSON.parse( JSON.stringify( query ) ); const query = proxyquire('../../../query/reverse', {
t.equal( compiled.body.size, expected[index], 'valid reverse query for size: '+ size); 'pelias-query': {
}); layout: {
FilteredBooleanQuery: MockQuery
},
view: views,
Vars: require('pelias-query').Vars
},
'./reverse_defaults': {}
})(clean);
t.notOk(query.body.vs.isset('focus:point:lat'));
t.notOk(query.body.vs.isset('focus:point:lon'));
t.end(); t.end();
}); });
test('valid boundary.country reverse search', function(t) { test('non-numeric point.lat and numeric point.lon should not add focus:point:* fields', t => {
var query = generate({ const clean = {
'point.lat': 29.49136, 'point.lat': 'this is non-numeric',
'point.lon': -82.50622, 'point.lon': 21.212121
'boundary.circle.lat': 29.49136, };
'boundary.circle.lon': -82.50622,
'boundary.circle.radius': 3,
'boundary.country': 'ABC'
});
var compiled = JSON.parse( JSON.stringify( query ) ); const query = proxyquire('../../../query/reverse', {
var expected = require('../fixture/reverse_with_boundary_country'); 'pelias-query': {
layout: {
FilteredBooleanQuery: MockQuery
},
view: views,
Vars: require('pelias-query').Vars
},
'./reverse_defaults': {}
})(clean);
t.notOk(query.body.vs.isset('focus:point:lat'));
t.notOk(query.body.vs.isset('focus:point:lon'));
t.end();
t.deepEqual(compiled.type, 'reverse', 'query type set'); });
t.deepEqual(compiled.body, expected, 'valid reverse query with boundary.country');
test('numeric point.lat and point.lon should add focus:point:* fields', t => {
const clean = {
'point.lat': 12.121212,
'point.lon': 21.212121
};
const query = proxyquire('../../../query/reverse', {
'pelias-query': {
layout: {
FilteredBooleanQuery: MockQuery
},
view: views,
Vars: require('pelias-query').Vars
},
'./reverse_defaults': {}
})(clean);
t.deepEquals(query.body.vs.var('focus:point:lat').toString(), 12.121212);
t.deepEquals(query.body.vs.var('focus:point:lon').toString(), 21.212121);
t.end(); t.end();
}); });
test('valid sources filter', function(t) { };
var query = generate({
'point.lat': 29.49136,
'point.lon': -82.50622,
'boundary.circle.lat': 29.49136,
'boundary.circle.lon': -82.50622,
'boundary.circle.radius': 3,
'sources': ['test']
});
var compiled = JSON.parse( JSON.stringify( query ) ); module.exports.tests.boundary_circle = (test, common) => {
var expected = require('../fixture/reverse_with_source_filtering'); test('numeric lat and non-numeric lon should not add boundary:circle:* fields', t => {
const clean = {
'boundary.circle.lat': 12.121212,
'boundary.circle.lon': 'this is non-numeric'
};
t.deepEqual(compiled.type, 'reverse', 'query type set'); const query = proxyquire('../../../query/reverse', {
t.deepEqual(compiled.body, expected, 'valid reverse query with source filtering'); 'pelias-query': {
layout: {
FilteredBooleanQuery: MockQuery
},
view: views,
Vars: require('pelias-query').Vars
},
'./reverse_defaults': {}
})(clean);
t.notOk(query.body.vs.isset('boundary:circle:lat'));
t.notOk(query.body.vs.isset('boundary:circle:lon'));
t.notOk(query.body.vs.isset('boundary:circle:radius'));
t.end(); t.end();
}); });
test('valid layers filter', (t) => { test('non-numeric lat and numeric lon should not add boundary:circle:* fields', t => {
const query = generate({ const clean = {
'point.lat': 29.49136, 'boundary.circle.lat': 'this is non-numeric',
'point.lon': -82.50622, 'boundary.circle.lon': 21.212121
'boundary.circle.lat': 29.49136, };
'boundary.circle.lon': -82.50622,
'boundary.circle.radius': 3, const query = proxyquire('../../../query/reverse', {
// only venue, address, and street layers should be retained 'pelias-query': {
'layers': ['neighbourhood', 'venue', 'locality', 'address', 'region', 'street', 'country'] layout: {
}); FilteredBooleanQuery: MockQuery
},
view: views,
Vars: require('pelias-query').Vars
},
'./reverse_defaults': {}
})(clean);
t.notOk(query.body.vs.isset('boundary:circle:lat'));
t.notOk(query.body.vs.isset('boundary:circle:lon'));
t.notOk(query.body.vs.isset('boundary:circle:radius'));
t.end();
});
test('radius not supplied should default to value from reverse_defaults', t => {
const clean = {
'boundary.circle.lat': 12.121212,
'boundary.circle.lon': 21.212121
};
const query = proxyquire('../../../query/reverse', {
'pelias-query': {
layout: {
FilteredBooleanQuery: MockQuery
},
view: views,
Vars: require('pelias-query').Vars
},
'./reverse_defaults': {
'boundary:circle:radius': 17
}
})(clean);
t.deepEquals(query.body.vs.var('boundary:circle:lat').toString(), 12.121212);
t.deepEquals(query.body.vs.var('boundary:circle:lon').toString(), 21.212121);
t.deepEquals(query.body.vs.var('boundary:circle:radius').toString(), 17);
t.end();
});
test('numeric radius supplied should be used instead of value from reverse_defaults', t => {
const clean = {
'boundary.circle.lat': 12.121212,
'boundary.circle.lon': 21.212121,
'boundary.circle.radius': 17
};
const compiled = JSON.parse( JSON.stringify( query ) ); const query = proxyquire('../../../query/reverse', {
const expected = require('../fixture/reverse_with_layer_filtering'); 'pelias-query': {
layout: {
FilteredBooleanQuery: MockQuery
},
view: views,
Vars: require('pelias-query').Vars
},
'./reverse_defaults': {
'boundary:circle:radius': 18
}
})(clean);
t.deepEquals(query.body.vs.var('boundary:circle:lat').toString(), 12.121212);
t.deepEquals(query.body.vs.var('boundary:circle:lon').toString(), 21.212121);
t.deepEquals(query.body.vs.var('boundary:circle:radius').toString(), '17km');
t.end();
});
test('non-numeric radius supplied should not set any boundary:circle:radius', t => {
const clean = {
'boundary.circle.lat': 12.121212,
'boundary.circle.lon': 21.212121,
'boundary.circle.radius': 'this is non-numeric'
};
t.deepEqual(compiled.type, 'reverse', 'query type set'); const query = proxyquire('../../../query/reverse', {
t.deepEqual(compiled.body, expected, 'valid reverse query with source filtering'); 'pelias-query': {
layout: {
FilteredBooleanQuery: MockQuery
},
view: views,
Vars: require('pelias-query').Vars
},
'./reverse_defaults': {
'boundary:circle:radius': 18
}
})(clean);
t.deepEquals(query.body.vs.var('boundary:circle:lat').toString(), 12.121212);
t.deepEquals(query.body.vs.var('boundary:circle:lon').toString(), 21.212121);
t.deepEquals(query.body.vs.var('boundary:circle:radius').toString(), 18);
t.end(); t.end();
}); });
test('valid layers filter - subset of non-coarse layers', (t) => { };
const query = generate({
'point.lat': 29.49136, module.exports.tests.boundary_country = (test, common) => {
'point.lon': -82.50622, test('non-string boundary.country should not set boundary:country', t => {
'boundary.circle.lat': 29.49136, [17, undefined, {}, [], true, null].forEach(value => {
'boundary.circle.lon': -82.50622, const clean = {
'boundary.circle.radius': 3, 'boundary.country': value
// only venue, address, and street layers should be retained };
'layers': ['neighbourhood', 'venue', 'street', 'locality']
const query = proxyquire('../../../query/reverse', {
'pelias-query': {
layout: {
FilteredBooleanQuery: MockQuery
},
view: views,
Vars: require('pelias-query').Vars
},
'./reverse_defaults': {}
})(clean);
t.notOk(query.body.vs.isset('boundary:country'));
}); });
const compiled = JSON.parse( JSON.stringify( query ) ); t.end();
const expected = require('../fixture/reverse_with_layer_filtering_non_coarse_subset');
});
test('string boundary.country should set boundary:country', t => {
const clean = {
'boundary.country': 'boundary country value'
};
const query = proxyquire('../../../query/reverse', {
'pelias-query': {
layout: {
FilteredBooleanQuery: MockQuery
},
view: views,
Vars: require('pelias-query').Vars
},
'./reverse_defaults': {}
})(clean);
t.deepEquals(query.body.vs.var('boundary:country').toString(), 'boundary country value');
t.end();
});
};
module.exports.tests.categories = (test, common) => {
test('categories supplied should set input:categories', t => {
const clean = {
categories: 'categories value'
};
t.deepEqual(compiled.type, 'reverse', 'query type set'); const query = proxyquire('../../../query/reverse', {
t.deepEqual(compiled.body, expected, 'valid reverse query with source filtering'); 'pelias-query': {
layout: {
FilteredBooleanQuery: MockQuery
},
view: views,
Vars: require('pelias-query').Vars
},
'./reverse_defaults': {}
})(clean);
t.deepEquals(query.body.vs.var('input:categories').toString(), 'categories value');
t.end(); t.end();
}); });

48
test/unit/query/search.js

@ -99,54 +99,6 @@ module.exports.tests.query = function(test, common) {
t.end(); t.end();
}); });
test('search search + viewport', function(t) {
var clean = {
parsed_text: {
street: 'street value'
},
text: 'test', querySize: 10,
'focus.viewport.min_lat': 28.49136,
'focus.viewport.max_lat': 30.49136,
'focus.viewport.min_lon': -87.50622,
'focus.viewport.max_lon': -77.50622,
layers: ['test']
};
var query = generate(clean);
var compiled = JSON.parse( JSON.stringify( query ) );
var expected = require('../fixture/search_linguistic_viewport');
t.deepEqual(compiled.type, 'fallback', 'query type set');
t.deepEqual(compiled.body, expected, 'search_linguistic_viewport');
t.end();
});
// viewport scale sizing currently disabled.
// ref: https://github.com/pelias/api/pull/388
test('search with viewport diagonal < 1km should set scale to 1km', function(t) {
var clean = {
parsed_text: {
street: 'street value'
},
text: 'test', querySize: 10,
'focus.viewport.min_lat': 28.49135,
'focus.viewport.max_lat': 28.49137,
'focus.viewport.min_lon': -87.50622,
'focus.viewport.max_lon': -87.50624,
layers: ['test']
};
var query = generate(clean);
var compiled = JSON.parse( JSON.stringify( query ) );
var expected = require('../fixture/search_linguistic_viewport_min_diagonal');
t.deepEqual(compiled.type, 'fallback', 'query type set');
t.deepEqual(compiled.body, expected, 'valid search query');
t.end();
});
test('search search + focus on null island', function(t) { test('search search + focus on null island', function(t) {
var clean = { var clean = {
parsed_text: { parsed_text: {

663
test/unit/query/structured_geocoding.js

@ -1,253 +1,606 @@
var generate = require('../../../query/structured_geocoding'); const generate = require('../../../query/structured_geocoding');
var fs = require('fs'); const _ = require('lodash');
const proxyquire = require('proxyquire').noCallThru();
const MockQuery = require('./MockQuery');
// helper for canned views
const views = {
focus_only_function: () => 'focus_only_function view',
popularity_only_function: 'popularity_only_function view',
population_only_function: 'population_only_function view',
boundary_country: 'boundary_country view',
boundary_circle: 'boundary_circle view',
boundary_rect: 'boundary_rect view',
sources: 'sources view',
layers: 'layers view',
categories: 'categories view'
};
module.exports.tests = {}; module.exports.tests = {};
module.exports.tests.interface = function(test, common) { module.exports.tests.interface = (test, common) => {
test('valid interface', function(t) { test('valid interface', t => {
t.equal(typeof generate, 'function', 'valid function'); t.equal(typeof generate, 'function', 'valid function');
t.end(); t.end();
}); });
}; };
module.exports.tests.query = function(test, common) { module.exports.tests.query = (test, common) => {
test('valid search + focus + bbox', function(t) { test('base no frills', t => {
var clean = { const clean = {
parsed_text: { text: 'text value',
}, sources: 'sources value',
text: 'test', layers: 'layers value'
querySize: 10,
'focus.point.lat': 29.49136, 'focus.point.lon': -82.50622,
'boundary.rect.min_lat': 47.47,
'boundary.rect.max_lon': -61.84,
'boundary.rect.max_lat': 11.51,
'boundary.rect.min_lon': -103.16,
layers: ['test']
}; };
var query = generate(clean); const query = proxyquire('../../../query/structured_geocoding', {
'pelias-query': {
var compiled = JSON.parse( JSON.stringify( query ) ); layout: {
var expected = require('../fixture/structured_geocoding/linguistic_focus_bbox'); StructuredFallbackQuery: MockQuery
},
view: views,
Vars: require('pelias-query').Vars
},
'./search_defaults': {
default_parameter_1: 'first default parameter',
default_parameter_2: 'second default parameter'
},
'./text_parser': () => {
t.fail('text_parser should not have been called');
}
})(clean);
t.equals(query.type, 'fallback', 'query type set');
t.equals(query.body.vs.var('input:name').toString(), 'text value');
t.equals(query.body.vs.var('sources').toString(), 'sources value');
t.equals(query.body.vs.var('layers').toString(), 'layers value');
t.deepEquals(query.body.vs.var('default_parameter_1').toString(), 'first default parameter');
t.deepEquals(query.body.vs.var('default_parameter_2').toString(), 'second default parameter');
t.notOk(query.body.vs.isset('size'));
t.notOk(query.body.vs.isset('focus:point:lat'));
t.notOk(query.body.vs.isset('focus:point:lon'));
t.notOk(query.body.vs.isset('boundary:rect:top'));
t.notOk(query.body.vs.isset('boundary:rect:right'));
t.notOk(query.body.vs.isset('boundary:rect:bottom'));
t.notOk(query.body.vs.isset('boundary:rect:left'));
t.notOk(query.body.vs.isset('boundary:circle:lat'));
t.notOk(query.body.vs.isset('boundary:circle:lon'));
t.notOk(query.body.vs.isset('boundary:circle:radius'));
t.notOk(query.body.vs.isset('boundary:country'));
t.deepEquals(query.body.score_functions, [
'focus_only_function view',
'popularity_only_function view',
'population_only_function view'
]);
t.deepEquals(query.body.filter_functions, [
'boundary_country view',
'boundary_circle view',
'boundary_rect view',
'sources view',
'layers view',
'categories view'
]);
t.deepEqual(compiled.type, 'fallback', 'query type set');
t.deepEqual(compiled.body, expected, 'search_linguistic_focus_bbox');
t.end(); t.end();
}); });
test('valid search + bbox', function(t) { test('clean.parsed_text and vs should be passed to textParser', t => {
var clean = { const clean = {
parsed_text: { text: 'text value',
sources: 'sources value',
layers: 'layers value',
parsed_text: 'parsed_text value'
};
const query = proxyquire('../../../query/structured_geocoding', {
'pelias-query': {
layout: {
StructuredFallbackQuery: MockQuery
},
view: views,
Vars: require('pelias-query').Vars
},
'./search_defaults': {
default_parameter_1: 'first default parameter',
default_parameter_2: 'second default parameter'
}, },
text: 'test', './text_parser': (parsed_text, vs) => {
querySize: 10, vs.var('text_parser:value', 'this value populated by text_parser');
'boundary.rect.min_lat': 47.47, }
'boundary.rect.max_lon': -61.84, })(clean);
'boundary.rect.max_lat': 11.51,
'boundary.rect.min_lon': -103.16, t.deepEquals(query.body.vs.var('text_parser:value').toString(), 'this value populated by text_parser');
layers: ['test'] t.end();
});
};
module.exports.tests.query_size = (test, common) => {
test('size should be set when querySize is available', t => {
const clean = {
text: 'text value',
sources: 'sources value',
layers: 'layers value',
querySize: 17
}; };
var query = generate(clean); const query = proxyquire('../../../query/structured_geocoding', {
'pelias-query': {
layout: {
StructuredFallbackQuery: MockQuery
},
view: views,
Vars: require('pelias-query').Vars
},
'./search_defaults': { },
'./text_parser': () => {
t.fail('text_parser should not have been called');
}
})(clean);
var compiled = JSON.parse( JSON.stringify( query ) ); t.equals(query.body.vs.var('size').toString(), 17);
var expected = require('../fixture/structured_geocoding/linguistic_bbox');
t.deepEqual(compiled.type, 'fallback', 'query type set');
t.deepEqual(compiled.body, expected, 'search_linguistic_bbox');
t.end(); t.end();
}); });
test('valid lingustic-only search', function(t) { };
var clean = {
parsed_text: { module.exports.tests.focus_point_lat_lon = (test, common) => {
}, test('missing focus.point.lat shouldn\'t set focus:point:lat/lon', t => {
text: 'test', querySize: 10, const clean = {
layers: ['test'] text: 'text value',
sources: 'sources value',
layers: 'layers value',
'focus.point.lon': 21.212121
}; };
var query = generate(clean); const query = proxyquire('../../../query/structured_geocoding', {
'pelias-query': {
layout: {
StructuredFallbackQuery: MockQuery
},
view: views,
Vars: require('pelias-query').Vars
},
'./search_defaults': { },
'./text_parser': () => {
t.fail('text_parser should not have been called');
}
})(clean);
var compiled = JSON.parse( JSON.stringify( query ) ); t.notOk(query.body.vs.isset('focus:point:lat'));
var expected = require('../fixture/structured_geocoding/linguistic_only'); t.notOk(query.body.vs.isset('focus:point:lon'));
t.deepEqual(compiled.type, 'fallback', 'query type set');
t.deepEqual(compiled.body, expected, 'search_linguistic_only');
t.end(); t.end();
}); });
test('search search + focus', function(t) { test('missing focus.point.lon shouldn\'t set focus:point:lat/lon', t => {
var clean = { const clean = {
parsed_text: { text: 'text value',
}, sources: 'sources value',
text: 'test', querySize: 10, layers: 'layers value',
'focus.point.lat': 29.49136, 'focus.point.lon': -82.50622, 'focus.point.lat': 12.121212
layers: ['test']
}; };
var query = generate(clean); const query = proxyquire('../../../query/structured_geocoding', {
'pelias-query': {
layout: {
StructuredFallbackQuery: MockQuery
},
view: views,
Vars: require('pelias-query').Vars
},
'./search_defaults': { },
'./text_parser': () => {
t.fail('text_parser should not have been called');
}
})(clean);
var compiled = JSON.parse( JSON.stringify( query ) ); t.notOk(query.body.vs.isset('focus:point:lat'));
var expected = require('../fixture/structured_geocoding/linguistic_focus'); t.notOk(query.body.vs.isset('focus:point:lon'));
t.deepEqual(compiled.type, 'fallback', 'query type set');
t.deepEqual(compiled.body, expected, 'search_linguistic_focus');
t.end(); t.end();
}); });
test('search search + viewport', function(t) { test('focus.point.lat/lon should set focus:point:lat/lon', t => {
var clean = { const clean = {
parsed_text: { text: 'text value',
}, sources: 'sources value',
text: 'test', querySize: 10, layers: 'layers value',
'focus.viewport.min_lat': 28.49136, 'focus.point.lat': 12.121212,
'focus.viewport.max_lat': 30.49136, 'focus.point.lon': 21.212121
'focus.viewport.min_lon': -87.50622,
'focus.viewport.max_lon': -77.50622,
layers: ['test']
}; };
var query = generate(clean); const query = proxyquire('../../../query/structured_geocoding', {
'pelias-query': {
layout: {
StructuredFallbackQuery: MockQuery
},
view: views,
Vars: require('pelias-query').Vars
},
'./search_defaults': { },
'./text_parser': () => {
t.fail('text_parser should not have been called');
}
})(clean);
var compiled = JSON.parse( JSON.stringify( query ) ); t.equals(query.body.vs.var('focus:point:lat').toString(), 12.121212);
var expected = require('../fixture/structured_geocoding/linguistic_viewport'); t.equals(query.body.vs.var('focus:point:lon').toString(), 21.212121);
t.deepEqual(compiled.type, 'fallback', 'query type set');
t.deepEqual(compiled.body, expected, 'search_linguistic_viewport');
t.end(); t.end();
}); });
// viewport scale sizing currently disabled. };
// ref: https://github.com/pelias/api/pull/388
test('search with viewport diagonal < 1km should set scale to 1km', function(t) { module.exports.tests.boundary_rect = (test, common) => {
var clean = { test('missing boundary.rect.min_lat shouldn\'t set boundary:rect fields', t => {
parsed_text: { const clean = {
}, text: 'text value',
text: 'test', querySize: 10, sources: 'sources value',
'focus.viewport.min_lat': 28.49135, layers: 'layers value',
'focus.viewport.max_lat': 28.49137, 'boundary.rect.max_lat': 13.131313,
'focus.viewport.min_lon': -87.50622, 'boundary.rect.min_lon': 21.212121,
'focus.viewport.max_lon': -87.50624, 'boundary.rect.max_lon': 31.313131
layers: ['test']
}; };
var query = generate(clean); const query = proxyquire('../../../query/structured_geocoding', {
'pelias-query': {
layout: {
StructuredFallbackQuery: MockQuery
},
view: views,
Vars: require('pelias-query').Vars
},
'./search_defaults': { },
'./text_parser': () => {
t.fail('text_parser should not have been called');
}
})(clean);
var compiled = JSON.parse( JSON.stringify( query ) ); t.notOk(query.body.vs.isset('boundary:rect:top'));
var expected = require('../fixture/structured_geocoding/linguistic_viewport_min_diagonal'); t.notOk(query.body.vs.isset('boundary:rect:right'));
t.notOk(query.body.vs.isset('boundary:rect:bottom'));
t.notOk(query.body.vs.isset('boundary:rect:left'));
t.deepEqual(compiled.type, 'fallback', 'query type set');
t.deepEqual(compiled.body, expected, 'valid search query');
t.end(); t.end();
}); });
test('search search + focus on null island', function(t) { test('missing boundary.rect.max_lat shouldn\'t set boundary:rect fields', t => {
var clean = { const clean = {
parsed_text: { text: 'text value',
sources: 'sources value',
layers: 'layers value',
'boundary.rect.min_lat': 12.121212,
'boundary.rect.min_lon': 21.212121,
'boundary.rect.max_lon': 31.313131
};
const query = proxyquire('../../../query/structured_geocoding', {
'pelias-query': {
layout: {
StructuredFallbackQuery: MockQuery
},
view: views,
Vars: require('pelias-query').Vars
}, },
text: 'test', querySize: 10, './search_defaults': { },
'focus.point.lat': 0, 'focus.point.lon': 0, './text_parser': () => {
layers: ['test'] t.fail('text_parser should not have been called');
}
})(clean);
t.notOk(query.body.vs.isset('boundary:rect:top'));
t.notOk(query.body.vs.isset('boundary:rect:right'));
t.notOk(query.body.vs.isset('boundary:rect:bottom'));
t.notOk(query.body.vs.isset('boundary:rect:left'));
t.end();
});
test('missing boundary.rect.min_lon shouldn\'t set boundary:rect fields', t => {
const clean = {
text: 'text value',
sources: 'sources value',
layers: 'layers value',
'boundary.rect.min_lat': 12.121212,
'boundary.rect.max_lat': 13.131313,
'boundary.rect.max_lon': 31.313131
}; };
var query = generate(clean); const query = proxyquire('../../../query/structured_geocoding', {
'pelias-query': {
layout: {
StructuredFallbackQuery: MockQuery
},
view: views,
Vars: require('pelias-query').Vars
},
'./search_defaults': { },
'./text_parser': () => {
t.fail('text_parser should not have been called');
}
})(clean);
var compiled = JSON.parse( JSON.stringify( query ) ); t.notOk(query.body.vs.isset('boundary:rect:top'));
var expected = require('../fixture/structured_geocoding/linguistic_focus_null_island'); t.notOk(query.body.vs.isset('boundary:rect:right'));
t.notOk(query.body.vs.isset('boundary:rect:bottom'));
t.notOk(query.body.vs.isset('boundary:rect:left'));
t.deepEqual(compiled.type, 'fallback', 'query type set');
t.deepEqual(compiled.body, expected, 'search_linguistic_focus_null_island');
t.end(); t.end();
}); });
test('parsed_text with all fields should use FallbackQuery', function(t) { test('missing boundary.rect.max_lon shouldn\'t set boundary:rect fields', t => {
var clean = { const clean = {
parsed_text: { text: 'text value',
query: 'query value', sources: 'sources value',
category: 'category value', layers: 'layers value',
number: 'number value', 'boundary.rect.min_lat': 12.121212,
street: 'street value', 'boundary.rect.max_lat': 13.131313,
neighbourhood: 'neighbourhood value', 'boundary.rect.min_lon': 21.212121
borough: 'borough value', };
postalcode: 'postalcode value',
city: 'city value', const query = proxyquire('../../../query/structured_geocoding', {
county: 'county value', 'pelias-query': {
state: 'state value', layout: {
country: 'country value' StructuredFallbackQuery: MockQuery
},
view: views,
Vars: require('pelias-query').Vars
},
'./search_defaults': { },
'./text_parser': () => {
t.fail('text_parser should not have been called');
} }
})(clean);
t.notOk(query.body.vs.isset('boundary:rect:top'));
t.notOk(query.body.vs.isset('boundary:rect:right'));
t.notOk(query.body.vs.isset('boundary:rect:bottom'));
t.notOk(query.body.vs.isset('boundary:rect:left'));
t.end();
});
test('focus.point.lat/lon should set focus:point:lat/lon', t => {
const clean = {
text: 'text value',
sources: 'sources value',
layers: 'layers value',
'boundary.rect.min_lat': 12.121212,
'boundary.rect.max_lat': 13.131313,
'boundary.rect.min_lon': 21.212121,
'boundary.rect.max_lon': 31.313131
}; };
var query = generate(clean); const query = proxyquire('../../../query/structured_geocoding', {
'pelias-query': {
layout: {
StructuredFallbackQuery: MockQuery
},
view: views,
Vars: require('pelias-query').Vars
},
'./search_defaults': { },
'./text_parser': () => {
t.fail('text_parser should not have been called');
}
})(clean);
var compiled = JSON.parse(JSON.stringify(query)); t.equals(query.body.vs.var('boundary:rect:top').toString(), 13.131313);
var expected = require('../fixture/structured_geocoding/fallback'); t.equals(query.body.vs.var('boundary:rect:right').toString(), 31.313131);
t.equals(query.body.vs.var('boundary:rect:bottom').toString(), 12.121212);
t.equals(query.body.vs.var('boundary:rect:left').toString(), 21.212121);
t.deepEqual(compiled.type, 'fallback', 'query type set');
t.deepEqual(compiled.body, expected, 'fallbackQuery');
t.end(); t.end();
}); });
test('parsed_text with all fields should use FallbackQuery', function(t) { };
var clean = {
parsed_text: { module.exports.tests.boundary_circle = (test, common) => {
postalcode: 'postalcode value' test('missing boundary.circle.lat shouldn\'t set boundary:circle fields', t => {
const clean = {
text: 'text value',
sources: 'sources value',
layers: 'layers value',
'boundary.circle.lon': 21.212121,
'boundary.circle.radius': 17
};
const query = proxyquire('../../../query/structured_geocoding', {
'pelias-query': {
layout: {
StructuredFallbackQuery: MockQuery
},
view: views,
Vars: require('pelias-query').Vars
},
'./search_defaults': { },
'./text_parser': () => {
t.fail('text_parser should not have been called');
} }
})(clean);
t.notOk(query.body.vs.isset('boundary:circle:lat'));
t.notOk(query.body.vs.isset('boundary:circle:lon'));
t.notOk(query.body.vs.isset('boundary:circle:radius'));
t.end();
});
test('missing boundary.circle.lon shouldn\'t set boundary:circle fields', t => {
const clean = {
text: 'text value',
sources: 'sources value',
layers: 'layers value',
'boundary.circle.lat': 12.121212,
'boundary.circle.radius': 17
}; };
var query = generate(clean); const query = proxyquire('../../../query/structured_geocoding', {
'pelias-query': {
layout: {
StructuredFallbackQuery: MockQuery
},
view: views,
Vars: require('pelias-query').Vars
},
'./search_defaults': { },
'./text_parser': () => {
t.fail('text_parser should not have been called');
}
})(clean);
var compiled = JSON.parse(JSON.stringify(query)); t.notOk(query.body.vs.isset('boundary:circle:lat'));
var expected = require('../fixture/structured_geocoding/postalcode_only'); t.notOk(query.body.vs.isset('boundary:circle:lon'));
t.notOk(query.body.vs.isset('boundary:circle:radius'));
t.deepEqual(compiled.type, 'fallback', 'query type set');
t.deepEqual(compiled.body, expected, 'structured postalcode only');
t.end(); t.end();
}); });
test('valid boundary.country search', function(t) { test('missing boundary.circle.radius should set lat/lon but not radius', t => {
var clean = { const clean = {
parsed_text: { text: 'text value',
sources: 'sources value',
layers: 'layers value',
'boundary.circle.lat': 12.121212,
'boundary.circle.lon': 21.212121
};
const query = proxyquire('../../../query/structured_geocoding', {
'pelias-query': {
layout: {
StructuredFallbackQuery: MockQuery
},
view: views,
Vars: require('pelias-query').Vars
}, },
text: 'test', querySize: 10, './search_defaults': { },
layers: ['test'], './text_parser': () => {
'boundary.country': 'ABC' t.fail('text_parser should not have been called');
}
})(clean);
t.equals(query.body.vs.var('boundary:circle:lat').toString(), 12.121212);
t.equals(query.body.vs.var('boundary:circle:lon').toString(), 21.212121);
t.notOk(query.body.vs.isset('boundary:circle:radius'));
t.end();
});
test('boundary.circle.* should set lat/lon/radius with last rounded and in kilometers', t => {
const clean = {
text: 'text value',
sources: 'sources value',
layers: 'layers value',
'boundary.circle.lat': 12.121212,
'boundary.circle.lon': 21.212121,
'boundary.circle.radius': 17.5
}; };
var query = generate(clean); const query = proxyquire('../../../query/structured_geocoding', {
'pelias-query': {
layout: {
StructuredFallbackQuery: MockQuery
},
view: views,
Vars: require('pelias-query').Vars
},
'./search_defaults': { },
'./text_parser': () => {
t.fail('text_parser should not have been called');
}
})(clean);
var compiled = JSON.parse( JSON.stringify( query ) ); t.equals(query.body.vs.var('boundary:circle:lat').toString(), 12.121212);
var expected = require('../fixture/structured_geocoding/boundary_country'); t.equals(query.body.vs.var('boundary:circle:lon').toString(), 21.212121);
t.equals(query.body.vs.var('boundary:circle:radius').toString(), '18km');
t.deepEqual(compiled.type, 'fallback', 'query type set');
t.deepEqual(compiled.body, expected, 'search: valid boundary.country query');
t.end(); t.end();
}); });
test('valid sources filter', function(t) { test('missing boundary.circle.lat/lon but existing boundary.circle.radius should not set any', t => {
var clean = { const clean = {
parsed_text: { text: 'text value',
sources: 'sources value',
layers: 'layers value',
'boundary.circle.radius': 17
};
const query = proxyquire('../../../query/structured_geocoding', {
'pelias-query': {
layout: {
StructuredFallbackQuery: MockQuery
},
view: views,
Vars: require('pelias-query').Vars
}, },
'text': 'test', './search_defaults': { },
'sources': ['test_source'] './text_parser': () => {
t.fail('text_parser should not have been called');
}
})(clean);
t.notOk(query.body.vs.isset('boundary:circle:lat'));
t.notOk(query.body.vs.isset('boundary:circle:lon'));
t.notOk(query.body.vs.isset('boundary:circle:radius'));
t.end();
});
};
module.exports.tests.boundary_country = (test, common) => {
test('boundary.country available shoul set boundary:country', t => {
const clean = {
text: 'text value',
sources: 'sources value',
layers: 'layers value',
'boundary.country': 'boundary country value'
}; };
var query = generate(clean); const query = proxyquire('../../../query/structured_geocoding', {
'pelias-query': {
layout: {
StructuredFallbackQuery: MockQuery
},
view: views,
Vars: require('pelias-query').Vars
},
'./search_defaults': { },
'./text_parser': () => {
t.fail('text_parser should not have been called');
}
})(clean);
var compiled = JSON.parse( JSON.stringify( query ) ); t.equals(query.body.vs.var('boundary:country').toString(), 'boundary country value');
var expected = require('../fixture/structured_geocoding/with_source_filtering');
t.deepEqual(compiled.type, 'fallback', 'query type set');
t.deepEqual(compiled.body, expected, 'search: valid search query with source filtering');
t.end(); t.end();
}); });
}; };
module.exports.all = function (tape, common) { module.exports.all = (tape, common) => {
function test(name, testFunction) { function test(name, testFunction) {
return tape('structured_geocoding query ' + name, testFunction); return tape(`structured_geocoding query ${name}`, testFunction);
} }
for( var testCase in module.exports.tests ){ for( var testCase in module.exports.tests ){

1
test/unit/run.js

@ -31,6 +31,7 @@ var tests = [
require('./controller/predicates/is_request_sources_only_whosonfirst'), require('./controller/predicates/is_request_sources_only_whosonfirst'),
require('./helper/debug'), require('./helper/debug'),
require('./helper/diffPlaces'), require('./helper/diffPlaces'),
require('./helper/geojsonify_place_details'),
require('./helper/geojsonify'), require('./helper/geojsonify'),
require('./helper/logging'), require('./helper/logging'),
require('./helper/type_mapping'), require('./helper/type_mapping'),

19
test/unit/sanitizer/_geo_common.js

@ -216,8 +216,8 @@ module.exports.tests.rect = function(test, common) {
test('valid rect quad', function (t) { test('valid rect quad', function (t) {
var clean = {}; var clean = {};
var params = { var params = {
'boundary.rect.min_lat': -40.659, 'boundary.rect.min_lat': -41.614,
'boundary.rect.max_lat': -41.614, 'boundary.rect.max_lat': -40.659,
'boundary.rect.min_lon': 174.612, 'boundary.rect.min_lon': 174.612,
'boundary.rect.max_lon': 176.333 'boundary.rect.max_lon': 176.333
}; };
@ -283,6 +283,21 @@ module.exports.tests.rect = function(test, common) {
t.end(); t.end();
}); });
test('invalid rect - max/min switched', function (t) {
var clean = {};
var params = {
'boundary.rect.max_lat': 52.2387,
'boundary.rect.max_lon': 14.1367,
'boundary.rect.min_lat': 52.7945,
'boundary.rect.min_lon': 12.6398
};
var mandatory = false;
t.throws( function() {
sanitize.sanitize_rect( 'boundary.rect', clean, params, mandatory );
});
t.end();
});
}; };
module.exports.tests.circle = function(test, common) { module.exports.tests.circle = function(test, common) {

46
test/unit/sanitizer/_geonames_deprecation.js

@ -2,6 +2,8 @@ const sanitizer = require('../../../sanitizer/_geonames_deprecation')();
module.exports.tests = {}; module.exports.tests = {};
const coarse_reverse_message ='coarse /reverse does not support geonames. See https://github.com/pelias/pelias/issues/675 for more info';
module.exports.tests.no_warnings_or_errors_conditions = (test, common) => { module.exports.tests.no_warnings_or_errors_conditions = (test, common) => {
test('undefined sources should add neither warnings nor errors', (t) => { test('undefined sources should add neither warnings nor errors', (t) => {
const clean = {}; const clean = {};
@ -27,41 +29,71 @@ module.exports.tests.no_warnings_or_errors_conditions = (test, common) => {
}); });
test('geonames/gn in sources with layers=venue should add neither warnings nor errors', (t) => {
const clean = {
sources: ['geonames'],
layers: ['venue']
};
const messages = sanitizer.sanitize(undefined, clean);
t.deepEquals(clean.sources, ['geonames']);
t.deepEquals(messages, { errors: [], warnings: [] });
t.end();
});
}; };
module.exports.tests.error_conditions = (test, common) => { module.exports.tests.error_conditions = (test, common) => {
test('only geonames in sources should not modify clean.sources and add error message', (t) => { test('only geonames in sources with layers=coarse should not modify clean.sources and add error message', (t) => {
['gn', 'geonames'].forEach((sources) => { ['gn', 'geonames'].forEach((sources) => {
const clean = { const clean = {
sources: [sources] sources: [sources],
layers: ['coarse']
}; };
const messages = sanitizer.sanitize(undefined, clean); const messages = sanitizer.sanitize(undefined, clean);
t.deepEquals(clean.sources, [sources]); t.deepEquals(clean.sources, [sources]);
t.deepEquals(messages.errors, ['/reverse does not support geonames']); t.deepEquals(messages.errors, [ coarse_reverse_message ]);
t.deepEquals(messages.warnings, []); t.deepEquals(messages.warnings, []);
}); });
t.end(); t.end();
}); });
test('only geonames in sources with layers=locality should not modify clean.sources and add error message', (t) => {
['gn', 'geonames'].forEach((sources) => {
const clean = {
sources: [sources],
layers: ['locality']
};
const messages = sanitizer.sanitize(undefined, clean);
t.deepEquals(clean.sources, [sources]);
t.deepEquals(messages.errors, [ coarse_reverse_message ]);
t.deepEquals(messages.warnings, []);
});
t.end();
});
}; };
module.exports.tests.warning_conditions = (test, common) => { module.exports.tests.warning_conditions = (test, common) => {
test('only geonames in sources should not modify clean.sources and add error message', (t) => { test('only geonames in sources and layers=coarse should not modify clean.sources and add error message', (t) => {
['gn', 'geonames'].forEach((sources) => { ['gn', 'geonames'].forEach((sources) => {
const clean = { const clean = {
sources: ['another source', sources, 'yet another source'] sources: ['another source', sources, 'yet another source'],
layers: ['coarse']
}; };
const messages = sanitizer.sanitize(undefined, clean); const messages = sanitizer.sanitize(undefined, clean);
t.deepEquals(clean.sources, ['another source', 'yet another source']); t.deepEquals(clean.sources, ['another source', 'yet another source']);
t.deepEquals(messages.errors, []); t.deepEquals(messages.errors, []);
t.deepEquals(messages.warnings, ['/reverse does not support geonames']); t.deepEquals(messages.warnings, [ coarse_reverse_message ]);
}); });

16
test/unit/sanitizer/_layers.js

@ -41,9 +41,10 @@ module.exports.tests.sanitize_layers = function(test, common) {
sanitizer.sanitize(raw, clean); sanitizer.sanitize(raw, clean);
var admin_layers = [ 'continent', 'country', 'dependency', var admin_layers = [ 'continent', 'empire', 'country', 'dependency', 'macroregion',
'macroregion', 'region', 'locality', 'localadmin', 'macrocounty', 'county', 'region', 'locality', 'localadmin', 'macrocounty', 'county', 'macrohood',
'macrohood', 'borough', 'neighbourhood', 'microhood', 'disputed', 'postalcode' ]; 'borough', 'neighbourhood', 'microhood', 'disputed', 'postalcode', 'ocean',
'marinearea' ];
t.deepEqual(clean.layers, admin_layers, 'coarse layers set'); t.deepEqual(clean.layers, admin_layers, 'coarse layers set');
t.end(); t.end();
@ -76,9 +77,10 @@ module.exports.tests.sanitize_layers = function(test, common) {
sanitizer.sanitize(raw, clean); sanitizer.sanitize(raw, clean);
var expected_layers = [ 'continent', 'country', 'dependency', var expected_layers = [ 'continent', 'empire', 'country', 'dependency',
'macroregion', 'region', 'locality', 'localadmin', 'macrocounty', 'county', 'macroregion', 'region', 'locality', 'localadmin', 'macrocounty', 'county',
'macrohood', 'borough', 'neighbourhood', 'microhood', 'disputed', 'postalcode' ]; 'macrohood', 'borough', 'neighbourhood', 'microhood', 'disputed', 'postalcode',
'ocean', 'marinearea'];
t.deepEqual(clean.layers, expected_layers, 'coarse + regular layers set'); t.deepEqual(clean.layers, expected_layers, 'coarse + regular layers set');
t.end(); t.end();
@ -112,10 +114,10 @@ module.exports.tests.sanitize_layers = function(test, common) {
sanitizer.sanitize(raw, clean); sanitizer.sanitize(raw, clean);
var coarse_layers = [ 'continent', var coarse_layers = [ 'continent', 'empire',
'country', 'dependency', 'macroregion', 'region', 'locality', 'localadmin', 'country', 'dependency', 'macroregion', 'region', 'locality', 'localadmin',
'macrocounty', 'county', 'macrohood', 'borough', 'neighbourhood', 'microhood', 'macrocounty', 'county', 'macrohood', 'borough', 'neighbourhood', 'microhood',
'disputed', 'postalcode' ]; 'disputed', 'postalcode', 'ocean', 'marinearea' ];
var venue_layers = [ 'venue' ]; var venue_layers = [ 'venue' ];
var expected_layers = venue_layers.concat(coarse_layers); var expected_layers = venue_layers.concat(coarse_layers);

56
test/unit/service/configurations/PointInPolygon.js

@ -53,16 +53,62 @@ module.exports.tests.all = (test, common) => {
}); });
test('getParameters should return an empty object', (t) => { test('getParameters should return an empty object when req is undefined', (t) => {
const configBlob = { const configBlob = {
url: 'http://localhost:1234', url: 'http://localhost:1234'
timeout: 17,
retries: 19
}; };
const pointInPolygon = new PointInPolygon(configBlob); const pointInPolygon = new PointInPolygon(configBlob);
t.deepEquals(pointInPolygon.getParameters(), {}); t.deepEquals(pointInPolygon.getParameters(undefined), {});
t.end();
});
test('getParameters should return an empty object when req.clean is undefined', (t) => {
const configBlob = {
url: 'http://localhost:1234'
};
const pointInPolygon = new PointInPolygon(configBlob);
const req = {};
t.deepEquals(pointInPolygon.getParameters(req), {});
t.end();
});
test('getParameters should return an empty object when req.clean.layers is undefined', (t) => {
const configBlob = {
url: 'http://localhost:1234'
};
const pointInPolygon = new PointInPolygon(configBlob);
const req = {
clean: {}
};
t.deepEquals(pointInPolygon.getParameters(req), {});
t.end();
});
test('getParameters should return object with layers property when req.clean.layers is specified', t => {
const configBlob = {
url: 'http://localhost:1234'
};
const pointInPolygon = new PointInPolygon(configBlob);
const req = {
clean: {
layers: ['layer1', 'layer2']
}
};
t.deepEquals(pointInPolygon.getParameters(req), { layers: 'layer1,layer2'});
t.end(); t.end();
}); });

Loading…
Cancel
Save