Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add number error handling for Peblar Rocksolid EV Chargers #133803

Merged
merged 2 commits into from
Dec 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions homeassistant/components/peblar/number.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
PeblarRuntimeData,
)
from .entity import PeblarEntity
from .helpers import peblar_exception_handler

PARALLEL_UPDATES = 1

Expand Down Expand Up @@ -94,6 +95,7 @@ def native_value(self) -> int | None:
"""Return the number value."""
return self.entity_description.value_fn(self.coordinator.data)

@peblar_exception_handler
async def async_set_native_value(self, value: float) -> None:
"""Change to new number value."""
await self.entity_description.set_value_fn(self.coordinator.api, value)
Expand Down
148 changes: 145 additions & 3 deletions tests/components/peblar/test_number.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,31 @@
"""Tests for the Peblar number platform."""

from unittest.mock import MagicMock

from peblar import PeblarAuthenticationError, PeblarConnectionError, PeblarError
import pytest
from syrupy.assertion import SnapshotAssertion

from homeassistant.components.number import (
ATTR_VALUE,
DOMAIN as NUMBER_DOMAIN,
SERVICE_SET_VALUE,
)
from homeassistant.components.peblar.const import DOMAIN
from homeassistant.const import Platform
from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntryState
from homeassistant.const import ATTR_ENTITY_ID, Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import device_registry as dr, entity_registry as er

from tests.common import MockConfigEntry, snapshot_platform

pytestmark = [
pytest.mark.parametrize("init_integration", [Platform.NUMBER], indirect=True),
pytest.mark.usefixtures("init_integration"),
]


@pytest.mark.parametrize("init_integration", [Platform.NUMBER], indirect=True)
@pytest.mark.usefixtures("init_integration")
async def test_entities(
hass: HomeAssistant,
snapshot: SnapshotAssertion,
Expand All @@ -33,3 +46,132 @@ async def test_entities(
)
for entity_entry in entity_entries:
assert entity_entry.device_id == device_entry.id


@pytest.mark.usefixtures("entity_registry_enabled_by_default")
async def test_number_set_value(
hass: HomeAssistant,
mock_peblar: MagicMock,
) -> None:
"""Test the Peblar EV charger numbers."""
entity_id = "number.peblar_ev_charger_charge_limit"
mocked_method = mock_peblar.rest_api.return_value.ev_interface
mocked_method.reset_mock()

# Test normal happy path number value change
await hass.services.async_call(
NUMBER_DOMAIN,
SERVICE_SET_VALUE,
{
ATTR_ENTITY_ID: entity_id,
ATTR_VALUE: 10,
},
blocking=True,
)

assert len(mocked_method.mock_calls) == 2
mocked_method.mock_calls[0].assert_called_with({"charge_current_limit": 10})


@pytest.mark.parametrize(
("error", "error_match", "translation_key", "translation_placeholders"),
[
(
PeblarConnectionError("Could not connect"),
(
r"An error occurred while communicating "
r"with the Peblar device: Could not connect"
),
"communication_error",
{"error": "Could not connect"},
),
(
PeblarError("Unknown error"),
(
r"An unknown error occurred while communicating "
r"with the Peblar device: Unknown error"
),
"unknown_error",
{"error": "Unknown error"},
),
],
)
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
async def test_number_set_value_communication_error(
hass: HomeAssistant,
mock_peblar: MagicMock,
error: Exception,
error_match: str,
translation_key: str,
translation_placeholders: dict,
) -> None:
"""Test the Peblar EV charger when a communication error occurs."""
entity_id = "number.peblar_ev_charger_charge_limit"
mock_peblar.rest_api.return_value.ev_interface.side_effect = error

with pytest.raises(
HomeAssistantError,
match=error_match,
) as excinfo:
await hass.services.async_call(
NUMBER_DOMAIN,
SERVICE_SET_VALUE,
{
ATTR_ENTITY_ID: entity_id,
ATTR_VALUE: 10,
},
blocking=True,
)

assert excinfo.value.translation_domain == DOMAIN
assert excinfo.value.translation_key == translation_key
assert excinfo.value.translation_placeholders == translation_placeholders


async def test_number_set_value_authentication_error(
hass: HomeAssistant,
mock_peblar: MagicMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test the Peblar EV charger when an authentication error occurs."""
entity_id = "number.peblar_ev_charger_charge_limit"
mock_peblar.rest_api.return_value.ev_interface.side_effect = (
PeblarAuthenticationError("Authentication error")
)
mock_peblar.login.side_effect = PeblarAuthenticationError("Authentication error")
with pytest.raises(
HomeAssistantError,
match=(
r"An authentication failure occurred while communicating "
r"with the Peblar device"
),
) as excinfo:
await hass.services.async_call(
NUMBER_DOMAIN,
SERVICE_SET_VALUE,
{
ATTR_ENTITY_ID: entity_id,
ATTR_VALUE: 10,
},
blocking=True,
)

assert excinfo.value.translation_domain == DOMAIN
assert excinfo.value.translation_key == "authentication_error"
assert not excinfo.value.translation_placeholders

# Ensure the device is reloaded on authentication error and triggers
# a reauthentication flow.
await hass.async_block_till_done()
assert mock_config_entry.state is ConfigEntryState.SETUP_ERROR

flows = hass.config_entries.flow.async_progress()
assert len(flows) == 1

flow = flows[0]
assert flow["step_id"] == "reauth_confirm"
assert flow["handler"] == DOMAIN

assert "context" in flow
assert flow["context"].get("source") == SOURCE_REAUTH
assert flow["context"].get("entry_id") == mock_config_entry.entry_id