mirror of
https://github.com/home-assistant/core.git
synced 2026-09-28 07:54:43 +01:00
bosch_shc: exercise via_device_id test through config entry setup (#178102)
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
eab1a0a0ce
commit
390d419087
@@ -1,10 +1,114 @@
|
||||
"""bosch_shc session fixtures."""
|
||||
|
||||
from unittest.mock import MagicMock
|
||||
from collections.abc import Generator
|
||||
from types import SimpleNamespace
|
||||
from typing import Any
|
||||
from unittest.mock import MagicMock, create_autospec, patch
|
||||
|
||||
from boschshcpy import BatteryLevelService, SHCBatteryDevice
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.bosch_shc.const import (
|
||||
CONF_SSL_CERTIFICATE,
|
||||
CONF_SSL_KEY,
|
||||
DOMAIN,
|
||||
)
|
||||
from homeassistant.const import CONF_HOST
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def bosch_shc_mock_async_zeroconf(mock_async_zeroconf: MagicMock) -> None:
|
||||
"""Auto mock zeroconf."""
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_config_entry() -> MockConfigEntry:
|
||||
"""Mock bosch_shc config entry."""
|
||||
return MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
data={
|
||||
CONF_HOST: "1.1.1.1",
|
||||
CONF_SSL_CERTIFICATE: "cert",
|
||||
CONF_SSL_KEY: "key",
|
||||
},
|
||||
unique_id="test-mac",
|
||||
)
|
||||
|
||||
|
||||
# Keep in sync with binary_sensor.py's device_helper buckets — a bucket
|
||||
# missing here breaks the mock_session fixture.
|
||||
_EMPTY_DEVICE_BUCKETS: dict[str, list[Any]] = {
|
||||
bucket: []
|
||||
for bucket in (
|
||||
"motion_detectors",
|
||||
"shutter_contacts",
|
||||
"shutter_contacts2",
|
||||
"smoke_detectors",
|
||||
"thermostats",
|
||||
"twinguards",
|
||||
"universal_switches",
|
||||
"wallthermostats",
|
||||
"water_leakage_detectors",
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def device_buckets(request: pytest.FixtureRequest) -> dict[str, list[Any]]:
|
||||
"""device_helper buckets for the mock session.
|
||||
|
||||
Empty by default; a test overrides specific buckets via
|
||||
``@pytest.mark.parametrize("device_buckets", [{...}], indirect=True)``.
|
||||
"""
|
||||
overrides: dict[str, list[Any]] = getattr(request, "param", {})
|
||||
return {**_EMPTY_DEVICE_BUCKETS, **overrides}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_session(device_buckets: dict[str, list[Any]]) -> Generator[MagicMock]:
|
||||
"""Mock SHCSession, patched in for the duration of the test."""
|
||||
session = MagicMock()
|
||||
session.information.unique_id = "test-mac"
|
||||
session.information.updateState.name = "UP_TO_DATE"
|
||||
session.information.version = "2.0"
|
||||
session.device_helper = SimpleNamespace(**device_buckets)
|
||||
with patch("homeassistant.components.bosch_shc.SHCSession", return_value=session):
|
||||
yield session
|
||||
|
||||
|
||||
async def setup_integration(
|
||||
hass: HomeAssistant, mock_config_entry: MockConfigEntry
|
||||
) -> None:
|
||||
"""Set up the bosch_shc integration for testing."""
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
assert await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
|
||||
def battery_only_device(
|
||||
device_id: str = "hdm:HomeMaticIP:motion1",
|
||||
name: str = "Motion",
|
||||
device_services: list[Any] | None = None,
|
||||
) -> SHCBatteryDevice:
|
||||
"""Build a minimal device double for the motion_detectors bucket.
|
||||
|
||||
motion_detectors only ever backs a single BatterySensor entity (unlike
|
||||
shutter_contacts, which backs both a ShutterContactSensor and a
|
||||
BatterySensor for the same device) — the single-entity shape keeps these
|
||||
entity.py tests free of a second entity's subscribe/unsubscribe calls.
|
||||
"""
|
||||
device = create_autospec(SHCBatteryDevice, instance=True, spec_set=True)
|
||||
device.name = name
|
||||
device.id = device_id
|
||||
device.root_device_id = "test-mac"
|
||||
device.serial = f"serial-{device_id}"
|
||||
device.batterylevel = BatteryLevelService.State.OK
|
||||
device.device_services = device_services or []
|
||||
device.manufacturer = "Bosch"
|
||||
device.device_model = "MD"
|
||||
device.status = "AVAILABLE"
|
||||
device.deleted = False
|
||||
return device
|
||||
|
||||
@@ -1,78 +1,85 @@
|
||||
"""Tests for the Bosch SHC entity base classes."""
|
||||
|
||||
from unittest.mock import MagicMock
|
||||
from collections.abc import Generator
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from boschshcpy import SHCDevice
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.bosch_shc.const import DOMAIN
|
||||
from homeassistant.components.bosch_shc.entity import SHCEntity
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
|
||||
from .conftest import battery_only_device, setup_integration
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
HUB_IDENTIFIER = (DOMAIN, "test-mac")
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def platforms() -> Generator[None]:
|
||||
"""Restrict bosch_shc setup to the binary_sensor platform."""
|
||||
with patch(
|
||||
"homeassistant.components.bosch_shc.PLATFORMS", [Platform.BINARY_SENSOR]
|
||||
):
|
||||
yield
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def motion_device(mock_session: MagicMock) -> MagicMock:
|
||||
"""The mock device backing the motion detector's battery sensor."""
|
||||
return mock_session.device_helper.motion_detectors[0]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"device_buckets",
|
||||
[{"motion_detectors": [battery_only_device()]}],
|
||||
indirect=True,
|
||||
)
|
||||
@pytest.mark.usefixtures("mock_session")
|
||||
async def test_shc_entity_via_device_id(
|
||||
hass: HomeAssistant, device_registry: dr.DeviceRegistry
|
||||
hass: HomeAssistant,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
motion_device: MagicMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test SHCEntity links its device to the SHC hub via via_device_id."""
|
||||
entry = MockConfigEntry(domain=DOMAIN)
|
||||
entry.add_to_hass(hass)
|
||||
"""SHCEntity links its device to the SHC hub via via_device_id."""
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
hub_device = device_registry.async_get_or_create(
|
||||
config_entry_id=entry.entry_id,
|
||||
identifiers={(DOMAIN, "root-serial")},
|
||||
manufacturer="Bosch",
|
||||
name="Bosch SHC",
|
||||
model="SmartHomeController",
|
||||
hub_device = device_registry.async_get_device(identifiers={HUB_IDENTIFIER})
|
||||
assert hub_device is not None
|
||||
|
||||
child_device = device_registry.async_get_device(
|
||||
identifiers={(DOMAIN, motion_device.id)}
|
||||
)
|
||||
|
||||
device = MagicMock(spec=SHCDevice)
|
||||
device.serial = "child-serial"
|
||||
device.manufacturer = "Bosch"
|
||||
device.device_model = "SWD"
|
||||
device.name = "Shutter Contact"
|
||||
device.id = "child-id"
|
||||
device.root_device_id = "root-serial"
|
||||
|
||||
entity = SHCEntity(
|
||||
hass=hass, device=device, parent_id="root-serial", entry_id=entry.entry_id
|
||||
)
|
||||
|
||||
assert entity.device_info is not None
|
||||
assert entity.device_info["via_device_id"] == hub_device.id
|
||||
assert child_device is not None
|
||||
assert child_device.via_device_id == hub_device.id
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"device_buckets",
|
||||
[{"motion_detectors": [battery_only_device()]}],
|
||||
indirect=True,
|
||||
)
|
||||
@pytest.mark.usefixtures("mock_session")
|
||||
async def test_shc_entity_via_device_id_mismatch(
|
||||
hass: HomeAssistant, device_registry: dr.DeviceRegistry
|
||||
hass: HomeAssistant,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
motion_device: MagicMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test SHCEntity sets up without a link when the hub identifier does not match.
|
||||
"""Setup does not crash and skips the link when the hub identifier does not match.
|
||||
|
||||
boschshcpy may render the hub identifier and a device's root_device_id
|
||||
differently, so the lookup can miss; setup must not crash in that case.
|
||||
differently, so the lookup can miss.
|
||||
"""
|
||||
entry = MockConfigEntry(domain=DOMAIN)
|
||||
entry.add_to_hass(hass)
|
||||
motion_device.root_device_id = "root-serial-mismatch"
|
||||
|
||||
device_registry.async_get_or_create(
|
||||
config_entry_id=entry.entry_id,
|
||||
identifiers={(DOMAIN, "hub-serial")},
|
||||
manufacturer="Bosch",
|
||||
name="Bosch SHC",
|
||||
model="SmartHomeController",
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
child_device = device_registry.async_get_device(
|
||||
identifiers={(DOMAIN, motion_device.id)}
|
||||
)
|
||||
|
||||
device = MagicMock(spec=SHCDevice)
|
||||
device.serial = "child-serial"
|
||||
device.manufacturer = "Bosch"
|
||||
device.device_model = "SWD"
|
||||
device.name = "Shutter Contact"
|
||||
device.id = "child-id"
|
||||
device.root_device_id = "root-serial-mismatch"
|
||||
|
||||
entity = SHCEntity(
|
||||
hass=hass, device=device, parent_id="hub-serial", entry_id=entry.entry_id
|
||||
)
|
||||
|
||||
assert entity.device_info is not None
|
||||
assert "via_device_id" not in entity.device_info
|
||||
assert child_device is not None
|
||||
assert child_device.via_device_id is None
|
||||
|
||||
Reference in New Issue
Block a user