Browse Source

Filter source and layer by new fields

This is functionally the same code as before, except that it's now
possible to do better logging for invalid source/layer combinations.
wof_focus_weighting
Julian Simioni 9 years ago
parent
commit
b9f3ff1b60
  1. 30
      controller/place.js
  2. 7
      controller/search.js
  3. 2
      helper/text_parser.js
  4. 126
      helper/type_mapping.js
  5. 43
      helper/types.js
  6. 40
      middleware/_types.js
  7. 8
      query/search.js
  8. 11
      sanitiser/_ids.js
  9. 32
      sanitiser/_sources_and_layers.js
  10. 22
      sanitiser/_targets.js
  11. 4
      sanitiser/_text.js
  12. 5
      sanitiser/reverse.js
  13. 5
      sanitiser/search.js
  14. 1
      service/mget.js
  15. 11
      test/unit/controller/place.js
  16. 2
      test/unit/helper/text_parser.js
  17. 130
      test/unit/helper/type_mapping.js
  18. 92
      test/unit/helper/types.js
  19. 1
      test/unit/run.js
  20. 47
      test/unit/sanitiser/_ids.js
  21. 54
      test/unit/sanitiser/_layers.js
  22. 31
      test/unit/sanitiser/_sources.js
  23. 1
      test/unit/sanitiser/_text.js
  24. 2
      test/unit/sanitiser/place.js
  25. 9
      test/unit/sanitiser/reverse.js
  26. 5
      test/unit/sanitiser/search.js

30
controller/place.js

