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