diff --git a/homeassistant/components/proxmoxve/config_flow.py b/homeassistant/components/proxmoxve/config_flow.py index 7845f5405b920e..8b8fb917a7ad98 100644 --- a/homeassistant/components/proxmoxve/config_flow.py +++ b/homeassistant/components/proxmoxve/config_flow.py @@ -97,6 +97,19 @@ def _get_nodes_data(data: dict[str, Any]) -> list[dict[str, Any]]: verify_ssl=data.get(CONF_VERIFY_SSL, DEFAULT_VERIFY_SSL), **auth_kwargs, ) + except AuthenticationError as err: + raise ProxmoxAuthenticationError from err + except SSLError as err: + raise ProxmoxSSLError from err + except ConnectTimeout as err: + raise ProxmoxConnectTimeout from err + except ResourceException as err: + _LOGGER.debug("Error during Proxmox client initialisation", exc_info=True) + raise ProxmoxInitFailed from err + except requests.exceptions.ConnectionError as err: + raise ProxmoxConnectionError from err + + try: nodes = client.nodes.get() except AuthenticationError as err: raise ProxmoxAuthenticationError from err @@ -105,6 +118,7 @@ def _get_nodes_data(data: dict[str, Any]) -> list[dict[str, Any]]: except ConnectTimeout as err: raise ProxmoxConnectTimeout from err except ResourceException as err: + _LOGGER.debug("Error fetching nodes", exc_info=True) raise ProxmoxNoNodesFound from err except requests.exceptions.ConnectionError as err: raise ProxmoxConnectionError from err @@ -115,7 +129,10 @@ def _get_nodes_data(data: dict[str, Any]) -> list[dict[str, Any]]: vms = client.nodes(node["node"]).qemu.get() containers = client.nodes(node["node"]).lxc.get() except ResourceException as err: - raise ProxmoxNoNodesFound from err + _LOGGER.debug( + "Error fetching VMs/LXC for node %s", node["node"], exc_info=True + ) + raise ProxmoxNoVMLXCFound from err except requests.exceptions.ConnectionError as err: raise ProxmoxConnectionError from err @@ -298,9 +315,15 @@ async def _validate_input( except ProxmoxSSLError as exc: errors["base"] = "ssl_error" err = exc + except ProxmoxInitFailed as exc: + errors["base"] = "api_error_no_details" + err = exc except ProxmoxNoNodesFound as exc: errors["base"] = "no_nodes_found" err = exc + except ProxmoxNoVMLXCFound as exc: + errors["base"] = "no_vmlxc_found" + err = exc except ProxmoxConnectionError as exc: errors["base"] = "cannot_connect" err = exc @@ -370,6 +393,14 @@ class ProxmoxNoNodesFound(ProxmoxError): """Error to indicate no nodes found.""" +class ProxmoxNoVMLXCFound(ProxmoxError): + """Error to indicate no LXC or VM found.""" + + +class ProxmoxInitFailed(ProxmoxError): + """Error to indicate API initialisation failure.""" + + class ProxmoxConnectTimeout(ProxmoxError): """Error to indicate a connection timeout.""" diff --git a/homeassistant/components/proxmoxve/strings.json b/homeassistant/components/proxmoxve/strings.json index a88c366f1fd323..f239ce8ed3415c 100644 --- a/homeassistant/components/proxmoxve/strings.json +++ b/homeassistant/components/proxmoxve/strings.json @@ -6,10 +6,12 @@ "reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]" }, "error": { + "api_error_no_details": "An error occurred while communicating with the Proxmox VE instance", "cannot_connect": "Cannot connect to Proxmox VE server", "connect_timeout": "[%key:common::config_flow::error::timeout_connect%]", "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]", - "no_nodes_found": "No active nodes found", + "no_nodes_found": "No active nodes were found on the Proxmox VE server.", + "no_vmlxc_found": "No LXC or VM were found on the Proxmox VE server.", "ssl_error": "SSL check failed. Check the SSL settings" }, "step": { @@ -324,6 +326,9 @@ "no_permission_vm_lxc_power": { "message": "The configured Proxmox VE user does not have permission to manage the power state of VMs and containers. Please grant the user the 'VM.PowerMgmt' permission and try again." }, + "no_vmlxc_found": { + "message": "No LXC or VM were found on the Proxmox VE server." + }, "permissions_error": { "message": "Failed to retrieve Proxmox VE permissions. Please check your credentials and try again." }, diff --git a/tests/components/proxmoxve/conftest.py b/tests/components/proxmoxve/conftest.py index 43a71cd0d91ad5..1decc74ac46c22 100644 --- a/tests/components/proxmoxve/conftest.py +++ b/tests/components/proxmoxve/conftest.py @@ -85,8 +85,8 @@ def mock_setup_entry() -> Generator[AsyncMock]: with patch( "homeassistant.components.proxmoxve.async_setup_entry", return_value=True, - ) as mock_setup_entry: - yield mock_setup_entry + ) as mock_setup: + yield mock_setup @pytest.fixture @@ -104,6 +104,9 @@ def mock_proxmox_client(): mock_api.return_value = mock_instance mock_api_cf.return_value = mock_instance + mock_instance._mock_api = mock_api + mock_instance._mock_api_cf = mock_api_cf + mock_instance.access.ticket.post.return_value = load_json_object_fixture( "access_ticket.json", DOMAIN ) diff --git a/tests/components/proxmoxve/test_config_flow.py b/tests/components/proxmoxve/test_config_flow.py index 24fcbea2b7d3ee..ab031995240156 100644 --- a/tests/components/proxmoxve/test_config_flow.py +++ b/tests/components/proxmoxve/test_config_flow.py @@ -147,8 +147,8 @@ async def test_form( "connect_timeout", ), ( - ResourceException("404", "status_message", "content"), - "no_nodes_found", + ResourceException("500", "status_message", "content"), + "api_error_no_details", ), ( requests.exceptions.ConnectionError("Connection error"), @@ -161,6 +161,71 @@ async def test_form_exceptions( mock_proxmox_client: MagicMock, exception: Exception, reason: str, +) -> None: + """Test we handle all exceptions.""" + mock_proxmox_client._mock_api_cf.side_effect = exception + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER} + ) + + assert result["type"] is FlowResultType.FORM + assert result["step_id"] == "user" + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input=MOCK_USER_STEP, + ) + + assert result["type"] is FlowResultType.FORM + assert result["step_id"] == "user_auth" + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input=MOCK_USER_AUTH_STEP_PASSWORD, + ) + + assert result["type"] is FlowResultType.FORM + assert result["errors"] == {"base": reason} + + mock_proxmox_client._mock_api_cf.side_effect = None + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input=MOCK_USER_AUTH_STEP_PASSWORD + ) + + assert result["type"] is FlowResultType.CREATE_ENTRY + + +@pytest.mark.parametrize( + ("exception", "reason"), + [ + ( + AuthenticationError("Invalid credentials"), + "invalid_auth", + ), + ( + SSLError("SSL handshake failed"), + "ssl_error", + ), + ( + ConnectTimeout("Connection timed out"), + "connect_timeout", + ), + ( + ResourceException("400", "status_message", "content"), + "no_nodes_found", + ), + ( + requests.exceptions.ConnectionError("Connection error"), + "cannot_connect", + ), + ], +) +async def test_form_node_exceptions( + hass: HomeAssistant, + mock_proxmox_client: MagicMock, + exception: Exception, + reason: str, ) -> None: """Test we handle all exceptions.""" mock_proxmox_client.nodes.get.side_effect = exception @@ -201,7 +266,7 @@ async def test_form_exceptions( [ ( ResourceException("404", "status_message", "content"), - "no_nodes_found", + "no_vmlxc_found", ), ( requests.exceptions.ConnectionError("Connection error"),