Browse Source

Merge pull request #210 from pelias/responses

Refactor responses to match geocodejson spec
pull/223/head
Diana Shkolnikov 9 years ago
parent
commit
2ad091b687
  1. 8
      app.js
  2. 14
      controller/place.js
  3. 12
      controller/search.js
  4. 198
      helper/geojsonify.js
  5. 4
      helper/outputGenerator.js
  6. 12
      helper/outputSchema.json
  7. 78
      middleware/geocodeJSON.js
  8. 66
      middleware/renamePlacenames.js
  9. 12
      middleware/sendJSON.js
  10. 2
      package.json
  11. 11
      routes/legacy.js
  12. 101
      routes/v1.js
  13. 30
      sanitiser/coarse.js
  14. 29
      sanitiser/suggest.js
  15. 14
      test/unit/controller/place.js
  16. 14
      test/unit/controller/search.js
  17. 169
      test/unit/helper/geojsonify.js
  18. 6
      test/unit/helper/outputSchema.js
  19. 2
      test/unit/run.js
  20. 72
      test/unit/sanitiser/coarse.js

8
app.js

@ -1,19 +1,21 @@
var app = require('express')();
var peliasConfig = require( 'pelias-config' ).generate().api;
if( peliasConfig.accessLog ){
app.use( require( './middleware/access_log' )( peliasConfig.accessLog ) );
}
/** ----------------------- middleware ----------------------- **/
/** ----------------------- pre-processing-middleware ----------------------- **/
app.use( require('./middleware/headers') );
app.use( require('./middleware/cors') );
app.use( require('./middleware/jsonp') );
/** ----------------------- routes ----------------------- **/
var legacy = require('./routes/legacy');
legacy.addRoutes(app, peliasConfig);
@ -26,4 +28,4 @@ app.use( require('./middleware/404') );
app.use( require('./middleware/408') );
app.use( require('./middleware/500') );
module.exports = app;
module.exports = app;

14
controller/place.js

@ -1,5 +1,4 @@
var service = { mget: require('../service/mget') };
var geojsonify = require('../helper/geojsonify').search;
function setup( backend ){
// allow overriding of dependencies
@ -14,18 +13,15 @@ function setup( backend ){
};
});
service.mget( backend, query, function( err, docs ){
service.mget( backend, query, function( err, docs ) {
// error handler
if( err ){ return next( err ); }
// convert docs to geojson
var geojson = geojsonify( docs, req.clean );
// response envelope
geojson.date = new Date().getTime();
req.results = {
data: docs
};
// respond
return res.status(200).json( geojson );
next();
});
}

12
controller/search.js

@ -1,6 +1,5 @@
var service = { search: require('../service/search') };
var geojsonify = require('../helper/geojsonify').search;
function setup( backend, query ){
@ -32,14 +31,11 @@ function setup( backend, query ){
// error handler
if( err ){ return next( err ); }
// convert docs to geojson
var geojson = geojsonify( docs, req.clean );
req.results = {
data: docs
};
// response envelope
geojson.date = new Date().getTime();
// respond
return res.status(200).json( geojson );
next();
});
}

198
helper/geojsonify.js

