mirror of
https://github.com/home-assistant/core.git
synced 2026-09-25 08:51:46 -04:00
Add regression test for ONVIF setup against a real ONVIFDevice (#172194)
Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude
parent
309b26f809
commit
219b9cbcaa
@@ -2,6 +2,7 @@
|
||||
|
||||
import collections
|
||||
from collections import defaultdict
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
from onvif.exceptions import ONVIFError
|
||||
@@ -53,8 +54,17 @@ def setup_mock_onvif_camera(
|
||||
no_profiles=False,
|
||||
auth_failure=False,
|
||||
wrong_port=False,
|
||||
with_full_setup=False,
|
||||
):
|
||||
"""Prepare mock onvif.ONVIFCamera."""
|
||||
"""Prepare mock onvif.ONVIFCamera.
|
||||
|
||||
When ``with_full_setup`` is set, the mock is additionally configured with
|
||||
every service ``ONVIFDevice.async_setup`` invokes, so that a config entry
|
||||
can be fully set up end-to-end. In this mode the profile and device
|
||||
information responses are replaced, so the flags that control them
|
||||
(``with_h264``, ``two_profiles``, ``no_profiles``, ``auth_fail`` and
|
||||
``profiles_transient_failure``) have no effect.
|
||||
"""
|
||||
devicemgmt = MagicMock()
|
||||
|
||||
device_info = MagicMock()
|
||||
@@ -111,6 +121,62 @@ def setup_mock_onvif_camera(
|
||||
mock_onvif_camera.xaddrs = {}
|
||||
mock_onvif_camera.services = {}
|
||||
|
||||
if with_full_setup:
|
||||
devicemgmt.GetSystemDateAndTime = AsyncMock(return_value=None)
|
||||
devicemgmt.GetDeviceInformation = AsyncMock(
|
||||
return_value=SimpleNamespace(
|
||||
Manufacturer=MANUFACTURER,
|
||||
Model=MODEL,
|
||||
FirmwareVersion=FIRMWARE_VERSION,
|
||||
SerialNumber=SERIAL_NUMBER if with_serial else None,
|
||||
)
|
||||
)
|
||||
|
||||
media_service.GetServiceCapabilities = AsyncMock(
|
||||
return_value=SimpleNamespace(SnapshotUri=False)
|
||||
)
|
||||
media_service.GetProfiles = AsyncMock(
|
||||
return_value=[
|
||||
SimpleNamespace(
|
||||
token="profile_token",
|
||||
Name="MainStream",
|
||||
VideoEncoderConfiguration=SimpleNamespace(
|
||||
Resolution=SimpleNamespace(Width=1920, Height=1080),
|
||||
Encoding="H264",
|
||||
),
|
||||
VideoSourceConfiguration=MagicMock(),
|
||||
PTZConfiguration=MagicMock(),
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
ptz_service = MagicMock()
|
||||
ptz_service.GetPresets = AsyncMock(return_value=[])
|
||||
mock_onvif_camera.create_ptz_service = AsyncMock(return_value=ptz_service)
|
||||
mock_onvif_camera.create_imaging_service = AsyncMock(return_value=MagicMock())
|
||||
mock_onvif_camera.get_snapshot = AsyncMock(return_value=False)
|
||||
mock_onvif_camera.get_capabilities = AsyncMock(
|
||||
return_value={
|
||||
"Media": {"XAddr": "http://media"},
|
||||
"PTZ": {"XAddr": "http://ptz"},
|
||||
"Imaging": {"XAddr": "http://imaging"},
|
||||
"Events": {
|
||||
"XAddr": None,
|
||||
"WSPullPointSupport": False,
|
||||
"WSSubscriptionPolicySupport": False,
|
||||
},
|
||||
}
|
||||
)
|
||||
# Let the real event managers run but fail gracefully at the onvif
|
||||
# library boundary, so async_start_events returns False and setup
|
||||
# still reaches LOADED (models a camera without working events).
|
||||
mock_onvif_camera.create_pullpoint_manager = AsyncMock(
|
||||
side_effect=Fault("no pullpoint support")
|
||||
)
|
||||
mock_onvif_camera.create_notification_manager = AsyncMock(
|
||||
side_effect=Fault("no notification support")
|
||||
)
|
||||
|
||||
def mock_constructor(
|
||||
host,
|
||||
port,
|
||||
|
||||
@@ -2,10 +2,27 @@
|
||||
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.const import (
|
||||
CONF_HOST,
|
||||
CONF_NAME,
|
||||
CONF_PASSWORD,
|
||||
CONF_PORT,
|
||||
CONF_USERNAME,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
|
||||
from . import MAC, setup_mock_device
|
||||
from . import (
|
||||
HOST,
|
||||
MAC,
|
||||
NAME,
|
||||
PASSWORD,
|
||||
PORT,
|
||||
USERNAME,
|
||||
setup_mock_device,
|
||||
setup_mock_onvif_camera,
|
||||
)
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
@@ -97,3 +114,33 @@ async def test_migrate_camera_entities_unique_ids(hass: HomeAssistant) -> None:
|
||||
# Make sure the unexisting index entity is unchanged
|
||||
assert entity_unexisting_index is not None
|
||||
assert entity_unexisting_index.unique_id == f"{MAC}_9"
|
||||
|
||||
|
||||
async def test_setup_entry(hass: HomeAssistant) -> None:
|
||||
"""Test setting up the config entry."""
|
||||
entry = MockConfigEntry(
|
||||
domain="onvif",
|
||||
title=NAME,
|
||||
unique_id=MAC,
|
||||
data={
|
||||
CONF_NAME: NAME,
|
||||
CONF_HOST: HOST,
|
||||
CONF_PORT: PORT,
|
||||
CONF_USERNAME: USERNAME,
|
||||
CONF_PASSWORD: PASSWORD,
|
||||
},
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.onvif.device.ONVIFCamera"
|
||||
) as mock_onvif_camera_cls:
|
||||
setup_mock_onvif_camera(mock_onvif_camera_cls, with_full_setup=True)
|
||||
|
||||
assert await hass.config_entries.async_setup(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert entry.state is ConfigEntryState.LOADED
|
||||
mock_onvif_camera_cls.assert_called_once()
|
||||
host, port, username, password = mock_onvif_camera_cls.call_args.args[:4]
|
||||
assert (host, port, username, password) == (HOST, PORT, USERNAME, PASSWORD)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
"""Test ONVIF PTZ capabilities."""
|
||||
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
from homeassistant.const import (
|
||||
@@ -12,19 +11,7 @@ from homeassistant.const import (
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from . import (
|
||||
FIRMWARE_VERSION,
|
||||
HOST,
|
||||
MAC,
|
||||
MANUFACTURER,
|
||||
MODEL,
|
||||
NAME,
|
||||
PASSWORD,
|
||||
PORT,
|
||||
SERIAL_NUMBER,
|
||||
USERNAME,
|
||||
setup_mock_onvif_camera,
|
||||
)
|
||||
from . import HOST, MAC, NAME, PASSWORD, PORT, USERNAME, setup_mock_onvif_camera
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
@@ -32,71 +19,15 @@ from tests.common import MockConfigEntry
|
||||
def _setup_ptz_camera_mocks(mock_onvif_camera_cls: MagicMock) -> MagicMock:
|
||||
"""Configure the patched ONVIFCamera class for PTZ tests.
|
||||
|
||||
Builds on the shared `setup_mock_onvif_camera` helper and only adds the
|
||||
extra mocks needed for `ONVIFDevice.async_setup` to complete end-to-end
|
||||
with PTZ capability. Returns the ptz_service mock so callers can assert
|
||||
against ContinuousMove/Stop calls.
|
||||
Uses the shared ``setup_mock_onvif_camera`` full-setup helper and only adds
|
||||
the PTZ service mocks the tests assert against, returning the ptz_service
|
||||
mock so callers can check ContinuousMove/Stop calls.
|
||||
"""
|
||||
setup_mock_onvif_camera(mock_onvif_camera_cls)
|
||||
setup_mock_onvif_camera(mock_onvif_camera_cls, with_full_setup=True)
|
||||
|
||||
# Override GetProfiles with a profile that has the attributes the PTZ
|
||||
# code path reads from (PTZConfiguration, VideoSourceConfiguration,
|
||||
# Resolution width/height, and a stable Name for entity_id generation).
|
||||
media_service = MagicMock()
|
||||
media_service.GetServiceCapabilities = AsyncMock(
|
||||
return_value=SimpleNamespace(SnapshotUri=False)
|
||||
)
|
||||
media_service.GetProfiles = AsyncMock(
|
||||
return_value=[
|
||||
SimpleNamespace(
|
||||
token="profile_token",
|
||||
Name="MainStream",
|
||||
VideoEncoderConfiguration=SimpleNamespace(
|
||||
Resolution=SimpleNamespace(Width=1920, Height=1080),
|
||||
Encoding="H264",
|
||||
),
|
||||
VideoSourceConfiguration=MagicMock(),
|
||||
PTZConfiguration=MagicMock(),
|
||||
)
|
||||
]
|
||||
)
|
||||
mock_onvif_camera_cls.create_media_service = AsyncMock(return_value=media_service)
|
||||
|
||||
ptz_service = MagicMock()
|
||||
ptz_service = mock_onvif_camera_cls.create_ptz_service.return_value
|
||||
ptz_service.ContinuousMove = AsyncMock()
|
||||
ptz_service.Stop = AsyncMock()
|
||||
ptz_service.GetPresets = AsyncMock(return_value=[])
|
||||
mock_onvif_camera_cls.create_ptz_service = AsyncMock(return_value=ptz_service)
|
||||
mock_onvif_camera_cls.create_imaging_service = AsyncMock(return_value=MagicMock())
|
||||
mock_onvif_camera_cls.create_pullpoint_manager = AsyncMock(return_value=MagicMock())
|
||||
mock_onvif_camera_cls.get_snapshot = AsyncMock(return_value=False)
|
||||
mock_onvif_camera_cls.get_capabilities = AsyncMock(
|
||||
return_value={
|
||||
"Media": {"XAddr": "http://media"},
|
||||
"PTZ": {"XAddr": "http://ptz"},
|
||||
"Imaging": {"XAddr": "http://imaging"},
|
||||
"Events": {
|
||||
"XAddr": None,
|
||||
"WSPullPointSupport": False,
|
||||
"WSSubscriptionPolicySupport": False,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
# Short-circuit async_check_date_and_time by returning None, and return
|
||||
# serializable device information so the device registry entry can be
|
||||
# persisted (the shared helper only sets SerialNumber).
|
||||
devicemgmt = mock_onvif_camera_cls.create_devicemgmt_service.return_value
|
||||
devicemgmt.GetSystemDateAndTime = AsyncMock(return_value=None)
|
||||
devicemgmt.GetDeviceInformation = AsyncMock(
|
||||
return_value=SimpleNamespace(
|
||||
Manufacturer=MANUFACTURER,
|
||||
Model=MODEL,
|
||||
FirmwareVersion=FIRMWARE_VERSION,
|
||||
SerialNumber=SERIAL_NUMBER,
|
||||
)
|
||||
)
|
||||
|
||||
return ptz_service
|
||||
|
||||
|
||||
@@ -149,10 +80,6 @@ async def test_ptz_continuous_move_calls_stop_when_duration_nonzero(
|
||||
"homeassistant.components.onvif.device.asyncio.sleep",
|
||||
new=AsyncMock(),
|
||||
) as mock_sleep,
|
||||
patch(
|
||||
"homeassistant.components.onvif.device.ONVIFDevice.async_start_events",
|
||||
new=AsyncMock(return_value=False),
|
||||
),
|
||||
):
|
||||
ptz_service = _setup_ptz_camera_mocks(mock_onvif_camera_cls)
|
||||
|
||||
@@ -183,10 +110,6 @@ async def test_ptz_continuous_move_does_not_call_stop_when_duration_zero(
|
||||
"homeassistant.components.onvif.device.asyncio.sleep",
|
||||
new=AsyncMock(),
|
||||
) as mock_sleep,
|
||||
patch(
|
||||
"homeassistant.components.onvif.device.ONVIFDevice.async_start_events",
|
||||
new=AsyncMock(return_value=False),
|
||||
),
|
||||
):
|
||||
ptz_service = _setup_ptz_camera_mocks(mock_onvif_camera_cls)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user