mirror of
https://github.com/home-assistant/core.git
synced 2025-07-09 22:37:11 +00:00
Improve error handling and add exception translations for NextDNS integration (#141005)
* Add exception translations * Coverage * Add missing auth_error * Coverage * Use async_start_reauth * Fix test * Remove method placeholder
This commit is contained in:
parent
a338205b73
commit
53f1dd8adf
@ -36,6 +36,7 @@ from .const import (
|
||||
ATTR_SETTINGS,
|
||||
ATTR_STATUS,
|
||||
CONF_PROFILE_ID,
|
||||
DOMAIN,
|
||||
UPDATE_INTERVAL_ANALYTICS,
|
||||
UPDATE_INTERVAL_CONNECTION,
|
||||
UPDATE_INTERVAL_SETTINGS,
|
||||
@ -88,9 +89,20 @@ async def async_setup_entry(hass: HomeAssistant, entry: NextDnsConfigEntry) -> b
|
||||
try:
|
||||
nextdns = await NextDns.create(websession, api_key)
|
||||
except (ApiError, ClientConnectorError, RetryError, TimeoutError) as err:
|
||||
raise ConfigEntryNotReady from err
|
||||
raise ConfigEntryNotReady(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="cannot_connect",
|
||||
translation_placeholders={
|
||||
"entry": entry.title,
|
||||
"error": repr(err),
|
||||
},
|
||||
) from err
|
||||
except InvalidApiKeyError as err:
|
||||
raise ConfigEntryAuthFailed from err
|
||||
raise ConfigEntryAuthFailed(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="auth_error",
|
||||
translation_placeholders={"entry": entry.title},
|
||||
) from err
|
||||
|
||||
tasks = []
|
||||
coordinators = {}
|
||||
|
@ -2,15 +2,19 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from nextdns import AnalyticsStatus
|
||||
from aiohttp import ClientError
|
||||
from aiohttp.client_exceptions import ClientConnectorError
|
||||
from nextdns import AnalyticsStatus, ApiError, InvalidApiKeyError
|
||||
|
||||
from homeassistant.components.button import ButtonEntity, ButtonEntityDescription
|
||||
from homeassistant.const import EntityCategory
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from . import NextDnsConfigEntry
|
||||
from .const import DOMAIN
|
||||
from .coordinator import NextDnsUpdateCoordinator
|
||||
|
||||
PARALLEL_UPDATES = 1
|
||||
@ -53,4 +57,21 @@ class NextDnsButton(
|
||||
|
||||
async def async_press(self) -> None:
|
||||
"""Trigger cleaning logs."""
|
||||
await self.coordinator.nextdns.clear_logs(self.coordinator.profile_id)
|
||||
try:
|
||||
await self.coordinator.nextdns.clear_logs(self.coordinator.profile_id)
|
||||
except (
|
||||
ApiError,
|
||||
ClientConnectorError,
|
||||
TimeoutError,
|
||||
ClientError,
|
||||
) as err:
|
||||
raise HomeAssistantError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="method_error",
|
||||
translation_placeholders={
|
||||
"entity": self.entity_id,
|
||||
"error": repr(err),
|
||||
},
|
||||
) from err
|
||||
except InvalidApiKeyError:
|
||||
self.coordinator.config_entry.async_start_reauth(self.hass)
|
||||
|
@ -79,9 +79,20 @@ class NextDnsUpdateCoordinator(DataUpdateCoordinator[CoordinatorDataT]):
|
||||
ClientConnectorError,
|
||||
RetryError,
|
||||
) as err:
|
||||
raise UpdateFailed(err) from err
|
||||
raise UpdateFailed(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="update_error",
|
||||
translation_placeholders={
|
||||
"entry": self.config_entry.title,
|
||||
"error": repr(err),
|
||||
},
|
||||
) from err
|
||||
except InvalidApiKeyError as err:
|
||||
raise ConfigEntryAuthFailed from err
|
||||
raise ConfigEntryAuthFailed(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="auth_error",
|
||||
translation_placeholders={"entry": self.config_entry.title},
|
||||
) from err
|
||||
|
||||
async def _async_update_data_internal(self) -> CoordinatorDataT:
|
||||
"""Update data via library."""
|
||||
|
@ -359,5 +359,19 @@
|
||||
"name": "Force YouTube restricted mode"
|
||||
}
|
||||
}
|
||||
},
|
||||
"exceptions": {
|
||||
"auth_error": {
|
||||
"message": "Authentication failed for {entry}, please update your API key"
|
||||
},
|
||||
"cannot_connect": {
|
||||
"message": "An error occurred while connecting to the NextDNS API for {entry}: {error}"
|
||||
},
|
||||
"method_error": {
|
||||
"message": "An error occurred while calling the NextDNS API method for {entity}: {error}"
|
||||
},
|
||||
"update_error": {
|
||||
"message": "An error occurred while retrieving data from the NextDNS API for {entry}: {error}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ from typing import Any
|
||||
|
||||
from aiohttp import ClientError
|
||||
from aiohttp.client_exceptions import ClientConnectorError
|
||||
from nextdns import ApiError, Settings
|
||||
from nextdns import ApiError, InvalidApiKeyError, Settings
|
||||
|
||||
from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription
|
||||
from homeassistant.const import EntityCategory
|
||||
@ -18,6 +18,7 @@ from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from . import NextDnsConfigEntry
|
||||
from .const import DOMAIN
|
||||
from .coordinator import NextDnsUpdateCoordinator
|
||||
|
||||
PARALLEL_UPDATES = 1
|
||||
@ -582,9 +583,16 @@ class NextDnsSwitch(
|
||||
ClientError,
|
||||
) as err:
|
||||
raise HomeAssistantError(
|
||||
"NextDNS API returned an error calling set_setting for"
|
||||
f" {self.entity_id}: {err}"
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="method_error",
|
||||
translation_placeholders={
|
||||
"entity": self.entity_id,
|
||||
"error": repr(err),
|
||||
},
|
||||
) from err
|
||||
except InvalidApiKeyError:
|
||||
self.coordinator.config_entry.async_start_reauth(self.hass)
|
||||
return
|
||||
|
||||
if result:
|
||||
self._attr_is_on = new_state
|
||||
|
@ -1,12 +1,19 @@
|
||||
"""Test button of NextDNS integration."""
|
||||
|
||||
from unittest.mock import patch
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
from aiohttp import ClientError
|
||||
from aiohttp.client_exceptions import ClientConnectorError
|
||||
from nextdns import ApiError, InvalidApiKeyError
|
||||
import pytest
|
||||
from syrupy import SnapshotAssertion
|
||||
|
||||
from homeassistant.components.button import DOMAIN as BUTTON_DOMAIN
|
||||
from homeassistant.components.button import DOMAIN as BUTTON_DOMAIN, SERVICE_PRESS
|
||||
from homeassistant.components.nextdns.const import DOMAIN
|
||||
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 entity_registry as er
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
@ -36,7 +43,7 @@ async def test_button_press(hass: HomeAssistant) -> None:
|
||||
):
|
||||
await hass.services.async_call(
|
||||
BUTTON_DOMAIN,
|
||||
"press",
|
||||
SERVICE_PRESS,
|
||||
{ATTR_ENTITY_ID: "button.fake_profile_clear_logs"},
|
||||
blocking=True,
|
||||
)
|
||||
@ -47,3 +54,60 @@ async def test_button_press(hass: HomeAssistant) -> None:
|
||||
state = hass.states.get("button.fake_profile_clear_logs")
|
||||
assert state
|
||||
assert state.state == now.isoformat()
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"exc",
|
||||
[
|
||||
ApiError(Mock()),
|
||||
TimeoutError,
|
||||
ClientConnectorError(Mock(), Mock()),
|
||||
ClientError,
|
||||
],
|
||||
)
|
||||
async def test_button_failure(hass: HomeAssistant, exc: Exception) -> None:
|
||||
"""Tests that the press action throws HomeAssistantError."""
|
||||
await init_integration(hass)
|
||||
|
||||
with (
|
||||
patch("homeassistant.components.nextdns.NextDns.clear_logs", side_effect=exc),
|
||||
pytest.raises(
|
||||
HomeAssistantError,
|
||||
match="An error occurred while calling the NextDNS API method for button.fake_profile_clear_logs",
|
||||
),
|
||||
):
|
||||
await hass.services.async_call(
|
||||
BUTTON_DOMAIN,
|
||||
SERVICE_PRESS,
|
||||
{ATTR_ENTITY_ID: "button.fake_profile_clear_logs"},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
|
||||
async def test_button_auth_error(hass: HomeAssistant) -> None:
|
||||
"""Tests that the press action starts re-auth flow."""
|
||||
entry = await init_integration(hass)
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.nextdns.NextDns.clear_logs",
|
||||
side_effect=InvalidApiKeyError,
|
||||
):
|
||||
await hass.services.async_call(
|
||||
BUTTON_DOMAIN,
|
||||
SERVICE_PRESS,
|
||||
{ATTR_ENTITY_ID: "button.fake_profile_clear_logs"},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
assert entry.state is ConfigEntryState.LOADED
|
||||
|
||||
flows = hass.config_entries.flow.async_progress()
|
||||
assert len(flows) == 1
|
||||
|
||||
flow = flows[0]
|
||||
assert flow.get("step_id") == "reauth_confirm"
|
||||
assert flow.get("handler") == DOMAIN
|
||||
|
||||
assert "context" in flow
|
||||
assert flow["context"].get("source") == SOURCE_REAUTH
|
||||
assert flow["context"].get("entry_id") == entry.entry_id
|
||||
|
@ -5,12 +5,14 @@ from unittest.mock import Mock, patch
|
||||
|
||||
from aiohttp import ClientError
|
||||
from aiohttp.client_exceptions import ClientConnectorError
|
||||
from nextdns import ApiError
|
||||
from nextdns import ApiError, InvalidApiKeyError
|
||||
import pytest
|
||||
from syrupy import SnapshotAssertion
|
||||
from tenacity import RetryError
|
||||
|
||||
from homeassistant.components.nextdns.const import DOMAIN
|
||||
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
|
||||
from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntryState
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID,
|
||||
SERVICE_TURN_OFF,
|
||||
@ -158,3 +160,32 @@ async def test_switch_failure(hass: HomeAssistant, exc: Exception) -> None:
|
||||
{ATTR_ENTITY_ID: "switch.fake_profile_block_page"},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
|
||||
async def test_switch_auth_error(hass: HomeAssistant) -> None:
|
||||
"""Tests that the turn on/off action starts re-auth flow."""
|
||||
entry = await init_integration(hass)
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.nextdns.NextDns.set_setting",
|
||||
side_effect=InvalidApiKeyError,
|
||||
):
|
||||
await hass.services.async_call(
|
||||
SWITCH_DOMAIN,
|
||||
SERVICE_TURN_ON,
|
||||
{ATTR_ENTITY_ID: "switch.fake_profile_block_page"},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
assert entry.state is ConfigEntryState.LOADED
|
||||
|
||||
flows = hass.config_entries.flow.async_progress()
|
||||
assert len(flows) == 1
|
||||
|
||||
flow = flows[0]
|
||||
assert flow.get("step_id") == "reauth_confirm"
|
||||
assert flow.get("handler") == DOMAIN
|
||||
|
||||
assert "context" in flow
|
||||
assert flow["context"].get("source") == SOURCE_REAUTH
|
||||
assert flow["context"].get("entry_id") == entry.entry_id
|
||||
|
Loading…
x
Reference in New Issue
Block a user