Reolink add 100% coverage of binary_sensor platfrom (#123862)

* Implement 100% coverage of binary_sensor

* fix styling

* Apply suggestions from code review

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>

* use get().state instead of is_state

* Remove unneeded "is True"

* Remove unneeded "is True"

* reset the mock and use assert_not_called

* use freezer

* fix styling

* fix styling

---------

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
This commit is contained in:
starkillerOG 2024-08-16 12:46:51 +02:00 committed by GitHub
parent 461ef33553
commit 0093276e93
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 170 additions and 40 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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