mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 11:17:21 +00:00
Allow configuring WebRTC stun and turn servers (#128984)
* Allow configuring WebRTC stun and turn servers * Add tests * Remove class WebRTCCoreConfiguration
This commit is contained in:
parent
4e8f878d83
commit
487593af38
@ -63,6 +63,7 @@ from homeassistant.helpers.network import get_url
|
|||||||
from homeassistant.helpers.template import Template
|
from homeassistant.helpers.template import Template
|
||||||
from homeassistant.helpers.typing import ConfigType, VolDictType
|
from homeassistant.helpers.typing import ConfigType, VolDictType
|
||||||
from homeassistant.loader import bind_hass
|
from homeassistant.loader import bind_hass
|
||||||
|
from homeassistant.util.webrtc import RTCIceServer, WebRTCClientConfiguration
|
||||||
|
|
||||||
from .const import ( # noqa: F401
|
from .const import ( # noqa: F401
|
||||||
_DEPRECATED_STREAM_TYPE_HLS,
|
_DEPRECATED_STREAM_TYPE_HLS,
|
||||||
@ -86,8 +87,6 @@ from .prefs import CameraPreferences, DynamicStreamSettings # noqa: F401
|
|||||||
from .webrtc import (
|
from .webrtc import (
|
||||||
DATA_ICE_SERVERS,
|
DATA_ICE_SERVERS,
|
||||||
CameraWebRTCProvider,
|
CameraWebRTCProvider,
|
||||||
RTCIceServer,
|
|
||||||
WebRTCClientConfiguration,
|
|
||||||
async_get_supported_providers,
|
async_get_supported_providers,
|
||||||
async_register_ice_servers,
|
async_register_ice_servers,
|
||||||
async_register_rtsp_to_web_rtc_provider, # noqa: F401
|
async_register_rtsp_to_web_rtc_provider, # noqa: F401
|
||||||
@ -403,6 +402,8 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
|||||||
|
|
||||||
@callback
|
@callback
|
||||||
def get_ice_servers() -> list[RTCIceServer]:
|
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:80")]
|
return [RTCIceServer(urls="stun:stun.home-assistant.io:80")]
|
||||||
|
|
||||||
async_register_ice_servers(hass, get_ice_servers)
|
async_register_ice_servers(hass, get_ice_servers)
|
||||||
|
@ -4,7 +4,6 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
from collections.abc import Awaitable, Callable, Iterable
|
from collections.abc import Awaitable, Callable, Iterable
|
||||||
from dataclasses import dataclass, field
|
|
||||||
from typing import TYPE_CHECKING, Any, Protocol
|
from typing import TYPE_CHECKING, Any, Protocol
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
@ -13,6 +12,7 @@ from homeassistant.components import websocket_api
|
|||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.helpers import config_validation as cv
|
from homeassistant.helpers import config_validation as cv
|
||||||
from homeassistant.util.hass_dict import HassKey
|
from homeassistant.util.hass_dict import HassKey
|
||||||
|
from homeassistant.util.webrtc import RTCIceServer
|
||||||
|
|
||||||
from .const import DATA_COMPONENT, DOMAIN, StreamType
|
from .const import DATA_COMPONENT, DOMAIN, StreamType
|
||||||
from .helper import get_camera_from_entity_id
|
from .helper import get_camera_from_entity_id
|
||||||
@ -29,69 +29,6 @@ DATA_ICE_SERVERS: HassKey[list[Callable[[], Iterable[RTCIceServer]]]] = HassKey(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class RTCIceServer:
|
|
||||||
"""RTC Ice Server.
|
|
||||||
|
|
||||||
See https://www.w3.org/TR/webrtc/#rtciceserver-dictionary
|
|
||||||
"""
|
|
||||||
|
|
||||||
urls: list[str] | str
|
|
||||||
username: str | None = None
|
|
||||||
credential: str | None = None
|
|
||||||
|
|
||||||
def to_frontend_dict(self) -> dict[str, Any]:
|
|
||||||
"""Return a dict that can be used by the frontend."""
|
|
||||||
|
|
||||||
data = {
|
|
||||||
"urls": self.urls,
|
|
||||||
}
|
|
||||||
if self.username is not None:
|
|
||||||
data["username"] = self.username
|
|
||||||
if self.credential is not None:
|
|
||||||
data["credential"] = self.credential
|
|
||||||
return data
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class RTCConfiguration:
|
|
||||||
"""RTC Configuration.
|
|
||||||
|
|
||||||
See https://www.w3.org/TR/webrtc/#rtcconfiguration-dictionary
|
|
||||||
"""
|
|
||||||
|
|
||||||
ice_servers: list[RTCIceServer] = field(default_factory=list)
|
|
||||||
|
|
||||||
def to_frontend_dict(self) -> dict[str, Any]:
|
|
||||||
"""Return a dict that can be used by the frontend."""
|
|
||||||
if not self.ice_servers:
|
|
||||||
return {}
|
|
||||||
|
|
||||||
return {
|
|
||||||
"iceServers": [server.to_frontend_dict() for server in self.ice_servers]
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(kw_only=True)
|
|
||||||
class WebRTCClientConfiguration:
|
|
||||||
"""WebRTC configuration for the client.
|
|
||||||
|
|
||||||
Not part of the spec, but required to configure client.
|
|
||||||
"""
|
|
||||||
|
|
||||||
configuration: RTCConfiguration = field(default_factory=RTCConfiguration)
|
|
||||||
data_channel: str | None = None
|
|
||||||
|
|
||||||
def to_frontend_dict(self) -> dict[str, Any]:
|
|
||||||
"""Return a dict that can be used by the frontend."""
|
|
||||||
data: dict[str, Any] = {
|
|
||||||
"configuration": self.configuration.to_frontend_dict(),
|
|
||||||
}
|
|
||||||
if self.data_channel is not None:
|
|
||||||
data["dataChannel"] = self.data_channel
|
|
||||||
return data
|
|
||||||
|
|
||||||
|
|
||||||
class CameraWebRTCProvider(Protocol):
|
class CameraWebRTCProvider(Protocol):
|
||||||
"""WebRTC provider."""
|
"""WebRTC provider."""
|
||||||
|
|
||||||
|
@ -21,7 +21,6 @@ from google_nest_sdm.device_manager import DeviceManager
|
|||||||
from google_nest_sdm.exceptions import ApiException
|
from google_nest_sdm.exceptions import ApiException
|
||||||
|
|
||||||
from homeassistant.components.camera import Camera, CameraEntityFeature, StreamType
|
from homeassistant.components.camera import Camera, CameraEntityFeature, StreamType
|
||||||
from homeassistant.components.camera.webrtc import WebRTCClientConfiguration
|
|
||||||
from homeassistant.components.stream import CONF_EXTRA_PART_WAIT_TIME
|
from homeassistant.components.stream import CONF_EXTRA_PART_WAIT_TIME
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
@ -29,6 +28,7 @@ from homeassistant.exceptions import HomeAssistantError
|
|||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
from homeassistant.helpers.event import async_track_point_in_utc_time
|
from homeassistant.helpers.event import async_track_point_in_utc_time
|
||||||
from homeassistant.util.dt import utcnow
|
from homeassistant.util.dt import utcnow
|
||||||
|
from homeassistant.util.webrtc import WebRTCClientConfiguration
|
||||||
|
|
||||||
from .const import DATA_DEVICE_MANAGER, DOMAIN
|
from .const import DATA_DEVICE_MANAGER, DOMAIN
|
||||||
from .device_info import NestDeviceInfo
|
from .device_info import NestDeviceInfo
|
||||||
|
@ -16,7 +16,7 @@ from pathlib import Path
|
|||||||
import re
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
from types import ModuleType
|
from types import ModuleType
|
||||||
from typing import TYPE_CHECKING, Any
|
from typing import TYPE_CHECKING, Any, Final
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
from awesomeversion import AwesomeVersion
|
from awesomeversion import AwesomeVersion
|
||||||
@ -57,6 +57,8 @@ from .const import (
|
|||||||
CONF_TIME_ZONE,
|
CONF_TIME_ZONE,
|
||||||
CONF_TYPE,
|
CONF_TYPE,
|
||||||
CONF_UNIT_SYSTEM,
|
CONF_UNIT_SYSTEM,
|
||||||
|
CONF_URL,
|
||||||
|
CONF_USERNAME,
|
||||||
LEGACY_CONF_WHITELIST_EXTERNAL_DIRS,
|
LEGACY_CONF_WHITELIST_EXTERNAL_DIRS,
|
||||||
__version__,
|
__version__,
|
||||||
)
|
)
|
||||||
@ -73,6 +75,7 @@ from .util.async_ import create_eager_task
|
|||||||
from .util.hass_dict import HassKey
|
from .util.hass_dict import HassKey
|
||||||
from .util.package import is_docker_env
|
from .util.package import is_docker_env
|
||||||
from .util.unit_system import get_unit_system, validate_unit_system
|
from .util.unit_system import get_unit_system, validate_unit_system
|
||||||
|
from .util.webrtc import RTCIceServer
|
||||||
from .util.yaml import SECRET_YAML, Secrets, YamlTypeError, load_yaml_dict
|
from .util.yaml import SECRET_YAML, Secrets, YamlTypeError, load_yaml_dict
|
||||||
from .util.yaml.objects import NodeStrClass
|
from .util.yaml.objects import NodeStrClass
|
||||||
|
|
||||||
@ -94,6 +97,10 @@ INTEGRATION_LOAD_EXCEPTIONS = (IntegrationNotFound, RequirementsNotFound)
|
|||||||
|
|
||||||
SAFE_MODE_FILENAME = "safe-mode"
|
SAFE_MODE_FILENAME = "safe-mode"
|
||||||
|
|
||||||
|
CONF_CREDENTIAL: Final = "credential"
|
||||||
|
CONF_ICE_SERVERS: Final = "ice_servers"
|
||||||
|
CONF_WEBRTC: Final = "webrtc"
|
||||||
|
|
||||||
DEFAULT_CONFIG = f"""
|
DEFAULT_CONFIG = f"""
|
||||||
# Loads default set of integrations. Do not remove.
|
# Loads default set of integrations. Do not remove.
|
||||||
default_config:
|
default_config:
|
||||||
@ -301,6 +308,16 @@ def _validate_currency(data: Any) -> Any:
|
|||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
def _validate_stun_or_turn_url(value: Any) -> str:
|
||||||
|
"""Validate an URL."""
|
||||||
|
url_in = str(value)
|
||||||
|
url = urlparse(url_in)
|
||||||
|
|
||||||
|
if url.scheme not in ("stun", "stuns", "turn", "turns"):
|
||||||
|
raise vol.Invalid("invalid url")
|
||||||
|
return url_in
|
||||||
|
|
||||||
|
|
||||||
CORE_CONFIG_SCHEMA = vol.All(
|
CORE_CONFIG_SCHEMA = vol.All(
|
||||||
CUSTOMIZE_CONFIG_SCHEMA.extend(
|
CUSTOMIZE_CONFIG_SCHEMA.extend(
|
||||||
{
|
{
|
||||||
@ -361,6 +378,24 @@ CORE_CONFIG_SCHEMA = vol.All(
|
|||||||
vol.Optional(CONF_COUNTRY): cv.country,
|
vol.Optional(CONF_COUNTRY): cv.country,
|
||||||
vol.Optional(CONF_LANGUAGE): cv.language,
|
vol.Optional(CONF_LANGUAGE): cv.language,
|
||||||
vol.Optional(CONF_DEBUG): cv.boolean,
|
vol.Optional(CONF_DEBUG): cv.boolean,
|
||||||
|
vol.Optional(CONF_WEBRTC): 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,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
),
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
_filter_bad_internal_external_urls,
|
_filter_bad_internal_external_urls,
|
||||||
@ -877,6 +912,16 @@ async def async_process_ha_core_config(hass: HomeAssistant, config: dict) -> Non
|
|||||||
if config.get(CONF_DEBUG):
|
if config.get(CONF_DEBUG):
|
||||||
hac.debug = True
|
hac.debug = True
|
||||||
|
|
||||||
|
if CONF_WEBRTC in config:
|
||||||
|
hac.webrtc.ice_servers = [
|
||||||
|
RTCIceServer(
|
||||||
|
server[CONF_URL],
|
||||||
|
server.get(CONF_USERNAME),
|
||||||
|
server.get(CONF_CREDENTIAL),
|
||||||
|
)
|
||||||
|
for server in config[CONF_WEBRTC][CONF_ICE_SERVERS]
|
||||||
|
]
|
||||||
|
|
||||||
_raise_issue_if_historic_currency(hass, hass.config.currency)
|
_raise_issue_if_historic_currency(hass, hass.config.currency)
|
||||||
_raise_issue_if_no_country(hass, hass.config.country)
|
_raise_issue_if_no_country(hass, hass.config.country)
|
||||||
|
|
||||||
|
@ -119,6 +119,7 @@ from .util.unit_system import (
|
|||||||
UnitSystem,
|
UnitSystem,
|
||||||
get_unit_system,
|
get_unit_system,
|
||||||
)
|
)
|
||||||
|
from .util.webrtc import RTCConfiguration
|
||||||
|
|
||||||
# Typing imports that create a circular dependency
|
# Typing imports that create a circular dependency
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@ -2966,6 +2967,8 @@ class Config:
|
|||||||
# If Home Assistant is running in safe mode
|
# If Home Assistant is running in safe mode
|
||||||
self.safe_mode: bool = False
|
self.safe_mode: bool = False
|
||||||
|
|
||||||
|
self.webrtc = RTCConfiguration()
|
||||||
|
|
||||||
def async_initialize(self) -> None:
|
def async_initialize(self) -> None:
|
||||||
"""Finish initializing a config object.
|
"""Finish initializing a config object.
|
||||||
|
|
||||||
|
69
homeassistant/util/webrtc.py
Normal file
69
homeassistant/util/webrtc.py
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
"""WebRTC container classes."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class RTCIceServer:
|
||||||
|
"""RTC Ice Server.
|
||||||
|
|
||||||
|
See https://www.w3.org/TR/webrtc/#rtciceserver-dictionary
|
||||||
|
"""
|
||||||
|
|
||||||
|
urls: list[str] | str
|
||||||
|
username: str | None = None
|
||||||
|
credential: str | None = None
|
||||||
|
|
||||||
|
def to_frontend_dict(self) -> dict[str, Any]:
|
||||||
|
"""Return a dict that can be used by the frontend."""
|
||||||
|
|
||||||
|
data = {
|
||||||
|
"urls": self.urls,
|
||||||
|
}
|
||||||
|
if self.username is not None:
|
||||||
|
data["username"] = self.username
|
||||||
|
if self.credential is not None:
|
||||||
|
data["credential"] = self.credential
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class RTCConfiguration:
|
||||||
|
"""RTC Configuration.
|
||||||
|
|
||||||
|
See https://www.w3.org/TR/webrtc/#rtcconfiguration-dictionary
|
||||||
|
"""
|
||||||
|
|
||||||
|
ice_servers: list[RTCIceServer] = field(default_factory=list)
|
||||||
|
|
||||||
|
def to_frontend_dict(self) -> dict[str, Any]:
|
||||||
|
"""Return a dict that can be used by the frontend."""
|
||||||
|
if not self.ice_servers:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
return {
|
||||||
|
"iceServers": [server.to_frontend_dict() for server in self.ice_servers]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(kw_only=True)
|
||||||
|
class WebRTCClientConfiguration:
|
||||||
|
"""WebRTC configuration for the client.
|
||||||
|
|
||||||
|
Not part of the spec, but required to configure client.
|
||||||
|
"""
|
||||||
|
|
||||||
|
configuration: RTCConfiguration = field(default_factory=RTCConfiguration)
|
||||||
|
data_channel: str | None = None
|
||||||
|
|
||||||
|
def to_frontend_dict(self) -> dict[str, Any]:
|
||||||
|
"""Return a dict that can be used by the frontend."""
|
||||||
|
data: dict[str, Any] = {
|
||||||
|
"configuration": self.configuration.to_frontend_dict(),
|
||||||
|
}
|
||||||
|
if self.data_channel is not None:
|
||||||
|
data["dataChannel"] = self.data_channel
|
||||||
|
return data
|
@ -13,6 +13,7 @@ from homeassistant.components.camera.webrtc import (
|
|||||||
async_register_webrtc_provider,
|
async_register_webrtc_provider,
|
||||||
)
|
)
|
||||||
from homeassistant.components.websocket_api import TYPE_RESULT
|
from homeassistant.components.websocket_api import TYPE_RESULT
|
||||||
|
from homeassistant.config import async_process_ha_core_config
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.setup import async_setup_component
|
from homeassistant.setup import async_setup_component
|
||||||
|
|
||||||
@ -225,6 +226,32 @@ async def test_ws_get_client_config(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures("mock_camera_web_rtc")
|
||||||
|
async def test_ws_get_client_config_custom_config(
|
||||||
|
hass: HomeAssistant, hass_ws_client: WebSocketGenerator
|
||||||
|
) -> None:
|
||||||
|
"""Test get WebRTC client config."""
|
||||||
|
await async_process_ha_core_config(
|
||||||
|
hass,
|
||||||
|
{"webrtc": {"ice_servers": [{"url": "stun:custom_stun_server:3478"}]}},
|
||||||
|
)
|
||||||
|
|
||||||
|
await async_setup_component(hass, "camera", {})
|
||||||
|
|
||||||
|
client = await hass_ws_client(hass)
|
||||||
|
await client.send_json_auto_id(
|
||||||
|
{"type": "camera/webrtc/get_client_config", "entity_id": "camera.demo_camera"}
|
||||||
|
)
|
||||||
|
msg = await client.receive_json()
|
||||||
|
|
||||||
|
# Assert WebSocket response
|
||||||
|
assert msg["type"] == TYPE_RESULT
|
||||||
|
assert msg["success"]
|
||||||
|
assert msg["result"] == {
|
||||||
|
"configuration": {"iceServers": [{"urls": ["stun:custom_stun_server:3478"]}]}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures("mock_camera_hls")
|
@pytest.mark.usefixtures("mock_camera_hls")
|
||||||
async def test_ws_get_client_config_no_rtc_camera(
|
async def test_ws_get_client_config_no_rtc_camera(
|
||||||
hass: HomeAssistant, hass_ws_client: WebSocketGenerator
|
hass: HomeAssistant, hass_ws_client: WebSocketGenerator
|
||||||
|
@ -48,6 +48,7 @@ from homeassistant.helpers.entity import Entity
|
|||||||
from homeassistant.helpers.typing import ConfigType
|
from homeassistant.helpers.typing import ConfigType
|
||||||
from homeassistant.loader import Integration, async_get_integration
|
from homeassistant.loader import Integration, async_get_integration
|
||||||
from homeassistant.setup import async_setup_component
|
from homeassistant.setup import async_setup_component
|
||||||
|
from homeassistant.util import webrtc as webrtc_util
|
||||||
from homeassistant.util.unit_system import (
|
from homeassistant.util.unit_system import (
|
||||||
METRIC_SYSTEM,
|
METRIC_SYSTEM,
|
||||||
US_CUSTOMARY_SYSTEM,
|
US_CUSTOMARY_SYSTEM,
|
||||||
@ -525,6 +526,8 @@ def test_core_config_schema() -> None:
|
|||||||
{"country": "xx"},
|
{"country": "xx"},
|
||||||
{"language": "xx"},
|
{"language": "xx"},
|
||||||
{"radius": -10},
|
{"radius": -10},
|
||||||
|
{"webrtc": "bla"},
|
||||||
|
{"webrtc": {}},
|
||||||
):
|
):
|
||||||
with pytest.raises(MultipleInvalid):
|
with pytest.raises(MultipleInvalid):
|
||||||
config_util.CORE_CONFIG_SCHEMA(value)
|
config_util.CORE_CONFIG_SCHEMA(value)
|
||||||
@ -542,6 +545,7 @@ def test_core_config_schema() -> None:
|
|||||||
"country": "SE",
|
"country": "SE",
|
||||||
"language": "sv",
|
"language": "sv",
|
||||||
"radius": "10",
|
"radius": "10",
|
||||||
|
"webrtc": {"ice_servers": [{"url": "stun:custom_stun_server:3478"}]},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -574,6 +578,97 @@ def test_customize_dict_schema() -> None:
|
|||||||
) == {ATTR_FRIENDLY_NAME: "2", ATTR_ASSUMED_STATE: False}
|
) == {ATTR_FRIENDLY_NAME: "2", ATTR_ASSUMED_STATE: False}
|
||||||
|
|
||||||
|
|
||||||
|
def test_webrtc_schema() -> None:
|
||||||
|
"""Test webrtc config validation."""
|
||||||
|
invalid_webrtc_configs = (
|
||||||
|
"bla",
|
||||||
|
{},
|
||||||
|
{"ice_servers": [], "unknown_key": 123},
|
||||||
|
{"ice_servers": [{}]},
|
||||||
|
{"ice_servers": [{"invalid_key": 123}]},
|
||||||
|
)
|
||||||
|
|
||||||
|
valid_webrtc_configs = (
|
||||||
|
(
|
||||||
|
{"ice_servers": []},
|
||||||
|
{"ice_servers": []},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
{"ice_servers": {"url": "stun:custom_stun_server:3478"}},
|
||||||
|
{"ice_servers": [{"url": ["stun:custom_stun_server:3478"]}]},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
{"ice_servers": [{"url": "stun:custom_stun_server:3478"}]},
|
||||||
|
{"ice_servers": [{"url": ["stun:custom_stun_server:3478"]}]},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
{"ice_servers": [{"url": ["stun:custom_stun_server:3478"]}]},
|
||||||
|
{"ice_servers": [{"url": ["stun:custom_stun_server:3478"]}]},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
{
|
||||||
|
"ice_servers": [
|
||||||
|
{
|
||||||
|
"url": ["stun:custom_stun_server:3478"],
|
||||||
|
"username": "bla",
|
||||||
|
"credential": "hunter2",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ice_servers": [
|
||||||
|
{
|
||||||
|
"url": ["stun:custom_stun_server:3478"],
|
||||||
|
"username": "bla",
|
||||||
|
"credential": "hunter2",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
for config in invalid_webrtc_configs:
|
||||||
|
with pytest.raises(MultipleInvalid):
|
||||||
|
config_util.CORE_CONFIG_SCHEMA({"webrtc": config})
|
||||||
|
|
||||||
|
for config, validated_webrtc in valid_webrtc_configs:
|
||||||
|
validated = config_util.CORE_CONFIG_SCHEMA({"webrtc": config})
|
||||||
|
assert validated["webrtc"] == validated_webrtc
|
||||||
|
|
||||||
|
|
||||||
|
def test_validate_stun_or_turn_url() -> None:
|
||||||
|
"""Test _validate_stun_or_turn_url."""
|
||||||
|
invalid_urls = (
|
||||||
|
"custom_stun_server",
|
||||||
|
"custom_stun_server:3478",
|
||||||
|
"bum:custom_stun_server:3478" "http://blah.com:80",
|
||||||
|
)
|
||||||
|
|
||||||
|
valid_urls = (
|
||||||
|
"stun:custom_stun_server:3478",
|
||||||
|
"turn:custom_stun_server:3478",
|
||||||
|
"stuns:custom_stun_server:3478",
|
||||||
|
"turns:custom_stun_server:3478",
|
||||||
|
# The validator does not reject urls with path
|
||||||
|
"stun:custom_stun_server:3478/path",
|
||||||
|
"turn:custom_stun_server:3478/path",
|
||||||
|
"stuns:custom_stun_server:3478/path",
|
||||||
|
"turns:custom_stun_server:3478/path",
|
||||||
|
# The validator allows any query
|
||||||
|
"stun:custom_stun_server:3478?query",
|
||||||
|
"turn:custom_stun_server:3478?query",
|
||||||
|
"stuns:custom_stun_server:3478?query",
|
||||||
|
"turns:custom_stun_server:3478?query",
|
||||||
|
)
|
||||||
|
|
||||||
|
for url in invalid_urls:
|
||||||
|
with pytest.raises(Invalid):
|
||||||
|
config_util._validate_stun_or_turn_url(url)
|
||||||
|
|
||||||
|
for url in valid_urls:
|
||||||
|
assert config_util._validate_stun_or_turn_url(url) == url
|
||||||
|
|
||||||
|
|
||||||
def test_customize_glob_is_ordered() -> None:
|
def test_customize_glob_is_ordered() -> None:
|
||||||
"""Test that customize_glob preserves order."""
|
"""Test that customize_glob preserves order."""
|
||||||
conf = config_util.CORE_CONFIG_SCHEMA({"customize_glob": OrderedDict()})
|
conf = config_util.CORE_CONFIG_SCHEMA({"customize_glob": OrderedDict()})
|
||||||
@ -870,6 +965,7 @@ async def test_loading_configuration(hass: HomeAssistant) -> None:
|
|||||||
"country": "SE",
|
"country": "SE",
|
||||||
"language": "sv",
|
"language": "sv",
|
||||||
"radius": 150,
|
"radius": 150,
|
||||||
|
"webrtc": {"ice_servers": [{"url": "stun:custom_stun_server:3478"}]},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -891,6 +987,9 @@ async def test_loading_configuration(hass: HomeAssistant) -> None:
|
|||||||
assert hass.config.country == "SE"
|
assert hass.config.country == "SE"
|
||||||
assert hass.config.language == "sv"
|
assert hass.config.language == "sv"
|
||||||
assert hass.config.radius == 150
|
assert hass.config.radius == 150
|
||||||
|
assert hass.config.webrtc == webrtc_util.RTCConfiguration(
|
||||||
|
[webrtc_util.RTCIceServer(urls=["stun:custom_stun_server:3478"])]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user