@ -1,68 +1,161 @@
var GeoJSON = require('geojson'),
extent = require('geojson-extent'),
outputGenerator = require('./outputGenerator');
var GeoJSON = require('geojson');
var extent = require('geojson-extent');
var outputGenerator = require('./outputGenerator');
var logger = require('pelias-logger').get('api');
// Properties to be copied when details=true
var DETAILS_PROPS = [
'alpha3',
'admin0',
'admin1',
'admin1_abbr',
'admin2',
'local_admin',
'locality',
'neighborhood',
'housenumber',
'street',
'category',
'address'
'postalcode',
'country_a',
'country',
'region',
'region_a',
'county',
'localadmin',
'locality',
'neighbourhood'
];
function search( docs, params ){
var SOURCES = {
'geoname': 'gn',
'osmnode': 'osm',
'osmway': 'osm',
'admin0': 'qs',
'admin1': 'qs',
'admin2': 'qs',
'neighborhood': 'qs',
'locality': 'qs',
'local_admin': 'qs',
'osmaddress': 'osm',
'openaddresses': 'oa'
};
function lookupSource(src) {
return SOURCES.hasOwnProperty(src._type) ? SOURCES[src._type] : src._type;
}
function lookupLayer(src) {
switch(src._type) {
case 'osmnode':
case 'osmway':
return 'venue';
case 'admin0':
return 'country';
case 'admin1':
return 'region';
case 'admin2':
return 'county';
case 'neighborhood':
return 'neighbourhood';
case 'locality':
return 'locality';
case 'local_admin':
return 'localadmin';
case 'osmaddress':
case 'openaddresses':
return 'address';
case 'geoname':
if (src.category && src.category.indexOf('admin') !== -1) {
if (src.category.indexOf('admin:city') !== -1) { return 'locality'; }
if (src.category.indexOf('admin:admin1') !== -1) { return 'region'; }
if (src.category.indexOf('admin:admin2') !== -1) { return 'county'; }
return 'neighbourhood'; // this could also be 'local_admin'
}
if (src.name) { return 'venue'; }
if (src.address) { return 'address'; }
}
logger.warn('[geojsonify]: could not map _type ', src._type);
return src._type;
}
function geojsonifyPlaces( docs, params ){
var details = params ? params.details : {};
details = details === true || details === 1;
// flatten & expand data for geojson conversion
var geodata = docs.map( function( doc ) {
var geodata = docs
.map(geojsonifyPlace.bind(null, details))
.filter( function( doc ){
return !!doc;
});
// something went very wrong
if( !doc || !doc.hasOwnProperty( 'center_point' ) ) {
return warning();
}
// convert to geojson
var geojson = GeoJSON.parse( geodata, { Point: ['lat', 'lng'] });
var output = {};
// bounding box calculations
computeBBox(geojson);
// provide metadata to consumer
output.id = doc._id;
output.layer = doc._type;
return geojson;
}
// map center_point
output.lat = parseFloat( doc.center_point.lat );
output.lng = parseFloat( doc.center_point.lon );
function geojsonifyPlace(details, place) {
if (details) {
// map name
if( !doc.name || !doc.name.default ) { return warning(); }
output.name = doc.name.default;
// something went very wrong
if( !place || !place.hasOwnProperty( 'center_point' ) ) {
return warning('No doc or center_point property');
}
copyProperties( doc, DETAILS_PROPS, output );
}
var geocoding = {};
// generate region-specific text string
output.text = outputGenerator( doc );
addMetaData(place, geocoding);
addDetails(details, place, geocoding);
addLabel(place, geocoding);
return output;
var output = {};
// filter-out invalid entries
}).filter( function( doc ){
return doc;
});
output.geocoding = geocoding;
// 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);
// convert to geojson
var geojson = GeoJSON.parse( geodata, { Point: ['lat', 'lng'] });
return output;
}
// bounding box calculations
/**
* Add details properties when needed
*
* @param {boolean} details
* @param {object} src
* @param {object} dst
*/
function addDetails(details, src, dst) {
if (details) {
// map name
if( !src.name || !src.name.default ) { return warning(src); }
dst.name = src.name.default;
copyProperties(src, DETAILS_PROPS, dst);
}
}
/**
* Add region-specific label string
*
* @param {object} src
* @param {object} dst
*/
function addLabel(src, dst) {
dst.label = outputGenerator(src);
}
/**
* Compute bbox that encompasses all features in the result set.
* Set bbox property on the geojson object.
*
* @param {object} geojson
*/
function computeBBox(geojson) {
// @note: extent() sometimes throws Errors for unusual data
// eg: https://github.com/pelias/pelias/issues/84
try {
@ -74,8 +167,6 @@ function search( docs, params ){
console.error( 'bbox error', e.message, e.stack );
console.error( 'geojson', JSON.stringify( geojson, null, 2 ) );
}
return geojson;
}
/**
@ -84,16 +175,27 @@ function search( docs, params ){
*
* @param {object} source
* @param {[]} props
* @param {object} dest
* @param {object} dst
*/
function copyProperties( source, props, dest ) {
function copyProperties( source, props, dst ) {
props.forEach( function ( prop ) {
if ( source.hasOwnProperty( prop ) ) {
dest[prop] = source[prop];
dst[prop] = source[prop];
}
});
}
/**
* Determine and set place id, type, and source
*
* @param {object} src
* @param {object} dst
*/
function addMetaData(src, dst) {
dst.id = src._id;
dst.layer = lookupLayer(src);
dst.source = lookupSource(src);
}
/**
* emit a warning if the doc format is invalid
@ -101,9 +203,9 @@ function copyProperties( source, props, dest ) {
* @note: if you see this error, fix it ASAP!
*/
function warning( doc ) {
console.error( 'error: invalid doc', __filename, doc );
console.error( 'error: invalid doc', __filename, doc);
return false; // remove offending doc from results
}
module.exports.search = search;
module.exports.search = geojsonifyPlaces;

4
helper/outputGenerator.js

@ -7,8 +7,8 @@ module.exports = function( record ){
var schema = schemas.default;
if (record.alpha3 && record.alpha3.length && schemas[record.alpha3]) {
schema = schemas[record.alpha3];
if (record.country_a && record.country_a.length && schemas[record.country_a]) {
schema = schemas[record.country_a];
}
var buildOutput = function(parts, schemaArr, record) {

12
helper/outputSchema.json

@ -1,14 +1,14 @@
{
"USA": {
"local": ["local_admin", "locality", "neighborhood", "admin2"],
"regional": ["admin1_abbr", "admin1", "admin0"]
"local": ["localadmin", "locality", "neighbourhood", "county"],
"regional": ["region_a", "region", "country"]
},
"GBR": {
"local": ["neighborhood", "admin2", "local_admin", "locality"],
"regional": ["admin2","admin0","admin1"]
"local": ["neighbourhood", "county", "localadmin", "locality"],
"regional": ["county","country","region"]
},
"default": {
"local": ["local_admin", "locality", "neighborhood", "admin2"],
"regional": ["admin1_abbr", "admin1", "admin0"]
"local": ["localadmin", "locality", "neighbourhood", "county"],
"regional": ["region_a", "region", "country"]
}
}

78
middleware/geocodeJSON.js

@ -0,0 +1,78 @@
var extend = require('extend');
var geojsonify = require('../helper/geojsonify').search;
function setup(peliasConfig) {
peliasConfig = peliasConfig || require( 'pelias-config' ).generate().api;
function middleware(req, res, next) {
return convertToGeocodeJSON(peliasConfig, req, next);
}
return middleware;
}
function convertToGeocodeJSON(peliasConfig, req, next) {
// do nothing if no result data set
if (!req.results || !req.results.data) {
return next();
}
req.results.geojson = { geocoding: {} };
// REQUIRED. A semver.org compliant version number. Describes the version of
// the GeocodeJSON spec that is implemented by this instance.
req.results.geojson.geocoding.version = '0.1';
// OPTIONAL. Default: null. The licence of the data. In case of multiple sources,
// and then multiple licences, can be an object with one key by source.
// Can be a freeform text property describing the licensing details.
// Can be a URI on the server, which outlines licensing details.
req.results.geojson.geocoding.license = peliasConfig.host + '/license';
// OPTIONAL. Default: null. The attribution of the data. In case of multiple sources,
// and then multiple attributions, can be an object with one key by source.
// Can be a URI on the server, which outlines attribution details.
req.results.geojson.geocoding.attribution = peliasConfig.host + '/attribution';
// OPTIONAL. Default: null. The query that has been issued to trigger the
// search.
// Freeform object.
// This is the equivalent of how the engine interpreted the incoming request.
// Helpful for debugging and understanding how the input impacts results.
req.results.geojson.geocoding.query = req.clean;
// OPTIONAL. Warnings and errors.
addMessages(req.results, 'warnings', req.results.geojson.geocoding);
addMessages(req.results, 'errors', req.results.geojson.geocoding);
// OPTIONAL
// Freeform
addEngine(peliasConfig, req.results.geojson.geocoding);
// response envelope
req.results.geojson.geocoding.timestamp = new Date().getTime();
// convert docs to geojson and merge with geocoding block
extend(req.results.geojson, geojsonify(req.results.data, req.clean));
next();
}
function addMessages(results, msgType, geocoding) {
if (results.hasOwnProperty(msgType)) {
geocoding.messages = geocoding.messages || {};
geocoding.messages[msgType] = results[msgType];
}
}
function addEngine(peliasConfig, geocoding) {
geocoding.engine = {
name: 'Pelias',
author: 'Mapzen',
version: peliasConfig.version
};
}
module.exports = setup;

66
middleware/renamePlacenames.js

@ -0,0 +1,66 @@
var extend = require('extend');
/**
- P is a preferred English name
- Q is a preferred name (in other languages)
- V is a well-known (but unofficial) variant for the place
(e.g. "New York City" for New York)
- S is either a synonym or a colloquial name for the place
(e.g. "Big Apple" for New York), or a version of the name which
is stripped of accent characters.
- A is an abbreviation or code for the place (e.g. "NYC" for New
York)
*/
// config mapping of old names to new ones
var NAME_MAP = {
'number': 'housenumber',
'zip': 'postalcode',
'alpha3': 'country_a',
'admin0': 'country',
'admin1': 'region',
'admin1_abbr': 'region_a',
'admin2': 'county',
'local_admin': 'localadmin',
'neighborhood': 'neighbourhood'
};
function setup() {
return renamePlacenames;
}
function renamePlacenames(req, res, next) {
// do nothing if no result data set
if (!req.results || !req.results.data) {
return next();
}
// loop through data items and remap placenames
req.results.data = req.results.data.map(renameProperties);
next();
}
function renameProperties(place) {
var newPlace = {};
Object.keys(place).forEach(function (property) {
if (property === 'address') {
extend(newPlace, renameProperties(place[property]));
}
else {
renameProperty(place, newPlace, property);
}
});
return newPlace;
}
function renameProperty(oldObj, newObj, property) {
if (!oldObj.hasOwnProperty(property)) {
return;
}
newObj[(NAME_MAP[property] || property)] = oldObj[property];
}
module.exports = setup;

12
middleware/sendJSON.js

@ -0,0 +1,12 @@
function sendJSONResponse(req, res, next) {
// do nothing if no result data set
if (!req.results || !req.results.geojson) {
return next();
}
// respond
return res.status(200).json(req.results.geojson);
}
module.exports = sendJSONResponse;

2
package.json

@ -46,7 +46,7 @@
"markdown": "0.5.0",
"microtime": "1.4.0",
"morgan": "1.5.2",
"pelias-config": "^0.1.4",
"pelias-config": "^1.0.1",
"pelias-esclient": "0.0.25",
"pelias-logger": "^0.0.8",
"pelias-schema": "1.0.0",

11
routes/legacy.js

@ -1,11 +1,16 @@
var proxy = require('express-http-proxy');
function addRoutes(app, peliasConfig) {
var sendToLegacy;
if (!peliasConfig.hasOwnProperty('legacyUrl')) {
return;
sendToLegacy = function doNothing(req, res, next) {
next(new Error('Invalid path, no legacy proxy specified'));
};
}
else {
sendToLegacy = proxy(peliasConfig.legacyUrl);
}
var sendToLegacy = proxy(peliasConfig.legacyUrl);
// api root
app.get( '/', sendToLegacy );

101
routes/v1.js

@ -1,36 +1,91 @@
var Router = require('express').Router;
var reverseQuery = require('../query/reverse');
/** ----------------------- sanitisers ----------------------- **/
var sanitisers = {};
sanitisers.place = require('../sanitiser/place');
sanitisers.suggest = require('../sanitiser/suggest');
sanitisers.search = require('../sanitiser/search');
sanitisers.coarse = require('../sanitiser/coarse');
sanitisers.reverse = require('../sanitiser/reverse');
var sanitisers = {
place: require('../sanitiser/place'),
search: require('../sanitiser/search'),
reverse: require('../sanitiser/reverse')
};
/** ----------------------- controllers ----------------------- **/
var controllers = {};
controllers.index = require('../controller/index');
controllers.place = require('../controller/place');
controllers.search = require('../controller/search');
var controllers = {
index: require('../controller/index'),
place: require('../controller/place'),
search: require('../controller/search')
};
/** ----------------------- controllers ----------------------- **/
var postProc = {
renamePlacenames: require('../middleware/renamePlacenames'),
geocodeJSON: require('../middleware/geocodeJSON'),
sendJSON: require('../middleware/sendJSON')
};
/**
* Append routes to app
*
* @param {object} app
* @param {object} peliasConfig
*/
function addRoutes(app, peliasConfig) {
// api root
app.get( '/v1/', controllers.index() );
// place API
app.get( '/v1/place', sanitisers.place.middleware, controllers.place() );
/** ------------------------- routers ------------------------- **/
// suggest APIs
app.get( '/v1/suggest', sanitisers.search.middleware, controllers.search() );
app.get( '/v1/suggest/nearby', sanitisers.suggest.middleware, controllers.search() );
app.get( '/v1/suggest/coarse', sanitisers.coarse.middleware, controllers.search() );
var routers = {
index: createRouter([
controllers.index()
]),
search: createRouter([
sanitisers.search.middleware,
controllers.search(),
postProc.renamePlacenames(),
postProc.geocodeJSON(peliasConfig),
postProc.sendJSON
]),
reverse: createRouter([
sanitisers.reverse.middleware,
controllers.search(undefined, reverseQuery),
postProc.renamePlacenames(),
postProc.geocodeJSON(peliasConfig),
postProc.sendJSON
]),
place: createRouter([
sanitisers.place.middleware,
controllers.place(),
postProc.renamePlacenames(),
postProc.geocodeJSON(peliasConfig),
postProc.sendJSON
])
};
// search APIs
app.get( '/v1/search', sanitisers.search.middleware, controllers.search() );
app.get( '/v1/search/coarse', sanitisers.coarse.middleware, controllers.search() );
// reverse API
app.get( '/v1/reverse', sanitisers.reverse.middleware, controllers.search(undefined, require('../query/reverse')) );
var base = '/v1/';
// api root
app.get ( base, routers.index );
app.get ( base + 'place', routers.place );
app.get ( base + 'autocomplete', routers.search );
app.get ( base + 'search', routers.search );
app.post( base + 'search', routers.search );
app.get ( base + 'reverse', routers.reverse );
}
/**
* Helper function for creating routers
*
* @param {[{function}]} functions
* @returns {express.Router}
*/
function createRouter(functions) {
var router = Router(); // jshint ignore:line
functions.forEach(function (f) {
router.use(f);
});
return router;
}
module.exports.addRoutes = addRoutes;

30
sanitiser/coarse.js

@ -1,30 +0,0 @@
var _sanitize = require('../sanitiser/_sanitize'),
sanitizers = {
input: require('../sanitiser/_input'),
size: require('../sanitiser/_size'),
layers: function( req ) {
req.query.layers = 'admin';
var layers = require('../sanitiser/_layers');
return layers(req);
},
latlonzoom: require('../sanitiser/_geo'),
details: require('../sanitiser/_details')
};
var sanitize = function(req, cb) { _sanitize(req, sanitizers, cb); };
// export sanitize for testing
module.exports.sanitize = sanitize;
// middleware
module.exports.middleware = function( req, res, next ){
sanitize( req, function( err, clean ){
if( err ){
res.status(400); // 400 Bad Request
return next(err);
}
req.clean = clean;
next();
});
};

29
sanitiser/suggest.js

@ -1,29 +0,0 @@
var _sanitize = require('../sanitiser/_sanitize'),
sanitizers = {
input: require('../sanitiser/_input'),
size: require('../sanitiser/_size'),
layers: require('../sanitiser/_layers'),
details: require('../sanitiser/_details'),
latlonzoom: function( req ) {
var geo = require('../sanitiser/_geo');
return geo(req, true);
}
};
var sanitize = function(req, cb) { _sanitize(req, sanitizers, cb); };
// export sanitize for testing
module.exports.sanitize = sanitize;
// middleware
module.exports.middleware = function( req, res, next ){
sanitize( req, function( err, clean ){
if( err ){
res.status(400); // 400 Bad Request
return next(err);
}
req.clean = clean;
next();
});
};

14
test/unit/controller/place.js

@ -55,10 +55,13 @@ module.exports.tests.functional_success = function(test, common) {
t.equal(json.type, 'FeatureCollection', 'valid geojson');
t.true(Array.isArray(json.features), 'features is array');
t.deepEqual(json.features, expected, 'values correctly mapped');
t.end();
}
};
controller( { clean: { ids: [ {'id' : 123, 'type': 'a' } ] } }, res );
var next = function next() {
t.equal(arguments.length, 0, 'next was called without error');
t.end();
};
controller( { clean: { ids: [ {'id' : 123, 'type': 'a' } ] } }, res, next );
});
var detailed_expectation = [{
@ -109,10 +112,13 @@ module.exports.tests.functional_success = function(test, common) {
t.equal(json.type, 'FeatureCollection', 'valid geojson');
t.true(Array.isArray(json.features), 'features is array');
t.deepEqual(json.features, detailed_expectation, 'values correctly mapped along with details');
t.end();
}
};
controller( { clean: { ids: [ {'id' : 123, 'type': 'a' } ], details: true } }, res );
var next = function next() {
t.equal(arguments.length, 0, 'next was called without error');
t.end();
};
controller( { clean: { ids: [ {'id' : 123, 'type': 'a' } ], details: true } }, res, next );
});
};

14
test/unit/controller/search.js

@ -57,10 +57,13 @@ module.exports.tests.functional_success = function(test, common) {
t.equal(json.type, 'FeatureCollection', 'valid geojson');
t.true(Array.isArray(json.features), 'features is array');
t.deepEqual(json.features, expected, 'values correctly mapped');
t.end();
}
};
controller( { clean: { a: 'b' } }, res );
var next = function next() {
t.equal(arguments.length, 0, 'next was called without error');
t.end();
};
controller( { clean: { a: 'b' } }, res, next );
});
var detailed_expectation = [{
@ -111,10 +114,13 @@ module.exports.tests.functional_success = function(test, common) {
t.equal(json.type, 'FeatureCollection', 'valid geojson');
t.true(Array.isArray(json.features), 'features is array');
t.deepEqual(json.features, detailed_expectation, 'values with details correctly mapped');
t.end();
}
};
controller( { clean: { a: 'b', details: true } }, res );
var next = function next() {
t.equal(arguments.length, 0, 'next was called without error');
t.end();
};
controller( { clean: { a: 'b', details: true } }, res, next );
});
};

