From c8a6c6a5c17f8633653fc0eaabcf00b727772335 Mon Sep 17 00:00:00 2001 From: Sid <27780930+autinerd@users.noreply.github.com> Date: Fri, 19 Jul 2024 10:01:46 +0200 Subject: [PATCH] Add fallback for webmin systems without MAC address (#113261) --- .../components/webmin/config_flow.py | 7 +- .../components/webmin/coordinator.py | 22 ++-- homeassistant/components/webmin/sensor.py | 2 +- tests/components/webmin/conftest.py | 11 +- .../fixtures/webmin_update_without_mac.json | 108 ++++++++++++++++++ tests/components/webmin/test_config_flow.py | 9 +- tests/components/webmin/test_init.py | 8 ++ 7 files changed, 149 insertions(+), 18 deletions(-) create mode 100644 tests/components/webmin/fixtures/webmin_update_without_mac.json diff --git a/homeassistant/components/webmin/config_flow.py b/homeassistant/components/webmin/config_flow.py index 5fa3aefb048..3f55bbd9110 100644 --- a/homeassistant/components/webmin/config_flow.py +++ b/homeassistant/components/webmin/config_flow.py @@ -53,9 +53,10 @@ async def validate_user_input( except Exception as err: raise SchemaFlowError("unknown") from err - await cast(SchemaConfigFlowHandler, handler.parent_handler).async_set_unique_id( - get_sorted_mac_addresses(data)[0] - ) + if len(mac_addresses := get_sorted_mac_addresses(data)) > 0: + await cast(SchemaConfigFlowHandler, handler.parent_handler).async_set_unique_id( + mac_addresses[0] + ) return user_input diff --git a/homeassistant/components/webmin/coordinator.py b/homeassistant/components/webmin/coordinator.py index dab5e495c1a..45261787e75 100644 --- a/homeassistant/components/webmin/coordinator.py +++ b/homeassistant/components/webmin/coordinator.py @@ -23,6 +23,7 @@ class WebminUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]): """The Webmin data update coordinator.""" mac_address: str + unique_id: str def __init__(self, hass: HomeAssistant, config_entry: ConfigEntry) -> None: """Initialize the Webmin data update coordinator.""" @@ -41,14 +42,19 @@ class WebminUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]): async def async_setup(self) -> None: """Provide needed data to the device info.""" mac_addresses = get_sorted_mac_addresses(self.data) - self.mac_address = mac_addresses[0] - self.device_info[ATTR_CONNECTIONS] = { - (CONNECTION_NETWORK_MAC, format_mac(mac_address)) - for mac_address in mac_addresses - } - self.device_info[ATTR_IDENTIFIERS] = { - (DOMAIN, format_mac(mac_address)) for mac_address in mac_addresses - } + if len(mac_addresses) > 0: + self.mac_address = mac_addresses[0] + self.unique_id = self.mac_address + self.device_info[ATTR_CONNECTIONS] = { + (CONNECTION_NETWORK_MAC, format_mac(mac_address)) + for mac_address in mac_addresses + } + self.device_info[ATTR_IDENTIFIERS] = { + (DOMAIN, format_mac(mac_address)) for mac_address in mac_addresses + } + else: + assert self.config_entry + self.unique_id = self.config_entry.entry_id async def _async_update_data(self) -> dict[str, Any]: data = await self.instance.update() diff --git a/homeassistant/components/webmin/sensor.py b/homeassistant/components/webmin/sensor.py index cf1a9845c02..785140393a2 100644 --- a/homeassistant/components/webmin/sensor.py +++ b/homeassistant/components/webmin/sensor.py @@ -235,7 +235,7 @@ class WebminSensor(CoordinatorEntity[WebminUpdateCoordinator], SensorEntity): super().__init__(coordinator) self.entity_description = description self._attr_device_info = coordinator.device_info - self._attr_unique_id = f"{coordinator.mac_address}_{description.key}" + self._attr_unique_id = f"{coordinator.unique_id}_{description.key}" @property def native_value(self) -> int | float: diff --git a/tests/components/webmin/conftest.py b/tests/components/webmin/conftest.py index 388297f9665..ae0d7b26b5a 100644 --- a/tests/components/webmin/conftest.py +++ b/tests/components/webmin/conftest.py @@ -37,14 +37,21 @@ def mock_setup_entry() -> Generator[AsyncMock]: yield mock_setup -async def async_init_integration(hass: HomeAssistant) -> MockConfigEntry: +async def async_init_integration( + hass: HomeAssistant, with_mac_address: bool = True +) -> MockConfigEntry: """Set up the Webmin integration in Home Assistant.""" entry = MockConfigEntry(domain=DOMAIN, options=TEST_USER_INPUT, title="name") entry.add_to_hass(hass) with patch( "homeassistant.components.webmin.helpers.WebminInstance.update", - return_value=load_json_object_fixture("webmin_update.json", DOMAIN), + return_value=load_json_object_fixture( + "webmin_update.json" + if with_mac_address + else "webmin_update_without_mac.json", + DOMAIN, + ), ): await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/webmin/fixtures/webmin_update_without_mac.json b/tests/components/webmin/fixtures/webmin_update_without_mac.json new file mode 100644 index 00000000000..e79c54d0ff2 --- /dev/null +++ b/tests/components/webmin/fixtures/webmin_update_without_mac.json @@ -0,0 +1,108 @@ +{ + "disk_total": 18104905818112, + "io": [0, 4], + "load": [ + 1.29, + 1.36, + 1.37, + 3589, + "Intel(R) Core(TM) i7-5820K CPU @ 3.30GHz", + "GenuineIntel", + 15728640, + 12 + ], + "disk_free": 7749321486336, + "kernel": { "os": "Linux", "arch": "x86_64", "version": "6.6.18-1-lts" }, + "disk_fs": [ + { + "device": "UUID=00000000-80b6-0000-8a06-000000000000", + "dir": "/", + "ifree": 14927206, + "total": 248431161344, + "used_percent": 80, + "type": "ext4", + "itotal": 15482880, + "iused": 555674, + "free": 49060442112, + "used": 186676502528, + "iused_percent": 4 + }, + { + "total": 11903838912512, + "used_percent": 38, + "iused": 3542318, + "type": "ext4", + "itotal": 366198784, + "device": "/dev/md127", + "ifree": 362656466, + "dir": "/media/disk2", + "iused_percent": 1, + "free": 7028764823552, + "used": 4275077644288 + }, + { + "dir": "/media/disk1", + "ifree": 183130757, + "device": "UUID=00000000-2bb2-0000-896c-000000000000", + "type": "ext4", + "itotal": 183140352, + "iused": 9595, + "used_percent": 89, + "total": 5952635744256, + "used": 4981066997760, + "free": 671496220672, + "iused_percent": 1 + } + ], + "drivetemps": [ + { "temp": 49, "device": "/dev/sda", "failed": "", "errors": "" }, + { "failed": "", "errors": "", "device": "/dev/sdb", "temp": 49 }, + { "device": "/dev/sdc", "temp": 51, "failed": "", "errors": "" }, + { "failed": "", "errors": "", "device": "/dev/sdd", "temp": 51 }, + { "errors": "", "failed": "", "temp": 43, "device": "/dev/sde" }, + { "device": "/dev/sdf", "temp": 40, "errors": "", "failed": "" } + ], + "mem": [32766344, 28530480, 1953088, 1944384, 27845756, ""], + "disk_used": 9442821144576, + "cputemps": [ + { "temp": 51, "core": 0 }, + { "temp": 49, "core": 1 }, + { "core": 2, "temp": 59 }, + { "temp": 51, "core": 3 }, + { "temp": 50, "core": 4 }, + { "temp": 49, "core": 5 } + ], + "procs": 310, + "cpu": [0, 8, 92, 0, 0], + "cpufans": [ + { "rpm": 0, "fan": 1 }, + { "fan": 2, "rpm": 1371 }, + { "rpm": 0, "fan": 3 }, + { "rpm": 927, "fan": 4 }, + { "rpm": 801, "fan": 5 } + ], + "load_1m": 1.29, + "load_5m": 1.36, + "load_15m": 1.37, + "mem_total": 32766344, + "mem_free": 28530480, + "swap_total": 1953088, + "swap_free": 1944384, + "uptime": { "days": 11, "minutes": 1, "seconds": 28 }, + "active_interfaces": [ + { + "scope6": ["host"], + "address": "127.0.0.1", + "address6": ["::1"], + "name": "lo", + "broadcast": 0, + "up": 1, + "index": 0, + "fullname": "lo", + "netmask6": [128], + "netmask": "255.0.0.0", + "mtu": 65536, + "edit": 1 + } + ] +} diff --git a/tests/components/webmin/test_config_flow.py b/tests/components/webmin/test_config_flow.py index a9f5eafc5c7..477ad230622 100644 --- a/tests/components/webmin/test_config_flow.py +++ b/tests/components/webmin/test_config_flow.py @@ -33,15 +33,16 @@ async def user_flow(hass: HomeAssistant) -> str: return result["flow_id"] +@pytest.mark.parametrize( + "fixture", ["webmin_update_without_mac.json", "webmin_update.json"] +) async def test_form_user( - hass: HomeAssistant, - user_flow: str, - mock_setup_entry: AsyncMock, + hass: HomeAssistant, user_flow: str, mock_setup_entry: AsyncMock, fixture: str ) -> None: """Test a successful user initiated flow.""" with patch( "homeassistant.components.webmin.helpers.WebminInstance.update", - return_value=load_json_object_fixture("webmin_update.json", DOMAIN), + return_value=load_json_object_fixture(fixture, DOMAIN), ): result = await hass.config_entries.flow.async_configure( user_flow, TEST_USER_INPUT diff --git a/tests/components/webmin/test_init.py b/tests/components/webmin/test_init.py index 7b6282edfae..36894f00d5f 100644 --- a/tests/components/webmin/test_init.py +++ b/tests/components/webmin/test_init.py @@ -19,3 +19,11 @@ async def test_unload_entry(hass: HomeAssistant) -> None: assert entry.state is ConfigEntryState.NOT_LOADED assert not hass.data.get(DOMAIN) + + +async def test_entry_without_mac_address(hass: HomeAssistant) -> None: + """Test an entry without MAC address.""" + + entry = await async_init_integration(hass, False) + + assert entry.runtime_data.unique_id == entry.entry_id