diff --git a/controller/libpostal.js b/controller/libpostal.js new file mode 100644 index 00000000..5e567665 --- /dev/null +++ b/controller/libpostal.js @@ -0,0 +1,30 @@ +const text_analyzer = require('pelias-text-analyzer'); +const _ = require('lodash'); +const iso3166 = require('iso3166-1'); + +function setup(should_execute) { + function controller( req, res, next ){ + // bail early if req/res don't pass conditions for execution + if (!should_execute(req, res)) { + return next(); + } + + // parse text with query parser + const parsed_text = text_analyzer.parse(req.clean.text); + + if (!_.isUndefined(parsed_text)) { + if (_.has(parsed_text, 'country') && iso3166.is2(_.toUpper(parsed_text.country))) { + parsed_text.country = iso3166.to3(_.toUpper(parsed_text.country)); + } + + req.clean.parsed_text = parsed_text; + } + + return next(); + + } + + return controller; +} + +module.exports = setup; diff --git a/test/unit/controller/libpostal.js b/test/unit/controller/libpostal.js new file mode 100644 index 00000000..e0d012a9 --- /dev/null +++ b/test/unit/controller/libpostal.js @@ -0,0 +1,290 @@ +'use strict'; + +const proxyquire = require('proxyquire').noCallThru(); +const _ = require('lodash'); + +module.exports.tests = {}; + +module.exports.tests.interface = (test, common) => { + test('valid interface', t => { + const controller = proxyquire('../../../controller/libpostal', { + 'pelias-text-analyzer': { + parse: () => undefined + } + }); + + t.equal(typeof controller, 'function', 'libpostal is a function'); + t.equal(typeof controller(), 'function', 'libpostal returns a controller'); + t.end(); + + }); + +}; + +module.exports.tests.should_execute = (test, common) => { + test('should_execute returning false should not call text-analyzer', t => { + const should_execute = (req, res) => { + // req and res should be passed to should_execute + t.deepEquals(req, { + clean: { + text: 'original query' + } + }); + t.deepEquals(res, { b: 2 }); + return false; + }; + + const controller = proxyquire('../../../controller/libpostal', { + 'pelias-text-analyzer': { + parse: () => { + t.fail('parse should not have been called'); + } + } + })(should_execute); + + const req = { + clean: { + text: 'original query' + } + }; + const res = { b: 2 }; + + controller(req, res, () => { + t.deepEquals(req, { + clean: { + text: 'original query' + } + }, 'req should not have been modified'); + t.deepEquals(res, { b: 2 }); + t.end(); + }); + + }); + + test('should_execute returning false should not call text-analyzer', t => { + t.plan(5); + + const should_execute = (req, res) => { + // req and res should be passed to should_execute + t.deepEquals(req, { + clean: { + text: 'original query' + } + }); + t.deepEquals(res, { b: 2 }); + return true; + }; + + const controller = proxyquire('../../../controller/libpostal', { + 'pelias-text-analyzer': { + parse: (query) => { + t.equals(query, 'original query'); + return undefined; + } + } + })(should_execute); + + const req = { + clean: { + text: 'original query' + } + }; + const res = { b: 2 }; + + controller(req, res, () => { + t.deepEquals(req, { + clean: { + text: 'original query' + } + }, 'req should not have been modified'); + t.deepEquals(res, { b: 2 }); + t.end(); + }); + + }); + +}; + +module.exports.tests.parse_is_called = (test, common) => { + test('parse returning undefined should not overwrite clean.parsed_text', t => { + const controller = proxyquire('../../../controller/libpostal', { + 'pelias-text-analyzer': { + parse: () => undefined + } + })(() => true); + + const req = { + clean: { + parsed_text: 'original parsed_text' + } + }; + const res = 'this is the response'; + + controller(req, res, () => { + t.deepEquals(req, { + clean: { + parsed_text: 'original parsed_text' + } + }); + t.deepEquals(res, 'this is the response'); + t.end(); + }); + + }); + + test('parse returning something should overwrite clean.parsed_text', t => { + const controller = proxyquire('../../../controller/libpostal', { + 'pelias-text-analyzer': { + parse: () => 'replacement parsed_text' + } + })(() => true); + + const req = { + clean: { + parsed_text: 'original parsed_text' + } + }; + const res = 'this is the response'; + + controller(req, res, () => { + t.deepEquals(req, { + clean: { + parsed_text: 'replacement parsed_text' + } + }); + t.deepEquals(res, 'this is the response'); + t.end(); + }); + + }); + +}; + +module.exports.tests.iso2_conversion = (test, common) => { + test('no country in parse response should not leave country unset', t => { + const controller = proxyquire('../../../controller/libpostal', { + 'pelias-text-analyzer': { + parse: () => ({ + locality: 'this is the locality' + }) + }, + 'iso3166-1': { + is2: () => t.fail('should not have been called'), + to3: () => t.fail('should not have been called') + } + })(() => true); + + const req = { + clean: { + parsed_text: 'original parsed_text' + } + }; + const res = 'this is the response'; + + controller(req, res, () => { + t.deepEquals(req, { + clean: { + parsed_text: { + locality: 'this is the locality' + } + } + }); + t.deepEquals(res, 'this is the response'); + t.end(); + }); + + }); + + test('unknown country should not be converted', t => { + t.plan(3); + + const controller = proxyquire('../../../controller/libpostal', { + 'pelias-text-analyzer': { + parse: () => ({ + country: 'unknown country code' + }) + }, + 'iso3166-1': { + is2: country => { + t.equals(country, 'UNKNOWN COUNTRY CODE'); + return false; + }, + to3: () => t.fail('should not have been called') + } + })(() => true); + + const req = { + clean: { + parsed_text: 'original parsed_text' + } + }; + const res = 'this is the response'; + + controller(req, res, () => { + t.deepEquals(req, { + clean: { + parsed_text: { + country: 'unknown country code' + } + } + }); + t.deepEquals(res, 'this is the response'); + t.end(); + }); + + }); + + test('ISO2 country should be converted to ISO3', t => { + t.plan(4); + + const controller = proxyquire('../../../controller/libpostal', { + 'pelias-text-analyzer': { + parse: () => ({ + country: 'ISO2 COUNTRY CODE' + }) + }, + 'iso3166-1': { + is2: country => { + t.equals(country, 'ISO2 COUNTRY CODE'); + return true; + }, + to3: country => { + t.equals(country, 'ISO2 COUNTRY CODE'); + return 'ISO3 COUNTRY CODE'; + } + } + })(() => true); + + const req = { + clean: { + parsed_text: 'original parsed_text' + } + }; + const res = 'this is the response'; + + controller(req, res, () => { + t.deepEquals(req, { + clean: { + parsed_text: { + country: 'ISO3 COUNTRY CODE' + } + } + }); + t.deepEquals(res, 'this is the response'); + t.end(); + }); + + }); + +}; + +module.exports.all = (tape, common) => { + + function test(name, testFunction) { + return tape(`GET /libpostal ${name}`, testFunction); + } + + for( const testCase in module.exports.tests ){ + module.exports.tests[testCase](test, common); + } +};