Browse Source

added placeholder service support to API for admin-only queries

Adds a controller, service (which could probably be made more generic and share with pointinpolygon), and predicates for determining when placeholder should be called.

This also renames `is_pip_service_enabled.js` to `is_service_enabled.js` since it's generic.  If placeholder returns a non-empty response then `res.data` is populated, otherwise `res.data` is not populated, allowing fallback to existing production behavior.
pull/850/head
Stephen Hess 8 years ago
parent
commit
f14400ebce
  1. 73
      controller/placeholder.js
  2. 13
      controller/predicates/is_admin_only_analysis.js
  3. 7
      controller/predicates/is_pip_service_enabled.js
  4. 5
      controller/predicates/is_service_enabled.js
  5. 4
      package.json
  6. 14
      routes/v1.js
  7. 62
      service/placeholder.js
  8. 378
      test/unit/controller/placeholder.js
  9. 77
      test/unit/controller/predicates/is_admin_only_analysis.js
  10. 12
      test/unit/controller/predicates/is_service_enabled.js
  11. 5
      test/unit/run.js
  12. 193
      test/unit/service/placeholder.js

73
controller/placeholder.js

@ -0,0 +1,73 @@
'use strict';
const _ = require('lodash');
const logger = require('pelias-logger').get('api');
const logging = require( '../helper/logging' );
const Document = require('pelias-model').Document;
function synthesizeDocs(result) {
return result.lineage.map((hierarchy) => {
const doc = new Document('whosonfirst', result.placetype, result.id.toString());
doc.setName('default', result.name);
doc.setCentroid( { lat: result.geom.lat, lon: result.geom.lon } );
const parsedBoundingBox = result.geom.bbox.split(',').map(parseFloat);
doc.setBoundingBox({
upperLeft: {
lat: parsedBoundingBox[3],
lon: parsedBoundingBox[0]
},
lowerRight: {
lat: parsedBoundingBox[1],
lon: parsedBoundingBox[2]
}
});
Object.keys(hierarchy)
.filter(doc.isSupportedParent)
.filter((placetype) => { return !_.isEmpty(_.trim(hierarchy[placetype].name)); } )
.forEach((placetype) => {
if (hierarchy[placetype].hasOwnProperty('abbr') && placetype === 'country') {
doc.setAlpha3(hierarchy[placetype].abbr);
}
doc.addParent(
placetype,
hierarchy[placetype].name,
hierarchy[placetype].id.toString(),
hierarchy[placetype].abbr);
});
const esDoc = doc.toESDocument();
esDoc.data._id = esDoc._id;
esDoc.data._type = esDoc._type;
return esDoc.data;
});
}
function setup(placeholderService, should_execute) {
function controller( req, res, next ){
if (!should_execute(req, res)) {
return next();
}
placeholderService.search(req.clean.text, req.clean.lang.iso6393, (err, results) => {
res.meta = {};
res.data = _.flatten(results.map((result) => {
return synthesizeDocs(result);
}));
return next();
});
}
return controller;
}
module.exports = setup;

13
controller/predicates/is_admin_only_analysis.js

@ -0,0 +1,13 @@
const _ = require('lodash');
module.exports = (request, response) => {
if (!request.clean.hasOwnProperty('parsed_text')) {
return false;
}
// return true only if all non-admin properties of parsed_text are empty
return ['number', 'street', 'query', 'category'].every((prop) => {
return _.isEmpty(request.clean.parsed_text[prop]);
});
};

7
controller/predicates/is_pip_service_enabled.js

@ -1,7 +0,0 @@
module.exports = (uri) => {
// this predicate relies upon the fact that the schema has already validated
// that api.pipService is a URI-formatted string
return (request, response) => {
return uri !== undefined;
};
};

5
controller/predicates/is_service_enabled.js

@ -0,0 +1,5 @@
module.exports = (uri) => {
return (request, response) => {
return uri !== undefined;
};
};

4
package.json

