mirror of
https://github.com/home-assistant/core.git
synced 2026-08-24 10:13:52 -05:00
Refactor thermopro tests to avoid thermopro_sensor.async_setup_entry (#173880)
This commit is contained in:
@@ -1,15 +1,6 @@
|
||||
"""Test the ThermoPro sensors."""
|
||||
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.bluetooth.passive_update_processor import (
|
||||
PassiveBluetoothDataProcessor,
|
||||
)
|
||||
from homeassistant.components.sensor import ATTR_STATE_CLASS, SensorEntityDescription
|
||||
import homeassistant.components.thermopro as thermopro_integration
|
||||
from homeassistant.components.thermopro import sensor as thermopro_sensor
|
||||
from homeassistant.components.sensor import ATTR_STATE_CLASS
|
||||
from homeassistant.components.thermopro.const import DOMAIN
|
||||
from homeassistant.const import ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT
|
||||
from homeassistant.core import HomeAssistant
|
||||
@@ -17,7 +8,10 @@ from homeassistant.core import HomeAssistant
|
||||
from . import TP357_SERVICE_INFO, TP962R_SERVICE_INFO, TP962R_SERVICE_INFO_2
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
from tests.components.bluetooth import inject_bluetooth_service_info
|
||||
from tests.components.bluetooth import (
|
||||
inject_bluetooth_service_info,
|
||||
patch_discovered_devices,
|
||||
)
|
||||
|
||||
|
||||
async def test_sensors_tp962r(hass: HomeAssistant) -> None:
|
||||
@@ -136,125 +130,32 @@ async def test_sensors(hass: HomeAssistant) -> None:
|
||||
await hass.async_block_till_done()
|
||||
|
||||
|
||||
class CoordinatorStub:
|
||||
"""Coordinator stub for testing entity restoration behavior."""
|
||||
|
||||
instances: list[CoordinatorStub] = []
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hass: HomeAssistant | None = None,
|
||||
logger: MagicMock | None = None,
|
||||
*,
|
||||
address: str | None = None,
|
||||
mode: MagicMock | None = None,
|
||||
update_method: MagicMock | None = None,
|
||||
) -> None:
|
||||
"""Initialize coordinator stub with signature matching real coordinator."""
|
||||
# Track created instances to avoid direct hass.data access in tests
|
||||
CoordinatorStub.instances.append(self)
|
||||
self.calls: list[tuple[MagicMock, type | None]] = []
|
||||
self._saw_sensor_entity_description = False
|
||||
self._restore_cb: MagicMock | None = None
|
||||
|
||||
def async_register_processor(
|
||||
self, processor: MagicMock, entity_description_cls: type | None = None
|
||||
) -> MagicMock:
|
||||
"""Register a processor and track if SensorEntityDescription was provided."""
|
||||
self.calls.append((processor, entity_description_cls))
|
||||
|
||||
if entity_description_cls is SensorEntityDescription:
|
||||
self._saw_sensor_entity_description = True
|
||||
|
||||
return lambda: None
|
||||
|
||||
def async_start(self) -> MagicMock:
|
||||
"""Return a no-op unsub function for start lifecycle."""
|
||||
return lambda: None
|
||||
|
||||
def trigger_restore_from_test(self) -> None:
|
||||
"""Trigger restoration callback if available."""
|
||||
if self._saw_sensor_entity_description and self._restore_cb:
|
||||
self._restore_cb([])
|
||||
|
||||
def set_restore_callback(self, callback: MagicMock) -> None:
|
||||
"""Set the callback used to restore entities during the test."""
|
||||
self._restore_cb = callback
|
||||
|
||||
|
||||
async def test_thermopro_restores_entities_on_restart_behavior(
|
||||
hass: HomeAssistant, monkeypatch: pytest.MonkeyPatch
|
||||
) -> None:
|
||||
"""Test that entities are restored on restart via SensorEntityDescription."""
|
||||
|
||||
add_entities_callbacks: list[MagicMock] = []
|
||||
|
||||
orig_add_listener = PassiveBluetoothDataProcessor.async_add_entities_listener
|
||||
|
||||
def wrapped_add_listener(
|
||||
self: PassiveBluetoothDataProcessor,
|
||||
entity_cls: type,
|
||||
add_entities: MagicMock,
|
||||
) -> MagicMock:
|
||||
add_entities_callbacks.append(add_entities)
|
||||
return orig_add_listener(self, entity_cls, add_entities)
|
||||
|
||||
monkeypatch.setattr(
|
||||
PassiveBluetoothDataProcessor,
|
||||
"async_add_entities_listener",
|
||||
wrapped_add_listener,
|
||||
async def test_sensors_restored_on_restart(hass: HomeAssistant) -> None:
|
||||
"""Test sensors are restored on restart before a new advertisement arrives."""
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
unique_id="4125DDBA-2774-4851-9889-6AADDD4CAC3D",
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
|
||||
first_called = {"v": False}
|
||||
second_called = {"v": False}
|
||||
|
||||
def add_entities_first(entities: list) -> None:
|
||||
first_called["v"] = True
|
||||
|
||||
def add_entities_second(entities: list) -> None:
|
||||
second_called["v"] = True
|
||||
|
||||
# Patch the integration to avoid platform forwarding and use the coordinator stub
|
||||
monkeypatch.setattr(thermopro_integration, "PLATFORMS", [])
|
||||
monkeypatch.setattr(
|
||||
thermopro_integration, "PassiveBluetoothProcessorCoordinator", CoordinatorStub
|
||||
)
|
||||
# Ensure a clean slate for stub instance tracking
|
||||
CoordinatorStub.instances.clear()
|
||||
|
||||
# First setup using real config entry setup to populate hass.data
|
||||
entry1 = MockConfigEntry(domain=DOMAIN, unique_id="00:11:22:33:44:55")
|
||||
entry1.add_to_hass(hass)
|
||||
assert await hass.config_entries.async_setup(entry1.entry_id)
|
||||
assert await hass.config_entries.async_setup(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Manually set up sensor platform with our callback
|
||||
# pylint: disable-next=home-assistant-tests-direct-platform-async-setup-entry
|
||||
await thermopro_sensor.async_setup_entry(hass, entry1, add_entities_first)
|
||||
inject_bluetooth_service_info(hass, TP357_SERVICE_INFO)
|
||||
await hass.async_block_till_done()
|
||||
assert len(hass.states.async_all()) == 3
|
||||
|
||||
assert await hass.config_entries.async_unload(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
coord = CoordinatorStub.instances[0]
|
||||
assert coord.calls, "Processor was not registered on first setup"
|
||||
assert not first_called["v"]
|
||||
# Simulate a restart with no fresh advertisement: drop the leftover states
|
||||
# and clear the cached advertisements so only the restore storage can
|
||||
# recreate the entities.
|
||||
for state in hass.states.async_all():
|
||||
hass.states.async_remove(state.entity_id)
|
||||
|
||||
# Second setup (simulating restart)
|
||||
entry2 = MockConfigEntry(domain=DOMAIN, unique_id="AA:BB:CC:DD:EE:FF")
|
||||
entry2.add_to_hass(hass)
|
||||
assert await hass.config_entries.async_setup(entry2.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
with patch_discovered_devices([]):
|
||||
assert await hass.config_entries.async_setup(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# pylint: disable-next=home-assistant-tests-direct-platform-async-setup-entry
|
||||
await thermopro_sensor.async_setup_entry(hass, entry2, add_entities_second)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert add_entities_callbacks, "No add_entities callback was registered"
|
||||
coord2 = CoordinatorStub.instances[1]
|
||||
coord2.set_restore_callback(add_entities_callbacks[-1])
|
||||
|
||||
coord2.trigger_restore_from_test()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert second_called["v"], (
|
||||
"ThermoPro did not trigger restoration on startup. "
|
||||
"Ensure async_register_processor(processor, SensorEntityDescription) is used."
|
||||
)
|
||||
assert len(hass.states.async_all()) == 3
|
||||
|
||||
Reference in New Issue
Block a user