diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 54c645c..cbc6b93 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -14,7 +14,7 @@ jobs:
- name: Install nodejs
uses: actions/setup-node@v4
with:
- node-version: "20.x"
+ node-version: "22.x"
- name: Install node dependencies
run: npm ci
diff --git a/.labrc.js b/.labrc.js
index 1207a37..e835d0d 100644
--- a/.labrc.js
+++ b/.labrc.js
@@ -7,7 +7,7 @@ const globalsAsArray = [
'__asyncGenerator', '__asyncDelegator', '__asyncValues', '__makeTemplateObject',
'__importStar', '__importDefault', '__classPrivateFieldGet', '__classPrivateFieldSet',
'__classPrivateFieldIn', '__addDisposableResource', '__disposeResources',
- '__rewriteRelativeImportExtension'
+ '__rewriteRelativeImportExtension', 'awslambda'
]
const globals = globalsAsArray.toString()
diff --git a/.nvmrc b/.nvmrc
index 4a207c5..c6a66a6 100644
--- a/.nvmrc
+++ b/.nvmrc
@@ -1 +1 @@
-v20.18.3
+v22.21.1
diff --git a/capAlert.json b/capAlert.json
deleted file mode 100644
index 8e4c9b0..0000000
--- a/capAlert.json
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "message": "\r\n\r\n 4eb3b7350ab7aa443650fc9351f02940E\r\n www.gov.uk/environment-agency\r\n 2026-05-28T11:00:02-00:00\r\n Actual\r\n Alert\r\n Flood warning service\r\n Public\r\n \r\n en-GB\r\n Met\r\n \r\n ImmediateMinorLikely2026-05-29T11:00:02-00:00Environment AgencyArea descriptionpointsTargetAreaCode"
-}
\ No newline at end of file
diff --git a/docker/.env b/docker/.env
index d3fe79e..7a8df33 100644
--- a/docker/.env
+++ b/docker/.env
@@ -43,7 +43,7 @@ LAMBDA_IGNORE_ARCHITECTURE=1
# debugging when cloning the remote repository into a container volume.
DEBUG_HOST_ADDRESS=192.168.0.5
CPX_DB_HOST=capxmldb
-NODEJS_VERSION=20
+NODEJS_VERSION=22
PGADMIN_DEFAULT_EMAIL=ubuntu@localhost.localdomain
# Database associated values including well known secrets for local development
diff --git a/docker/dev-tools.yml b/docker/dev-tools.yml
index 257dd64..8a8b3bb 100644
--- a/docker/dev-tools.yml
+++ b/docker/dev-tools.yml
@@ -27,7 +27,7 @@ services:
- capxmlliquibase:/capxmldb
networks:
ls:
- command: update
+ command: /bin/sh -c "lpm add postgresql && liquibase update"
volumes:
capxmlpgadmin:
external: true
diff --git a/docker/scripts/initialize-named-volumes.sh b/docker/scripts/initialize-named-volumes.sh
index bba45df..f8ea3f5 100755
--- a/docker/scripts/initialize-named-volumes.sh
+++ b/docker/scripts/initialize-named-volumes.sh
@@ -4,13 +4,13 @@ set -e
# The macOS version of realpath does not support the -m switch so the GNU version
# is needed.
-if [ `uname` = "Darwin" ] && [ x`command -v grealpath` = "x" ]; then
+if [ $(uname) = "Darwin" ] && [ x$(command -v grealpath) = "x" ]; then
echo "GNU coreutils need to be installed to use realpath with the -m switch"
exit 1
fi
# If running on macOS use the GNU version of realpath.
-if [ `uname` = "Darwin" ]; then
+if [ $(uname) = "Darwin" ]; then
alias realpath="grealpath"
fi
@@ -74,7 +74,7 @@ fi
docker container create --name capxmlpgbootstraptemp -v capxmlpgbootstrap:/docker-entrypoint-initdb.d -v capxmlpgtmp:/tmp alpine
echo Created capxmlpgbootstraptemp container
docker cp ${CAP_XML_HOST_DIR}/docker/cap-xml-db/bootstrap-cap-xml-db.sh capxmlpgbootstraptemp:/docker-entrypoint-initdb.d/bootstrap-cap-xml-db.sh
-(cd `realpath -m ${CAP_XML_HOST_DIR}`/../cap-xml-db && docker cp ./cx/0.0.1/setup.sql capxmlpgbootstraptemp:/tmp/setup.sql)
+(cd $(realpath -m ${CAP_XML_HOST_DIR})/../cap-xml-db && docker cp ./cx/0.0.1/setup.sql capxmlpgbootstraptemp:/tmp/setup.sql)
docker rm capxmlpgbootstraptemp
echo Removed capxmlpgbootstraptemp container
@@ -90,6 +90,6 @@ fi
# https://stackoverflow.com/questions/37468788/what-is-the-right-way-to-add-data-to-an-existing-named-volume-in-docker
docker container create --name capxmlliquibasetemp -v capxmlliquibase:/capxmldb alpine
echo Created capxmlliquibasetemp container
-(cd `realpath -m ${CAP_XML_HOST_DIR}`/../cap-xml-db/cx && docker cp . capxmlliquibasetemp:/capxmldb)
+(cd $(realpath -m ${CAP_XML_HOST_DIR})/../cap-xml-db/cx && docker cp . capxmlliquibasetemp:/capxmldb)
docker rm capxmlliquibasetemp
echo Removed capxmlliquibasetemp container
\ No newline at end of file
diff --git a/docker/scripts/link-workspace-folder-on-host-to-local-repository.sh b/docker/scripts/link-workspace-folder-on-host-to-local-repository.sh
index 252a131..5731885 100755
--- a/docker/scripts/link-workspace-folder-on-host-to-local-repository.sh
+++ b/docker/scripts/link-workspace-folder-on-host-to-local-repository.sh
@@ -2,31 +2,31 @@
# This script MUST be run on the host before attempting to create a development container.
set -e
-if [ `whoami` != root ]; then
+if [ $(whoami) != root ]; then
echo This script must be run as root
exit 1
fi
-if [ ! -d "$LOCAL_CAP_XML_DIR"/.git ] && [ x`echo $"$LOCAL_CAP_XML_DIR" | grep -E /cap-xml/?$` = "x" ]; then
+if [ ! -d "$LOCAL_CAP_XML_DIR"/.git ] && [ x$(echo $"$LOCAL_CAP_XML_DIR" | grep -E /cap-xml/?$) = "x" ]; then
echo LOCAL_CAP_XML_DIR must be set to the absolute path of the root of a local cap-xml repository
exit 1
fi
-if [ `uname` != "Linux" ] && [ `uname` != "Darwin" ]; then
- echo "Unsupported operating system `uname` detected - Linux and macOS are supported"
+if [ $(uname) != "Linux" ] && [ $(uname) != "Darwin" ]; then
+ echo "Unsupported operating system $(uname) detected - Linux and macOS are supported"
exit 1
fi
# The macOS version of realpath does not support the -m switch so the GNU version
# is needed.
-if [ `uname` = "Darwin" ] && [ x`command -v grealpath` = "x" ]; then
+if [ $(uname) = "Darwin" ] && [ x$(command -v grealpath) = "x" ]; then
echo "GNU coreutils need to be installed to use realpath with the -m switch"
exit 1
fi
# If running on macOS use the GNU version of realpath.
-if [ `uname` = "Darwin" ]; then
+if [ $(uname) = "Darwin" ]; then
alias realpath="grealpath"
fi
@@ -65,11 +65,11 @@ CAP_XML_VOLUME_WORKSPACE_DIR=/workspaces/cap-xml
# (see https://apple.stackexchange.com/questions/388236/unable-to-create-folder-in-root-of-macintosh-hd),
# /workspaces/cap-xml cannot be created. Container volume based running/debugging is NOT supported using default
# macOS configuration accordingly.
-if [ `uname` = "Linux" ] && [ ! -L "$CAP_XML_VOLUME_WORKSPACE_DIR" ] && [ $(realpath -m "$CAP_XML_VOLUME_WORKSPACE_DIR") != $(realpath -m "$CAP_XML_WORKSPACE_DIR") ]; then
+if [ $(uname) = "Linux" ] && [ ! -L "$CAP_XML_VOLUME_WORKSPACE_DIR" ] && [ $(realpath -m "$CAP_XML_VOLUME_WORKSPACE_DIR") != $(realpath -m "$CAP_XML_WORKSPACE_DIR") ]; then
mkdir -p /workspaces
ln -s "$CAP_XML_WORKSPACE_DIR" "$CAP_XML_VOLUME_WORKSPACE_DIR"
echo Created symbolic link from "$CAP_XML_VOLUME_WORKSPACE_DIR" to "$CAP_XML_WORKSPACE_DIR"
-elif [ `uname` = "Darwin" ]; then
+elif [ $(uname) = "Darwin" ]; then
echo "macOS detected - WARNING - Running/debugging is only supported when creating a development container from a local cap-xml repository"
fi
diff --git a/docker/scripts/load-dummy-data.sh b/docker/scripts/load-dummy-data.sh
index 3f3171a..da2b4d8 100755
--- a/docker/scripts/load-dummy-data.sh
+++ b/docker/scripts/load-dummy-data.sh
@@ -7,9 +7,12 @@ set -e
# Constants
BASE_GUID="4eb3b7350ab7aa443650fc9351f02940E"
BASE_AREA="TESTAREA"
-DATA_FILE="capAlert.json"
+DATA_FILE="test/lib/functions/data/nws-alert.xml"
LAMBDA_URL=http://$(awslocal apigateway get-rest-apis | jq -r ".items[0].id").execute-api.localhost.localstack.cloud:4566/local/message
+# Calculate tomorrow's date
+TOMORROW=$(date -u -d "+1 day" +"%Y-%m-%dT%H:%M:%S+00:00")
+
# Loop 10 times
i=1
while [ $i -le 10 ]; do
@@ -21,7 +24,7 @@ while [ $i -le 10 ]; do
# Perform find and replace, then send with curl
curl -X POST "$LAMBDA_URL" \
-H "Content-Type: text/xml" \
- -d "$(sed -e "s/${BASE_GUID}/${NEW_GUID}/g" -e "s/${BASE_AREA}/${NEW_AREA}/g" "$DATA_FILE")"
+ -d "$(sed -e "s/${BASE_GUID}/${NEW_GUID}/g" -e "s/${BASE_AREA}/${NEW_AREA}/g" -e "s|2025-11-16T08:00:27+00:00|${TOMORROW}|g" "$DATA_FILE")"
echo "Done with POST $i"
i=$((i + 1))
diff --git a/docker/scripts/register-api-gateway.sh b/docker/scripts/register-api-gateway.sh
index cb43da7..4f2414a 100755
--- a/docker/scripts/register-api-gateway.sh
+++ b/docker/scripts/register-api-gateway.sh
@@ -11,16 +11,27 @@ main() {
cap_xml_rest_api_root_resource_id=$(awslocal apigateway get-resources --rest-api-id $cap_xml_rest_api_id | jq -r '.items[0].id')
lambda_functions_dir="lib/functions"
- for lambda_function in "$lambda_functions_dir"/*; do
+ find "$lambda_functions_dir" -type f -name "*.js" | while read -r lambda_function; do
+ relative_path="${lambda_function#$lambda_functions_dir/}"
+ dir_prefix=$(dirname "$relative_path")
lambda_function_name=$(basename "$lambda_function" .js)
http_method=$(get_http_method $lambda_function_name)
+
+ case "$dir_prefix" in
+ v[0-9]*)
+ lambda_function_name="${lambda_function_name}_${dir_prefix}"
+ ;;
+ *)
+ echo "No version prefix"
+ ;;
+ esac
if [ $lambda_function_name = "archiveMessages" ]; then
echo Skipping $lambda_function because it is not accessed through an API Gateway
continue
fi
-
- # Convert the Lambda function name from camel case to undersore case to call the correct API gateway registration function.
+
+ # Convert the Lambda function name from camel case to undersore case to call the correct API gateway registration function.
$(echo register_api_gateway_support_for_$lambda_function_name | sed -E "s/([a-z0-9])([A-Z])/\1_\2/g; s/([A-Z])([A-Z][a-z])/\1_\2/g" | tr "[:upper:]" "[:lower:]")
echo "API Gateway support added for $lambda_function_name"
@@ -47,11 +58,30 @@ register_api_gateway_support_for_get_message() {
put_method_and_integration $message_resource_id
}
+register_api_gateway_support_for_get_message_v2() {
+ if [ -z "$v2_resource_id" ]; then
+ v2_resource_id=$(create_resource "$cap_xml_rest_api_root_resource_id" "v2")
+ fi
+ get_message_v2_resource_id=$(create_resource $v2_resource_id "message")
+ message_v2_resource_id=$(create_resource $get_message_v2_resource_id "{id}")
+ put_method_and_integration $message_v2_resource_id
+ return 0
+}
+
register_api_gateway_support_for_get_messages_atom() {
get_messages_atom_resource_id=$(create_resource $cap_xml_rest_api_root_resource_id "messages.atom")
put_method_and_integration $get_messages_atom_resource_id
}
+register_api_gateway_support_for_get_messages_atom_v2() {
+ if [ -z "$v2_resource_id" ]; then
+ v2_resource_id=$(create_resource "$cap_xml_rest_api_root_resource_id" "v2")
+ fi
+ get_messages_atom_v2_resource_id=$(create_resource $v2_resource_id "messages.atom")
+ put_method_and_integration $get_messages_atom_v2_resource_id
+ return 0
+}
+
register_api_gateway_support_for_process_message() {
process_message_resource_id=$(create_resource $cap_xml_rest_api_root_resource_id "message")
put_method_and_integration $process_message_resource_id
@@ -90,7 +120,7 @@ put_integration() {
# by a function. This results in some duplication.
case $lambda_function_name in
- getMessage)
+ getMessage|getMessage_v2)
awslocal apigateway put-integration \
--rest-api-id $cap_xml_rest_api_id \
--resource-id $resource_id \
@@ -104,7 +134,7 @@ put_integration() {
put_responses_for_get_message
;;
- getMessagesAtom)
+ getMessagesAtom|getMessagesAtom_v2)
awslocal apigateway put-integration \
--rest-api-id $cap_xml_rest_api_id \
--resource-id $resource_id \
@@ -127,7 +157,11 @@ put_integration() {
--uri arn:aws:apigateway:eu-west-2:lambda:path/2015-03-31/functions/arn:aws:lambda:eu-west-2:000000000000:function:$lambda_function_name/invocations \
--passthrough-behavior WHEN_NO_TEMPLATES \
--content-handling CONVERT_TO_TEXT \
- --request-templates '{"text/html": "{\"bodyXml\": $input.json(\"$.message\")}", "text/xml": "{\"bodyXml\": $input.json(\"$.message\")}"}'
+ --request-templates '{
+ "text/html": "{\"bodyXml\": \"$util.escapeJavaScript($input.body)\"}",
+ "text/xml": "{\"bodyXml\": \"$util.escapeJavaScript($input.body)\"}"
+ }'
+
put_responses_for_process_message
;;
diff --git a/docker/scripts/register-lambda-functions.sh b/docker/scripts/register-lambda-functions.sh
index c95f205..faf5ab5 100755
--- a/docker/scripts/register-lambda-functions.sh
+++ b/docker/scripts/register-lambda-functions.sh
@@ -17,18 +17,33 @@ set -- $cpx_db_username $cpx_db_password $cpx_db_name $cpx_db_host $cpx_agw_url
custom_environment_variables=$(printf '%s,' "$@" | sed 's/,*$//g')
# Iterate over each file in lambda_functions_dir
-for lambda_function in "$lambda_functions_dir"/*; do
+find "$lambda_functions_dir" -type f -name "*.js" | while read -r lambda_function; do
if [ -f "$lambda_function" ]; then
+ relative_path="${lambda_function#$lambda_functions_dir/}"
+ dir_prefix=$(dirname "$relative_path")
function_name=$(basename "$lambda_function" .js)
+ handler_path="lib/functions/$function_name.$function_name" # default
+
+ # If the directory matches v{number}, update function name and handler path
+ case "$dir_prefix" in
+ v[0-9]*)
+ handler_path="lib/functions/$dir_prefix/$function_name.$function_name"
+ function_name="${function_name}_${dir_prefix}"
+ ;;
+ *)
+ echo "No version prefix"
+ ;;
+ esac
+
echo Registering $function_name with LocalStack
awslocal lambda create-function \
--function-name "$function_name" \
--code S3Bucket="hot-reload",S3Key="$(pwd)/" \
- --runtime nodejs20.x \
+ --runtime nodejs${NODEJS_VERSION}.x \
--timeout $LAMBDA_TIMEOUT \
--role arn:aws:iam::000000000000:role/lambda-role \
- --handler lib/functions/$function_name.$function_name \
+ --handler "$handler_path" \
--environment "Variables={$custom_environment_variables}" \
--no-cli-pager
sleep 1
diff --git a/docker/scripts/setup-for-rootless-docker-with-dev-container.sh b/docker/scripts/setup-for-rootless-docker-with-dev-container.sh
index 959e3f5..f232a6d 100755
--- a/docker/scripts/setup-for-rootless-docker-with-dev-container.sh
+++ b/docker/scripts/setup-for-rootless-docker-with-dev-container.sh
@@ -2,15 +2,15 @@
# This script MUST be run on the host before attempting to create a dev container using rootless Docker.
set -e
-if [ `whoami` != root ]; then
+if [ $(whoami) != root ]; then
echo This script must be run as root
exit 1
fi
HOST_UID=$(id -u "$CAP_XML_HOST_USERNAME")
HOST_GID=$(id -g "$CAP_XML_HOST_USERNAME")
-HOST_SUBUID=$(echo $(cat /etc/subuid | grep `echo $CAP_XML_HOST_USERNAME` | cut -d ':' -f 2))
-HOST_SUBGID=$(echo $(cat /etc/subgid | grep `echo $CAP_XML_HOST_USERNAME` | cut -d ':' -f 2))
+HOST_SUBUID=$(echo $(cat /etc/subuid | grep $(echo $CAP_XML_HOST_USERNAME) | cut -d ':' -f 2))
+HOST_SUBGID=$(echo $(cat /etc/subgid | grep $(echo $CAP_XML_HOST_USERNAME) | cut -d ':' -f 2))
if [ x"$HOST_SUBUID" = "x" ]; then
echo The host user $CAP_XML_HOST_USERNAME does not have a subuid entry in /etc/subuid
@@ -22,9 +22,9 @@ if [ x"$HOST_SUBGID" = "x" ]; then
exit 1
fi
-DEV_CONTAINER_UID_ON_HOST=`echo $((($HOST_SUBUID + $HOST_UID) - 1))`
-DEV_CONTAINER_GID_ON_HOST=`echo $((($HOST_SUBGID + $HOST_GID) - 1))`
-DEV_CONTAINER_DOCKER_GID_ON_HOST=$((($HOST_SUBGID + `getent group docker | cut -d ':' -f 3`) - 1))
+DEV_CONTAINER_UID_ON_HOST=$(echo $((($HOST_SUBUID + $HOST_UID) - 1)))
+DEV_CONTAINER_GID_ON_HOST=$(echo $((($HOST_SUBGID + $HOST_GID) - 1)))
+DEV_CONTAINER_DOCKER_GID_ON_HOST=$((($HOST_SUBGID + $(getent group docker | cut -d ':' -f 3)) - 1))
DOCKER_SOCKET=/var/run/docker.sock
ROOTLESS_DOCKER_SOCKET=/run/user/$HOST_UID/docker.sock
CAP_XML_WORKSPACE_DIR=/workspaces/cap-xml/
@@ -32,7 +32,7 @@ CAP_XML_WORKSPACE_DOCKER_DIR=${CAP_XML_WORKSPACE_DIR}docker
WORKSPACE_FOLDER_HOST_OWNERSHIP=$DEV_CONTAINER_UID_ON_HOST:$DEV_CONTAINER_GID_ON_HOST
WORKSPACE_DOCKER_FOLDER_HOST_OWNERSHIP=$HOST_UID:$HOST_GID
-if [ ! -d "$LOCAL_CAP_XML_DIR"/.git ] && [ x`echo $"$LOCAL_CAP_XML_DIR" | grep -E /cap-xml/?$` = "x" ]; then
+if [ ! -d "$LOCAL_CAP_XML_DIR"/.git ] && [ x$(echo $"$LOCAL_CAP_XML_DIR" | grep -E /cap-xml/?$) = "x" ]; then
echo LOCAL_CAP_XML_DIR must be set to the absolute path of the root of a local cap-xml repository
exit 1
fi
@@ -64,7 +64,7 @@ fi
#
# If creating a dev container by cloning the cap-xml repository into a container volume, the dev container user has ownership
# of items in the volume without risk of git reporting dubious ownership.
-if [ `stat -c "%u:%g" $CAP_XML_WORKSPACE_DIR` != $WORKSPACE_FOLDER_HOST_OWNERSHIP ]; then
+if [ $(stat -c "%u:%g" $CAP_XML_WORKSPACE_DIR) != $WORKSPACE_FOLDER_HOST_OWNERSHIP ]; then
chown -R $WORKSPACE_FOLDER_HOST_OWNERSHIP $CAP_XML_WORKSPACE_DIR
echo Changed UID:GID for $CAP_XML_WORKSPACE_DIR to $WORKSPACE_FOLDER_HOST_OWNERSHIP
else
@@ -73,7 +73,7 @@ fi
# Ensure the local cap-xml repository docker directory hierarchy UID:GID is set to HOST_UID:HOST_GID so that
# named Docker volumes can be created.
-if [ `stat -c "%u:%g" $CAP_XML_WORKSPACE_DOCKER_DIR` != $WORKSPACE_DOCKER_FOLDER_HOST_OWNERSHIP ]; then
+if [ $(stat -c "%u:%g" $CAP_XML_WORKSPACE_DOCKER_DIR) != $WORKSPACE_DOCKER_FOLDER_HOST_OWNERSHIP ]; then
chown -R $WORKSPACE_DOCKER_FOLDER_HOST_OWNERSHIP $CAP_XML_WORKSPACE_DOCKER_DIR
echo Changed UID:GID for $CAP_XML_WORKSPACE_DOCKER_DIR to $WORKSPACE_DOCKER_FOLDER_HOST_OWNERSHIP
else
diff --git a/docker/scripts/setup-for-rootless-docker-without-dev-container.sh b/docker/scripts/setup-for-rootless-docker-without-dev-container.sh
index a84f755..9ccd2cc 100755
--- a/docker/scripts/setup-for-rootless-docker-without-dev-container.sh
+++ b/docker/scripts/setup-for-rootless-docker-without-dev-container.sh
@@ -4,7 +4,7 @@
set -e
-if [ `whoami` != root ]; then
+if [ $(whoami) != root ]; then
echo This script must be run as root
exit 1
fi
@@ -13,7 +13,7 @@ HOST_UID=$(id -u "$CAP_XML_HOST_USERNAME")
DOCKER_SOCKET=/var/run/docker.sock
ROOTLESS_DOCKER_SOCKET=/run/user/$HOST_UID/docker.sock
-if [ ! -d "$LOCAL_CAP_XML_DIR"/.git ] && [ x`echo $"$LOCAL_CAP_XML_DIR" | grep -E /cap-xml/?$` = "x" ]; then
+if [ ! -d "$LOCAL_CAP_XML_DIR"/.git ] && [ x$(echo $"$LOCAL_CAP_XML_DIR" | grep -E /cap-xml/?$) = "x" ]; then
echo LOCAL_CAP_XML_DIR must be set to the absolute path of the root of a local cap-xml repository
exit 1
fi
diff --git a/lib/functions/getMessage.js b/lib/functions/getMessage.js
index 2638d41..220e16d 100644
--- a/lib/functions/getMessage.js
+++ b/lib/functions/getMessage.js
@@ -1,45 +1,5 @@
-'use strict'
+const { getMessage } = require('../helpers/message')
-const service = require('../helpers/service')
-const eventSchema = require('../schemas/getMessageEventSchema')
-const { validateXML } = require('xmllint-wasm')
-const fs = require('fs')
-const path = require('path')
-const xsdSchema = fs.readFileSync(path.join(__dirname, '..', 'schemas', 'CAP-v1.2.xsd'), 'utf8')
-
-module.exports.getMessage = async (event) => {
- const { error } = eventSchema.validate(event)
-
- if (error) {
- throw error
- }
-
- const ret = await service.getMessage(event.pathParameters.id)
-
- if (!ret || !ret.rows || !Array.isArray(ret.rows) || ret.rows.length < 1 || !ret.rows[0].getmessage) {
- console.log('No message found for ' + event.pathParameters.id)
- throw new Error('No message found')
- }
-
- const validationResult = await validateXML({
- xml: [{
- fileName: 'message.xml',
- contents: ret.rows[0].getmessage.alert
- }],
- schema: [xsdSchema]
- })
-
- // NI-95 log validation errors and continue processing
- if (validationResult.errors?.length > 0) {
- console.log('CAP get message failed validation')
- console.log(JSON.stringify(validationResult.errors))
- }
-
- return {
- statusCode: 200,
- headers: {
- 'content-type': 'application/xml'
- },
- body: ret.rows[0].getmessage.alert
- }
+module.exports.getMessage = (event) => {
+ return getMessage(event, false)
}
diff --git a/lib/functions/getMessagesAtom.js b/lib/functions/getMessagesAtom.js
index 7c97be0..13f6951 100644
--- a/lib/functions/getMessagesAtom.js
+++ b/lib/functions/getMessagesAtom.js
@@ -1,67 +1,5 @@
-'use strict'
+const { messages } = require('../helpers/messages')
-const service = require('../helpers/service')
-const { validateXML } = require('xmllint-wasm')
-const fs = require('fs')
-const path = require('path')
-const xsdSchema = fs.readFileSync(path.join(__dirname, '..', 'schemas', 'atom.xsd'), 'utf8')
-
-module.exports.getMessagesAtom = async (event) => {
- const { Feed } = await import('feed')
-
- const ret = await service.getAllMessages()
-
- const feed = new Feed({
- title: 'Flood warnings for England',
- generator: 'Environment Agency CAP XML flood warnings',
- description: 'Flood warnings for England',
- id: `${process.env.CPX_AGW_URL}/messages.atom`,
- link: `${process.env.CPX_AGW_URL}/messages.atom`,
- updated: new Date(),
- author: {
- name: 'Environment Agency',
- email: 'enquiries@environment-agency.gov.uk',
- link: 'https://www.gov.uk/government/organisations/environment-agency'
- },
- copyright: 'Copyright, Environment Agency. Licensed under Creative Commons BY 4.0'
- })
-
- if (!!ret && Array.isArray(ret.rows)) {
- ret.rows.forEach((item) => {
- feed.addItem({
- title: item.fwis_code,
- id: `${process.env.CPX_AGW_URL}/message/${item.identifier}`,
- link: `${process.env.CPX_AGW_URL}/message/${item.identifier}`,
- author: {
- name: 'Environment Agency',
- email: 'enquiries@environment-agency.gov.uk',
- link: 'https://www.gov.uk/government/organisations/environment-agency'
- },
- date: item.sent
- })
- })
- }
-
- const xmlFeed = feed.atom1()
-
- const validationResult = await validateXML({
- xml: [{
- fileName: 'atom-feed.xml',
- contents: xmlFeed
- }],
- schema: [xsdSchema]
- })
- // NI-95 log validation errors and continue processing
- if (validationResult.errors?.length > 0) {
- console.log('ATOM feed failed validation')
- console.log(JSON.stringify(validationResult.errors))
- }
-
- return {
- statusCode: 200,
- headers: {
- 'content-type': 'application/xml'
- },
- body: xmlFeed
- }
+module.exports.getMessagesAtom = () => {
+ return messages(false)
}
diff --git a/lib/functions/processMessage.js b/lib/functions/processMessage.js
index 36f305a..37ba1b4 100644
--- a/lib/functions/processMessage.js
+++ b/lib/functions/processMessage.js
@@ -11,6 +11,9 @@ 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 CODE = 'MCP:v2.0'
+const severityV2Mapping = require('../models/v2MessageMapping')
module.exports.processMessage = async (event) => {
try {
@@ -19,7 +22,7 @@ module.exports.processMessage = async (event) => {
// parse the xml
const message = new Message(event.bodyXml)
- console.log(`Processing CAP message: ' + ${message.identifier} for ${message.fwisCode}`)
+ console.log(`Processing CAP message: ${message.identifier} for ${message.fwisCode}`)
// get Last message
const dbResult = await service.getLastMessage(message.fwisCode)
@@ -30,18 +33,21 @@ module.exports.processMessage = async (event) => {
message.status = 'Test'
}
- // Add in the references field and update msgtype to Update if references exist and is Alert
- const references = getReferences(lastMessage, message.sender)
+ // Add in the references field and update msgtype to Update if references exist and is Alert (does this in message model)
+ const references = buildReference(lastMessage, message.sender, 'identifier', 'references')
if (references) {
message.references = references
}
- // do validation
+ // Generate message V2 for meteoalarm spec
+ const messageV2 = processMessageV2(message, lastMessage)
+
+ // do validation against OASIS CAP xml schema and extended JOI schema
const results = await Promise.allSettled([
- // Validate xml against CAP XSD schema https://eaflood.atlassian.net/browse/NI-95
validateAgainstXsdSchema(message),
- // Convert xml to js object for joi extended validation https://eaflood.atlassian.net/browse/NI-113
- validateAgainstJoiSchema(message)
+ validateAgainstJoiSchema(message),
+ validateAgainstXsdSchema(messageV2),
+ validateAgainstJoiSchema(messageV2)
])
// Check for validation failures and throw
@@ -51,7 +57,7 @@ module.exports.processMessage = async (event) => {
}
// store the message in database
- await service.putMessage(message.putQuery())
+ await service.putMessage(message.putQuery(message, messageV2))
console.log(`Finished processing CAP message: ${message.identifier} for ${message.fwisCode}`)
return {
@@ -74,7 +80,7 @@ module.exports.processMessage = async (event) => {
}
const processFailedMessage = async (originalError, xmlResult) => {
- // For backwards compapibility, only send a notification if an AWS SNS topic
+ // For backwards compatibility, only send a notification if an AWS SNS topic
// is configured.
if (process.env.CPX_SNS_TOPIC) {
try {
@@ -98,13 +104,12 @@ const processFailedMessage = async (originalError, xmlResult) => {
}
}
-const getReferences = (lastMessage, sender) => {
+const buildReference = (lastMessage, sender, idField, refField) => {
if (lastMessage && lastMessage.expires > new Date()) {
- const newReference = `${sender},${lastMessage.identifier},${moment(lastMessage.sent).utc().format('YYYY-MM-DDTHH:mm:ssZ')}`
- return lastMessage.references ? `${lastMessage.references} ${newReference}` : newReference
- } else {
- return ''
+ const newReference = `${sender},${lastMessage[idField]},${moment(lastMessage.sent).utc().format('YYYY-MM-DDTHH:mm:ssZ')}`
+ return lastMessage[refField] ? `${lastMessage[refField]} ${newReference}` : newReference
}
+ return ''
}
const validateAgainstXsdSchema = async (message) => {
@@ -131,6 +136,55 @@ const validateAgainstJoiSchema = async (message) => {
const joiValidation = additionalCapMessageSchema.validate(jsMessage, { abortEarly: false })
if (joiValidation.error) {
- throw joiValidation.error.details ?? [joiValidation.error]
+ throw joiValidation.error?.details
+ }
+}
+
+const formatDate = (isoString) => {
+ const date = new Date(isoString)
+ const pad = n => n.toString().padStart(2, '0')
+
+ const YYYY = date.getUTCFullYear()
+ const MM = pad(date.getUTCMonth() + 1)
+ const DD = pad(date.getUTCDate())
+ const HH = pad(date.getUTCHours())
+ const mm = pad(date.getUTCMinutes())
+ const SS = pad(date.getUTCSeconds())
+
+ return `${YYYY}${MM}${DD}${HH}${mm}${SS}`
+}
+
+// Generates a new message based on the Meteoalarm specification https://eaflood.atlassian.net/browse/NI-121
+const processMessageV2 = (message, lastMessage) => {
+ const messageV2 = new Message(message.toString())
+ messageV2.identifier = message.sent && message.identifier ? `${EA_WHO}.${formatDate(message.sent)}.${message.identifier}` : ''
+ messageV2.code = CODE
+ // Add in the references field and update msgtype to Update if references exist and is Alert (does this in message model)
+ const referencesV2 = buildReference(lastMessage, message.sender, 'identifier_v2', 'references_v2')
+ if (referencesV2) {
+ messageV2.references = referencesV2
}
+ messageV2.event = `${severityV2Mapping[message.severity]?.description}: ${messageV2.areaDesc}`
+ messageV2.severity = severityV2Mapping[message.severity]?.severity || ''
+ messageV2.onset = message.sent
+ messageV2.headline = `${severityV2Mapping[message.severity]?.headline}: ${messageV2.areaDesc}`
+
+ let instruction = severityV2Mapping[message.severity]?.instruction
+ if (instruction) {
+ const quickdialSentence = severityV2Mapping[message.severity]?.quickdialSentence
+ const quickdialNumber = messageV2.quickdialNumber
+ // add fwisCode to instruction target area url
+ instruction = instruction.replace('{{ fwisCode }}', messageV2.fwisCode)
+ // if we have a number inject into the sentence, otherwise remove the sentence fully
+ instruction = instruction.replace('{{ quickdialSentence }}', quickdialNumber ? quickdialSentence.replace('{{ quickdialNumber }}', quickdialNumber) : '')
+ messageV2.instruction = instruction
+ }
+
+ messageV2.addParameter('awareness_level', severityV2Mapping[message.severity]?.awarenessLevel || '')
+ messageV2.addParameter('awareness_type', '12; Flooding')
+ messageV2.addParameter('impacts', severityV2Mapping[message.severity]?.impact || '')
+ messageV2.addParameter('use_polygon_over_geocode', 'true')
+ messageV2.addParameter('uk_ea_ta_code', message.fwisCode)
+
+ return messageV2
}
diff --git a/lib/functions/v2/getMessage.js b/lib/functions/v2/getMessage.js
new file mode 100644
index 0000000..e384680
--- /dev/null
+++ b/lib/functions/v2/getMessage.js
@@ -0,0 +1,5 @@
+const { getMessage } = require('../../helpers/message')
+
+module.exports.getMessage = (event) => {
+ return getMessage(event, true)
+}
diff --git a/lib/functions/v2/getMessagesAtom.js b/lib/functions/v2/getMessagesAtom.js
new file mode 100644
index 0000000..6b83a69
--- /dev/null
+++ b/lib/functions/v2/getMessagesAtom.js
@@ -0,0 +1,5 @@
+const { messages } = require('../../helpers/messages')
+
+module.exports.getMessagesAtom = () => {
+ return messages(true)
+}
diff --git a/lib/helpers/message.js b/lib/helpers/message.js
new file mode 100644
index 0000000..9b2fe64
--- /dev/null
+++ b/lib/helpers/message.js
@@ -0,0 +1,47 @@
+'use strict'
+
+const service = require('../helpers/service')
+const eventSchema = require('../schemas/getMessageEventSchema')
+const { validateXML } = require('xmllint-wasm')
+const fs = require('node:fs')
+const path = require('node:path')
+const xsdSchema = fs.readFileSync(path.join(__dirname, '..', 'schemas', 'CAP-v1.2.xsd'), 'utf8')
+
+module.exports.getMessage = async (event, v2) => {
+ const { error } = eventSchema.validate(event)
+
+ if (error) {
+ throw error
+ }
+
+ const ret = await service.getMessage(event.pathParameters.id)
+
+ if (!ret?.rows || !Array.isArray(ret.rows) || ret.rows.length < 1 || !ret.rows[0].getmessage) {
+ console.log('No message found for ' + event.pathParameters.id)
+ throw new Error('No message found')
+ }
+
+ const body = v2 ? ret.rows[0].getmessage.alert_v2 : ret.rows[0].getmessage.alert
+
+ const validationResult = await validateXML({
+ xml: [{
+ fileName: 'message.xml',
+ contents: body
+ }],
+ schema: [xsdSchema]
+ })
+
+ // NI-95 log validation errors and continue processing
+ if (validationResult.errors?.length > 0) {
+ console.log('CAP get message failed validation')
+ console.log(JSON.stringify(validationResult.errors))
+ }
+
+ return {
+ statusCode: 200,
+ headers: {
+ 'content-type': 'application/xml'
+ },
+ body
+ }
+}
diff --git a/lib/helpers/messages.js b/lib/helpers/messages.js
new file mode 100644
index 0000000..a6036f4
--- /dev/null
+++ b/lib/helpers/messages.js
@@ -0,0 +1,66 @@
+'use strict'
+const service = require('./service')
+const { validateXML } = require('xmllint-wasm')
+const fs = require('node:fs')
+const path = require('node:path')
+const xsdSchema = fs.readFileSync(path.join(__dirname, '..', 'schemas', 'atom.xsd'), 'utf8')
+
+module.exports.messages = async (v2 = false) => {
+ const { Feed } = await import('feed')
+ const ret = await service.getAllMessages()
+ const uriPrefix = v2 ? '/v2' : ''
+
+ const feed = new Feed({
+ title: 'Flood warnings for England',
+ generator: 'Environment Agency CAP XML flood warnings',
+ description: 'Flood warnings for England',
+ id: `${process.env.CPX_AGW_URL}${uriPrefix}/messages.atom`,
+ link: `${process.env.CPX_AGW_URL}${uriPrefix}/messages.atom`,
+ updated: new Date(),
+ author: {
+ name: 'Environment Agency',
+ email: 'enquiries@environment-agency.gov.uk',
+ link: 'https://www.gov.uk/government/organisations/environment-agency'
+ },
+ copyright: 'Copyright, Environment Agency. Licensed under Creative Commons BY 4.0'
+ })
+
+ if (!!ret && Array.isArray(ret.rows)) {
+ for (const item of ret.rows) {
+ feed.addItem({
+ title: item.fwis_code,
+ id: `${process.env.CPX_AGW_URL}${uriPrefix}/message/${item.identifier}`,
+ link: `${process.env.CPX_AGW_URL}${uriPrefix}/message/${item.identifier}`,
+ author: {
+ name: 'Environment Agency',
+ email: 'enquiries@environment-agency.gov.uk',
+ link: 'https://www.gov.uk/government/organisations/environment-agency'
+ },
+ date: item.sent
+ })
+ }
+ }
+
+ const xmlFeed = feed.atom1()
+
+ const validationResult = await validateXML({
+ xml: [{
+ fileName: 'atom-feed.xml',
+ contents: xmlFeed
+ }],
+ schema: [xsdSchema]
+ })
+ // NI-95 log validation errors and continue processing
+ if (validationResult.errors?.length > 0) {
+ console.log('ATOM feed failed validation')
+ console.log(JSON.stringify(validationResult.errors))
+ }
+
+ return {
+ statusCode: 200,
+ headers: {
+ 'content-type': 'application/xml'
+ },
+ body: xmlFeed
+ }
+}
diff --git a/lib/models/message.js b/lib/models/message.js
index 2370b44..5408909 100644
--- a/lib/models/message.js
+++ b/lib/models/message.js
@@ -4,7 +4,7 @@ const { Sql } = require('sql-ts')
const sql = new Sql('postgres')
const messages = sql.define({
name: 'messages',
- columns: ['identifier', 'msg_type', 'references', 'alert', 'fwis_code', 'expires', 'sent', 'created']
+ columns: ['identifier', 'msg_type', 'references', 'alert', 'fwis_code', 'expires', 'sent', 'created', 'identifier_v2', 'references_v2', 'alert_v2']
})
class Message {
@@ -13,19 +13,23 @@ class Message {
}
get fwisCode () {
- return this.getFirstElement('geocode').getElementsByTagName('value')[0].textContent
+ return this.getFirstElement('geocode')?.getElementsByTagName('value')[0].textContent || ''
}
get identifier () {
- return this.getFirstElement('identifier').textContent
+ return this.getFirstElement('identifier')?.textContent || ''
+ }
+
+ set identifier (value) {
+ this.getFirstElement('identifier').textContent = value
}
get sender () {
- return this.getFirstElement('sender').textContent
+ return this.getFirstElement('sender')?.textContent || ''
}
get msgType () {
- return this.getFirstElement('msgType').textContent
+ return this.getFirstElement('msgType')?.textContent || ''
}
set msgType (value) {
@@ -33,24 +37,23 @@ class Message {
}
get references () {
- return this.getFirstElement('references') ? this.getFirstElement('references').textContent : ''
+ return this.getFirstElement('references')?.textContent || ''
}
set references (value) {
- if (value) {
- if (this.references) {
- this.getFirstElement('references').textContent = value
- } else {
- this.addElement('scope', 'references', value)
- }
- if (this.msgType === 'Alert') {
- this.msgType = 'Update'
- }
+ const referencesEl = this.getFirstElement('references')
+ if (referencesEl) {
+ referencesEl.textContent = value
+ } else {
+ this.addElement('scope', 'references', value)
+ }
+ if (this.msgType === 'Alert') {
+ this.msgType = 'Update'
}
}
get status () {
- return this.getFirstElement('status').textContent
+ return this.getFirstElement('status')?.textContent || ''
}
set status (value) {
@@ -58,38 +61,139 @@ class Message {
}
get expires () {
- return this.getFirstElement('expires').textContent
+ return this.getFirstElement('expires')?.textContent || ''
}
get sent () {
- return this.getFirstElement('sent').textContent
+ return this.getFirstElement('sent')?.textContent || ''
+ }
+
+ get code () {
+ return this.getFirstElement('code')?.textContent || ''
+ }
+
+ set code (value) {
+ const codeEl = this.getFirstElement('code')
+ if (codeEl) {
+ codeEl.textContent = value
+ } else {
+ this.addElement('scope', 'code', value)
+ }
+ }
+
+ get event () {
+ return this.getFirstElement('event')?.textContent || ''
+ }
+
+ set event (value) {
+ this.getFirstElement('event').textContent = value
+ }
+
+ get severity () {
+ return this.getFirstElement('severity')?.textContent || ''
+ }
+
+ set severity (value) {
+ this.getFirstElement('severity').textContent = value
+ }
+
+ get onset () {
+ return this.getFirstElement('onset')?.textContent || ''
+ }
+
+ set onset (value) {
+ const onsetEl = this.getFirstElement('onset')
+ if (onsetEl) {
+ onsetEl.textContent = value
+ } else {
+ this.addElement('certainty', 'onset', value)
+ }
+ }
+
+ get headline () {
+ return this.getFirstElement('headline')?.textContent || ''
+ }
+
+ set headline (value) {
+ const headlineEl = this.getFirstElement('headline')
+ if (headlineEl) {
+ headlineEl.textContent = value
+ } else {
+ this.addElement('senderName', 'headline', value)
+ }
+ }
+
+ get areaDesc () {
+ return this.getFirstElement('areaDesc')?.textContent || ''
+ }
+
+ get quickdialNumber () {
+ return this.getFirstElement('instruction')?.textContent.match(/quickdial code:\s*(\d{6})\./i)?.[1] || ''
+ }
+
+ get instruction () {
+ return this.getFirstElement('instruction')?.textContent || ''
+ }
+
+ set instruction (value) {
+ const instruction = this.doc.getElementsByTagName('instruction')[0]
+ const newCData = this.doc.createCDATASection(value)
+ if (instruction) {
+ while (instruction.firstChild) {
+ instruction.removeChild(instruction.firstChild)
+ }
+ instruction.appendChild(newCData)
+ } else {
+ this.addElement('description', 'instruction', '').appendChild(newCData)
+ }
}
getFirstElement (tagName) {
return this.doc.getElementsByTagName(tagName)[0]
}
- addElement (parentTag, elTag, elValue) {
- const parentEl = this.doc.getElementsByTagName(parentTag)[0]
+ addElement (afterTag, elTag, elValue) {
+ const afterTagEl = this.doc.getElementsByTagName(afterTag)[0]
const newEl = this.doc.createElement(elTag)
newEl.textContent = elValue
- return parentEl.parentNode.insertBefore(newEl, parentEl.nextSibling)
+ return afterTagEl.parentNode.insertBefore(newEl, afterTagEl.nextSibling)
+ }
+
+ addParameter (name, value) {
+ const infoEl = this.doc.getElementsByTagName('info')[0]
+ const areaEl = infoEl.getElementsByTagName('area')[0]
+ const parameterEl = this.doc.createElement('parameter')
+ const valueNameEl = this.doc.createElement('valueName')
+ const valueEl = this.doc.createElement('value')
+ valueNameEl.textContent = name
+ valueEl.textContent = value
+ parameterEl.appendChild(valueNameEl)
+ parameterEl.appendChild(valueEl)
+ if (areaEl) {
+ return infoEl.insertBefore(parameterEl, areaEl)
+ } else {
+ return infoEl.appendChild(parameterEl)
+ }
}
toString () {
return xmlFormat(new xmldom.XMLSerializer().serializeToString(this.doc), { indentation: ' ', collapseContent: true })
}
- putQuery () {
+ // Handles multiple message versions to create the single database record
+ putQuery (messageV1, messageV2) {
const message = {
- identifier: this.identifier,
- msg_type: this.msgType,
- references: this.references,
- alert: this.toString(),
- fwis_code: this.fwisCode,
- expires: this.expires,
- sent: this.sent,
- created: new Date().toISOString()
+ identifier: messageV1.identifier,
+ msg_type: messageV1.msgType,
+ references: messageV1.references,
+ alert: messageV1.toString(),
+ fwis_code: messageV1.fwisCode,
+ expires: messageV1.expires,
+ sent: messageV1.sent,
+ created: new Date().toISOString(),
+ identifier_v2: messageV2.identifier,
+ references_v2: messageV2.references,
+ alert_v2: messageV2.toString()
}
return messages.insert(message).toQuery()
}
diff --git a/lib/models/v2MessageMapping.js b/lib/models/v2MessageMapping.js
new file mode 100644
index 0000000..983cc41
--- /dev/null
+++ b/lib/models/v2MessageMapping.js
@@ -0,0 +1,84 @@
+const quickdialSentence = '- call Floodline on 0345 988 1188, using quickdial code {{ quickdialNumber }}'
+
+const extreme = {
+ severity: 'Extreme',
+ description: 'Severe Flood Warning',
+ headline: 'Danger to life',
+ impact: 'Danger to life - act now',
+ instruction: `Act now - danger to life
+
+You should:
+
+- call 999 if you are in immediate danger
+
+- go to Check for flooding for a map of the area and to monitor up-to-date local flood information – https://check-for-flooding.service.gov.uk/target-area/{{ fwisCode }}
+- act on your personal flood plan if you have one - https://www.gov.uk/government/publications/personal-flood-plan
+- follow the guidance in 'What to do before or during a flood' - https://www.gov.uk/help-during-flood
+
+You can also read more about what severe flood warnings are – [https://www.gov.uk/guidance/flood-alerts-and-warnings-what-they-are-and-what-to-do#severe-flood-warning]
+
+Stay up to date
+
+To get the latest flood information, you can:
+
+- go to Check for flooding
+- monitor local weather, news and travel updates
+{{ quickdialSentence }}`,
+ awarenessLevel: '4; red; Extreme',
+ quickdialSentence
+}
+
+module.exports = {
+ Minor: {
+ severity: 'Minor',
+ description: 'Flood Alert',
+ headline: 'Flooding is possible',
+ impact: 'Flooding is possible - be prepared',
+ instruction: `Be prepared
+
+You should:
+
+- go to Check for flooding for a map of the area and to monitor up-to-date local flood information – https://check-for-flooding.service.gov.uk/target-area/{{ fwisCode }}
+- get ready to act on your personal flood plan if you have one - https://www.gov.uk/government/publications/personal-flood-plan
+- follow the guidance in 'What to do before or during a flood' - https://www.gov.uk/help-during-flood
+
+You can also read more about what flood alerts are – [https://www.gov.uk/guidance/flood-alerts-and-warnings-what-they-are-and-what-to-do#flood-alert]
+
+Stay up to date
+
+To get the latest flood information, you can:
+
+- go to Check for flooding
+- monitor local weather, news and travel updates
+{{ quickdialSentence }}`,
+ awarenessLevel: '1; green; Minor',
+ quickdialSentence
+ },
+ Moderate: {
+ severity: 'Severe',
+ description: 'Flood Warning',
+ headline: 'Flooding is expected',
+ impact: 'Flooding is expected - act now',
+ instruction: `Act now
+
+You should:
+
+- go to Check for flooding for a map of the area and to monitor up-to-date local flood information – https://check-for-flooding.service.gov.uk/target-area/{{ fwisCode }}
+- act on your personal flood plan if you have one - https://www.gov.uk/government/publications/personal-flood-plan
+- follow the guidance in 'What to do before or during a flood' - https://www.gov.uk/help-during-flood
+
+You can also read more about what flood warnings are – [https://www.gov.uk/guidance/flood-alerts-and-warnings-what-they-are-and-what-to-do#flood-warning]
+
+Stay up to date
+
+To get the latest flood information, you can:
+
+- go to Check for flooding
+- monitor local weather, news and travel updates
+{{ quickdialSentence }}`,
+ awarenessLevel: '3; orange; Severe',
+ quickdialSentence
+ },
+ Severe: extreme,
+ Extreme: extreme
+}
diff --git a/lib/schemas/additionalCapMessageSchema.js b/lib/schemas/additionalCapMessageSchema.js
index 77ffef3..31c9bda 100644
--- a/lib/schemas/additionalCapMessageSchema.js
+++ b/lib/schemas/additionalCapMessageSchema.js
@@ -9,6 +9,7 @@ const areaSchema = Joi.object({
const infoSchema = Joi.object({
event: Joi.array().items(Joi.string().min(1)).max(1).required(),
+ severity: Joi.array().items(Joi.string().valid('Extreme', 'Severe', 'Moderate', 'Minor')).max(1).required(),
senderName: Joi.array().items(Joi.string().min(1)).max(1).required(),
area: Joi.array().items(areaSchema)
}).unknown(true)
diff --git a/package-lock.json b/package-lock.json
index 56552d8..441dd30 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -6,30 +6,30 @@
"packages": {
"": {
"name": "cap-xml",
- "version": "2.1.0",
+ "version": "3.0.0",
"license": "OGL",
"dependencies": {
- "@aws-sdk/client-sns": "^3.873.0",
- "@xmldom/xmldom": "^0.8.11",
+ "@aws-sdk/client-sns": "3.932.0",
+ "@xmldom/xmldom": "0.8.11",
"feed": "5.1.0",
- "joi": "^18.0.1",
- "moment": "^2.30.1",
+ "joi": "18.0.1",
+ "moment": "2.30.1",
"pg": "8.16.3",
"sql-ts": "7.1.0",
- "xml-formatter": "^3.6.7",
+ "xml-formatter": "3.6.7",
"xml2js": "0.6.2",
- "xmllint-wasm": "^5.0.0"
+ "xmllint-wasm": "5.1.0"
},
"devDependencies": {
- "@hapi/code": "^9.0.3",
- "@hapi/lab": "^26.0.0",
- "aws-sdk-client-mock": "^4.1.0",
- "proxyquire": "^2.1.3",
- "sinon": "^21.0.0",
+ "@hapi/code": "9.0.3",
+ "@hapi/lab": "26.0.0",
+ "aws-sdk-client-mock": "4.1.0",
+ "proxyquire": "2.1.3",
+ "sinon": "21.0.0",
"standard": "17.1.2"
},
"engines": {
- "node": ">=20"
+ "node": "22.x"
}
},
"node_modules/@ampproject/remapping": {
@@ -172,49 +172,49 @@
}
},
"node_modules/@aws-sdk/client-sns": {
- "version": "3.873.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/client-sns/-/client-sns-3.873.0.tgz",
- "integrity": "sha512-NuAkmtMozX1I9biFNfyGazm91lbfbmZfF43SW32lJLmEyAV/1acn2MubTh91SjmnLGqxfgc9jrplX9b/M8Mnyw==",
+ "version": "3.932.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/client-sns/-/client-sns-3.932.0.tgz",
+ "integrity": "sha512-xkNxxViG9YOsm4DUYM8wQ8KnkAgc9yktVijbFKhIItEdlyaXl4A4sHATsOzZfhuqz/JGZnVF7gUGfBdntOtukA==",
"license": "Apache-2.0",
"dependencies": {
"@aws-crypto/sha256-browser": "5.2.0",
"@aws-crypto/sha256-js": "5.2.0",
- "@aws-sdk/core": "3.873.0",
- "@aws-sdk/credential-provider-node": "3.873.0",
- "@aws-sdk/middleware-host-header": "3.873.0",
- "@aws-sdk/middleware-logger": "3.873.0",
- "@aws-sdk/middleware-recursion-detection": "3.873.0",
- "@aws-sdk/middleware-user-agent": "3.873.0",
- "@aws-sdk/region-config-resolver": "3.873.0",
- "@aws-sdk/types": "3.862.0",
- "@aws-sdk/util-endpoints": "3.873.0",
- "@aws-sdk/util-user-agent-browser": "3.873.0",
- "@aws-sdk/util-user-agent-node": "3.873.0",
- "@smithy/config-resolver": "^4.1.5",
- "@smithy/core": "^3.8.0",
- "@smithy/fetch-http-handler": "^5.1.1",
- "@smithy/hash-node": "^4.0.5",
- "@smithy/invalid-dependency": "^4.0.5",
- "@smithy/middleware-content-length": "^4.0.5",
- "@smithy/middleware-endpoint": "^4.1.18",
- "@smithy/middleware-retry": "^4.1.19",
- "@smithy/middleware-serde": "^4.0.9",
- "@smithy/middleware-stack": "^4.0.5",
- "@smithy/node-config-provider": "^4.1.4",
- "@smithy/node-http-handler": "^4.1.1",
- "@smithy/protocol-http": "^5.1.3",
- "@smithy/smithy-client": "^4.4.10",
- "@smithy/types": "^4.3.2",
- "@smithy/url-parser": "^4.0.5",
- "@smithy/util-base64": "^4.0.0",
- "@smithy/util-body-length-browser": "^4.0.0",
- "@smithy/util-body-length-node": "^4.0.0",
- "@smithy/util-defaults-mode-browser": "^4.0.26",
- "@smithy/util-defaults-mode-node": "^4.0.26",
- "@smithy/util-endpoints": "^3.0.7",
- "@smithy/util-middleware": "^4.0.5",
- "@smithy/util-retry": "^4.0.7",
- "@smithy/util-utf8": "^4.0.0",
+ "@aws-sdk/core": "3.932.0",
+ "@aws-sdk/credential-provider-node": "3.932.0",
+ "@aws-sdk/middleware-host-header": "3.930.0",
+ "@aws-sdk/middleware-logger": "3.930.0",
+ "@aws-sdk/middleware-recursion-detection": "3.930.0",
+ "@aws-sdk/middleware-user-agent": "3.932.0",
+ "@aws-sdk/region-config-resolver": "3.930.0",
+ "@aws-sdk/types": "3.930.0",
+ "@aws-sdk/util-endpoints": "3.930.0",
+ "@aws-sdk/util-user-agent-browser": "3.930.0",
+ "@aws-sdk/util-user-agent-node": "3.932.0",
+ "@smithy/config-resolver": "^4.4.3",
+ "@smithy/core": "^3.18.2",
+ "@smithy/fetch-http-handler": "^5.3.6",
+ "@smithy/hash-node": "^4.2.5",
+ "@smithy/invalid-dependency": "^4.2.5",
+ "@smithy/middleware-content-length": "^4.2.5",
+ "@smithy/middleware-endpoint": "^4.3.9",
+ "@smithy/middleware-retry": "^4.4.9",
+ "@smithy/middleware-serde": "^4.2.5",
+ "@smithy/middleware-stack": "^4.2.5",
+ "@smithy/node-config-provider": "^4.3.5",
+ "@smithy/node-http-handler": "^4.4.5",
+ "@smithy/protocol-http": "^5.3.5",
+ "@smithy/smithy-client": "^4.9.5",
+ "@smithy/types": "^4.9.0",
+ "@smithy/url-parser": "^4.2.5",
+ "@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.8",
+ "@smithy/util-defaults-mode-node": "^4.2.11",
+ "@smithy/util-endpoints": "^3.2.5",
+ "@smithy/util-middleware": "^4.2.5",
+ "@smithy/util-retry": "^4.2.5",
+ "@smithy/util-utf8": "^4.2.0",
"tslib": "^2.6.2"
},
"engines": {
@@ -222,48 +222,48 @@
}
},
"node_modules/@aws-sdk/client-sso": {
- "version": "3.873.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.873.0.tgz",
- "integrity": "sha512-EmcrOgFODWe7IsLKFTeSXM9TlQ80/BO1MBISlr7w2ydnOaUYIiPGRRJnDpeIgMaNqT4Rr2cRN2RiMrbFO7gDdA==",
+ "version": "3.932.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.932.0.tgz",
+ "integrity": "sha512-XHqHa5iv2OQsKoM2tUQXs7EAyryploC00Wg0XSFra/KAKqyGizUb5XxXsGlyqhebB29Wqur+zwiRwNmejmN0+Q==",
"license": "Apache-2.0",
"dependencies": {
"@aws-crypto/sha256-browser": "5.2.0",
"@aws-crypto/sha256-js": "5.2.0",
- "@aws-sdk/core": "3.873.0",
- "@aws-sdk/middleware-host-header": "3.873.0",
- "@aws-sdk/middleware-logger": "3.873.0",
- "@aws-sdk/middleware-recursion-detection": "3.873.0",
- "@aws-sdk/middleware-user-agent": "3.873.0",
- "@aws-sdk/region-config-resolver": "3.873.0",
- "@aws-sdk/types": "3.862.0",
- "@aws-sdk/util-endpoints": "3.873.0",
- "@aws-sdk/util-user-agent-browser": "3.873.0",
- "@aws-sdk/util-user-agent-node": "3.873.0",
- "@smithy/config-resolver": "^4.1.5",
- "@smithy/core": "^3.8.0",
- "@smithy/fetch-http-handler": "^5.1.1",
- "@smithy/hash-node": "^4.0.5",
- "@smithy/invalid-dependency": "^4.0.5",
- "@smithy/middleware-content-length": "^4.0.5",
- "@smithy/middleware-endpoint": "^4.1.18",
- "@smithy/middleware-retry": "^4.1.19",
- "@smithy/middleware-serde": "^4.0.9",
- "@smithy/middleware-stack": "^4.0.5",
- "@smithy/node-config-provider": "^4.1.4",
- "@smithy/node-http-handler": "^4.1.1",
- "@smithy/protocol-http": "^5.1.3",
- "@smithy/smithy-client": "^4.4.10",
- "@smithy/types": "^4.3.2",
- "@smithy/url-parser": "^4.0.5",
- "@smithy/util-base64": "^4.0.0",
- "@smithy/util-body-length-browser": "^4.0.0",
- "@smithy/util-body-length-node": "^4.0.0",
- "@smithy/util-defaults-mode-browser": "^4.0.26",
- "@smithy/util-defaults-mode-node": "^4.0.26",
- "@smithy/util-endpoints": "^3.0.7",
- "@smithy/util-middleware": "^4.0.5",
- "@smithy/util-retry": "^4.0.7",
- "@smithy/util-utf8": "^4.0.0",
+ "@aws-sdk/core": "3.932.0",
+ "@aws-sdk/middleware-host-header": "3.930.0",
+ "@aws-sdk/middleware-logger": "3.930.0",
+ "@aws-sdk/middleware-recursion-detection": "3.930.0",
+ "@aws-sdk/middleware-user-agent": "3.932.0",
+ "@aws-sdk/region-config-resolver": "3.930.0",
+ "@aws-sdk/types": "3.930.0",
+ "@aws-sdk/util-endpoints": "3.930.0",
+ "@aws-sdk/util-user-agent-browser": "3.930.0",
+ "@aws-sdk/util-user-agent-node": "3.932.0",
+ "@smithy/config-resolver": "^4.4.3",
+ "@smithy/core": "^3.18.2",
+ "@smithy/fetch-http-handler": "^5.3.6",
+ "@smithy/hash-node": "^4.2.5",
+ "@smithy/invalid-dependency": "^4.2.5",
+ "@smithy/middleware-content-length": "^4.2.5",
+ "@smithy/middleware-endpoint": "^4.3.9",
+ "@smithy/middleware-retry": "^4.4.9",
+ "@smithy/middleware-serde": "^4.2.5",
+ "@smithy/middleware-stack": "^4.2.5",
+ "@smithy/node-config-provider": "^4.3.5",
+ "@smithy/node-http-handler": "^4.4.5",
+ "@smithy/protocol-http": "^5.3.5",
+ "@smithy/smithy-client": "^4.9.5",
+ "@smithy/types": "^4.9.0",
+ "@smithy/url-parser": "^4.2.5",
+ "@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.8",
+ "@smithy/util-defaults-mode-node": "^4.2.11",
+ "@smithy/util-endpoints": "^3.2.5",
+ "@smithy/util-middleware": "^4.2.5",
+ "@smithy/util-retry": "^4.2.5",
+ "@smithy/util-utf8": "^4.2.0",
"tslib": "^2.6.2"
},
"engines": {
@@ -271,25 +271,23 @@
}
},
"node_modules/@aws-sdk/core": {
- "version": "3.873.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.873.0.tgz",
- "integrity": "sha512-WrROjp8X1VvmnZ4TBzwM7RF+EB3wRaY9kQJLXw+Aes0/3zRjUXvGIlseobGJMqMEGnM0YekD2F87UaVfot1xeQ==",
+ "version": "3.932.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.932.0.tgz",
+ "integrity": "sha512-AS8gypYQCbNojwgjvZGkJocC2CoEICDx9ZJ15ILsv+MlcCVLtUJSRSx3VzJOUY2EEIaGLRrPNlIqyn/9/fySvA==",
"license": "Apache-2.0",
"dependencies": {
- "@aws-sdk/types": "3.862.0",
- "@aws-sdk/xml-builder": "3.873.0",
- "@smithy/core": "^3.8.0",
- "@smithy/node-config-provider": "^4.1.4",
- "@smithy/property-provider": "^4.0.5",
- "@smithy/protocol-http": "^5.1.3",
- "@smithy/signature-v4": "^5.1.3",
- "@smithy/smithy-client": "^4.4.10",
- "@smithy/types": "^4.3.2",
- "@smithy/util-base64": "^4.0.0",
- "@smithy/util-body-length-browser": "^4.0.0",
- "@smithy/util-middleware": "^4.0.5",
- "@smithy/util-utf8": "^4.0.0",
- "fast-xml-parser": "5.2.5",
+ "@aws-sdk/types": "3.930.0",
+ "@aws-sdk/xml-builder": "3.930.0",
+ "@smithy/core": "^3.18.2",
+ "@smithy/node-config-provider": "^4.3.5",
+ "@smithy/property-provider": "^4.2.5",
+ "@smithy/protocol-http": "^5.3.5",
+ "@smithy/signature-v4": "^5.3.5",
+ "@smithy/smithy-client": "^4.9.5",
+ "@smithy/types": "^4.9.0",
+ "@smithy/util-base64": "^4.3.0",
+ "@smithy/util-middleware": "^4.2.5",
+ "@smithy/util-utf8": "^4.2.0",
"tslib": "^2.6.2"
},
"engines": {
@@ -297,15 +295,15 @@
}
},
"node_modules/@aws-sdk/credential-provider-env": {
- "version": "3.873.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.873.0.tgz",
- "integrity": "sha512-FWj1yUs45VjCADv80JlGshAttUHBL2xtTAbJcAxkkJZzLRKVkdyrepFWhv/95MvDyzfbT6PgJiWMdW65l/8ooA==",
+ "version": "3.932.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.932.0.tgz",
+ "integrity": "sha512-ozge/c7NdHUDyHqro6+P5oHt8wfKSUBN+olttiVfBe9Mw3wBMpPa3gQ0pZnG+gwBkKskBuip2bMR16tqYvUSEA==",
"license": "Apache-2.0",
"dependencies": {
- "@aws-sdk/core": "3.873.0",
- "@aws-sdk/types": "3.862.0",
- "@smithy/property-provider": "^4.0.5",
- "@smithy/types": "^4.3.2",
+ "@aws-sdk/core": "3.932.0",
+ "@aws-sdk/types": "3.930.0",
+ "@smithy/property-provider": "^4.2.5",
+ "@smithy/types": "^4.9.0",
"tslib": "^2.6.2"
},
"engines": {
@@ -313,20 +311,20 @@
}
},
"node_modules/@aws-sdk/credential-provider-http": {
- "version": "3.873.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.873.0.tgz",
- "integrity": "sha512-0sIokBlXIsndjZFUfr3Xui8W6kPC4DAeBGAXxGi9qbFZ9PWJjn1vt2COLikKH3q2snchk+AsznREZG8NW6ezSg==",
+ "version": "3.932.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.932.0.tgz",
+ "integrity": "sha512-b6N9Nnlg8JInQwzBkUq5spNaXssM3h3zLxGzpPrnw0nHSIWPJPTbZzA5Ca285fcDUFuKP+qf3qkuqlAjGOdWhg==",
"license": "Apache-2.0",
"dependencies": {
- "@aws-sdk/core": "3.873.0",
- "@aws-sdk/types": "3.862.0",
- "@smithy/fetch-http-handler": "^5.1.1",
- "@smithy/node-http-handler": "^4.1.1",
- "@smithy/property-provider": "^4.0.5",
- "@smithy/protocol-http": "^5.1.3",
- "@smithy/smithy-client": "^4.4.10",
- "@smithy/types": "^4.3.2",
- "@smithy/util-stream": "^4.2.4",
+ "@aws-sdk/core": "3.932.0",
+ "@aws-sdk/types": "3.930.0",
+ "@smithy/fetch-http-handler": "^5.3.6",
+ "@smithy/node-http-handler": "^4.4.5",
+ "@smithy/property-provider": "^4.2.5",
+ "@smithy/protocol-http": "^5.3.5",
+ "@smithy/smithy-client": "^4.9.5",
+ "@smithy/types": "^4.9.0",
+ "@smithy/util-stream": "^4.5.6",
"tslib": "^2.6.2"
},
"engines": {
@@ -334,23 +332,23 @@
}
},
"node_modules/@aws-sdk/credential-provider-ini": {
- "version": "3.873.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.873.0.tgz",
- "integrity": "sha512-bQdGqh47Sk0+2S3C+N46aNQsZFzcHs7ndxYLARH/avYXf02Nl68p194eYFaAHJSQ1re5IbExU1+pbums7FJ9fA==",
+ "version": "3.932.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.932.0.tgz",
+ "integrity": "sha512-ZBjSAXVGy7danZRHCRMJQ7sBkG1Dz39thYlvTiUaf9BKZ+8ymeiFhuTeV1OkWUBBnY0ki2dVZJvboTqfINhNxA==",
"license": "Apache-2.0",
"dependencies": {
- "@aws-sdk/core": "3.873.0",
- "@aws-sdk/credential-provider-env": "3.873.0",
- "@aws-sdk/credential-provider-http": "3.873.0",
- "@aws-sdk/credential-provider-process": "3.873.0",
- "@aws-sdk/credential-provider-sso": "3.873.0",
- "@aws-sdk/credential-provider-web-identity": "3.873.0",
- "@aws-sdk/nested-clients": "3.873.0",
- "@aws-sdk/types": "3.862.0",
- "@smithy/credential-provider-imds": "^4.0.7",
- "@smithy/property-provider": "^4.0.5",
- "@smithy/shared-ini-file-loader": "^4.0.5",
- "@smithy/types": "^4.3.2",
+ "@aws-sdk/core": "3.932.0",
+ "@aws-sdk/credential-provider-env": "3.932.0",
+ "@aws-sdk/credential-provider-http": "3.932.0",
+ "@aws-sdk/credential-provider-process": "3.932.0",
+ "@aws-sdk/credential-provider-sso": "3.932.0",
+ "@aws-sdk/credential-provider-web-identity": "3.932.0",
+ "@aws-sdk/nested-clients": "3.932.0",
+ "@aws-sdk/types": "3.930.0",
+ "@smithy/credential-provider-imds": "^4.2.5",
+ "@smithy/property-provider": "^4.2.5",
+ "@smithy/shared-ini-file-loader": "^4.4.0",
+ "@smithy/types": "^4.9.0",
"tslib": "^2.6.2"
},
"engines": {
@@ -358,22 +356,22 @@
}
},
"node_modules/@aws-sdk/credential-provider-node": {
- "version": "3.873.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.873.0.tgz",
- "integrity": "sha512-+v/xBEB02k2ExnSDL8+1gD6UizY4Q/HaIJkNSkitFynRiiTQpVOSkCkA0iWxzksMeN8k1IHTE5gzeWpkEjNwbA==",
+ "version": "3.932.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.932.0.tgz",
+ "integrity": "sha512-SEG9t2taBT86qe3gTunfrK8BxT710GVLGepvHr+X5Pw+qW225iNRaGN0zJH+ZE/j91tcW9wOaIoWnURkhR5wIg==",
"license": "Apache-2.0",
"dependencies": {
- "@aws-sdk/credential-provider-env": "3.873.0",
- "@aws-sdk/credential-provider-http": "3.873.0",
- "@aws-sdk/credential-provider-ini": "3.873.0",
- "@aws-sdk/credential-provider-process": "3.873.0",
- "@aws-sdk/credential-provider-sso": "3.873.0",
- "@aws-sdk/credential-provider-web-identity": "3.873.0",
- "@aws-sdk/types": "3.862.0",
- "@smithy/credential-provider-imds": "^4.0.7",
- "@smithy/property-provider": "^4.0.5",
- "@smithy/shared-ini-file-loader": "^4.0.5",
- "@smithy/types": "^4.3.2",
+ "@aws-sdk/credential-provider-env": "3.932.0",
+ "@aws-sdk/credential-provider-http": "3.932.0",
+ "@aws-sdk/credential-provider-ini": "3.932.0",
+ "@aws-sdk/credential-provider-process": "3.932.0",
+ "@aws-sdk/credential-provider-sso": "3.932.0",
+ "@aws-sdk/credential-provider-web-identity": "3.932.0",
+ "@aws-sdk/types": "3.930.0",
+ "@smithy/credential-provider-imds": "^4.2.5",
+ "@smithy/property-provider": "^4.2.5",
+ "@smithy/shared-ini-file-loader": "^4.4.0",
+ "@smithy/types": "^4.9.0",
"tslib": "^2.6.2"
},
"engines": {
@@ -381,16 +379,16 @@
}
},
"node_modules/@aws-sdk/credential-provider-process": {
- "version": "3.873.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.873.0.tgz",
- "integrity": "sha512-ycFv9WN+UJF7bK/ElBq1ugWA4NMbYS//1K55bPQZb2XUpAM2TWFlEjG7DIyOhLNTdl6+CbHlCdhlKQuDGgmm0A==",
+ "version": "3.932.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.932.0.tgz",
+ "integrity": "sha512-BodZYKvT4p/Dkm28Ql/FhDdS1+p51bcZeMMu2TRtU8PoMDHnVDhHz27zASEKSZwmhvquxHrZHB0IGuVqjZUtSQ==",
"license": "Apache-2.0",
"dependencies": {
- "@aws-sdk/core": "3.873.0",
- "@aws-sdk/types": "3.862.0",
- "@smithy/property-provider": "^4.0.5",
- "@smithy/shared-ini-file-loader": "^4.0.5",
- "@smithy/types": "^4.3.2",
+ "@aws-sdk/core": "3.932.0",
+ "@aws-sdk/types": "3.930.0",
+ "@smithy/property-provider": "^4.2.5",
+ "@smithy/shared-ini-file-loader": "^4.4.0",
+ "@smithy/types": "^4.9.0",
"tslib": "^2.6.2"
},
"engines": {
@@ -398,18 +396,18 @@
}
},
"node_modules/@aws-sdk/credential-provider-sso": {
- "version": "3.873.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.873.0.tgz",
- "integrity": "sha512-SudkAOZmjEEYgUrqlUUjvrtbWJeI54/0Xo87KRxm4kfBtMqSx0TxbplNUAk8Gkg4XQNY0o7jpG8tK7r2Wc2+uw==",
+ "version": "3.932.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.932.0.tgz",
+ "integrity": "sha512-XYmkv+ltBjjmPZ6AmR1ZQZkQfD0uzG61M18/Lif3HAGxyg3dmod0aWx9aL6lj9SvxAGqzscrx5j4PkgLqjZruw==",
"license": "Apache-2.0",
"dependencies": {
- "@aws-sdk/client-sso": "3.873.0",
- "@aws-sdk/core": "3.873.0",
- "@aws-sdk/token-providers": "3.873.0",
- "@aws-sdk/types": "3.862.0",
- "@smithy/property-provider": "^4.0.5",
- "@smithy/shared-ini-file-loader": "^4.0.5",
- "@smithy/types": "^4.3.2",
+ "@aws-sdk/client-sso": "3.932.0",
+ "@aws-sdk/core": "3.932.0",
+ "@aws-sdk/token-providers": "3.932.0",
+ "@aws-sdk/types": "3.930.0",
+ "@smithy/property-provider": "^4.2.5",
+ "@smithy/shared-ini-file-loader": "^4.4.0",
+ "@smithy/types": "^4.9.0",
"tslib": "^2.6.2"
},
"engines": {
@@ -417,16 +415,17 @@
}
},
"node_modules/@aws-sdk/credential-provider-web-identity": {
- "version": "3.873.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.873.0.tgz",
- "integrity": "sha512-Gw2H21+VkA6AgwKkBtTtlGZ45qgyRZPSKWs0kUwXVlmGOiPz61t/lBX0vG6I06ZIz2wqeTJ5OA1pWZLqw1j0JQ==",
+ "version": "3.932.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.932.0.tgz",
+ "integrity": "sha512-Yw/hYNnC1KHuVIQF9PkLXbuKN7ljx70OSbJYDRufllQvej3kRwNcqQSnzI1M4KaObccqKaE6srg22DqpPy9p8w==",
"license": "Apache-2.0",
"dependencies": {
- "@aws-sdk/core": "3.873.0",
- "@aws-sdk/nested-clients": "3.873.0",
- "@aws-sdk/types": "3.862.0",
- "@smithy/property-provider": "^4.0.5",
- "@smithy/types": "^4.3.2",
+ "@aws-sdk/core": "3.932.0",
+ "@aws-sdk/nested-clients": "3.932.0",
+ "@aws-sdk/types": "3.930.0",
+ "@smithy/property-provider": "^4.2.5",
+ "@smithy/shared-ini-file-loader": "^4.4.0",
+ "@smithy/types": "^4.9.0",
"tslib": "^2.6.2"
},
"engines": {
@@ -434,14 +433,14 @@
}
},
"node_modules/@aws-sdk/middleware-host-header": {
- "version": "3.873.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.873.0.tgz",
- "integrity": "sha512-KZ/W1uruWtMOs7D5j3KquOxzCnV79KQW9MjJFZM/M0l6KI8J6V3718MXxFHsTjUE4fpdV6SeCNLV1lwGygsjJA==",
+ "version": "3.930.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.930.0.tgz",
+ "integrity": "sha512-x30jmm3TLu7b/b+67nMyoV0NlbnCVT5DI57yDrhXAPCtdgM1KtdLWt45UcHpKOm1JsaIkmYRh2WYu7Anx4MG0g==",
"license": "Apache-2.0",
"dependencies": {
- "@aws-sdk/types": "3.862.0",
- "@smithy/protocol-http": "^5.1.3",
- "@smithy/types": "^4.3.2",
+ "@aws-sdk/types": "3.930.0",
+ "@smithy/protocol-http": "^5.3.5",
+ "@smithy/types": "^4.9.0",
"tslib": "^2.6.2"
},
"engines": {
@@ -449,13 +448,13 @@
}
},
"node_modules/@aws-sdk/middleware-logger": {
- "version": "3.873.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.873.0.tgz",
- "integrity": "sha512-QhNZ8X7pW68kFez9QxUSN65Um0Feo18ZmHxszQZNUhKDsXew/EG9NPQE/HgYcekcon35zHxC4xs+FeNuPurP2g==",
+ "version": "3.930.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.930.0.tgz",
+ "integrity": "sha512-vh4JBWzMCBW8wREvAwoSqB2geKsZwSHTa0nSt0OMOLp2PdTYIZDi0ZiVMmpfnjcx9XbS6aSluLv9sKx4RrG46A==",
"license": "Apache-2.0",
"dependencies": {
- "@aws-sdk/types": "3.862.0",
- "@smithy/types": "^4.3.2",
+ "@aws-sdk/types": "3.930.0",
+ "@smithy/types": "^4.9.0",
"tslib": "^2.6.2"
},
"engines": {
@@ -463,14 +462,15 @@
}
},
"node_modules/@aws-sdk/middleware-recursion-detection": {
- "version": "3.873.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.873.0.tgz",
- "integrity": "sha512-OtgY8EXOzRdEWR//WfPkA/fXl0+WwE8hq0y9iw2caNyKPtca85dzrrZWnPqyBK/cpImosrpR1iKMYr41XshsCg==",
+ "version": "3.930.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.930.0.tgz",
+ "integrity": "sha512-gv0sekNpa2MBsIhm2cjP3nmYSfI4nscx/+K9u9ybrWZBWUIC4kL2sV++bFjjUz4QxUIlvKByow3/a9ARQyCu7Q==",
"license": "Apache-2.0",
"dependencies": {
- "@aws-sdk/types": "3.862.0",
- "@smithy/protocol-http": "^5.1.3",
- "@smithy/types": "^4.3.2",
+ "@aws-sdk/types": "3.930.0",
+ "@aws/lambda-invoke-store": "^0.1.1",
+ "@smithy/protocol-http": "^5.3.5",
+ "@smithy/types": "^4.9.0",
"tslib": "^2.6.2"
},
"engines": {
@@ -478,17 +478,17 @@
}
},
"node_modules/@aws-sdk/middleware-user-agent": {
- "version": "3.873.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.873.0.tgz",
- "integrity": "sha512-gHqAMYpWkPhZLwqB3Yj83JKdL2Vsb64sryo8LN2UdpElpS+0fT4yjqSxKTfp7gkhN6TCIxF24HQgbPk5FMYJWw==",
+ "version": "3.932.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.932.0.tgz",
+ "integrity": "sha512-9BGTbJyA/4PTdwQWE9hAFIJGpsYkyEW20WON3i15aDqo5oRZwZmqaVageOD57YYqG8JDJjvcwKyDdR4cc38dvg==",
"license": "Apache-2.0",
"dependencies": {
- "@aws-sdk/core": "3.873.0",
- "@aws-sdk/types": "3.862.0",
- "@aws-sdk/util-endpoints": "3.873.0",
- "@smithy/core": "^3.8.0",
- "@smithy/protocol-http": "^5.1.3",
- "@smithy/types": "^4.3.2",
+ "@aws-sdk/core": "3.932.0",
+ "@aws-sdk/types": "3.930.0",
+ "@aws-sdk/util-endpoints": "3.930.0",
+ "@smithy/core": "^3.18.2",
+ "@smithy/protocol-http": "^5.3.5",
+ "@smithy/types": "^4.9.0",
"tslib": "^2.6.2"
},
"engines": {
@@ -496,48 +496,48 @@
}
},
"node_modules/@aws-sdk/nested-clients": {
- "version": "3.873.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.873.0.tgz",
- "integrity": "sha512-yg8JkRHuH/xO65rtmLOWcd9XQhxX1kAonp2CliXT44eA/23OBds6XoheY44eZeHfCTgutDLTYitvy3k9fQY6ZA==",
+ "version": "3.932.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.932.0.tgz",
+ "integrity": "sha512-E2ucBfiXSpxZflHTf3UFbVwao4+7v7ctAeg8SWuglc1UMqMlpwMFFgWiSONtsf0SR3+ZDoWGATyCXOfDWerJuw==",
"license": "Apache-2.0",
"dependencies": {
"@aws-crypto/sha256-browser": "5.2.0",
"@aws-crypto/sha256-js": "5.2.0",
- "@aws-sdk/core": "3.873.0",
- "@aws-sdk/middleware-host-header": "3.873.0",
- "@aws-sdk/middleware-logger": "3.873.0",
- "@aws-sdk/middleware-recursion-detection": "3.873.0",
- "@aws-sdk/middleware-user-agent": "3.873.0",
- "@aws-sdk/region-config-resolver": "3.873.0",
- "@aws-sdk/types": "3.862.0",
- "@aws-sdk/util-endpoints": "3.873.0",
- "@aws-sdk/util-user-agent-browser": "3.873.0",
- "@aws-sdk/util-user-agent-node": "3.873.0",
- "@smithy/config-resolver": "^4.1.5",
- "@smithy/core": "^3.8.0",
- "@smithy/fetch-http-handler": "^5.1.1",
- "@smithy/hash-node": "^4.0.5",
- "@smithy/invalid-dependency": "^4.0.5",
- "@smithy/middleware-content-length": "^4.0.5",
- "@smithy/middleware-endpoint": "^4.1.18",
- "@smithy/middleware-retry": "^4.1.19",
- "@smithy/middleware-serde": "^4.0.9",
- "@smithy/middleware-stack": "^4.0.5",
- "@smithy/node-config-provider": "^4.1.4",
- "@smithy/node-http-handler": "^4.1.1",
- "@smithy/protocol-http": "^5.1.3",
- "@smithy/smithy-client": "^4.4.10",
- "@smithy/types": "^4.3.2",
- "@smithy/url-parser": "^4.0.5",
- "@smithy/util-base64": "^4.0.0",
- "@smithy/util-body-length-browser": "^4.0.0",
- "@smithy/util-body-length-node": "^4.0.0",
- "@smithy/util-defaults-mode-browser": "^4.0.26",
- "@smithy/util-defaults-mode-node": "^4.0.26",
- "@smithy/util-endpoints": "^3.0.7",
- "@smithy/util-middleware": "^4.0.5",
- "@smithy/util-retry": "^4.0.7",
- "@smithy/util-utf8": "^4.0.0",
+ "@aws-sdk/core": "3.932.0",
+ "@aws-sdk/middleware-host-header": "3.930.0",
+ "@aws-sdk/middleware-logger": "3.930.0",
+ "@aws-sdk/middleware-recursion-detection": "3.930.0",
+ "@aws-sdk/middleware-user-agent": "3.932.0",
+ "@aws-sdk/region-config-resolver": "3.930.0",
+ "@aws-sdk/types": "3.930.0",
+ "@aws-sdk/util-endpoints": "3.930.0",
+ "@aws-sdk/util-user-agent-browser": "3.930.0",
+ "@aws-sdk/util-user-agent-node": "3.932.0",
+ "@smithy/config-resolver": "^4.4.3",
+ "@smithy/core": "^3.18.2",
+ "@smithy/fetch-http-handler": "^5.3.6",
+ "@smithy/hash-node": "^4.2.5",
+ "@smithy/invalid-dependency": "^4.2.5",
+ "@smithy/middleware-content-length": "^4.2.5",
+ "@smithy/middleware-endpoint": "^4.3.9",
+ "@smithy/middleware-retry": "^4.4.9",
+ "@smithy/middleware-serde": "^4.2.5",
+ "@smithy/middleware-stack": "^4.2.5",
+ "@smithy/node-config-provider": "^4.3.5",
+ "@smithy/node-http-handler": "^4.4.5",
+ "@smithy/protocol-http": "^5.3.5",
+ "@smithy/smithy-client": "^4.9.5",
+ "@smithy/types": "^4.9.0",
+ "@smithy/url-parser": "^4.2.5",
+ "@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.8",
+ "@smithy/util-defaults-mode-node": "^4.2.11",
+ "@smithy/util-endpoints": "^3.2.5",
+ "@smithy/util-middleware": "^4.2.5",
+ "@smithy/util-retry": "^4.2.5",
+ "@smithy/util-utf8": "^4.2.0",
"tslib": "^2.6.2"
},
"engines": {
@@ -545,16 +545,15 @@
}
},
"node_modules/@aws-sdk/region-config-resolver": {
- "version": "3.873.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.873.0.tgz",
- "integrity": "sha512-q9sPoef+BBG6PJnc4x60vK/bfVwvRWsPgcoQyIra057S/QGjq5VkjvNk6H8xedf6vnKlXNBwq9BaANBXnldUJg==",
+ "version": "3.930.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.930.0.tgz",
+ "integrity": "sha512-KL2JZqH6aYeQssu1g1KuWsReupdfOoxD6f1as2VC+rdwYFUu4LfzMsFfXnBvvQWWqQ7rZHWOw1T+o5gJmg7Dzw==",
"license": "Apache-2.0",
"dependencies": {
- "@aws-sdk/types": "3.862.0",
- "@smithy/node-config-provider": "^4.1.4",
- "@smithy/types": "^4.3.2",
- "@smithy/util-config-provider": "^4.0.0",
- "@smithy/util-middleware": "^4.0.5",
+ "@aws-sdk/types": "3.930.0",
+ "@smithy/config-resolver": "^4.4.3",
+ "@smithy/node-config-provider": "^4.3.5",
+ "@smithy/types": "^4.9.0",
"tslib": "^2.6.2"
},
"engines": {
@@ -562,17 +561,17 @@
}
},
"node_modules/@aws-sdk/token-providers": {
- "version": "3.873.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.873.0.tgz",
- "integrity": "sha512-BWOCeFeV/Ba8fVhtwUw/0Hz4wMm9fjXnMb4Z2a5he/jFlz5mt1/rr6IQ4MyKgzOaz24YrvqsJW2a0VUKOaYDvg==",
+ "version": "3.932.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.932.0.tgz",
+ "integrity": "sha512-43u82ulVuHK4zWhcSPyuPS18l0LNHi3QJQ1YtP2MfP8bPf5a6hMYp5e3lUr9oTDEWcpwBYtOW0m1DVmoU/3veA==",
"license": "Apache-2.0",
"dependencies": {
- "@aws-sdk/core": "3.873.0",
- "@aws-sdk/nested-clients": "3.873.0",
- "@aws-sdk/types": "3.862.0",
- "@smithy/property-provider": "^4.0.5",
- "@smithy/shared-ini-file-loader": "^4.0.5",
- "@smithy/types": "^4.3.2",
+ "@aws-sdk/core": "3.932.0",
+ "@aws-sdk/nested-clients": "3.932.0",
+ "@aws-sdk/types": "3.930.0",
+ "@smithy/property-provider": "^4.2.5",
+ "@smithy/shared-ini-file-loader": "^4.4.0",
+ "@smithy/types": "^4.9.0",
"tslib": "^2.6.2"
},
"engines": {
@@ -580,12 +579,12 @@
}
},
"node_modules/@aws-sdk/types": {
- "version": "3.862.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.862.0.tgz",
- "integrity": "sha512-Bei+RL0cDxxV+lW2UezLbCYYNeJm6Nzee0TpW0FfyTRBhH9C1XQh4+x+IClriXvgBnRquTMMYsmJfvx8iyLKrg==",
+ "version": "3.930.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.930.0.tgz",
+ "integrity": "sha512-we/vaAgwlEFW7IeftmCLlLMw+6hFs3DzZPJw7lVHbj/5HJ0bz9gndxEsS2lQoeJ1zhiiLqAqvXxmM43s0MBg0A==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/types": "^4.3.2",
+ "@smithy/types": "^4.9.0",
"tslib": "^2.6.2"
},
"engines": {
@@ -593,15 +592,15 @@
}
},
"node_modules/@aws-sdk/util-endpoints": {
- "version": "3.873.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.873.0.tgz",
- "integrity": "sha512-YByHrhjxYdjKRf/RQygRK1uh0As1FIi9+jXTcIEX/rBgN8mUByczr2u4QXBzw7ZdbdcOBMOkPnLRjNOWW1MkFg==",
+ "version": "3.930.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.930.0.tgz",
+ "integrity": "sha512-M2oEKBzzNAYr136RRc6uqw3aWlwCxqTP1Lawps9E1d2abRPvl1p1ztQmmXp1Ak4rv8eByIZ+yQyKQ3zPdRG5dw==",
"license": "Apache-2.0",
"dependencies": {
- "@aws-sdk/types": "3.862.0",
- "@smithy/types": "^4.3.2",
- "@smithy/url-parser": "^4.0.5",
- "@smithy/util-endpoints": "^3.0.7",
+ "@aws-sdk/types": "3.930.0",
+ "@smithy/types": "^4.9.0",
+ "@smithy/url-parser": "^4.2.5",
+ "@smithy/util-endpoints": "^3.2.5",
"tslib": "^2.6.2"
},
"engines": {
@@ -609,9 +608,9 @@
}
},
"node_modules/@aws-sdk/util-locate-window": {
- "version": "3.873.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.873.0.tgz",
- "integrity": "sha512-xcVhZF6svjM5Rj89T1WzkjQmrTF6dpR2UvIHPMTnSZoNe6CixejPZ6f0JJ2kAhO8H+dUHwNBlsUgOTIKiK/Syg==",
+ "version": "3.893.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.893.0.tgz",
+ "integrity": "sha512-T89pFfgat6c8nMmpI8eKjBcDcgJq36+m9oiXbcUzeU55MP9ZuGgBomGjGnHaEyF36jenW9gmg3NfZDm0AO2XPg==",
"license": "Apache-2.0",
"dependencies": {
"tslib": "^2.6.2"
@@ -621,27 +620,27 @@
}
},
"node_modules/@aws-sdk/util-user-agent-browser": {
- "version": "3.873.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.873.0.tgz",
- "integrity": "sha512-AcRdbK6o19yehEcywI43blIBhOCSo6UgyWcuOJX5CFF8k39xm1ILCjQlRRjchLAxWrm0lU0Q7XV90RiMMFMZtA==",
+ "version": "3.930.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.930.0.tgz",
+ "integrity": "sha512-q6lCRm6UAe+e1LguM5E4EqM9brQlDem4XDcQ87NzEvlTW6GzmNCO0w1jS0XgCFXQHjDxjdlNFX+5sRbHijwklg==",
"license": "Apache-2.0",
"dependencies": {
- "@aws-sdk/types": "3.862.0",
- "@smithy/types": "^4.3.2",
+ "@aws-sdk/types": "3.930.0",
+ "@smithy/types": "^4.9.0",
"bowser": "^2.11.0",
"tslib": "^2.6.2"
}
},
"node_modules/@aws-sdk/util-user-agent-node": {
- "version": "3.873.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.873.0.tgz",
- "integrity": "sha512-9MivTP+q9Sis71UxuBaIY3h5jxH0vN3/ZWGxO8ADL19S2OIfknrYSAfzE5fpoKROVBu0bS4VifHOFq4PY1zsxw==",
+ "version": "3.932.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.932.0.tgz",
+ "integrity": "sha512-/kC6cscHrZL74TrZtgiIL5jJNbVsw9duGGPurmaVgoCbP7NnxyaSWEurbNV3VPNPhNE3bV3g4Ci+odq+AlsYQg==",
"license": "Apache-2.0",
"dependencies": {
- "@aws-sdk/middleware-user-agent": "3.873.0",
- "@aws-sdk/types": "3.862.0",
- "@smithy/node-config-provider": "^4.1.4",
- "@smithy/types": "^4.3.2",
+ "@aws-sdk/middleware-user-agent": "3.932.0",
+ "@aws-sdk/types": "3.930.0",
+ "@smithy/node-config-provider": "^4.3.5",
+ "@smithy/types": "^4.9.0",
"tslib": "^2.6.2"
},
"engines": {
@@ -657,18 +656,28 @@
}
},
"node_modules/@aws-sdk/xml-builder": {
- "version": "3.873.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.873.0.tgz",
- "integrity": "sha512-kLO7k7cGJ6KaHiExSJWojZurF7SnGMDHXRuQunFnEoD0n1yB6Lqy/S/zHiQ7oJnBhPr9q0TW9qFkrsZb1Uc54w==",
+ "version": "3.930.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.930.0.tgz",
+ "integrity": "sha512-YIfkD17GocxdmlUVc3ia52QhcWuRIUJonbF8A2CYfcWNV3HzvAqpcPeC0bYUhkK+8e8YO1ARnLKZQE0TlwzorA==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/types": "^4.3.2",
+ "@smithy/types": "^4.9.0",
+ "fast-xml-parser": "5.2.5",
"tslib": "^2.6.2"
},
"engines": {
"node": ">=18.0.0"
}
},
+ "node_modules/@aws/lambda-invoke-store": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.1.1.tgz",
+ "integrity": "sha512-RcLam17LdlbSOSp9VxmUu1eI6Mwxp+OwhD2QhiSNmNCzoDb0EeUXTD2n/WbcnrAYMGlmf05th6QYq23VqvJqpA==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
"node_modules/@babel/code-frame": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
@@ -1678,12 +1687,12 @@
"license": "(Unlicense OR Apache-2.0)"
},
"node_modules/@smithy/abort-controller": {
- "version": "4.0.5",
- "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.0.5.tgz",
- "integrity": "sha512-jcrqdTQurIrBbUm4W2YdLVMQDoL0sA9DTxYd2s+R/y+2U9NLOP7Xf/YqfSg1FZhlZIYEnvk2mwbyvIfdLEPo8g==",
+ "version": "4.2.5",
+ "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.5.tgz",
+ "integrity": "sha512-j7HwVkBw68YW8UmFRcjZOmssE77Rvk0GWAIN1oFBhsaovQmZWYCIcGa9/pwRB0ExI8Sk9MWNALTjftjHZea7VA==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/types": "^4.3.2",
+ "@smithy/types": "^4.9.0",
"tslib": "^2.6.2"
},
"engines": {
@@ -1691,15 +1700,16 @@
}
},
"node_modules/@smithy/config-resolver": {
- "version": "4.1.5",
- "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.1.5.tgz",
- "integrity": "sha512-viuHMxBAqydkB0AfWwHIdwf/PRH2z5KHGUzqyRtS/Wv+n3IHI993Sk76VCA7dD/+GzgGOmlJDITfPcJC1nIVIw==",
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.3.tgz",
+ "integrity": "sha512-ezHLe1tKLUxDJo2LHtDuEDyWXolw8WGOR92qb4bQdWq/zKenO5BvctZGrVJBK08zjezSk7bmbKFOXIVyChvDLw==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/node-config-provider": "^4.1.4",
- "@smithy/types": "^4.3.2",
- "@smithy/util-config-provider": "^4.0.0",
- "@smithy/util-middleware": "^4.0.5",
+ "@smithy/node-config-provider": "^4.3.5",
+ "@smithy/types": "^4.9.0",
+ "@smithy/util-config-provider": "^4.2.0",
+ "@smithy/util-endpoints": "^3.2.5",
+ "@smithy/util-middleware": "^4.2.5",
"tslib": "^2.6.2"
},
"engines": {
@@ -1707,37 +1717,36 @@
}
},
"node_modules/@smithy/core": {
- "version": "3.8.0",
- "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.8.0.tgz",
- "integrity": "sha512-EYqsIYJmkR1VhVE9pccnk353xhs+lB6btdutJEtsp7R055haMJp2yE16eSxw8fv+G0WUY6vqxyYOP8kOqawxYQ==",
+ "version": "3.18.4",
+ "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.18.4.tgz",
+ "integrity": "sha512-o5tMqPZILBvvROfC8vC+dSVnWJl9a0u9ax1i1+Bq8515eYjUJqqk5XjjEsDLoeL5dSqGSh6WGdVx1eJ1E/Nwhw==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/middleware-serde": "^4.0.9",
- "@smithy/protocol-http": "^5.1.3",
- "@smithy/types": "^4.3.2",
- "@smithy/util-base64": "^4.0.0",
- "@smithy/util-body-length-browser": "^4.0.0",
- "@smithy/util-middleware": "^4.0.5",
- "@smithy/util-stream": "^4.2.4",
- "@smithy/util-utf8": "^4.0.0",
- "@types/uuid": "^9.0.1",
- "tslib": "^2.6.2",
- "uuid": "^9.0.1"
+ "@smithy/middleware-serde": "^4.2.6",
+ "@smithy/protocol-http": "^5.3.5",
+ "@smithy/types": "^4.9.0",
+ "@smithy/util-base64": "^4.3.0",
+ "@smithy/util-body-length-browser": "^4.2.0",
+ "@smithy/util-middleware": "^4.2.5",
+ "@smithy/util-stream": "^4.5.6",
+ "@smithy/util-utf8": "^4.2.0",
+ "@smithy/uuid": "^1.1.0",
+ "tslib": "^2.6.2"
},
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/@smithy/credential-provider-imds": {
- "version": "4.0.7",
- "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.0.7.tgz",
- "integrity": "sha512-dDzrMXA8d8riFNiPvytxn0mNwR4B3h8lgrQ5UjAGu6T9z/kRg/Xncf4tEQHE/+t25sY8IH3CowcmWi+1U5B1Gw==",
+ "version": "4.2.5",
+ "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.5.tgz",
+ "integrity": "sha512-BZwotjoZWn9+36nimwm/OLIcVe+KYRwzMjfhd4QT7QxPm9WY0HiOV8t/Wlh+HVUif0SBVV7ksq8//hPaBC/okQ==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/node-config-provider": "^4.1.4",
- "@smithy/property-provider": "^4.0.5",
- "@smithy/types": "^4.3.2",
- "@smithy/url-parser": "^4.0.5",
+ "@smithy/node-config-provider": "^4.3.5",
+ "@smithy/property-provider": "^4.2.5",
+ "@smithy/types": "^4.9.0",
+ "@smithy/url-parser": "^4.2.5",
"tslib": "^2.6.2"
},
"engines": {
@@ -1745,15 +1754,15 @@
}
},
"node_modules/@smithy/fetch-http-handler": {
- "version": "5.1.1",
- "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.1.1.tgz",
- "integrity": "sha512-61WjM0PWmZJR+SnmzaKI7t7G0UkkNFboDpzIdzSoy7TByUzlxo18Qlh9s71qug4AY4hlH/CwXdubMtkcNEb/sQ==",
+ "version": "5.3.6",
+ "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.6.tgz",
+ "integrity": "sha512-3+RG3EA6BBJ/ofZUeTFJA7mHfSYrZtQIrDP9dI8Lf7X6Jbos2jptuLrAAteDiFVrmbEmLSuRG/bUKzfAXk7dhg==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/protocol-http": "^5.1.3",
- "@smithy/querystring-builder": "^4.0.5",
- "@smithy/types": "^4.3.2",
- "@smithy/util-base64": "^4.0.0",
+ "@smithy/protocol-http": "^5.3.5",
+ "@smithy/querystring-builder": "^4.2.5",
+ "@smithy/types": "^4.9.0",
+ "@smithy/util-base64": "^4.3.0",
"tslib": "^2.6.2"
},
"engines": {
@@ -1761,14 +1770,14 @@
}
},
"node_modules/@smithy/hash-node": {
- "version": "4.0.5",
- "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.0.5.tgz",
- "integrity": "sha512-cv1HHkKhpyRb6ahD8Vcfb2Hgz67vNIXEp2vnhzfxLFGRukLCNEA5QdsorbUEzXma1Rco0u3rx5VTqbM06GcZqQ==",
+ "version": "4.2.5",
+ "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.5.tgz",
+ "integrity": "sha512-DpYX914YOfA3UDT9CN1BM787PcHfWRBB43fFGCYrZFUH0Jv+5t8yYl+Pd5PW4+QzoGEDvn5d5QIO4j2HyYZQSA==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/types": "^4.3.2",
- "@smithy/util-buffer-from": "^4.0.0",
- "@smithy/util-utf8": "^4.0.0",
+ "@smithy/types": "^4.9.0",
+ "@smithy/util-buffer-from": "^4.2.0",
+ "@smithy/util-utf8": "^4.2.0",
"tslib": "^2.6.2"
},
"engines": {
@@ -1776,12 +1785,12 @@
}
},
"node_modules/@smithy/invalid-dependency": {
- "version": "4.0.5",
- "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.0.5.tgz",
- "integrity": "sha512-IVnb78Qtf7EJpoEVo7qJ8BEXQwgC4n3igeJNNKEj/MLYtapnx8A67Zt/J3RXAj2xSO1910zk0LdFiygSemuLow==",
+ "version": "4.2.5",
+ "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.5.tgz",
+ "integrity": "sha512-2L2erASEro1WC5nV+plwIMxrTXpvpfzl4e+Nre6vBVRR2HKeGGcvpJyyL3/PpiSg+cJG2KpTmZmq934Olb6e5A==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/types": "^4.3.2",
+ "@smithy/types": "^4.9.0",
"tslib": "^2.6.2"
},
"engines": {
@@ -1789,9 +1798,9 @@
}
},
"node_modules/@smithy/is-array-buffer": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.0.0.tgz",
- "integrity": "sha512-saYhF8ZZNoJDTvJBEWgeBccCg+yvp1CX+ed12yORU3NilJScfc6gfch2oVb4QgxZrGUx3/ZJlb+c/dJbyupxlw==",
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.0.tgz",
+ "integrity": "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ==",
"license": "Apache-2.0",
"dependencies": {
"tslib": "^2.6.2"
@@ -1801,13 +1810,13 @@
}
},
"node_modules/@smithy/middleware-content-length": {
- "version": "4.0.5",
- "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.0.5.tgz",
- "integrity": "sha512-l1jlNZoYzoCC7p0zCtBDE5OBXZ95yMKlRlftooE5jPWQn4YBPLgsp+oeHp7iMHaTGoUdFqmHOPa8c9G3gBsRpQ==",
+ "version": "4.2.5",
+ "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.5.tgz",
+ "integrity": "sha512-Y/RabVa5vbl5FuHYV2vUCwvh/dqzrEY/K2yWPSqvhFUwIY0atLqO4TienjBXakoy4zrKAMCZwg+YEqmH7jaN7A==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/protocol-http": "^5.1.3",
- "@smithy/types": "^4.3.2",
+ "@smithy/protocol-http": "^5.3.5",
+ "@smithy/types": "^4.9.0",
"tslib": "^2.6.2"
},
"engines": {
@@ -1815,18 +1824,18 @@
}
},
"node_modules/@smithy/middleware-endpoint": {
- "version": "4.1.18",
- "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.1.18.tgz",
- "integrity": "sha512-ZhvqcVRPZxnZlokcPaTwb+r+h4yOIOCJmx0v2d1bpVlmP465g3qpVSf7wxcq5zZdu4jb0H4yIMxuPwDJSQc3MQ==",
+ "version": "4.3.11",
+ "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.3.11.tgz",
+ "integrity": "sha512-eJXq9VJzEer1W7EQh3HY2PDJdEcEUnv6sKuNt4eVjyeNWcQFS4KmnY+CKkYOIR6tSqarn6bjjCqg1UB+8UJiPQ==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/core": "^3.8.0",
- "@smithy/middleware-serde": "^4.0.9",
- "@smithy/node-config-provider": "^4.1.4",
- "@smithy/shared-ini-file-loader": "^4.0.5",
- "@smithy/types": "^4.3.2",
- "@smithy/url-parser": "^4.0.5",
- "@smithy/util-middleware": "^4.0.5",
+ "@smithy/core": "^3.18.4",
+ "@smithy/middleware-serde": "^4.2.6",
+ "@smithy/node-config-provider": "^4.3.5",
+ "@smithy/shared-ini-file-loader": "^4.4.0",
+ "@smithy/types": "^4.9.0",
+ "@smithy/url-parser": "^4.2.5",
+ "@smithy/util-middleware": "^4.2.5",
"tslib": "^2.6.2"
},
"engines": {
@@ -1834,34 +1843,33 @@
}
},
"node_modules/@smithy/middleware-retry": {
- "version": "4.1.19",
- "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.1.19.tgz",
- "integrity": "sha512-X58zx/NVECjeuUB6A8HBu4bhx72EoUz+T5jTMIyeNKx2lf+Gs9TmWPNNkH+5QF0COjpInP/xSpJGJ7xEnAklQQ==",
+ "version": "4.4.11",
+ "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.11.tgz",
+ "integrity": "sha512-EL5OQHvFOKneJVRgzRW4lU7yidSwp/vRJOe542bHgExN3KNThr1rlg0iE4k4SnA+ohC+qlUxoK+smKeAYPzfAQ==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/node-config-provider": "^4.1.4",
- "@smithy/protocol-http": "^5.1.3",
- "@smithy/service-error-classification": "^4.0.7",
- "@smithy/smithy-client": "^4.4.10",
- "@smithy/types": "^4.3.2",
- "@smithy/util-middleware": "^4.0.5",
- "@smithy/util-retry": "^4.0.7",
- "@types/uuid": "^9.0.1",
- "tslib": "^2.6.2",
- "uuid": "^9.0.1"
+ "@smithy/node-config-provider": "^4.3.5",
+ "@smithy/protocol-http": "^5.3.5",
+ "@smithy/service-error-classification": "^4.2.5",
+ "@smithy/smithy-client": "^4.9.7",
+ "@smithy/types": "^4.9.0",
+ "@smithy/util-middleware": "^4.2.5",
+ "@smithy/util-retry": "^4.2.5",
+ "@smithy/uuid": "^1.1.0",
+ "tslib": "^2.6.2"
},
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/@smithy/middleware-serde": {
- "version": "4.0.9",
- "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.0.9.tgz",
- "integrity": "sha512-uAFFR4dpeoJPGz8x9mhxp+RPjo5wW0QEEIPPPbLXiRRWeCATf/Km3gKIVR5vaP8bN1kgsPhcEeh+IZvUlBv6Xg==",
+ "version": "4.2.6",
+ "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.6.tgz",
+ "integrity": "sha512-VkLoE/z7e2g8pirwisLz8XJWedUSY8my/qrp81VmAdyrhi94T+riBfwP+AOEEFR9rFTSonC/5D2eWNmFabHyGQ==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/protocol-http": "^5.1.3",
- "@smithy/types": "^4.3.2",
+ "@smithy/protocol-http": "^5.3.5",
+ "@smithy/types": "^4.9.0",
"tslib": "^2.6.2"
},
"engines": {
@@ -1869,12 +1877,12 @@
}
},
"node_modules/@smithy/middleware-stack": {
- "version": "4.0.5",
- "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.0.5.tgz",
- "integrity": "sha512-/yoHDXZPh3ocRVyeWQFvC44u8seu3eYzZRveCMfgMOBcNKnAmOvjbL9+Cp5XKSIi9iYA9PECUuW2teDAk8T+OQ==",
+ "version": "4.2.5",
+ "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.5.tgz",
+ "integrity": "sha512-bYrutc+neOyWxtZdbB2USbQttZN0mXaOyYLIsaTbJhFsfpXyGWUxJpEuO1rJ8IIJm2qH4+xJT0mxUSsEDTYwdQ==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/types": "^4.3.2",
+ "@smithy/types": "^4.9.0",
"tslib": "^2.6.2"
},
"engines": {
@@ -1882,14 +1890,14 @@
}
},
"node_modules/@smithy/node-config-provider": {
- "version": "4.1.4",
- "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.1.4.tgz",
- "integrity": "sha512-+UDQV/k42jLEPPHSn39l0Bmc4sB1xtdI9Gd47fzo/0PbXzJ7ylgaOByVjF5EeQIumkepnrJyfx86dPa9p47Y+w==",
+ "version": "4.3.5",
+ "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.5.tgz",
+ "integrity": "sha512-UTurh1C4qkVCtqggI36DGbLB2Kv8UlcFdMXDcWMbqVY2uRg0XmT9Pb4Vj6oSQ34eizO1fvR0RnFV4Axw4IrrAg==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/property-provider": "^4.0.5",
- "@smithy/shared-ini-file-loader": "^4.0.5",
- "@smithy/types": "^4.3.2",
+ "@smithy/property-provider": "^4.2.5",
+ "@smithy/shared-ini-file-loader": "^4.4.0",
+ "@smithy/types": "^4.9.0",
"tslib": "^2.6.2"
},
"engines": {
@@ -1897,15 +1905,15 @@
}
},
"node_modules/@smithy/node-http-handler": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.1.1.tgz",
- "integrity": "sha512-RHnlHqFpoVdjSPPiYy/t40Zovf3BBHc2oemgD7VsVTFFZrU5erFFe0n52OANZZ/5sbshgD93sOh5r6I35Xmpaw==",
+ "version": "4.4.5",
+ "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.5.tgz",
+ "integrity": "sha512-CMnzM9R2WqlqXQGtIlsHMEZfXKJVTIrqCNoSd/QpAyp+Dw0a1Vps13l6ma1fH8g7zSPNsA59B/kWgeylFuA/lw==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/abort-controller": "^4.0.5",
- "@smithy/protocol-http": "^5.1.3",
- "@smithy/querystring-builder": "^4.0.5",
- "@smithy/types": "^4.3.2",
+ "@smithy/abort-controller": "^4.2.5",
+ "@smithy/protocol-http": "^5.3.5",
+ "@smithy/querystring-builder": "^4.2.5",
+ "@smithy/types": "^4.9.0",
"tslib": "^2.6.2"
},
"engines": {
@@ -1913,12 +1921,12 @@
}
},
"node_modules/@smithy/property-provider": {
- "version": "4.0.5",
- "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.0.5.tgz",
- "integrity": "sha512-R/bswf59T/n9ZgfgUICAZoWYKBHcsVDurAGX88zsiUtOTA/xUAPyiT+qkNCPwFn43pZqN84M4MiUsbSGQmgFIQ==",
+ "version": "4.2.5",
+ "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.5.tgz",
+ "integrity": "sha512-8iLN1XSE1rl4MuxvQ+5OSk/Zb5El7NJZ1td6Tn+8dQQHIjp59Lwl6bd0+nzw6SKm2wSSriH2v/I9LPzUic7EOg==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/types": "^4.3.2",
+ "@smithy/types": "^4.9.0",
"tslib": "^2.6.2"
},
"engines": {
@@ -1926,12 +1934,12 @@
}
},
"node_modules/@smithy/protocol-http": {
- "version": "5.1.3",
- "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.1.3.tgz",
- "integrity": "sha512-fCJd2ZR7D22XhDY0l+92pUag/7je2BztPRQ01gU5bMChcyI0rlly7QFibnYHzcxDvccMjlpM/Q1ev8ceRIb48w==",
+ "version": "5.3.5",
+ "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.5.tgz",
+ "integrity": "sha512-RlaL+sA0LNMp03bf7XPbFmT5gN+w3besXSWMkA8rcmxLSVfiEXElQi4O2IWwPfxzcHkxqrwBFMbngB8yx/RvaQ==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/types": "^4.3.2",
+ "@smithy/types": "^4.9.0",
"tslib": "^2.6.2"
},
"engines": {
@@ -1939,13 +1947,13 @@
}
},
"node_modules/@smithy/querystring-builder": {
- "version": "4.0.5",
- "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.0.5.tgz",
- "integrity": "sha512-NJeSCU57piZ56c+/wY+AbAw6rxCCAOZLCIniRE7wqvndqxcKKDOXzwWjrY7wGKEISfhL9gBbAaWWgHsUGedk+A==",
+ "version": "4.2.5",
+ "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.5.tgz",
+ "integrity": "sha512-y98otMI1saoajeik2kLfGyRp11e5U/iJYH/wLCh3aTV/XutbGT9nziKGkgCaMD1ghK7p6htHMm6b6scl9JRUWg==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/types": "^4.3.2",
- "@smithy/util-uri-escape": "^4.0.0",
+ "@smithy/types": "^4.9.0",
+ "@smithy/util-uri-escape": "^4.2.0",
"tslib": "^2.6.2"
},
"engines": {
@@ -1953,12 +1961,12 @@
}
},
"node_modules/@smithy/querystring-parser": {
- "version": "4.0.5",
- "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.0.5.tgz",
- "integrity": "sha512-6SV7md2CzNG/WUeTjVe6Dj8noH32r4MnUeFKZrnVYsQxpGSIcphAanQMayi8jJLZAWm6pdM9ZXvKCpWOsIGg0w==",
+ "version": "4.2.5",
+ "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.5.tgz",
+ "integrity": "sha512-031WCTdPYgiQRYNPXznHXof2YM0GwL6SeaSyTH/P72M1Vz73TvCNH2Nq8Iu2IEPq9QP2yx0/nrw5YmSeAi/AjQ==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/types": "^4.3.2",
+ "@smithy/types": "^4.9.0",
"tslib": "^2.6.2"
},
"engines": {
@@ -1966,24 +1974,24 @@
}
},
"node_modules/@smithy/service-error-classification": {
- "version": "4.0.7",
- "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.0.7.tgz",
- "integrity": "sha512-XvRHOipqpwNhEjDf2L5gJowZEm5nsxC16pAZOeEcsygdjv9A2jdOh3YoDQvOXBGTsaJk6mNWtzWalOB9976Wlg==",
+ "version": "4.2.5",
+ "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.5.tgz",
+ "integrity": "sha512-8fEvK+WPE3wUAcDvqDQG1Vk3ANLR8Px979te96m84CbKAjBVf25rPYSzb4xU4hlTyho7VhOGnh5i62D/JVF0JQ==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/types": "^4.3.2"
+ "@smithy/types": "^4.9.0"
},
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/@smithy/shared-ini-file-loader": {
- "version": "4.0.5",
- "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.0.5.tgz",
- "integrity": "sha512-YVVwehRDuehgoXdEL4r1tAAzdaDgaC9EQvhK0lEbfnbrd0bd5+CTQumbdPryX3J2shT7ZqQE+jPW4lmNBAB8JQ==",
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.0.tgz",
+ "integrity": "sha512-5WmZ5+kJgJDjwXXIzr1vDTG+RhF9wzSODQBfkrQ2VVkYALKGvZX1lgVSxEkgicSAFnFhPj5rudJV0zoinqS0bA==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/types": "^4.3.2",
+ "@smithy/types": "^4.9.0",
"tslib": "^2.6.2"
},
"engines": {
@@ -1991,18 +1999,18 @@
}
},
"node_modules/@smithy/signature-v4": {
- "version": "5.1.3",
- "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.1.3.tgz",
- "integrity": "sha512-mARDSXSEgllNzMw6N+mC+r1AQlEBO3meEAkR/UlfAgnMzJUB3goRBWgip1EAMG99wh36MDqzo86SfIX5Y+VEaw==",
+ "version": "5.3.5",
+ "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.5.tgz",
+ "integrity": "sha512-xSUfMu1FT7ccfSXkoLl/QRQBi2rOvi3tiBZU2Tdy3I6cgvZ6SEi9QNey+lqps/sJRnogIS+lq+B1gxxbra2a/w==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/is-array-buffer": "^4.0.0",
- "@smithy/protocol-http": "^5.1.3",
- "@smithy/types": "^4.3.2",
- "@smithy/util-hex-encoding": "^4.0.0",
- "@smithy/util-middleware": "^4.0.5",
- "@smithy/util-uri-escape": "^4.0.0",
- "@smithy/util-utf8": "^4.0.0",
+ "@smithy/is-array-buffer": "^4.2.0",
+ "@smithy/protocol-http": "^5.3.5",
+ "@smithy/types": "^4.9.0",
+ "@smithy/util-hex-encoding": "^4.2.0",
+ "@smithy/util-middleware": "^4.2.5",
+ "@smithy/util-uri-escape": "^4.2.0",
+ "@smithy/util-utf8": "^4.2.0",
"tslib": "^2.6.2"
},
"engines": {
@@ -2010,17 +2018,17 @@
}
},
"node_modules/@smithy/smithy-client": {
- "version": "4.4.10",
- "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.4.10.tgz",
- "integrity": "sha512-iW6HjXqN0oPtRS0NK/zzZ4zZeGESIFcxj2FkWed3mcK8jdSdHzvnCKXSjvewESKAgGKAbJRA+OsaqKhkdYRbQQ==",
+ "version": "4.9.7",
+ "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.9.7.tgz",
+ "integrity": "sha512-pskaE4kg0P9xNQWihfqlTMyxyFR3CH6Sr6keHYghgyqqDXzjl2QJg5lAzuVe/LzZiOzcbcVtxKYi1/fZPt/3DA==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/core": "^3.8.0",
- "@smithy/middleware-endpoint": "^4.1.18",
- "@smithy/middleware-stack": "^4.0.5",
- "@smithy/protocol-http": "^5.1.3",
- "@smithy/types": "^4.3.2",
- "@smithy/util-stream": "^4.2.4",
+ "@smithy/core": "^3.18.4",
+ "@smithy/middleware-endpoint": "^4.3.11",
+ "@smithy/middleware-stack": "^4.2.5",
+ "@smithy/protocol-http": "^5.3.5",
+ "@smithy/types": "^4.9.0",
+ "@smithy/util-stream": "^4.5.6",
"tslib": "^2.6.2"
},
"engines": {
@@ -2028,9 +2036,9 @@
}
},
"node_modules/@smithy/types": {
- "version": "4.3.2",
- "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.3.2.tgz",
- "integrity": "sha512-QO4zghLxiQ5W9UZmX2Lo0nta2PuE1sSrXUYDoaB6HMR762C0P7v/HEPHf6ZdglTVssJG1bsrSBxdc3quvDSihw==",
+ "version": "4.9.0",
+ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.9.0.tgz",
+ "integrity": "sha512-MvUbdnXDTwykR8cB1WZvNNwqoWVaTRA0RLlLmf/cIFNMM2cKWz01X4Ly6SMC4Kks30r8tT3Cty0jmeWfiuyHTA==",
"license": "Apache-2.0",
"dependencies": {
"tslib": "^2.6.2"
@@ -2040,13 +2048,13 @@
}
},
"node_modules/@smithy/url-parser": {
- "version": "4.0.5",
- "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.0.5.tgz",
- "integrity": "sha512-j+733Um7f1/DXjYhCbvNXABV53NyCRRA54C7bNEIxNPs0YjfRxeMKjjgm2jvTYrciZyCjsicHwQ6Q0ylo+NAUw==",
+ "version": "4.2.5",
+ "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.5.tgz",
+ "integrity": "sha512-VaxMGsilqFnK1CeBX+LXnSuaMx4sTL/6znSZh2829txWieazdVxr54HmiyTsIbpOTLcf5nYpq9lpzmwRdxj6rQ==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/querystring-parser": "^4.0.5",
- "@smithy/types": "^4.3.2",
+ "@smithy/querystring-parser": "^4.2.5",
+ "@smithy/types": "^4.9.0",
"tslib": "^2.6.2"
},
"engines": {
@@ -2054,13 +2062,13 @@
}
},
"node_modules/@smithy/util-base64": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.0.0.tgz",
- "integrity": "sha512-CvHfCmO2mchox9kjrtzoHkWHxjHZzaFojLc8quxXY7WAAMAg43nuxwv95tATVgQFNDwd4M9S1qFzj40Ul41Kmg==",
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.3.0.tgz",
+ "integrity": "sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/util-buffer-from": "^4.0.0",
- "@smithy/util-utf8": "^4.0.0",
+ "@smithy/util-buffer-from": "^4.2.0",
+ "@smithy/util-utf8": "^4.2.0",
"tslib": "^2.6.2"
},
"engines": {
@@ -2068,9 +2076,9 @@
}
},
"node_modules/@smithy/util-body-length-browser": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.0.0.tgz",
- "integrity": "sha512-sNi3DL0/k64/LO3A256M+m3CDdG6V7WKWHdAiBBMUN8S3hK3aMPhwnPik2A/a2ONN+9doY9UxaLfgqsIRg69QA==",
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.2.0.tgz",
+ "integrity": "sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg==",
"license": "Apache-2.0",
"dependencies": {
"tslib": "^2.6.2"
@@ -2080,9 +2088,9 @@
}
},
"node_modules/@smithy/util-body-length-node": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.0.0.tgz",
- "integrity": "sha512-q0iDP3VsZzqJyje8xJWEJCNIu3lktUGVoSy1KB0UWym2CL1siV3artm+u1DFYTLejpsrdGyCSWBdGNjJzfDPjg==",
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.2.1.tgz",
+ "integrity": "sha512-h53dz/pISVrVrfxV1iqXlx5pRg3V2YWFcSQyPyXZRrZoZj4R4DeWRDo1a7dd3CPTcFi3kE+98tuNyD2axyZReA==",
"license": "Apache-2.0",
"dependencies": {
"tslib": "^2.6.2"
@@ -2092,12 +2100,12 @@
}
},
"node_modules/@smithy/util-buffer-from": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.0.0.tgz",
- "integrity": "sha512-9TOQ7781sZvddgO8nxueKi3+yGvkY35kotA0Y6BWRajAv8jjmigQ1sBwz0UX47pQMYXJPahSKEKYFgt+rXdcug==",
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.0.tgz",
+ "integrity": "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/is-array-buffer": "^4.0.0",
+ "@smithy/is-array-buffer": "^4.2.0",
"tslib": "^2.6.2"
},
"engines": {
@@ -2105,9 +2113,9 @@
}
},
"node_modules/@smithy/util-config-provider": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.0.0.tgz",
- "integrity": "sha512-L1RBVzLyfE8OXH+1hsJ8p+acNUSirQnWQ6/EgpchV88G6zGBTDPdXiiExei6Z1wR2RxYvxY/XLw6AMNCCt8H3w==",
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.2.0.tgz",
+ "integrity": "sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q==",
"license": "Apache-2.0",
"dependencies": {
"tslib": "^2.6.2"
@@ -2117,15 +2125,14 @@
}
},
"node_modules/@smithy/util-defaults-mode-browser": {
- "version": "4.0.26",
- "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.0.26.tgz",
- "integrity": "sha512-xgl75aHIS/3rrGp7iTxQAOELYeyiwBu+eEgAk4xfKwJJ0L8VUjhO2shsDpeil54BOFsqmk5xfdesiewbUY5tKQ==",
+ "version": "4.3.10",
+ "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.10.tgz",
+ "integrity": "sha512-3iA3JVO1VLrP21FsZZpMCeF93aqP3uIOMvymAT3qHIJz2YlgDeRvNUspFwCNqd/j3qqILQJGtsVQnJZICh/9YA==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/property-provider": "^4.0.5",
- "@smithy/smithy-client": "^4.4.10",
- "@smithy/types": "^4.3.2",
- "bowser": "^2.11.0",
+ "@smithy/property-provider": "^4.2.5",
+ "@smithy/smithy-client": "^4.9.7",
+ "@smithy/types": "^4.9.0",
"tslib": "^2.6.2"
},
"engines": {
@@ -2133,17 +2140,17 @@
}
},
"node_modules/@smithy/util-defaults-mode-node": {
- "version": "4.0.26",
- "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.0.26.tgz",
- "integrity": "sha512-z81yyIkGiLLYVDetKTUeCZQ8x20EEzvQjrqJtb/mXnevLq2+w3XCEWTJ2pMp401b6BkEkHVfXb/cROBpVauLMQ==",
+ "version": "4.2.13",
+ "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.13.tgz",
+ "integrity": "sha512-PTc6IpnpSGASuzZAgyUtaVfOFpU0jBD2mcGwrgDuHf7PlFgt5TIPxCYBDbFQs06jxgeV3kd/d/sok1pzV0nJRg==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/config-resolver": "^4.1.5",
- "@smithy/credential-provider-imds": "^4.0.7",
- "@smithy/node-config-provider": "^4.1.4",
- "@smithy/property-provider": "^4.0.5",
- "@smithy/smithy-client": "^4.4.10",
- "@smithy/types": "^4.3.2",
+ "@smithy/config-resolver": "^4.4.3",
+ "@smithy/credential-provider-imds": "^4.2.5",
+ "@smithy/node-config-provider": "^4.3.5",
+ "@smithy/property-provider": "^4.2.5",
+ "@smithy/smithy-client": "^4.9.7",
+ "@smithy/types": "^4.9.0",
"tslib": "^2.6.2"
},
"engines": {
@@ -2151,13 +2158,13 @@
}
},
"node_modules/@smithy/util-endpoints": {
- "version": "3.0.7",
- "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.0.7.tgz",
- "integrity": "sha512-klGBP+RpBp6V5JbrY2C/VKnHXn3d5V2YrifZbmMY8os7M6m8wdYFoO6w/fe5VkP+YVwrEktW3IWYaSQVNZJ8oQ==",
+ "version": "3.2.5",
+ "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.2.5.tgz",
+ "integrity": "sha512-3O63AAWu2cSNQZp+ayl9I3NapW1p1rR5mlVHcF6hAB1dPZUQFfRPYtplWX/3xrzWthPGj5FqB12taJJCfH6s8A==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/node-config-provider": "^4.1.4",
- "@smithy/types": "^4.3.2",
+ "@smithy/node-config-provider": "^4.3.5",
+ "@smithy/types": "^4.9.0",
"tslib": "^2.6.2"
},
"engines": {
@@ -2165,9 +2172,9 @@
}
},
"node_modules/@smithy/util-hex-encoding": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.0.0.tgz",
- "integrity": "sha512-Yk5mLhHtfIgW2W2WQZWSg5kuMZCVbvhFmC7rV4IO2QqnZdbEFPmQnCcGMAX2z/8Qj3B9hYYNjZOhWym+RwhePw==",
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.0.tgz",
+ "integrity": "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw==",
"license": "Apache-2.0",
"dependencies": {
"tslib": "^2.6.2"
@@ -2177,12 +2184,12 @@
}
},
"node_modules/@smithy/util-middleware": {
- "version": "4.0.5",
- "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.0.5.tgz",
- "integrity": "sha512-N40PfqsZHRSsByGB81HhSo+uvMxEHT+9e255S53pfBw/wI6WKDI7Jw9oyu5tJTLwZzV5DsMha3ji8jk9dsHmQQ==",
+ "version": "4.2.5",
+ "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.5.tgz",
+ "integrity": "sha512-6Y3+rvBF7+PZOc40ybeZMcGln6xJGVeY60E7jy9Mv5iKpMJpHgRE6dKy9ScsVxvfAYuEX4Q9a65DQX90KaQ3bA==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/types": "^4.3.2",
+ "@smithy/types": "^4.9.0",
"tslib": "^2.6.2"
},
"engines": {
@@ -2190,13 +2197,13 @@
}
},
"node_modules/@smithy/util-retry": {
- "version": "4.0.7",
- "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.0.7.tgz",
- "integrity": "sha512-TTO6rt0ppK70alZpkjwy+3nQlTiqNfoXja+qwuAchIEAIoSZW8Qyd76dvBv3I5bCpE38APafG23Y/u270NspiQ==",
+ "version": "4.2.5",
+ "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.5.tgz",
+ "integrity": "sha512-GBj3+EZBbN4NAqJ/7pAhsXdfzdlznOh8PydUijy6FpNIMnHPSMO2/rP4HKu+UFeikJxShERk528oy7GT79YiJg==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/service-error-classification": "^4.0.7",
- "@smithy/types": "^4.3.2",
+ "@smithy/service-error-classification": "^4.2.5",
+ "@smithy/types": "^4.9.0",
"tslib": "^2.6.2"
},
"engines": {
@@ -2204,18 +2211,18 @@
}
},
"node_modules/@smithy/util-stream": {
- "version": "4.2.4",
- "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.2.4.tgz",
- "integrity": "sha512-vSKnvNZX2BXzl0U2RgCLOwWaAP9x/ddd/XobPK02pCbzRm5s55M53uwb1rl/Ts7RXZvdJZerPkA+en2FDghLuQ==",
+ "version": "4.5.6",
+ "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.6.tgz",
+ "integrity": "sha512-qWw/UM59TiaFrPevefOZ8CNBKbYEP6wBAIlLqxn3VAIo9rgnTNc4ASbVrqDmhuwI87usnjhdQrxodzAGFFzbRQ==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/fetch-http-handler": "^5.1.1",
- "@smithy/node-http-handler": "^4.1.1",
- "@smithy/types": "^4.3.2",
- "@smithy/util-base64": "^4.0.0",
- "@smithy/util-buffer-from": "^4.0.0",
- "@smithy/util-hex-encoding": "^4.0.0",
- "@smithy/util-utf8": "^4.0.0",
+ "@smithy/fetch-http-handler": "^5.3.6",
+ "@smithy/node-http-handler": "^4.4.5",
+ "@smithy/types": "^4.9.0",
+ "@smithy/util-base64": "^4.3.0",
+ "@smithy/util-buffer-from": "^4.2.0",
+ "@smithy/util-hex-encoding": "^4.2.0",
+ "@smithy/util-utf8": "^4.2.0",
"tslib": "^2.6.2"
},
"engines": {
@@ -2223,9 +2230,9 @@
}
},
"node_modules/@smithy/util-uri-escape": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.0.0.tgz",
- "integrity": "sha512-77yfbCbQMtgtTylO9itEAdpPXSog3ZxMe09AEhm0dU0NLTalV70ghDZFR+Nfi1C60jnJoh/Re4090/DuZh2Omg==",
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.0.tgz",
+ "integrity": "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA==",
"license": "Apache-2.0",
"dependencies": {
"tslib": "^2.6.2"
@@ -2235,12 +2242,24 @@
}
},
"node_modules/@smithy/util-utf8": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.0.0.tgz",
- "integrity": "sha512-b+zebfKCfRdgNJDknHCob3O7FpeYQN6ZG6YLExMcasDHsCXlsXCEuiPZeLnJLpwa5dvPetGlnGCiMHuLwGvFow==",
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.0.tgz",
+ "integrity": "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/util-buffer-from": "^4.2.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/uuid": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@smithy/uuid/-/uuid-1.1.0.tgz",
+ "integrity": "sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/util-buffer-from": "^4.0.0",
"tslib": "^2.6.2"
},
"engines": {
@@ -2300,12 +2319,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/@types/uuid": {
- "version": "9.0.8",
- "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.8.tgz",
- "integrity": "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==",
- "license": "MIT"
- },
"node_modules/@ungap/structured-clone": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz",
@@ -2625,9 +2638,9 @@
"dev": true
},
"node_modules/bowser": {
- "version": "2.12.0",
- "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.12.0.tgz",
- "integrity": "sha512-HcOcTudTeEWgbHh0Y1Tyb6fdeR71m4b/QACf0D4KswGTsNeIJQmg38mRENZPAYPZvGFN3fk3604XbQEPdxXdKg==",
+ "version": "2.12.1",
+ "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.12.1.tgz",
+ "integrity": "sha512-z4rE2Gxh7tvshQ4hluIT7XcFrgLIQaw9X3A+kTTRdovCz5PMukm/0QC/BKSYPj3omF5Qfypn9O/c5kgpmvYUCw==",
"license": "MIT"
},
"node_modules/brace-expansion": {
@@ -4711,10 +4724,11 @@
"dev": true
},
"node_modules/js-yaml": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
- "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
+ "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"argparse": "^2.0.1"
},
@@ -6446,19 +6460,6 @@
"punycode": "^2.1.0"
}
},
- "node_modules/uuid": {
- "version": "9.0.1",
- "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
- "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==",
- "funding": [
- "https://github.com/sponsors/broofa",
- "https://github.com/sponsors/ctavan"
- ],
- "license": "MIT",
- "bin": {
- "uuid": "dist/bin/uuid"
- }
- },
"node_modules/version-guard": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/version-guard/-/version-guard-1.1.3.tgz",
@@ -6662,9 +6663,9 @@
}
},
"node_modules/xmllint-wasm": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/xmllint-wasm/-/xmllint-wasm-5.0.0.tgz",
- "integrity": "sha512-vHgxKtU1ooKxlvaB/YcUj+bO+c53EvPXrk9my83/SZhcnf8D32GbACPiC3kyrMLqJQJzpkzSykmh23Cv21fvlg==",
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/xmllint-wasm/-/xmllint-wasm-5.1.0.tgz",
+ "integrity": "sha512-6HCIJKAJWt96UzA2dgPXsnMuYQihD7U1DU9Tu3BdXqVruha1KV8nUofOxbw8f5ULgQGdNsJMwtX3dyaTHd9hQQ==",
"license": "MIT",
"engines": {
"node": ">=16"
diff --git a/package.json b/package.json
index acf774d..8adb999 100644
--- a/package.json
+++ b/package.json
@@ -3,7 +3,7 @@
"version": "3.0.0",
"description": "CAP XML service",
"engines": {
- "node": ">=20"
+ "node": "22.x"
},
"main": "handler.js",
"scripts": {
@@ -18,23 +18,23 @@
"author": "The Environment Agency",
"license": "OGL",
"dependencies": {
- "@aws-sdk/client-sns": "^3.873.0",
- "@xmldom/xmldom": "^0.8.11",
+ "@aws-sdk/client-sns": "3.932.0",
+ "@xmldom/xmldom": "0.8.11",
"feed": "5.1.0",
- "joi": "^18.0.1",
- "moment": "^2.30.1",
+ "joi": "18.0.1",
+ "moment": "2.30.1",
"pg": "8.16.3",
"sql-ts": "7.1.0",
- "xml-formatter": "^3.6.7",
+ "xml-formatter": "3.6.7",
"xml2js": "0.6.2",
- "xmllint-wasm": "^5.0.0"
+ "xmllint-wasm": "5.1.0"
},
"devDependencies": {
- "@hapi/code": "^9.0.3",
- "@hapi/lab": "^26.0.0",
- "aws-sdk-client-mock": "^4.1.0",
- "proxyquire": "^2.1.3",
- "sinon": "^21.0.0",
+ "@hapi/code": "9.0.3",
+ "@hapi/lab": "26.0.0",
+ "aws-sdk-client-mock": "4.1.0",
+ "proxyquire": "2.1.3",
+ "sinon": "21.0.0",
"standard": "17.1.2"
}
}
diff --git a/readme.md b/readme.md
index ff206d4..43119c8 100644
--- a/readme.md
+++ b/readme.md
@@ -20,7 +20,7 @@ This project provides CAP XML services through the use of AWS Lambda.
## Prerequisites
-- **Node.js 20** or higher
+- **Node.js 22** or higher
## Installing
diff --git a/test/lib/functions/data/capAlert.json b/test/lib/functions/data/capAlert.json
deleted file mode 100644
index 416bdcf..0000000
--- a/test/lib/functions/data/capAlert.json
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "bodyXml": "\r\n\r\n 4eb3b7350ab7aa443650fc9351f02940E\r\n www.gov.uk/environment-agency\r\n 2017-05-28T11:00:02-00:00\r\n Actual\r\n Alert\r\n Flood warning service\r\n Public\r\n \r\n en-GB\r\n Met\r\n \r\n ImmediateMinorLikely2017-05-29T11:00:02-00:00Environment AgencyArea descriptionpointspointspointsTargetAreaCode"
-}
\ No newline at end of file
diff --git a/test/lib/functions/data/capMessageId.json b/test/lib/functions/data/capMessageId.json
deleted file mode 100644
index e82a1c2..0000000
--- a/test/lib/functions/data/capMessageId.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "pathParameters": {
- "id": "4eb3b7350ab7aa443650fc9351f"
- }
-}
\ No newline at end of file
diff --git a/test/lib/functions/data/capUpdate.json b/test/lib/functions/data/capUpdate.json
deleted file mode 100644
index 8aa77c5..0000000
--- a/test/lib/functions/data/capUpdate.json
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "bodyXml": "\r\n\r\n 4eb3b7350ab7aa443650fc9351f02940E\r\n www.gov.uk/environment-agency\r\n 2017-05-28T11:00:02-00:00\r\n Actual\r\n Update\r\n Flood warning service\r\n Public\r\n \r\n en-GB\r\n Met\r\n \r\n ImmediateMinorLikely2017-05-29T11:00:02-00:00Environment AgencyArea descriptionpointsTargetAreaCode"
-}
\ No newline at end of file
diff --git a/test/lib/functions/data/nws-alert.xml b/test/lib/functions/data/nws-alert.xml
new file mode 100644
index 0000000..aaefc79
--- /dev/null
+++ b/test/lib/functions/data/nws-alert.xml
@@ -0,0 +1,34 @@
+
+ 4eb3b7350ab7aa443650fc9351f02940E
+ www.gov.uk/environment-agency
+ 2025-11-06T08:00:27+00:00
+ Actual
+ Alert
+ Flood warning service
+ Public
+
+
+ en-GB
+ Met
+
+ Immediate
+ Minor
+ Likely
+ 2025-11-16T08:00:27+00:00
+ Environment Agency
+
+
+ https://check-for-flooding.service.gov.uk
+ 0345 988 1188
+
+
+ 54.54509,-2.95255 54.54498,-2.95268...
+
+ TargetAreaCode
+
+
+
+
+
\ No newline at end of file
diff --git a/test/lib/functions/getMessage.js b/test/lib/functions/getMessage.js
index 0dc88d3..7714d07 100644
--- a/test/lib/functions/getMessage.js
+++ b/test/lib/functions/getMessage.js
@@ -4,153 +4,43 @@ const Lab = require('@hapi/lab')
const lab = exports.lab = Lab.script()
const Code = require('@hapi/code')
const sinon = require('sinon')
-const fs = require('fs')
-const path = require('path')
-let getMessage = require('../../../lib/functions/getMessage').getMessage
-const service = require('../../../lib/helpers/service')
-const getMessageXmlInvalid = fs.readFileSync(path.join(__dirname, 'data', 'getMessage-invalid.xml'), 'utf8')
-const getMessageXmlValid = fs.readFileSync(path.join(__dirname, 'data', 'getMessage-valid.xml'), 'utf8')
-let event
-
-lab.experiment('getMessage', () => {
- lab.beforeEach(() => {
- event = {
- pathParameters: {
- id: '4eb3b7350ab7aa443650fc9351f'
- }
- }
- // mock service
- service.getMessage = (query, params) => Promise.resolve({
- rows: [{
- getmessage: {
- alert: 'test'
- }
- }]
+const Proxyquire = require('proxyquire').noCallThru()
+
+lab.experiment('getMessage v1 wrapper', () => {
+ lab.test('Calls getMessage helper with v2=false', async () => {
+ const getMessageStub = sinon.stub().resolves({
+ statusCode: 200,
+ headers: { 'content-type': 'application/xml' },
+ body: 'test'
})
- })
- lab.test('Correct data test', async () => {
- const ret = await getMessage(event)
- Code.expect(ret.statusCode).to.equal(200)
- Code.expect(ret.headers['content-type']).to.equal('application/xml')
- Code.expect(ret.body).to.equal('test')
- })
+ const getMessage = Proxyquire('../../../lib/functions/getMessage', {
+ '../helpers/message': { getMessage: getMessageStub }
+ }).getMessage
- lab.test('No data found test', async () => {
- service.getMessage = (query, params) => Promise.resolve({
- rows: []
- })
+ const event = { pathParameters: { id: 'test123' } }
+ await getMessage(event)
- const err = await Code.expect(getMessage(event)).to.reject()
- Code.expect(err.message).to.equal('No message found')
+ Code.expect(getMessageStub.callCount).to.equal(1)
+ Code.expect(getMessageStub.calledWith(event, false)).to.be.true()
})
- lab.test('Incorrect database rows object', async () => {
- service.getMessage = (query, params) => Promise.resolve({
- rows: 1
- })
-
- const err = await Code.expect(getMessage(event)).to.reject()
- Code.expect(err.message).to.equal('No message found')
- })
-
- lab.test('Incorrect database rows object', async () => {
- service.getMessage = (query, params) => Promise.resolve({
- rows: [{}]
- })
-
- const err = await Code.expect(getMessage(event)).to.reject()
- Code.expect(err.message).to.equal('No message found')
- })
-
- lab.test('Missing database rows object', async () => {
- service.getMessage = (query, params) => Promise.resolve({
- no_rows: []
- })
-
- const err = await Code.expect(getMessage(event)).to.reject()
- Code.expect(err.message).to.equal('No message found')
- })
-
- lab.test('No database return', async () => {
- service.getMessage = (query, params) => {
- return new Promise((resolve, reject) => {
- resolve()
- })
+ lab.test('Returns the result from getMessage helper', async () => {
+ const expectedResult = {
+ statusCode: 200,
+ headers: { 'content-type': 'application/xml' },
+ body: 'v1 alert'
}
- const err = await Code.expect(getMessage(event)).to.reject()
- Code.expect(err.message).to.equal('No message found')
- })
-
- lab.test('Error test', async () => {
- service.getMessage = (query, params) => Promise.reject(new Error('test error'))
- const err = await Code.expect(getMessage(event)).to.reject()
- Code.expect(err.message).to.equal('test error')
- })
-
- lab.test('event validation test', async () => {
- event.id = {}
- await Code.expect(getMessage(event)).to.reject()
- })
+ const getMessageStub = sinon.stub().resolves(expectedResult)
- lab.test('event validation test 2', async () => {
- event = {}
- await Code.expect(getMessage(event)).to.reject()
- })
- lab.test('Invalid id format test', async () => {
- event.pathParameters.id = 'invalid_id_format'
+ const getMessage = Proxyquire('../../../lib/functions/getMessage', {
+ '../helpers/message': { getMessage: getMessageStub }
+ }).getMessage
- await Code.expect(getMessage(event)).to.reject()
- })
- lab.test('Valid id format test', async () => {
- event.pathParameters.id = 'a1b2c3'
+ const event = { pathParameters: { id: 'test123' } }
const result = await getMessage(event)
- const body = result.body
-
- Code.expect(body).to.equal('test')
- })
- lab.test('XsdSchema validation test: invalid alert', async () => {
- let consoleLogStub
- try {
- delete require.cache[require.resolve('../../../lib/functions/getMessage')]
- consoleLogStub = sinon.stub(console, 'log')
- const func = require('../../../lib/functions/getMessage').getMessage
- service.getMessage = () => Promise.resolve({
- rows: [{
- getmessage: {
- alert: getMessageXmlInvalid
- }
- }]
- })
- await func(event)
- Code.expect(consoleLogStub.callCount).to.equal(2)
- Code.expect(consoleLogStub.getCall(0).args[0]).to.equal('CAP get message failed validation')
- Code.expect(consoleLogStub.getCall(1).args[0]).to.equal('[{"rawMessage":"message.xml:19: Schemas validity error : Element \'{urn:oasis:names:tc:emergency:cap:1.2}geocode\': This element is not expected. Expected is ( {urn:oasis:names:tc:emergency:cap:1.2}areaDesc ).","message":"Schemas validity error : Element \'{urn:oasis:names:tc:emergency:cap:1.2}geocode\': This element is not expected. Expected is ( {urn:oasis:names:tc:emergency:cap:1.2}areaDesc ).","loc":{"fileName":"message.xml","lineNumber":19}}]')
- } finally {
- consoleLogStub.restore()
- getMessage = require('../../../lib/functions/getMessage').getMessage
- }
- })
- lab.test('XsdSchema validation test: valid alert', async () => {
- let consoleLogStub
- try {
- delete require.cache[require.resolve('../../../lib/functions/getMessage')]
- consoleLogStub = sinon.stub(console, 'log')
- const func = require('../../../lib/functions/getMessage').getMessage
- service.getMessage = () => Promise.resolve({
- rows: [{
- getmessage: {
- alert: getMessageXmlValid
- }
- }]
- })
- await func(event)
- Code.expect(consoleLogStub.callCount).to.equal(0)
- } finally {
- consoleLogStub.restore()
- getMessage = require('../../../lib/functions/getMessage').getMessage
- }
+ Code.expect(result).to.equal(expectedResult)
})
})
diff --git a/test/lib/functions/getMessageAtomValidation.js b/test/lib/functions/getMessageAtomValidation.js
index dabf520..0137470 100644
--- a/test/lib/functions/getMessageAtomValidation.js
+++ b/test/lib/functions/getMessageAtomValidation.js
@@ -8,9 +8,9 @@ const Proxyquire = require('proxyquire').noCallThru()
// Mock the service.getAllMessages function for validation experiment and tests
const loadHandlerWithValidateMock = (validateMock) => {
- return Proxyquire('../../../lib/functions/getMessagesAtom', {
+ return Proxyquire('../../../lib/helpers/messages', {
'xmllint-wasm': { validateXML: validateMock }
- }).getMessagesAtom
+ }).messages
}
lab.experiment('getMessagesAtom validation logging', () => {
diff --git a/test/lib/functions/getMessagesAtom.js b/test/lib/functions/getMessagesAtom.js
index fd601da..5ab9887 100644
--- a/test/lib/functions/getMessagesAtom.js
+++ b/test/lib/functions/getMessagesAtom.js
@@ -3,63 +3,42 @@
const Lab = require('@hapi/lab')
const lab = exports.lab = Lab.script()
const Code = require('@hapi/code')
-const getMessagesAtom = require('../../../lib/functions/getMessagesAtom').getMessagesAtom
-const service = require('../../../lib/helpers/service')
+const sinon = require('sinon')
+const Proxyquire = require('proxyquire').noCallThru()
-lab.experiment('getMessagesAtom', () => {
- lab.beforeEach(() => {
- // mock database query
- service.getAllMessages = (query) => {
- return new Promise((resolve, reject) => {
- resolve({
- rows: [{
- fwis_code: 'test_fwis_code',
- alert: 'test',
- sent: new Date(),
- identifier: '4eb3b7350ab7aa443650fc9351f'
- }]
- })
- })
- }
- })
+lab.experiment('getMessagesAtom v1 wrapper', () => {
+ lab.test('Calls messages helper with v2=false', async () => {
+ const messagesStub = sinon.stub().resolves({
+ statusCode: 200,
+ headers: { 'content-type': 'application/xml' },
+ body: 'test'
+ })
- lab.test('Correct data test', async () => {
- const ret = await getMessagesAtom({})
- Code.expect(ret.statusCode).to.equal(200)
- Code.expect(ret.headers['content-type']).to.equal('application/xml')
- })
+ const getMessagesAtom = Proxyquire('../../../lib/functions/getMessagesAtom', {
+ '../helpers/messages': { messages: messagesStub }
+ }).getMessagesAtom
- lab.test('Bad rows returned', async () => {
- service.getAllMessages = (query) => {
- return new Promise((resolve, reject) => {
- resolve({
- rows: 1
- })
- })
- }
- const ret = await getMessagesAtom({})
- Code.expect(ret.statusCode).to.equal(200)
- Code.expect(ret.headers['content-type']).to.equal('application/xml')
- })
+ await getMessagesAtom()
- lab.test('No return from database', async () => {
- service.getAllMessages = (query) => {
- return new Promise((resolve, reject) => {
- resolve()
- })
- }
- const ret = await getMessagesAtom({})
- Code.expect(ret.statusCode).to.equal(200)
- Code.expect(ret.headers['content-type']).to.equal('application/xml')
+ Code.expect(messagesStub.callCount).to.equal(1)
+ Code.expect(messagesStub.calledWith(false)).to.be.true()
})
- lab.test('Error test', async () => {
- service.getAllMessages = (query) => {
- return new Promise((resolve, reject) => {
- reject(new Error('test error'))
- })
+ lab.test('Returns the result from messages helper', async () => {
+ const expectedResult = {
+ statusCode: 200,
+ headers: { 'content-type': 'application/xml' },
+ body: 'v1 feed'
}
- const err = await Code.expect(getMessagesAtom({})).to.reject()
- Code.expect(err.message).to.equal('test error')
+
+ const messagesStub = sinon.stub().resolves(expectedResult)
+
+ const getMessagesAtom = Proxyquire('../../../lib/functions/getMessagesAtom', {
+ '../helpers/messages': { messages: messagesStub }
+ }).getMessagesAtom
+
+ const result = await getMessagesAtom()
+
+ Code.expect(result).to.equal(expectedResult)
})
})
diff --git a/test/lib/functions/processMessage.js b/test/lib/functions/processMessage.js
index 1d062b4..e8aec78 100644
--- a/test/lib/functions/processMessage.js
+++ b/test/lib/functions/processMessage.js
@@ -4,24 +4,141 @@ const Lab = require('@hapi/lab')
const lab = exports.lab = Lab.script()
const Code = require('@hapi/code')
const sinon = require('sinon')
+const fs = require('fs')
+const path = require('path')
+const xml2js = require('xml2js')
const processMessage = require('../../../lib/functions/processMessage').processMessage
const service = require('../../../lib/helpers/service')
const aws = require('../../../lib/helpers/aws')
-const moment = require('moment')
-let capAlert
-let capUpdate
-
+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') }
const ORIGINAL_ENV = process.env
-
+let clock
const tomorrow = new Date(new Date().getTime() + (24 * 60 * 60 * 1000))
-const yesterday = new Date(new Date().getTime() - (24 * 60 * 60 * 1000))
+const identifier = '4eb3b7350ab7aa443650fc9351f02940E'
+const identifierV2 = `2.49.0.0.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'
+
+// ***********************************************************
+// Helper functions
+// ***********************************************************
+const expectResponse = (response, putQuery, severity = 'Minor', status = 'Test', msgType = 'Alert', references = false, previousReferences = false, quickdialNumber = true) => {
+ expectResponseAndPutQuery(response, putQuery, status, msgType, references, previousReferences)
+ expectMessageV1(new Message(putQuery.values[3]), severity, status, references, previousReferences, quickdialNumber)
+ expectMessageV2(new Message(putQuery.values[10]), severity, status, references, previousReferences, quickdialNumber)
+}
+
+const expectResponseAndPutQuery = (response, putQuery, status, msgType, references, previousReferences) => {
+ // test response
+ Code.expect(response.statusCode).to.equal(200)
+ Code.expect(response.body.identifier).to.equal(identifier)
+ Code.expect(response.body.fwisCode).to.equal('TESTAREA1')
+ Code.expect(response.body.sent).to.equal('2025-11-06T08:00:27+00:00')
+ Code.expect(response.body.expires).to.equal('2025-11-16T08:00:27+00:00')
+ Code.expect(response.body.status).to.equal(status)
+
+ // test putquery
+ Code.expect(putQuery.text).to.equal('INSERT INTO "messages" ("identifier", "msg_type", "references", "alert", "fwis_code", "expires", "sent", "created", "identifier_v2", "references_v2", "alert_v2") VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)')
+ Code.expect(putQuery.values[0]).to.equal(identifier)
+ Code.expect(putQuery.values[1]).to.equal(msgType)
+ if (references) {
+ Code.expect(putQuery.values[2]).to.equal(previousReferences ? `${referencesV1} ${referencesV1}` : referencesV1)
+ } else {
+ Code.expect(putQuery.values[2]).to.be.empty()
+ }
+ Code.expect(putQuery.values[3]).to.not.be.empty()
+ Code.expect(putQuery.values[4]).to.equal('TESTAREA1')
+ Code.expect(putQuery.values[5]).to.equal('2025-11-16T08:00:27+00:00')
+ Code.expect(putQuery.values[6]).to.equal('2025-11-06T08:00:27+00:00')
+ Code.expect(putQuery.values[7]).to.equal('2020-01-01T00:00:00.000Z')
+ Code.expect(putQuery.values[8]).to.equal(identifierV2)
+ if (references) {
+ Code.expect(putQuery.values[9]).to.equal(previousReferences ? `${referencesV2} ${referencesV2}` : referencesV2)
+ } else {
+ Code.expect(putQuery.values[9]).to.be.empty()
+ }
+ Code.expect(putQuery.values[10]).to.not.be.empty()
+}
+
+const expectMessageV1 = (message, severity, status, references, previousReferences, quickdialNumber) => {
+ Code.expect(message.identifier).to.equal(identifier)
+ Code.expect(message.status).to.equal(status)
+ Code.expect(message.code).to.equal('')
+ if (references) {
+ Code.expect(message.references).to.equal(previousReferences ? `${referencesV1} ${referencesV1}` : referencesV1)
+ } else {
+ Code.expect(message.references).to.be.empty()
+ }
+ Code.expect(message.event).to.equal('Update')
+ Code.expect(message.severity).to.equal(severity)
+ Code.expect(message.onset).to.equal('')
+ Code.expect(message.headline).to.equal('')
+ Code.expect(message.instruction).not.to.contain('https://check-for-flooding.service.gov.uk/target-area/TESTAREA1')
+ if (quickdialNumber) {
+ Code.expect(message.instruction).not.to.contain('- call Floodline on 0345 988 1188, using quickdial code 210010')
+ Code.expect(message.instruction).to.contain('- For access to flood warning information offline call Floodline on 0345 988 1188 using quickdial code: 210010.')
+ } else {
+ Code.expect(message.instruction).not.to.contain('- call Floodline on 0345 988 1188, using quickdial code 210010')
+ Code.expect(message.instruction).to.contain('- For access to flood warning information offline call Floodline on 0345 988 1188 using')
+ }
+}
+
+const expectMessageV2 = (message, severity, status, references, previousReferences, quickdialNumber) => {
+ const normalize = s => s.replace(/\r\n/g, '\n')
+ const messageString = normalize(message.toString())
+ const mapping = v2MessageMapping[severity]
+ // Test message fields updated for message V2
+ Code.expect(message.identifier).to.equal(identifierV2)
+ Code.expect(message.status).to.equal(status)
+ Code.expect(message.code).to.equal(code)
+ if (references) {
+ Code.expect(message.references).to.equal(previousReferences ? `${referencesV2} ${referencesV2}` : referencesV2)
+ } else {
+ Code.expect(message.references).to.be.empty()
+ }
+ Code.expect(message.event).to.equal(`${mapping.description}: Rivers Lowther and Eamont`)
+ Code.expect(message.severity).to.equal(mapping.severity)
+ Code.expect(message.onset).to.equal(message.sent)
+ Code.expect(message.headline).to.equal(`${mapping.headline}: Rivers Lowther and Eamont`)
+ Code.expect(message.instruction).to.contain('https://check-for-flooding.service.gov.uk/target-area/TESTAREA1')
+ if (quickdialNumber) {
+ Code.expect(message.instruction).to.contain('- call Floodline on 0345 988 1188, using quickdial code 210010')
+ Code.expect(message.instruction).not.to.contain('- For access to flood warning information offline call Floodline on 0345 988 1188 using quickdial code: 210010.')
+ } else {
+ Code.expect(message.instruction).not.to.contain('- call Floodline on 0345 988 1188, using quickdial code 210010')
+ Code.expect(message.instruction).not.to.contain('- For access to flood warning information offline call Floodline on 0345 988 1188 using')
+ }
+ // Test for parameters
+ Code.expect(messageString).to.contain(`
+ awareness_level
+ ${mapping.awarenessLevel}
+ `)
+ Code.expect(messageString).to.contain(`
+ awareness_type
+ 12; Flooding
+ `)
+ Code.expect(messageString).to.contain(`
+ impacts
+ ${mapping.impact}
+ `)
+ Code.expect(messageString).to.contain(`
+ use_polygon_over_geocode
+ true
+ `)
+ Code.expect(messageString).to.contain(`
+ uk_ea_ta_code
+ TESTAREA1
+ `)
+}
+// ***********************************************************
lab.experiment('processMessage', () => {
lab.beforeEach(() => {
+ clock = sinon.useFakeTimers(new Date('2020-01-01T00:00:00Z').getTime())
process.env = { ...ORIGINAL_ENV }
- capAlert = require('./data/capAlert.json')
- capUpdate = require('./data/capUpdate.json')
-
// mock services
service.putMessage = (query) => {
return new Promise((resolve, reject) => {
@@ -37,187 +154,208 @@ lab.experiment('processMessage', () => {
})
lab.afterEach(() => {
+ clock.restore()
sinon.restore()
})
- lab.test('Correct data test with no previous alert on test', async () => {
+ lab.test('Correct data test with no previous alert on test (empty array from db)', async () => {
service.getLastMessage = (id) => Promise.resolve({
rows: []
})
+ let putQuery
service.putMessage = (query) => Promise.resolve().then(() => {
- Code.expect(query.values[2]).to.be.empty()
- Code.expect(query.values[1]).to.equal('Alert')
+ putQuery = query
})
- const ret = await processMessage(capAlert)
- Code.expect(ret.statusCode).to.equal(200)
- Code.expect(ret.body.identifier).to.equal('4eb3b7350ab7aa443650fc9351f02940E')
- Code.expect(ret.body.fwisCode).to.equal('TESTAREA1')
- Code.expect(ret.body.sent).to.equal('2017-05-28T11:00:02-00:00')
- Code.expect(ret.body.expires).to.equal('2017-05-29T11:00:02-00:00')
- Code.expect(ret.body.status).to.equal('Test')
+ // do alert and test output xml
+ let response = await processMessage(nwsAlert)
+ expectResponse(response, putQuery, 'Minor')
+
+ // do warning and test output xml
+ response = await processMessage({ bodyXml: nwsAlert.bodyXml.replace('Minor', 'Moderate') })
+ expectResponse(response, putQuery, 'Moderate')
+
+ // do severe warning and test output xml
+ response = await processMessage({ bodyXml: nwsAlert.bodyXml.replace('Minor', 'Severe') })
+ expectResponse(response, putQuery, 'Severe')
})
- lab.test('Correct data test with no previous alert on test 2', async () => {
- service.getLastMessage = (id) => {
- return new Promise((resolve, reject) => {
+ lab.test('Correct data test with no previous alert on test 2 (nothing resolved from db)', async () => {
+ service.getLastMessage = () => {
+ return new Promise((resolve) => {
resolve()
})
}
- service.putMessage = (query) => {
- return new Promise((resolve, reject) => {
- Code.expect(query.values[2]).to.be.empty()
- Code.expect(query.values[1]).to.equal('Alert')
- resolve()
- })
- }
- const ret = await processMessage(capAlert)
- Code.expect(ret.statusCode).to.equal(200)
- Code.expect(ret.body.identifier).to.equal('4eb3b7350ab7aa443650fc9351f02940E')
- Code.expect(ret.body.fwisCode).to.equal('TESTAREA1')
- Code.expect(ret.body.sent).to.equal('2017-05-28T11:00:02-00:00')
- Code.expect(ret.body.expires).to.equal('2017-05-29T11:00:02-00:00')
- Code.expect(ret.body.status).to.equal('Test')
+ let putQuery
+ service.putMessage = (query) => Promise.resolve().then(() => {
+ putQuery = query
+ })
+ // do alert and test output xml
+ let response = await processMessage(nwsAlert)
+ expectResponse(response, putQuery, 'Minor')
+
+ // do warning and test output xml
+ response = await processMessage({ bodyXml: nwsAlert.bodyXml.replace('Minor', 'Moderate') })
+ expectResponse(response, putQuery, 'Moderate')
+
+ // do severe warning and test output xml
+ response = await processMessage({ bodyXml: nwsAlert.bodyXml.replace('Minor', 'Severe') })
+ expectResponse(response, putQuery, 'Severe')
})
- lab.test('Correct data test with no previous alert on production', async () => {
+ lab.test('Correct data test with no previous alert on production, tests status switches to Actual', async () => {
process.env.stage = 'prd'
+ let putQuery
+ service.putMessage = (query) => Promise.resolve().then(() => {
+ putQuery = query
+ })
- service.putMessage = (query) => {
- return new Promise((resolve, reject) => {
- // Check that reference field is blank
- Code.expect(query.values[2]).to.be.empty()
- Code.expect(query.values[1]).to.equal('Alert')
- resolve()
- })
- }
+ // do alert and test output xml
+ let response = await processMessage(nwsAlert)
+ expectResponse(response, putQuery, 'Minor', 'Actual')
- const ret = await processMessage(capAlert)
- Code.expect(ret.statusCode).to.equal(200)
- Code.expect(ret.body.identifier).to.equal('4eb3b7350ab7aa443650fc9351f02940E')
- Code.expect(ret.body.fwisCode).to.equal('TESTAREA1')
- Code.expect(ret.body.sent).to.equal('2017-05-28T11:00:02-00:00')
- Code.expect(ret.body.expires).to.equal('2017-05-29T11:00:02-00:00')
- Code.expect(ret.body.status).to.not.equal('Test')
- Code.expect(ret.body.status).to.equal('Actual')
+ // do warning and test output xml
+ response = await processMessage({ bodyXml: nwsAlert.bodyXml.replace('Minor', 'Moderate') })
+ expectResponse(response, putQuery, 'Moderate', 'Actual')
+
+ // do severe warning and test output xml
+ response = await processMessage({ bodyXml: nwsAlert.bodyXml.replace('Minor', 'Severe') })
+ expectResponse(response, putQuery, 'Severe', 'Actual')
})
lab.test('Correct data test with active alert on test', async () => {
- process.env.stage = 'prd'
-
service.getLastMessage = (id) => Promise.resolve({
rows: [{
id: '51',
identifier: '4eb3b7350ab7aa443650fc9351f2',
expires: tomorrow,
- sent: yesterday
+ sent: '2020-01-01T00:00:00Z',
+ identifier_v2: identifierV2
}]
})
+ let putQuery
+
service.putMessage = (query) => Promise.resolve().then(() => {
- Code.expect(query.values[2]).to.not.be.empty()
- Code.expect(query.values[2]).to.contain(yesterday.toISOString().substring(0, yesterday.toISOString().length - 5))
- Code.expect(query.values[1]).to.equal('Update')
+ putQuery = query
})
- const ret = await processMessage(capAlert)
- Code.expect(ret.statusCode).to.equal(200)
- Code.expect(ret.body.identifier).to.equal('4eb3b7350ab7aa443650fc9351f02940E')
- Code.expect(ret.body.fwisCode).to.equal('TESTAREA1')
- Code.expect(ret.body.sent).to.equal('2017-05-28T11:00:02-00:00')
- Code.expect(ret.body.expires).to.equal('2017-05-29T11:00:02-00:00')
- Code.expect(ret.body.status).to.not.equal('Test')
- Code.expect(ret.body.status).to.equal('Actual')
+ // do alert and test output xml
+ let response = await processMessage(nwsAlert)
+ expectResponse(response, putQuery, 'Minor', 'Test', 'Update', true)
+
+ // do warning and test output xml
+ response = await processMessage({ bodyXml: nwsAlert.bodyXml.replace('Minor', 'Moderate') })
+ expectResponse(response, putQuery, 'Moderate', 'Test', 'Update', true)
+
+ // do severe warning and test output xml
+ response = await processMessage({ bodyXml: nwsAlert.bodyXml.replace('Minor', 'Severe') })
+ expectResponse(response, putQuery, 'Severe', 'Test', 'Update', true)
})
- lab.test('Correct data test with active alert on test with prexisting references field', async () => {
+ lab.test('Correct alert data test with an active on production', async () => {
process.env.stage = 'prd'
service.getLastMessage = (id) => Promise.resolve({
rows: [{
id: '51',
identifier: '4eb3b7350ab7aa443650fc9351f2',
+ sent: '2020-01-01T00:00:00Z',
expires: tomorrow,
- sent: yesterday,
- references: yesterday.toISOString()
+ msgType: 'Alert',
+ identifier_v2: identifierV2
}]
})
-
+ let putQuery
service.putMessage = (query) => Promise.resolve().then(() => {
- Code.expect(query.values[2]).to.not.be.empty()
- Code.expect(query.values[2]).to.contain(yesterday.toISOString().substring(0, yesterday.toISOString().length - 5))
- Code.expect(query.values[1]).to.equal('Update')
+ putQuery = query
})
- const ret = await processMessage(capAlert)
- Code.expect(ret.statusCode).to.equal(200)
- Code.expect(ret.body.identifier).to.equal('4eb3b7350ab7aa443650fc9351f02940E')
- Code.expect(ret.body.fwisCode).to.equal('TESTAREA1')
- Code.expect(ret.body.sent).to.equal('2017-05-28T11:00:02-00:00')
- Code.expect(ret.body.expires).to.equal('2017-05-29T11:00:02-00:00')
- Code.expect(ret.body.status).to.not.equal('Test')
- Code.expect(ret.body.status).to.equal('Actual')
- })
+ // do alert and test output xml
+ let response = await processMessage(nwsAlert)
+ expectResponse(response, putQuery, 'Minor', 'Actual', 'Update', true)
- lab.test('Correct alert data test with an active on production', async () => {
- process.env.stage = 'prd'
+ // do warning and test output xml
+ response = await processMessage({ bodyXml: nwsAlert.bodyXml.replace('Minor', 'Moderate') })
+ expectResponse(response, putQuery, 'Moderate', 'Actual', 'Update', true)
+ // do severe warning and test output xml
+ response = await processMessage({ bodyXml: nwsAlert.bodyXml.replace('Minor', 'Severe') })
+ expectResponse(response, putQuery, 'Severe', 'Actual', 'Update', true)
+ })
+
+ lab.test('Edge cases: Correct data test with active alert on test including references and no quickdial code', async () => {
service.getLastMessage = (id) => Promise.resolve({
rows: [{
id: '51',
identifier: '4eb3b7350ab7aa443650fc9351f2',
- sent: yesterday,
+ references: referencesV1,
expires: tomorrow,
- msgType: 'Alert'
+ sent: '2020-01-01T00:00:00Z',
+ identifier_v2: identifierV2,
+ references_v2: referencesV2
}]
})
+ let putQuery
+
service.putMessage = (query) => Promise.resolve().then(() => {
- Code.expect(query.values[2]).to.not.be.empty()
- Code.expect(query.values[1]).to.equal('Update')
- Code.expect(query.values[2]).to.contain(yesterday.toISOString().substring(0, yesterday.toISOString().length - 5))
+ putQuery = query
})
- const ret = await processMessage(capAlert)
- Code.expect(ret.statusCode).to.equal(200)
- Code.expect(ret.body.identifier).to.equal('4eb3b7350ab7aa443650fc9351f02940E')
- Code.expect(ret.body.fwisCode).to.equal('TESTAREA1')
- Code.expect(ret.body.sent).to.equal('2017-05-28T11:00:02-00:00')
- Code.expect(ret.body.expires).to.equal('2017-05-29T11:00:02-00:00')
- Code.expect(ret.body.status).to.not.equal('Test')
- Code.expect(ret.body.status).to.equal('Actual')
+ // strip out quick dial code
+ const alert = { bodyXml: nwsAlert.bodyXml.replace('quickdial code: 210010.', '') }
+
+ // do alert and test output xml
+ let response = await processMessage(alert)
+ expectResponse(response, putQuery, 'Minor', 'Test', 'Update', true, true, false)
+
+ // do warning and test output xml
+ 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
+ response = await processMessage({ bodyXml: alert.bodyXml.replace('Minor', 'Severe') })
+ expectResponse(response, putQuery, 'Severe', 'Test', 'Update', true, true, false)
})
- lab.test('Correct update data test with an active on production', async () => {
+ lab.test('Edge cases: Correct alert data test with an active on production including references and no quickdial code', async () => {
process.env.stage = 'prd'
service.getLastMessage = (id) => Promise.resolve({
rows: [{
id: '51',
identifier: '4eb3b7350ab7aa443650fc9351f2',
- sent: yesterday,
+ references: referencesV1,
+ sent: '2020-01-01T00:00:00Z',
expires: tomorrow,
- msgType: 'Alert'
+ msgType: 'Alert',
+ identifier_v2: identifierV2,
+ references_v2: referencesV2
}]
})
-
+ let putQuery
service.putMessage = (query) => Promise.resolve().then(() => {
- Code.expect(query.values[2]).to.not.be.empty()
- Code.expect(query.values[1]).to.equal('Update')
- Code.expect(query.values[2]).to.contain(yesterday.toISOString().substring(0, yesterday.toISOString().length - 5))
+ putQuery = query
})
- const ret = await processMessage(capUpdate)
- Code.expect(ret.statusCode).to.equal(200)
- Code.expect(ret.body.identifier).to.equal('4eb3b7350ab7aa443650fc9351f02940E')
- Code.expect(ret.body.fwisCode).to.equal('TESTAREA1')
- Code.expect(ret.body.sent).to.equal('2017-05-28T11:00:02-00:00')
- Code.expect(ret.body.expires).to.equal('2017-05-29T11:00:02-00:00')
- Code.expect(ret.body.status).to.not.equal('Test')
- Code.expect(ret.body.status).to.equal('Actual')
+ // do alert and test output xml
+ let response = await processMessage(nwsAlert)
+ expectResponse(response, putQuery, 'Minor', 'Actual', 'Update', true, true)
+
+ // do warning and test output xml
+ response = await processMessage({ bodyXml: nwsAlert.bodyXml.replace('Minor', 'Moderate') })
+ expectResponse(response, putQuery, 'Moderate', 'Actual', 'Update', true, true)
+
+ // do severe warning and test output xml
+ response = await processMessage({ bodyXml: nwsAlert.bodyXml.replace('Minor', 'Severe') })
+ expectResponse(response, putQuery, 'Severe', 'Actual', 'Update', true, true)
})
+ // ***********************************************************
+ // Sad path tests
+ // ***********************************************************
lab.test('Bad data test', async () => {
sinon.stub(aws.email, 'publishMessage').callsFake((message) => {
return new Promise((resolve, reject) => {
@@ -234,47 +372,16 @@ lab.experiment('processMessage', () => {
lab.test('Database error', async () => {
service.putMessage = (query) => Promise.reject(new Error('unit test error'))
- const err = await Code.expect(processMessage(capAlert)).to.reject()
+ const err = await Code.expect(processMessage(nwsAlert)).to.reject()
Code.expect(err.message).to.equal('unit test error')
})
lab.test('Database error 2', async () => {
service.getLastMessage = (id) => Promise.reject(new Error('unit test error'))
- const err = await Code.expect(processMessage(capAlert)).to.reject()
+ const err = await Code.expect(processMessage(nwsAlert)).to.reject()
Code.expect(err.message).to.equal('unit test error')
})
-
- lab.test('Correct data test for processMessage where previous message is active and has reference', async () => {
- process.env.stage = 'prd'
- // Replace the trivial promise with Promise.resolve
- service.getLastMessage = (id) => Promise.resolve({
- rows: [{
- id: '51',
- identifier: '4eb3b7350ab7aa443650fc9351f2',
- expires: tomorrow,
- sent: yesterday,
- references: 'Previous_Active_Message'
- }]
- })
-
- service.putMessage = (query) => Promise.resolve().then(() => {
- const lastDate = moment(yesterday).utc().format('YYYY-MM-DDTHH:mm:ssZ')
- Code.expect(query.values[2]).to.not.be.empty()
- Code.expect(query.values[1]).to.equal('Update')
- Code.expect(query.values[2]).to.contain(`Previous_Active_Message www.gov.uk/environment-agency,4eb3b7350ab7aa443650fc9351f2,${lastDate}`)
- Code.expect(query.values[2]).to.not.contain('00:00+00:00')
- })
-
- const ret = await processMessage(capAlert)
- Code.expect(ret.statusCode).to.equal(200)
- Code.expect(ret.body.identifier).to.equal('4eb3b7350ab7aa443650fc9351f02940E')
- Code.expect(ret.body.fwisCode).to.equal('TESTAREA1')
- Code.expect(ret.body.sent).to.equal('2017-05-28T11:00:02-00:00')
- Code.expect(ret.body.expires).to.equal('2017-05-29T11:00:02-00:00')
- Code.expect(ret.body.status).to.not.equal('Test')
- Code.expect(ret.body.status).to.equal('Actual')
- })
lab.test('Invalid bodyXml format test', async () => {
// Set bodyXml to an invalid value (e.g., null, undefined, or an object)
const invalidBodyXml = null
@@ -282,9 +389,10 @@ lab.experiment('processMessage', () => {
// Expect the processMessage function to reject due to validation failure
await Code.expect(processMessage({ bodyXml: invalidBodyXml })).to.reject()
})
- lab.test('Valid bodyXml format test', async () => {
- const validBodyXml = capAlert.bodyXml
-
- await Code.expect(processMessage({ bodyXml: validBodyXml })).to.not.reject()
+ lab.test('Handles xml2js error', async () => {
+ sinon.stub(xml2js, 'parseString').callsFake((xml, callback) => {
+ callback(new Error('xml2js parse error'))
+ })
+ await Code.expect(processMessage(nwsAlert)).to.reject()
})
})
diff --git a/test/lib/functions/processMessageValidation.js b/test/lib/functions/processMessageValidation.js
index 806b933..b288074 100644
--- a/test/lib/functions/processMessageValidation.js
+++ b/test/lib/functions/processMessageValidation.js
@@ -5,7 +5,9 @@ const lab = exports.lab = Lab.script()
const Code = require('@hapi/code')
const Proxyquire = require('proxyquire').noCallThru()
const sinon = require('sinon')
-const capAlert = require('./data/capAlert.json')
+const fs = require('node:fs')
+const path = require('node:path')
+const nwsAlert = { bodyXml: fs.readFileSync(path.join(__dirname, 'data', 'nws-alert.xml'), 'utf8') }
const fakeService = {
getLastMessage: async () => ({ rows: [] }),
@@ -34,9 +36,9 @@ lab.experiment('processMessage validation logging', () => {
const validateMock = async () => ({ errors: [{ message: 'oops' }] })
const processMessage = loadWithValidateMock(validateMock)
- await Code.expect(processMessage(capAlert))
+ await Code.expect(processMessage(nwsAlert))
.to
- .reject('[{"message":"oops"}]')
+ .reject('[{"message":"oops"},{"message":"oops"}]')
Code.expect(fakeAws.email.publishMessage.callCount).to.equal(0)
})
@@ -45,9 +47,9 @@ lab.experiment('processMessage validation logging', () => {
const validateMock = async () => ({ errors: [{ message: 'oops' }] })
const processMessage = loadWithValidateMock(validateMock)
- await Code.expect(processMessage(capAlert))
+ await Code.expect(processMessage(nwsAlert))
.to
- .reject('[500] [{"message":"oops"}]')
+ .reject('[500] [{"message":"oops"},{"message":"oops"}]')
Code.expect(fakeAws.email.publishMessage.callCount).to.equal(1)
})
@@ -61,7 +63,7 @@ lab.experiment('processMessage validation logging', () => {
console.log = (msg) => logs.push(String(msg))
try {
- await processMessage(capAlert)
+ await processMessage(nwsAlert)
Code.expect(logs).to.include('Finished processing CAP message: 4eb3b7350ab7aa443650fc9351f02940E for TESTAREA1')
Code.expect(logs.some(l => l.includes('failed validation'))).to.be.false()
} finally {
@@ -78,12 +80,12 @@ lab.experiment('processMessage validation logging', () => {
'../helpers/aws': awsStub
}).processMessage
- const ret = await processMessage(capAlert)
+ const ret = await processMessage(nwsAlert)
Code.expect(ret.statusCode).to.equal(200)
Code.expect(ret.body.identifier).to.equal('4eb3b7350ab7aa443650fc9351f02940E')
Code.expect(ret.body.fwisCode).to.equal('TESTAREA1')
- Code.expect(ret.body.sent).to.equal('2017-05-28T11:00:02-00:00')
- Code.expect(ret.body.expires).to.equal('2017-05-29T11:00:02-00:00')
+ Code.expect(ret.body.sent).to.equal('2025-11-06T08:00:27+00:00')
+ Code.expect(ret.body.expires).to.equal('2025-11-16T08:00:27+00:00')
Code.expect(ret.body.status).to.equal('Test')
Code.expect(awsStub.email.publishMessage.callCount).to.equal(0)
@@ -134,7 +136,7 @@ lab.experiment('processMessage validation logging', () => {
.reject()
const errors = JSON.parse(ret.message.replace('[500] ', ''))
- Code.expect(errors.length).to.equal(15)
+ Code.expect(errors.length).to.equal(35)
// Helper to generate message asserts below
// errors.forEach((er, i) => {
// console.log(`Code.expect(errors[${i}].message).to.equal('${er.message.replace(/'/g, "\\'")}')`)
@@ -152,9 +154,29 @@ lab.experiment('processMessage validation logging', () => {
Code.expect(errors[9].message).to.equal('"alert.sender[0]" must be [www.gov.uk/environment-agency]')
Code.expect(errors[10].message).to.equal('"alert.sender[0]" is not allowed to be empty')
Code.expect(errors[11].message).to.equal('"alert.source[0]" is not allowed to be empty')
- Code.expect(errors[12].message).to.equal('"alert.info[0].senderName[0]" is not allowed to be empty')
- Code.expect(errors[13].message).to.equal('"alert.info[0].area[0].areaDesc[0]" is not allowed to be empty')
- Code.expect(errors[14].message).to.equal('"alert.info[0].area[0].polygon[0]" is not allowed to be empty')
+ Code.expect(errors[12].message).to.equal('"alert.info[0].severity[0]" must be one of [Extreme, Severe, Moderate, Minor]')
+ Code.expect(errors[13].message).to.equal('"alert.info[0].severity[0]" is not allowed to be empty')
+ Code.expect(errors[14].message).to.equal('"alert.info[0].senderName[0]" is not allowed to be empty')
+ Code.expect(errors[15].message).to.equal('"alert.info[0].area[0].areaDesc[0]" is not allowed to be empty')
+ Code.expect(errors[16].message).to.equal('"alert.info[0].area[0].polygon[0]" is not allowed to be empty')
+ // v2 errors
+ Code.expect(errors[17].message).to.equal('Schemas validity error : Element \'{urn:oasis:names:tc:emergency:cap:1.2}sent\': \'\' is not a valid value of the local atomic type.')
+ Code.expect(errors[18].message).to.equal('Schemas validity error : Element \'{urn:oasis:names:tc:emergency:cap:1.2}msgType\': [facet \'enumeration\'] The value \'\' is not an element of the set {\'Alert\', \'Update\', \'Cancel\', \'Ack\', \'Error\'}.')
+ Code.expect(errors[19].message).to.equal('Schemas validity error : Element \'{urn:oasis:names:tc:emergency:cap:1.2}scope\': [facet \'enumeration\'] The value \'\' is not an element of the set {\'Public\', \'Restricted\', \'Private\'}.')
+ Code.expect(errors[20].message).to.equal('Schemas validity error : Element \'{urn:oasis:names:tc:emergency:cap:1.2}category\': [facet \'enumeration\'] The value \'\' is not an element of the set {\'Geo\', \'Met\', \'Safety\', \'Security\', \'Rescue\', \'Fire\', \'Health\', \'Env\', \'Transport\', \'Infra\', \'CBRNE\', \'Other\'}.')
+ Code.expect(errors[21].message).to.equal('Schemas validity error : Element \'{urn:oasis:names:tc:emergency:cap:1.2}urgency\': [facet \'enumeration\'] The value \'\' is not an element of the set {\'Immediate\', \'Expected\', \'Future\', \'Past\', \'Unknown\'}.')
+ Code.expect(errors[22].message).to.equal('Schemas validity error : Element \'{urn:oasis:names:tc:emergency:cap:1.2}severity\': [facet \'enumeration\'] The value \'\' is not an element of the set {\'Extreme\', \'Severe\', \'Moderate\', \'Minor\', \'Unknown\'}.')
+ Code.expect(errors[23].message).to.equal('Schemas validity error : Element \'{urn:oasis:names:tc:emergency:cap:1.2}certainty\': [facet \'enumeration\'] The value \'\' is not an element of the set {\'Observed\', \'Likely\', \'Possible\', \'Unlikely\', \'Unknown\'}.')
+ Code.expect(errors[24].message).to.equal('Schemas validity error : Element \'{urn:oasis:names:tc:emergency:cap:1.2}onset\': \'\' is not a valid value of the local atomic type.')
+ Code.expect(errors[25].message).to.equal('Schemas validity error : Element \'{urn:oasis:names:tc:emergency:cap:1.2}expires\': \'\' is not a valid value of the local atomic type.')
+ Code.expect(errors[27].message).to.equal('"alert.sender[0]" must be [www.gov.uk/environment-agency]')
+ Code.expect(errors[28].message).to.equal('"alert.sender[0]" is not allowed to be empty')
+ Code.expect(errors[29].message).to.equal('"alert.source[0]" is not allowed to be empty')
+ Code.expect(errors[30].message).to.equal('"alert.info[0].severity[0]" must be one of [Extreme, Severe, Moderate, Minor]')
+ Code.expect(errors[31].message).to.equal('"alert.info[0].severity[0]" is not allowed to be empty')
+ Code.expect(errors[32].message).to.equal('"alert.info[0].senderName[0]" is not allowed to be empty')
+ Code.expect(errors[33].message).to.equal('"alert.info[0].area[0].areaDesc[0]" is not allowed to be empty')
+ Code.expect(errors[34].message).to.equal('"alert.info[0].area[0].polygon[0]" is not allowed to be empty')
Code.expect(awsStub.email.publishMessage.callCount).to.equal(1)
})
@@ -202,7 +224,7 @@ lab.experiment('processMessage validation logging', () => {
.to
.reject()
const errors = JSON.parse(ret.message.replace('[500] ', ''))
- Code.expect(errors.length).to.equal(9)
+ Code.expect(errors.length).to.equal(22)
Code.expect(errors[0].message).to.equal('Schemas validity error : Element \'{urn:oasis:names:tc:emergency:cap:1.2}sent\': \'2026-05-28\' is not a valid value of the local atomic type.')
Code.expect(errors[1].message).to.equal('Schemas validity error : Element \'{urn:oasis:names:tc:emergency:cap:1.2}msgType\': [facet \'enumeration\'] The value \'invalid\' is not an element of the set {\'Alert\', \'Update\', \'Cancel\', \'Ack\', \'Error\'}.')
Code.expect(errors[2].message).to.equal('Schemas validity error : Element \'{urn:oasis:names:tc:emergency:cap:1.2}scope\': [facet \'enumeration\'] The value \'invalid\' is not an element of the set {\'Public\', \'Restricted\', \'Private\'}.')
@@ -212,6 +234,20 @@ lab.experiment('processMessage validation logging', () => {
Code.expect(errors[6].message).to.equal('Schemas validity error : Element \'{urn:oasis:names:tc:emergency:cap:1.2}certainty\': [facet \'enumeration\'] The value \'invalid\' is not an element of the set {\'Observed\', \'Likely\', \'Possible\', \'Unlikely\', \'Unknown\'}.')
Code.expect(errors[7].message).to.equal('Schemas validity error : Element \'{urn:oasis:names:tc:emergency:cap:1.2}expires\': \'2026-05-29\' is not a valid value of the local atomic type.')
Code.expect(errors[8].message).to.equal('"alert.sender[0]" must be [www.gov.uk/environment-agency]')
+ Code.expect(errors[9].message).to.equal('"alert.info[0].severity[0]" must be one of [Extreme, Severe, Moderate, Minor]')
+ // v2 errors
+ Code.expect(errors[10].message).to.equal('Schemas validity error : Element \'{urn:oasis:names:tc:emergency:cap:1.2}sent\': \'2026-05-28\' is not a valid value of the local atomic type.')
+ Code.expect(errors[11].message).to.equal('Schemas validity error : Element \'{urn:oasis:names:tc:emergency:cap:1.2}msgType\': [facet \'enumeration\'] The value \'invalid\' is not an element of the set {\'Alert\', \'Update\', \'Cancel\', \'Ack\', \'Error\'}.')
+ Code.expect(errors[12].message).to.equal('Schemas validity error : Element \'{urn:oasis:names:tc:emergency:cap:1.2}scope\': [facet \'enumeration\'] The value \'invalid\' is not an element of the set {\'Public\', \'Restricted\', \'Private\'}.')
+ Code.expect(errors[13].message).to.equal('Schemas validity error : Element \'{urn:oasis:names:tc:emergency:cap:1.2}category\': [facet \'enumeration\'] The value \'invalid\' is not an element of the set {\'Geo\', \'Met\', \'Safety\', \'Security\', \'Rescue\', \'Fire\', \'Health\', \'Env\', \'Transport\', \'Infra\', \'CBRNE\', \'Other\'}.')
+ Code.expect(errors[14].message).to.equal('Schemas validity error : Element \'{urn:oasis:names:tc:emergency:cap:1.2}urgency\': [facet \'enumeration\'] The value \'invalid\' is not an element of the set {\'Immediate\', \'Expected\', \'Future\', \'Past\', \'Unknown\'}.')
+ Code.expect(errors[15].message).to.equal('Schemas validity error : Element \'{urn:oasis:names:tc:emergency:cap:1.2}severity\': [facet \'enumeration\'] The value \'\' is not an element of the set {\'Extreme\', \'Severe\', \'Moderate\', \'Minor\', \'Unknown\'}.')
+ Code.expect(errors[16].message).to.equal('Schemas validity error : Element \'{urn:oasis:names:tc:emergency:cap:1.2}certainty\': [facet \'enumeration\'] The value \'invalid\' is not an element of the set {\'Observed\', \'Likely\', \'Possible\', \'Unlikely\', \'Unknown\'}.')
+ Code.expect(errors[17].message).to.equal('Schemas validity error : Element \'{urn:oasis:names:tc:emergency:cap:1.2}onset\': \'2026-05-28\' is not a valid value of the local atomic type.')
+ Code.expect(errors[18].message).to.equal('Schemas validity error : Element \'{urn:oasis:names:tc:emergency:cap:1.2}expires\': \'2026-05-29\' is not a valid value of the local atomic type.')
+ Code.expect(errors[19].message).to.equal('"alert.sender[0]" must be [www.gov.uk/environment-agency]')
+ Code.expect(errors[20].message).to.equal('"alert.info[0].severity[0]" must be one of [Extreme, Severe, Moderate, Minor]')
+ Code.expect(errors[21].message).to.equal('"alert.info[0].severity[0]" is not allowed to be empty')
Code.expect(awsStub.email.publishMessage.callCount).to.equal(1)
})
diff --git a/test/lib/functions/v2/getMessage.js b/test/lib/functions/v2/getMessage.js
new file mode 100644
index 0000000..f7be287
--- /dev/null
+++ b/test/lib/functions/v2/getMessage.js
@@ -0,0 +1,46 @@
+'use strict'
+
+const Lab = require('@hapi/lab')
+const lab = exports.lab = Lab.script()
+const Code = require('@hapi/code')
+const sinon = require('sinon')
+const Proxyquire = require('proxyquire').noCallThru()
+
+lab.experiment('getMessage v2 wrapper', () => {
+ lab.test('Calls getMessage helper with v2=true', async () => {
+ const getMessageStub = sinon.stub().resolves({
+ statusCode: 200,
+ headers: { 'content-type': 'application/xml' },
+ body: 'test'
+ })
+
+ const getMessage = Proxyquire('../../../../lib/functions/v2/getMessage', {
+ '../../helpers/message': { getMessage: getMessageStub }
+ }).getMessage
+
+ const event = { pathParameters: { id: 'test123' } }
+ await getMessage(event)
+
+ Code.expect(getMessageStub.callCount).to.equal(1)
+ Code.expect(getMessageStub.calledWith(event, true)).to.be.true()
+ })
+
+ lab.test('Returns the result from getMessage helper', async () => {
+ const expectedResult = {
+ statusCode: 200,
+ headers: { 'content-type': 'application/xml' },
+ body: 'v2 alert'
+ }
+
+ const getMessageStub = sinon.stub().resolves(expectedResult)
+
+ const getMessage = Proxyquire('../../../../lib/functions/v2/getMessage', {
+ '../../helpers/message': { getMessage: getMessageStub }
+ }).getMessage
+
+ const event = { pathParameters: { id: 'test123' } }
+ const result = await getMessage(event)
+
+ Code.expect(result).to.equal(expectedResult)
+ })
+})
diff --git a/test/lib/functions/v2/getMessagesAtom.js b/test/lib/functions/v2/getMessagesAtom.js
new file mode 100644
index 0000000..ee37f16
--- /dev/null
+++ b/test/lib/functions/v2/getMessagesAtom.js
@@ -0,0 +1,44 @@
+'use strict'
+
+const Lab = require('@hapi/lab')
+const lab = exports.lab = Lab.script()
+const Code = require('@hapi/code')
+const sinon = require('sinon')
+const Proxyquire = require('proxyquire').noCallThru()
+
+lab.experiment('getMessagesAtom v2 wrapper', () => {
+ lab.test('Calls messages helper with v2=true', async () => {
+ const messagesStub = sinon.stub().resolves({
+ statusCode: 200,
+ headers: { 'content-type': 'application/xml' },
+ body: 'test'
+ })
+
+ const getMessagesAtom = Proxyquire('../../../../lib/functions/v2/getMessagesAtom', {
+ '../../helpers/messages': { messages: messagesStub }
+ }).getMessagesAtom
+
+ await getMessagesAtom()
+
+ Code.expect(messagesStub.callCount).to.equal(1)
+ Code.expect(messagesStub.calledWith(true)).to.be.true()
+ })
+
+ lab.test('Returns the result from messages helper', async () => {
+ const expectedResult = {
+ statusCode: 200,
+ headers: { 'content-type': 'application/xml' },
+ body: 'v2 feed'
+ }
+
+ const messagesStub = sinon.stub().resolves(expectedResult)
+
+ const getMessagesAtom = Proxyquire('../../../../lib/functions/v2/getMessagesAtom', {
+ '../../helpers/messages': { messages: messagesStub }
+ }).getMessagesAtom
+
+ const result = await getMessagesAtom()
+
+ Code.expect(result).to.equal(expectedResult)
+ })
+})
diff --git a/test/lib/helpers/message.js b/test/lib/helpers/message.js
new file mode 100644
index 0000000..71e68f6
--- /dev/null
+++ b/test/lib/helpers/message.js
@@ -0,0 +1,335 @@
+'use strict'
+
+const Lab = require('@hapi/lab')
+const lab = exports.lab = Lab.script()
+const Code = require('@hapi/code')
+const sinon = require('sinon')
+const fs = require('fs')
+const path = require('path')
+const { getMessage } = require('../../../lib/helpers/message')
+const service = require('../../../lib/helpers/service')
+const getMessageXmlInvalid = fs.readFileSync(path.join(__dirname, '..', 'functions', 'data', 'getMessage-invalid.xml'), 'utf8')
+const getMessageXmlValid = fs.readFileSync(path.join(__dirname, '..', 'functions', 'data', 'getMessage-valid.xml'), 'utf8')
+let event
+
+lab.experiment('getMessage helper', () => {
+ lab.beforeEach(() => {
+ event = {
+ pathParameters: {
+ id: '4eb3b7350ab7aa443650fc9351f'
+ }
+ }
+ })
+
+ lab.experiment('getMessage v1 (v2=false)', () => {
+ lab.beforeEach(() => {
+ // mock service
+ service.getMessage = (query, params) => Promise.resolve({
+ rows: [{
+ getmessage: {
+ alert: 'test',
+ alert_v2: 'test v2'
+ }
+ }]
+ })
+ })
+
+ lab.test('Returns v1 alert when v2=false', async () => {
+ const ret = await getMessage(event, false)
+ Code.expect(ret.statusCode).to.equal(200)
+ Code.expect(ret.headers['content-type']).to.equal('application/xml')
+ Code.expect(ret.body).to.equal('test')
+ })
+
+ lab.test('No data found test', async () => {
+ service.getMessage = (query, params) => Promise.resolve({
+ rows: []
+ })
+
+ const err = await Code.expect(getMessage(event, false)).to.reject()
+ Code.expect(err.message).to.equal('No message found')
+ })
+
+ lab.test('Incorrect database rows object (not array)', async () => {
+ service.getMessage = (query, params) => Promise.resolve({
+ rows: 1
+ })
+
+ const err = await Code.expect(getMessage(event, false)).to.reject()
+ Code.expect(err.message).to.equal('No message found')
+ })
+
+ lab.test('Incorrect database rows object (empty getmessage)', async () => {
+ service.getMessage = (query, params) => Promise.resolve({
+ rows: [{}]
+ })
+
+ const err = await Code.expect(getMessage(event, false)).to.reject()
+ Code.expect(err.message).to.equal('No message found')
+ })
+
+ lab.test('Missing database rows object', async () => {
+ service.getMessage = (query, params) => Promise.resolve({
+ no_rows: []
+ })
+
+ const err = await Code.expect(getMessage(event, false)).to.reject()
+ Code.expect(err.message).to.equal('No message found')
+ })
+
+ lab.test('No database return', async () => {
+ service.getMessage = (query, params) => {
+ return new Promise((resolve, reject) => {
+ resolve()
+ })
+ }
+ const err = await Code.expect(getMessage(event, false)).to.reject()
+ Code.expect(err.message).to.equal('No message found')
+ })
+
+ lab.test('Database error', async () => {
+ service.getMessage = (query, params) => Promise.reject(new Error('test error'))
+
+ const err = await Code.expect(getMessage(event, false)).to.reject()
+ Code.expect(err.message).to.equal('test error')
+ })
+
+ lab.test('Event validation test (invalid id property)', async () => {
+ event.id = {}
+ await Code.expect(getMessage(event, false)).to.reject()
+ })
+
+ lab.test('Event validation test (missing pathParameters)', async () => {
+ event = {}
+ await Code.expect(getMessage(event, false)).to.reject()
+ })
+
+ lab.test('Invalid id format test', async () => {
+ event.pathParameters.id = 'invalid_id_format'
+
+ await Code.expect(getMessage(event, false)).to.reject()
+ })
+
+ lab.test('Valid id format test', async () => {
+ event.pathParameters.id = 'a1b2c3'
+ const result = await getMessage(event, false)
+ const body = result.body
+
+ Code.expect(body).to.equal('test')
+ })
+
+ lab.test('XSD validation logs errors for invalid alert but continues', async () => {
+ const consoleLogStub = sinon.stub(console, 'log')
+ try {
+ service.getMessage = () => Promise.resolve({
+ rows: [{
+ getmessage: {
+ alert: getMessageXmlInvalid
+ }
+ }]
+ })
+ await getMessage(event, false)
+ Code.expect(consoleLogStub.callCount).to.equal(2)
+ Code.expect(consoleLogStub.getCall(0).args[0]).to.equal('CAP get message failed validation')
+ Code.expect(consoleLogStub.getCall(1).args[0]).to.equal('[{"rawMessage":"message.xml:19: Schemas validity error : Element \'{urn:oasis:names:tc:emergency:cap:1.2}geocode\': This element is not expected. Expected is ( {urn:oasis:names:tc:emergency:cap:1.2}areaDesc ).","message":"Schemas validity error : Element \'{urn:oasis:names:tc:emergency:cap:1.2}geocode\': This element is not expected. Expected is ( {urn:oasis:names:tc:emergency:cap:1.2}areaDesc ).","loc":{"fileName":"message.xml","lineNumber":19}}]')
+ } finally {
+ consoleLogStub.restore()
+ }
+ })
+
+ lab.test('XSD validation does not log for valid alert', async () => {
+ const consoleLogStub = sinon.stub(console, 'log')
+ try {
+ service.getMessage = () => Promise.resolve({
+ rows: [{
+ getmessage: {
+ alert: getMessageXmlValid
+ }
+ }]
+ })
+ await getMessage(event, false)
+ Code.expect(consoleLogStub.callCount).to.equal(0)
+ } finally {
+ consoleLogStub.restore()
+ }
+ })
+ })
+
+ lab.experiment('getMessage v2 (v2=true)', () => {
+ lab.beforeEach(() => {
+ // mock service
+ service.getMessage = (query, params) => Promise.resolve({
+ rows: [{
+ getmessage: {
+ alert: 'test',
+ alert_v2: 'test v2'
+ }
+ }]
+ })
+ })
+
+ lab.test('Returns v2 alert when v2=true', async () => {
+ const ret = await getMessage(event, true)
+ Code.expect(ret.statusCode).to.equal(200)
+ Code.expect(ret.headers['content-type']).to.equal('application/xml')
+ Code.expect(ret.body).to.equal('test v2')
+ })
+
+ lab.test('No data found test', async () => {
+ service.getMessage = (query, params) => Promise.resolve({
+ rows: []
+ })
+
+ const err = await Code.expect(getMessage(event, true)).to.reject()
+ Code.expect(err.message).to.equal('No message found')
+ })
+
+ lab.test('Incorrect database rows object (not array)', async () => {
+ service.getMessage = (query, params) => Promise.resolve({
+ rows: 1
+ })
+
+ const err = await Code.expect(getMessage(event, true)).to.reject()
+ Code.expect(err.message).to.equal('No message found')
+ })
+
+ lab.test('Incorrect database rows object (empty getmessage)', async () => {
+ service.getMessage = (query, params) => Promise.resolve({
+ rows: [{}]
+ })
+
+ const err = await Code.expect(getMessage(event, true)).to.reject()
+ Code.expect(err.message).to.equal('No message found')
+ })
+
+ lab.test('Missing database rows object', async () => {
+ service.getMessage = (query, params) => Promise.resolve({
+ no_rows: []
+ })
+
+ const err = await Code.expect(getMessage(event, true)).to.reject()
+ Code.expect(err.message).to.equal('No message found')
+ })
+
+ lab.test('No database return', async () => {
+ service.getMessage = (query, params) => {
+ return new Promise((resolve, reject) => {
+ resolve()
+ })
+ }
+ const err = await Code.expect(getMessage(event, true)).to.reject()
+ Code.expect(err.message).to.equal('No message found')
+ })
+
+ lab.test('Database error', async () => {
+ service.getMessage = (query, params) => Promise.reject(new Error('test error'))
+
+ const err = await Code.expect(getMessage(event, true)).to.reject()
+ Code.expect(err.message).to.equal('test error')
+ })
+
+ lab.test('Event validation test (invalid id property)', async () => {
+ event.id = {}
+ await Code.expect(getMessage(event, true)).to.reject()
+ })
+
+ lab.test('Event validation test (missing pathParameters)', async () => {
+ event = {}
+ await Code.expect(getMessage(event, true)).to.reject()
+ })
+
+ lab.test('Invalid id format test', async () => {
+ event.pathParameters.id = 'invalid_id_format'
+
+ await Code.expect(getMessage(event, true)).to.reject()
+ })
+
+ lab.test('Valid id format test', async () => {
+ event.pathParameters.id = 'a1b2c3'
+ const result = await getMessage(event, true)
+ const body = result.body
+
+ Code.expect(body).to.equal('test v2')
+ })
+
+ lab.test('XSD validation logs errors for invalid alert but continues', async () => {
+ const consoleLogStub = sinon.stub(console, 'log')
+ try {
+ service.getMessage = () => Promise.resolve({
+ rows: [{
+ getmessage: {
+ alert_v2: getMessageXmlInvalid
+ }
+ }]
+ })
+ await getMessage(event, true)
+ Code.expect(consoleLogStub.callCount).to.equal(2)
+ Code.expect(consoleLogStub.getCall(0).args[0]).to.equal('CAP get message failed validation')
+ Code.expect(consoleLogStub.getCall(1).args[0]).to.equal('[{"rawMessage":"message.xml:19: Schemas validity error : Element \'{urn:oasis:names:tc:emergency:cap:1.2}geocode\': This element is not expected. Expected is ( {urn:oasis:names:tc:emergency:cap:1.2}areaDesc ).","message":"Schemas validity error : Element \'{urn:oasis:names:tc:emergency:cap:1.2}geocode\': This element is not expected. Expected is ( {urn:oasis:names:tc:emergency:cap:1.2}areaDesc ).","loc":{"fileName":"message.xml","lineNumber":19}}]')
+ } finally {
+ consoleLogStub.restore()
+ }
+ })
+
+ lab.test('XSD validation does not log for valid alert', async () => {
+ const consoleLogStub = sinon.stub(console, 'log')
+ try {
+ service.getMessage = () => Promise.resolve({
+ rows: [{
+ getmessage: {
+ alert_v2: getMessageXmlValid
+ }
+ }]
+ })
+ await getMessage(event, true)
+ Code.expect(consoleLogStub.callCount).to.equal(0)
+ } finally {
+ consoleLogStub.restore()
+ }
+ })
+ })
+
+ lab.experiment('Edge cases and behavior differences', () => {
+ lab.beforeEach(() => {
+ service.getMessage = (query, params) => Promise.resolve({
+ rows: [{
+ getmessage: {
+ alert: 'v1 content',
+ alert_v2: 'v2 content'
+ }
+ }]
+ })
+ })
+
+ lab.test('Returns different content for v1 vs v2', async () => {
+ const retV1 = await getMessage(event, false)
+ const retV2 = await getMessage(event, true)
+
+ Code.expect(retV1.body).to.equal('v1 content')
+ Code.expect(retV2.body).to.equal('v2 content')
+ Code.expect(retV1.body).to.not.equal(retV2.body)
+ })
+
+ lab.test('Both v1 and v2 return same status code and headers', async () => {
+ const retV1 = await getMessage(event, false)
+ const retV2 = await getMessage(event, true)
+
+ Code.expect(retV1.statusCode).to.equal(retV2.statusCode)
+ Code.expect(retV1.headers).to.equal(retV2.headers)
+ })
+
+ lab.test('Logs correct message id when no message found', async () => {
+ const consoleLogStub = sinon.stub(console, 'log')
+ service.getMessage = () => Promise.resolve({ rows: [] })
+
+ try {
+ await getMessage(event, false)
+ } catch (err) {
+ Code.expect(consoleLogStub.callCount).to.equal(1)
+ Code.expect(consoleLogStub.getCall(0).args[0]).to.equal('No message found for 4eb3b7350ab7aa443650fc9351f')
+ } finally {
+ consoleLogStub.restore()
+ }
+ })
+ })
+})
diff --git a/test/lib/helpers/messages.js b/test/lib/helpers/messages.js
new file mode 100644
index 0000000..15b0761
--- /dev/null
+++ b/test/lib/helpers/messages.js
@@ -0,0 +1,280 @@
+'use strict'
+
+const Lab = require('@hapi/lab')
+const lab = exports.lab = Lab.script()
+const Code = require('@hapi/code')
+const { messages } = require('../../../lib/helpers/messages')
+const service = require('../../../lib/helpers/service')
+let CPX_AGW_URL
+
+lab.experiment('messages helper', () => {
+ lab.before(() => {
+ CPX_AGW_URL = process.env.CPX_AGW_URL
+ process.env.CPX_AGW_URL = 'http://localhost:3000'
+ })
+
+ lab.after(() => {
+ process.env.CPX_AGW_URL = CPX_AGW_URL
+ })
+
+ lab.experiment('messages v1 (v2=false)', () => {
+ lab.beforeEach(() => {
+ // mock database query
+ service.getAllMessages = (query) => {
+ return new Promise((resolve, reject) => {
+ resolve({
+ rows: [{
+ fwis_code: 'test_fwis_code',
+ alert: 'test',
+ sent: new Date(),
+ identifier: '4eb3b7350ab7aa443650fc9351f'
+ }]
+ })
+ })
+ }
+ })
+
+ lab.test('Returns v1 atom feed with correct URLs', async () => {
+ const ret = await messages(false)
+ Code.expect(ret.statusCode).to.equal(200)
+ Code.expect(ret.headers['content-type']).to.equal('application/xml')
+ Code.expect(ret.body).to.contain('http://localhost:3000/messages.atom')
+ Code.expect(ret.body).to.contain('http://localhost:3000/message/4eb3b7350ab7aa443650fc9351f')
+ Code.expect(ret.body).to.not.contain('/v2/')
+ })
+
+ lab.test('Handles bad rows returned', async () => {
+ service.getAllMessages = (query) => {
+ return new Promise((resolve, reject) => {
+ resolve({
+ rows: 1
+ })
+ })
+ }
+ const ret = await messages(false)
+ Code.expect(ret.statusCode).to.equal(200)
+ Code.expect(ret.headers['content-type']).to.equal('application/xml')
+ })
+
+ lab.test('Handles no return from database', async () => {
+ service.getAllMessages = (query) => {
+ return new Promise((resolve, reject) => {
+ resolve()
+ })
+ }
+ const ret = await messages(false)
+ Code.expect(ret.statusCode).to.equal(200)
+ Code.expect(ret.headers['content-type']).to.equal('application/xml')
+ })
+
+ lab.test('Throws error on database failure', async () => {
+ service.getAllMessages = (query) => {
+ return new Promise((resolve, reject) => {
+ reject(new Error('test error'))
+ })
+ }
+ const err = await Code.expect(messages(false)).to.reject()
+ Code.expect(err.message).to.equal('test error')
+ })
+
+ lab.test('Includes feed metadata', async () => {
+ const ret = await messages(false)
+ Code.expect(ret.body).to.contain('
Flood warnings for England')
+ Code.expect(ret.body).to.contain('Environment Agency CAP XML flood warnings')
+ Code.expect(ret.body).to.contain('Environment Agency')
+ Code.expect(ret.body).to.contain('enquiries@environment-agency.gov.uk')
+ })
+
+ lab.test('Includes entry for each message', async () => {
+ service.getAllMessages = () => {
+ return Promise.resolve({
+ rows: [
+ {
+ fwis_code: 'AREA1',
+ alert: 'test1',
+ sent: new Date('2025-01-01'),
+ identifier: 'id1'
+ },
+ {
+ fwis_code: 'AREA2',
+ alert: 'test2',
+ sent: new Date('2025-01-02'),
+ identifier: 'id2'
+ }
+ ]
+ })
+ }
+ const ret = await messages(false)
+ Code.expect(ret.body).to.contain('')
+ Code.expect(ret.body).to.contain('')
+ Code.expect(ret.body).to.contain('http://localhost:3000/message/id1')
+ Code.expect(ret.body).to.contain('http://localhost:3000/message/id2')
+ })
+ })
+
+ lab.experiment('messages v2 (v2=true)', () => {
+ lab.beforeEach(() => {
+ // mock database query
+ service.getAllMessages = (query) => {
+ return new Promise((resolve, reject) => {
+ resolve({
+ rows: [{
+ fwis_code: 'test_fwis_code',
+ alert: 'test',
+ sent: new Date(),
+ identifier: '4eb3b7350ab7aa443650fc9351f',
+ identifier_v2: '2.49.0.0.826.1.YYYYMMDDHHMMSS.4eb3b7350ab7aa443650fc9351f'
+ }]
+ })
+ })
+ }
+ })
+
+ lab.test('Returns v2 atom feed with correct URLs', async () => {
+ const ret = await messages(true)
+ Code.expect(ret.statusCode).to.equal(200)
+ Code.expect(ret.headers['content-type']).to.equal('application/xml')
+ Code.expect(ret.body).to.contain('http://localhost:3000/v2/messages.atom')
+ Code.expect(ret.body).to.contain('http://localhost:3000/v2/message/4eb3b7350ab7aa443650fc9351f')
+ })
+
+ lab.test('Handles bad rows returned', async () => {
+ service.getAllMessages = (query) => {
+ return new Promise((resolve, reject) => {
+ resolve({
+ rows: 1
+ })
+ })
+ }
+ const ret = await messages(true)
+ Code.expect(ret.statusCode).to.equal(200)
+ Code.expect(ret.headers['content-type']).to.equal('application/xml')
+ })
+
+ lab.test('Handles no return from database', async () => {
+ service.getAllMessages = (query) => {
+ return new Promise((resolve, reject) => {
+ resolve()
+ })
+ }
+ const ret = await messages(true)
+ Code.expect(ret.statusCode).to.equal(200)
+ Code.expect(ret.headers['content-type']).to.equal('application/xml')
+ })
+
+ lab.test('Throws error on database failure', async () => {
+ service.getAllMessages = (query) => {
+ return new Promise((resolve, reject) => {
+ reject(new Error('test error'))
+ })
+ }
+ const err = await Code.expect(messages(true)).to.reject()
+ Code.expect(err.message).to.equal('test error')
+ })
+
+ lab.test('Includes feed metadata', async () => {
+ const ret = await messages(true)
+ Code.expect(ret.body).to.contain('Flood warnings for England')
+ Code.expect(ret.body).to.contain('Environment Agency CAP XML flood warnings')
+ Code.expect(ret.body).to.contain('Environment Agency')
+ Code.expect(ret.body).to.contain('enquiries@environment-agency.gov.uk')
+ })
+
+ lab.test('Includes entry for each message', async () => {
+ service.getAllMessages = () => {
+ return Promise.resolve({
+ rows: [
+ {
+ fwis_code: 'AREA1',
+ alert: 'test1',
+ sent: new Date('2025-01-01'),
+ identifier: 'id1',
+ identifier_v2: '2.49.0.0.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'
+ }
+ ]
+ })
+ }
+ const ret = await messages(true)
+ Code.expect(ret.body).to.contain('')
+ Code.expect(ret.body).to.contain('')
+ Code.expect(ret.body).to.contain('http://localhost:3000/v2/message/id1')
+ Code.expect(ret.body).to.contain('http://localhost:3000/v2/message/id2')
+ })
+ })
+
+ lab.experiment('Edge cases and behavior differences', () => {
+ lab.beforeEach(() => {
+ service.getAllMessages = () => {
+ return Promise.resolve({
+ rows: [{
+ fwis_code: 'TEST_CODE',
+ alert: 'test',
+ sent: new Date('2025-01-01T12:00:00Z'),
+ identifier: 'test_id',
+ identifier_v2: '2.49.0.0.826.1.20250101120000.test_id'
+ }]
+ })
+ }
+ })
+
+ lab.test('V1 and V2 feeds have different URI prefixes', async () => {
+ const retV1 = await messages(false)
+ const retV2 = await messages(true)
+
+ Code.expect(retV1.body).to.contain('http://localhost:3000/messages.atom')
+ Code.expect(retV1.body).to.not.contain('/v2/')
+
+ Code.expect(retV2.body).to.contain('http://localhost:3000/v2/messages.atom')
+ Code.expect(retV2.body).to.contain('/v2/message/')
+ })
+
+ lab.test('Both v1 and v2 return same status code and headers', async () => {
+ const retV1 = await messages(false)
+ const retV2 = await messages(true)
+
+ Code.expect(retV1.statusCode).to.equal(retV2.statusCode)
+ Code.expect(retV1.headers).to.equal(retV2.headers)
+ })
+
+ lab.test('Empty database returns valid empty feed for both versions', async () => {
+ service.getAllMessages = () => Promise.resolve({ rows: [] })
+
+ const retV1 = await messages(false)
+ const retV2 = await messages(true)
+
+ Code.expect(retV1.statusCode).to.equal(200)
+ Code.expect(retV2.statusCode).to.equal(200)
+ Code.expect(retV1.body).to.contain(' {
+ service.getAllMessages = () => {
+ return Promise.resolve({
+ rows: Array.from({ length: 5 }, (_, i) => ({
+ fwis_code: `AREA${i}`,
+ 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}`
+ }))
+ })
+ }
+
+ const retV1 = await messages(false)
+ const retV2 = await messages(true)
+
+ for (let i = 0; i < 5; i++) {
+ Code.expect(retV1.body).to.contain(``)
+ Code.expect(retV2.body).to.contain(``)
+ }
+ })
+ })
+})
diff --git a/test/lib/models/message.js b/test/lib/models/message.js
index 8edbdc1..66ea022 100644
--- a/test/lib/models/message.js
+++ b/test/lib/models/message.js
@@ -3,47 +3,117 @@
const Lab = require('@hapi/lab')
const lab = exports.lab = Lab.script()
const Code = require('@hapi/code')
-
+const sinon = require('sinon')
+const fs = require('node:fs')
+const path = require('node:path')
const Message = require('../../../lib/models/message')
+let clock
+const xml = fs.readFileSync(path.join(__dirname, '..', 'functions', 'data', 'nws-alert.xml'), 'utf8')
+
+const blankXml = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`
-const xml = `
+const blankXml2 = `
- 123456
- www.gov.uk/environment-agency
- 2026-05-28T11:00:02-00:00
- Actual
- Alert
- Flood warning service
- Public
+
+
+
+
+
+
+
+
+
- en-GB
- Met
-
- Immediate
- Minor
- Likely
- 2026-05-29T11:00:02-00:00
- Environment Agency
+
+
+
+
+
+
+
+
+
+
+
+
- Area description
- points
+
+
- TargetAreaCode
-
+
+
`
+const blankXmlMissingFields = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`
+
lab.experiment('Message class', () => {
- let message
+ let message, messageV2
lab.beforeEach(() => {
+ clock = sinon.useFakeTimers(new Date('2020-01-01T00:00:00Z').getTime())
message = new Message(xml)
+ messageV2 = new Message(xml)
+ })
+
+ lab.afterEach(() => {
+ clock.restore()
+ sinon.restore()
})
lab.test('parses identifier', () => {
- Code.expect(message.identifier).to.equal('123456')
+ Code.expect(message.identifier).to.equal('4eb3b7350ab7aa443650fc9351f02940E')
})
lab.test('parses sender', () => {
@@ -51,7 +121,7 @@ lab.experiment('Message class', () => {
})
lab.test('parses fwisCode (geocode value)', () => {
- Code.expect(message.fwisCode).to.equal('TESTAREA')
+ Code.expect(message.fwisCode).to.equal('TESTAREA1')
})
lab.test('parses msgType', () => {
@@ -73,11 +143,11 @@ lab.experiment('Message class', () => {
})
lab.test('parses sent timestamp', () => {
- Code.expect(message.sent).to.equal('2026-05-28T11:00:02-00:00')
+ Code.expect(message.sent).to.equal('2025-11-06T08:00:27+00:00')
})
lab.test('parses expires timestamp', () => {
- Code.expect(message.expires).to.equal('2026-05-29T11:00:02-00:00')
+ Code.expect(message.expires).to.equal('2025-11-16T08:00:27+00:00')
})
lab.test('references defaults to empty string when missing', () => {
@@ -94,10 +164,9 @@ lab.experiment('Message class', () => {
})
lab.test('does not add references if value is falsy', () => {
- // Initial: no references
Code.expect(message.references).to.equal('')
- message.references = '' // falsy value
- Code.expect(message.references).to.equal('') // still unchanged
+ message.references = ''
+ Code.expect(message.references).to.equal('')
Code.expect(message.toString()).to.not.include('')
})
@@ -112,17 +181,136 @@ lab.experiment('Message class', () => {
Code.expect(message.toString()).to.include('REF2')
})
+ lab.test('parses quickdial number from instruction', () => {
+ Code.expect(message.quickdialNumber).to.equal('210010')
+ })
+
+ lab.test('parses instruction', () => {
+ Code.expect(message.instruction).to.equal(`instructions
+ - For access to flood warning information offline call Floodline on 0345 988 1188 using quickdial code: 210010.
+ `)
+ })
+
lab.test('toString returns valid XML string containing identifier', () => {
const xmlOut = message.toString()
Code.expect(xmlOut).to.be.a.string()
- Code.expect(xmlOut).to.include('123456')
+ Code.expect(xmlOut).to.include('4eb3b7350ab7aa443650fc9351f02940E')
})
lab.test('putQuery generates SQL insert with correct values', () => {
- const sql = message.putQuery()
- Code.expect(sql.text).to.equal('INSERT INTO "messages" ("identifier", "msg_type", "references", "alert", "fwis_code", "expires", "sent", "created") VALUES ($1, $2, $3, $4, $5, $6, $7, $8)')
- Code.expect(sql.values).to.include('123456')
- Code.expect(sql.values).to.include('TESTAREA')
- Code.expect(sql.values).to.include('2026-05-29T11:00:02-00:00')
+ const sql = message.putQuery(message, messageV2)
+ Code.expect(sql.text).to.equal('INSERT INTO "messages" ("identifier", "msg_type", "references", "alert", "fwis_code", "expires", "sent", "created", "identifier_v2", "references_v2", "alert_v2") VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)')
+ // TODO need to test for more values and v2 values here
+ Code.expect(sql.values[0]).to.equal('4eb3b7350ab7aa443650fc9351f02940E')
+ Code.expect(sql.values[1]).to.equal('Alert')
+ Code.expect(sql.values[2]).to.be.empty()
+ Code.expect(sql.values[3]).to.not.be.empty()
+ Code.expect(sql.values[4]).to.equal('TESTAREA1')
+ Code.expect(sql.values[5]).to.equal('2025-11-16T08:00:27+00:00')
+ Code.expect(sql.values[6]).to.equal('2025-11-06T08:00:27+00:00')
+ Code.expect(sql.values[7]).to.equal('2020-01-01T00:00:00.000Z') // TODO: bug change to not use Zulu shorthand timezone
+ Code.expect(sql.values[8]).to.equal('4eb3b7350ab7aa443650fc9351f02940E')
+ Code.expect(sql.values[9]).to.be.empty()
+ Code.expect(sql.values[10]).to.not.be.empty()
+ })
+
+ lab.test('blank message results in blank fields', () => {
+ const messageBlank = new Message(blankXml)
+ Code.expect(messageBlank.fwisCode).to.equal('')
+ Code.expect(messageBlank.identifier).to.equal('')
+ Code.expect(messageBlank.sender).to.equal('')
+ Code.expect(messageBlank.msgType).to.equal('')
+ Code.expect(messageBlank.references).to.equal('')
+ Code.expect(messageBlank.status).to.equal('')
+ Code.expect(messageBlank.expires).to.equal('')
+ Code.expect(messageBlank.instruction).to.equal('')
+ Code.expect(messageBlank.quickdialNumber).to.equal('')
+ Code.expect(messageBlank.sent).to.equal('')
+ Code.expect(messageBlank.code).to.equal('')
+ Code.expect(messageBlank.event).to.equal('')
+ Code.expect(messageBlank.severity).to.equal('')
+ Code.expect(messageBlank.onset).to.equal('')
+ Code.expect(messageBlank.headline).to.equal('')
+ Code.expect(messageBlank.areaDesc).to.equal('')
+ })
+
+ lab.test('Test setters with blank message with syntax', () => {
+ const messageBlank = new Message(blankXml2)
+ messageBlank.identifier = 'ID123'
+ messageBlank.msgType = 'Alert'
+ messageBlank.references = 'REF123'
+ messageBlank.status = 'Actual'
+ messageBlank.code = 'CODE123'
+ messageBlank.event = 'Test Event'
+ messageBlank.severity = 'Severe'
+ messageBlank.onset = '2026-06-01T10:00:00-00:00'
+ messageBlank.headline = 'Test Headline'
+ messageBlank.instruction = 'Test Instruction'
+
+ Code.expect(messageBlank.identifier).to.equal('ID123')
+ Code.expect(messageBlank.references).to.equal('REF123')
+ Code.expect(messageBlank.msgType).to.equal('Update') // references setter flips msgType
+ 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.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')
+ Code.expect(messageBlank.instruction).to.equal('Test Instruction')
+ })
+ lab.test('Test setters with blank message and missing fields with syntax', () => {
+ const messageBlank = new Message(blankXmlMissingFields)
+ messageBlank.identifier = 'ID123'
+ messageBlank.msgType = 'Alert'
+ messageBlank.references = 'REF123'
+ messageBlank.status = 'Actual'
+ messageBlank.code = 'CODE123'
+ messageBlank.event = 'Test Event'
+ messageBlank.severity = 'Severe'
+ messageBlank.onset = '2026-06-01T10:00:00-00:00'
+ messageBlank.headline = 'Test Headline'
+ messageBlank.instruction = 'Test Instruction'
+
+ Code.expect(messageBlank.identifier).to.equal('ID123')
+ Code.expect(messageBlank.references).to.equal('REF123')
+ Code.expect(messageBlank.msgType).to.equal('Update') // references setter flips msgType
+ 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.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')
+ Code.expect(messageBlank.instruction).to.equal('Test Instruction')
+ })
+
+ lab.test('Setting parameters on a message (no getter available, so must check XML)', () => {
+ const normalize = s => s.replace(/\r\n/g, '\n')
+ const messageBlankMissingFields = new Message(blankXmlMissingFields)
+ messageBlankMissingFields.addParameter('awareness_level', 'awareness level')
+ messageBlankMissingFields.addParameter('awareness_type', '12; Flooding')
+ messageBlankMissingFields.addParameter('impacts', 'headline')
+ messageBlankMissingFields.addParameter('use_polygon_over_geocode', 'true')
+ messageBlankMissingFields.addParameter('uk_ea_ta_code', 'fwisCode')
+
+ Code.expect(normalize(messageBlankMissingFields.toString())).to.include(normalize(`
+ awareness_level
+ awareness level
+
+
+ awareness_type
+ 12; Flooding
+
+
+ impacts
+ headline
+
+
+ use_polygon_over_geocode
+ true
+
+
+ uk_ea_ta_code
+ fwisCode
+ `))
})
})