#!/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}" client_id="qipai-device-smoke-$(date +%s)" allowed_publish_topic="${QIPAI_MQTT_ALLOWED_PUBLISH_TOPIC:-qipai/${client_id}/status}" allowed_subscribe_topic="${QIPAI_MQTT_ALLOWED_SUBSCRIBE_TOPIC:-qipai/${client_id}/command/#}" 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:-qipai/${client_id}/will}" will_message="${QIPAI_MQTT_WILL_MESSAGE:-{\"type\":\"will\",\"clientId\":\"${client_id}\"}}" idempotency_topic="${QIPAI_MQTT_IDEMPOTENCY_TOPIC:-qipai/${client_id}/status}" 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