diff --git a/package.json b/package.json index 8018919c..3acd057d 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "geolib": "^2.0.18", "geopipes-elasticsearch-backend": "^0.2.0", "lodash": "^3.10.1", + "iso3166-1": "^0.2.3", "markdown": "0.5.0", "microtime": "1.4.0", "morgan": "1.5.2", diff --git a/sanitiser/_boundary_country.js b/sanitiser/_boundary_country.js new file mode 100644 index 00000000..05760f73 --- /dev/null +++ b/sanitiser/_boundary_country.js @@ -0,0 +1,40 @@ +var isObject = require('is-object'); +var iso3166 = require('iso3166-1'); + +function sanitize(raw, clean) { + // error & warning messages + var messages = { errors: [], warnings: [] }; + + // init clean.boundary (if not already init) + clean.boundary = clean.boundary || {}; + + if (raw.boundary && raw.boundary.country) { + var country = raw.boundary.country + + if (typeof country !== 'string') { + messages.warnings.push('boundary.country is not a string'); + clean.boundary.country = undefined; + } + else if (!containsIsoCode(country.toUpperCase())) { + messages.warnings.push(country + ' is not a valid ISO2/ISO3 country code'); + clean.boundary.country = undefined; + } + else { + clean.boundary.country = iso3166.to2(country.toUpperCase()); + } + + } else { + clean.boundary.country = undefined; + } + + return messages; + +} + +function containsIsoCode(isoCode) { + return iso3166.list().filter(function(row) { + return row.alpha2 === isoCode || row.alpha3 === isoCode; + }).length > 0; +} + +module.exports = sanitize; diff --git a/test/unit/run.js b/test/unit/run.js index 761d2fef..6d86509b 100644 --- a/test/unit/run.js +++ b/test/unit/run.js @@ -9,6 +9,7 @@ var tests = [ require('./service/mget'), require('./service/search'), require('./sanitiser/_sources'), + require('./sanitiser/_boundary_country'), require('./sanitiser/search'), require('./sanitiser/_layers'), require('./sanitiser/reverse'), diff --git a/test/unit/sanitiser/_boundary_country.js b/test/unit/sanitiser/_boundary_country.js new file mode 100644 index 00000000..68816f3d --- /dev/null +++ b/test/unit/sanitiser/_boundary_country.js @@ -0,0 +1,88 @@ +var sanitize = require('../../../sanitiser/_boundary_country'); + +module.exports.tests = {}; + +module.exports.tests.sanitize_boundary_country = function(test, common) { + test('raw w/o boundary should set boundary.country undefined', function(t) { + var raw = { }; + var clean = {}; + var errorsAndWarnings = sanitize(raw, clean); + t.equals(clean.boundary.country, undefined, 'should be undefined'); + t.deepEquals(errorsAndWarnings, { errors: [], warnings: [] }, 'no warnings or errors'); + t.end(); + }); + + test('boundary w/o country in raw should leave boundary.country undefined', function(t) { + var raw = { boundary: {} }; + var clean = {}; + var errorsAndWarnings = sanitize(raw, clean); + t.equals(clean.boundary.country, undefined, 'should be undefined'); + t.deepEquals(errorsAndWarnings, { errors: [], warnings: [] }, 'no warnings or errors'); + t.end(); + }); + + test('boundary.country explicitly undefined in raw should leave boundary.country undefined', function(t) { + var raw = { boundary: {country: undefined } }; + var clean = {}; + var errorsAndWarnings = sanitize(raw, clean); + t.equals(clean.boundary.country, undefined, 'should be undefined'); + t.deepEquals(errorsAndWarnings, { errors: [], warnings: [] }, 'no warnings or errors'); + t.end(); + }); + + test('non-string boundary.country should set boundary.country to undefined and return warning', function(t) { + var raw = { boundary: {country: ['this isn\'t a string primitive'] } }; + var clean = {}; + var errorsAndWarnings = sanitize(raw, clean); + t.equals(clean.boundary.country, undefined, 'should be undefined'); + t.deepEquals(errorsAndWarnings, { errors: [], warnings: ['boundary.country is not a string'] }, 'non-string country warning'); + t.end(); + }); + + test('iso2 boundary.country in raw should set boundary.country to ISO2 uppercased', function(t) { + var raw = { boundary: {country: 'aq'} }; + var clean = {}; + var errorsAndWarnings = sanitize(raw, clean); + t.equals(clean.boundary.country, 'AQ', 'should be uppercased ISO2'); + t.deepEquals(errorsAndWarnings, { errors: [], warnings: [] }, 'no warnings or errors'); + t.end(); + }); + + test('iso3 boundary.country in raw should set boundary.country to matching ISO2 uppercased', function(t) { + var raw = { boundary: {country: 'aTa'} }; + var clean = {}; + var errorsAndWarnings = sanitize(raw, clean); + t.equals(clean.boundary.country, 'AQ', 'should be uppercased ISO2'); + t.deepEquals(errorsAndWarnings, { errors: [], warnings: [] }, 'no warnings or errors'); + t.end(); + }); + + test('unknown 2-character boundary.country should set boundary.country to undefined', function(t) { + var raw = { boundary: {country: 'zq'} }; + var clean = {}; + var errorsAndWarnings = sanitize(raw, clean); + t.equals(clean.boundary.country, undefined, 'should be undefined'); + t.deepEquals(errorsAndWarnings, { errors: [], warnings: ['zq is not a valid ISO2/ISO3 country code'] }, 'country not found warning`'); + t.end(); + }); + + test('unknown 3-character boundary.country should set boundary.country to undefined', function(t) { + var raw = { boundary: {country: 'zqx'} }; + var clean = {}; + var errorsAndWarnings = sanitize(raw, clean); + t.equals(clean.boundary.country, undefined, 'should be undefined'); + t.deepEquals(errorsAndWarnings, { errors: [], warnings: ['zqx is not a valid ISO2/ISO3 country code'] }, 'country not found warning`'); + t.end(); + }); + +}; + +module.exports.all = function (tape, common) { + function test(name, testFunction) { + return tape('SANTIZE _boundary_country ' + name, testFunction); + } + + for( var testCase in module.exports.tests ){ + module.exports.tests[testCase](test, common); + } +};