@ -13,40 +13,16 @@ function setup( backend ){
return next();
}
/* req.clean.ids contains an array of objects with id and types properties.
* types is an array of one or more types, since it can't always be known which single
* type a gid might belong to (osmnode and osmway both have source osm and layer venue).
*
* However, the mget Elasticsearch query only accepts a single type at a
* time.
*
* So, first create a new array that, has an entry
* with each type and id combination. This requires creating a new array with more entries
* than req.clean.ids in the case where entries have multiple types.
*/
var recordsToReturn = req.clean.ids.reduce(function (acc, ids_element) {
ids_element.types.forEach(function(type) {
acc.push({
id: ids_element.id,
type: type
});
});
return acc;
}, []);
/*
* Next, map the list of records to an Elasticsearch mget query
*/
var query = recordsToReturn.map( function(id) {
var query = req.clean.ids.map( function(id) {
return {
_index: 'pelias',
_type: id.type,
_type: id.layers,
_id: id.id
};
});
service.mget( backend, query, function( err, docs ) {
console.log('err:' + err);
// error handler
if( err ){

7
controller/search.js

@ -8,10 +8,10 @@ function setup( backend, query ){
query = query || require('../query/search');
function controller( req, res, next ){
// do not run controller when a request
// validation error has occurred.
if( req.errors && req.errors.length ){
delete req.clean.type; //to declutter output
return next();
}
@ -25,9 +25,12 @@ function setup( backend, query ){
body: query( req.clean )
};
// ?
// set the Elasticsearch types to filter by,
// and remove the property from clean so the API
// response output is cleaner
if( req.clean.hasOwnProperty('type') ){
cmd.type = req.clean.type;
delete req.clean.type;
}
logger.debug( '[ES req]', JSON.stringify(cmd) );

2
helper/text_parser.js

@ -15,7 +15,7 @@ module.exports = {};
module.exports.get_layers = function get_layers(query) {
if (query.length <= 3 ) {
// no address parsing required
return type_mapping.layer_with_aliases_to_type.coarse;
return type_mapping.layer_mapping.coarse;
}
};

126
helper/type_mapping.js

@ -1,87 +1,85 @@
var extend = require('extend'),
_ = require('lodash');
var TYPE_TO_SOURCE = {
'geoname': 'gn',
'osmnode': 'osm',
'osmway': 'osm',
'admin0': 'qs',
'admin1': 'qs',
'admin2': 'qs',
'neighborhood': 'qs',
'locality': 'qs',
'local_admin': 'qs',
'osmaddress': 'osm',
'openaddresses': 'oa'
};
function addStandardTargetsToAliases(standard, aliases) {
var combined = _.extend({}, aliases);
standard.forEach(function(target) {
if (combined[target] === undefined) {
combined[target] = [target];
}
});
return combined;
}
/*
* This doesn't include alias layers such as coarse
* Sources
*/
var TYPE_TO_LAYER = {
'geoname': 'venue',
'osmnode': 'venue',
'osmway': 'venue',
'admin0': 'country',
'admin1': 'region',
'admin2': 'county',
'neighborhood': 'neighbourhood',
'locality': 'locality',
'local_admin': 'localadmin',
'osmaddress': 'address',
'openaddresses': 'address'
};
var SOURCE_TO_TYPE = {
'gn' : ['geoname'],
'geonames' : ['geoname'],
'oa' : ['openaddresses'],
'openaddresses' : ['openaddresses'],
'qs' : ['admin0', 'admin1', 'admin2', 'neighborhood', 'locality', 'local_admin'],
'quattroshapes' : ['admin0', 'admin1', 'admin2', 'neighborhood', 'locality', 'local_admin'],
'osm' : ['osmaddress', 'osmnode', 'osmway'],
'openstreetmap' : ['osmaddress', 'osmnode', 'osmway']
// a list of all sources
var SOURCES = ['openstreetmap', 'openaddresses', 'geonames', 'quattroshapes', 'whosonfirst'];
/*
* A list of alternate names for sources, mostly used to save typing
*/
var SOURCE_ALIASES = {
'osm': ['openstreetmap'],
'oa': ['openaddresses'],
'gn': ['geonames'],
'qs': ['quattroshapes'],
'wof': ['whosonfirst']
};
/**
* This does not included alias layers, those are built separately
/*
* Create an object that contains all sources or aliases. The key is the source or alias,
* the value is either that source, or the canonical name for that alias if it's an alias.
*/
var LAYER_TO_TYPE = {
'venue': ['geoname','osmnode','osmway'],
'address': ['osmaddress','openaddresses'],
'country': ['admin0'],
'region': ['admin1'],
'county': ['admin2'],
'locality': ['locality'],
'localadmin': ['local_admin'],
'neighbourhood': ['neighborhood']
var SOURCE_MAPPING = addStandardTargetsToAliases(SOURCES, SOURCE_ALIASES);
/*
* Layers
*/
/*
* A list of all layers in each source. This is used for convenience elswhere
* and to determine when a combination of source and layer parameters is
* not going to match any records and will return no results.
*/
var LAYERS_BY_SOURCE = {
openstreetmap: [ 'address', 'venue' ],
openaddresses: [ 'address' ],
geonames: [ 'country', 'region', 'county', 'locality', 'venue' ],
quattroshapes: ['admin0', 'admin1', 'admin2', 'neighborhood', 'locality', 'local_admin'],
whosonfirst: [ 'continent', 'macrocountry', 'country', 'dependency', 'region',
'locality', 'localadmin', 'county', 'macrohood', 'neighbourhood', 'microhood', 'disputed']
};
/*
* A list of layer aliases that can be used to support specific use cases
* (like coarse geocoding) * or work around the fact that different sources
* may have layers that mean the same thing but have a different name
*/
var LAYER_ALIASES = {
'coarse': ['admin0','admin1','admin2','neighborhood','locality','local_admin']
'coarse': LAYERS_BY_SOURCE.whosonfirst.concat(LAYERS_BY_SOURCE.quattroshapes),
'country': ['country', 'admin0'], // Include both QS and WOF layers for various types of places
'region': ['region', 'admin1'] // Alias layers that include themselves look weird, but do work
};
var LAYER_WITH_ALIASES_TO_TYPE = extend({}, LAYER_ALIASES, LAYER_TO_TYPE);
// create a list of all layers by combining each entry from LAYERS_BY_SOURCE
var LAYERS = _.uniq(Object.keys(LAYERS_BY_SOURCE).reduce(function(acc, key) {
return acc.concat(LAYERS_BY_SOURCE[key]);
}, []));
/*
* derive the list of types, sources, and layers from above mappings
* Create the an object that has a key for each possible layer or alias,
* and returns either that layer, or all the layers in the alias
*/
var TYPES = Object.keys(TYPE_TO_SOURCE);
var SOURCES = Object.keys(SOURCE_TO_TYPE);
var LAYERS = Object.keys(LAYER_TO_TYPE);
var sourceAndLayerToType = function sourceAndLayerToType(source, layer) {
return _.intersection(SOURCE_TO_TYPE[source], LAYER_WITH_ALIASES_TO_TYPE[layer]);
};
var LAYER_MAPPING = addStandardTargetsToAliases(LAYERS, LAYER_ALIASES);
module.exports = {
types: TYPES,
sources: SOURCES,
layers: LAYERS,
type_to_source: TYPE_TO_SOURCE,
type_to_layer: TYPE_TO_LAYER,
source_to_type: SOURCE_TO_TYPE,
layer_to_type: LAYER_TO_TYPE,
layer_with_aliases_to_type: LAYER_WITH_ALIASES_TO_TYPE,
source_and_layer_to_type: sourceAndLayerToType
source_mapping: SOURCE_MAPPING,
layer_mapping: LAYER_MAPPING,
layers_by_source: LAYERS_BY_SOURCE
};

43
helper/types.js

@ -1,43 +0,0 @@
var type_mapping = require( '../helper/type_mapping' );
var _ = require('lodash');
/**
* Different parts of the code express "preferences" for which Elasticsearch types are going to be searched
* This method decides how to combine all the preferences.
*
* @param {Array} clean_types
* @returns {Array}
*/
module.exports = function calculate_types(clean_types) {
//Check that at least one preference of types is defined
if (!clean_types || !(clean_types.from_layers || clean_types.from_sources || clean_types.from_text_parser)) {
throw new Error('clean_types should not be null or undefined');
}
/* the layers and source parameters are cumulative:
* perform a set intersection of their specified types
*/
if (clean_types.from_layers || clean_types.from_sources) {
var types = type_mapping.types;
if (clean_types.from_layers) {
types = _.intersection(types, clean_types.from_layers);
}
if (clean_types.from_sources) {
types = _.intersection(types, clean_types.from_sources);
}
return types;
}
/*
* Type restrictions requested by the address parser should only be used
* if both the source and layers parameters are empty, so do this last
*/
if (clean_types.from_text_parser) {
return clean_types.from_text_parser;
}
throw new Error('no types specified');
};

40
middleware/_types.js

@ -1,43 +1,15 @@
var types_helper = require( '../helper/types' );
/**
* Validate the types specified to be searched.
* Take the layers specified by the layers parameter and use them to set the
* list of Elasticsearch types to filter.
*
* Elasticsearch interprets an empty array of types as "search anything" rather
* than "search nothing", so in the case of an empty array, return an error
* message instead of searching at all.
* This has to be done outside the layers sanitizer since it doesn't know that
* the layers property is eventualy used to choose the _type.
*/
function middleware(req, res, next) {
req.clean = req.clean || {};
if (req.clean.hasOwnProperty('types')) {
try {
var types = types_helper(req.clean.types);
if ((types instanceof Array) && types.length === 0) {
var err = 'You have specified both the `sources` and `layers` ' +
'parameters in a combination that will return no results.';
req.errors.push( err );
}
else {
req.clean.type = types;
}
}
// @todo: refactor this flow, it is confusing as `types_helper()` can throw
// with an error "clean_types should not be null or undefined" which is
// not returned to the user yet the return value CAN trigger a user error.
// I would have liked to throw for BOTH cases and then handle the users errors
// inside the 'catch' but this is not possible.
// also: why are we deleting things from $clean?
catch (err) {
// this means there were no types specified
delete req.clean.types;
}
if (req.clean.hasOwnProperty('layers')) {
req.clean.type = req.clean.layers;
}
next();

8
query/search.js

@ -37,7 +37,7 @@ query.score( peliasQuery.view.admin('neighborhood') );
// non-scoring hard filters
query.filter( peliasQuery.view.boundary_circle );
query.filter( peliasQuery.view.boundary_rect );
query.filter( peliasQuery.view.sources );
// --------------------------------
/**
@ -51,6 +51,8 @@ function generateQuery( clean ){
// input text
vs.var( 'input:name', clean.text );
vs.var( 'sources', clean.sources);
// size
if( clean.querySize ) {
vs.var( 'size', clean.querySize );
@ -119,7 +121,9 @@ function generateQuery( clean ){
textParser( clean.parsed_text, vs );
}
return query.render( vs );
var q = query.render( vs );
//console.log(JSON.stringify(q, null, 2));
return q;
}
// return diagonal distance in km, with min=1

11
sanitiser/_ids.js

@ -45,17 +45,10 @@ function sanitizeId(rawId, messages) {
return;
}
//TODO: remove this once we have a better set of layers for Geonames
var types;
if (source === 'gn' || source === 'geonames') {
types = ['geoname'];
} else {
types = type_mapping.source_and_layer_to_type(source, layer);
}
return {
source: source,
layer: layer,
id: id,
types: types
};
}

32
sanitiser/_sources_and_layers.js

@ -0,0 +1,32 @@
var _ = require( 'lodash' );
var type_mapping = require( '../helper/type_mapping' );
function sanitize( raw, clean ){
var messages = { errors: [], warnings: [] };
var possible_errors = [];
var at_least_one_valid_combination = false;
if (clean.layers && clean.sources) {
clean.sources.forEach(function(source) {
var layers_for_source = type_mapping.layers_by_source[source];
clean.layers.forEach(function(layer) {
if (_.includes(layers_for_source, layer)) {
at_least_one_valid_combination = true;
} else {
var message = 'You have specified both the `sources` and `layers`' +
'parameters in a combination that will return no results: the ' +
source + ' source has nothing in the ' + layer + ' layer';
possible_errors.push(message);
}
});
});
if (!at_least_one_valid_combination) {
messages.errors = possible_errors;
}
}
return messages;
}
module.exports = sanitize;

22
sanitiser/_targets.js

@ -1,25 +1,27 @@
var _ = require('lodash'),
check = require('check-types');
function getValidKeys(mapping) {
return _.uniq(Object.keys(mapping)).join(',');
}
function setup( paramName, targetMap ) {
return function( raw, clean ){
return sanitize( raw, clean, {
paramName: paramName,
targetMap: targetMap,
targetMapKeysString: Object.keys(targetMap).join(',')
targetMapKeysString: getValidKeys(targetMap)
});
};
}
function sanitize( raw, clean, opts ) {
// TODO: remove this next line
var propertyName = opts.paramName;//opts.paramName.slice(0,-1);
// error & warning messages
var messages = { errors: [], warnings: [] };
// init clean.types
clean.types = clean.types || {};
// the string of targets (comma delimeted)
var targetsString = raw[opts.paramName];
@ -51,19 +53,13 @@ function sanitize( raw, clean, opts ) {
// only set types value when no error occured
if( !messages.errors.length ){
// store the values under a new key as 'clean.types.from_*'
var typesKey = 'from_' + opts.paramName;
// ?
clean.types[typesKey] = targets.reduce(function(acc, target) {
clean[propertyName] = targets.reduce(function(acc, target) {
return acc.concat(opts.targetMap[target]);
}, []);
// dedupe in case aliases expanded to common things or user typed in duplicates
clean.types[typesKey] = _.unique(clean.types[typesKey]);
clean[propertyName] = _.unique(clean[propertyName]);
}
}
}

4
sanitiser/_text.js

@ -23,10 +23,6 @@ function sanitize( raw, clean ){
if (check.assigned(parsed_text)) {
clean.parsed_text = parsed_text;
}
// try to set layers from query parser results
clean.types = clean.layers || {};
clean.types.from_text_parser = text_parser.get_layers(clean.text);
}
return messages;

5
sanitiser/reverse.js

@ -3,8 +3,9 @@ var type_mapping = require('../helper/type_mapping');
var sanitizeAll = require('../sanitiser/sanitizeAll'),
sanitizers = {
singleScalarParameters: require('../sanitiser/_single_scalar_parameters'),
layers: require('../sanitiser/_targets')('layers', type_mapping.layer_with_aliases_to_type),
sources: require('../sanitiser/_targets')('sources', type_mapping.source_to_type),
layers: require('../sanitiser/_targets')('layers', type_mapping.layer_mapping),
sources: require('../sanitiser/_targets')('sources', type_mapping.source_mapping),
sources_and_layers: require('../sanitiser/_sources_and_layers'),
size: require('../sanitiser/_size'),
private: require('../sanitiser/_flag_bool')('private', false),
geo_reverse: require('../sanitiser/_geo_reverse'),

5
sanitiser/search.js

@ -5,8 +5,9 @@ var sanitizeAll = require('../sanitiser/sanitizeAll'),
singleScalarParameters: require('../sanitiser/_single_scalar_parameters'),
text: require('../sanitiser/_text'),
size: require('../sanitiser/_size'),
layers: require('../sanitiser/_targets')('layers', type_mapping.layer_with_aliases_to_type),
sources: require('../sanitiser/_targets')('sources', type_mapping.source_to_type),
layers: require('../sanitiser/_targets')('layers', type_mapping.layer_mapping),
sources: require('../sanitiser/_targets')('sources', type_mapping.source_mapping),
sources_and_layers: require('../sanitiser/_sources_and_layers'),
private: require('../sanitiser/_flag_bool')('private', false),
geo_search: require('../sanitiser/_geo_search'),
boundary_country: require('../sanitiser/_boundary_country'),

1
service/mget.js

@ -24,6 +24,7 @@ function service( backend, query, cb ){
// query new backend
backend().client.mget( cmd, function( err, data ){
console.log('service err: ' + err);
// handle backend errors
if( err ){ return cb( err ); }

11
test/unit/controller/place.js

@ -41,7 +41,7 @@ module.exports.tests.functional_success = function(test, common) {
test('functional success', function(t) {
var backend = mockBackend( 'client/mget/ok/1', function( cmd ){
t.deepEqual(cmd, { body: { docs: [ { _id: 123, _index: 'pelias', _type: 'a' } ] } }, 'correct backend command');
t.deepEqual(cmd, { body: { docs: [ { _id: 123, _index: 'pelias', _type: [ 'a' ] } ] } }, 'correct backend command');
});
var controller = setup( backend );
var res = {
@ -57,7 +57,7 @@ module.exports.tests.functional_success = function(test, common) {
t.deepEqual(json.features, expected, 'values correctly mapped');
}
};
var req = { clean: { ids: [ {'id' : 123, types: [ 'a' ] } ] }, errors: [], warnings: [] };
var req = { clean: { ids: [ {'id' : 123, layers: [ 'a' ] } ] }, errors: [], warnings: [] };
var next = function next() {
t.equal(req.errors.length, 0, 'next was called without error');
t.end();
@ -70,10 +70,13 @@ module.exports.tests.functional_success = function(test, common) {
module.exports.tests.functional_failure = function(test, common) {
test('functional failure', function(t) {
var backend = mockBackend( 'client/mget/fail/1', function( cmd ){
t.deepEqual(cmd, { body: { docs: [ { _id: 123, _index: 'pelias', _type: 'b' } ] } }, 'correct backend command');
var e = new Error('wtf is going on');
console.log(e.stack);
console.log('cmd: ' + JSON.stringify(cmd, null, 2));
t.deepEqual(cmd, { body: { docs: [ { _id: 123, _index: 'pelias', _type: ['b'] } ] } }, 'correct backend command');
});
var controller = setup( backend );
var req = { clean: { ids: [ {'id' : 123, types: [ 'b' ] } ] }, errors: [], warnings: [] };
var req = { clean: { ids: [ {'id' : 123, layers: [ 'b' ] } ] }, errors: [], warnings: [] };
var next = function( message ){
t.equal(req.errors[0],'a backend error occurred','error passed to errorHandler');
t.end();

2
test/unit/helper/text_parser.js

@ -1,7 +1,7 @@
var parser = require('../../../helper/text_parser');
var type_mapping = require('../../../helper/type_mapping');
var layers_map = type_mapping.layer_with_aliases_to_type;
var layers_map = type_mapping.layer_mapping;
module.exports.tests = {};

130
test/unit/helper/type_mapping.js

@ -4,132 +4,32 @@ var type_mapping = require('../../../helper/type_mapping');
module.exports.tests = {};
module.exports.tests.interfaces = function(test, common) {
test('types list', function(t) {
t.ok(check.array(type_mapping.types), 'is array');
t.ok(check.hasLength(type_mapping.types, 11), 'has correct number of elements');
test('basic layer mapping', function(t) {
t.deepEquals(type_mapping.layer_mapping.venue, ['venue']);
t.deepEquals(type_mapping.layer_mapping.address, ['address']);
t.end();
});
test('type to source mapping', function(t) {
t.ok(check.object(type_mapping.type_to_source), 'is object');
t.ok(check.hasLength(Object.keys(type_mapping.type_to_source), 11), 'has correct number of elements');
test('alias layer mapping', function(t) {
t.deepEquals(type_mapping.layer_mapping.coarse,
[ 'continent', 'macrocountry', 'country', 'dependency',
'region', 'locality', 'localadmin', 'county', 'macrohood',
'neighbourhood', 'microhood', 'disputed', 'admin0',
'admin1', 'admin2', 'neighborhood', 'locality', 'local_admin']);
t.end();
});
test('type to layer mapping', function(t) {
t.ok(check.object(type_mapping.type_to_layer), 'is object');
t.ok(check.hasLength(Object.keys(type_mapping.type_to_layer), 11), 'has correct number of elements');
test('basic source mapping', function(t) {
t.deepEquals(type_mapping.source_mapping.osm, ['openstreetmap']);
t.deepEquals(type_mapping.source_mapping.openaddresses, ['openaddresses']);
t.end();
});
test('source to type mapping', function(t) {
t.ok(check.object(type_mapping.source_to_type), 'is object');
t.ok(check.hasLength(Object.keys(type_mapping.source_to_type), 8), 'has correct number of elements');
test('alias source mapping', function(t) {
t.deepEquals(type_mapping.source_mapping.openstreetmap, ['openstreetmap']);
t.deepEquals(type_mapping.source_mapping.wof, ['whosonfirst']);
t.end();
});
test('layer to type mapping', function(t) {
t.ok(check.object(type_mapping.layer_to_type), 'is object');
t.equal(Object.keys(type_mapping.layer_to_type).length, 8, 'has correct number of elements');
t.end();
});
test('layer to type mapping (with aliases)', function(t) {
t.ok(check.object(type_mapping.layer_with_aliases_to_type), 'is object');
t.ok(check.hasLength(Object.keys(type_mapping.layer_with_aliases_to_type), 9), 'has correct number of elements');
t.end();
});
test('\'osm\' and \'openstreetmap\' sources should only successfully map to \'venue\' and \'address\' layers', function(t) {
t.deepEquals(type_mapping.source_and_layer_to_type('osm', 'venue'), ['osmnode', 'osmway']);
t.deepEquals(type_mapping.source_and_layer_to_type('osm', 'address'), ['osmaddress']);
t.deepEquals(type_mapping.source_and_layer_to_type('osm', 'country'), []);
t.deepEquals(type_mapping.source_and_layer_to_type('osm', 'region'), []);
t.deepEquals(type_mapping.source_and_layer_to_type('osm', 'county'), []);
t.deepEquals(type_mapping.source_and_layer_to_type('osm', 'locality'), []);
t.deepEquals(type_mapping.source_and_layer_to_type('osm', 'localadmin'), []);
t.deepEquals(type_mapping.source_and_layer_to_type('osm', 'neighbourhood'), []);
t.deepEquals(type_mapping.source_and_layer_to_type('osm', 'coarse'), []);
t.deepEquals(type_mapping.source_and_layer_to_type('openstreetmap', 'venue'), ['osmnode', 'osmway']);
t.deepEquals(type_mapping.source_and_layer_to_type('openstreetmap', 'address'), ['osmaddress']);
t.deepEquals(type_mapping.source_and_layer_to_type('openstreetmap', 'country'), []);
t.deepEquals(type_mapping.source_and_layer_to_type('openstreetmap', 'region'), []);
t.deepEquals(type_mapping.source_and_layer_to_type('openstreetmap', 'county'), []);
t.deepEquals(type_mapping.source_and_layer_to_type('openstreetmap', 'locality'), []);
t.deepEquals(type_mapping.source_and_layer_to_type('openstreetmap', 'localadmin'), []);
t.deepEquals(type_mapping.source_and_layer_to_type('openstreetmap', 'neighbourhood'), []);
t.deepEquals(type_mapping.source_and_layer_to_type('openstreetmap', 'coarse'), []);
t.end();
});
test('\'gn\' and \'geonames\' sources should only successfully map to \'venue\' layers', function(t) {
t.deepEquals(type_mapping.source_and_layer_to_type('gn', 'venue'), ['geoname']);
t.deepEquals(type_mapping.source_and_layer_to_type('gn', 'address'), []);
t.deepEquals(type_mapping.source_and_layer_to_type('gn', 'country'), []);
t.deepEquals(type_mapping.source_and_layer_to_type('gn', 'region'), []);
t.deepEquals(type_mapping.source_and_layer_to_type('gn', 'county'), []);
t.deepEquals(type_mapping.source_and_layer_to_type('gn', 'locality'), []);
t.deepEquals(type_mapping.source_and_layer_to_type('gn', 'localadmin'), []);
t.deepEquals(type_mapping.source_and_layer_to_type('gn', 'neighbourhood'), []);
t.deepEquals(type_mapping.source_and_layer_to_type('gn', 'coarse'), []);
t.deepEquals(type_mapping.source_and_layer_to_type('geonames', 'venue'), ['geoname']);
t.deepEquals(type_mapping.source_and_layer_to_type('geonames', 'address'), []);
t.deepEquals(type_mapping.source_and_layer_to_type('geonames', 'country'), []);
t.deepEquals(type_mapping.source_and_layer_to_type('geonames', 'region'), []);
t.deepEquals(type_mapping.source_and_layer_to_type('geonames', 'county'), []);
t.deepEquals(type_mapping.source_and_layer_to_type('geonames', 'locality'), []);
t.deepEquals(type_mapping.source_and_layer_to_type('geonames', 'localadmin'), []);
t.deepEquals(type_mapping.source_and_layer_to_type('geonames', 'neighbourhood'), []);
t.deepEquals(type_mapping.source_and_layer_to_type('geonames', 'coarse'), []);
t.end();
});
test('\'oa\' and \'openaddresses\' sources should only successfully map to \'address\' layer', function(t) {
t.deepEquals(type_mapping.source_and_layer_to_type('oa', 'venue'), []);
t.deepEquals(type_mapping.source_and_layer_to_type('oa', 'address'), ['openaddresses']);
t.deepEquals(type_mapping.source_and_layer_to_type('oa', 'country'), []);
t.deepEquals(type_mapping.source_and_layer_to_type('oa', 'region'), []);
t.deepEquals(type_mapping.source_and_layer_to_type('oa', 'county'), []);
t.deepEquals(type_mapping.source_and_layer_to_type('oa', 'locality'), []);
t.deepEquals(type_mapping.source_and_layer_to_type('oa', 'localadmin'), []);
t.deepEquals(type_mapping.source_and_layer_to_type('oa', 'neighbourhood'), []);
t.deepEquals(type_mapping.source_and_layer_to_type('oa', 'coarse'), []);
t.deepEquals(type_mapping.source_and_layer_to_type('openaddresses', 'venue'), []);
t.deepEquals(type_mapping.source_and_layer_to_type('openaddresses', 'address'), ['openaddresses']);
t.deepEquals(type_mapping.source_and_layer_to_type('openaddresses', 'country'), []);
t.deepEquals(type_mapping.source_and_layer_to_type('openaddresses', 'region'), []);
t.deepEquals(type_mapping.source_and_layer_to_type('openaddresses', 'county'), []);
t.deepEquals(type_mapping.source_and_layer_to_type('openaddresses', 'locality'), []);
t.deepEquals(type_mapping.source_and_layer_to_type('openaddresses', 'localadmin'), []);
t.deepEquals(type_mapping.source_and_layer_to_type('openaddresses', 'neighbourhood'), []);
t.deepEquals(type_mapping.source_and_layer_to_type('openaddresses', 'coarse'), []);
t.end();
});
test('\'qs\' and \'quattroshapes\' sources should only successfully map to \'address\' layer', function(t) {
t.deepEquals(type_mapping.source_and_layer_to_type('qs', 'venue'), []);
t.deepEquals(type_mapping.source_and_layer_to_type('qs', 'address'), []);
t.deepEquals(type_mapping.source_and_layer_to_type('qs', 'country'), ['admin0']);
t.deepEquals(type_mapping.source_and_layer_to_type('qs', 'region'), ['admin1']);
t.deepEquals(type_mapping.source_and_layer_to_type('qs', 'county'), ['admin2']);
t.deepEquals(type_mapping.source_and_layer_to_type('qs', 'locality'), ['locality']);
t.deepEquals(type_mapping.source_and_layer_to_type('qs', 'localadmin'), ['local_admin']);
t.deepEquals(type_mapping.source_and_layer_to_type('qs', 'neighbourhood'), ['neighborhood']);
t.deepEquals(type_mapping.source_and_layer_to_type('qs', 'coarse'),
['admin0','admin1','admin2','neighborhood','locality','local_admin']);
t.deepEquals(type_mapping.source_and_layer_to_type('quattroshapes', 'venue'), []);
t.deepEquals(type_mapping.source_and_layer_to_type('quattroshapes', 'address'), []);
t.deepEquals(type_mapping.source_and_layer_to_type('quattroshapes', 'country'), ['admin0']);
t.deepEquals(type_mapping.source_and_layer_to_type('quattroshapes', 'region'), ['admin1']);
t.deepEquals(type_mapping.source_and_layer_to_type('quattroshapes', 'county'), ['admin2']);
t.deepEquals(type_mapping.source_and_layer_to_type('quattroshapes', 'locality'), ['locality']);
t.deepEquals(type_mapping.source_and_layer_to_type('quattroshapes', 'localadmin'), ['local_admin']);
t.deepEquals(type_mapping.source_and_layer_to_type('quattroshapes', 'neighbourhood'), ['neighborhood']);
t.deepEquals(type_mapping.source_and_layer_to_type('quattroshapes', 'coarse'),
['admin0','admin1','admin2','neighborhood','locality','local_admin']);
t.end();
});
};
module.exports.all = function (tape, common) {

92
test/unit/helper/types.js

@ -1,92 +0,0 @@
var types = require('../../../helper/types');
module.exports.tests = {};
module.exports.tests.no_cleaned_types = function(test, common) {
test('no cleaned types', function(t) {
function testIt() {
types({});
}
t.throws(testIt, /clean_types should not be null or undefined/, 'no input should result in exception');
t.end();
});
};
module.exports.tests.address_parser = function(test, common) {
test('address parser specifies only admin layers', function(t) {
var cleaned_types = {
from_text_parser: ['admin0'] // simplified return value from address parser
};
var actual = types(cleaned_types);
var expected = ['admin0']; // simplified expected value for all admin layers
t.deepEqual(actual, expected, 'only layers specified by address parser returned');
t.end();
});
};
module.exports.tests.layers_parameter = function(test, common) {
test('layers parameter specifies only some layers', function(t) {
var cleaned_types = {
from_layers: ['geoname']
};
var actual = types(cleaned_types);
var expected = ['geoname'];
t.deepEqual(actual, expected, 'only types specified by layers parameter returned');
t.end();
});
};
module.exports.tests.layers_parameter_and_address_parser = function(test, common) {
test('layers parameter and address parser present', function(t) {
var cleaned_types = {
from_layers: ['geoname'],
from_text_parser: ['admin0'] // simplified return value from address parse
};
var actual = types(cleaned_types);
var expected = ['geoname'];
t.deepEqual(actual, expected, 'layers parameter overrides address parser completely');
t.end();
});
};
module.exports.tests.source_parameter = function(test, common) {
test('source parameter specified', function(t) {
var cleaned_types = {
from_sources: ['openaddresses']
};
var actual = types(cleaned_types);
var expected = ['openaddresses'];
t.deepEqual(actual, expected, 'type parameter set to types specified by source');
t.end();
});
};
module.exports.tests.source_and_layers_parameters = function(test, common) {
test('source and layers parameter both specified', function(t) {
var cleaned_types = {
from_sources: ['openaddresses'],
from_layers: ['osmaddress', 'openaddresses']
};
var actual = types(cleaned_types);
var expected = ['openaddresses'];
t.deepEqual(actual, expected, 'type set to intersection of source and layer types');
t.end();
});
};
module.exports.all = function (tape, common) {
function test(name, testFunction) {
return tape('types: ' + name, testFunction);
}
for( var testCase in module.exports.tests ){
module.exports.tests[testCase](test, common);
}
};

1
test/unit/run.js

@ -11,7 +11,6 @@ var tests = [
require('./helper/labelSchema'),
require('./helper/text_parser'),
require('./helper/type_mapping'),
require('./helper/types'),
require('./helper/sizeCalculator'),
require('./middleware/confidenceScore'),
require('./middleware/confidenceScoreReverse'),

47
test/unit/sanitiser/_ids.js

@ -94,60 +94,79 @@ module.exports.tests.valid_ids = function(test, common) {
test('ids: valid input (openaddresses)', function(t) {
var raw = { ids: 'openaddresses:address:20' };
var clean = {};
var expected_ids = [{
id: '20',
types: [ 'openaddresses' ]
}];
var messages = sanitize( raw, clean );
var expected_ids = [{
source: 'openaddresses',
layer: 'address',
id: '20',
}];
t.deepEqual( messages.errors, [], ' no errors');
t.deepEqual( clean.ids, expected_ids, 'single type value returned');
t.end();
});
test('ids: valid input (osm)', function(t) {
var raw = { ids: 'osm:venue:500' };
var raw = { ids: 'openstreetmap:venue:node:500' };
var clean = {};
var expected_ids = [{
id: '500',
types: [ 'osmnode', 'osmway' ]
source: 'openstreetmap',
layer: 'venue',
id: 'node:500',
}];
var messages = sanitize( raw, clean );
t.deepEqual( messages.errors, [], ' no errors');
t.deepEqual( clean.ids, expected_ids, 'osm could be two types, but that\'s ok');
t.deepEqual( clean.ids, expected_ids, 'osm has node: or way: in id field');
t.end();
});
};
module.exports.tests.multiple_ids = function(test, common) {
test('multiple ids', function(t) {
var raw = { ids: 'geonames:venue:1,osm:venue:2' };
var raw = { ids: 'geonames:venue:1,openstreetmap:address:way:2' };
var clean = {};
var expected_clean = { ids: [ { id: '1', types: [ 'geoname' ] }, { id: '2', types: [ 'osmnode', 'osmway' ] } ] };
var messages = sanitize( raw, clean);
var expected_ids = [ {
source: 'geonames',
layer: 'venue',
id: '1'
}, {
source: 'openstreetmap',
layer: 'address',
id: 'way:2'
} ];
t.deepEqual( messages.errors, [], 'no errors' );
t.deepEqual( messages.warnings, [], 'no warnings' );
t.deepEqual(clean, expected_clean, 'clean set correctly');
t.deepEqual(clean.ids, expected_ids, 'clean set correctly');
t.end();
});
};
module.exports.tests.de_dupe = function(test, common) {
test('duplicate ids', function(t) {
var expected_clean = { ids: [ { id: '1', types: [ 'geoname' ] }, { id: '2', types: [ 'osmnode', 'osmway' ] } ] };
var raw = { ids: 'geonames:venue:1,osm:venue:2,geonames:venue:1' };
var raw = { ids: 'geonames:venue:1,openstreetmap:venue:node:2,geonames:venue:1' };
var clean = {};
var messages = sanitize( raw, clean );
var expected_ids = [ {
source: 'geonames',
layer: 'venue',
id: '1'
}, {
source: 'openstreetmap',
layer: 'venue',
id: 'node:2'
} ];
t.deepEqual( messages.errors, [], 'no errors' );
t.deepEqual( messages.warnings, [], 'no warnings' );
t.deepEqual(clean, expected_clean, 'clean set correctly');
t.deepEqual(clean.ids, expected_ids, 'clean set correctly');
t.end();
});
};

54
test/unit/sanitiser/_layers.js

@ -1,6 +1,6 @@
var type_mapping = require('../../../helper/type_mapping');
var sanitize = require('../../../sanitiser/_targets')('layers', type_mapping.layer_with_aliases_to_type);
var sanitize = require('../../../sanitiser/_targets')('layers', type_mapping.layer_mapping);
module.exports.tests = {};
@ -25,95 +25,101 @@ module.exports.tests.sanitize_layers = function(test, common) {
});
test('venue (alias) layer', function(t) {
var venue_layers = ['geoname','osmnode','osmway'];
var raw = { layers: 'venue' };
var clean = {};
sanitize(raw, clean);
t.deepEqual(clean.types.from_layers, venue_layers, 'venue layers set');
var venue_layers = ['venue'];
t.deepEqual(clean.layers, venue_layers, 'venue layers set');
t.end();
});
test('coarse (alias) layer', function(t) {
var admin_layers = ['admin0','admin1','admin2','neighborhood','locality','local_admin'];
var raw = { layers: 'coarse' };
var clean = {};
sanitize(raw, clean);
t.deepEqual(clean.types.from_layers, admin_layers, 'coarse layers set');
var admin_layers = [ 'continent', 'macrocountry', 'country', 'dependency',
'region', 'locality', 'localadmin', 'county', 'macrohood', 'neighbourhood',
'microhood', 'disputed', 'admin0', 'admin1', 'admin2', 'neighborhood', 'local_admin' ];
t.deepEqual(clean.layers, admin_layers, 'coarse layers set');
t.end();
});
test('address (alias) layer', function(t) {
var address_layers = ['osmaddress','openaddresses'];
test('address layer', function(t) {
var raw = { layers: 'address' };
var clean = {};
sanitize(raw, clean);
t.deepEqual(clean.types.from_layers, address_layers, 'address layers set');
t.deepEqual(clean.layers, ['address'], 'address layer set');
t.end();
});
test('venue alias layer plus regular layers', function(t) {
var venue_layers = ['geoname','osmnode','osmway'];
var reg_layers = ['admin0', 'admin1'];
var raw = { layers: 'venue,country,region' };
var clean = {};
sanitize(raw, clean);
t.deepEqual(clean.types.from_layers, venue_layers.concat(reg_layers), 'venue + regular layers');
var expected_layers = ['venue', 'country', 'admin0', 'region', 'admin1'];
t.deepEqual(clean.layers, expected_layers, 'venue + regular layers');
t.end();
});
test('coarse alias layer plus regular layers', function(t) {
var admin_layers = ['admin0','admin1','admin2','neighborhood','locality','local_admin'];
var reg_layers = ['geoname', 'osmnode', 'osmway'];
var raw = { layers: 'coarse,venue,country' };
var raw = { layers: 'coarse,country' };
var clean = {};
sanitize(raw, clean);
t.deepEqual(clean.types.from_layers, admin_layers.concat(reg_layers), 'coarse + regular layers set');
var expected_layers = [ 'continent', 'macrocountry', 'country', 'dependency',
'region', 'locality', 'localadmin', 'county', 'macrohood', 'neighbourhood',
'microhood', 'disputed', 'admin0', 'admin1', 'admin2', 'neighborhood', 'local_admin' ];
t.deepEqual(clean.layers, expected_layers, 'coarse + regular layers set');
t.end();
});
test('address alias layer plus regular layers', function(t) {
var address_layers = ['osmaddress','openaddresses'];
var reg_layers = ['admin0', 'locality'];
var raw = { layers: 'address,country,locality' };
var clean = {};
sanitize(raw, clean);
t.deepEqual(clean.types.from_layers, address_layers.concat(reg_layers), 'address + regular layers set');
var expected_layers = ['address', 'country', 'admin0', 'locality' ];
t.deepEqual(clean.layers, expected_layers, 'address + regular layers set');
t.end();
});
test('alias layer plus regular layers (no duplicates)', function(t) {
var venue_layers = ['geoname','osmnode','osmway','admin0'];
var raw = { layers: 'venue,country' };
var clean = {};
sanitize(raw, clean);
t.deepEqual(clean.types.from_layers, venue_layers, 'venue layers found (no duplicates)');
var expected_layers = ['venue', 'country', 'admin0'];
t.deepEqual(clean.layers, expected_layers, 'venue layers found (no duplicates)');
t.end();
});
test('multiple alias layers (no duplicates)', function(t) {
var alias_layers = ['geoname','osmnode','osmway','admin0','admin1','admin2','neighborhood','locality','local_admin'];
var raw = { layers: 'venue,coarse' };
var clean = {};
sanitize(raw, clean);
t.deepEqual(clean.types.from_layers, alias_layers, 'all layers found (no duplicates)');
var coarse_layers = [ 'continent', 'macrocountry',
'country', 'dependency', 'region', 'locality', 'localadmin',
'county', 'macrohood', 'neighbourhood', 'microhood',
'disputed', 'admin0', 'admin1', 'admin2', 'neighborhood', 'local_admin'];
var venue_layers = [ 'venue' ];
var expected_layers = venue_layers.concat(coarse_layers);
t.deepEqual(clean.layers, expected_layers, 'all layers found (no duplicates)');
t.end();
});
};

31
test/unit/sanitiser/_sources.js

@ -1,5 +1,5 @@
var type_mapping = require('../../../helper/type_mapping');
var sanitize = require( '../../../sanitiser/_targets' )('sources', type_mapping.source_to_type);
var sanitize = require( '../../../sanitiser/_targets' )('sources', type_mapping.source_mapping);
var success_messages = { error: false };
@ -14,7 +14,7 @@ module.exports.tests.no_sources = function(test, common) {
var messages = sanitize(req.query, req.clean);
t.deepEqual(req.clean.types, {}, 'clean.types should be empty object');
t.equal(req.clean.sources, undefined, 'no sources should be defined');
t.deepEqual(messages.errors, [], 'no error returned');
t.deepEqual(messages.warnings, [], 'no warnings returned');
t.end();
@ -29,11 +29,11 @@ module.exports.tests.no_sources = function(test, common) {
};
var expected_error = 'sources parameter cannot be an empty string. ' +
'Valid options: gn,geonames,oa,openaddresses,qs,quattroshapes,osm,openstreetmap';
'Valid options: osm,oa,gn,qs,wof,openstreetmap,openaddresses,geonames,quattroshapes,whosonfirst';
var messages = sanitize(req.query, req.clean);
t.deepEqual(req.clean.types, {}, 'clean.types should be empty object');
t.equal(req.clean.sources, undefined, 'no sources should be defined');
t.deepEqual(messages.errors.length, 1, 'error returned');
t.deepEqual(messages.errors[0], expected_error, 'error returned');
t.deepEqual(messages.warnings, [], 'no warnings returned');
@ -52,27 +52,24 @@ module.exports.tests.valid_sources = function(test, common) {
var messages = sanitize(req.query, req.clean);
t.deepEqual(req.clean.types, { from_sources: ['geoname'] }, 'clean.types should contain from_source entry with geonames');
t.deepEqual(req.clean.sources, ['geonames'], 'sources should contain geonames');
t.deepEqual(messages.errors, [], 'no error returned');
t.deepEqual(messages.warnings, [], 'no warnings returned');
t.end();
});
test('openstreetmap source', function(t) {
test('openstreetmap (abbreviated) source', function(t) {
var req = {
query: {
sources: 'openstreetmap'
sources: 'osm'
},
clean: { }
};
var expected_types = {
from_sources: ['osmaddress', 'osmnode', 'osmway']
};
var messages = sanitize(req.query, req.clean);
t.deepEqual(req.clean.types, expected_types, 'clean.types should contain from_source entry with multiple entries for openstreetmap');
t.deepEqual(req.clean.sources, ['openstreetmap'], 'abbreviation is expanded to full version');
t.deepEqual(messages.errors, [], 'no error returned');
t.deepEqual(messages.warnings, [], 'no warnings returned');
t.end();
@ -85,14 +82,11 @@ module.exports.tests.valid_sources = function(test, common) {
},
clean: { }
};
var expected_types = {
from_sources: ['osmaddress', 'osmnode', 'osmway', 'openaddresses']
};
var messages = sanitize(req.query, req.clean);
t.deepEqual(req.clean.types, expected_types,
'clean.types should contain from_source entry with multiple entries for openstreetmap and openadresses');
t.deepEqual(req.clean.sources, ['openstreetmap', 'openaddresses'],
'clean.sources should contain openstreetmap and openadresses');
t.deepEqual(messages.errors, [], 'no error returned');
t.deepEqual(messages.warnings, [], 'no warnings returned');
t.end();
@ -109,7 +103,8 @@ module.exports.tests.invalid_sources = function(test, common) {
};
var expected_messages = {
errors: [
'\'notasource\' is an invalid sources parameter. Valid options: gn,geonames,oa,openaddresses,qs,quattroshapes,osm,openstreetmap'
'\'notasource\' is an invalid sources parameter. ' +
'Valid options: osm,oa,gn,qs,wof,openstreetmap,openaddresses,geonames,quattroshapes,whosonfirst'
],
warnings: []
};
@ -117,7 +112,7 @@ module.exports.tests.invalid_sources = function(test, common) {
var messages = sanitize(req.query, req.clean);
t.deepEqual(messages, expected_messages, 'error with message returned');
t.deepEqual(req.clean.types, { }, 'clean.types should remain empty');
t.equal(req.clean.sources, undefined, 'clean.sources should remain empty');
t.end();
});
};

1
test/unit/sanitiser/_text.js

@ -15,7 +15,6 @@ module.exports.tests.text_parser = function(test, common) {
t.deepEquals(messages.errors, [], 'no errors');
t.deepEquals(messages.warnings, [], 'no warnings');
t.equal(clean.types.from_text_parser, type_mapping.layer_with_aliases_to_type.coarse, 'coarse layers preferred');
t.end();
});

2
test/unit/sanitiser/place.js

@ -1,7 +1,7 @@
var place = require('../../../sanitiser/place'),
sanitize = place.sanitize,
middleware = place.middleware,
defaultClean = { ids: [ { id: '123', types: [ 'geoname' ] } ], private: false };
defaultClean = { ids: [ { source: 'geonames', layer: 'venue', id: '123' } ], private: false };
// these are the default values you would expect when no input params are specified.
module.exports.tests = {};

9
test/unit/sanitiser/reverse.js

@ -11,15 +11,13 @@ var reverse = require('../../../sanitiser/reverse'),
'boundary.circle.lat': 0,
'boundary.circle.lon': 0,
'boundary.circle.radius': parseFloat(defaults['boundary:circle:radius']),
types: {
},
size: 10,
private: false
};
// these are the default values you would expect when no input params are specified.
// @todo: why is this different from $defaultClean?
var emptyClean = { private: false, size: 10, types: {} };
var emptyClean = { private: false, size: 10 };
module.exports.tests = {};
@ -38,7 +36,8 @@ module.exports.tests.interface = function(test, common) {
module.exports.tests.sanitisers = function(test, common) {
test('check sanitiser list', function (t) {
var expected = ['singleScalarParameters', 'layers', 'sources', 'size', 'private', 'geo_reverse', 'boundary_country'];
var expected = ['singleScalarParameters', 'layers', 'sources',
'sources_and_layers', 'size', 'private', 'geo_reverse', 'boundary_country'];
t.deepEqual(Object.keys(reverse.sanitiser_list), expected);
t.end();
});
@ -104,7 +103,7 @@ module.exports.tests.sanitize_lon = function(test, common) {
var req = { query: { 'point.lat': 0, 'point.lon': lon } };
// @todo: why is lat set?
var expected = { 'point.lat': 0, private: false, size: 10, types: {} };
var expected = { 'point.lat': 0, private: false, size: 10 };
sanitize(req, function(){
t.equal(req.errors[0], 'missing param \'point.lon\'', 'longitude is a required field');
t.deepEqual(req.clean, expected, 'clean only has default values set');

5
test/unit/sanitiser/search.js

@ -5,7 +5,7 @@ var extend = require('extend'),
middleware = search.middleware,
defaultError = 'invalid param \'text\': text length, must be >0';
// these are the default values you would expect when no input params are specified.
var emptyClean = { private: false, size: 10, types: {} };
var emptyClean = { private: false, size: 10 };
module.exports.tests = {};
@ -24,7 +24,8 @@ module.exports.tests.interface = function(test, common) {
module.exports.tests.sanitisers = function(test, common) {
test('check sanitiser list', function (t) {
var expected = ['singleScalarParameters', 'text', 'size', 'layers', 'sources', 'private', 'geo_search', 'boundary_country' ];
var expected = ['singleScalarParameters', 'text', 'size', 'layers', 'sources',
'sources_and_layers', 'private', 'geo_search', 'boundary_country' ];
t.deepEqual(Object.keys(search.sanitiser_list), expected);
t.end();
});

Loading…
Cancel
Save