mirror of
https://github.com/home-assistant/core.git
synced 2026-09-08 14:41:38 +01:00
795 lines
26 KiB
Python
795 lines
26 KiB
Python
"""Tests for the OpenEVSE sensor platform."""
|
|
|
|
from ipaddress import ip_address
|
|
from unittest.mock import MagicMock
|
|
|
|
from openevsehttp.exceptions import AuthenticationError, MissingSerial
|
|
import pytest
|
|
|
|
from homeassistant.components.openevse.const import DOMAIN
|
|
from homeassistant.config_entries import (
|
|
SOURCE_IMPORT,
|
|
SOURCE_RECONFIGURE,
|
|
SOURCE_USER,
|
|
SOURCE_ZEROCONF,
|
|
)
|
|
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.service_info.zeroconf import ZeroconfServiceInfo
|
|
|
|
from tests.common import MockConfigEntry
|
|
|
|
|
|
@pytest.mark.usefixtures("mock_setup_entry")
|
|
async def test_user_flow(hass: HomeAssistant, mock_charger: MagicMock) -> None:
|
|
"""Test user flow create entry with bad charger."""
|
|
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"], {CONF_HOST: "10.0.0.131"}
|
|
)
|
|
assert result["type"] is FlowResultType.CREATE_ENTRY
|
|
assert result["title"] == "OpenEVSE 10.0.0.131"
|
|
assert result["data"] == {CONF_HOST: "10.0.0.131"}
|
|
assert result["result"].unique_id == "deadbeeffeed"
|
|
|
|
|
|
@pytest.mark.usefixtures("mock_setup_entry")
|
|
async def test_user_flow_flaky(hass: HomeAssistant, mock_charger: MagicMock) -> None:
|
|
"""Test user flow create entry with flaky charger."""
|
|
result = await hass.config_entries.flow.async_init(
|
|
DOMAIN, context={"source": SOURCE_USER}
|
|
)
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "user"
|
|
mock_charger.test_and_get.side_effect = TimeoutError
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"], {CONF_HOST: "10.0.0.131"}
|
|
)
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "user"
|
|
assert result["errors"] == {"base": "cannot_connect"}
|
|
|
|
mock_charger.test_and_get.side_effect = None
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"], {CONF_HOST: "10.0.0.131"}
|
|
)
|
|
assert result["type"] is FlowResultType.CREATE_ENTRY
|
|
assert result["title"] == "OpenEVSE 10.0.0.131"
|
|
assert result["data"] == {CONF_HOST: "10.0.0.131"}
|
|
assert result["result"].unique_id == "deadbeeffeed"
|
|
|
|
|
|
@pytest.mark.usefixtures("mock_setup_entry")
|
|
async def test_user_flow_duplicate(
|
|
hass: HomeAssistant, mock_config_entry: MagicMock, mock_charger: MagicMock
|
|
) -> None:
|
|
"""Test user flow aborts when config entry already exists."""
|
|
mock_config_entry.add_to_hass(hass)
|
|
|
|
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"], {CONF_HOST: "192.168.1.100"}
|
|
)
|
|
assert result["type"] is FlowResultType.ABORT
|
|
assert result["reason"] == "already_configured"
|
|
|
|
|
|
@pytest.mark.usefixtures("mock_setup_entry")
|
|
async def test_user_flow_no_serial(
|
|
hass: HomeAssistant, mock_charger: MagicMock
|
|
) -> None:
|
|
"""Test user flow handles missing serial gracefully."""
|
|
mock_charger.test_and_get.side_effect = [{}, MissingSerial]
|
|
|
|
result = await hass.config_entries.flow.async_init(
|
|
DOMAIN, context={"source": SOURCE_USER}
|
|
)
|
|
assert result["type"] is FlowResultType.FORM
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"], {CONF_HOST: "10.0.0.131"}
|
|
)
|
|
assert result["type"] is FlowResultType.CREATE_ENTRY
|
|
assert result["title"] == "OpenEVSE 10.0.0.131"
|
|
assert result["result"].unique_id is None
|
|
|
|
|
|
@pytest.mark.usefixtures("mock_setup_entry")
|
|
async def test_import_flow_no_serial(
|
|
hass: HomeAssistant, mock_charger: MagicMock
|
|
) -> None:
|
|
"""Test import flow handles missing serial gracefully."""
|
|
mock_charger.test_and_get.side_effect = [{}, MissingSerial]
|
|
|
|
result = await hass.config_entries.flow.async_init(
|
|
DOMAIN, context={"source": SOURCE_IMPORT}, data={CONF_HOST: "10.0.0.131"}
|
|
)
|
|
|
|
# Assert the flow continued to create the entry
|
|
assert result["type"] is FlowResultType.CREATE_ENTRY
|
|
assert result["title"] == "OpenEVSE 10.0.0.131"
|
|
assert result["result"].unique_id is None
|
|
|
|
|
|
@pytest.mark.usefixtures("mock_setup_entry")
|
|
async def test_user_flow_with_auth(
|
|
hass: HomeAssistant, mock_charger: MagicMock
|
|
) -> None:
|
|
"""Test user flow create entry with authentication."""
|
|
mock_charger.test_and_get.side_effect = [
|
|
AuthenticationError,
|
|
{"serial": "deadbeeffeed"},
|
|
]
|
|
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"], {CONF_HOST: "10.0.0.131"}
|
|
)
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "auth"
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
{CONF_USERNAME: "fakeuser", CONF_PASSWORD: "muchpassword"},
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.CREATE_ENTRY
|
|
assert result["title"] == "OpenEVSE 10.0.0.131"
|
|
assert result["data"] == {
|
|
CONF_HOST: "10.0.0.131",
|
|
CONF_USERNAME: "fakeuser",
|
|
CONF_PASSWORD: "muchpassword",
|
|
}
|
|
assert result["result"].unique_id == "deadbeeffeed"
|
|
|
|
|
|
async def test_user_flow_with_auth_error(
|
|
hass: HomeAssistant, mock_charger: MagicMock
|
|
) -> None:
|
|
"""Test user flow create entry with authentication error."""
|
|
mock_charger.test_and_get.side_effect = [
|
|
AuthenticationError,
|
|
AuthenticationError,
|
|
{},
|
|
]
|
|
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"],
|
|
{CONF_HOST: "10.0.0.131"},
|
|
)
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "auth"
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
{CONF_USERNAME: "fakeuser", CONF_PASSWORD: "muchpassword"},
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["errors"]["base"] == "invalid_auth"
|
|
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
{CONF_USERNAME: "fakeuser", CONF_PASSWORD: "muchpassword"},
|
|
)
|
|
assert result["type"] is FlowResultType.CREATE_ENTRY
|
|
|
|
|
|
async def test_user_flow_with_missing_serial(
|
|
hass: HomeAssistant, mock_charger: MagicMock
|
|
) -> None:
|
|
"""Test user flow create entry with authentication error."""
|
|
mock_charger.test_and_get.side_effect = [AuthenticationError, MissingSerial]
|
|
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"], {CONF_HOST: "10.0.0.131"}
|
|
)
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "auth"
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
{CONF_USERNAME: "fakeuser", CONF_PASSWORD: "muchpassword"},
|
|
)
|
|
assert result["type"] is FlowResultType.CREATE_ENTRY
|
|
assert result["title"] == "OpenEVSE 10.0.0.131"
|
|
assert result["data"] == {
|
|
CONF_HOST: "10.0.0.131",
|
|
CONF_USERNAME: "fakeuser",
|
|
CONF_PASSWORD: "muchpassword",
|
|
}
|
|
assert result["result"].unique_id is None
|
|
|
|
|
|
@pytest.mark.usefixtures("mock_setup_entry")
|
|
async def test_import_flow(hass: HomeAssistant, mock_charger: MagicMock) -> None:
|
|
"""Test import flow."""
|
|
result = await hass.config_entries.flow.async_init(
|
|
DOMAIN, context={"source": SOURCE_IMPORT}, data={CONF_HOST: "10.0.0.131"}
|
|
)
|
|
assert result["type"] is FlowResultType.CREATE_ENTRY
|
|
assert result["title"] == "OpenEVSE 10.0.0.131"
|
|
assert result["data"] == {CONF_HOST: "10.0.0.131"}
|
|
assert result["result"].unique_id == "deadbeeffeed"
|
|
|
|
|
|
@pytest.mark.usefixtures("mock_setup_entry")
|
|
async def test_import_flow_bad(hass: HomeAssistant, mock_charger: MagicMock) -> None:
|
|
"""Test import flow with bad charger."""
|
|
mock_charger.test_and_get.side_effect = TimeoutError
|
|
|
|
result = await hass.config_entries.flow.async_init(
|
|
DOMAIN, context={"source": SOURCE_IMPORT}, data={CONF_HOST: "10.0.0.131"}
|
|
)
|
|
assert result["type"] is FlowResultType.ABORT
|
|
assert result["reason"] == "unavailable_host"
|
|
|
|
|
|
@pytest.mark.usefixtures("mock_setup_entry")
|
|
async def test_import_flow_duplicate(
|
|
hass: HomeAssistant, mock_config_entry: MagicMock, mock_charger: MagicMock
|
|
) -> None:
|
|
"""Test import flow aborts when config entry already exists."""
|
|
mock_config_entry.add_to_hass(hass)
|
|
|
|
result = await hass.config_entries.flow.async_init(
|
|
DOMAIN,
|
|
context={"source": SOURCE_IMPORT},
|
|
data={CONF_HOST: "192.168.1.100"},
|
|
)
|
|
assert result["type"] is FlowResultType.ABORT
|
|
assert result["reason"] == "already_configured"
|
|
|
|
|
|
@pytest.mark.usefixtures("mock_setup_entry")
|
|
async def test_zeroconf_discovery(hass: HomeAssistant, mock_charger: MagicMock) -> None:
|
|
"""Test zeroconf discovery."""
|
|
discovery_info = ZeroconfServiceInfo(
|
|
ip_address=ip_address("192.168.1.123"),
|
|
ip_addresses=[ip_address("192.168.1.123")],
|
|
hostname="openevse-deadbeeffeed.local.",
|
|
name="openevse-deadbeeffeed._openevse._tcp.local.",
|
|
port=80,
|
|
properties={"id": "deadbeeffeed", "type": "openevse"},
|
|
type="_openevse._tcp.local.",
|
|
)
|
|
|
|
# Trigger the zeroconf step
|
|
result = await hass.config_entries.flow.async_init(
|
|
DOMAIN,
|
|
context={"source": SOURCE_ZEROCONF},
|
|
data=discovery_info,
|
|
)
|
|
|
|
# Should present a confirmation form
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "discovery_confirm"
|
|
assert result["description_placeholders"] == {
|
|
"name": "OpenEVSE openevse-deadbeeffeed"
|
|
}
|
|
|
|
# Confirm the discovery
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"], user_input={}
|
|
)
|
|
|
|
# Should create the entry
|
|
assert result["type"] is FlowResultType.CREATE_ENTRY
|
|
assert result["title"] == "OpenEVSE openevse-deadbeeffeed"
|
|
assert result["data"] == {CONF_HOST: "192.168.1.123"}
|
|
assert result["result"].unique_id == "deadbeeffeed"
|
|
|
|
|
|
@pytest.mark.usefixtures("mock_setup_entry")
|
|
async def test_zeroconf_already_configured_unique_id(
|
|
hass: HomeAssistant, mock_charger: MagicMock, mock_config_entry: MockConfigEntry
|
|
) -> None:
|
|
"""Test zeroconf discovery updates info if unique_id is already configured."""
|
|
mock_config_entry.add_to_hass(hass)
|
|
|
|
discovery_info = ZeroconfServiceInfo(
|
|
ip_address=ip_address("192.168.1.124"),
|
|
ip_addresses=[ip_address("192.168.1.124"), ip_address("2001:db8::1")],
|
|
hostname="openevse-deadbeeffeed.local.",
|
|
name="openevse-deadbeeffeed._openevse._tcp.local.",
|
|
port=80,
|
|
properties={"id": "deadbeeffeed", "type": "openevse"},
|
|
type="_openevse._tcp.local.",
|
|
)
|
|
|
|
result = await hass.config_entries.flow.async_init(
|
|
DOMAIN,
|
|
context={"source": SOURCE_ZEROCONF},
|
|
data=discovery_info,
|
|
)
|
|
|
|
# Should abort because unique_id matches, but it updates the config entry
|
|
assert result["type"] is FlowResultType.ABORT
|
|
assert result["reason"] == "already_configured"
|
|
|
|
# Verify the entry IP was updated to the new discovery IP
|
|
assert mock_config_entry.data["host"] == "192.168.1.124"
|
|
|
|
|
|
async def test_zeroconf_connection_error(
|
|
hass: HomeAssistant, mock_charger: MagicMock
|
|
) -> None:
|
|
"""Test zeroconf discovery with connection failure."""
|
|
mock_charger.test_and_get.side_effect = TimeoutError
|
|
discovery_info = ZeroconfServiceInfo(
|
|
ip_address=ip_address("192.168.1.123"),
|
|
ip_addresses=[ip_address("192.168.1.123"), ip_address("2001:db8::1")],
|
|
hostname="openevse-deadbeeffeed.local.",
|
|
name="openevse-deadbeeffeed._openevse._tcp.local.",
|
|
port=80,
|
|
properties={"id": "deadbeeffeed", "type": "openevse"},
|
|
type="_openevse._tcp.local.",
|
|
)
|
|
|
|
result = await hass.config_entries.flow.async_init(
|
|
DOMAIN,
|
|
context={"source": SOURCE_ZEROCONF},
|
|
data=discovery_info,
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.ABORT
|
|
assert result["reason"] == "unavailable_host"
|
|
|
|
|
|
async def test_zeroconf_auth(hass: HomeAssistant, mock_charger: MagicMock) -> None:
|
|
"""Test zeroconf discovery with connection failure."""
|
|
mock_charger.test_and_get.side_effect = [AuthenticationError, {}]
|
|
discovery_info = ZeroconfServiceInfo(
|
|
ip_address=ip_address("192.168.1.123"),
|
|
ip_addresses=[ip_address("192.168.1.123"), ip_address("2001:db8::1")],
|
|
hostname="openevse-deadbeeffeed.local.",
|
|
name="openevse-deadbeeffeed._openevse._tcp.local.",
|
|
port=80,
|
|
properties={"id": "deadbeeffeed", "type": "openevse"},
|
|
type="_openevse._tcp.local.",
|
|
)
|
|
|
|
result = await hass.config_entries.flow.async_init(
|
|
DOMAIN,
|
|
context={"source": SOURCE_ZEROCONF},
|
|
data=discovery_info,
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "auth"
|
|
assert not result["errors"]
|
|
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
{CONF_USERNAME: "fakeuser", CONF_PASSWORD: "muchpassword"},
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.CREATE_ENTRY
|
|
assert result["data"] == {
|
|
CONF_HOST: "192.168.1.123",
|
|
CONF_USERNAME: "fakeuser",
|
|
CONF_PASSWORD: "muchpassword",
|
|
}
|
|
|
|
|
|
async def test_zeroconf_auth_failure(
|
|
hass: HomeAssistant, mock_charger: MagicMock
|
|
) -> None:
|
|
"""Test zeroconf discovery with connection failure."""
|
|
mock_charger.test_and_get.side_effect = [
|
|
AuthenticationError,
|
|
AuthenticationError,
|
|
{},
|
|
]
|
|
discovery_info = ZeroconfServiceInfo(
|
|
ip_address=ip_address("192.168.1.123"),
|
|
ip_addresses=[ip_address("192.168.1.123"), ip_address("2001:db8::1")],
|
|
hostname="openevse-deadbeeffeed.local.",
|
|
name="openevse-deadbeeffeed._openevse._tcp.local.",
|
|
port=80,
|
|
properties={"id": "deadbeeffeed", "type": "openevse"},
|
|
type="_openevse._tcp.local.",
|
|
)
|
|
|
|
result = await hass.config_entries.flow.async_init(
|
|
DOMAIN,
|
|
context={"source": SOURCE_ZEROCONF},
|
|
data=discovery_info,
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "auth"
|
|
assert not result["errors"]
|
|
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
{CONF_USERNAME: "fakeuser", CONF_PASSWORD: "muchpassword"},
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "auth"
|
|
assert result["errors"] == {"base": "invalid_auth"}
|
|
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
{CONF_USERNAME: "fakeuser", CONF_PASSWORD: "muchpassword"},
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.CREATE_ENTRY
|
|
assert result["data"] == {
|
|
CONF_HOST: "192.168.1.123",
|
|
CONF_USERNAME: "fakeuser",
|
|
CONF_PASSWORD: "muchpassword",
|
|
}
|
|
|
|
|
|
@pytest.mark.usefixtures("mock_setup_entry")
|
|
async def test_zeroconf_already_configured_host(
|
|
hass: HomeAssistant, mock_config_entry: MockConfigEntry
|
|
) -> None:
|
|
"""Test zeroconf discovery aborts if host is already configured."""
|
|
mock_config_entry.add_to_hass(hass)
|
|
|
|
discovery_info = ZeroconfServiceInfo(
|
|
ip_address=ip_address("192.168.1.100"),
|
|
ip_addresses=[ip_address("192.168.1.100"), ip_address("2001:db8::1")],
|
|
hostname="openevse-deadbeeffeed.local.",
|
|
name="openevse-deadbeeffeed._openevse._tcp.local.",
|
|
port=80,
|
|
properties={"id": "deadbeeffeed", "type": "openevse"},
|
|
type="_openevse._tcp.local.",
|
|
)
|
|
|
|
result = await hass.config_entries.flow.async_init(
|
|
DOMAIN,
|
|
context={"source": SOURCE_ZEROCONF},
|
|
data=discovery_info,
|
|
)
|
|
|
|
# Should abort because the host matches an existing entry
|
|
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_charger: MagicMock, 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"
|
|
assert result["description_placeholders"][CONF_HOST] == "192.168.1.100"
|
|
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
{CONF_USERNAME: "newuser", CONF_PASSWORD: "newpassword"},
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.ABORT
|
|
assert result["reason"] == "reauth_successful"
|
|
assert mock_config_entry.data == {
|
|
CONF_HOST: "192.168.1.100",
|
|
CONF_USERNAME: "newuser",
|
|
CONF_PASSWORD: "newpassword",
|
|
}
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("exception", "error_base"),
|
|
[
|
|
(AuthenticationError, "invalid_auth"),
|
|
(TimeoutError, "cannot_connect"),
|
|
],
|
|
)
|
|
@pytest.mark.usefixtures("mock_setup_entry")
|
|
async def test_reauth_flow_errors(
|
|
hass: HomeAssistant,
|
|
mock_charger: MagicMock,
|
|
mock_config_entry: MockConfigEntry,
|
|
exception: Exception,
|
|
error_base: str,
|
|
) -> None:
|
|
"""Test reauthentication flow recovers from 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_charger.test_and_get.side_effect = exception
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
{CONF_USERNAME: "newuser", CONF_PASSWORD: "wrongpassword"},
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "reauth_confirm"
|
|
assert result["errors"] == {"base": error_base}
|
|
|
|
mock_charger.test_and_get.side_effect = None
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
{CONF_USERNAME: "newuser", CONF_PASSWORD: "newpassword"},
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.ABORT
|
|
assert result["reason"] == "reauth_successful"
|
|
assert mock_config_entry.data == {
|
|
CONF_HOST: "192.168.1.100",
|
|
CONF_USERNAME: "newuser",
|
|
CONF_PASSWORD: "newpassword",
|
|
}
|
|
|
|
|
|
@pytest.mark.usefixtures("mock_setup_entry")
|
|
async def test_reconfigure_flow_host_only(
|
|
hass: HomeAssistant, mock_charger: MagicMock
|
|
) -> None:
|
|
"""Test reconfiguration flow for entry with host only."""
|
|
config_entry = MockConfigEntry(
|
|
title="openevse_mock_config",
|
|
domain=DOMAIN,
|
|
data={CONF_HOST: "192.168.1.100"},
|
|
entry_id="FAKE",
|
|
unique_id="deadbeeffeed",
|
|
)
|
|
config_entry.add_to_hass(hass)
|
|
|
|
result = await hass.config_entries.flow.async_init(
|
|
DOMAIN,
|
|
context={"source": SOURCE_RECONFIGURE, "entry_id": config_entry.entry_id},
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "reconfigure"
|
|
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"], {CONF_HOST: "192.168.1.101"}
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.ABORT
|
|
assert result["reason"] == "reconfigure_successful"
|
|
assert config_entry.data == {
|
|
CONF_HOST: "192.168.1.101",
|
|
CONF_USERNAME: None,
|
|
CONF_PASSWORD: None,
|
|
}
|
|
|
|
|
|
@pytest.mark.usefixtures("mock_setup_entry")
|
|
async def test_reconfigure_flow_with_auth(
|
|
hass: HomeAssistant, mock_charger: MagicMock
|
|
) -> None:
|
|
"""Test reconfiguration flow for entry with credentials."""
|
|
config_entry = MockConfigEntry(
|
|
title="openevse_mock_config",
|
|
domain=DOMAIN,
|
|
data={
|
|
CONF_HOST: "192.168.1.100",
|
|
CONF_USERNAME: "olduser",
|
|
CONF_PASSWORD: "oldpassword",
|
|
},
|
|
entry_id="FAKE",
|
|
unique_id="deadbeeffeed",
|
|
)
|
|
config_entry.add_to_hass(hass)
|
|
|
|
result = await hass.config_entries.flow.async_init(
|
|
DOMAIN,
|
|
context={"source": SOURCE_RECONFIGURE, "entry_id": config_entry.entry_id},
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "reconfigure"
|
|
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
{
|
|
CONF_HOST: "192.168.1.101",
|
|
CONF_USERNAME: "newuser",
|
|
CONF_PASSWORD: "newpassword",
|
|
},
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.ABORT
|
|
assert result["reason"] == "reconfigure_successful"
|
|
assert config_entry.data == {
|
|
CONF_HOST: "192.168.1.101",
|
|
CONF_USERNAME: "newuser",
|
|
CONF_PASSWORD: "newpassword",
|
|
}
|
|
|
|
|
|
@pytest.mark.usefixtures("mock_setup_entry")
|
|
async def test_reconfigure_flow_duplicate_host(
|
|
hass: HomeAssistant, mock_charger: MagicMock
|
|
) -> None:
|
|
"""Test reconfiguration flow aborts when host matches another entry."""
|
|
other_entry = MockConfigEntry(
|
|
title="openevse_other_config",
|
|
domain=DOMAIN,
|
|
data={CONF_HOST: "192.168.1.101"},
|
|
entry_id="OTHER",
|
|
unique_id="otherfeedfeed",
|
|
)
|
|
other_entry.add_to_hass(hass)
|
|
|
|
config_entry = MockConfigEntry(
|
|
title="openevse_mock_config",
|
|
domain=DOMAIN,
|
|
data={CONF_HOST: "192.168.1.100"},
|
|
entry_id="FAKE",
|
|
unique_id="deadbeeffeed",
|
|
)
|
|
config_entry.add_to_hass(hass)
|
|
|
|
result = await hass.config_entries.flow.async_init(
|
|
DOMAIN,
|
|
context={"source": SOURCE_RECONFIGURE, "entry_id": config_entry.entry_id},
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "reconfigure"
|
|
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"], {CONF_HOST: "192.168.1.101"}
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.ABORT
|
|
assert result["reason"] == "already_configured"
|
|
assert config_entry.data == {CONF_HOST: "192.168.1.100"}
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("exception", "error_base"),
|
|
[
|
|
(AuthenticationError, "invalid_auth"),
|
|
(TimeoutError, "cannot_connect"),
|
|
],
|
|
)
|
|
@pytest.mark.usefixtures("mock_setup_entry")
|
|
async def test_reconfigure_flow_errors(
|
|
hass: HomeAssistant,
|
|
mock_charger: MagicMock,
|
|
exception: Exception,
|
|
error_base: str,
|
|
) -> None:
|
|
"""Test reconfiguration flow handles connection errors."""
|
|
config_entry = MockConfigEntry(
|
|
title="openevse_mock_config",
|
|
domain=DOMAIN,
|
|
data={CONF_HOST: "192.168.1.100"},
|
|
entry_id="FAKE",
|
|
unique_id="deadbeeffeed",
|
|
)
|
|
config_entry.add_to_hass(hass)
|
|
|
|
result = await hass.config_entries.flow.async_init(
|
|
DOMAIN,
|
|
context={"source": SOURCE_RECONFIGURE, "entry_id": config_entry.entry_id},
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "reconfigure"
|
|
|
|
mock_charger.test_and_get.side_effect = exception
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"], {CONF_HOST: "192.168.1.101"}
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "reconfigure"
|
|
assert result["errors"] == {"base": error_base}
|
|
|
|
mock_charger.test_and_get.side_effect = None
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"], {CONF_HOST: "192.168.1.102"}
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.ABORT
|
|
assert result["reason"] == "reconfigure_successful"
|
|
assert config_entry.data == {
|
|
CONF_HOST: "192.168.1.102",
|
|
CONF_USERNAME: None,
|
|
CONF_PASSWORD: None,
|
|
}
|
|
|
|
|
|
@pytest.mark.usefixtures("mock_setup_entry")
|
|
async def test_reconfigure_flow_changed_serial(
|
|
hass: HomeAssistant, mock_charger: MagicMock
|
|
) -> None:
|
|
"""Test reconfiguration flow aborts when device serial changes."""
|
|
config_entry = MockConfigEntry(
|
|
title="openevse_mock_config",
|
|
domain=DOMAIN,
|
|
data={CONF_HOST: "192.168.1.100"},
|
|
entry_id="FAKE",
|
|
unique_id="deadbeeffeed",
|
|
)
|
|
config_entry.add_to_hass(hass)
|
|
|
|
result = await hass.config_entries.flow.async_init(
|
|
DOMAIN,
|
|
context={"source": SOURCE_RECONFIGURE, "entry_id": config_entry.entry_id},
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "reconfigure"
|
|
|
|
mock_charger.test_and_get.return_value = {
|
|
"serial": "newfeedfeed",
|
|
"model": "openevse_wifi_v1",
|
|
}
|
|
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"], {CONF_HOST: "192.168.1.101"}
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.ABORT
|
|
assert result["reason"] == "unique_id_mismatch"
|
|
|
|
|
|
@pytest.mark.usefixtures("mock_setup_entry")
|
|
async def test_reconfigure_flow_no_serial(
|
|
hass: HomeAssistant, mock_charger: MagicMock
|
|
) -> None:
|
|
"""Test reconfiguration flow when device doesn't return serial."""
|
|
config_entry = MockConfigEntry(
|
|
title="openevse_mock_config",
|
|
domain=DOMAIN,
|
|
data={CONF_HOST: "192.168.1.100"},
|
|
entry_id="FAKE",
|
|
unique_id=None,
|
|
)
|
|
config_entry.add_to_hass(hass)
|
|
|
|
mock_charger.test_and_get.return_value = {"model": "openevse_wifi_v1"}
|
|
|
|
result = await hass.config_entries.flow.async_init(
|
|
DOMAIN,
|
|
context={"source": SOURCE_RECONFIGURE, "entry_id": config_entry.entry_id},
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "reconfigure"
|
|
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"], {CONF_HOST: "192.168.1.101"}
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.ABORT
|
|
assert result["reason"] == "reconfigure_successful"
|
|
assert config_entry.data == {
|
|
CONF_HOST: "192.168.1.101",
|
|
CONF_USERNAME: None,
|
|
CONF_PASSWORD: None,
|
|
}
|