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
pull/106598/head
mkmer 2023-12-28 13:56:40 -05:00 committed by GitHub
parent 1909163c8e
commit e7e0ae8f6a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 209 additions and 387 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -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`."
}
}
}
}
}
}

View File

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