diff --git a/homeassistant/components/camera/__init__.py b/homeassistant/components/camera/__init__.py index 1feb7dffd3b..47d8b9dfbd0 100644 --- a/homeassistant/components/camera/__init__.py +++ b/homeassistant/components/camera/__init__.py @@ -827,16 +827,17 @@ 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() - ] - config.configuration.ice_servers.extend(ice_servers) + if not self._webrtc_sync_offer: + # Until 2024.11, the frontend was not resolving any ice servers + # The async approach was added 2024.11 and new integrations need to use it + ice_servers = [ + server + for servers in self.hass.data.get(DATA_ICE_SERVERS, []) + for server in servers() + ] + config.configuration.ice_servers.extend(ice_servers) - config.get_candidates_upfront = ( - self._webrtc_sync_offer or self._legacy_webrtc_provider is not None - ) + config.get_candidates_upfront = self._legacy_webrtc_provider is not None return config diff --git a/tests/components/camera/conftest.py b/tests/components/camera/conftest.py index bec44704ec2..a88cd898e33 100644 --- a/tests/components/camera/conftest.py +++ b/tests/components/camera/conftest.py @@ -1,13 +1,14 @@ """Test helpers for camera.""" from collections.abc import AsyncGenerator, Generator -from unittest.mock import AsyncMock, PropertyMock, patch +from unittest.mock import AsyncMock, Mock, PropertyMock, patch import pytest from homeassistant.components import camera from homeassistant.components.camera.const import StreamType from homeassistant.components.camera.webrtc import WebRTCAnswer, WebRTCSendMessage +from homeassistant.config_entries import ConfigEntry, ConfigFlow from homeassistant.const import Platform from homeassistant.core import HomeAssistant from homeassistant.helpers.device_registry import DeviceInfo @@ -15,6 +16,15 @@ from homeassistant.setup import async_setup_component from .common import STREAM_SOURCE, WEBRTC_ANSWER +from tests.common import ( + MockConfigEntry, + MockModule, + mock_config_flow, + mock_integration, + mock_platform, + setup_test_component_platform, +) + @pytest.fixture(autouse=True) async def setup_homeassistant(hass: HomeAssistant) -> None: @@ -142,3 +152,66 @@ def mock_stream_source_fixture() -> Generator[AsyncMock]: return_value=STREAM_SOURCE, ) as mock_stream_source: yield mock_stream_source + + +@pytest.fixture +async def mock_camera_webrtc_native_sync_offer(hass: HomeAssistant) -> None: + """Initialize a test camera with native sync WebRTC support.""" + + # Cannot use the fixture mock_camera_web_rtc as it's mocking Camera.async_handle_web_rtc_offer + # and native support is checked by verify the function "async_handle_web_rtc_offer" was + # overwritten(implemented) or not + class MockCamera(camera.Camera): + """Mock Camera Entity.""" + + _attr_name = "Test" + _attr_supported_features: camera.CameraEntityFeature = ( + camera.CameraEntityFeature.STREAM + ) + _attr_frontend_stream_type: camera.StreamType = camera.StreamType.WEB_RTC + + async def stream_source(self) -> str | None: + return STREAM_SOURCE + + async def async_handle_web_rtc_offer(self, offer_sdp: str) -> str | None: + return WEBRTC_ANSWER + + domain = "test" + + entry = MockConfigEntry(domain=domain) + entry.add_to_hass(hass) + + async def async_setup_entry_init( + hass: HomeAssistant, config_entry: ConfigEntry + ) -> bool: + """Set up test config entry.""" + await hass.config_entries.async_forward_entry_setups( + config_entry, [camera.DOMAIN] + ) + return True + + async def async_unload_entry_init( + hass: HomeAssistant, config_entry: ConfigEntry + ) -> bool: + """Unload test config entry.""" + await hass.config_entries.async_forward_entry_unload( + config_entry, camera.DOMAIN + ) + return True + + mock_integration( + hass, + MockModule( + domain, + async_setup_entry=async_setup_entry_init, + async_unload_entry=async_unload_entry_init, + ), + ) + setup_test_component_platform( + hass, camera.DOMAIN, [MockCamera()], from_config_entry=True + ) + mock_platform(hass, f"{domain}.config_flow", Mock()) + + with mock_config_flow(domain, ConfigFlow): + assert await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() diff --git a/tests/components/camera/test_init.py b/tests/components/camera/test_init.py index e7279f60848..0a173065564 100644 --- a/tests/components/camera/test_init.py +++ b/tests/components/camera/test_init.py @@ -25,7 +25,6 @@ from homeassistant.components.camera.const import ( ) from homeassistant.components.camera.helper import get_camera_from_entity_id from homeassistant.components.websocket_api import TYPE_RESULT -from homeassistant.config_entries import ConfigEntry, ConfigFlow from homeassistant.const import ( ATTR_ENTITY_ID, EVENT_HOMEASSISTANT_STARTED, @@ -38,18 +37,12 @@ from homeassistant.helpers import entity_registry as er, issue_registry as ir from homeassistant.setup import async_setup_component from homeassistant.util import dt as dt_util -from .common import EMPTY_8_6_JPEG, STREAM_SOURCE, WEBRTC_ANSWER, mock_turbo_jpeg +from .common import EMPTY_8_6_JPEG, STREAM_SOURCE, mock_turbo_jpeg from tests.common import ( - MockConfigEntry, - MockModule, async_fire_time_changed, help_test_all, import_and_test_deprecated_constant_enum, - mock_config_flow, - mock_integration, - mock_platform, - setup_test_component_platform, ) from tests.typing import ClientSessionGenerator, WebSocketGenerator @@ -986,62 +979,13 @@ async def test_camera_capabilities_hls( ) +@pytest.mark.usefixtures("mock_camera_webrtc_native_sync_offer") async def test_camera_capabilities_webrtc( hass: HomeAssistant, hass_ws_client: WebSocketGenerator, ) -> None: """Test WebRTC camera capabilities.""" - # Cannot use the fixture mock_camera_web_rtc as it's mocking Camera.async_handle_web_rtc_offer - # Camera capabilities are determined by by checking if the function was overwritten(implemented) or not - class MockCamera(camera.Camera): - """Mock Camera Entity.""" - - _attr_name = "Test" - _attr_supported_features: camera.CameraEntityFeature = ( - camera.CameraEntityFeature.STREAM - ) - - async def stream_source(self) -> str | None: - return STREAM_SOURCE - - async def async_handle_web_rtc_offer(self, offer_sdp: str) -> str | None: - return WEBRTC_ANSWER - - domain = "test" - - entry = MockConfigEntry(domain=domain) - entry.add_to_hass(hass) - - async def async_setup_entry_init( - hass: HomeAssistant, config_entry: ConfigEntry - ) -> bool: - """Set up test config entry.""" - await hass.config_entries.async_forward_entry_setups(config_entry, [DOMAIN]) - return True - - async def async_unload_entry_init( - hass: HomeAssistant, config_entry: ConfigEntry - ) -> bool: - """Unload test config entry.""" - await hass.config_entries.async_forward_entry_unload(config_entry, DOMAIN) - return True - - mock_integration( - hass, - MockModule( - domain, - async_setup_entry=async_setup_entry_init, - async_unload_entry=async_unload_entry_init, - ), - ) - setup_test_component_platform(hass, DOMAIN, [MockCamera()], from_config_entry=True) - mock_platform(hass, f"{domain}.config_flow", Mock()) - - with mock_config_flow(domain, ConfigFlow): - assert await hass.config_entries.async_setup(entry.entry_id) - await hass.async_block_till_done() - await _test_capabilities( hass, hass_ws_client, "camera.test", {StreamType.WEB_RTC}, {StreamType.WEB_RTC} ) diff --git a/tests/components/camera/test_webrtc.py b/tests/components/camera/test_webrtc.py index 27c50848ebf..2970a41408c 100644 --- a/tests/components/camera/test_webrtc.py +++ b/tests/components/camera/test_webrtc.py @@ -393,6 +393,29 @@ async def test_ws_get_client_config( } +@pytest.mark.usefixtures("mock_camera_webrtc_native_sync_offer") +async def test_ws_get_client_config_sync_offer( + hass: HomeAssistant, hass_ws_client: WebSocketGenerator +) -> None: + """Test get WebRTC client config, when camera is supporting sync offer.""" + await async_setup_component(hass, "camera", {}) + await hass.async_block_till_done() + + client = await hass_ws_client(hass) + await client.send_json_auto_id( + {"type": "camera/webrtc/get_client_config", "entity_id": "camera.test"} + ) + msg = await client.receive_json() + + # Assert WebSocket response + assert msg["type"] == TYPE_RESULT + assert msg["success"] + assert msg["result"] == { + "configuration": {}, + "getCandidatesUpfront": False, + } + + @pytest.mark.usefixtures("mock_camera_webrtc") async def test_ws_get_client_config_custom_config( hass: HomeAssistant, hass_ws_client: WebSocketGenerator