mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 21:27:38 +00:00
Improve exception handling in Pterodactyl (#141955)
Improve exception handling
This commit is contained in:
parent
03c70e18df
commit
4a562b5085
@ -5,20 +5,16 @@ from enum import StrEnum
|
||||
import logging
|
||||
|
||||
from pydactyl import PterodactylClient
|
||||
from pydactyl.exceptions import (
|
||||
BadRequestError,
|
||||
ClientConfigError,
|
||||
PterodactylApiError,
|
||||
PydactylError,
|
||||
)
|
||||
from pydactyl.exceptions import BadRequestError, PterodactylApiError
|
||||
from requests.exceptions import ConnectionError, HTTPError
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class PterodactylConfigurationError(Exception):
|
||||
"""Raised when the configuration is invalid."""
|
||||
class PterodactylAuthorizationError(Exception):
|
||||
"""Raised when access to server is unauthorized."""
|
||||
|
||||
|
||||
class PterodactylConnectionError(Exception):
|
||||
@ -75,13 +71,12 @@ class PterodactylAPI:
|
||||
paginated_response = await self.hass.async_add_executor_job(
|
||||
self.pterodactyl.client.servers.list_servers
|
||||
)
|
||||
except ClientConfigError as error:
|
||||
raise PterodactylConfigurationError(error) from error
|
||||
except (
|
||||
PydactylError,
|
||||
BadRequestError,
|
||||
PterodactylApiError,
|
||||
) as error:
|
||||
except (BadRequestError, PterodactylApiError, ConnectionError) as error:
|
||||
raise PterodactylConnectionError(error) from error
|
||||
except HTTPError as error:
|
||||
if error.response.status_code == 401:
|
||||
raise PterodactylAuthorizationError(error) from error
|
||||
|
||||
raise PterodactylConnectionError(error) from error
|
||||
else:
|
||||
game_servers = paginated_response.collect()
|
||||
@ -108,11 +103,12 @@ class PterodactylAPI:
|
||||
server, utilization = await self.hass.async_add_executor_job(
|
||||
self.get_server_data, identifier
|
||||
)
|
||||
except (
|
||||
PydactylError,
|
||||
BadRequestError,
|
||||
PterodactylApiError,
|
||||
) as error:
|
||||
except (BadRequestError, PterodactylApiError, ConnectionError) as error:
|
||||
raise PterodactylConnectionError(error) from error
|
||||
except HTTPError as error:
|
||||
if error.response.status_code == 401:
|
||||
raise PterodactylAuthorizationError(error) from error
|
||||
|
||||
raise PterodactylConnectionError(error) from error
|
||||
else:
|
||||
data[identifier] = PterodactylData(
|
||||
@ -145,9 +141,10 @@ class PterodactylAPI:
|
||||
identifier,
|
||||
command,
|
||||
)
|
||||
except (
|
||||
PydactylError,
|
||||
BadRequestError,
|
||||
PterodactylApiError,
|
||||
) as error:
|
||||
except (BadRequestError, PterodactylApiError, ConnectionError) as error:
|
||||
raise PterodactylConnectionError(error) from error
|
||||
except HTTPError as error:
|
||||
if error.response.status_code == 401:
|
||||
raise PterodactylAuthorizationError(error) from error
|
||||
|
||||
raise PterodactylConnectionError(error) from error
|
||||
|
@ -9,7 +9,11 @@ from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from .api import PterodactylCommand, PterodactylConnectionError
|
||||
from .api import (
|
||||
PterodactylAuthorizationError,
|
||||
PterodactylCommand,
|
||||
PterodactylConnectionError,
|
||||
)
|
||||
from .coordinator import PterodactylConfigEntry, PterodactylCoordinator
|
||||
from .entity import PterodactylEntity
|
||||
|
||||
@ -94,5 +98,9 @@ class PterodactylButtonEntity(PterodactylEntity, ButtonEntity):
|
||||
)
|
||||
except PterodactylConnectionError as err:
|
||||
raise HomeAssistantError(
|
||||
f"Failed to send action '{self.entity_description.key}'"
|
||||
f"Failed to send action '{self.entity_description.key}': Connection error"
|
||||
) from err
|
||||
except PterodactylAuthorizationError as err:
|
||||
raise HomeAssistantError(
|
||||
f"Failed to send action '{self.entity_description.key}': Unauthorized"
|
||||
) from err
|
||||
|
@ -13,7 +13,7 @@ from homeassistant.const import CONF_API_KEY, CONF_URL
|
||||
|
||||
from .api import (
|
||||
PterodactylAPI,
|
||||
PterodactylConfigurationError,
|
||||
PterodactylAuthorizationError,
|
||||
PterodactylConnectionError,
|
||||
)
|
||||
from .const import DOMAIN
|
||||
@ -49,7 +49,9 @@ class PterodactylConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
|
||||
try:
|
||||
await api.async_init()
|
||||
except (PterodactylConfigurationError, PterodactylConnectionError):
|
||||
except PterodactylAuthorizationError:
|
||||
errors["base"] = "invalid_auth"
|
||||
except PterodactylConnectionError:
|
||||
errors["base"] = "cannot_connect"
|
||||
except Exception:
|
||||
_LOGGER.exception("Unexpected exception occurred during config flow")
|
||||
|
@ -12,7 +12,7 @@ from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, Upda
|
||||
|
||||
from .api import (
|
||||
PterodactylAPI,
|
||||
PterodactylConfigurationError,
|
||||
PterodactylAuthorizationError,
|
||||
PterodactylConnectionError,
|
||||
PterodactylData,
|
||||
)
|
||||
@ -55,12 +55,12 @@ class PterodactylCoordinator(DataUpdateCoordinator[dict[str, PterodactylData]]):
|
||||
|
||||
try:
|
||||
await self.api.async_init()
|
||||
except PterodactylConfigurationError as error:
|
||||
except (PterodactylAuthorizationError, PterodactylConnectionError) as error:
|
||||
raise UpdateFailed(error) from error
|
||||
|
||||
async def _async_update_data(self) -> dict[str, PterodactylData]:
|
||||
"""Get updated data from the Pterodactyl server."""
|
||||
try:
|
||||
return await self.api.async_get_data()
|
||||
except PterodactylConnectionError as error:
|
||||
except (PterodactylAuthorizationError, PterodactylConnectionError) as error:
|
||||
raise UpdateFailed(error) from error
|
||||
|
@ -14,6 +14,7 @@
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
|
||||
"unknown": "[%key:common::config_flow::error::unknown%]"
|
||||
},
|
||||
"abort": {
|
||||
|
@ -1,8 +1,10 @@
|
||||
"""Test the Pterodactyl config flow."""
|
||||
|
||||
from pydactyl import PterodactylClient
|
||||
from pydactyl.exceptions import ClientConfigError, PterodactylApiError
|
||||
from pydactyl.exceptions import BadRequestError, PterodactylApiError
|
||||
import pytest
|
||||
from requests.exceptions import HTTPError
|
||||
from requests.models import Response
|
||||
|
||||
from homeassistant.components.pterodactyl.const import DOMAIN
|
||||
from homeassistant.config_entries import SOURCE_USER
|
||||
@ -14,6 +16,14 @@ from .conftest import TEST_URL, TEST_USER_INPUT
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
def mock_response():
|
||||
"""Mock HTTP response."""
|
||||
mock = Response()
|
||||
mock.status_code = 401
|
||||
|
||||
return mock
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("mock_pterodactyl", "mock_setup_entry")
|
||||
async def test_full_flow(hass: HomeAssistant) -> None:
|
||||
"""Test full flow without errors."""
|
||||
@ -36,18 +46,21 @@ async def test_full_flow(hass: HomeAssistant) -> None:
|
||||
|
||||
@pytest.mark.usefixtures("mock_setup_entry")
|
||||
@pytest.mark.parametrize(
|
||||
"exception_type",
|
||||
("exception_type", "expected_error"),
|
||||
[
|
||||
ClientConfigError,
|
||||
PterodactylApiError,
|
||||
(PterodactylApiError, "cannot_connect"),
|
||||
(BadRequestError, "cannot_connect"),
|
||||
(Exception, "unknown"),
|
||||
(HTTPError(response=mock_response()), "invalid_auth"),
|
||||
],
|
||||
)
|
||||
async def test_recovery_after_api_error(
|
||||
async def test_recovery_after_error(
|
||||
hass: HomeAssistant,
|
||||
exception_type,
|
||||
exception_type: Exception,
|
||||
expected_error: str,
|
||||
mock_pterodactyl: PterodactylClient,
|
||||
) -> None:
|
||||
"""Test recovery after an API error."""
|
||||
"""Test recovery after an error."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": SOURCE_USER}
|
||||
)
|
||||
@ -63,42 +76,7 @@ async def test_recovery_after_api_error(
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["errors"] == {"base": "cannot_connect"}
|
||||
|
||||
mock_pterodactyl.reset_mock(side_effect=True)
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
flow_id=result["flow_id"], user_input=TEST_USER_INPUT
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||
assert result["title"] == TEST_URL
|
||||
assert result["data"] == TEST_USER_INPUT
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("mock_setup_entry")
|
||||
async def test_recovery_after_unknown_error(
|
||||
hass: HomeAssistant,
|
||||
mock_pterodactyl: PterodactylClient,
|
||||
) -> None:
|
||||
"""Test recovery after an API error."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": SOURCE_USER}
|
||||
)
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["errors"] == {}
|
||||
|
||||
mock_pterodactyl.client.servers.list_servers.side_effect = Exception
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
flow_id=result["flow_id"],
|
||||
user_input=TEST_USER_INPUT,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["errors"] == {"base": "unknown"}
|
||||
assert result["errors"] == {"base": expected_error}
|
||||
|
||||
mock_pterodactyl.reset_mock(side_effect=True)
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user