Rewrite the UniFi button entity tests (#118771)

This commit is contained in:
Robert Svensson 2024-06-09 12:25:06 +02:00 committed by GitHub
parent 5829d9d8ab
commit ff493a8a9d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -1,12 +1,14 @@
"""UniFi Network button platform tests.""" """UniFi Network button platform tests."""
from datetime import timedelta from datetime import timedelta
from typing import Any
from unittest.mock import patch
import pytest import pytest
from homeassistant.components.button import DOMAIN as BUTTON_DOMAIN, ButtonDeviceClass from homeassistant.components.button import DOMAIN as BUTTON_DOMAIN, ButtonDeviceClass
from homeassistant.components.unifi.const import CONF_SITE_ID from homeassistant.components.unifi.const import CONF_SITE_ID
from homeassistant.config_entries import RELOAD_AFTER_UPDATE_DELAY from homeassistant.config_entries import RELOAD_AFTER_UPDATE_DELAY, ConfigEntry
from homeassistant.const import ( from homeassistant.const import (
ATTR_DEVICE_CLASS, ATTR_DEVICE_CLASS,
CONF_HOST, CONF_HOST,
@ -22,266 +24,298 @@ import homeassistant.util.dt as dt_util
from tests.common import async_fire_time_changed from tests.common import async_fire_time_changed
from tests.test_util.aiohttp import AiohttpClientMocker from tests.test_util.aiohttp import AiohttpClientMocker
WLAN_ID = "_id" RANDOM_TOKEN = "random_token"
WLAN = {
WLAN_ID: "012345678910111213141516",
"bc_filter_enabled": False,
"bc_filter_list": [],
"dtim_mode": "default",
"dtim_na": 1,
"dtim_ng": 1,
"enabled": True,
"group_rekey": 3600,
"mac_filter_enabled": False,
"mac_filter_list": [],
"mac_filter_policy": "allow",
"minrate_na_advertising_rates": False,
"minrate_na_beacon_rate_kbps": 6000,
"minrate_na_data_rate_kbps": 6000,
"minrate_na_enabled": False,
"minrate_na_mgmt_rate_kbps": 6000,
"minrate_ng_advertising_rates": False,
"minrate_ng_beacon_rate_kbps": 1000,
"minrate_ng_data_rate_kbps": 1000,
"minrate_ng_enabled": False,
"minrate_ng_mgmt_rate_kbps": 1000,
"name": "SSID 1",
"no2ghz_oui": False,
"schedule": [],
"security": "wpapsk",
"site_id": "5a32aa4ee4b0412345678910",
"usergroup_id": "012345678910111213141518",
"wep_idx": 1,
"wlangroup_id": "012345678910111213141519",
"wpa_enc": "ccmp",
"wpa_mode": "wpa2",
"x_iapp_key": "01234567891011121314151617181920",
"x_passphrase": "password",
}
@pytest.mark.parametrize( @pytest.fixture(autouse=True)
"device_payload", def mock_secret():
[ """Mock secret."""
[ with patch("secrets.token_urlsafe", return_value=RANDOM_TOKEN):
yield
DEVICE_RESTART = [
{
"board_rev": 3,
"device_id": "mock-id",
"ip": "10.0.0.1",
"last_seen": 1562600145,
"mac": "00:00:00:00:01:01",
"model": "US16P150",
"name": "switch",
"state": 1,
"type": "usw",
"version": "4.0.42.10433",
}
]
DEVICE_POWER_CYCLE_POE = [
{
"board_rev": 3,
"device_id": "mock-id",
"ip": "10.0.0.1",
"last_seen": 1562600145,
"mac": "00:00:00:00:01:01",
"model": "US16P150",
"name": "switch",
"state": 1,
"type": "usw",
"version": "4.0.42.10433",
"port_table": [
{ {
"board_rev": 3, "media": "GE",
"device_id": "mock-id", "name": "Port 1",
"ip": "10.0.0.1", "port_idx": 1,
"last_seen": 1562600145, "poe_caps": 7,
"mac": "00:00:00:00:01:01", "poe_class": "Class 4",
"model": "US16P150", "poe_enable": True,
"name": "switch", "poe_mode": "auto",
"state": 1, "poe_power": "2.56",
"type": "usw", "poe_voltage": "53.40",
"version": "4.0.42.10433", "portconf_id": "1a1",
} "port_poe": True,
] "up": True,
], },
) ],
async def test_restart_device_button( }
]
WLAN_REGENERATE_PASSWORD = [
{
"_id": "012345678910111213141516",
"bc_filter_enabled": False,
"bc_filter_list": [],
"dtim_mode": "default",
"dtim_na": 1,
"dtim_ng": 1,
"enabled": True,
"group_rekey": 3600,
"mac_filter_enabled": False,
"mac_filter_list": [],
"mac_filter_policy": "allow",
"minrate_na_advertising_rates": False,
"minrate_na_beacon_rate_kbps": 6000,
"minrate_na_data_rate_kbps": 6000,
"minrate_na_enabled": False,
"minrate_na_mgmt_rate_kbps": 6000,
"minrate_ng_advertising_rates": False,
"minrate_ng_beacon_rate_kbps": 1000,
"minrate_ng_data_rate_kbps": 1000,
"minrate_ng_enabled": False,
"minrate_ng_mgmt_rate_kbps": 1000,
"name": "SSID 1",
"no2ghz_oui": False,
"schedule": [],
"security": "wpapsk",
"site_id": "5a32aa4ee4b0412345678910",
"usergroup_id": "012345678910111213141518",
"wep_idx": 1,
"wlangroup_id": "012345678910111213141519",
"wpa_enc": "ccmp",
"wpa_mode": "wpa2",
"x_iapp_key": "01234567891011121314151617181920",
"x_passphrase": "password",
}
]
async def _test_button_entity(
hass: HomeAssistant, hass: HomeAssistant,
entity_registry: er.EntityRegistry, entity_registry: er.EntityRegistry,
aioclient_mock: AiohttpClientMocker, aioclient_mock: AiohttpClientMocker,
config_entry_setup,
websocket_mock, websocket_mock,
config_entry: ConfigEntry,
entity_count: int,
entity_id: str,
unique_id: str,
device_class: ButtonDeviceClass,
request_method: str,
request_path: str,
request_data: dict[str, Any],
call: dict[str, str],
) -> None: ) -> None:
"""Test restarting device button.""" """Test button entity."""
config_entry = config_entry_setup assert len(hass.states.async_entity_ids(BUTTON_DOMAIN)) == entity_count
assert len(hass.states.async_entity_ids(BUTTON_DOMAIN)) == 1
ent_reg_entry = entity_registry.async_get("button.switch_restart") ent_reg_entry = entity_registry.async_get(entity_id)
assert ent_reg_entry.unique_id == "device_restart-00:00:00:00:01:01" assert ent_reg_entry.unique_id == unique_id
assert ent_reg_entry.entity_category is EntityCategory.CONFIG assert ent_reg_entry.entity_category is EntityCategory.CONFIG
# Validate state object # Validate state object
button = hass.states.get("button.switch_restart") button = hass.states.get(entity_id)
assert button is not None assert button is not None
assert button.attributes.get(ATTR_DEVICE_CLASS) == ButtonDeviceClass.RESTART assert button.attributes.get(ATTR_DEVICE_CLASS) == device_class
# Send restart device command # Send and validate device command
aioclient_mock.clear_requests() aioclient_mock.clear_requests()
aioclient_mock.post( aioclient_mock.request(
request_method,
f"https://{config_entry.data[CONF_HOST]}:1234" f"https://{config_entry.data[CONF_HOST]}:1234"
f"/api/s/{config_entry.data[CONF_SITE_ID]}/cmd/devmgr", f"/api/s/{config_entry.data[CONF_SITE_ID]}{request_path}",
**request_data,
) )
await hass.services.async_call( await hass.services.async_call(
BUTTON_DOMAIN, BUTTON_DOMAIN, "press", {"entity_id": entity_id}, blocking=True
"press",
{"entity_id": "button.switch_restart"},
blocking=True,
) )
assert aioclient_mock.call_count == 1 assert aioclient_mock.call_count == 1
assert aioclient_mock.mock_calls[0][2] == { assert aioclient_mock.mock_calls[0][2] == call
"cmd": "restart",
"mac": "00:00:00:00:01:01",
"reboot_type": "soft",
}
# Availability signalling # Availability signalling
# Controller disconnects # Controller disconnects
await websocket_mock.disconnect() await websocket_mock.disconnect()
assert hass.states.get("button.switch_restart").state == STATE_UNAVAILABLE assert hass.states.get(entity_id).state == STATE_UNAVAILABLE
# Controller reconnects # Controller reconnects
await websocket_mock.reconnect() await websocket_mock.reconnect()
assert hass.states.get("button.switch_restart").state != STATE_UNAVAILABLE assert hass.states.get(entity_id).state != STATE_UNAVAILABLE
@pytest.mark.parametrize( @pytest.mark.parametrize(
"device_payload", (
"device_payload",
"entity_count",
"entity_id",
"unique_id",
"device_class",
"request_method",
"request_path",
"call",
),
[ [
[ (
DEVICE_RESTART,
1,
"button.switch_restart",
"device_restart-00:00:00:00:01:01",
ButtonDeviceClass.RESTART,
"post",
"/cmd/devmgr",
{ {
"board_rev": 3, "cmd": "restart",
"device_id": "mock-id",
"ip": "10.0.0.1",
"last_seen": 1562600145,
"mac": "00:00:00:00:01:01", "mac": "00:00:00:00:01:01",
"model": "US16P150", "reboot_type": "soft",
"name": "switch", },
"state": 1, ),
"type": "usw", (
"version": "4.0.42.10433", DEVICE_POWER_CYCLE_POE,
"port_table": [ 2,
{ "button.switch_port_1_power_cycle",
"media": "GE", "power_cycle-00:00:00:00:01:01_1",
"name": "Port 1", ButtonDeviceClass.RESTART,
"port_idx": 1, "post",
"poe_caps": 7, "/cmd/devmgr",
"poe_class": "Class 4", {
"poe_enable": True, "cmd": "power-cycle",
"poe_mode": "auto", "mac": "00:00:00:00:01:01",
"poe_power": "2.56", "port_idx": 1,
"poe_voltage": "53.40", },
"portconf_id": "1a1", ),
"port_poe": True,
"up": True,
},
],
}
]
], ],
) )
async def test_power_cycle_poe( async def test_device_button_entities(
hass: HomeAssistant, hass: HomeAssistant,
entity_registry: er.EntityRegistry, entity_registry: er.EntityRegistry,
aioclient_mock: AiohttpClientMocker, aioclient_mock: AiohttpClientMocker,
config_entry_setup, config_entry_setup,
websocket_mock, websocket_mock,
entity_count: int,
entity_id: str,
unique_id: str,
device_class: ButtonDeviceClass,
request_method: str,
request_path: str,
call: dict[str, str],
) -> None: ) -> None:
"""Test restarting device button.""" """Test button entities based on device sources."""
config_entry = config_entry_setup await _test_button_entity(
assert len(hass.states.async_entity_ids(BUTTON_DOMAIN)) == 2 hass,
entity_registry,
ent_reg_entry = entity_registry.async_get("button.switch_port_1_power_cycle") aioclient_mock,
assert ent_reg_entry.unique_id == "power_cycle-00:00:00:00:01:01_1" websocket_mock,
assert ent_reg_entry.entity_category is EntityCategory.CONFIG config_entry_setup,
entity_count,
# Validate state object entity_id,
button = hass.states.get("button.switch_port_1_power_cycle") unique_id,
assert button is not None device_class,
assert button.attributes.get(ATTR_DEVICE_CLASS) == ButtonDeviceClass.RESTART request_method,
request_path,
# Send restart device command {},
aioclient_mock.clear_requests() call,
aioclient_mock.post(
f"https://{config_entry.data[CONF_HOST]}:1234"
f"/api/s/{config_entry.data[CONF_SITE_ID]}/cmd/devmgr",
)
await hass.services.async_call(
BUTTON_DOMAIN,
"press",
{"entity_id": "button.switch_port_1_power_cycle"},
blocking=True,
)
assert aioclient_mock.call_count == 1
assert aioclient_mock.mock_calls[0][2] == {
"cmd": "power-cycle",
"mac": "00:00:00:00:01:01",
"port_idx": 1,
}
# Availability signalling
# Controller disconnects
await websocket_mock.disconnect()
assert (
hass.states.get("button.switch_port_1_power_cycle").state == STATE_UNAVAILABLE
)
# Controller reconnects
await websocket_mock.reconnect()
assert (
hass.states.get("button.switch_port_1_power_cycle").state != STATE_UNAVAILABLE
) )
@pytest.mark.parametrize("wlan_payload", [[WLAN]]) @pytest.mark.parametrize(
async def test_wlan_regenerate_password( (
"wlan_payload",
"entity_count",
"entity_id",
"unique_id",
"device_class",
"request_method",
"request_path",
"request_data",
"call",
),
[
(
WLAN_REGENERATE_PASSWORD,
1,
"button.ssid_1_regenerate_password",
"regenerate_password-012345678910111213141516",
ButtonDeviceClass.UPDATE,
"put",
f"/rest/wlanconf/{WLAN_REGENERATE_PASSWORD[0]["_id"]}",
{
"json": {"data": "password changed successfully", "meta": {"rc": "ok"}},
"headers": {"content-type": CONTENT_TYPE_JSON},
},
{"x_passphrase": RANDOM_TOKEN},
),
],
)
async def test_wlan_button_entities(
hass: HomeAssistant, hass: HomeAssistant,
entity_registry: er.EntityRegistry, entity_registry: er.EntityRegistry,
aioclient_mock: AiohttpClientMocker, aioclient_mock: AiohttpClientMocker,
config_entry_setup, config_entry_setup,
websocket_mock, websocket_mock,
entity_count: int,
entity_id: str,
unique_id: str,
device_class: ButtonDeviceClass,
request_method: str,
request_path: str,
request_data: dict[str, Any],
call: dict[str, str],
) -> None: ) -> None:
"""Test WLAN regenerate password button.""" """Test button entities based on WLAN sources."""
config_entry = config_entry_setup
assert len(hass.states.async_entity_ids(BUTTON_DOMAIN)) == 0 assert len(hass.states.async_entity_ids(BUTTON_DOMAIN)) == 0
button_regenerate_password = "button.ssid_1_regenerate_password" ent_reg_entry = entity_registry.async_get(entity_id)
ent_reg_entry = entity_registry.async_get(button_regenerate_password)
assert ent_reg_entry.unique_id == "regenerate_password-012345678910111213141516"
assert ent_reg_entry.disabled_by == RegistryEntryDisabler.INTEGRATION assert ent_reg_entry.disabled_by == RegistryEntryDisabler.INTEGRATION
assert ent_reg_entry.entity_category is EntityCategory.CONFIG
# Enable entity # Enable entity
entity_registry.async_update_entity( entity_registry.async_update_entity(entity_id=entity_id, disabled_by=None)
entity_id=button_regenerate_password, disabled_by=None
)
await hass.async_block_till_done()
async_fire_time_changed( async_fire_time_changed(
hass, hass,
dt_util.utcnow() + timedelta(seconds=RELOAD_AFTER_UPDATE_DELAY + 1), dt_util.utcnow() + timedelta(seconds=RELOAD_AFTER_UPDATE_DELAY + 1),
) )
await hass.async_block_till_done() await hass.async_block_till_done()
assert len(hass.states.async_entity_ids(BUTTON_DOMAIN)) == 1 await _test_button_entity(
hass,
# Validate state object entity_registry,
button = hass.states.get(button_regenerate_password) aioclient_mock,
assert button is not None websocket_mock,
assert button.attributes.get(ATTR_DEVICE_CLASS) == ButtonDeviceClass.UPDATE config_entry_setup,
entity_count,
aioclient_mock.clear_requests() entity_id,
aioclient_mock.put( unique_id,
f"https://{config_entry.data[CONF_HOST]}:1234" device_class,
f"/api/s/{config_entry.data[CONF_SITE_ID]}/rest/wlanconf/{WLAN[WLAN_ID]}", request_method,
json={"data": "password changed successfully", "meta": {"rc": "ok"}}, request_path,
headers={"content-type": CONTENT_TYPE_JSON}, request_data,
call,
) )
# Send WLAN regenerate password command
await hass.services.async_call(
BUTTON_DOMAIN,
"press",
{"entity_id": button_regenerate_password},
blocking=True,
)
assert aioclient_mock.call_count == 1
assert next(iter(aioclient_mock.mock_calls[0][2])) == "x_passphrase"
# Availability signalling
# Controller disconnects
await websocket_mock.disconnect()
assert hass.states.get(button_regenerate_password).state == STATE_UNAVAILABLE
# Controller reconnects
await websocket_mock.reconnect()
assert hass.states.get(button_regenerate_password).state != STATE_UNAVAILABLE