Add WS command for discovering Thread routers (#88021)

* Add WS command for discovering Thread routers

* Fix type annotations

* Mock zeroconf in tests

* Key discovery by external MAC address

* Add tests

* Include hostname in data, allow missing fields

* Fix typo

* Include server instead of hostname
This commit is contained in:
Erik Montnemery 2023-02-15 15:15:29 +01:00 committed by GitHub
parent c7fc90f8a0
commit 8613d60c5e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 793 additions and 3 deletions

View file

@ -1,11 +1,21 @@
"""Test the thread websocket API."""
from homeassistant.components.thread import dataset_store
from unittest.mock import ANY, AsyncMock
from zeroconf.asyncio import AsyncServiceInfo
from homeassistant.components.thread import dataset_store, discovery
from homeassistant.components.thread.const import DOMAIN
from homeassistant.core import HomeAssistant
from homeassistant.setup import async_setup_component
from . import DATASET_1, DATASET_2, DATASET_3
from . import (
DATASET_1,
DATASET_2,
DATASET_3,
ROUTER_DISCOVERY_GOOGLE_1,
ROUTER_DISCOVERY_HASS,
)
async def test_add_dataset(hass: HomeAssistant, hass_ws_client) -> None:
@ -121,3 +131,98 @@ async def test_list_get_dataset(hass: HomeAssistant, hass_ws_client) -> None:
msg = await client.receive_json()
assert not msg["success"]
assert msg["error"] == {"code": "not_found", "message": "unknown dataset"}
async def test_discover_routers(
hass: HomeAssistant, hass_ws_client, mock_async_zeroconf
) -> None:
"""Test discovering thread routers."""
mock_async_zeroconf.async_add_service_listener = AsyncMock()
mock_async_zeroconf.async_remove_service_listener = AsyncMock()
mock_async_zeroconf.async_get_service_info = AsyncMock()
assert await async_setup_component(hass, DOMAIN, {})
await hass.async_block_till_done()
client = await hass_ws_client(hass)
# Subscribe
await client.send_json({"id": 1, "type": "thread/discover_routers"})
msg = await client.receive_json()
assert msg["success"]
assert msg["result"] is None
mock_async_zeroconf.async_add_service_listener.assert_called_once_with(
"_meshcop._udp.local.", ANY
)
listener: discovery.ThreadRouterDiscovery.ThreadServiceListener = (
mock_async_zeroconf.async_add_service_listener.mock_calls[0][1][1]
)
# Discover a service
mock_async_zeroconf.async_get_service_info.return_value = AsyncServiceInfo(
**ROUTER_DISCOVERY_HASS
)
listener.add_service(
None, ROUTER_DISCOVERY_HASS["type_"], ROUTER_DISCOVERY_HASS["name"]
)
msg = await client.receive_json()
assert msg == {
"event": {
"data": {
"brand": "homeassistant",
"extended_pan_id": "e60fc7c186212ce5",
"model_name": "OpenThreadBorderRouter",
"network_name": "OpenThread HC",
"server": "core-silabs-multiprotocol.local.",
"vendor_name": "HomeAssistant",
},
"key": "aeeb2f594b570bbf",
"type": "router_discovered",
},
"id": 1,
"type": "event",
}
# Discover another service - we don't care if zeroconf considers this an update
mock_async_zeroconf.async_get_service_info.return_value = AsyncServiceInfo(
**ROUTER_DISCOVERY_GOOGLE_1
)
listener.update_service(
None, ROUTER_DISCOVERY_GOOGLE_1["type_"], ROUTER_DISCOVERY_GOOGLE_1["name"]
)
msg = await client.receive_json()
assert msg == {
"event": {
"data": {
"brand": "google",
"extended_pan_id": "9e75e256f61409a3",
"model_name": "Google Nest Hub",
"network_name": "NEST-PAN-E1AF",
"server": "2d99f293-cd8e-2770-8dd2-6675de9fa000.local.",
"vendor_name": "Google Inc.",
},
"key": "f6a99b425a67abed",
"type": "router_discovered",
},
"id": 1,
"type": "event",
}
# Remove a service
listener.remove_service(
None, ROUTER_DISCOVERY_HASS["type_"], ROUTER_DISCOVERY_HASS["name"]
)
msg = await client.receive_json()
assert msg == {
"event": {"key": "aeeb2f594b570bbf", "type": "router_removed"},
"id": 1,
"type": "event",
}
# Unsubscribe
await client.send_json({"id": 2, "type": "unsubscribe_events", "subscription": 1})
response = await client.receive_json()
assert response["success"]
mock_async_zeroconf.async_remove_service_listener.assert_called_once_with(listener)