async def test__state_change_removed(self, mock_device: Device): """Test that service information are not processed on state change to removed.""" with patch("devolo_plc_api.device.Device._retry_zeroconf_info"), patch( "devolo_plc_api.device.Device._get_service_info") as gsi: mock_device._state_change(Mock(), PLCNETAPI, PLCNETAPI, ServiceStateChange.Removed) assert gsi.call_count == 0
async def test__not_get_plcnet_info(self, mock_device: Device, device_api: DeviceApi): """Test that devices know to not have a plcnet API don't query it.""" mock_device.mt_number = DEVICES_WITHOUT_PLCNET[0] with patch("devolo_plc_api.device.Device._get_zeroconf_info" ) as gzi, patch( "devolo_plc_api.device.Device._get_device_info"): mock_device.device = device_api await mock_device.async_connect() assert gzi.call_count == 0 assert not mock_device.plcnet
async def test_async_connect(self, mock_device: Device, device_api: DeviceApi, plcnet_api: PlcNetApi): """Test that connecting to a device calls methos to collect information from the APIs.""" with patch( "devolo_plc_api.device.Device._get_device_info") as gdi, patch( "devolo_plc_api.device.Device._get_plcnet_info") as gpi: mock_device.device = device_api mock_device.plcnet = plcnet_api await mock_device.async_connect() assert gdi.call_count == 1 assert gpi.call_count == 1 assert getattr(mock_device, "_connected") await mock_device.async_connect(session_instance=AsyncMock()) assert gdi.call_count == 2 assert gpi.call_count == 2 assert getattr(mock_device, "_connected")
async def test__get_plcnet_info_multicast(self, test_data: TestData): """Test that devices having trouble with unicast zeroconf are queried twice.""" with patch("devolo_plc_api.device.Device._get_zeroconf_info" ) as gzi, pytest.raises(DeviceNotFound): device = Device(test_data.ip) await device.async_connect() assert getattr(device, "_multicast") assert gzi.call_count == 2
async def test__get_zeroconf_info( self, test_data: TestData, mock_service_browser: MockServiceBrowser): """Test that after getting zeroconf information browsing is stopped.""" with patch("devolo_plc_api.device.Device._state_change", state_change): mock_device = Device(ip=test_data.ip, plcnetapi=None, deviceapi=test_data.device_info[DEVICEAPI]) await mock_device.async_connect() assert mock_service_browser.async_cancel.call_count == 1
async def test__state_change_added(self, test_data: TestData): """Test that service information are processed on state change to added.""" with patch("devolo_plc_api.device.Device._get_service_info" ) as gsi, patch( "devolo_plc_api.device.Device._retry_zeroconf_info" ), patch("asyncio.sleep"): mock_device = Device(ip=test_data.ip, plcnetapi=None, deviceapi=test_data.device_info[DEVICEAPI]) await mock_device.async_connect() assert gsi.call_count == 1
async def test__state_change_no_service_info(self, test_data: TestData): """Test that waiting for mDNS responses continues, if no service info were received.""" with patch("devolo_plc_api.device.Device.info_from_service" ) as ifs, patch( "devolo_plc_api.device.AsyncServiceInfo.async_request" ), patch("asyncio.sleep"): mock_device = Device(ip=test_data.ip, plcnetapi=None, deviceapi=test_data.device_info[DEVICEAPI]) await mock_device.async_connect() assert ifs.call_count == 0
async def test__get_service_info(self, test_data: TestData): """Test storing of information discovered via mDNS.""" with patch( "devolo_plc_api.device.AsyncServiceInfo", StubAsyncServiceInfo ), patch("devolo_plc_api.device.PlcNetApi"), patch( "devolo_plc_api.device.AsyncServiceInfo.async_request") as ar: mock_device = Device(ip=test_data.ip, plcnetapi=None, deviceapi=test_data.device_info[DEVICEAPI]) await mock_device.async_connect() assert ar.call_count == 1 assert mock_device.mac == test_data.device_info[PLCNETAPI][ "properties"]["PlcMacAddress"]
async def validate_input(hass: core.HomeAssistant, data: dict[str, Any]) -> dict[str, str]: """Validate the user input allows us to connect. Data has the keys from STEP_USER_DATA_SCHEMA with values provided by the user. """ zeroconf_instance = await zeroconf.async_get_instance(hass) async_client = get_async_client(hass) device = Device(data[CONF_IP_ADDRESS], zeroconf_instance=zeroconf_instance) await device.async_connect(session_instance=async_client) await device.async_disconnect() return { SERIAL_NUMBER: str(device.serial_number), TITLE: device.hostname.split(".")[0], }
async def test__async_wrong_password_type(self, httpx_mock: HTTPXMock, mock_device: Device): """Test using different password hash if original password failed.""" await mock_device.async_connect() assert mock_device.device mock_device.password = "******" httpx_mock.add_response(status_code=HTTPStatus.UNAUTHORIZED) with pytest.raises(DevicePasswordProtected): await mock_device.device.async_get_wifi_connected_station() assert mock_device.device.password == "5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8" httpx_mock.add_response(status_code=HTTPStatus.UNAUTHORIZED) with pytest.raises(DevicePasswordProtected): await mock_device.device.async_get_wifi_connected_station() assert mock_device.device.password == "113459eb7bb31bddee85ade5230d6ad5d8b2fb52879e00a84ff6ae1067a210d3" await mock_device.async_disconnect()
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up devolo Home Network from a config entry.""" hass.data.setdefault(DOMAIN, {}) zeroconf_instance = await zeroconf.async_get_async_instance(hass) async_client = get_async_client(hass) try: device = Device(ip=entry.data[CONF_IP_ADDRESS], zeroconf_instance=zeroconf_instance) await device.async_connect(session_instance=async_client) except DeviceNotFound as err: raise ConfigEntryNotReady( f"Unable to connect to {entry.data[CONF_IP_ADDRESS]}") from err async def async_update_connected_plc_devices() -> dict[str, Any]: """Fetch data from API endpoint.""" try: async with async_timeout.timeout(10): return await device.plcnet.async_get_network_overview( ) # type: ignore[no-any-return, union-attr] except DeviceUnavailable as err: raise UpdateFailed(err) from err async def async_update_wifi_connected_station() -> dict[str, Any]: """Fetch data from API endpoint.""" try: async with async_timeout.timeout(10): return await device.device.async_get_wifi_connected_station( ) # type: ignore[no-any-return, union-attr] except DeviceUnavailable as err: raise UpdateFailed(err) from err async def async_update_wifi_neighbor_access_points() -> dict[str, Any]: """Fetch data from API endpoint.""" try: async with async_timeout.timeout(30): return await device.device.async_get_wifi_neighbor_access_points( ) # type: ignore[no-any-return, union-attr] except DeviceUnavailable as err: raise UpdateFailed(err) from err async def disconnect(event: Event) -> None: """Disconnect from device.""" await device.async_disconnect() coordinators: dict[str, DataUpdateCoordinator] = {} if device.plcnet: coordinators[CONNECTED_PLC_DEVICES] = DataUpdateCoordinator( hass, _LOGGER, name=CONNECTED_PLC_DEVICES, update_method=async_update_connected_plc_devices, update_interval=LONG_UPDATE_INTERVAL, ) if device.device and "wifi1" in device.device.features: coordinators[CONNECTED_WIFI_CLIENTS] = DataUpdateCoordinator( hass, _LOGGER, name=CONNECTED_WIFI_CLIENTS, update_method=async_update_wifi_connected_station, update_interval=SHORT_UPDATE_INTERVAL, ) coordinators[NEIGHBORING_WIFI_NETWORKS] = DataUpdateCoordinator( hass, _LOGGER, name=NEIGHBORING_WIFI_NETWORKS, update_method=async_update_wifi_neighbor_access_points, update_interval=LONG_UPDATE_INTERVAL, ) hass.data[DOMAIN][entry.entry_id] = { "device": device, "coordinators": coordinators } for coordinator in coordinators.values(): await coordinator.async_config_entry_first_refresh() await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) entry.async_on_unload( hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, disconnect)) return True
async def test_async_context_manager(self, test_data: TestData): """Test the async context manager.""" with patch("devolo_plc_api.device.Device._state_change", state_change): async with Device(test_data.ip) as device: assert getattr(device, "_connected") assert not getattr(device, "_connected")
def test_disconnect(self, mock_device: Device): """Test that the sync disconnect method just calls the async disconnect method.""" with patch("devolo_plc_api.device.Device.async_disconnect", new=AsyncMock()) as ad: mock_device.disconnect() assert ad.call_count == 1
def test_info_from_service_no_address(self, mock_device: Device): """Test ignoring information received for an other address.""" service_info = Mock() service_info.addresses = None assert mock_device.info_from_service(service_info) == {}
def test_set_password(self, mock_device: Device, device_api: DeviceApi): """Test setting a device password is also reflected in the device API.""" mock_device.device = device_api mock_device.password = "******" assert mock_device.device.password == "super_secret"