diff --git a/server/routes/alerts-and-warnings.js b/server/routes/alerts-and-warnings.js index 8540d8564..8d4012e44 100644 --- a/server/routes/alerts-and-warnings.js +++ b/server/routes/alerts-and-warnings.js @@ -77,7 +77,7 @@ async function locationRouteHandler (request, h) { const canonicalUrl = request.url.origin + request.url.pathname const location = util.cleanseLocation(request.params.location) - const [place] = await locationService.find(location) + const [place] = await locationService.get(location) if (isLocationEngland(location)) { return h.redirect(`/${route}`) diff --git a/server/routes/location.js b/server/routes/location.js index 3be396000..fd87b7b83 100644 --- a/server/routes/location.js +++ b/server/routes/location.js @@ -30,9 +30,9 @@ async function routeHandler (request, h) { return h.redirect('/') } - const [place] = await locationService.find(location) + const [place] = await locationService.get(location) - if (place?.slug !== location) { + if (!place) { return boom.notFound(`Location ${location} not found`) } diff --git a/server/routes/river-and-sea-levels.js b/server/routes/river-and-sea-levels.js index eb442da41..c965d6972 100644 --- a/server/routes/river-and-sea-levels.js +++ b/server/routes/river-and-sea-levels.js @@ -40,7 +40,7 @@ async function locationRouteHandler (request, h) { const canonicalUrl = request.url.origin + request.url.pathname const location = util.cleanseLocation(request.params.location) - const [place] = await locationService.find(location) + const [place] = await locationService.get(location) if (isLocationEngland(location)) { return h.redirect(`/${route}`) diff --git a/server/services/lib/bing-results-parser.js b/server/services/lib/bing-results-parser.js index 7bab9b6e0..405848d59 100644 --- a/server/services/lib/bing-results-parser.js +++ b/server/services/lib/bing-results-parser.js @@ -1,114 +1,78 @@ -const { addBufferToBbox, formatName } = require('../../util') +const { addBufferToBbox, formatName, slugify } = require('./bing-utils') // source: https://en.wikipedia.org/wiki/Ceremonial_counties_of_England // see also for a description of the difference between ceremonial and administrative counties const englishCeremonialCounties = - [ - 'bedfordshire', - 'berkshire', - 'bristol', - 'buckinghamshire', - 'cambridgeshire', - 'cheshire', - 'city of london', - 'cornwall', - 'cumbria', - 'derbyshire', - 'devon', - 'dorset', - 'durham', - 'east riding of yorkshire', - 'east sussex', - 'essex', - 'gloucestershire', - 'greater london', - 'greater manchester', - 'hampshire', - 'herefordshire', - 'hertfordshire', - 'isle of wight', - 'kent', - 'lancashire', - 'leicestershire', - 'lincolnshire', - 'merseyside', - 'norfolk', - 'north yorkshire', - 'northamptonshire', - 'northumberland', - 'nottinghamshire', - 'oxfordshire', - 'rutland', - 'shropshire', - 'somerset', - 'south yorkshire', - 'staffordshire', - 'suffolk', - 'surrey', - 'tyne and wear', - 'warwickshire', - 'west midlands', - 'west sussex', - 'west yorkshire', - 'wiltshire', - 'worcestershire' - ] - -function slugify (text = '') { - return text.replace(/,/g, '').replace(/ /g, '-').toLowerCase() -} - -async function bingResultsParser (bingData) { - const set = bingData.resourceSets[0] - if (set.estimatedTotal === 0) { - return [] - } - - // following discussion with team, going to try out only high confidence - // results. This should reduce spurious results. - const allowedConfidences = ['high'] - - // note that allowedTypes also captures precedance rules for when multiple - // results are returned (e.g admindivision2 takes precedance over admindivision1) - const allowedTypes = [ - 'postcode1', - 'postcode3', - 'admindivision1', - 'admindivision2', - 'populatedplace', - 'neighborhood' + [ + 'bedfordshire', + 'berkshire', + 'bristol', + 'buckinghamshire', + 'cambridgeshire', + 'cheshire', + 'city of london', + 'cornwall', + 'cumbria', + 'derbyshire', + 'devon', + 'dorset', + 'durham', + 'east riding of yorkshire', + 'east sussex', + 'essex', + 'gloucestershire', + 'greater london', + 'greater manchester', + 'hampshire', + 'herefordshire', + 'hertfordshire', + 'isle of wight', + 'kent', + 'lancashire', + 'leicestershire', + 'lincolnshire', + 'merseyside', + 'norfolk', + 'north yorkshire', + 'northamptonshire', + 'northumberland', + 'nottinghamshire', + 'oxfordshire', + 'rutland', + 'shropshire', + 'somerset', + 'south yorkshire', + 'staffordshire', + 'suffolk', + 'surrey', + 'tyne and wear', + 'warwickshire', + 'west midlands', + 'west sussex', + 'west yorkshire', + 'wiltshire', + 'worcestershire' ] - function englandOnlyFilter (r) { - if (r.entityType.toLowerCase() === 'admindivision1') { - return englishCeremonialCounties.indexOf(r.name.toLowerCase()) >= 0 - } - - return r.address.adminDistrict.toLowerCase() === 'england' - } - - const data = set.resources - .filter(r => allowedConfidences.includes(r.confidence.toLowerCase())) - .filter(r => allowedTypes.includes(r.entityType.toLowerCase())) - .filter(r => englandOnlyFilter(r)) - .sort((a, b) => - allowedTypes.indexOf(a.entityType.toLowerCase()) - - allowedTypes.indexOf(b.entityType.toLowerCase())) - .sort((a, b) => - allowedConfidences.indexOf(a.confidence.toLowerCase()) - - allowedConfidences.indexOf(b.confidence.toLowerCase()))[0] - - if (!data) { - return [] - } - - const { - bbox, - point: { coordinates: center } - } = data - - const name = formatName(data.name) +// note that allowedTypes also captures precedance rules for when multiple +// results are returned (e.g admindivision2 takes precedance over admindivision1) +const allowedTypes = [ + 'postcode1', + 'postcode3', + 'admindivision1', + 'admindivision2', + 'populatedplace', + 'neighborhood' +] + +const distanceInMetres = { + '2k': 2000, + '10k': 10000 +} +const mapper = (r) => { + const name = formatName(r.name) + const bbox = r.bbox.reverse() // query is the value to use in a search box or the slug to replicate the // search and get the same result. If the bing format of the name (place // name + postcode) is used then some postcode searches which were @@ -118,39 +82,71 @@ async function bingResultsParser (bingData) { // This causes problems with validity checking // Retained both name and query for display purposes for post codes // (even though name and query are the are the same for non-postcodes) - const query = ['postcode1', 'postcode3'].includes(data.entityType.toLowerCase()) - ? data.address.postalCode + const query = ['postcode1', 'postcode3'].includes(r.entityType.toLowerCase()) + ? r.address.postalCode : name - const slug = slugify(query) - - // Reverse as Bing returns as [y (lat), x (long)] - bbox.reverse() - center.reverse() - - const isUK = data.address.countryRegionIso2 === 'GB' + return { + name, + query, + slug: slugify(query), + center: r.point.coordinates.reverse(), + bbox2k: addBufferToBbox(bbox, distanceInMetres['2k']), + bbox10k: addBufferToBbox(bbox, distanceInMetres['10k']), + isUK: r.address.countryRegionIso2 === 'GB', + isEngland: { is_england: true } + } +} - // const isEngland = await getIsEngland(center[0], center[1]) +const confidenceFilter = (r) => r.confidence.toLowerCase() === 'high' - const distanceInMetres = { - '2k': 2000, - '10k': 10000 +const englandOnlyFilter = (r) => { + if (r.entityType.toLowerCase() === 'admindivision1') { + return englishCeremonialCounties.indexOf(r.name.toLowerCase()) >= 0 } - // add buffer to place.bbox for stations search - const bbox2k = addBufferToBbox(bbox, distanceInMetres['2k']) - const bbox10k = addBufferToBbox(bbox, distanceInMetres['10k']) + return r.address.adminDistrict?.toLowerCase() === 'england' +} - return [{ - name, - slug, - query, - center, - bbox2k, - bbox10k, - isUK, - isEngland: { is_england: true } - }] +const allowedTypesFilter = (r) => + allowedTypes.includes(r.entityType.toLowerCase()) + +const baseFilter = (r) => + allowedTypesFilter(r) && englandOnlyFilter(r) + +const typesSort = (a, b) => + allowedTypes.indexOf(a.entityType.toLowerCase()) - + allowedTypes.indexOf(b.entityType.toLowerCase()) + +const removeDuplicatesFilter = (place, index, self) => + self.findIndex(p => p.slug === place.slug) === index + +async function find (bingResponse) { + const set = bingResponse.resourceSets[0] + return set.estimatedTotal + ? set.resources + .filter(confidenceFilter) + .filter(baseFilter) + .sort(typesSort) + .map(mapper) + .filter(removeDuplicatesFilter) + : [] } -module.exports = bingResultsParser +async function get (bingResponse, slug) { + const matchingSlugFilter = (r) => r.slug === slug + const set = bingResponse.resourceSets[0] + return set.estimatedTotal + ? set.resources + .filter(baseFilter) + .sort(typesSort) + .map(mapper) + .filter(removeDuplicatesFilter) + .filter(matchingSlugFilter) + : [] +} + +module.exports = { + find, + get +} diff --git a/server/services/lib/bing-utils.js b/server/services/lib/bing-utils.js new file mode 100644 index 000000000..060b3c38b --- /dev/null +++ b/server/services/lib/bing-utils.js @@ -0,0 +1,48 @@ +const turf = require('@turf/turf') + +function removeRepeatingEntries (inputString) { + const itemsArray = inputString.split(',').map(item => item.trim()) + const uniqueItemsArray = [...new Set(itemsArray)] + return uniqueItemsArray.join(', ') +} + +function hasCityQualifier (itemsArray) { + const regex = new RegExp(`^(Greater|City Of) ${itemsArray[0]}$`, 'i') + return regex.test(itemsArray[1]) +} + +function removeCityQualifiers (inputString) { + // remove qualifiers such as Greater London and City Of Portsmouth from the final entry in a place name + // e.g. Camberwell, London, Greater London => Camberwell, London + // e.g. London, Greater London => London + const splitToken = ', ' + const itemsArray = inputString.split(splitToken) + const length = itemsArray.length + const penultimate = -2 + if (length >= 2 && hasCityQualifier(itemsArray.slice(penultimate))) { + return itemsArray.slice(0, -1).join(splitToken) + } + return inputString +} + +function formatName (name) { + if (!name) { + return '' + } + return removeCityQualifiers(removeRepeatingEntries(name)) +} + +function slugify (text = '') { + return text.replace(/,/g, '').replace(/ /g, '-').toLowerCase() +} + +function addBufferToBbox (bbox, m) { + // Convert bbox (binding box) )into polygon, add buffer, and convert back to bbox as db query needs a bbox envelope + return turf.bbox(turf.buffer(turf.bboxPolygon(bbox), m, { units: 'meters' })) +} + +module.exports = { + formatName, + slugify, + addBufferToBbox +} diff --git a/server/services/location.js b/server/services/location.js index a2cf5acd3..5ec71af3a 100644 --- a/server/services/location.js +++ b/server/services/location.js @@ -2,11 +2,10 @@ const joi = require('@hapi/joi') const { bingKeyLocation, bingUrl } = require('../config') const { getJson } = require('../util') const util = require('util') -const bingResultsParser = require('./lib/bing-results-parser') +const { find, get } = require('./lib/bing-results-parser') const LocationSearchError = require('../location-search-error') -const floodServices = require('./flood') -const MAX_BING_RESULTS = 3 +const MAX_BING_RESULTS = 5 function bingSearchNotNeeded (searchTerm) { const mustNotMatch = /[<>]|^england$|^scotland$|^wales$|^united kingdom$|^northern ireland$/i @@ -35,15 +34,16 @@ function validateBingResponse (response) { } } -async function find (location) { - const validatedLocation = validateSearchTerm(location) +async function getBingResponse (query) { + const validatedQuery = validateSearchTerm(query) + const emptyBingResponse = { resourceSets: [{ estimatedTotal: 0 }] } - if (bingSearchNotNeeded(validatedLocation)) { - return [] + if (bingSearchNotNeeded(validatedQuery)) { + return emptyBingResponse } - const query = encodeURIComponent(validatedLocation) - const url = util.format(bingUrl, query, MAX_BING_RESULTS, bingKeyLocation) + const encodedQuery = encodeURIComponent(validatedQuery) + const url = util.format(bingUrl, encodedQuery, MAX_BING_RESULTS, bingKeyLocation) let bingData try { @@ -54,7 +54,20 @@ async function find (location) { validateBingResponse(bingData) - return bingResultsParser(bingData, floodServices.getIsEngland) + return bingData } -module.exports = { find } +async function getLocationBySlug (locationSlug) { + const bingData = await getBingResponse(locationSlug) + return get(bingData, locationSlug) +} + +async function findLocationByQuery (locationQuery) { + const bingData = await getBingResponse(locationQuery) + return find(bingData) +} + +module.exports = { + find: findLocationByQuery, + get: getLocationBySlug +} diff --git a/server/util.js b/server/util.js index d196a6cf5..5fc1844c1 100644 --- a/server/util.js +++ b/server/util.js @@ -1,4 +1,3 @@ -const turf = require('@turf/turf') const moment = require('moment-timezone') const config = require('./config') const wreck = require('@hapi/wreck').defaults({ @@ -105,11 +104,6 @@ function removeSpikes (data) { return data.filter(el => el._ < maxVal) } -function addBufferToBbox (bbox, m) { - // Convert bbox (binding box) )into polygon, add buffer, and convert back to bbox as db query needs a bbox envelope - return turf.bbox(turf.buffer(turf.bboxPolygon(bbox), m, { units: 'meters' })) -} - function formatValue (val) { return parseFloat(Math.round(val * Math.pow(10, 1)) / (Math.pow(10, 1))).toFixed(1) } @@ -155,38 +149,6 @@ function rainfallTelemetryPadOut (values, valueDuration) { return values } -function removeRepeatingEntries (inputString) { - const itemsArray = inputString.split(',').map(item => item.trim()) - const uniqueItemsArray = [...new Set(itemsArray)] - return uniqueItemsArray.join(', ') -} - -function hasCityQualifier (itemsArray) { - const regex = new RegExp(`^(Greater|City Of) ${itemsArray[0]}$`, 'i') - return regex.test(itemsArray[1]) -} - -function removeCityQualifiers (inputString) { - // remove qualifiers such as Greater London and City Of Portsmouth from the final entry in a place name - // e.g. Camberwell, London, Greater London => Camberwell, London - // e.g. London, Greater London => London - const splitToken = ', ' - const itemsArray = inputString.split(splitToken) - const length = itemsArray.length - const penultimate = -2 - if (length >= 2 && hasCityQualifier(itemsArray.slice(penultimate))) { - return itemsArray.slice(0, -1).join(splitToken) - } - return inputString -} - -function formatName (name) { - if (!name) { - return '' - } - return removeCityQualifiers(removeRepeatingEntries(name)) -} - module.exports = { get, post, @@ -198,9 +160,7 @@ module.exports = { toFixed, groupBy, cleanseLocation, - addBufferToBbox, formatValue, - formatName, toMarked, dateDiff, formatRainfallTelemetry, diff --git a/test/server/util.js b/test/server/util.js index 3922d7b00..3a21a9371 100644 --- a/test/server/util.js +++ b/test/server/util.js @@ -3,7 +3,7 @@ const Code = require('@hapi/code') const lab = exports.lab = Lab.script() const spikeTelem = require('../data/spikeTelem.json') const nonSpikeTelem = require('../data/nonSpikeTelem.json') -const { formatName, toMarked, cleanseLocation, removeSpikes } = require('../../server/util') +const { toMarked, cleanseLocation, removeSpikes } = require('../../server/util') lab.experiment('util', () => { lab.experiment('toMarked', () => { @@ -37,29 +37,4 @@ lab.experiment('util', () => { Code.expect(telem.length).to.equal(480) }) }) - lab.experiment('formatName', () => { - lab.test('Repeating parts are removed', async () => { - Code.expect(formatName('Middlesbrough, Middlesbrough')).to.equal('Middlesbrough') - }) - lab.test('Similar parts are not removed', async () => { - Code.expect(formatName('Durham, County Durham')).to.equal('Durham, County Durham') - }) - lab.test('City qualifier "City Of" is removed', async () => { - Code.expect(formatName('Bristol, City Of Bristol')).to.equal('Bristol') - }) - lab.test('City qualifier "City of" is removed (ie case insensitive)', async () => { - Code.expect(formatName('Bristol, City of Bristol')).to.equal('Bristol') - }) - lab.test('City qualifier "Greater" is removed', async () => { - Code.expect(formatName('London, Greater London')).to.equal('London') - }) - lab.test('City qualifier removed from neighbourhood', async () => { - Code.expect(formatName('Camberwell, London, Greater London')).to.equal('Camberwell, London') - }) - lab.experiment('Error handling', () => { - lab.test('Fails gracefully with undefined name', async () => { - Code.expect(formatName()).to.equal('') - }) - }) - }) }) diff --git a/test/services/lib/bing-results-parser.js b/test/services/lib/bing-results-parser.js index f3b4b7352..9ca029ebe 100644 --- a/test/services/lib/bing-results-parser.js +++ b/test/services/lib/bing-results-parser.js @@ -3,7 +3,7 @@ const { expect } = require('@hapi/code') const { experiment, test } = exports.lab = Lab.script() const responseTemplate = require('./bing-results-template.json') -const bingResultsParser = require('../../../server/services/lib/bing-results-parser') +const { find, get } = require('../../../server/services/lib/bing-results-parser') function getBingResponse (resources = []) { const response = { ...responseTemplate } @@ -14,912 +14,1251 @@ function getBingResponse (resources = []) { return response } -async function checkParsedResponse (resources, stubGetEngland, expectedResult) { +async function checkParsedResponse (resources, expectedResult) { const bingResponse = getBingResponse(resources) - const result = await bingResultsParser(bingResponse, stubGetEngland) + const result = await find(bingResponse) expect(result).to.equal(expectedResult) } experiment('bingResultsParser', () => { experiment('english searches', () => { - const stubGetEngland = async () => { return { is_england: true } } - test('empty resource set should return empty results', async () => { - const bingResponse = getBingResponse() - const result = await bingResultsParser(bingResponse, stubGetEngland) - expect(result).to.equal([]) - }) - test('english town location search should return populated result', async () => { - const resources = [ - { - __type: 'Location:http://schemas.microsoft.com/search/local/ws/rest/v1', - bbox: [ - 53.99038314819336, - -1.5035173892974854, - 54.03419876098633, - -1.4180587530136108 - ], - name: 'Knaresborough, North Yorkshire', - point: { - type: 'Point', - coordinates: [ - 54.00714111, - -1.46303844 - ] - }, - address: { - adminDistrict: 'England', - adminDistrict2: 'North Yorkshire', - countryRegion: 'United Kingdom', - formattedAddress: 'Knaresborough, North Yorkshire', - locality: 'Knaresborough', - countryRegionIso2: 'GB' - }, - confidence: 'High', - entityType: 'PopulatedPlace', - geocodePoints: [ - { + experiment('get by slug', () => { + test('can find a result in the response by slug regardless of confidence level', async () => { + const resources = [ + { + __type: 'Location:http://schemas.microsoft.com/search/local/ws/rest/v1', + bbox: [ + 51.12405776977539, + 0.8380475640296936, + 51.17716598510742, + 0.9264887571334839 + ], + name: 'Ashford, Kent', + point: { type: 'Point', coordinates: [ - 54.00714111, - -1.46303844 - ], - calculationMethod: 'Rooftop', - usageTypes: [ - 'Display' + 51.14772797, + 0.87279475 ] - } - ], - matchCodes: [ - 'Good' - ] - } - ] - - const expectedResult = [ - { - name: 'Knaresborough, North Yorkshire', - query: 'Knaresborough, North Yorkshire', - slug: 'knaresborough-north-yorkshire', - center: [-1.46303844, 54.00714111], - bbox2k: [ - -1.534142855800849, - 53.972396744766755, - -1.3874332865102472, - 54.05218516440792 - ], - bbox10k: [ - -1.6566444925899468, - 53.90045113102211, - -1.2649316497211494, - 54.12413077805586 - ], - isUK: true, - isEngland: { is_england: true } - } - ] - checkParsedResponse(resources, stubGetEngland, expectedResult) - }) - test('english county search should return ceremonial county (AdminDivision1) over administrative county (AdminDivision2)', async () => { - const resources = [ - { - __type: 'Location:http://schemas.microsoft.com/search/local/ws/rest/v1', - bbox: [ - 54.7823600769043, - -2.6897950172424316, - 55.811668395996094, - -1.460276484489441 - ], - name: 'Northumberland', - point: { - type: 'Point', - coordinates: [ - 55.24245834, - -2.06545234 + }, + address: { + adminDistrict: 'England', + adminDistrict2: 'Kent', + countryRegion: 'United Kingdom', + formattedAddress: 'Ashford, Kent', + locality: 'Ashford', + countryRegionIso2: 'GB' + }, + confidence: 'High', + entityType: 'PopulatedPlace', + geocodePoints: [ + { + type: 'Point', + coordinates: [ + 51.14772797, + 0.87279475 + ], + calculationMethod: 'Rooftop', + usageTypes: [ + 'Display' + ] + } + ], + matchCodes: [ + 'Good' ] }, - address: { - adminDistrict: 'Northumberland', - countryRegion: 'United Kingdom', - formattedAddress: 'Northumberland', - countryRegionIso2: 'GB' - }, - confidence: 'High', - entityType: 'AdminDivision1', - geocodePoints: [ - { + { + __type: 'Location:http://schemas.microsoft.com/search/local/ws/rest/v1', + bbox: [ + 51.02236557006836, + -0.4873929023742676, + 51.44563293457031, + 1.0504722595214844 + ], + name: 'Ashford, Surrey', + point: { type: 'Point', coordinates: [ - 55.24245834, - -2.06545234 - ], - calculationMethod: 'Rooftop', - usageTypes: [ - 'Display' + 51.43230057, + -0.46049938 ] - } - ], - matchCodes: [ - 'Good' - ] - }, - { - __type: 'Location:http://schemas.microsoft.com/search/local/ws/rest/v1', - bbox: [ - 54.7823600769043, - -2.6897950172424316, - 55.811668395996094, - -1.4027706384658813 - ], - name: 'Northumberland', - point: { - type: 'Point', - coordinates: [ - 55.23395538, - -2.04782939 + }, + address: { + adminDistrict: 'England', + adminDistrict2: 'Surrey', + countryRegion: 'United Kingdom', + formattedAddress: 'Ashford, Surrey', + locality: 'Ashford', + countryRegionIso2: 'GB' + }, + confidence: 'Medium', + entityType: 'PopulatedPlace', + geocodePoints: [ + { + type: 'Point', + coordinates: [ + 51.43230057, + -0.46049938 + ], + calculationMethod: 'Rooftop', + usageTypes: [ + 'Display' + ] + } + ], + matchCodes: [ + 'Good' ] }, - address: { - adminDistrict: 'Northumberland', - countryRegion: 'United Kingdom', - formattedAddress: 'Northumberland', - countryRegionIso2: 'GB' - }, - confidence: 'High', - entityType: 'AdminDivision1', - geocodePoints: [ - { + { + __type: 'Location:http://schemas.microsoft.com/search/local/ws/rest/v1', + bbox: [ + 50.89913783233948, + -1.8659905171462143, + 50.957078598324586, + -1.7434982085160171 + ], + name: 'Ashford, Fordingbridge, Hampshire', + point: { type: 'Point', coordinates: [ - 55.23395538, - -2.04782939 - ], - calculationMethod: 'Rooftop', - usageTypes: [ - 'Display' + 50.92810822, + -1.80474436 ] - } - ], - matchCodes: [ - 'Good' - ] - }, - { - __type: 'Location:http://schemas.microsoft.com/search/local/ws/rest/v1', - bbox: [ - 54.7823600769043, - -2.6897950172424316, - 55.811676025390625, - -1.4601497650146484 - ], - name: 'Northumberland', - point: { - type: 'Point', - coordinates: [ - 55.17995834, - -1.80139947 + }, + address: { + adminDistrict: 'England', + adminDistrict2: 'Hampshire', + countryRegion: 'United Kingdom', + formattedAddress: 'Ashford, Fordingbridge, Hampshire', + locality: 'Ashford', + countryRegionIso2: 'GB' + }, + confidence: 'Medium', + entityType: 'PopulatedPlace', + geocodePoints: [ + { + type: 'Point', + coordinates: [ + 50.92810822, + -1.80474436 + ], + calculationMethod: 'Rooftop', + usageTypes: [ + 'Display' + ] + } + ], + matchCodes: [ + 'Good' ] }, - address: { - adminDistrict: 'England', - adminDistrict2: 'Northumberland', - countryRegion: 'United Kingdom', - formattedAddress: 'Northumberland', - countryRegionIso2: 'GB' - }, - confidence: 'High', - entityType: 'AdminDivision2', - geocodePoints: [ - { + { + __type: 'Location:http://schemas.microsoft.com/search/local/ws/rest/v1', + bbox: [ + 50.9887580871582, + 0.5919979214668274, + 51.26953887939453, + 1.030938744544983 + ], + name: 'Ashford', + point: { type: 'Point', coordinates: [ - 55.17995834, - -1.80139947 - ], - calculationMethod: 'Rooftop', - usageTypes: [ - 'Display' + 51.13436127, + 0.83433753 ] - } - ], - matchCodes: [ - 'Good' - ] - } - ] + }, + address: { + adminDistrict: 'England', + adminDistrict2: 'Kent', + countryRegion: 'United Kingdom', + formattedAddress: 'Ashford', + countryRegionIso2: 'GB' + }, + confidence: 'Low', + entityType: 'AdminDivision3', + geocodePoints: [ + { + type: 'Point', + coordinates: [ + 51.13436127, + 0.83433753 + ], + calculationMethod: 'Rooftop', + usageTypes: [ + 'Display' + ] + } + ], + matchCodes: [ + 'Good' + ] + } + ] + const bingResponse = getBingResponse(resources) + const result = await get(bingResponse, 'ashford-surrey') - const expectedResult = [ - // AdminDivision1 should take precedance over other entity types in - // response - { - name: 'Northumberland', - query: 'Northumberland', - slug: 'northumberland', - center: [-2.06545234, 55.24245834], - bbox2k: [ - -2.721803715494745, - 54.7643744779302, - -1.4282677862371271, - 55.829653988329156 - ], - bbox10k: [ - -2.8498382313297492, - 54.692432073840145, - -1.300233270402123, - 55.90159634910422 - ], - isUK: true, - isEngland: { is_england: true } - } - ] - checkParsedResponse(resources, stubGetEngland, expectedResult) - }) - test('english county search for ceremonial county should return the ceremonial county when there are no administrative counties', async () => { - const resources = [ - { - __type: 'Location:http://schemas.microsoft.com/search/local/ws/rest/v1', - bbox: [ - 54.03961944580078, - -3.6406314373016357, - 55.18899154663086, - -2.1589810848236084 - ], - name: 'Cumbria', - point: { - type: 'Point', - coordinates: [ - 54.57675934, - -2.91157079 + const expectedResult = [ + { + name: 'Ashford, Surrey', + query: 'Ashford, Surrey', + slug: 'ashford-surrey', + center: [-0.46049938, 51.43230057], + bbox2k: [ + -0.5162515713809895, + 51.00438035668807, + 1.0793309285282062, + 51.463618142955355 + ], + bbox10k: [ + -0.631686066459741, + 50.9324394922677, + 1.194765423606958, + 51.53555896537625 + ], + isUK: true, + isEngland: { is_england: true } + } + ] + expect(result).to.equal(expectedResult) + }) + test('can find a result in the response by slug even when name has been de-duplicated', async () => { + const resources = [ + { + __type: 'Location:http://schemas.microsoft.com/search/local/ws/rest/v1', + bbox: [ + 53.289649963378906, + -2.694925308227539, + 53.493385314941406, + -2.451319694519043 + ], + name: 'Warrington, Warrington', + point: { + type: 'Point', + coordinates: [ + 53.38956833, + -2.59089661 + ] + }, + address: { + adminDistrict: 'England', + adminDistrict2: 'Warrington', + countryRegion: 'United Kingdom', + formattedAddress: 'Warrington, Warrington', + locality: 'Warrington', + countryRegionIso2: 'GB' + }, + confidence: 'High', + entityType: 'PopulatedPlace', + geocodePoints: [ + { + type: 'Point', + coordinates: [ + 53.38956833, + -2.59089661 + ], + calculationMethod: 'Rooftop', + usageTypes: [ + 'Display' + ] + } + ], + matchCodes: [ + 'Good' ] }, - address: { - adminDistrict: 'Cumbria', - countryRegion: 'United Kingdom', - formattedAddress: 'Cumbria', - countryRegionIso2: 'GB' - }, - confidence: 'High', - entityType: 'AdminDivision1', - geocodePoints: [ - { + { + __type: 'Location:http://schemas.microsoft.com/search/local/ws/rest/v1', + bbox: [ + 53.322303771972656, + -2.6977343559265137, + 53.48092269897461, + -2.4253716468811035 + ], + name: 'Warrington', + point: { type: 'Point', coordinates: [ - 54.57675934, - -2.91157079 - ], - calculationMethod: 'Rooftop', - usageTypes: [ - 'Display' + 53.397789, + -2.57385659 ] - } - ], - matchCodes: [ - 'Good' - ] - } - ] - - // AdminDivision1 (ceremonial county) - const expectedResult = [ - { - name: 'Cumbria', - query: 'Cumbria', - slug: 'cumbria', - center: [-2.91157079, 54.57675934], - bbox2k: [ - -3.672137848226576, - 54.02163420058362, - -2.127474673898669, - 55.20697678117478 - ], - bbox10k: [ - -3.798163229693759, - 53.94969320818414, - -2.001449292431486, - 55.278917707262806 - ], - isUK: true, - isEngland: { is_england: true } - } - ] - checkParsedResponse(resources, stubGetEngland, expectedResult) - }) - test('successful postcode search should return populated result', async () => { - const resources = [ - { - __type: 'Location:http://schemas.microsoft.com/search/local/ws/rest/v1', - bbox: [ - 54.005693099079714, - -1.4739542828842274, - 54.01341853422107, - -1.4564274920181164 - ], - name: 'Knaresborough, HG5 0JL', - point: { - type: 'Point', - coordinates: [ - 54.00955582, - -1.46519089 + }, + address: { + adminDistrict: 'England', + adminDistrict2: 'Warrington', + countryRegion: 'United Kingdom', + formattedAddress: 'Warrington', + countryRegionIso2: 'GB' + }, + confidence: 'High', + entityType: 'AdminDivision2', + geocodePoints: [ + { + type: 'Point', + coordinates: [ + 53.397789, + -2.57385659 + ], + calculationMethod: 'Rooftop', + usageTypes: [ + 'Display' + ] + } + ], + matchCodes: [ + 'Good' ] }, - address: { - adminDistrict: 'England', - adminDistrict2: 'North Yorkshire', - countryRegion: 'United Kingdom', - formattedAddress: 'Knaresborough, HG5 0JL', - locality: 'Knaresborough', - postalCode: 'HG5 0JL', - countryRegionIso2: 'GB' - }, - confidence: 'High', - entityType: 'Postcode1', - geocodePoints: [ - { + { + __type: 'Location:http://schemas.microsoft.com/search/local/ws/rest/v1', + bbox: [ + 52.162532806396484, + -0.7140147089958191, + 52.196353912353516, + -0.6784347295761108 + ], + name: 'Warrington, Olney, Milton Keynes', + point: { type: 'Point', coordinates: [ - 54.00955582, - -1.46519089 - ], - calculationMethod: 'Rooftop', - usageTypes: [ - 'Display' + 52.17710495, + -0.6892029 ] - } - ], - matchCodes: [ - 'Good' - ] - } - ] - const bingResponse = getBingResponse(resources) - const result = await bingResultsParser(bingResponse, stubGetEngland) + }, + address: { + adminDistrict: 'England', + adminDistrict2: 'Milton Keynes', + countryRegion: 'United Kingdom', + formattedAddress: 'Warrington, Olney, Milton Keynes', + locality: 'Warrington', + countryRegionIso2: 'GB' + }, + confidence: 'Medium', + entityType: 'PopulatedPlace', + geocodePoints: [ + { + type: 'Point', + coordinates: [ + 52.17710495, + -0.6892029 + ], + calculationMethod: 'Rooftop', + usageTypes: [ + 'Display' + ] + } + ], + matchCodes: [ + 'Good' + ] + } + ] + const bingResponse = getBingResponse(resources) + const result = await get(bingResponse, 'warrington') - const expectedResult = [ - { - name: 'Knaresborough, HG5 0JL', - query: 'HG5 0JL', - slug: 'hg5-0jl', - center: [-1.46519089, 54.00955582], - bbox2k: [ - -1.5045644526149113, - 53.9877066919671, - -1.4258173222874324, - 54.03140494133353 - ], - bbox10k: [ - -1.6270049027390623, - 53.91576106351499, - -1.3033768721632815, - 54.10335056978173 - ], - isUK: true, - isEngland: { is_england: true } - } - ] - expect(result).to.equal(expectedResult) + const expectedResult = [ + { + name: 'Warrington', + query: 'Warrington', + slug: 'warrington', + center: [-2.57385659, 53.397789], + bbox2k: [ + -2.727959007910171, + 53.30431740342236, + -2.3951469948974466, + 53.49890906744219 + ], + bbox10k: [ + -2.8488573986091374, + 53.23237192884462, + -2.2742486041984797, + 53.57085454093165 + ], + isUK: true, + isEngland: { is_england: true } + } + ] + expect(result).to.equal(expectedResult) + }) }) - test('successful outcode search should return populated result', async () => { - const resources = [ - { - __type: 'Location:http://schemas.microsoft.com/search/local/ws/rest/v1', - bbox: [ - 53.964805603027344, - -1.5241378545761108, - 54.07632064819336, - -1.344303846359253 - ], - name: 'Knaresborough, HG5', - point: { - type: 'Point', - coordinates: [ - 54.01323318, - -1.45626473 + experiment('find by query string', () => { + test('empty resource set should return empty results', async () => { + checkParsedResponse([], []) + }) + test('english town location search should return populated result', async () => { + const resources = [ + { + __type: 'Location:http://schemas.microsoft.com/search/local/ws/rest/v1', + bbox: [ + 53.99038314819336, + -1.5035173892974854, + 54.03419876098633, + -1.4180587530136108 + ], + name: 'Knaresborough, North Yorkshire', + point: { + type: 'Point', + coordinates: [ + 54.00714111, + -1.46303844 + ] + }, + address: { + adminDistrict: 'England', + adminDistrict2: 'North Yorkshire', + countryRegion: 'United Kingdom', + formattedAddress: 'Knaresborough, North Yorkshire', + locality: 'Knaresborough', + countryRegionIso2: 'GB' + }, + confidence: 'High', + entityType: 'PopulatedPlace', + geocodePoints: [ + { + type: 'Point', + coordinates: [ + 54.00714111, + -1.46303844 + ], + calculationMethod: 'Rooftop', + usageTypes: [ + 'Display' + ] + } + ], + matchCodes: [ + 'Good' + ] + } + ] + + const expectedResult = [ + { + name: 'Knaresborough, North Yorkshire', + query: 'Knaresborough, North Yorkshire', + slug: 'knaresborough-north-yorkshire', + center: [-1.46303844, 54.00714111], + bbox2k: [ + -1.534142855800849, + 53.972396744766755, + -1.3874332865102472, + 54.05218516440792 + ], + bbox10k: [ + -1.6566444925899468, + 53.90045113102211, + -1.2649316497211494, + 54.12413077805586 + ], + isUK: true, + isEngland: { is_england: true } + } + ] + checkParsedResponse(resources, expectedResult) + }) + test('english county search should return ceremonial county (AdminDivision1) over administrative county (AdminDivision2)', async () => { + const resources = [ + { + __type: 'Location:http://schemas.microsoft.com/search/local/ws/rest/v1', + bbox: [ + 54.7823600769043, + -2.6897950172424316, + 55.811668395996094, + -1.460276484489441 + ], + name: 'Northumberland', + point: { + type: 'Point', + coordinates: [ + 55.24245834, + -2.06545234 + ] + }, + address: { + adminDistrict: 'Northumberland', + countryRegion: 'United Kingdom', + formattedAddress: 'Northumberland', + countryRegionIso2: 'GB' + }, + confidence: 'High', + entityType: 'AdminDivision1', + geocodePoints: [ + { + type: 'Point', + coordinates: [ + 55.24245834, + -2.06545234 + ], + calculationMethod: 'Rooftop', + usageTypes: [ + 'Display' + ] + } + ], + matchCodes: [ + 'Good' ] }, - address: { - adminDistrict: 'England', - adminDistrict2: 'North Yorkshire', - countryRegion: 'United Kingdom', - formattedAddress: 'Knaresborough, HG5', - locality: 'Knaresborough', - postalCode: 'HG5', - countryRegionIso2: 'GB' + { + __type: 'Location:http://schemas.microsoft.com/search/local/ws/rest/v1', + bbox: [ + 54.7823600769043, + -2.6897950172424316, + 55.811668395996094, + -1.4027706384658813 + ], + name: 'Northumberland', + point: { + type: 'Point', + coordinates: [ + 55.23395538, + -2.04782939 + ] + }, + address: { + adminDistrict: 'Northumberland', + countryRegion: 'United Kingdom', + formattedAddress: 'Northumberland', + countryRegionIso2: 'GB' + }, + confidence: 'High', + entityType: 'AdminDivision1', + geocodePoints: [ + { + type: 'Point', + coordinates: [ + 55.23395538, + -2.04782939 + ], + calculationMethod: 'Rooftop', + usageTypes: [ + 'Display' + ] + } + ], + matchCodes: [ + 'Good' + ] }, - confidence: 'High', - entityType: 'Postcode3', - geocodePoints: [ - { + { + __type: 'Location:http://schemas.microsoft.com/search/local/ws/rest/v1', + bbox: [ + 54.7823600769043, + -2.6897950172424316, + 55.811676025390625, + -1.4601497650146484 + ], + name: 'Northumberland', + point: { type: 'Point', coordinates: [ - 54.01323318, - -1.45626473 - ], - calculationMethod: 'Rooftop', - usageTypes: [ - 'Display' + 55.17995834, + -1.80139947 ] - } - ], - matchCodes: [ - 'Good' - ] - } - - ] - const bingResponse = getBingResponse(resources) - const result = await bingResultsParser(bingResponse, stubGetEngland) + }, + address: { + adminDistrict: 'England', + adminDistrict2: 'Northumberland', + countryRegion: 'United Kingdom', + formattedAddress: 'Northumberland', + countryRegionIso2: 'GB' + }, + confidence: 'High', + entityType: 'AdminDivision2', + geocodePoints: [ + { + type: 'Point', + coordinates: [ + 55.17995834, + -1.80139947 + ], + calculationMethod: 'Rooftop', + usageTypes: [ + 'Display' + ] + } + ], + matchCodes: [ + 'Good' + ] + } + ] - const expectedResult = [ - { - name: 'Knaresborough, HG5', - query: 'HG5', - slug: 'hg5', - center: [-1.45626473, 54.01323318], - bbox2k: [ - -1.554794384621328, - 53.94681921279003, - -1.3136473163140359, - 54.09430703840006 - ], - bbox10k: [ - -1.677420274668105, - 53.874873651672104, - -1.1910214262672587, - 54.16625259905653 - ], - isUK: true, - isEngland: { is_england: true } - } - ] - expect(result).to.equal(expectedResult) - }) - test('successful address search should return empty result', async () => { - const resources = [ - { - __type: 'Location:http://schemas.microsoft.com/search/local/ws/rest/v1', - bbox: [ - 53.85298628242932, - -1.571548389701314, - 53.86071171757067, - -1.5540856102986857 - ], - name: '1 The Avenue, Alwoodley, Leeds, LS17 7BD', - point: { - type: 'Point', - coordinates: [ - 53.856849, - -1.562817 + const expectedResult = [ + // AdminDivision1 should take precedance over other entity types in + // response + { + name: 'Northumberland', + query: 'Northumberland', + slug: 'northumberland', + center: [-2.06545234, 55.24245834], + bbox2k: [ + -2.721803715494745, + 54.7643744779302, + -1.4282677862371271, + 55.829653988329156 + ], + bbox10k: [ + -2.8498382313297492, + 54.692432073840145, + -1.300233270402123, + 55.90159634910422 + ], + isUK: true, + isEngland: { is_england: true } + } + ] + checkParsedResponse(resources, expectedResult) + }) + test('english county search for ceremonial county should return the ceremonial county when there are no administrative counties', async () => { + const resources = [ + { + __type: 'Location:http://schemas.microsoft.com/search/local/ws/rest/v1', + bbox: [ + 54.03961944580078, + -3.6406314373016357, + 55.18899154663086, + -2.1589810848236084 + ], + name: 'Cumbria', + point: { + type: 'Point', + coordinates: [ + 54.57675934, + -2.91157079 + ] + }, + address: { + adminDistrict: 'Cumbria', + countryRegion: 'United Kingdom', + formattedAddress: 'Cumbria', + countryRegionIso2: 'GB' + }, + confidence: 'High', + entityType: 'AdminDivision1', + geocodePoints: [ + { + type: 'Point', + coordinates: [ + 54.57675934, + -2.91157079 + ], + calculationMethod: 'Rooftop', + usageTypes: [ + 'Display' + ] + } + ], + matchCodes: [ + 'Good' ] - }, - address: { - addressLine: '1 The Avenue', - adminDistrict: 'England', - adminDistrict2: 'West Yorkshire', - countryRegion: 'United Kingdom', - formattedAddress: '1 The Avenue, Alwoodley, Leeds, LS17 7BD', - locality: 'Leeds', - postalCode: 'LS17 7BD', - countryRegionIso2: 'GB' - }, - confidence: 'Medium', - entityType: 'Address', - geocodePoints: [ - { + } + ] + + // AdminDivision1 (ceremonial county) + const expectedResult = [ + { + name: 'Cumbria', + query: 'Cumbria', + slug: 'cumbria', + center: [-2.91157079, 54.57675934], + bbox2k: [ + -3.672137848226576, + 54.02163420058362, + -2.127474673898669, + 55.20697678117478 + ], + bbox10k: [ + -3.798163229693759, + 53.94969320818414, + -2.001449292431486, + 55.278917707262806 + ], + isUK: true, + isEngland: { is_england: true } + } + ] + checkParsedResponse(resources, expectedResult) + }) + test('successful postcode search should return populated result', async () => { + const resources = [ + { + __type: 'Location:http://schemas.microsoft.com/search/local/ws/rest/v1', + bbox: [ + 54.005693099079714, + -1.4739542828842274, + 54.01341853422107, + -1.4564274920181164 + ], + name: 'Knaresborough, HG5 0JL', + point: { type: 'Point', coordinates: [ - 53.856849, - -1.562817 - ], - calculationMethod: 'Rooftop', - usageTypes: [ - 'Display' + 54.00955582, + -1.46519089 ] }, - { + address: { + adminDistrict: 'England', + adminDistrict2: 'North Yorkshire', + countryRegion: 'United Kingdom', + formattedAddress: 'Knaresborough, HG5 0JL', + locality: 'Knaresborough', + postalCode: 'HG5 0JL', + countryRegionIso2: 'GB' + }, + confidence: 'High', + entityType: 'Postcode1', + geocodePoints: [ + { + type: 'Point', + coordinates: [ + 54.00955582, + -1.46519089 + ], + calculationMethod: 'Rooftop', + usageTypes: [ + 'Display' + ] + } + ], + matchCodes: [ + 'Good' + ] + } + ] + const expectedResult = [ + { + name: 'Knaresborough, HG5 0JL', + query: 'HG5 0JL', + slug: 'hg5-0jl', + center: [-1.46519089, 54.00955582], + bbox2k: [ + -1.5045644526149113, + 53.9877066919671, + -1.4258173222874324, + 54.03140494133353 + ], + bbox10k: [ + -1.6270049027390623, + 53.91576106351499, + -1.3033768721632815, + 54.10335056978173 + ], + isUK: true, + isEngland: { is_england: true } + } + ] + checkParsedResponse(resources, expectedResult) + }) + test('successful outcode search should return populated result', async () => { + const resources = [ + { + __type: 'Location:http://schemas.microsoft.com/search/local/ws/rest/v1', + bbox: [ + 53.964805603027344, + -1.5241378545761108, + 54.07632064819336, + -1.344303846359253 + ], + name: 'Knaresborough, HG5', + point: { type: 'Point', coordinates: [ - 53.8567274, - -1.5627166 - ], - calculationMethod: 'Rooftop', - usageTypes: [ - 'Route' + 54.01323318, + -1.45626473 ] - } - ], - matchCodes: [ - 'Ambiguous' - ] - } - ] - const bingResponse = getBingResponse(resources) - const result = await bingResultsParser(bingResponse, stubGetEngland) + }, + address: { + adminDistrict: 'England', + adminDistrict2: 'North Yorkshire', + countryRegion: 'United Kingdom', + formattedAddress: 'Knaresborough, HG5', + locality: 'Knaresborough', + postalCode: 'HG5', + countryRegionIso2: 'GB' + }, + confidence: 'High', + entityType: 'Postcode3', + geocodePoints: [ + { + type: 'Point', + coordinates: [ + 54.01323318, + -1.45626473 + ], + calculationMethod: 'Rooftop', + usageTypes: [ + 'Display' + ] + } + ], + matchCodes: [ + 'Good' + ] + } - expect(result).to.equal([]) - }) - test('high confidence response should return populated result', async () => { - const resources = [ - { - __type: 'Location:http://schemas.microsoft.com/search/local/ws/rest/v1', - bbox: [ - 53.99038314819336, - -1.5035173892974854, - 54.03419876098633, - -1.4180587530136108 - ], - name: 'Knaresborough, North Yorkshire', - point: { - type: 'Point', - coordinates: [ - 54.00714111, - -1.46303844 + ] + const expectedResult = [ + { + name: 'Knaresborough, HG5', + query: 'HG5', + slug: 'hg5', + center: [-1.45626473, 54.01323318], + bbox2k: [ + -1.554794384621328, + 53.94681921279003, + -1.3136473163140359, + 54.09430703840006 + ], + bbox10k: [ + -1.677420274668105, + 53.874873651672104, + -1.1910214262672587, + 54.16625259905653 + ], + isUK: true, + isEngland: { is_england: true } + } + ] + checkParsedResponse(resources, expectedResult) + }) + test('successful address search should return empty result', async () => { + const resources = [ + { + __type: 'Location:http://schemas.microsoft.com/search/local/ws/rest/v1', + bbox: [ + 53.85298628242932, + -1.571548389701314, + 53.86071171757067, + -1.5540856102986857 + ], + name: '1 The Avenue, Alwoodley, Leeds, LS17 7BD', + point: { + type: 'Point', + coordinates: [ + 53.856849, + -1.562817 + ] + }, + address: { + addressLine: '1 The Avenue', + adminDistrict: 'England', + adminDistrict2: 'West Yorkshire', + countryRegion: 'United Kingdom', + formattedAddress: '1 The Avenue, Alwoodley, Leeds, LS17 7BD', + locality: 'Leeds', + postalCode: 'LS17 7BD', + countryRegionIso2: 'GB' + }, + confidence: 'Medium', + entityType: 'Address', + geocodePoints: [ + { + type: 'Point', + coordinates: [ + 53.856849, + -1.562817 + ], + calculationMethod: 'Rooftop', + usageTypes: [ + 'Display' + ] + }, + { + type: 'Point', + coordinates: [ + 53.8567274, + -1.5627166 + ], + calculationMethod: 'Rooftop', + usageTypes: [ + 'Route' + ] + } + ], + matchCodes: [ + 'Ambiguous' ] - }, - address: { - adminDistrict: 'England', - adminDistrict2: 'North Yorkshire', - countryRegion: 'United Kingdom', - formattedAddress: 'Knaresborough, North Yorkshire', - locality: 'Knaresborough', - countryRegionIso2: 'GB' - }, - confidence: 'High', - entityType: 'PopulatedPlace', - geocodePoints: [ - { + } + ] + checkParsedResponse(resources, []) + }) + test('high confidence response should return populated result', async () => { + const resources = [ + { + __type: 'Location:http://schemas.microsoft.com/search/local/ws/rest/v1', + bbox: [ + 53.99038314819336, + -1.5035173892974854, + 54.03419876098633, + -1.4180587530136108 + ], + name: 'Knaresborough, North Yorkshire', + point: { type: 'Point', coordinates: [ 54.00714111, -1.46303844 - ], - calculationMethod: 'Rooftop', - usageTypes: [ - 'Display' ] - } - ], - matchCodes: [ - 'Good' - ] - } - ] - const bingResponse = getBingResponse(resources) - const result = await bingResultsParser(bingResponse, stubGetEngland) - - const expectedResult = [ - { - name: 'Knaresborough, North Yorkshire', - query: 'Knaresborough, North Yorkshire', - slug: 'knaresborough-north-yorkshire', - center: [-1.46303844, 54.00714111], - bbox2k: [ - -1.534142855800849, - 53.972396744766755, - -1.3874332865102472, - 54.05218516440792 - ], - bbox10k: [ - -1.6566444925899468, - 53.90045113102211, - -1.2649316497211494, - 54.12413077805586 - ], - isUK: true, - isEngland: { is_england: true } - } - ] - expect(result).to.equal(expectedResult) - }) - test('medium confidence response should return empty result', async () => { - const resources = [ - { - __type: 'Location:http://schemas.microsoft.com/search/local/ws/rest/v1', - bbox: [ - 53.99038314819336, - -1.5035173892974854, - 54.03419876098633, - -1.4180587530136108 - ], - name: 'Knaresborough, North Yorkshire', - point: { - type: 'Point', - coordinates: [ - 54.00714111, - -1.46303844 + }, + address: { + adminDistrict: 'England', + adminDistrict2: 'North Yorkshire', + countryRegion: 'United Kingdom', + formattedAddress: 'Knaresborough, North Yorkshire', + locality: 'Knaresborough', + countryRegionIso2: 'GB' + }, + confidence: 'High', + entityType: 'PopulatedPlace', + geocodePoints: [ + { + type: 'Point', + coordinates: [ + 54.00714111, + -1.46303844 + ], + calculationMethod: 'Rooftop', + usageTypes: [ + 'Display' + ] + } + ], + matchCodes: [ + 'Good' ] - }, - address: { - adminDistrict: 'England', - adminDistrict2: 'North Yorkshire', - countryRegion: 'United Kingdom', - formattedAddress: 'Knaresborough, North Yorkshire', - locality: 'Knaresborough', - countryRegionIso2: 'GB' - }, - confidence: 'Medium', - entityType: 'PopulatedPlace', - geocodePoints: [ - { + } + ] + const expectedResult = [ + { + name: 'Knaresborough, North Yorkshire', + query: 'Knaresborough, North Yorkshire', + slug: 'knaresborough-north-yorkshire', + center: [-1.46303844, 54.00714111], + bbox2k: [ + -1.534142855800849, + 53.972396744766755, + -1.3874332865102472, + 54.05218516440792 + ], + bbox10k: [ + -1.6566444925899468, + 53.90045113102211, + -1.2649316497211494, + 54.12413077805586 + ], + isUK: true, + isEngland: { is_england: true } + } + ] + checkParsedResponse(resources, expectedResult) + }) + test('medium confidence response should return empty result', async () => { + const resources = [ + { + __type: 'Location:http://schemas.microsoft.com/search/local/ws/rest/v1', + bbox: [ + 53.99038314819336, + -1.5035173892974854, + 54.03419876098633, + -1.4180587530136108 + ], + name: 'Knaresborough, North Yorkshire', + point: { type: 'Point', coordinates: [ 54.00714111, -1.46303844 - ], - calculationMethod: 'Rooftop', - usageTypes: [ - 'Display' ] - } - ], - matchCodes: [ - 'Good' - ] - } - ] - const bingResponse = getBingResponse(resources) - const result = await bingResultsParser(bingResponse, stubGetEngland) - - const expectedResult = [] - expect(result).to.equal(expectedResult) - }) - test('low confidence response should return empty result', async () => { - const resources = [ - { - __type: 'Location:http://schemas.microsoft.com/search/local/ws/rest/v1', - bbox: [ - 53.99038314819336, - -1.5035173892974854, - 54.03419876098633, - -1.4180587530136108 - ], - name: 'Knaresborough, North Yorkshire', - point: { - type: 'Point', - coordinates: [ - 54.00714111, - -1.46303844 + }, + address: { + adminDistrict: 'England', + adminDistrict2: 'North Yorkshire', + countryRegion: 'United Kingdom', + formattedAddress: 'Knaresborough, North Yorkshire', + locality: 'Knaresborough', + countryRegionIso2: 'GB' + }, + confidence: 'Medium', + entityType: 'PopulatedPlace', + geocodePoints: [ + { + type: 'Point', + coordinates: [ + 54.00714111, + -1.46303844 + ], + calculationMethod: 'Rooftop', + usageTypes: [ + 'Display' + ] + } + ], + matchCodes: [ + 'Good' ] - }, - address: { - adminDistrict: 'England', - adminDistrict2: 'North Yorkshire', - countryRegion: 'United Kingdom', - formattedAddress: 'Knaresborough, North Yorkshire', - locality: 'Knaresborough', - countryRegionIso2: 'GB' - }, - confidence: 'Low', - entityType: 'PopulatedPlace', - geocodePoints: [ - { + } + ] + const expectedResult = [] + checkParsedResponse(resources, expectedResult) + }) + test('low confidence response should return empty result', async () => { + const resources = [ + { + __type: 'Location:http://schemas.microsoft.com/search/local/ws/rest/v1', + bbox: [ + 53.99038314819336, + -1.5035173892974854, + 54.03419876098633, + -1.4180587530136108 + ], + name: 'Knaresborough, North Yorkshire', + point: { type: 'Point', coordinates: [ 54.00714111, -1.46303844 - ], - calculationMethod: 'Rooftop', - usageTypes: [ - 'Display' ] - } - ], - matchCodes: [ - 'Good' - ] - } - ] - const bingResponse = getBingResponse(resources) - const result = await bingResultsParser(bingResponse, stubGetEngland) - - expect(result).to.equal([]) - }) - test('multiple items in response should return the first non-low confidence result', async () => { - // Note: we currently limit the max results returned from bing using the maxResults URL query parameter - // to just 1 - // The results are still returned as an array with just a single entry but this test demonstrates that - // the code works should this parameter change in the future. - const resources = [ - { - __type: 'Location:http://schemas.microsoft.com/search/local/ws/rest/v1', - bbox: [ - 51.12405776977539, - 0.8380475640296936, - 51.17716598510742, - 0.9264887571334839 - ], - name: 'Ashford, Kent', - point: { - type: 'Point', - coordinates: [ - 51.14772797, - 0.87279475 + }, + address: { + adminDistrict: 'England', + adminDistrict2: 'North Yorkshire', + countryRegion: 'United Kingdom', + formattedAddress: 'Knaresborough, North Yorkshire', + locality: 'Knaresborough', + countryRegionIso2: 'GB' + }, + confidence: 'Low', + entityType: 'PopulatedPlace', + geocodePoints: [ + { + type: 'Point', + coordinates: [ + 54.00714111, + -1.46303844 + ], + calculationMethod: 'Rooftop', + usageTypes: [ + 'Display' + ] + } + ], + matchCodes: [ + 'Good' ] - }, - address: { - adminDistrict: 'England', - adminDistrict2: 'Kent', - countryRegion: 'United Kingdom', - formattedAddress: 'Ashford, Kent', - locality: 'Ashford', - countryRegionIso2: 'GB' - }, - confidence: 'High', - entityType: 'PopulatedPlace', - geocodePoints: [ - { + } + ] + checkParsedResponse(resources, []) + }) + test('multiple items in response should return the only high confidence result', async () => { + // Note: we currently limit the max results returned from bing using the maxResults URL query parameter + // to just 1 + // The results are still returned as an array with just a single entry but this test demonstrates that + // the code works should this parameter change in the future. + const resources = [ + { + __type: 'Location:http://schemas.microsoft.com/search/local/ws/rest/v1', + bbox: [ + 51.12405776977539, + 0.8380475640296936, + 51.17716598510742, + 0.9264887571334839 + ], + name: 'Ashford, Kent', + point: { type: 'Point', coordinates: [ 51.14772797, 0.87279475 - ], - calculationMethod: 'Rooftop', - usageTypes: [ - 'Display' ] - } - ], - matchCodes: [ - 'Good' - ] - }, - { - __type: 'Location:http://schemas.microsoft.com/search/local/ws/rest/v1', - bbox: [ - 51.02236557006836, - -0.4873929023742676, - 51.44563293457031, - 1.0504722595214844 - ], - name: 'Ashford, Surrey', - point: { - type: 'Point', - coordinates: [ - 51.43230057, - -0.46049938 + }, + address: { + adminDistrict: 'England', + adminDistrict2: 'Kent', + countryRegion: 'United Kingdom', + formattedAddress: 'Ashford, Kent', + locality: 'Ashford', + countryRegionIso2: 'GB' + }, + confidence: 'High', + entityType: 'PopulatedPlace', + geocodePoints: [ + { + type: 'Point', + coordinates: [ + 51.14772797, + 0.87279475 + ], + calculationMethod: 'Rooftop', + usageTypes: [ + 'Display' + ] + } + ], + matchCodes: [ + 'Good' ] }, - address: { - adminDistrict: 'England', - adminDistrict2: 'Surrey', - countryRegion: 'United Kingdom', - formattedAddress: 'Ashford, Surrey', - locality: 'Ashford', - countryRegionIso2: 'GB' - }, - confidence: 'Medium', - entityType: 'PopulatedPlace', - geocodePoints: [ - { + { + __type: 'Location:http://schemas.microsoft.com/search/local/ws/rest/v1', + bbox: [ + 51.02236557006836, + -0.4873929023742676, + 51.44563293457031, + 1.0504722595214844 + ], + name: 'Ashford, Surrey', + point: { type: 'Point', coordinates: [ 51.43230057, -0.46049938 - ], - calculationMethod: 'Rooftop', - usageTypes: [ - 'Display' ] - } - ], - matchCodes: [ - 'Good' - ] - }, - { - __type: 'Location:http://schemas.microsoft.com/search/local/ws/rest/v1', - bbox: [ - 50.89913783233948, - -1.8659905171462143, - 50.957078598324586, - -1.7434982085160171 - ], - name: 'Ashford, Fordingbridge, Hampshire', - point: { - type: 'Point', - coordinates: [ - 50.92810822, - -1.80474436 + }, + address: { + adminDistrict: 'England', + adminDistrict2: 'Surrey', + countryRegion: 'United Kingdom', + formattedAddress: 'Ashford, Surrey', + locality: 'Ashford', + countryRegionIso2: 'GB' + }, + confidence: 'Medium', + entityType: 'PopulatedPlace', + geocodePoints: [ + { + type: 'Point', + coordinates: [ + 51.43230057, + -0.46049938 + ], + calculationMethod: 'Rooftop', + usageTypes: [ + 'Display' + ] + } + ], + matchCodes: [ + 'Good' ] }, - address: { - adminDistrict: 'England', - adminDistrict2: 'Hampshire', - countryRegion: 'United Kingdom', - formattedAddress: 'Ashford, Fordingbridge, Hampshire', - locality: 'Ashford', - countryRegionIso2: 'GB' - }, - confidence: 'Medium', - entityType: 'PopulatedPlace', - geocodePoints: [ - { + { + __type: 'Location:http://schemas.microsoft.com/search/local/ws/rest/v1', + bbox: [ + 50.89913783233948, + -1.8659905171462143, + 50.957078598324586, + -1.7434982085160171 + ], + name: 'Ashford, Fordingbridge, Hampshire', + point: { type: 'Point', coordinates: [ 50.92810822, -1.80474436 - ], - calculationMethod: 'Rooftop', - usageTypes: [ - 'Display' ] - } - ], - matchCodes: [ - 'Good' - ] - }, - { - __type: 'Location:http://schemas.microsoft.com/search/local/ws/rest/v1', - bbox: [ - 50.9887580871582, - 0.5919979214668274, - 51.26953887939453, - 1.030938744544983 - ], - name: 'Ashford', - point: { - type: 'Point', - coordinates: [ - 51.13436127, - 0.83433753 + }, + address: { + adminDistrict: 'England', + adminDistrict2: 'Hampshire', + countryRegion: 'United Kingdom', + formattedAddress: 'Ashford, Fordingbridge, Hampshire', + locality: 'Ashford', + countryRegionIso2: 'GB' + }, + confidence: 'Medium', + entityType: 'PopulatedPlace', + geocodePoints: [ + { + type: 'Point', + coordinates: [ + 50.92810822, + -1.80474436 + ], + calculationMethod: 'Rooftop', + usageTypes: [ + 'Display' + ] + } + ], + matchCodes: [ + 'Good' ] }, - address: { - adminDistrict: 'England', - adminDistrict2: 'Kent', - countryRegion: 'United Kingdom', - formattedAddress: 'Ashford', - countryRegionIso2: 'GB' - }, - confidence: 'Low', - entityType: 'AdminDivision3', - geocodePoints: [ - { + { + __type: 'Location:http://schemas.microsoft.com/search/local/ws/rest/v1', + bbox: [ + 50.9887580871582, + 0.5919979214668274, + 51.26953887939453, + 1.030938744544983 + ], + name: 'Ashford', + point: { type: 'Point', coordinates: [ 51.13436127, 0.83433753 - ], - calculationMethod: 'Rooftop', - usageTypes: [ - 'Display' ] - } - ], - matchCodes: [ - 'Good' - ] - } - ] - const bingResponse = getBingResponse(resources) - const result = await bingResultsParser(bingResponse, stubGetEngland) + }, + address: { + adminDistrict: 'England', + adminDistrict2: 'Kent', + countryRegion: 'United Kingdom', + formattedAddress: 'Ashford', + countryRegionIso2: 'GB' + }, + confidence: 'Low', + entityType: 'AdminDivision3', + geocodePoints: [ + { + type: 'Point', + coordinates: [ + 51.13436127, + 0.83433753 + ], + calculationMethod: 'Rooftop', + usageTypes: [ + 'Display' + ] + } + ], + matchCodes: [ + 'Good' + ] + } + ] - const expectedResult = [ - { - name: 'Ashford, Kent', - query: 'Ashford, Kent', - slug: 'ashford-kent', - center: [0.87279475, 51.14772797], - bbox2k: [ - 0.80935719234919, - 51.106071366450024, - 0.9551791288139874, - 51.19515238842755 - ], - bbox10k: [ - 0.6945958802395501, - 51.034125753112406, - 1.0699404409236273, - 51.267098001671634 - ], - isUK: true, - isEngland: { is_england: true } - } - ] - expect(result).to.equal(expectedResult) + const expectedResult = [ + { + name: 'Ashford, Kent', + query: 'Ashford, Kent', + slug: 'ashford-kent', + center: [0.87279475, 51.14772797], + bbox2k: [ + 0.80935719234919, + 51.106071366450024, + 0.9551791288139874, + 51.19515238842755 + ], + bbox10k: [ + 0.6945958802395501, + 51.034125753112406, + 1.0699404409236273, + 51.267098001671634 + ], + isUK: true, + isEngland: { is_england: true } + } + ] + checkParsedResponse(resources, expectedResult) + }) }) }) experiment('non-english searches', () => { - const stubGetEngland = async () => { return { is_england: false } } test('scottish town location search should return empty result', async () => { const resources = [ { @@ -968,7 +1307,7 @@ experiment('bingResultsParser', () => { ] const expectedResult = [ ] - checkParsedResponse(resources, stubGetEngland, expectedResult) + checkParsedResponse(resources, expectedResult) }) test('welsh preserved county location search should return empty result', async () => { const resources = [ @@ -1016,7 +1355,7 @@ experiment('bingResultsParser', () => { ] const expectedResult = [ ] - checkParsedResponse(resources, stubGetEngland, expectedResult) + checkParsedResponse(resources, expectedResult) }) }) }) diff --git a/test/services/lib/bing-utils.js b/test/services/lib/bing-utils.js new file mode 100644 index 000000000..dc11ba4f2 --- /dev/null +++ b/test/services/lib/bing-utils.js @@ -0,0 +1,43 @@ +const Lab = require('@hapi/lab') +const { expect } = require('@hapi/code') +const { experiment, it } = exports.lab = Lab.script() +const { formatName, slugify } = require('../../../server/services/lib/bing-utils') + +experiment('bing utils', () => { + experiment('formatName', () => { + it('Repeating parts are removed', async () => { + expect(formatName('Middlesbrough, Middlesbrough')).to.equal('Middlesbrough') + }) + it('Similar parts are not removed', async () => { + expect(formatName('Durham, County Durham')).to.equal('Durham, County Durham') + }) + it('City qualifier "City Of" is removed', async () => { + expect(formatName('Bristol, City Of Bristol')).to.equal('Bristol') + }) + it('City qualifier "City of" is removed (ie case insensitive)', async () => { + expect(formatName('Bristol, City of Bristol')).to.equal('Bristol') + }) + it('City qualifier "Greater" is removed', async () => { + expect(formatName('London, Greater London')).to.equal('London') + }) + it('City qualifier removed from neighbourhood', async () => { + expect(formatName('Camberwell, London, Greater London')).to.equal('Camberwell, London') + }) + experiment('Error handling', () => { + it('Fails gracefully with undefined name', async () => { + expect(formatName()).to.equal('') + }) + }) + }) + experiment('slugify', () => { + it('Simple name is slugified', async () => { + expect(slugify('Leeds, West Yorkshire')).to.equal('leeds-west-yorkshire') + }) + it('Non-alphanumerics (!) are preserved', async () => { + expect(slugify('Westward Ho!, Bideford, Devon')).to.equal('westward-ho!-bideford-devon') + }) + it('Non-alphanumerics (\') are preserved', async () => { + expect(slugify('Bishop\'s Stortford')).to.equal('bishop\'s-stortford') + }) + }) +}) diff --git a/test/services/location-integration.js b/test/services/location-integration.js new file mode 100644 index 000000000..36af25c67 --- /dev/null +++ b/test/services/location-integration.js @@ -0,0 +1,322 @@ +'use strict' + +const Lab = require('@hapi/lab') +const { expect } = require('@hapi/code') +const { describe, it } = exports.lab = Lab.script() +const location = require('../../server/services/location') + +// Generated by ChatGPT as a sample of 100 English villages +// commented out villages are failures and appear in the sad path +// lists below +const villages = [ + 'Abbotsbury', + 'Alfriston', + 'Amberley', + 'Appledore', + 'Ashwell', + 'Braithwaite', + 'Broadway', + 'Buckfastleigh', + 'Castle Combe', + 'Chipping Campden', + 'Clovelly', + 'Cranborne', + 'Craster', + 'Dedham', + 'Dittisham', + 'Dunster', + 'East Dean', + 'Ebrington', + 'Elterwater', + 'Exbury', + 'Fingest', + 'Finchingfield', + 'Fordwich', + 'Grasmere', + 'Great Tew', + 'Hathersage', + 'Hawkshead', + 'Hever', + 'Heyshott', + 'Hinton St George', + 'Holme-next-the-Sea', + 'Hoxne', + 'Icklesham', + 'Ilmington', + 'Kersey', + 'Kingswear', + 'Lacock', + 'Langton Herring', + 'Lechlade', + // 'Little Moreton', + 'Lustleigh', + 'Lympstone', + 'Malham', + 'Marsden', + 'Mevagissey', + 'Monyash', + 'Mousehole', + 'Nether Stowey', + 'Norham', + 'Nunney', + 'Orford', + 'Overbury', + 'Polperro', + 'Porlock', + 'Ravenglass', + 'Robin Hood’s Bay', + 'Rye', + 'Selworthy', + 'Shere', + // 'Slad', + 'Snowshill', + 'Southwold', + 'St Mawes', + 'Staithes', + 'Stanton', + 'Steeple Ashton', + 'Stoke Gabriel', + 'Stow-on-the-Wold', + 'Swanton Morley', + 'Symondsbury', + 'Taddington', + 'Tatsfield', + 'Thornton-le-Dale', + 'Tintagel', + 'Tissington', + 'Turville', + 'Upper Slaughter', + 'Warkworth', + 'Watermillock', + 'West Hoathly', + 'West Wycombe', + 'Widecombe-in-the-Moor', + 'Winchelsea', + 'Windsor', + 'Wooler', + 'Woolpit', + 'Wrotham', + 'Wycoller', + 'Yalding', + 'Yealmpton', + 'Yoxford' +] + +// Generated by ChatGPT as a sample of 100 English towns +const towns = [ + 'Alnwick', + 'Amersham', + 'Andover', + 'Ashbourne', + 'Ashby-de-la-Zouch', + 'Atherstone', + 'Bakewell', + 'Banbury', + 'Basingstoke', + 'Beverley', + 'Bishop\'s Stortford', + 'Bodmin', + 'Boston', + 'Bridgnorth', + 'Bridport', + 'Brigg', + 'Brixham', + 'Bude', + 'Burford', + 'Burnham-on-Sea', + 'Calne', + 'Cannock', + 'Chesham', + 'Chesterfield', + 'Chippenham', + 'Cirencester', + 'Clitheroe', + 'Congleton', + 'Corby', + 'Corsham', + 'Crediton', + 'Crewkerne', + 'Dartington', + 'Daventry', + 'Deal', + 'Dereham', + 'Devizes', + 'Didcot', + 'Diss', + 'Dorchester', + 'Dorking', + 'Dover', + 'Easingwold', + 'Evesham', + 'Fakenham', + 'Farnham', + 'Fleet', + 'Gainsborough', + 'Garstang', + 'Godalming', + 'Grantham', + 'Grays', + 'Halesowen', + 'Harlow', + 'Harpenden', + 'Helston', + 'Hertford', + 'Hexham', + 'Hitchin', + 'Honiton', + 'Hungerford', + 'Ilkeston', + 'Kendal', + 'Kenilworth', + 'Keswick', + 'Kidderminster', + 'Kirkby Lonsdale', + 'Knutsford', + 'Leek', + 'Liskeard', + 'Louth', + 'Lutterworth', + 'Malton', + 'Malvern', + 'Market Harborough', + 'Market Rasen', + 'Marlborough', + 'Melton Mowbray', + 'Midhurst', + 'Morpeth', + 'Nantwich', + 'Newark-on-Trent', + 'Newbury', + 'Newmarket', + 'Northallerton', + 'Oakham', + 'Okehampton', + 'Penrith', + 'Pershore', + 'Pickering', + 'Retford', + 'Richmond, North Yorkshire', + 'Ringwood', + 'Ripon', + 'Ross-on-Wye', + 'Saffron Walden', + 'Sandbach', + 'Shepton Mallet', + 'St Ives, Cornwall', + 'Stone', + 'Wetherby' +] + +const counties = [ + 'Bedfordshire', + 'Berkshire', + 'Buckinghamshire', + 'Cambridgeshire', + 'Cheshire', + 'Cornwall', + 'Cumbria', + 'Derbyshire', + 'Devon', + 'Dorset', + 'Durham', + 'East Sussex', + 'Essex', + 'Gloucestershire', + 'Greater Manchester', + 'Hampshire', + 'Herefordshire', + 'Hertfordshire', + 'Isle of Wight', + 'Kent', + 'Lancashire', + 'Leicestershire', + 'Lincolnshire', + 'Shropshire', + 'West Yorkshire' +] + +const cities = [ + 'Bath', + 'Birmingham', + 'Bradford', + 'Brighton and Hove', + 'Bristol', + 'Cambridge', + 'Canterbury', + 'Carlisle', + 'Chester', + 'Coventry', + 'Derby', + 'Durham', + 'Exeter', + 'Gloucester', + 'Kingston upon Hull', + 'Lancaster', + 'Leeds', + 'Leicester', + 'Liverpool', + 'Manchester', + 'Newcastle upon Tyne', + 'Norwich', + 'Nottingham', + 'Oxford', + 'Sheffield' +] + +// These are towns where searching by the name doesn't +// return a high confidence UK location +const unknownPlaces = [ + 'Little Moreton', + 'Slad', + 'Totnes' +] + +// These are towns where searching by the Bing qualified name doesn't +// return a location which matches the slugified Bing name +const brokenPlaces = [ + 'Kington' // => Kington, County of Herefordshire => none +] + +// These are towns where searching by the Bing qualified name +// returns a different high confidence location which doesn't match +// the Bing name, was hoxne but now works +const brokenPlaces2 = [ +] + +async function resultCheck (town) { + const result1 = await location.find(town) + expect(result1.length, `No match found for ${town}`).to.be.greaterThan(0) + const result2 = await location.get(result1[0].slug) + expect(result2.length, `No match found when searching by qualified name (${result1[0].name})`).to.equal(1) + expect(result1[0].name).to.equal(result2[0].name) +} + +// These tests document known issues with bing search and, as such, they will +// will hit bing and the sad path tests will fail (hence the use of skip) +// They are retained as a useful check on Bing search and are intended to be run +// ad-hoc (by selective removal of skip and the --grep option of lab +describe.skip('location service integration tests', () => { + // Helper function to dynamically generate tests + const generateTests = (places, description) => { + describe(description, () => { + places.forEach(place => { + it(`should find result for place ${place}`, async () => { + await resultCheck(place) + }) + }) + }) + } + + describe('Happy path', () => { + generateTests(villages, 'Villages: Searching by found name returns the same location') + generateTests(towns, 'Towns: Searching by found name returns the same location') + generateTests(counties, 'Counties: Searching by found name returns the same location') + generateTests(cities, 'Cities: Searching by found name returns the same location') + }) + + describe('Sad path: known Bing issues', () => { + generateTests(unknownPlaces, 'Searching by name finds no location') + generateTests(brokenPlaces, 'Searching by found name returns no location') + generateTests(brokenPlaces2, 'Searching by found name returns different location') + }) +}) diff --git a/test/services/location.js b/test/services/location.js index 3fd76762e..f07d8a80a 100644 --- a/test/services/location.js +++ b/test/services/location.js @@ -10,24 +10,21 @@ const adminDivision1 = require('./data/location/wales.json') const LocationSearchError = require('../../server/location-search-error') const flushAppRequireCache = require('../lib/flush-app-require-cache') -function setupStubs (context, locationData, isEngland = true) { +function setupStubs (context, locationData) { context.stubs.getJson.onFirstCall().returns(locationData) - context.stubs.getIsEngland.returns({ is_england: isEngland }) } describe('location service', () => { - let location, util, floodServices, context + let location, util, context beforeEach(() => { flushAppRequireCache() const config = require('../../server/config') sinon.stub(config, 'bingUrl').value('http://bing?query=%s&maxResults=%s&key=%s') sinon.stub(config, 'bingKeyLocation').value('12345') util = require('../../server/util') - floodServices = require('../../server/services/flood') context = { stubs: { - getJson: sinon.stub(util, 'getJson'), - getIsEngland: sinon.stub(floodServices, 'getIsEngland') + getJson: sinon.stub(util, 'getJson') } } location = require('../../server/services/location') @@ -85,7 +82,7 @@ describe('location service', () => { const searchTerm = 'ashford' await location.find(searchTerm) expect(context.stubs.getJson.callCount).to.equal(1) - expect(context.stubs.getJson.args[0][0]).to.equal(`http://bing?query=${searchTerm}&maxResults=3&key=12345`) + expect(context.stubs.getJson.args[0][0]).to.equal(`http://bing?query=${searchTerm}&maxResults=5&key=12345`) }) it('should not query Bing if search term is longer than 60 characters', async () => { setupStubs(context, {}) @@ -149,4 +146,14 @@ describe('location service', () => { }) }) }) + describe('get happy path', () => { + describe('get by slug', () => { + it('should return result matching slug', async () => { + setupStubs(context, populatedPlace) + const result = await location.get('ashford-kent') + expect(result.length).to.equal(1) + expect(result[0].name).to.equal('Ashford, Kent') + }) + }) + }) })