test(mqtt): Mosquitto TLS+auth fixture + env-gated plain live suite
Adds the MQTT driver integration-test project: an eclipse-mosquitto fixture running auth AND TLS (allow_anonymous false on both listeners), a mosquitto_pub-based JSON publisher emitting retained messages, a cert/password generator script whose output is gitignored, and a live suite gated on MQTT_FIXTURE_ENDPOINT that skips clean offline. The fixture is never anonymous by design: an anonymous broker would let the driver's TLS/CA-pin and auth paths go untested, and those fail silently. The CA-pin leg carries its own falsifiability control (a foreign CA generated in-process must be rejected). Live gate on 10.100.0.35: 7/7 passed. It found a driver defect, reported not fixed here (src/ is out of this task's scope): MqttConnection.ConnectAsync RETURNS NORMALLY when Mosquitto rejects a CONNECT as not-authorized -- MQTTnet 5 does not throw on an unsuccessful CONNACK, so a wrong broker password yields DriverState.Healthy from InitializeAsync. The auth-negative test therefore asserts the invariant that holds either way (no session is ever established) and documents the finding. Claude-Session: https://claude.ai/code/session_01GASWkNEi68FSCtvr6rLoEW
This commit is contained in:
+67
@@ -0,0 +1,67 @@
|
||||
#!/bin/sh
|
||||
# ---------------------------------------------------------------------------
|
||||
# The fixture's JSON publisher. Runs inside a second eclipse-mosquitto container (the
|
||||
# image already ships mosquitto_pub, so no bespoke image / paho dependency is needed)
|
||||
# and feeds the broker over the PLAIN listener from inside the compose network —
|
||||
# authenticated like every other client, since the broker is allow_anonymous false.
|
||||
#
|
||||
# Topics (all under $TOPIC_PREFIX, default otopcua/fixture):
|
||||
#
|
||||
# retained/seed published EXACTLY ONCE, retain=true, never republished.
|
||||
# This is what makes the retained-seed test meaningful: a
|
||||
# subscriber that connects minutes later and still receives a
|
||||
# value on this topic can only have received the BROKER'S
|
||||
# RETAINED COPY. On a topic that is also republished every few
|
||||
# seconds, a retained seed is indistinguishable from a live
|
||||
# publish that happened to arrive quickly.
|
||||
# line1/temperature JSON object, retain=true, republished every $PUBLISH_INTERVAL
|
||||
# line1/counter JSON object, retain=true, monotonically increasing
|
||||
# line1/state bare scalar text (no JSON), retain=true
|
||||
# noise/chatter JSON, retain=FALSE — deliberately never authored as a tag.
|
||||
# A broker carrying traffic the deployment did not author is
|
||||
# the normal case, and the driver must stay silent about it.
|
||||
# ---------------------------------------------------------------------------
|
||||
set -eu
|
||||
|
||||
HOST=${BROKER_HOST:-mosquitto}
|
||||
PORT=${BROKER_PORT:-1883}
|
||||
PREFIX=${TOPIC_PREFIX:-otopcua/fixture}
|
||||
INTERVAL=${PUBLISH_INTERVAL:-2}
|
||||
|
||||
: "${MQTT_FIXTURE_USERNAME:?publisher needs MQTT_FIXTURE_USERNAME}"
|
||||
: "${MQTT_FIXTURE_PASSWORD:?publisher needs MQTT_FIXTURE_PASSWORD}"
|
||||
|
||||
pub_retained() {
|
||||
mosquitto_pub -h "$HOST" -p "$PORT" \
|
||||
-u "$MQTT_FIXTURE_USERNAME" -P "$MQTT_FIXTURE_PASSWORD" \
|
||||
-q 1 -r -t "$1" -m "$2"
|
||||
}
|
||||
|
||||
pub_volatile() {
|
||||
mosquitto_pub -h "$HOST" -p "$PORT" \
|
||||
-u "$MQTT_FIXTURE_USERNAME" -P "$MQTT_FIXTURE_PASSWORD" \
|
||||
-q 0 -t "$1" -m "$2"
|
||||
}
|
||||
|
||||
echo "publisher: broker=$HOST:$PORT prefix=$PREFIX interval=${INTERVAL}s"
|
||||
|
||||
# The one-shot retained seed. Published before the loop starts and never again.
|
||||
pub_retained "$PREFIX/retained/seed" \
|
||||
'{"value":42.5,"unit":"C","note":"published once at fixture start; never republished"}'
|
||||
echo "publisher: retained seed published on $PREFIX/retained/seed"
|
||||
|
||||
n=0
|
||||
while true; do
|
||||
n=$((n + 1))
|
||||
temperature=$(awk "BEGIN { printf \"%.2f\", 20 + ($n % 50) / 10 }")
|
||||
|
||||
pub_retained "$PREFIX/line1/temperature" \
|
||||
"{\"value\":$temperature,\"unit\":\"C\",\"seq\":$n}"
|
||||
pub_retained "$PREFIX/line1/counter" "{\"value\":$n}"
|
||||
pub_retained "$PREFIX/line1/state" "RUNNING"
|
||||
|
||||
# Unauthored chatter — the driver must never materialise a tag for this.
|
||||
pub_volatile "$PREFIX/noise/chatter" "{\"value\":$n,\"source\":\"chatter\"}"
|
||||
|
||||
sleep "$INTERVAL"
|
||||
done
|
||||
Reference in New Issue
Block a user