mirror of
https://github.com/home-assistant/core.git
synced 2025-12-19 18:38:58 +00:00
Achieve Bronze quality rating for TP-Link Omada (#156697)
This commit is contained in:
@@ -14,7 +14,12 @@ from tplink_omada_client.exceptions import (
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.core import HomeAssistant, ServiceCall
|
||||
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
|
||||
from homeassistant.exceptions import (
|
||||
ConfigEntryAuthFailed,
|
||||
ConfigEntryNotReady,
|
||||
HomeAssistantError,
|
||||
ServiceValidationError,
|
||||
)
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
|
||||
from .config_flow import CONF_SITE, create_omada_client
|
||||
@@ -61,12 +66,15 @@ async def async_setup_entry(hass: HomeAssistant, entry: OmadaConfigEntry) -> boo
|
||||
entry.runtime_data = controller
|
||||
|
||||
async def handle_reconnect_client(call: ServiceCall) -> None:
|
||||
"""Handle the service action call."""
|
||||
"""Handle the service action to force reconnection of a network client."""
|
||||
mac: str | None = call.data.get("mac")
|
||||
if not mac:
|
||||
return
|
||||
raise ServiceValidationError("MAC address is required")
|
||||
|
||||
await site_client.reconnect_client(mac)
|
||||
try:
|
||||
await site_client.reconnect_client(mac)
|
||||
except OmadaClientException as ex:
|
||||
raise HomeAssistantError("Failed to reconnect client") from ex
|
||||
|
||||
hass.services.async_register(DOMAIN, "reconnect_client", handle_reconnect_client)
|
||||
|
||||
|
||||
80
homeassistant/components/tplink_omada/quality_scale.yaml
Normal file
80
homeassistant/components/tplink_omada/quality_scale.yaml
Normal file
@@ -0,0 +1,80 @@
|
||||
rules:
|
||||
# Bronze
|
||||
action-setup:
|
||||
status: todo
|
||||
comment: Actions are created in async_setup_entry, and need to be moved.
|
||||
appropriate-polling:
|
||||
status: done
|
||||
comment: Service data APIs are polled every 5 minutes
|
||||
brands: done
|
||||
common-modules:
|
||||
status: todo
|
||||
comment: The coordinator for the update platform should be moved to common module.
|
||||
config-flow-test-coverage:
|
||||
status: todo
|
||||
comment: "test_form_single_site is patching config flow internals, and should only patch external APIs. Must address feedback from #156697."
|
||||
config-flow: done
|
||||
dependency-transparency:
|
||||
status: done
|
||||
comment: API dependency published on PyPI, MIT licensed.
|
||||
docs-actions: done
|
||||
docs-high-level-description: done
|
||||
docs-installation-instructions: done
|
||||
docs-removal-instructions: done
|
||||
entity-event-setup:
|
||||
status: exempt
|
||||
comment: Integration does not subscribe to events.
|
||||
entity-unique-id: done
|
||||
has-entity-name: done
|
||||
runtime-data: done
|
||||
test-before-configure: done
|
||||
test-before-setup: done
|
||||
unique-config-entry:
|
||||
status: done
|
||||
comment: Omada Site unique ID checked during config flow.
|
||||
|
||||
# Silver
|
||||
action-exceptions: done
|
||||
config-entry-unloading: done
|
||||
docs-configuration-parameters:
|
||||
status: exempt
|
||||
comment: No configuration parameters or options flow yet.
|
||||
docs-installation-parameters: done
|
||||
entity-unavailable: done
|
||||
integration-owner: done
|
||||
log-when-unavailable: done
|
||||
parallel-updates: todo
|
||||
reauthentication-flow: done
|
||||
test-coverage: todo
|
||||
|
||||
# Gold
|
||||
devices: done
|
||||
diagnostics: todo
|
||||
discovery-update-info: todo
|
||||
discovery: todo
|
||||
docs-data-update: todo
|
||||
docs-examples: todo
|
||||
docs-known-limitations: todo
|
||||
docs-supported-devices: done
|
||||
docs-supported-functions: todo
|
||||
docs-troubleshooting: todo
|
||||
docs-use-cases: todo
|
||||
dynamic-devices: todo
|
||||
entity-category: done
|
||||
entity-device-class: done
|
||||
entity-disabled-by-default: todo
|
||||
entity-translations: done
|
||||
exception-translations: todo
|
||||
icon-translations: done
|
||||
reconfiguration-flow: todo
|
||||
repair-issues: todo
|
||||
stale-devices:
|
||||
status: todo
|
||||
comment: Stale devices are auto-deleted at startup, not yet during runtime.
|
||||
|
||||
# Platinum
|
||||
async-dependency: done
|
||||
inject-websession:
|
||||
status: done
|
||||
comment: Uses async_create_clientsession in case where unsafe cookies are needed.
|
||||
strict-typing: todo
|
||||
@@ -17,6 +17,10 @@
|
||||
"password": "[%key:common::config_flow::data::password%]",
|
||||
"username": "[%key:common::config_flow::data::username%]"
|
||||
},
|
||||
"data_description": {
|
||||
"password": "Password for the Omada controller user.",
|
||||
"username": "Username for the Omada controller user."
|
||||
},
|
||||
"description": "The provided credentials have stopped working. Please update them.",
|
||||
"title": "Update TP-Link Omada credentials"
|
||||
},
|
||||
@@ -24,7 +28,10 @@
|
||||
"data": {
|
||||
"site": "Site"
|
||||
},
|
||||
"title": "Choose which site(s) to manage"
|
||||
"data_description": {
|
||||
"site": "Select the site you want to manage in Home Assistant."
|
||||
},
|
||||
"title": "Choose which site to manage"
|
||||
},
|
||||
"user": {
|
||||
"data": {
|
||||
@@ -34,7 +41,10 @@
|
||||
"verify_ssl": "[%key:common::config_flow::data::verify_ssl%]"
|
||||
},
|
||||
"data_description": {
|
||||
"host": "URL of the management interface of your TP-Link Omada controller."
|
||||
"host": "URL of the management interface of your TP-Link Omada controller.",
|
||||
"password": "Password for the Omada controller user.",
|
||||
"username": "Username for the Omada controller user.",
|
||||
"verify_ssl": "Uncheck this box if you are using the default self-signed certificate on the controller."
|
||||
},
|
||||
"description": "Enter the connection details for the Omada controller. Cloud controllers aren't supported."
|
||||
}
|
||||
|
||||
@@ -983,7 +983,6 @@ INTEGRATIONS_WITHOUT_QUALITY_SCALE_FILE = [
|
||||
"touchline",
|
||||
"touchline_sl",
|
||||
"tplink_lte",
|
||||
"tplink_omada",
|
||||
"traccar",
|
||||
"traccar_server",
|
||||
"tractive",
|
||||
|
||||
@@ -86,6 +86,7 @@ async def mock_omada_site_client(hass: HomeAssistant) -> AsyncGenerator[AsyncMoc
|
||||
|
||||
site_client.get_known_clients.return_value = async_empty()
|
||||
site_client.get_connected_clients.return_value = async_empty()
|
||||
site_client.reconnect_client = AsyncMock()
|
||||
return site_client
|
||||
|
||||
|
||||
@@ -159,6 +160,7 @@ def mock_omada_client(mock_omada_site_client: AsyncMock) -> Generator[MagicMock]
|
||||
client = client_mock.return_value
|
||||
|
||||
client.get_site_client.return_value = mock_omada_site_client
|
||||
client.login = AsyncMock()
|
||||
yield client
|
||||
|
||||
|
||||
|
||||
@@ -2,8 +2,17 @@
|
||||
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
from tplink_omada_client.exceptions import (
|
||||
ConnectionFailed,
|
||||
OmadaClientException,
|
||||
UnsupportedControllerVersion,
|
||||
)
|
||||
|
||||
from homeassistant.components.tplink_omada.const import DOMAIN
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
@@ -17,6 +26,41 @@ MOCK_ENTRY_DATA = {
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("side_effect", "entry_state"),
|
||||
[
|
||||
(
|
||||
UnsupportedControllerVersion("4.0.0"),
|
||||
ConfigEntryState.SETUP_ERROR,
|
||||
),
|
||||
(
|
||||
ConnectionFailed(),
|
||||
ConfigEntryState.SETUP_RETRY,
|
||||
),
|
||||
(
|
||||
OmadaClientException(),
|
||||
ConfigEntryState.SETUP_RETRY,
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_setup_entry_login_failed_raises_configentryauthfailed(
|
||||
hass: HomeAssistant,
|
||||
mock_omada_client: MagicMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
side_effect: OmadaClientException,
|
||||
entry_state: ConfigEntryState,
|
||||
) -> None:
|
||||
"""Test setup entry with login failed raises ConfigEntryAuthFailed."""
|
||||
mock_omada_client.login.side_effect = side_effect
|
||||
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
|
||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert mock_config_entry.state == entry_state
|
||||
|
||||
|
||||
async def test_missing_devices_removed_at_startup(
|
||||
hass: HomeAssistant,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
@@ -45,3 +89,73 @@ async def test_missing_devices_removed_at_startup(
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert device_registry.async_get(device_entry.id) is None
|
||||
|
||||
|
||||
async def test_service_reconnect_client(
|
||||
hass: HomeAssistant,
|
||||
mock_omada_site_client: MagicMock,
|
||||
mock_omada_client: MagicMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test reconnect client service."""
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
|
||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
mac = "AA:BB:CC:DD:EE:FF"
|
||||
await hass.services.async_call(
|
||||
DOMAIN,
|
||||
"reconnect_client",
|
||||
{"mac": mac},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
mock_omada_site_client.reconnect_client.assert_awaited_once_with(mac)
|
||||
|
||||
|
||||
async def test_service_reconnect_failed_raises_servicevalidationerror(
|
||||
hass: HomeAssistant,
|
||||
mock_omada_site_client: MagicMock,
|
||||
mock_omada_client: MagicMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test reconnect with missing mac address raises ServiceValidationError."""
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
|
||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
with pytest.raises(ServiceValidationError):
|
||||
await hass.services.async_call(
|
||||
DOMAIN,
|
||||
"reconnect_client",
|
||||
{},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
|
||||
async def test_service_reconnect_failed_raises_homeassistanterror(
|
||||
hass: HomeAssistant,
|
||||
mock_omada_site_client: MagicMock,
|
||||
mock_omada_client: MagicMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test reconnect client service raises the right kind of exception on service failure."""
|
||||
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
|
||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
mac = "AA:BB:CC:DD:EE:FF"
|
||||
mock_omada_site_client.reconnect_client.side_effect = OmadaClientException
|
||||
with pytest.raises(HomeAssistantError):
|
||||
await hass.services.async_call(
|
||||
DOMAIN,
|
||||
"reconnect_client",
|
||||
{"mac": mac},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
mock_omada_site_client.reconnect_client.assert_awaited_once_with(mac)
|
||||
|
||||
Reference in New Issue
Block a user