diff --git a/homeassistant/components/pterodactyl/api.py b/homeassistant/components/pterodactyl/api.py index a60962ecf51..40ede9de103 100644 --- a/homeassistant/components/pterodactyl/api.py +++ b/homeassistant/components/pterodactyl/api.py @@ -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 diff --git a/homeassistant/components/pterodactyl/button.py b/homeassistant/components/pterodactyl/button.py index a1201f3ced5..44d3a6d0a82 100644 --- a/homeassistant/components/pterodactyl/button.py +++ b/homeassistant/components/pterodactyl/button.py @@ -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 diff --git a/homeassistant/components/pterodactyl/config_flow.py b/homeassistant/components/pterodactyl/config_flow.py index a36069d2bb9..e78ae776123 100644 --- a/homeassistant/components/pterodactyl/config_flow.py +++ b/homeassistant/components/pterodactyl/config_flow.py @@ -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") diff --git a/homeassistant/components/pterodactyl/coordinator.py b/homeassistant/components/pterodactyl/coordinator.py index 36456ade630..c8456ce9e55 100644 --- a/homeassistant/components/pterodactyl/coordinator.py +++ b/homeassistant/components/pterodactyl/coordinator.py @@ -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 diff --git a/homeassistant/components/pterodactyl/strings.json b/homeassistant/components/pterodactyl/strings.json index 97b33566f39..fe2b7730e1b 100644 --- a/homeassistant/components/pterodactyl/strings.json +++ b/homeassistant/components/pterodactyl/strings.json @@ -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": { diff --git a/tests/components/pterodactyl/test_config_flow.py b/tests/components/pterodactyl/test_config_flow.py index 14bb2d2f69f..3cb7f1c19d4 100644 --- a/tests/components/pterodactyl/test_config_flow.py +++ b/tests/components/pterodactyl/test_config_flow.py @@ -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)