diff --git a/tests/components/unifi/test_device_tracker.py b/tests/components/unifi/test_device_tracker.py index 51dbd735e10..33dda33be24 100644 --- a/tests/components/unifi/test_device_tracker.py +++ b/tests/components/unifi/test_device_tracker.py @@ -1,5 +1,5 @@ """The tests for the UniFi device tracker platform.""" -from copy import copy + from datetime import timedelta from unittest.mock import patch @@ -22,7 +22,7 @@ from homeassistant.components.unifi.const import ( CONF_TRACK_WIRED_CLIENTS, DOMAIN as UNIFI_DOMAIN, ) -from homeassistant.const import STATE_UNAVAILABLE +from homeassistant.const import STATE_HOME, STATE_NOT_HOME, STATE_UNAVAILABLE from homeassistant.helpers import entity_registry import homeassistant.util.dt as dt_util @@ -30,126 +30,8 @@ from .test_controller import ENTRY_CONFIG, setup_unifi_integration from tests.common import async_fire_time_changed -CLIENT_1 = { - "ap_mac": "00:00:00:00:02:01", - "essid": "ssid", - "hostname": "client_1", - "ip": "10.0.0.1", - "is_wired": False, - "last_seen": 1562600145, - "mac": "00:00:00:00:00:01", -} -CLIENT_2 = { - "hostname": "client_2", - "ip": "10.0.0.2", - "is_wired": True, - "last_seen": 1562600145, - "mac": "00:00:00:00:00:02", - "name": "Wired Client", -} -CLIENT_3 = { - "essid": "ssid2", - "hostname": "client_3", - "ip": "10.0.0.3", - "is_wired": False, - "last_seen": 1562600145, - "mac": "00:00:00:00:00:03", -} -CLIENT_4 = { - "essid": "ssid", - "hostname": "client_4", - "ip": "10.0.0.4", - "is_wired": True, - "last_seen": 1562600145, - "mac": "00:00:00:00:00:04", -} -CLIENT_5 = { - "essid": "ssid", - "hostname": "client_5", - "ip": "10.0.0.5", - "is_wired": True, - "last_seen": None, - "mac": "00:00:00:00:00:05", -} -DEVICE_1 = { - "board_rev": 3, - "device_id": "mock-id", - "has_fan": True, - "fan_level": 0, - "ip": "10.0.1.1", - "last_seen": 1562600145, - "mac": "00:00:00:00:01:01", - "model": "US16P150", - "name": "device_1", - "next_interval": 20, - "overheating": True, - "state": 1, - "type": "usw", - "upgradable": True, - "version": "4.0.42.10433", -} -DEVICE_2 = { - "board_rev": 3, - "device_id": "mock-id", - "has_fan": True, - "ip": "10.0.1.2", - "mac": "00:00:00:00:01:02", - "model": "US16P150", - "name": "device_2", - "next_interval": 20, - "state": 0, - "type": "usw", - "version": "4.0.42.10433", -} - -EVENT_CLIENT_1_WIRELESS_CONNECTED = { - "user": CLIENT_1["mac"], - "ssid": CLIENT_1["essid"], - "ap": CLIENT_1["ap_mac"], - "radio": "na", - "channel": "44", - "hostname": CLIENT_1["hostname"], - "key": "EVT_WU_Connected", - "subsystem": "wlan", - "site_id": "name", - "time": 1587753456179, - "datetime": "2020-04-24T18:37:36Z", - "msg": f'User{[CLIENT_1["mac"]]} has connected to AP[{CLIENT_1["ap_mac"]}] with SSID "{CLIENT_1["essid"]}" on "channel 44(na)"', - "_id": "5ea331fa30c49e00f90ddc1a", -} - -EVENT_CLIENT_1_WIRELESS_DISCONNECTED = { - "user": CLIENT_1["mac"], - "ssid": CLIENT_1["essid"], - "hostname": CLIENT_1["hostname"], - "ap": CLIENT_1["ap_mac"], - "duration": 467, - "bytes": 459039, - "key": "EVT_WU_Disconnected", - "subsystem": "wlan", - "site_id": "name", - "time": 1587752927000, - "datetime": "2020-04-24T18:28:47Z", - "msg": f'User{[CLIENT_1["mac"]]} disconnected from "{CLIENT_1["essid"]}" (7m 47s connected, 448.28K bytes, last AP[{CLIENT_1["ap_mac"]}])', - "_id": "5ea32ff730c49e00f90dca1a", -} - -EVENT_DEVICE_2_UPGRADED = { - "_id": "5eae7fe02ab79c00f9d38960", - "datetime": "2020-05-09T20:06:37Z", - "key": "EVT_SW_Upgraded", - "msg": f'Switch[{DEVICE_2["mac"]}] was upgraded from "{DEVICE_2["version"]}" to "4.3.13.11253"', - "subsystem": "lan", - "sw": DEVICE_2["mac"], - "sw_name": DEVICE_2["name"], - "time": 1589054797635, - "version_from": {DEVICE_2["version"]}, - "version_to": "4.3.13.11253", -} - - -async def test_no_clients(hass, aioclient_mock): +async def test_no_entities(hass, aioclient_mock): """Test the update_clients function when no clients are found.""" await setup_unifi_integration(hass, aioclient_mock) @@ -157,196 +39,297 @@ async def test_no_clients(hass, aioclient_mock): async def test_tracked_wireless_clients(hass, aioclient_mock, mock_unifi_websocket): - """Test the update_items function with some clients.""" + """Verify tracking of wireless clients.""" + client = { + "ap_mac": "00:00:00:00:02:01", + "essid": "ssid", + "hostname": "client", + "ip": "10.0.0.1", + "is_wired": False, + "last_seen": 1562600145, + "mac": "00:00:00:00:00:01", + } config_entry = await setup_unifi_integration( - hass, aioclient_mock, clients_response=[CLIENT_1] + hass, aioclient_mock, clients_response=[client] ) controller = hass.data[UNIFI_DOMAIN][config_entry.entry_id] - assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 1 - client_1 = hass.states.get("device_tracker.client_1") - assert client_1 is not None - assert client_1.state == "not_home" + assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 1 + assert hass.states.get("device_tracker.client").state == STATE_NOT_HOME # State change signalling works without events - client_1_copy = copy(CLIENT_1) + mock_unifi_websocket( data={ "meta": {"message": MESSAGE_CLIENT}, - "data": [client_1_copy], + "data": [client], } ) await hass.async_block_till_done() - client_1 = hass.states.get("device_tracker.client_1") - assert client_1.state == "home" - assert client_1.attributes["ip"] == "10.0.0.1" - assert client_1.attributes["mac"] == "00:00:00:00:00:01" - assert client_1.attributes["hostname"] == "client_1" - assert client_1.attributes["host_name"] == "client_1" + client_state = hass.states.get("device_tracker.client") + assert client_state.state == "home" + assert client_state.attributes["ip"] == "10.0.0.1" + assert client_state.attributes["mac"] == "00:00:00:00:00:01" + assert client_state.attributes["hostname"] == "client" + assert client_state.attributes["host_name"] == "client" # State change signalling works with events + # Disconnected event + + event = { + "user": client["mac"], + "ssid": client["essid"], + "hostname": client["hostname"], + "ap": client["ap_mac"], + "duration": 467, + "bytes": 459039, + "key": "EVT_WU_Disconnected", + "subsystem": "wlan", + "site_id": "name", + "time": 1587752927000, + "datetime": "2020-04-24T18:28:47Z", + "msg": f'User{[client["mac"]]} disconnected from "{client["essid"]}" (7m 47s connected, 448.28K bytes, last AP[{client["ap_mac"]}])', + "_id": "5ea32ff730c49e00f90dca1a", + } mock_unifi_websocket( data={ "meta": {"message": MESSAGE_EVENT}, - "data": [EVENT_CLIENT_1_WIRELESS_DISCONNECTED], + "data": [event], } ) await hass.async_block_till_done() - client_1 = hass.states.get("device_tracker.client_1") - assert client_1.state == "home" + assert hass.states.get("device_tracker.client").state == STATE_HOME + + # Change time to mark client as away new_time = dt_util.utcnow() + controller.option_detection_time with patch("homeassistant.util.dt.utcnow", return_value=new_time): async_fire_time_changed(hass, new_time) await hass.async_block_till_done() - client_1 = hass.states.get("device_tracker.client_1") - assert client_1.state == "not_home" + assert hass.states.get("device_tracker.client").state == STATE_NOT_HOME + # Connected event + + event = { + "user": client["mac"], + "ssid": client["essid"], + "ap": client["ap_mac"], + "radio": "na", + "channel": "44", + "hostname": client["hostname"], + "key": "EVT_WU_Connected", + "subsystem": "wlan", + "site_id": "name", + "time": 1587753456179, + "datetime": "2020-04-24T18:37:36Z", + "msg": f'User{[client["mac"]]} has connected to AP[{client["ap_mac"]}] with SSID "{client["essid"]}" on "channel 44(na)"', + "_id": "5ea331fa30c49e00f90ddc1a", + } mock_unifi_websocket( data={ "meta": {"message": MESSAGE_EVENT}, - "data": [EVENT_CLIENT_1_WIRELESS_CONNECTED], + "data": [event], } ) await hass.async_block_till_done() - client_1 = hass.states.get("device_tracker.client_1") - assert client_1.state == "home" + assert hass.states.get("device_tracker.client").state == STATE_HOME async def test_tracked_clients(hass, aioclient_mock, mock_unifi_websocket): """Test the update_items function with some clients.""" - client_4_copy = copy(CLIENT_4) - client_4_copy["last_seen"] = dt_util.as_timestamp(dt_util.utcnow()) + client_1 = { + "ap_mac": "00:00:00:00:02:01", + "essid": "ssid", + "hostname": "client_1", + "ip": "10.0.0.1", + "is_wired": False, + "last_seen": 1562600145, + "mac": "00:00:00:00:00:01", + } + client_2 = { + "ip": "10.0.0.2", + "is_wired": True, + "last_seen": 1562600145, + "mac": "00:00:00:00:00:02", + "name": "Client 2", + } + client_3 = { + "essid": "ssid2", + "hostname": "client_3", + "ip": "10.0.0.3", + "is_wired": False, + "last_seen": 1562600145, + "mac": "00:00:00:00:00:03", + } + client_4 = { + "essid": "ssid", + "hostname": "client_4", + "ip": "10.0.0.4", + "is_wired": True, + "last_seen": dt_util.as_timestamp(dt_util.utcnow()), + "mac": "00:00:00:00:00:04", + } + client_5 = { + "essid": "ssid", + "hostname": "client_5", + "ip": "10.0.0.5", + "is_wired": True, + "last_seen": None, + "mac": "00:00:00:00:00:05", + } await setup_unifi_integration( hass, aioclient_mock, options={CONF_SSID_FILTER: ["ssid"]}, - clients_response=[CLIENT_1, CLIENT_2, CLIENT_3, CLIENT_5, client_4_copy], - known_wireless_clients=(CLIENT_4["mac"],), + clients_response=[client_1, client_2, client_3, client_4, client_5], + known_wireless_clients=(client_4["mac"],), ) + assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 4 - - client_1 = hass.states.get("device_tracker.client_1") - assert client_1 is not None - assert client_1.state == "not_home" - - client_2 = hass.states.get("device_tracker.wired_client") - assert client_2 is not None - assert client_2.state == "not_home" + assert hass.states.get("device_tracker.client_1").state == STATE_NOT_HOME + assert hass.states.get("device_tracker.client_2").state == STATE_NOT_HOME # Client on SSID not in SSID filter - client_3 = hass.states.get("device_tracker.client_3") - assert not client_3 + assert not hass.states.get("device_tracker.client_3") # Wireless client with wired bug, if bug active on restart mark device away - client_4 = hass.states.get("device_tracker.client_4") - assert client_4 is not None - assert client_4.state == "not_home" + assert hass.states.get("device_tracker.client_4").state == STATE_NOT_HOME # A client that has never been seen should be marked away. - client_5 = hass.states.get("device_tracker.client_5") - assert client_5 is not None - assert client_5.state == "not_home" + assert hass.states.get("device_tracker.client_5").state == STATE_NOT_HOME # State change signalling works - client_1_copy = copy(CLIENT_1) + mock_unifi_websocket( data={ "meta": {"message": MESSAGE_CLIENT}, - "data": [client_1_copy], + "data": [client_1], } ) await hass.async_block_till_done() - client_1 = hass.states.get("device_tracker.client_1") - assert client_1.state == "home" + assert hass.states.get("device_tracker.client_1").state == STATE_HOME async def test_tracked_devices(hass, aioclient_mock, mock_unifi_websocket): """Test the update_items function with some devices.""" + device_1 = { + "board_rev": 3, + "device_id": "mock-id", + "has_fan": True, + "fan_level": 0, + "ip": "10.0.1.1", + "last_seen": 1562600145, + "mac": "00:00:00:00:01:01", + "model": "US16P150", + "name": "Device 1", + "next_interval": 20, + "overheating": True, + "state": 1, + "type": "usw", + "upgradable": True, + "version": "4.0.42.10433", + } + device_2 = { + "board_rev": 3, + "device_id": "mock-id", + "has_fan": True, + "ip": "10.0.1.2", + "mac": "00:00:00:00:01:02", + "model": "US16P150", + "name": "Device 2", + "next_interval": 20, + "state": 0, + "type": "usw", + "version": "4.0.42.10433", + } await setup_unifi_integration( hass, aioclient_mock, - devices_response=[DEVICE_1, DEVICE_2], + devices_response=[device_1, device_2], ) assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 2 - - device_1 = hass.states.get("device_tracker.device_1") - assert device_1 - assert device_1.state == "home" - - device_2 = hass.states.get("device_tracker.device_2") - assert device_2 - assert device_2.state == "not_home" + assert hass.states.get("device_tracker.device_1").state == STATE_HOME + assert hass.states.get("device_tracker.device_2").state == STATE_NOT_HOME # State change signalling work - device_1_copy = copy(DEVICE_1) - device_1_copy["next_interval"] = 20 + + device_1["next_interval"] = 20 mock_unifi_websocket( data={ "meta": {"message": MESSAGE_DEVICE}, - "data": [device_1_copy], + "data": [device_1], } ) - device_2_copy = copy(DEVICE_2) - device_2_copy["next_interval"] = 50 + device_2["next_interval"] = 50 mock_unifi_websocket( data={ "meta": {"message": MESSAGE_DEVICE}, - "data": [device_2_copy], + "data": [device_2], } ) await hass.async_block_till_done() - device_1 = hass.states.get("device_tracker.device_1") - assert device_1.state == "home" - device_2 = hass.states.get("device_tracker.device_2") - assert device_2.state == "home" + assert hass.states.get("device_tracker.device_1").state == STATE_HOME + assert hass.states.get("device_tracker.device_2").state == STATE_HOME + + # Change of time can mark device not_home outside of expected reporting interval new_time = dt_util.utcnow() + timedelta(seconds=90) with patch("homeassistant.util.dt.utcnow", return_value=new_time): async_fire_time_changed(hass, new_time) await hass.async_block_till_done() - device_1 = hass.states.get("device_tracker.device_1") - assert device_1.state == "not_home" - device_2 = hass.states.get("device_tracker.device_2") - assert device_2.state == "home" + assert hass.states.get("device_tracker.device_1").state == STATE_NOT_HOME + assert hass.states.get("device_tracker.device_2").state == STATE_HOME # Disabled device is unavailable - device_1_copy = copy(DEVICE_1) - device_1_copy["disabled"] = True + + device_1["disabled"] = True mock_unifi_websocket( data={ "meta": {"message": MESSAGE_DEVICE}, - "data": [device_1_copy], + "data": [device_1], } ) await hass.async_block_till_done() - device_1 = hass.states.get("device_tracker.device_1") - assert device_1.state == STATE_UNAVAILABLE + assert hass.states.get("device_tracker.device_1").state == STATE_UNAVAILABLE + assert hass.states.get("device_tracker.device_2").state == STATE_HOME # Update device registry when device is upgraded - device_2_copy = copy(DEVICE_2) - device_2_copy["version"] = EVENT_DEVICE_2_UPGRADED["version_to"] + + event = { + "_id": "5eae7fe02ab79c00f9d38960", + "datetime": "2020-05-09T20:06:37Z", + "key": "EVT_SW_Upgraded", + "msg": f'Switch[{device_2["mac"]}] was upgraded from "{device_2["version"]}" to "4.3.13.11253"', + "subsystem": "lan", + "sw": device_2["mac"], + "sw_name": device_2["name"], + "time": 1589054797635, + "version_from": {device_2["version"]}, + "version_to": "4.3.13.11253", + } + + device_2["version"] = event["version_to"] mock_unifi_websocket( data={ "meta": {"message": MESSAGE_DEVICE}, - "data": [device_2_copy], + "data": [device_2], } ) mock_unifi_websocket( data={ "meta": {"message": MESSAGE_EVENT}, - "data": [EVENT_DEVICE_2_UPGRADED], + "data": [event], } ) await hass.async_block_till_done() @@ -356,97 +339,145 @@ async def test_tracked_devices(hass, aioclient_mock, mock_unifi_websocket): entry = entity_registry.async_get("device_tracker.device_2") device_registry = await hass.helpers.device_registry.async_get_registry() device = device_registry.async_get(entry.device_id) - assert device.sw_version == EVENT_DEVICE_2_UPGRADED["version_to"] + assert device.sw_version == event["version_to"] async def test_remove_clients(hass, aioclient_mock, mock_unifi_websocket): """Test the remove_items function with some clients.""" + client_1 = { + "essid": "ssid", + "hostname": "client_1", + "is_wired": False, + "last_seen": 1562600145, + "mac": "00:00:00:00:00:01", + } + client_2 = { + "hostname": "client_2", + "is_wired": True, + "last_seen": 1562600145, + "mac": "00:00:00:00:00:02", + } await setup_unifi_integration( - hass, aioclient_mock, clients_response=[CLIENT_1, CLIENT_2] + hass, aioclient_mock, clients_response=[client_1, client_2] ) assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 2 + assert hass.states.get("device_tracker.client_1") + assert hass.states.get("device_tracker.client_2") - client_1 = hass.states.get("device_tracker.client_1") - assert client_1 is not None - - wired_client = hass.states.get("device_tracker.wired_client") - assert wired_client is not None + # Remove client mock_unifi_websocket( data={ "meta": {"message": MESSAGE_CLIENT_REMOVED}, - "data": [CLIENT_1], + "data": [client_1], } ) await hass.async_block_till_done() await hass.async_block_till_done() assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 1 - - client_1 = hass.states.get("device_tracker.client_1") - assert client_1 is None - - wired_client = hass.states.get("device_tracker.wired_client") - assert wired_client is not None + assert not hass.states.get("device_tracker.client_1") + assert hass.states.get("device_tracker.client_2") async def test_controller_state_change(hass, aioclient_mock, mock_unifi_websocket): """Verify entities state reflect on controller becoming unavailable.""" + client = { + "essid": "ssid", + "hostname": "client", + "is_wired": False, + "last_seen": 1562600145, + "mac": "00:00:00:00:00:01", + } + device = { + "board_rev": 3, + "device_id": "mock-id", + "has_fan": True, + "fan_level": 0, + "ip": "10.0.1.1", + "last_seen": 1562600145, + "mac": "00:00:00:00:01:01", + "model": "US16P150", + "name": "Device", + "next_interval": 20, + "overheating": True, + "state": 1, + "type": "usw", + "upgradable": True, + "version": "4.0.42.10433", + } + await setup_unifi_integration( hass, aioclient_mock, - clients_response=[CLIENT_1], - devices_response=[DEVICE_1], + clients_response=[client], + devices_response=[device], ) assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 2 - - client_1 = hass.states.get("device_tracker.client_1") - assert client_1.state == "not_home" - - device_1 = hass.states.get("device_tracker.device_1") - assert device_1.state == "home" + assert hass.states.get("device_tracker.client").state == STATE_NOT_HOME + assert hass.states.get("device_tracker.device").state == STATE_HOME # Controller unavailable mock_unifi_websocket(state=STATE_DISCONNECTED) await hass.async_block_till_done() - client_1 = hass.states.get("device_tracker.client_1") - assert client_1.state == STATE_UNAVAILABLE - - device_1 = hass.states.get("device_tracker.device_1") - assert device_1.state == STATE_UNAVAILABLE + assert hass.states.get("device_tracker.client").state == STATE_UNAVAILABLE + assert hass.states.get("device_tracker.device").state == STATE_UNAVAILABLE # Controller available mock_unifi_websocket(state=STATE_RUNNING) await hass.async_block_till_done() - client_1 = hass.states.get("device_tracker.client_1") - assert client_1.state == "not_home" - - device_1 = hass.states.get("device_tracker.device_1") - assert device_1.state == "home" + assert hass.states.get("device_tracker.client").state == STATE_NOT_HOME + assert hass.states.get("device_tracker.device").state == STATE_HOME async def test_option_track_clients(hass, aioclient_mock): """Test the tracking of clients can be turned off.""" + wireless_client = { + "essid": "ssid", + "hostname": "wireless_client", + "is_wired": False, + "last_seen": 1562600145, + "mac": "00:00:00:00:00:01", + } + wired_client = { + "is_wired": True, + "last_seen": 1562600145, + "mac": "00:00:00:00:00:02", + "name": "Wired Client", + } + device = { + "board_rev": 3, + "device_id": "mock-id", + "has_fan": True, + "fan_level": 0, + "ip": "10.0.1.1", + "last_seen": 1562600145, + "mac": "00:00:00:00:01:01", + "model": "US16P150", + "name": "Device", + "next_interval": 20, + "overheating": True, + "state": 1, + "type": "usw", + "upgradable": True, + "version": "4.0.42.10433", + } + config_entry = await setup_unifi_integration( hass, aioclient_mock, - clients_response=[CLIENT_1, CLIENT_2], - devices_response=[DEVICE_1], + clients_response=[wireless_client, wired_client], + devices_response=[device], ) + assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 3 - - client_1 = hass.states.get("device_tracker.client_1") - assert client_1 is not None - - client_2 = hass.states.get("device_tracker.wired_client") - assert client_2 is not None - - device_1 = hass.states.get("device_tracker.device_1") - assert device_1 is not None + assert hass.states.get("device_tracker.wireless_client") + assert hass.states.get("device_tracker.wired_client") + assert hass.states.get("device_tracker.device") hass.config_entries.async_update_entry( config_entry, @@ -454,14 +485,9 @@ async def test_option_track_clients(hass, aioclient_mock): ) await hass.async_block_till_done() - client_1 = hass.states.get("device_tracker.client_1") - assert client_1 is None - - client_2 = hass.states.get("device_tracker.wired_client") - assert client_2 is None - - device_1 = hass.states.get("device_tracker.device_1") - assert device_1 is not None + assert not hass.states.get("device_tracker.wireless_client") + assert not hass.states.get("device_tracker.wired_client") + assert hass.states.get("device_tracker.device") hass.config_entries.async_update_entry( config_entry, @@ -469,34 +495,55 @@ async def test_option_track_clients(hass, aioclient_mock): ) await hass.async_block_till_done() - client_1 = hass.states.get("device_tracker.client_1") - assert client_1 is not None - - client_2 = hass.states.get("device_tracker.wired_client") - assert client_2 is not None - - device_1 = hass.states.get("device_tracker.device_1") - assert device_1 is not None + assert hass.states.get("device_tracker.wireless_client") + assert hass.states.get("device_tracker.wired_client") + assert hass.states.get("device_tracker.device") async def test_option_track_wired_clients(hass, aioclient_mock): """Test the tracking of wired clients can be turned off.""" + wireless_client = { + "essid": "ssid", + "hostname": "wireless_client", + "is_wired": False, + "last_seen": 1562600145, + "mac": "00:00:00:00:00:01", + } + wired_client = { + "is_wired": True, + "last_seen": 1562600145, + "mac": "00:00:00:00:00:02", + "name": "Wired Client", + } + device = { + "board_rev": 3, + "device_id": "mock-id", + "has_fan": True, + "fan_level": 0, + "ip": "10.0.1.1", + "last_seen": 1562600145, + "mac": "00:00:00:00:01:01", + "model": "US16P150", + "name": "Device", + "next_interval": 20, + "overheating": True, + "state": 1, + "type": "usw", + "upgradable": True, + "version": "4.0.42.10433", + } + config_entry = await setup_unifi_integration( hass, aioclient_mock, - clients_response=[CLIENT_1, CLIENT_2], - devices_response=[DEVICE_1], + clients_response=[wireless_client, wired_client], + devices_response=[device], ) + assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 3 - - client_1 = hass.states.get("device_tracker.client_1") - assert client_1 is not None - - client_2 = hass.states.get("device_tracker.wired_client") - assert client_2 is not None - - device_1 = hass.states.get("device_tracker.device_1") - assert device_1 is not None + assert hass.states.get("device_tracker.wireless_client") + assert hass.states.get("device_tracker.wired_client") + assert hass.states.get("device_tracker.device") hass.config_entries.async_update_entry( config_entry, @@ -504,14 +551,9 @@ async def test_option_track_wired_clients(hass, aioclient_mock): ) await hass.async_block_till_done() - client_1 = hass.states.get("device_tracker.client_1") - assert client_1 is not None - - client_2 = hass.states.get("device_tracker.wired_client") - assert client_2 is None - - device_1 = hass.states.get("device_tracker.device_1") - assert device_1 is not None + assert hass.states.get("device_tracker.wireless_client") + assert not hass.states.get("device_tracker.wired_client") + assert hass.states.get("device_tracker.device") hass.config_entries.async_update_entry( config_entry, @@ -519,34 +561,44 @@ async def test_option_track_wired_clients(hass, aioclient_mock): ) await hass.async_block_till_done() - client_1 = hass.states.get("device_tracker.client_1") - assert client_1 is not None - - client_2 = hass.states.get("device_tracker.wired_client") - assert client_2 is not None - - device_1 = hass.states.get("device_tracker.device_1") - assert device_1 is not None + assert hass.states.get("device_tracker.wireless_client") + assert hass.states.get("device_tracker.wired_client") + assert hass.states.get("device_tracker.device") async def test_option_track_devices(hass, aioclient_mock): """Test the tracking of devices can be turned off.""" + client = { + "hostname": "client", + "is_wired": True, + "last_seen": 1562600145, + "mac": "00:00:00:00:00:01", + } + device = { + "board_rev": 3, + "device_id": "mock-id", + "last_seen": 1562600145, + "mac": "00:00:00:00:01:01", + "model": "US16P150", + "name": "Device", + "next_interval": 20, + "overheating": True, + "state": 1, + "type": "usw", + "upgradable": True, + "version": "4.0.42.10433", + } + config_entry = await setup_unifi_integration( hass, aioclient_mock, - clients_response=[CLIENT_1, CLIENT_2], - devices_response=[DEVICE_1], + clients_response=[client], + devices_response=[device], ) - assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 3 - client_1 = hass.states.get("device_tracker.client_1") - assert client_1 is not None - - client_2 = hass.states.get("device_tracker.wired_client") - assert client_2 is not None - - device_1 = hass.states.get("device_tracker.device_1") - assert device_1 is not None + assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 2 + assert hass.states.get("device_tracker.client") + assert hass.states.get("device_tracker.device") hass.config_entries.async_update_entry( config_entry, @@ -554,14 +606,8 @@ async def test_option_track_devices(hass, aioclient_mock): ) await hass.async_block_till_done() - client_1 = hass.states.get("device_tracker.client_1") - assert client_1 is not None - - client_2 = hass.states.get("device_tracker.wired_client") - assert client_2 is not None - - device_1 = hass.states.get("device_tracker.device_1") - assert device_1 is None + assert hass.states.get("device_tracker.client") + assert not hass.states.get("device_tracker.device") hass.config_entries.async_update_entry( config_entry, @@ -569,36 +615,40 @@ async def test_option_track_devices(hass, aioclient_mock): ) await hass.async_block_till_done() - client_1 = hass.states.get("device_tracker.client_1") - assert client_1 is not None - - client_2 = hass.states.get("device_tracker.wired_client") - assert client_2 is not None - - device_1 = hass.states.get("device_tracker.device_1") - assert device_1 is not None + assert hass.states.get("device_tracker.client") + assert hass.states.get("device_tracker.device") async def test_option_ssid_filter(hass, aioclient_mock, mock_unifi_websocket): """Test the SSID filter works. - Client 1 will travel from a supported SSID to an unsupported ssid. - Client 3 will be removed on change of options since it is in an unsupported SSID. + Client will travel from a supported SSID to an unsupported ssid. + Client on SSID2 will be removed on change of options. """ - client_1_copy = copy(CLIENT_1) - client_1_copy["last_seen"] = dt_util.as_timestamp(dt_util.utcnow()) + client = { + "essid": "ssid", + "hostname": "client", + "is_wired": False, + "last_seen": dt_util.as_timestamp(dt_util.utcnow()), + "mac": "00:00:00:00:00:01", + } + client_on_ssid2 = { + "essid": "ssid2", + "hostname": "client_on_ssid2", + "is_wired": False, + "last_seen": 1562600145, + "mac": "00:00:00:00:00:02", + } config_entry = await setup_unifi_integration( - hass, aioclient_mock, clients_response=[client_1_copy, CLIENT_3] + hass, aioclient_mock, clients_response=[client, client_on_ssid2] ) controller = hass.data[UNIFI_DOMAIN][config_entry.entry_id] + assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 2 - client_1 = hass.states.get("device_tracker.client_1") - assert client_1.state == "home" - - client_3 = hass.states.get("device_tracker.client_3") - assert client_3 + assert hass.states.get("device_tracker.client").state == STATE_HOME + assert hass.states.get("device_tracker.client_on_ssid2").state == STATE_NOT_HOME # Setting SSID filter will remove clients outside of filter hass.config_entries.async_update_entry( @@ -608,40 +658,34 @@ async def test_option_ssid_filter(hass, aioclient_mock, mock_unifi_websocket): await hass.async_block_till_done() # Not affected by SSID filter - client_1 = hass.states.get("device_tracker.client_1") - assert client_1.state == "home" + assert hass.states.get("device_tracker.client").state == STATE_HOME # Removed due to SSID filter - client_3 = hass.states.get("device_tracker.client_3") - assert not client_3 + assert not hass.states.get("device_tracker.client_on_ssid2") # Roams to SSID outside of filter - client_1_copy = copy(CLIENT_1) - client_1_copy["essid"] = "other_ssid" + client["essid"] = "other_ssid" mock_unifi_websocket( data={ "meta": {"message": MESSAGE_CLIENT}, - "data": [client_1_copy], + "data": [client], } ) # Data update while SSID filter is in effect shouldn't create the client - client_3_copy = copy(CLIENT_3) - client_3_copy["last_seen"] = dt_util.as_timestamp(dt_util.utcnow()) + client_on_ssid2["last_seen"] = dt_util.as_timestamp(dt_util.utcnow()) mock_unifi_websocket( data={ "meta": {"message": MESSAGE_CLIENT}, - "data": [client_3_copy], + "data": [client_on_ssid2], } ) await hass.async_block_till_done() # SSID filter marks client as away - client_1 = hass.states.get("device_tracker.client_1") - assert client_1.state == "not_home" + assert hass.states.get("device_tracker.client").state == STATE_NOT_HOME # SSID still outside of filter - client_3 = hass.states.get("device_tracker.client_3") - assert not client_3 + assert not hass.states.get("device_tracker.client_on_ssid2") # Remove SSID filter hass.config_entries.async_update_entry( @@ -653,47 +697,45 @@ async def test_option_ssid_filter(hass, aioclient_mock, mock_unifi_websocket): mock_unifi_websocket( data={ "meta": {"message": MESSAGE_CLIENT}, - "data": [client_1_copy], + "data": [client], } ) mock_unifi_websocket( data={ "meta": {"message": MESSAGE_CLIENT}, - "data": [client_3_copy], + "data": [client_on_ssid2], } ) await hass.async_block_till_done() - client_1 = hass.states.get("device_tracker.client_1") - assert client_1.state == "home" + assert hass.states.get("device_tracker.client").state == STATE_HOME + assert hass.states.get("device_tracker.client_on_ssid2").state == STATE_HOME - client_3 = hass.states.get("device_tracker.client_3") - assert client_3.state == "home" + # Time pass to mark client as away new_time = dt_util.utcnow() + controller.option_detection_time with patch("homeassistant.util.dt.utcnow", return_value=new_time): async_fire_time_changed(hass, new_time) await hass.async_block_till_done() - client_1 = hass.states.get("device_tracker.client_1") - assert client_1.state == "not_home" + assert hass.states.get("device_tracker.client").state == STATE_NOT_HOME mock_unifi_websocket( data={ "meta": {"message": MESSAGE_CLIENT}, - "data": [client_3_copy], + "data": [client_on_ssid2], } ) await hass.async_block_till_done() + # Client won't go away until after next update - client_3 = hass.states.get("device_tracker.client_3") - assert client_3.state == "home" + assert hass.states.get("device_tracker.client_on_ssid2").state == STATE_HOME # Trigger update to get client marked as away mock_unifi_websocket( data={ "meta": {"message": MESSAGE_CLIENT}, - "data": [client_3_copy], + "data": [client_on_ssid2], } ) await hass.async_block_till_done() @@ -705,8 +747,7 @@ async def test_option_ssid_filter(hass, aioclient_mock, mock_unifi_websocket): async_fire_time_changed(hass, new_time) await hass.async_block_till_done() - client_3 = hass.states.get("device_tracker.client_3") - assert client_3.state == "not_home" + assert hass.states.get("device_tracker.client_on_ssid2").state == STATE_NOT_HOME async def test_wireless_client_go_wired_issue( @@ -716,35 +757,41 @@ async def test_wireless_client_go_wired_issue( UniFi has a known issue that when a wireless device goes away it sometimes gets marked as wired. """ - client_1_client = copy(CLIENT_1) - client_1_client["last_seen"] = dt_util.as_timestamp(dt_util.utcnow()) + client = { + "essid": "ssid", + "hostname": "client", + "ip": "10.0.0.1", + "is_wired": False, + "last_seen": dt_util.as_timestamp(dt_util.utcnow()), + "mac": "00:00:00:00:00:01", + } config_entry = await setup_unifi_integration( - hass, aioclient_mock, clients_response=[client_1_client] + hass, aioclient_mock, clients_response=[client] ) controller = hass.data[UNIFI_DOMAIN][config_entry.entry_id] + assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 1 # Client is wireless - client_1 = hass.states.get("device_tracker.client_1") - assert client_1 is not None - assert client_1.state == "home" - assert client_1.attributes["is_wired"] is False + client_state = hass.states.get("device_tracker.client") + assert client_state.state == STATE_HOME + assert client_state.attributes["is_wired"] is False # Trigger wired bug - client_1_client["is_wired"] = True + client["is_wired"] = True mock_unifi_websocket( data={ "meta": {"message": MESSAGE_CLIENT}, - "data": [client_1_client], + "data": [client], } ) await hass.async_block_till_done() # Wired bug fix keeps client marked as wireless - client_1 = hass.states.get("device_tracker.client_1") - assert client_1.state == "home" - assert client_1.attributes["is_wired"] is False + client_state = hass.states.get("device_tracker.client") + assert client_state.state == STATE_HOME + assert client_state.attributes["is_wired"] is False # Pass time new_time = dt_util.utcnow() + controller.option_detection_time @@ -753,74 +800,80 @@ async def test_wireless_client_go_wired_issue( await hass.async_block_till_done() # Marked as home according to the timer - client_1 = hass.states.get("device_tracker.client_1") - assert client_1.state == "not_home" - assert client_1.attributes["is_wired"] is False + client_state = hass.states.get("device_tracker.client") + assert client_state.state == STATE_NOT_HOME + assert client_state.attributes["is_wired"] is False # Try to mark client as connected mock_unifi_websocket( data={ "meta": {"message": MESSAGE_CLIENT}, - "data": [client_1_client], + "data": [client], } ) await hass.async_block_till_done() # Make sure it don't go online again until wired bug disappears - client_1 = hass.states.get("device_tracker.client_1") - assert client_1.state == "not_home" - assert client_1.attributes["is_wired"] is False + client_state = hass.states.get("device_tracker.client") + assert client_state.state == STATE_NOT_HOME + assert client_state.attributes["is_wired"] is False # Make client wireless - client_1_client["is_wired"] = False + client["is_wired"] = False mock_unifi_websocket( data={ "meta": {"message": MESSAGE_CLIENT}, - "data": [client_1_client], + "data": [client], } ) await hass.async_block_till_done() # Client is no longer affected by wired bug and can be marked online - client_1 = hass.states.get("device_tracker.client_1") - assert client_1.state == "home" - assert client_1.attributes["is_wired"] is False + client_state = hass.states.get("device_tracker.client") + assert client_state.state == STATE_HOME + assert client_state.attributes["is_wired"] is False async def test_option_ignore_wired_bug(hass, aioclient_mock, mock_unifi_websocket): """Test option to ignore wired bug.""" - client_1_client = copy(CLIENT_1) - client_1_client["last_seen"] = dt_util.as_timestamp(dt_util.utcnow()) + client = { + "ap_mac": "00:00:00:00:02:01", + "essid": "ssid", + "hostname": "client", + "ip": "10.0.0.1", + "is_wired": False, + "last_seen": dt_util.as_timestamp(dt_util.utcnow()), + "mac": "00:00:00:00:00:01", + } config_entry = await setup_unifi_integration( hass, aioclient_mock, options={CONF_IGNORE_WIRED_BUG: True}, - clients_response=[client_1_client], + clients_response=[client], ) controller = hass.data[UNIFI_DOMAIN][config_entry.entry_id] assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 1 # Client is wireless - client_1 = hass.states.get("device_tracker.client_1") - assert client_1 is not None - assert client_1.state == "home" - assert client_1.attributes["is_wired"] is False + client_state = hass.states.get("device_tracker.client") + assert client_state.state == STATE_HOME + assert client_state.attributes["is_wired"] is False # Trigger wired bug - client_1_client["is_wired"] = True + client["is_wired"] = True mock_unifi_websocket( data={ "meta": {"message": MESSAGE_CLIENT}, - "data": [client_1_client], + "data": [client], } ) await hass.async_block_till_done() # Wired bug in effect - client_1 = hass.states.get("device_tracker.client_1") - assert client_1.state == "home" - assert client_1.attributes["is_wired"] is True + client_state = hass.states.get("device_tracker.client") + assert client_state.state == STATE_HOME + assert client_state.attributes["is_wired"] is True # pass time new_time = dt_util.utcnow() + controller.option_detection_time @@ -829,42 +882,61 @@ async def test_option_ignore_wired_bug(hass, aioclient_mock, mock_unifi_websocke await hass.async_block_till_done() # Timer marks client as away - client_1 = hass.states.get("device_tracker.client_1") - assert client_1.state == "not_home" - assert client_1.attributes["is_wired"] is True + client_state = hass.states.get("device_tracker.client") + assert client_state.state == STATE_NOT_HOME + assert client_state.attributes["is_wired"] is True # Mark client as connected again mock_unifi_websocket( data={ "meta": {"message": MESSAGE_CLIENT}, - "data": [client_1_client], + "data": [client], } ) await hass.async_block_till_done() # Ignoring wired bug allows client to go home again even while affected - client_1 = hass.states.get("device_tracker.client_1") - assert client_1.state == "home" - assert client_1.attributes["is_wired"] is True + client_state = hass.states.get("device_tracker.client") + assert client_state.state == STATE_HOME + assert client_state.attributes["is_wired"] is True # Make client wireless - client_1_client["is_wired"] = False + client["is_wired"] = False mock_unifi_websocket( data={ "meta": {"message": MESSAGE_CLIENT}, - "data": [client_1_client], + "data": [client], } ) await hass.async_block_till_done() # Client is wireless and still connected - client_1 = hass.states.get("device_tracker.client_1") - assert client_1.state == "home" - assert client_1.attributes["is_wired"] is False + client_state = hass.states.get("device_tracker.client") + assert client_state.state == STATE_HOME + assert client_state.attributes["is_wired"] is False async def test_restoring_client(hass, aioclient_mock): - """Test the update_items function with some clients.""" + """Verify clients are restored from clients_all if they ever was registered to entity registry.""" + client = { + "hostname": "client", + "is_wired": True, + "last_seen": 1562600145, + "mac": "00:00:00:00:00:01", + } + restored = { + "hostname": "restored", + "is_wired": True, + "last_seen": 1562600145, + "mac": "00:00:00:00:00:02", + } + not_restored = { + "hostname": "not_restored", + "is_wired": True, + "last_seen": 1562600145, + "mac": "00:00:00:00:00:03", + } + config_entry = config_entries.ConfigEntry( version=1, domain=UNIFI_DOMAIN, @@ -881,15 +953,8 @@ async def test_restoring_client(hass, aioclient_mock): registry.async_get_or_create( TRACKER_DOMAIN, UNIFI_DOMAIN, - f'{CLIENT_1["mac"]}-site_id', - suggested_object_id=CLIENT_1["hostname"], - config_entry=config_entry, - ) - registry.async_get_or_create( - TRACKER_DOMAIN, - UNIFI_DOMAIN, - f'{CLIENT_2["mac"]}-site_id', - suggested_object_id=CLIENT_2["hostname"], + f'{restored["mac"]}-site_id', + suggested_object_id=restored["hostname"], config_entry=config_entry, ) @@ -897,31 +962,63 @@ async def test_restoring_client(hass, aioclient_mock): hass, aioclient_mock, options={CONF_BLOCK_CLIENT: True}, - clients_response=[CLIENT_2], - clients_all_response=[CLIENT_1], + clients_response=[client], + clients_all_response=[restored, not_restored], ) - assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 2 - device_1 = hass.states.get("device_tracker.client_1") - assert device_1 is not None + assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 2 + assert hass.states.get("device_tracker.client") + assert hass.states.get("device_tracker.restored") + assert not hass.states.get("device_tracker.not_restored") async def test_dont_track_clients(hass, aioclient_mock): """Test don't track clients config works.""" + wireless_client = { + "essid": "ssid", + "hostname": "Wireless client", + "ip": "10.0.0.1", + "is_wired": False, + "last_seen": 1562600145, + "mac": "00:00:00:00:00:01", + } + wired_client = { + "hostname": "Wired client", + "ip": "10.0.0.2", + "is_wired": True, + "last_seen": 1562600145, + "mac": "00:00:00:00:00:02", + } + device = { + "board_rev": 3, + "device_id": "mock-id", + "has_fan": True, + "fan_level": 0, + "ip": "10.0.1.1", + "last_seen": 1562600145, + "mac": "00:00:00:00:01:01", + "model": "US16P150", + "name": "Device", + "next_interval": 20, + "overheating": True, + "state": 1, + "type": "usw", + "upgradable": True, + "version": "4.0.42.10433", + } + config_entry = await setup_unifi_integration( hass, aioclient_mock, options={CONF_TRACK_CLIENTS: False}, - clients_response=[CLIENT_1], - devices_response=[DEVICE_1], + clients_response=[wireless_client, wired_client], + devices_response=[device], ) + assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 1 - - client_1 = hass.states.get("device_tracker.client_1") - assert client_1 is None - - device_1 = hass.states.get("device_tracker.device_1") - assert device_1 is not None + assert not hass.states.get("device_tracker.wireless_client") + assert not hass.states.get("device_tracker.wired_client") + assert hass.states.get("device_tracker.device") hass.config_entries.async_update_entry( config_entry, @@ -929,31 +1026,49 @@ async def test_dont_track_clients(hass, aioclient_mock): ) await hass.async_block_till_done() - assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 2 - - client_1 = hass.states.get("device_tracker.client_1") - assert client_1 is not None - - device_1 = hass.states.get("device_tracker.device_1") - assert device_1 is not None + assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 3 + assert hass.states.get("device_tracker.wireless_client") + assert hass.states.get("device_tracker.wired_client") + assert hass.states.get("device_tracker.device") async def test_dont_track_devices(hass, aioclient_mock): """Test don't track devices config works.""" + client = { + "hostname": "client", + "is_wired": True, + "last_seen": 1562600145, + "mac": "00:00:00:00:00:01", + } + device = { + "board_rev": 3, + "device_id": "mock-id", + "has_fan": True, + "fan_level": 0, + "ip": "10.0.1.1", + "last_seen": 1562600145, + "mac": "00:00:00:00:01:01", + "model": "US16P150", + "name": "Device", + "next_interval": 20, + "overheating": True, + "state": 1, + "type": "usw", + "upgradable": True, + "version": "4.0.42.10433", + } + config_entry = await setup_unifi_integration( hass, aioclient_mock, options={CONF_TRACK_DEVICES: False}, - clients_response=[CLIENT_1], - devices_response=[DEVICE_1], + clients_response=[client], + devices_response=[device], ) + assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 1 - - client_1 = hass.states.get("device_tracker.client_1") - assert client_1 is not None - - device_1 = hass.states.get("device_tracker.device_1") - assert device_1 is None + assert hass.states.get("device_tracker.client") + assert not hass.states.get("device_tracker.device") hass.config_entries.async_update_entry( config_entry, @@ -962,29 +1077,36 @@ async def test_dont_track_devices(hass, aioclient_mock): await hass.async_block_till_done() assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 2 - - client_1 = hass.states.get("device_tracker.client_1") - assert client_1 is not None - - device_1 = hass.states.get("device_tracker.device_1") - assert device_1 is not None + assert hass.states.get("device_tracker.client") + assert hass.states.get("device_tracker.device") async def test_dont_track_wired_clients(hass, aioclient_mock): """Test don't track wired clients config works.""" + wireless_client = { + "essid": "ssid", + "hostname": "Wireless Client", + "is_wired": False, + "last_seen": 1562600145, + "mac": "00:00:00:00:00:01", + } + wired_client = { + "is_wired": True, + "last_seen": 1562600145, + "mac": "00:00:00:00:00:02", + "name": "Wired Client", + } + config_entry = await setup_unifi_integration( hass, aioclient_mock, options={CONF_TRACK_WIRED_CLIENTS: False}, - clients_response=[CLIENT_1, CLIENT_2], + clients_response=[wireless_client, wired_client], ) + assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 1 - - client_1 = hass.states.get("device_tracker.client_1") - assert client_1 is not None - - client_2 = hass.states.get("device_tracker.wired_client") - assert client_2 is None + assert hass.states.get("device_tracker.wireless_client") + assert not hass.states.get("device_tracker.wired_client") hass.config_entries.async_update_entry( config_entry, @@ -993,9 +1115,5 @@ async def test_dont_track_wired_clients(hass, aioclient_mock): await hass.async_block_till_done() assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 2 - - client_1 = hass.states.get("device_tracker.client_1") - assert client_1 is not None - - client_2 = hass.states.get("device_tracker.wired_client") - assert client_2 is not None + assert hass.states.get("device_tracker.wireless_client") + assert hass.states.get("device_tracker.wired_client")