From f4d4b522045369494796e340f56537b6f8adb91f Mon Sep 17 00:00:00 2001 From: ollo69 <60491700+ollo69@users.noreply.github.com> Date: Mon, 18 Apr 2022 21:20:40 +0200 Subject: [PATCH] Improve AsusWrt test coverage (#70215) Co-authored-by: J. Nick Koston --- .coveragerc | 2 - homeassistant/components/asuswrt/router.py | 7 +- tests/components/asuswrt/test_sensor.py | 170 +++++++++++++++++---- 3 files changed, 145 insertions(+), 34 deletions(-) diff --git a/.coveragerc b/.coveragerc index 787bf7966ac0..d2d3a19d6ab5 100644 --- a/.coveragerc +++ b/.coveragerc @@ -83,9 +83,7 @@ omit = homeassistant/components/aseko_pool_live/sensor.py homeassistant/components/asterisk_cdr/mailbox.py homeassistant/components/asterisk_mbox/* - homeassistant/components/asuswrt/__init__.py homeassistant/components/asuswrt/diagnostics.py - homeassistant/components/asuswrt/router.py homeassistant/components/aten_pe/* homeassistant/components/atome/* homeassistant/components/aurora/__init__.py diff --git a/homeassistant/components/asuswrt/router.py b/homeassistant/components/asuswrt/router.py index 67e592c71ef9..0664582f03bf 100644 --- a/homeassistant/components/asuswrt/router.py +++ b/homeassistant/components/asuswrt/router.py @@ -277,7 +277,7 @@ class AsusWrtRouter: # migrate entity unique ID if wrong formatted if device_mac != entry.unique_id: existing_entity_id = entity_reg.async_get_entity_id( - DOMAIN, TRACKER_DOMAIN, device_mac + TRACKER_DOMAIN, DOMAIN, device_mac ) if existing_entity_id: # entity with uniqueid properly formatted already @@ -470,11 +470,6 @@ class AsusWrtRouter: """Return sensors coordinators.""" return self._sensors_coordinator - @property - def api(self) -> AsusWrt: - """Return router API.""" - return self._api - async def _get_nvram_info(api: AsusWrt, info_type: str) -> dict[str, Any]: """Get AsusWrt router info from nvram.""" diff --git a/tests/components/asuswrt/test_sensor.py b/tests/components/asuswrt/test_sensor.py index bfb62dae7e05..3e0c01071c11 100644 --- a/tests/components/asuswrt/test_sensor.py +++ b/tests/components/asuswrt/test_sensor.py @@ -6,9 +6,10 @@ from aioasuswrt.asuswrt import Device import pytest from homeassistant.components import device_tracker, sensor -from homeassistant.components.asuswrt.const import DOMAIN +from homeassistant.components.asuswrt.const import CONF_INTERFACE, DOMAIN from homeassistant.components.asuswrt.sensor import DEFAULT_PREFIX from homeassistant.components.device_tracker.const import CONF_CONSIDER_HOME +from homeassistant.config_entries import ConfigEntryState from homeassistant.const import ( CONF_HOST, CONF_MODE, @@ -18,6 +19,7 @@ from homeassistant.const import ( CONF_USERNAME, STATE_HOME, STATE_NOT_HOME, + STATE_UNAVAILABLE, ) from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.util import slugify @@ -31,7 +33,7 @@ IP_ADDRESS = "192.168.1.1" CONFIG_DATA = { CONF_HOST: HOST, CONF_PORT: 22, - CONF_PROTOCOL: "ssh", + CONF_PROTOCOL: "telnet", CONF_USERNAME: "user", CONF_PASSWORD: "pwd", CONF_MODE: "router", @@ -41,9 +43,10 @@ MOCK_BYTES_TOTAL = [60000000000, 50000000000] MOCK_CURRENT_TRANSFER_RATES = [20000000, 10000000] MOCK_LOAD_AVG = [1.1, 1.2, 1.3] MOCK_TEMPERATURES = {"2.4GHz": 40, "5.0GHz": 0, "CPU": 71.2} -MOCK_MAC_1 = "a1:b1:c1:d1:e1:f1" -MOCK_MAC_2 = "a2:b2:c2:d2:e2:f2" -MOCK_MAC_3 = "a3:b3:c3:d3:e3:f3" +MOCK_MAC_1 = "A1:B1:C1:D1:E1:F1" +MOCK_MAC_2 = "A2:B2:C2:D2:E2:F2" +MOCK_MAC_3 = "A3:B3:C3:D3:E3:F3" +MOCK_MAC_4 = "A4:B4:C4:D4:E4:F4" SENSOR_NAMES = [ "Devices Connected", @@ -79,21 +82,20 @@ def mock_available_temps_list(): @pytest.fixture(name="create_device_registry_devices") def create_device_registry_devices_fixture(hass): - """Create device registry devices so the device tracker entities are enabled.""" + """Create device registry devices so the device tracker entities are enabled when added.""" dev_reg = dr.async_get(hass) config_entry = MockConfigEntry(domain="something_else") for idx, device in enumerate( ( - MOCK_MAC_1, - MOCK_MAC_2, MOCK_MAC_3, + MOCK_MAC_4, ) ): dev_reg.async_get_or_create( name=f"Device {idx}", config_entry_id=config_entry.entry_id, - connections={(dr.CONNECTION_NETWORK_MAC, device)}, + connections={(dr.CONNECTION_NETWORK_MAC, dr.format_mac(device))}, ) @@ -132,14 +134,31 @@ def mock_controller_connect(mock_devices, mock_available_temps): yield service_mock -async def test_sensors( - hass, - connect, - mock_devices, - mock_available_temps, - create_device_registry_devices, -): - """Test creating an AsusWRT sensor.""" +@pytest.fixture(name="connect_sens_fail") +def mock_controller_connect_sens_fail(): + """Mock a successful connection with sensor fail.""" + with patch("homeassistant.components.asuswrt.router.AsusWrt") as service_mock: + service_mock.return_value.connection.async_connect = AsyncMock() + service_mock.return_value.is_connected = True + service_mock.return_value.connection.disconnect = Mock() + service_mock.return_value.async_get_nvram = AsyncMock(side_effect=OSError) + service_mock.return_value.async_get_connected_devices = AsyncMock( + side_effect=OSError + ) + service_mock.return_value.async_get_bytes_total = AsyncMock(side_effect=OSError) + service_mock.return_value.async_get_current_transfer_rates = AsyncMock( + side_effect=OSError + ) + service_mock.return_value.async_get_loadavg = AsyncMock(side_effect=OSError) + service_mock.return_value.async_get_temperature = AsyncMock(side_effect=OSError) + service_mock.return_value.async_find_temperature_commands = AsyncMock( + return_value=[True, True, True] + ) + yield service_mock + + +def _setup_entry(hass): + """Create mock config entry.""" entity_reg = er.async_get(hass) # init config entry @@ -165,6 +184,33 @@ async def test_sensors( disabled_by=None, ) + # Create the first device tracker to test mac conversion + for mac, name in { + MOCK_MAC_1: "test", + dr.format_mac(MOCK_MAC_2): "testtwo", + MOCK_MAC_2: "testremove", + }.items(): + entity_reg.async_get_or_create( + device_tracker.DOMAIN, + DOMAIN, + mac, + suggested_object_id=name, + config_entry=config_entry, + disabled_by=None, + ) + + return config_entry, sensor_prefix + + +async def test_sensors( + hass, + connect, + mock_devices, + mock_available_temps, + create_device_registry_devices, +): + """Test creating an AsusWRT sensor.""" + config_entry, sensor_prefix = _setup_entry(hass) config_entry.add_to_hass(hass) # initial devices setup @@ -189,19 +235,22 @@ async def test_sensors( assert not hass.states.get(f"{sensor_prefix}_5ghz_temperature") assert not hass.states.get(f"{sensor_prefix}_cpu_temperature") - # add one device and remove another + # remove first track device mock_devices.pop(MOCK_MAC_1) - mock_devices[MOCK_MAC_3] = Device(MOCK_MAC_3, "192.168.1.4", "TestThree") async_fire_time_changed(hass, utcnow() + timedelta(seconds=30)) await hass.async_block_till_done() - # consider home option set, all devices still home + # consider home option set, all devices still home but only 1 device connected assert hass.states.get(f"{device_tracker.DOMAIN}.test").state == STATE_HOME assert hass.states.get(f"{device_tracker.DOMAIN}.testtwo").state == STATE_HOME - assert hass.states.get(f"{device_tracker.DOMAIN}.testthree").state == STATE_HOME - assert hass.states.get(f"{sensor_prefix}_devices_connected").state == "2" + assert hass.states.get(f"{sensor_prefix}_devices_connected").state == "1" + # add 2 new device, one unnamed that should be ignored but counted + mock_devices[MOCK_MAC_3] = Device(MOCK_MAC_3, "192.168.1.4", "TestThree") + mock_devices[MOCK_MAC_4] = Device(MOCK_MAC_4, "192.168.1.5", None) + + # change consider home settings to have status not home of removed track device hass.config_entries.async_update_entry( config_entry, options={CONF_CONSIDER_HOME: 0} ) @@ -209,17 +258,86 @@ async def test_sensors( async_fire_time_changed(hass, utcnow() + timedelta(seconds=30)) await hass.async_block_till_done() - # consider home option not set, device "test" not home + # consider home option set to 0, device "test" not home assert hass.states.get(f"{device_tracker.DOMAIN}.test").state == STATE_NOT_HOME + assert hass.states.get(f"{device_tracker.DOMAIN}.testtwo").state == STATE_HOME + assert hass.states.get(f"{device_tracker.DOMAIN}.testthree").state == STATE_HOME + assert hass.states.get(f"{sensor_prefix}_devices_connected").state == "3" # checking temperature sensors without exceptions mock_available_temps.append(True) await hass.config_entries.async_reload(config_entry.entry_id) await hass.async_block_till_done() - async_fire_time_changed(hass, utcnow() + timedelta(seconds=30)) - await hass.async_block_till_done() - assert hass.states.get(f"{sensor_prefix}_load_avg_15m").state == "1.3" assert hass.states.get(f"{sensor_prefix}_2_4ghz_temperature").state == "40.0" assert not hass.states.get(f"{sensor_prefix}_5ghz_temperature") assert hass.states.get(f"{sensor_prefix}_cpu_temperature").state == "71.2" + assert hass.states.get(f"{sensor_prefix}_devices_connected").state == "3" + + # change an option that require integration reload + hass.config_entries.async_update_entry( + config_entry, options={CONF_CONSIDER_HOME: 60, CONF_INTERFACE: "eth1"} + ) + await hass.async_block_till_done() + assert config_entry.state is ConfigEntryState.LOADED + assert hass.states.get(f"{sensor_prefix}_devices_connected").state == "3" + + +@pytest.mark.parametrize( + "side_effect", + [OSError, None], +) +async def test_connect_fail(hass, side_effect): + """Test AsusWRT connect fail.""" + + # init config entry + config_entry = MockConfigEntry( + domain=DOMAIN, + data=CONFIG_DATA, + ) + config_entry.add_to_hass(hass) + + with patch("homeassistant.components.asuswrt.router.AsusWrt") as asus_wrt: + asus_wrt.return_value.connection.async_connect = AsyncMock( + side_effect=side_effect + ) + asus_wrt.return_value.is_connected = False + + # initial setup fail + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + assert config_entry.state is ConfigEntryState.SETUP_RETRY + + +async def test_sensors_polling_fails( + hass, + connect_sens_fail, +): + """Test AsusWRT sensors are unavailable when polling fails.""" + config_entry, sensor_prefix = _setup_entry(hass) + config_entry.add_to_hass(hass) + + # initial devices setup + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + async_fire_time_changed(hass, utcnow() + timedelta(seconds=30)) + await hass.async_block_till_done() + + assert hass.states.get(f"{sensor_prefix}_download_speed").state == STATE_UNAVAILABLE + assert hass.states.get(f"{sensor_prefix}_download").state == STATE_UNAVAILABLE + assert hass.states.get(f"{sensor_prefix}_upload_speed").state == STATE_UNAVAILABLE + assert hass.states.get(f"{sensor_prefix}_upload").state == STATE_UNAVAILABLE + assert hass.states.get(f"{sensor_prefix}_load_avg_1m").state == STATE_UNAVAILABLE + assert hass.states.get(f"{sensor_prefix}_load_avg_5m").state == STATE_UNAVAILABLE + assert hass.states.get(f"{sensor_prefix}_load_avg_15m").state == STATE_UNAVAILABLE + assert hass.states.get(f"{sensor_prefix}_devices_connected").state == "0" + assert ( + hass.states.get(f"{sensor_prefix}_2_4ghz_temperature").state + == STATE_UNAVAILABLE + ) + assert ( + hass.states.get(f"{sensor_prefix}_5ghz_temperature").state == STATE_UNAVAILABLE + ) + assert ( + hass.states.get(f"{sensor_prefix}_cpu_temperature").state == STATE_UNAVAILABLE + )