const placeholder = require('../../../controller/placeholder'); const proxyquire = require('proxyquire').noCallThru(); const mock_logger = require('pelias-mock-logger'); const _ = require('lodash'); module.exports.tests = {}; module.exports.tests.interface = (test, common) => { test('valid interface', (t) => { t.equal(typeof placeholder, 'function', 'placeholder is a function'); t.equal(typeof placeholder(), 'function', 'placeholder returns a controller'); t.end(); }); }; module.exports.tests.should_execute = (test, common) => { test('should_execute returning false should not call service', (t) => { let placeholder_service_was_called = false; const placeholder_service = () => { placeholder_service_was_called = true; }; const should_execute = (req, res) => { // req and res should be passed to should_execute t.deepEquals(req, { a: 1 }); t.deepEquals(res, { b: 2 }); return false; }; const controller = placeholder(placeholder_service, true, should_execute); const req = { a: 1 }; const res = { b: 2 }; controller(req, res, () => { t.notOk(placeholder_service_was_called); t.deepEquals(res, { b: 2 }); t.end(); }); }); test('should_execute returning true should call service', (t) => { let placeholder_service_was_called = false; const placeholder_service = (req, callback) => { t.deepEqual(req, { param1: 'param1 value' }); placeholder_service_was_called = true; callback(null, []); }; const controller = placeholder(placeholder_service, true, () => true); const req = { param1: 'param1 value' }; const res = { b: 2 }; controller(req, res, () => { t.ok(placeholder_service_was_called); t.deepEquals(res.data, []); t.end(); }); }); }; module.exports.tests.success = (test, common) => { test('response from service should be converted', (t) => { const logger = mock_logger(); const placeholder_service = (req, callback) => { t.deepEqual(req, { param1: 'param1 value' }); const response = [ { id: 123, name: 'name 1', placetype: 'neighbourhood', population: 1234, popularity: 5678, lineage: [ { country: { id: 1, name: 'country name 1', abbr: 'ABC' }, dependency: { id: 2, name: 'dependency name 1' }, macroregion: { id: 3, name: 'macroregion name 1' }, region: { id: 4, name: 'region name 1' }, macrocounty: { id: 5, name: 'macrocounty name 1' }, county: { id: 6, name: 'county name 1' }, localadmin: { id: 7, name: 'localadmin name 1' }, locality: { id: 8, name: 'locality name 1' }, borough: { id: 9, name: 'borough name 1' }, neighbourhood: { id: 10, name: 'neighbourhood name 1' } }, { country: { id: 11, name: 'country name 2', abbr: 'XYZ' }, dependency: { id: 12, name: 'dependency name 2', abbr: 'dependency abbr 2' }, macroregion: { id: 13, name: 'macroregion name 2', abbr: 'macroregion abbr 2' }, region: { id: 14, name: 'region name 2', abbr: 'region abbr 2' }, macrocounty: { id: 15, name: 'macrocounty name 2', abbr: 'macrocounty abbr 2' }, county: { id: 16, name: 'county name 2', abbr: 'county abbr 2' }, localadmin: { id: 17, name: 'localadmin name 2', abbr: 'localadmin abbr 2' }, locality: { id: 18, name: 'locality name 2', abbr: 'locality abbr 2' }, borough: { id: 19, name: 'borough name 2', abbr: 'borough abbr 2' }, neighbourhood: { id: 20, name: 'neighbourhood name 2', abbr: 'neighbourhood abbr 2' } } ], geom: { area: 12.34, bbox: '21.212121,12.121212,31.313131,13.131313', lat: 14.141414, lon: 41.414141 } }, { id: 456, name: 'name 3', placetype: 'locality', population: 4321, popularity: 8765, lineage: [ {} ], geom: { area: 23.45, bbox: '51.515151,15.151515,61.616161,16.161616', lat: 17.171717, lon: 71.717171 } } ]; callback(null, response); }; const controller = proxyquire('../../../controller/placeholder', { 'pelias-logger': logger })(placeholder_service, true, () => true); const req = { param1: 'param1 value' }; const res = { }; controller(req, res, () => { const expected_res = { meta: { query_type: 'search_fallback' }, data: [ { _id: '123', _type: 'neighbourhood', layer: 'neighbourhood', source: 'whosonfirst', source_id: '123', center_point: { lat: 14.141414, lon: 41.414141 }, bounding_box: '{"min_lat":12.121212,"max_lat":13.131313,"min_lon":21.212121,"max_lon":31.313131}', name: { 'default': 'name 1' }, phrase: { 'default': 'name 1' }, population: 1234, popularity: 5678, parent: { neighbourhood: ['neighbourhood name 1', 'neighbourhood name 2'], neighbourhood_id: ['10', '20'], neighbourhood_a: [null, 'neighbourhood abbr 2'], borough: ['borough name 1', 'borough name 2'], borough_id: ['9', '19'], borough_a: [null, 'borough abbr 2'], locality: ['locality name 1', 'locality name 2'], locality_id: ['8', '18'], locality_a: [null, 'locality abbr 2'], localadmin: ['localadmin name 1', 'localadmin name 2'], localadmin_id: ['7', '17'], localadmin_a: [null, 'localadmin abbr 2'], county: ['county name 1', 'county name 2'], county_id: ['6', '16'], county_a: [null, 'county abbr 2'], macrocounty: ['macrocounty name 1', 'macrocounty name 2'], macrocounty_id: ['5', '15'], macrocounty_a: [null, 'macrocounty abbr 2'], region: ['region name 1', 'region name 2'], region_id: ['4', '14'], region_a: [null, 'region abbr 2'], macroregion: ['macroregion name 1', 'macroregion name 2'], macroregion_id: ['3', '13'], macroregion_a: [null, 'macroregion abbr 2'], dependency: ['dependency name 1', 'dependency name 2'], dependency_id: ['2', '12'], dependency_a: [null, 'dependency abbr 2'], country: ['country name 1', 'country name 2'], country_id: ['1', '11'], country_a: ['ABC', 'XYZ'] } }, { _id: '456', _type: 'locality', layer: 'locality', source: 'whosonfirst', source_id: '456', center_point: { lat: 17.171717, lon: 71.717171 }, bounding_box: '{"min_lat":15.151515,"max_lat":16.161616,"min_lon":51.515151,"max_lon":61.616161}', name: { 'default': 'name 3' }, phrase: { 'default': 'name 3' }, population: 4321, popularity: 8765 } ] }; t.deepEquals(res, expected_res); t.ok(logger.isDebugMessage('[controller:placeholder] [result_count:2]')); t.end(); }); }); test('results with no lineage should not set any parent fields', (t) => { const logger = require('pelias-mock-logger')(); const placeholder_service = (req, callback) => { t.deepEqual(req, { param1: 'param1 value' }); const response = [ { id: 123, name: 'name 1', placetype: 'neighbourhood', geom: { area: 12.34, bbox: '21.212121,12.121212,31.313131,13.131313', lat: 14.141414, lon: 41.414141 } } ]; callback(null, response); }; const controller = proxyquire('../../../controller/placeholder', { 'pelias-logger': logger })(placeholder_service, true, () => true); const req = { param1: 'param1 value' }; const res = { }; controller(req, res, () => { const expected_res = { meta: { query_type: 'search_fallback' }, data: [ { _id: '123', _type: 'neighbourhood', layer: 'neighbourhood', source: 'whosonfirst', source_id: '123', center_point: { lat: 14.141414, lon: 41.414141 }, bounding_box: '{"min_lat":12.121212,"max_lat":13.131313,"min_lon":21.212121,"max_lon":31.313131}', name: { 'default': 'name 1' }, phrase: { 'default': 'name 1' } } ] }; t.deepEquals(res, expected_res); t.ok(logger.isDebugMessage('[controller:placeholder] [result_count:1]')); t.end(); }); }); test('results with string population/popularity should convert to number', (t) => { const logger = require('pelias-mock-logger')(); const placeholder_service = (req, callback) => { t.deepEqual(req, { param1: 'param1 value' }); const response = [ { id: 123, name: 'name 1', placetype: 'neighbourhood', population: '123.4', popularity: '567.8', geom: { area: 12.34 } } ]; callback(null, response); }; const controller = proxyquire('../../../controller/placeholder', { 'pelias-logger': logger })(placeholder_service, true, () => true); const req = { param1: 'param1 value' }; const res = { }; controller(req, res, () => { const expected_res = { meta: { query_type: 'search_fallback' }, data: [ { _id: '123', _type: 'neighbourhood', layer: 'neighbourhood', source: 'whosonfirst', source_id: '123', population: 123.4, popularity: 568, name: { 'default': 'name 1' }, phrase: { 'default': 'name 1' } } ] }; t.deepEquals(res, expected_res); t.ok(logger.isDebugMessage('[controller:placeholder] [result_count:1]')); t.end(); }); }); test('results with negative population/popularity should not set population/popularity', (t) => { const logger = require('pelias-mock-logger')(); const placeholder_service = (req, callback) => { t.deepEqual(req, { param1: 'param1 value' }); const response = [ { id: 123, name: 'name 1', placetype: 'neighbourhood', population: -1, popularity: -1, geom: { area: 12.34 } } ]; callback(null, response); }; const controller = proxyquire('../../../controller/placeholder', { 'pelias-logger': logger })(placeholder_service, true, () => true); const req = { param1: 'param1 value' }; const res = { }; controller(req, res, () => { const expected_res = { meta: { query_type: 'search_fallback' }, data: [ { _id: '123', _type: 'neighbourhood', layer: 'neighbourhood', source: 'whosonfirst', source_id: '123', name: { 'default': 'name 1' }, phrase: { 'default': 'name 1' } } ] }; t.deepEquals(res, expected_res); t.ok(logger.isDebugMessage('[controller:placeholder] [result_count:1]')); t.end(); }); }); test('results with undefined or empty name should be skipped', (t) => { [undefined, '', ' \t '].forEach((invalid_name) => { const logger = require('pelias-mock-logger')(); const placeholder_service = (req, callback) => { t.deepEqual(req, { param1: 'param1 value' }); const response = [ { id: 123, name: invalid_name, placetype: 'neighbourhood', geom: { area: 12.34 } }, { id: 456, name: 'name 2', placetype: 'neighbourhood', geom: { area: 12.34 } } ]; callback(null, response); }; const controller = proxyquire('../../../controller/placeholder', { 'pelias-logger': logger })(placeholder_service, true, () => true); const req = { param1: 'param1 value' }; const res = { }; controller(req, res, () => { const expected_res = { meta: { query_type: 'search_fallback' }, data: [ { _id: '456', _type: 'neighbourhood', layer: 'neighbourhood', source: 'whosonfirst', source_id: '456', name: { 'default': 'name 2' }, phrase: { 'default': 'name 2' } } ] }; t.deepEquals(res, expected_res); t.ok(logger.isDebugMessage('[controller:placeholder] [result_count:1]')); }); }); t.end(); }); test('results with string geom.lat/geom.lon should convert to numbers', (t) => { const logger = require('pelias-mock-logger')(); const placeholder_service = (req, callback) => { t.deepEqual(req, { param1: 'param1 value' }); const response = [ { id: 1, name: 'name 1', placetype: 'neighbourhood', geom: { area: 12.34, lat: '14.141414', lon: '41.414141' } } ]; callback(null, response); }; const should_execute = (req, res) => { return true; }; const controller = proxyquire('../../../controller/placeholder', { 'pelias-logger': logger })(placeholder_service, true, () => true); const req = { param1: 'param1 value' }; const res = { }; controller(req, res, () => { const expected_res = { meta: { query_type: 'search_fallback' }, data: [ { _id: '1', _type: 'neighbourhood', layer: 'neighbourhood', source: 'whosonfirst', source_id: '1', center_point: { lat: 14.141414, lon: 41.414141 }, name: { 'default': 'name 1' }, phrase: { 'default': 'name 1' } } ] }; t.deepEquals(res, expected_res); t.ok(logger.isDebugMessage('[controller:placeholder] [result_count:1]')); t.end(); }); }); }; module.exports.tests.result_filtering = (test, common) => { test('when boundary.rect is available, results outside of it should be removed', (t) => { const logger = require('pelias-mock-logger')(); const placeholder_service = (req, callback) => { t.deepEqual(req, { param1: 'param1 value', clean: { 'boundary.rect.min_lat': -2, 'boundary.rect.max_lat': 2, 'boundary.rect.min_lon': -2, 'boundary.rect.max_lon': 2 } }); const response = [ { // inside bbox id: 1, name: 'name 1', placetype: 'neighbourhood', geom: { lat: -1, lon: -1 } }, { // outside bbox on max_lon id: 2, name: 'name 2', placetype: 'neighbourhood', geom: { lat: -1, lon: 3 } }, { // outside bbox on max_lat id: 3, name: 'name 3', placetype: 'neighbourhood', geom: { lat: 3, lon: -1 } }, { // outside bbox on min_lon id: 4, name: 'name 4', placetype: 'neighbourhood', geom: { lat: -1, lon: -3 } }, { // outside bbox on min_lat id: 5, name: 'name 5', placetype: 'neighbourhood', geom: { lat: -3, lon: -1 } }, { // no lat/lon id: 6, name: 'name 6', placetype: 'neighbourhood', geom: { } }, { // empty string lat/lon id: 7, name: 'name 7', placetype: 'neighbourhood', geom: { lat: '', lon: '' } }, { // valid lat, empty string lon id: 8, name: 'name 8', placetype: 'neighbourhood', geom: { lat: 0, lon: ' ' } }, { // valid lon, empty string lat id: 9, name: 'name 9', placetype: 'neighbourhood', geom: { lat: ' ', lon: 0 } }, { // inside bbox id: 10, name: 'name 10', placetype: 'neighbourhood', geom: { lat: 1, lon: 1 } } ]; callback(null, response); }; const should_execute = (req, res) => { return true; }; const controller = proxyquire('../../../controller/placeholder', { 'pelias-logger': logger })(placeholder_service, true, () => true); const req = { param1: 'param1 value', clean: { 'boundary.rect.min_lat': -2, 'boundary.rect.max_lat': 2, 'boundary.rect.min_lon': -2, 'boundary.rect.max_lon': 2 } }; const res = { }; controller(req, res, () => { const expected_res = { meta: { query_type: 'search_fallback' }, data: [ { _id: '1', _type: 'neighbourhood', layer: 'neighbourhood', source: 'whosonfirst', source_id: '1', center_point: { lat: -1, lon: -1 }, name: { 'default': 'name 1' }, phrase: { 'default': 'name 1' } }, { _id: '10', _type: 'neighbourhood', layer: 'neighbourhood', source: 'whosonfirst', source_id: '10', center_point: { lat: 1, lon: 1 }, name: { 'default': 'name 10' }, phrase: { 'default': 'name 10' } } ] }; t.deepEquals(res, expected_res); t.end(); }); }); test('when geometric_filters_apply is false, boundary.rect should not apply', (t) => { const logger = require('pelias-mock-logger')(); const placeholder_service = (req, callback) => { t.deepEqual(req, { param1: 'param1 value', clean: { 'boundary.rect.min_lat': -1, 'boundary.rect.max_lat': 1, 'boundary.rect.min_lon': -1, 'boundary.rect.max_lon': 1 } }); const response = [ { // inside bbox id: 1, name: 'name 1', placetype: 'neighbourhood', geom: { lat: 0, lon: 0 } }, { // outside bbox id: 2, name: 'name 2', placetype: 'neighbourhood', geom: { lat: -2, lon: 2 } }, { // outside bbox id: 3, name: 'name 3', placetype: 'neighbourhood', geom: { lat: 2, lon: -2 } } ]; callback(null, response); }; const should_execute = (req, res) => { return true; }; const controller = proxyquire('../../../controller/placeholder', { 'pelias-logger': logger })(placeholder_service, false, () => true); const req = { param1: 'param1 value', clean: { 'boundary.rect.min_lat': -1, 'boundary.rect.max_lat': 1, 'boundary.rect.min_lon': -1, 'boundary.rect.max_lon': 1 } }; const res = { }; controller(req, res, () => { const expected_res = { meta: { query_type: 'search_fallback' }, data: [ { _id: '1', _type: 'neighbourhood', layer: 'neighbourhood', source: 'whosonfirst', source_id: '1', center_point: { lat: 0, lon: 0 }, name: { 'default': 'name 1' }, phrase: { 'default': 'name 1' } }, { _id: '2', _type: 'neighbourhood', layer: 'neighbourhood', source: 'whosonfirst', source_id: '2', center_point: { lat: -2, lon: 2 }, name: { 'default': 'name 2' }, phrase: { 'default': 'name 2' } }, { _id: '3', _type: 'neighbourhood', layer: 'neighbourhood', source: 'whosonfirst', source_id: '3', center_point: { lat: 2, lon: -2 }, name: { 'default': 'name 3' }, phrase: { 'default': 'name 3' } } ] }; t.deepEquals(res, expected_res); t.end(); }); }); test('when boundary.circle is available, results outside of it should be removed', (t) => { const logger = require('pelias-mock-logger')(); const placeholder_service = (req, callback) => { t.deepEqual(req, { param1: 'param1 value', clean: { 'boundary.circle.lat': 0, 'boundary.circle.lon': 0, 'boundary.circle.radius': 500 } }); const response = [ { // inside circle id: 1, name: 'name 1', placetype: 'neighbourhood', geom: { lat: 1, lon: 1 } }, { // outside circle on +lon id: 2, name: 'name 2', placetype: 'neighbourhood', geom: { lat: 0, lon: 45 } }, { // outside bbox on +lat id: 3, name: 'name 3', placetype: 'neighbourhood', geom: { lat: 45, lon: 0 } }, { // outside bbox on -lon id: 4, name: 'name 4', placetype: 'neighbourhood', geom: { lat: 0, lon: -45 } }, { // outside bbox on -lat id: 5, name: 'name 5', placetype: 'neighbourhood', geom: { lat: -45, lon: 0 } }, { // no lat/lon id: 6, name: 'name 6', placetype: 'neighbourhood', geom: { } }, { // empty string lat/lon id: 7, name: 'name 7', placetype: 'neighbourhood', geom: { lat: '', lon: '' } }, { // valid lat, empty string lon id: 8, name: 'name 8', placetype: 'neighbourhood', geom: { lat: 0, lon: ' ' } }, { // valid lon, empty string lat id: 9, name: 'name 9', placetype: 'neighbourhood', geom: { lat: ' ', lon: 0 } }, { // inside circle id: 10, name: 'name 10', placetype: 'neighbourhood', geom: { lat: -1, lon: -1 } } ]; callback(null, response); }; const should_execute = (req, res) => { return true; }; const controller = proxyquire('../../../controller/placeholder', { 'pelias-logger': logger })(placeholder_service, true, () => true); const req = { param1: 'param1 value', clean: { 'boundary.circle.lat': 0, 'boundary.circle.lon': 0, 'boundary.circle.radius': 500 } }; const res = { }; controller(req, res, () => { const expected_res = { meta: { query_type: 'search_fallback' }, data: [ { _id: '1', _type: 'neighbourhood', layer: 'neighbourhood', source: 'whosonfirst', source_id: '1', center_point: { lat: 1, lon: 1 }, name: { 'default': 'name 1' }, phrase: { 'default': 'name 1' } }, { _id: '10', _type: 'neighbourhood', layer: 'neighbourhood', source: 'whosonfirst', source_id: '10', center_point: { lat: -1, lon: -1 }, name: { 'default': 'name 10' }, phrase: { 'default': 'name 10' } } ] }; t.deepEquals(res, expected_res); t.end(); }); }); test('when geometric_filters_apply is false, boundary.circle should not apply', (t) => { const logger = require('pelias-mock-logger')(); const placeholder_service = (req, callback) => { t.deepEqual(req, { param1: 'param1 value', clean: { 'boundary.circle.lat': 0, 'boundary.circle.lon': 0, 'boundary.circle.radius': 500 } }); const response = [ { // inside circle id: 1, name: 'name 1', placetype: 'neighbourhood', geom: { lat: 1, lon: 1 } }, { // outside circle on +lon id: 2, name: 'name 2', placetype: 'neighbourhood', geom: { lat: -45, lon: 45 } }, { // outside bbox on +lat id: 3, name: 'name 3', placetype: 'neighbourhood', geom: { lat: 45, lon: -45 } } ]; callback(null, response); }; const should_execute = (req, res) => { return true; }; const controller = proxyquire('../../../controller/placeholder', { 'pelias-logger': logger })(placeholder_service, false, () => true); const req = { param1: 'param1 value', clean: { 'boundary.circle.lat': 0, 'boundary.circle.lon': 0, 'boundary.circle.radius': 500 } }; const res = { }; controller(req, res, () => { const expected_res = { meta: { query_type: 'search_fallback' }, data: [ { _id: '1', _type: 'neighbourhood', layer: 'neighbourhood', source: 'whosonfirst', source_id: '1', center_point: { lat: 1, lon: 1 }, name: { 'default': 'name 1' }, phrase: { 'default': 'name 1' } }, { _id: '2', _type: 'neighbourhood', layer: 'neighbourhood', source: 'whosonfirst', source_id: '2', center_point: { lat: -45, lon: 45 }, name: { 'default': 'name 2' }, phrase: { 'default': 'name 2' } }, { _id: '3', _type: 'neighbourhood', layer: 'neighbourhood', source: 'whosonfirst', source_id: '3', center_point: { lat: 45, lon: -45 }, name: { 'default': 'name 3' }, phrase: { 'default': 'name 3' } } ] }; t.deepEquals(res, expected_res); t.end(); }); }); test('only results matching explicit layers should be returned', (t) => { const logger = mock_logger(); const placeholder_service = (req, callback) => { t.deepEqual(req, { param1: 'param1 value', clean: { layers: ['neighbourhood', 'locality', 'county'] } }); const response = [ { id: 1, name: 'name 1', placetype: 'neighbourhood', lineage: [ {} ], geom: { area: 1, lat: 14.141414, lon: 41.414141 } }, { id: 2, name: 'name 2', placetype: 'borough', lineage: [ {} ], geom: { area: 2, lat: 15.151515, lon: 51.515151 } }, { id: 3, name: 'name 3', placetype: 'locality', lineage: [ {} ], geom: { area: 3, lat: 16.161616, lon: 61.616161 } }, { id: 4, name: 'name 4', placetype: 'localadmin', lineage: [ {} ], geom: { area: 4, lat: 17.171717, lon: 71.717171 } }, { id: 5, name: 'name 5', placetype: 'county', lineage: [ {} ], geom: { area: 5, lat: 18.181818, lon: 81.818181 } } ]; callback(null, response); }; const controller = proxyquire('../../../controller/placeholder', { 'pelias-logger': logger })(placeholder_service, true, () => true); const req = { param1: 'param1 value', clean: { layers: [ 'neighbourhood', 'locality', 'county' ] } }; const res = { }; controller(req, res, () => { const expected_res = { meta: { query_type: 'search_fallback' }, data: [ { _id: '1', _type: 'neighbourhood', layer: 'neighbourhood', source: 'whosonfirst', source_id: '1', center_point: { lat: 14.141414, lon: 41.414141 }, name: { 'default': 'name 1' }, phrase: { 'default': 'name 1' } }, { _id: '3', _type: 'locality', layer: 'locality', source: 'whosonfirst', source_id: '3', center_point: { lat: 16.161616, lon: 61.616161 }, name: { 'default': 'name 3' }, phrase: { 'default': 'name 3' } }, { _id: '5', _type: 'county', layer: 'county', source: 'whosonfirst', source_id: '5', center_point: { lat: 18.181818, lon: 81.818181 }, name: { 'default': 'name 5' }, phrase: { 'default': 'name 5' } } ] }; t.deepEquals(res, expected_res); t.ok(logger.isDebugMessage('[controller:placeholder] [result_count:3]')); t.end(); }); }); test('if req.clean.parsed_text contains street, don\'t filter on anything', (t) => { const logger = mock_logger(); const placeholder_service = (req, callback) => { t.deepEqual(req, { param1: 'param1 value', clean: { layers: ['neighbourhood'], parsed_text: { street: 'street value' } } }); const response = [ { id: 1, name: 'name 1', placetype: 'neighbourhood', lineage: [ {} ], geom: { area: 1, lat: 14.141414, lon: 41.414141 } }, { id: 2, name: 'name 2', placetype: 'borough', lineage: [ {} ], geom: { area: 2, lat: 15.151515, lon: 51.515151 } }, { id: 3, name: 'name 3', placetype: 'locality', lineage: [ {} ], geom: { area: 3, lat: 16.161616, lon: 61.616161 } } ]; callback(null, response); }; const controller = proxyquire('../../../controller/placeholder', { 'pelias-logger': logger })(placeholder_service, true, () => true); const req = { param1: 'param1 value', clean: { layers: ['neighbourhood'], parsed_text: { street: 'street value' } } }; const res = { }; controller(req, res, () => { const expected_res = { meta: { query_type: 'search_fallback' }, data: [ { _id: '1', _type: 'neighbourhood', layer: 'neighbourhood', source: 'whosonfirst', source_id: '1', center_point: { lat: 14.141414, lon: 41.414141 }, name: { 'default': 'name 1' }, phrase: { 'default': 'name 1' } }, { _id: '2', _type: 'borough', layer: 'borough', source: 'whosonfirst', source_id: '2', center_point: { lat: 15.151515, lon: 51.515151 }, name: { 'default': 'name 2' }, phrase: { 'default': 'name 2' } }, { _id: '3', _type: 'locality', layer: 'locality', source: 'whosonfirst', source_id: '3', center_point: { lat: 16.161616, lon: 61.616161 }, name: { 'default': 'name 3' }, phrase: { 'default': 'name 3' } } ] }; t.deepEquals(res, expected_res); t.ok(logger.isDebugMessage('[controller:placeholder] [result_count:3]')); t.end(); }); }); test('only synthesized docs matching explicit boundary.country should be returned', (t) => { const logger = require('pelias-mock-logger')(); const placeholder_service = (req, callback) => { t.deepEqual(req, { param1: 'param1 value', clean: { 'boundary.country': 'ABC' } }); const response = [ { id: 1, name: 'name 1', placetype: 'locality', lineage: [ { country: { id: 1, name: 'country name 1', abbr: 'ABC' } }, { country: { id: 2, name: 'country name 2', abbr: 'DEF' } } ], geom: { lat: 14.141414, lon: 41.414141 } }, { id: 3, name: 'name 3', placetype: 'locality', lineage: [ {} ], geom: { lat: 15.151515, lon: 51.515151 } }, { id: 4, name: 'name 4', placetype: 'locality', lineage: [ { country: { id: 4, name: 'country name 4', abbr: 'ABC' } } ], geom: { lat: 16.161616, lon: 61.616161 } } ]; callback(null, response); }; const controller = proxyquire('../../../controller/placeholder', { 'pelias-logger': logger })(placeholder_service, true, () => true); const req = { param1: 'param1 value', clean: { 'boundary.country': 'ABC' } }; const res = { }; controller(req, res, () => { const expected_res = { meta: { query_type: 'search_fallback' }, data: [ { _id: '1', _type: 'locality', layer: 'locality', source: 'whosonfirst', source_id: '1', center_point: { lat: 14.141414, lon: 41.414141 }, name: { 'default': 'name 1' }, phrase: { 'default': 'name 1' }, parent: { country: ['country name 1'], country_id: ['1'], country_a: ['ABC'] } }, { _id: '4', _type: 'locality', layer: 'locality', source: 'whosonfirst', source_id: '4', center_point: { lat: 16.161616, lon: 61.616161 }, name: { 'default': 'name 4' }, phrase: { 'default': 'name 4' }, parent: { country: ['country name 4'], country_id: ['4'], country_a: ['ABC'] } } ] }; t.deepEquals(res, expected_res); t.ok(logger.isDebugMessage('[controller:placeholder] [result_count:2]')); t.end(); }); }); test('when geometric_filters_apply is false, boundary.country should not apply', (t) => { const logger = require('pelias-mock-logger')(); const placeholder_service = (req, callback) => { t.deepEqual(req, { param1: 'param1 value', clean: { 'boundary.country': 'ABC' } }); const response = [ { id: 1, name: 'name 1', placetype: 'locality', lineage: [ { country: { id: 1, name: 'country name 1', abbr: 'ABC' } }, { country: { id: 2, name: 'country name 2', abbr: 'DEF' } } ], geom: { lat: 14.141414, lon: 41.414141 } }, { id: 3, name: 'name 3', placetype: 'locality', lineage: [ { country: { id: 3, name: 'country name 3', abbr: 'ABC' } } ], geom: { lat: 15.151515, lon: 51.515151 } }, { id: 4, name: 'name 4', placetype: 'locality', lineage: [ { country: { id: 4, name: 'country name 4', abbr: 'GHI' } } ], geom: { lat: 16.161616, lon: 61.616161 } } ]; callback(null, response); }; const controller = proxyquire('../../../controller/placeholder', { 'pelias-logger': logger })(placeholder_service, false, () => true); const req = { param1: 'param1 value', clean: { 'boundary.country': 'ABC' } }; const res = { }; controller(req, res, () => { const expected_res = { meta: { query_type: 'search_fallback' }, data: [ { _id: '1', _type: 'locality', layer: 'locality', source: 'whosonfirst', source_id: '1', center_point: { lat: 14.141414, lon: 41.414141 }, name: { 'default': 'name 1' }, phrase: { 'default': 'name 1' }, parent: { country: ['country name 1', 'country name 2'], country_id: ['1', '2'], country_a: ['ABC', 'DEF'] } }, { _id: '3', _type: 'locality', layer: 'locality', source: 'whosonfirst', source_id: '3', center_point: { lat: 15.151515, lon: 51.515151 }, name: { 'default': 'name 3' }, phrase: { 'default': 'name 3' }, parent: { country: ['country name 3'], country_id: ['3'], country_a: ['ABC'] } }, { _id: '4', _type: 'locality', layer: 'locality', source: 'whosonfirst', source_id: '4', center_point: { lat: 16.161616, lon: 61.616161 }, name: { 'default': 'name 4' }, phrase: { 'default': 'name 4' }, parent: { country: ['country name 4'], country_id: ['4'], country_a: ['GHI'] } } ] }; t.deepEquals(res, expected_res); t.ok(logger.isDebugMessage('[controller:placeholder] [result_count:3]')); t.end(); }); }); }; module.exports.tests.lineage_errors = (test, common) => { test('unsupported lineage placetypes should be ignored', (t) => { const logger = mock_logger(); const placeholder_service = (req, callback) => { t.deepEqual(req, { param1: 'param1 value' }); const response = [ { id: 123, name: 'name 1', placetype: 'neighbourhood', lineage: [ { country: { id: 1, name: 'country name 1', abbr: 'country abbr 1' }, unknown: { id: 2, name: 'unknown name 2', abbr: 'unknown abbr 2' } } ], geom: { area: 12.34 } } ]; callback(null, response); }; const controller = proxyquire('../../../controller/placeholder', { 'pelias-logger': logger })(placeholder_service, true, () => true); const req = { param1: 'param1 value' }; const res = { }; controller(req, res, () => { const expected_res = { meta: { query_type: 'search_fallback' }, data: [ { _id: '123', _type: 'neighbourhood', layer: 'neighbourhood', source: 'whosonfirst', source_id: '123', name: { 'default': 'name 1' }, phrase: { 'default': 'name 1' }, parent: { country: ['country name 1'], country_id: ['1'], country_a: ['country abbr 1'] } } ] }; t.deepEquals(res, expected_res); t.end(); }); }); test('lineage placetypes lacking names should be ignored', (t) => { const logger = mock_logger(); const placeholder_service = (req, callback) => { t.deepEqual(req, { param1: 'param1 value' }); const response = [ { id: 123, name: 'name 1', placetype: 'neighbourhood', lineage: [ { country: { id: 1, name: 'country name 1', abbr: 'country abbr 1' }, region: { id: 2, abbr: 'region abbr 2' } } ], geom: { area: 12.34 } } ]; callback(null, response); }; const controller = proxyquire('../../../controller/placeholder', { 'pelias-logger': logger })(placeholder_service, true, () => true); const req = { param1: 'param1 value' }; const res = { }; controller(req, res, () => { const expected_res = { meta: { query_type: 'search_fallback' }, data: [ { _id: '123', _type: 'neighbourhood', layer: 'neighbourhood', source: 'whosonfirst', source_id: '123', name: { 'default': 'name 1' }, phrase: { 'default': 'name 1' }, parent: { country: ['country name 1'], country_id: ['1'], country_a: ['country abbr 1'] } } ] }; t.deepEquals(res, expected_res); t.end(); }); }); test('lineage placetypes lacking ids should be ignored', (t) => { const logger = mock_logger(); const placeholder_service = (req, callback) => { t.deepEqual(req, { param1: 'param1 value' }); const response = [ { id: 123, name: 'name 1', placetype: 'neighbourhood', lineage: [ { country: { id: 1, name: 'country name 1', abbr: 'country abbr 1' }, region: { name: 'region name 2', abbr: 'region abbr 2' } } ], geom: { area: 12.34 } } ]; callback(null, response); }; const controller = proxyquire('../../../controller/placeholder', { 'pelias-logger': logger })(placeholder_service, true, () => true); const req = { param1: 'param1 value' }; const res = { }; controller(req, res, () => { const expected_res = { meta: { query_type: 'search_fallback' }, data: [ { _id: '123', _type: 'neighbourhood', layer: 'neighbourhood', source: 'whosonfirst', source_id: '123', name: { 'default': 'name 1' }, phrase: { 'default': 'name 1' }, parent: { country: ['country name 1'], country_id: ['1'], country_a: ['country abbr 1'] } } ] }; t.deepEquals(res, expected_res); t.end(); }); }); }; module.exports.tests.geometry_errors = (test, common) => { test('result without geometry should not cause problems', (t) => { const logger = mock_logger(); const placeholder_service = (req, callback) => { t.deepEqual(req, { param1: 'param1 value' }); const response = [ { id: 123, name: 'name 1', placetype: 'neighbourhood' } ]; callback(null, response); }; const controller = proxyquire('../../../controller/placeholder', { 'pelias-logger': logger })(placeholder_service, true, () => true); const req = { param1: 'param1 value' }; const res = { }; controller(req, res, () => { const expected_res = { meta: { query_type: 'search_fallback' }, data: [ { _id: '123', _type: 'neighbourhood', layer: 'neighbourhood', source: 'whosonfirst', source_id: '123', name: { 'default': 'name 1' }, phrase: { 'default': 'name 1' } } ] }; t.deepEquals(res, expected_res); t.end(); }); }); }; module.exports.tests.centroid_errors = (test, common) => { test('result without geom.lat/geom.lon should leave centroid undefined', (t) => { const logger = require('pelias-mock-logger')(); const placeholder_service = (req, callback) => { t.deepEqual(req, { param1: 'param1 value' }); const response = [ { id: 123, name: 'name 1', placetype: 'neighbourhood', geom: { area: 12.34, bbox: '21.212121,12.121212,31.313131,13.131313' } } ]; callback(null, response); }; const controller = proxyquire('../../../controller/placeholder', { 'pelias-logger': logger })(placeholder_service, true, () => true); const req = { param1: 'param1 value' }; const res = { }; controller(req, res, () => { const expected_res = { meta: { query_type: 'search_fallback' }, data: [ { _id: '123', _type: 'neighbourhood', layer: 'neighbourhood', source: 'whosonfirst', source_id: '123', bounding_box: '{"min_lat":12.121212,"max_lat":13.131313,"min_lon":21.212121,"max_lon":31.313131}', name: { 'default': 'name 1' }, phrase: { 'default': 'name 1' } } ] }; t.deepEquals(res, expected_res); t.ok(logger.isErrorMessage(`could not parse centroid for id 123`)); t.end(); }); }); test('result with non-number-parseable geom.lat/geom.lon should leave centroid undefined and log error', (t) => { const logger = mock_logger(); const placeholder_service = (req, callback) => { t.deepEqual(req, { param1: 'param1 value' }); const response = [ { id: 123, name: 'name 1', placetype: 'neighbourhood', geom: { area: 12.34, bbox: '21.212121,12.121212,31.313131,13.131313', lat: 'this is not a number', lon: 'this is not a number' } } ]; callback(null, response); }; const controller = proxyquire('../../../controller/placeholder', { 'pelias-logger': logger })(placeholder_service, true, () => true); const req = { param1: 'param1 value' }; const res = { }; controller(req, res, () => { const expected_res = { meta: { query_type: 'search_fallback' }, data: [ { _id: '123', _type: 'neighbourhood', layer: 'neighbourhood', source: 'whosonfirst', source_id: '123', bounding_box: '{"min_lat":12.121212,"max_lat":13.131313,"min_lon":21.212121,"max_lon":31.313131}', name: { 'default': 'name 1' }, phrase: { 'default': 'name 1' } } ] }; t.deepEquals(res, expected_res); t.ok(logger.isErrorMessage(`could not parse centroid for id 123`)); t.end(); }); }); }; module.exports.tests.boundingbox_errors = (test, common) => { test('result with invalid geom.bbox should leave bounding_box undefined and log error', (t) => { [ undefined, '21.212121,12.121212,31.313131,13.131313,41.414141', '21.212121,12.121212,31.313131', '21.212121,this is not parseable as a number,31.313131,13.131313', '21.212121,NaN,31.313131,13.131313', '21.212121,Infinity,31.313131,13.131313' ].forEach((bbox) => { const logger = mock_logger(); const placeholder_service = (req, callback) => { const response = [ { id: 123, name: 'name 1', placetype: 'neighbourhood', geom: { area: 12.34, bbox: bbox, lat: 14.141414, lon: 41.414141 } } ]; t.deepEqual(req, { param1: 'param1 value' }); callback(null, response); }; const controller = proxyquire('../../../controller/placeholder', { 'pelias-logger': logger })(placeholder_service, true, () => true); const req = { param1: 'param1 value' }; const res = { }; controller(req, res, () => { const expected_res = { meta: { query_type: 'search_fallback' }, data: [ { _id: '123', _type: 'neighbourhood', layer: 'neighbourhood', source: 'whosonfirst', source_id: '123', center_point: { lat: 14.141414, lon: 41.414141 }, name: { 'default': 'name 1' }, phrase: { 'default': 'name 1' } } ] }; t.deepEquals(res, expected_res); t.ok(logger.isErrorMessage(`could not parse bbox for id 123: ${bbox}`)); }); }); t.end(); }); }; module.exports.tests.error_conditions = (test, common) => { test('service return error string should add to req.errors', (t) => { const logger = mock_logger(); const placeholder_service = (req, callback) => { callback('placeholder service error', []); }; const controller = proxyquire('../../../controller/placeholder', { 'pelias-logger': logger })(placeholder_service, true, () => true); const req = { errors: [] }; const res = {}; controller(req, res, () => { t.deepEquals(res, {}, 'res should not have been modified'); t.deepEquals(req.errors, ['placeholder service error']); t.notOk(logger.isDebugMessage(/\\[controller:placeholder\\] \\[result_count:\\d+\\]/)); t.end(); }); }); test('service return error object should add message to req.errors', (t) => { const logger = mock_logger(); const placeholder_service = (req, callback) => { callback(Error('placeholder service error'), []); }; const should_execute = (req, res) => { return true; }; const controller = proxyquire('../../../controller/placeholder', { 'pelias-logger': logger })(placeholder_service, true, () => true); const req = { errors: [] }; const res = {}; controller(req, res, () => { t.deepEquals(res, {}, 'res should not have been modified'); t.deepEquals(req.errors, ['placeholder service error']); t.notOk(logger.isDebugMessage(/\\[controller:placeholder\\] \\[result_count:\\d+\\]/)); t.end(); }); }); test('service return error object should add stringified error to req.errors', (t) => { const logger = mock_logger(); const placeholder_service = (req, callback) => { callback({ error_key: 'error_value' }, []); }; const controller = proxyquire('../../../controller/placeholder', { 'pelias-logger': logger })(placeholder_service, true, () => true); const req = { errors: [] }; const res = {}; controller(req, res, () => { t.deepEquals(res, {}, 'res should not have been modified'); t.deepEquals(req.errors, [{ error_key: 'error_value' }]); t.notOk(logger.isDebugMessage(/\\[controller:placeholder\\] \\[result_count:\\d+\\]/)); t.end(); }); }); }; module.exports.all = (tape, common) => { function test(name, testFunction) { return tape(`GET /placeholder ${name}`, testFunction); } for( const testCase in module.exports.tests ){ module.exports.tests[testCase](test, common); } };