mirror of https://github.com/pelias/api.git
Browse Source
several things are in this commit: - coarse reverse controller / pip service - inject "conditional execute" predicate into search service to shortcut execution conditions - added coarse reverse controller to precede standard reverse controller - lots of tests!pull/810/head
Stephen Hess
8 years ago
16 changed files with 1195 additions and 70 deletions
@ -0,0 +1,129 @@ |
|||||||
|
const logger = require('pelias-logger').get('coarse_reverse'); |
||||||
|
const _ = require('lodash'); |
||||||
|
const Document = require('pelias-model').Document; |
||||||
|
|
||||||
|
const granularities = [ |
||||||
|
'neighbourhood', |
||||||
|
'borough', |
||||||
|
'locality', |
||||||
|
'localadmin', |
||||||
|
'county', |
||||||
|
'macrocounty', |
||||||
|
'region', |
||||||
|
'macroregion', |
||||||
|
'dependency', |
||||||
|
'country' |
||||||
|
]; |
||||||
|
|
||||||
|
function getMostGranularLayer(results) { |
||||||
|
return granularities.find((granularity) => { |
||||||
|
return results.hasOwnProperty(granularity); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
function hasResultsAtRequestedLayers(results, layers) { |
||||||
|
return _.intersection(layers, Object.keys(results)).length > 0; |
||||||
|
} |
||||||
|
|
||||||
|
function synthesizeDoc(results) { |
||||||
|
// now create a model.Document from what's level, using the most granular
|
||||||
|
// result available as the starting point
|
||||||
|
// the immediately above cannot be re-used since county may be the most
|
||||||
|
// granular layer requested but the results may start at region (no county found)
|
||||||
|
const most_granular_layer = getMostGranularLayer(results); |
||||||
|
const id = results[most_granular_layer][0].id; |
||||||
|
|
||||||
|
const doc = new Document('whosonfirst', most_granular_layer, id.toString()); |
||||||
|
doc.setName('default', results[most_granular_layer][0].name); |
||||||
|
|
||||||
|
if (results[most_granular_layer][0].hasOwnProperty('centroid')) { |
||||||
|
doc.setCentroid( results[most_granular_layer][0].centroid ); |
||||||
|
} |
||||||
|
|
||||||
|
if (results[most_granular_layer][0].hasOwnProperty('bounding_box')) { |
||||||
|
const parsedBoundingBox = results[most_granular_layer][0].bounding_box.split(',').map(parseFloat); |
||||||
|
doc.setBoundingBox({ |
||||||
|
upperLeft: { |
||||||
|
lat: parsedBoundingBox[3], |
||||||
|
lon: parsedBoundingBox[0] |
||||||
|
}, |
||||||
|
lowerRight: { |
||||||
|
lat: parsedBoundingBox[1], |
||||||
|
lon: parsedBoundingBox[2] |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
if (_.has(results, 'country[0].abbr')) { |
||||||
|
doc.setAlpha3(results.country[0].abbr); |
||||||
|
} |
||||||
|
|
||||||
|
// assign the administrative hierarchy
|
||||||
|
Object.keys(results).forEach((layer) => { |
||||||
|
if (results[layer][0].hasOwnProperty('abbr')) { |
||||||
|
doc.addParent(layer, results[layer][0].name, results[layer][0].id.toString(), results[layer][0].abbr); |
||||||
|
} else { |
||||||
|
doc.addParent(layer, results[layer][0].name, results[layer][0].id.toString()); |
||||||
|
} |
||||||
|
|
||||||
|
}); |
||||||
|
|
||||||
|
const esDoc = doc.toESDocument(); |
||||||
|
esDoc.data._id = esDoc._id; |
||||||
|
esDoc.data._type = esDoc._type; |
||||||
|
return esDoc.data; |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
function setup(service, should_execute) { |
||||||
|
function controller(req, res, next) { |
||||||
|
// do not run controller when a request validation error has occurred
|
||||||
|
if (!should_execute(req, res)) { |
||||||
|
return next(); |
||||||
|
} |
||||||
|
|
||||||
|
const centroid = { |
||||||
|
lat: req.clean['point.lat'], |
||||||
|
lon: req.clean['point.lon'] |
||||||
|
}; |
||||||
|
|
||||||
|
service(centroid, (err, results) => { |
||||||
|
// if there's an error, log it and bail
|
||||||
|
if (err) { |
||||||
|
logger.error(err); |
||||||
|
return next(); |
||||||
|
} |
||||||
|
|
||||||
|
// find the finest granularity requested
|
||||||
|
const finest_granularity_requested = granularities.findIndex((granularity) => { |
||||||
|
return req.clean.layers.indexOf(granularity) !== -1; |
||||||
|
}); |
||||||
|
|
||||||
|
// now remove everything from the response that is more granular than the
|
||||||
|
// most granular layer requested. that is, if req.clean.layers=['county'],
|
||||||
|
// remove neighbourhoods, localities, and localadmins
|
||||||
|
Object.keys(results).forEach((layer) => { |
||||||
|
if (granularities.indexOf(layer) < finest_granularity_requested) { |
||||||
|
delete results[layer]; |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
res.meta = {}; |
||||||
|
res.data = []; |
||||||
|
// synthesize a doc from results if there's a result at the request layer(s)
|
||||||
|
if (hasResultsAtRequestedLayers(results, req.clean.layers)) { |
||||||
|
res.data.push(synthesizeDoc(results)); |
||||||
|
} |
||||||
|
|
||||||
|
return next(); |
||||||
|
|
||||||
|
}); |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
return controller; |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
module.exports = setup; |
@ -0,0 +1,5 @@ |
|||||||
|
const _ = require('lodash'); |
||||||
|
|
||||||
|
module.exports = (request, response) => { |
||||||
|
return _.get(response, 'data', []).length > 0; |
||||||
|
}; |
@ -0,0 +1,5 @@ |
|||||||
|
const _ = require('lodash'); |
||||||
|
|
||||||
|
module.exports = (request, response) => { |
||||||
|
return _.get(request, 'errors', []).length > 0; |
||||||
|
}; |
@ -0,0 +1,7 @@ |
|||||||
|
const _ = require('lodash'); |
||||||
|
|
||||||
|
module.exports = (req, res) => { |
||||||
|
// returns true if layers is undefined, empty, or contains 'address' or 'venue'
|
||||||
|
return !_.isEmpty(req.clean.layers) && |
||||||
|
_.intersection(req.clean.layers, ['address', 'venue']).length === 0; |
||||||
|
}; |
@ -0,0 +1,33 @@ |
|||||||
|
const logger = require( 'pelias-logger' ).get( 'pointinpolygon' ); |
||||||
|
const request = require('request'); |
||||||
|
|
||||||
|
module.exports = (url) => { |
||||||
|
function service( centroid, callback ){ |
||||||
|
const requestUrl = `${url}/${centroid.lon}/${centroid.lat}`; |
||||||
|
|
||||||
|
request.get(requestUrl, (err, response, body) => { |
||||||
|
if (err) { |
||||||
|
logger.error(JSON.stringify(err)); |
||||||
|
callback(err); |
||||||
|
} |
||||||
|
else if (response.statusCode === 200) { |
||||||
|
try { |
||||||
|
const parsed = JSON.parse(body); |
||||||
|
callback(err, parsed); |
||||||
|
} |
||||||
|
catch (err) { |
||||||
|
logger.error(`${requestUrl}: could not parse response body: ${body}`); |
||||||
|
callback(`${requestUrl} returned status 200 but with non-JSON response: ${body}`); |
||||||
|
} |
||||||
|
} |
||||||
|
else { |
||||||
|
logger.error(`${requestUrl} returned status ${response.statusCode}: ${body}`); |
||||||
|
callback(`${requestUrl} returned status ${response.statusCode}: ${body}`); |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
return service; |
||||||
|
|
||||||
|
}; |
@ -0,0 +1,578 @@ |
|||||||
|
'use strict'; |
||||||
|
|
||||||
|
const setup = require('../../../controller/coarse_reverse'); |
||||||
|
const proxyquire = require('proxyquire').noCallThru(); |
||||||
|
|
||||||
|
module.exports.tests = {}; |
||||||
|
|
||||||
|
module.exports.tests.interface = (test, common) => { |
||||||
|
test('valid interface', (t) => { |
||||||
|
t.equal(typeof setup, 'function', 'setup is a function'); |
||||||
|
t.equal(typeof setup(), 'function', 'setup returns a controller'); |
||||||
|
t.end(); |
||||||
|
}); |
||||||
|
}; |
||||||
|
|
||||||
|
module.exports.tests.early_exit_conditions = (test, common) => { |
||||||
|
test('should_execute returning false should not call service', (t) => { |
||||||
|
const service = () => { |
||||||
|
throw Error('service should not have been called'); |
||||||
|
}; |
||||||
|
|
||||||
|
const should_execute = () => { return false; }; |
||||||
|
const controller = setup(service, should_execute); |
||||||
|
|
||||||
|
const req = { |
||||||
|
clean: { |
||||||
|
layers: ['locality'] |
||||||
|
}, |
||||||
|
errors: ['error'] |
||||||
|
}; |
||||||
|
|
||||||
|
// verify that next was called
|
||||||
|
let next_was_called = false; |
||||||
|
const next = () => { |
||||||
|
next_was_called = true; |
||||||
|
}; |
||||||
|
|
||||||
|
// passing res=undefined verifies that it wasn't interacted with
|
||||||
|
t.doesNotThrow(controller.bind(null, req, undefined, next)); |
||||||
|
t.ok(next_was_called); |
||||||
|
t.end(); |
||||||
|
|
||||||
|
}); |
||||||
|
|
||||||
|
}; |
||||||
|
|
||||||
|
module.exports.tests.error_conditions = (test, common) => { |
||||||
|
test('service error should log and call next', (t) => { |
||||||
|
const service = (point, callback) => { |
||||||
|
callback('this is an error'); |
||||||
|
}; |
||||||
|
|
||||||
|
const logger = require('pelias-mock-logger')(); |
||||||
|
|
||||||
|
const should_execute = () => { return true; }; |
||||||
|
const controller = proxyquire('../../../controller/coarse_reverse', { |
||||||
|
'pelias-logger': logger |
||||||
|
})(service, should_execute); |
||||||
|
|
||||||
|
const req = { |
||||||
|
clean: { |
||||||
|
layers: ['locality'], |
||||||
|
point: { |
||||||
|
lat: 12.121212, |
||||||
|
lon: 21.212121 |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
// verify that next was called
|
||||||
|
let next_was_called = false; |
||||||
|
const next = () => { |
||||||
|
next_was_called = true; |
||||||
|
}; |
||||||
|
|
||||||
|
// passing res=undefined verifies that it wasn't interacted with
|
||||||
|
controller(req, undefined, next); |
||||||
|
|
||||||
|
t.ok(logger.isErrorMessage('this is an error')); |
||||||
|
t.ok(next_was_called); |
||||||
|
t.end(); |
||||||
|
|
||||||
|
}); |
||||||
|
|
||||||
|
}; |
||||||
|
|
||||||
|
module.exports.tests.success_conditions = (test, common) => { |
||||||
|
test('service returning results should use first entry for each layer', (t) => { |
||||||
|
const service = (point, callback) => { |
||||||
|
const results = { |
||||||
|
neighbourhood: [ |
||||||
|
{ |
||||||
|
id: 10, |
||||||
|
name: 'neighbourhood name', |
||||||
|
abbr: 'neighbourhood abbr', |
||||||
|
centroid: { |
||||||
|
lat: 12.121212, |
||||||
|
lon: 21.212121 |
||||||
|
}, |
||||||
|
bounding_box: '-76.345902,40.006751,-76.254038,40.072939' |
||||||
|
}, |
||||||
|
{ id: 11, name: 'neighbourhood name 2'} |
||||||
|
], |
||||||
|
borough: [ |
||||||
|
{ id: 20, name: 'borough name', abbr: 'borough abbr'}, |
||||||
|
{ id: 21, name: 'borough name 2'} |
||||||
|
], |
||||||
|
locality: [ |
||||||
|
{ id: 30, name: 'locality name', abbr: 'locality abbr'}, |
||||||
|
{ id: 31, name: 'locality name 2'} |
||||||
|
], |
||||||
|
localadmin: [ |
||||||
|
{ id: 40, name: 'localadmin name', abbr: 'localadmin abbr'}, |
||||||
|
{ id: 41, name: 'localadmin name 2'} |
||||||
|
], |
||||||
|
county: [ |
||||||
|
{ id: 50, name: 'county name', abbr: 'county abbr'}, |
||||||
|
{ id: 51, name: 'county name 2'} |
||||||
|
], |
||||||
|
macrocounty: [ |
||||||
|
{ id: 60, name: 'macrocounty name', abbr: 'macrocounty abbr'}, |
||||||
|
{ id: 61, name: 'macrocounty name 2'} |
||||||
|
], |
||||||
|
region: [ |
||||||
|
{ id: 70, name: 'region name', abbr: 'region abbr'}, |
||||||
|
{ id: 71, name: 'region name 2'} |
||||||
|
], |
||||||
|
macroregion: [ |
||||||
|
{ id: 80, name: 'macroregion name', abbr: 'macroregion abbr'}, |
||||||
|
{ id: 81, name: 'macroregion name 2'} |
||||||
|
], |
||||||
|
dependency: [ |
||||||
|
{ id: 90, name: 'dependency name', abbr: 'dependency abbr'}, |
||||||
|
{ id: 91, name: 'dependency name 2'} |
||||||
|
], |
||||||
|
country: [ |
||||||
|
{ id: 100, name: 'country name', abbr: 'xyz'}, |
||||||
|
{ id: 101, name: 'country name 2'} |
||||||
|
] |
||||||
|
}; |
||||||
|
|
||||||
|
callback(undefined, results); |
||||||
|
}; |
||||||
|
|
||||||
|
const logger = require('pelias-mock-logger')(); |
||||||
|
|
||||||
|
const should_execute = () => { return true; }; |
||||||
|
const controller = proxyquire('../../../controller/coarse_reverse', { |
||||||
|
'pelias-logger': logger |
||||||
|
})(service, should_execute); |
||||||
|
|
||||||
|
const req = { |
||||||
|
clean: { |
||||||
|
layers: ['neighbourhood'], |
||||||
|
point: { |
||||||
|
lat: 12.121212, |
||||||
|
lon: 21.212121 |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
const res = { }; |
||||||
|
|
||||||
|
// verify that next was called
|
||||||
|
let next_was_called = false; |
||||||
|
const next = () => { |
||||||
|
next_was_called = true; |
||||||
|
}; |
||||||
|
|
||||||
|
controller(req, res, next); |
||||||
|
|
||||||
|
const expected = { |
||||||
|
meta: {}, |
||||||
|
data: [ |
||||||
|
{ |
||||||
|
_id: '10', |
||||||
|
_type: 'neighbourhood', |
||||||
|
layer: 'neighbourhood', |
||||||
|
source: 'whosonfirst', |
||||||
|
source_id: '10', |
||||||
|
name: { |
||||||
|
'default': 'neighbourhood name' |
||||||
|
}, |
||||||
|
phrase: { |
||||||
|
'default': 'neighbourhood name' |
||||||
|
}, |
||||||
|
parent: { |
||||||
|
neighbourhood: ['neighbourhood name'], |
||||||
|
neighbourhood_id: ['10'], |
||||||
|
neighbourhood_a: ['neighbourhood abbr'], |
||||||
|
borough: ['borough name'], |
||||||
|
borough_id: ['20'], |
||||||
|
borough_a: ['borough abbr'], |
||||||
|
locality: ['locality name'], |
||||||
|
locality_id: ['30'], |
||||||
|
locality_a: ['locality abbr'], |
||||||
|
localadmin: ['localadmin name'], |
||||||
|
localadmin_id: ['40'], |
||||||
|
localadmin_a: ['localadmin abbr'], |
||||||
|
county: ['county name'], |
||||||
|
county_id: ['50'], |
||||||
|
county_a: ['county abbr'], |
||||||
|
macrocounty: ['macrocounty name'], |
||||||
|
macrocounty_id: ['60'], |
||||||
|
macrocounty_a: ['macrocounty abbr'], |
||||||
|
region: ['region name'], |
||||||
|
region_id: ['70'], |
||||||
|
region_a: ['region abbr'], |
||||||
|
macroregion: ['macroregion name'], |
||||||
|
macroregion_id: ['80'], |
||||||
|
macroregion_a: ['macroregion abbr'], |
||||||
|
dependency: ['dependency name'], |
||||||
|
dependency_id: ['90'], |
||||||
|
dependency_a: ['dependency abbr'], |
||||||
|
country: ['country name'], |
||||||
|
country_id: ['100'], |
||||||
|
country_a: ['xyz'] |
||||||
|
}, |
||||||
|
alpha3: 'XYZ', |
||||||
|
center_point: { |
||||||
|
lat: 12.121212, |
||||||
|
lon: 21.212121 |
||||||
|
}, |
||||||
|
bounding_box: '{"min_lat":40.006751,"max_lat":40.072939,"min_lon":-76.345902,"max_lon":-76.254038}' |
||||||
|
} |
||||||
|
] |
||||||
|
}; |
||||||
|
|
||||||
|
t.deepEquals(res, expected); |
||||||
|
|
||||||
|
t.notOk(logger.hasErrorMessages()); |
||||||
|
t.ok(next_was_called); |
||||||
|
t.end(); |
||||||
|
|
||||||
|
}); |
||||||
|
|
||||||
|
test('layers missing from results should be ignored', (t) => { |
||||||
|
const service = (point, callback) => { |
||||||
|
const results = { |
||||||
|
neighbourhood: [ |
||||||
|
{ |
||||||
|
id: 10, |
||||||
|
name: 'neighbourhood name', |
||||||
|
abbr: 'neighbourhood abbr', |
||||||
|
centroid: { |
||||||
|
lat: 12.121212, |
||||||
|
lon: 21.212121 |
||||||
|
}, |
||||||
|
bounding_box: '-76.345902,40.006751,-76.254038,40.072939' |
||||||
|
} |
||||||
|
] |
||||||
|
}; |
||||||
|
|
||||||
|
callback(undefined, results); |
||||||
|
}; |
||||||
|
|
||||||
|
const logger = require('pelias-mock-logger')(); |
||||||
|
|
||||||
|
const should_execute = () => { return true; }; |
||||||
|
const controller = proxyquire('../../../controller/coarse_reverse', { |
||||||
|
'pelias-logger': logger |
||||||
|
})(service, should_execute); |
||||||
|
|
||||||
|
const req = { |
||||||
|
clean: { |
||||||
|
layers: ['neighbourhood'], |
||||||
|
point: { |
||||||
|
lat: 12.121212, |
||||||
|
lon: 21.212121 |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
const res = { }; |
||||||
|
|
||||||
|
// verify that next was called
|
||||||
|
let next_was_called = false; |
||||||
|
const next = () => { |
||||||
|
next_was_called = true; |
||||||
|
}; |
||||||
|
|
||||||
|
controller(req, res, next); |
||||||
|
|
||||||
|
const expected = { |
||||||
|
meta: {}, |
||||||
|
data: [ |
||||||
|
{ |
||||||
|
_id: '10', |
||||||
|
_type: 'neighbourhood', |
||||||
|
layer: 'neighbourhood', |
||||||
|
source: 'whosonfirst', |
||||||
|
source_id: '10', |
||||||
|
name: { |
||||||
|
'default': 'neighbourhood name' |
||||||
|
}, |
||||||
|
phrase: { |
||||||
|
'default': 'neighbourhood name' |
||||||
|
}, |
||||||
|
parent: { |
||||||
|
neighbourhood: ['neighbourhood name'], |
||||||
|
neighbourhood_id: ['10'], |
||||||
|
neighbourhood_a: ['neighbourhood abbr'] |
||||||
|
}, |
||||||
|
center_point: { |
||||||
|
lat: 12.121212, |
||||||
|
lon: 21.212121 |
||||||
|
}, |
||||||
|
bounding_box: '{"min_lat":40.006751,"max_lat":40.072939,"min_lon":-76.345902,"max_lon":-76.254038}' |
||||||
|
} |
||||||
|
] |
||||||
|
}; |
||||||
|
|
||||||
|
t.deepEquals(res, expected); |
||||||
|
|
||||||
|
t.notOk(logger.hasErrorMessages()); |
||||||
|
t.ok(next_was_called); |
||||||
|
t.end(); |
||||||
|
|
||||||
|
}); |
||||||
|
|
||||||
|
test('most granular layer missing centroid should not set', (t) => { |
||||||
|
const service = (point, callback) => { |
||||||
|
const results = { |
||||||
|
neighbourhood: [ |
||||||
|
{ |
||||||
|
id: 10, |
||||||
|
name: 'neighbourhood name', |
||||||
|
abbr: 'neighbourhood abbr', |
||||||
|
bounding_box: '-76.345902,40.006751,-76.254038,40.072939' |
||||||
|
} |
||||||
|
] |
||||||
|
}; |
||||||
|
|
||||||
|
callback(undefined, results); |
||||||
|
}; |
||||||
|
|
||||||
|
const logger = require('pelias-mock-logger')(); |
||||||
|
|
||||||
|
const should_execute = () => { return true; }; |
||||||
|
const controller = proxyquire('../../../controller/coarse_reverse', { |
||||||
|
'pelias-logger': logger |
||||||
|
})(service, should_execute); |
||||||
|
|
||||||
|
const req = { |
||||||
|
clean: { |
||||||
|
layers: ['neighbourhood'], |
||||||
|
point: { |
||||||
|
lat: 12.121212, |
||||||
|
lon: 21.212121 |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
const res = { }; |
||||||
|
|
||||||
|
// verify that next was called
|
||||||
|
let next_was_called = false; |
||||||
|
const next = () => { |
||||||
|
next_was_called = true; |
||||||
|
}; |
||||||
|
|
||||||
|
controller(req, res, next); |
||||||
|
|
||||||
|
const expected = { |
||||||
|
meta: {}, |
||||||
|
data: [ |
||||||
|
{ |
||||||
|
_id: '10', |
||||||
|
_type: 'neighbourhood', |
||||||
|
layer: 'neighbourhood', |
||||||
|
source: 'whosonfirst', |
||||||
|
source_id: '10', |
||||||
|
name: { |
||||||
|
'default': 'neighbourhood name' |
||||||
|
}, |
||||||
|
phrase: { |
||||||
|
'default': 'neighbourhood name' |
||||||
|
}, |
||||||
|
parent: { |
||||||
|
neighbourhood: ['neighbourhood name'], |
||||||
|
neighbourhood_id: ['10'], |
||||||
|
neighbourhood_a: ['neighbourhood abbr'] |
||||||
|
}, |
||||||
|
bounding_box: '{"min_lat":40.006751,"max_lat":40.072939,"min_lon":-76.345902,"max_lon":-76.254038}' |
||||||
|
} |
||||||
|
] |
||||||
|
}; |
||||||
|
|
||||||
|
t.deepEquals(res, expected); |
||||||
|
|
||||||
|
t.notOk(logger.hasErrorMessages()); |
||||||
|
t.ok(next_was_called); |
||||||
|
t.end(); |
||||||
|
|
||||||
|
}); |
||||||
|
|
||||||
|
test('most granular layer missing bounding_box should not set', (t) => { |
||||||
|
const service = (point, callback) => { |
||||||
|
const results = { |
||||||
|
neighbourhood: [ |
||||||
|
{ |
||||||
|
id: 10, |
||||||
|
name: 'neighbourhood name', |
||||||
|
abbr: 'neighbourhood abbr', |
||||||
|
centroid: { |
||||||
|
lat: 12.121212, |
||||||
|
lon: 21.212121 |
||||||
|
} |
||||||
|
} |
||||||
|
] |
||||||
|
}; |
||||||
|
|
||||||
|
callback(undefined, results); |
||||||
|
}; |
||||||
|
|
||||||
|
const logger = require('pelias-mock-logger')(); |
||||||
|
|
||||||
|
const should_execute = () => { return true; }; |
||||||
|
const controller = proxyquire('../../../controller/coarse_reverse', { |
||||||
|
'pelias-logger': logger |
||||||
|
})(service, should_execute); |
||||||
|
|
||||||
|
const req = { |
||||||
|
clean: { |
||||||
|
layers: ['neighbourhood'], |
||||||
|
point: { |
||||||
|
lat: 12.121212, |
||||||
|
lon: 21.212121 |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
const res = { }; |
||||||
|
|
||||||
|
// verify that next was called
|
||||||
|
let next_was_called = false; |
||||||
|
const next = () => { |
||||||
|
next_was_called = true; |
||||||
|
}; |
||||||
|
|
||||||
|
controller(req, res, next); |
||||||
|
|
||||||
|
const expected = { |
||||||
|
meta: {}, |
||||||
|
data: [ |
||||||
|
{ |
||||||
|
_id: '10', |
||||||
|
_type: 'neighbourhood', |
||||||
|
layer: 'neighbourhood', |
||||||
|
source: 'whosonfirst', |
||||||
|
source_id: '10', |
||||||
|
name: { |
||||||
|
'default': 'neighbourhood name' |
||||||
|
}, |
||||||
|
phrase: { |
||||||
|
'default': 'neighbourhood name' |
||||||
|
}, |
||||||
|
parent: { |
||||||
|
neighbourhood: ['neighbourhood name'], |
||||||
|
neighbourhood_id: ['10'], |
||||||
|
neighbourhood_a: ['neighbourhood abbr'] |
||||||
|
}, |
||||||
|
center_point: { |
||||||
|
lat: 12.121212, |
||||||
|
lon: 21.212121 |
||||||
|
} |
||||||
|
} |
||||||
|
] |
||||||
|
}; |
||||||
|
|
||||||
|
t.deepEquals(res, expected); |
||||||
|
|
||||||
|
t.notOk(logger.hasErrorMessages()); |
||||||
|
t.ok(next_was_called); |
||||||
|
t.end(); |
||||||
|
|
||||||
|
}); |
||||||
|
|
||||||
|
}; |
||||||
|
|
||||||
|
module.exports.tests.failure_conditions = (test, common) => { |
||||||
|
test('service returning 0 results at the requested layer should return nothing', (t) => { |
||||||
|
const service = (point, callback) => { |
||||||
|
// response without neighbourhood results
|
||||||
|
const results = { |
||||||
|
borough: [ |
||||||
|
{ id: 20, name: 'borough name', abbr: 'borough abbr'}, |
||||||
|
{ id: 21, name: 'borough name 2'} |
||||||
|
], |
||||||
|
locality: [ |
||||||
|
{ id: 30, name: 'locality name', abbr: 'locality abbr'}, |
||||||
|
{ id: 31, name: 'locality name 2'} |
||||||
|
], |
||||||
|
localadmin: [ |
||||||
|
{ id: 40, name: 'localadmin name', abbr: 'localadmin abbr'}, |
||||||
|
{ id: 41, name: 'localadmin name 2'} |
||||||
|
], |
||||||
|
county: [ |
||||||
|
{ id: 50, name: 'county name', abbr: 'county abbr'}, |
||||||
|
{ id: 51, name: 'county name 2'} |
||||||
|
], |
||||||
|
macrocounty: [ |
||||||
|
{ id: 60, name: 'macrocounty name', abbr: 'macrocounty abbr'}, |
||||||
|
{ id: 61, name: 'macrocounty name 2'} |
||||||
|
], |
||||||
|
region: [ |
||||||
|
{ id: 70, name: 'region name', abbr: 'region abbr'}, |
||||||
|
{ id: 71, name: 'region name 2'} |
||||||
|
], |
||||||
|
macroregion: [ |
||||||
|
{ id: 80, name: 'macroregion name', abbr: 'macroregion abbr'}, |
||||||
|
{ id: 81, name: 'macroregion name 2'} |
||||||
|
], |
||||||
|
dependency: [ |
||||||
|
{ id: 90, name: 'dependency name', abbr: 'dependency abbr'}, |
||||||
|
{ id: 91, name: 'dependency name 2'} |
||||||
|
], |
||||||
|
country: [ |
||||||
|
{ id: 100, name: 'country name', abbr: 'xyz'}, |
||||||
|
{ id: 101, name: 'country name 2'} |
||||||
|
] |
||||||
|
}; |
||||||
|
|
||||||
|
callback(undefined, results); |
||||||
|
}; |
||||||
|
|
||||||
|
const logger = require('pelias-mock-logger')(); |
||||||
|
|
||||||
|
const should_execute = () => { return true; }; |
||||||
|
const controller = proxyquire('../../../controller/coarse_reverse', { |
||||||
|
'pelias-logger': logger |
||||||
|
})(service, should_execute); |
||||||
|
|
||||||
|
const req = { |
||||||
|
clean: { |
||||||
|
layers: ['neighbourhood'], |
||||||
|
point: { |
||||||
|
lat: 12.121212, |
||||||
|
lon: 21.212121 |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
const res = { }; |
||||||
|
|
||||||
|
// verify that next was called
|
||||||
|
let next_was_called = false; |
||||||
|
const next = () => { |
||||||
|
next_was_called = true; |
||||||
|
}; |
||||||
|
|
||||||
|
controller(req, res, next); |
||||||
|
|
||||||
|
const expected = { |
||||||
|
meta: {}, |
||||||
|
data: [] |
||||||
|
}; |
||||||
|
|
||||||
|
t.deepEquals(res, expected); |
||||||
|
|
||||||
|
t.notOk(logger.hasErrorMessages()); |
||||||
|
t.ok(next_was_called); |
||||||
|
t.end(); |
||||||
|
|
||||||
|
}); |
||||||
|
|
||||||
|
}; |
||||||
|
|
||||||
|
module.exports.all = (tape, common) => { |
||||||
|
|
||||||
|
function test(name, testFunction) { |
||||||
|
return tape(`GET /coarse_reverse ${name}`, testFunction); |
||||||
|
} |
||||||
|
|
||||||
|
for( const testCase in module.exports.tests ){ |
||||||
|
module.exports.tests[testCase](test, common); |
||||||
|
} |
||||||
|
}; |
@ -0,0 +1,60 @@ |
|||||||
|
'use strict'; |
||||||
|
|
||||||
|
const _ = require('lodash'); |
||||||
|
const has_data = require('../../../../controller/predicates/has_data'); |
||||||
|
|
||||||
|
module.exports.tests = {}; |
||||||
|
|
||||||
|
module.exports.tests.interface = (test, common) => { |
||||||
|
test('valid interface', (t) => { |
||||||
|
t.equal(typeof has_data, 'function', 'has_data is a function'); |
||||||
|
t.end(); |
||||||
|
}); |
||||||
|
}; |
||||||
|
|
||||||
|
module.exports.tests.true_conditions = (test, common) => { |
||||||
|
test('response with non-empty data should return true', (t) => { |
||||||
|
const req = {}; |
||||||
|
const res = { |
||||||
|
data: [1] |
||||||
|
}; |
||||||
|
|
||||||
|
t.ok(has_data(req, res)); |
||||||
|
t.end(); |
||||||
|
|
||||||
|
}); |
||||||
|
|
||||||
|
}; |
||||||
|
|
||||||
|
module.exports.tests.false_conditions = (test, common) => { |
||||||
|
test('response with undefined data should return true', (t) => { |
||||||
|
const req = {}; |
||||||
|
const res = {}; |
||||||
|
|
||||||
|
t.notOk(has_data(req, res)); |
||||||
|
t.end(); |
||||||
|
|
||||||
|
}); |
||||||
|
|
||||||
|
test('response with empty data array should return true', (t) => { |
||||||
|
const req = {}; |
||||||
|
const res = { |
||||||
|
data: [] |
||||||
|
}; |
||||||
|
|
||||||
|
t.notOk(has_data(req, res)); |
||||||
|
t.end(); |
||||||
|
|
||||||
|
}); |
||||||
|
|
||||||
|
}; |
||||||
|
|
||||||
|
module.exports.all = (tape, common) => { |
||||||
|
function test(name, testFunction) { |
||||||
|
return tape(`GET /has_data ${name}`, testFunction); |
||||||
|
} |
||||||
|
|
||||||
|
for( const testCase in module.exports.tests ){ |
||||||
|
module.exports.tests[testCase](test, common); |
||||||
|
} |
||||||
|
}; |
@ -0,0 +1,60 @@ |
|||||||
|
'use strict'; |
||||||
|
|
||||||
|
const _ = require('lodash'); |
||||||
|
const has_errors = require('../../../../controller/predicates/has_errors'); |
||||||
|
|
||||||
|
module.exports.tests = {}; |
||||||
|
|
||||||
|
module.exports.tests.interface = (test, common) => { |
||||||
|
test('valid interface', (t) => { |
||||||
|
t.equal(typeof has_errors, 'function', 'has_errors is a function'); |
||||||
|
t.end(); |
||||||
|
}); |
||||||
|
}; |
||||||
|
|
||||||
|
module.exports.tests.true_conditions = (test, common) => { |
||||||
|
test('request with non-empty errors should return true', (t) => { |
||||||
|
const req = { |
||||||
|
errors: ['error'] |
||||||
|
}; |
||||||
|
const res = {}; |
||||||
|
|
||||||
|
t.ok(has_errors(req, res)); |
||||||
|
t.end(); |
||||||
|
|
||||||
|
}); |
||||||
|
|
||||||
|
}; |
||||||
|
|
||||||
|
module.exports.tests.false_conditions = (test, common) => { |
||||||
|
test('response with undefined errors should return false', (t) => { |
||||||
|
const req = {}; |
||||||
|
const res = {}; |
||||||
|
|
||||||
|
t.notOk(has_errors(req, res)); |
||||||
|
t.end(); |
||||||
|
|
||||||
|
}); |
||||||
|
|
||||||
|
test('response with empty errors array should return false', (t) => { |
||||||
|
const req = { |
||||||
|
errors: [] |
||||||
|
}; |
||||||
|
const res = {}; |
||||||
|
|
||||||
|
t.notOk(has_errors(req, res)); |
||||||
|
t.end(); |
||||||
|
|
||||||
|
}); |
||||||
|
|
||||||
|
}; |
||||||
|
|
||||||
|
module.exports.all = (tape, common) => { |
||||||
|
function test(name, testFunction) { |
||||||
|
return tape(`GET /has_errors ${name}`, testFunction); |
||||||
|
} |
||||||
|
|
||||||
|
for( const testCase in module.exports.tests ){ |
||||||
|
module.exports.tests[testCase](test, common); |
||||||
|
} |
||||||
|
}; |
@ -0,0 +1,128 @@ |
|||||||
|
'use strict'; |
||||||
|
|
||||||
|
const _ = require('lodash'); |
||||||
|
const is_coarse_reverse = require('../../../../controller/predicates/is_coarse_reverse'); |
||||||
|
|
||||||
|
const coarse_layers = [ |
||||||
|
'continent', |
||||||
|
'country', |
||||||
|
'dependency', |
||||||
|
'macroregion', |
||||||
|
'region', |
||||||
|
'locality', |
||||||
|
'localadmin', |
||||||
|
'macrocounty', |
||||||
|
'county', |
||||||
|
'macrohood', |
||||||
|
'borough', |
||||||
|
'neighbourhood', |
||||||
|
'microhood', |
||||||
|
'disputed' |
||||||
|
]; |
||||||
|
|
||||||
|
module.exports.tests = {}; |
||||||
|
|
||||||
|
module.exports.tests.interface = (test, common) => { |
||||||
|
test('valid interface', (t) => { |
||||||
|
t.equal(typeof is_coarse_reverse, 'function', 'is_coarse_reverse is a function'); |
||||||
|
t.end(); |
||||||
|
}); |
||||||
|
}; |
||||||
|
|
||||||
|
module.exports.tests.false_conditions = (test, common) => { |
||||||
|
test('request without layers should return false', (t) => { |
||||||
|
const req = { |
||||||
|
clean: {} |
||||||
|
}; |
||||||
|
|
||||||
|
t.notOk(is_coarse_reverse(req)); |
||||||
|
t.end(); |
||||||
|
|
||||||
|
}); |
||||||
|
|
||||||
|
test('request with empty layers should return false', (t) => { |
||||||
|
const req = { |
||||||
|
clean: { |
||||||
|
layers: [] |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
t.notOk(is_coarse_reverse(req)); |
||||||
|
t.end(); |
||||||
|
|
||||||
|
}); |
||||||
|
|
||||||
|
test('request with layers just "address" or "venue" return false', (t) => { |
||||||
|
['address', 'venue'].forEach((non_coarse_layer) => { |
||||||
|
const req = { |
||||||
|
clean: { |
||||||
|
layers: [non_coarse_layer] |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
t.notOk(is_coarse_reverse(req)); |
||||||
|
|
||||||
|
}); |
||||||
|
|
||||||
|
t.end(); |
||||||
|
|
||||||
|
}); |
||||||
|
|
||||||
|
test('request with layers containing "address" or "venue" and a coarse layer should return false', (t) => { |
||||||
|
['address', 'venue'].forEach((non_coarse_layer) => { |
||||||
|
const req = { |
||||||
|
clean: { |
||||||
|
layers: [_.sample(coarse_layers), non_coarse_layer] |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
t.notOk(is_coarse_reverse(req)); |
||||||
|
|
||||||
|
}); |
||||||
|
|
||||||
|
t.end(); |
||||||
|
|
||||||
|
}); |
||||||
|
|
||||||
|
test('request with layers containing "address" and "venue" should return false', (t) => { |
||||||
|
const req = { |
||||||
|
clean: { |
||||||
|
layers: ['address', 'venue'] |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
t.notOk(is_coarse_reverse(req)); |
||||||
|
t.end(); |
||||||
|
|
||||||
|
}); |
||||||
|
|
||||||
|
}; |
||||||
|
|
||||||
|
module.exports.tests.true_conditions = (test, common) => { |
||||||
|
test('request with non-empty layers and not containing "address" or "venue" should return true', (t) => { |
||||||
|
coarse_layers.forEach((coarse_layer) => { |
||||||
|
const req = { |
||||||
|
clean: { |
||||||
|
layers: [coarse_layer] |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
t.ok(is_coarse_reverse(req)); |
||||||
|
|
||||||
|
}); |
||||||
|
|
||||||
|
t.end(); |
||||||
|
|
||||||
|
}); |
||||||
|
|
||||||
|
}; |
||||||
|
|
||||||
|
module.exports.all = (tape, common) => { |
||||||
|
function test(name, testFunction) { |
||||||
|
return tape(`GET /is_coarse_reverse ${name}`, testFunction); |
||||||
|
} |
||||||
|
|
||||||
|
for( const testCase in module.exports.tests ){ |
||||||
|
module.exports.tests[testCase](test, common); |
||||||
|
} |
||||||
|
}; |
@ -0,0 +1,140 @@ |
|||||||
|
const proxyquire = require('proxyquire').noCallThru(); |
||||||
|
|
||||||
|
const setup = require('../../../service/pointinpolygon'); |
||||||
|
|
||||||
|
module.exports.tests = {}; |
||||||
|
|
||||||
|
module.exports.tests.interface = (test, common) => { |
||||||
|
test('valid interface', (t) => { |
||||||
|
const logger = require('pelias-mock-logger')(); |
||||||
|
|
||||||
|
var service = proxyquire('../../../service/pointinpolygon', { |
||||||
|
'pelias-logger': logger |
||||||
|
}); |
||||||
|
|
||||||
|
t.equal(typeof service, 'function', 'service is a function'); |
||||||
|
t.end(); |
||||||
|
}); |
||||||
|
}; |
||||||
|
|
||||||
|
module.exports.tests.success = (test, common) => { |
||||||
|
test('lat and lon should be passed to server', (t) => { |
||||||
|
const pipServer = require('express')(); |
||||||
|
pipServer.get('/:lon/:lat', (req, res, next) => { |
||||||
|
t.equals(req.params.lat, '12.121212'); |
||||||
|
t.equals(req.params.lon, '21.212121'); |
||||||
|
|
||||||
|
res.send('{ "field": "value" }'); |
||||||
|
}); |
||||||
|
|
||||||
|
const server = pipServer.listen(); |
||||||
|
|
||||||
|
const service = setup(`http://localhost:${server.address().port}`); |
||||||
|
|
||||||
|
service({ lat: 12.121212, lon: 21.212121}, (err, results) => { |
||||||
|
t.notOk(err); |
||||||
|
t.deepEquals(results, { field: 'value' }); |
||||||
|
t.end(); |
||||||
|
|
||||||
|
server.close(); |
||||||
|
|
||||||
|
}); |
||||||
|
|
||||||
|
}); |
||||||
|
|
||||||
|
}; |
||||||
|
|
||||||
|
module.exports.tests.failure = (test, common) => { |
||||||
|
test('server returning success but non-JSON body should log error and return no results', (t) => { |
||||||
|
const pipServer = require('express')(); |
||||||
|
pipServer.get('/:lon/:lat', (req, res, next) => { |
||||||
|
t.equals(req.params.lat, '12.121212'); |
||||||
|
t.equals(req.params.lon, '21.212121'); |
||||||
|
|
||||||
|
res.send('this is not JSON'); |
||||||
|
}); |
||||||
|
|
||||||
|
const server = pipServer.listen(); |
||||||
|
const port = server.address().port; |
||||||
|
|
||||||
|
const logger = require('pelias-mock-logger')(); |
||||||
|
|
||||||
|
const service = proxyquire('../../../service/pointinpolygon', { |
||||||
|
'pelias-logger': logger |
||||||
|
})(`http://localhost:${port}`); |
||||||
|
|
||||||
|
service({ lat: 12.121212, lon: 21.212121}, (err, results) => { |
||||||
|
t.equals(err, `http://localhost:${port}/21.212121/12.121212 returned status 200 but with non-JSON response: this is not JSON`); |
||||||
|
t.notOk(results); |
||||||
|
t.ok(logger.isErrorMessage(`http://localhost:${port}/21.212121/12.121212: could not parse response body: this is not JSON`)); |
||||||
|
t.end(); |
||||||
|
|
||||||
|
server.close(); |
||||||
|
|
||||||
|
}); |
||||||
|
|
||||||
|
}); |
||||||
|
|
||||||
|
test('server returning error should log it and return no results', (t) => { |
||||||
|
const server = require('express')().listen(); |
||||||
|
const port = server.address().port; |
||||||
|
|
||||||
|
// immediately close the server so to ensure an error response
|
||||||
|
server.close(); |
||||||
|
|
||||||
|
const logger = require('pelias-mock-logger')(); |
||||||
|
|
||||||
|
const service = proxyquire('../../../service/pointinpolygon', { |
||||||
|
'pelias-logger': logger |
||||||
|
})(`http://localhost:${port}`); |
||||||
|
|
||||||
|
service({ lat: 12.121212, lon: 21.212121}, (err, results) => { |
||||||
|
t.equals(err.code, 'ECONNREFUSED'); |
||||||
|
t.notOk(results); |
||||||
|
t.ok(logger.isErrorMessage(/ECONNREFUSED/), 'there should be a connection refused error message'); |
||||||
|
t.end(); |
||||||
|
|
||||||
|
server.close(); |
||||||
|
|
||||||
|
}); |
||||||
|
|
||||||
|
}); |
||||||
|
|
||||||
|
test('non-OK status should log error and return no results', (t) => { |
||||||
|
const pipServer = require('express')(); |
||||||
|
pipServer.get('/:lat/:lon', (req, res, next) => { |
||||||
|
res.status(400).send('a bad request was made'); |
||||||
|
}); |
||||||
|
|
||||||
|
const server = pipServer.listen(); |
||||||
|
const port = server.address().port; |
||||||
|
|
||||||
|
const logger = require('pelias-mock-logger')(); |
||||||
|
|
||||||
|
const service = proxyquire('../../../service/pointinpolygon', { |
||||||
|
'pelias-logger': logger |
||||||
|
})(`http://localhost:${port}`); |
||||||
|
|
||||||
|
service({ lat: 12.121212, lon: 21.212121}, (err, results) => { |
||||||
|
t.equals(err, `http://localhost:${port}/21.212121/12.121212 returned status 400: a bad request was made`); |
||||||
|
t.notOk(results); |
||||||
|
t.ok(logger.isErrorMessage(`http://localhost:${port}/21.212121/12.121212 returned status 400: a bad request was made`)); |
||||||
|
t.end(); |
||||||
|
|
||||||
|
server.close(); |
||||||
|
|
||||||
|
}); |
||||||
|
|
||||||
|
}); |
||||||
|
|
||||||
|
}; |
||||||
|
|
||||||
|
module.exports.all = (tape, common) => { |
||||||
|
function test(name, testFunction) { |
||||||
|
return tape(`SERVICE /pointinpolygon ${name}`, testFunction); |
||||||
|
} |
||||||
|
|
||||||
|
for( var testCase in module.exports.tests ){ |
||||||
|
module.exports.tests[testCase](test, common); |
||||||
|
} |
||||||
|
}; |
Loading…
Reference in new issue