diff --git a/package-lock.json b/package-lock.json index cd1faabe0..e3385483a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "flood-app", - "version": "8.4.0", + "version": "8.5.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "flood-app", - "version": "8.4.0", + "version": "8.5.0", "hasInstallScript": true, "license": "ISC", "dependencies": { @@ -72,7 +72,7 @@ "husky": "^8.0.3", "jsdom": "^22.1.0", "mockdate": "^3.0.5", - "node-html-parser": "^6.1.11", + "node-html-parser": "^6.1.13", "nodemon": "^3.0.1", "proxyquire": "^2.1.3" }, @@ -8791,9 +8791,9 @@ } }, "node_modules/node-html-parser": { - "version": "6.1.12", - "resolved": "https://registry.npmjs.org/node-html-parser/-/node-html-parser-6.1.12.tgz", - "integrity": "sha512-/bT/Ncmv+fbMGX96XG9g05vFt43m/+SYKIs9oAemQVYyVcZmDAI2Xq/SbNcpOA35eF0Zk2av3Ksf+Xk8Vt8abA==", + "version": "6.1.13", + "resolved": "https://registry.npmjs.org/node-html-parser/-/node-html-parser-6.1.13.tgz", + "integrity": "sha512-qIsTMOY4C/dAa5Q5vsobRpOOvPfC4pB61UVW2uSwZNUp0QU/jCekTal1vMmbO0DgdHeLUJpv/ARmDqErVxA3Sg==", "dev": true, "dependencies": { "css-select": "^5.1.0", diff --git a/package.json b/package.json index b65dcce5f..96ee182e3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "flood-app", - "version": "8.4.0", + "version": "8.5.0", "description": "Flood risk app", "main": "index.js", "repository": "github:defra/flood-app", @@ -96,7 +96,7 @@ "husky": "^8.0.3", "jsdom": "^22.1.0", "mockdate": "^3.0.5", - "node-html-parser": "^6.1.11", + "node-html-parser": "^6.1.13", "nodemon": "^3.0.1", "proxyquire": "^2.1.3" } diff --git a/release-docs/CFF-8.5.0.md b/release-docs/CFF-8.5.0.md new file mode 100644 index 000000000..3ec86f03b --- /dev/null +++ b/release-docs/CFF-8.5.0.md @@ -0,0 +1,27 @@ +# Check For Flooding Release + +* Version: 8.5.0 +* Proposed Release Date: 17th July 2024 +* Jira Release Overview: https://eaflood.atlassian.net/projects/FSR/versions/17136/tab/release-report-all-issues + + +## Tickets + + + * FSR-1208 | river and sea levels search redirects (#723) + + * FSR-1207 | update page title to use place name + + + +## Instructions + + + 1 - Execute LFW_{STAGE}_04_UPDATE_FLOOD_APP_AND_SERVICE_PIPELINE + + +Execute smoke tests and forward results + +## Related Infrastructure Changes Required + +* None diff --git a/server/models/views/alerts-and-warnings.js b/server/models/views/alerts-and-warnings.js index 65ef129d3..0a109a20f 100644 --- a/server/models/views/alerts-and-warnings.js +++ b/server/models/views/alerts-and-warnings.js @@ -2,36 +2,39 @@ const { bingKeyMaps } = require('../../config') const config = require('../../config') class ViewModel { - constructor ({ location, place, floods, station, error }) { + constructor ({ q, location, place, floods = [], station, canonical, error }) { Object.assign(this, { - q: location, + q, + station, map: station ? 'map-station' : 'map', - station: station || null, - placeName: place ? place.name : '', - placeBbox: place ? place.bbox2k : [], - placeCentre: place ? place.center : [], + placeName: place?.name || '', + placeBbox: place?.bbox2k || [], + placeCentre: place?.center || [], timestamp: Date.now(), - error: error ? true : null, + metaCanonical: canonical, + canonicalUrl: canonical, + error: !!error, displayGetWarningsLink: true, displayLongTermLink: true, - isEngland: place ? place.isEngland.is_england : null, - isDummyData: floods ? floods.isDummyData : false, + isEngland: place?.isEngland?.is_england, + isDummyData: floods?.isDummyData, floodRiskUrl: config.floodRiskUrl }) + if (this.station && this.station.agency_name) { + this.pageTitle = `${this.station.agency_name} - flood alerts and warnings` + } else { + const pageTitle = place?.name ?? location + + this.pageTitle = `${pageTitle ? pageTitle + ' - f' : 'F'}lood alerts and warnings` + } + if (error) { this.pageTitle = 'Sorry, there is currently a problem searching a location' - } else { - if (this.station && this.station.agency_name) { - this.pageTitle = `${this.station.agency_name} - flood alerts and warnings` - } else { - this.pageTitle = `${location ? location + ' - f' : 'F'}lood alerts and warnings` - } } - this.countFloods = floods ? floods.floods.length : 0 - this.floods = floods - ? floods.groups.map(item => item) - : [] + + this.countFloods = floods?.floods?.length ?? 0 + this.floods = floods?.groups?.map(item => item) this.expose = { station: this.station, diff --git a/server/models/views/river-and-sea-levels.js b/server/models/views/river-and-sea-levels.js index dc100de3b..6579a4437 100644 --- a/server/models/views/river-and-sea-levels.js +++ b/server/models/views/river-and-sea-levels.js @@ -112,7 +112,7 @@ function referencedStationViewModel (referencePoint, stations) { } } -function placeViewModel ({ location, place, stations = [], queryGroup }) { +function placeViewModel ({ location, place, stations = [], queryGroup, canonical, q }) { let distStatement, title, description const isEngland = place ? place.isEngland.is_england : true @@ -128,10 +128,10 @@ function placeViewModel ({ location, place, stations = [], queryGroup }) { deleteUndefinedProperties(stations) if (location && isEngland) { - title = `${location} - ${pageTitle}` + title = `${place.name ?? location} - ${pageTitle}` description = `Find river, sea, groundwater and rainfall levels in ${location}. Check the last updated height, trend and state recorded by the measuring station.` } else { - title = location ? `${location} - ${pageTitle}` : pageTitle + title = place ? `${place.name} - ${pageTitle}` : pageTitle description = metaDescription } @@ -141,13 +141,15 @@ function placeViewModel ({ location, place, stations = [], queryGroup }) { filters, floodRiskUrl, distStatement, - q: location, + q: q || location, clientModel: getClientModel(isEngland ? place.bbox10k : []), queryGroup: activeFilter, placeAddress: place.address, ...getDataJourneyClickStrings(), pageTitle: title, - metaDescription: description + metaDescription: description, + metaCanonical: canonical, + canonicalUrl: canonical } } diff --git a/server/routes/alerts-and-warnings.js b/server/routes/alerts-and-warnings.js index f371f2b6c..66557dc37 100644 --- a/server/routes/alerts-and-warnings.js +++ b/server/routes/alerts-and-warnings.js @@ -3,90 +3,160 @@ const ViewModel = require('../models/views/alerts-and-warnings') const Floods = require('../models/floods') const locationService = require('../services/location') const util = require('../util') +const { + slugify, + isLocationEngland, + isValidLocationSlug, + isPlaceEngland, + failActionHandler, + renderNotFound, + renderLocationNotFound, + createQueryParametersString +} = require('./lib/utils') + +const route = 'alerts-and-warnings' +const QUERY_STRING_LOCATION_MAX_LENGTH = 200 + +async function routeHandler (request, h) { + let location = request.query.q || request.payload?.location + + location = util.cleanseLocation(location) + + request.yar.set('q', { location }) + + const direction = request.query.direction === 'downstream' ? 'd' : 'u' + + let model, floods + + if (request.query.station) { + const station = await request.server.methods.flood.getStationById(request.query.station, direction) + const warningsAlerts = await request.server.methods.flood.getWarningsAlertsWithinStationBuffer(station.rloi_id) + + floods = new Floods({ floods: warningsAlerts }) + model = new ViewModel({ location, floods, station }) + return h.view(route, { model }) + } + + if (!location) { + const data = await request.server.methods.flood.getFloods() + floods = new Floods(data) + model = new ViewModel({ location, floods }) + return h.view(route, { model }) + } + + if (isLocationEngland(location)) { + return h.redirect(`/${route}`) + } + + const [place] = await locationService.find(location) + + if (!place) { + if (request.method === 'get') { + return renderNotFound(location) + } + + return renderLocationNotFound(route, location, h) + } + + if (!isPlaceEngland(place)) { + request.logger.warn({ + situation: 'Location search error: Valid response but location not in England.' + }) + + if (request.method === 'post') { + return renderLocationNotFound(route, location, h) + } + } + + const queryString = createQueryParametersString(request.query) + + return h.redirect(`/${route}/${slugify(place?.name)}${queryString}`).permanent() +} + +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) + + if (isLocationEngland(location)) { + return h.redirect(`/${route}`) + } + + if (!isValidLocationSlug(location, place)) { + return renderNotFound(location) + } + + if (!isPlaceEngland(place)) { + request.logger.warn({ + situation: 'Location search error: Valid response but location not in England.' + }) + + return renderNotFound(location) + } + + // Data passed to floods model so the schema is the same as cached floods + const data = await request.server.methods.flood.getFloodsWithin(place.bbox2k) + const floods = new Floods(data) + const model = new ViewModel({ location, place, floods, canonical: canonicalUrl, q: request.yar.get('q')?.location }) + request.yar.set('q', null) + return h.view(route, { model }) +} module.exports = [{ method: 'GET', - path: '/alerts-and-warnings', - handler: async (request, h) => { - const { q: location } = request.query - let model, place, floods - - const direction = request.query.direction === 'downstream' ? 'd' : 'u' - const station = request.query.station - ? await request.server.methods.flood.getStationById(request.query.station, direction) - : null - - if (station) { - const warningsAlerts = await request.server.methods.flood.getWarningsAlertsWithinStationBuffer(station.rloi_id) - floods = new Floods({ floods: warningsAlerts }) - model = new ViewModel({ location, place, floods, station }) - return h.view('alerts-and-warnings', { model }) - } else if (typeof location === 'undefined' || location === '' || location.match(/^england$/i)) { - floods = new Floods(await request.server.methods.flood.getFloods()) - model = new ViewModel({ location, place, floods, station }) - return h.view('alerts-and-warnings', { model }) - } else { - try { - [place] = await locationService.find(util.cleanseLocation(location)) - } catch (error) { - request.logger.warn({ - situation: `Location search error: [${error.name}] [${error.message}]`, - err: error - }) - const floods = new Floods(await request.server.methods.flood.getFloods()) - model = new ViewModel({ location, place, floods, station, error }) - return h.view('alerts-and-warnings', { model }) - } - - if (!place) { - model = new ViewModel({ location, place, floods, station }) - return h.view('alerts-and-warnings', { model }) - } - - if (!place?.isEngland.is_england) { - // If no place return empty floods - model = new ViewModel({ location, place, floods, station }) - return h.view('alerts-and-warnings', { model }) - } else { - // Data passed to floods model so the schema is the same as cached floods - const data = await request.server.methods.flood.getFloodsWithin(place.bbox2k) - floods = new Floods(data) - model = new ViewModel({ location, place, floods, station }) - return h.view('alerts-and-warnings', { model }) - } + path: `/${route}`, + handler: routeHandler, + options: { + validate: { + query: joi.object({ + q: joi.string().trim().max(QUERY_STRING_LOCATION_MAX_LENGTH), + station: joi.string(), + btn: joi.string(), + ext: joi.string(), + fid: joi.string(), + lyr: joi.string(), + v: joi.string() + }), + failAction: (request, h) => failActionHandler(request, h, route) } - }, + } +}, +{ + method: 'GET', + path: `/${route}/{location}`, + handler: locationRouteHandler, options: { validate: { + params: joi.object({ + location: joi.string().lowercase() + }), query: joi.object({ - q: joi.string().allow('').trim().max(200), station: joi.string(), btn: joi.string(), ext: joi.string(), fid: joi.string(), lyr: joi.string(), v: joi.string() - }) + }), + failAction: (request, h) => failActionHandler(request, h, route) } } -}, { +}, +{ method: 'POST', - path: '/alerts-and-warnings', - handler: async (request, h) => { - const { location } = request.payload - if (location === '') { - return h.redirect(`/alerts-and-warnings?q=${location}`) - } - return h.redirect(`/alerts-and-warnings?q=${encodeURIComponent(util.cleanseLocation(location))}`) - }, + path: `/${route}`, + handler: routeHandler, options: { validate: { payload: joi.object({ - location: joi.string().allow('').trim().max(200).required() + location: joi.string() + .allow('') + .trim() + .max(QUERY_STRING_LOCATION_MAX_LENGTH) + .required() }), - failAction: (request, h, _err) => { - return h.view('alerts-and-warnings').takeover() - } + failAction: (request, h) => failActionHandler(request, h, route) } } }] diff --git a/server/routes/lib/utils.js b/server/routes/lib/utils.js index 0fb99ff6b..5dac60f57 100644 --- a/server/routes/lib/utils.js +++ b/server/routes/lib/utils.js @@ -1,7 +1,57 @@ +const qs = require('qs') +const boom = require('@hapi/boom') + function slugify (text = '') { return text.replace(/,/g, '').replace(/ /g, '-').toLowerCase() } +function isLocationEngland (location) { + return location.match(/^england$/i) +} + +function isPlaceEngland (place) { + return place?.isEngland.is_england +} + +function isValidLocationSlug (location, place) { + return slugify(place?.name) === location +} + +function createQueryParametersString (queryObject) { + const { q, location, ...otherParameters } = queryObject + const queryString = qs.stringify(otherParameters, { addQueryPrefix: true, encode: false }) + return queryString +} + +function renderLocationNotFound (href, location, h) { + return h.view('location-not-found', { pageTitle: 'Error: Find location - Check for flooding', href, location }).takeover() +} + +function renderNotFound (location) { + return boom.notFound(`Location ${location} not found`) +} + +function failActionHandler (request, h, page) { + request.logger.warn({ + situation: 'Location search error: Invalid or no string input.' + }) + + const location = request.query.q || request.payload?.location + + if (!location) { + return h.redirect(page).takeover() + } else { + return renderLocationNotFound(page, location, h) + } +} + module.exports = { - slugify + slugify, + failActionHandler, + isLocationEngland, + isPlaceEngland, + isValidLocationSlug, + renderNotFound, + renderLocationNotFound, + createQueryParametersString } diff --git a/server/routes/river-and-sea-levels.js b/server/routes/river-and-sea-levels.js index 6c0dff8d3..46af2b90e 100644 --- a/server/routes/river-and-sea-levels.js +++ b/server/routes/river-and-sea-levels.js @@ -12,14 +12,109 @@ const { } = require('../models/views/river-and-sea-levels') const locationService = require('../services/location') const util = require('../util') +const { + slugify, + failActionHandler, + renderNotFound, + renderLocationNotFound, + createQueryParametersString, + isValidLocationSlug, + isLocationEngland, + isPlaceEngland +} = require('./lib/utils') const route = 'river-and-sea-levels' +const QUERY_STRING_LOCATION_MAX_LENGTH = 200 const miles = 1609.344 const joiValidationQMax = 200 const joiValidationGroupMax = 11 const joiValidationSearchTypeMax = 11 +async function locationRouteHandler (request, h) { + const referer = request.headers.referer + const queryGroup = request.query.group + + const canonicalUrl = request.url.origin + request.url.pathname + const location = util.cleanseLocation(request.params.location) + + const [place] = await locationService.find(location) + + if (isLocationEngland(location)) { + return h.redirect(`/${route}`) + } else if (!isValidLocationSlug(location, place)) { + return renderNotFound(location) + } else if (!isPlaceEngland(place)) { + request.logger.warn({ + situation: 'Location search error: Valid response but location not in England.' + }) + return renderNotFound(location) + } else { + const stations = await request.server.methods.flood.getStationsWithin(place.bbox10k) + const model = placeViewModel({ location, place, stations, referer, queryGroup, canonical: canonicalUrl, q: request.yar.get('q')?.location }) + request.yar.set('q', null) + return h.view(route, { model }) + } +} + +async function locationQueryHandler (request, h) { + let location = request.query.q || request.payload?.location + + location = util.cleanseLocation(location) + + request.yar.set('q', { location }) + + if (!location) { + return h.view(route, { model: emptyResultsModel() }) + } + + if (isLocationEngland(location)) { + return h.redirect(`/${route}`) + } + + const rivers = await request.server.methods.flood.getRiversByName(location) + const places = await findPlaces(location) + + if (places.length + rivers.length > 1) { + return h.view(`${route}-list`, { model: disambiguationModel(location, places, rivers) }) + } + + if (places.length === 0) { + if (rivers.length === 0) { + if (request.method === 'get') { + return renderNotFound(location) + } + + return renderLocationNotFound(route, location, h) + } + + return h.redirect(`/${route}/river/${rivers[0].id}`) + } + + const place = places[0] + + if (!isPlaceEngland(place)) { + request.logger.warn({ + situation: 'Location search error: Valid response but location not in England.' + }) + + if (request.method === 'post') { + return renderLocationNotFound(route, location, h) + } + } + + const queryString = createQueryParametersString(request.query) + + return h.redirect(`/${route}/${slugify(place?.name)}${queryString}`).permanent() +} + +async function findPlaces (location) { + // NOTE: at the moment locationService.find just returns a single place + // using the [] for no results and with a nod to upcoming work to return >1 result + const [place] = await locationService.find(location) + return place ? [place] : [] +} + module.exports = [{ method: 'GET', path: `/${route}/target-area/{targetAreaCode}`, @@ -134,82 +229,48 @@ module.exports = [{ q: joi.string().trim().max(joiValidationQMax), group: joi.string().trim().max(joiValidationGroupMax), searchType: joi.string().trim().max(joiValidationSearchTypeMax), - includeTypes: joi.string().default('place,river'), 'rloi-id': joi.string(), 'rainfall-id': joi.string(), 'target-area': joi.string(), riverId: joi.string() }), - failAction: (request, h) => { - request.logger.warn({ - situation: 'River and Sea levels search error: Invalid or no string input.' - }) - - return h.redirect() - } + failAction: (request, h) => failActionHandler(request, h, route) + } + } +}, { + method: 'GET', + path: `/${route}/{location}`, + handler: locationRouteHandler, + options: { + validate: { + params: joi.object({ + location: joi.string().lowercase() + }), + query: joi.object({ + group: joi.string().trim().max(joiValidationGroupMax), + searchType: joi.string().trim().max(joiValidationSearchTypeMax), + 'rloi-id': joi.string(), + 'rainfall-id': joi.string(), + 'target-area': joi.string(), + riverId: joi.string() + }), + failAction: (request, h) => failActionHandler(request, h, route) } } }, { method: 'POST', path: `/${route}`, - handler: async (request, h) => { - const { location } = request.payload - return h.redirect(`/${route}?q=${encodeURIComponent(location)}`).takeover() - }, + handler: locationQueryHandler, options: { validate: { payload: joi.object({ location: joi.string() + .allow('') .trim() - .regex(new RegExp(`^[${util.ALLOWED_SEARCH_CHARS}]*$`)).required() + .max(QUERY_STRING_LOCATION_MAX_LENGTH) + .required() }), - failAction: (_request, h, _err) => h.view(route, { model: emptyResultsModel(_request.payload?.location.trim()) }).takeover() + failAction: (request, h) => failActionHandler(request, h, route) } } }] - -async function locationQueryHandler (request, h) { - const location = request.query.q - const referer = request.headers.referer - const includeTypes = request.query.includeTypes.split(',') - const queryGroup = request.query.group - - let rivers = [] - let places = [] - const cleanLocation = util.cleanseLocation(location) - if (cleanLocation && cleanLocation.length > 1 && !cleanLocation.match(/^england$/i)) { - if (includeTypes.includes('place')) { - places = await findPlaces(cleanLocation) - } - if (includeTypes.includes('river')) { - rivers = await request.server.methods.flood.getRiversByName(cleanLocation) - } - } - - if (places.length === 0) { - if (rivers.length === 0) { - return h.view(route, { model: emptyResultsModel(location) }) - } - if (rivers.length === 1) { - return h.redirect(`/${route}/river/${rivers[0].id}`) - } - } - - if (places.length + rivers.length > 1) { - return h.view(`${route}-list`, { model: disambiguationModel(location, places, rivers) }) - } - - const place = places[0] - const stations = await request.server.methods.flood.getStationsWithin(place.bbox10k) - const model = placeViewModel({ location, place, stations, referer, queryGroup }) - return h.view(route, { model }) -} - -const inUk = place => place?.isUK && !place?.isScotlandOrNorthernIreland - -async function findPlaces (location) { - // NOTE: at the moment locationService.find just returns a single place - // using the [] for no results and with a nod to upcoming work to return >1 result - const [place] = await locationService.find(location) - return inUk(place) ? [place] : [] -} diff --git a/server/services/lib/bing-results-parser.js b/server/services/lib/bing-results-parser.js index 6f6f7ad55..441023941 100644 --- a/server/services/lib/bing-results-parser.js +++ b/server/services/lib/bing-results-parser.js @@ -8,7 +8,7 @@ async function bingResultsParser (bingData, getIsEngland) { const allowedConfidences = ['medium', 'high'] const allowedTypes = [ - 'populatedplace', 'postcode1', 'postcode3', 'admindivision2', 'neighborhood' + 'populatedplace', 'postcode1', 'postcode3', 'admindivision2', 'neighborhood', 'religiousstructure', 'roadblock' ] const data = set.resources .filter(r => allowedConfidences.includes(r.confidence.toLowerCase())) diff --git a/server/views/layout.html b/server/views/layout.html index f9e35eb87..60dada24d 100644 --- a/server/views/layout.html +++ b/server/views/layout.html @@ -30,7 +30,7 @@ {% if metaCanonical %} - + {% endif %} " } + url: '/alerts-and-warnings', + payload: { + location: '' + } } const response = await server.inject(options) - Code.expect(response.statusCode).to.equal(200) - const root = parse(response.payload) - const headers = root.querySelectorAll('h2') - Code.expect(headers.some(h => h.text.trim().startsWith("No results for \x3Cscript>alert('TEST')\x3C/script>"))).to.be.false() + Code.expect(response.statusCode).to.equal(404) + Code.expect(response.result.message).to.equal('Not Found') }) + lab.experiment('RLOI', () => { lab.test('GET /river-and-sea-levels?rloi-id=7224 should redirect', async () => { - const floodService = require('../../server/services/flood') - - const fakeStationsData = () => data.stationsWithinRadius - - const originalStation = () => data.riverStation7224 - const cachedStation = () => data.cachedStation - - sandbox.stub(floodService, 'getStationsByRadius').callsFake(fakeStationsData) - sandbox.stub(floodService, 'getStationById').callsFake(originalStation) - sandbox.stub(floodService, 'getStationsGeoJson').callsFake(cachedStation) + stubs.getJson.callsFake(() => data.warringtonGetJson) + stubs.getStationsByRadius.callsFake(() => data.stationsWithinRadius) + stubs.getStationById.callsFake(() => data.riverStation7224) + stubs.getStationsGeoJson.callsFake(() => data.cachedStation) + stubs.getIsEngland.callsFake(() => ({ is_england: true })) // Set cached stationsGeojson - + const floodService = require('../../server/services/flood') floodService.stationsGeojson = await floodService.getStationsGeoJson() - const riversPlugin = { - plugin: { - name: 'rivers', - register: (server, options) => { - server.route(require('../../server/routes/river-and-sea-levels')) - } - } - } - - await server.register(require('../../server/plugins/views')) - await server.register(require('../../server/plugins/session')) - await server.register(riversPlugin) - // Add Cache methods to server - const registerServerMethods = require('../../server/services/server-methods') - registerServerMethods(server) - - await server.initialize() const options = { method: 'GET', url: '/river-and-sea-levels?rloi-id=7224' @@ -2640,39 +940,18 @@ lab.experiment('Test - /river-and-sea-levels', () => { Code.expect(response.statusCode).to.equal(302) Code.expect(response.headers.location).to.equal('/river-and-sea-levels/rloi/7224') }) - lab.test('GET /river-and-sea-levels/rloi/7224', async () => { - const floodService = require('../../server/services/flood') - - const fakeStationsData = () => data.stationsWithinRadius - const originalStation = () => data.riverStation7224 - const cachedStation = () => data.cachedStation - - sandbox.stub(floodService, 'getStationsByRadius').callsFake(fakeStationsData) - sandbox.stub(floodService, 'getStationById').callsFake(originalStation) - sandbox.stub(floodService, 'getStationsGeoJson').callsFake(cachedStation) + lab.test('GET /river-and-sea-levels/rloi/7224', async () => { + stubs.getJson.callsFake(() => data.warringtonGetJson) + stubs.getStationsByRadius.callsFake(() => data.stationsWithinRadius) + stubs.getStationById.callsFake(() => data.riverStation7224) + stubs.getStationsGeoJson.callsFake(() => data.cachedStation) + stubs.getIsEngland.callsFake(() => ({ is_england: true })) // Set cached stationsGeojson - + const floodService = require('../../server/services/flood') floodService.stationsGeojson = await floodService.getStationsGeoJson() - const riversPlugin = { - plugin: { - name: 'rivers', - register: (server, options) => { - server.route(require('../../server/routes/river-and-sea-levels')) - } - } - } - - await server.register(require('../../server/plugins/views')) - await server.register(require('../../server/plugins/session')) - await server.register(riversPlugin) - // Add Cache methods to server - const registerServerMethods = require('../../server/services/server-methods') - registerServerMethods(server) - - await server.initialize() const options = { method: 'GET', url: '/river-and-sea-levels/rloi/7224' @@ -2686,40 +965,19 @@ lab.experiment('Test - /river-and-sea-levels', () => { Code.expect(response.payload).to.contain('Showing levels within 5 miles of Grants Bridge.') }) }) + lab.experiment('River', () => { lab.test('GET /river-and-sea-levels?river-id=river-nidd should redirect', async () => { - const floodService = require('../../server/services/flood') - - const fakeStationsData = () => data.stationsWithinRadius - - const originalStation = () => data.riverStation7224 - const cachedStation = () => data.cachedStation - - sandbox.stub(floodService, 'getStationsByRadius').callsFake(fakeStationsData) - sandbox.stub(floodService, 'getStationById').callsFake(originalStation) - sandbox.stub(floodService, 'getStationsGeoJson').callsFake(cachedStation) + stubs.getJson.callsFake(() => data.warringtonGetJson) + stubs.getStationsByRadius.callsFake(() => data.stationsWithinRadius) + stubs.getStationById.callsFake(() => data.riverStation7224) + stubs.getStationsGeoJson.callsFake(() => data.cachedStation) + stubs.getIsEngland.callsFake(() => ({ is_england: true })) // Set cached stationsGeojson - + const floodService = require('../../server/services/flood') floodService.stationsGeojson = await floodService.getStationsGeoJson() - const riversPlugin = { - plugin: { - name: 'rivers', - register: (server, options) => { - server.route(require('../../server/routes/river-and-sea-levels')) - } - } - } - - await server.register(require('../../server/plugins/views')) - await server.register(require('../../server/plugins/session')) - await server.register(riversPlugin) - // Add Cache methods to server - const registerServerMethods = require('../../server/services/server-methods') - registerServerMethods(server) - - await server.initialize() const options = { method: 'GET', url: '/river-and-sea-levels?riverId=river-nidd' @@ -2730,30 +988,10 @@ lab.experiment('Test - /river-and-sea-levels', () => { Code.expect(response.statusCode).to.equal(302) Code.expect(response.headers.location).to.equal('/river-and-sea-levels/river/river-nidd') }) - lab.test('GET /river-and-sea-levels/river/river-nidd', async () => { - const floodService = require('../../server/services/flood') - - const riverStation = () => data.riverNiddStations - - sandbox.stub(floodService, 'getRiverById').callsFake(riverStation) - - const riversPlugin = { - plugin: { - name: 'rivers', - register: (server, options) => { - server.route(require('../../server/routes/river-and-sea-levels')) - } - } - } - await server.register(require('../../server/plugins/views')) - await server.register(require('../../server/plugins/session')) - await server.register(riversPlugin) - // Add Cache methods to server - const registerServerMethods = require('../../server/services/server-methods') - registerServerMethods(server) + lab.test('GET /river-and-sea-levels/river/river-nidd', async () => { + stubs.getRiverById.callsFake(() => data.riverNiddStations) - await server.initialize() const options = { method: 'GET', url: '/river-and-sea-levels/river/river-nidd' @@ -2768,36 +1006,11 @@ lab.experiment('Test - /river-and-sea-levels', () => { const riversTab = root.querySelectorAll('ul#filter.defra-navbar__list li.defra-navbar__item--selected')[0].text.trim() Code.expect(riversTab).to.be.equal('River (6)') }) - lab.test('GET /river-and-sea-levels - Check for related content links', async () => { - const floodService = require('../../server/services/flood') - - const fakeIsEngland = () => { - return { is_england: true } - } - - const fakeStationsData = () => [] - - sandbox.stub(floodService, 'getIsEngland').callsFake(fakeIsEngland) - sandbox.stub(floodService, 'getStations').callsFake(fakeStationsData) - const riversPlugin = { - plugin: { - name: 'rivers', - register: (server, options) => { - server.route(require('../../server/routes/river-and-sea-levels')) - } - } - } - - await server.register(require('../../server/plugins/views')) - await server.register(require('../../server/plugins/session')) - await server.register(require('../../server/plugins/logging')) - await server.register(riversPlugin) - // Add Cache methods to server - const registerServerMethods = require('../../server/services/server-methods') - registerServerMethods(server) + lab.test('GET /river-and-sea-levels - Check for related content links', async () => { + stubs.getStations.callsFake(() => []) + stubs.getIsEngland.callsFake(() => ({ is_england: true })) - await server.initialize() const options = { method: 'GET', url: '/river-and-sea-levels' @@ -2812,14 +1025,3 @@ lab.experiment('Test - /river-and-sea-levels', () => { }) }) }) - -function checkTitleAndDescription (root, title, description) { - const metaDescription = root - .querySelectorAll('[name="description"]') - - const metaTitle = root - .querySelectorAll('[property="og:title"]') - - Code.expect(metaDescription[0]._attrs.content).to.equal(description) - Code.expect(metaTitle[0]._attrs.content).to.contain(title) -}