From 0c9be604c237cee34e1005d1708d421bfd33fe4c Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Fri, 28 Jan 2022 09:07:41 -0800 Subject: [PATCH] Add diagnostics for rtsp_to_webrtc (#65138) --- .../components/rtsp_to_webrtc/diagnostics.py | 17 +++ tests/components/rtsp_to_webrtc/conftest.py | 98 +++++++++++++++ .../rtsp_to_webrtc/test_diagnostics.py | 27 ++++ tests/components/rtsp_to_webrtc/test_init.py | 118 ++++-------------- 4 files changed, 168 insertions(+), 92 deletions(-) create mode 100644 homeassistant/components/rtsp_to_webrtc/diagnostics.py create mode 100644 tests/components/rtsp_to_webrtc/conftest.py create mode 100644 tests/components/rtsp_to_webrtc/test_diagnostics.py diff --git a/homeassistant/components/rtsp_to_webrtc/diagnostics.py b/homeassistant/components/rtsp_to_webrtc/diagnostics.py new file mode 100644 index 00000000000..ab13e0a64ee --- /dev/null +++ b/homeassistant/components/rtsp_to_webrtc/diagnostics.py @@ -0,0 +1,17 @@ +"""Diagnostics support for Nest.""" + +from __future__ import annotations + +from typing import Any + +from rtsp_to_webrtc import client + +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant + + +async def async_get_config_entry_diagnostics( + hass: HomeAssistant, config_entry: ConfigEntry +) -> dict[str, Any]: + """Return diagnostics for a config entry.""" + return dict(client.get_diagnostics()) diff --git a/tests/components/rtsp_to_webrtc/conftest.py b/tests/components/rtsp_to_webrtc/conftest.py new file mode 100644 index 00000000000..7148896e454 --- /dev/null +++ b/tests/components/rtsp_to_webrtc/conftest.py @@ -0,0 +1,98 @@ +"""Tests for RTSPtoWebRTC inititalization.""" + +from __future__ import annotations + +from collections.abc import AsyncGenerator, Awaitable, Callable, Generator +from typing import Any, TypeVar +from unittest.mock import patch + +import pytest +import rtsp_to_webrtc + +from homeassistant.components import camera +from homeassistant.components.rtsp_to_webrtc import DOMAIN +from homeassistant.config_entries import ConfigEntryState +from homeassistant.core import HomeAssistant +from homeassistant.setup import async_setup_component + +from tests.common import MockConfigEntry + +STREAM_SOURCE = "rtsp://example.com" +SERVER_URL = "http://127.0.0.1:8083" + +CONFIG_ENTRY_DATA = {"server_url": SERVER_URL} + +# Typing helpers +ComponentSetup = Callable[[], Awaitable[None]] +T = TypeVar("T") +YieldFixture = Generator[T, None, None] + + +@pytest.fixture(autouse=True) +async def webrtc_server() -> None: + """Patch client library to force usage of RTSPtoWebRTC server.""" + with patch( + "rtsp_to_webrtc.client.WebClient.heartbeat", + side_effect=rtsp_to_webrtc.exceptions.ResponseError(), + ): + yield + + +@pytest.fixture +async def mock_camera(hass) -> AsyncGenerator[None, None]: + """Initialize a demo camera platform.""" + assert await async_setup_component( + hass, "camera", {camera.DOMAIN: {"platform": "demo"}} + ) + await hass.async_block_till_done() + with patch( + "homeassistant.components.demo.camera.Path.read_bytes", + return_value=b"Test", + ), patch( + "homeassistant.components.camera.Camera.stream_source", + return_value=STREAM_SOURCE, + ), patch( + "homeassistant.components.camera.Camera.supported_features", + return_value=camera.SUPPORT_STREAM, + ): + yield + + +@pytest.fixture +async def config_entry_data() -> dict[str, Any]: + """Fixture for MockConfigEntry data.""" + return CONFIG_ENTRY_DATA + + +@pytest.fixture +async def config_entry(config_entry_data: dict[str, Any]) -> MockConfigEntry: + """Fixture for MockConfigEntry.""" + return MockConfigEntry(domain=DOMAIN, data=config_entry_data) + + +@pytest.fixture +async def rtsp_to_webrtc_client() -> None: + """Fixture for mock rtsp_to_webrtc client.""" + with patch("rtsp_to_webrtc.client.Client.heartbeat"): + yield + + +@pytest.fixture +async def setup_integration( + hass: HomeAssistant, config_entry: MockConfigEntry +) -> YieldFixture[ComponentSetup]: + """Fixture for setting up the component.""" + config_entry.add_to_hass(hass) + + async def func() -> None: + assert await async_setup_component(hass, DOMAIN, {}) + await hass.async_block_till_done() + + yield func + entries = hass.config_entries.async_entries(DOMAIN) + assert len(entries) == 1 + await hass.config_entries.async_unload(entries[0].entry_id) + await hass.async_block_till_done() + + assert not hass.data.get(DOMAIN) + assert entries[0].state is ConfigEntryState.NOT_LOADED diff --git a/tests/components/rtsp_to_webrtc/test_diagnostics.py b/tests/components/rtsp_to_webrtc/test_diagnostics.py new file mode 100644 index 00000000000..27b801a71ed --- /dev/null +++ b/tests/components/rtsp_to_webrtc/test_diagnostics.py @@ -0,0 +1,27 @@ +"""Test nest diagnostics.""" + +from typing import Any + +from .conftest import ComponentSetup + +from tests.common import MockConfigEntry +from tests.components.diagnostics import get_diagnostics_for_config_entry + +THERMOSTAT_TYPE = "sdm.devices.types.THERMOSTAT" + + +async def test_entry_diagnostics( + hass, + hass_client, + config_entry: MockConfigEntry, + rtsp_to_webrtc_client: Any, + setup_integration: ComponentSetup, +): + """Test config entry diagnostics.""" + await setup_integration() + + assert await get_diagnostics_for_config_entry(hass, hass_client, config_entry) == { + "discovery": {"attempt": 1, "web.failure": 1, "webrtc.success": 1}, + "web": {}, + "webrtc": {}, + } diff --git a/tests/components/rtsp_to_webrtc/test_init.py b/tests/components/rtsp_to_webrtc/test_init.py index 94ec3529836..759fea7c813 100644 --- a/tests/components/rtsp_to_webrtc/test_init.py +++ b/tests/components/rtsp_to_webrtc/test_init.py @@ -3,7 +3,7 @@ from __future__ import annotations import base64 -from collections.abc import AsyncGenerator, Awaitable, Callable +from collections.abc import Awaitable, Callable from typing import Any from unittest.mock import patch @@ -11,147 +11,84 @@ import aiohttp import pytest import rtsp_to_webrtc -from homeassistant.components import camera from homeassistant.components.rtsp_to_webrtc import DOMAIN from homeassistant.components.websocket_api.const import TYPE_RESULT from homeassistant.config_entries import ConfigEntryState from homeassistant.core import HomeAssistant -from homeassistant.setup import async_setup_component -from tests.common import MockConfigEntry +from .conftest import SERVER_URL, STREAM_SOURCE, ComponentSetup + from tests.test_util.aiohttp import AiohttpClientMocker -STREAM_SOURCE = "rtsp://example.com" # The webrtc component 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..." -SERVER_URL = "http://127.0.0.1:8083" -CONFIG_ENTRY_DATA = {"server_url": SERVER_URL} - - -@pytest.fixture(autouse=True) -async def webrtc_server() -> None: - """Patch client library to force usage of RTSPtoWebRTC server.""" - with patch( - "rtsp_to_webrtc.client.WebClient.heartbeat", - side_effect=rtsp_to_webrtc.exceptions.ResponseError(), - ): - yield - - -@pytest.fixture -async def mock_camera(hass) -> AsyncGenerator[None, None]: - """Initialize a demo camera platform.""" - assert await async_setup_component( - hass, "camera", {camera.DOMAIN: {"platform": "demo"}} - ) - await hass.async_block_till_done() - with patch( - "homeassistant.components.demo.camera.Path.read_bytes", - return_value=b"Test", - ), patch( - "homeassistant.components.camera.Camera.stream_source", - return_value=STREAM_SOURCE, - ), patch( - "homeassistant.components.camera.Camera.supported_features", - return_value=camera.SUPPORT_STREAM, - ): - yield - - -async def async_setup_rtsp_to_webrtc(hass: HomeAssistant) -> None: - """Set up the component.""" - return await async_setup_component(hass, DOMAIN, {}) - - -async def test_setup_success(hass: HomeAssistant) -> None: +async def test_setup_success( + hass: HomeAssistant, rtsp_to_webrtc_client: Any, setup_integration: ComponentSetup +) -> None: """Test successful setup and unload.""" - config_entry = MockConfigEntry(domain=DOMAIN, data=CONFIG_ENTRY_DATA) - config_entry.add_to_hass(hass) - - with patch("rtsp_to_webrtc.client.Client.heartbeat"): - assert await async_setup_rtsp_to_webrtc(hass) - await hass.async_block_till_done() + await setup_integration() entries = hass.config_entries.async_entries(DOMAIN) assert len(entries) == 1 assert entries[0].state is ConfigEntryState.LOADED - await hass.config_entries.async_unload(config_entry.entry_id) - await hass.async_block_till_done() - assert not hass.data.get(DOMAIN) - assert config_entry.state is ConfigEntryState.NOT_LOADED - - -async def test_invalid_config_entry(hass: HomeAssistant) -> None: +@pytest.mark.parametrize("config_entry_data", [{}]) +async def test_invalid_config_entry( + hass: HomeAssistant, rtsp_to_webrtc_client: Any, setup_integration: ComponentSetup +) -> None: """Test a config entry with missing required fields.""" - config_entry = MockConfigEntry(domain=DOMAIN, data={}) - config_entry.add_to_hass(hass) - - assert await async_setup_rtsp_to_webrtc(hass) + await setup_integration() entries = hass.config_entries.async_entries(DOMAIN) assert len(entries) == 1 assert entries[0].state is ConfigEntryState.SETUP_ERROR -async def test_setup_server_failure(hass: HomeAssistant) -> None: +async def test_setup_server_failure( + hass: HomeAssistant, setup_integration: ComponentSetup +) -> None: """Test server responds with a failure on startup.""" - config_entry = MockConfigEntry(domain=DOMAIN, data=CONFIG_ENTRY_DATA) - config_entry.add_to_hass(hass) - with patch( "rtsp_to_webrtc.client.Client.heartbeat", side_effect=rtsp_to_webrtc.exceptions.ResponseError(), ): - assert await async_setup_rtsp_to_webrtc(hass) - await hass.async_block_till_done() + await setup_integration() entries = hass.config_entries.async_entries(DOMAIN) assert len(entries) == 1 assert entries[0].state is ConfigEntryState.SETUP_RETRY - await hass.config_entries.async_unload(config_entry.entry_id) - await hass.async_block_till_done() - -async def test_setup_communication_failure(hass: HomeAssistant) -> None: +async def test_setup_communication_failure( + hass: HomeAssistant, setup_integration: ComponentSetup +) -> None: """Test unable to talk to server on startup.""" - config_entry = MockConfigEntry(domain=DOMAIN, data=CONFIG_ENTRY_DATA) - config_entry.add_to_hass(hass) - with patch( "rtsp_to_webrtc.client.Client.heartbeat", side_effect=rtsp_to_webrtc.exceptions.ClientError(), ): - assert await async_setup_rtsp_to_webrtc(hass) - await hass.async_block_till_done() + await setup_integration() entries = hass.config_entries.async_entries(DOMAIN) assert len(entries) == 1 assert entries[0].state is ConfigEntryState.SETUP_RETRY - await hass.config_entries.async_unload(config_entry.entry_id) - await hass.async_block_till_done() - async def test_offer_for_stream_source( hass: HomeAssistant, aioclient_mock: AiohttpClientMocker, hass_ws_client: Callable[[...], Awaitable[aiohttp.ClientWebSocketResponse]], mock_camera: Any, + rtsp_to_webrtc_client: Any, + setup_integration: ComponentSetup, ) -> None: """Test successful response from RTSPtoWebRTC server.""" - config_entry = MockConfigEntry(domain=DOMAIN, data=CONFIG_ENTRY_DATA) - config_entry.add_to_hass(hass) - - with patch("rtsp_to_webrtc.client.Client.heartbeat"): - assert await async_setup_rtsp_to_webrtc(hass) - await hass.async_block_till_done() + await setup_integration() aioclient_mock.post( f"{SERVER_URL}/stream", @@ -188,14 +125,11 @@ async def test_offer_failure( aioclient_mock: AiohttpClientMocker, hass_ws_client: Callable[[...], Awaitable[aiohttp.ClientWebSocketResponse]], mock_camera: Any, + rtsp_to_webrtc_client: Any, + setup_integration: ComponentSetup, ) -> None: """Test a transient failure talking to RTSPtoWebRTC server.""" - config_entry = MockConfigEntry(domain=DOMAIN, data=CONFIG_ENTRY_DATA) - config_entry.add_to_hass(hass) - - with patch("rtsp_to_webrtc.client.Client.heartbeat"): - assert await async_setup_rtsp_to_webrtc(hass) - await hass.async_block_till_done() + await setup_integration() aioclient_mock.post( f"{SERVER_URL}/stream",