diff --git a/tests/components/reolink/conftest.py b/tests/components/reolink/conftest.py index 981dcc30e60..ddea36cb292 100644 --- a/tests/components/reolink/conftest.py +++ b/tests/components/reolink/conftest.py @@ -53,10 +53,6 @@ def mock_setup_entry() -> Generator[AsyncMock]: def reolink_connect_class() -> Generator[MagicMock]: """Mock reolink connection and return both the host_mock and host_mock_class.""" with ( - patch( - "homeassistant.components.reolink.host.webhook.async_register", - return_value=True, - ), patch( "homeassistant.components.reolink.host.Host", autospec=True ) as host_mock_class, diff --git a/tests/components/reolink/test_binary_sensor.py b/tests/components/reolink/test_binary_sensor.py new file mode 100644 index 00000000000..e02742afe1d --- /dev/null +++ b/tests/components/reolink/test_binary_sensor.py @@ -0,0 +1,52 @@ +"""Test the Reolink binary sensor platform.""" + +from unittest.mock import MagicMock, patch + +from freezegun.api import FrozenDateTimeFactory + +from homeassistant.components.reolink import DEVICE_UPDATE_INTERVAL, const +from homeassistant.config_entries import ConfigEntryState +from homeassistant.const import STATE_OFF, STATE_ON, Platform +from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er + +from .conftest import TEST_NVR_NAME, TEST_UID + +from tests.common import MockConfigEntry, async_fire_time_changed +from tests.typing import ClientSessionGenerator + + +async def test_motion_sensor( + hass: HomeAssistant, + hass_client_no_auth: ClientSessionGenerator, + freezer: FrozenDateTimeFactory, + config_entry: MockConfigEntry, + reolink_connect: MagicMock, + entity_registry: er.EntityRegistry, +) -> None: + """Test binary sensor entity with motion sensor.""" + reolink_connect.model = "Reolink Duo PoE" + reolink_connect.motion_detected.return_value = True + with patch("homeassistant.components.reolink.PLATFORMS", [Platform.BINARY_SENSOR]): + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + assert config_entry.state is ConfigEntryState.LOADED + + entity_id = f"{Platform.BINARY_SENSOR}.{TEST_NVR_NAME}_motion_lens_0" + assert hass.states.get(entity_id).state == STATE_ON + + reolink_connect.motion_detected.return_value = False + freezer.tick(DEVICE_UPDATE_INTERVAL) + async_fire_time_changed(hass) + await hass.async_block_till_done() + + assert hass.states.get(entity_id).state == STATE_OFF + + # test webhook callback + reolink_connect.motion_detected.return_value = True + reolink_connect.ONVIF_event_callback.return_value = [0] + webhook_id = f"{const.DOMAIN}_{TEST_UID.replace(':', '')}_ONVIF" + client = await hass_client_no_auth() + await client.post(f"/api/webhook/{webhook_id}", data="test_data") + + assert hass.states.get(entity_id).state == STATE_ON diff --git a/tests/components/reolink/test_config_flow.py b/tests/components/reolink/test_config_flow.py index 6e57a7924e7..55dd0d4fea9 100644 --- a/tests/components/reolink/test_config_flow.py +++ b/tests/components/reolink/test_config_flow.py @@ -1,10 +1,10 @@ """Test the Reolink config flow.""" -from datetime import timedelta import json from typing import Any from unittest.mock import AsyncMock, MagicMock, call +from freezegun.api import FrozenDateTimeFactory import pytest from reolink_aio.exceptions import ApiError, CredentialsInvalidError, ReolinkError @@ -25,7 +25,6 @@ from homeassistant.const import ( from homeassistant.core import HomeAssistant from homeassistant.data_entry_flow import FlowResultType from homeassistant.helpers.device_registry import format_mac -from homeassistant.util.dt import utcnow from .conftest import ( DHCP_FORMATTED_MAC, @@ -439,6 +438,7 @@ async def test_dhcp_flow(hass: HomeAssistant, mock_setup_entry: MagicMock) -> No ) async def test_dhcp_ip_update( hass: HomeAssistant, + freezer: FrozenDateTimeFactory, reolink_connect_class: MagicMock, reolink_connect: MagicMock, last_update_success: bool, @@ -472,9 +472,8 @@ async def test_dhcp_ip_update( if not last_update_success: # ensure the last_update_succes is False for the device_coordinator. reolink_connect.get_states = AsyncMock(side_effect=ReolinkError("Test error")) - async_fire_time_changed( - hass, utcnow() + DEVICE_UPDATE_INTERVAL + timedelta(minutes=1) - ) + freezer.tick(DEVICE_UPDATE_INTERVAL) + async_fire_time_changed(hass) await hass.async_block_till_done() dhcp_data = dhcp.DhcpServiceInfo( diff --git a/tests/components/reolink/test_host.py b/tests/components/reolink/test_host.py new file mode 100644 index 00000000000..690bfd035f8 --- /dev/null +++ b/tests/components/reolink/test_host.py @@ -0,0 +1,85 @@ +"""Test the Reolink host.""" + +from asyncio import CancelledError +from unittest.mock import AsyncMock, MagicMock + +from aiohttp import ClientResponseError +import pytest + +from homeassistant.components.reolink import const +from homeassistant.components.webhook import async_handle_webhook +from homeassistant.config_entries import ConfigEntryState +from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er +from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.util.aiohttp import MockRequest + +from .conftest import TEST_UID + +from tests.common import MockConfigEntry +from tests.typing import ClientSessionGenerator + + +async def test_webhook_callback( + hass: HomeAssistant, + hass_client_no_auth: ClientSessionGenerator, + config_entry: MockConfigEntry, + reolink_connect: MagicMock, + entity_registry: er.EntityRegistry, +) -> None: + """Test webhook callback with motion sensor.""" + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + assert config_entry.state is ConfigEntryState.LOADED + + webhook_id = f"{const.DOMAIN}_{TEST_UID.replace(':', '')}_ONVIF" + + signal_all = MagicMock() + signal_ch = MagicMock() + async_dispatcher_connect(hass, f"{webhook_id}_all", signal_all) + async_dispatcher_connect(hass, f"{webhook_id}_0", signal_ch) + + client = await hass_client_no_auth() + + # test webhook callback success all channels + reolink_connect.ONVIF_event_callback.return_value = None + await client.post(f"/api/webhook/{webhook_id}") + signal_all.assert_called_once() + + # test webhook callback all channels with failure to read motion_state + signal_all.reset_mock() + reolink_connect.get_motion_state_all_ch.return_value = False + await client.post(f"/api/webhook/{webhook_id}") + signal_all.assert_not_called() + + # test webhook callback success single channel + reolink_connect.ONVIF_event_callback.return_value = [0] + await client.post(f"/api/webhook/{webhook_id}", data="test_data") + signal_ch.assert_called_once() + + # test webhook callback single channel with error in event callback + signal_ch.reset_mock() + reolink_connect.ONVIF_event_callback = AsyncMock( + side_effect=Exception("Test error") + ) + await client.post(f"/api/webhook/{webhook_id}", data="test_data") + signal_ch.assert_not_called() + + # test failure to read date from webhook post + request = MockRequest( + method="POST", + content=bytes("test", "utf-8"), + mock_source="test", + ) + request.read = AsyncMock(side_effect=ConnectionResetError("Test error")) + await async_handle_webhook(hass, webhook_id, request) + signal_all.assert_not_called() + + request.read = AsyncMock(side_effect=ClientResponseError("Test error", "Test")) + await async_handle_webhook(hass, webhook_id, request) + signal_all.assert_not_called() + + request.read = AsyncMock(side_effect=CancelledError("Test error")) + with pytest.raises(CancelledError): + await async_handle_webhook(hass, webhook_id, request) + signal_all.assert_not_called() diff --git a/tests/components/reolink/test_init.py b/tests/components/reolink/test_init.py index 1c93114217c..f5cd56a05d2 100644 --- a/tests/components/reolink/test_init.py +++ b/tests/components/reolink/test_init.py @@ -1,10 +1,10 @@ """Test the Reolink init.""" import asyncio -from datetime import timedelta from typing import Any from unittest.mock import AsyncMock, MagicMock, Mock, patch +from freezegun.api import FrozenDateTimeFactory import pytest from reolink_aio.api import Chime from reolink_aio.exceptions import CredentialsInvalidError, ReolinkError @@ -25,7 +25,6 @@ from homeassistant.helpers import ( issue_registry as ir, ) from homeassistant.setup import async_setup_component -from homeassistant.util.dt import utcnow from .conftest import ( TEST_CAM_MODEL, @@ -104,6 +103,7 @@ async def test_failures_parametrized( async def test_firmware_error_twice( hass: HomeAssistant, + freezer: FrozenDateTimeFactory, reolink_connect: MagicMock, config_entry: MockConfigEntry, ) -> None: @@ -112,31 +112,31 @@ async def test_firmware_error_twice( side_effect=ReolinkError("Test error") ) with patch("homeassistant.components.reolink.PLATFORMS", [Platform.UPDATE]): - assert await hass.config_entries.async_setup(config_entry.entry_id) is True + assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() assert config_entry.state is ConfigEntryState.LOADED entity_id = f"{Platform.UPDATE}.{TEST_NVR_NAME}_firmware" - assert hass.states.is_state(entity_id, STATE_OFF) + assert hass.states.get(entity_id).state == STATE_OFF - async_fire_time_changed( - hass, utcnow() + FIRMWARE_UPDATE_INTERVAL + timedelta(minutes=1) - ) + freezer.tick(FIRMWARE_UPDATE_INTERVAL) + async_fire_time_changed(hass) await hass.async_block_till_done() - assert hass.states.is_state(entity_id, STATE_UNAVAILABLE) + assert hass.states.get(entity_id).state == STATE_UNAVAILABLE async def test_credential_error_three( hass: HomeAssistant, + freezer: FrozenDateTimeFactory, reolink_connect: MagicMock, config_entry: MockConfigEntry, issue_registry: ir.IssueRegistry, ) -> None: """Test when the update gives credential error 3 times.""" with patch("homeassistant.components.reolink.PLATFORMS", [Platform.SWITCH]): - assert await hass.config_entries.async_setup(config_entry.entry_id) is True + assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() assert config_entry.state is ConfigEntryState.LOADED @@ -147,9 +147,8 @@ async def test_credential_error_three( issue_id = f"config_entry_reauth_{const.DOMAIN}_{config_entry.entry_id}" for _ in range(NUM_CRED_ERRORS): assert (HOMEASSISTANT_DOMAIN, issue_id) not in issue_registry.issues - async_fire_time_changed( - hass, utcnow() + DEVICE_UPDATE_INTERVAL + timedelta(seconds=30) - ) + freezer.tick(DEVICE_UPDATE_INTERVAL) + async_fire_time_changed(hass) await hass.async_block_till_done() assert (HOMEASSISTANT_DOMAIN, issue_id) in issue_registry.issues diff --git a/tests/components/reolink/test_media_source.py b/tests/components/reolink/test_media_source.py index 66ed32ca823..b09c267fcfd 100644 --- a/tests/components/reolink/test_media_source.py +++ b/tests/components/reolink/test_media_source.py @@ -275,7 +275,7 @@ async def test_browsing_rec_playback_unsupported( reolink_connect.api_version.return_value = 0 with patch("homeassistant.components.reolink.PLATFORMS", [Platform.CAMERA]): - assert await hass.config_entries.async_setup(config_entry.entry_id) is True + assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() # browse root @@ -296,7 +296,7 @@ async def test_browsing_errors( reolink_connect.api_version.return_value = 1 with patch("homeassistant.components.reolink.PLATFORMS", [Platform.CAMERA]): - assert await hass.config_entries.async_setup(config_entry.entry_id) is True + assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() # browse root @@ -315,7 +315,7 @@ async def test_browsing_not_loaded( reolink_connect.api_version.return_value = 1 with patch("homeassistant.components.reolink.PLATFORMS", [Platform.CAMERA]): - assert await hass.config_entries.async_setup(config_entry.entry_id) is True + assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() reolink_connect.get_host_data = AsyncMock(side_effect=ReolinkError("Test error")) diff --git a/tests/components/reolink/test_select.py b/tests/components/reolink/test_select.py index 5536797d7d3..5536e85afb9 100644 --- a/tests/components/reolink/test_select.py +++ b/tests/components/reolink/test_select.py @@ -1,8 +1,8 @@ """Test the Reolink select platform.""" -from datetime import timedelta from unittest.mock import AsyncMock, MagicMock, patch +from freezegun.api import FrozenDateTimeFactory import pytest from reolink_aio.api import Chime from reolink_aio.exceptions import InvalidParameterError, ReolinkError @@ -19,7 +19,6 @@ from homeassistant.const import ( from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError, ServiceValidationError from homeassistant.helpers import entity_registry as er -from homeassistant.util.dt import utcnow from .conftest import TEST_NVR_NAME @@ -28,18 +27,19 @@ from tests.common import MockConfigEntry, async_fire_time_changed async def test_floodlight_mode_select( hass: HomeAssistant, + freezer: FrozenDateTimeFactory, config_entry: MockConfigEntry, reolink_connect: MagicMock, entity_registry: er.EntityRegistry, ) -> None: """Test select entity with floodlight_mode.""" with patch("homeassistant.components.reolink.PLATFORMS", [Platform.SELECT]): - assert await hass.config_entries.async_setup(config_entry.entry_id) is True + assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() assert config_entry.state is ConfigEntryState.LOADED entity_id = f"{Platform.SELECT}.{TEST_NVR_NAME}_floodlight_mode" - assert hass.states.is_state(entity_id, "auto") + assert hass.states.get(entity_id).state == "auto" reolink_connect.set_whiteled = AsyncMock() await hass.services.async_call( @@ -71,12 +71,11 @@ async def test_floodlight_mode_select( ) reolink_connect.whiteled_mode.return_value = -99 # invalid value - async_fire_time_changed( - hass, utcnow() + DEVICE_UPDATE_INTERVAL + timedelta(seconds=30) - ) + freezer.tick(DEVICE_UPDATE_INTERVAL) + async_fire_time_changed(hass) await hass.async_block_till_done() - assert hass.states.is_state(entity_id, STATE_UNKNOWN) + assert hass.states.get(entity_id).state == STATE_UNKNOWN async def test_play_quick_reply_message( @@ -88,12 +87,12 @@ async def test_play_quick_reply_message( """Test select play_quick_reply_message entity.""" reolink_connect.quick_reply_dict.return_value = {0: "off", 1: "test message"} with patch("homeassistant.components.reolink.PLATFORMS", [Platform.SELECT]): - assert await hass.config_entries.async_setup(config_entry.entry_id) is True + assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() assert config_entry.state is ConfigEntryState.LOADED entity_id = f"{Platform.SELECT}.{TEST_NVR_NAME}_play_quick_reply_message" - assert hass.states.is_state(entity_id, STATE_UNKNOWN) + assert hass.states.get(entity_id).state == STATE_UNKNOWN reolink_connect.play_quick_reply = AsyncMock() await hass.services.async_call( @@ -107,6 +106,7 @@ async def test_play_quick_reply_message( async def test_chime_select( hass: HomeAssistant, + freezer: FrozenDateTimeFactory, config_entry: MockConfigEntry, reolink_connect: MagicMock, test_chime: Chime, @@ -114,13 +114,13 @@ async def test_chime_select( ) -> None: """Test chime select entity.""" with patch("homeassistant.components.reolink.PLATFORMS", [Platform.SELECT]): - assert await hass.config_entries.async_setup(config_entry.entry_id) is True + assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() assert config_entry.state is ConfigEntryState.LOADED entity_id = f"{Platform.SELECT}.test_chime_visitor_ringtone" - assert hass.states.is_state(entity_id, "pianokey") + assert hass.states.get(entity_id).state == "pianokey" test_chime.set_tone = AsyncMock() await hass.services.async_call( @@ -150,9 +150,8 @@ async def test_chime_select( ) test_chime.event_info = {} - async_fire_time_changed( - hass, utcnow() + DEVICE_UPDATE_INTERVAL + timedelta(seconds=30) - ) + freezer.tick(DEVICE_UPDATE_INTERVAL) + async_fire_time_changed(hass) await hass.async_block_till_done() - assert hass.states.is_state(entity_id, STATE_UNKNOWN) + assert hass.states.get(entity_id).state == STATE_UNKNOWN