Browse Source

Merge pull request #1143 from pelias/master

Merge master into staging
pull/1167/head
Julian Simioni 7 years ago committed by GitHub
parent
commit
6310ee1ff0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      README.md
  2. 146
      helper/TypeMapping.js
  3. 90
      helper/type_mapping.js
  4. 4
      middleware/geocodeJSON.js
  5. 3
      middleware/interpolate.js
  6. 24
      package.json
  7. 208
      test/unit/helper/TypeMapping.js
  8. 91
      test/unit/helper/type_mapping.js
  9. 75
      test/unit/middleware/interpolate.js
  10. 1
      test/unit/run.js

1
README.md

@ -45,6 +45,7 @@ The API recognizes the following properties under the top-level `api` key in you
|`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), [libpostal](https://github.com/whosonfirst/go-whosonfirst-libpostal), [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.| |`services`|*no*||service definitions for [point-in-polygon](https://github.com/pelias/pip-service), [libpostal](https://github.com/whosonfirst/go-whosonfirst-libpostal), [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
|`targets.layers_by_source` <br> `targets.source_aliases` <br> `targets.layer_aliases`|no | |custom values for which `sources` and `layers` the API accepts ([more info](https://github.com/pelias/api/pull/1131)).
A good starting configuration file includes this section (fill in the service and Elasticsearch hosts as needed): A good starting configuration file includes this section (fill in the service and Elasticsearch hosts as needed):

146
helper/TypeMapping.js

@ -0,0 +1,146 @@
const _ = require('lodash');
const elasticsearch = require('elasticsearch');
var TypeMapping = function(){
// A list of all sources
this.sources = [];
// A list of alternate names for sources, mostly used to save typing
this.source_aliases = {};
// A list of all layers
this.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.
*/
this.layers_by_source = {};
/*
* 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
*/
this.layer_aliases = {};
/*
* 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.
*/
this.source_mapping = {};
/*
* An object that has a key for each possible layer or alias,
* and returns either that layer, or gall the layers in the alias
*/
this.layer_mapping = {};
};
TypeMapping.addStandardTargetsToAliases = function(standard, aliases) {
var combined = _.extend({}, aliases);
standard.forEach(function(target) {
if (combined[target] === undefined) {
combined[target] = [target];
}
});
return combined;
};
// source alias setter
TypeMapping.prototype.setSourceAliases = function( aliases ){
this.source_aliases = aliases;
};
// layers-by-source alias setter
TypeMapping.prototype.setLayersBySource = function( lbs ){
this.layers_by_source = lbs;
};
// layer alias setter
TypeMapping.prototype.setLayerAliases = function( aliases ){
this.layer_aliases = aliases;
};
// generate mappings after setters have been run
TypeMapping.prototype.generateMappings = function(){
this.sources = Object.keys( this.layers_by_source );
this.source_mapping = TypeMapping.addStandardTargetsToAliases(this.sources, this.source_aliases);
this.layers = _.uniq(Object.keys(this.layers_by_source).reduce(function(acc, key) {
return acc.concat(this.layers_by_source[key]);
}.bind(this), []));
this.layer_mapping = TypeMapping.addStandardTargetsToAliases(this.layers, this.layer_aliases);
};
// load values from targets block
TypeMapping.prototype.loadTargets = function( targetsBlock ){
if( !_.isObject(targetsBlock) ){ return; }
// set values from targets block
this.setSourceAliases( targetsBlock.source_aliases || {} );
this.setLayersBySource( targetsBlock.layers_by_source || {} );
this.setLayerAliases( targetsBlock.layer_aliases || {} );
// generate the mappings
this.generateMappings();
};
// load values from either pelias config file or from elasticsearch
TypeMapping.prototype.load = function( done ){
// load pelias config
const peliasConfigTargets = _.get(
require('pelias-config').generate(require('../schema')),
'api.targets', {}
);
// load targets from config file
this.loadTargets( peliasConfigTargets );
// do not load values from elasticsearch
if( true !== peliasConfigTargets.auto_discover ){
if( 'function' === typeof done ){ done(); }
return;
}
if( 'function' === typeof done ){ done(); }
return;
// load values from elasticsearch
// create connection to elasticsearch
// const esclient = elasticsearch.Client(peliasConfig.esclient);
// const query = {
// requestCache: true,
// preference: '_replica_first',
// timeout: '10s',
// body: {
// aggs: {
// sources: {
// terms: {
// field: 'source',
// size: 100
// }
// },
// layers: {
// terms: {
// field: 'layer',
// size: 100
// }
// }
// },
// size: 0
// }
// };
// esclient.search( query, ( err, res ) => {
// console.error( err, res );
// });
};
module.exports = TypeMapping;

90
helper/type_mapping.js

@ -1,86 +1,8 @@
const _ = require('lodash'); const TypeMapping = require('./TypeMapping');
function addStandardTargetsToAliases(standard, aliases) { // instantiate a new type mapping
var combined = _.extend({}, aliases); var tm = new TypeMapping();
standard.forEach(function(target) { tm.load();
if (combined[target] === undefined) {
combined[target] = [target];
}
});
return combined; // export singleton
} module.exports = tm;
/*
* Sources
*/
// a list of all sources
var SOURCES = ['openstreetmap', 'openaddresses', 'geonames', 'whosonfirst'];
/*
* A list of alternate names for sources, mostly used to save typing
*/
var SOURCE_ALIASES = {
'osm': ['openstreetmap'],
'oa': ['openaddresses'],
'gn': ['geonames'],
'wof': ['whosonfirst']
};
/*
* 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 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', 'street' ],
openaddresses: [ 'address' ],
geonames: [ 'country','macroregion', 'region', 'county','localadmin',
'locality','borough', 'neighbourhood', 'venue' ],
whosonfirst: [ 'continent', 'empire', 'country', 'dependency', 'macroregion', 'region',
'locality', 'localadmin', 'macrocounty', 'county', 'macrohood', 'borough',
'neighbourhood', 'microhood', 'disputed', 'venue', 'postalcode',
'continent', 'ocean', 'marinearea']
};
/*
* 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': [ 'continent', 'empire', 'country', 'dependency', 'macroregion',
'region', 'locality', 'localadmin', 'macrocounty', 'county', 'macrohood',
'borough', 'neighbourhood', 'microhood', 'disputed', 'postalcode',
'continent', 'ocean', 'marinearea']
};
// 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]);
}, []));
/*
* 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 LAYER_MAPPING = addStandardTargetsToAliases(LAYERS, LAYER_ALIASES);
module.exports = {
sources: SOURCES,
layers: LAYERS,
source_mapping: SOURCE_MAPPING,
layer_mapping: LAYER_MAPPING,
layers_by_source: LAYERS_BY_SOURCE
};

4
middleware/geocodeJSON.js

@ -47,10 +47,10 @@ function convertToGeocodeJSON(req, res, next, opts) {
// OPTIONAL. Default: null. The attribution of the data. In case of multiple sources, // OPTIONAL. Default: null. The attribution of the data. In case of multiple sources,
// and then multiple attributions, can be an object with one key by source. // and then multiple attributions, can be an object with one key by source.
// Can be a URI on the server, which outlines attribution details. // Can be a URI on the server, which outlines attribution details.
res.body.geocoding.attribution = url.format({ res.body.geocoding.attribution = opts.config.attributionURL || url.format({
protocol: req.protocol, protocol: req.protocol,
host: req.get('host'), host: req.get('host'),
pathname: opts.basePath + 'attribution' pathname: 'attribution'
}); });
// OPTIONAL. Default: null. The query that has been issued to trigger the // OPTIONAL. Default: null. The query that has been issued to trigger the

3
middleware/interpolate.js

@ -2,6 +2,7 @@ const async = require('async');
const logger = require( 'pelias-logger' ).get( 'api' ); const logger = require( 'pelias-logger' ).get( 'api' );
const source_mapping = require('../helper/type_mapping').source_mapping; const source_mapping = require('../helper/type_mapping').source_mapping;
const _ = require('lodash'); const _ = require('lodash');
const stable = require('stable');
/** /**
example response from interpolation web service: example response from interpolation web service:
@ -116,7 +117,7 @@ function setup(service, should_execute) {
// sort the results to ensure that addresses show up higher than street centroids // sort the results to ensure that addresses show up higher than street centroids
if (_.has(res, 'data')) { if (_.has(res, 'data')) {
res.data.sort((a, b) => { res.data = stable(res.data, (a, b) => {
if (a.layer === 'address' && b.layer !== 'address') { return -1; } if (a.layer === 'address' && b.layer !== 'address') { return -1; }
if (a.layer !== 'address' && b.layer === 'address') { return 1; } if (a.layer !== 'address' && b.layer === 'address') { return 1; }
return 0; return 0;

24
package.json

@ -36,32 +36,33 @@
"node": ">=6.0.0" "node": ">=6.0.0"
}, },
"dependencies": { "dependencies": {
"@mapbox/geojson-extent": "^0.3.1",
"addressit": "1.5.0", "addressit": "1.5.0",
"async": "^2.0.0", "async": "^2.0.0",
"check-types": "^7.0.0", "check-types": "^7.0.0",
"elasticsearch": "^14.2.1", "elasticsearch": "^15.0.0",
"elasticsearch-exceptions": "0.0.4", "elasticsearch-exceptions": "0.0.4",
"express": "^4.8.8", "express": "^4.8.8",
"geojson": "^0.5.0", "geojson": "^0.5.0",
"@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": "^12.0.0", "joi": "^13.1.3",
"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.14.0", "pelias-config": "3.0.2",
"pelias-labels": "1.8.0", "pelias-labels": "1.8.0",
"pelias-logger": "0.3.1", "pelias-logger": "0.4.2",
"pelias-microservice-wrapper": "1.3.0", "pelias-microservice-wrapper": "1.4.0",
"pelias-model": "5.3.2", "pelias-model": "5.5.2",
"pelias-query": "9.1.1", "pelias-query": "9.1.1",
"pelias-sorting": "1.1.0", "pelias-sorting": "1.2.0",
"predicates": "^2.0.0", "predicates": "^2.0.0",
"retry": "^0.10.1", "retry": "^0.12.0",
"stable": "^0.1.8",
"stats-lite": "^2.0.4", "stats-lite": "^2.0.4",
"through2": "^2.0.3" "through2": "^2.0.3"
}, },
@ -71,7 +72,7 @@
"istanbul": "^0.4.2", "istanbul": "^0.4.2",
"jshint": "^2.5.6", "jshint": "^2.5.6",
"nsp": "^3.0.0", "nsp": "^3.0.0",
"pelias-mock-logger": "1.2.0", "pelias-mock-logger": "1.3.0",
"precommit-hook": "^3.0.0", "precommit-hook": "^3.0.0",
"proxyquire": "^2.0.0", "proxyquire": "^2.0.0",
"semantic-release": "^15.1.4", "semantic-release": "^15.1.4",
@ -88,6 +89,7 @@
"test" "test"
], ],
"release": { "release": {
"branch": "production" "branch": "production",
"success": []
} }
} }

208
test/unit/helper/TypeMapping.js

@ -0,0 +1,208 @@
const _ = require('lodash');
const TypeMapping = require('../../../helper/TypeMapping');
module.exports.tests = {};
module.exports.tests.interface = function(test) {
test('valid interface', function(t) {
t.equal(typeof TypeMapping, 'function', 'TypeMapping is a function');
t.equal(typeof TypeMapping.addStandardTargetsToAliases, 'function', 'addStandardTargetsToAliases() is a function');
t.equal(typeof TypeMapping.prototype.setSourceAliases, 'function', 'setSourceAliases() is a function');
t.equal(typeof TypeMapping.prototype.setLayersBySource, 'function', 'setLayersBySource() is a function');
t.equal(typeof TypeMapping.prototype.setLayerAliases, 'function', 'setLayerAliases() is a function');
t.equal(typeof TypeMapping.prototype.generateMappings, 'function', 'generateMappings() is a function');
t.equal(typeof TypeMapping.prototype.loadTargets, 'function', 'loadTargets() is a function');
t.equal(typeof TypeMapping.prototype.load, 'function', 'load() is a function');
t.end();
});
};
module.exports.tests.constructor = function(test) {
test('constructor', function(t) {
var doc = new TypeMapping();
// initial values
t.deepEqual(doc.sources, [], 'initial value');
t.deepEqual(doc.source_aliases, {}, 'initial value');
t.deepEqual(doc.layers, [], 'initial value');
t.deepEqual(doc.layers_by_source, {}, 'initial value');
t.deepEqual(doc.layer_aliases, {}, 'initial value');
t.deepEqual(doc.source_mapping, {}, 'initial value');
t.deepEqual(doc.layer_mapping, {}, 'initial value');
t.end();
});
};
module.exports.tests.addStandardTargetsToAliases = function(test) {
test('static method addStandardTargetsToAliases', function(t) {
var aliases = { test: ['test2'] };
t.deepEqual(
TypeMapping.addStandardTargetsToAliases([], aliases),
{ test: ['test2'] }
);
t.deepEqual(aliases, aliases, 'aliases object not mutated');
t.deepEqual(
TypeMapping.addStandardTargetsToAliases(['test'], aliases),
{ test: ['test2'] },
'not modified'
);
t.deepEqual(aliases, aliases, 'aliases object not mutated');
t.deepEqual(
TypeMapping.addStandardTargetsToAliases(['baz'], aliases),
{ test: ['test2'], baz: ['baz'] }
);
t.deepEqual(aliases, aliases, 'aliases object not mutated');
t.deepEqual(
TypeMapping.addStandardTargetsToAliases(['baz','boo'], aliases),
{ test: ['test2'], baz: ['baz'], boo: ['boo'] }
);
t.deepEqual(aliases, aliases, 'aliases object not mutated');
t.end();
});
};
module.exports.tests.setSourceAliases = function(test) {
test('setter setSourceAliases', function(t) {
var tm = new TypeMapping();
t.deepEqual(tm.source_aliases, {});
tm.setSourceAliases({ foo: ['foo', 'bar'] });
t.deepEqual(tm.source_aliases, { foo: ['foo', 'bar'] });
t.end();
});
};
module.exports.tests.setLayersBySource = function(test) {
test('setter setLayersBySource', function(t) {
var tm = new TypeMapping();
t.deepEqual(tm.layers_by_source, {});
tm.setLayersBySource({ foo: ['foo', 'bar'] });
t.deepEqual(tm.layers_by_source, { foo: ['foo', 'bar'] });
t.end();
});
};
module.exports.tests.setLayerAliases = function(test) {
test('setter setLayerAliases', function(t) {
var tm = new TypeMapping();
t.deepEqual(tm.layer_aliases, {});
tm.setLayerAliases({ foo: ['foo', 'bar'] });
t.deepEqual(tm.layer_aliases, { foo: ['foo', 'bar'] });
t.end();
});
};
module.exports.tests.generateMappings = function(test) {
test('generateMappings - no-op', function(t) {
var tm = new TypeMapping();
t.deepEqual(tm.sources, []);
t.deepEqual(tm.source_mapping, {});
t.deepEqual(tm.layers, []);
t.deepEqual(tm.layer_mapping, {});
tm.generateMappings();
t.deepEqual(tm.sources, []);
t.deepEqual(tm.source_mapping, {});
t.deepEqual(tm.layers, []);
t.deepEqual(tm.layer_mapping, {});
t.end();
});
test('generateMappings - sources', function(t) {
var tm = new TypeMapping();
tm.layers_by_source = { foo: ['foo'], faz: ['faz'] };
tm.generateMappings();
t.deepEqual(tm.sources, ['foo', 'faz']);
t.end();
});
test('generateMappings - source_mapping', function(t) {
var tm = new TypeMapping();
tm.layers_by_source = { foo: ['foo'], faz: ['faz'] };
tm.source_aliases = { foo: ['foo','f'], bar: ['bar', 'b'], baz: ['baz'] };
tm.generateMappings();
t.deepEqual(tm.source_mapping, { foo: ['foo', 'f'], bar: ['bar', 'b'], baz: ['baz'], faz: ['faz'] });
t.end();
});
test('generateMappings - layers', function(t) {
var tm = new TypeMapping();
tm.layers_by_source = { foo: ['foo'], faz: ['faz'] };
tm.generateMappings();
t.deepEqual(tm.layers, ['foo','faz']);
t.end();
});
test('generateMappings - layer_mapping', function(t) {
var tm = new TypeMapping();
tm.layers_by_source = { foo: ['foo'], faz: ['faz'] };
tm.layer_aliases = { foo: ['foo','f'], bar: ['bar', 'b'], baz: ['baz'] };
tm.generateMappings();
t.deepEqual(tm.layer_mapping, { foo: ['foo', 'f'], bar: ['bar', 'b'], baz: ['baz'], faz: ['faz'] });
t.end();
});
};
module.exports.tests.loadTargets = function(test) {
test('loadTargets - undefined', function(t) {
var tm = new TypeMapping();
tm.loadTargets();
t.deepEqual(tm.sources, []);
t.deepEqual(tm.source_mapping, {});
t.deepEqual(tm.layers, []);
t.deepEqual(tm.layer_mapping, {});
t.end();
});
test('loadTargets', function(t) {
var tm = new TypeMapping();
tm.loadTargets({
source_aliases: { source1: ['s1', 's2'], source2: ['s3', 's4'] },
layers_by_source: { source1: ['layer1', 'layer3'], source2: ['layer2'] },
layer_aliases: { layer1: ['l1', 'l2'], layer2: ['l3', 'l4'] },
});
t.deepEqual(tm.sources, [ 'source1', 'source2' ]);
t.deepEqual(tm.source_mapping, { source1: [ 's1', 's2' ], source2: [ 's3', 's4' ] });
t.deepEqual(tm.layers, [ 'layer1', 'layer3', 'layer2' ]);
t.deepEqual(tm.layer_mapping, { layer1: [ 'l1', 'l2' ], layer2: [ 'l3', 'l4' ], layer3: [ 'layer3' ] });
t.end();
});
};
module.exports.tests.load = function(test) {
test('load from pelias config', function(t) {
var tm = new TypeMapping();
tm.load(() => {
// load pelias config
const expected = _.get(
require('pelias-config').generate(require('../../../schema')),
'api.targets', {}
);
// values copied from config
t.deepEqual(tm.layers_by_source, expected.layers_by_source || {});
t.deepEqual(tm.source_aliases, expected.source_aliases || {});
t.deepEqual(tm.layer_aliases, expected.layer_aliases || {});
t.end();
});
});
};
module.exports.all = function (tape, common) {
function test(name, testFunction) {
return tape('TypeMapping: ' + name, testFunction);
}
for( var testCase in module.exports.tests ){
module.exports.tests[testCase](test, common);
}
};

91
test/unit/helper/type_mapping.js

@ -4,18 +4,31 @@ var type_mapping = require('../../../helper/type_mapping');
module.exports.tests = {}; module.exports.tests = {};
module.exports.tests.interfaces = function(test, common) { module.exports.tests.interfaces = function(test, common) {
test('basic layer mapping', function(t) {
t.deepEquals(type_mapping.layer_mapping.venue, ['venue']); test('complete sources', function(t) {
t.deepEquals(type_mapping.layer_mapping.address, ['address']); t.deepEquals(type_mapping.sources, [ 'openstreetmap', 'openaddresses', 'geonames', 'whosonfirst' ]);
t.end(); t.end();
}); });
test('alias layer mapping', function(t) { test('complete layers', function(t) {
t.deepEquals(type_mapping.layer_mapping.coarse, t.deepEquals(type_mapping.layers, [ 'address', 'venue', 'street', 'country', 'macroregion',
[ 'continent', 'empire', 'country', 'dependency', 'macroregion', 'region', 'county', 'localadmin', 'locality', 'borough', 'neighbourhood', 'continent',
'region', 'locality', 'localadmin', 'macrocounty', 'county', 'macrohood', 'empire', 'dependency', 'macrocounty', 'macrohood', 'microhood', 'disputed',
'borough', 'neighbourhood', 'microhood', 'disputed', 'postalcode', 'postalcode', 'ocean', 'marinearea' ]);
'continent', 'ocean', 'marinearea']); t.end();
});
test('complete source mapping', function(t) {
t.deepEquals(type_mapping.source_mapping, {
osm: [ 'openstreetmap' ],
oa: [ 'openaddresses' ],
gn: [ 'geonames' ],
wof: [ 'whosonfirst' ],
openstreetmap: [ 'openstreetmap' ],
openaddresses: [ 'openaddresses' ],
geonames: [ 'geonames' ],
whosonfirst: [ 'whosonfirst' ]
});
t.end(); t.end();
}); });
@ -30,6 +43,66 @@ module.exports.tests.interfaces = function(test, common) {
t.deepEquals(type_mapping.source_mapping.wof, ['whosonfirst']); t.deepEquals(type_mapping.source_mapping.wof, ['whosonfirst']);
t.end(); t.end();
}); });
test('complete layer mapping', function(t) {
t.deepEquals(type_mapping.layer_mapping, {
coarse: [ 'continent', 'empire', 'country', 'dependency', 'macroregion', 'region',
'locality', 'localadmin', 'macrocounty', 'county', 'macrohood', 'borough',
'neighbourhood', 'microhood', 'disputed', 'postalcode', 'continent', 'ocean', 'marinearea'
],
address: [ 'address' ],
venue: [ 'venue' ],
street: [ 'street' ],
country: [ 'country' ],
macroregion: [ 'macroregion' ],
region: [ 'region' ],
county: [ 'county' ],
localadmin: [ 'localadmin' ],
locality: [ 'locality' ],
borough: [ 'borough' ],
neighbourhood: [ 'neighbourhood' ],
continent: [ 'continent' ],
empire: [ 'empire' ],
dependency: [ 'dependency' ],
macrocounty: [ 'macrocounty' ],
macrohood: [ 'macrohood' ],
microhood: [ 'microhood' ],
disputed: [ 'disputed' ],
postalcode: [ 'postalcode' ],
ocean: [ 'ocean' ],
marinearea: [ 'marinearea' ]
});
t.end();
});
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('alias layer mapping', function(t) {
t.deepEquals(type_mapping.layer_mapping.coarse,
[ 'continent', 'empire', 'country', 'dependency', 'macroregion',
'region', 'locality', 'localadmin', 'macrocounty', 'county', 'macrohood',
'borough', 'neighbourhood', 'microhood', 'disputed', 'postalcode',
'continent', 'ocean', 'marinearea']);
t.end();
});
test('complete layers by source', function(t) {
t.deepEquals(type_mapping.layers_by_source, {
openstreetmap: [ 'address', 'venue', 'street' ],
openaddresses: [ 'address' ],
geonames: [ 'country', 'macroregion', 'region', 'county', 'localadmin',
'locality', 'borough', 'neighbourhood', 'venue' ],
whosonfirst: [ 'continent', 'empire', 'country', 'dependency', 'macroregion',
'region', 'locality', 'localadmin', 'macrocounty', 'county', 'macrohood',
'borough', 'neighbourhood', 'microhood', 'disputed', 'venue', 'postalcode',
'continent', 'ocean', 'marinearea' ]
});
t.end();
});
}; };
module.exports.all = function (tape, common) { module.exports.all = function (tape, common) {

75
test/unit/middleware/interpolate.js

@ -277,6 +277,81 @@ module.exports.tests.success_conditions = (test, common) => {
}); });
test('results should be sorted first by address/non-address. previous ordering should otherwise be maintained via a stable sort', t => {
const service = (req, res, callback) => {
// results 5 and 7 will have interpolated results returned
// this is to ensure results are re-sorted to move the addresses first
if (res.id === 5 || res.id === 7) {
callback(null, {
properties: {
number: 17,
source: 'Source Abbr 1',
source_id: 'source 1 source id',
lat: 12.121212,
lon: 21.212121
}
});
} else {
// return empty results in most cases
callback(null, {});
}
};
const logger = require('pelias-mock-logger')();
const controller = proxyquire('../../../middleware/interpolate', {
'pelias-logger': logger
})(service, () => true);
const req = {
clean: {
parsed_text: 'this is req.clean.parsed_text'
}
};
const res = {};
// helper method to generate test results which default to streets
function generateTestStreets(id) {
return {
id: id+1,
layer: 'street',
name: { default: `name ${id+1}` },
address_parts: {},
source_id: 'original source_id'
};
}
// generate a set of street results of desired size
// NOTE: this set must be of 11 elements or greater
// Node.js uses stable insertion sort for arrays of 10 or fewer elements,
// but _unstable_ QuickSort for larger arrays
const resultCount = 11;
const sequence_array = Array.from(new Array(resultCount),(val,index)=>index);
res.data = sequence_array.map(generateTestStreets);
controller(req, res, () => {
t.notOk(logger.hasErrorMessages(), 'there shouldn\'t be any error messages');
const results = res.data;
t.equals(results.length, results.length, 'correct number of results should be returned');
t.equals(results[0].layer, 'address', 'first result should be interpolated address');
t.equals(results[1].layer, 'address', 'second result should be interpolated address');
// iterate through all remaining records, ensuring their ids are increasing,
// as was the case when the set of streets was originally generated
let previous_id;
for (let i = 2; i < results.length; i++) {
if (previous_id) {
t.ok(results[i].id > previous_id, `id ${results[i].id} should be higher than ${previous_id}, to ensure sort is stable`);
}
previous_id = results[i].id;
}
t.end();
});
});
test('service call returning error should not map in interpolated results for non-errors', t => { test('service call returning error should not map in interpolated results for non-errors', t => {
const service = (req, res, callback) => { const service = (req, res, callback) => {
if (res.id === 1) { if (res.id === 1) {

1
test/unit/run.js

@ -36,6 +36,7 @@ var tests = [
require('./helper/geojsonify_place_details'), require('./helper/geojsonify_place_details'),
require('./helper/geojsonify'), require('./helper/geojsonify'),
require('./helper/logging'), require('./helper/logging'),
require('./helper/TypeMapping'),
require('./helper/type_mapping'), require('./helper/type_mapping'),
require('./helper/stackTraceLine'), require('./helper/stackTraceLine'),
require('./middleware/access_log'), require('./middleware/access_log'),

Loading…
Cancel
Save