@ -39,6 +39,7 @@
"dependencies": { "dependencies": {
"addressit": "1.5.0", "addressit": "1.5.0",
"async": "^2.0.0", "async": "^2.0.0",
"bl": "^1.2.0",
"check-types": "^7.0.0", "check-types": "^7.0.0",
"elasticsearch": "^12.0.1", "elasticsearch": "^12.0.1",
"elasticsearch-exceptions": "0.0.4", "elasticsearch-exceptions": "0.0.4",
@ -57,6 +58,7 @@
"morgan": "^1.8.2", "morgan": "^1.8.2",
"pelias-config": "2.10.0", "pelias-config": "2.10.0",
"pelias-categories": "1.2.0", "pelias-categories": "1.2.0",
"pelias-config": "2.10.0",
"pelias-labels": "1.6.0", "pelias-labels": "1.6.0",
"pelias-logger": "0.2.0", "pelias-logger": "0.2.0",
"pelias-mock-logger": "^1.0.1", "pelias-mock-logger": "^1.0.1",
@ -64,8 +66,8 @@
"pelias-query": "8.15.0", "pelias-query": "8.15.0",
"pelias-text-analyzer": "1.8.2", "pelias-text-analyzer": "1.8.2",
"predicates": "^1.0.1", "predicates": "^1.0.1",
"request": "^2.81.0",
"retry": "^0.10.1", "retry": "^0.10.1",
"request": "^2.79.0",
"stats-lite": "^2.0.4", "stats-lite": "^2.0.4",
"superagent": "^3.2.1", "superagent": "^3.2.1",
"through2": "^2.0.3" "through2": "^2.0.3"

14
routes/v1.js

@ -28,6 +28,7 @@ var controllers = {
coarse_reverse: require('../controller/coarse_reverse'), coarse_reverse: require('../controller/coarse_reverse'),
mdToHTML: require('../controller/markdownToHtml'), mdToHTML: require('../controller/markdownToHtml'),
place: require('../controller/place'), place: require('../controller/place'),
placeholder: require('../controller/placeholder'),
search: require('../controller/search'), search: require('../controller/search'),
status: require('../controller/status') status: require('../controller/status')
}; };
@ -66,6 +67,7 @@ var postProc = {
const hasResponseData = require('../controller/predicates/has_response_data'); const hasResponseData = require('../controller/predicates/has_response_data');
const hasRequestErrors = require('../controller/predicates/has_request_errors'); const hasRequestErrors = require('../controller/predicates/has_request_errors');
const isCoarseReverse = require('../controller/predicates/is_coarse_reverse'); const isCoarseReverse = require('../controller/predicates/is_coarse_reverse');
const isAdminOnlyAnalysis = require('../controller/predicates/is_admin_only_analysis');
// shorthand for standard early-exit conditions // shorthand for standard early-exit conditions
const hasResponseDataOrRequestErrors = any(hasResponseData, hasRequestErrors); const hasResponseDataOrRequestErrors = any(hasResponseData, hasRequestErrors);
@ -79,13 +81,20 @@ const hasResponseDataOrRequestErrors = any(hasResponseData, hasRequestErrors);
function addRoutes(app, peliasConfig) { function addRoutes(app, peliasConfig) {
const esclient = elasticsearch.Client(peliasConfig.esclient); const esclient = elasticsearch.Client(peliasConfig.esclient);
const isPipServiceEnabled = require('../controller/predicates/is_pip_service_enabled')(peliasConfig.api.pipService); const isPipServiceEnabled = require('../controller/predicates/is_service_enabled')(peliasConfig.api.pipService);
const isPlaceholderServiceEnabled = require('../controller/predicates/is_service_enabled')(peliasConfig.api.placeholderService);
const pipService = require('../service/pointinpolygon')(peliasConfig.api.pipService); const pipService = require('../service/pointinpolygon')(peliasConfig.api.pipService);
const placeholderService = require('../service/placeholder')('http://localhost:3000/parser');
const coarse_reverse_should_execute = all( const coarse_reverse_should_execute = all(
not(hasRequestErrors), isPipServiceEnabled, isCoarseReverse not(hasRequestErrors), isPipServiceEnabled, isCoarseReverse
); );
const placeholderShouldExecute = all(
not(hasResponseDataOrRequestErrors), isPlaceholderServiceEnabled, isAdminOnlyAnalysis
);
// execute under the following conditions: // execute under the following conditions:
// - there are no errors or data // - there are no errors or data
// - request is not coarse OR pip service is disabled // - request is not coarse OR pip service is disabled
@ -112,6 +121,7 @@ function addRoutes(app, peliasConfig) {
sanitizers.search.middleware, sanitizers.search.middleware,
middleware.requestLanguage, middleware.requestLanguage,
middleware.calcSize(), middleware.calcSize(),
controllers.placeholder(placeholderService, placeholderShouldExecute),
// 3rd parameter is which query module to use, use fallback/geodisambiguation // 3rd parameter is which query module to use, use fallback/geodisambiguation
// first, then use original search strategy if first query didn't return anything // first, then use original search strategy if first query didn't return anything
controllers.search(peliasConfig.api, esclient, queries.libpostal, not(hasResponseDataOrRequestErrors)), controllers.search(peliasConfig.api, esclient, queries.libpostal, not(hasResponseDataOrRequestErrors)),
@ -143,7 +153,7 @@ function addRoutes(app, peliasConfig) {
postProc.confidenceScores(peliasConfig.api), postProc.confidenceScores(peliasConfig.api),
postProc.confidenceScoresFallback(), postProc.confidenceScoresFallback(),
postProc.interpolate(), postProc.interpolate(),
postProc.dedupe(), postProc.dedupe(),
postProc.accuracy(), postProc.accuracy(),
postProc.localNamingConventions(), postProc.localNamingConventions(),
postProc.renamePlacenames(), postProc.renamePlacenames(),

62
service/placeholder.js

@ -0,0 +1,62 @@
const request = require('request');
const bl = require('bl');
const _ = require('lodash');
const logger = require( 'pelias-logger' ).get( 'placeholder' );
module.exports = function setup(url) {
if (_.isEmpty(url)) {
logger.warn('placeholder service disabled');
return {
search: (text, lang, callback) => {
callback(`placeholder service disabled`);
}
};
}
logger.info(`using placeholder service at ${url}`);
return {
search: (text, lang, callback) => {
const requestUrl = `${url}/search?text=${text}&lang=${lang}`;
request
.get(requestUrl)
.on('response', (response) => {
// pipe the response thru bl which will accumulate the entire body
response.pipe(bl((err, data) => {
if (response.statusCode === 200) {
// parse and return w/o error unless response wasn't JSON
try {
const parsed = JSON.parse(data);
return callback(null, parsed);
}
catch (err) {
logger.error(`${encodeURI(requestUrl)} could not parse response: ${data}`);
return callback(`${encodeURI(requestUrl)} could not parse response: ${data}`);
}
}
else if (response.statusCode === 404) {
// placeholder returns a 404 when no results are found which
// should be handled differently since it's technically not an error
logger.debug(`returned 0 results for '${text}'`);
return callback(null, []);
}
else {
// otherwise there was a non-200/404 status so handle generically
logger.error(`${encodeURI(requestUrl)} returned status ${response.statusCode}: ${data}`);
return callback(`${encodeURI(requestUrl)} returned status ${response.statusCode}: ${data}`);
}
}));
})
.on('error', (err) => {
logger.error(JSON.stringify(err));
callback(err);
});
}
};
};

378
test/unit/controller/placeholder.js

@ -0,0 +1,378 @@
'use strict';
const placeholder = require('../../../controller/placeholder');
const proxyquire = require('proxyquire').noCallThru();
module.exports.tests = {};
module.exports.tests.interface = (test, common) => {
test('valid interface', (t) => {
t.equal(typeof placeholder, 'function', 'placeholder is a function');
t.equal(typeof placeholder(), 'function', 'placeholder returns a controller');
t.end();
});
};
module.exports.tests.should_execute_failure = function(test, common) {
test('should_execute returning false should return without calling service', (t) => {
let placeholderService_was_called = false;
const placeholderService = {
search: () => {
placeholderService_was_called = true;
}
};
const should_execute = (req, res) => {
// req and res should be passed to should_execute
t.deepEquals(req, { a: 1 });
t.deepEquals(res, { b: 2 });
return false;
};
const controller = placeholder(placeholderService, should_execute);
const req = { a: 1 };
const res = { b: 2 };
controller(req, res, () => {
t.notOk(placeholderService_was_called);
t.end();
});
});
};
module.exports.tests.success = function(test, common) {
test('should_execute returning true should call service', (t) => {
let placeholderService_was_called = false;
const placeholderService = {
search: (text, language, callback) => {
t.equals(text, 'query value');
t.equals(language, 'language value');
placeholderService_was_called = true;
callback(null, []);
}
};
const should_execute = (req, res) => {
return true;
};
const controller = placeholder(placeholderService, should_execute);
const req = {
clean: {
text: 'query value',
lang: {
iso6393: 'language value'
}
}
};
const res = { b: 2 };
controller(req, res, () => {
t.ok(placeholderService_was_called);
t.end();
});
});
test('response from service should be converted', (t) => {
let placeholderService_was_called = false;
const placeholder_response = [
{
id: 123,
name: 'name 1',
placetype: 'neighbourhood',
lineage: [
{
country: {
id: 1,
name: 'country name 1'
},
dependency: {
id: 2,
name: 'dependency name 1'
},
macroregion: {
id: 3,
name: 'macroregion name 1'
},
region: {
id: 4,
name: 'region name 1'
},
macrocounty: {
id: 5,
name: 'macrocounty name 1'
},
county: {
id: 6,
name: 'county name 1'
},
localadmin: {
id: 7,
name: 'localadmin name 1'
},
locality: {
id: 8,
name: 'locality name 1'
},
borough: {
id: 9,
name: 'borough name 1'
},
neighbourhood: {
id: 10,
name: 'neighbourhood name 1'
}
},
{
country: {
id: 11,
name: 'country name 2',
abbr: 'XYZ'
},
dependency: {
id: 12,
name: 'dependency name 2',
abbr: 'dependency abbr 2'
},
macroregion: {
id: 13,
name: 'macroregion name 2',
abbr: 'macroregion abbr 2'
},
region: {
id: 14,
name: 'region name 2',
abbr: 'region abbr 2'
},
macrocounty: {
id: 15,
name: 'macrocounty name 2',
abbr: 'macrocounty abbr 2'
},
county: {
id: 16,
name: 'county name 2',
abbr: 'county abbr 2'
},
localadmin: {
id: 17,
name: 'localadmin name 2',
abbr: 'localadmin abbr 2'
},
locality: {
id: 18,
name: 'locality name 2',
abbr: 'locality abbr 2'
},
borough: {
id: 19,
name: 'borough name 2',
abbr: 'borough abbr 2'
},
neighbourhood: {
id: 20,
name: 'neighbourhood name 2',
abbr: 'neighbourhood abbr 2'
}
}
],
geom: {
area: 12.34,
bbox: '21.212121,12.121212,31.313131,13.131313',
lat: 14.141414,
lon: 41.414141
}
},
{
id: 456,
name: 'name 3',
placetype: 'locality',
lineage: [ {} ],
geom: {
area: 23.45,
bbox: '51.515151,15.151515,61.616161,16.161616',
lat: 17.171717,
lon: 71.717171
}
}
];
const placeholderService = {
search: (text, language, callback) => {
t.equals(text, 'query value');
t.equals(language, 'language value');
placeholderService_was_called = true;
callback(null, placeholder_response);
}
};
const should_execute = (req, res) => {
return true;
};
const controller = placeholder(placeholderService, should_execute);
const req = {
clean: {
text: 'query value',
lang: {
iso6393: 'language value'
}
}
};
const res = { };
const expected_res = {
meta: {},
data: [
{
_id: '123',
_type: 'neighbourhood',
layer: 'neighbourhood',
source: 'whosonfirst',
source_id: '123',
center_point: {
lat: 14.141414,
lon: 41.414141
},
bounding_box: '{"min_lat":12.121212,"max_lat":13.131313,"min_lon":21.212121,"max_lon":31.313131}',
name: {
'default': 'name 1'
},
phrase: {
'default': 'name 1'
},
parent: {
neighbourhood: ['neighbourhood name 1'],
neighbourhood_id: ['10'],
neighbourhood_a: [null],
borough: ['borough name 1'],
borough_id: ['9'],
borough_a: [null],
locality: ['locality name 1'],
locality_id: ['8'],
locality_a: [null],
localadmin: ['localadmin name 1'],
localadmin_id: ['7'],
localadmin_a: [null],
county: ['county name 1'],
county_id: ['6'],
county_a: [null],
macrocounty: ['macrocounty name 1'],
macrocounty_id: ['5'],
macrocounty_a: [null],
region: ['region name 1'],
region_id: ['4'],
region_a: [null],
macroregion: ['macroregion name 1'],
macroregion_id: ['3'],
macroregion_a: [null],
dependency: ['dependency name 1'],
dependency_id: ['2'],
dependency_a: [null],
country: ['country name 1'],
country_id: ['1'],
country_a: [null]
}
},
{
_id: '123',
_type: 'neighbourhood',
layer: 'neighbourhood',
source: 'whosonfirst',
source_id: '123',
center_point: {
lat: 14.141414,
lon: 41.414141
},
bounding_box: '{"min_lat":12.121212,"max_lat":13.131313,"min_lon":21.212121,"max_lon":31.313131}',
name: {
'default': 'name 1'
},
phrase: {
'default': 'name 1'
},
alpha3: 'XYZ',
parent: {
neighbourhood: ['neighbourhood name 2'],
neighbourhood_id: ['20'],
neighbourhood_a: ['neighbourhood abbr 2'],
borough: ['borough name 2'],
borough_id: ['19'],
borough_a: ['borough abbr 2'],
locality: ['locality name 2'],
locality_id: ['18'],
locality_a: ['locality abbr 2'],
localadmin: ['localadmin name 2'],
localadmin_id: ['17'],
localadmin_a: ['localadmin abbr 2'],
county: ['county name 2'],
county_id: ['16'],
county_a: ['county abbr 2'],
macrocounty: ['macrocounty name 2'],
macrocounty_id: ['15'],
macrocounty_a: ['macrocounty abbr 2'],
region: ['region name 2'],
region_id: ['14'],
region_a: ['region abbr 2'],
macroregion: ['macroregion name 2'],
macroregion_id: ['13'],
macroregion_a: ['macroregion abbr 2'],
dependency: ['dependency name 2'],
dependency_id: ['12'],
dependency_a: ['dependency abbr 2'],
country: ['country name 2'],
country_id: ['11'],
country_a: ['XYZ']
}
},
{
_id: '456',
_type: 'locality',
layer: 'locality',
source: 'whosonfirst',
source_id: '456',
center_point: {
lat: 17.171717,
lon: 71.717171
},
bounding_box: '{"min_lat":15.151515,"max_lat":16.161616,"min_lon":51.515151,"max_lon":61.616161}',
name: {
'default': 'name 3'
},
phrase: {
'default': 'name 3'
},
parent: { }
}
]
};
controller(req, res, () => {
t.ok(placeholderService_was_called);
t.deepEquals(res, expected_res);
t.end();
});
});
};
module.exports.all = function (tape, common) {
function test(name, testFunction) {
return tape('GET /placeholder ' + name, testFunction);
}
for( const testCase in module.exports.tests ){
module.exports.tests[testCase](test, common);
}
};

77
test/unit/controller/predicates/is_admin_only_analysis.js

@ -0,0 +1,77 @@
'use strict';
const _ = require('lodash');
const is_admin_only_analysis = require('../../../../controller/predicates/is_admin_only_analysis');
module.exports.tests = {};
module.exports.tests.interface = (test, common) => {
test('valid interface', (t) => {
t.equal(typeof is_admin_only_analysis, 'function', 'is_admin_only_analysis is a function');
t.end();
});
};
module.exports.tests.true_conditions = (test, common) => {
test('parsed_text with admin-only properties should return true', (t) => {
['neighbourhood', 'borough', 'city', 'county', 'state', 'postalcode', 'country'].forEach((property) => {
const req = {
clean: {
parsed_text: {}
}
};
const res = {};
req.clean.parsed_text[property] = `${property} value`;
t.ok(is_admin_only_analysis(req, res));
});
t.end();
});
};
module.exports.tests.false_conditions = (test, common) => {
test('req.clean with no parsed_text should return false', (t) => {
const req = {
clean: {
}
};
const res = {};
t.notOk(is_admin_only_analysis(req, res));
t.end();
});
test('parsed_text with non-admin properties should return false', (t) => {
['number', 'street', 'query', 'category'].forEach((property) => {
const req = {
clean: {
parsed_text: {}
}
};
const res = {};
req.clean.parsed_text[property] = `${property} value`;
t.notOk(is_admin_only_analysis(req, res));
});
t.end();
});
};
module.exports.all = (tape, common) => {
function test(name, testFunction) {
return tape(`GET /is_admin_only_analysis ${name}`, testFunction);
}
for( const testCase in module.exports.tests ){
module.exports.tests[testCase](test, common);
}
};

12
test/unit/controller/predicates/is_pip_service_enabled.js → test/unit/controller/predicates/is_service_enabled.js

@ -1,21 +1,21 @@
'use strict'; 'use strict';
const _ = require('lodash'); const _ = require('lodash');
const is_pip_service_enabled = require('../../../../controller/predicates/is_pip_service_enabled'); const is_service_enabled = require('../../../../controller/predicates/is_service_enabled');
module.exports.tests = {}; module.exports.tests = {};
module.exports.tests.interface = (test, common) => { module.exports.tests.interface = (test, common) => {
test('valid interface', (t) => { test('valid interface', (t) => {
t.equal(typeof is_pip_service_enabled, 'function', 'is_pip_service_enabled is a function'); t.equal(typeof is_service_enabled, 'function', 'is_service_enabled is a function');
t.equal(typeof is_pip_service_enabled(), 'function', 'is_pip_service_enabled() is a function'); t.equal(typeof is_service_enabled(), 'function', 'is_service_enabled() is a function');
t.end(); t.end();
}); });
}; };
module.exports.tests.true_conditions = (test, common) => { module.exports.tests.true_conditions = (test, common) => {
test('string uri should return true', (t) => { test('string uri should return true', (t) => {
t.ok(is_pip_service_enabled('pip uri')()); t.ok(is_service_enabled('pip uri')());
t.end(); t.end();
}); });
@ -24,7 +24,7 @@ module.exports.tests.true_conditions = (test, common) => {
module.exports.tests.false_conditions = (test, common) => { module.exports.tests.false_conditions = (test, common) => {
test('undefined uri should return false', (t) => { test('undefined uri should return false', (t) => {
t.notOk(is_pip_service_enabled()()); t.notOk(is_service_enabled()());
t.end(); t.end();
}); });
@ -33,7 +33,7 @@ module.exports.tests.false_conditions = (test, common) => {
module.exports.all = (tape, common) => { module.exports.all = (tape, common) => {
function test(name, testFunction) { function test(name, testFunction) {
return tape(`GET /is_pip_service_enabled ${name}`, testFunction); return tape(`GET /is_service_enabled ${name}`, testFunction);
} }
for( const testCase in module.exports.tests ){ for( const testCase in module.exports.tests ){

5
test/unit/run.js

@ -14,11 +14,13 @@ var tests = [
require('./controller/coarse_reverse'), require('./controller/coarse_reverse'),
require('./controller/index'), require('./controller/index'),
require('./controller/place'), require('./controller/place'),
require('./controller/placeholder'),
require('./controller/search'), require('./controller/search'),
require('./controller/predicates/has_response_data'), require('./controller/predicates/has_response_data'),
require('./controller/predicates/has_request_errors'), require('./controller/predicates/has_request_errors'),
require('./controller/predicates/is_admin_only_analysis'),
require('./controller/predicates/is_coarse_reverse'), require('./controller/predicates/is_coarse_reverse'),
require('./controller/predicates/is_pip_service_enabled'), require('./controller/predicates/is_service_enabled'),
require('./helper/diffPlaces'), require('./helper/diffPlaces'),
require('./helper/geojsonify'), require('./helper/geojsonify'),
require('./helper/logging'), require('./helper/logging'),
@ -81,6 +83,7 @@ var tests = [
require('./service/mget'), require('./service/mget'),
require('./service/search'), require('./service/search'),
require('./service/interpolation'), require('./service/interpolation'),
require('./service/placeholder'),
require('./service/pointinpolygon'), require('./service/pointinpolygon'),
require('./service/language') require('./service/language')
]; ];

193
test/unit/service/placeholder.js

@ -0,0 +1,193 @@
const proxyquire = require('proxyquire').noCallThru();
const express = require('express');
const setup = require('../../../service/placeholder');
module.exports.tests = {};
module.exports.tests.interface = (test, common) => {
test('valid interface', (t) => {
const logger = require('pelias-mock-logger')();
var service = proxyquire('../../../service/placeholder', {
'pelias-logger': logger
});
t.equal(typeof service, 'function', 'service is a function');
t.end();
});
};
module.exports.tests.do_nothing_service = (test, common) => {
test('undefined url should return service that logs fact that placeholder service is not available', (t) => {
const logger = require('pelias-mock-logger')();
const service = proxyquire('../../../service/placeholder', {
'pelias-logger': logger
})();
service.search('search text', 'search lang', (err) => {
t.deepEquals(logger.getWarnMessages(), [
'placeholder service disabled'
]);
t.equals(err, 'placeholder service disabled');
t.end();
});
});
};
module.exports.tests.failure_conditions = (test, common) => {
test('server returning error should log it and return no results', (t) => {
const server = express().listen();
const port = server.address().port;
// immediately close the server so to ensure an error response
server.close();
const logger = require('pelias-mock-logger')();
const service = proxyquire('../../../service/placeholder', {
'pelias-logger': logger
})(`http://localhost:${port}`);
service.search('search text', 'search lang', (err, results) => {
t.equals(err.code, 'ECONNREFUSED');
t.notOk(results);
t.ok(logger.isErrorMessage(/ECONNREFUSED/), 'there should be a connection refused error message');
t.end();
server.close();
});
});
test('server returning non-200/404 response should log error and return no results', (t) => {
const placeholderServer = express();
placeholderServer.get('/search', (req, res, next) => {
res.status(400).send('a bad request was made');
});
const server = placeholderServer.listen();
const port = server.address().port;
const logger = require('pelias-mock-logger')();
const service = proxyquire('../../../service/placeholder', {
'pelias-logger': logger
})(`http://localhost:${port}`);
service.search('search text', 'search lang', (err, results) => {
t.equals(err, `http://localhost:${port}/search?text=search%20text&lang=search%20lang returned status 400: a bad request was made`);
t.notOk(results);
t.ok(logger.isErrorMessage(`http://localhost:${port}/search?text=search%20text&lang=search%20lang ` +
`returned status 400: a bad request was made`));
t.end();
server.close();
});
});
test('server returning 404 statusCode should log debug message and return no error or results', (t) => {
const placeholderServer = express();
placeholderServer.get('/search', (req, res, next) => {
res.status(404).send('no results found');
});
const server = placeholderServer.listen();
const port = server.address().port;
const logger = require('pelias-mock-logger')();
const service = proxyquire('../../../service/placeholder', {
'pelias-logger': logger
})(`http://localhost:${port}`);
service.search('search text', 'search lang', (err, results) => {
t.notOk(err);
t.deepEquals(results, [], 'should return an empty array');
t.ok(logger.isDebugMessage('returned 0 results for \'search text\''));
t.end();
server.close();
});
});
test('server returning 200 statusCode but with non-JSON response should log error and return undefined', (t) => {
const placeholderServer = express();
placeholderServer.get('/search', (req, res, next) => {
res.status(200).send('this is not parseable as JSON');
});
const server = placeholderServer.listen();
const port = server.address().port;
const logger = require('pelias-mock-logger')();
const service = proxyquire('../../../service/placeholder', {
'pelias-logger': logger
})(`http://localhost:${port}`);
service.search('search text', 'search lang', (err, results) => {
t.equals(err, `http://localhost:${port}/search?text=search%20text&lang=search%20lang ` +
`could not parse response: this is not parseable as JSON`);
t.notOk(results, 'should return undefined');
t.ok(logger.isErrorMessage(`http://localhost:${port}/search?text=search%20text&lang=search%20lang ` +
`could not parse response: this is not parseable as JSON`));
t.end();
server.close();
});
});
};
module.exports.tests.success_conditions = (test, common) => {
test('server returning statusCode 200 should return no error and parsed output', (t) => {
const placeholderServer = express();
placeholderServer.get('/search', (req, res, next) => {
if (req.query.text === 'search text' && req.query.lang === 'search lang') {
res.status(200).send('[1, 2, 3]');
}
});
const server = placeholderServer.listen();
const port = server.address().port;
const logger = require('pelias-mock-logger')();
const service = proxyquire('../../../service/placeholder', {
'pelias-logger': logger
})(`http://localhost:${port}`);
service.search('search text', 'search lang', (err, results) => {
t.notOk(err, 'should be no error');
t.deepEquals(results, [1, 2, 3]);
t.notOk(logger.hasErrorMessages());
t.end();
server.close();
});
});
};
module.exports.all = (tape, common) => {
function test(name, testFunction) {
return tape(`SERVICE /placeholder ${name}`, testFunction);
}
for( var testCase in module.exports.tests ){
module.exports.tests[testCase](test, common);
}
};
Loading…
Cancel
Save