Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 105 additions & 0 deletions homeassistant/components/librenms/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,3 +123,108 @@ async def async_step_user(
return self.async_show_form(
step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors
)

async def async_step_reauth(
self, entry_data: Mapping[str, Any]
) -> ConfigFlowResult:
"""Trigger a reauthentication flow."""
self._current_data = entry_data
self._name = entry_data[CONF_HOST]

return await self.async_step_reauth_confirm()

async def async_step_reauth_confirm(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle reauthorization flow."""
errors = {}

if user_input is not None:
try:
await check_connection(
self.hass,
self._current_data[CONF_HOST],
self._current_data[CONF_PORT],
self._current_data[CONF_SSL],
self._current_data[CONF_VERIFY_SSL],
user_input[CONF_API_KEY],
)
except LibrenmsUnauthenticatedError:
errors["base"] = "invalid_auth"
except CONNECT_ERRORS:
errors["base"] = "cannot_connect"
except Exception:
_LOGGER.exception("Unexpected exception")
errors["base"] = "unknown"
else:
return self.async_update_reload_and_abort(
self._get_reauth_entry(), data_updates=user_input
)

return self.async_show_form(
step_id="reauth_confirm",
data_schema=vol.Schema({vol.Required(CONF_API_KEY): str}),
description_placeholders={"name": self._name},
errors=errors,
)

async def async_step_reconfigure(
self,
user_input: Mapping[str, Any] | None = None,
) -> ConfigFlowResult:
"""Handle reconfiguration of LibreNMS."""
entry = self._get_reconfigure_entry()
current_data = entry.data

url = f"{'https' if current_data[CONF_SSL] else 'http'}://{current_data[CONF_HOST]}:{current_data[CONF_PORT]}"
verify_ssl = current_data[CONF_VERIFY_SSL]

errors: dict[str, str] = {}
if user_input is not None:
url = user_input[CONF_URL]
verify_ssl = user_input[CONF_VERIFY_SSL]
try:
(host, port, ssl) = _parse_url(user_input[CONF_URL])
except InvalidUrl:
errors[CONF_URL] = "invalid_url"
else:
try:
Comment on lines +190 to +191
await check_connection(
self.hass,
host,
port,
ssl,
user_input[CONF_VERIFY_SSL],
current_data[CONF_API_KEY],
)
except LibrenmsUnauthenticatedError:
errors["base"] = "invalid_auth"
except CONNECT_ERRORS:
errors["base"] = "cannot_connect"
except Exception:
_LOGGER.exception("Unexpected exception")
errors["base"] = "unknown"
else:
return self.async_update_reload_and_abort(
entry,
data_updates={
**current_data,
CONF_HOST: host,
CONF_PORT: port,
CONF_SSL: ssl,
CONF_VERIFY_SSL: user_input[CONF_VERIFY_SSL],
},
)

return self.async_show_form(
step_id="reconfigure",
data_schema=vol.Schema(
{
vol.Required(CONF_URL, default=url): TextSelector(
config=TextSelectorConfig(type=TextSelectorType.URL)
),
vol.Required(CONF_VERIFY_SSL, default=verify_ssl): bool,
}
),
errors=errors,
)
2 changes: 1 addition & 1 deletion homeassistant/components/librenms/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@
"integration_type": "service",
"iot_class": "local_polling",
"loggers": ["aiolibrenms"],
"quality_scale": "bronze",
"quality_scale": "silver",
"requirements": ["aiolibrenms==0.0.3"]
}
4 changes: 2 additions & 2 deletions homeassistant/components/librenms/quality_scale.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ rules:
integration-owner: done
log-when-unavailable: done
parallel-updates: done
reauthentication-flow: todo
reauthentication-flow: done
test-coverage: done

# Gold
Expand All @@ -66,7 +66,7 @@ rules:
entity-translations: done
exception-translations: done
icon-translations: done
reconfiguration-flow: todo
reconfiguration-flow: done
repair-issues:
status: exempt
comment: No repair issues needed
Expand Down
23 changes: 22 additions & 1 deletion homeassistant/components/librenms/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
},
"config": {
"abort": {
"already_configured": "This LibreNMS instance is already configured."
"already_configured": "This LibreNMS instance is already configured.",
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]"
},
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
Expand All @@ -15,6 +17,25 @@
"unknown": "[%key:common::config_flow::error::unknown%]"
},
"step": {
"reauth_confirm": {
"data": {
"api_key": "[%key:common::config_flow::data::api_key%]"
},
"data_description": {
"api_key": "[%key:component::librenms::common::data_desc_api_key%]"
},
"description": "Update the API key for {name}."
},
"reconfigure": {
"data": {
"url": "[%key:common::config_flow::data::url%]",
"verify_ssl": "[%key:common::config_flow::data::verify_ssl%]"
},
"data_description": {
"url": "[%key:component::librenms::common::data_desc_url%]",
"verify_ssl": "[%key:component::librenms::common::data_desc_ssl_verify%]"
}
},
"user": {
"data": {
"api_key": "[%key:common::config_flow::data::api_key%]",
Expand Down
177 changes: 176 additions & 1 deletion tests/components/librenms/test_config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,14 @@

from homeassistant.components.librenms.const import DOMAIN
from homeassistant.config_entries import SOURCE_USER
from homeassistant.const import CONF_URL
from homeassistant.const import (
CONF_API_KEY,
CONF_HOST,
CONF_PORT,
CONF_SSL,
CONF_URL,
CONF_VERIFY_SSL,
)
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType

Expand Down Expand Up @@ -123,3 +130,171 @@ async def test_user_already_configured(
)
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "already_configured"


@pytest.mark.usefixtures("mock_setup_entry")
async def test_reauth_flow(
hass: HomeAssistant, mock_librenms: Mock, mock_config_entry: MockConfigEntry
) -> None:
"""Test reauthentication flow."""
mock_config_entry.add_to_hass(hass)
result = await mock_config_entry.start_reauth_flow(hass)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "reauth_confirm"

result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input={
CONF_API_KEY: "other_fake_api_key",
},
)

assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "reauth_successful"
assert mock_config_entry.data[CONF_API_KEY] == "other_fake_api_key"


@pytest.mark.parametrize(
("exception", "error"),
[
(
LibrenmsUnauthenticatedError({"message": "Unauthenticated."}),
"invalid_auth",
),
(ClientError, "cannot_connect"),
(Exception, "unknown"),
],
)
async def test_reauth_flow_error_handling(
hass: HomeAssistant,
mock_setup_entry: AsyncMock,
mock_librenms: Mock,
mock_config_entry: MockConfigEntry,
exception: Exception,
error: str,
) -> None:
"""Test reauthentication flow with errors."""
mock_config_entry.add_to_hass(hass)
result = await mock_config_entry.start_reauth_flow(hass)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "reauth_confirm"

mock_librenms.system.async_get_system_info.side_effect = exception

result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input={
CONF_API_KEY: "other_fake_api_key",
},
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "reauth_confirm"
assert result["errors"] == {"base": error}

mock_librenms.system.async_get_system_info.side_effect = None

result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input={
CONF_API_KEY: "other_fake_api_key",
},
)

assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "reauth_successful"
assert mock_config_entry.data[CONF_API_KEY] == "other_fake_api_key"
assert len(mock_setup_entry.mock_calls) == 1


@pytest.mark.usefixtures("mock_setup_entry")
async def test_reconfigure_flow(
hass: HomeAssistant, mock_librenms: Mock, mock_config_entry: MockConfigEntry
) -> None:
"""Test reconfigure flow."""
mock_config_entry.add_to_hass(hass)
result = await mock_config_entry.start_reconfigure_flow(hass)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "reconfigure"

result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input={CONF_URL: "https://librenms:8443", CONF_VERIFY_SSL: True},
)

assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "reconfigure_successful"
assert mock_config_entry.data[CONF_HOST] == "librenms"
assert mock_config_entry.data[CONF_PORT] == 8443
assert mock_config_entry.data[CONF_SSL] is True
assert mock_config_entry.data[CONF_VERIFY_SSL] is True


@pytest.mark.parametrize(
("exception", "error"),
[
(
LibrenmsUnauthenticatedError({"message": "Unauthenticated."}),
"invalid_auth",
),
(ClientError, "cannot_connect"),
(Exception, "unknown"),
],
)
@pytest.mark.usefixtures("mock_setup_entry")
async def test_step_reconfigure_error_handling(
hass: HomeAssistant,
mock_librenms: Mock,
mock_config_entry: MockConfigEntry,
exception: Exception,
error: str,
) -> None:
"""Test a user initiated config flow with errors."""
mock_config_entry.add_to_hass(hass)
result = await mock_config_entry.start_reconfigure_flow(hass)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "reconfigure"

mock_librenms.system.async_get_system_info.side_effect = exception

result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input={CONF_URL: "https://librenms:8443", CONF_VERIFY_SSL: True},
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "reconfigure"
assert result["errors"] == {"base": error}

mock_librenms.system.async_get_system_info.side_effect = None

result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input={CONF_URL: "https://librenms:8443", CONF_VERIFY_SSL: True},
)
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "reconfigure_successful"


@pytest.mark.usefixtures("mock_setup_entry")
async def test_step_reconfigure_invalid_url(
hass: HomeAssistant, mock_librenms: Mock, mock_config_entry: MockConfigEntry
) -> None:
"""Test a user initiated config flow with errors."""
mock_config_entry.add_to_hass(hass)
result = await mock_config_entry.start_reconfigure_flow(hass)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "reconfigure"

result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input={CONF_URL: "hts://invalid"},
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "reconfigure"
assert result["errors"] == {CONF_URL: "invalid_url"}

result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input={CONF_URL: "https://librenms:8443", CONF_VERIFY_SSL: True},
)
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "reconfigure_successful"
Loading