"""Common fixtures for the Harbor tests.""" from collections.abc import Awaitable, Callable, Generator from types import SimpleNamespace from typing import Any from unittest.mock import AsyncMock, patch import pytest from homeassistant.components.harbor.const import ( CONF_CERT_PEM, CONF_KEY_PEM, CONF_SERIAL, DOMAIN, ) from homeassistant.const import CONF_IP_ADDRESS from tests.common import MockConfigEntry SERIAL = "1234567890" CERT_PEM = "-----BEGIN CERTIFICATE-----\nMIIBdummy\n-----END CERTIFICATE-----" KEY_PEM = "-----BEGIN PRIVATE KEY-----\nMIIBdummy\n-----END PRIVATE KEY-----" HEARTBEAT_TOPIC = f"cameras/{SERIAL}/events/heartbeat" LIVEKIT_TOPIC = f"cameras/{SERIAL}/events/local_livekit_heartbeat" SETTINGS_TOPIC = f"cameras/{SERIAL}/responses/get-settings" SETTINGS_PAYLOAD: dict[str, Any] = { "settings": { "preference_stream_paused": False, "preference_video_flip": True, "preference_video_has_clock_display": False, }, } HEARTBEAT_PAYLOAD: dict[str, Any] = { "temperature": 98.6, "os_version": "1.2.3", "settings": {"preference_display_name": "Nursery"}, } LIVEKIT_PAYLOAD: dict[str, Any] = { "bitrate": 1234.5, "network_bars": 3, "stream_quality": "GOOD", "viewers_by_identity_full": { "viewer-1": {"identity": "alice"}, "viewer-2": {"identity": "bob"}, }, "os_version": "1.2.3", "app_version": "4.5.6", } def connection_callback( mock_mqtt_client: AsyncMock, ) -> Callable[[bool], Awaitable[None]]: """Return the on_connection_change callback the integration registered.""" return mock_mqtt_client.call_args.kwargs["on_connection_change"] async def emit_message( mock_mqtt_client: AsyncMock, topic: str, payload: dict[str, Any] ) -> None: """Deliver an MQTT message through the handler the integration registered.""" await mock_mqtt_client.call_args.kwargs["message_handler"](topic, payload) async def set_connected(mock_mqtt_client: AsyncMock, connected: bool) -> None: """Drive the MQTT connection state the integration observes.""" await connection_callback(mock_mqtt_client)(connected) @pytest.fixture(autouse=True) def mock_connect_timeout() -> Generator[None]: """Patch the connect timeout so unreachable-camera tests run quickly.""" with patch("homeassistant.components.harbor.coordinator.CONNECT_TIMEOUT", 0): yield @pytest.fixture def mock_setup_entry() -> Generator[AsyncMock]: """Override async_setup_entry.""" with patch( "homeassistant.components.harbor.async_setup_entry", return_value=True ) as mock_setup_entry: yield mock_setup_entry @pytest.fixture def mock_mqtt_client() -> Generator[AsyncMock]: """Mock the Harbor MQTT client, reporting a successful connection on start.""" with patch( "homeassistant.components.harbor.coordinator.HarborMQTTClient", autospec=True, ) as mock_client: async def _start() -> None: await set_connected(mock_client, True) # Setup waits for the first device message too; simulate the # initial-commands response landing right after connect, the # same way a real camera answers before any explicit test # message. Empty so it doesn't set values tests don't expect. await mock_client.call_args.kwargs["message_handler"](HEARTBEAT_TOPIC, {}) mock_client.return_value.start.side_effect = _start # The config flow probes get-settings for the camera's friendly name; # default to an unnamed camera so the title falls back to the serial. mock_client.return_value.get_settings.return_value = SimpleNamespace( settings=SimpleNamespace(preference_display_name=None) ) yield mock_client @pytest.fixture def mock_config_entry() -> MockConfigEntry: """Return a mock Harbor config entry.""" return MockConfigEntry( domain=DOMAIN, unique_id=SERIAL, title=f"Camera {SERIAL}", data={ CONF_SERIAL: SERIAL, CONF_CERT_PEM: CERT_PEM, CONF_KEY_PEM: KEY_PEM, CONF_IP_ADDRESS: "192.168.1.10", }, )