"""Test Zeroconf component setup process.""" from unittest.mock import patch import pytest from zeroconf import ServiceInfo, ServiceStateChange from homeassistant.components import zeroconf from homeassistant.generated import zeroconf as zc_gen from homeassistant.setup import async_setup_component @pytest.fixture def mock_zeroconf(): """Mock zeroconf.""" with patch("homeassistant.components.zeroconf.Zeroconf") as mock_zc: yield mock_zc.return_value def service_update_mock(zeroconf, service, handlers): """Call service update handler.""" handlers[0](zeroconf, service, f"name.{service}", ServiceStateChange.Added) def get_service_info_mock(service_type, name): """Return service info for get_service_info.""" return ServiceInfo( service_type, name, address=b"\n\x00\x00\x14", port=80, weight=0, priority=0, server="name.local.", properties={b"macaddress": b"ABCDEF012345", b"non-utf8-value": b"ABCDEF\x8a"}, ) def get_homekit_info_mock(model): """Return homekit info for get_service_info.""" def mock_homekit_info(service_type, name): return ServiceInfo( service_type, name, address=b"\n\x00\x00\x14", port=80, weight=0, priority=0, server="name.local.", properties={b"md": model.encode()}, ) return mock_homekit_info async def test_setup(hass, mock_zeroconf): """Test configured options for a device are loaded via config entry.""" with patch.object(hass.config_entries, "flow") as mock_config_flow, patch.object( zeroconf, "ServiceBrowser", side_effect=service_update_mock ) as mock_service_browser: mock_zeroconf.get_service_info.side_effect = get_service_info_mock assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}}) assert len(mock_service_browser.mock_calls) == len(zc_gen.ZEROCONF) assert len(mock_config_flow.mock_calls) == len(zc_gen.ZEROCONF) * 2 async def test_homekit_match_partial(hass, mock_zeroconf): """Test configured options for a device are loaded via config entry.""" with patch.dict( zc_gen.ZEROCONF, {zeroconf.HOMEKIT_TYPE: ["homekit_controller"]}, clear=True ), patch.object(hass.config_entries, "flow") as mock_config_flow, patch.object( zeroconf, "ServiceBrowser", side_effect=service_update_mock ) as mock_service_browser: mock_zeroconf.get_service_info.side_effect = get_homekit_info_mock("LIFX bulb") assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}}) assert len(mock_service_browser.mock_calls) == 1 assert len(mock_config_flow.mock_calls) == 2 assert mock_config_flow.mock_calls[0][1][0] == "lifx" async def test_homekit_match_full(hass, mock_zeroconf): """Test configured options for a device are loaded via config entry.""" with patch.dict( zc_gen.ZEROCONF, {zeroconf.HOMEKIT_TYPE: ["homekit_controller"]}, clear=True ), patch.object(hass.config_entries, "flow") as mock_config_flow, patch.object( zeroconf, "ServiceBrowser", side_effect=service_update_mock ) as mock_service_browser: mock_zeroconf.get_service_info.side_effect = get_homekit_info_mock("BSB002") assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}}) assert len(mock_service_browser.mock_calls) == 1 assert len(mock_config_flow.mock_calls) == 2 assert mock_config_flow.mock_calls[0][1][0] == "hue" async def test_info_from_service_non_utf8(hass): """Test info_from_service handles non UTF-8 property values correctly.""" service_type = "_test._tcp.local." info = zeroconf.info_from_service( get_service_info_mock(service_type, f"test.{service_type}") ) raw_info = info["properties"].pop("_raw", False) assert raw_info assert len(info["properties"]) <= len(raw_info) assert "non-utf8-value" not in info["properties"] assert raw_info["non-utf8-value"] is not None