core/tests/components/nasweb/test_config_flow.py
nasWebio ed1366f463
Add NASweb integration (#98118)
* Add NASweb integration

* Fix DeviceInfo import

* Remove commented out code

* Change class name for uniquness

* Drop CoordinatorEntity inheritance

* Rename class Output to more descriptive: RelaySwitch

* Update required webio-api version

* Implement on-the-fly addition/removal of entities

* Set coordinator name matching device name

* Set entities with too old status as unavailable

* Drop Optional in favor of modern typing

* Fix spelling of a variable

* Rename commons to more fitting name: helper

* Remove redundant code

* Let unload fail when there is no coordinator

* Fix bad docstring

* Rename cord to coordinator for clarity

* Remove default value for pop and let it raise exception

* Drop workaround and use get_url from helper.network

* Use webhook to send data from device

* Deinitialize coordinator when no longer needed

* Use Python formattable string

* Use dataclass to store integration data in hass.data

* Raise ConfigEntryNotReady when appropriate

* Refactor NASwebData class

* Move RelaySwitch to switch.py

* Fix ConfigFlow tests

* Create issues when entry fails to load

* Respond when correctly received status update

* Depend on webhook instead of http

* Create issue when status is not received during entry set up

* Make issue_id unique across integration entries

* Remove unnecessary initializations

* Inherit CoordinatorEntity to avoid code duplication

* Optimize property access via assignment in __init__

* Use preexisting mechanism to fill schema with user input

* Fix translation strings

* Handle unavailable or unreachable internal url

* Implement custom coordinator for push driven data updates

* Move module-specific constants to respective modules

* Fix requirements_all.txt

* Fix CODEOWNERS file

* Raise ConfigEntryError instead of issue creation

* Fix entity registry import

* Use HassKey as key in hass.data

* Use typed ConfigEntry

* Store runtime data in config entry

* Rewrite to be more Pythonic

* Move add/remove of switch entities to switch.py

* Skip unnecessary check

* Remove unnecessary type hints

* Remove unnecessary nonlocal

* Use a more descriptive docstring

* Add docstrings to NASwebCoordinator

* Fix formatting

* Use correct return type

* Fix tests to align with changed code

* Remove commented code

* Use serial number as config entry id

* Catch AbortFlow exception

* Update tests to check ConfigEntry Unique ID

* Remove unnecessary form abort
2024-11-08 12:03:32 +01:00

209 lines
6.7 KiB
Python

"""Test the NASweb config flow."""
from unittest.mock import AsyncMock, MagicMock, patch
import pytest
from webio_api.api_client import AuthError
from homeassistant import config_entries
from homeassistant.components.nasweb.const import DOMAIN
from homeassistant.config_entries import ConfigFlowResult
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType
from homeassistant.helpers.network import NoURLAvailableError
from .conftest import (
BASE_CONFIG_FLOW,
BASE_COORDINATOR,
BASE_NASWEB_DATA,
TEST_SERIAL_NUMBER,
)
pytestmark = pytest.mark.usefixtures("mock_setup_entry")
TEST_USER_INPUT = {
CONF_HOST: "1.1.1.1",
CONF_USERNAME: "test-username",
CONF_PASSWORD: "test-password",
}
async def _add_test_config_entry(hass: HomeAssistant) -> ConfigFlowResult:
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
assert result.get("type") == FlowResultType.FORM
assert not result.get("errors")
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"], TEST_USER_INPUT
)
await hass.async_block_till_done()
return result2
async def test_form(
hass: HomeAssistant,
mock_setup_entry: AsyncMock,
validate_input_all_ok: dict[str, AsyncMock | MagicMock],
) -> None:
"""Test the form."""
result = await _add_test_config_entry(hass)
assert result.get("type") == FlowResultType.CREATE_ENTRY
assert result.get("title") == "1.1.1.1"
assert result.get("data") == TEST_USER_INPUT
config_entry = result.get("result")
assert config_entry is not None
assert config_entry.unique_id == TEST_SERIAL_NUMBER
assert len(mock_setup_entry.mock_calls) == 1
async def test_form_cannot_connect(
hass: HomeAssistant,
validate_input_all_ok: dict[str, AsyncMock | MagicMock],
) -> None:
"""Test cannot connect error."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
with patch(BASE_CONFIG_FLOW + "WebioAPI.check_connection", return_value=False):
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"], TEST_USER_INPUT
)
assert result2.get("type") == FlowResultType.FORM
assert result2.get("errors") == {"base": "cannot_connect"}
async def test_form_invalid_auth(
hass: HomeAssistant,
validate_input_all_ok: dict[str, AsyncMock | MagicMock],
) -> None:
"""Test invalid auth."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
with patch(
BASE_CONFIG_FLOW + "WebioAPI.refresh_device_info",
side_effect=AuthError,
):
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"], TEST_USER_INPUT
)
assert result2.get("type") == FlowResultType.FORM
assert result2.get("errors") == {"base": "invalid_auth"}
async def test_form_missing_internal_url(
hass: HomeAssistant,
validate_input_all_ok: dict[str, AsyncMock | MagicMock],
) -> None:
"""Test missing internal url."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
with patch(
BASE_NASWEB_DATA + "NASwebData.get_webhook_url", side_effect=NoURLAvailableError
):
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"], TEST_USER_INPUT
)
assert result2.get("type") == FlowResultType.FORM
assert result2.get("errors") == {"base": "missing_internal_url"}
async def test_form_missing_nasweb_data(
hass: HomeAssistant,
validate_input_all_ok: dict[str, AsyncMock | MagicMock],
) -> None:
"""Test invalid auth."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
with patch(
BASE_CONFIG_FLOW + "WebioAPI.get_serial_number",
return_value=None,
):
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"], TEST_USER_INPUT
)
assert result2.get("type") == FlowResultType.FORM
assert result2.get("errors") == {"base": "missing_nasweb_data"}
with patch(BASE_CONFIG_FLOW + "WebioAPI.status_subscription", return_value=False):
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"], TEST_USER_INPUT
)
assert result2.get("type") == FlowResultType.FORM
assert result2.get("errors") == {"base": "missing_nasweb_data"}
async def test_missing_status(
hass: HomeAssistant,
validate_input_all_ok: dict[str, AsyncMock | MagicMock],
) -> None:
"""Test missing status update."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
with patch(
BASE_COORDINATOR + "NotificationCoordinator.check_connection",
return_value=False,
):
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"], TEST_USER_INPUT
)
assert result2.get("type") == FlowResultType.FORM
assert result2.get("errors") == {"base": "missing_status"}
async def test_form_exception(
hass: HomeAssistant,
validate_input_all_ok: dict[str, AsyncMock | MagicMock],
) -> None:
"""Test other exceptions."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
with patch(
"homeassistant.components.nasweb.config_flow.validate_input",
side_effect=Exception,
):
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"], TEST_USER_INPUT
)
assert result2.get("type") == FlowResultType.FORM
assert result2.get("errors") == {"base": "unknown"}
async def test_form_already_configured(
hass: HomeAssistant,
validate_input_all_ok: dict[str, AsyncMock | MagicMock],
) -> None:
"""Test already configured device."""
result = await _add_test_config_entry(hass)
config_entry = result.get("result")
assert config_entry is not None
assert config_entry.unique_id == TEST_SERIAL_NUMBER
result2_1 = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
result2_2 = await hass.config_entries.flow.async_configure(
result2_1["flow_id"], TEST_USER_INPUT
)
await hass.async_block_till_done()
assert result2_2.get("type") == FlowResultType.ABORT
assert result2_2.get("reason") == "already_configured"