From 4911b32c5e43741c3035fae55fbae1769f4d01fe Mon Sep 17 00:00:00 2001 From: Andrei Buzera Date: Tue, 27 Oct 2026 03:50:29 +0200 Subject: [PATCH] Test that reloads leave one relay listener and one polling coordinator Three reloads in a row: a relay change reaches only the current coordinator, replaced coordinators neither learn nor publish, and after unload nothing is recorded or published. Checked to fail when the listener is not unsubscribed. Co-Authored-By: Claude Opus 5.5 --- tests/ha/test_lifecycle.py | 39 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/tests/ha/test_lifecycle.py b/tests/ha/test_lifecycle.py index 01fa9f7..e865a86 100644 --- a/tests/ha/test_lifecycle.py +++ b/tests/ha/test_lifecycle.py @@ -149,6 +149,45 @@ async def test_scheduled_sample_saves_and_unload_stops_polling(hass, entry, inpu assert coordinator.learners["sensor.room"].count == 1 +async def test_reloads_leave_one_relay_listener_and_one_polling_coordinator(hass, entry, inputs, freezer): + coordinators = [await setup(hass, entry)] + for _ in range(3): + assert await hass.config_entries.async_reload(entry.entry_id) + await hass.async_block_till_done() + coordinators.append(hass.data[DOMAIN][entry.entry_id]) + *old, current = coordinators + assert len({id(c) for c in coordinators}) == 4 + # A relay change reaches only the current coordinator. + hass.states.async_set("switch.boiler", "on") + await hass.async_block_till_done() + assert [state for _, state in current._relay_events] == ["on"] + assert not any(c._relay_events for c in old) + # Only the current coordinator polls; the replaced ones neither learn nor publish. + status_id = er.async_get(hass).async_get_entity_id("sensor", DOMAIN, f"{entry.entry_id}_status") + updates = [] + hass.bus.async_listen("state_changed", lambda event: updates.append(event.data["entity_id"])) + before = [(c.learners["sensor.room"].count, c.data) for c in old] + future = dt_util.utcnow() + timedelta(minutes=5, seconds=1) + freezer.move_to(future) + async_fire_time_changed(hass, future) + await hass.async_block_till_done() + assert [(c.learners["sensor.room"].count, c.data) for c in old] == before + assert updates.count(status_id) <= 1 + assert hass.states.get(status_id).state == current.data["status"] + # After unload, a relay change is recorded nowhere and nothing is published. + assert await hass.config_entries.async_unload(entry.entry_id) + await hass.async_block_till_done() + events = list(current._relay_events) + updates.clear() + hass.states.async_set("switch.boiler", "off") + future += timedelta(minutes=5) + freezer.move_to(future) + async_fire_time_changed(hass, future) + await hass.async_block_till_done() + assert list(current._relay_events) == events + assert status_id not in updates + + @pytest.mark.parametrize("payload", ["broken", {"learners": []}, {"learners": {"sensor.room": {"theta": []}}}]) async def test_corrupt_saved_payload_starts_learning(hass, entry, inputs, hass_storage, payload): hass_storage[f"{DOMAIN}.{entry.entry_id}"] = {"version": 1, "data": payload}