diff --git a/tests/components/unifiprotect/__init__.py b/tests/components/unifiprotect/__init__.py index cc59bca3506..51a7e9af177 100644 --- a/tests/components/unifiprotect/__init__.py +++ b/tests/components/unifiprotect/__init__.py @@ -1,9 +1,12 @@ """Tests for the UniFi Protect integration.""" +from collections.abc import Generator from contextlib import contextmanager -from unittest.mock import AsyncMock, MagicMock, patch +from typing import Any +from unittest.mock import AsyncMock, MagicMock, Mock, patch import pytest +from uiprotect.data.base import ProtectModel from unifi_discovery import AIOUnifiScanner, UnifiDevice, UnifiService DEVICE_HOSTNAME = "unvr" @@ -46,3 +49,26 @@ def _patch_discovery(device=None, no_device=False): yield return _patcher() + + +@contextmanager +def patch_ufp_method( + obj: ProtectModel, method: str, *args: Any, **kwargs: Any +) -> Generator[MagicMock]: + """Patch a method on a UniFi Protect pydantic model. + + Pydantic models have frozen fields that cannot be directly patched. + This context manager temporarily modifies the field descriptor to allow + patching. + + Note: The field modification is intentionally not restored, as test fixtures + create fresh model instances for each test. + + Usage: + with patch_ufp_method(doorbell, "set_lcd_text", new_callable=AsyncMock) as mock: + await hass.services.async_call(...) + mock.assert_called_once_with(...) + """ + obj.__pydantic_fields__[method] = Mock(final=False, frozen=False) + with patch.object(obj, method, *args, **kwargs) as mock_method: + yield mock_method diff --git a/tests/components/unifiprotect/test_camera.py b/tests/components/unifiprotect/test_camera.py index 717f2c3a392..e76de9ea151 100644 --- a/tests/components/unifiprotect/test_camera.py +++ b/tests/components/unifiprotect/test_camera.py @@ -44,6 +44,7 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.setup import async_setup_component +from . import patch_ufp_method from .utils import ( Camera, MockUFPFixture, @@ -615,14 +616,14 @@ async def test_camera_motion_detection( assert_entity_counts(hass, Platform.CAMERA, 2, 1) entity_id = "camera.test_camera_high_resolution_channel" - camera.__pydantic_fields__["set_motion_detection"] = Mock(final=False, frozen=False) - camera.set_motion_detection = AsyncMock() + with patch_ufp_method( + camera, "set_motion_detection", new_callable=AsyncMock + ) as mock_method: + await hass.services.async_call( + "camera", + service, + {ATTR_ENTITY_ID: entity_id}, + blocking=True, + ) - await hass.services.async_call( - "camera", - service, - {ATTR_ENTITY_ID: entity_id}, - blocking=True, - ) - - camera.set_motion_detection.assert_called_once_with(expected_value) + mock_method.assert_called_once_with(expected_value) diff --git a/tests/components/unifiprotect/test_lock.py b/tests/components/unifiprotect/test_lock.py index 9095c092ea2..94fecb36da2 100644 --- a/tests/components/unifiprotect/test_lock.py +++ b/tests/components/unifiprotect/test_lock.py @@ -17,6 +17,7 @@ from homeassistant.const import ( from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er +from . import patch_ufp_method from .utils import ( MockUFPFixture, adopt_devices, @@ -210,17 +211,17 @@ async def test_lock_do_lock( await init_entry(hass, ufp, [doorlock, unadopted_doorlock]) assert_entity_counts(hass, Platform.LOCK, 1, 1) - doorlock.__pydantic_fields__["close_lock"] = Mock(final=False, frozen=False) - doorlock.close_lock = AsyncMock() + with patch_ufp_method( + doorlock, "close_lock", new_callable=AsyncMock + ) as mock_method: + await hass.services.async_call( + "lock", + "lock", + {ATTR_ENTITY_ID: "lock.test_lock_lock"}, + blocking=True, + ) - await hass.services.async_call( - "lock", - "lock", - {ATTR_ENTITY_ID: "lock.test_lock_lock"}, - blocking=True, - ) - - doorlock.close_lock.assert_called_once() + mock_method.assert_called_once() async def test_lock_do_unlock( @@ -245,14 +246,12 @@ async def test_lock_do_unlock( ufp.ws_msg(mock_msg) await hass.async_block_till_done() - doorlock.__pydantic_fields__["open_lock"] = Mock(final=False, frozen=False) - new_lock.open_lock = AsyncMock() + with patch_ufp_method(new_lock, "open_lock", new_callable=AsyncMock) as mock_method: + await hass.services.async_call( + "lock", + "unlock", + {ATTR_ENTITY_ID: "lock.test_lock_lock"}, + blocking=True, + ) - await hass.services.async_call( - "lock", - "unlock", - {ATTR_ENTITY_ID: "lock.test_lock_lock"}, - blocking=True, - ) - - new_lock.open_lock.assert_called_once() + mock_method.assert_called_once() diff --git a/tests/components/unifiprotect/test_media_player.py b/tests/components/unifiprotect/test_media_player.py index 0d5e8d4ed4b..f163687a2c6 100644 --- a/tests/components/unifiprotect/test_media_player.py +++ b/tests/components/unifiprotect/test_media_player.py @@ -25,6 +25,7 @@ from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import entity_registry as er +from . import patch_ufp_method from .utils import ( MockUFPFixture, adopt_devices, @@ -116,17 +117,17 @@ async def test_media_player_set_volume( await init_entry(hass, ufp, [doorbell, unadopted_camera]) assert_entity_counts(hass, Platform.MEDIA_PLAYER, 1, 1) - doorbell.__pydantic_fields__["set_speaker_volume"] = Mock(final=False, frozen=False) - doorbell.set_speaker_volume = AsyncMock() + with patch_ufp_method( + doorbell, "set_speaker_volume", new_callable=AsyncMock + ) as mock_method: + await hass.services.async_call( + "media_player", + "volume_set", + {ATTR_ENTITY_ID: "media_player.test_camera_speaker", "volume_level": 0.5}, + blocking=True, + ) - await hass.services.async_call( - "media_player", - "volume_set", - {ATTR_ENTITY_ID: "media_player.test_camera_speaker", "volume_level": 0.5}, - blocking=True, - ) - - doorbell.set_speaker_volume.assert_called_once_with(50) + mock_method.assert_called_once_with(50) async def test_media_player_stop( @@ -173,30 +174,26 @@ async def test_media_player_play( await init_entry(hass, ufp, [doorbell, unadopted_camera]) assert_entity_counts(hass, Platform.MEDIA_PLAYER, 1, 1) - doorbell.__pydantic_fields__["stop_audio"] = Mock(final=False, frozen=False) - doorbell.__pydantic_fields__["play_audio"] = Mock(final=False, frozen=False) - doorbell.__pydantic_fields__["wait_until_audio_completes"] = Mock( - final=False, frozen=False - ) - doorbell.stop_audio = AsyncMock() - doorbell.play_audio = AsyncMock() - doorbell.wait_until_audio_completes = AsyncMock() + with ( + patch_ufp_method(doorbell, "stop_audio", new_callable=AsyncMock), + patch_ufp_method(doorbell, "play_audio", new_callable=AsyncMock) as mock_play, + patch_ufp_method( + doorbell, "wait_until_audio_completes", new_callable=AsyncMock + ) as mock_wait, + ): + await hass.services.async_call( + "media_player", + "play_media", + { + ATTR_ENTITY_ID: "media_player.test_camera_speaker", + "media_content_id": "http://example.com/test.mp3", + "media_content_type": "music", + }, + blocking=True, + ) - await hass.services.async_call( - "media_player", - "play_media", - { - ATTR_ENTITY_ID: "media_player.test_camera_speaker", - "media_content_id": "http://example.com/test.mp3", - "media_content_type": "music", - }, - blocking=True, - ) - - doorbell.play_audio.assert_called_once_with( - "http://example.com/test.mp3", blocking=False - ) - doorbell.wait_until_audio_completes.assert_called_once() + mock_play.assert_called_once_with("http://example.com/test.mp3", blocking=False) + mock_wait.assert_called_once() async def test_media_player_play_media_source( @@ -210,18 +207,16 @@ async def test_media_player_play_media_source( await init_entry(hass, ufp, [doorbell, unadopted_camera]) assert_entity_counts(hass, Platform.MEDIA_PLAYER, 1, 1) - doorbell.__pydantic_fields__["stop_audio"] = Mock(final=False, frozen=False) - doorbell.__pydantic_fields__["play_audio"] = Mock(final=False, frozen=False) - doorbell.__pydantic_fields__["wait_until_audio_completes"] = Mock( - final=False, frozen=False - ) - doorbell.stop_audio = AsyncMock() - doorbell.play_audio = AsyncMock() - doorbell.wait_until_audio_completes = AsyncMock() - - with patch( - "homeassistant.components.media_source.async_resolve_media", - return_value=Mock(url="http://example.com/test.mp3"), + with ( + patch_ufp_method(doorbell, "stop_audio", new_callable=AsyncMock), + patch_ufp_method(doorbell, "play_audio", new_callable=AsyncMock) as mock_play, + patch_ufp_method( + doorbell, "wait_until_audio_completes", new_callable=AsyncMock + ) as mock_wait, + patch( + "homeassistant.components.media_source.async_resolve_media", + return_value=Mock(url="http://example.com/test.mp3"), + ), ): await hass.services.async_call( "media_player", @@ -234,10 +229,8 @@ async def test_media_player_play_media_source( blocking=True, ) - doorbell.play_audio.assert_called_once_with( - "http://example.com/test.mp3", blocking=False - ) - doorbell.wait_until_audio_completes.assert_called_once() + mock_play.assert_called_once_with("http://example.com/test.mp3", blocking=False) + mock_wait.assert_called_once() async def test_media_player_play_invalid( @@ -251,22 +244,22 @@ async def test_media_player_play_invalid( await init_entry(hass, ufp, [doorbell, unadopted_camera]) assert_entity_counts(hass, Platform.MEDIA_PLAYER, 1, 1) - doorbell.__pydantic_fields__["play_audio"] = Mock(final=False, frozen=False) - doorbell.play_audio = AsyncMock() + with patch_ufp_method( + doorbell, "play_audio", new_callable=AsyncMock + ) as mock_method: + with pytest.raises(HomeAssistantError): + await hass.services.async_call( + "media_player", + "play_media", + { + ATTR_ENTITY_ID: "media_player.test_camera_speaker", + "media_content_id": "/test.png", + "media_content_type": "image", + }, + blocking=True, + ) - with pytest.raises(HomeAssistantError): - await hass.services.async_call( - "media_player", - "play_media", - { - ATTR_ENTITY_ID: "media_player.test_camera_speaker", - "media_content_id": "/test.png", - "media_content_type": "image", - }, - blocking=True, - ) - - assert not doorbell.play_audio.called + assert not mock_method.called async def test_media_player_play_error( @@ -280,24 +273,25 @@ async def test_media_player_play_error( await init_entry(hass, ufp, [doorbell, unadopted_camera]) assert_entity_counts(hass, Platform.MEDIA_PLAYER, 1, 1) - doorbell.__pydantic_fields__["play_audio"] = Mock(final=False, frozen=False) - doorbell.__pydantic_fields__["wait_until_audio_completes"] = Mock( - final=False, frozen=False - ) - doorbell.play_audio = AsyncMock(side_effect=StreamError) - doorbell.wait_until_audio_completes = AsyncMock() + with ( + patch_ufp_method( + doorbell, "play_audio", new_callable=AsyncMock, side_effect=StreamError + ) as mock_play, + patch_ufp_method( + doorbell, "wait_until_audio_completes", new_callable=AsyncMock + ) as mock_wait, + ): + with pytest.raises(HomeAssistantError): + await hass.services.async_call( + "media_player", + "play_media", + { + ATTR_ENTITY_ID: "media_player.test_camera_speaker", + "media_content_id": "/test.mp3", + "media_content_type": "music", + }, + blocking=True, + ) - with pytest.raises(HomeAssistantError): - await hass.services.async_call( - "media_player", - "play_media", - { - ATTR_ENTITY_ID: "media_player.test_camera_speaker", - "media_content_id": "/test.mp3", - "media_content_type": "music", - }, - blocking=True, - ) - - assert doorbell.play_audio.called - assert not doorbell.wait_until_audio_completes.called + assert mock_play.called + assert not mock_wait.called diff --git a/tests/components/unifiprotect/test_number.py b/tests/components/unifiprotect/test_number.py index 7ec104caf73..d308d0199d7 100644 --- a/tests/components/unifiprotect/test_number.py +++ b/tests/components/unifiprotect/test_number.py @@ -3,7 +3,7 @@ from __future__ import annotations from datetime import timedelta -from unittest.mock import AsyncMock, Mock, patch +from unittest.mock import AsyncMock import pytest from uiprotect.data import Camera, Doorlock, IRLEDMode, Light @@ -19,6 +19,7 @@ from homeassistant.const import ATTR_ATTRIBUTION, ATTR_ENTITY_ID, Platform from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er +from . import patch_ufp_method from .utils import ( MockUFPFixture, adopt_devices, @@ -166,18 +167,21 @@ async def test_number_light_sensitivity( description = LIGHT_NUMBERS[0] assert description.ufp_set_method is not None - light.__pydantic_fields__["set_sensitivity"] = Mock(final=False, frozen=False) - light.set_sensitivity = AsyncMock() - _, entity_id = await ids_from_device_description( hass, Platform.NUMBER, light, description ) - await hass.services.async_call( - "number", "set_value", {ATTR_ENTITY_ID: entity_id, "value": 15.0}, blocking=True - ) + with patch_ufp_method( + light, "set_sensitivity", new_callable=AsyncMock + ) as mock_method: + await hass.services.async_call( + "number", + "set_value", + {ATTR_ENTITY_ID: entity_id, "value": 15.0}, + blocking=True, + ) - light.set_sensitivity.assert_called_once_with(15.0) + mock_method.assert_called_once_with(15.0) async def test_number_light_duration( @@ -190,18 +194,19 @@ async def test_number_light_duration( description = LIGHT_NUMBERS[1] - light.__pydantic_fields__["set_duration"] = Mock(final=False, frozen=False) - light.set_duration = AsyncMock() - _, entity_id = await ids_from_device_description( hass, Platform.NUMBER, light, description ) - await hass.services.async_call( - "number", "set_value", {ATTR_ENTITY_ID: entity_id, "value": 15.0}, blocking=True - ) + with patch_ufp_method(light, "set_duration", new_callable=AsyncMock) as mock_method: + await hass.services.async_call( + "number", + "set_value", + {ATTR_ENTITY_ID: entity_id, "value": 15.0}, + blocking=True, + ) - light.set_duration.assert_called_once_with(timedelta(seconds=15.0)) + mock_method.assert_called_once_with(timedelta(seconds=15.0)) @pytest.mark.parametrize("description", CAMERA_NUMBERS) @@ -221,11 +226,9 @@ async def test_number_camera_simple( hass, Platform.NUMBER, camera_all_features, description ) - camera_all_features.__pydantic_fields__[description.ufp_set_method] = Mock( - final=False, frozen=False - ) - mock_method = AsyncMock() - with patch.object(camera_all_features, description.ufp_set_method, mock_method): + with patch_ufp_method( + camera_all_features, description.ufp_set_method, new_callable=AsyncMock + ) as mock_method: await hass.services.async_call( "number", "set_value", @@ -246,17 +249,18 @@ async def test_number_lock_auto_close( description = DOORLOCK_NUMBERS[0] - doorlock.__pydantic_fields__["set_auto_close_time"] = Mock( - final=False, frozen=False - ) - doorlock.set_auto_close_time = AsyncMock() - _, entity_id = await ids_from_device_description( hass, Platform.NUMBER, doorlock, description ) - await hass.services.async_call( - "number", "set_value", {ATTR_ENTITY_ID: entity_id, "value": 15.0}, blocking=True - ) + with patch_ufp_method( + doorlock, "set_auto_close_time", new_callable=AsyncMock + ) as mock_method: + await hass.services.async_call( + "number", + "set_value", + {ATTR_ENTITY_ID: entity_id, "value": 15.0}, + blocking=True, + ) - doorlock.set_auto_close_time.assert_called_once_with(timedelta(seconds=15.0)) + mock_method.assert_called_once_with(timedelta(seconds=15.0)) diff --git a/tests/components/unifiprotect/test_select.py b/tests/components/unifiprotect/test_select.py index 699144a65fc..fdf3b7bb70a 100644 --- a/tests/components/unifiprotect/test_select.py +++ b/tests/components/unifiprotect/test_select.py @@ -31,6 +31,7 @@ from homeassistant.const import ATTR_ATTRIBUTION, ATTR_ENTITY_ID, ATTR_OPTION, P from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er +from . import patch_ufp_method from .utils import ( MockUFPFixture, adopt_devices, @@ -262,8 +263,6 @@ async def test_select_update_doorbell_settings( expected_length += 1 new_nvr = copy(ufp.api.bootstrap.nvr) - new_nvr.__pydantic_fields__["update_all_messages"] = Mock(final=False, frozen=False) - new_nvr.update_all_messages = Mock() new_nvr.doorbell_settings.all_messages = [ *new_nvr.doorbell_settings.all_messages, @@ -277,11 +276,12 @@ async def test_select_update_doorbell_settings( mock_msg.changed_data = {"doorbell_settings": {}} mock_msg.new_obj = new_nvr - ufp.api.bootstrap.nvr = new_nvr - ufp.ws_msg(mock_msg) - await hass.async_block_till_done() + with patch_ufp_method(new_nvr, "update_all_messages") as mock_method: + ufp.api.bootstrap.nvr = new_nvr + ufp.ws_msg(mock_msg) + await hass.async_block_till_done() - new_nvr.update_all_messages.assert_called_once() + mock_method.assert_called_once() state = hass.states.get(entity_id) assert state @@ -334,19 +334,17 @@ async def test_select_set_option_light_motion( hass, Platform.SELECT, light, LIGHT_SELECTS[0] ) - light.__pydantic_fields__["set_light_settings"] = Mock(final=False, frozen=False) - light.set_light_settings = AsyncMock() + with patch_ufp_method( + light, "set_light_settings", new_callable=AsyncMock + ) as mock_method: + await hass.services.async_call( + "select", + "select_option", + {ATTR_ENTITY_ID: entity_id, ATTR_OPTION: LIGHT_MODE_OFF}, + blocking=True, + ) - await hass.services.async_call( - "select", - "select_option", - {ATTR_ENTITY_ID: entity_id, ATTR_OPTION: LIGHT_MODE_OFF}, - blocking=True, - ) - - light.set_light_settings.assert_called_once_with( - LightModeType.MANUAL, enable_at=None - ) + mock_method.assert_called_once_with(LightModeType.MANUAL, enable_at=None) async def test_select_set_option_light_camera( @@ -361,28 +359,28 @@ async def test_select_set_option_light_camera( hass, Platform.SELECT, light, LIGHT_SELECTS[1] ) - light.__pydantic_fields__["set_paired_camera"] = Mock(final=False, frozen=False) - light.set_paired_camera = AsyncMock() - camera = list(light.api.bootstrap.cameras.values())[0] - await hass.services.async_call( - "select", - "select_option", - {ATTR_ENTITY_ID: entity_id, ATTR_OPTION: camera.name}, - blocking=True, - ) + with patch_ufp_method( + light, "set_paired_camera", new_callable=AsyncMock + ) as mock_method: + await hass.services.async_call( + "select", + "select_option", + {ATTR_ENTITY_ID: entity_id, ATTR_OPTION: camera.name}, + blocking=True, + ) - light.set_paired_camera.assert_called_once_with(camera) + mock_method.assert_called_once_with(camera) - await hass.services.async_call( - "select", - "select_option", - {ATTR_ENTITY_ID: entity_id, ATTR_OPTION: "Not Paired"}, - blocking=True, - ) + await hass.services.async_call( + "select", + "select_option", + {ATTR_ENTITY_ID: entity_id, ATTR_OPTION: "Not Paired"}, + blocking=True, + ) - light.set_paired_camera.assert_called_with(None) + mock_method.assert_called_with(None) async def test_select_set_option_camera_recording( @@ -397,17 +395,17 @@ async def test_select_set_option_camera_recording( hass, Platform.SELECT, doorbell, CAMERA_SELECTS[0] ) - doorbell.__pydantic_fields__["set_recording_mode"] = Mock(final=False, frozen=False) - doorbell.set_recording_mode = AsyncMock() + with patch_ufp_method( + doorbell, "set_recording_mode", new_callable=AsyncMock + ) as mock_method: + await hass.services.async_call( + "select", + "select_option", + {ATTR_ENTITY_ID: entity_id, ATTR_OPTION: "never"}, + blocking=True, + ) - await hass.services.async_call( - "select", - "select_option", - {ATTR_ENTITY_ID: entity_id, ATTR_OPTION: "never"}, - blocking=True, - ) - - doorbell.set_recording_mode.assert_called_once_with(RecordingMode.NEVER) + mock_method.assert_called_once_with(RecordingMode.NEVER) async def test_select_set_option_camera_ir( @@ -422,17 +420,17 @@ async def test_select_set_option_camera_ir( hass, Platform.SELECT, doorbell, CAMERA_SELECTS[1] ) - doorbell.__pydantic_fields__["set_ir_led_model"] = Mock(final=False, frozen=False) - doorbell.set_ir_led_model = AsyncMock() + with patch_ufp_method( + doorbell, "set_ir_led_model", new_callable=AsyncMock + ) as mock_method: + await hass.services.async_call( + "select", + "select_option", + {ATTR_ENTITY_ID: entity_id, ATTR_OPTION: "on"}, + blocking=True, + ) - await hass.services.async_call( - "select", - "select_option", - {ATTR_ENTITY_ID: entity_id, ATTR_OPTION: "on"}, - blocking=True, - ) - - doorbell.set_ir_led_model.assert_called_once_with(IRLEDMode.ON) + mock_method.assert_called_once_with(IRLEDMode.ON) async def test_select_set_option_camera_doorbell_custom( @@ -447,19 +445,19 @@ async def test_select_set_option_camera_doorbell_custom( hass, Platform.SELECT, doorbell, CAMERA_SELECTS[2] ) - doorbell.__pydantic_fields__["set_lcd_text"] = Mock(final=False, frozen=False) - doorbell.set_lcd_text = AsyncMock() + with patch_ufp_method( + doorbell, "set_lcd_text", new_callable=AsyncMock + ) as mock_method: + await hass.services.async_call( + "select", + "select_option", + {ATTR_ENTITY_ID: entity_id, ATTR_OPTION: "Test"}, + blocking=True, + ) - await hass.services.async_call( - "select", - "select_option", - {ATTR_ENTITY_ID: entity_id, ATTR_OPTION: "Test"}, - blocking=True, - ) - - doorbell.set_lcd_text.assert_called_once_with( - DoorbellMessageType.CUSTOM_MESSAGE, text="Test" - ) + mock_method.assert_called_once_with( + DoorbellMessageType.CUSTOM_MESSAGE, text="Test" + ) async def test_select_set_option_camera_doorbell_unifi( @@ -474,34 +472,32 @@ async def test_select_set_option_camera_doorbell_unifi( hass, Platform.SELECT, doorbell, CAMERA_SELECTS[2] ) - doorbell.__pydantic_fields__["set_lcd_text"] = Mock(final=False, frozen=False) - doorbell.set_lcd_text = AsyncMock() + with patch_ufp_method( + doorbell, "set_lcd_text", new_callable=AsyncMock + ) as mock_method: + await hass.services.async_call( + "select", + "select_option", + { + ATTR_ENTITY_ID: entity_id, + ATTR_OPTION: "LEAVE PACKAGE AT DOOR", + }, + blocking=True, + ) - await hass.services.async_call( - "select", - "select_option", - { - ATTR_ENTITY_ID: entity_id, - ATTR_OPTION: "LEAVE PACKAGE AT DOOR", - }, - blocking=True, - ) + mock_method.assert_called_once_with(DoorbellMessageType.LEAVE_PACKAGE_AT_DOOR) - doorbell.set_lcd_text.assert_called_once_with( - DoorbellMessageType.LEAVE_PACKAGE_AT_DOOR - ) + await hass.services.async_call( + "select", + "select_option", + { + ATTR_ENTITY_ID: entity_id, + ATTR_OPTION: "Default Message (Welcome)", + }, + blocking=True, + ) - await hass.services.async_call( - "select", - "select_option", - { - ATTR_ENTITY_ID: entity_id, - ATTR_OPTION: "Default Message (Welcome)", - }, - blocking=True, - ) - - doorbell.set_lcd_text.assert_called_with(None) + mock_method.assert_called_with(None) async def test_select_set_option_camera_doorbell_default( @@ -516,20 +512,20 @@ async def test_select_set_option_camera_doorbell_default( hass, Platform.SELECT, doorbell, CAMERA_SELECTS[2] ) - doorbell.__pydantic_fields__["set_lcd_text"] = Mock(final=False, frozen=False) - doorbell.set_lcd_text = AsyncMock() + with patch_ufp_method( + doorbell, "set_lcd_text", new_callable=AsyncMock + ) as mock_method: + await hass.services.async_call( + "select", + "select_option", + { + ATTR_ENTITY_ID: entity_id, + ATTR_OPTION: "Default Message (Welcome)", + }, + blocking=True, + ) - await hass.services.async_call( - "select", - "select_option", - { - ATTR_ENTITY_ID: entity_id, - ATTR_OPTION: "Default Message (Welcome)", - }, - blocking=True, - ) - - doorbell.set_lcd_text.assert_called_once_with(None) + mock_method.assert_called_once_with(None) async def test_select_set_option_viewer( @@ -545,16 +541,16 @@ async def test_select_set_option_viewer( hass, Platform.SELECT, viewer, VIEWER_SELECTS[0] ) - viewer.__pydantic_fields__["set_liveview"] = Mock(final=False, frozen=False) - viewer.set_liveview = AsyncMock() - liveview = list(viewer.api.bootstrap.liveviews.values())[0] - await hass.services.async_call( - "select", - "select_option", - {ATTR_ENTITY_ID: entity_id, ATTR_OPTION: liveview.name}, - blocking=True, - ) + with patch_ufp_method( + viewer, "set_liveview", new_callable=AsyncMock + ) as mock_method: + await hass.services.async_call( + "select", + "select_option", + {ATTR_ENTITY_ID: entity_id, ATTR_OPTION: liveview.name}, + blocking=True, + ) - viewer.set_liveview.assert_called_once_with(liveview) + mock_method.assert_called_once_with(liveview) diff --git a/tests/components/unifiprotect/test_services.py b/tests/components/unifiprotect/test_services.py index 8073715dcd1..f08e7157b83 100644 --- a/tests/components/unifiprotect/test_services.py +++ b/tests/components/unifiprotect/test_services.py @@ -32,6 +32,7 @@ from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import device_registry as dr, entity_registry as er +from . import patch_ufp_method from .utils import MockUFPFixture, init_entry @@ -66,19 +67,18 @@ async def test_global_service_bad_device( """Test global service, invalid device ID.""" nvr = ufp.api.bootstrap.nvr - nvr.__pydantic_fields__["add_custom_doorbell_message"] = Mock( - final=False, frozen=False - ) - nvr.add_custom_doorbell_message = AsyncMock() - with pytest.raises(HomeAssistantError): - await hass.services.async_call( - DOMAIN, - SERVICE_ADD_DOORBELL_TEXT, - {ATTR_DEVICE_ID: "bad_device_id", ATTR_MESSAGE: "Test Message"}, - blocking=True, - ) - assert not nvr.add_custom_doorbell_message.called + with patch_ufp_method( + nvr, "add_custom_doorbell_message", new_callable=AsyncMock + ) as mock_method: + with pytest.raises(HomeAssistantError): + await hass.services.async_call( + DOMAIN, + SERVICE_ADD_DOORBELL_TEXT, + {ATTR_DEVICE_ID: "bad_device_id", ATTR_MESSAGE: "Test Message"}, + blocking=True, + ) + assert not mock_method.called async def test_global_service_exception( @@ -87,19 +87,21 @@ async def test_global_service_exception( """Test global service, unexpected error.""" nvr = ufp.api.bootstrap.nvr - nvr.__pydantic_fields__["add_custom_doorbell_message"] = Mock( - final=False, frozen=False - ) - nvr.add_custom_doorbell_message = AsyncMock(side_effect=BadRequest) - with pytest.raises(HomeAssistantError): - await hass.services.async_call( - DOMAIN, - SERVICE_ADD_DOORBELL_TEXT, - {ATTR_DEVICE_ID: device.id, ATTR_MESSAGE: "Test Message"}, - blocking=True, - ) - assert nvr.add_custom_doorbell_message.called + with patch_ufp_method( + nvr, + "add_custom_doorbell_message", + new_callable=AsyncMock, + side_effect=BadRequest, + ) as mock_method: + with pytest.raises(HomeAssistantError): + await hass.services.async_call( + DOMAIN, + SERVICE_ADD_DOORBELL_TEXT, + {ATTR_DEVICE_ID: device.id, ATTR_MESSAGE: "Test Message"}, + blocking=True, + ) + assert mock_method.called async def test_add_doorbell_text( @@ -108,18 +110,17 @@ async def test_add_doorbell_text( """Test add_doorbell_text service.""" nvr = ufp.api.bootstrap.nvr - nvr.__pydantic_fields__["add_custom_doorbell_message"] = Mock( - final=False, frozen=False - ) - nvr.add_custom_doorbell_message = AsyncMock() - await hass.services.async_call( - DOMAIN, - SERVICE_ADD_DOORBELL_TEXT, - {ATTR_DEVICE_ID: device.id, ATTR_MESSAGE: "Test Message"}, - blocking=True, - ) - nvr.add_custom_doorbell_message.assert_called_once_with("Test Message") + with patch_ufp_method( + nvr, "add_custom_doorbell_message", new_callable=AsyncMock + ) as mock_method: + await hass.services.async_call( + DOMAIN, + SERVICE_ADD_DOORBELL_TEXT, + {ATTR_DEVICE_ID: device.id, ATTR_MESSAGE: "Test Message"}, + blocking=True, + ) + mock_method.assert_called_once_with("Test Message") async def test_remove_doorbell_text( @@ -128,18 +129,17 @@ async def test_remove_doorbell_text( """Test remove_doorbell_text service.""" nvr = ufp.api.bootstrap.nvr - nvr.__pydantic_fields__["remove_custom_doorbell_message"] = Mock( - final=False, frozen=False - ) - nvr.remove_custom_doorbell_message = AsyncMock() - await hass.services.async_call( - DOMAIN, - SERVICE_REMOVE_DOORBELL_TEXT, - {ATTR_DEVICE_ID: subdevice.id, ATTR_MESSAGE: "Test Message"}, - blocking=True, - ) - nvr.remove_custom_doorbell_message.assert_called_once_with("Test Message") + with patch_ufp_method( + nvr, "remove_custom_doorbell_message", new_callable=AsyncMock + ) as mock_method: + await hass.services.async_call( + DOMAIN, + SERVICE_REMOVE_DOORBELL_TEXT, + {ATTR_DEVICE_ID: subdevice.id, ATTR_MESSAGE: "Test Message"}, + blocking=True, + ) + mock_method.assert_called_once_with("Test Message") async def test_add_doorbell_text_disabled_config_entry( @@ -147,24 +147,23 @@ async def test_add_doorbell_text_disabled_config_entry( ) -> None: """Test add_doorbell_text service.""" nvr = ufp.api.bootstrap.nvr - nvr.__pydantic_fields__["add_custom_doorbell_message"] = Mock( - final=False, frozen=False - ) - nvr.add_custom_doorbell_message = AsyncMock() await hass.config_entries.async_set_disabled_by( ufp.entry.entry_id, ConfigEntryDisabler.USER ) await hass.async_block_till_done() - with pytest.raises(HomeAssistantError): - await hass.services.async_call( - DOMAIN, - SERVICE_ADD_DOORBELL_TEXT, - {ATTR_DEVICE_ID: device.id, ATTR_MESSAGE: "Test Message"}, - blocking=True, - ) - assert not nvr.add_custom_doorbell_message.called + with patch_ufp_method( + nvr, "add_custom_doorbell_message", new_callable=AsyncMock + ) as mock_method: + with pytest.raises(HomeAssistantError): + await hass.services.async_call( + DOMAIN, + SERVICE_ADD_DOORBELL_TEXT, + {ATTR_DEVICE_ID: device.id, ATTR_MESSAGE: "Test Message"}, + blocking=True, + ) + assert not mock_method.called async def test_set_chime_paired_doorbells( diff --git a/tests/components/unifiprotect/test_switch.py b/tests/components/unifiprotect/test_switch.py index 0e5efd8a182..852ecb62f7b 100644 --- a/tests/components/unifiprotect/test_switch.py +++ b/tests/components/unifiprotect/test_switch.py @@ -2,7 +2,7 @@ from __future__ import annotations -from unittest.mock import AsyncMock, Mock, patch +from unittest.mock import AsyncMock, Mock import pytest from uiprotect.data import Camera, Light, Permission, RecordingMode, VideoMode @@ -22,6 +22,7 @@ from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import entity_registry as er +from . import patch_ufp_method from .utils import ( MockUFPFixture, adopt_devices, @@ -90,21 +91,20 @@ async def test_switch_nvr(hass: HomeAssistant, ufp: MockUFPFixture) -> None: assert_entity_counts(hass, Platform.SWITCH, 2, 2) nvr = ufp.api.bootstrap.nvr - nvr.__pydantic_fields__["set_insights"] = Mock(final=False, frozen=False) - nvr.set_insights = AsyncMock() entity_id = "switch.unifiprotect_insights_enabled" - await hass.services.async_call( - "switch", "turn_on", {ATTR_ENTITY_ID: entity_id}, blocking=True - ) + with patch_ufp_method(nvr, "set_insights", new_callable=AsyncMock) as mock_method: + await hass.services.async_call( + "switch", "turn_on", {ATTR_ENTITY_ID: entity_id}, blocking=True + ) - nvr.set_insights.assert_called_once_with(True) + mock_method.assert_called_once_with(True) - await hass.services.async_call( - "switch", "turn_off", {ATTR_ENTITY_ID: entity_id}, blocking=True - ) + await hass.services.async_call( + "switch", "turn_off", {ATTR_ENTITY_ID: entity_id}, blocking=True + ) - nvr.set_insights.assert_called_with(False) + mock_method.assert_called_with(False) async def test_switch_setup_no_perm( @@ -267,24 +267,24 @@ async def test_switch_light_status( description = LIGHT_SWITCHES[1] - light.__pydantic_fields__["set_status_light"] = Mock(final=False, frozen=False) - light.set_status_light = AsyncMock() - _, entity_id = await ids_from_device_description( hass, Platform.SWITCH, light, description ) - await hass.services.async_call( - "switch", "turn_on", {ATTR_ENTITY_ID: entity_id}, blocking=True - ) + with patch_ufp_method( + light, "set_status_light", new_callable=AsyncMock + ) as mock_method: + await hass.services.async_call( + "switch", "turn_on", {ATTR_ENTITY_ID: entity_id}, blocking=True + ) - light.set_status_light.assert_called_once_with(True) + mock_method.assert_called_once_with(True) - await hass.services.async_call( - "switch", "turn_off", {ATTR_ENTITY_ID: entity_id}, blocking=True - ) + await hass.services.async_call( + "switch", "turn_off", {ATTR_ENTITY_ID: entity_id}, blocking=True + ) - light.set_status_light.assert_called_with(False) + mock_method.assert_called_with(False) async def test_switch_camera_ssh( @@ -297,25 +297,23 @@ async def test_switch_camera_ssh( description = CAMERA_SWITCHES[0] - doorbell.__pydantic_fields__["set_ssh"] = Mock(final=False, frozen=False) - doorbell.set_ssh = AsyncMock() - _, entity_id = await ids_from_device_description( hass, Platform.SWITCH, doorbell, description ) await enable_entity(hass, ufp.entry.entry_id, entity_id) - await hass.services.async_call( - "switch", "turn_on", {ATTR_ENTITY_ID: entity_id}, blocking=True - ) + with patch_ufp_method(doorbell, "set_ssh", new_callable=AsyncMock) as mock_method: + await hass.services.async_call( + "switch", "turn_on", {ATTR_ENTITY_ID: entity_id}, blocking=True + ) - doorbell.set_ssh.assert_called_once_with(True) + mock_method.assert_called_once_with(True) - await hass.services.async_call( - "switch", "turn_off", {ATTR_ENTITY_ID: entity_id}, blocking=True - ) + await hass.services.async_call( + "switch", "turn_off", {ATTR_ENTITY_ID: entity_id}, blocking=True + ) - doorbell.set_ssh.assert_called_with(False) + mock_method.assert_called_with(False) @pytest.mark.parametrize("description", CAMERA_SWITCHES_NO_EXTRA) @@ -332,11 +330,9 @@ async def test_switch_camera_simple( assert description.ufp_set_method is not None - doorbell.__pydantic_fields__[description.ufp_set_method] = Mock( - final=False, frozen=False - ) - mock_method = AsyncMock() - with patch.object(doorbell, description.ufp_set_method, mock_method): + with patch_ufp_method( + doorbell, description.ufp_set_method, new_callable=AsyncMock + ) as mock_method: _, entity_id = await ids_from_device_description( hass, Platform.SWITCH, doorbell, description ) @@ -364,24 +360,24 @@ async def test_switch_camera_highfps( description = CAMERA_SWITCHES[3] - doorbell.__pydantic_fields__["set_video_mode"] = Mock(final=False, frozen=False) - doorbell.set_video_mode = AsyncMock() - _, entity_id = await ids_from_device_description( hass, Platform.SWITCH, doorbell, description ) - await hass.services.async_call( - "switch", "turn_on", {ATTR_ENTITY_ID: entity_id}, blocking=True - ) + with patch_ufp_method( + doorbell, "set_video_mode", new_callable=AsyncMock + ) as mock_method: + await hass.services.async_call( + "switch", "turn_on", {ATTR_ENTITY_ID: entity_id}, blocking=True + ) - doorbell.set_video_mode.assert_called_once_with(VideoMode.HIGH_FPS) + mock_method.assert_called_once_with(VideoMode.HIGH_FPS) - await hass.services.async_call( - "switch", "turn_off", {ATTR_ENTITY_ID: entity_id}, blocking=True - ) + await hass.services.async_call( + "switch", "turn_off", {ATTR_ENTITY_ID: entity_id}, blocking=True + ) - doorbell.set_video_mode.assert_called_with(VideoMode.DEFAULT) + mock_method.assert_called_with(VideoMode.DEFAULT) async def test_switch_camera_privacy( @@ -397,9 +393,6 @@ async def test_switch_camera_privacy( description = PRIVACY_MODE_SWITCH - doorbell.__pydantic_fields__["set_privacy"] = Mock(final=False, frozen=False) - doorbell.set_privacy = AsyncMock() - _, entity_id = await ids_from_device_description( hass, Platform.SWITCH, doorbell, description ) @@ -409,35 +402,38 @@ async def test_switch_camera_privacy( assert ATTR_PREV_MIC not in state.attributes assert ATTR_PREV_RECORD not in state.attributes - await hass.services.async_call( - "switch", "turn_on", {ATTR_ENTITY_ID: entity_id}, blocking=True - ) + with patch_ufp_method( + doorbell, "set_privacy", new_callable=AsyncMock + ) as mock_set_privacy: + await hass.services.async_call( + "switch", "turn_on", {ATTR_ENTITY_ID: entity_id}, blocking=True + ) - doorbell.set_privacy.assert_called_with(True, 0, RecordingMode.NEVER) + mock_set_privacy.assert_called_with(True, 0, RecordingMode.NEVER) - new_doorbell = doorbell.model_copy() - new_doorbell.add_privacy_zone() - new_doorbell.mic_volume = 0 - new_doorbell.recording_settings.mode = RecordingMode.NEVER - ufp.api.bootstrap.cameras = {new_doorbell.id: new_doorbell} + new_doorbell = doorbell.model_copy() + new_doorbell.add_privacy_zone() + new_doorbell.mic_volume = 0 + new_doorbell.recording_settings.mode = RecordingMode.NEVER + ufp.api.bootstrap.cameras = {new_doorbell.id: new_doorbell} - mock_msg = Mock() - mock_msg.changed_data = {} - mock_msg.new_obj = new_doorbell - ufp.ws_msg(mock_msg) + mock_msg = Mock() + mock_msg.changed_data = {} + mock_msg.new_obj = new_doorbell + ufp.ws_msg(mock_msg) - state = hass.states.get(entity_id) - assert state and state.state == "on" - assert state.attributes[ATTR_PREV_MIC] == previous_mic - assert state.attributes[ATTR_PREV_RECORD] == previous_record.value + state = hass.states.get(entity_id) + assert state and state.state == "on" + assert state.attributes[ATTR_PREV_MIC] == previous_mic + assert state.attributes[ATTR_PREV_RECORD] == previous_record.value - doorbell.set_privacy.reset_mock() + mock_set_privacy.reset_mock() - await hass.services.async_call( - "switch", "turn_off", {ATTR_ENTITY_ID: entity_id}, blocking=True - ) + await hass.services.async_call( + "switch", "turn_off", {ATTR_ENTITY_ID: entity_id}, blocking=True + ) - doorbell.set_privacy.assert_called_with(False, previous_mic, previous_record) + mock_set_privacy.assert_called_with(False, previous_mic, previous_record) async def test_switch_camera_privacy_already_on( @@ -451,18 +447,18 @@ async def test_switch_camera_privacy_already_on( description = PRIVACY_MODE_SWITCH - doorbell.__pydantic_fields__["set_privacy"] = Mock(final=False, frozen=False) - doorbell.set_privacy = AsyncMock() - _, entity_id = await ids_from_device_description( hass, Platform.SWITCH, doorbell, description ) - await hass.services.async_call( - "switch", "turn_off", {ATTR_ENTITY_ID: entity_id}, blocking=True - ) + with patch_ufp_method( + doorbell, "set_privacy", new_callable=AsyncMock + ) as mock_set_privacy: + await hass.services.async_call( + "switch", "turn_off", {ATTR_ENTITY_ID: entity_id}, blocking=True + ) - doorbell.set_privacy.assert_called_once_with(False, 100, RecordingMode.ALWAYS) + mock_set_privacy.assert_called_once_with(False, 100, RecordingMode.ALWAYS) async def test_switch_turn_on_client_error( @@ -474,14 +470,19 @@ async def test_switch_turn_on_client_error( description = LIGHT_SWITCHES[1] - light.__pydantic_fields__["set_status_light"] = Mock(final=False, frozen=False) - light.set_status_light = AsyncMock(side_effect=ClientError("Test error")) - _, entity_id = await ids_from_device_description( hass, Platform.SWITCH, light, description ) - with pytest.raises(HomeAssistantError): + with ( + patch_ufp_method( + light, + "set_status_light", + new_callable=AsyncMock, + side_effect=ClientError("Test error"), + ), + pytest.raises(HomeAssistantError), + ): await hass.services.async_call( "switch", "turn_on", {ATTR_ENTITY_ID: entity_id}, blocking=True ) @@ -496,14 +497,19 @@ async def test_switch_turn_on_not_authorized( description = LIGHT_SWITCHES[1] - light.__pydantic_fields__["set_status_light"] = Mock(final=False, frozen=False) - light.set_status_light = AsyncMock(side_effect=NotAuthorized("Not authorized")) - _, entity_id = await ids_from_device_description( hass, Platform.SWITCH, light, description ) - with pytest.raises(HomeAssistantError): + with ( + patch_ufp_method( + light, + "set_status_light", + new_callable=AsyncMock, + side_effect=NotAuthorized("Not authorized"), + ), + pytest.raises(HomeAssistantError), + ): await hass.services.async_call( "switch", "turn_on", {ATTR_ENTITY_ID: entity_id}, blocking=True ) diff --git a/tests/components/unifiprotect/test_text.py b/tests/components/unifiprotect/test_text.py index bf9f0502e35..37213c3c99d 100644 --- a/tests/components/unifiprotect/test_text.py +++ b/tests/components/unifiprotect/test_text.py @@ -2,7 +2,7 @@ from __future__ import annotations -from unittest.mock import AsyncMock, Mock +from unittest.mock import AsyncMock from uiprotect.data import Camera, DoorbellMessageType, LCDMessage @@ -12,6 +12,7 @@ from homeassistant.const import ATTR_ATTRIBUTION, ATTR_ENTITY_ID, Platform from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er +from . import patch_ufp_method from .utils import ( MockUFPFixture, adopt_devices, @@ -78,16 +79,16 @@ async def test_text_camera_set( hass, Platform.TEXT, doorbell, description ) - doorbell.__pydantic_fields__["set_lcd_text"] = Mock(final=False, frozen=False) - doorbell.set_lcd_text = AsyncMock() + with patch_ufp_method( + doorbell, "set_lcd_text", new_callable=AsyncMock + ) as mock_method: + await hass.services.async_call( + "text", + "set_value", + {ATTR_ENTITY_ID: entity_id, "value": "Test test"}, + blocking=True, + ) - await hass.services.async_call( - "text", - "set_value", - {ATTR_ENTITY_ID: entity_id, "value": "Test test"}, - blocking=True, - ) - - doorbell.set_lcd_text.assert_called_once_with( - DoorbellMessageType.CUSTOM_MESSAGE, text="Test test" - ) + mock_method.assert_called_once_with( + DoorbellMessageType.CUSTOM_MESSAGE, text="Test test" + )