From 23bed25e5206b1ec16c241e1825aad12752e268e Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Tue, 8 Nov 2022 07:48:54 +0100 Subject: [PATCH] Remove old UniFi POE client implementation (#81749) Remove all references to POE client implementation --- homeassistant/components/unifi/__init__.py | 22 ++ homeassistant/components/unifi/config_flow.py | 6 - homeassistant/components/unifi/const.py | 3 - homeassistant/components/unifi/controller.py | 10 +- homeassistant/components/unifi/switch.py | 185 +--------- tests/components/unifi/test_config_flow.py | 3 - tests/components/unifi/test_switch.py | 324 +++--------------- 7 files changed, 66 insertions(+), 487 deletions(-) diff --git a/homeassistant/components/unifi/__init__.py b/homeassistant/components/unifi/__init__.py index 84540f7bea4..e37e89b3da5 100644 --- a/homeassistant/components/unifi/__init__.py +++ b/homeassistant/components/unifi/__init__.py @@ -6,6 +6,7 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.const import EVENT_HOMEASSISTANT_STOP from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady +from homeassistant.helpers import entity_registry as er from homeassistant.helpers.storage import Store from homeassistant.helpers.typing import ConfigType @@ -36,6 +37,9 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b """Set up the UniFi Network integration.""" hass.data.setdefault(UNIFI_DOMAIN, {}) + # Removal of legacy PoE control was introduced with 2022.12 + async_remove_poe_client_entities(hass, config_entry) + # Flat configuration was introduced with 2021.3 await async_flatten_entry_data(hass, config_entry) @@ -82,6 +86,24 @@ async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> return await controller.async_reset() +@callback +def async_remove_poe_client_entities( + hass: HomeAssistant, config_entry: ConfigEntry +) -> None: + """Remove PoE client entities.""" + ent_reg = er.async_get(hass) + + entity_ids_to_be_removed = [ + entry.entity_id + for entry in ent_reg.entities.values() + if entry.config_entry_id == config_entry.entry_id + and entry.unique_id.startswith("poe-") + ] + + for entity_id in entity_ids_to_be_removed: + ent_reg.async_remove(entity_id) + + async def async_flatten_entry_data( hass: HomeAssistant, config_entry: ConfigEntry ) -> None: diff --git a/homeassistant/components/unifi/config_flow.py b/homeassistant/components/unifi/config_flow.py index 4944dd91296..caf256ded20 100644 --- a/homeassistant/components/unifi/config_flow.py +++ b/homeassistant/components/unifi/config_flow.py @@ -37,14 +37,12 @@ from .const import ( CONF_DETECTION_TIME, CONF_DPI_RESTRICTIONS, CONF_IGNORE_WIRED_BUG, - CONF_POE_CLIENTS, CONF_SITE_ID, CONF_SSID_FILTER, CONF_TRACK_CLIENTS, CONF_TRACK_DEVICES, CONF_TRACK_WIRED_CLIENTS, DEFAULT_DPI_RESTRICTIONS, - DEFAULT_POE_CLIENTS, DOMAIN as UNIFI_DOMAIN, ) from .controller import UniFiController, get_unifi_controller @@ -396,10 +394,6 @@ class UnifiOptionsFlowHandler(config_entries.OptionsFlow): vol.Optional( CONF_BLOCK_CLIENT, default=selected_clients_to_block ): cv.multi_select(clients_to_block), - vol.Optional( - CONF_POE_CLIENTS, - default=self.options.get(CONF_POE_CLIENTS, DEFAULT_POE_CLIENTS), - ): bool, vol.Optional( CONF_DPI_RESTRICTIONS, default=self.options.get( diff --git a/homeassistant/components/unifi/const.py b/homeassistant/components/unifi/const.py index bf0aaef45dd..85f744e481f 100644 --- a/homeassistant/components/unifi/const.py +++ b/homeassistant/components/unifi/const.py @@ -25,7 +25,6 @@ CONF_BLOCK_CLIENT = "block_client" CONF_DETECTION_TIME = "detection_time" CONF_DPI_RESTRICTIONS = "dpi_restrictions" CONF_IGNORE_WIRED_BUG = "ignore_wired_bug" -CONF_POE_CLIENTS = "poe_clients" CONF_TRACK_CLIENTS = "track_clients" CONF_TRACK_DEVICES = "track_devices" CONF_TRACK_WIRED_CLIENTS = "track_wired_clients" @@ -35,7 +34,6 @@ DEFAULT_ALLOW_BANDWIDTH_SENSORS = False DEFAULT_ALLOW_UPTIME_SENSORS = False DEFAULT_DPI_RESTRICTIONS = True DEFAULT_IGNORE_WIRED_BUG = False -DEFAULT_POE_CLIENTS = True DEFAULT_TRACK_CLIENTS = True DEFAULT_TRACK_DEVICES = True DEFAULT_TRACK_WIRED_CLIENTS = True @@ -45,5 +43,4 @@ ATTR_MANUFACTURER = "Ubiquiti Networks" BLOCK_SWITCH = "block" DPI_SWITCH = "dpi" -POE_SWITCH = "poe" OUTLET_SWITCH = "outlet" diff --git a/homeassistant/components/unifi/controller.py b/homeassistant/components/unifi/controller.py index c421cb5391a..8aae95bda41 100644 --- a/homeassistant/components/unifi/controller.py +++ b/homeassistant/components/unifi/controller.py @@ -44,7 +44,6 @@ from .const import ( CONF_DETECTION_TIME, CONF_DPI_RESTRICTIONS, CONF_IGNORE_WIRED_BUG, - CONF_POE_CLIENTS, CONF_SITE_ID, CONF_SSID_FILTER, CONF_TRACK_CLIENTS, @@ -55,14 +54,12 @@ from .const import ( DEFAULT_DETECTION_TIME, DEFAULT_DPI_RESTRICTIONS, DEFAULT_IGNORE_WIRED_BUG, - DEFAULT_POE_CLIENTS, DEFAULT_TRACK_CLIENTS, DEFAULT_TRACK_DEVICES, DEFAULT_TRACK_WIRED_CLIENTS, DOMAIN as UNIFI_DOMAIN, LOGGER, PLATFORMS, - POE_SWITCH, UNIFI_WIRELESS_CLIENTS, ) from .errors import AuthenticationRequired, CannotConnect @@ -140,8 +137,6 @@ class UniFiController: # Client control options - # Config entry option to control poe clients. - self.option_poe_clients = options.get(CONF_POE_CLIENTS, DEFAULT_POE_CLIENTS) # Config entry option with list of clients to control network access. self.option_block_clients = options.get(CONF_BLOCK_CLIENT, []) # Config entry option to control DPI restriction groups. @@ -305,9 +300,8 @@ class UniFiController: ): if entry.domain == Platform.DEVICE_TRACKER: mac = entry.unique_id.split("-", 1)[0] - elif entry.domain == Platform.SWITCH and ( - entry.unique_id.startswith(BLOCK_SWITCH) - or entry.unique_id.startswith(POE_SWITCH) + elif entry.domain == Platform.SWITCH and entry.unique_id.startswith( + BLOCK_SWITCH ): mac = entry.unique_id.split("-", 1)[1] else: diff --git a/homeassistant/components/unifi/switch.py b/homeassistant/components/unifi/switch.py index e63d5548ebc..5a88caca807 100644 --- a/homeassistant/components/unifi/switch.py +++ b/homeassistant/components/unifi/switch.py @@ -44,11 +44,9 @@ from homeassistant.helpers.device_registry import ( from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import DeviceInfo, EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.restore_state import RestoreEntity -from .const import ATTR_MANUFACTURER, DOMAIN as UNIFI_DOMAIN, POE_SWITCH +from .const import ATTR_MANUFACTURER, DOMAIN as UNIFI_DOMAIN from .controller import UniFiController -from .unifi_client import UniFiClient CLIENT_BLOCKED = (EventKey.WIRED_CLIENT_BLOCKED, EventKey.WIRELESS_CLIENT_BLOCKED) CLIENT_UNBLOCKED = (EventKey.WIRED_CLIENT_UNBLOCKED, EventKey.WIRELESS_CLIENT_UNBLOCKED) @@ -268,49 +266,15 @@ async def async_setup_entry( ) -> None: """Set up switches for UniFi Network integration.""" controller: UniFiController = hass.data[UNIFI_DOMAIN][config_entry.entry_id] - controller.entities[DOMAIN] = {POE_SWITCH: set()} if controller.site_role != "admin": return - # Store previously known POE control entities in case their POE are turned off. - known_poe_clients = [] - entity_registry = er.async_get(hass) - for entry in er.async_entries_for_config_entry( - entity_registry, config_entry.entry_id - ): - - if not entry.unique_id.startswith(POE_SWITCH): - continue - - mac = entry.unique_id.replace(f"{POE_SWITCH}-", "") - if mac not in controller.api.clients: - continue - - known_poe_clients.append(mac) - for mac in controller.option_block_clients: if mac not in controller.api.clients and mac in controller.api.clients_all: client = controller.api.clients_all[mac] controller.api.clients.process_raw([client.raw]) - @callback - def items_added( - clients: set = controller.api.clients, - devices: set = controller.api.devices, - ) -> None: - """Update the values of the controller.""" - if controller.option_poe_clients: - add_poe_entities(controller, async_add_entities, clients, known_poe_clients) - - for signal in (controller.signal_update, controller.signal_options_update): - config_entry.async_on_unload( - async_dispatcher_connect(hass, signal, items_added) - ) - - items_added() - known_poe_clients.clear() - @callback def async_load_entities(description: UnifiEntityDescription) -> None: """Load and subscribe to UniFi devices.""" @@ -341,153 +305,6 @@ async def async_setup_entry( async_load_entities(description) -@callback -def add_poe_entities(controller, async_add_entities, clients, known_poe_clients): - """Add new switch entities from the controller.""" - switches = [] - - devices = controller.api.devices - - for mac in clients: - if mac in controller.entities[DOMAIN][POE_SWITCH]: - continue - - client = controller.api.clients[mac] - - # Try to identify new clients powered by POE. - # Known POE clients have been created in previous HASS sessions. - # If port_poe is None the port does not support POE - # If poe_enable is False we can't know if a POE client is available for control. - if mac not in known_poe_clients and ( - mac in controller.wireless_clients - or client.switch_mac not in devices - or not devices[client.switch_mac].ports[client.switch_port].port_poe - or not devices[client.switch_mac].ports[client.switch_port].poe_enable - or controller.mac == client.mac - ): - continue - - # Multiple POE-devices on same port means non UniFi POE driven switch - multi_clients_on_port = False - for client2 in controller.api.clients.values(): - - if mac in known_poe_clients: - break - - if ( - client2.is_wired - and client.mac != client2.mac - and client.switch_mac == client2.switch_mac - and client.switch_port == client2.switch_port - ): - multi_clients_on_port = True - break - - if multi_clients_on_port: - continue - - switches.append(UniFiPOEClientSwitch(client, controller)) - - async_add_entities(switches) - - -class UniFiPOEClientSwitch(UniFiClient, SwitchEntity, RestoreEntity): - """Representation of a client that uses POE.""" - - DOMAIN = DOMAIN - TYPE = POE_SWITCH - - _attr_entity_category = EntityCategory.CONFIG - - def __init__(self, client, controller): - """Set up POE switch.""" - super().__init__(client, controller) - - self.poe_mode = None - if client.switch_port and self.port.poe_mode != "off": - self.poe_mode = self.port.poe_mode - - async def async_added_to_hass(self) -> None: - """Call when entity about to be added to Home Assistant.""" - await super().async_added_to_hass() - - if self.poe_mode: # POE is enabled and client in a known state - return - - if (state := await self.async_get_last_state()) is None: - return - - self.poe_mode = state.attributes.get("poe_mode") - - if not self.client.switch_mac: - self.client.raw["sw_mac"] = state.attributes.get("switch") - - if not self.client.switch_port: - self.client.raw["sw_port"] = state.attributes.get("port") - - @property - def is_on(self): - """Return true if POE is active.""" - return self.port.poe_mode != "off" - - @property - def available(self) -> bool: - """Return if switch is available. - - Poe_mode None means its POE state is unknown. - Sw_mac unavailable means restored client. - """ - return ( - self.poe_mode is not None - and self.controller.available - and self.client.switch_port - and self.client.switch_mac - and self.client.switch_mac in self.controller.api.devices - ) - - async def async_turn_on(self, **kwargs: Any) -> None: - """Enable POE for client.""" - await self.controller.api.request( - DeviceSetPoePortModeRequest.create( - self.device, self.client.switch_port, self.poe_mode - ) - ) - - async def async_turn_off(self, **kwargs: Any) -> None: - """Disable POE for client.""" - await self.controller.api.request( - DeviceSetPoePortModeRequest.create( - self.device, self.client.switch_port, "off" - ) - ) - - @property - def extra_state_attributes(self): - """Return the device state attributes.""" - attributes = { - "power": self.port.poe_power, - "switch": self.client.switch_mac, - "port": self.client.switch_port, - "poe_mode": self.poe_mode, - } - return attributes - - @property - def device(self): - """Shortcut to the switch that client is connected to.""" - return self.controller.api.devices[self.client.switch_mac] - - @property - def port(self): - """Shortcut to the switch port that client is connected to.""" - return self.device.ports[self.client.switch_port] - - async def options_updated(self) -> None: - """Config entry options are updated, remove entity if option is disabled.""" - if not self.controller.option_poe_clients: - await self.remove_item({self.client.mac}) - - class UnifiSwitchEntity(SwitchEntity): """Base representation of a UniFi switch.""" diff --git a/tests/components/unifi/test_config_flow.py b/tests/components/unifi/test_config_flow.py index a1f6f3d4b02..078c068c8ed 100644 --- a/tests/components/unifi/test_config_flow.py +++ b/tests/components/unifi/test_config_flow.py @@ -16,7 +16,6 @@ from homeassistant.components.unifi.const import ( CONF_DETECTION_TIME, CONF_DPI_RESTRICTIONS, CONF_IGNORE_WIRED_BUG, - CONF_POE_CLIENTS, CONF_SITE_ID, CONF_SSID_FILTER, CONF_TRACK_CLIENTS, @@ -473,7 +472,6 @@ async def test_advanced_option_flow(hass, aioclient_mock): result["flow_id"], user_input={ CONF_BLOCK_CLIENT: [CLIENTS[0]["mac"]], - CONF_POE_CLIENTS: False, CONF_DPI_RESTRICTIONS: False, }, ) @@ -498,7 +496,6 @@ async def test_advanced_option_flow(hass, aioclient_mock): CONF_SSID_FILTER: ["SSID 1", "SSID 2_IOT", "SSID 3"], CONF_DETECTION_TIME: 100, CONF_IGNORE_WIRED_BUG: False, - CONF_POE_CLIENTS: False, CONF_DPI_RESTRICTIONS: False, CONF_BLOCK_CLIENT: [CLIENTS[0]["mac"]], CONF_ALLOW_BANDWIDTH_SENSORS: True, diff --git a/tests/components/unifi/test_switch.py b/tests/components/unifi/test_switch.py index e6357b03172..5178e3dda37 100644 --- a/tests/components/unifi/test_switch.py +++ b/tests/components/unifi/test_switch.py @@ -6,7 +6,7 @@ from datetime import timedelta from aiounifi.models.message import MessageKey from aiounifi.websocket import WebsocketState -from homeassistant import config_entries, core +from homeassistant import config_entries from homeassistant.components.switch import ( DOMAIN as SWITCH_DOMAIN, SERVICE_TURN_OFF, @@ -16,12 +16,10 @@ from homeassistant.components.switch import ( from homeassistant.components.unifi.const import ( CONF_BLOCK_CLIENT, CONF_DPI_RESTRICTIONS, - CONF_POE_CLIENTS, CONF_TRACK_CLIENTS, CONF_TRACK_DEVICES, DOMAIN as UNIFI_DOMAIN, ) -from homeassistant.components.unifi.switch import POE_SWITCH from homeassistant.config_entries import RELOAD_AFTER_UPDATE_DELAY from homeassistant.const import ( ATTR_DEVICE_CLASS, @@ -38,13 +36,12 @@ from homeassistant.util import dt from .test_controller import ( CONTROLLER_HOST, - DEFAULT_CONFIG_ENTRY_ID, DESCRIPTION, ENTRY_CONFIG, setup_unifi_integration, ) -from tests.common import async_fire_time_changed, mock_restore_cache +from tests.common import async_fire_time_changed CLIENT_1 = { "hostname": "client_1", @@ -636,23 +633,14 @@ async def test_switches(hass, aioclient_mock): CONF_TRACK_CLIENTS: False, CONF_TRACK_DEVICES: False, }, - clients_response=[CLIENT_1, CLIENT_4], - devices_response=[DEVICE_1], + clients_response=[CLIENT_4], clients_all_response=[BLOCKED, UNBLOCKED, CLIENT_1], dpigroup_response=DPI_GROUPS, dpiapp_response=DPI_APPS, ) controller = hass.data[UNIFI_DOMAIN][config_entry.entry_id] - assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 4 - - switch_1 = hass.states.get("switch.poe_client_1") - assert switch_1 is not None - assert switch_1.state == "on" - assert switch_1.attributes["power"] == "2.56" - assert switch_1.attributes[SWITCH_DOMAIN] == "10:00:00:00:01:01" - assert switch_1.attributes["port"] == 1 - assert switch_1.attributes["poe_mode"] == "auto" + assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 3 switch_4 = hass.states.get("switch.poe_client_4") assert switch_4 is None @@ -671,11 +659,7 @@ async def test_switches(hass, aioclient_mock): assert dpi_switch.attributes["icon"] == "mdi:network" ent_reg = er.async_get(hass) - for entry_id in ( - "switch.poe_client_1", - "switch.block_client_1", - "switch.block_media_streaming", - ): + for entry_id in ("switch.block_client_1", "switch.block_media_streaming"): assert ent_reg.async_get(entry_id).entity_category is EntityCategory.CONFIG # Block and unblock client @@ -729,7 +713,7 @@ async def test_switches(hass, aioclient_mock): # Make sure no duplicates arise on generic signal update async_dispatcher_send(hass, controller.signal_update) await hass.async_block_till_done() - assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 4 + assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 3 async def test_remove_switches(hass, aioclient_mock, mock_unifi_websocket): @@ -738,24 +722,21 @@ async def test_remove_switches(hass, aioclient_mock, mock_unifi_websocket): hass, aioclient_mock, options={CONF_BLOCK_CLIENT: [UNBLOCKED["mac"]]}, - clients_response=[CLIENT_1, UNBLOCKED], - devices_response=[DEVICE_1], + clients_response=[UNBLOCKED], dpigroup_response=DPI_GROUPS, dpiapp_response=DPI_APPS, ) - assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 3 + assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 2 - assert hass.states.get("switch.poe_client_1") is not None assert hass.states.get("switch.block_client_2") is not None assert hass.states.get("switch.block_media_streaming") is not None - mock_unifi_websocket(message=MessageKey.CLIENT_REMOVED, data=[CLIENT_1, UNBLOCKED]) + mock_unifi_websocket(message=MessageKey.CLIENT_REMOVED, data=[UNBLOCKED]) await hass.async_block_till_done() assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 1 - assert hass.states.get("switch.poe_client_1") is None assert hass.states.get("switch.block_client_2") is None assert hass.states.get("switch.block_media_streaming") is not None @@ -1089,273 +1070,20 @@ async def test_option_remove_switches(hass, aioclient_mock): CONF_TRACK_DEVICES: False, }, clients_response=[CLIENT_1], - devices_response=[DEVICE_1], dpigroup_response=DPI_GROUPS, dpiapp_response=DPI_APPS, ) - assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 2 + assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 1 # Disable DPI Switches hass.config_entries.async_update_entry( config_entry, - options={CONF_DPI_RESTRICTIONS: False, CONF_POE_CLIENTS: False}, + options={CONF_DPI_RESTRICTIONS: False}, ) await hass.async_block_till_done() assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 0 -async def test_new_client_discovered_on_poe_control( - hass, aioclient_mock, mock_unifi_websocket -): - """Test if 2nd update has a new client.""" - config_entry = await setup_unifi_integration( - hass, - aioclient_mock, - options={CONF_TRACK_CLIENTS: False, CONF_TRACK_DEVICES: False}, - clients_response=[CLIENT_1], - devices_response=[DEVICE_1], - ) - controller = hass.data[UNIFI_DOMAIN][config_entry.entry_id] - - assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 1 - - mock_unifi_websocket(message=MessageKey.CLIENT, data=CLIENT_2) - await hass.async_block_till_done() - - assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 1 - - mock_unifi_websocket(message=MessageKey.EVENT, data=EVENT_CLIENT_2_CONNECTED) - await hass.async_block_till_done() - - assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 2 - switch_2 = hass.states.get("switch.poe_client_2") - assert switch_2 is not None - - aioclient_mock.put( - f"https://{controller.host}:1234/api/s/{controller.site}/rest/device/mock-id", - ) - - await hass.services.async_call( - SWITCH_DOMAIN, "turn_off", {"entity_id": "switch.poe_client_1"}, blocking=True - ) - assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 2 - assert aioclient_mock.call_count == 11 - assert aioclient_mock.mock_calls[10][2] == { - "port_overrides": [{"port_idx": 1, "portconf_id": "1a1", "poe_mode": "off"}] - } - - await hass.services.async_call( - SWITCH_DOMAIN, "turn_on", {"entity_id": "switch.poe_client_1"}, blocking=True - ) - assert aioclient_mock.call_count == 12 - assert aioclient_mock.mock_calls[11][2] == { - "port_overrides": [{"port_idx": 1, "portconf_id": "1a1", "poe_mode": "auto"}] - } - - -async def test_ignore_multiple_poe_clients_on_same_port(hass, aioclient_mock): - """Ignore when there are multiple POE driven clients on same port. - - If there is a non-UniFi switch powered by POE, - clients will be transparently marked as having POE as well. - """ - await setup_unifi_integration( - hass, - aioclient_mock, - clients_response=POE_SWITCH_CLIENTS, - devices_response=[DEVICE_1], - ) - - switch_1 = hass.states.get("switch.poe_client_1") - switch_2 = hass.states.get("switch.poe_client_2") - assert switch_1 is None - assert switch_2 is None - - -async def test_restore_client_succeed(hass, aioclient_mock): - """Test that RestoreEntity works as expected.""" - POE_DEVICE = { - "device_id": "12345", - "ip": "1.0.1.1", - "mac": "00:00:00:00:01:01", - "last_seen": 1562600145, - "model": "US16P150", - "name": "POE Switch", - "port_overrides": [ - { - "poe_mode": "off", - "port_idx": 1, - "portconf_id": "5f3edd2aba4cc806a19f2db2", - } - ], - "port_table": [ - { - "media": "GE", - "name": "Port 1", - "op_mode": "switch", - "poe_caps": 7, - "poe_class": "Unknown", - "poe_current": "0.00", - "poe_enable": False, - "poe_good": False, - "poe_mode": "off", - "poe_power": "0.00", - "poe_voltage": "0.00", - "port_idx": 1, - "port_poe": True, - "portconf_id": "5f3edd2aba4cc806a19f2db2", - "up": False, - }, - ], - "state": 1, - "type": "usw", - "version": "4.0.42.10433", - } - POE_CLIENT = { - "hostname": "poe_client", - "ip": "1.0.0.1", - "is_wired": True, - "last_seen": 1562600145, - "mac": "00:00:00:00:00:01", - "name": "POE Client", - "oui": "Producer", - } - - fake_state = core.State( - "switch.poe_client", - "off", - { - "power": "0.00", - "switch": POE_DEVICE["mac"], - "port": 1, - "poe_mode": "auto", - }, - ) - mock_restore_cache(hass, (fake_state,)) - - config_entry = config_entries.ConfigEntry( - version=1, - domain=UNIFI_DOMAIN, - title="Mock Title", - data=ENTRY_CONFIG, - source="test", - options={}, - entry_id=DEFAULT_CONFIG_ENTRY_ID, - ) - - registry = er.async_get(hass) - registry.async_get_or_create( - SWITCH_DOMAIN, - UNIFI_DOMAIN, - f'{POE_SWITCH}-{POE_CLIENT["mac"]}', - suggested_object_id=POE_CLIENT["hostname"], - config_entry=config_entry, - ) - - await setup_unifi_integration( - hass, - aioclient_mock, - options={ - CONF_TRACK_CLIENTS: False, - CONF_TRACK_DEVICES: False, - }, - clients_response=[], - devices_response=[POE_DEVICE], - clients_all_response=[POE_CLIENT], - ) - - assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 1 - - poe_client = hass.states.get("switch.poe_client") - assert poe_client.state == "off" - - -async def test_restore_client_no_old_state(hass, aioclient_mock): - """Test that RestoreEntity without old state makes entity unavailable.""" - POE_DEVICE = { - "device_id": "12345", - "ip": "1.0.1.1", - "mac": "00:00:00:00:01:01", - "last_seen": 1562600145, - "model": "US16P150", - "name": "POE Switch", - "port_overrides": [ - { - "poe_mode": "off", - "port_idx": 1, - "portconf_id": "5f3edd2aba4cc806a19f2db2", - } - ], - "port_table": [ - { - "media": "GE", - "name": "Port 1", - "op_mode": "switch", - "poe_caps": 7, - "poe_class": "Unknown", - "poe_current": "0.00", - "poe_enable": False, - "poe_good": False, - "poe_mode": "off", - "poe_power": "0.00", - "poe_voltage": "0.00", - "port_idx": 1, - "port_poe": True, - "portconf_id": "5f3edd2aba4cc806a19f2db2", - "up": False, - }, - ], - "state": 1, - "type": "usw", - "version": "4.0.42.10433", - } - POE_CLIENT = { - "hostname": "poe_client", - "ip": "1.0.0.1", - "is_wired": True, - "last_seen": 1562600145, - "mac": "00:00:00:00:00:01", - "name": "POE Client", - "oui": "Producer", - } - - config_entry = config_entries.ConfigEntry( - version=1, - domain=UNIFI_DOMAIN, - title="Mock Title", - data=ENTRY_CONFIG, - source="test", - options={}, - entry_id=DEFAULT_CONFIG_ENTRY_ID, - ) - - registry = er.async_get(hass) - registry.async_get_or_create( - SWITCH_DOMAIN, - UNIFI_DOMAIN, - f'{POE_SWITCH}-{POE_CLIENT["mac"]}', - suggested_object_id=POE_CLIENT["hostname"], - config_entry=config_entry, - ) - - await setup_unifi_integration( - hass, - aioclient_mock, - options={ - CONF_TRACK_CLIENTS: False, - CONF_TRACK_DEVICES: False, - }, - clients_response=[], - devices_response=[POE_DEVICE], - clients_all_response=[POE_CLIENT], - ) - - assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 1 - - poe_client = hass.states.get("switch.poe_client") - assert poe_client.state == "unavailable" # self.poe_mode is None - - async def test_poe_port_switches(hass, aioclient_mock, mock_unifi_websocket): """Test the update_items function with some clients.""" config_entry = await setup_unifi_integration( @@ -1447,3 +1175,33 @@ async def test_poe_port_switches(hass, aioclient_mock, mock_unifi_websocket): mock_unifi_websocket(message=MessageKey.DEVICE, data=device_1) await hass.async_block_till_done() assert hass.states.get("switch.mock_name_port_1_poe").state == STATE_OFF + + +async def test_remove_poe_client_switches(hass, aioclient_mock): + """Test old PoE client switches are removed.""" + + config_entry = config_entries.ConfigEntry( + version=1, + domain=UNIFI_DOMAIN, + title="Mock Title", + data=ENTRY_CONFIG, + source="test", + options={}, + entry_id="1", + ) + + ent_reg = er.async_get(hass) + ent_reg.async_get_or_create( + SWITCH_DOMAIN, + UNIFI_DOMAIN, + "poe-123", + config_entry=config_entry, + ) + + await setup_unifi_integration(hass, aioclient_mock) + + assert not [ + entry + for entry in ent_reg.entities.values() + if entry.config_entry_id == config_entry.entry_id + ]