Add diagnostics for rtsp_to_webrtc (#65138)

This commit is contained in:
Allen Porter 2022-01-28 09:07:41 -08:00 committed by GitHub
parent d0d55db936
commit 0c9be604c2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 168 additions and 92 deletions

View file

@ -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())

View file

@ -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

View file

@ -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": {},
}

View file

@ -3,7 +3,7 @@
from __future__ import annotations from __future__ import annotations
import base64 import base64
from collections.abc import AsyncGenerator, Awaitable, Callable from collections.abc import Awaitable, Callable
from typing import Any from typing import Any
from unittest.mock import patch from unittest.mock import patch
@ -11,147 +11,84 @@ import aiohttp
import pytest import pytest
import rtsp_to_webrtc import rtsp_to_webrtc
from homeassistant.components import camera
from homeassistant.components.rtsp_to_webrtc import DOMAIN from homeassistant.components.rtsp_to_webrtc import DOMAIN
from homeassistant.components.websocket_api.const import TYPE_RESULT from homeassistant.components.websocket_api.const import TYPE_RESULT
from homeassistant.config_entries import ConfigEntryState from homeassistant.config_entries import ConfigEntryState
from homeassistant.core import HomeAssistant 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 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, # The webrtc component does not inspect the details of the offer and answer,
# and is only a pass through. # and is only a pass through.
OFFER_SDP = "v=0\r\no=carol 28908764872 28908764872 IN IP4 100.3.6.6\r\n..." 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..." 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} async def test_setup_success(
hass: HomeAssistant, rtsp_to_webrtc_client: Any, setup_integration: ComponentSetup
) -> 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
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:
"""Test successful setup and unload.""" """Test successful setup and unload."""
config_entry = MockConfigEntry(domain=DOMAIN, data=CONFIG_ENTRY_DATA) await setup_integration()
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()
entries = hass.config_entries.async_entries(DOMAIN) entries = hass.config_entries.async_entries(DOMAIN)
assert len(entries) == 1 assert len(entries) == 1
assert entries[0].state is ConfigEntryState.LOADED 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) @pytest.mark.parametrize("config_entry_data", [{}])
assert config_entry.state is ConfigEntryState.NOT_LOADED async def test_invalid_config_entry(
hass: HomeAssistant, rtsp_to_webrtc_client: Any, setup_integration: ComponentSetup
) -> None:
async def test_invalid_config_entry(hass: HomeAssistant) -> None:
"""Test a config entry with missing required fields.""" """Test a config entry with missing required fields."""
config_entry = MockConfigEntry(domain=DOMAIN, data={}) await setup_integration()
config_entry.add_to_hass(hass)
assert await async_setup_rtsp_to_webrtc(hass)
entries = hass.config_entries.async_entries(DOMAIN) entries = hass.config_entries.async_entries(DOMAIN)
assert len(entries) == 1 assert len(entries) == 1
assert entries[0].state is ConfigEntryState.SETUP_ERROR 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.""" """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( with patch(
"rtsp_to_webrtc.client.Client.heartbeat", "rtsp_to_webrtc.client.Client.heartbeat",
side_effect=rtsp_to_webrtc.exceptions.ResponseError(), side_effect=rtsp_to_webrtc.exceptions.ResponseError(),
): ):
assert await async_setup_rtsp_to_webrtc(hass) await setup_integration()
await hass.async_block_till_done()
entries = hass.config_entries.async_entries(DOMAIN) entries = hass.config_entries.async_entries(DOMAIN)
assert len(entries) == 1 assert len(entries) == 1
assert entries[0].state is ConfigEntryState.SETUP_RETRY 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(
async def test_setup_communication_failure(hass: HomeAssistant) -> None: hass: HomeAssistant, setup_integration: ComponentSetup
) -> None:
"""Test unable to talk to server on startup.""" """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( with patch(
"rtsp_to_webrtc.client.Client.heartbeat", "rtsp_to_webrtc.client.Client.heartbeat",
side_effect=rtsp_to_webrtc.exceptions.ClientError(), side_effect=rtsp_to_webrtc.exceptions.ClientError(),
): ):
assert await async_setup_rtsp_to_webrtc(hass) await setup_integration()
await hass.async_block_till_done()
entries = hass.config_entries.async_entries(DOMAIN) entries = hass.config_entries.async_entries(DOMAIN)
assert len(entries) == 1 assert len(entries) == 1
assert entries[0].state is ConfigEntryState.SETUP_RETRY 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( async def test_offer_for_stream_source(
hass: HomeAssistant, hass: HomeAssistant,
aioclient_mock: AiohttpClientMocker, aioclient_mock: AiohttpClientMocker,
hass_ws_client: Callable[[...], Awaitable[aiohttp.ClientWebSocketResponse]], hass_ws_client: Callable[[...], Awaitable[aiohttp.ClientWebSocketResponse]],
mock_camera: Any, mock_camera: Any,
rtsp_to_webrtc_client: Any,
setup_integration: ComponentSetup,
) -> None: ) -> None:
"""Test successful response from RTSPtoWebRTC server.""" """Test successful response from RTSPtoWebRTC server."""
config_entry = MockConfigEntry(domain=DOMAIN, data=CONFIG_ENTRY_DATA) await setup_integration()
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()
aioclient_mock.post( aioclient_mock.post(
f"{SERVER_URL}/stream", f"{SERVER_URL}/stream",
@ -188,14 +125,11 @@ async def test_offer_failure(
aioclient_mock: AiohttpClientMocker, aioclient_mock: AiohttpClientMocker,
hass_ws_client: Callable[[...], Awaitable[aiohttp.ClientWebSocketResponse]], hass_ws_client: Callable[[...], Awaitable[aiohttp.ClientWebSocketResponse]],
mock_camera: Any, mock_camera: Any,
rtsp_to_webrtc_client: Any,
setup_integration: ComponentSetup,
) -> None: ) -> None:
"""Test a transient failure talking to RTSPtoWebRTC server.""" """Test a transient failure talking to RTSPtoWebRTC server."""
config_entry = MockConfigEntry(domain=DOMAIN, data=CONFIG_ENTRY_DATA) await setup_integration()
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()
aioclient_mock.post( aioclient_mock.post(
f"{SERVER_URL}/stream", f"{SERVER_URL}/stream",