fix(adr-115/test): per-test MQTT client_id so session-takeover doesn't break state test

The mqtt-integration test suite still failed `state_messages_published_
on_snapshot_broadcast` after the timing fix (5ed8e3451) — but with a
new symptom: 'expected ON state, got []'. The subscriber captured ZERO
messages on the presence state topic.

Root cause: all three integration tests built `client_id` as
`ruview-int-test-<pid>` — the same string for every test in the
sequential cargo-test run. MQTT brokers default to "session takeover":
when a new connect arrives with the same client_id as an existing
session, mosquitto disconnects the old one immediately.

Sequence on CI (`--test-threads=1`):
  1. discovery_topics_appear_on_broker connects (ruview-int-test-1234)
  2. test passes; publisher task continues running in background
  3. privacy_mode_suppresses_biometric_discovery connects (same id)
     → mosquitto kicks test 1's publisher mid-rumqttc-disconnect-handshake
  4. state_messages_published_on_snapshot_broadcast connects (same id)
     → mosquitto kicks test 2's publisher
     → test 3's publisher in turn races with the broker's cleanup
        and its first publishes may land in a half-cleaned session
     → state messages dropped silently

Fix: include a per-test label in the client_id
(`ruview-int-test-<pid>-<label>` — labels: "discovery", "privacy",
"state"). Each test gets its own MQTT session; no cross-test takeover.

Refs PR #778, issue #776.

Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
ruv 2026-05-23 15:44:56 -04:00
parent 00e766ec28
commit 2aeed32a72
1 changed files with 8 additions and 5 deletions

View File

@ -58,13 +58,16 @@ fn should_run() -> Option<u16> {
Some(port)
}
fn make_cfg(port: u16, privacy_mode: bool) -> std::sync::Arc<MqttConfig> {
fn make_cfg(port: u16, privacy_mode: bool, label: &str) -> std::sync::Arc<MqttConfig> {
std::sync::Arc::new(MqttConfig {
host: "127.0.0.1".into(),
port,
username: None,
password: None,
client_id: format!("ruview-int-test-{}", std::process::id()),
// Per-test client_id so cargo test --test-threads=1 doesn't make
// mosquitto kick the previous session when the next test connects
// with the same client_id (default MQTT session-takeover behaviour).
client_id: format!("ruview-int-test-{}-{}", std::process::id(), label),
discovery_prefix: "homeassistant".into(),
tls: TlsConfig::Off,
refresh_secs: 60,
@ -139,7 +142,7 @@ async fn discovery_topics_appear_on_broker() {
subscribe_client(port, &["homeassistant/#"]).await;
// Spawn the publisher.
let cfg = make_cfg(port, false);
let cfg = make_cfg(port, false, "discovery");
let builder = make_builder("inttest1");
let (_tx, rx) = broadcast::channel::<VitalsSnapshot>(32);
let _handle = spawn(cfg, builder, rx);
@ -189,7 +192,7 @@ async fn privacy_mode_suppresses_biometric_discovery() {
let (sub, mut sub_loop) =
subscribe_client(port, &["homeassistant/#"]).await;
let cfg = make_cfg(port, /* privacy_mode = */ true);
let cfg = make_cfg(port, /* privacy_mode = */ true, "privacy");
let builder = make_builder("inttest2");
let (_tx, rx) = broadcast::channel::<VitalsSnapshot>(32);
let _handle = spawn(cfg, builder, rx);
@ -237,7 +240,7 @@ async fn state_messages_published_on_snapshot_broadcast() {
)
.await;
let cfg = make_cfg(port, false);
let cfg = make_cfg(port, false, "state");
let builder = make_builder("inttest3");
let (tx, rx) = broadcast::channel::<VitalsSnapshot>(32);
let _handle = spawn(cfg, builder, rx);