diff --git a/docker/.env b/docker/.env
index a013eee..708b7b1 100644
--- a/docker/.env
+++ b/docker/.env
@@ -55,6 +55,9 @@ CPX_DB_CONNECTION_STRING=postgres://${CPX_DB_USERNAME}:${CPX_DB_PASSWORD}@capxml
CPX_REDIS_HOST=cap-xml-redis
CPX_REDIS_PORT=6379
CPX_REDIS_TLS=false
+CPX_METEOALARM_API_URL=http://mock-api:8080 # wiremock url
+CPX_METEOALARM_API_USERNAME=username
+CPX_METEOALARM_API_PASSWORD=password
PGADMIN_DEFAULT_PASSWORD=pgadmin
POSTGRES_PASSWORD=postgres
LIQUIBASE_COMMAND_CHANGELOG_FILE=./changelog/db.changelog-master.xml
diff --git a/docker/dev-tools.yml b/docker/dev-tools.yml
index 8a8b3bb..fb36943 100644
--- a/docker/dev-tools.yml
+++ b/docker/dev-tools.yml
@@ -28,6 +28,15 @@ services:
networks:
ls:
command: /bin/sh -c "lpm add postgresql && liquibase update"
+ mock-api:
+ image: wiremock/wiremock:latest
+ ports:
+ - "8081:8080"
+ volumes:
+ - ./docker/wiremock/mappings:/home/wiremock/mappings
+ command: ["--global-response-templating", "--verbose"]
+ networks:
+ ls:
volumes:
capxmlpgadmin:
external: true
diff --git a/docker/docker/wiremock/mappings/third-party-api.json b/docker/docker/wiremock/mappings/third-party-api.json
new file mode 100644
index 0000000..0c198b1
--- /dev/null
+++ b/docker/docker/wiremock/mappings/third-party-api.json
@@ -0,0 +1,24 @@
+{
+ "mappings": [
+ {
+ "request": {
+ "method": "POST",
+ "urlPath": "/warnings"
+ },
+ "response": {
+ "status": 201,
+ "body": "{\"warning\": {\"uuid\": \"{{randomValue type='UUID'}}\" } }"
+ }
+ },
+ {
+ "request": {
+ "method": "POST",
+ "urlPath": "/tokens"
+ },
+ "response": {
+ "status": 200,
+ "body": "{\"tokenType\": \"Bearer\", \"token\": \"{{randomValue length=64 type='ALPHANUMERIC'}}\", \"expiresIn\": \"300\" }"
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/docker/scripts/register-lambda-functions.sh b/docker/scripts/register-lambda-functions.sh
index 7acde00..45ea2eb 100755
--- a/docker/scripts/register-lambda-functions.sh
+++ b/docker/scripts/register-lambda-functions.sh
@@ -16,7 +16,10 @@ cpx_agw_url=$(echo CPX_AGW_URL=$deployed_cpx_agw_url)
cpx_redis_host=$(echo CPX_REDIS_HOST=$CPX_REDIS_HOST)
cpx_redis_port=$(echo CPX_REDIS_PORT=$CPX_REDIS_PORT)
cpx_redis_tls=$(echo CPX_REDIS_TLS=$CPX_REDIS_TLS)
-set -- $cpx_db_username $cpx_db_password $cpx_db_name $cpx_db_host $cpx_agw_url $cpx_redis_host $cpx_redis_port $cpx_redis_tls
+cpx_meteoalarm_api_url=$(echo CPX_METEOALARM_API_URL=$CPX_METEOALARM_API_URL)
+cpx_meteoalarm_api_username=$(echo CPX_METEOALARM_API_USERNAME=$CPX_METEOALARM_API_USERNAME)
+cpx_meteoalarm_api_password=$(echo CPX_METEOALARM_API_PASSWORD=$CPX_METEOALARM_API_PASSWORD)
+set -- $cpx_db_username $cpx_db_password $cpx_db_name $cpx_db_host $cpx_agw_url $cpx_redis_host $cpx_redis_port $cpx_redis_tls $cpx_meteoalarm_api_url $cpx_meteoalarm_api_username $cpx_meteoalarm_api_password
custom_environment_variables=$(printf '%s,' "$@" | sed 's/,*$//g')
# Iterate over each file in lambda_functions_dir
diff --git a/lib/functions/processMessage.js b/lib/functions/processMessage.js
index ee04749..bfe0851 100644
--- a/lib/functions/processMessage.js
+++ b/lib/functions/processMessage.js
@@ -11,10 +11,11 @@ const path = require('node:path')
const xsdSchema = fs.readFileSync(path.join(__dirname, '..', 'schemas', 'CAP-v1.2.xsd'), 'utf8')
const additionalCapMessageSchema = require('../schemas/additionalCapMessageSchema')
const Message = require('../models/message')
-const EA_WHO = '2.49.0.0.826.1'
+const EA_WHO = '2.49.0.1.826.1'
const CODE = 'MCP:v2.0'
const severityV2Mapping = require('../models/v2MessageMapping')
const redis = require('../helpers/redis')
+const meteoalarm = require('../helpers/meteoalarm')
module.exports.processMessage = async (event) => {
try {
@@ -58,8 +59,12 @@ module.exports.processMessage = async (event) => {
}
const { message: redisMessage, query: dbQuery } = message.putQuery(message, messageV2)
- // store the message in database and redis/elasticache
- await Promise.all([service.putMessage(dbQuery), redis.set(redisMessage.identifier, redisMessage)])
+ // store the message in database, redis/elasticache, and post to Meteoalarm
+ await Promise.all([
+ service.putMessage(dbQuery),
+ redis.set(redisMessage.identifier, redisMessage),
+ meteoalarm.postWarning(messageV2.toString(), message.identifier)
+ ])
console.log(`Finished processing CAP message: ${message.identifier} for ${message.fwisCode}`)
return {
@@ -167,6 +172,7 @@ const processMessageV2 = (message, lastMessage) => {
messageV2.references = referencesV2
}
messageV2.event = `${severityV2Mapping[message.severity]?.description}: ${messageV2.areaDesc}`
+ messageV2.responseType = 'Monitor'
messageV2.severity = severityV2Mapping[message.severity]?.severity || ''
messageV2.onset = message.sent
messageV2.headline = `${severityV2Mapping[message.severity]?.headline}: ${messageV2.areaDesc}`
@@ -188,5 +194,7 @@ const processMessageV2 = (message, lastMessage) => {
messageV2.addParameter('use_polygon_over_geocode', 'true')
messageV2.addParameter('uk_ea_ta_code', message.fwisCode)
+ messageV2.removeNode('geocode')
+
return messageV2
}
diff --git a/lib/helpers/meteoalarm.js b/lib/helpers/meteoalarm.js
new file mode 100644
index 0000000..f5b12d8
--- /dev/null
+++ b/lib/helpers/meteoalarm.js
@@ -0,0 +1,116 @@
+'use strict'
+
+const axios = require('axios')
+const https = require('node:https')
+let cachedToken = null
+let tokenExpiry = null
+const CPX_METEOALARM_API_URL = process.env.CPX_METEOALARM_API_URL
+const CPX_METEOALARM_API_USERNAME = process.env.CPX_METEOALARM_API_USERNAME
+const CPX_METEOALARM_API_PASSWORD = process.env.CPX_METEOALARM_API_PASSWORD
+const MAX_RETRIES = 3
+const TOKEN_EXPIRY_MS = 3600000 // 1 hour in milliseconds
+const API_REQUEST_TIMEOUT_MS = 10000 // 10 seconds
+const DEFAULT_RETRY_DELAY_MULTIPLIER = 1000 // 1 second base delay
+const HTTP_STATUS_OK = 200
+const HTTP_STATUS_CREATED = 201
+const HTTP_STATUS_UNAUTHORIZED = 401
+const config = {
+ retryDelayMultiplier: DEFAULT_RETRY_DELAY_MULTIPLIER // Can be overridden for testing
+}
+
+const getValidToken = async () => {
+ // Check if we have a cached token that hasn't expired
+ if (cachedToken && tokenExpiry && new Date() < tokenExpiry) {
+ return cachedToken
+ }
+
+ try {
+ const response = await axios.post(`${CPX_METEOALARM_API_URL}/tokens`, {
+ username: CPX_METEOALARM_API_USERNAME,
+ password: CPX_METEOALARM_API_PASSWORD
+ }, {
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ httpsAgent: new https.Agent({
+ rejectUnauthorized: false
+ })
+ })
+
+ if (response.status !== HTTP_STATUS_OK) {
+ throw new Error(`Failed to authenticate: ${response.status} ${response.statusText}`)
+ }
+
+ cachedToken = response.data.token
+ // Set token expiry to 1 hour from now
+ tokenExpiry = new Date(Date.now() + TOKEN_EXPIRY_MS)
+ return cachedToken
+ } catch (err) {
+ console.error('Error fetching bearer token:', err.message)
+ throw new Error(`Failed to authenticate with Meteoalarm: ${err.message}`)
+ }
+}
+
+const postWarning = async (xmlMessage, identifier) => {
+ let lastError = null
+ for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
+ try {
+ const token = await getValidToken()
+ const response = await axios.post(`${CPX_METEOALARM_API_URL}/warnings`, xmlMessage, {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ 'Content-Type': 'application/xml'
+ },
+ timeout: API_REQUEST_TIMEOUT_MS,
+ httpsAgent: new https.Agent({
+ rejectUnauthorized: false
+ })
+ })
+
+ if (response.status === HTTP_STATUS_CREATED) {
+ console.log(`Successfully posted warning to Meteoalarm: ${identifier}`)
+ console.log(response.data)
+ return response.data
+ }
+ throw new Error(`Received non-201 response: ${response.status}`)
+ } catch (err) {
+ lastError = err
+ console.error(`Meteoalarm post attempt ${attempt} failed: ${err.message}`)
+ if (err.response?.data) {
+ console.error(JSON.stringify(err.response.data))
+ }
+
+ // If it's a 401 error, clear the cached token and retry
+ if (err.response?.status === HTTP_STATUS_UNAUTHORIZED) {
+ console.log('Received 401, clearing cached token')
+ cachedToken = null
+ tokenExpiry = null
+ }
+
+ // If this isn't the last attempt, wait before retrying
+ if (attempt < MAX_RETRIES) {
+ const delayMs = attempt * config.retryDelayMultiplier
+ console.log(`Waiting ${delayMs}ms before retry...`)
+ await new Promise(resolve => setTimeout(resolve, delayMs))
+ }
+ }
+ }
+ throw new Error(`Failed to post warning to Meteoalarm after ${MAX_RETRIES} attempts: ${lastError.message}`)
+}
+
+const clearTokenCache = () => {
+ cachedToken = null
+ tokenExpiry = null
+}
+
+const setRetryDelayMultiplier = (multiplier) => {
+ config.retryDelayMultiplier = multiplier
+}
+
+module.exports = {
+ postWarning,
+ clearTokenCache,
+ // Export for testing
+ getValidToken,
+ setRetryDelayMultiplier
+}
diff --git a/lib/models/message.js b/lib/models/message.js
index f3e2b71..e48c903 100644
--- a/lib/models/message.js
+++ b/lib/models/message.js
@@ -89,6 +89,19 @@ class Message {
this.getFirstElement('event').textContent = value
}
+ get responseType () {
+ return this.getFirstElement('responseType')?.textContent || ''
+ }
+
+ set responseType (value) {
+ const responseTypeEl = this.getFirstElement('responseType')
+ if (responseTypeEl) {
+ responseTypeEl.textContent = value
+ } else {
+ this.addElement('event', 'responseType', value)
+ }
+ }
+
get severity () {
return this.getFirstElement('severity')?.textContent || ''
}
@@ -176,6 +189,15 @@ class Message {
}
}
+ removeNode (name) {
+ const nodes = this.doc.getElementsByTagName(name)
+ // Using parentNode.removeChild() because @xmldom/xmldom doesn't support node.remove()
+ for (let i = nodes.length - 1; i >= 0; i--) {
+ const node = nodes[i]
+ node.parentNode.removeChild(node) // NOSONAR - remove() not available in xmldom
+ }
+ }
+
toString () {
return xmlFormat(new xmldom.XMLSerializer().serializeToString(this.doc), { indentation: ' ', collapseContent: true })
}
diff --git a/package-lock.json b/package-lock.json
index 8fd5bc3..54d730e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,8 +9,9 @@
"version": "3.0.0",
"license": "OGL",
"dependencies": {
- "@aws-sdk/client-sns": "3.981.0",
+ "@aws-sdk/client-sns": "3.988.0",
"@xmldom/xmldom": "0.8.11",
+ "axios": "1.13.5",
"feed": "5.2.0",
"ioredis": "5.9.2",
"joi": "18.0.2",
@@ -173,45 +174,45 @@
}
},
"node_modules/@aws-sdk/client-sns": {
- "version": "3.981.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/client-sns/-/client-sns-3.981.0.tgz",
- "integrity": "sha512-vz4KUqG7yrjDwFok9B4nQylyNvLb1D+OVywmDzMzLG3t1nIPiSunMMHOK8NltwhUxU+Tp/exPgPUmgt/0arcQA==",
+ "version": "3.988.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/client-sns/-/client-sns-3.988.0.tgz",
+ "integrity": "sha512-cUTrbWsnAevr93QJ+ayHeTI+u0T5Jc8Bx+NaRmBs1C+IsVC9t6krH+hs4jzNrvI6Ti5M3tNe7dSMw3pk/wcxkg==",
"license": "Apache-2.0",
"dependencies": {
"@aws-crypto/sha256-browser": "5.2.0",
"@aws-crypto/sha256-js": "5.2.0",
- "@aws-sdk/core": "^3.973.5",
- "@aws-sdk/credential-provider-node": "^3.972.4",
+ "@aws-sdk/core": "^3.973.8",
+ "@aws-sdk/credential-provider-node": "^3.972.7",
"@aws-sdk/middleware-host-header": "^3.972.3",
"@aws-sdk/middleware-logger": "^3.972.3",
"@aws-sdk/middleware-recursion-detection": "^3.972.3",
- "@aws-sdk/middleware-user-agent": "^3.972.5",
+ "@aws-sdk/middleware-user-agent": "^3.972.8",
"@aws-sdk/region-config-resolver": "^3.972.3",
"@aws-sdk/types": "^3.973.1",
- "@aws-sdk/util-endpoints": "3.981.0",
+ "@aws-sdk/util-endpoints": "3.988.0",
"@aws-sdk/util-user-agent-browser": "^3.972.3",
- "@aws-sdk/util-user-agent-node": "^3.972.3",
+ "@aws-sdk/util-user-agent-node": "^3.972.6",
"@smithy/config-resolver": "^4.4.6",
- "@smithy/core": "^3.22.0",
+ "@smithy/core": "^3.23.0",
"@smithy/fetch-http-handler": "^5.3.9",
"@smithy/hash-node": "^4.2.8",
"@smithy/invalid-dependency": "^4.2.8",
"@smithy/middleware-content-length": "^4.2.8",
- "@smithy/middleware-endpoint": "^4.4.12",
- "@smithy/middleware-retry": "^4.4.29",
+ "@smithy/middleware-endpoint": "^4.4.14",
+ "@smithy/middleware-retry": "^4.4.31",
"@smithy/middleware-serde": "^4.2.9",
"@smithy/middleware-stack": "^4.2.8",
"@smithy/node-config-provider": "^4.3.8",
- "@smithy/node-http-handler": "^4.4.8",
+ "@smithy/node-http-handler": "^4.4.10",
"@smithy/protocol-http": "^5.3.8",
- "@smithy/smithy-client": "^4.11.1",
+ "@smithy/smithy-client": "^4.11.3",
"@smithy/types": "^4.12.0",
"@smithy/url-parser": "^4.2.8",
"@smithy/util-base64": "^4.3.0",
"@smithy/util-body-length-browser": "^4.2.0",
"@smithy/util-body-length-node": "^4.2.1",
- "@smithy/util-defaults-mode-browser": "^4.3.28",
- "@smithy/util-defaults-mode-node": "^4.2.31",
+ "@smithy/util-defaults-mode-browser": "^4.3.30",
+ "@smithy/util-defaults-mode-node": "^4.2.33",
"@smithy/util-endpoints": "^3.2.8",
"@smithy/util-middleware": "^4.2.8",
"@smithy/util-retry": "^4.2.8",
@@ -223,44 +224,44 @@
}
},
"node_modules/@aws-sdk/client-sso": {
- "version": "3.980.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.980.0.tgz",
- "integrity": "sha512-AhNXQaJ46C1I+lQ+6Kj+L24il5K9lqqIanJd8lMszPmP7bLnmX0wTKK0dxywcvrLdij3zhWttjAKEBNgLtS8/A==",
+ "version": "3.988.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.988.0.tgz",
+ "integrity": "sha512-ThqQ7aF1k0Zz4yJRwegHw+T1rM3a7ZPvvEUSEdvn5Z8zTeWgJAbtqW/6ejPsMLmFOlHgNcwDQN/e69OvtEOoIQ==",
"license": "Apache-2.0",
"dependencies": {
"@aws-crypto/sha256-browser": "5.2.0",
"@aws-crypto/sha256-js": "5.2.0",
- "@aws-sdk/core": "^3.973.5",
+ "@aws-sdk/core": "^3.973.8",
"@aws-sdk/middleware-host-header": "^3.972.3",
"@aws-sdk/middleware-logger": "^3.972.3",
"@aws-sdk/middleware-recursion-detection": "^3.972.3",
- "@aws-sdk/middleware-user-agent": "^3.972.5",
+ "@aws-sdk/middleware-user-agent": "^3.972.8",
"@aws-sdk/region-config-resolver": "^3.972.3",
"@aws-sdk/types": "^3.973.1",
- "@aws-sdk/util-endpoints": "3.980.0",
+ "@aws-sdk/util-endpoints": "3.988.0",
"@aws-sdk/util-user-agent-browser": "^3.972.3",
- "@aws-sdk/util-user-agent-node": "^3.972.3",
+ "@aws-sdk/util-user-agent-node": "^3.972.6",
"@smithy/config-resolver": "^4.4.6",
- "@smithy/core": "^3.22.0",
+ "@smithy/core": "^3.23.0",
"@smithy/fetch-http-handler": "^5.3.9",
"@smithy/hash-node": "^4.2.8",
"@smithy/invalid-dependency": "^4.2.8",
"@smithy/middleware-content-length": "^4.2.8",
- "@smithy/middleware-endpoint": "^4.4.12",
- "@smithy/middleware-retry": "^4.4.29",
+ "@smithy/middleware-endpoint": "^4.4.14",
+ "@smithy/middleware-retry": "^4.4.31",
"@smithy/middleware-serde": "^4.2.9",
"@smithy/middleware-stack": "^4.2.8",
"@smithy/node-config-provider": "^4.3.8",
- "@smithy/node-http-handler": "^4.4.8",
+ "@smithy/node-http-handler": "^4.4.10",
"@smithy/protocol-http": "^5.3.8",
- "@smithy/smithy-client": "^4.11.1",
+ "@smithy/smithy-client": "^4.11.3",
"@smithy/types": "^4.12.0",
"@smithy/url-parser": "^4.2.8",
"@smithy/util-base64": "^4.3.0",
"@smithy/util-body-length-browser": "^4.2.0",
"@smithy/util-body-length-node": "^4.2.1",
- "@smithy/util-defaults-mode-browser": "^4.3.28",
- "@smithy/util-defaults-mode-node": "^4.2.31",
+ "@smithy/util-defaults-mode-browser": "^4.3.30",
+ "@smithy/util-defaults-mode-node": "^4.2.33",
"@smithy/util-endpoints": "^3.2.8",
"@smithy/util-middleware": "^4.2.8",
"@smithy/util-retry": "^4.2.8",
@@ -271,36 +272,20 @@
"node": ">=20.0.0"
}
},
- "node_modules/@aws-sdk/client-sso/node_modules/@aws-sdk/util-endpoints": {
- "version": "3.980.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.980.0.tgz",
- "integrity": "sha512-AjKBNEc+rjOZQE1HwcD9aCELqg1GmUj1rtICKuY8cgwB73xJ4U/kNyqKKpN2k9emGqlfDY2D8itIp/vDc6OKpw==",
- "license": "Apache-2.0",
- "dependencies": {
- "@aws-sdk/types": "^3.973.1",
- "@smithy/types": "^4.12.0",
- "@smithy/url-parser": "^4.2.8",
- "@smithy/util-endpoints": "^3.2.8",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=20.0.0"
- }
- },
"node_modules/@aws-sdk/core": {
- "version": "3.973.5",
- "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.973.5.tgz",
- "integrity": "sha512-IMM7xGfLGW6lMvubsA4j6BHU5FPgGAxoQ/NA63KqNLMwTS+PeMBcx8DPHL12Vg6yqOZnqok9Mu4H2BdQyq7gSA==",
+ "version": "3.973.8",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.973.8.tgz",
+ "integrity": "sha512-WeYJ2sfvRLbbUIrjGMUXcEHGu5SJk53jz3K9F8vFP42zWyROzPJ2NB6lMu9vWl5hnMwzwabX7pJc9Euh3JyMGw==",
"license": "Apache-2.0",
"dependencies": {
"@aws-sdk/types": "^3.973.1",
- "@aws-sdk/xml-builder": "^3.972.2",
- "@smithy/core": "^3.22.0",
+ "@aws-sdk/xml-builder": "^3.972.4",
+ "@smithy/core": "^3.23.0",
"@smithy/node-config-provider": "^4.3.8",
"@smithy/property-provider": "^4.2.8",
"@smithy/protocol-http": "^5.3.8",
"@smithy/signature-v4": "^5.3.8",
- "@smithy/smithy-client": "^4.11.1",
+ "@smithy/smithy-client": "^4.11.3",
"@smithy/types": "^4.12.0",
"@smithy/util-base64": "^4.3.0",
"@smithy/util-middleware": "^4.2.8",
@@ -312,12 +297,12 @@
}
},
"node_modules/@aws-sdk/credential-provider-env": {
- "version": "3.972.3",
- "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.3.tgz",
- "integrity": "sha512-OBYNY4xQPq7Rx+oOhtyuyO0AQvdJSpXRg7JuPNBJH4a1XXIzJQl4UHQTPKZKwfJXmYLpv4+OkcFen4LYmDPd3g==",
+ "version": "3.972.6",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.6.tgz",
+ "integrity": "sha512-+dYEBWgTqkQQHFUllvBL8SLyXyLKWdxLMD1LmKJRvmb0NMJuaJFG/qg78C+LE67eeGbipYcE+gJ48VlLBGHlMw==",
"license": "Apache-2.0",
"dependencies": {
- "@aws-sdk/core": "^3.973.5",
+ "@aws-sdk/core": "^3.973.8",
"@aws-sdk/types": "^3.973.1",
"@smithy/property-provider": "^4.2.8",
"@smithy/types": "^4.12.0",
@@ -328,20 +313,20 @@
}
},
"node_modules/@aws-sdk/credential-provider-http": {
- "version": "3.972.5",
- "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.5.tgz",
- "integrity": "sha512-GpvBgEmSZPvlDekd26Zi+XsI27Qz7y0utUx0g2fSTSiDzhnd1FSa1owuodxR0BcUKNL7U2cOVhhDxgZ4iSoPVg==",
+ "version": "3.972.8",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.8.tgz",
+ "integrity": "sha512-z3QkozMV8kOFisN2pgRag/f0zPDrw96mY+ejAM0xssV/+YQ2kklbylRNI/TcTQUDnGg0yPxNjyV6F2EM2zPTwg==",
"license": "Apache-2.0",
"dependencies": {
- "@aws-sdk/core": "^3.973.5",
+ "@aws-sdk/core": "^3.973.8",
"@aws-sdk/types": "^3.973.1",
"@smithy/fetch-http-handler": "^5.3.9",
- "@smithy/node-http-handler": "^4.4.8",
+ "@smithy/node-http-handler": "^4.4.10",
"@smithy/property-provider": "^4.2.8",
"@smithy/protocol-http": "^5.3.8",
- "@smithy/smithy-client": "^4.11.1",
+ "@smithy/smithy-client": "^4.11.3",
"@smithy/types": "^4.12.0",
- "@smithy/util-stream": "^4.5.10",
+ "@smithy/util-stream": "^4.5.12",
"tslib": "^2.6.2"
},
"engines": {
@@ -349,19 +334,19 @@
}
},
"node_modules/@aws-sdk/credential-provider-ini": {
- "version": "3.972.3",
- "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.3.tgz",
- "integrity": "sha512-rMQAIxstP7cLgYfsRGrGOlpyMl0l8JL2mcke3dsIPLWke05zKOFyR7yoJzWCsI/QiIxjRbxpvPiAeKEA6CoYkg==",
+ "version": "3.972.6",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.6.tgz",
+ "integrity": "sha512-6tkIYFv3sZH1XsjQq+veOmx8XWRnyqTZ5zx/sMtdu/xFRIzrJM1Y2wAXeCJL1rhYSB7uJSZ1PgALI2WVTj78ow==",
"license": "Apache-2.0",
"dependencies": {
- "@aws-sdk/core": "^3.973.5",
- "@aws-sdk/credential-provider-env": "^3.972.3",
- "@aws-sdk/credential-provider-http": "^3.972.5",
- "@aws-sdk/credential-provider-login": "^3.972.3",
- "@aws-sdk/credential-provider-process": "^3.972.3",
- "@aws-sdk/credential-provider-sso": "^3.972.3",
- "@aws-sdk/credential-provider-web-identity": "^3.972.3",
- "@aws-sdk/nested-clients": "3.980.0",
+ "@aws-sdk/core": "^3.973.8",
+ "@aws-sdk/credential-provider-env": "^3.972.6",
+ "@aws-sdk/credential-provider-http": "^3.972.8",
+ "@aws-sdk/credential-provider-login": "^3.972.6",
+ "@aws-sdk/credential-provider-process": "^3.972.6",
+ "@aws-sdk/credential-provider-sso": "^3.972.6",
+ "@aws-sdk/credential-provider-web-identity": "^3.972.6",
+ "@aws-sdk/nested-clients": "3.988.0",
"@aws-sdk/types": "^3.973.1",
"@smithy/credential-provider-imds": "^4.2.8",
"@smithy/property-provider": "^4.2.8",
@@ -374,13 +359,13 @@
}
},
"node_modules/@aws-sdk/credential-provider-login": {
- "version": "3.972.3",
- "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.3.tgz",
- "integrity": "sha512-Gc3O91iVvA47kp2CLIXOwuo5ffo1cIpmmyIewcYjAcvurdFHQ8YdcBe1KHidnbbBO4/ZtywGBACsAX5vr3UdoA==",
+ "version": "3.972.6",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.6.tgz",
+ "integrity": "sha512-LXsoBoaTSGHdRCQXlWSA0CHHh05KWncb592h9ElklnPus++8kYn1Ic6acBR4LKFQ0RjjMVgwe5ypUpmTSUOjPA==",
"license": "Apache-2.0",
"dependencies": {
- "@aws-sdk/core": "^3.973.5",
- "@aws-sdk/nested-clients": "3.980.0",
+ "@aws-sdk/core": "^3.973.8",
+ "@aws-sdk/nested-clients": "3.988.0",
"@aws-sdk/types": "^3.973.1",
"@smithy/property-provider": "^4.2.8",
"@smithy/protocol-http": "^5.3.8",
@@ -393,17 +378,17 @@
}
},
"node_modules/@aws-sdk/credential-provider-node": {
- "version": "3.972.4",
- "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.4.tgz",
- "integrity": "sha512-UwerdzosMSY7V5oIZm3NsMDZPv2aSVzSkZxYxIOWHBeKTZlUqW7XpHtJMZ4PZpJ+HMRhgP+MDGQx4THndgqJfQ==",
+ "version": "3.972.7",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.7.tgz",
+ "integrity": "sha512-PuJ1IkISG7ZDpBFYpGotaay6dYtmriBYuHJ/Oko4VHxh8YN5vfoWnMNYFEWuzOfyLmP7o9kDVW0BlYIpb3skvw==",
"license": "Apache-2.0",
"dependencies": {
- "@aws-sdk/credential-provider-env": "^3.972.3",
- "@aws-sdk/credential-provider-http": "^3.972.5",
- "@aws-sdk/credential-provider-ini": "^3.972.3",
- "@aws-sdk/credential-provider-process": "^3.972.3",
- "@aws-sdk/credential-provider-sso": "^3.972.3",
- "@aws-sdk/credential-provider-web-identity": "^3.972.3",
+ "@aws-sdk/credential-provider-env": "^3.972.6",
+ "@aws-sdk/credential-provider-http": "^3.972.8",
+ "@aws-sdk/credential-provider-ini": "^3.972.6",
+ "@aws-sdk/credential-provider-process": "^3.972.6",
+ "@aws-sdk/credential-provider-sso": "^3.972.6",
+ "@aws-sdk/credential-provider-web-identity": "^3.972.6",
"@aws-sdk/types": "^3.973.1",
"@smithy/credential-provider-imds": "^4.2.8",
"@smithy/property-provider": "^4.2.8",
@@ -416,12 +401,12 @@
}
},
"node_modules/@aws-sdk/credential-provider-process": {
- "version": "3.972.3",
- "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.3.tgz",
- "integrity": "sha512-xkSY7zjRqeVc6TXK2xr3z1bTLm0wD8cj3lAkproRGaO4Ku7dPlKy843YKnHrUOUzOnMezdZ4xtmFc0eKIDTo2w==",
+ "version": "3.972.6",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.6.tgz",
+ "integrity": "sha512-Yf34cjIZJHVnD92jnVYy3tNjM+Q4WJtffLK2Ehn0nKpZfqd1m7SI0ra22Lym4C53ED76oZENVSS2wimoXJtChQ==",
"license": "Apache-2.0",
"dependencies": {
- "@aws-sdk/core": "^3.973.5",
+ "@aws-sdk/core": "^3.973.8",
"@aws-sdk/types": "^3.973.1",
"@smithy/property-provider": "^4.2.8",
"@smithy/shared-ini-file-loader": "^4.4.3",
@@ -433,14 +418,14 @@
}
},
"node_modules/@aws-sdk/credential-provider-sso": {
- "version": "3.972.3",
- "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.3.tgz",
- "integrity": "sha512-8Ww3F5Ngk8dZ6JPL/V5LhCU1BwMfQd3tLdoEuzaewX8FdnT633tPr+KTHySz9FK7fFPcz5qG3R5edVEhWQD4AA==",
+ "version": "3.972.6",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.6.tgz",
+ "integrity": "sha512-2+5UVwUYdD4BBOkLpKJ11MQ8wQeyJGDVMDRH5eWOULAh9d6HJq07R69M/mNNMC9NTjr3mB1T0KGDn4qyQh5jzg==",
"license": "Apache-2.0",
"dependencies": {
- "@aws-sdk/client-sso": "3.980.0",
- "@aws-sdk/core": "^3.973.5",
- "@aws-sdk/token-providers": "3.980.0",
+ "@aws-sdk/client-sso": "3.988.0",
+ "@aws-sdk/core": "^3.973.8",
+ "@aws-sdk/token-providers": "3.988.0",
"@aws-sdk/types": "^3.973.1",
"@smithy/property-provider": "^4.2.8",
"@smithy/shared-ini-file-loader": "^4.4.3",
@@ -452,13 +437,13 @@
}
},
"node_modules/@aws-sdk/credential-provider-web-identity": {
- "version": "3.972.3",
- "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.3.tgz",
- "integrity": "sha512-62VufdcH5rRfiRKZRcf1wVbbt/1jAntMj1+J0qAd+r5pQRg2t0/P9/Rz16B1o5/0Se9lVL506LRjrhIJAhYBfA==",
+ "version": "3.972.6",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.6.tgz",
+ "integrity": "sha512-pdJzwKtlDxBnvZ04pWMqttijmkUIlwOsS0GcxCjzEVyUMpARysl0S0ks74+gs2Pdev3Ujz+BTAjOc1tQgAxGqA==",
"license": "Apache-2.0",
"dependencies": {
- "@aws-sdk/core": "^3.973.5",
- "@aws-sdk/nested-clients": "3.980.0",
+ "@aws-sdk/core": "^3.973.8",
+ "@aws-sdk/nested-clients": "3.988.0",
"@aws-sdk/types": "^3.973.1",
"@smithy/property-provider": "^4.2.8",
"@smithy/shared-ini-file-loader": "^4.4.3",
@@ -515,15 +500,15 @@
}
},
"node_modules/@aws-sdk/middleware-user-agent": {
- "version": "3.972.5",
- "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.5.tgz",
- "integrity": "sha512-TVZQ6PWPwQbahUI8V+Er+gS41ctIawcI/uMNmQtQ7RMcg3JYn6gyKAFKUb3HFYx2OjYlx1u11sETSwwEUxVHTg==",
+ "version": "3.972.8",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.8.tgz",
+ "integrity": "sha512-3PGL+Kvh1PhB0EeJeqNqOWQgipdqFheO4OUKc6aYiFwEpM5t9AyE5hjjxZ5X6iSj8JiduWFZLPwASzF6wQRgFg==",
"license": "Apache-2.0",
"dependencies": {
- "@aws-sdk/core": "^3.973.5",
+ "@aws-sdk/core": "^3.973.8",
"@aws-sdk/types": "^3.973.1",
- "@aws-sdk/util-endpoints": "3.980.0",
- "@smithy/core": "^3.22.0",
+ "@aws-sdk/util-endpoints": "3.988.0",
+ "@smithy/core": "^3.23.0",
"@smithy/protocol-http": "^5.3.8",
"@smithy/types": "^4.12.0",
"tslib": "^2.6.2"
@@ -532,61 +517,45 @@
"node": ">=20.0.0"
}
},
- "node_modules/@aws-sdk/middleware-user-agent/node_modules/@aws-sdk/util-endpoints": {
- "version": "3.980.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.980.0.tgz",
- "integrity": "sha512-AjKBNEc+rjOZQE1HwcD9aCELqg1GmUj1rtICKuY8cgwB73xJ4U/kNyqKKpN2k9emGqlfDY2D8itIp/vDc6OKpw==",
- "license": "Apache-2.0",
- "dependencies": {
- "@aws-sdk/types": "^3.973.1",
- "@smithy/types": "^4.12.0",
- "@smithy/url-parser": "^4.2.8",
- "@smithy/util-endpoints": "^3.2.8",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=20.0.0"
- }
- },
"node_modules/@aws-sdk/nested-clients": {
- "version": "3.980.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.980.0.tgz",
- "integrity": "sha512-/dONY5xc5/CCKzOqHZCTidtAR4lJXWkGefXvTRKdSKMGaYbbKsxDckisd6GfnvPSLxWtvQzwgRGRutMRoYUApQ==",
+ "version": "3.988.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.988.0.tgz",
+ "integrity": "sha512-OgYV9k1oBCQ6dOM+wWAMNNehXA8L4iwr7ydFV+JDHyuuu0Ko7tDXnLEtEmeQGYRcAFU3MGasmlBkMB8vf4POrg==",
"license": "Apache-2.0",
"dependencies": {
"@aws-crypto/sha256-browser": "5.2.0",
"@aws-crypto/sha256-js": "5.2.0",
- "@aws-sdk/core": "^3.973.5",
+ "@aws-sdk/core": "^3.973.8",
"@aws-sdk/middleware-host-header": "^3.972.3",
"@aws-sdk/middleware-logger": "^3.972.3",
"@aws-sdk/middleware-recursion-detection": "^3.972.3",
- "@aws-sdk/middleware-user-agent": "^3.972.5",
+ "@aws-sdk/middleware-user-agent": "^3.972.8",
"@aws-sdk/region-config-resolver": "^3.972.3",
"@aws-sdk/types": "^3.973.1",
- "@aws-sdk/util-endpoints": "3.980.0",
+ "@aws-sdk/util-endpoints": "3.988.0",
"@aws-sdk/util-user-agent-browser": "^3.972.3",
- "@aws-sdk/util-user-agent-node": "^3.972.3",
+ "@aws-sdk/util-user-agent-node": "^3.972.6",
"@smithy/config-resolver": "^4.4.6",
- "@smithy/core": "^3.22.0",
+ "@smithy/core": "^3.23.0",
"@smithy/fetch-http-handler": "^5.3.9",
"@smithy/hash-node": "^4.2.8",
"@smithy/invalid-dependency": "^4.2.8",
"@smithy/middleware-content-length": "^4.2.8",
- "@smithy/middleware-endpoint": "^4.4.12",
- "@smithy/middleware-retry": "^4.4.29",
+ "@smithy/middleware-endpoint": "^4.4.14",
+ "@smithy/middleware-retry": "^4.4.31",
"@smithy/middleware-serde": "^4.2.9",
"@smithy/middleware-stack": "^4.2.8",
"@smithy/node-config-provider": "^4.3.8",
- "@smithy/node-http-handler": "^4.4.8",
+ "@smithy/node-http-handler": "^4.4.10",
"@smithy/protocol-http": "^5.3.8",
- "@smithy/smithy-client": "^4.11.1",
+ "@smithy/smithy-client": "^4.11.3",
"@smithy/types": "^4.12.0",
"@smithy/url-parser": "^4.2.8",
"@smithy/util-base64": "^4.3.0",
"@smithy/util-body-length-browser": "^4.2.0",
"@smithy/util-body-length-node": "^4.2.1",
- "@smithy/util-defaults-mode-browser": "^4.3.28",
- "@smithy/util-defaults-mode-node": "^4.2.31",
+ "@smithy/util-defaults-mode-browser": "^4.3.30",
+ "@smithy/util-defaults-mode-node": "^4.2.33",
"@smithy/util-endpoints": "^3.2.8",
"@smithy/util-middleware": "^4.2.8",
"@smithy/util-retry": "^4.2.8",
@@ -597,22 +566,6 @@
"node": ">=20.0.0"
}
},
- "node_modules/@aws-sdk/nested-clients/node_modules/@aws-sdk/util-endpoints": {
- "version": "3.980.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.980.0.tgz",
- "integrity": "sha512-AjKBNEc+rjOZQE1HwcD9aCELqg1GmUj1rtICKuY8cgwB73xJ4U/kNyqKKpN2k9emGqlfDY2D8itIp/vDc6OKpw==",
- "license": "Apache-2.0",
- "dependencies": {
- "@aws-sdk/types": "^3.973.1",
- "@smithy/types": "^4.12.0",
- "@smithy/url-parser": "^4.2.8",
- "@smithy/util-endpoints": "^3.2.8",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=20.0.0"
- }
- },
"node_modules/@aws-sdk/region-config-resolver": {
"version": "3.972.3",
"resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.972.3.tgz",
@@ -630,13 +583,13 @@
}
},
"node_modules/@aws-sdk/token-providers": {
- "version": "3.980.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.980.0.tgz",
- "integrity": "sha512-1nFileg1wAgDmieRoj9dOawgr2hhlh7xdvcH57b1NnqfPaVlcqVJyPc6k3TLDUFPY69eEwNxdGue/0wIz58vjA==",
+ "version": "3.988.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.988.0.tgz",
+ "integrity": "sha512-xvXVlRVKHnF2h6fgWBm64aPP5J+58aJyGfRrQa/uFh8a9mcK68mLfJOYq+ZSxQy/UN3McafJ2ILAy7IWzT9kRw==",
"license": "Apache-2.0",
"dependencies": {
- "@aws-sdk/core": "^3.973.5",
- "@aws-sdk/nested-clients": "3.980.0",
+ "@aws-sdk/core": "^3.973.8",
+ "@aws-sdk/nested-clients": "3.988.0",
"@aws-sdk/types": "^3.973.1",
"@smithy/property-provider": "^4.2.8",
"@smithy/shared-ini-file-loader": "^4.4.3",
@@ -661,9 +614,9 @@
}
},
"node_modules/@aws-sdk/util-endpoints": {
- "version": "3.981.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.981.0.tgz",
- "integrity": "sha512-a8nXh/H3/4j+sxhZk+N3acSDlgwTVSZbX9i55dx41gI1H+geuonuRG+Shv3GZsCb46vzc08RK2qC78ypO8uRlg==",
+ "version": "3.988.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.988.0.tgz",
+ "integrity": "sha512-HuXu4boeUWU0DQiLslbgdvuQ4ZMCo4Lsk97w8BIUokql2o9MvjE5dwqI5pzGt0K7afO1FybjidUQVTMLuZNTOA==",
"license": "Apache-2.0",
"dependencies": {
"@aws-sdk/types": "^3.973.1",
@@ -701,12 +654,12 @@
}
},
"node_modules/@aws-sdk/util-user-agent-node": {
- "version": "3.972.3",
- "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.972.3.tgz",
- "integrity": "sha512-gqG+02/lXQtO0j3US6EVnxtwwoXQC5l2qkhLCrqUrqdtcQxV7FDMbm9wLjKqoronSHyELGTjbFKK/xV5q1bZNA==",
+ "version": "3.972.6",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.972.6.tgz",
+ "integrity": "sha512-966xH8TPqkqOXP7EwnEThcKKz0SNP9kVJBKd9M8bNXE4GSqVouMKKnFBwYnzbWVKuLXubzX5seokcX4a0JLJIA==",
"license": "Apache-2.0",
"dependencies": {
- "@aws-sdk/middleware-user-agent": "^3.972.5",
+ "@aws-sdk/middleware-user-agent": "^3.972.8",
"@aws-sdk/types": "^3.973.1",
"@smithy/node-config-provider": "^4.3.8",
"@smithy/types": "^4.12.0",
@@ -725,9 +678,9 @@
}
},
"node_modules/@aws-sdk/xml-builder": {
- "version": "3.972.3",
- "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.3.tgz",
- "integrity": "sha512-bCk63RsBNCWW4tt5atv5Sbrh+3J3e8YzgyF6aZb1JeXcdzG4k5SlPLeTMFOIXFuuFHIwgphUhn4i3uS/q49eww==",
+ "version": "3.972.4",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.4.tgz",
+ "integrity": "sha512-0zJ05ANfYqI6+rGqj8samZBFod0dPPousBjLEqg8WdxSgbMAkRgLyn81lP215Do0rFJ/17LIXwr7q0yK24mP6Q==",
"license": "Apache-2.0",
"dependencies": {
"@smithy/types": "^4.12.0",
@@ -1597,9 +1550,9 @@
}
},
"node_modules/@smithy/core": {
- "version": "3.22.1",
- "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.22.1.tgz",
- "integrity": "sha512-x3ie6Crr58MWrm4viHqqy2Du2rHYZjwu8BekasrQx4ca+Y24dzVAwq3yErdqIbc2G3I0kLQA13PQ+/rde+u65g==",
+ "version": "3.23.0",
+ "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.23.0.tgz",
+ "integrity": "sha512-Yq4UPVoQICM9zHnByLmG8632t2M0+yap4T7ANVw482J0W7HW0pOuxwVmeOwzJqX2Q89fkXz0Vybz55Wj2Xzrsg==",
"license": "Apache-2.0",
"dependencies": {
"@smithy/middleware-serde": "^4.2.9",
@@ -1608,7 +1561,7 @@
"@smithy/util-base64": "^4.3.0",
"@smithy/util-body-length-browser": "^4.2.0",
"@smithy/util-middleware": "^4.2.8",
- "@smithy/util-stream": "^4.5.11",
+ "@smithy/util-stream": "^4.5.12",
"@smithy/util-utf8": "^4.2.0",
"@smithy/uuid": "^1.1.0",
"tslib": "^2.6.2"
@@ -1704,12 +1657,12 @@
}
},
"node_modules/@smithy/middleware-endpoint": {
- "version": "4.4.13",
- "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.13.tgz",
- "integrity": "sha512-x6vn0PjYmGdNuKh/juUJJewZh7MoQ46jYaJ2mvekF4EesMuFfrl4LaW/k97Zjf8PTCPQmPgMvwewg7eNoH9n5w==",
+ "version": "4.4.14",
+ "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.14.tgz",
+ "integrity": "sha512-FUFNE5KVeaY6U/GL0nzAAHkaCHzXLZcY1EhtQnsAqhD8Du13oPKtMB9/0WK4/LK6a/T5OZ24wPoSShff5iI6Ag==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/core": "^3.22.1",
+ "@smithy/core": "^3.23.0",
"@smithy/middleware-serde": "^4.2.9",
"@smithy/node-config-provider": "^4.3.8",
"@smithy/shared-ini-file-loader": "^4.4.3",
@@ -1723,15 +1676,15 @@
}
},
"node_modules/@smithy/middleware-retry": {
- "version": "4.4.30",
- "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.30.tgz",
- "integrity": "sha512-CBGyFvN0f8hlnqKH/jckRDz78Snrp345+PVk8Ux7pnkUCW97Iinse59lY78hBt04h1GZ6hjBN94BRwZy1xC8Bg==",
+ "version": "4.4.31",
+ "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.31.tgz",
+ "integrity": "sha512-RXBzLpMkIrxBPe4C8OmEOHvS8aH9RUuCOH++Acb5jZDEblxDjyg6un72X9IcbrGTJoiUwmI7hLypNfuDACypbg==",
"license": "Apache-2.0",
"dependencies": {
"@smithy/node-config-provider": "^4.3.8",
"@smithy/protocol-http": "^5.3.8",
"@smithy/service-error-classification": "^4.2.8",
- "@smithy/smithy-client": "^4.11.2",
+ "@smithy/smithy-client": "^4.11.3",
"@smithy/types": "^4.12.0",
"@smithy/util-middleware": "^4.2.8",
"@smithy/util-retry": "^4.2.8",
@@ -1785,9 +1738,9 @@
}
},
"node_modules/@smithy/node-http-handler": {
- "version": "4.4.9",
- "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.9.tgz",
- "integrity": "sha512-KX5Wml5mF+luxm1szW4QDz32e3NObgJ4Fyw+irhph4I/2geXwUy4jkIMUs5ZPGflRBeR6BUkC2wqIab4Llgm3w==",
+ "version": "4.4.10",
+ "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.10.tgz",
+ "integrity": "sha512-u4YeUwOWRZaHbWaebvrs3UhwQwj+2VNmcVCwXcYTvPIuVyM7Ex1ftAj+fdbG/P4AkBwLq/+SKn+ydOI4ZJE9PA==",
"license": "Apache-2.0",
"dependencies": {
"@smithy/abort-controller": "^4.2.8",
@@ -1898,17 +1851,17 @@
}
},
"node_modules/@smithy/smithy-client": {
- "version": "4.11.2",
- "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.11.2.tgz",
- "integrity": "sha512-SCkGmFak/xC1n7hKRsUr6wOnBTJ3L22Qd4e8H1fQIuKTAjntwgU8lrdMe7uHdiT2mJAOWA/60qaW9tiMu69n1A==",
+ "version": "4.11.3",
+ "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.11.3.tgz",
+ "integrity": "sha512-Q7kY5sDau8OoE6Y9zJoRGgje8P4/UY0WzH8R2ok0PDh+iJ+ZnEKowhjEqYafVcubkbYxQVaqwm3iufktzhprGg==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/core": "^3.22.1",
- "@smithy/middleware-endpoint": "^4.4.13",
+ "@smithy/core": "^3.23.0",
+ "@smithy/middleware-endpoint": "^4.4.14",
"@smithy/middleware-stack": "^4.2.8",
"@smithy/protocol-http": "^5.3.8",
"@smithy/types": "^4.12.0",
- "@smithy/util-stream": "^4.5.11",
+ "@smithy/util-stream": "^4.5.12",
"tslib": "^2.6.2"
},
"engines": {
@@ -2005,13 +1958,13 @@
}
},
"node_modules/@smithy/util-defaults-mode-browser": {
- "version": "4.3.29",
- "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.29.tgz",
- "integrity": "sha512-nIGy3DNRmOjaYaaKcQDzmWsro9uxlaqUOhZDHQed9MW/GmkBZPtnU70Pu1+GT9IBmUXwRdDuiyaeiy9Xtpn3+Q==",
+ "version": "4.3.30",
+ "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.30.tgz",
+ "integrity": "sha512-cMni0uVU27zxOiU8TuC8pQLC1pYeZ/xEMxvchSK/ILwleRd1ugobOcIRr5vXtcRqKd4aBLWlpeBoDPJJ91LQng==",
"license": "Apache-2.0",
"dependencies": {
"@smithy/property-provider": "^4.2.8",
- "@smithy/smithy-client": "^4.11.2",
+ "@smithy/smithy-client": "^4.11.3",
"@smithy/types": "^4.12.0",
"tslib": "^2.6.2"
},
@@ -2020,16 +1973,16 @@
}
},
"node_modules/@smithy/util-defaults-mode-node": {
- "version": "4.2.32",
- "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.32.tgz",
- "integrity": "sha512-7dtFff6pu5fsjqrVve0YMhrnzJtccCWDacNKOkiZjJ++fmjGExmmSu341x+WU6Oc1IccL7lDuaUj7SfrHpWc5Q==",
+ "version": "4.2.33",
+ "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.33.tgz",
+ "integrity": "sha512-LEb2aq5F4oZUSzWBG7S53d4UytZSkOEJPXcBq/xbG2/TmK9EW5naUZ8lKu1BEyWMzdHIzEVN16M3k8oxDq+DJA==",
"license": "Apache-2.0",
"dependencies": {
"@smithy/config-resolver": "^4.4.6",
"@smithy/credential-provider-imds": "^4.2.8",
"@smithy/node-config-provider": "^4.3.8",
"@smithy/property-provider": "^4.2.8",
- "@smithy/smithy-client": "^4.11.2",
+ "@smithy/smithy-client": "^4.11.3",
"@smithy/types": "^4.12.0",
"tslib": "^2.6.2"
},
@@ -2091,13 +2044,13 @@
}
},
"node_modules/@smithy/util-stream": {
- "version": "4.5.11",
- "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.11.tgz",
- "integrity": "sha512-lKmZ0S/3Qj2OF5H1+VzvDLb6kRxGzZHq6f3rAsoSu5cTLGsn3v3VQBA8czkNNXlLjoFEtVu3OQT2jEeOtOE2CA==",
+ "version": "4.5.12",
+ "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.12.tgz",
+ "integrity": "sha512-D8tgkrmhAX/UNeCZbqbEO3uqyghUnEmmoO9YEvRuwxjlkKKUE7FOgCJnqpTlQPe9MApdWPky58mNQQHbnCzoNg==",
"license": "Apache-2.0",
"dependencies": {
"@smithy/fetch-http-handler": "^5.3.9",
- "@smithy/node-http-handler": "^4.4.9",
+ "@smithy/node-http-handler": "^4.4.10",
"@smithy/types": "^4.12.0",
"@smithy/util-base64": "^4.3.0",
"@smithy/util-buffer-from": "^4.2.0",
@@ -2467,6 +2420,12 @@
"node": ">= 0.4"
}
},
+ "node_modules/asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
+ "license": "MIT"
+ },
"node_modules/available-typed-arrays": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
@@ -2537,6 +2496,17 @@
"node": ">=8"
}
},
+ "node_modules/axios": {
+ "version": "1.13.5",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.5.tgz",
+ "integrity": "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==",
+ "license": "MIT",
+ "dependencies": {
+ "follow-redirects": "^1.15.11",
+ "form-data": "^4.0.5",
+ "proxy-from-env": "^1.1.0"
+ }
+ },
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@@ -2544,9 +2514,9 @@
"dev": true
},
"node_modules/bowser": {
- "version": "2.13.1",
- "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.13.1.tgz",
- "integrity": "sha512-OHawaAbjwx6rqICCKgSG0SAnT05bzd7ppyKLVUITZpANBaaMFBAsaNkto3LoQ31tyFP5kNujE8Cdx85G9VzOkw==",
+ "version": "2.14.1",
+ "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.14.1.tgz",
+ "integrity": "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==",
"license": "MIT"
},
"node_modules/brace-expansion": {
@@ -2657,7 +2627,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
- "dev": true,
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
@@ -2774,6 +2743,18 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/combined-stream": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+ "license": "MIT",
+ "dependencies": {
+ "delayed-stream": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -2914,6 +2895,15 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
"node_modules/denque": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
@@ -2962,7 +2952,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
- "dev": true,
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.1",
@@ -3063,7 +3052,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@@ -3073,7 +3061,6 @@
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@@ -3111,7 +3098,6 @@
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0"
@@ -3124,7 +3110,6 @@
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
@@ -3848,6 +3833,26 @@
"dev": true,
"license": "ISC"
},
+ "node_modules/follow-redirects": {
+ "version": "1.15.11",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
+ "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/RubenVerborgh"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=4.0"
+ },
+ "peerDependenciesMeta": {
+ "debug": {
+ "optional": true
+ }
+ }
+ },
"node_modules/for-each": {
"version": "0.3.5",
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz",
@@ -3864,6 +3869,22 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/form-data": {
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
+ "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
+ "license": "MIT",
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "es-set-tostringtag": "^2.1.0",
+ "hasown": "^2.0.2",
+ "mime-types": "^2.1.12"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
@@ -3875,7 +3896,6 @@
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
- "dev": true,
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
@@ -3935,7 +3955,6 @@
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
- "dev": true,
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.2",
@@ -3960,7 +3979,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
- "dev": true,
"license": "MIT",
"dependencies": {
"dunder-proto": "^1.0.1",
@@ -4089,7 +4107,6 @@
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@@ -4197,7 +4214,6 @@
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@@ -4210,7 +4226,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
- "dev": true,
"license": "MIT",
"dependencies": {
"has-symbols": "^1.0.3"
@@ -4226,7 +4241,6 @@
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
- "dev": true,
"dependencies": {
"function-bind": "^1.1.2"
},
@@ -4992,7 +5006,6 @@
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@@ -5030,6 +5043,27 @@
"node": ">=8.6"
}
},
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
"node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
@@ -5737,6 +5771,12 @@
"react-is": "^16.13.1"
}
},
+ "node_modules/proxy-from-env": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
+ "license": "MIT"
+ },
"node_modules/proxyquire": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/proxyquire/-/proxyquire-2.1.3.tgz",
diff --git a/package.json b/package.json
index 9dfb8b5..7d739b4 100644
--- a/package.json
+++ b/package.json
@@ -18,9 +18,10 @@
"author": "The Environment Agency",
"license": "OGL",
"dependencies": {
- "@aws-sdk/client-sns": "3.981.0",
+ "@aws-sdk/client-sns": "3.988.0",
"@xmldom/xmldom": "0.8.11",
"feed": "5.2.0",
+ "axios": "1.13.5",
"ioredis": "5.9.2",
"joi": "18.0.2",
"moment": "2.30.1",
diff --git a/readme.md b/readme.md
index 1d732cf..a61afc9 100644
--- a/readme.md
+++ b/readme.md
@@ -20,6 +20,9 @@ This project provides CAP XML services through the use of AWS Lambda.
| CPX_REDIS_HOST | Redis/Elasticache host| yes | | |
| CPX_REDIS_PORT | Redis/Elasticache port| yes | | |
| CPX_REDIS_TLS | Redis/Elasticache tls | yes | | |
+| CPX_METEOALARM_API_URL | Meteoalarm url | yes | | |
+| CPX_METEOALARM_API_USERNAME | Meteoalarm username | yes | | |
+| CPX_METEOALARM_API_PASSWORD | Meteoalarm password | yes | | |
## Prerequisites
diff --git a/test/lib/functions/processMessage.js b/test/lib/functions/processMessage.js
index 170cf52..f0ec86c 100644
--- a/test/lib/functions/processMessage.js
+++ b/test/lib/functions/processMessage.js
@@ -11,6 +11,7 @@ const processMessage = require('../../../lib/functions/processMessage').processM
const service = require('../../../lib/helpers/service')
const aws = require('../../../lib/helpers/aws')
const redis = require('../../../lib/helpers/redis')
+const meteoalarm = require('../../../lib/helpers/meteoalarm')
const Message = require('../../../lib/models/message')
const v2MessageMapping = require('../../../lib/models/v2MessageMapping')
const nwsAlert = { bodyXml: fs.readFileSync(path.join(__dirname, 'data', 'nws-alert.xml'), 'utf8') }
@@ -18,10 +19,10 @@ const ORIGINAL_ENV = process.env
let clock
const tomorrow = new Date(new Date().getTime() + (24 * 60 * 60 * 1000))
const identifier = '4eb3b7350ab7aa443650fc9351f02940E'
-const identifierV2 = `2.49.0.0.826.1.20251106080027.${identifier}`
+const identifierV2 = `2.49.0.1.826.1.20251106080027.${identifier}`
const code = 'MCP:v2.0'
const referencesV1 = 'www.gov.uk/environment-agency,4eb3b7350ab7aa443650fc9351f2,2020-01-01T00:00:00+00:00'
-const referencesV2 = 'www.gov.uk/environment-agency,2.49.0.0.826.1.20251106080027.4eb3b7350ab7aa443650fc9351f02940E,2020-01-01T00:00:00+00:00'
+const referencesV2 = 'www.gov.uk/environment-agency,2.49.0.1.826.1.20251106080027.4eb3b7350ab7aa443650fc9351f02940E,2020-01-01T00:00:00+00:00'
// ***********************************************************
// Helper functions
@@ -31,6 +32,7 @@ const expectResponse = (response, putQuery, severity = 'Minor', status = 'Test',
expectMessageV1(new Message(putQuery.values[3]), severity, status, references, previousReferences, quickdialNumber)
expectMessageV2(new Message(putQuery.values[10]), severity, status, references, previousReferences, quickdialNumber)
expectRedisSet(identifier)
+ expectMeteoalarmPost(putQuery.values[10])
}
const expectRedisSet = (identifier) => {
@@ -43,6 +45,13 @@ const expectRedisSet = (identifier) => {
Code.expect(value.alert_v2).to.not.be.empty()
}
+const expectMeteoalarmPost = (messageV2Xml) => {
+ Code.expect(meteoalarm.postWarning.calledOnce).to.be.true()
+ const [xmlMessage, messageIdentifier] = meteoalarm.postWarning.firstCall.args
+ Code.expect(xmlMessage).to.equal(messageV2Xml)
+ Code.expect(messageIdentifier).to.equal(identifier)
+}
+
const expectResponseAndPutQuery = (response, putQuery, status, msgType, references, previousReferences) => {
// test response
Code.expect(response.statusCode).to.equal(200)
@@ -165,6 +174,8 @@ lab.experiment('processMessage', () => {
})
// mock redis
sinon.stub(redis, 'set').resolves('OK')
+ // mock meteoalarm
+ sinon.stub(meteoalarm, 'postWarning').resolves({ id: 'meteoalarm-warning-id' })
})
lab.afterEach(() => {
@@ -184,16 +195,19 @@ lab.experiment('processMessage', () => {
// do alert and test output xml
redis.set.resetHistory()
+ meteoalarm.postWarning.resetHistory()
let response = await processMessage(nwsAlert)
expectResponse(response, putQuery, 'Minor')
// do warning and test output xml
redis.set.resetHistory()
+ meteoalarm.postWarning.resetHistory()
response = await processMessage({ bodyXml: nwsAlert.bodyXml.replace('Minor', 'Moderate') })
expectResponse(response, putQuery, 'Moderate')
// do severe warning and test output xml
redis.set.resetHistory()
+ meteoalarm.postWarning.resetHistory()
response = await processMessage({ bodyXml: nwsAlert.bodyXml.replace('Minor', 'Severe') })
expectResponse(response, putQuery, 'Severe')
})
@@ -210,16 +224,19 @@ lab.experiment('processMessage', () => {
})
// do alert and test output xml
redis.set.resetHistory()
+ meteoalarm.postWarning.resetHistory()
let response = await processMessage(nwsAlert)
expectResponse(response, putQuery, 'Minor')
// do warning and test output xml
redis.set.resetHistory()
+ meteoalarm.postWarning.resetHistory()
response = await processMessage({ bodyXml: nwsAlert.bodyXml.replace('Minor', 'Moderate') })
expectResponse(response, putQuery, 'Moderate')
// do severe warning and test output xml
redis.set.resetHistory()
+ meteoalarm.postWarning.resetHistory()
response = await processMessage({ bodyXml: nwsAlert.bodyXml.replace('Minor', 'Severe') })
expectResponse(response, putQuery, 'Severe')
})
@@ -233,16 +250,19 @@ lab.experiment('processMessage', () => {
// do alert and test output xml
redis.set.resetHistory()
+ meteoalarm.postWarning.resetHistory()
let response = await processMessage(nwsAlert)
expectResponse(response, putQuery, 'Minor', 'Actual')
// do warning and test output xml
redis.set.resetHistory()
+ meteoalarm.postWarning.resetHistory()
response = await processMessage({ bodyXml: nwsAlert.bodyXml.replace('Minor', 'Moderate') })
expectResponse(response, putQuery, 'Moderate', 'Actual')
// do severe warning and test output xml
redis.set.resetHistory()
+ meteoalarm.postWarning.resetHistory()
response = await processMessage({ bodyXml: nwsAlert.bodyXml.replace('Minor', 'Severe') })
expectResponse(response, putQuery, 'Severe', 'Actual')
})
@@ -266,16 +286,19 @@ lab.experiment('processMessage', () => {
// do alert and test output xml
redis.set.resetHistory()
+ meteoalarm.postWarning.resetHistory()
let response = await processMessage(nwsAlert)
expectResponse(response, putQuery, 'Minor', 'Test', 'Update', true)
// do warning and test output xml
redis.set.resetHistory()
+ meteoalarm.postWarning.resetHistory()
response = await processMessage({ bodyXml: nwsAlert.bodyXml.replace('Minor', 'Moderate') })
expectResponse(response, putQuery, 'Moderate', 'Test', 'Update', true)
// do severe warning and test output xml
redis.set.resetHistory()
+ meteoalarm.postWarning.resetHistory()
response = await processMessage({ bodyXml: nwsAlert.bodyXml.replace('Minor', 'Severe') })
expectResponse(response, putQuery, 'Severe', 'Test', 'Update', true)
})
@@ -300,16 +323,19 @@ lab.experiment('processMessage', () => {
// do alert and test output xml
redis.set.resetHistory()
+ meteoalarm.postWarning.resetHistory()
let response = await processMessage(nwsAlert)
expectResponse(response, putQuery, 'Minor', 'Actual', 'Update', true)
// do warning and test output xml
redis.set.resetHistory()
+ meteoalarm.postWarning.resetHistory()
response = await processMessage({ bodyXml: nwsAlert.bodyXml.replace('Minor', 'Moderate') })
expectResponse(response, putQuery, 'Moderate', 'Actual', 'Update', true)
// do severe warning and test output xml
redis.set.resetHistory()
+ meteoalarm.postWarning.resetHistory()
response = await processMessage({ bodyXml: nwsAlert.bodyXml.replace('Minor', 'Severe') })
expectResponse(response, putQuery, 'Severe', 'Actual', 'Update', true)
})
@@ -338,16 +364,19 @@ lab.experiment('processMessage', () => {
// do alert and test output xml
redis.set.resetHistory()
+ meteoalarm.postWarning.resetHistory()
let response = await processMessage(alert)
expectResponse(response, putQuery, 'Minor', 'Test', 'Update', true, true, false)
// do warning and test output xml
redis.set.resetHistory()
+ meteoalarm.postWarning.resetHistory()
response = await processMessage({ bodyXml: alert.bodyXml.replace('Minor', 'Moderate') })
expectResponse(response, putQuery, 'Moderate', 'Test', 'Update', true, true, false)
// do severe warning and test output xml
redis.set.resetHistory()
+ meteoalarm.postWarning.resetHistory()
response = await processMessage({ bodyXml: alert.bodyXml.replace('Minor', 'Severe') })
expectResponse(response, putQuery, 'Severe', 'Test', 'Update', true, true, false)
})
@@ -374,16 +403,19 @@ lab.experiment('processMessage', () => {
// do alert and test output xml
redis.set.resetHistory()
+ meteoalarm.postWarning.resetHistory()
let response = await processMessage(nwsAlert)
expectResponse(response, putQuery, 'Minor', 'Actual', 'Update', true, true)
// do warning and test output xml
redis.set.resetHistory()
+ meteoalarm.postWarning.resetHistory()
response = await processMessage({ bodyXml: nwsAlert.bodyXml.replace('Minor', 'Moderate') })
expectResponse(response, putQuery, 'Moderate', 'Actual', 'Update', true, true)
// do severe warning and test output xml
redis.set.resetHistory()
+ meteoalarm.postWarning.resetHistory()
response = await processMessage({ bodyXml: nwsAlert.bodyXml.replace('Minor', 'Severe') })
expectResponse(response, putQuery, 'Severe', 'Actual', 'Update', true, true)
})
@@ -430,4 +462,87 @@ lab.experiment('processMessage', () => {
})
await Code.expect(processMessage(nwsAlert)).to.reject()
})
+
+ lab.test('Throws error when pre/post validation has errors with no SNS message', async () => {
+ const consoleLogStub = sinon.stub(console, 'log')
+ const badAlert = { bodyXml: nwsAlert.bodyXml.replace('4eb3b7350ab7aa443650fc9351f02940E', '') }
+ await Code.expect(processMessage(badAlert)).to.reject()
+ Code.expect(consoleLogStub.calledWith(badAlert.bodyXml)).to.be.true()
+ consoleLogStub.restore()
+ })
+
+ lab.test('Throws error when pre/post validation has errors with SNS message sent', async () => {
+ sinon.stub(aws.email, 'publishMessage').resolves()
+ process.env.CPX_SNS_TOPIC = 'arn:aws:sns:region:account:topic'
+ const consoleLogStub = sinon.stub(console, 'log')
+ const badAlert = { bodyXml: nwsAlert.bodyXml.replace('4eb3b7350ab7aa443650fc9351f02940E', '') }
+ const err = await Code.expect(processMessage(badAlert)).to.reject()
+ Code.expect(err.message).to.contain('[500]')
+ Code.expect(aws.email.publishMessage.calledOnce).to.be.true()
+ Code.expect(consoleLogStub.calledWith(badAlert.bodyXml)).to.be.true()
+ consoleLogStub.restore()
+ })
+
+ lab.test('does not log when validator has no errors', async () => {
+ const consoleLogStub = sinon.stub(console, 'log')
+ service.putMessage = (query) => Promise.resolve()
+ const response = await processMessage(nwsAlert)
+ Code.expect(response.statusCode).to.equal(200)
+ // Check that the error logging for validation didn't occur
+ // (processMessage itself logs processing messages, so we check it doesn't log the bodyXml)
+ Code.expect(consoleLogStub.calledWith(nwsAlert.bodyXml)).to.be.false()
+ consoleLogStub.restore()
+ })
+
+ lab.test('Meteoalarm failure triggers error with no SNS notification (no SNS configured)', async () => {
+ const consoleLogStub = sinon.stub(console, 'log')
+ meteoalarm.postWarning.rejects(new Error('Meteoalarm API unavailable'))
+
+ const putMessageStub = sinon.stub(service, 'putMessage').resolves()
+
+ const err = await Code.expect(processMessage(nwsAlert)).to.reject()
+
+ // Should throw the meteoalarm error
+ Code.expect(err.message).to.equal('Meteoalarm API unavailable')
+
+ // Should have logged the bodyXml
+ Code.expect(consoleLogStub.calledWith(nwsAlert.bodyXml)).to.be.true()
+
+ // Should have attempted other services before meteoalarm failed
+ Code.expect(putMessageStub.calledOnce).to.be.true()
+ Code.expect(redis.set.calledOnce).to.be.true()
+
+ consoleLogStub.restore()
+ })
+
+ lab.test('Meteoalarm failure triggers error with SNS notification', async () => {
+ sinon.stub(aws.email, 'publishMessage').resolves()
+ process.env.CPX_SNS_TOPIC = 'arn:aws:sns:region:account:topic'
+ const consoleLogStub = sinon.stub(console, 'log')
+ meteoalarm.postWarning.rejects(new Error('Meteoalarm API unavailable'))
+
+ const putMessageStub = sinon.stub(service, 'putMessage').resolves()
+
+ const err = await Code.expect(processMessage(nwsAlert)).to.reject()
+
+ // Should throw the error with [500] prefix
+ Code.expect(err.message).to.contain('[500]')
+ Code.expect(err.message).to.contain('Meteoalarm API unavailable')
+
+ // Should have sent SNS notification
+ Code.expect(aws.email.publishMessage.calledOnce).to.be.true()
+ const publishArgs = aws.email.publishMessage.firstCall.args[0]
+ Code.expect(publishArgs.receivedMessage).to.equal(JSON.stringify(nwsAlert.bodyXml))
+ Code.expect(publishArgs.errorMessage).to.equal('Meteoalarm API unavailable')
+ Code.expect(publishArgs.dateCreated).to.exist()
+
+ // Should have logged the bodyXml
+ Code.expect(consoleLogStub.calledWith(nwsAlert.bodyXml)).to.be.true()
+
+ // Should have attempted other services before meteoalarm failed
+ Code.expect(putMessageStub.calledOnce).to.be.true()
+ Code.expect(redis.set.calledOnce).to.be.true()
+
+ consoleLogStub.restore()
+ })
})
diff --git a/test/lib/functions/processMessageValidation.js b/test/lib/functions/processMessageValidation.js
index 828b3d6..3d7e69d 100644
--- a/test/lib/functions/processMessageValidation.js
+++ b/test/lib/functions/processMessageValidation.js
@@ -17,6 +17,7 @@ const fakeService = {
const fakeSchema = { validateAsync: async () => ({ error: null }) }
const fakeAws = { email: { publishMessage: sinon.stub() } }
const fakeRedis = { set: sinon.stub().resolves('OK') }
+const fakeMeteoalarm = { postWarning: sinon.stub().resolves({ id: 'meteoalarm-warning-id' }) }
const loadWithValidateMock = (validateMock) => {
return Proxyquire('../../../lib/functions/processMessage', {
@@ -24,7 +25,8 @@ const loadWithValidateMock = (validateMock) => {
'../helpers/service': fakeService,
'../schemas/processMessageEventSchema': fakeSchema,
'../helpers/aws': fakeAws,
- '../helpers/redis': fakeRedis
+ '../helpers/redis': fakeRedis,
+ '../helpers/meteoalarm': fakeMeteoalarm
}).processMessage
}
@@ -33,8 +35,12 @@ const CPX_SNS_TOPIC = process.env.CPX_SNS_TOPIC
lab.experiment('processMessage validation logging', () => {
lab.afterEach(() => {
process.env.CPX_SNS_TOPIC = CPX_SNS_TOPIC
+ fakeAws.email.publishMessage.resetHistory()
+ fakeRedis.set.resetHistory()
+ fakeMeteoalarm.postWarning.resetHistory()
})
lab.test('Throws error when pre/post validation has errors with no SNS message', async () => {
+ delete process.env.CPX_SNS_TOPIC
const validateMock = async () => ({ errors: [{ message: 'oops' }] })
const processMessage = loadWithValidateMock(validateMock)
@@ -77,11 +83,13 @@ lab.experiment('processMessage validation logging', () => {
process.env.CPX_SNS_TOPIC = true
const awsStub = { email: { publishMessage: sinon.stub() } }
const redisStub = { set: sinon.stub().resolves('OK') }
+ const meteoalarmStub = { postWarning: sinon.stub().resolves({ id: 'meteoalarm-warning-id' }) }
const processMessage = Proxyquire('../../../lib/functions/processMessage', {
'../helpers/service': fakeService,
'../schemas/processMessageEventSchema': fakeSchema,
'../helpers/aws': awsStub,
- '../helpers/redis': redisStub
+ '../helpers/redis': redisStub,
+ '../helpers/meteoalarm': meteoalarmStub
}).processMessage
const ret = await processMessage(nwsAlert)
diff --git a/test/lib/helpers/messages.js b/test/lib/helpers/messages.js
index 15b0761..fbac803 100644
--- a/test/lib/helpers/messages.js
+++ b/test/lib/helpers/messages.js
@@ -123,7 +123,7 @@ lab.experiment('messages helper', () => {
alert: 'test',
sent: new Date(),
identifier: '4eb3b7350ab7aa443650fc9351f',
- identifier_v2: '2.49.0.0.826.1.YYYYMMDDHHMMSS.4eb3b7350ab7aa443650fc9351f'
+ identifier_v2: '2.49.0.1.826.1.YYYYMMDDHHMMSS.4eb3b7350ab7aa443650fc9351f'
}]
})
})
@@ -189,14 +189,14 @@ lab.experiment('messages helper', () => {
alert: 'test1',
sent: new Date('2025-01-01'),
identifier: 'id1',
- identifier_v2: '2.49.0.0.826.1.20250101000000.id1'
+ identifier_v2: '2.49.0.1.826.1.20250101000000.id1'
},
{
fwis_code: 'AREA2',
alert: 'test2',
sent: new Date('2025-01-02'),
identifier: 'id2',
- identifier_v2: '2.49.0.0.826.1.20250102000000.id2'
+ identifier_v2: '2.49.0.1.826.1.20250102000000.id2'
}
]
})
@@ -218,7 +218,7 @@ lab.experiment('messages helper', () => {
alert: 'test',
sent: new Date('2025-01-01T12:00:00Z'),
identifier: 'test_id',
- identifier_v2: '2.49.0.0.826.1.20250101120000.test_id'
+ identifier_v2: '2.49.0.1.826.1.20250101120000.test_id'
}]
})
}
@@ -263,7 +263,7 @@ lab.experiment('messages helper', () => {
alert: `test${i}`,
sent: new Date(`2025-01-0${i + 1}`),
identifier: `id${i}`,
- identifier_v2: `2.49.0.0.826.1.2025010${i + 1}000000.id${i}`
+ identifier_v2: `2.49.0.1.826.1.2025010${i + 1}000000.id${i}`
}))
})
}
diff --git a/test/lib/helpers/meteoalarm.js b/test/lib/helpers/meteoalarm.js
new file mode 100644
index 0000000..925aa3e
--- /dev/null
+++ b/test/lib/helpers/meteoalarm.js
@@ -0,0 +1,327 @@
+'use strict'
+
+const Lab = require('@hapi/lab')
+const Code = require('@hapi/code')
+const sinon = require('sinon')
+const Proxyquire = require('proxyquire')
+
+const lab = exports.lab = Lab.script()
+
+const ORIGINAL_ENV = process.env
+
+lab.experiment('meteoalarm helper', () => {
+ let meteoalarm
+ let axiosStub
+
+ lab.beforeEach(() => {
+ // Mock environment - must be set before loading module
+ process.env = { ...ORIGINAL_ENV }
+ process.env.CPX_METEOALARM_API_URL = 'https://test-meteoalarm.example.com'
+ process.env.CPX_METEOALARM_API_USERNAME = 'test-user'
+ process.env.CPX_METEOALARM_API_PASSWORD = 'test-password'
+
+ // Create axios stub
+ axiosStub = {
+ post: sinon.stub()
+ }
+
+ // Clear the require cache to force fresh module load
+ delete require.cache[require.resolve('../../../lib/helpers/meteoalarm')]
+
+ // Load module with mocked axios
+ meteoalarm = Proxyquire('../../../lib/helpers/meteoalarm', {
+ axios: axiosStub
+ })
+
+ // Clear any cached token before each test
+ meteoalarm.clearTokenCache()
+ })
+
+ lab.afterEach(() => {
+ sinon.restore()
+ process.env = ORIGINAL_ENV
+ })
+
+ lab.experiment('getValidToken', () => {
+ lab.test('fetches a new token successfully', async () => {
+ axiosStub.post.resolves({
+ status: 200,
+ data: { token: 'test-bearer-token-123' }
+ })
+
+ const token = await meteoalarm.getValidToken()
+
+ Code.expect(token).to.equal('test-bearer-token-123')
+ Code.expect(axiosStub.post.calledOnce).to.be.true()
+ Code.expect(axiosStub.post.firstCall.args[0]).to.equal('https://test-meteoalarm.example.com/tokens')
+ Code.expect(axiosStub.post.firstCall.args[1]).to.equal({
+ username: 'test-user',
+ password: 'test-password'
+ })
+ Code.expect(axiosStub.post.firstCall.args[2].headers['Content-Type']).to.equal('application/json')
+ })
+
+ lab.test('returns cached token if still valid', async () => {
+ // First call to get token
+ axiosStub.post.resolves({
+ status: 200,
+ data: { token: 'cached-token' }
+ })
+
+ const token1 = await meteoalarm.getValidToken()
+ Code.expect(token1).to.equal('cached-token')
+ Code.expect(axiosStub.post.calledOnce).to.be.true()
+
+ // Second call should return cached token without making another API call
+ const token2 = await meteoalarm.getValidToken()
+ Code.expect(token2).to.equal('cached-token')
+ Code.expect(axiosStub.post.calledOnce).to.be.true() // Still only called once
+ })
+
+ lab.test('throws error when authentication fails with non-200 status', async () => {
+ axiosStub.post.resolves({
+ status: 401,
+ statusText: 'Unauthorized'
+ })
+
+ await Code.expect(meteoalarm.getValidToken()).to.reject(Error, 'Failed to authenticate with Meteoalarm: Failed to authenticate: 401 Unauthorized')
+ })
+
+ lab.test('throws error when axios request fails', async () => {
+ axiosStub.post.rejects(new Error('Network error'))
+
+ await Code.expect(meteoalarm.getValidToken()).to.reject(Error, 'Failed to authenticate with Meteoalarm: Network error')
+ })
+ })
+
+ lab.experiment('postWarning', () => {
+ lab.beforeEach(() => {
+ // Set a very short retry delay for testing (10ms instead of 1000ms)
+ meteoalarm.setRetryDelayMultiplier(10)
+ })
+
+ lab.afterEach(() => {
+ // Reset to default
+ meteoalarm.setRetryDelayMultiplier(1000)
+ })
+
+ lab.test('successfully posts warning on first attempt', async () => {
+ const xmlMessage = 'test-id'
+ const identifier = 'test-id'
+
+ // Mock token request
+ axiosStub.post.onFirstCall().resolves({
+ status: 200,
+ data: { token: 'test-token' }
+ })
+
+ // Mock warning post
+ axiosStub.post.onSecondCall().resolves({
+ status: 201,
+ data: { id: 'warning-123', status: 'created' }
+ })
+
+ const result = await meteoalarm.postWarning(xmlMessage, identifier)
+
+ Code.expect(result).to.equal({ id: 'warning-123', status: 'created' })
+ Code.expect(axiosStub.post.calledTwice).to.be.true()
+
+ // Verify warning post call
+ const warningCall = axiosStub.post.secondCall
+ Code.expect(warningCall.args[0]).to.equal('https://test-meteoalarm.example.com/warnings')
+ Code.expect(warningCall.args[1]).to.equal(xmlMessage)
+ Code.expect(warningCall.args[2].headers.Authorization).to.equal('Bearer test-token')
+ Code.expect(warningCall.args[2].headers['Content-Type']).to.equal('application/xml')
+ Code.expect(warningCall.args[2].timeout).to.equal(10000)
+ })
+
+ lab.test('retries on failure and succeeds on second attempt', async () => {
+ const xmlMessage = 'test-id'
+ const identifier = 'test-id'
+
+ // Mock token request using withArgs for better matching
+ axiosStub.post.withArgs('https://test-meteoalarm.example.com/tokens').resolves({
+ status: 200,
+ data: { token: 'test-token' }
+ })
+
+ // Track warning post attempts
+ let attemptCount = 0
+ axiosStub.post.withArgs('https://test-meteoalarm.example.com/warnings').callsFake(() => {
+ attemptCount++
+ if (attemptCount === 1) {
+ const error = new Error('Timeout')
+ error.response = { data: { error: 'timeout' } }
+ return Promise.reject(error)
+ } else {
+ return Promise.resolve({
+ status: 201,
+ data: { id: 'warning-123' }
+ })
+ }
+ })
+
+ const result = await meteoalarm.postWarning(xmlMessage, identifier)
+
+ Code.expect(result).to.equal({ id: 'warning-123' })
+ Code.expect(attemptCount).to.equal(2)
+ })
+
+ lab.test('clears cached token on 401 and retries', async () => {
+ const xmlMessage = 'test-id'
+ const identifier = 'test-id'
+
+ // Track token requests
+ let tokenRequestCount = 0
+ axiosStub.post.withArgs('https://test-meteoalarm.example.com/tokens').callsFake(() => {
+ tokenRequestCount++
+ return Promise.resolve({
+ status: 200,
+ data: { token: tokenRequestCount === 1 ? 'expired-token' : 'fresh-token' }
+ })
+ })
+
+ // Track warning post attempts
+ let warningAttemptCount = 0
+ axiosStub.post.withArgs('https://test-meteoalarm.example.com/warnings').callsFake(() => {
+ warningAttemptCount++
+ if (warningAttemptCount === 1) {
+ const error = new Error('Unauthorized')
+ error.response = { status: 401, data: { error: 'token expired' } }
+ return Promise.reject(error)
+ } else {
+ return Promise.resolve({
+ status: 201,
+ data: { id: 'warning-456' }
+ })
+ }
+ })
+
+ const result = await meteoalarm.postWarning(xmlMessage, identifier)
+
+ Code.expect(result).to.equal({ id: 'warning-456' })
+ // Should have fetched token twice because of 401
+ Code.expect(tokenRequestCount).to.equal(2)
+ })
+
+ lab.test('throws error after max retries exceeded', async () => {
+ const xmlMessage = 'test-id'
+ const identifier = 'test-id'
+
+ // Mock token request
+ axiosStub.post.withArgs('https://test-meteoalarm.example.com/tokens').resolves({
+ status: 200,
+ data: { token: 'test-token' }
+ })
+
+ // All warning posts fail
+ const error = new Error('Service unavailable')
+ error.response = { data: { error: 'service down' } }
+ axiosStub.post.withArgs('https://test-meteoalarm.example.com/warnings').rejects(error)
+
+ await Code.expect(meteoalarm.postWarning(xmlMessage, identifier)).to.reject(Error, 'Failed to post warning to Meteoalarm after 3 attempts: Service unavailable')
+ })
+
+ lab.test('throws error when non-201 status is received', async () => {
+ const xmlMessage = 'test-id'
+ const identifier = 'test-id'
+
+ // Mock token request
+ axiosStub.post.withArgs('https://test-meteoalarm.example.com/tokens').resolves({
+ status: 200,
+ data: { token: 'test-token' }
+ })
+
+ // Warning post returns non-201 status and will retry
+ axiosStub.post.withArgs('https://test-meteoalarm.example.com/warnings').resolves({
+ status: 200,
+ data: { message: 'accepted but not created' }
+ })
+
+ await Code.expect(meteoalarm.postWarning(xmlMessage, identifier)).to.reject(Error, 'Failed to post warning to Meteoalarm after 3 attempts: Received non-201 response: 200')
+ })
+
+ lab.test('handles error without response object', async () => {
+ const xmlMessage = 'test-id'
+ const identifier = 'test-id'
+
+ // Mock token request
+ axiosStub.post.withArgs('https://test-meteoalarm.example.com/tokens').resolves({
+ status: 200,
+ data: { token: 'test-token' }
+ })
+
+ // All warning posts fail without response object
+ axiosStub.post.withArgs('https://test-meteoalarm.example.com/warnings').rejects(new Error('Network error'))
+
+ await Code.expect(meteoalarm.postWarning(xmlMessage, identifier)).to.reject(Error, 'Failed to post warning to Meteoalarm after 3 attempts: Network error')
+ })
+ })
+
+ lab.experiment('clearTokenCache', () => {
+ lab.test('clears cached token requiring new fetch', async () => {
+ // First call to get token
+ axiosStub.post.resolves({
+ status: 200,
+ data: { token: 'first-token' }
+ })
+
+ const token1 = await meteoalarm.getValidToken()
+ Code.expect(token1).to.equal('first-token')
+ Code.expect(axiosStub.post.calledOnce).to.be.true()
+
+ // Clear the cache
+ meteoalarm.clearTokenCache()
+
+ // Mock a different token for next call
+ axiosStub.post.resolves({
+ status: 200,
+ data: { token: 'second-token' }
+ })
+
+ // Next call should fetch a new token
+ const token2 = await meteoalarm.getValidToken()
+ Code.expect(token2).to.equal('second-token')
+ Code.expect(axiosStub.post.calledTwice).to.be.true()
+ })
+ })
+
+ lab.experiment('integration scenarios', () => {
+ lab.test('uses cached token across multiple warning posts', async () => {
+ const xmlMessage1 = 'test-id-1'
+ const xmlMessage2 = 'test-id-2'
+
+ // Mock token request once - should only be called once
+ let tokenCallCount = 0
+ axiosStub.post.callsFake((url, data, config) => {
+ if (url.includes('/tokens')) {
+ tokenCallCount++
+ return Promise.resolve({
+ status: 200,
+ data: { token: 'shared-token' }
+ })
+ } else if (url.includes('/warnings')) {
+ // Return different results for different messages
+ if (data === xmlMessage1) {
+ return Promise.resolve({
+ status: 201,
+ data: { id: 'warning-1' }
+ })
+ } else if (data === xmlMessage2) {
+ return Promise.resolve({
+ status: 201,
+ data: { id: 'warning-2' }
+ })
+ }
+ }
+ return Promise.reject(new Error('Unexpected call'))
+ })
+
+ await meteoalarm.postWarning(xmlMessage1, 'test-id-1')
+ await meteoalarm.postWarning(xmlMessage2, 'test-id-2')
+
+ // Token should only be fetched once
+ Code.expect(tokenCallCount).to.equal(1)
+ })
+ })
+})
diff --git a/test/lib/models/message.js b/test/lib/models/message.js
index e881793..8307019 100644
--- a/test/lib/models/message.js
+++ b/test/lib/models/message.js
@@ -181,6 +181,30 @@ lab.experiment('Message class', () => {
Code.expect(message.toString()).to.include('REF2')
})
+ lab.test('responseType defaults to empty string when missing', () => {
+ Code.expect(message.responseType).to.equal('')
+ })
+
+ lab.test('setting responseType adds element when not present', () => {
+ message.responseType = 'Prepare'
+ Code.expect(message.responseType).to.equal('Prepare')
+ Code.expect(message.toString()).to.include('Prepare')
+ })
+
+ lab.test('setting responseType updates existing element', () => {
+ // First set to add the element
+ message.responseType = 'Monitor'
+ Code.expect(message.responseType).to.equal('Monitor')
+
+ // Second set should update existing element
+ message.responseType = 'Evacuate'
+ Code.expect(message.responseType).to.equal('Evacuate')
+ Code.expect(message.toString()).to.include('Evacuate')
+ // Should only have one responseType element
+ const responseTypeCount = (message.toString().match(//g) || []).length
+ Code.expect(responseTypeCount).to.equal(1)
+ })
+
lab.test('parses quickdial number from instruction', () => {
Code.expect(message.quickdialNumber).to.equal('210010')
})
@@ -239,6 +263,7 @@ lab.experiment('Message class', () => {
Code.expect(messageBlank.sent).to.equal('')
Code.expect(messageBlank.code).to.equal('')
Code.expect(messageBlank.event).to.equal('')
+ Code.expect(messageBlank.responseType).to.equal('')
Code.expect(messageBlank.severity).to.equal('')
Code.expect(messageBlank.onset).to.equal('')
Code.expect(messageBlank.headline).to.equal('')
@@ -253,6 +278,7 @@ lab.experiment('Message class', () => {
messageBlank.status = 'Actual'
messageBlank.code = 'CODE123'
messageBlank.event = 'Test Event'
+ messageBlank.responseType = 'Shelter'
messageBlank.severity = 'Severe'
messageBlank.onset = '2026-06-01T10:00:00-00:00'
messageBlank.headline = 'Test Headline'
@@ -264,6 +290,7 @@ lab.experiment('Message class', () => {
Code.expect(messageBlank.status).to.equal('Actual')
Code.expect(messageBlank.code).to.equal('CODE123')
Code.expect(messageBlank.event).to.equal('Test Event')
+ Code.expect(messageBlank.responseType).to.equal('Shelter')
Code.expect(messageBlank.severity).to.equal('Severe')
Code.expect(messageBlank.onset).to.equal('2026-06-01T10:00:00-00:00')
Code.expect(messageBlank.headline).to.equal('Test Headline')
@@ -277,6 +304,7 @@ lab.experiment('Message class', () => {
messageBlank.status = 'Actual'
messageBlank.code = 'CODE123'
messageBlank.event = 'Test Event'
+ messageBlank.responseType = 'AllClear'
messageBlank.severity = 'Severe'
messageBlank.onset = '2026-06-01T10:00:00-00:00'
messageBlank.headline = 'Test Headline'
@@ -288,6 +316,7 @@ lab.experiment('Message class', () => {
Code.expect(messageBlank.status).to.equal('Actual')
Code.expect(messageBlank.code).to.equal('CODE123')
Code.expect(messageBlank.event).to.equal('Test Event')
+ Code.expect(messageBlank.responseType).to.equal('AllClear')
Code.expect(messageBlank.severity).to.equal('Severe')
Code.expect(messageBlank.onset).to.equal('2026-06-01T10:00:00-00:00')
Code.expect(messageBlank.headline).to.equal('Test Headline')
@@ -324,4 +353,83 @@ lab.experiment('Message class', () => {
fwisCode
`))
})
+
+ lab.test('removeNode removes a single node from the document', () => {
+ // Verify instruction exists before removal
+ Code.expect(message.instruction).to.not.be.empty()
+ Code.expect(message.toString()).to.include('')
+
+ // Remove instruction node
+ message.removeNode('instruction')
+
+ // Verify instruction is removed
+ Code.expect(message.instruction).to.equal('')
+ Code.expect(message.toString()).to.not.include('')
+ })
+
+ lab.test('removeNode removes multiple nodes of the same type', () => {
+ // Add multiple parameters
+ message.addParameter('param1', 'value1')
+ message.addParameter('param2', 'value2')
+ message.addParameter('param3', 'value3')
+
+ // Verify parameters exist
+ const xmlBefore = message.toString()
+ Code.expect(xmlBefore).to.include('')
+ Code.expect(xmlBefore).to.include('param1')
+ Code.expect(xmlBefore).to.include('param2')
+ Code.expect(xmlBefore).to.include('param3')
+
+ // Remove all parameter nodes
+ message.removeNode('parameter')
+
+ // Verify all parameters are removed
+ const xmlAfter = message.toString()
+ Code.expect(xmlAfter).to.not.include('')
+ Code.expect(xmlAfter).to.not.include('param1')
+ Code.expect(xmlAfter).to.not.include('param2')
+ Code.expect(xmlAfter).to.not.include('param3')
+ })
+
+ lab.test('removeNode handles non-existent nodes gracefully', () => {
+ const xmlBefore = message.toString()
+
+ // Try to remove a node that doesn't exist
+ message.removeNode('nonExistentNode')
+
+ // XML should remain unchanged
+ const xmlAfter = message.toString()
+ Code.expect(xmlAfter).to.equal(xmlBefore)
+ })
+
+ lab.test('removeNode removes code node when present', () => {
+ const messageWithCode = new Message(blankXml2)
+ messageWithCode.code = 'TEST_CODE'
+
+ // Verify code exists
+ Code.expect(messageWithCode.code).to.equal('TEST_CODE')
+ Code.expect(messageWithCode.toString()).to.include('TEST_CODE')
+
+ // Remove code node
+ messageWithCode.removeNode('code')
+
+ // Verify code is removed
+ Code.expect(messageWithCode.code).to.equal('')
+ Code.expect(messageWithCode.toString()).to.not.include('TEST_CODE')
+ })
+
+ lab.test('removeNode removes references node', () => {
+ message.references = 'REF123'
+
+ // Verify references exists
+ Code.expect(message.references).to.equal('REF123')
+ Code.expect(message.toString()).to.include('REF123')
+
+ // Remove references node
+ message.removeNode('references')
+
+ // Verify references is removed
+ Code.expect(message.references).to.equal('')
+ Code.expect(message.toString()).to.not.include('')
+ })
})