Use non-autospec mock for Reolink's host tests (#147619)

This commit is contained in:
Abílio Costa 2025-06-26 19:55:46 +01:00 committed by GitHub
parent babecdf32c
commit 06d04c001d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 59 additions and 86 deletions

View File

@ -81,6 +81,7 @@ def _init_host_mock(host_mock: MagicMock) -> None:
host_mock.set_audio = AsyncMock() host_mock.set_audio = AsyncMock()
host_mock.set_email = AsyncMock() host_mock.set_email = AsyncMock()
host_mock.ONVIF_event_callback = AsyncMock() host_mock.ONVIF_event_callback = AsyncMock()
host_mock.renew = AsyncMock()
host_mock.is_nvr = True host_mock.is_nvr = True
host_mock.is_hub = False host_mock.is_hub = False
host_mock.mac_address = TEST_MAC host_mock.mac_address = TEST_MAC

View File

@ -39,11 +39,10 @@ async def test_setup_with_tcp_push(
hass: HomeAssistant, hass: HomeAssistant,
freezer: FrozenDateTimeFactory, freezer: FrozenDateTimeFactory,
config_entry: MockConfigEntry, config_entry: MockConfigEntry,
reolink_connect: MagicMock, reolink_host: MagicMock,
) -> None: ) -> None:
"""Test successful setup of the integration with TCP push callbacks.""" """Test successful setup of the integration with TCP push callbacks."""
reolink_connect.baichuan.events_active = True reolink_host.baichuan.events_active = True
reolink_connect.baichuan.subscribe_events.reset_mock(side_effect=True)
with patch("homeassistant.components.reolink.PLATFORMS", [Platform.BINARY_SENSOR]): with patch("homeassistant.components.reolink.PLATFORMS", [Platform.BINARY_SENSOR]):
assert await hass.config_entries.async_setup(config_entry.entry_id) assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done() await hass.async_block_till_done()
@ -54,47 +53,39 @@ async def test_setup_with_tcp_push(
await hass.async_block_till_done() await hass.async_block_till_done()
# ONVIF push subscription not called # ONVIF push subscription not called
assert not reolink_connect.subscribe.called assert not reolink_host.subscribe.called
reolink_connect.baichuan.events_active = False
reolink_connect.baichuan.subscribe_events.side_effect = ReolinkError("Test error")
async def test_unloading_with_tcp_push( async def test_unloading_with_tcp_push(
hass: HomeAssistant, hass: HomeAssistant,
config_entry: MockConfigEntry, config_entry: MockConfigEntry,
reolink_connect: MagicMock, reolink_host: MagicMock,
) -> None: ) -> None:
"""Test successful unloading of the integration with TCP push callbacks.""" """Test successful unloading of the integration with TCP push callbacks."""
reolink_connect.baichuan.events_active = True reolink_host.baichuan.events_active = True
reolink_connect.baichuan.subscribe_events.reset_mock(side_effect=True)
with patch("homeassistant.components.reolink.PLATFORMS", [Platform.BINARY_SENSOR]): with patch("homeassistant.components.reolink.PLATFORMS", [Platform.BINARY_SENSOR]):
assert await hass.config_entries.async_setup(config_entry.entry_id) assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done() await hass.async_block_till_done()
assert config_entry.state is ConfigEntryState.LOADED assert config_entry.state is ConfigEntryState.LOADED
reolink_connect.baichuan.unsubscribe_events.side_effect = ReolinkError("Test error") reolink_host.baichuan.unsubscribe_events.side_effect = ReolinkError("Test error")
# Unload the config entry # Unload the config entry
assert await hass.config_entries.async_unload(config_entry.entry_id) assert await hass.config_entries.async_unload(config_entry.entry_id)
await hass.async_block_till_done() await hass.async_block_till_done()
assert config_entry.state is ConfigEntryState.NOT_LOADED assert config_entry.state is ConfigEntryState.NOT_LOADED
reolink_connect.baichuan.events_active = False
reolink_connect.baichuan.subscribe_events.side_effect = ReolinkError("Test error")
reolink_connect.baichuan.unsubscribe_events.reset_mock(side_effect=True)
async def test_webhook_callback( async def test_webhook_callback(
hass: HomeAssistant, hass: HomeAssistant,
hass_client_no_auth: ClientSessionGenerator, hass_client_no_auth: ClientSessionGenerator,
freezer: FrozenDateTimeFactory, freezer: FrozenDateTimeFactory,
config_entry: MockConfigEntry, config_entry: MockConfigEntry,
reolink_connect: MagicMock, reolink_host: MagicMock,
entity_registry: er.EntityRegistry, entity_registry: er.EntityRegistry,
) -> None: ) -> None:
"""Test webhook callback with motion sensor.""" """Test webhook callback with motion sensor."""
reolink_connect.motion_detected.return_value = False reolink_host.motion_detected.return_value = False
with patch("homeassistant.components.reolink.PLATFORMS", [Platform.BINARY_SENSOR]): with patch("homeassistant.components.reolink.PLATFORMS", [Platform.BINARY_SENSOR]):
assert await hass.config_entries.async_setup(config_entry.entry_id) assert await hass.config_entries.async_setup(config_entry.entry_id)
@ -115,9 +106,9 @@ async def test_webhook_callback(
assert hass.states.get(entity_id).state == STATE_OFF assert hass.states.get(entity_id).state == STATE_OFF
# test webhook callback success all channels # test webhook callback success all channels
reolink_connect.get_motion_state_all_ch.return_value = True reolink_host.get_motion_state_all_ch.return_value = True
reolink_connect.motion_detected.return_value = True reolink_host.motion_detected.return_value = True
reolink_connect.ONVIF_event_callback.return_value = None reolink_host.ONVIF_event_callback.return_value = None
await client.post(f"/api/webhook/{webhook_id}") await client.post(f"/api/webhook/{webhook_id}")
await hass.async_block_till_done() await hass.async_block_till_done()
signal_all.assert_called_once() signal_all.assert_called_once()
@ -129,7 +120,7 @@ async def test_webhook_callback(
# test webhook callback all channels with failure to read motion_state # test webhook callback all channels with failure to read motion_state
signal_all.reset_mock() signal_all.reset_mock()
reolink_connect.get_motion_state_all_ch.return_value = False reolink_host.get_motion_state_all_ch.return_value = False
await client.post(f"/api/webhook/{webhook_id}") await client.post(f"/api/webhook/{webhook_id}")
await hass.async_block_till_done() await hass.async_block_till_done()
signal_all.assert_not_called() signal_all.assert_not_called()
@ -137,8 +128,8 @@ async def test_webhook_callback(
assert hass.states.get(entity_id).state == STATE_ON assert hass.states.get(entity_id).state == STATE_ON
# test webhook callback success single channel # test webhook callback success single channel
reolink_connect.motion_detected.return_value = False reolink_host.motion_detected.return_value = False
reolink_connect.ONVIF_event_callback.return_value = [0] reolink_host.ONVIF_event_callback.return_value = [0]
await client.post(f"/api/webhook/{webhook_id}", data="test_data") await client.post(f"/api/webhook/{webhook_id}", data="test_data")
await hass.async_block_till_done() await hass.async_block_till_done()
signal_ch.assert_called_once() signal_ch.assert_called_once()
@ -146,7 +137,7 @@ async def test_webhook_callback(
# test webhook callback single channel with error in event callback # test webhook callback single channel with error in event callback
signal_ch.reset_mock() signal_ch.reset_mock()
reolink_connect.ONVIF_event_callback.side_effect = Exception("Test error") reolink_host.ONVIF_event_callback.side_effect = Exception("Test error")
await client.post(f"/api/webhook/{webhook_id}", data="test_data") await client.post(f"/api/webhook/{webhook_id}", data="test_data")
await hass.async_block_till_done() await hass.async_block_till_done()
signal_ch.assert_not_called() signal_ch.assert_not_called()
@ -171,45 +162,42 @@ async def test_webhook_callback(
await async_handle_webhook(hass, webhook_id, request) await async_handle_webhook(hass, webhook_id, request)
signal_all.assert_not_called() signal_all.assert_not_called()
reolink_connect.ONVIF_event_callback.reset_mock(side_effect=True)
async def test_no_mac( async def test_no_mac(
hass: HomeAssistant, hass: HomeAssistant,
config_entry: MockConfigEntry, config_entry: MockConfigEntry,
reolink_connect: MagicMock, reolink_host: MagicMock,
) -> None: ) -> None:
"""Test setup of host with no mac.""" """Test setup of host with no mac."""
original = reolink_connect.mac_address original = reolink_host.mac_address
reolink_connect.mac_address = None reolink_host.mac_address = None
assert not await hass.config_entries.async_setup(config_entry.entry_id) assert not await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done() await hass.async_block_till_done()
assert config_entry.state is ConfigEntryState.SETUP_RETRY assert config_entry.state is ConfigEntryState.SETUP_RETRY
reolink_connect.mac_address = original reolink_host.mac_address = original
async def test_subscribe_error( async def test_subscribe_error(
hass: HomeAssistant, hass: HomeAssistant,
config_entry: MockConfigEntry, config_entry: MockConfigEntry,
reolink_connect: MagicMock, reolink_host: MagicMock,
) -> None: ) -> None:
"""Test error when subscribing to ONVIF does not block startup.""" """Test error when subscribing to ONVIF does not block startup."""
reolink_connect.subscribe.side_effect = ReolinkError("Test Error") reolink_host.subscribe.side_effect = ReolinkError("Test Error")
reolink_connect.subscribed.return_value = False reolink_host.subscribed.return_value = False
assert await hass.config_entries.async_setup(config_entry.entry_id) assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done() await hass.async_block_till_done()
assert config_entry.state is ConfigEntryState.LOADED assert config_entry.state is ConfigEntryState.LOADED
reolink_connect.subscribe.reset_mock(side_effect=True)
async def test_subscribe_unsuccesfull( async def test_subscribe_unsuccesfull(
hass: HomeAssistant, hass: HomeAssistant,
config_entry: MockConfigEntry, config_entry: MockConfigEntry,
reolink_connect: MagicMock, reolink_host: MagicMock,
) -> None: ) -> None:
"""Test that a unsuccessful ONVIF subscription does not block startup.""" """Test that a unsuccessful ONVIF subscription does not block startup."""
reolink_connect.subscribed.return_value = False reolink_host.subscribed.return_value = False
assert await hass.config_entries.async_setup(config_entry.entry_id) assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done() await hass.async_block_till_done()
assert config_entry.state is ConfigEntryState.LOADED assert config_entry.state is ConfigEntryState.LOADED
@ -218,7 +206,7 @@ async def test_subscribe_unsuccesfull(
async def test_initial_ONVIF_not_supported( async def test_initial_ONVIF_not_supported(
hass: HomeAssistant, hass: HomeAssistant,
config_entry: MockConfigEntry, config_entry: MockConfigEntry,
reolink_connect: MagicMock, reolink_host: MagicMock,
) -> None: ) -> None:
"""Test setup when initial ONVIF is not supported.""" """Test setup when initial ONVIF is not supported."""
@ -228,7 +216,7 @@ async def test_initial_ONVIF_not_supported(
return False return False
return True return True
reolink_connect.supported = test_supported reolink_host.supported = test_supported
assert await hass.config_entries.async_setup(config_entry.entry_id) assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done() await hass.async_block_till_done()
@ -238,7 +226,7 @@ async def test_initial_ONVIF_not_supported(
async def test_ONVIF_not_supported( async def test_ONVIF_not_supported(
hass: HomeAssistant, hass: HomeAssistant,
config_entry: MockConfigEntry, config_entry: MockConfigEntry,
reolink_connect: MagicMock, reolink_host: MagicMock,
) -> None: ) -> None:
"""Test setup is not blocked when ONVIF API returns NotSupportedError.""" """Test setup is not blocked when ONVIF API returns NotSupportedError."""
@ -248,26 +236,23 @@ async def test_ONVIF_not_supported(
return False return False
return True return True
reolink_connect.supported = test_supported reolink_host.supported = test_supported
reolink_connect.subscribed.return_value = False reolink_host.subscribed.return_value = False
reolink_connect.subscribe.side_effect = NotSupportedError("Test error") reolink_host.subscribe.side_effect = NotSupportedError("Test error")
assert await hass.config_entries.async_setup(config_entry.entry_id) assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done() await hass.async_block_till_done()
assert config_entry.state is ConfigEntryState.LOADED assert config_entry.state is ConfigEntryState.LOADED
reolink_connect.subscribe.reset_mock(side_effect=True)
reolink_connect.subscribed.return_value = True
async def test_renew( async def test_renew(
hass: HomeAssistant, hass: HomeAssistant,
freezer: FrozenDateTimeFactory, freezer: FrozenDateTimeFactory,
config_entry: MockConfigEntry, config_entry: MockConfigEntry,
reolink_connect: MagicMock, reolink_host: MagicMock,
) -> None: ) -> None:
"""Test renew of the ONVIF subscription.""" """Test renew of the ONVIF subscription."""
reolink_connect.renewtimer.return_value = 1 reolink_host.renewtimer.return_value = 1
assert await hass.config_entries.async_setup(config_entry.entry_id) assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done() await hass.async_block_till_done()
@ -277,56 +262,51 @@ async def test_renew(
async_fire_time_changed(hass) async_fire_time_changed(hass)
await hass.async_block_till_done() await hass.async_block_till_done()
reolink_connect.renew.assert_called() reolink_host.renew.assert_called()
reolink_connect.renew.side_effect = SubscriptionError("Test error") reolink_host.renew.side_effect = SubscriptionError("Test error")
freezer.tick(DEVICE_UPDATE_INTERVAL) freezer.tick(DEVICE_UPDATE_INTERVAL)
async_fire_time_changed(hass) async_fire_time_changed(hass)
await hass.async_block_till_done() await hass.async_block_till_done()
reolink_connect.subscribe.assert_called() reolink_host.subscribe.assert_called()
reolink_connect.subscribe.reset_mock() reolink_host.subscribe.reset_mock()
reolink_connect.subscribe.side_effect = SubscriptionError("Test error") reolink_host.subscribe.side_effect = SubscriptionError("Test error")
freezer.tick(DEVICE_UPDATE_INTERVAL) freezer.tick(DEVICE_UPDATE_INTERVAL)
async_fire_time_changed(hass) async_fire_time_changed(hass)
await hass.async_block_till_done() await hass.async_block_till_done()
reolink_connect.subscribe.assert_called() reolink_host.subscribe.assert_called()
reolink_connect.renew.reset_mock(side_effect=True)
reolink_connect.subscribe.reset_mock(side_effect=True)
async def test_long_poll_renew_fail( async def test_long_poll_renew_fail(
hass: HomeAssistant, hass: HomeAssistant,
freezer: FrozenDateTimeFactory, freezer: FrozenDateTimeFactory,
config_entry: MockConfigEntry, config_entry: MockConfigEntry,
reolink_connect: MagicMock, reolink_host: MagicMock,
) -> None: ) -> None:
"""Test ONVIF long polling errors while renewing.""" """Test ONVIF long polling errors while renewing."""
assert await hass.config_entries.async_setup(config_entry.entry_id) assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done() await hass.async_block_till_done()
assert config_entry.state is ConfigEntryState.LOADED assert config_entry.state is ConfigEntryState.LOADED
reolink_connect.subscribe.side_effect = NotSupportedError("Test error") reolink_host.subscribe.side_effect = NotSupportedError("Test error")
freezer.tick(timedelta(seconds=FIRST_ONVIF_TIMEOUT)) freezer.tick(timedelta(seconds=FIRST_ONVIF_TIMEOUT))
async_fire_time_changed(hass) async_fire_time_changed(hass)
await hass.async_block_till_done() await hass.async_block_till_done()
# ensure long polling continues # ensure long polling continues
reolink_connect.pull_point_request.assert_called() reolink_host.pull_point_request.assert_called()
reolink_connect.subscribe.reset_mock(side_effect=True)
async def test_register_webhook_errors( async def test_register_webhook_errors(
hass: HomeAssistant, hass: HomeAssistant,
config_entry: MockConfigEntry, config_entry: MockConfigEntry,
reolink_connect: MagicMock, reolink_host: MagicMock,
) -> None: ) -> None:
"""Test errors while registering the webhook.""" """Test errors while registering the webhook."""
with patch( with patch(
@ -343,7 +323,7 @@ async def test_long_poll_stop_when_push(
hass_client_no_auth: ClientSessionGenerator, hass_client_no_auth: ClientSessionGenerator,
freezer: FrozenDateTimeFactory, freezer: FrozenDateTimeFactory,
config_entry: MockConfigEntry, config_entry: MockConfigEntry,
reolink_connect: MagicMock, reolink_host: MagicMock,
) -> None: ) -> None:
"""Test ONVIF long polling stops when ONVIF push comes in.""" """Test ONVIF long polling stops when ONVIF push comes in."""
assert await hass.config_entries.async_setup(config_entry.entry_id) assert await hass.config_entries.async_setup(config_entry.entry_id)
@ -357,7 +337,7 @@ async def test_long_poll_stop_when_push(
# simulate ONVIF push callback # simulate ONVIF push callback
client = await hass_client_no_auth() client = await hass_client_no_auth()
reolink_connect.ONVIF_event_callback.return_value = None reolink_host.ONVIF_event_callback.return_value = None
webhook_id = config_entry.runtime_data.host.webhook_id webhook_id = config_entry.runtime_data.host.webhook_id
await client.post(f"/api/webhook/{webhook_id}") await client.post(f"/api/webhook/{webhook_id}")
@ -365,31 +345,29 @@ async def test_long_poll_stop_when_push(
async_fire_time_changed(hass) async_fire_time_changed(hass)
await hass.async_block_till_done() await hass.async_block_till_done()
reolink_connect.unsubscribe.assert_called_with(sub_type=SubType.long_poll) reolink_host.unsubscribe.assert_called_with(sub_type=SubType.long_poll)
async def test_long_poll_errors( async def test_long_poll_errors(
hass: HomeAssistant, hass: HomeAssistant,
freezer: FrozenDateTimeFactory, freezer: FrozenDateTimeFactory,
config_entry: MockConfigEntry, config_entry: MockConfigEntry,
reolink_connect: MagicMock, reolink_host: MagicMock,
) -> None: ) -> None:
"""Test errors during ONVIF long polling.""" """Test errors during ONVIF long polling."""
reolink_connect.pull_point_request.reset_mock()
assert await hass.config_entries.async_setup(config_entry.entry_id) assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done() await hass.async_block_till_done()
assert config_entry.state is ConfigEntryState.LOADED assert config_entry.state is ConfigEntryState.LOADED
reolink_connect.pull_point_request.side_effect = ReolinkError("Test error") reolink_host.pull_point_request.side_effect = ReolinkError("Test error")
# start ONVIF long polling because ONVIF push did not came in # start ONVIF long polling because ONVIF push did not came in
freezer.tick(timedelta(seconds=FIRST_ONVIF_TIMEOUT)) freezer.tick(timedelta(seconds=FIRST_ONVIF_TIMEOUT))
async_fire_time_changed(hass) async_fire_time_changed(hass)
await hass.async_block_till_done() await hass.async_block_till_done()
reolink_connect.pull_point_request.assert_called_once() reolink_host.pull_point_request.assert_called_once()
reolink_connect.pull_point_request.side_effect = Exception("Test error") reolink_host.pull_point_request.side_effect = Exception("Test error")
freezer.tick(timedelta(seconds=LONG_POLL_ERROR_COOLDOWN)) freezer.tick(timedelta(seconds=LONG_POLL_ERROR_COOLDOWN))
async_fire_time_changed(hass) async_fire_time_changed(hass)
@ -399,21 +377,18 @@ async def test_long_poll_errors(
async_fire_time_changed(hass) async_fire_time_changed(hass)
await hass.async_block_till_done() await hass.async_block_till_done()
reolink_connect.unsubscribe.assert_called_with(sub_type=SubType.long_poll) reolink_host.unsubscribe.assert_called_with(sub_type=SubType.long_poll)
reolink_connect.pull_point_request.reset_mock(side_effect=True)
async def test_fast_polling_errors( async def test_fast_polling_errors(
hass: HomeAssistant, hass: HomeAssistant,
freezer: FrozenDateTimeFactory, freezer: FrozenDateTimeFactory,
config_entry: MockConfigEntry, config_entry: MockConfigEntry,
reolink_connect: MagicMock, reolink_host: MagicMock,
) -> None: ) -> None:
"""Test errors during ONVIF fast polling.""" """Test errors during ONVIF fast polling."""
reolink_connect.get_motion_state_all_ch.reset_mock() reolink_host.get_motion_state_all_ch.side_effect = ReolinkError("Test error")
reolink_connect.get_motion_state_all_ch.side_effect = ReolinkError("Test error") reolink_host.pull_point_request.side_effect = ReolinkError("Test error")
reolink_connect.pull_point_request.side_effect = ReolinkError("Test error")
assert await hass.config_entries.async_setup(config_entry.entry_id) assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done() await hass.async_block_till_done()
@ -429,17 +404,14 @@ async def test_fast_polling_errors(
async_fire_time_changed(hass) async_fire_time_changed(hass)
await hass.async_block_till_done() await hass.async_block_till_done()
assert reolink_connect.get_motion_state_all_ch.call_count == 1 assert reolink_host.get_motion_state_all_ch.call_count == 1
freezer.tick(timedelta(seconds=POLL_INTERVAL_NO_PUSH)) freezer.tick(timedelta(seconds=POLL_INTERVAL_NO_PUSH))
async_fire_time_changed(hass) async_fire_time_changed(hass)
await hass.async_block_till_done() await hass.async_block_till_done()
# fast polling continues despite errors # fast polling continues despite errors
assert reolink_connect.get_motion_state_all_ch.call_count == 2 assert reolink_host.get_motion_state_all_ch.call_count == 2
reolink_connect.get_motion_state_all_ch.reset_mock(side_effect=True)
reolink_connect.pull_point_request.reset_mock(side_effect=True)
async def test_diagnostics_event_connection( async def test_diagnostics_event_connection(
@ -447,7 +419,7 @@ async def test_diagnostics_event_connection(
hass_client: ClientSessionGenerator, hass_client: ClientSessionGenerator,
hass_client_no_auth: ClientSessionGenerator, hass_client_no_auth: ClientSessionGenerator,
freezer: FrozenDateTimeFactory, freezer: FrozenDateTimeFactory,
reolink_connect: MagicMock, reolink_host: MagicMock,
config_entry: MockConfigEntry, config_entry: MockConfigEntry,
) -> None: ) -> None:
"""Test Reolink diagnostics event connection return values.""" """Test Reolink diagnostics event connection return values."""
@ -468,7 +440,7 @@ async def test_diagnostics_event_connection(
# simulate ONVIF push callback # simulate ONVIF push callback
client = await hass_client_no_auth() client = await hass_client_no_auth()
reolink_connect.ONVIF_event_callback.return_value = None reolink_host.ONVIF_event_callback.return_value = None
webhook_id = config_entry.runtime_data.host.webhook_id webhook_id = config_entry.runtime_data.host.webhook_id
await client.post(f"/api/webhook/{webhook_id}") await client.post(f"/api/webhook/{webhook_id}")
@ -476,6 +448,6 @@ async def test_diagnostics_event_connection(
assert diag["event connection"] == "ONVIF push" assert diag["event connection"] == "ONVIF push"
# set TCP push as active # set TCP push as active
reolink_connect.baichuan.events_active = True reolink_host.baichuan.events_active = True
diag = await get_diagnostics_for_config_entry(hass, hass_client, config_entry) diag = await get_diagnostics_for_config_entry(hass, hass_client, config_entry)
assert diag["event connection"] == "TCP push" assert diag["event connection"] == "TCP push"