core/tests/components/peblar/test_config_flow.py

503 lines
15 KiB
Python

"""Configuration flow tests for the Peblar integration."""
from ipaddress import ip_address
from unittest.mock import MagicMock
from peblar import PeblarAuthenticationError, PeblarConnectionError
import pytest
from homeassistant.components.peblar.const import DOMAIN
from homeassistant.config_entries import SOURCE_USER, SOURCE_ZEROCONF
from homeassistant.const import CONF_HOST, CONF_PASSWORD
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType
from homeassistant.helpers.service_info.zeroconf import ZeroconfServiceInfo
from tests.common import MockConfigEntry
pytestmark = pytest.mark.usefixtures("mock_setup_entry")
@pytest.mark.usefixtures("mock_peblar")
async def test_user_flow(hass: HomeAssistant) -> None:
"""Test the full happy path user flow from start to finish."""
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={
CONF_HOST: "127.0.0.1",
CONF_PASSWORD: "OMGPUPPIES",
},
)
assert result["type"] is FlowResultType.CREATE_ENTRY
config_entry = result["result"]
assert config_entry.unique_id == "23-45-A4O-MOF"
assert config_entry.data == {
CONF_HOST: "127.0.0.1",
CONF_PASSWORD: "OMGPUPPIES",
}
assert not config_entry.options
@pytest.mark.parametrize(
("side_effect", "expected_error"),
[
(PeblarConnectionError, {CONF_HOST: "cannot_connect"}),
(PeblarAuthenticationError, {CONF_PASSWORD: "invalid_auth"}),
(Exception, {"base": "unknown"}),
],
)
async def test_user_flow_errors(
hass: HomeAssistant,
mock_peblar: MagicMock,
side_effect: Exception,
expected_error: dict[str, str],
) -> None:
"""Test we show user form on a connection error."""
mock_peblar.login.side_effect = side_effect
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_USER},
data={
CONF_HOST: "127.0.0.1",
CONF_PASSWORD: "OMGCATS!",
},
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "user"
assert result["errors"] == expected_error
mock_peblar.login.side_effect = None
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input={
CONF_HOST: "127.0.0.2",
CONF_PASSWORD: "OMGPUPPIES!",
},
)
assert result["type"] is FlowResultType.CREATE_ENTRY
config_entry = result["result"]
assert config_entry.unique_id == "23-45-A4O-MOF"
assert config_entry.data == {
CONF_HOST: "127.0.0.2",
CONF_PASSWORD: "OMGPUPPIES!",
}
assert not config_entry.options
@pytest.mark.usefixtures("mock_peblar")
async def test_user_flow_already_configured(
hass: HomeAssistant, mock_config_entry: MockConfigEntry
) -> None:
"""Test configuration flow aborts when the device is already configured."""
mock_config_entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_USER},
data={
CONF_HOST: "127.0.0.1",
CONF_PASSWORD: "OMGSPIDERS",
},
)
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "already_configured"
@pytest.mark.usefixtures("mock_peblar")
async def test_reconfigure_flow(
hass: HomeAssistant, mock_config_entry: MockConfigEntry
) -> None:
"""Test the full happy path reconfigure flow from start to finish."""
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"
assert mock_config_entry.data == {
CONF_HOST: "127.0.0.127",
CONF_PASSWORD: "OMGSPIDERS",
}
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input={
CONF_HOST: "127.0.0.1",
CONF_PASSWORD: "OMGPUPPIES",
},
)
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "reconfigure_successful"
assert mock_config_entry.data == {
CONF_HOST: "127.0.0.1",
CONF_PASSWORD: "OMGPUPPIES",
}
@pytest.mark.usefixtures("mock_peblar")
async def test_reconfigure_to_different_device(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test reconfiguring to a different device doesn't work."""
mock_config_entry.add_to_hass(hass)
# Change the unique ID of the entry, so we have a mismatch
hass.config_entries.async_update_entry(mock_config_entry, unique_id="mismatch")
result = await mock_config_entry.start_reconfigure_flow(hass)
assert result["type"] is FlowResultType.FORM
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input={
CONF_HOST: "127.0.0.1",
CONF_PASSWORD: "OMGPUPPIES",
},
)
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "different_device"
@pytest.mark.parametrize(
("side_effect", "expected_error"),
[
(PeblarConnectionError, {CONF_HOST: "cannot_connect"}),
(PeblarAuthenticationError, {CONF_PASSWORD: "invalid_auth"}),
(Exception, {"base": "unknown"}),
],
)
async def test_reconfigure_flow_errors(
hass: HomeAssistant,
mock_peblar: MagicMock,
mock_config_entry: MockConfigEntry,
side_effect: Exception,
expected_error: dict[str, str],
) -> None:
"""Test we show user form on a connection error."""
mock_config_entry.add_to_hass(hass)
mock_peblar.login.side_effect = side_effect
result = await mock_config_entry.start_reconfigure_flow(hass)
assert result["type"] is FlowResultType.FORM
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input={
CONF_HOST: "127.0.0.1",
CONF_PASSWORD: "OMGPUPPIES",
},
)
assert result["type"] is FlowResultType.FORM
assert result["errors"] == expected_error
mock_peblar.login.side_effect = None
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input={
CONF_HOST: "127.0.0.2",
CONF_PASSWORD: "OMGPUPPIES",
},
)
assert result["type"] is FlowResultType.ABORT
assert mock_config_entry.data == {
CONF_HOST: "127.0.0.2",
CONF_PASSWORD: "OMGPUPPIES",
}
@pytest.mark.usefixtures("mock_peblar")
async def test_zeroconf_flow(hass: HomeAssistant) -> None:
"""Test the zeroconf happy flow from start to finish."""
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_ZEROCONF},
data=ZeroconfServiceInfo(
ip_address=ip_address("127.0.0.1"),
ip_addresses=[ip_address("127.0.0.1")],
port=80,
hostname="pblr-0000645.local.",
name="mock_name",
properties={
"sn": "23-45-A4O-MOF",
"version": "1.6.1+1+WL-1",
},
type="mock_type",
),
)
assert result["step_id"] == "zeroconf_confirm"
assert result["type"] is FlowResultType.FORM
progress = hass.config_entries.flow.async_progress()
assert len(progress) == 1
assert progress[0].get("flow_id") == result["flow_id"]
result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input={CONF_PASSWORD: "OMGPINEAPPLES"}
)
assert result["type"] is FlowResultType.CREATE_ENTRY
config_entry = result["result"]
assert config_entry.unique_id == "23-45-A4O-MOF"
assert config_entry.data == {
CONF_HOST: "127.0.0.1",
CONF_PASSWORD: "OMGPINEAPPLES",
}
assert not config_entry.options
async def test_zeroconf_flow_abort_no_serial(hass: HomeAssistant) -> None:
"""Test the zeroconf aborts when it advertises incompatible data."""
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_ZEROCONF},
data=ZeroconfServiceInfo(
ip_address=ip_address("127.0.0.1"),
ip_addresses=[ip_address("127.0.0.1")],
port=80,
hostname="pblr-0000645.local.",
name="mock_name",
properties={},
type="mock_type",
),
)
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "no_serial_number"
@pytest.mark.parametrize(
("side_effect", "expected_error"),
[
(PeblarConnectionError, {"base": "unknown"}),
(PeblarAuthenticationError, {CONF_PASSWORD: "invalid_auth"}),
(Exception, {"base": "unknown"}),
],
)
async def test_zeroconf_flow_errors(
hass: HomeAssistant,
mock_peblar: MagicMock,
side_effect: Exception,
expected_error: dict[str, str],
) -> None:
"""Test we show form on a error."""
mock_peblar.login.side_effect = side_effect
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_ZEROCONF},
data=ZeroconfServiceInfo(
ip_address=ip_address("127.0.0.1"),
ip_addresses=[ip_address("127.0.0.1")],
port=80,
hostname="pblr-0000645.local.",
name="mock_name",
properties={
"sn": "23-45-A4O-MOF",
"version": "1.6.1+1+WL-1",
},
type="mock_type",
),
)
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input={
CONF_PASSWORD: "OMGPUPPIES",
},
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "zeroconf_confirm"
assert result["errors"] == expected_error
mock_peblar.login.side_effect = None
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input={
CONF_PASSWORD: "OMGPUPPIES",
},
)
assert result["type"] is FlowResultType.CREATE_ENTRY
config_entry = result["result"]
assert config_entry.unique_id == "23-45-A4O-MOF"
assert config_entry.data == {
CONF_HOST: "127.0.0.1",
CONF_PASSWORD: "OMGPUPPIES",
}
assert not config_entry.options
@pytest.mark.usefixtures("mock_peblar")
async def test_zeroconf_flow_not_discovered_again(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test the zeroconf doesn't re-discover an existing device."""
mock_config_entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_ZEROCONF},
data=ZeroconfServiceInfo(
ip_address=ip_address("127.0.0.1"),
ip_addresses=[ip_address("127.0.0.1")],
port=80,
hostname="pblr-0000645.local.",
name="mock_name",
properties={
"sn": "23-45-A4O-MOF",
"version": "1.6.1+1+WL-1",
},
type="mock_type",
),
)
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "already_configured"
@pytest.mark.usefixtures("mock_peblar")
async def test_user_flow_with_zeroconf_in_progress(hass: HomeAssistant) -> None:
"""Test the full happy path user flow from start to finish.
While zeroconf discovery is already in progress.
"""
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_ZEROCONF},
data=ZeroconfServiceInfo(
ip_address=ip_address("127.0.0.1"),
ip_addresses=[ip_address("127.0.0.1")],
port=80,
hostname="pblr-0000645.local.",
name="mock_name",
properties={
"sn": "23-45-A4O-MOF",
"version": "1.6.1+1+WL-1",
},
type="mock_type",
),
)
progress = hass.config_entries.flow.async_progress()
assert len(progress) == 1
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_USER},
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "user"
progress = hass.config_entries.flow.async_progress()
assert len(progress) == 2
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input={
CONF_HOST: "127.0.0.1",
CONF_PASSWORD: "OMGPUPPIES",
},
)
assert result["type"] is FlowResultType.CREATE_ENTRY
assert not hass.config_entries.flow.async_progress()
@pytest.mark.usefixtures("mock_peblar")
async def test_reauth_flow(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test the reauthentication configuration flow."""
mock_config_entry.add_to_hass(hass)
assert mock_config_entry.data[CONF_PASSWORD] == "OMGSPIDERS"
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"],
{CONF_PASSWORD: "OMGPUPPIES"},
)
await hass.async_block_till_done()
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "reauth_successful"
assert mock_config_entry.data == {
CONF_HOST: "127.0.0.127",
CONF_PASSWORD: "OMGPUPPIES",
}
@pytest.mark.parametrize(
("side_effect", "expected_error"),
[
(PeblarConnectionError, {"base": "cannot_connect"}),
(PeblarAuthenticationError, {CONF_PASSWORD: "invalid_auth"}),
(Exception, {"base": "unknown"}),
],
)
async def test_reauth_flow_errors(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_peblar: MagicMock,
side_effect: Exception,
expected_error: dict[str, str],
) -> None:
"""Test we show form on a error."""
mock_config_entry.add_to_hass(hass)
mock_peblar.login.side_effect = side_effect
result = await mock_config_entry.start_reauth_flow(hass)
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input={
CONF_PASSWORD: "OMGPUPPIES",
},
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "reauth_confirm"
assert result["errors"] == expected_error
mock_peblar.login.side_effect = None
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input={
CONF_PASSWORD: "OMGPUPPIES",
},
)
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "reauth_successful"