From 8e4fade725cda6b3de697a1b17e969829993bf98 Mon Sep 17 00:00:00 2001 From: mkmer Date: Thu, 28 Dec 2023 13:56:40 -0500 Subject: [PATCH] Move services to entity services in blink (#105413) * Use device name to lookup camera * Fix device registry serial * Move to entity based services * Update tests * Use config_entry Move refresh service out of camera * Use config entry for services * Fix service schema * Add depreciation note * Depreciation note * key error changes deprecated (not depreciated) repair issue * tweak message * deprication v2 * back out update field change * backout update schema changes * Finish rollback on update service * update doc strings * move to 2024.7.0 More verbosity to deprecation message --- homeassistant/components/blink/camera.py | 61 +++- homeassistant/components/blink/const.py | 1 + homeassistant/components/blink/services.py | 124 ++------ homeassistant/components/blink/services.yaml | 42 +-- homeassistant/components/blink/strings.json | 50 ++- tests/components/blink/test_services.py | 318 +++++-------------- 6 files changed, 209 insertions(+), 387 deletions(-) diff --git a/homeassistant/components/blink/camera.py b/homeassistant/components/blink/camera.py index f507364f17f..4d05aea88a5 100644 --- a/homeassistant/components/blink/camera.py +++ b/homeassistant/components/blink/camera.py @@ -8,17 +8,26 @@ import logging from typing import Any from requests.exceptions import ChunkedEncodingError +import voluptuous as vol from homeassistant.components.camera import Camera from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_FILE_PATH, CONF_FILENAME from homeassistant.core import HomeAssistant -from homeassistant.exceptions import HomeAssistantError +from homeassistant.exceptions import HomeAssistantError, ServiceValidationError from homeassistant.helpers import entity_platform +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.update_coordinator import CoordinatorEntity -from .const import DEFAULT_BRAND, DOMAIN, SERVICE_TRIGGER +from .const import ( + DEFAULT_BRAND, + DOMAIN, + SERVICE_SAVE_RECENT_CLIPS, + SERVICE_SAVE_VIDEO, + SERVICE_TRIGGER, +) from .coordinator import BlinkUpdateCoordinator _LOGGER = logging.getLogger(__name__) @@ -43,6 +52,16 @@ async def async_setup_entry( platform = entity_platform.async_get_current_platform() platform.async_register_entity_service(SERVICE_TRIGGER, {}, "trigger_camera") + platform.async_register_entity_service( + SERVICE_SAVE_RECENT_CLIPS, + {vol.Required(CONF_FILE_PATH): cv.string}, + "save_recent_clips", + ) + platform.async_register_entity_service( + SERVICE_SAVE_VIDEO, + {vol.Required(CONF_FILENAME): cv.string}, + "save_video", + ) class BlinkCamera(CoordinatorEntity[BlinkUpdateCoordinator], Camera): @@ -64,7 +83,7 @@ class BlinkCamera(CoordinatorEntity[BlinkUpdateCoordinator], Camera): manufacturer=DEFAULT_BRAND, model=camera.camera_type, ) - _LOGGER.debug("Initialized blink camera %s", self.name) + _LOGGER.debug("Initialized blink camera %s", self._camera.name) @property def extra_state_attributes(self) -> Mapping[str, Any] | None: @@ -121,3 +140,39 @@ class BlinkCamera(CoordinatorEntity[BlinkUpdateCoordinator], Camera): except TypeError: _LOGGER.debug("No cached image for %s", self._camera.name) return None + + async def save_recent_clips(self, file_path) -> None: + """Save multiple recent clips to output directory.""" + if not self.hass.config.is_allowed_path(file_path): + raise ServiceValidationError( + translation_domain=DOMAIN, + translation_key="no_path", + translation_placeholders={"target": file_path}, + ) + + try: + await self._camera.save_recent_clips(output_dir=file_path) + except OSError as err: + raise ServiceValidationError( + str(err), + translation_domain=DOMAIN, + translation_key="cant_write", + ) from err + + async def save_video(self, filename) -> None: + """Handle save video service calls.""" + if not self.hass.config.is_allowed_path(filename): + raise ServiceValidationError( + translation_domain=DOMAIN, + translation_key="no_path", + translation_placeholders={"target": filename}, + ) + + try: + await self._camera.video_to_file(filename) + except OSError as err: + raise ServiceValidationError( + str(err), + translation_domain=DOMAIN, + translation_key="cant_write", + ) from err diff --git a/homeassistant/components/blink/const.py b/homeassistant/components/blink/const.py index d394b5c0008..7aa3d0d388e 100644 --- a/homeassistant/components/blink/const.py +++ b/homeassistant/components/blink/const.py @@ -24,6 +24,7 @@ SERVICE_TRIGGER = "trigger_camera" SERVICE_SAVE_VIDEO = "save_video" SERVICE_SAVE_RECENT_CLIPS = "save_recent_clips" SERVICE_SEND_PIN = "send_pin" +ATTR_CONFIG_ENTRY_ID = "config_entry_id" PLATFORMS = [ Platform.ALARM_CONTROL_PANEL, diff --git a/homeassistant/components/blink/services.py b/homeassistant/components/blink/services.py index dae2f0ad951..5c034cdb7c5 100644 --- a/homeassistant/components/blink/services.py +++ b/homeassistant/components/blink/services.py @@ -4,25 +4,16 @@ from __future__ import annotations import voluptuous as vol from homeassistant.config_entries import ConfigEntry, ConfigEntryState -from homeassistant.const import ( - ATTR_DEVICE_ID, - CONF_FILE_PATH, - CONF_FILENAME, - CONF_NAME, - CONF_PIN, -) +from homeassistant.const import ATTR_DEVICE_ID, CONF_PIN from homeassistant.core import HomeAssistant, ServiceCall from homeassistant.exceptions import HomeAssistantError, ServiceValidationError -import homeassistant.helpers.config_validation as cv -import homeassistant.helpers.device_registry as dr - -from .const import ( - DOMAIN, - SERVICE_REFRESH, - SERVICE_SAVE_RECENT_CLIPS, - SERVICE_SAVE_VIDEO, - SERVICE_SEND_PIN, +from homeassistant.helpers import ( + config_validation as cv, + device_registry as dr, + issue_registry as ir, ) + +from .const import ATTR_CONFIG_ENTRY_ID, DOMAIN, SERVICE_REFRESH, SERVICE_SEND_PIN from .coordinator import BlinkUpdateCoordinator SERVICE_UPDATE_SCHEMA = vol.Schema( @@ -30,26 +21,12 @@ SERVICE_UPDATE_SCHEMA = vol.Schema( vol.Required(ATTR_DEVICE_ID): vol.All(cv.ensure_list, [cv.string]), } ) -SERVICE_SAVE_VIDEO_SCHEMA = vol.Schema( - { - vol.Required(ATTR_DEVICE_ID): vol.All(cv.ensure_list, [cv.string]), - vol.Required(CONF_NAME): cv.string, - vol.Required(CONF_FILENAME): cv.string, - } -) SERVICE_SEND_PIN_SCHEMA = vol.Schema( { - vol.Required(ATTR_DEVICE_ID): vol.All(cv.ensure_list, [cv.string]), + vol.Required(ATTR_CONFIG_ENTRY_ID): vol.All(cv.ensure_list, [cv.string]), vol.Optional(CONF_PIN): cv.string, } ) -SERVICE_SAVE_RECENT_CLIPS_SCHEMA = vol.Schema( - { - vol.Required(ATTR_DEVICE_ID): vol.All(cv.ensure_list, [cv.string]), - vol.Required(CONF_NAME): cv.string, - vol.Required(CONF_FILE_PATH): cv.string, - } -) def setup_services(hass: HomeAssistant) -> None: @@ -94,57 +71,22 @@ def setup_services(hass: HomeAssistant) -> None: coordinators.append(hass.data[DOMAIN][config_entry.entry_id]) return coordinators - async def async_handle_save_video_service(call: ServiceCall) -> None: - """Handle save video service calls.""" - camera_name = call.data[CONF_NAME] - video_path = call.data[CONF_FILENAME] - if not hass.config.is_allowed_path(video_path): - raise ServiceValidationError( - translation_domain=DOMAIN, - translation_key="no_path", - translation_placeholders={"target": video_path}, - ) - - for coordinator in collect_coordinators(call.data[ATTR_DEVICE_ID]): - all_cameras = coordinator.api.cameras - if camera_name in all_cameras: - try: - await all_cameras[camera_name].video_to_file(video_path) - except OSError as err: - raise ServiceValidationError( - str(err), - translation_domain=DOMAIN, - translation_key="cant_write", - ) from err - - async def async_handle_save_recent_clips_service(call: ServiceCall) -> None: - """Save multiple recent clips to output directory.""" - camera_name = call.data[CONF_NAME] - clips_dir = call.data[CONF_FILE_PATH] - if not hass.config.is_allowed_path(clips_dir): - raise ServiceValidationError( - translation_domain=DOMAIN, - translation_key="no_path", - translation_placeholders={"target": clips_dir}, - ) - - for coordinator in collect_coordinators(call.data[ATTR_DEVICE_ID]): - all_cameras = coordinator.api.cameras - if camera_name in all_cameras: - try: - await all_cameras[camera_name].save_recent_clips( - output_dir=clips_dir - ) - except OSError as err: - raise ServiceValidationError( - str(err), - translation_domain=DOMAIN, - translation_key="cant_write", - ) from err - async def send_pin(call: ServiceCall): """Call blink to send new pin.""" - for coordinator in collect_coordinators(call.data[ATTR_DEVICE_ID]): + for entry_id in call.data[ATTR_CONFIG_ENTRY_ID]: + if not (config_entry := hass.config_entries.async_get_entry(entry_id)): + raise ServiceValidationError( + translation_domain=DOMAIN, + translation_key="integration_not_found", + translation_placeholders={"target": DOMAIN}, + ) + if config_entry.state != ConfigEntryState.LOADED: + raise HomeAssistantError( + translation_domain=DOMAIN, + translation_key="not_loaded", + translation_placeholders={"target": config_entry.title}, + ) + coordinator = hass.data[DOMAIN][entry_id] await coordinator.api.auth.send_auth_key( coordinator.api, call.data[CONF_PIN], @@ -152,22 +94,24 @@ def setup_services(hass: HomeAssistant) -> None: async def blink_refresh(call: ServiceCall): """Call blink to refresh info.""" + ir.async_create_issue( + hass, + DOMAIN, + "service_deprecation", + breaks_in_ha_version="2024.7.0", + is_fixable=True, + is_persistent=True, + severity=ir.IssueSeverity.WARNING, + translation_key="service_deprecation", + ) + for coordinator in collect_coordinators(call.data[ATTR_DEVICE_ID]): await coordinator.api.refresh(force_cache=True) # Register all the above services + # Refresh service is deprecated and will be removed in 7/2024 service_mapping = [ (blink_refresh, SERVICE_REFRESH, SERVICE_UPDATE_SCHEMA), - ( - async_handle_save_video_service, - SERVICE_SAVE_VIDEO, - SERVICE_SAVE_VIDEO_SCHEMA, - ), - ( - async_handle_save_recent_clips_service, - SERVICE_SAVE_RECENT_CLIPS, - SERVICE_SAVE_RECENT_CLIPS_SCHEMA, - ), (send_pin, SERVICE_SEND_PIN, SERVICE_SEND_PIN_SCHEMA), ] diff --git a/homeassistant/components/blink/services.yaml b/homeassistant/components/blink/services.yaml index aaecde64353..87083a990ef 100644 --- a/homeassistant/components/blink/services.yaml +++ b/homeassistant/components/blink/services.yaml @@ -9,25 +9,17 @@ blink_update: integration: blink trigger_camera: - fields: - device_id: - required: true - selector: - device: - integration: blink + target: + entity: + integration: blink + domain: camera save_video: + target: + entity: + integration: blink + domain: camera fields: - device_id: - required: true - selector: - device: - integration: blink - name: - required: true - example: "Living Room" - selector: - text: filename: required: true example: "/tmp/video.mp4" @@ -35,17 +27,11 @@ save_video: text: save_recent_clips: + target: + entity: + integration: blink + domain: camera fields: - device_id: - required: true - selector: - device: - integration: blink - name: - required: true - example: "Living Room" - selector: - text: file_path: required: true example: "/tmp" @@ -54,10 +40,10 @@ save_recent_clips: send_pin: fields: - device_id: + config_entry_id: required: true selector: - device: + config_entry: integration: blink pin: example: "abc123" diff --git a/homeassistant/components/blink/strings.json b/homeassistant/components/blink/strings.json index fc0450dc8ea..87e2fc68c20 100644 --- a/homeassistant/components/blink/strings.json +++ b/homeassistant/components/blink/strings.json @@ -67,29 +67,15 @@ }, "trigger_camera": { "name": "Trigger camera", - "description": "Requests camera to take new image.", - "fields": { - "device_id": { - "name": "Device ID", - "description": "The Blink device id." - } - } + "description": "Requests camera to take new image." }, "save_video": { "name": "Save video", "description": "Saves last recorded video clip to local file.", "fields": { - "name": { - "name": "[%key:common::config_flow::data::name%]", - "description": "Name of camera to grab video from." - }, "filename": { "name": "File name", "description": "Filename to writable path (directory may need to be included in allowlist_external_dirs in config)." - }, - "device_id": { - "name": "Device ID", - "description": "The Blink device id." } } }, @@ -97,17 +83,9 @@ "name": "Save recent clips", "description": "Saves all recent video clips to local directory with file pattern \"%Y%m%d_%H%M%S_{name}.mp4\".", "fields": { - "name": { - "name": "[%key:common::config_flow::data::name%]", - "description": "Name of camera to grab recent clips from." - }, "file_path": { "name": "Output directory", "description": "Directory name of writable path (directory may need to be included in allowlist_external_dirs in config)." - }, - "device_id": { - "name": "Device ID", - "description": "The Blink device id." } } }, @@ -119,19 +97,16 @@ "name": "Pin", "description": "PIN received from blink. Leave empty if you only received a verification email." }, - "device_id": { - "name": "Device ID", - "description": "The Blink device id." + "config_entry_id": { + "name": "Integration ID", + "description": "The Blink Integration id." } } } }, "exceptions": { - "invalid_device": { - "message": "Device '{target}' is not a {domain} device" - }, - "device_not_found": { - "message": "Device '{target}' not found in device registry" + "integration_not_found": { + "message": "Integraion '{target}' not found in registry" }, "no_path": { "message": "Can't write to directory {target}, no access to path!" @@ -142,5 +117,18 @@ "not_loaded": { "message": "{target} is not loaded" } + }, + "issues": { + "service_deprecation": { + "title": "Blink update service is being removed", + "fix_flow": { + "step": { + "confirm": { + "title": "[%key:component::blink::issues::service_deprecation::title%]", + "description": "Blink update service is deprecated and will be removed.\nPlease update your automations and scripts to use `Home Assistant Core Integration: Update entity`." + } + } + } + } } } diff --git a/tests/components/blink/test_services.py b/tests/components/blink/test_services.py index ccc326dac1f..1c2faa32d04 100644 --- a/tests/components/blink/test_services.py +++ b/tests/components/blink/test_services.py @@ -4,22 +4,15 @@ from unittest.mock import AsyncMock, MagicMock, Mock import pytest from homeassistant.components.blink.const import ( + ATTR_CONFIG_ENTRY_ID, DOMAIN, SERVICE_REFRESH, - SERVICE_SAVE_RECENT_CLIPS, - SERVICE_SAVE_VIDEO, SERVICE_SEND_PIN, ) from homeassistant.config_entries import ConfigEntryState -from homeassistant.const import ( - ATTR_DEVICE_ID, - CONF_FILE_PATH, - CONF_FILENAME, - CONF_NAME, - CONF_PIN, -) +from homeassistant.const import ATTR_DEVICE_ID, CONF_PIN from homeassistant.core import HomeAssistant -from homeassistant.exceptions import HomeAssistantError, ServiceValidationError +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import device_registry as dr from tests.common import MockConfigEntry @@ -43,7 +36,6 @@ async def test_refresh_service_calls( await hass.async_block_till_done() device_entry = device_registry.async_get_device(identifiers={(DOMAIN, "12345")}) - assert device_entry assert mock_config_entry.state is ConfigEntryState.LOADED @@ -67,163 +59,8 @@ async def test_refresh_service_calls( ) -async def test_video_service_calls( - hass: HomeAssistant, - device_registry: dr.DeviceRegistry, - mock_blink_api: MagicMock, - mock_blink_auth_api: MagicMock, - mock_config_entry: MockConfigEntry, - caplog: pytest.LogCaptureFixture, -) -> None: - """Test video service calls.""" - - mock_config_entry.add_to_hass(hass) - assert await hass.config_entries.async_setup(mock_config_entry.entry_id) - await hass.async_block_till_done() - - device_entry = device_registry.async_get_device(identifiers={(DOMAIN, "12345")}) - - assert device_entry - - assert mock_config_entry.state is ConfigEntryState.LOADED - assert mock_blink_api.refresh.call_count == 1 - - with pytest.raises(HomeAssistantError): - await hass.services.async_call( - DOMAIN, - SERVICE_SAVE_VIDEO, - { - ATTR_DEVICE_ID: [device_entry.id], - CONF_NAME: CAMERA_NAME, - CONF_FILENAME: FILENAME, - }, - blocking=True, - ) - - hass.config.is_allowed_path = Mock(return_value=True) - caplog.clear() - mock_blink_api.cameras = {CAMERA_NAME: AsyncMock()} - await hass.services.async_call( - DOMAIN, - SERVICE_SAVE_VIDEO, - { - ATTR_DEVICE_ID: [device_entry.id], - CONF_NAME: CAMERA_NAME, - CONF_FILENAME: FILENAME, - }, - blocking=True, - ) - mock_blink_api.cameras[CAMERA_NAME].video_to_file.assert_awaited_once() - - with pytest.raises(HomeAssistantError): - await hass.services.async_call( - DOMAIN, - SERVICE_SAVE_VIDEO, - { - ATTR_DEVICE_ID: ["bad-device_id"], - CONF_NAME: CAMERA_NAME, - CONF_FILENAME: FILENAME, - }, - blocking=True, - ) - - mock_blink_api.cameras[CAMERA_NAME].video_to_file = AsyncMock(side_effect=OSError) - - with pytest.raises(ServiceValidationError): - await hass.services.async_call( - DOMAIN, - SERVICE_SAVE_VIDEO, - { - ATTR_DEVICE_ID: [device_entry.id], - CONF_NAME: CAMERA_NAME, - CONF_FILENAME: FILENAME, - }, - blocking=True, - ) - - hass.config.is_allowed_path = Mock(return_value=False) - - -async def test_picture_service_calls( - hass: HomeAssistant, - device_registry: dr.DeviceRegistry, - mock_blink_api: MagicMock, - mock_blink_auth_api: MagicMock, - mock_config_entry: MockConfigEntry, - caplog: pytest.LogCaptureFixture, -) -> None: - """Test picture servcie calls.""" - - mock_config_entry.add_to_hass(hass) - assert await hass.config_entries.async_setup(mock_config_entry.entry_id) - await hass.async_block_till_done() - - device_entry = device_registry.async_get_device(identifiers={(DOMAIN, "12345")}) - - assert device_entry - - assert mock_config_entry.state is ConfigEntryState.LOADED - assert mock_blink_api.refresh.call_count == 1 - - with pytest.raises(ServiceValidationError): - await hass.services.async_call( - DOMAIN, - SERVICE_SAVE_RECENT_CLIPS, - { - ATTR_DEVICE_ID: [device_entry.id], - CONF_NAME: CAMERA_NAME, - CONF_FILE_PATH: FILENAME, - }, - blocking=True, - ) - - hass.config.is_allowed_path = Mock(return_value=True) - mock_blink_api.cameras = {CAMERA_NAME: AsyncMock()} - - await hass.services.async_call( - DOMAIN, - SERVICE_SAVE_RECENT_CLIPS, - { - ATTR_DEVICE_ID: [device_entry.id], - CONF_NAME: CAMERA_NAME, - CONF_FILE_PATH: FILENAME, - }, - blocking=True, - ) - mock_blink_api.cameras[CAMERA_NAME].save_recent_clips.assert_awaited_once() - - mock_blink_api.cameras[CAMERA_NAME].save_recent_clips = AsyncMock( - side_effect=OSError - ) - - with pytest.raises(ServiceValidationError): - await hass.services.async_call( - DOMAIN, - SERVICE_SAVE_RECENT_CLIPS, - { - ATTR_DEVICE_ID: [device_entry.id], - CONF_NAME: CAMERA_NAME, - CONF_FILE_PATH: FILENAME, - }, - blocking=True, - ) - - with pytest.raises(HomeAssistantError): - await hass.services.async_call( - DOMAIN, - SERVICE_SAVE_RECENT_CLIPS, - { - ATTR_DEVICE_ID: ["bad-device_id"], - CONF_NAME: CAMERA_NAME, - CONF_FILE_PATH: FILENAME, - }, - blocking=True, - ) - - async def test_pin_service_calls( hass: HomeAssistant, - device_registry: dr.DeviceRegistry, mock_blink_api: MagicMock, mock_blink_auth_api: MagicMock, mock_config_entry: MockConfigEntry, @@ -234,17 +71,13 @@ async def test_pin_service_calls( assert await hass.config_entries.async_setup(mock_config_entry.entry_id) await hass.async_block_till_done() - device_entry = device_registry.async_get_device(identifiers={(DOMAIN, "12345")}) - - assert device_entry - assert mock_config_entry.state is ConfigEntryState.LOADED assert mock_blink_api.refresh.call_count == 1 await hass.services.async_call( DOMAIN, SERVICE_SEND_PIN, - {ATTR_DEVICE_ID: [device_entry.id], CONF_PIN: PIN}, + {ATTR_CONFIG_ENTRY_ID: [mock_config_entry.entry_id], CONF_PIN: PIN}, blocking=True, ) assert mock_blink_api.auth.send_auth_key.assert_awaited_once @@ -253,41 +86,18 @@ async def test_pin_service_calls( await hass.services.async_call( DOMAIN, SERVICE_SEND_PIN, - {ATTR_DEVICE_ID: ["bad-device_id"], CONF_PIN: PIN}, + {ATTR_CONFIG_ENTRY_ID: ["bad-config_id"], CONF_PIN: PIN}, blocking=True, ) -@pytest.mark.parametrize( - ("service", "params"), - [ - (SERVICE_SEND_PIN, {CONF_PIN: PIN}), - ( - SERVICE_SAVE_RECENT_CLIPS, - { - CONF_NAME: CAMERA_NAME, - CONF_FILE_PATH: FILENAME, - }, - ), - ( - SERVICE_SAVE_VIDEO, - { - CONF_NAME: CAMERA_NAME, - CONF_FILENAME: FILENAME, - }, - ), - ], -) -async def test_service_called_with_non_blink_device( +async def test_service_pin_called_with_non_blink_device( hass: HomeAssistant, - device_registry: dr.DeviceRegistry, mock_blink_api: MagicMock, mock_blink_auth_api: MagicMock, mock_config_entry: MockConfigEntry, - service, - params, ) -> None: - """Test service calls with non blink device.""" + """Test pin service calls with non blink device.""" mock_config_entry.add_to_hass(hass) assert await hass.config_entries.async_setup(mock_config_entry.entry_id) @@ -295,11 +105,48 @@ async def test_service_called_with_non_blink_device( other_domain = "NotBlink" other_config_id = "555" - await hass.config_entries.async_add( - MockConfigEntry( - title="Not Blink", domain=other_domain, entry_id=other_config_id - ) + other_mock_config_entry = MockConfigEntry( + title="Not Blink", domain=other_domain, entry_id=other_config_id ) + await hass.config_entries.async_add(other_mock_config_entry) + + hass.config.is_allowed_path = Mock(return_value=True) + mock_blink_api.cameras = {CAMERA_NAME: AsyncMock()} + + parameters = { + ATTR_CONFIG_ENTRY_ID: [other_mock_config_entry.entry_id], + CONF_PIN: PIN, + } + + with pytest.raises(HomeAssistantError): + await hass.services.async_call( + DOMAIN, + SERVICE_SEND_PIN, + parameters, + blocking=True, + ) + + +async def test_service_update_called_with_non_blink_device( + hass: HomeAssistant, + mock_blink_api: MagicMock, + device_registry: dr.DeviceRegistry, + mock_blink_auth_api: MagicMock, + mock_config_entry: MockConfigEntry, +) -> None: + """Test update service calls with non blink device.""" + + mock_config_entry.add_to_hass(hass) + assert await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() + + other_domain = "NotBlink" + other_config_id = "555" + other_mock_config_entry = MockConfigEntry( + title="Not Blink", domain=other_domain, entry_id=other_config_id + ) + await hass.config_entries.async_add(other_mock_config_entry) + device_entry = device_registry.async_get_or_create( config_entry_id=other_config_id, identifiers={ @@ -311,67 +158,68 @@ async def test_service_called_with_non_blink_device( mock_blink_api.cameras = {CAMERA_NAME: AsyncMock()} parameters = {ATTR_DEVICE_ID: [device_entry.id]} - parameters.update(params) - with pytest.raises(ServiceValidationError): + with pytest.raises(HomeAssistantError): await hass.services.async_call( DOMAIN, - service, + SERVICE_REFRESH, parameters, blocking=True, ) -@pytest.mark.parametrize( - ("service", "params"), - [ - (SERVICE_SEND_PIN, {CONF_PIN: PIN}), - ( - SERVICE_SAVE_RECENT_CLIPS, - { - CONF_NAME: CAMERA_NAME, - CONF_FILE_PATH: FILENAME, - }, - ), - ( - SERVICE_SAVE_VIDEO, - { - CONF_NAME: CAMERA_NAME, - CONF_FILENAME: FILENAME, - }, - ), - ], -) -async def test_service_called_with_unloaded_entry( +async def test_service_pin_called_with_unloaded_entry( + hass: HomeAssistant, + mock_blink_api: MagicMock, + mock_blink_auth_api: MagicMock, + mock_config_entry: MockConfigEntry, +) -> None: + """Test pin service calls with not ready config entry.""" + + mock_config_entry.add_to_hass(hass) + assert await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() + mock_config_entry.state = ConfigEntryState.SETUP_ERROR + hass.config.is_allowed_path = Mock(return_value=True) + mock_blink_api.cameras = {CAMERA_NAME: AsyncMock()} + + parameters = {ATTR_CONFIG_ENTRY_ID: [mock_config_entry.entry_id], CONF_PIN: PIN} + + with pytest.raises(HomeAssistantError): + await hass.services.async_call( + DOMAIN, + SERVICE_SEND_PIN, + parameters, + blocking=True, + ) + + +async def test_service_update_called_with_unloaded_entry( hass: HomeAssistant, device_registry: dr.DeviceRegistry, mock_blink_api: MagicMock, mock_blink_auth_api: MagicMock, mock_config_entry: MockConfigEntry, - service, - params, ) -> None: - """Test service calls with unloaded config entry.""" + """Test update service calls with not ready config entry.""" mock_config_entry.add_to_hass(hass) assert await hass.config_entries.async_setup(mock_config_entry.entry_id) await hass.async_block_till_done() - await mock_config_entry.async_unload(hass) - - device_entry = device_registry.async_get_device(identifiers={(DOMAIN, "12345")}) - - assert device_entry + mock_config_entry.state = ConfigEntryState.SETUP_ERROR hass.config.is_allowed_path = Mock(return_value=True) mock_blink_api.cameras = {CAMERA_NAME: AsyncMock()} + device_entry = device_registry.async_get_device(identifiers={(DOMAIN, "12345")}) + assert device_entry + parameters = {ATTR_DEVICE_ID: [device_entry.id]} - parameters.update(params) with pytest.raises(HomeAssistantError): await hass.services.async_call( DOMAIN, - service, + SERVICE_REFRESH, parameters, blocking=True, )