Files
core/tests/components/go2rtc/test_init.py

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

220 lines
6.7 KiB
Python
Raw Normal View History

"""The tests for the go2rtc component."""
from collections.abc import Callable
from unittest.mock import AsyncMock, Mock
from go2rtc_client import Stream, WebRTCSdpAnswer, WebRTCSdpOffer
from go2rtc_client.models import Producer
import pytest
from homeassistant.components.camera import (
DOMAIN as CAMERA_DOMAIN,
Camera,
CameraEntityFeature,
)
from homeassistant.components.camera.const import StreamType
from homeassistant.components.camera.helper import get_camera_from_entity_id
from homeassistant.components.go2rtc import WebRTCProvider
from homeassistant.components.go2rtc.const import DOMAIN
from homeassistant.config_entries import ConfigEntry, ConfigEntryState, ConfigFlow
from homeassistant.const import CONF_HOST
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from . import setup_integration
from tests.common import (
MockConfigEntry,
MockModule,
mock_config_flow,
mock_integration,
mock_platform,
setup_test_component_platform,
)
TEST_DOMAIN = "test"
# The go2rtc provider does not inspect the details of the offer and answer,
# and is only a pass through.
OFFER_SDP = "v=0\r\no=carol 28908764872 28908764872 IN IP4 100.3.6.6\r\n..."
ANSWER_SDP = "v=0\r\no=bob 2890844730 2890844730 IN IP4 host.example.com\r\n..."
class MockCamera(Camera):
"""Mock Camera Entity."""
_attr_name = "Test"
_attr_supported_features: CameraEntityFeature = CameraEntityFeature.STREAM
def __init__(self) -> None:
"""Initialize the mock entity."""
super().__init__()
self._stream_source: str | None = "rtsp://stream"
def set_stream_source(self, stream_source: str | None) -> None:
"""Set the stream source."""
self._stream_source = stream_source
async def stream_source(self) -> str | None:
"""Return the source of the stream.
This is used by cameras with CameraEntityFeature.STREAM
and StreamType.HLS.
"""
return self._stream_source
@pytest.fixture
def integration_entity() -> MockCamera:
"""Mock Camera Entity."""
return MockCamera()
@pytest.fixture
def integration_config_entry(hass: HomeAssistant) -> ConfigEntry:
"""Test mock config entry."""
entry = MockConfigEntry(domain=TEST_DOMAIN)
entry.add_to_hass(hass)
return entry
@pytest.fixture
async def init_test_integration(
hass: HomeAssistant,
integration_config_entry: ConfigEntry,
integration_entity: MockCamera,
) -> None:
"""Initialize components."""
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(
TEST_DOMAIN,
async_setup_entry=async_setup_entry_init,
async_unload_entry=async_unload_entry_init,
),
)
setup_test_component_platform(
hass, CAMERA_DOMAIN, [integration_entity], from_config_entry=True
)
mock_platform(hass, f"{TEST_DOMAIN}.config_flow", Mock())
with mock_config_flow(TEST_DOMAIN, ConfigFlow):
assert await hass.config_entries.async_setup(integration_config_entry.entry_id)
await hass.async_block_till_done()
return integration_config_entry
@pytest.mark.usefixtures("init_test_integration")
async def _test_setup(
hass: HomeAssistant,
mock_client: AsyncMock,
mock_config_entry: MockConfigEntry,
after_setup_fn: Callable[[], None],
) -> None:
"""Test the go2rtc config entry."""
entity_id = "camera.test"
camera = get_camera_from_entity_id(hass, entity_id)
assert camera.frontend_stream_type == StreamType.HLS
await setup_integration(hass, mock_config_entry)
after_setup_fn()
mock_client.webrtc.forward_whep_sdp_offer.return_value = WebRTCSdpAnswer(ANSWER_SDP)
answer = await camera.async_handle_web_rtc_offer(OFFER_SDP)
assert answer == ANSWER_SDP
mock_client.webrtc.forward_whep_sdp_offer.assert_called_once_with(
entity_id, WebRTCSdpOffer(OFFER_SDP)
)
mock_client.streams.add.assert_called_once_with(entity_id, "rtsp://stream")
# If the stream is already added, the stream should not be added again.
mock_client.streams.add.reset_mock()
mock_client.streams.list.return_value = {
entity_id: Stream([Producer("rtsp://stream")])
}
answer = await camera.async_handle_web_rtc_offer(OFFER_SDP)
assert answer == ANSWER_SDP
mock_client.streams.add.assert_not_called()
assert mock_client.webrtc.forward_whep_sdp_offer.call_count == 2
assert isinstance(camera._webrtc_providers[0], WebRTCProvider)
# Set stream source to None and provider should be skipped
mock_client.streams.list.return_value = {}
camera.set_stream_source(None)
with pytest.raises(
HomeAssistantError,
match="WebRTC offer was not accepted by the supported providers",
):
await camera.async_handle_web_rtc_offer(OFFER_SDP)
# Remove go2rtc config entry
assert mock_config_entry.state is ConfigEntryState.LOADED
await hass.config_entries.async_remove(mock_config_entry.entry_id)
await hass.async_block_till_done()
assert mock_config_entry.state is ConfigEntryState.NOT_LOADED
assert camera._webrtc_providers == []
assert camera.frontend_stream_type == StreamType.HLS
@pytest.mark.usefixtures("init_test_integration")
async def test_setup_go_binary(
hass: HomeAssistant,
mock_client: AsyncMock,
mock_server: Mock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test the go2rtc config entry with binary."""
def after_setup() -> None:
mock_server.assert_called_once_with("/usr/bin/go2rtc")
mock_server.return_value.start.assert_called_once()
await _test_setup(hass, mock_client, mock_config_entry, after_setup)
mock_server.return_value.stop.assert_called_once()
@pytest.mark.usefixtures("init_test_integration")
async def test_setup_go(
hass: HomeAssistant,
mock_client: AsyncMock,
mock_server: Mock,
) -> None:
"""Test the go2rtc config entry without binary."""
config_entry = MockConfigEntry(
domain=DOMAIN,
title=DOMAIN,
data={CONF_HOST: "http://localhost:1984/"},
)
def after_setup() -> None:
mock_server.assert_not_called()
await _test_setup(hass, mock_client, config_entry, after_setup)
mock_server.assert_not_called()