Files
qipai/scripts/dev/wsl/mqtt-smoke.sh
T
2026-06-22 17:25:33 +08:00

223 lines
8.1 KiB
Bash

#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
if [ -f "${SCRIPT_DIR}/mqtt-smoke.env" ]; then
# shellcheck disable=SC1091
. "${SCRIPT_DIR}/mqtt-smoke.env"
fi
host="${QIPAI_MQTT_HOST:-127.0.0.1}"
port="${QIPAI_MQTT_PORT:-1883}"
protocol="${QIPAI_MQTT_PROTOCOL:-mqtt}"
mqtt_version="${QIPAI_MQTT_VERSION:-3.1.1}"
qos="${QIPAI_MQTT_QOS:-1}"
device_id="${QIPAI_MQTT_DEVICE_ID:-SMOKE_DEVICE}"
client_id="qipai-device-${device_id}"
allowed_publish_topic="${QIPAI_MQTT_ALLOWED_PUBLISH_TOPIC:-/devicesend/${device_id}}"
allowed_subscribe_topic="${QIPAI_MQTT_ALLOWED_SUBSCRIBE_TOPIC:-/deviceaccept/${device_id}}"
denied_subscribe_topic="${QIPAI_MQTT_DENIED_SUBSCRIBE_TOPIC:-\$SYS/#}"
enable_tls_smoke="${QIPAI_MQTT_ENABLE_TLS_SMOKE:-false}"
enable_will_smoke="${QIPAI_MQTT_ENABLE_WILL_SMOKE:-false}"
enable_idempotency_smoke="${QIPAI_MQTT_ENABLE_IDEMPOTENCY_SMOKE:-false}"
tls_host="${QIPAI_MQTT_TLS_HOST:-$host}"
tls_port="${QIPAI_MQTT_TLS_PORT:-8883}"
tls_protocol="${QIPAI_MQTT_TLS_PROTOCOL:-mqtts}"
tls_insecure="${QIPAI_MQTT_TLS_INSECURE:-false}"
will_topic="${QIPAI_MQTT_WILL_TOPIC:-/devicewill/${device_id}}"
will_message="${QIPAI_MQTT_WILL_MESSAGE:-{\"type\":\"will\",\"clientId\":\"${client_id}\"}}"
idempotency_topic="${QIPAI_MQTT_IDEMPOTENCY_TOPIC:-/devicesend/${device_id}}"
idempotency_key="${QIPAI_MQTT_IDEMPOTENCY_KEY:-smoke-${client_id}}"
echo "INFO: MQTT smoke target ${protocol}://${host}:${port}, version ${mqtt_version}, qos ${qos}"
check_config_only=false
if [ "${1:-}" = "--check-config" ]; then
check_config_only=true
fi
if ! command -v mqttx >/dev/null 2>&1; then
echo "FAIL: mqttx CLI not found"
exit 1
fi
is_true() {
case "${1:-}" in
true|TRUE|1|yes|YES|on|ON) return 0 ;;
*) return 1 ;;
esac
}
append_tls_args() {
local -n target_args=$1
if [ -n "${QIPAI_MQTT_TLS_CA:-}" ]; then
target_args+=(--ca "$QIPAI_MQTT_TLS_CA")
fi
if [ -n "${QIPAI_MQTT_TLS_CERT:-}" ]; then
target_args+=(--cert "$QIPAI_MQTT_TLS_CERT")
fi
if [ -n "${QIPAI_MQTT_TLS_KEY:-}" ]; then
target_args+=(--key "$QIPAI_MQTT_TLS_KEY")
fi
if is_true "$tls_insecure"; then
target_args+=(--insecure)
fi
}
if [ ! -f "${SCRIPT_DIR}/mqtt-smoke.env" ]; then
echo "WARN: local env not found: scripts/dev/wsl/mqtt-smoke.env"
echo "INFO: copy scripts/dev/wsl/mqtt-smoke.env.example and fill local credentials outside Git"
fi
if [ -z "${QIPAI_MQTT_USERNAME:-}" ] || [ -z "${QIPAI_MQTT_PASSWORD:-}" ]; then
echo "SKIP: QIPAI_MQTT_USERNAME/QIPAI_MQTT_PASSWORD not configured in Git-ignored local env"
echo "INFO: service-level check remains available via scripts/dev/wsl/check-local-mqtt.sh"
exit 0
fi
if [ "$QIPAI_MQTT_PASSWORD" = "QIPAI_EXPECTED_BAD_PASSWORD" ]; then
echo "FAIL: local password equals the negative-test password placeholder"
exit 1
fi
if [ "$check_config_only" = true ]; then
echo "PASS: local MQTT smoke configuration is present"
echo "INFO: username configured; password is intentionally not printed"
echo "INFO: optional TLS smoke enabled: ${enable_tls_smoke}"
echo "INFO: optional will-message smoke enabled: ${enable_will_smoke}"
echo "INFO: optional idempotency smoke enabled: ${enable_idempotency_smoke}"
exit 0
fi
common_args=(
--hostname "$host"
--port "$port"
--protocol "$protocol"
--mqtt-version "$mqtt_version"
--qos "$qos"
--username "$QIPAI_MQTT_USERNAME"
--password "$QIPAI_MQTT_PASSWORD"
)
echo "INFO: verifying allowed publish topic ${allowed_publish_topic}"
mqttx pub "${common_args[@]}" \
--client-id "${client_id}-pub" \
--topic "$allowed_publish_topic" \
--message "{\"type\":\"smoke\",\"clientId\":\"${client_id}\"}" >/dev/null
echo "PASS: allowed publish accepted"
echo "INFO: verifying allowed subscribe topic ${allowed_subscribe_topic}"
timeout 6 mqttx sub "${common_args[@]}" \
--client-id "${client_id}-sub" \
--topic "$allowed_subscribe_topic" \
--output-mode clean >/tmp/qipai-mqtt-smoke-sub.out 2>/tmp/qipai-mqtt-smoke-sub.err || true
if grep -qiE "error|not authorized|unauthorized|connack.*5|refused" /tmp/qipai-mqtt-smoke-sub.err; then
echo "FAIL: allowed subscribe was rejected"
cat /tmp/qipai-mqtt-smoke-sub.err
exit 1
fi
echo "PASS: allowed subscribe connection did not report authorization failure"
echo "INFO: verifying wrong password is rejected"
if timeout 6 mqttx pub \
--hostname "$host" \
--port "$port" \
--protocol "$protocol" \
--mqtt-version "$mqtt_version" \
--qos "$qos" \
--username "$QIPAI_MQTT_USERNAME" \
--password "QIPAI_EXPECTED_BAD_PASSWORD" \
--client-id "${client_id}-badpass" \
--topic "$allowed_publish_topic" \
--message "bad-password-should-fail" >/tmp/qipai-mqtt-smoke-badpass.out 2>/tmp/qipai-mqtt-smoke-badpass.err; then
echo "FAIL: wrong password publish succeeded"
exit 1
else
echo "PASS: wrong password rejected"
fi
echo "INFO: probing denied subscribe topic ${denied_subscribe_topic}"
timeout 6 mqttx sub "${common_args[@]}" \
--client-id "${client_id}-deny" \
--topic "$denied_subscribe_topic" \
--output-mode clean >/tmp/qipai-mqtt-smoke-deny.out 2>/tmp/qipai-mqtt-smoke-deny.err || true
if grep -qiE "not authorized|unauthorized|refused|error" /tmp/qipai-mqtt-smoke-deny.err; then
echo "PASS: denied topic produced authorization error"
else
echo "WARN: denied topic did not produce a clear authorization error; verify ACL manually"
fi
if is_true "$enable_tls_smoke"; then
tls_args=(
--hostname "$tls_host"
--port "$tls_port"
--protocol "$tls_protocol"
--mqtt-version "$mqtt_version"
--qos "$qos"
--username "$QIPAI_MQTT_USERNAME"
--password "$QIPAI_MQTT_PASSWORD"
)
append_tls_args tls_args
echo "INFO: verifying TLS publish target ${tls_protocol}://${tls_host}:${tls_port}"
mqttx pub "${tls_args[@]}" \
--client-id "${client_id}-tls" \
--topic "$allowed_publish_topic" \
--message "{\"type\":\"tls-smoke\",\"clientId\":\"${client_id}\"}" >/dev/null
echo "PASS: TLS publish accepted"
else
echo "SKIP: optional TLS smoke disabled; set QIPAI_MQTT_ENABLE_TLS_SMOKE=true to run it"
fi
if is_true "$enable_idempotency_smoke"; then
echo "INFO: publishing duplicate idempotency key ${idempotency_key} to ${idempotency_topic}"
mqttx pub "${common_args[@]}" \
--client-id "${client_id}-idem-a" \
--topic "$idempotency_topic" \
--message "{\"type\":\"idempotency-smoke\",\"clientId\":\"${client_id}\",\"idempotencyKey\":\"${idempotency_key}\",\"sequence\":1}" >/dev/null
mqttx pub "${common_args[@]}" \
--client-id "${client_id}-idem-b" \
--topic "$idempotency_topic" \
--message "{\"type\":\"idempotency-smoke\",\"clientId\":\"${client_id}\",\"idempotencyKey\":\"${idempotency_key}\",\"sequence\":1}" >/dev/null
echo "PASS: duplicate idempotency messages published"
echo "INFO: downstream service/device logs must still prove the duplicate was handled idempotently"
else
echo "SKIP: optional idempotency smoke disabled; set QIPAI_MQTT_ENABLE_IDEMPOTENCY_SMOKE=true to publish duplicates"
fi
if is_true "$enable_will_smoke"; then
will_capture="/tmp/qipai-mqtt-smoke-will-${client_id}.out"
rm -f "$will_capture"
echo "INFO: attempting will-message observation on ${will_topic}"
timeout 10 mqttx sub "${common_args[@]}" \
--client-id "${client_id}-will-listener" \
--topic "$will_topic" \
--output-mode clean \
--file-write "$will_capture" >/tmp/qipai-mqtt-smoke-will-sub.out 2>/tmp/qipai-mqtt-smoke-will-sub.err &
will_sub_pid=$!
sleep 2
timeout 3 mqttx sub "${common_args[@]}" \
--client-id "${client_id}-will-source" \
--topic "$allowed_subscribe_topic" \
--will-topic "$will_topic" \
--will-message "$will_message" \
--will-qos "$qos" \
--output-mode clean >/tmp/qipai-mqtt-smoke-will-source.out 2>/tmp/qipai-mqtt-smoke-will-source.err || true
sleep 3
if grep -Fq "$will_message" "$will_capture" 2>/dev/null; then
echo "PASS: will message observed"
else
echo "WARN: will message was not observed; MQTTX timeout may have closed gracefully, verify broker/client failure path manually"
fi
kill "$will_sub_pid" >/dev/null 2>&1 || true
wait "$will_sub_pid" >/dev/null 2>&1 || true
else
echo "SKIP: optional will-message smoke disabled; set QIPAI_MQTT_ENABLE_WILL_SMOKE=true to attempt observation"
fi