Improve ProxmoxVE config flow preparing bug fixing (#169682)

Co-authored-by: Erwin Douna <e.douna@gmail.com>
This commit is contained in:
Tom
2026-05-04 12:44:18 -04:00
committed by Paulus Schoutsen
co-authored by Erwin Douna
parent 9e357e7e5a
commit ce4fc9e880
4 changed files with 111 additions and 7 deletions
@@ -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 @@ class ProxmoxveConfigFlow(ConfigFlow, domain=DOMAIN):
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."""
@@ -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."
},
+5 -2
View File
@@ -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
)
+68 -3
View File
@@ -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"),