mirror of
https://github.com/home-assistant/core.git
synced 2026-02-15 07:36:16 +00:00
Extract WebRTC integration (#157648)
This commit is contained in:
2
CODEOWNERS
generated
2
CODEOWNERS
generated
@@ -1805,6 +1805,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/weatherflow_cloud/ @jeeftor
|
||||
/homeassistant/components/weatherkit/ @tjhorner
|
||||
/tests/components/weatherkit/ @tjhorner
|
||||
/homeassistant/components/web_rtc/ @home-assistant/core
|
||||
/tests/components/web_rtc/ @home-assistant/core
|
||||
/homeassistant/components/webdav/ @jpbede
|
||||
/tests/components/webdav/ @jpbede
|
||||
/homeassistant/components/webhook/ @home-assistant/core
|
||||
|
||||
@@ -20,7 +20,7 @@ from aiohttp import hdrs, web
|
||||
import attr
|
||||
from propcache.api import cached_property, under_cached_property
|
||||
import voluptuous as vol
|
||||
from webrtc_models import RTCIceCandidateInit, RTCIceServer
|
||||
from webrtc_models import RTCIceCandidateInit
|
||||
|
||||
from homeassistant.components import websocket_api
|
||||
from homeassistant.components.http import KEY_AUTHENTICATED, HomeAssistantView
|
||||
@@ -37,6 +37,7 @@ from homeassistant.components.stream import (
|
||||
Stream,
|
||||
create_stream,
|
||||
)
|
||||
from homeassistant.components.web_rtc import async_get_ice_servers
|
||||
from homeassistant.components.websocket_api import ActiveConnection
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
@@ -84,7 +85,6 @@ from .prefs import (
|
||||
get_dynamic_camera_stream_settings,
|
||||
)
|
||||
from .webrtc import (
|
||||
DATA_ICE_SERVERS,
|
||||
CameraWebRTCProvider,
|
||||
WebRTCAnswer, # noqa: F401
|
||||
WebRTCCandidate, # noqa: F401
|
||||
@@ -93,7 +93,6 @@ from .webrtc import (
|
||||
WebRTCMessage, # noqa: F401
|
||||
WebRTCSendMessage,
|
||||
async_get_supported_provider,
|
||||
async_register_ice_servers,
|
||||
async_register_webrtc_provider, # noqa: F401
|
||||
async_register_ws,
|
||||
)
|
||||
@@ -400,20 +399,6 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
SERVICE_RECORD, CAMERA_SERVICE_RECORD, async_handle_record_service
|
||||
)
|
||||
|
||||
@callback
|
||||
def get_ice_servers() -> list[RTCIceServer]:
|
||||
if hass.config.webrtc.ice_servers:
|
||||
return hass.config.webrtc.ice_servers
|
||||
return [
|
||||
RTCIceServer(
|
||||
urls=[
|
||||
"stun:stun.home-assistant.io:3478",
|
||||
"stun:stun.home-assistant.io:80",
|
||||
]
|
||||
),
|
||||
]
|
||||
|
||||
async_register_ice_servers(hass, get_ice_servers)
|
||||
return True
|
||||
|
||||
|
||||
@@ -731,11 +716,7 @@ class Camera(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||
"""Return the WebRTC client configuration and extend it with the registered ice servers."""
|
||||
config = self._async_get_webrtc_client_configuration()
|
||||
|
||||
ice_servers = [
|
||||
server
|
||||
for servers in self.hass.data.get(DATA_ICE_SERVERS, [])
|
||||
for server in servers()
|
||||
]
|
||||
ice_servers = async_get_ice_servers(self.hass)
|
||||
config.configuration.ice_servers.extend(ice_servers)
|
||||
|
||||
return config
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"name": "Camera",
|
||||
"after_dependencies": ["media_player"],
|
||||
"codeowners": ["@home-assistant/core"],
|
||||
"dependencies": ["http"],
|
||||
"dependencies": ["http", "web_rtc"],
|
||||
"documentation": "https://www.home-assistant.io/integrations/camera",
|
||||
"integration_type": "entity",
|
||||
"quality_scale": "internal",
|
||||
|
||||
@@ -4,7 +4,7 @@ from __future__ import annotations
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
import asyncio
|
||||
from collections.abc import Awaitable, Callable, Iterable
|
||||
from collections.abc import Awaitable, Callable
|
||||
from dataclasses import asdict, dataclass, field
|
||||
from functools import cache, partial, wraps
|
||||
import logging
|
||||
@@ -12,12 +12,7 @@ from typing import TYPE_CHECKING, Any
|
||||
|
||||
from mashumaro import MissingField
|
||||
import voluptuous as vol
|
||||
from webrtc_models import (
|
||||
RTCConfiguration,
|
||||
RTCIceCandidate,
|
||||
RTCIceCandidateInit,
|
||||
RTCIceServer,
|
||||
)
|
||||
from webrtc_models import RTCConfiguration, RTCIceCandidate, RTCIceCandidateInit
|
||||
|
||||
from homeassistant.components import websocket_api
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
@@ -38,9 +33,6 @@ _LOGGER = logging.getLogger(__name__)
|
||||
DATA_WEBRTC_PROVIDERS: HassKey[set[CameraWebRTCProvider]] = HassKey(
|
||||
"camera_webrtc_providers"
|
||||
)
|
||||
DATA_ICE_SERVERS: HassKey[list[Callable[[], Iterable[RTCIceServer]]]] = HassKey(
|
||||
"camera_webrtc_ice_servers"
|
||||
)
|
||||
|
||||
|
||||
_WEBRTC = "WebRTC"
|
||||
@@ -367,21 +359,3 @@ async def async_get_supported_provider(
|
||||
return provider
|
||||
|
||||
return None
|
||||
|
||||
|
||||
@callback
|
||||
def async_register_ice_servers(
|
||||
hass: HomeAssistant,
|
||||
get_ice_server_fn: Callable[[], Iterable[RTCIceServer]],
|
||||
) -> Callable[[], None]:
|
||||
"""Register a ICE server.
|
||||
|
||||
The registering integration is responsible to implement caching if needed.
|
||||
"""
|
||||
servers = hass.data.setdefault(DATA_ICE_SERVERS, [])
|
||||
|
||||
def remove() -> None:
|
||||
servers.remove(get_ice_server_fn)
|
||||
|
||||
servers.append(get_ice_server_fn)
|
||||
return remove
|
||||
|
||||
@@ -19,8 +19,8 @@ from homeassistant.components.alexa import (
|
||||
errors as alexa_errors,
|
||||
smart_home as alexa_smart_home,
|
||||
)
|
||||
from homeassistant.components.camera import async_register_ice_servers
|
||||
from homeassistant.components.google_assistant import smart_home as ga
|
||||
from homeassistant.components.web_rtc import async_register_ice_servers
|
||||
from homeassistant.const import __version__ as HA_VERSION
|
||||
from homeassistant.core import Context, HassJob, HomeAssistant, callback
|
||||
from homeassistant.helpers.aiohttp_client import SERVER_SOFTWARE
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"google_assistant"
|
||||
],
|
||||
"codeowners": ["@home-assistant/cloud"],
|
||||
"dependencies": ["auth", "http", "repairs", "webhook"],
|
||||
"dependencies": ["auth", "http", "repairs", "webhook", "web_rtc"],
|
||||
"documentation": "https://www.home-assistant.io/integrations/cloud",
|
||||
"integration_type": "system",
|
||||
"iot_class": "cloud_push",
|
||||
|
||||
138
homeassistant/components/web_rtc/__init__.py
Normal file
138
homeassistant/components/web_rtc/__init__.py
Normal file
@@ -0,0 +1,138 @@
|
||||
"""The WebRTC integration."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable, Iterable
|
||||
from typing import Any
|
||||
|
||||
import voluptuous as vol
|
||||
from webrtc_models import RTCIceServer
|
||||
|
||||
from homeassistant.components import websocket_api
|
||||
from homeassistant.const import CONF_URL, CONF_USERNAME
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.core_config import (
|
||||
CONF_CREDENTIAL,
|
||||
CONF_ICE_SERVERS,
|
||||
validate_stun_or_turn_url,
|
||||
)
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
from homeassistant.util.hass_dict import HassKey
|
||||
|
||||
__all__ = [
|
||||
"async_get_ice_servers",
|
||||
"async_register_ice_servers",
|
||||
]
|
||||
|
||||
DOMAIN = "web_rtc"
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema(
|
||||
{
|
||||
DOMAIN: vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_ICE_SERVERS): vol.All(
|
||||
cv.ensure_list,
|
||||
[
|
||||
vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_URL): vol.All(
|
||||
cv.ensure_list, [validate_stun_or_turn_url]
|
||||
),
|
||||
vol.Optional(CONF_USERNAME): cv.string,
|
||||
vol.Optional(CONF_CREDENTIAL): cv.string,
|
||||
}
|
||||
)
|
||||
],
|
||||
)
|
||||
}
|
||||
)
|
||||
},
|
||||
extra=vol.ALLOW_EXTRA,
|
||||
)
|
||||
|
||||
DATA_ICE_SERVERS_USER: HassKey[Iterable[RTCIceServer]] = HassKey(
|
||||
"web_rtc_ice_servers_user"
|
||||
)
|
||||
DATA_ICE_SERVERS: HassKey[list[Callable[[], Iterable[RTCIceServer]]]] = HassKey(
|
||||
"web_rtc_ice_servers"
|
||||
)
|
||||
|
||||
|
||||
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
"""Set up the WebRTC integration."""
|
||||
servers = [
|
||||
RTCIceServer(
|
||||
server[CONF_URL],
|
||||
server.get(CONF_USERNAME),
|
||||
server.get(CONF_CREDENTIAL),
|
||||
)
|
||||
for server in config.get(DOMAIN, {}).get(CONF_ICE_SERVERS, [])
|
||||
]
|
||||
if servers:
|
||||
hass.data[DATA_ICE_SERVERS_USER] = servers
|
||||
|
||||
hass.data[DATA_ICE_SERVERS] = []
|
||||
websocket_api.async_register_command(hass, ws_ice_servers)
|
||||
return True
|
||||
|
||||
|
||||
@callback
|
||||
def async_register_ice_servers(
|
||||
hass: HomeAssistant,
|
||||
get_ice_server_fn: Callable[[], Iterable[RTCIceServer]],
|
||||
) -> Callable[[], None]:
|
||||
"""Register an ICE server.
|
||||
|
||||
The registering integration is responsible to implement caching if needed.
|
||||
"""
|
||||
servers = hass.data[DATA_ICE_SERVERS]
|
||||
|
||||
def remove() -> None:
|
||||
servers.remove(get_ice_server_fn)
|
||||
|
||||
servers.append(get_ice_server_fn)
|
||||
return remove
|
||||
|
||||
|
||||
@callback
|
||||
def async_get_ice_servers(hass: HomeAssistant) -> list[RTCIceServer]:
|
||||
"""Return all registered ICE servers."""
|
||||
servers: list[RTCIceServer] = []
|
||||
|
||||
if hass.config.webrtc.ice_servers:
|
||||
servers.extend(hass.config.webrtc.ice_servers)
|
||||
|
||||
if DATA_ICE_SERVERS_USER in hass.data:
|
||||
servers.extend(hass.data[DATA_ICE_SERVERS_USER])
|
||||
|
||||
if not servers:
|
||||
servers = [
|
||||
RTCIceServer(
|
||||
urls=[
|
||||
"stun:stun.home-assistant.io:3478",
|
||||
"stun:stun.home-assistant.io:80",
|
||||
]
|
||||
),
|
||||
]
|
||||
|
||||
for gen_servers in hass.data[DATA_ICE_SERVERS]:
|
||||
servers.extend(gen_servers())
|
||||
|
||||
return servers
|
||||
|
||||
|
||||
@websocket_api.websocket_command(
|
||||
{
|
||||
"type": "web_rtc/ice_servers",
|
||||
}
|
||||
)
|
||||
@callback
|
||||
def ws_ice_servers(
|
||||
hass: HomeAssistant,
|
||||
connection: websocket_api.ActiveConnection,
|
||||
msg: dict[str, Any],
|
||||
) -> None:
|
||||
"""Handle get WebRTC ICE servers websocket command."""
|
||||
ice_servers = [server.to_dict() for server in async_get_ice_servers(hass)]
|
||||
connection.send_result(msg["id"], ice_servers)
|
||||
8
homeassistant/components/web_rtc/manifest.json
Normal file
8
homeassistant/components/web_rtc/manifest.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"domain": "web_rtc",
|
||||
"name": "WebRTC",
|
||||
"codeowners": ["@home-assistant/core"],
|
||||
"documentation": "https://www.home-assistant.io/integrations/web_rtc",
|
||||
"integration_type": "system",
|
||||
"quality_scale": "internal"
|
||||
}
|
||||
@@ -249,7 +249,7 @@ def _validate_currency(data: Any) -> Any:
|
||||
raise
|
||||
|
||||
|
||||
def _validate_stun_or_turn_url(value: Any) -> str:
|
||||
def validate_stun_or_turn_url(value: Any) -> str:
|
||||
"""Validate an URL."""
|
||||
url_in = str(value)
|
||||
url = urlparse(url_in)
|
||||
@@ -331,7 +331,7 @@ CORE_CONFIG_SCHEMA = vol.All(
|
||||
vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_URL): vol.All(
|
||||
cv.ensure_list, [_validate_stun_or_turn_url]
|
||||
cv.ensure_list, [validate_stun_or_turn_url]
|
||||
),
|
||||
vol.Optional(CONF_USERNAME): cv.string,
|
||||
vol.Optional(CONF_CREDENTIAL): cv.string,
|
||||
|
||||
@@ -115,6 +115,7 @@ NO_IOT_CLASS = [
|
||||
"tag",
|
||||
"timer",
|
||||
"trace",
|
||||
"web_rtc",
|
||||
"webhook",
|
||||
"websocket_api",
|
||||
"zone",
|
||||
|
||||
@@ -2196,6 +2196,7 @@ NO_QUALITY_SCALE = [
|
||||
"timer",
|
||||
"trace",
|
||||
"usage_prediction",
|
||||
"web_rtc",
|
||||
"webhook",
|
||||
"websocket_api",
|
||||
"zone",
|
||||
|
||||
@@ -8,7 +8,6 @@ import pytest
|
||||
from webrtc_models import RTCIceCandidate, RTCIceCandidateInit, RTCIceServer
|
||||
|
||||
from homeassistant.components.camera import (
|
||||
DATA_ICE_SERVERS,
|
||||
Camera,
|
||||
CameraWebRTCProvider,
|
||||
StreamType,
|
||||
@@ -17,10 +16,10 @@ from homeassistant.components.camera import (
|
||||
WebRTCError,
|
||||
WebRTCMessage,
|
||||
WebRTCSendMessage,
|
||||
async_register_ice_servers,
|
||||
async_register_webrtc_provider,
|
||||
get_camera_from_entity_id,
|
||||
)
|
||||
from homeassistant.components.web_rtc import async_register_ice_servers
|
||||
from homeassistant.components.websocket_api import TYPE_RESULT
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.core_config import async_process_ha_core_config
|
||||
@@ -101,89 +100,6 @@ async def test_async_register_webrtc_provider_camera_not_loaded(
|
||||
async_register_webrtc_provider(hass, SomeTestProvider())
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("mock_test_webrtc_cameras")
|
||||
async def test_async_register_ice_server(
|
||||
hass: HomeAssistant,
|
||||
) -> None:
|
||||
"""Test registering an ICE server."""
|
||||
# Clear any existing ICE servers
|
||||
hass.data[DATA_ICE_SERVERS].clear()
|
||||
|
||||
called = 0
|
||||
|
||||
@callback
|
||||
def get_ice_servers() -> list[RTCIceServer]:
|
||||
nonlocal called
|
||||
called += 1
|
||||
return [
|
||||
RTCIceServer(urls="stun:example.com"),
|
||||
RTCIceServer(urls="turn:example.com"),
|
||||
]
|
||||
|
||||
unregister = async_register_ice_servers(hass, get_ice_servers)
|
||||
assert not called
|
||||
|
||||
camera = get_camera_from_entity_id(hass, "camera.async")
|
||||
config = camera.async_get_webrtc_client_configuration()
|
||||
|
||||
assert config.configuration.ice_servers == [
|
||||
RTCIceServer(urls="stun:example.com"),
|
||||
RTCIceServer(urls="turn:example.com"),
|
||||
]
|
||||
assert called == 1
|
||||
|
||||
# register another ICE server
|
||||
called_2 = 0
|
||||
|
||||
@callback
|
||||
def get_ice_servers_2() -> list[RTCIceServer]:
|
||||
nonlocal called_2
|
||||
called_2 += 1
|
||||
return [
|
||||
RTCIceServer(
|
||||
urls=["stun:example2.com", "turn:example2.com"],
|
||||
username="user",
|
||||
credential="pass",
|
||||
)
|
||||
]
|
||||
|
||||
unregister_2 = async_register_ice_servers(hass, get_ice_servers_2)
|
||||
|
||||
config = camera.async_get_webrtc_client_configuration()
|
||||
assert config.configuration.ice_servers == [
|
||||
RTCIceServer(urls="stun:example.com"),
|
||||
RTCIceServer(urls="turn:example.com"),
|
||||
RTCIceServer(
|
||||
urls=["stun:example2.com", "turn:example2.com"],
|
||||
username="user",
|
||||
credential="pass",
|
||||
),
|
||||
]
|
||||
assert called == 2
|
||||
assert called_2 == 1
|
||||
|
||||
# unregister the first ICE server
|
||||
|
||||
unregister()
|
||||
|
||||
config = camera.async_get_webrtc_client_configuration()
|
||||
assert config.configuration.ice_servers == [
|
||||
RTCIceServer(
|
||||
urls=["stun:example2.com", "turn:example2.com"],
|
||||
username="user",
|
||||
credential="pass",
|
||||
),
|
||||
]
|
||||
assert called == 2
|
||||
assert called_2 == 2
|
||||
|
||||
# unregister the second ICE server
|
||||
unregister_2()
|
||||
|
||||
config = camera.async_get_webrtc_client_configuration()
|
||||
assert config.configuration.ice_servers == []
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("mock_test_webrtc_cameras")
|
||||
async def test_ws_get_client_config(
|
||||
hass: HomeAssistant, hass_ws_client: WebSocketGenerator
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
## Active Integrations
|
||||
|
||||
Built-in integrations: 21
|
||||
Built-in integrations: 22
|
||||
Custom integrations: 1
|
||||
|
||||
<details><summary>Built-in integrations</summary>
|
||||
@@ -48,6 +48,7 @@
|
||||
stt | Speech-to-text (STT)
|
||||
system_health | System Health
|
||||
tts | Text-to-speech (TTS)
|
||||
web_rtc | WebRTC
|
||||
webhook | Webhook
|
||||
|
||||
</details>
|
||||
@@ -122,7 +123,7 @@
|
||||
|
||||
## Active Integrations
|
||||
|
||||
Built-in integrations: 21
|
||||
Built-in integrations: 22
|
||||
Custom integrations: 0
|
||||
|
||||
<details><summary>Built-in integrations</summary>
|
||||
@@ -149,6 +150,7 @@
|
||||
stt | Speech-to-text (STT)
|
||||
system_health | System Health
|
||||
tts | Text-to-speech (TTS)
|
||||
web_rtc | WebRTC
|
||||
webhook | Webhook
|
||||
|
||||
</details>
|
||||
|
||||
1
tests/components/web_rtc/__init__.py
Normal file
1
tests/components/web_rtc/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""Tests for the WebRTC integration."""
|
||||
250
tests/components/web_rtc/test_init.py
Normal file
250
tests/components/web_rtc/test_init.py
Normal file
@@ -0,0 +1,250 @@
|
||||
"""Test the WebRTC integration."""
|
||||
|
||||
from webrtc_models import RTCIceServer
|
||||
|
||||
from homeassistant.components.web_rtc import (
|
||||
async_get_ice_servers,
|
||||
async_register_ice_servers,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.core_config import async_process_ha_core_config
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from tests.typing import WebSocketGenerator
|
||||
|
||||
|
||||
async def test_async_setup(hass: HomeAssistant) -> None:
|
||||
"""Test setting up the web_rtc integration."""
|
||||
assert await async_setup_component(hass, "web_rtc", {})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Verify default ICE servers are registered
|
||||
ice_servers = async_get_ice_servers(hass)
|
||||
assert len(ice_servers) == 1
|
||||
assert ice_servers[0].urls == [
|
||||
"stun:stun.home-assistant.io:3478",
|
||||
"stun:stun.home-assistant.io:80",
|
||||
]
|
||||
|
||||
|
||||
async def test_async_setup_custom_ice_servers_core(hass: HomeAssistant) -> None:
|
||||
"""Test setting up web_rtc with custom ICE servers in config."""
|
||||
await async_process_ha_core_config(
|
||||
hass,
|
||||
{"webrtc": {"ice_servers": [{"url": "stun:custom_stun_server:3478"}]}},
|
||||
)
|
||||
|
||||
assert await async_setup_component(hass, "web_rtc", {})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
ice_servers = async_get_ice_servers(hass)
|
||||
assert len(ice_servers) == 1
|
||||
assert ice_servers[0].urls == ["stun:custom_stun_server:3478"]
|
||||
|
||||
|
||||
async def test_async_setup_custom_ice_servers_integration(hass: HomeAssistant) -> None:
|
||||
"""Test setting up web_rtc with custom ICE servers in config."""
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
"web_rtc",
|
||||
{
|
||||
"web_rtc": {
|
||||
"ice_servers": [
|
||||
{"url": "stun:custom_stun_server:3478"},
|
||||
{
|
||||
"url": "stun:custom_stun_server:3478",
|
||||
"credential": "mock-credential",
|
||||
},
|
||||
{
|
||||
"url": "stun:custom_stun_server:3478",
|
||||
"username": "mock-username",
|
||||
},
|
||||
{
|
||||
"url": "stun:custom_stun_server:3478",
|
||||
"credential": "mock-credential",
|
||||
"username": "mock-username",
|
||||
},
|
||||
]
|
||||
}
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
ice_servers = async_get_ice_servers(hass)
|
||||
assert ice_servers == [
|
||||
RTCIceServer(
|
||||
urls=["stun:custom_stun_server:3478"],
|
||||
),
|
||||
RTCIceServer(
|
||||
urls=["stun:custom_stun_server:3478"],
|
||||
credential="mock-credential",
|
||||
),
|
||||
RTCIceServer(
|
||||
urls=["stun:custom_stun_server:3478"],
|
||||
username="mock-username",
|
||||
),
|
||||
RTCIceServer(
|
||||
urls=["stun:custom_stun_server:3478"],
|
||||
username="mock-username",
|
||||
credential="mock-credential",
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
async def test_async_setup_custom_ice_servers_core_and_integration(
|
||||
hass: HomeAssistant,
|
||||
) -> None:
|
||||
"""Test setting up web_rtc with custom ICE servers in config."""
|
||||
await async_process_ha_core_config(
|
||||
hass,
|
||||
{"webrtc": {"ice_servers": [{"url": "stun:custom_stun_server_core:3478"}]}},
|
||||
)
|
||||
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
"web_rtc",
|
||||
{
|
||||
"web_rtc": {
|
||||
"ice_servers": [{"url": "stun:custom_stun_server_integration:3478"}]
|
||||
}
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
ice_servers = async_get_ice_servers(hass)
|
||||
assert ice_servers == [
|
||||
RTCIceServer(
|
||||
urls=["stun:custom_stun_server_core:3478"],
|
||||
),
|
||||
RTCIceServer(
|
||||
urls=["stun:custom_stun_server_integration:3478"],
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
async def test_async_register_ice_servers(hass: HomeAssistant) -> None:
|
||||
"""Test registering ICE servers."""
|
||||
assert await async_setup_component(hass, "web_rtc", {})
|
||||
await hass.async_block_till_done()
|
||||
default_servers = async_get_ice_servers(hass)
|
||||
|
||||
called = 0
|
||||
|
||||
@callback
|
||||
def get_ice_servers() -> list[RTCIceServer]:
|
||||
nonlocal called
|
||||
called += 1
|
||||
return [
|
||||
RTCIceServer(urls="stun:example.com"),
|
||||
RTCIceServer(urls="turn:example.com"),
|
||||
]
|
||||
|
||||
unregister = async_register_ice_servers(hass, get_ice_servers)
|
||||
assert called == 0
|
||||
|
||||
# Getting ice servers should call the callback
|
||||
ice_servers = async_get_ice_servers(hass)
|
||||
assert called == 1
|
||||
assert ice_servers == [
|
||||
*default_servers,
|
||||
RTCIceServer(urls="stun:example.com"),
|
||||
RTCIceServer(urls="turn:example.com"),
|
||||
]
|
||||
|
||||
# Unregister and verify servers are removed
|
||||
unregister()
|
||||
ice_servers = async_get_ice_servers(hass)
|
||||
assert ice_servers == default_servers
|
||||
|
||||
|
||||
async def test_multiple_ice_server_registrations(hass: HomeAssistant) -> None:
|
||||
"""Test registering multiple ICE server providers."""
|
||||
assert await async_setup_component(hass, "web_rtc", {})
|
||||
await hass.async_block_till_done()
|
||||
default_servers = async_get_ice_servers(hass)
|
||||
|
||||
@callback
|
||||
def get_ice_servers_1() -> list[RTCIceServer]:
|
||||
return [RTCIceServer(urls="stun:server1.com")]
|
||||
|
||||
@callback
|
||||
def get_ice_servers_2() -> list[RTCIceServer]:
|
||||
return [
|
||||
RTCIceServer(
|
||||
urls=["stun:server2.com", "turn:server2.com"],
|
||||
username="user",
|
||||
credential="pass",
|
||||
)
|
||||
]
|
||||
|
||||
unregister_1 = async_register_ice_servers(hass, get_ice_servers_1)
|
||||
unregister_2 = async_register_ice_servers(hass, get_ice_servers_2)
|
||||
|
||||
ice_servers = async_get_ice_servers(hass)
|
||||
assert ice_servers == [
|
||||
*default_servers,
|
||||
RTCIceServer(urls="stun:server1.com"),
|
||||
RTCIceServer(
|
||||
urls=["stun:server2.com", "turn:server2.com"],
|
||||
username="user",
|
||||
credential="pass",
|
||||
),
|
||||
]
|
||||
|
||||
# Unregister first provider
|
||||
unregister_1()
|
||||
ice_servers = async_get_ice_servers(hass)
|
||||
assert ice_servers == [
|
||||
*default_servers,
|
||||
RTCIceServer(
|
||||
urls=["stun:server2.com", "turn:server2.com"],
|
||||
username="user",
|
||||
credential="pass",
|
||||
),
|
||||
]
|
||||
|
||||
# Unregister second provider
|
||||
unregister_2()
|
||||
ice_servers = async_get_ice_servers(hass)
|
||||
assert ice_servers == default_servers
|
||||
|
||||
|
||||
async def test_ws_ice_servers_with_registered_servers(
|
||||
hass: HomeAssistant, hass_ws_client: WebSocketGenerator
|
||||
) -> None:
|
||||
"""Test WebSocket ICE servers endpoint with registered servers."""
|
||||
assert await async_setup_component(hass, "web_rtc", {})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
@callback
|
||||
def get_ice_server() -> list[RTCIceServer]:
|
||||
return [
|
||||
RTCIceServer(
|
||||
urls=["stun:example2.com", "turn:example2.com"],
|
||||
username="user",
|
||||
credential="pass",
|
||||
)
|
||||
]
|
||||
|
||||
async_register_ice_servers(hass, get_ice_server)
|
||||
|
||||
client = await hass_ws_client(hass)
|
||||
await client.send_json_auto_id({"type": "web_rtc/ice_servers"})
|
||||
msg = await client.receive_json()
|
||||
|
||||
# Assert WebSocket response includes registered ICE servers
|
||||
assert msg["type"] == "result"
|
||||
assert msg["success"]
|
||||
assert msg["result"] == [
|
||||
{
|
||||
"urls": [
|
||||
"stun:stun.home-assistant.io:3478",
|
||||
"stun:stun.home-assistant.io:80",
|
||||
]
|
||||
},
|
||||
{
|
||||
"urls": ["stun:example2.com", "turn:example2.com"],
|
||||
"username": "user",
|
||||
"credential": "pass",
|
||||
},
|
||||
]
|
||||
@@ -34,8 +34,8 @@ from homeassistant.core_config import (
|
||||
DATA_CUSTOMIZE,
|
||||
Config,
|
||||
ConfigSource,
|
||||
_validate_stun_or_turn_url,
|
||||
async_process_ha_core_config,
|
||||
validate_stun_or_turn_url,
|
||||
)
|
||||
from homeassistant.helpers import issue_registry as ir
|
||||
from homeassistant.helpers.entity import Entity, EntityPlatformState
|
||||
@@ -175,8 +175,8 @@ def test_webrtc_schema() -> None:
|
||||
assert validated["webrtc"] == validated_webrtc
|
||||
|
||||
|
||||
def test_validate_stun_or_turn_url() -> None:
|
||||
"""Test _validate_stun_or_turn_url."""
|
||||
def testvalidate_stun_or_turn_url() -> None:
|
||||
"""Test validate_stun_or_turn_url."""
|
||||
invalid_urls = (
|
||||
"custom_stun_server",
|
||||
"custom_stun_server:3478",
|
||||
@@ -203,10 +203,10 @@ def test_validate_stun_or_turn_url() -> None:
|
||||
|
||||
for url in invalid_urls:
|
||||
with pytest.raises(Invalid):
|
||||
_validate_stun_or_turn_url(url)
|
||||
validate_stun_or_turn_url(url)
|
||||
|
||||
for url in valid_urls:
|
||||
assert _validate_stun_or_turn_url(url) == url
|
||||
assert validate_stun_or_turn_url(url) == url
|
||||
|
||||
|
||||
def test_customize_glob_is_ordered() -> None:
|
||||
|
||||
Reference in New Issue
Block a user