diff --git a/.travis.yml b/.travis.yml index 28a218c8..3ce77f80 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,20 +9,15 @@ matrix: fast_finish: true env: global: - - CXX=g++-4.8 + - BUILD_LEADER_ID=2 script: npm run travis -addons: - apt: - sources: - - ubuntu-toolchain-r-test - packages: - - g++-4.8 before_install: - npm i -g npm@^3.0.0 before_script: - npm prune after_success: - - npm run semantic-release + - npm install -g npx + - npx -p node@8 npm run semantic-release branches: except: - /^v\d+\.\d+\.\d+$/ diff --git a/circle.yml b/circle.yml deleted file mode 100644 index 59a2989c..00000000 --- a/circle.yml +++ /dev/null @@ -1,17 +0,0 @@ -machine: - ruby: - version: 2.1.2 - node: - version: 0.12.2 - -deployment: - dev: - branch: master - commands: - - git clone git@github.com:mapzen/pelias-deploy.git && cd pelias-deploy && bundle install - - cd pelias-deploy && bundle exec rake deploy:api dev - prod_build: - branch: staging - commands: - - git clone git@github.com:mapzen/pelias-deploy.git && cd pelias-deploy && bundle install - - cd pelias-deploy && bundle exec rake deploy:api prod_build diff --git a/controller/coarse_reverse.js b/controller/coarse_reverse.js index cae79a78..3f8129a3 100644 --- a/controller/coarse_reverse.js +++ b/controller/coarse_reverse.js @@ -15,7 +15,11 @@ const coarse_granularities = [ 'region', 'macroregion', 'dependency', - 'country' + 'country', + 'empire', + 'continent', + 'ocean', + 'marinearea' ]; // remove non-coarse layers and return what's left (or all if empty) @@ -68,7 +72,7 @@ function synthesizeDoc(results) { // assign the administrative hierarchy _.keys(results).forEach((layer) => { - doc.addParent(layer, results[layer][0].name, results[layer][0].id.toString(), results[layer][0].abbr); + doc.addParent(layer, results[layer][0].name, results[layer][0].id.toString(), results[layer][0].abbr || undefined); }); // set centroid if available diff --git a/helper/geojsonify.js b/helper/geojsonify.js index 01acb886..515446ce 100644 --- a/helper/geojsonify.js +++ b/helper/geojsonify.js @@ -1,28 +1,32 @@ -var GeoJSON = require('geojson'); -var extent = require('@mapbox/geojson-extent'); -var logger = require('pelias-logger').get('api'); -var type_mapping = require('./type_mapping'); -var _ = require('lodash'); -var addDetails = require('./geojsonify_place_details'); -var addMetaData = require('./geojsonify_meta_data'); +const GeoJSON = require('geojson'); +const extent = require('@mapbox/geojson-extent'); +const logger = require('pelias-logger').get('geojsonify'); +const collectDetails = require('./geojsonify_place_details'); +const _ = require('lodash'); +const Document = require('pelias-model').Document; function geojsonifyPlaces( params, docs ){ // flatten & expand data for geojson conversion - var geodata = docs - .map(geojsonifyPlace.bind(null, params)) - .filter( function( doc ){ - return !!doc; - }); + const geodata = docs + .filter(doc => { + if (!_.has(doc, 'center_point')) { + logger.warn('No doc or center_point property'); + return false; + } else { + return true; + } + }) + .map(geojsonifyPlace.bind(null, params)); // get all the bounding_box corners as well as single points // to be used for computing the overall bounding_box for the FeatureCollection - var extentPoints = extractExtentPoints(geodata); + const extentPoints = extractExtentPoints(geodata); // convert to geojson - var geojson = GeoJSON.parse( geodata, { Point: ['lat', 'lng'] }); - var geojsonExtentPoints = GeoJSON.parse( extentPoints, { Point: ['lat', 'lng'] }); + const geojson = GeoJSON.parse( geodata, { Point: ['lat', 'lng'] }); + const geojsonExtentPoints = GeoJSON.parse( extentPoints, { Point: ['lat', 'lng'] }); // to insert the bbox property at the top level of each feature, it must be done separately after // initial geojson construction is finished @@ -35,36 +39,29 @@ function geojsonifyPlaces( params, docs ){ } function geojsonifyPlace(params, place) { - - // something went very wrong - if( !place || !place.hasOwnProperty( 'center_point' ) ) { - return warning('No doc or center_point property'); + // setup the base doc + const doc = { + id: place._id, + gid: new Document(place.source, place.layer, place._id).getGid(), + layer: place.layer, + source: place.source, + source_id: place.source_id, + bounding_box: place.bounding_box, + lat: parseFloat(place.center_point.lat), + lng: parseFloat(place.center_point.lon) + }; + + // assign name, logging a warning if it doesn't exist + if (_.has(place, 'name.default')) { + doc.name = place.name.default; + } else { + logger.warn(`doc ${doc.gid} does not contain name.default`); } - var output = {}; - - addMetaData(place, output); - addName(place, output); - addDetails(params, place, output); - - // map center_point for GeoJSON to work properly - // these should not show up in the final feature properties - output.lat = parseFloat(place.center_point.lat); - output.lng = parseFloat(place.center_point.lon); + // assign all the details info into the doc + Object.assign(doc, collectDetails(params, place)); - return output; -} - -/** - * Validate and add name property - * - * @param {object} src - * @param {object} dst - */ -function addName(src, dst) { - // map name - if( !src.name || !src.name.default ) { return warning(src); } - dst.name = src.name.default; + return doc; } /** @@ -73,12 +70,7 @@ function addName(src, dst) { * @param {object} geojson */ function addBBoxPerFeature(geojson) { - geojson.features.forEach(function (feature) { - - if (!feature.properties.hasOwnProperty('bounding_box')) { - return; - } - + geojson.features.forEach(feature => { if (feature.properties.bounding_box) { feature.bbox = [ feature.properties.bounding_box.min_lon, @@ -101,8 +93,8 @@ function addBBoxPerFeature(geojson) { * @returns {Array} */ function extractExtentPoints(geodata) { - var extentPoints = []; - geodata.forEach(function (place) { + return geodata.reduce((extentPoints, place) => { + // if there's a bounding_box, use the LL/UR for the extent if (place.bounding_box) { extentPoints.push({ lng: place.bounding_box.min_lon, @@ -112,16 +104,20 @@ function extractExtentPoints(geodata) { lng: place.bounding_box.max_lon, lat: place.bounding_box.max_lat }); + } else { + // otherwise, use the point for the extent extentPoints.push({ lng: place.lng, lat: place.lat }); + } - }); + return extentPoints; + + }, []); - return extentPoints; } /** @@ -144,15 +140,4 @@ function computeBBox(geojson, geojsonExtentPoints) { } } -/** - * emit a warning if the doc format is invalid - * - * @note: if you see this error, fix it ASAP! - */ -function warning( doc ) { - console.error( 'error: invalid doc', __filename, doc); - return false; // remove offending doc from results -} - - module.exports = geojsonifyPlaces; diff --git a/helper/geojsonify_meta_data.js b/helper/geojsonify_meta_data.js deleted file mode 100644 index 7fdf0394..00000000 --- a/helper/geojsonify_meta_data.js +++ /dev/null @@ -1,42 +0,0 @@ -var Document = require('pelias-model').Document; - -/** - * Determine and set place id, type, and source - * - * @param {object} src - * @param {object} dst - */ -function addMetaData(src, dst) { - dst.id = src._id; - dst.gid = makeGid(src); - dst.layer = lookupLayer(src); - dst.source = lookupSource(src); - dst.source_id = lookupSourceId(src); - if (src.hasOwnProperty('bounding_box')) { - dst.bounding_box = src.bounding_box; - } -} - -/** - * Create a gid from a document - * - * @param {object} src - */ -function makeGid(src) { - var doc = new Document(lookupSource(src), lookupLayer(src), src._id); - return doc.getGid(); -} - -function lookupSource(src) { - return src.source; -} - -function lookupSourceId(src) { - return src.source_id; -} - -function lookupLayer(src) { - return src.layer; -} - -module.exports = addMetaData; \ No newline at end of file diff --git a/helper/geojsonify_place_details.js b/helper/geojsonify_place_details.js index d9a000ee..b546c9a9 100644 --- a/helper/geojsonify_place_details.js +++ b/helper/geojsonify_place_details.js @@ -1,9 +1,11 @@ -var _ = require('lodash'); +'use strict'; + +const _ = require('lodash'); // Properties to be copied // If a property is identified as a single string, assume it should be presented as a string in response // If something other than string is desired, use the following structure: { name: 'category', type: 'array' } -var DETAILS_PROPS = [ +const DETAILS_PROPS = [ { name: 'housenumber', type: 'string' }, { name: 'street', type: 'string' }, { name: 'postalcode', type: 'string' }, @@ -41,6 +43,15 @@ var DETAILS_PROPS = [ { name: 'borough_a', type: 'string' }, { name: 'neighbourhood', type: 'string' }, { name: 'neighbourhood_gid', type: 'string' }, + { name: 'continent', type: 'string' }, + { name: 'continent_gid', type: 'string' }, + { name: 'continent_a', type: 'string' }, + { name: 'ocean', type: 'string' }, + { name: 'ocean_gid', type: 'string' }, + { name: 'ocean_a', type: 'string' }, + { name: 'marinearea', type: 'string' }, + { name: 'marinearea_gid', type: 'string' }, + { name: 'marinearea_a', type: 'string' }, { name: 'bounding_box', type: 'default' }, { name: 'label', type: 'string' }, { name: 'category', type: 'array', condition: checkCategoryParam } @@ -51,58 +62,44 @@ function checkCategoryParam(params) { } /** - * Add details properties - * - * @param {object} params clean query params - * @param {object} src - * @param {object} dst - */ -function addDetails(params, src, dst) { - copyProperties(params, src, DETAILS_PROPS, dst); -} - -/** - * Copy specified properties from source to dest. + * Collect the specified properties from source into an object and return it * Ignore missing properties. * * @param {object} params clean query params * @param {object} source - * @param {[]} props * @param {object} dst */ -function copyProperties( params, source, props, dst ) { - props.forEach( function ( prop ) { - - // if condition isn't met, just return without setting the property +function collectProperties( params, source ) { + return DETAILS_PROPS.reduce((result, prop) => { + // if condition isn't met, don't set the property if (_.isFunction(prop.condition) && !prop.condition(params)) { - return; + return result; } - var property = { - name: prop.name || prop, - type: prop.type || 'default' - }; + if ( source.hasOwnProperty( prop.name ) ) { + let value = null; - var value = null; - if ( source.hasOwnProperty( property.name ) ) { - - switch (property.type) { + switch (prop.type) { case 'string': - value = getStringValue(source[property.name]); + value = getStringValue(source[prop.name]); break; case 'array': - value = getArrayValue(source[property.name]); + value = getArrayValue(source[prop.name]); break; // default behavior is to copy property exactly as is default: - value = source[property.name]; + value = source[prop.name]; } if (_.isNumber(value) || (value && !_.isEmpty(value))) { - dst[property.name] = value; + result[prop.name] = value; } } - }); + + return result; + + }, {}); + } function getStringValue(property) { @@ -123,7 +120,6 @@ function getStringValue(property) { return _.toString(property); } - function getArrayValue(property) { // isEmpty check works for all types of values: strings, arrays, objects if (_.isEmpty(property)) { @@ -137,4 +133,4 @@ function getArrayValue(property) { return [property]; } -module.exports = addDetails; +module.exports = collectProperties; diff --git a/helper/placeTypes.js b/helper/placeTypes.js index defc9274..3591521c 100644 --- a/helper/placeTypes.js +++ b/helper/placeTypes.js @@ -1,4 +1,8 @@ module.exports = [ + 'ocean', + 'marinearea', + 'continent', + 'empire', 'country', 'dependency', 'macroregion', diff --git a/helper/type_mapping.js b/helper/type_mapping.js index 7b9de5a0..3caea41d 100644 --- a/helper/type_mapping.js +++ b/helper/type_mapping.js @@ -49,9 +49,10 @@ var LAYERS_BY_SOURCE = { openaddresses: [ 'address' ], geonames: [ 'country','macroregion', 'region', 'county','localadmin', 'locality','borough', 'neighbourhood', 'venue' ], - whosonfirst: [ 'continent', 'country', 'dependency', 'macroregion', 'region', + whosonfirst: [ 'continent', 'empire', 'country', 'dependency', 'macroregion', 'region', 'locality', 'localadmin', 'macrocounty', 'county', 'macrohood', 'borough', - 'neighbourhood', 'microhood', 'disputed', 'venue', 'postalcode'] + 'neighbourhood', 'microhood', 'disputed', 'venue', 'postalcode', + 'continent', 'ocean', 'marinearea'] }; /* @@ -60,9 +61,10 @@ var LAYERS_BY_SOURCE = { * may have layers that mean the same thing but have a different name */ var LAYER_ALIASES = { - 'coarse': [ 'continent', 'country', 'dependency', 'macroregion', 'region', - 'locality', 'localadmin', 'macrocounty', 'county', 'macrohood', 'borough', - 'neighbourhood', 'microhood', 'disputed', 'postalcode' ] + '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 diff --git a/middleware/cors.js b/middleware/cors.js index d090f46f..6d30529f 100644 --- a/middleware/cors.js +++ b/middleware/cors.js @@ -3,8 +3,7 @@ function middleware(req, res, next){ res.header('Access-Control-Allow-Origin', '*'); res.header('Access-Control-Allow-Methods', 'GET, OPTIONS'); res.header('Access-Control-Allow-Headers', 'X-Requested-With,content-type'); - res.header('Access-Control-Allow-Credentials', true); next(); } -module.exports = middleware; \ No newline at end of file +module.exports = middleware; diff --git a/package.json b/package.json index c5f731a9..db7b2426 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,8 @@ "validate": "npm ls", "semantic-release": "semantic-release pre && npm publish && semantic-release post", "config": "node -e \"console.log(JSON.stringify(require( 'pelias-config' ).generate(require('./schema')), null, 2))\"", - "check-dependencies": "node_modules/.bin/npm-check --production --ignore pelias-interpolation" + "check-dependencies": "node_modules/.bin/npm-check --production --ignore pelias-interpolation", + "prune": "npm prune" }, "repository": { "type": "git", @@ -55,14 +56,14 @@ "markdown": "0.5.0", "morgan": "^1.8.2", "pelias-categories": "1.2.0", - "pelias-config": "2.12.0", + "pelias-config": "2.12.1", "pelias-labels": "1.6.0", "pelias-logger": "0.2.0", - "pelias-microservice-wrapper": "1.2.0", - "pelias-model": "5.0.1", + "pelias-microservice-wrapper": "1.2.1", + "pelias-model": "5.1.0", "pelias-query": "9.1.0", "pelias-sorting": "1.0.1", - "pelias-text-analyzer": "1.9.1", + "pelias-text-analyzer": "1.9.2", "predicates": "^1.0.1", "retry": "^0.10.1", "stats-lite": "^2.0.4", @@ -75,11 +76,11 @@ "jshint": "^2.5.6", "npm-check": "git://github.com/orangejulius/npm-check.git#disable-update-check", "nsp": "^2.2.0", - "pelias-mock-logger": "1.1.1", + "pelias-mock-logger": "1.2.0", "precommit-hook": "^3.0.0", "proxyquire": "^1.7.10", - "semantic-release": "^7.0.1", - "source-map": "^0.5.6", + "semantic-release": "^8.0.0", + "source-map": "^0.6.0", "tap-dot": "1.0.5", "tape": "^4.5.1", "tmp": "0.0.33", @@ -87,6 +88,7 @@ }, "pre-commit": [ "lint", + "prune", "validate", "test", "check-dependencies" diff --git a/service/configurations/PointInPolygon.js b/service/configurations/PointInPolygon.js index 66a766f9..58b52093 100644 --- a/service/configurations/PointInPolygon.js +++ b/service/configurations/PointInPolygon.js @@ -11,6 +11,16 @@ class PointInPolygon extends ServiceConfiguration { super('pip', o); } + getParameters(req) { + if (_.has(req, 'clean.layers')) { + return { + layers: _.join(req.clean.layers, ',') + }; + } + + return {}; + } + getUrl(req) { // use resolve to eliminate possibility of duplicate /'s in URL return url.resolve(this.baseUrl, `${req.clean['point.lon']}/${req.clean['point.lat']}`); diff --git a/test/ciao/CORS/headers_GET.coffee b/test/ciao/CORS/headers_GET.coffee index 53866718..185838ab 100644 --- a/test/ciao/CORS/headers_GET.coffee +++ b/test/ciao/CORS/headers_GET.coffee @@ -6,4 +6,3 @@ path: '/' response.should.have.header 'Access-Control-Allow-Origin','*' response.should.have.header 'Access-Control-Allow-Methods','GET, OPTIONS' response.should.have.header 'Access-Control-Allow-Headers','X-Requested-With,content-type' -response.should.have.header 'Access-Control-Allow-Credentials','true' \ No newline at end of file diff --git a/test/ciao/CORS/headers_OPTIONS.coffee b/test/ciao/CORS/headers_OPTIONS.coffee index 5575391a..3cb47b37 100644 --- a/test/ciao/CORS/headers_OPTIONS.coffee +++ b/test/ciao/CORS/headers_OPTIONS.coffee @@ -7,4 +7,3 @@ method: 'OPTIONS' response.should.have.header 'Access-Control-Allow-Origin','*' response.should.have.header 'Access-Control-Allow-Methods','GET, OPTIONS' response.should.have.header 'Access-Control-Allow-Headers','X-Requested-With,content-type' -response.should.have.header 'Access-Control-Allow-Credentials','true' \ No newline at end of file diff --git a/test/unit/controller/coarse_reverse.js b/test/unit/controller/coarse_reverse.js index d4e357cb..df231075 100644 --- a/test/unit/controller/coarse_reverse.js +++ b/test/unit/controller/coarse_reverse.js @@ -210,6 +210,22 @@ module.exports.tests.success_conditions = (test, common) => { country: [ { id: 100, name: 'country name', abbr: 'xyz'}, { id: 101, name: 'country name 2'} + ], + empire: [ + { id: 110, name: 'empire name', abbr: 'empire abbr'}, + { id: 111, name: 'empire name 2'} + ], + continent: [ + { id: 120, name: 'continent name', abbr: 'continent abbr'}, + { id: 121, name: 'continent name 2'} + ], + ocean: [ + { id: 130, name: 'ocean name', abbr: 'ocean abbr'}, + { id: 131, name: 'ocean name 2'} + ], + marinearea: [ + { id: 140, name: 'marinearea name', abbr: 'marinearea abbr'}, + { id: 141, name: 'marinearea name 2'} ] }; @@ -282,7 +298,19 @@ module.exports.tests.success_conditions = (test, common) => { dependency_a: ['dependency abbr'], country: ['country name'], country_id: ['100'], - country_a: ['xyz'] + country_a: ['xyz'], + empire: ['empire name'], + empire_id: ['110'], + empire_a: ['empire abbr'], + continent: ['continent name'], + continent_id: ['120'], + continent_a: ['continent abbr'], + ocean: ['ocean name'], + ocean_id: ['130'], + ocean_a: ['ocean abbr'], + marinearea: ['marinearea name'], + marinearea_id: ['140'], + marinearea_a: ['marinearea abbr'], }, center_point: { lat: 12.121212, @@ -822,6 +850,22 @@ module.exports.tests.failure_conditions = (test, common) => { country: [ { id: 100, name: 'country name', abbr: 'xyz'}, { id: 101, name: 'country name 2'} + ], + empire: [ + { id: 110, name: 'empire name', abbr: 'empire abbr'}, + { id: 111, name: 'empire name 2'} + ], + continent: [ + { id: 120, name: 'continent name', abbr: 'continent abbr'}, + { id: 121, name: 'continent name 2'} + ], + ocean: [ + { id: 130, name: 'ocean name', abbr: 'ocean abbr'}, + { id: 131, name: 'ocean name 2'} + ], + marinearea: [ + { id: 140, name: 'marinearea name', abbr: 'marinearea abbr'}, + { id: 141, name: 'marinearea name 2'} ] }; @@ -911,6 +955,66 @@ module.exports.tests.failure_conditions = (test, common) => { t.end(); }); + + test('service returns 0 length abbr', (t) => { + t.plan(4); + + const service = (req, callback) => { + t.deepEquals(req, { clean: { layers: ['neighbourhood'] } } ); + + const results = { + neighbourhood: [ + { id: 20, name: 'Example', abbr: '' } + ] + }; + + callback(undefined, results); + }; + + const logger = require('pelias-mock-logger')(); + + const controller = proxyquire('../../../controller/coarse_reverse', { + 'pelias-logger': logger + })(service, _.constant(true)); + + const req = { + clean: { + layers: ['neighbourhood'] + } + }; + + const res = { }; + + // verify that next was called + const next = () => { + t.pass('next() was called'); + }; + + controller(req, res, next); + + const expected = { + meta: {}, + data: [{ + name: { default: 'Example' }, + phrase: { default: 'Example' }, + parent: { + neighbourhood: [ 'Example' ], + neighbourhood_id: [ '20' ], + neighbourhood_a: [ null ] + }, + source: 'whosonfirst', + layer: 'neighbourhood', + source_id: '20', + _id: '20', + _type: 'neighbourhood' + }] + }; + + t.deepEquals(res, expected); + t.notOk(logger.hasErrorMessages()); + t.end(); + + }); }; module.exports.all = (tape, common) => { diff --git a/test/unit/controller/predicates/is_coarse_reverse.js b/test/unit/controller/predicates/is_coarse_reverse.js index 2a38e401..33b0611d 100644 --- a/test/unit/controller/predicates/is_coarse_reverse.js +++ b/test/unit/controller/predicates/is_coarse_reverse.js @@ -17,7 +17,9 @@ const coarse_layers = [ 'borough', 'neighbourhood', 'microhood', - 'disputed' + 'disputed', + 'ocean', + 'marinearea' ]; module.exports.tests = {}; diff --git a/test/unit/controller/search_with_ids.js b/test/unit/controller/search_with_ids.js index e4c1d21d..ff884be9 100644 --- a/test/unit/controller/search_with_ids.js +++ b/test/unit/controller/search_with_ids.js @@ -388,7 +388,7 @@ module.exports.tests.service_errors = (test, common) => { const next = () => { t.deepEqual(logger.getInfoMessages(), [ - '[req]', + '[req] endpoint=undefined {}', 'request timed out on attempt 1, retrying', 'request timed out on attempt 2, retrying', 'request timed out on attempt 3, retrying' diff --git a/test/unit/helper/geojsonify.js b/test/unit/helper/geojsonify.js index 809b1ae9..72ff6392 100644 --- a/test/unit/helper/geojsonify.js +++ b/test/unit/helper/geojsonify.js @@ -1,5 +1,6 @@ +const geojsonify = require('../../../helper/geojsonify'); -var geojsonify = require('../../../helper/geojsonify'); +const proxyquire = require('proxyquire').noCallThru(); module.exports.tests = {}; @@ -36,433 +37,610 @@ module.exports.tests.earth = function(test, common) { }; -module.exports.tests.geojsonify = function(test, common) { +module.exports.tests.all = (test, common) => { + test('bounding_box should be calculated using points when avaiable', t => { + const input = [ + { + _id: 'id 1', + source: 'source 1', + source_id: 'source_id 1', + layer: 'layer 1', + name: { + default: 'name 1', + }, + center_point: { + lat: 12.121212, + lon: 21.212121 + } + }, + { + _id: 'id 2', + source: 'source 2', + source_id: 'source_id 2', + layer: 'layer 2', + name: { + default: 'name 2', + }, + center_point: { + lat: 13.131313, + lon: 31.313131 + } + } + ]; + + const geojsonify = proxyquire('../../../helper/geojsonify', { + './geojsonify_place_details': (params, source, dst) => { + if (source._id === 'id 1') { + return { + property1: 'property 1', + property2: 'property 2' + }; + } else if (source._id === 'id 2') { + return { + property3: 'property 3', + property4: 'property 4' + }; + } + + } + }); + + const actual = geojsonify({}, input); + + const expected = { + type: 'FeatureCollection', + features: [ + { + type: 'Feature', + geometry: { + type: 'Point', + coordinates: [ 21.212121, 12.121212 ] + }, + properties: { + id: 'id 1', + gid: 'source 1:layer 1:id 1', + layer: 'layer 1', + source: 'source 1', + source_id: 'source_id 1', + name: 'name 1', + property1: 'property 1', + property2: 'property 2' + } + }, + { + type: 'Feature', + geometry: { + type: 'Point', + coordinates: [ 31.313131, 13.131313 ] + }, + properties: { + id: 'id 2', + gid: 'source 2:layer 2:id 2', + layer: 'layer 2', + source: 'source 2', + source_id: 'source_id 2', + name: 'name 2', + property3: 'property 3', + property4: 'property 4' + } + } + ], + bbox: [21.212121, 12.121212, 31.313131, 13.131313] + }; + + t.deepEquals(actual, expected); + t.end(); - test('geojsonify(doc)', function(t) { - var input = [ + }); + + test('bounding_box should be calculated using polygons when avaiable', t => { + const input = [ { - '_id': 'id1', - '_type': 'layer1', - 'source': 'source1', - 'source_id': 'source_id_1', - 'layer': 'layer1', - 'center_point': { - 'lat': 51.5337144, - 'lon': -0.1069716 + _id: 'id 1', + source: 'source 1', + source_id: 'source_id 1', + layer: 'layer 1', + name: { + default: 'name 1', }, - 'name': { - 'default': '\'Round Midnight Jazz and Blues Bar' + bounding_box: { + min_lon: 1, + min_lat: 1, + max_lon: 2, + max_lat: 2 }, - 'housenumber': '13', - 'street': 'Liverpool Road', - 'postalcode': 'N1 0RW', - 'country_a': 'GBR', - 'country': 'United Kingdom', - 'dependency': 'dependency name', - 'region': 'Islington', - 'region_a': 'ISL', - 'macroregion': 'England', - 'county': 'Angel', - 'localadmin': 'test1', - 'locality': 'test2', - 'neighbourhood': 'test3', - 'category': [ - 'food', - 'nightlife' - ], - 'label': 'label for id id1' + center_point: { + lat: 12.121212, + lon: 21.212121 + } }, { - '_id': 'id2', - '_type': 'layer2', - 'source': 'source2', - 'source_id': 'source_id_2', - 'layer': 'layer2', - 'name': { - 'default': 'Blues Cafe' + _id: 'id 2', + source: 'source 2', + source_id: 'source_id 2', + layer: 'layer 2', + name: { + default: 'name 2', }, - 'center_point': { - 'lat': '51.517806', - 'lon': '-0.101795' + bounding_box: { + min_lon: -3, + min_lat: -3, + max_lon: -1, + max_lat: -1 }, - 'country_a': 'GBR', - 'country': 'United Kingdom', - 'dependency': 'dependency name', - 'region': 'City And County Of The City Of London', - 'region_a': 'COL', - 'macroregion': 'England', - 'county': 'Smithfield', - 'localadmin': 'test1', - 'locality': 'test2', - 'neighbourhood': 'test3', - 'label': 'label for id id2' + center_point: { + lat: 13.131313, + lon: 31.313131 + } + } + ]; + + const geojsonify = proxyquire('../../../helper/geojsonify', { + './geojsonify_place_details': (params, source, dst) => { + if (source._id === 'id 1') { + return { + property1: 'property 1', + property2: 'property 2' + }; + } else if (source._id === 'id 2') { + return { + property3: 'property 3', + property4: 'property 4' + }; + } + + } + }); + + const actual = geojsonify({}, input); + + const expected = { + type: 'FeatureCollection', + features: [ + { + type: 'Feature', + geometry: { + type: 'Point', + coordinates: [ 21.212121, 12.121212 ] + }, + properties: { + id: 'id 1', + gid: 'source 1:layer 1:id 1', + layer: 'layer 1', + source: 'source 1', + source_id: 'source_id 1', + name: 'name 1', + property1: 'property 1', + property2: 'property 2' + }, + bbox: [ 1, 1, 2, 2 ] + }, + { + type: 'Feature', + geometry: { + type: 'Point', + coordinates: [ 31.313131, 13.131313 ] + }, + properties: { + id: 'id 2', + gid: 'source 2:layer 2:id 2', + layer: 'layer 2', + source: 'source 2', + source_id: 'source_id 2', + name: 'name 2', + property3: 'property 3', + property4: 'property 4' + }, + bbox: [ -3, -3, -1, -1 ] + } + ], + bbox: [ -3, -3, 2, 2 ] + }; + + t.deepEquals(actual, expected); + t.end(); + + }); + + test('bounding_box should be calculated using polygons AND points when avaiable', t => { + const input = [ + { + _id: 'id 1', + source: 'source 1', + source_id: 'source_id 1', + layer: 'layer 1', + name: { + default: 'name 1', + }, + center_point: { + lat: 12.121212, + lon: 21.212121 + } }, { - '_id': 'node:34633854', - '_type': 'venue', - 'source': 'openstreetmap', - 'source_id': 'source_id_3', - 'layer': 'venue', - 'name': { - 'default': 'Empire State Building' + _id: 'id 2', + source: 'source 2', + source_id: 'source_id 2', + layer: 'layer 2', + name: { + default: 'name 2', }, - 'center_point': { - 'lat': '40.748432', - 'lon': '-73.985656' + bounding_box: { + min_lon: -3, + min_lat: -3, + max_lon: -1, + max_lat: -1 }, - 'country_a': 'USA', - 'country': 'United States', - 'dependency': 'dependency name', - 'region': 'New York', - 'region_a': 'NY', - 'county': 'New York', - 'borough': 'Manhattan', - 'locality': 'New York', - 'neighbourhood': 'Koreatown', - 'category': [ - 'tourism', - 'transport' - ], - 'label': 'label for id node:34633854' + center_point: { + lat: 13.131313, + lon: 31.313131 + } } ]; - var expected = { - 'type': 'FeatureCollection', - 'bbox': [ -73.985656, 40.748432, -0.101795, 51.5337144 ], - 'features': [ + const geojsonify = proxyquire('../../../helper/geojsonify', { + './geojsonify_place_details': (params, source, dst) => { + if (source._id === 'id 1') { + return { + property1: 'property 1', + property2: 'property 2' + }; + } else if (source._id === 'id 2') { + return { + property3: 'property 3', + property4: 'property 4' + }; + } + + } + }); + + const actual = geojsonify({}, input); + + const expected = { + type: 'FeatureCollection', + features: [ { - 'type': 'Feature', - 'geometry': { - 'type': 'Point', - 'coordinates': [ - -0.1069716, - 51.5337144 - ] + type: 'Feature', + geometry: { + type: 'Point', + coordinates: [ 21.212121, 12.121212 ] }, - 'properties': { - 'id': 'id1', - 'gid': 'source1:layer1:id1', - 'layer': 'layer1', - 'source': 'source1', - 'source_id': 'source_id_1', - 'name': '\'Round Midnight Jazz and Blues Bar', - 'country_a': 'GBR', - 'country': 'United Kingdom', - 'dependency': 'dependency name', - 'macroregion': 'England', - 'region': 'Islington', - 'region_a': 'ISL', - 'county': 'Angel', - 'localadmin': 'test1', - 'locality': 'test2', - 'neighbourhood': 'test3', - 'housenumber': '13', - 'street': 'Liverpool Road', - 'postalcode': 'N1 0RW', - 'category': [ - 'food', - 'nightlife' - ], - 'label': 'label for id id1' + properties: { + id: 'id 1', + gid: 'source 1:layer 1:id 1', + layer: 'layer 1', + source: 'source 1', + source_id: 'source_id 1', + name: 'name 1', + property1: 'property 1', + property2: 'property 2' } }, { - 'type': 'Feature', - 'geometry': { - 'type': 'Point', - 'coordinates': [ - -0.101795, - 51.517806 - ] + type: 'Feature', + geometry: { + type: 'Point', + coordinates: [ 31.313131, 13.131313 ] }, - 'properties': { - 'id': 'id2', - 'gid': 'source2:layer2:id2', - 'layer': 'layer2', - 'source': 'source2', - 'source_id': 'source_id_2', - 'name': 'Blues Cafe', - 'country_a': 'GBR', - 'country': 'United Kingdom', - 'dependency': 'dependency name', - 'macroregion': 'England', - 'region': 'City And County Of The City Of London', - 'region_a': 'COL', - 'county': 'Smithfield', - 'localadmin': 'test1', - 'locality': 'test2', - 'neighbourhood': 'test3', - 'label': 'label for id id2' - } + properties: { + id: 'id 2', + gid: 'source 2:layer 2:id 2', + layer: 'layer 2', + source: 'source 2', + source_id: 'source_id 2', + name: 'name 2', + property3: 'property 3', + property4: 'property 4' + }, + bbox: [ -3, -3, -1, -1 ] + } + ], + bbox: [ -3, -3, 21.212121, 12.121212 ] + }; + + t.deepEquals(actual, expected); + t.end(); + + }); + +}; + +module.exports.tests.non_optimal_conditions = (test, common) => { + test('null/undefined places should log warnings and be ignored', t => { + const logger = require('pelias-mock-logger')(); + + const input = [ + null, + undefined, + { + _id: 'id 1', + source: 'source 1', + source_id: 'source_id 1', + layer: 'layer 1', + name: { + default: 'name 1', }, + center_point: { + lat: 12.121212, + lon: 21.212121 + } + } + ]; + + const geojsonify = proxyquire('../../../helper/geojsonify', { + './geojsonify_place_details': (params, source, dst) => { + if (source._id === 'id 1') { + return { + property1: 'property 1', + property2: 'property 2' + }; + } + }, + 'pelias-logger': logger + }); + + const actual = geojsonify({}, input); + + const expected = { + type: 'FeatureCollection', + features: [ { - 'type': 'Feature', - 'geometry': { - 'type': 'Point', - 'coordinates': [ - -73.985656, - 40.748432 - ] + type: 'Feature', + geometry: { + type: 'Point', + coordinates: [ 21.212121, 12.121212 ] }, - 'properties': { - 'id': 'node:34633854', - 'gid': 'openstreetmap:venue:node:34633854', - 'layer': 'venue', - 'source': 'openstreetmap', - 'source_id': 'source_id_3', - 'name': 'Empire State Building', - 'country_a': 'USA', - 'country': 'United States', - 'dependency': 'dependency name', - 'region': 'New York', - 'region_a': 'NY', - 'county': 'New York', - 'borough': 'Manhattan', - 'locality': 'New York', - 'neighbourhood': 'Koreatown', - 'category': [ - 'tourism', - 'transport' - ], - 'label': 'label for id node:34633854' + properties: { + id: 'id 1', + gid: 'source 1:layer 1:id 1', + layer: 'layer 1', + source: 'source 1', + source_id: 'source_id 1', + name: 'name 1', + property1: 'property 1', + property2: 'property 2' } } - ] + ], + bbox: [21.212121, 12.121212, 21.212121, 12.121212] }; - var json = geojsonify( {categories: 'foo'}, input ); - - t.deepEqual(json, expected, 'all docs mapped'); + t.deepEquals(actual, expected); + t.ok(logger.isWarnMessage('No doc or center_point property')); t.end(); + }); - test('filtering out empty items', function (t) { - var input = [ + test('places w/o center_point should log warnings and be ignored', t => { + const logger = require('pelias-mock-logger')(); + + const input = [ { - 'bounding_box': { - 'min_lat': 40.6514712164, - 'max_lat': 40.6737320588, - 'min_lon': -73.8967895508, - 'max_lon': -73.8665771484 - }, - 'locality': [ - 'New York' - ], - 'source': 'whosonfirst', - 'layer': 'neighbourhood', - 'population': 173198, - 'popularity': 495, - 'center_point': { - 'lon': -73.881319, - 'lat': 40.663303 - }, - 'name': { - 'default': 'East New York' + _id: 'id 1', + source: 'source 1', + source_id: 'source_id 1', + layer: 'layer 1', + name: { + default: 'name 1', + } + }, + { + _id: 'id 2', + source: 'source 2', + source_id: 'source_id 2', + layer: 'layer 2', + name: { + default: 'name 2', }, - 'source_id': '85816607', - 'category': ['government'], - '_id': '85816607', - '_type': 'neighbourhood', - '_score': 21.434, - 'confidence': 0.888, - 'country': [ - 'United States' - ], - 'country_gid': [ - '85633793' - ], - 'country_a': [ - 'USA' - ], - 'dependency': [ - 'dependency name' - ], - 'dependency_gid': [ - 'dependency id' - ], - 'dependency_a': [ - 'dependency abbrevation' - ], - 'macroregion': [ - 'MacroRegion Name' - ], - 'macroregion_gid': [ - 'MacroRegion Id' - ], - 'macroregion_a': [ - 'MacroRegion Abbreviation' - ], - 'region': [ - 'New York' - ], - 'region_gid': [ - '85688543' - ], - 'region_a': [ - 'NY' - ], - 'macrocounty': [ - 'MacroCounty Name' - ], - 'macrocounty_gid': [ - 'MacroCounty Id' - ], - 'macrocounty_a': [ - 'MacroCounty Abbreviation' - ], - 'county': [ - 'Kings County' - ], - 'county_gid': [ - '102082361' - ], - 'county_a': [ - null - ], - 'borough': [ - 'Brooklyn' - ], - 'localadmin_gid': [ - '404521211' - ], - 'borough_a': [ - null - ], - 'locality_gid': [ - '85977539' - ], - 'locality_a': [ - null - ], - 'neighbourhood': [], - 'neighbourhood_gid': [], - 'label': 'label for id 85816607' + center_point: { + lat: 13.131313, + lon: 31.313131 + } } ]; - var expected = { - 'type': 'FeatureCollection', - 'bbox': [-73.8967895508, 40.6514712164, -73.8665771484, 40.6737320588], - 'features': [ + const geojsonify = proxyquire('../../../helper/geojsonify', { + './geojsonify_place_details': (params, source, dst) => { + if (source._id === 'id 2') { + return { + property3: 'property 3', + property4: 'property 4' + }; + } + }, + 'pelias-logger': logger + }); + + const actual = geojsonify({}, input); + + const expected = { + type: 'FeatureCollection', + features: [ { - 'type': 'Feature', - 'properties': { - 'id': '85816607', - 'gid': 'whosonfirst:neighbourhood:85816607', - 'layer': 'neighbourhood', - 'source': 'whosonfirst', - 'source_id': '85816607', - 'name': 'East New York', - 'category': ['government'], - 'confidence': 0.888, - 'country': 'United States', - 'country_gid': '85633793', - 'country_a': 'USA', - 'dependency': 'dependency name', - 'dependency_gid': 'dependency id', - 'dependency_a': 'dependency abbrevation', - 'macroregion': 'MacroRegion Name', - 'macroregion_gid': 'MacroRegion Id', - 'macroregion_a': 'MacroRegion Abbreviation', - 'region': 'New York', - 'region_gid': '85688543', - 'region_a': 'NY', - 'macrocounty': 'MacroCounty Name', - 'macrocounty_gid': 'MacroCounty Id', - 'macrocounty_a': 'MacroCounty Abbreviation', - 'county': 'Kings County', - 'borough': 'Brooklyn', - 'county_gid': '102082361', - 'localadmin_gid': '404521211', - 'locality': 'New York', - 'locality_gid': '85977539', - 'label': 'label for id 85816607' + type: 'Feature', + geometry: { + type: 'Point', + coordinates: [ 31.313131, 13.131313 ] }, - 'bbox': [-73.8967895508,40.6514712164,-73.8665771484,40.6737320588], - 'geometry': { - 'type': 'Point', - 'coordinates': [ - -73.881319, - 40.663303 - ] + properties: { + id: 'id 2', + gid: 'source 2:layer 2:id 2', + layer: 'layer 2', + source: 'source 2', + source_id: 'source_id 2', + name: 'name 2', + property3: 'property 3', + property4: 'property 4' } } - ] + ], + bbox: [31.313131, 13.131313, 31.313131, 13.131313] }; - var json = geojsonify( {categories: 'foo'}, input ); - - t.deepEqual(json, expected, 'all wanted properties exposed'); + t.deepEquals(actual, expected); + t.ok(logger.isWarnMessage('No doc or center_point property')); t.end(); + }); -}; -module.exports.tests.categories = function (test, common) { - test('only set category if categories filter was used', function (t) { - var input = [ + test('places w/o names should log warnings and be ignored', t => { + const logger = require('pelias-mock-logger')(); + + const input = [ { - '_id': '85816607', - 'bounding_box': { - 'min_lat': 40.6514712164, - 'max_lat': 40.6737320588, - 'min_lon': -73.8967895508, - 'max_lon': -73.8665771484 - }, - 'source': 'whosonfirst', - 'layer': 'neighbourhood', - 'center_point': { - 'lon': -73.881319, - 'lat': 40.663303 - }, - 'name': { - 'default': 'East New York' + _id: 'id 1', + source: 'source 1', + source_id: 'source_id 1', + layer: 'layer 1', + center_point: { + lat: 12.121212, + lon: 21.212121 + } + }, + { + _id: 'id 2', + source: 'source 2', + source_id: 'source_id 2', + layer: 'layer 2', + name: {}, + center_point: { + lat: 13.131313, + lon: 31.313131 + } + }, + { + _id: 'id 3', + source: 'source 3', + source_id: 'source_id 3', + layer: 'layer 3', + name: { + default: 'name 3', }, - 'source_id': '85816607', - 'category': ['government'], - 'label': 'label for id 85816607' + center_point: { + lat: 14.141414, + lon: 41.414141 + } } ]; - var expected = { - 'type': 'FeatureCollection', - 'bbox': [-73.8967895508, 40.6514712164, -73.8665771484, 40.6737320588], - 'features': [ + const geojsonify = proxyquire('../../../helper/geojsonify', { + './geojsonify_place_details': (params, source, dst) => { + if (source._id === 'id 1') { + return { + property1: 'property 1', + property2: 'property 2' + }; + } else if (source._id === 'id 2') { + return { + property3: 'property 3', + property4: 'property 4' + }; + } else if (source._id === 'id 3') { + return { + property5: 'property 5', + property6: 'property 6' + }; + + } + }, + 'pelias-logger': logger + }); + + const actual = geojsonify({}, input); + + const expected = { + type: 'FeatureCollection', + features: [ { - 'type': 'Feature', - 'properties': { - 'id': '85816607', - 'gid': 'whosonfirst:neighbourhood:85816607', - 'layer': 'neighbourhood', - 'source': 'whosonfirst', - 'source_id': '85816607', - 'name': 'East New York', - 'category': ['government'], - 'label': 'label for id 85816607' + type: 'Feature', + geometry: { + type: 'Point', + coordinates: [ 21.212121, 12.121212 ] }, - 'bbox': [-73.8967895508,40.6514712164,-73.8665771484,40.6737320588], - 'geometry': { - 'type': 'Point', - 'coordinates': [ - -73.881319, - 40.663303 - ] + properties: { + id: 'id 1', + gid: 'source 1:layer 1:id 1', + layer: 'layer 1', + source: 'source 1', + source_id: 'source_id 1', + property1: 'property 1', + property2: 'property 2' + } + }, + { + type: 'Feature', + geometry: { + type: 'Point', + coordinates: [ 31.313131, 13.131313 ] + }, + properties: { + id: 'id 2', + gid: 'source 2:layer 2:id 2', + layer: 'layer 2', + source: 'source 2', + source_id: 'source_id 2', + property3: 'property 3', + property4: 'property 4' + } + }, + { + type: 'Feature', + geometry: { + type: 'Point', + coordinates: [ 41.414141, 14.141414 ] + }, + properties: { + id: 'id 3', + gid: 'source 3:layer 3:id 3', + layer: 'layer 3', + source: 'source 3', + source_id: 'source_id 3', + name: 'name 3', + property5: 'property 5', + property6: 'property 6' } } - ] + ], + bbox: [21.212121, 12.121212, 41.414141, 14.141414] }; - var json = geojsonify( {categories: 'foo'}, input ); + t.deepEquals(actual, expected); + t.ok(logger.isWarnMessage('doc source 1:layer 1:id 1 does not contain name.default')); + t.ok(logger.isWarnMessage('doc source 2:layer 2:id 2 does not contain name.default')); + t.end(); - t.deepEqual(json, expected, 'all wanted properties exposed'); + }); + + test('no points', t => { + const logger = require('pelias-mock-logger')(); + + const input = []; + + const geojsonify = proxyquire('../../../helper/geojsonify', { + './geojsonify_place_details': (params, source, dst) => { + t.fail('should not have bee called'); + }, + 'pelias-logger': logger + }); + + const actual = geojsonify({}, input); + + const expected = { + type: 'FeatureCollection', + features: [] + }; + + t.deepEquals(actual, expected); t.end(); + }); -}; -module.exports.all = function (tape, common) { +}; +module.exports.all = (tape, common) => { function test(name, testFunction) { - return tape('geojsonify: ' + name, testFunction); + return tape(`geojsonify: ${name}`, testFunction); } for( var testCase in module.exports.tests ){ diff --git a/test/unit/helper/geojsonify_place_details.js b/test/unit/helper/geojsonify_place_details.js new file mode 100644 index 00000000..c1fe22da --- /dev/null +++ b/test/unit/helper/geojsonify_place_details.js @@ -0,0 +1,544 @@ +const geojsonify = require('../../../helper/geojsonify_place_details'); + +module.exports.tests = {}; + +module.exports.tests.geojsonify_place_details = (test, common) => { + test('plain old string values should be copied verbatim, replacing old values', t => { + const source = { + housenumber: 'housenumber value', + street: 'street value', + postalcode: 'postalcode value', + postalcode_gid: 'postalcode_gid value', + match_type: 'match_type value', + accuracy: 'accuracy value', + country: 'country value', + country_gid: 'country_gid value', + country_a: 'country_a value', + dependency: 'dependency value', + dependency_gid: 'dependency_gid value', + dependency_a: 'dependency_a value', + macroregion: 'macroregion value', + macroregion_gid: 'macroregion_gid value', + macroregion_a: 'macroregion_a value', + region: 'region value', + region_gid: 'region_gid value', + region_a: 'region_a value', + macrocounty: 'macrocounty value', + macrocounty_gid: 'macrocounty_gid value', + macrocounty_a: 'macrocounty_a value', + county: 'county value', + county_gid: 'county_gid value', + county_a: 'county_a value', + localadmin: 'localadmin value', + localadmin_gid: 'localadmin_gid value', + localadmin_a: 'localadmin_a value', + locality: 'locality value', + locality_gid: 'locality_gid value', + locality_a: 'locality_a value', + borough: 'borough value', + borough_gid: 'borough_gid value', + borough_a: 'borough_a value', + neighbourhood: 'neighbourhood value', + neighbourhood_gid: 'neighbourhood_gid value', + continent: 'continent value', + continent_gid: 'continent_gid value', + continent_a: 'continent_a value', + ocean: 'ocean value', + ocean_gid: 'ocean_gid value', + ocean_a: 'ocean_a value', + marinearea: 'marinearea value', + marinearea_gid: 'marinearea_gid value', + marinearea_a: 'marinearea_a value', + label: 'label value' + }; + + const expected = { + housenumber: 'housenumber value', + street: 'street value', + postalcode: 'postalcode value', + postalcode_gid: 'postalcode_gid value', + match_type: 'match_type value', + accuracy: 'accuracy value', + country: 'country value', + country_gid: 'country_gid value', + country_a: 'country_a value', + dependency: 'dependency value', + dependency_gid: 'dependency_gid value', + dependency_a: 'dependency_a value', + macroregion: 'macroregion value', + macroregion_gid: 'macroregion_gid value', + macroregion_a: 'macroregion_a value', + region: 'region value', + region_gid: 'region_gid value', + region_a: 'region_a value', + macrocounty: 'macrocounty value', + macrocounty_gid: 'macrocounty_gid value', + macrocounty_a: 'macrocounty_a value', + county: 'county value', + county_gid: 'county_gid value', + county_a: 'county_a value', + localadmin: 'localadmin value', + localadmin_gid: 'localadmin_gid value', + localadmin_a: 'localadmin_a value', + locality: 'locality value', + locality_gid: 'locality_gid value', + locality_a: 'locality_a value', + borough: 'borough value', + borough_gid: 'borough_gid value', + borough_a: 'borough_a value', + neighbourhood: 'neighbourhood value', + neighbourhood_gid: 'neighbourhood_gid value', + continent: 'continent value', + continent_gid: 'continent_gid value', + continent_a: 'continent_a value', + ocean: 'ocean value', + ocean_gid: 'ocean_gid value', + ocean_a: 'ocean_a value', + marinearea: 'marinearea value', + marinearea_gid: 'marinearea_gid value', + marinearea_a: 'marinearea_a value', + label: 'label value' + }; + + const actual = geojsonify({}, source); + + t.deepEqual(actual, expected); + t.end(); + + }); + + test('\'empty\' string-type values should be output as \'\'', t => { + [ [], {}, '', 17, true, null, undefined ].forEach(empty_value => { + const source = { + housenumber: empty_value, + street: empty_value, + postalcode: empty_value, + postalcode_gid: empty_value, + match_type: empty_value, + accuracy: empty_value, + country: empty_value, + country_gid: empty_value, + country_a: empty_value, + dependency: empty_value, + dependency_gid: empty_value, + dependency_a: empty_value, + macroregion: empty_value, + macroregion_gid: empty_value, + macroregion_a: empty_value, + region: empty_value, + region_gid: empty_value, + region_a: empty_value, + macrocounty: empty_value, + macrocounty_gid: empty_value, + macrocounty_a: empty_value, + county: empty_value, + county_gid: empty_value, + county_a: empty_value, + localadmin: empty_value, + localadmin_gid: empty_value, + localadmin_a: empty_value, + locality: empty_value, + locality_gid: empty_value, + locality_a: empty_value, + borough: empty_value, + borough_gid: empty_value, + borough_a: empty_value, + neighbourhood: empty_value, + neighbourhood_gid: empty_value, + continent: empty_value, + continent_gid: empty_value, + continent_a: empty_value, + ocean: empty_value, + ocean_gid: empty_value, + ocean_a: empty_value, + marinearea: empty_value, + marinearea_gid: empty_value, + marinearea_a: empty_value, + label: empty_value + }; + const expected = {}; + + const actual = geojsonify({}, source); + + t.deepEqual(actual, expected); + + }); + + t.end(); + + }); + + test('source arrays should be copy first value', t => { + const source = { + housenumber: ['housenumber value 1', 'housenumber value 2'], + street: ['street value 1', 'street value 2'], + postalcode: ['postalcode value 1', 'postalcode value 2'], + postalcode_gid: ['postalcode_gid value 1', 'postalcode_gid value 2'], + match_type: ['match_type value 1', 'match_type value 2'], + accuracy: ['accuracy value 1', 'accuracy value 2'], + country: ['country value 1', 'country value 2'], + country_gid: ['country_gid value 1', 'country_gid value 2'], + country_a: ['country_a value 1', 'country_a value 2'], + dependency: ['dependency value 1', 'dependency value 2'], + dependency_gid: ['dependency_gid value 1', 'dependency_gid value 2'], + dependency_a: ['dependency_a value 1', 'dependency_a value 2'], + macroregion: ['macroregion value 1', 'macroregion value 2'], + macroregion_gid: ['macroregion_gid value 1', 'macroregion_gid value 2'], + macroregion_a: ['macroregion_a value 1', 'macroregion_a value 2'], + region: ['region value 1', 'region value 2'], + region_gid: ['region_gid value 1', 'region_gid value 2'], + region_a: ['region_a value 1', 'region_a value 2'], + macrocounty: ['macrocounty value 1', 'macrocounty value 2'], + macrocounty_gid: ['macrocounty_gid value 1', 'macrocounty_gid value 2'], + macrocounty_a: ['macrocounty_a value 1', 'macrocounty_a value 2'], + county: ['county value 1', 'county value 2'], + county_gid: ['county_gid value 1', 'county_gid value 2'], + county_a: ['county_a value 1', 'county_a value 2'], + localadmin: ['localadmin value 1', 'localadmin value 2'], + localadmin_gid: ['localadmin_gid value 1', 'localadmin_gid value 2'], + localadmin_a: ['localadmin_a value 1', 'localadmin_a value 2'], + locality: ['locality value 1', 'locality value 2'], + locality_gid: ['locality_gid value 1', 'locality_gid value 2'], + locality_a: ['locality_a value 1', 'locality_a value 2'], + borough: ['borough value 1', 'borough value 2'], + borough_gid: ['borough_gid value 1', 'borough_gid value 2'], + borough_a: ['borough_a value 1', 'borough_a value 2'], + neighbourhood: ['neighbourhood value 1', 'neighbourhood value 2'], + neighbourhood_gid: ['neighbourhood_gid value 1', 'neighbourhood_gid value 2'], + continent: ['continent value 1', 'continent value 2'], + continent_gid: ['continent_gid value 1', 'continent_gid value 2'], + continent_a: ['continent_a value 1', 'continent_a value 2'], + ocean: ['ocean value 1', 'ocean value 2'], + ocean_gid: ['ocean_gid value 1', 'ocean_gid value 2'], + ocean_a: ['ocean_a value 1', 'ocean_a value 2'], + marinearea: ['marinearea value 1', 'marinearea value 2'], + marinearea_gid: ['marinearea_gid value 1', 'marinearea_gid value 2'], + marinearea_a: ['marinearea_a value 1','marinearea_a value 2'], + label: ['label value 1', 'label value 2'] + }; + + const expected = { + housenumber: 'housenumber value 1', + street: 'street value 1', + postalcode: 'postalcode value 1', + postalcode_gid: 'postalcode_gid value 1', + match_type: 'match_type value 1', + accuracy: 'accuracy value 1', + country: 'country value 1', + country_gid: 'country_gid value 1', + country_a: 'country_a value 1', + dependency: 'dependency value 1', + dependency_gid: 'dependency_gid value 1', + dependency_a: 'dependency_a value 1', + macroregion: 'macroregion value 1', + macroregion_gid: 'macroregion_gid value 1', + macroregion_a: 'macroregion_a value 1', + region: 'region value 1', + region_gid: 'region_gid value 1', + region_a: 'region_a value 1', + macrocounty: 'macrocounty value 1', + macrocounty_gid: 'macrocounty_gid value 1', + macrocounty_a: 'macrocounty_a value 1', + county: 'county value 1', + county_gid: 'county_gid value 1', + county_a: 'county_a value 1', + localadmin: 'localadmin value 1', + localadmin_gid: 'localadmin_gid value 1', + localadmin_a: 'localadmin_a value 1', + locality: 'locality value 1', + locality_gid: 'locality_gid value 1', + locality_a: 'locality_a value 1', + borough: 'borough value 1', + borough_gid: 'borough_gid value 1', + borough_a: 'borough_a value 1', + neighbourhood: 'neighbourhood value 1', + neighbourhood_gid: 'neighbourhood_gid value 1', + continent: 'continent value 1', + continent_gid: 'continent_gid value 1', + continent_a: 'continent_a value 1', + ocean: 'ocean value 1', + ocean_gid: 'ocean_gid value 1', + ocean_a: 'ocean_a value 1', + marinearea: 'marinearea value 1', + marinearea_gid: 'marinearea_gid value 1', + marinearea_a: 'marinearea_a value 1', + label: 'label value 1' + }; + + const actual = geojsonify({}, source); + + t.deepEqual(actual, expected); + t.end(); + + }); + + test('non-empty objects should be converted to strings', t => { + // THIS TEST SHOWS THAT THE CODE DOES NOT DO WHAT IT EXPECTED + const source = { + housenumber: { housenumber: 'housenumber value'}, + street: { street: 'street value'}, + postalcode: { postalcode: 'postalcode value'}, + postalcode_gid: { postalcode_gid: 'postalcode_gid value'}, + match_type: { match_type: 'match_type value'}, + accuracy: { accuracy: 'accuracy value'}, + country: { country: 'country value'}, + country_gid: { country_gid: 'country_gid value'}, + country_a: { country_a: 'country_a value'}, + dependency: { dependency: 'dependency value'}, + dependency_gid: { dependency_gid: 'dependency_gid value'}, + dependency_a: { dependency_a: 'dependency_a value'}, + macroregion: { macroregion: 'macroregion value'}, + macroregion_gid: { macroregion_gid: 'macroregion_gid value'}, + macroregion_a: { macroregion_a: 'macroregion_a value'}, + region: { region: 'region value'}, + region_gid: { region_gid: 'region_gid value'}, + region_a: { region_a: 'region_a value'}, + macrocounty: { macrocounty: 'macrocounty value'}, + macrocounty_gid: { macrocounty_gid: 'macrocounty_gid value'}, + macrocounty_a: { macrocounty_a: 'macrocounty_a value'}, + county: { county: 'county value'}, + county_gid: { county_gid: 'county_gid value'}, + county_a: { county_a: 'county_a value'}, + localadmin: { localadmin: 'localadmin value'}, + localadmin_gid: { localadmin_gid: 'localadmin_gid value'}, + localadmin_a: { localadmin_a: 'localadmin_a value'}, + locality: { locality: 'locality value'}, + locality_gid: { locality_gid: 'locality_gid value'}, + locality_a: { locality_a: 'locality_a value'}, + borough: { borough: 'borough value'}, + borough_gid: { borough_gid: 'borough_gid value'}, + borough_a: { borough_a: 'borough_a value'}, + neighbourhood: { neighbourhood: 'neighbourhood value'}, + neighbourhood_gid: { neighbourhood_gid: 'neighbourhood_gid value'}, + continent: { continent: 'continent value'} , + continent_gid: { continent: 'continent_gid value'}, + continent_a: { continent: 'continent_a value'}, + ocean: { ocean: 'ocean value'}, + ocean_gid: { ocean_gid: 'ocean_gid value'}, + ocean_a: { ocean_a: 'ocean_a value'}, + marinearea: { marinearea: 'marinearea value'}, + marinearea_gid: { marinearea_gid: 'marinearea_gid value'}, + marinearea_a: { marinearea_a: 'marinearea_a value'}, + label: { label: 'label value'} + }; + + const expected = { + housenumber: '[object Object]', + street: '[object Object]', + postalcode: '[object Object]', + postalcode_gid: '[object Object]', + match_type: '[object Object]', + accuracy: '[object Object]', + country: '[object Object]', + country_gid: '[object Object]', + country_a: '[object Object]', + dependency: '[object Object]', + dependency_gid: '[object Object]', + dependency_a: '[object Object]', + macroregion: '[object Object]', + macroregion_gid: '[object Object]', + macroregion_a: '[object Object]', + region: '[object Object]', + region_gid: '[object Object]', + region_a: '[object Object]', + macrocounty: '[object Object]', + macrocounty_gid: '[object Object]', + macrocounty_a: '[object Object]', + county: '[object Object]', + county_gid: '[object Object]', + county_a: '[object Object]', + localadmin: '[object Object]', + localadmin_gid: '[object Object]', + localadmin_a: '[object Object]', + locality: '[object Object]', + locality_gid: '[object Object]', + locality_a: '[object Object]', + borough: '[object Object]', + borough_gid: '[object Object]', + borough_a: '[object Object]', + neighbourhood: '[object Object]', + neighbourhood_gid: '[object Object]', + continent: '[object Object]', + continent_gid: '[object Object]', + continent_a: '[object Object]', + ocean: '[object Object]', + ocean_gid: '[object Object]', + ocean_a: '[object Object]', + marinearea: '[object Object]', + marinearea_gid: '[object Object]', + marinearea_a: '[object Object]', + label: '[object Object]' + }; + + const actual = geojsonify({}, source); + + t.deepEqual(actual, expected); + t.end(); + + }); + + test('\'default\'-type properties should be copied without type conversion and overwrite old values', t => { + [ 'this is a string', 17.3, { a: 1 }, [1, 2, 3] ].forEach(value => { + const source = { + confidence: value, + distance: value, + bounding_box: value + }; + + const expected = { + confidence: value, + distance: value, + bounding_box: value + }; + + const actual = geojsonify({}, source); + + t.deepEqual(actual, expected); + + }); + + t.end(); + + }); + + test('\'default\'-type properties that are numbers should be output as numbers', t => { + [ 17, 17.3 ].forEach(value => { + const source = { + confidence: value, + distance: value, + bounding_box: value + }; + const expected = { + confidence: value, + distance: value, + bounding_box: value + }; + + const actual = geojsonify({}, source); + + t.deepEqual(actual, expected); + + }); + + t.end(); + + }); + + test('\'empty\' values for default-type properties should not be output', t => { + [ undefined, null, true, {}, [] ].forEach(value => { + const source = { + confidence: value, + distance: value, + bounding_box: value + }; + const expected = {}; + + const actual = geojsonify({}, source); + + t.deepEqual(actual, expected); + + }); + + t.end(); + + }); + + test('array-type properties should not be output when empty', t => { + const source = { + category: [] + }; + const expected = {}; + + const actual = geojsonify({}, source); + + t.deepEqual(actual, expected); + t.end(); + + }); + + test('array-type properties with array values should be output as arrays', t => { + const source = { + category: [ 1, 2 ] + }; + const expected = { + category: [ 1, 2 ] + }; + + const clean = { + categories: true + }; + + const actual = geojsonify(clean, source); + + t.deepEqual(actual, expected); + t.end(); + + }); + + test('category property should be output when params contains \'category\' property', t => { + [ {a: 1}, 'this is a string'].forEach(value => { + const source = { + category: value + }; + const expected = { + category: [ value ] + }; + + const clean = { + categories: true + }; + + const actual = geojsonify(clean, source); + + t.deepEqual(actual, expected); + + }); + + t.end(); + + }); + + test('category property should not be output when params does not contain \'category\' property', t => { + const source = { + category: [ 1, 2 ] + }; + const expected = { + }; + + const clean = {}; + + const actual = geojsonify(clean, source); + + t.deepEqual(actual, expected); + t.end(); + + }); + + test('category property should not be output when params is not an object', t => { + const source = { + category: [ 1, 2 ] + }; + const expected = { + }; + + const clean = 'this is not an object'; + + const actual = geojsonify(clean, source); + + t.deepEqual(actual, expected); + t.end(); + + }); + +}; + +module.exports.all = (tape, common) => { + + function test(name, testFunction) { + return tape(`geojsonify: ${name}`, testFunction); + } + + for( var testCase in module.exports.tests ){ + module.exports.tests[testCase](test, common); + } +}; diff --git a/test/unit/helper/type_mapping.js b/test/unit/helper/type_mapping.js index d67218c5..ec8cd379 100644 --- a/test/unit/helper/type_mapping.js +++ b/test/unit/helper/type_mapping.js @@ -12,9 +12,10 @@ module.exports.tests.interfaces = function(test, common) { test('alias layer mapping', function(t) { t.deepEquals(type_mapping.layer_mapping.coarse, - [ 'continent', 'country', 'dependency', 'macroregion', + [ 'continent', 'empire', 'country', 'dependency', 'macroregion', 'region', 'locality', 'localadmin', 'macrocounty', 'county', 'macrohood', - 'borough', 'neighbourhood', 'microhood', 'disputed', 'postalcode' ]); + 'borough', 'neighbourhood', 'microhood', 'disputed', 'postalcode', + 'continent', 'ocean', 'marinearea']); t.end(); }); diff --git a/test/unit/run.js b/test/unit/run.js index 0cd9d4c9..5631b9f3 100644 --- a/test/unit/run.js +++ b/test/unit/run.js @@ -31,6 +31,7 @@ var tests = [ require('./controller/predicates/is_request_sources_only_whosonfirst'), require('./helper/debug'), require('./helper/diffPlaces'), + require('./helper/geojsonify_place_details'), require('./helper/geojsonify'), require('./helper/logging'), require('./helper/type_mapping'), diff --git a/test/unit/sanitizer/_layers.js b/test/unit/sanitizer/_layers.js index 11c618c8..10e444c2 100644 --- a/test/unit/sanitizer/_layers.js +++ b/test/unit/sanitizer/_layers.js @@ -41,9 +41,10 @@ module.exports.tests.sanitize_layers = function(test, common) { sanitizer.sanitize(raw, clean); - var admin_layers = [ 'continent', 'country', 'dependency', - 'macroregion', 'region', 'locality', 'localadmin', 'macrocounty', 'county', - 'macrohood', 'borough', 'neighbourhood', 'microhood', 'disputed', 'postalcode' ]; + var admin_layers = [ 'continent', 'empire', 'country', 'dependency', 'macroregion', + 'region', 'locality', 'localadmin', 'macrocounty', 'county', 'macrohood', + 'borough', 'neighbourhood', 'microhood', 'disputed', 'postalcode', 'ocean', + 'marinearea' ]; t.deepEqual(clean.layers, admin_layers, 'coarse layers set'); t.end(); @@ -76,9 +77,10 @@ module.exports.tests.sanitize_layers = function(test, common) { sanitizer.sanitize(raw, clean); - var expected_layers = [ 'continent', 'country', 'dependency', + var expected_layers = [ 'continent', 'empire', 'country', 'dependency', 'macroregion', 'region', 'locality', 'localadmin', 'macrocounty', 'county', - 'macrohood', 'borough', 'neighbourhood', 'microhood', 'disputed', 'postalcode' ]; + 'macrohood', 'borough', 'neighbourhood', 'microhood', 'disputed', 'postalcode', + 'ocean', 'marinearea']; t.deepEqual(clean.layers, expected_layers, 'coarse + regular layers set'); t.end(); @@ -112,10 +114,10 @@ module.exports.tests.sanitize_layers = function(test, common) { sanitizer.sanitize(raw, clean); - var coarse_layers = [ 'continent', + var coarse_layers = [ 'continent', 'empire', 'country', 'dependency', 'macroregion', 'region', 'locality', 'localadmin', 'macrocounty', 'county', 'macrohood', 'borough', 'neighbourhood', 'microhood', - 'disputed', 'postalcode' ]; + 'disputed', 'postalcode', 'ocean', 'marinearea' ]; var venue_layers = [ 'venue' ]; var expected_layers = venue_layers.concat(coarse_layers); diff --git a/test/unit/service/configurations/PointInPolygon.js b/test/unit/service/configurations/PointInPolygon.js index c754e7f9..6906950f 100644 --- a/test/unit/service/configurations/PointInPolygon.js +++ b/test/unit/service/configurations/PointInPolygon.js @@ -53,16 +53,62 @@ module.exports.tests.all = (test, common) => { }); - test('getParameters should return an empty object', (t) => { + test('getParameters should return an empty object when req is undefined', (t) => { const configBlob = { - url: 'http://localhost:1234', - timeout: 17, - retries: 19 + url: 'http://localhost:1234' }; const pointInPolygon = new PointInPolygon(configBlob); - t.deepEquals(pointInPolygon.getParameters(), {}); + t.deepEquals(pointInPolygon.getParameters(undefined), {}); + t.end(); + + }); + + test('getParameters should return an empty object when req.clean is undefined', (t) => { + const configBlob = { + url: 'http://localhost:1234' + }; + + const pointInPolygon = new PointInPolygon(configBlob); + + const req = {}; + + t.deepEquals(pointInPolygon.getParameters(req), {}); + t.end(); + + }); + + test('getParameters should return an empty object when req.clean.layers is undefined', (t) => { + const configBlob = { + url: 'http://localhost:1234' + }; + + const pointInPolygon = new PointInPolygon(configBlob); + + const req = { + clean: {} + }; + + t.deepEquals(pointInPolygon.getParameters(req), {}); + t.end(); + + }); + + test('getParameters should return object with layers property when req.clean.layers is specified', t => { + const configBlob = { + url: 'http://localhost:1234' + }; + + const pointInPolygon = new PointInPolygon(configBlob); + + const req = { + clean: { + layers: ['layer1', 'layer2'] + } + }; + + t.deepEquals(pointInPolygon.getParameters(req), { layers: 'layer1,layer2'}); t.end(); });