169
test/unit/helper/geojsonify.js

@ -48,20 +48,17 @@ module.exports.tests.search = function(test, common) {
'name': {
'default': '\'Round Midnight Jazz and Blues Bar'
},
'type': 'node',
'address': {
'number': '13',
'street': 'Liverpool Road',
'zip': 'N1 0RW'
},
'alpha3': 'GBR',
'admin0': 'United Kingdom',
'admin1': 'Islington',
'admin1_abbr': 'ISL',
'admin2': 'Angel',
'local_admin': 'test1',
'housenumber': '13',
'street': 'Liverpool Road',
'postalcode': 'N1 0RW',
'country_a': 'GBR',
'country': 'United Kingdom',
'region': 'Islington',
'region_a': 'ISL',
'county': 'Angel',
'localadmin': 'test1',
'locality': 'test2',
'neighborhood': 'test3',
'neighbourhood': 'test3',
'suggest': {
'input': [
'\'round midnight jazz and blues bar'
@ -76,7 +73,6 @@ module.exports.tests.search = function(test, common) {
{
'_id': 'id2',
'_type': 'type2',
'type': 'way',
'name': {
'default': 'Blues Cafe'
},
@ -84,14 +80,14 @@ module.exports.tests.search = function(test, common) {
'lat': '51.517806',
'lon': '-0.101795'
},
'alpha3': 'GBR',
'admin0': 'United Kingdom',
'admin1': 'City And County Of The City Of London',
'admin1_abbr': 'COL',
'admin2': 'Smithfield',
'local_admin': 'test1',
'country_a': 'GBR',
'country': 'United Kingdom',
'region': 'City And County Of The City Of London',
'region_a': 'COL',
'county': 'Smithfield',
'localadmin': 'test1',
'locality': 'test2',
'neighborhood': 'test3',
'neighbourhood': 'test3',
'suggest': {
'input': [
'blues cafe'
@ -102,7 +98,6 @@ module.exports.tests.search = function(test, common) {
{
'_id': '34633854',
'_type': 'osmway',
'type': 'osmway',
'name': {
'default': 'Empire State Building'
},
@ -110,14 +105,14 @@ module.exports.tests.search = function(test, common) {
'lat': '40.748432',
'lon': '-73.985656'
},
'alpha3': 'USA',
'admin0': 'United States',
'admin1': 'New York',
'admin1_abbr': 'NY',
'admin2': 'New York',
'local_admin': 'Manhattan',
'country_a': 'USA',
'country': 'United States',
'region': 'New York',
'region_a': 'NY',
'county': 'New York',
'localadmin': 'Manhattan',
'locality': 'New York',
'neighborhood': 'Koreatown',
'neighbourhood': 'Koreatown',
'suggest': {
'input': [
'empire state building'
@ -145,23 +140,24 @@ module.exports.tests.search = function(test, common) {
]
},
'properties': {
'id': 'id1',
'layer': 'type1',
'text': '\'Round Midnight Jazz and Blues Bar, test3, Angel',
'name': '\'Round Midnight Jazz and Blues Bar',
'alpha3': 'GBR',
'admin0': 'United Kingdom',
'admin1': 'Islington',
'admin1_abbr': 'ISL',
'admin2': 'Angel',
'local_admin': 'test1',
'locality': 'test2',
'neighborhood': 'test3',
'category': [ 'food', 'nightlife' ],
'address': {
'number': '13',
'geocoding': {
'id': 'id1',
'layer': 'type1',
'source': 'type1',
'label': '\'Round Midnight Jazz and Blues Bar, test3, Angel',
'name': '\'Round Midnight Jazz and Blues Bar',
'country_a': 'GBR',
'country': 'United Kingdom',
'region': 'Islington',
'region_a': 'ISL',
'county': 'Angel',
'localadmin': 'test1',
'locality': 'test2',
'neighbourhood': 'test3',
'category': ['food', 'nightlife'],
'housenumber': '13',
'street': 'Liverpool Road',
'zip': 'N1 0RW'
'postalcode': 'N1 0RW'
}
}
},
@ -175,18 +171,21 @@ module.exports.tests.search = function(test, common) {
]
},
'properties': {
'id': 'id2',
'layer': 'type2',
'text': 'Blues Cafe, test3, Smithfield',
'name': 'Blues Cafe',
'alpha3': 'GBR',
'admin0': 'United Kingdom',
'admin1': 'City And County Of The City Of London',
'admin1_abbr': 'COL',
'admin2': 'Smithfield',
'local_admin': 'test1',
'locality': 'test2',
'neighborhood': 'test3'
'geocoding': {
'id': 'id2',
'layer': 'type2',
'source': 'type2',
'label': 'Blues Cafe, test3, Smithfield',
'name': 'Blues Cafe',
'country_a': 'GBR',
'country': 'United Kingdom',
'region': 'City And County Of The City Of London',
'region_a': 'COL',
'county': 'Smithfield',
'localadmin': 'test1',
'locality': 'test2',
'neighbourhood': 'test3'
}
}
},
{
@ -199,19 +198,22 @@ module.exports.tests.search = function(test, common) {
]
},
'properties': {
'id': '34633854',
'layer': 'osmway',
'text': 'Empire State Building, Manhattan, NY',
'name': 'Empire State Building',
'alpha3': 'USA',
'admin0': 'United States',
'admin1': 'New York',
'admin1_abbr': 'NY',
'admin2': 'New York',
'local_admin': 'Manhattan',
'locality': 'New York',
'neighborhood': 'Koreatown',
'category': [ 'tourism', 'transport' ]
'geocoding': {
'id': '34633854',
'layer': 'venue',
'source': 'osm',
'label': 'Empire State Building, Manhattan, NY',
'name': 'Empire State Building',
'country_a': 'USA',
'country': 'United States',
'region': 'New York',
'region_a': 'NY',
'county': 'New York',
'localadmin': 'Manhattan',
'locality': 'New York',
'neighbourhood': 'Koreatown',
'category': ['tourism', 'transport']
}
}
}
]
@ -247,9 +249,12 @@ module.exports.tests.search = function(test, common) {
]
},
'properties': {
'id': 'id1',
'layer': 'type1',
'text': '\'Round Midnight Jazz and Blues Bar, test3, Angel'
'geocoding': {
'id': 'id1',
'layer': 'type1',
'source': 'type1',
'label': '\'Round Midnight Jazz and Blues Bar, test3, Angel'
}
}
},
{
@ -262,9 +267,12 @@ module.exports.tests.search = function(test, common) {
]
},
'properties': {
'id': 'id2',
'layer': 'type2',
'text': 'Blues Cafe, test3, Smithfield'
'geocoding': {
'id': 'id2',
'layer': 'type2',
'source': 'type2',
'label': 'Blues Cafe, test3, Smithfield'
}
}
},
{
@ -277,9 +285,12 @@ module.exports.tests.search = function(test, common) {
]
},
'properties': {
'id': '34633854',
'layer': 'osmway',
'text': 'Empire State Building, Manhattan, NY'
'geocoding': {
'id': '34633854',
'layer': 'venue',
'source': 'osm',
'label': 'Empire State Building, Manhattan, NY'
}
}
}
]

6
test/unit/helper/outputSchema.js

@ -13,10 +13,10 @@ module.exports.tests.interface = function(test, common) {
};
module.exports.tests.valid = function(test, common) {
var valid_keys = ['local_admin', 'locality', 'neighborhood', 'admin2', 'admin1_abbr', 'admin1', 'admin0'];
var valid_keys = ['localadmin', 'locality', 'neighbourhood', 'county', 'region_a', 'region', 'country'];
var default_schema = {
local: ['local_admin', 'locality', 'neighborhood', 'admin2'],
regional: ['admin1_abbr', 'admin1', 'admin0']
local: ['localadmin', 'locality', 'neighbourhood', 'county'],
regional: ['region_a', 'region', 'country']
};
var isValid = function(keys, schema) {

2
test/unit/run.js

@ -8,11 +8,9 @@ var tests = [
require('./controller/search'),
require('./service/mget'),
require('./service/search'),
require('./sanitiser/suggest'),
require('./sanitiser/search'),
require('./sanitiser/reverse'),
require('./sanitiser/place'),
require('./sanitiser/coarse'),
require('./query/indeces'),
require('./query/sort'),
require('./query/search'),

72
test/unit/sanitiser/coarse.js

@ -1,72 +0,0 @@
var coarse = require('../../../sanitiser/coarse'),
_sanitize = coarse.sanitize,
middleware = coarse.middleware,
valid_layers = [ 'admin0', 'admin1', 'admin2', 'neighborhood', 'locality', 'local_admin' ],
defaultClean = require('../sanitiser/_input').defaultClean,
sanitize = function(query, cb) { _sanitize({'query':query}, cb); };
module.exports.tests = {};
module.exports.tests.interface = function(test, common) {
test('sanitize interface', function(t) {
t.equal(typeof sanitize, 'function', 'sanitize is a function');
t.equal(sanitize.length, 2, 'sanitize interface');
t.end();
});
test('middleware interface', function(t) {
t.equal(typeof middleware, 'function', 'middleware is a function');
t.equal(middleware.length, 3, 'sanitizee has a valid middleware');
t.end();
});
};
module.exports.tests.layers = function(test, common) {
test('valid layers', function(t) {
sanitize({ input: 'test', lat: 0, lon: 0 }, function( err, clean ){
t.equal(err, undefined, 'no error');
t.deepEqual(clean.layers, valid_layers, 'layers set correctly');
});
t.end();
});
};
module.exports.tests.middleware_failure = function(test, common) {
test('middleware failure', function(t) {
var res = { status: function( code ){
t.equal(code, 400, 'status set');
}};
var next = function( message ){
var defaultError = 'invalid param \'input\': text length, must be >0';
t.equal(message, defaultError);
t.end();
};
middleware( {}, res, next );
});
};
module.exports.tests.middleware_success = function(test, common) {
test('middleware success', function(t) {
var req = { query: { input: 'test', lat: 0, lon: 0 }};
var clean = defaultClean;
clean.layers = valid_layers;
var next = function( message ){
t.equal(message, undefined, 'no error message set');
t.deepEqual(req.clean, clean);
t.end();
};
middleware( req, undefined, next );
});
};
module.exports.all = function (tape, common) {
function test(name, testFunction) {
return tape('SANTIZE /coarse ' + name, testFunction);
}
for( var testCase in module.exports.tests ){
module.exports.tests[testCase](test, common);
}
};
Loading…
Cancel
Save