mirror of
https://github.com/Electric-Special/ha-core.git
synced 2026-03-21 05:06:13 +01:00
raise proper service exceptions in fressnapf_tracker (#159707)
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
bfef048a7c
commit
afc256622a
@@ -2,6 +2,8 @@
|
||||
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from fressnapftracker import FressnapfTrackerError
|
||||
|
||||
from homeassistant.components.light import (
|
||||
ATTR_BRIGHTNESS,
|
||||
ColorMode,
|
||||
@@ -16,6 +18,7 @@ from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from . import FressnapfTrackerConfigEntry
|
||||
from .const import DOMAIN
|
||||
from .entity import FressnapfTrackerEntity
|
||||
from .services import handle_fressnapf_tracker_exception
|
||||
|
||||
PARALLEL_UPDATES = 1
|
||||
|
||||
@@ -61,12 +64,18 @@ class FressnapfTrackerLight(FressnapfTrackerEntity, LightEntity):
|
||||
self.raise_if_not_activatable()
|
||||
brightness = kwargs.get(ATTR_BRIGHTNESS, 255)
|
||||
brightness = int((brightness / 255) * 100)
|
||||
await self.coordinator.client.set_led_brightness(brightness)
|
||||
try:
|
||||
await self.coordinator.client.set_led_brightness(brightness)
|
||||
except FressnapfTrackerError as e:
|
||||
handle_fressnapf_tracker_exception(e)
|
||||
await self.coordinator.async_request_refresh()
|
||||
|
||||
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||
"""Turn off the device."""
|
||||
await self.coordinator.client.set_led_brightness(0)
|
||||
try:
|
||||
await self.coordinator.client.set_led_brightness(0)
|
||||
except FressnapfTrackerError as e:
|
||||
handle_fressnapf_tracker_exception(e)
|
||||
await self.coordinator.async_request_refresh()
|
||||
|
||||
def raise_if_not_activatable(self) -> None:
|
||||
|
||||
@@ -26,7 +26,7 @@ rules:
|
||||
unique-config-entry: done
|
||||
|
||||
# Silver
|
||||
action-exceptions: todo
|
||||
action-exceptions: done
|
||||
config-entry-unloading: done
|
||||
docs-configuration-parameters: done
|
||||
docs-installation-parameters: done
|
||||
|
||||
21
homeassistant/components/fressnapf_tracker/services.py
Normal file
21
homeassistant/components/fressnapf_tracker/services.py
Normal file
@@ -0,0 +1,21 @@
|
||||
"""Services and service helpers for fressnapf_tracker."""
|
||||
|
||||
from fressnapftracker import FressnapfTrackerError, FressnapfTrackerInvalidTokenError
|
||||
|
||||
from homeassistant.exceptions import ConfigEntryAuthFailed, HomeAssistantError
|
||||
|
||||
from .const import DOMAIN
|
||||
|
||||
|
||||
def handle_fressnapf_tracker_exception(exception: FressnapfTrackerError):
|
||||
"""Handle the different FressnapfTracker errors."""
|
||||
if isinstance(exception, FressnapfTrackerInvalidTokenError):
|
||||
raise ConfigEntryAuthFailed(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="invalid_auth",
|
||||
) from exception
|
||||
raise HomeAssistantError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="api_error",
|
||||
translation_placeholders={"error_message": str(exception)},
|
||||
) from exception
|
||||
@@ -77,6 +77,9 @@
|
||||
}
|
||||
},
|
||||
"exceptions": {
|
||||
"api_error": {
|
||||
"message": "An error occurred while communicating with the Fressnapf Tracker API: {error_message}"
|
||||
},
|
||||
"charging": {
|
||||
"message": "The flashlight cannot be activated while charging."
|
||||
},
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from fressnapftracker import FressnapfTrackerError
|
||||
|
||||
from homeassistant.components.switch import (
|
||||
SwitchDeviceClass,
|
||||
SwitchEntity,
|
||||
@@ -13,6 +15,7 @@ from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from . import FressnapfTrackerConfigEntry
|
||||
from .entity import FressnapfTrackerEntity
|
||||
from .services import handle_fressnapf_tracker_exception
|
||||
|
||||
PARALLEL_UPDATES = 1
|
||||
|
||||
@@ -43,12 +46,18 @@ class FressnapfTrackerSwitch(FressnapfTrackerEntity, SwitchEntity):
|
||||
|
||||
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||
"""Turn on the device."""
|
||||
await self.coordinator.client.set_energy_saving(True)
|
||||
try:
|
||||
await self.coordinator.client.set_energy_saving(True)
|
||||
except FressnapfTrackerError as e:
|
||||
handle_fressnapf_tracker_exception(e)
|
||||
await self.coordinator.async_request_refresh()
|
||||
|
||||
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||
"""Turn off the device."""
|
||||
await self.coordinator.client.set_energy_saving(False)
|
||||
try:
|
||||
await self.coordinator.client.set_energy_saving(False)
|
||||
except FressnapfTrackerError as e:
|
||||
handle_fressnapf_tracker_exception(e)
|
||||
await self.coordinator.async_request_refresh()
|
||||
|
||||
@property
|
||||
|
||||
@@ -33,34 +33,6 @@ MOCK_USER_ID = 12345
|
||||
MOCK_ACCESS_TOKEN = "mock_access_token"
|
||||
MOCK_SERIAL_NUMBER = "ABC123456"
|
||||
MOCK_DEVICE_TOKEN = "mock_device_token"
|
||||
MOCK_TRACKER = Tracker(
|
||||
name="Fluffy",
|
||||
battery=85,
|
||||
charging=False,
|
||||
position=Position(
|
||||
lat=52.520008,
|
||||
lng=13.404954,
|
||||
accuracy=10,
|
||||
timestamp="2024-01-15T12:00:00Z",
|
||||
),
|
||||
tracker_settings=TrackerSettings(
|
||||
generation="GPS Tracker 2.0",
|
||||
features=TrackerFeatures(
|
||||
flash_light=True, energy_saving_mode=True, live_tracking=True
|
||||
),
|
||||
),
|
||||
led_brightness=LedBrightness(status="ok", value=50),
|
||||
energy_saving=EnergySaving(status="ok", value=1),
|
||||
deep_sleep=None,
|
||||
led_activatable=LedActivatable(
|
||||
has_led=True,
|
||||
seen_recently=True,
|
||||
nonempty_battery=True,
|
||||
not_charging=True,
|
||||
overall=True,
|
||||
),
|
||||
icon="http://res.cloudinary.com/iot-venture/image/upload/v1717594357/kyaqq7nfitrdvaoakb8s.jpg",
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -136,7 +108,36 @@ def mock_api_client() -> Generator[MagicMock]:
|
||||
"homeassistant.components.fressnapf_tracker.coordinator.ApiClient"
|
||||
) as mock_api_client:
|
||||
client = mock_api_client.return_value
|
||||
client.get_tracker = AsyncMock(return_value=MOCK_TRACKER)
|
||||
client.get_tracker = AsyncMock(
|
||||
return_value=Tracker(
|
||||
name="Fluffy",
|
||||
battery=85,
|
||||
charging=False,
|
||||
position=Position(
|
||||
lat=52.520008,
|
||||
lng=13.404954,
|
||||
accuracy=10,
|
||||
timestamp="2024-01-15T12:00:00Z",
|
||||
),
|
||||
tracker_settings=TrackerSettings(
|
||||
generation="GPS Tracker 2.0",
|
||||
features=TrackerFeatures(
|
||||
flash_light=True, energy_saving_mode=True, live_tracking=True
|
||||
),
|
||||
),
|
||||
led_brightness=LedBrightness(status="ok", value=50),
|
||||
energy_saving=EnergySaving(status="ok", value=1),
|
||||
deep_sleep=None,
|
||||
led_activatable=LedActivatable(
|
||||
has_led=True,
|
||||
seen_recently=True,
|
||||
nonempty_battery=True,
|
||||
not_charging=True,
|
||||
overall=True,
|
||||
),
|
||||
icon="http://res.cloudinary.com/iot-venture/image/upload/v1717594357/kyaqq7nfitrdvaoakb8s.jpg",
|
||||
)
|
||||
)
|
||||
client.set_led_brightness = AsyncMock(return_value=None)
|
||||
client.set_energy_saving = AsyncMock(return_value=None)
|
||||
yield client
|
||||
|
||||
@@ -3,7 +3,13 @@
|
||||
from collections.abc import AsyncGenerator
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from fressnapftracker import Tracker, TrackerFeatures, TrackerSettings
|
||||
from fressnapftracker import (
|
||||
FressnapfTrackerError,
|
||||
FressnapfTrackerInvalidTokenError,
|
||||
Tracker,
|
||||
TrackerFeatures,
|
||||
TrackerSettings,
|
||||
)
|
||||
import pytest
|
||||
from syrupy.assertion import SnapshotAssertion
|
||||
|
||||
@@ -15,7 +21,7 @@ from homeassistant.components.light import (
|
||||
)
|
||||
from homeassistant.const import ATTR_ENTITY_ID, STATE_ON, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.exceptions import ConfigEntryAuthFailed, HomeAssistantError
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
|
||||
from tests.common import MockConfigEntry, snapshot_platform
|
||||
@@ -172,3 +178,41 @@ async def test_turn_on_led_not_activatable(
|
||||
)
|
||||
|
||||
mock_api_client.set_led_brightness.assert_not_called()
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("api_exception", "expected_exception"),
|
||||
[
|
||||
(FressnapfTrackerError("Something went wrong"), HomeAssistantError),
|
||||
(
|
||||
FressnapfTrackerInvalidTokenError("Token no longer valid"),
|
||||
ConfigEntryAuthFailed,
|
||||
),
|
||||
],
|
||||
)
|
||||
@pytest.mark.parametrize("service", [SERVICE_TURN_ON, SERVICE_TURN_OFF])
|
||||
@pytest.mark.usefixtures("mock_auth_client")
|
||||
async def test_turn_on_off_error(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_api_client: MagicMock,
|
||||
api_exception: FressnapfTrackerError,
|
||||
expected_exception: type[HomeAssistantError],
|
||||
service: str,
|
||||
) -> None:
|
||||
"""Test that errors during service handling are handled correctly."""
|
||||
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
entity_id = "light.fluffy_flashlight"
|
||||
|
||||
mock_api_client.set_led_brightness.side_effect = api_exception
|
||||
with pytest.raises(expected_exception):
|
||||
await hass.services.async_call(
|
||||
LIGHT_DOMAIN,
|
||||
service,
|
||||
{ATTR_ENTITY_ID: entity_id},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
@@ -3,7 +3,13 @@
|
||||
from collections.abc import AsyncGenerator
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from fressnapftracker import Tracker, TrackerFeatures, TrackerSettings
|
||||
from fressnapftracker import (
|
||||
FressnapfTrackerError,
|
||||
FressnapfTrackerInvalidTokenError,
|
||||
Tracker,
|
||||
TrackerFeatures,
|
||||
TrackerSettings,
|
||||
)
|
||||
import pytest
|
||||
from syrupy.assertion import SnapshotAssertion
|
||||
|
||||
@@ -14,6 +20,7 @@ from homeassistant.components.switch import (
|
||||
)
|
||||
from homeassistant.const import ATTR_ENTITY_ID, STATE_ON, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryAuthFailed, HomeAssistantError
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
|
||||
from tests.common import MockConfigEntry, snapshot_platform
|
||||
@@ -112,3 +119,41 @@ async def test_turn_off(
|
||||
)
|
||||
|
||||
mock_api_client.set_energy_saving.assert_called_once_with(False)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("api_exception", "expected_exception"),
|
||||
[
|
||||
(FressnapfTrackerError("Something went wrong"), HomeAssistantError),
|
||||
(
|
||||
FressnapfTrackerInvalidTokenError("Token no longer valid"),
|
||||
ConfigEntryAuthFailed,
|
||||
),
|
||||
],
|
||||
)
|
||||
@pytest.mark.parametrize("service", [SERVICE_TURN_ON, SERVICE_TURN_OFF])
|
||||
@pytest.mark.usefixtures("mock_auth_client")
|
||||
async def test_turn_on_off_error(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_api_client: MagicMock,
|
||||
api_exception: FressnapfTrackerError,
|
||||
expected_exception: type[HomeAssistantError],
|
||||
service: str,
|
||||
) -> None:
|
||||
"""Test that errors during service handling are handled correctly."""
|
||||
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
entity_id = "switch.fluffy_sleep_mode"
|
||||
|
||||
mock_api_client.set_energy_saving.side_effect = api_exception
|
||||
with pytest.raises(expected_exception):
|
||||
await hass.services.async_call(
|
||||
SWITCH_DOMAIN,
|
||||
service,
|
||||
{ATTR_ENTITY_ID: entity_id},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user