Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/check-pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ jobs:
run: mkdir -p -m 777 coverage

- name: Run Tests
run: npm test
run: npm test -- --coverage
- name: Test Docker Image Build
run: |
set +e
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
"server:watch": "nodemon --inspect=0.0.0.0 --ext js,njk --legacy-watch ./src",
"setup:husky": "node -e \"try { (await import('husky')).default() } catch (e) { if (e.code !== 'ERR_MODULE_NOT_FOUND') throw e }\" --input-type module",
"start": "NODE_ENV=production node --use-strict .",
"test": "TZ=UTC vitest run --coverage",
"test": "TZ=UTC vitest run",
"test:coverage": "TZ=UTC vitest run --coverage",
"test:watch": "TZ=UTC vitest",
"docker:test": "npm run lint && npm test"
},
Expand Down Expand Up @@ -68,7 +69,6 @@
"hapi-pulse": "3.0.1",
"ioredis": "5.8.2",
"lodash": "4.17.21",
"node-fetch": "3.3.2",
"nunjucks": "3.2.4",
"pino": "10.1.0",
"pino-pretty": "13.1.3",
Expand Down
31 changes: 26 additions & 5 deletions src/lib/flood-service.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,35 @@
import fetch from 'node-fetch'
import { ProxyAgent } from 'undici'
import { config } from '../config/config.js'

const API_BASE_URL = config.get('api.floodMonitoring.baseUrl')

/**
* Fetch via proxy using Node.js native fetch
* To use the fetch dispatcher option on Node.js native fetch, Node.js v18.2.0 or greater is required
*/
export function proxyFetch (url, options = {}) {
const proxyUrlConfig = config.get('httpProxy') // bound to HTTP_PROXY

if (!proxyUrlConfig) {
return fetch(url, options)
}

return fetch(url, {
...options,
dispatcher: new ProxyAgent({
uri: proxyUrlConfig,
keepAliveTimeout: 10,
keepAliveMaxTimeout: 10
})
})
}

/**
* Fetch station details by RLOI ID (Check for Flooding ID)
*/
export async function getStation (stationId) {
try {
const response = await fetch(`${API_BASE_URL}/id/stations?RLOIid=${stationId}`)
const response = await proxyFetch(`${API_BASE_URL}/id/stations?RLOIid=${stationId}`)
if (!response.ok) {
throw new Error(`Failed to fetch station: ${response.statusText}`)
}
Expand All @@ -30,7 +51,7 @@ export async function getStation (stationId) {
export async function getStationReadings (stationId, since = null) {
try {
// First get the station to find its measures
const stationResponse = await fetch(`${API_BASE_URL}/id/stations?RLOIid=${stationId}`)
const stationResponse = await proxyFetch(`${API_BASE_URL}/id/stations?RLOIid=${stationId}`)
if (!stationResponse.ok) {
throw new Error('Station not found')
}
Expand Down Expand Up @@ -59,7 +80,7 @@ export async function getStationReadings (stationId, since = null) {
// Get all available readings - filtering happens in formatTelemetryData
const url = `${API_BASE_URL}/data/readings?measure=${measureId}&_sorted&_limit=10000`
console.log('Fetching readings from:', url)
const response = await fetch(url)
const response = await proxyFetch(url)

if (!response.ok) {
console.error('Readings API error:', response.status, response.statusText)
Expand Down Expand Up @@ -170,7 +191,7 @@ export async function searchStations (query = {}) {
if (query.riverName) params.append('riverName', query.riverName)

const url = `${API_BASE_URL}/id/stations?${params.toString()}&_limit=50`
const response = await fetch(url)
const response = await proxyFetch(url)

if (!response.ok) {
throw new Error(`Failed to search stations: ${response.statusText}`)
Expand Down
69 changes: 68 additions & 1 deletion test/integration/narrow/routes/station.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,73 @@
import { describe, beforeAll, afterAll, test, expect } from 'vitest'
import { describe, beforeAll, afterAll, test, expect, vi } from 'vitest'
import { createServer } from '../../../../src/server.js'

// Mock the flood-service to avoid external API calls
vi.mock('../../../../src/lib/flood-service.js', () => ({
getStation: vi.fn().mockImplementation((stationId) => {
if (stationId === '8085') {
return Promise.resolve({
'@id': 'http://environment.data.gov.uk/flood-monitoring/id/stations/8085',
RLOIid: '8085',
label: 'Test Station',
stationReference: 'E8085',
riverName: 'Test River',
town: 'Test Town',
measures: [{
'@id': 'http://environment.data.gov.uk/flood-monitoring/id/measures/8085-level-stage-i-15_min-mASD',
parameter: 'level',
parameterName: 'Water Level',
unitName: 'mASD'
}]
})
} else if (stationId === '999999' || stationId === 'invalid') {
return Promise.resolve(null)
}
return Promise.resolve(null)
}),
getStationReadings: vi.fn().mockResolvedValue([
{
'@id': 'http://environment.data.gov.uk/flood-monitoring/data/readings/8085-level-stage-i-15_min-mASD/2024-01-01T00:00:00Z',
dateTime: '2024-01-01T00:00:00Z',
measure: 'http://environment.data.gov.uk/flood-monitoring/id/measures/8085-level-stage-i-15_min-mASD',
value: 1.234
},
{
'@id': 'http://environment.data.gov.uk/flood-monitoring/data/readings/8085-level-stage-i-15_min-mASD/2024-01-01T00:15:00Z',
dateTime: '2024-01-01T00:15:00Z',
measure: 'http://environment.data.gov.uk/flood-monitoring/id/measures/8085-level-stage-i-15_min-mASD',
value: 1.245
}
]),
formatStationData: vi.fn().mockImplementation((stationData, readings) => {
return {
id: stationData?.RLOIid || '8085',
name: stationData?.label || 'Test Station',
river: stationData?.riverName || 'Test River',
type: 'S',
recentValue: {
value: '1.25',
formattedTime: '12:15am',
latestDayFormatted: '1 January'
},
trend: 'rising',
state: 'normal',
stateInformation: '0.50m to 2.00m',
hasPercentiles: true,
isActive: true,
status: 'active',
lat: 51.5,
long: -0.1,
rloiId: stationData?.RLOIid || '8085'
}
}),
formatTelemetryData: vi.fn().mockImplementation((readings) => ({
observed: readings?.map(r => ({
dateTime: r.dateTime,
value: r.value
})) || []
}))
}))

describe('Station route', () => {
let server

Expand Down
12 changes: 9 additions & 3 deletions test/unit/common/helpers/secure-context/secure-context.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,9 @@ describe('secureContext', () => {

afterEach(async () => {
config.set('isSecureContextEnabled', false)
await server.stop({ timeout: 0 })
if (server) {
await server.stop({ timeout: 0 })
}
})

test('secureContext decorator should not be available', () => {
Expand Down Expand Up @@ -74,7 +76,9 @@ describe('secureContext', () => {

afterEach(async () => {
config.set('isSecureContextEnabled', false)
await server.stop({ timeout: 0 })
if (server) {
await server.stop({ timeout: 0 })
}
})

afterAll(() => {
Expand Down Expand Up @@ -105,7 +109,9 @@ describe('secureContext', () => {

afterEach(async () => {
config.set('isSecureContextEnabled', false)
await server.stop({ timeout: 0 })
if (server) {
await server.stop({ timeout: 0 })
}
})

test('Should log about not finding any TRUSTSTORE_ certs', () => {
Expand Down
Loading