diff --git a/package-lock.json b/package-lock.json index e3385483a..cd1faabe0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "flood-app", - "version": "8.5.0", + "version": "8.4.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "flood-app", - "version": "8.5.0", + "version": "8.4.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.13", + "node-html-parser": "^6.1.11", "nodemon": "^3.0.1", "proxyquire": "^2.1.3" }, @@ -8791,9 +8791,9 @@ } }, "node_modules/node-html-parser": { - "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==", + "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==", "dev": true, "dependencies": { "css-select": "^5.1.0", diff --git a/package.json b/package.json index 96ee182e3..b65dcce5f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "flood-app", - "version": "8.5.0", + "version": "8.4.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.13", + "node-html-parser": "^6.1.11", "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 deleted file mode 100644 index 3ec86f03b..000000000 --- a/release-docs/CFF-8.5.0.md +++ /dev/null @@ -1,27 +0,0 @@ -# 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 0a109a20f..65ef129d3 100644 --- a/server/models/views/alerts-and-warnings.js +++ b/server/models/views/alerts-and-warnings.js @@ -2,39 +2,36 @@ const { bingKeyMaps } = require('../../config') const config = require('../../config') class ViewModel { - constructor ({ q, location, place, floods = [], station, canonical, error }) { + constructor ({ location, place, floods, station, error }) { Object.assign(this, { - q, - station, + q: location, map: station ? 'map-station' : 'map', - placeName: place?.name || '', - placeBbox: place?.bbox2k || [], - placeCentre: place?.center || [], + station: station || null, + placeName: place ? place.name : '', + placeBbox: place ? place.bbox2k : [], + placeCentre: place ? place.center : [], timestamp: Date.now(), - metaCanonical: canonical, - canonicalUrl: canonical, - error: !!error, + error: error ? true : null, displayGetWarningsLink: true, displayLongTermLink: true, - isEngland: place?.isEngland?.is_england, - isDummyData: floods?.isDummyData, + isEngland: place ? place.isEngland.is_england : null, + isDummyData: floods ? floods.isDummyData : false, 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?.length ?? 0 - this.floods = floods?.groups?.map(item => item) + this.countFloods = floods ? floods.floods.length : 0 + this.floods = 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 6579a4437..dc100de3b 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, canonical, q }) { +function placeViewModel ({ location, place, stations = [], queryGroup }) { let distStatement, title, description const isEngland = place ? place.isEngland.is_england : true @@ -128,10 +128,10 @@ function placeViewModel ({ location, place, stations = [], queryGroup, canonical deleteUndefinedProperties(stations) if (location && isEngland) { - title = `${place.name ?? location} - ${pageTitle}` + title = `${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 = place ? `${place.name} - ${pageTitle}` : pageTitle + title = location ? `${location} - ${pageTitle}` : pageTitle description = metaDescription } @@ -141,15 +141,13 @@ function placeViewModel ({ location, place, stations = [], queryGroup, canonical filters, floodRiskUrl, distStatement, - q: q || location, + q: location, clientModel: getClientModel(isEngland ? place.bbox10k : []), queryGroup: activeFilter, placeAddress: place.address, ...getDataJourneyClickStrings(), pageTitle: title, - metaDescription: description, - metaCanonical: canonical, - canonicalUrl: canonical + metaDescription: description } } diff --git a/server/routes/alerts-and-warnings.js b/server/routes/alerts-and-warnings.js index 66557dc37..f371f2b6c 100644 --- a/server/routes/alerts-and-warnings.js +++ b/server/routes/alerts-and-warnings.js @@ -3,160 +3,90 @@ 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: `/${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) + 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 }) + } } - } -}, -{ - 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: `/${route}`, - handler: routeHandler, + 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))}`) + }, options: { validate: { payload: joi.object({ - location: joi.string() - .allow('') - .trim() - .max(QUERY_STRING_LOCATION_MAX_LENGTH) - .required() + location: joi.string().allow('').trim().max(200).required() }), - failAction: (request, h) => failActionHandler(request, h, route) + failAction: (request, h, _err) => { + return h.view('alerts-and-warnings').takeover() + } } } }] diff --git a/server/routes/lib/utils.js b/server/routes/lib/utils.js index 5dac60f57..0fb99ff6b 100644 --- a/server/routes/lib/utils.js +++ b/server/routes/lib/utils.js @@ -1,57 +1,7 @@ -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, - failActionHandler, - isLocationEngland, - isPlaceEngland, - isValidLocationSlug, - renderNotFound, - renderLocationNotFound, - createQueryParametersString + slugify } diff --git a/server/routes/river-and-sea-levels.js b/server/routes/river-and-sea-levels.js index 46af2b90e..6c0dff8d3 100644 --- a/server/routes/river-and-sea-levels.js +++ b/server/routes/river-and-sea-levels.js @@ -12,109 +12,14 @@ 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}`, @@ -229,48 +134,82 @@ 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) => 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) + failAction: (request, h) => { + request.logger.warn({ + situation: 'River and Sea levels search error: Invalid or no string input.' + }) + + return h.redirect() + } } } }, { method: 'POST', path: `/${route}`, - handler: locationQueryHandler, + handler: async (request, h) => { + const { location } = request.payload + return h.redirect(`/${route}?q=${encodeURIComponent(location)}`).takeover() + }, options: { validate: { payload: joi.object({ location: joi.string() - .allow('') .trim() - .max(QUERY_STRING_LOCATION_MAX_LENGTH) - .required() + .regex(new RegExp(`^[${util.ALLOWED_SEARCH_CHARS}]*$`)).required() }), - failAction: (request, h) => failActionHandler(request, h, route) + failAction: (_request, h, _err) => h.view(route, { model: emptyResultsModel(_request.payload?.location.trim()) }).takeover() } } }] + +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 441023941..6f6f7ad55 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', 'religiousstructure', 'roadblock' + 'populatedplace', 'postcode1', 'postcode3', 'admindivision2', 'neighborhood' ] const data = set.resources .filter(r => allowedConfidences.includes(r.confidence.toLowerCase())) diff --git a/server/views/layout.html b/server/views/layout.html index 60dada24d..f9e35eb87 100644 --- a/server/views/layout.html +++ b/server/views/layout.html @@ -30,7 +30,7 @@ {% if metaCanonical %} - + {% endif %} ' - } + url: '/river-and-sea-levels', + payload: { location: "" } } const response = await server.inject(options) - Code.expect(response.statusCode).to.equal(404) - Code.expect(response.result.message).to.equal('Not Found') + 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() }) - lab.experiment('RLOI', () => { lab.test('GET /river-and-sea-levels?rloi-id=7224 should redirect', 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 })) + 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) // 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' @@ -940,18 +2640,39 @@ 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 () => { - 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 })) + 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) // 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' @@ -965,19 +2686,40 @@ 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 () => { - 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 })) + 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) // 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' @@ -988,10 +2730,30 @@ 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 () => { - stubs.getRiverById.callsFake(() => data.riverNiddStations) + 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) + await server.initialize() const options = { method: 'GET', url: '/river-and-sea-levels/river/river-nidd' @@ -1006,11 +2768,36 @@ 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 () => { - stubs.getStations.callsFake(() => []) - stubs.getIsEngland.callsFake(() => ({ is_england: true })) + 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) + + await server.initialize() const options = { method: 'GET', url: '/river-and-sea-levels' @@ -1025,3 +2812,14 @@ 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) +}