const setup = require('../../../middleware/interpolate'); const proxyquire = require('proxyquire').noCallThru(); const _ = require('lodash'); module.exports.tests = {}; module.exports.tests.interface = (test, common) => { test('valid interface', t => { t.equal(typeof setup, 'function', 'setup is a function'); t.equal(typeof setup(), 'function', 'setup returns a controller'); t.end(); }); }; module.exports.tests.early_exit_conditions = (test, common) => { test('should_execute returning false should not call service', t => { t.plan(3, 'should_execute will assert 2 things + 1 for next() was called'); const service = () => { t.fail('service should not have been called'); }; const should_execute = (req, res) => { t.deepEquals(req, { a: 1 }); t.deepEquals(res, { b: 2 }); return false; }; const controller = setup(service, should_execute); controller({ a: 1 }, { b: 2 }, () => { t.pass('next was called'); }); }); }; module.exports.tests.success_conditions = (test, common) => { test('undefined res should not cause errors', t => { const service = (req, res, callback) => { t.fail('should not have been called'); }; const logger = require('pelias-mock-logger')(); const controller = proxyquire('../../../middleware/interpolate', { 'pelias-logger': logger })(service, () => true); const req = { clean: { parsed_text: 'this is req.clean.parsed_text' } }; controller(req, undefined, () => { t.notOk(logger.hasErrorMessages(), 'there shouldn\'t be any error messages'); t.ok(logger.isInfoMessage(/\[interpolation\] \[took\] \d+ ms/)); t.ok(logger.isInfoMessage(/\[interpolation\] \[street_results\] count=0/)); t.end(); }); }); test('undefined res.data should not cause errors', t => { const service = (req, res, callback) => { t.fail('should not have been called'); }; const logger = require('pelias-mock-logger')(); const controller = proxyquire('../../../middleware/interpolate', { 'pelias-logger': logger })(service, () => true); const req = { clean: { parsed_text: 'this is req.clean.parsed_text' } }; const res = {}; controller(req, res, () => { t.notOk(logger.hasErrorMessages(), 'there shouldn\'t be any error messages'); t.ok(logger.isInfoMessage(/\[interpolation\] \[took\] \d+ ms/)); t.ok(logger.isInfoMessage(/\[interpolation\] \[street_results\] count=0/)); t.deepEquals(res, {}); t.end(); }); }); test('only \'street\' layer results should attempt interpolation', t => { const service = (req, res, callback) => { if (res.id === 1) { callback(null, { properties: { number: 17, source: 'Source Abbr 1', source_id: 'source 1 source id', lat: 12.121212, lon: 21.212121 } }); } else if (res.id === 3) { callback(null, { properties: { number: 18, source: 'Source Abbr 2', source_id: 'source 2 source id', lat: 13.131313, lon: 31.313131 } }); } else if (res.id === 4) { callback(null, { properties: { number: 19, source: 'non-OSM/OA', source_id: 'mixed source id', lat: 14.141414, lon: 41.414141 } }); } else { t.fail(`unexpected id ${res.id}`); } }; const logger = require('pelias-mock-logger')(); const controller = proxyquire('../../../middleware/interpolate', { 'pelias-logger': logger, '../helper/type_mapping': { source_mapping: { 'source abbr 1': ['full source name 1'], 'source abbr 2': ['full source name 2'] } } })(service, () => true); const req = { clean: { parsed_text: 'this is req.clean.parsed_text' } }; const res = { data: [ { id: 1, layer: 'street', name: { default: 'street name 1' }, address_parts: {}, // will be replaced source_id: 'original source_id', // bounding_box should be removed bounding_box: {} }, { // this is not a street result and should not attempt interpolation id: 2, layer: 'not street', name: { default: 'name 2' }, address_parts: {} }, { id: 3, layer: 'street', name: { default: 'street name 3' }, address_parts: {} }, { id: 4, layer: 'street', name: { default: 'street name 4' }, address_parts: {} } ] }; controller(req, res, () => { t.notOk(logger.hasErrorMessages(), 'there shouldn\'t be any error messages'); t.ok(logger.isInfoMessage(/\[interpolation\] \[took\] \d+ ms/), 'timing should be info-logged'); t.ok(logger.isInfoMessage(/\[interpolation\] \[street_results\] count=3/), 'street count should be info-logged'); // test debug messages very vaguely to avoid brittle tests t.ok(logger.isDebugMessage(/^\[interpolation\] \[hit\] this is req.clean.parsed_text \{.+?\}$/), 'hits should be debug-logged'); t.deepEquals(res, { data: [ { id: 1, layer: 'address', match_type: 'interpolated', name: { default: '17 street name 1' }, source: 'full source name 1', source_id: 'source 1 source id', address_parts: { number: 17 }, center_point: { lat: 12.121212, lon: 21.212121 } }, { id: 3, layer: 'address', match_type: 'interpolated', name: { default: '18 street name 3' }, source: 'full source name 2', source_id: 'source 2 source id', address_parts: { number: 18 }, center_point: { lat: 13.131313, lon: 31.313131 } }, { id: 4, layer: 'address', match_type: 'interpolated', name: { default: '19 street name 4' }, source: 'mixed', source_id: 'mixed source id', address_parts: { number: 19 }, center_point: { lat: 14.141414, lon: 41.414141 } }, { id: 2, layer: 'not street', name: { default: 'name 2' }, address_parts: {} } ] }, 'hits should be mapped in and res.data sorted with addresses first and non-addresses last'); t.end(); }); }); test('service call returning error should not map in interpolated results for non-errors', t => { const service = (req, res, callback) => { if (res.id === 1) { callback(null, { properties: { number: 17, source: 'Source Abbr 1', source_id: 'source 1 source id', lat: 12.121212, lon: 21.212121 } }); } else if (res.id === 2) { callback('id 2 produced an error string', {}); } else if (res.id === 3) { callback({ message: 'id 3 produced an error object' }, {}); } else if (res.id === 4) { callback(null, { properties: { number: 18, source: 'Source Abbr 4', source_id: 'source 4 source id', lat: 13.131313, lon: 31.313131 } }); } else { console.error(res.id); t.fail(`unexpected id ${res.id}`); } }; const logger = require('pelias-mock-logger')(); const controller = proxyquire('../../../middleware/interpolate', { 'pelias-logger': logger, '../helper/type_mapping': { source_mapping: { 'source abbr 1': ['full source name 1'], 'source abbr 4': ['full source name 4'] } } })(service, () => true); const req = { clean: { parsed_text: 'this is req.clean.parsed_text' } }; const res = { data: [ { id: 1, layer: 'street', name: { default: 'street name 1' }, address_parts: {} }, { id: 2, layer: 'street', name: { default: 'street name 2' }, address_parts: {} }, { id: 3, layer: 'street', name: { default: 'street name 3' }, address_parts: {} }, { id: 4, layer: 'street', name: { default: 'street name 4' }, address_parts: {} } ] }; controller(req, res, () => { t.deepEquals(logger.getErrorMessages(), [ '[middleware:interpolation] id 2 produced an error string', '[middleware:interpolation] id 3 produced an error object' ]); t.ok(logger.isInfoMessage(/\[interpolation\] \[took\] \d+ ms/), 'timing should be info-logged'); t.ok(logger.isInfoMessage(/\[interpolation\] \[street_results\] count=4/), 'street count should be info-logged'); // test debug messages very vaguely to avoid brittle tests t.ok(logger.isDebugMessage(/^\[interpolation\] \[hit\] this is req.clean.parsed_text \{.+?\}$/), 'hits should be debug-logged'); t.deepEquals(res, { data: [ { id: 1, layer: 'address', match_type: 'interpolated', name: { default: '17 street name 1' }, source: 'full source name 1', source_id: 'source 1 source id', address_parts: { number: 17 }, center_point: { lat: 12.121212, lon: 21.212121 } }, { id: 4, layer: 'address', match_type: 'interpolated', name: { default: '18 street name 4' }, source: 'full source name 4', source_id: 'source 4 source id', address_parts: { number: 18 }, center_point: { lat: 13.131313, lon: 31.313131 } }, { id: 2, layer: 'street', name: { default: 'street name 2' }, address_parts: {} }, { id: 3, layer: 'street', name: { default: 'street name 3' }, address_parts: {} } ] }, 'hits should be mapped in and res.data sorted with addresses first and non-addresses last'); t.end(); }); }); test('interpolation result without source_id should remove all together', t => { const service = (req, res, callback) => { if (res.id === 1) { callback(null, { properties: { number: 17, source: 'OA', lat: 12.121212, lon: 21.212121 } }); } else { t.fail(`should not have been called with id ${res.id}`); } }; const logger = require('pelias-mock-logger')(); const controller = proxyquire('../../../middleware/interpolate', { 'pelias-logger': logger })(service, () => true); const req = { clean: { parsed_text: 'this is req.clean.parsed_text' } }; const res = { data: [ // doc with 2 layer names that will be changed { id: 1, layer: 'street', name: { default: 'street name 1' }, // will be removed source_id: 'original source_id', address_parts: {} } ] }; controller(req, res, () => { t.notOk(logger.hasErrorMessages(), 'there shouldn\'t be any error messages'); t.ok(logger.isInfoMessage(/\[interpolation\] \[took\] \d+ ms/)); t.ok(logger.isInfoMessage(/\[interpolation\] \[street_results\] count=1/), 'street count should be info-logged'); t.deepEquals(res, { data: [ { id: 1, layer: 'address', match_type: 'interpolated', name: { default: '17 street name 1' }, source: 'openaddresses', address_parts: { number: 17 }, center_point: { lat: 12.121212, lon: 21.212121 } } ] }, 'interpolation result did not have source_id so removed from source result'); t.end(); }); }); test('undefined results should be skipped and not be fatal', t => { const service = (req, res, callback) => { if (res.id === 1) { callback(null, undefined); } else if (res.id === 2) { callback(null, { properties: { number: 18, source: 'OA', source_id: 'openaddresses source id', lat: 13.131313, lon: 31.313131 } }); } else { t.fail(`should not have been called with id ${res.id}`); } }; const logger = require('pelias-mock-logger')(); const controller = proxyquire('../../../middleware/interpolate', { 'pelias-logger': logger })(service, () => true); const req = { clean: { parsed_text: 'this is req.clean.parsed_text' } }; const res = { data: [ // doc with 2 layer names that will be changed { id: 1, layer: 'street', name: { default: 'street name 1' }, address_parts: {} }, { id: 2, layer: 'street', name: { default: 'street name 2' }, address_parts: {} } ] }; controller(req, res, () => { t.notOk(logger.hasErrorMessages(), 'there shouldn\'t be any error messages'); t.ok(logger.isInfoMessage(/\[interpolation\] \[took\] \d+ ms/)); t.ok(logger.isInfoMessage(/\[interpolation\] \[street_results\] count=2/), 'street count should be info-logged'); // test debug messages very vaguely to avoid brittle tests t.ok(logger.isDebugMessage('[interpolation] [miss] this is req.clean.parsed_text')); t.deepEquals(res, { data: [ { id: 2, layer: 'address', match_type: 'interpolated', name: { default: '18 street name 2' }, source: 'openaddresses', source_id: 'openaddresses source id', address_parts: { number: 18 }, center_point: { lat: 13.131313, lon: 31.313131 } }, { id: 1, layer: 'street', name: { default: 'street name 1' }, address_parts: {} } ] }, 'only hits should have been mapped in'); t.end(); }); }); test('results missing \'properties\' should be skipped and not be fatal', t => { const service = (req, res, callback) => { if (res.id === 1) { callback(null, {}); } else if (res.id === 2) { callback(null, { properties: { number: 18, source: 'OA', source_id: 'openaddresses source id', lat: 13.131313, lon: 31.313131 } }); } else { t.fail(`should not have been called with id ${res.id}`); } }; const logger = require('pelias-mock-logger')(); const controller = proxyquire('../../../middleware/interpolate', { 'pelias-logger': logger })(service, () => true); const req = { clean: { parsed_text: 'this is req.clean.parsed_text' } }; const res = { data: [ // doc with 2 layer names that will be changed { id: 1, layer: 'street', name: { default: 'street name 1' }, address_parts: {} }, { id: 2, layer: 'street', name: { default: 'street name 2' }, address_parts: {} } ] }; controller(req, res, () => { t.notOk(logger.hasErrorMessages(), 'there shouldn\'t be any error messages'); t.ok(logger.isInfoMessage(/\[interpolation\] \[took\] \d+ ms/)); t.ok(logger.isInfoMessage(/\[interpolation\] \[street_results\] count=2/), 'street count should be info-logged'); // test debug messages very vaguely to avoid brittle tests t.ok(logger.isDebugMessage('[interpolation] [miss] this is req.clean.parsed_text')); t.deepEquals(res, { data: [ { id: 2, layer: 'address', match_type: 'interpolated', name: { default: '18 street name 2' }, source: 'openaddresses', source_id: 'openaddresses source id', address_parts: { number: 18 }, center_point: { lat: 13.131313, lon: 31.313131 } }, { id: 1, layer: 'street', name: { default: 'street name 1' }, address_parts: {} } ] }); t.end(); }, 'only hits should have been mapped in'); }); }; module.exports.all = function (tape, common) { function test(name, testFunction) { return tape(`[middleware] interpolate: ${name}`, testFunction); } for( var testCase in module.exports.tests ){ module.exports.tests[testCase](test, common); } };