mirror of
https://github.com/home-assistant/core.git
synced 2025-11-10 03:19:34 +00:00
* Replace `FirmwareGuess` with `FirmwareInfo` with owner tracking
* Fix up config flow
* Account for OTBR addon existing independent of integration
* Fix remaining unit tests
* Add some tests for ownership
* Unit test `get_zha_firmware_info`
* ZHA `homeassistant_hardware` platform
* OTBR `homeassistant_hardware` platform
* Rework imports
* Fix unit tests
* Add OTBR unit tests
* Add hassfest exemption for `homeassistant_hardware` and `otbr`
* Invert registration to decouple the hardware integration
* Revert "Add hassfest exemption for `homeassistant_hardware` and `otbr`"
This reverts commit c8c6e7044f.
* Fix circular imports
* Fix unit tests
* Address review comments
* Simplify API a little
* Fix `| None` mypy issues
* Remove the `unregister_firmware_info_provider` API
* 100% coverage
* Add `HardwareInfoDispatcher.register_firmware_info_callback`
* Unit test `register_firmware_info_callback` (zha)
* Unit test `register_firmware_info_callback` (otbr)
* Update existing hardware helper tests to use the new function
* Add `async_` prefix to helper function names
* Move OTBR implementation to a separate PR
* Update ZHA diagnostics snapshot
* Switch from `dict.setdefault` to `defaultdict`
* Add some error handling to `iter_firmware_info` and increase test coverage
* Oops
186 lines
6.5 KiB
Python
186 lines
6.5 KiB
Python
"""Test hardware helpers."""
|
|
|
|
import logging
|
|
from unittest.mock import AsyncMock, MagicMock, Mock, call
|
|
|
|
import pytest
|
|
|
|
from homeassistant.components.homeassistant_hardware.const import DATA_COMPONENT
|
|
from homeassistant.components.homeassistant_hardware.helpers import (
|
|
async_notify_firmware_info,
|
|
async_register_firmware_info_callback,
|
|
async_register_firmware_info_provider,
|
|
)
|
|
from homeassistant.components.homeassistant_hardware.util import (
|
|
ApplicationType,
|
|
FirmwareInfo,
|
|
)
|
|
from homeassistant.config_entries import ConfigEntryState
|
|
from homeassistant.core import HomeAssistant
|
|
from homeassistant.setup import async_setup_component
|
|
|
|
from tests.common import MockConfigEntry
|
|
|
|
FIRMWARE_INFO_EZSP = FirmwareInfo(
|
|
device="/dev/serial/by-id/device1",
|
|
firmware_type=ApplicationType.EZSP,
|
|
firmware_version=None,
|
|
source="zha",
|
|
owners=[AsyncMock(is_running=AsyncMock(return_value=True))],
|
|
)
|
|
|
|
FIRMWARE_INFO_SPINEL = FirmwareInfo(
|
|
device="/dev/serial/by-id/device2",
|
|
firmware_type=ApplicationType.SPINEL,
|
|
firmware_version=None,
|
|
source="otbr",
|
|
owners=[AsyncMock(is_running=AsyncMock(return_value=True))],
|
|
)
|
|
|
|
|
|
async def test_dispatcher_registration(hass: HomeAssistant) -> None:
|
|
"""Test HardwareInfoDispatcher registration."""
|
|
|
|
await async_setup_component(hass, "homeassistant_hardware", {})
|
|
|
|
# Mock provider 1 with a synchronous method to pull firmware info
|
|
provider1_config_entry = MockConfigEntry(
|
|
domain="zha",
|
|
unique_id="some_unique_id1",
|
|
data={},
|
|
)
|
|
provider1_config_entry.add_to_hass(hass)
|
|
provider1_config_entry.mock_state(hass, ConfigEntryState.LOADED)
|
|
|
|
provider1_firmware = MagicMock(spec=["get_firmware_info"])
|
|
provider1_firmware.get_firmware_info = MagicMock(return_value=FIRMWARE_INFO_EZSP)
|
|
async_register_firmware_info_provider(hass, "zha", provider1_firmware)
|
|
|
|
# Mock provider 2 with an asynchronous method to pull firmware info
|
|
provider2_config_entry = MockConfigEntry(
|
|
domain="otbr",
|
|
unique_id="some_unique_id2",
|
|
data={},
|
|
)
|
|
provider2_config_entry.add_to_hass(hass)
|
|
provider2_config_entry.mock_state(hass, ConfigEntryState.LOADED)
|
|
|
|
provider2_firmware = MagicMock(spec=["async_get_firmware_info"])
|
|
provider2_firmware.async_get_firmware_info = AsyncMock(
|
|
return_value=FIRMWARE_INFO_SPINEL
|
|
)
|
|
async_register_firmware_info_provider(hass, "otbr", provider2_firmware)
|
|
|
|
# Double registration won't work
|
|
with pytest.raises(ValueError, match="Domain zha is already registered"):
|
|
async_register_firmware_info_provider(hass, "zha", provider1_firmware)
|
|
|
|
# We can iterate over the results
|
|
info = [i async for i in hass.data[DATA_COMPONENT].iter_firmware_info()]
|
|
assert info == [
|
|
FIRMWARE_INFO_EZSP,
|
|
FIRMWARE_INFO_SPINEL,
|
|
]
|
|
|
|
callback1 = Mock()
|
|
cancel1 = async_register_firmware_info_callback(
|
|
hass, "/dev/serial/by-id/device1", callback1
|
|
)
|
|
|
|
callback2 = Mock()
|
|
cancel2 = async_register_firmware_info_callback(
|
|
hass, "/dev/serial/by-id/device2", callback2
|
|
)
|
|
|
|
# And receive notification callbacks
|
|
await async_notify_firmware_info(hass, "zha", firmware_info=FIRMWARE_INFO_EZSP)
|
|
await async_notify_firmware_info(hass, "otbr", firmware_info=FIRMWARE_INFO_SPINEL)
|
|
await async_notify_firmware_info(hass, "zha", firmware_info=FIRMWARE_INFO_EZSP)
|
|
cancel1()
|
|
await async_notify_firmware_info(hass, "zha", firmware_info=FIRMWARE_INFO_EZSP)
|
|
await async_notify_firmware_info(hass, "otbr", firmware_info=FIRMWARE_INFO_SPINEL)
|
|
cancel2()
|
|
|
|
assert callback1.mock_calls == [
|
|
call(FIRMWARE_INFO_EZSP),
|
|
call(FIRMWARE_INFO_EZSP),
|
|
]
|
|
|
|
assert callback2.mock_calls == [
|
|
call(FIRMWARE_INFO_SPINEL),
|
|
call(FIRMWARE_INFO_SPINEL),
|
|
]
|
|
|
|
|
|
async def test_dispatcher_iter_error_handling(
|
|
hass: HomeAssistant, caplog: pytest.LogCaptureFixture
|
|
) -> None:
|
|
"""Test HardwareInfoDispatcher ignoring errors from firmware info providers."""
|
|
|
|
await async_setup_component(hass, "homeassistant_hardware", {})
|
|
|
|
provider1_config_entry = MockConfigEntry(
|
|
domain="zha",
|
|
unique_id="some_unique_id1",
|
|
data={},
|
|
)
|
|
provider1_config_entry.add_to_hass(hass)
|
|
provider1_config_entry.mock_state(hass, ConfigEntryState.LOADED)
|
|
|
|
provider1_firmware = MagicMock(spec=["get_firmware_info"])
|
|
provider1_firmware.get_firmware_info = MagicMock(side_effect=Exception("Boom!"))
|
|
async_register_firmware_info_provider(hass, "zha", provider1_firmware)
|
|
|
|
provider2_config_entry = MockConfigEntry(
|
|
domain="otbr",
|
|
unique_id="some_unique_id2",
|
|
data={},
|
|
)
|
|
provider2_config_entry.add_to_hass(hass)
|
|
provider2_config_entry.mock_state(hass, ConfigEntryState.LOADED)
|
|
|
|
provider2_firmware = MagicMock(spec=["async_get_firmware_info"])
|
|
provider2_firmware.async_get_firmware_info = AsyncMock(
|
|
return_value=FIRMWARE_INFO_SPINEL
|
|
)
|
|
async_register_firmware_info_provider(hass, "otbr", provider2_firmware)
|
|
|
|
with caplog.at_level(logging.ERROR):
|
|
info = [i async for i in hass.data[DATA_COMPONENT].iter_firmware_info()]
|
|
|
|
assert info == [FIRMWARE_INFO_SPINEL]
|
|
assert "Error while getting firmware info from" in caplog.text
|
|
|
|
|
|
async def test_dispatcher_callback_error_handling(
|
|
hass: HomeAssistant, caplog: pytest.LogCaptureFixture
|
|
) -> None:
|
|
"""Test HardwareInfoDispatcher ignoring errors from firmware info callbacks."""
|
|
|
|
await async_setup_component(hass, "homeassistant_hardware", {})
|
|
provider1_config_entry = MockConfigEntry(
|
|
domain="zha",
|
|
unique_id="some_unique_id1",
|
|
data={},
|
|
)
|
|
provider1_config_entry.add_to_hass(hass)
|
|
provider1_config_entry.mock_state(hass, ConfigEntryState.LOADED)
|
|
|
|
provider1_firmware = MagicMock(spec=["get_firmware_info"])
|
|
provider1_firmware.get_firmware_info = MagicMock(return_value=FIRMWARE_INFO_EZSP)
|
|
async_register_firmware_info_provider(hass, "zha", provider1_firmware)
|
|
|
|
callback1 = Mock(side_effect=Exception("Some error"))
|
|
async_register_firmware_info_callback(hass, "/dev/serial/by-id/device1", callback1)
|
|
|
|
callback2 = Mock()
|
|
async_register_firmware_info_callback(hass, "/dev/serial/by-id/device1", callback2)
|
|
|
|
with caplog.at_level(logging.ERROR):
|
|
await async_notify_firmware_info(hass, "zha", firmware_info=FIRMWARE_INFO_EZSP)
|
|
|
|
assert "Error while notifying firmware info listener" in caplog.text
|
|
|
|
assert callback1.mock_calls == [call(FIRMWARE_INFO_EZSP)]
|
|
assert callback2.mock_calls == [call(FIRMWARE_INFO_EZSP)]
|