mirror of
https://github.com/home-assistant/core.git
synced 2026-08-28 10:16:02 -05:00
1691 lines
53 KiB
Python
1691 lines
53 KiB
Python
"""Tests for Overkiz config flow."""
|
|
|
|
from ipaddress import ip_address
|
|
from unittest.mock import AsyncMock, Mock, patch
|
|
|
|
from aiohttp import ClientConnectorCertificateError, ClientError
|
|
from pyoverkiz.client import GatewayCandidate
|
|
from pyoverkiz.const import (
|
|
REXEL_OAUTH_AUTHORIZE_URL,
|
|
REXEL_OAUTH_POLICY,
|
|
REXEL_OAUTH_SCOPE,
|
|
REXEL_OAUTH_TOKEN_URL,
|
|
)
|
|
from pyoverkiz.exceptions import (
|
|
ApplicationNotAllowedError,
|
|
BadCredentialsError,
|
|
MaintenanceError,
|
|
NoSuchTokenError,
|
|
TooManyAttemptsBannedError,
|
|
TooManyRequestsError,
|
|
UnknownUserError,
|
|
)
|
|
import pytest
|
|
|
|
from homeassistant import config_entries
|
|
from homeassistant.components.application_credentials import (
|
|
ClientCredential,
|
|
async_import_client_credential,
|
|
)
|
|
from homeassistant.components.overkiz.const import DOMAIN
|
|
from homeassistant.core import HomeAssistant
|
|
from homeassistant.data_entry_flow import FlowResultType
|
|
from homeassistant.helpers import config_entry_oauth2_flow
|
|
from homeassistant.helpers.service_info.dhcp import DhcpServiceInfo
|
|
from homeassistant.helpers.service_info.zeroconf import ZeroconfServiceInfo
|
|
from homeassistant.setup import async_setup_component
|
|
|
|
from tests.common import MockConfigEntry
|
|
from tests.test_util.aiohttp import AiohttpClientMocker
|
|
from tests.typing import ClientSessionGenerator
|
|
|
|
pytestmark = pytest.mark.usefixtures("mock_setup_entry")
|
|
|
|
REXEL_CLIENT_ID = "1234"
|
|
REXEL_REDIRECT_URI = "https://example.com/auth/external/callback"
|
|
|
|
TEST_EMAIL = "test@testdomain.com"
|
|
TEST_EMAIL2 = "test@testdomain.nl"
|
|
TEST_PASSWORD = "test-password"
|
|
TEST_PASSWORD2 = "test-password2"
|
|
TEST_SERVER = "somfy_europe"
|
|
TEST_SERVER2 = "hi_kumo_europe"
|
|
TEST_SERVER_COZYTOUCH = "atlantic_cozytouch"
|
|
TEST_GATEWAY_ID = "1234-5678-9123"
|
|
TEST_GATEWAY_ID2 = "4321-5678-9123"
|
|
TEST_GATEWAY_ID3 = "SOMFY_PROTECT-v0NT53occUBPyuJRzx59kalW1hFfzimN"
|
|
|
|
TEST_HOST = "gateway-1234-5678-9123.local:8443"
|
|
TEST_HOST2 = "192.168.11.104:8443"
|
|
TEST_TOKEN = "1234123412341234"
|
|
|
|
MOCK_GATEWAY_RESPONSE = [Mock(id=TEST_GATEWAY_ID)]
|
|
MOCK_GATEWAY2_RESPONSE = [Mock(id=TEST_GATEWAY_ID3), Mock(id=TEST_GATEWAY_ID2)]
|
|
|
|
FAKE_ZERO_CONF_INFO = ZeroconfServiceInfo(
|
|
ip_address=ip_address("192.168.0.51"),
|
|
ip_addresses=[ip_address("192.168.0.51")],
|
|
port=443,
|
|
hostname=f"gateway-{TEST_GATEWAY_ID}.local.",
|
|
type="_kizbox._tcp.local.",
|
|
name=f"gateway-{TEST_GATEWAY_ID}._kizbox._tcp.local.",
|
|
properties={
|
|
"api_version": "1",
|
|
"gateway_pin": TEST_GATEWAY_ID,
|
|
"fw_version": "2021.5.4-29",
|
|
},
|
|
)
|
|
|
|
FAKE_ZERO_CONF_INFO_LOCAL = ZeroconfServiceInfo(
|
|
ip_address=ip_address("192.168.0.51"),
|
|
ip_addresses=[ip_address("192.168.0.51")],
|
|
port=8443,
|
|
hostname=f"gateway-{TEST_GATEWAY_ID}.local.",
|
|
type="_kizboxdev._tcp.local.",
|
|
name=f"gateway-{TEST_GATEWAY_ID}._kizboxdev._tcp.local.",
|
|
properties={
|
|
"api_version": "1",
|
|
"gateway_pin": TEST_GATEWAY_ID,
|
|
"fw_version": "2021.5.4-29",
|
|
},
|
|
)
|
|
|
|
|
|
async def test_form_cloud(hass: HomeAssistant, mock_setup_entry: AsyncMock) -> None:
|
|
"""Test we get the form."""
|
|
result = await hass.config_entries.flow.async_init(
|
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.FORM
|
|
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
{"hub": TEST_SERVER},
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "local_or_cloud"
|
|
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
{"api_type": "cloud"},
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "cloud"
|
|
|
|
with (
|
|
patch(
|
|
"homeassistant.components.overkiz.config_flow.OverkizClient.login",
|
|
return_value=True,
|
|
),
|
|
patch(
|
|
"homeassistant.components.overkiz.config_flow.OverkizClient.get_gateways",
|
|
return_value=MOCK_GATEWAY_RESPONSE,
|
|
),
|
|
):
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
{"username": TEST_EMAIL, "password": TEST_PASSWORD},
|
|
)
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
assert len(mock_setup_entry.mock_calls) == 1
|
|
|
|
|
|
async def test_form_only_cloud_supported(
|
|
hass: HomeAssistant, mock_setup_entry: AsyncMock
|
|
) -> None:
|
|
"""Test we get the form."""
|
|
result = await hass.config_entries.flow.async_init(
|
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.FORM
|
|
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
{"hub": TEST_SERVER2},
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "cloud"
|
|
|
|
with (
|
|
patch(
|
|
"homeassistant.components.overkiz.config_flow.OverkizClient.login",
|
|
return_value=True,
|
|
),
|
|
patch(
|
|
"homeassistant.components.overkiz.config_flow.OverkizClient.get_gateways",
|
|
return_value=MOCK_GATEWAY_RESPONSE,
|
|
),
|
|
):
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
{"username": TEST_EMAIL, "password": TEST_PASSWORD},
|
|
)
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
assert len(mock_setup_entry.mock_calls) == 1
|
|
|
|
|
|
async def test_form_local_happy_flow(
|
|
hass: HomeAssistant, mock_setup_entry: AsyncMock
|
|
) -> None:
|
|
"""Test local API configuration flow."""
|
|
result = await hass.config_entries.flow.async_init(
|
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.FORM
|
|
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
{"hub": TEST_SERVER},
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "local_or_cloud"
|
|
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
{"api_type": "local"},
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "local"
|
|
|
|
with patch.multiple(
|
|
"homeassistant.components.overkiz.config_flow.OverkizClient",
|
|
login=AsyncMock(return_value=True),
|
|
get_gateways=AsyncMock(return_value=MOCK_GATEWAY_RESPONSE),
|
|
):
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
{
|
|
"host": "gateway-1234-5678-1234.local:8443",
|
|
"token": TEST_TOKEN,
|
|
"verify_ssl": True,
|
|
},
|
|
)
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
assert result["type"] is FlowResultType.CREATE_ENTRY
|
|
assert result["title"] == "gateway-1234-5678-1234.local:8443"
|
|
assert result["data"] == {
|
|
"host": "gateway-1234-5678-1234.local:8443",
|
|
"token": TEST_TOKEN,
|
|
"verify_ssl": True,
|
|
"hub": TEST_SERVER,
|
|
"api_type": "local",
|
|
}
|
|
assert len(mock_setup_entry.mock_calls) == 1
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("side_effect", "error"),
|
|
[
|
|
(BadCredentialsError, "invalid_auth"),
|
|
(TooManyRequestsError, "too_many_requests"),
|
|
(TimeoutError, "cannot_connect"),
|
|
(ClientError, "cannot_connect"),
|
|
(MaintenanceError, "server_in_maintenance"),
|
|
(TooManyAttemptsBannedError, "too_many_attempts"),
|
|
(UnknownUserError, "unsupported_hardware"),
|
|
(ApplicationNotAllowedError, "application_not_allowed"),
|
|
(Exception, "unknown"),
|
|
],
|
|
)
|
|
async def test_form_invalid_auth_cloud(
|
|
hass: HomeAssistant, side_effect: Exception, error: str
|
|
) -> None:
|
|
"""Test we handle invalid auth (cloud)."""
|
|
result = await hass.config_entries.flow.async_init(
|
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.FORM
|
|
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
{"hub": TEST_SERVER},
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "local_or_cloud"
|
|
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
{"api_type": "cloud"},
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "cloud"
|
|
|
|
with patch(
|
|
"homeassistant.components.overkiz.config_flow.OverkizClient.login",
|
|
side_effect=side_effect,
|
|
):
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
{"username": TEST_EMAIL, "password": TEST_PASSWORD},
|
|
)
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["errors"] == {"base": error}
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("side_effect", "description_placeholder", "server"),
|
|
[
|
|
(UnknownUserError, "CozyTouch", TEST_SERVER_COZYTOUCH),
|
|
(UnknownUserError, "Unknown", TEST_SERVER2),
|
|
],
|
|
)
|
|
async def test_form_invalid_hardware_cloud(
|
|
hass: HomeAssistant,
|
|
side_effect: Exception,
|
|
description_placeholder: str,
|
|
server: str,
|
|
) -> None:
|
|
"""Test we handle unsupported hardware (cloud)."""
|
|
result = await hass.config_entries.flow.async_init(
|
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.FORM
|
|
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
{"hub": server},
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "cloud"
|
|
|
|
with patch(
|
|
"homeassistant.components.overkiz.config_flow.OverkizClient.login",
|
|
side_effect=side_effect,
|
|
):
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
{"username": TEST_EMAIL, "password": TEST_PASSWORD},
|
|
)
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["errors"] == {"base": "unsupported_hardware"}
|
|
assert result["description_placeholders"] == {
|
|
"unsupported_device": description_placeholder
|
|
}
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("side_effect", "description_placeholder", "server"),
|
|
[
|
|
(UnknownUserError, "Somfy Protect", TEST_SERVER),
|
|
],
|
|
)
|
|
async def test_form_invalid_hardware_cloud_local(
|
|
hass: HomeAssistant,
|
|
side_effect: Exception,
|
|
description_placeholder: str,
|
|
server: str,
|
|
) -> None:
|
|
"""Test we handle unsupported hardware (cloud and local)."""
|
|
result = await hass.config_entries.flow.async_init(
|
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.FORM
|
|
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
{"hub": server},
|
|
)
|
|
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
{"api_type": "cloud"},
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "cloud"
|
|
|
|
with patch(
|
|
"homeassistant.components.overkiz.config_flow.OverkizClient.login",
|
|
side_effect=side_effect,
|
|
):
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
{"username": TEST_EMAIL, "password": TEST_PASSWORD},
|
|
)
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["errors"] == {"base": "unsupported_hardware"}
|
|
assert result["description_placeholders"] == {
|
|
"unsupported_device": description_placeholder
|
|
}
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("side_effect", "error"),
|
|
[
|
|
(BadCredentialsError, "invalid_auth"),
|
|
(TooManyRequestsError, "too_many_requests"),
|
|
(
|
|
ClientConnectorCertificateError(Mock(host=TEST_HOST), Exception),
|
|
"certificate_verify_failed",
|
|
),
|
|
(TimeoutError, "cannot_connect"),
|
|
(ClientError, "cannot_connect"),
|
|
(MaintenanceError, "server_in_maintenance"),
|
|
(TooManyAttemptsBannedError, "too_many_attempts"),
|
|
(UnknownUserError, "unsupported_hardware"),
|
|
(NoSuchTokenError, "invalid_auth"),
|
|
(Exception, "unknown"),
|
|
],
|
|
)
|
|
async def test_form_invalid_auth_local(
|
|
hass: HomeAssistant, side_effect: Exception, error: str
|
|
) -> None:
|
|
"""Test we handle invalid auth (local)."""
|
|
result = await hass.config_entries.flow.async_init(
|
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.FORM
|
|
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
{"hub": TEST_SERVER},
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "local_or_cloud"
|
|
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
{"api_type": "local"},
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "local"
|
|
|
|
with patch(
|
|
"homeassistant.components.overkiz.config_flow.OverkizClient.login",
|
|
side_effect=side_effect,
|
|
):
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
{
|
|
"host": TEST_HOST,
|
|
"token": TEST_TOKEN,
|
|
"verify_ssl": True,
|
|
},
|
|
)
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["errors"] == {"base": error}
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("side_effect", "error"),
|
|
[
|
|
(BadCredentialsError, "unsupported_hardware"),
|
|
],
|
|
)
|
|
async def test_form_invalid_cozytouch_auth(
|
|
hass: HomeAssistant, side_effect: Exception, error: str
|
|
) -> None:
|
|
"""Test we handle invalid auth (cloud)."""
|
|
result = await hass.config_entries.flow.async_init(
|
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.FORM
|
|
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
{"hub": TEST_SERVER_COZYTOUCH},
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "cloud"
|
|
|
|
with patch(
|
|
"homeassistant.components.overkiz.config_flow.OverkizClient.login",
|
|
side_effect=side_effect,
|
|
):
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
{"username": TEST_EMAIL, "password": TEST_PASSWORD},
|
|
)
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["errors"] == {"base": error}
|
|
assert result["step_id"] == "cloud"
|
|
|
|
|
|
async def test_cloud_abort_on_duplicate_entry(hass: HomeAssistant) -> None:
|
|
"""Test we get the form."""
|
|
|
|
MockConfigEntry(
|
|
domain=DOMAIN,
|
|
unique_id=TEST_GATEWAY_ID,
|
|
data={"username": TEST_EMAIL, "password": TEST_PASSWORD, "hub": TEST_SERVER},
|
|
).add_to_hass(hass)
|
|
|
|
result = await hass.config_entries.flow.async_init(
|
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.FORM
|
|
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
{"hub": TEST_SERVER},
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "local_or_cloud"
|
|
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
{"api_type": "cloud"},
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "cloud"
|
|
|
|
with (
|
|
patch(
|
|
"homeassistant.components.overkiz.config_flow.OverkizClient.login",
|
|
return_value=True,
|
|
),
|
|
patch(
|
|
"homeassistant.components.overkiz.config_flow.OverkizClient.get_gateways",
|
|
return_value=MOCK_GATEWAY_RESPONSE,
|
|
),
|
|
):
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
{"username": TEST_EMAIL, "password": TEST_PASSWORD},
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.ABORT
|
|
assert result["reason"] == "already_configured"
|
|
|
|
|
|
async def test_local_abort_on_duplicate_entry(hass: HomeAssistant) -> None:
|
|
"""Test local API configuration is aborted if gateway already exists."""
|
|
|
|
MockConfigEntry(
|
|
domain=DOMAIN,
|
|
unique_id=TEST_GATEWAY_ID,
|
|
version=2,
|
|
data={
|
|
"host": TEST_HOST,
|
|
"token": TEST_TOKEN,
|
|
"verify_ssl": True,
|
|
"hub": TEST_SERVER,
|
|
"api_type": "local",
|
|
},
|
|
).add_to_hass(hass)
|
|
|
|
result = await hass.config_entries.flow.async_init(
|
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.FORM
|
|
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
{"hub": TEST_SERVER},
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "local_or_cloud"
|
|
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
{"api_type": "local"},
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "local"
|
|
|
|
with patch.multiple(
|
|
"homeassistant.components.overkiz.config_flow.OverkizClient",
|
|
login=AsyncMock(return_value=True),
|
|
get_gateways=AsyncMock(return_value=MOCK_GATEWAY_RESPONSE),
|
|
get_setup_option=AsyncMock(return_value=True),
|
|
):
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
{
|
|
"host": TEST_HOST,
|
|
"token": TEST_TOKEN,
|
|
"verify_ssl": True,
|
|
},
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.ABORT
|
|
assert result["reason"] == "already_configured"
|
|
|
|
|
|
async def test_cloud_allow_multiple_unique_entries(hass: HomeAssistant) -> None:
|
|
"""Test we get the form."""
|
|
|
|
MockConfigEntry(
|
|
version=1,
|
|
domain=DOMAIN,
|
|
unique_id=TEST_GATEWAY_ID2,
|
|
data={"username": TEST_EMAIL, "password": TEST_PASSWORD, "hub": TEST_SERVER},
|
|
).add_to_hass(hass)
|
|
|
|
result = await hass.config_entries.flow.async_init(
|
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.FORM
|
|
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
{"hub": TEST_SERVER},
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "local_or_cloud"
|
|
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
{"api_type": "cloud"},
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "cloud"
|
|
|
|
with (
|
|
patch(
|
|
"homeassistant.components.overkiz.config_flow.OverkizClient.login",
|
|
return_value=True,
|
|
),
|
|
patch(
|
|
"homeassistant.components.overkiz.config_flow.OverkizClient.get_gateways",
|
|
return_value=MOCK_GATEWAY_RESPONSE,
|
|
),
|
|
):
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
{"username": TEST_EMAIL, "password": TEST_PASSWORD},
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.CREATE_ENTRY
|
|
assert result["title"] == TEST_EMAIL
|
|
assert result["data"] == {
|
|
"api_type": "cloud",
|
|
"username": TEST_EMAIL,
|
|
"password": TEST_PASSWORD,
|
|
"hub": TEST_SERVER,
|
|
}
|
|
|
|
|
|
async def test_cloud_reauth_success(hass: HomeAssistant) -> None:
|
|
"""Test reauthentication flow."""
|
|
|
|
mock_entry = MockConfigEntry(
|
|
domain=DOMAIN,
|
|
unique_id=TEST_GATEWAY_ID,
|
|
version=2,
|
|
data={
|
|
"username": TEST_EMAIL,
|
|
"password": TEST_PASSWORD,
|
|
"hub": TEST_SERVER2,
|
|
"api_type": "cloud",
|
|
},
|
|
)
|
|
mock_entry.add_to_hass(hass)
|
|
|
|
result = await mock_entry.start_reauth_flow(hass)
|
|
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "cloud"
|
|
|
|
with (
|
|
patch(
|
|
"homeassistant.components.overkiz.config_flow.OverkizClient.login",
|
|
return_value=True,
|
|
),
|
|
patch(
|
|
"homeassistant.components.overkiz.config_flow.OverkizClient.get_gateways",
|
|
return_value=MOCK_GATEWAY_RESPONSE,
|
|
),
|
|
):
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
user_input={
|
|
"username": TEST_EMAIL,
|
|
"password": TEST_PASSWORD2,
|
|
},
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.ABORT
|
|
assert result["reason"] == "reauth_successful"
|
|
assert mock_entry.data["username"] == TEST_EMAIL
|
|
assert mock_entry.data["password"] == TEST_PASSWORD2
|
|
|
|
|
|
async def test_cloud_reauth_wrong_account(hass: HomeAssistant) -> None:
|
|
"""Test reauthentication flow."""
|
|
|
|
mock_entry = MockConfigEntry(
|
|
domain=DOMAIN,
|
|
unique_id=TEST_GATEWAY_ID,
|
|
version=2,
|
|
data={
|
|
"username": TEST_EMAIL,
|
|
"password": TEST_PASSWORD,
|
|
"hub": TEST_SERVER2,
|
|
"api_type": "cloud",
|
|
},
|
|
)
|
|
mock_entry.add_to_hass(hass)
|
|
|
|
result = await mock_entry.start_reauth_flow(hass)
|
|
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "cloud"
|
|
|
|
with (
|
|
patch(
|
|
"homeassistant.components.overkiz.config_flow.OverkizClient.login",
|
|
return_value=True,
|
|
),
|
|
patch(
|
|
"homeassistant.components.overkiz.config_flow.OverkizClient.get_gateways",
|
|
return_value=MOCK_GATEWAY2_RESPONSE,
|
|
),
|
|
):
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
user_input={
|
|
"username": TEST_EMAIL,
|
|
"password": TEST_PASSWORD2,
|
|
},
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.ABORT
|
|
assert result["reason"] == "reauth_wrong_account"
|
|
|
|
|
|
async def test_local_reauth_legacy(hass: HomeAssistant) -> None:
|
|
"""Test legacy reauthentication flow with username/password."""
|
|
mock_entry = MockConfigEntry(
|
|
domain=DOMAIN,
|
|
unique_id=TEST_GATEWAY_ID,
|
|
version=2,
|
|
data={
|
|
"host": TEST_HOST,
|
|
"username": TEST_EMAIL,
|
|
"password": TEST_PASSWORD,
|
|
"verify_ssl": True,
|
|
"hub": TEST_SERVER,
|
|
"api_type": "local",
|
|
},
|
|
)
|
|
mock_entry.add_to_hass(hass)
|
|
|
|
result = await mock_entry.start_reauth_flow(hass)
|
|
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "local_or_cloud"
|
|
|
|
result2 = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
{"api_type": "local"},
|
|
)
|
|
|
|
assert result2["step_id"] == "local"
|
|
|
|
with patch.multiple(
|
|
"homeassistant.components.overkiz.config_flow.OverkizClient",
|
|
login=AsyncMock(return_value=True),
|
|
get_gateways=AsyncMock(return_value=MOCK_GATEWAY_RESPONSE),
|
|
):
|
|
result3 = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
{
|
|
"host": TEST_HOST,
|
|
"token": "new_token",
|
|
"verify_ssl": True,
|
|
},
|
|
)
|
|
|
|
assert result3["type"] is FlowResultType.ABORT
|
|
assert result3["reason"] == "reauth_successful"
|
|
assert mock_entry.data["host"] == TEST_HOST
|
|
assert mock_entry.data["token"] == "new_token"
|
|
assert mock_entry.data["verify_ssl"] is True
|
|
# The legacy username/password are dropped after migrating to a token.
|
|
assert "username" not in mock_entry.data
|
|
assert "password" not in mock_entry.data
|
|
|
|
|
|
async def test_local_reauth_success(hass: HomeAssistant) -> None:
|
|
"""Test modern local reauth flow."""
|
|
mock_entry = MockConfigEntry(
|
|
domain=DOMAIN,
|
|
unique_id=TEST_GATEWAY_ID,
|
|
version=2,
|
|
data={
|
|
"host": TEST_HOST,
|
|
"token": "old_token",
|
|
"verify_ssl": True,
|
|
"hub": TEST_SERVER,
|
|
"api_type": "local",
|
|
},
|
|
)
|
|
mock_entry.add_to_hass(hass)
|
|
|
|
result = await mock_entry.start_reauth_flow(hass)
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "local_or_cloud"
|
|
|
|
result2 = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
{"api_type": "local"},
|
|
)
|
|
|
|
assert result2["step_id"] == "local"
|
|
|
|
with patch.multiple(
|
|
"homeassistant.components.overkiz.config_flow.OverkizClient",
|
|
login=AsyncMock(return_value=True),
|
|
get_gateways=AsyncMock(return_value=MOCK_GATEWAY_RESPONSE),
|
|
):
|
|
result3 = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
{
|
|
"host": TEST_HOST,
|
|
"token": "new_token",
|
|
"verify_ssl": True,
|
|
},
|
|
)
|
|
|
|
assert result3["type"] is FlowResultType.ABORT
|
|
assert result3["reason"] == "reauth_successful"
|
|
assert mock_entry.data["host"] == TEST_HOST
|
|
assert mock_entry.data["token"] == "new_token"
|
|
assert mock_entry.data["verify_ssl"] is True
|
|
assert "username" not in mock_entry.data
|
|
assert "password" not in mock_entry.data
|
|
|
|
|
|
async def test_local_reauth_wrong_account(hass: HomeAssistant) -> None:
|
|
"""Test local reauth flow with wrong gateway account."""
|
|
|
|
mock_entry = MockConfigEntry(
|
|
domain=DOMAIN,
|
|
unique_id=TEST_GATEWAY_ID2,
|
|
version=2,
|
|
data={
|
|
"host": TEST_HOST,
|
|
"token": "old_token",
|
|
"verify_ssl": True,
|
|
"hub": TEST_SERVER,
|
|
"api_type": "local",
|
|
},
|
|
)
|
|
mock_entry.add_to_hass(hass)
|
|
|
|
result = await mock_entry.start_reauth_flow(hass)
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "local_or_cloud"
|
|
|
|
result2 = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
{"api_type": "local"},
|
|
)
|
|
|
|
assert result2["step_id"] == "local"
|
|
|
|
with patch.multiple(
|
|
"homeassistant.components.overkiz.config_flow.OverkizClient",
|
|
login=AsyncMock(return_value=True),
|
|
get_gateways=AsyncMock(return_value=MOCK_GATEWAY_RESPONSE),
|
|
):
|
|
result3 = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
{
|
|
"host": TEST_HOST,
|
|
"token": "new_token",
|
|
"verify_ssl": True,
|
|
},
|
|
)
|
|
|
|
assert result3["type"] is FlowResultType.ABORT
|
|
assert result3["reason"] == "reauth_wrong_account"
|
|
|
|
|
|
async def test_cloud_reconfigure_success(hass: HomeAssistant) -> None:
|
|
"""Test reconfiguration flow on a cloud entry."""
|
|
mock_entry = MockConfigEntry(
|
|
domain=DOMAIN,
|
|
unique_id=TEST_GATEWAY_ID,
|
|
version=2,
|
|
data={
|
|
"username": TEST_EMAIL,
|
|
"password": TEST_PASSWORD,
|
|
"hub": TEST_SERVER2,
|
|
"api_type": "cloud",
|
|
},
|
|
)
|
|
mock_entry.add_to_hass(hass)
|
|
|
|
result = await mock_entry.start_reconfigure_flow(hass)
|
|
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "cloud"
|
|
|
|
with (
|
|
patch(
|
|
"homeassistant.components.overkiz.config_flow.OverkizClient.login",
|
|
return_value=True,
|
|
),
|
|
patch(
|
|
"homeassistant.components.overkiz.config_flow.OverkizClient.get_gateways",
|
|
return_value=MOCK_GATEWAY_RESPONSE,
|
|
),
|
|
):
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
user_input={
|
|
"username": TEST_EMAIL,
|
|
"password": TEST_PASSWORD2,
|
|
},
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.ABORT
|
|
assert result["reason"] == "reconfigure_successful"
|
|
assert mock_entry.data["username"] == TEST_EMAIL
|
|
assert mock_entry.data["password"] == TEST_PASSWORD2
|
|
|
|
|
|
async def test_cloud_reconfigure_wrong_account(hass: HomeAssistant) -> None:
|
|
"""Test reconfiguration aborts when the gateway account differs."""
|
|
mock_entry = MockConfigEntry(
|
|
domain=DOMAIN,
|
|
unique_id=TEST_GATEWAY_ID,
|
|
version=2,
|
|
data={
|
|
"username": TEST_EMAIL,
|
|
"password": TEST_PASSWORD,
|
|
"hub": TEST_SERVER2,
|
|
"api_type": "cloud",
|
|
},
|
|
)
|
|
mock_entry.add_to_hass(hass)
|
|
|
|
result = await mock_entry.start_reconfigure_flow(hass)
|
|
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "cloud"
|
|
|
|
with (
|
|
patch(
|
|
"homeassistant.components.overkiz.config_flow.OverkizClient.login",
|
|
return_value=True,
|
|
),
|
|
patch(
|
|
"homeassistant.components.overkiz.config_flow.OverkizClient.get_gateways",
|
|
return_value=MOCK_GATEWAY2_RESPONSE,
|
|
),
|
|
):
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
user_input={
|
|
"username": TEST_EMAIL,
|
|
"password": TEST_PASSWORD2,
|
|
},
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.ABORT
|
|
assert result["reason"] == "reconfigure_wrong_account"
|
|
|
|
|
|
async def test_local_reconfigure_success(hass: HomeAssistant) -> None:
|
|
"""Test reconfiguration flow on a local entry."""
|
|
mock_entry = MockConfigEntry(
|
|
domain=DOMAIN,
|
|
unique_id=TEST_GATEWAY_ID,
|
|
version=2,
|
|
data={
|
|
"host": TEST_HOST,
|
|
"token": "old_token",
|
|
"verify_ssl": True,
|
|
"hub": TEST_SERVER,
|
|
"api_type": "local",
|
|
},
|
|
)
|
|
mock_entry.add_to_hass(hass)
|
|
|
|
result = await mock_entry.start_reconfigure_flow(hass)
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "local_or_cloud"
|
|
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
{"api_type": "local"},
|
|
)
|
|
|
|
assert result["step_id"] == "local"
|
|
|
|
with patch.multiple(
|
|
"homeassistant.components.overkiz.config_flow.OverkizClient",
|
|
login=AsyncMock(return_value=True),
|
|
get_gateways=AsyncMock(return_value=MOCK_GATEWAY_RESPONSE),
|
|
):
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
{
|
|
"host": TEST_HOST,
|
|
"token": "new_token",
|
|
"verify_ssl": True,
|
|
},
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.ABORT
|
|
assert result["reason"] == "reconfigure_successful"
|
|
assert mock_entry.data["host"] == TEST_HOST
|
|
assert mock_entry.data["token"] == "new_token"
|
|
assert mock_entry.data["verify_ssl"] is True
|
|
|
|
|
|
async def test_local_reconfigure_wrong_account(hass: HomeAssistant) -> None:
|
|
"""Test local reconfiguration aborts when the gateway account differs."""
|
|
mock_entry = MockConfigEntry(
|
|
domain=DOMAIN,
|
|
unique_id=TEST_GATEWAY_ID2,
|
|
version=2,
|
|
data={
|
|
"host": TEST_HOST,
|
|
"token": "old_token",
|
|
"verify_ssl": True,
|
|
"hub": TEST_SERVER,
|
|
"api_type": "local",
|
|
},
|
|
)
|
|
mock_entry.add_to_hass(hass)
|
|
|
|
result = await mock_entry.start_reconfigure_flow(hass)
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "local_or_cloud"
|
|
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
{"api_type": "local"},
|
|
)
|
|
|
|
assert result["step_id"] == "local"
|
|
|
|
with patch.multiple(
|
|
"homeassistant.components.overkiz.config_flow.OverkizClient",
|
|
login=AsyncMock(return_value=True),
|
|
get_gateways=AsyncMock(return_value=MOCK_GATEWAY_RESPONSE),
|
|
):
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
{
|
|
"host": TEST_HOST,
|
|
"token": "new_token",
|
|
"verify_ssl": True,
|
|
},
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.ABORT
|
|
assert result["reason"] == "reconfigure_wrong_account"
|
|
|
|
|
|
async def test_reconfigure_switch_cloud_to_local(hass: HomeAssistant) -> None:
|
|
"""Test reconfiguring a cloud entry to use the local API."""
|
|
mock_entry = MockConfigEntry(
|
|
domain=DOMAIN,
|
|
unique_id=TEST_GATEWAY_ID,
|
|
version=2,
|
|
data={
|
|
"username": TEST_EMAIL,
|
|
"password": TEST_PASSWORD,
|
|
"hub": TEST_SERVER,
|
|
"api_type": "cloud",
|
|
},
|
|
)
|
|
mock_entry.add_to_hass(hass)
|
|
|
|
result = await mock_entry.start_reconfigure_flow(hass)
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "local_or_cloud"
|
|
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
{"api_type": "local"},
|
|
)
|
|
|
|
assert result["step_id"] == "local"
|
|
|
|
with patch.multiple(
|
|
"homeassistant.components.overkiz.config_flow.OverkizClient",
|
|
login=AsyncMock(return_value=True),
|
|
get_gateways=AsyncMock(return_value=MOCK_GATEWAY_RESPONSE),
|
|
):
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
{
|
|
"host": TEST_HOST,
|
|
"token": TEST_TOKEN,
|
|
"verify_ssl": True,
|
|
},
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.ABORT
|
|
assert result["reason"] == "reconfigure_successful"
|
|
assert mock_entry.data["api_type"] == "local"
|
|
assert mock_entry.data["host"] == TEST_HOST
|
|
assert mock_entry.data["token"] == TEST_TOKEN
|
|
# Full data replace drops the previous cloud credentials.
|
|
assert "username" not in mock_entry.data
|
|
assert "password" not in mock_entry.data
|
|
|
|
|
|
async def test_dhcp_flow(hass: HomeAssistant, mock_setup_entry: AsyncMock) -> None:
|
|
"""Test that DHCP discovery for new bridge works."""
|
|
result = await hass.config_entries.flow.async_init(
|
|
DOMAIN,
|
|
data=DhcpServiceInfo(
|
|
hostname="gateway-1234-5678-9123",
|
|
ip="192.168.1.4",
|
|
macaddress="f8811a000000",
|
|
),
|
|
context={"source": config_entries.SOURCE_DHCP},
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == config_entries.SOURCE_USER
|
|
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
{"hub": TEST_SERVER},
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "local_or_cloud"
|
|
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
{"api_type": "cloud"},
|
|
)
|
|
|
|
with (
|
|
patch(
|
|
"homeassistant.components.overkiz.config_flow.OverkizClient.login",
|
|
return_value=True,
|
|
),
|
|
patch(
|
|
"homeassistant.components.overkiz.config_flow.OverkizClient.get_gateways",
|
|
return_value=None,
|
|
),
|
|
):
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
{
|
|
"username": TEST_EMAIL,
|
|
"password": TEST_PASSWORD,
|
|
},
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.CREATE_ENTRY
|
|
assert result["title"] == TEST_EMAIL
|
|
assert result["data"] == {
|
|
"username": TEST_EMAIL,
|
|
"password": TEST_PASSWORD,
|
|
"hub": TEST_SERVER,
|
|
"api_type": "cloud",
|
|
}
|
|
|
|
assert len(mock_setup_entry.mock_calls) == 1
|
|
|
|
|
|
async def test_dhcp_flow_already_configured(hass: HomeAssistant) -> None:
|
|
"""Test that DHCP doesn't setup already configured gateways."""
|
|
config_entry = MockConfigEntry(
|
|
domain=DOMAIN,
|
|
unique_id=TEST_GATEWAY_ID,
|
|
data={"username": TEST_EMAIL, "password": TEST_PASSWORD, "hub": TEST_SERVER},
|
|
)
|
|
config_entry.add_to_hass(hass)
|
|
|
|
result = await hass.config_entries.flow.async_init(
|
|
DOMAIN,
|
|
data=DhcpServiceInfo(
|
|
hostname="gateway-1234-5678-9123",
|
|
ip="192.168.1.4",
|
|
macaddress="f8811a000000",
|
|
),
|
|
context={"source": config_entries.SOURCE_DHCP},
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.ABORT
|
|
assert result["reason"] == "already_configured"
|
|
|
|
|
|
async def test_zeroconf_flow(hass: HomeAssistant, mock_setup_entry: AsyncMock) -> None:
|
|
"""Test that zeroconf discovery for new bridge works."""
|
|
result = await hass.config_entries.flow.async_init(
|
|
DOMAIN,
|
|
data=FAKE_ZERO_CONF_INFO,
|
|
context={"source": config_entries.SOURCE_ZEROCONF},
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == config_entries.SOURCE_USER
|
|
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
{"hub": TEST_SERVER},
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "local_or_cloud"
|
|
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
{"api_type": "cloud"},
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "cloud"
|
|
|
|
with (
|
|
patch(
|
|
"homeassistant.components.overkiz.config_flow.OverkizClient.login",
|
|
return_value=True,
|
|
),
|
|
patch(
|
|
"homeassistant.components.overkiz.config_flow.OverkizClient.get_gateways",
|
|
return_value=MOCK_GATEWAY_RESPONSE,
|
|
),
|
|
):
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
{"username": TEST_EMAIL, "password": TEST_PASSWORD},
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.CREATE_ENTRY
|
|
assert result["title"] == TEST_EMAIL
|
|
assert result["data"] == {
|
|
"username": TEST_EMAIL,
|
|
"password": TEST_PASSWORD,
|
|
"hub": TEST_SERVER,
|
|
"api_type": "cloud",
|
|
}
|
|
|
|
assert len(mock_setup_entry.mock_calls) == 1
|
|
|
|
|
|
async def test_local_zeroconf_flow(
|
|
hass: HomeAssistant, mock_setup_entry: AsyncMock
|
|
) -> None:
|
|
"""Test that zeroconf discovery for new local bridge works."""
|
|
result = await hass.config_entries.flow.async_init(
|
|
DOMAIN,
|
|
data=FAKE_ZERO_CONF_INFO_LOCAL,
|
|
context={"source": config_entries.SOURCE_ZEROCONF},
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == config_entries.SOURCE_USER
|
|
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
{"hub": TEST_SERVER},
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "local_or_cloud"
|
|
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
{"api_type": "local"},
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "local"
|
|
|
|
with patch.multiple(
|
|
"homeassistant.components.overkiz.config_flow.OverkizClient",
|
|
login=AsyncMock(return_value=True),
|
|
get_gateways=AsyncMock(return_value=MOCK_GATEWAY_RESPONSE),
|
|
):
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
{
|
|
"host": "gateway-1234-5678-9123.local:8443",
|
|
"token": TEST_TOKEN,
|
|
"verify_ssl": False,
|
|
},
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.CREATE_ENTRY
|
|
assert result["title"] == "gateway-1234-5678-9123.local:8443"
|
|
|
|
# Verify no username/password in data
|
|
assert result["data"] == {
|
|
"host": "gateway-1234-5678-9123.local:8443",
|
|
"token": TEST_TOKEN,
|
|
"verify_ssl": False,
|
|
"hub": TEST_SERVER,
|
|
"api_type": "local",
|
|
}
|
|
assert len(mock_setup_entry.mock_calls) == 1
|
|
|
|
|
|
async def test_zeroconf_flow_already_configured(hass: HomeAssistant) -> None:
|
|
"""Test that zeroconf doesn't setup already configured gateways."""
|
|
config_entry = MockConfigEntry(
|
|
domain=DOMAIN,
|
|
unique_id=TEST_GATEWAY_ID,
|
|
data={"username": TEST_EMAIL, "password": TEST_PASSWORD, "hub": TEST_SERVER},
|
|
)
|
|
config_entry.add_to_hass(hass)
|
|
|
|
result = await hass.config_entries.flow.async_init(
|
|
DOMAIN,
|
|
data=FAKE_ZERO_CONF_INFO,
|
|
context={"source": config_entries.SOURCE_ZEROCONF},
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.ABORT
|
|
assert result["reason"] == "already_configured"
|
|
|
|
|
|
@pytest.fixture
|
|
async def setup_rexel_credentials(hass: HomeAssistant) -> None:
|
|
"""Set up the application credential used by the Rexel OAuth2 flow."""
|
|
assert await async_setup_component(hass, "application_credentials", {})
|
|
await async_import_client_credential(
|
|
hass,
|
|
DOMAIN,
|
|
ClientCredential(REXEL_CLIENT_ID, ""),
|
|
DOMAIN,
|
|
)
|
|
|
|
|
|
async def _async_rexel_oauth_external_step(
|
|
hass: HomeAssistant,
|
|
hass_client_no_auth: ClientSessionGenerator,
|
|
aioclient_mock: AiohttpClientMocker,
|
|
flow_id: str,
|
|
) -> None:
|
|
"""Drive the OAuth2 external step and stub the token exchange."""
|
|
state = config_entry_oauth2_flow._encode_jwt(
|
|
hass,
|
|
{"flow_id": flow_id, "redirect_uri": REXEL_REDIRECT_URI},
|
|
)
|
|
|
|
client = await hass_client_no_auth()
|
|
resp = await client.get(f"/auth/external/callback?code=abcd&state={state}")
|
|
assert resp.status == 200
|
|
|
|
aioclient_mock.post(
|
|
REXEL_OAUTH_TOKEN_URL,
|
|
json={
|
|
"refresh_token": "mock-refresh-token",
|
|
"access_token": "mock-access-token",
|
|
"type": "Bearer",
|
|
"expires_in": 60,
|
|
},
|
|
)
|
|
|
|
|
|
@pytest.mark.usefixtures("current_request_with_host", "setup_rexel_credentials")
|
|
async def test_rexel_full_flow_single_gateway(
|
|
hass: HomeAssistant,
|
|
hass_client_no_auth: ClientSessionGenerator,
|
|
aioclient_mock: AiohttpClientMocker,
|
|
mock_setup_entry: AsyncMock,
|
|
) -> None:
|
|
"""A single-gateway Rexel account auto-selects and creates an entry."""
|
|
result = await hass.config_entries.flow.async_init(
|
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
|
)
|
|
|
|
# Rexel is part of SERVERS_WITH_LOCAL_API, so the local/cloud choice is
|
|
# shown before the OAuth2 flow starts.
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"], {"hub": "rexel"}
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "local_or_cloud"
|
|
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"], {"api_type": "cloud"}
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.EXTERNAL_STEP
|
|
assert REXEL_OAUTH_AUTHORIZE_URL in result["url"]
|
|
# Azure AD B2C needs the policy on the authorize URL; the helper rebuilds
|
|
# the query string, so it must survive via extra_authorize_data.
|
|
assert f"p={REXEL_OAUTH_POLICY}" in result["url"]
|
|
# offline_access is required for B2C to return a refresh token.
|
|
assert f"{REXEL_OAUTH_SCOPE}+offline_access" in result["url"]
|
|
|
|
await _async_rexel_oauth_external_step(
|
|
hass, hass_client_no_auth, aioclient_mock, result["flow_id"]
|
|
)
|
|
|
|
with patch(
|
|
"homeassistant.components.overkiz.config_flow.OverkizClient.discover_gateways",
|
|
return_value=[GatewayCandidate(gateway_id=TEST_GATEWAY_ID, label="My Home")],
|
|
):
|
|
result = await hass.config_entries.flow.async_configure(result["flow_id"])
|
|
|
|
assert result["type"] is FlowResultType.CREATE_ENTRY
|
|
assert result["title"] == "My Home"
|
|
assert result["result"].unique_id == TEST_GATEWAY_ID
|
|
assert result["data"]["hub"] == "rexel"
|
|
assert result["data"]["gateway_id"] == TEST_GATEWAY_ID
|
|
assert "token" in result["data"]
|
|
assert len(mock_setup_entry.mock_calls) == 1
|
|
|
|
|
|
@pytest.mark.usefixtures("current_request_with_host")
|
|
async def test_rexel_flow_reimports_removed_credential(
|
|
hass: HomeAssistant,
|
|
) -> None:
|
|
"""The Rexel flow re-imports its client credential if the user removed it."""
|
|
assert await async_setup_component(hass, "application_credentials", {})
|
|
|
|
result = await hass.config_entries.flow.async_init(
|
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
|
)
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"], {"hub": "rexel"}
|
|
)
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"], {"api_type": "cloud"}
|
|
)
|
|
|
|
# Reaching the OAuth2 external step proves an implementation was available,
|
|
# i.e. the credential was re-imported despite not being present beforehand.
|
|
assert result["type"] is FlowResultType.EXTERNAL_STEP
|
|
assert REXEL_OAUTH_AUTHORIZE_URL in result["url"]
|
|
|
|
|
|
@pytest.mark.usefixtures("current_request_with_host", "setup_rexel_credentials")
|
|
async def test_rexel_full_flow_multiple_gateways(
|
|
hass: HomeAssistant,
|
|
hass_client_no_auth: ClientSessionGenerator,
|
|
aioclient_mock: AiohttpClientMocker,
|
|
mock_setup_entry: AsyncMock,
|
|
) -> None:
|
|
"""A multi-gateway Rexel account shows a selection step."""
|
|
result = await hass.config_entries.flow.async_init(
|
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
|
)
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"], {"hub": "rexel"}
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "local_or_cloud"
|
|
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"], {"api_type": "cloud"}
|
|
)
|
|
await _async_rexel_oauth_external_step(
|
|
hass, hass_client_no_auth, aioclient_mock, result["flow_id"]
|
|
)
|
|
|
|
with patch(
|
|
"homeassistant.components.overkiz.config_flow.OverkizClient.discover_gateways",
|
|
return_value=[
|
|
GatewayCandidate(gateway_id=TEST_GATEWAY_ID, label="Home"),
|
|
GatewayCandidate(gateway_id=TEST_GATEWAY_ID2, label="Office"),
|
|
],
|
|
):
|
|
result = await hass.config_entries.flow.async_configure(result["flow_id"])
|
|
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "select_gateway"
|
|
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"], {"gateway_id": TEST_GATEWAY_ID2}
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.CREATE_ENTRY
|
|
assert result["title"] == "Office"
|
|
assert result["result"].unique_id == TEST_GATEWAY_ID2
|
|
assert result["data"]["gateway_id"] == TEST_GATEWAY_ID2
|
|
assert len(mock_setup_entry.mock_calls) == 1
|
|
|
|
|
|
@pytest.mark.usefixtures("current_request_with_host", "setup_rexel_credentials")
|
|
async def test_rexel_flow_no_gateways(
|
|
hass: HomeAssistant,
|
|
hass_client_no_auth: ClientSessionGenerator,
|
|
aioclient_mock: AiohttpClientMocker,
|
|
) -> None:
|
|
"""A Rexel account without gateways aborts."""
|
|
result = await hass.config_entries.flow.async_init(
|
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
|
)
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"], {"hub": "rexel"}
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "local_or_cloud"
|
|
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"], {"api_type": "cloud"}
|
|
)
|
|
await _async_rexel_oauth_external_step(
|
|
hass, hass_client_no_auth, aioclient_mock, result["flow_id"]
|
|
)
|
|
|
|
with patch(
|
|
"homeassistant.components.overkiz.config_flow.OverkizClient.discover_gateways",
|
|
return_value=[],
|
|
):
|
|
result = await hass.config_entries.flow.async_configure(result["flow_id"])
|
|
|
|
assert result["type"] is FlowResultType.ABORT
|
|
assert result["reason"] == "no_gateways"
|
|
|
|
|
|
@pytest.mark.usefixtures("current_request_with_host", "setup_rexel_credentials")
|
|
async def test_rexel_flow_cannot_connect(
|
|
hass: HomeAssistant,
|
|
hass_client_no_auth: ClientSessionGenerator,
|
|
aioclient_mock: AiohttpClientMocker,
|
|
) -> None:
|
|
"""A Rexel gateway discovery error aborts."""
|
|
result = await hass.config_entries.flow.async_init(
|
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
|
)
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"], {"hub": "rexel"}
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "local_or_cloud"
|
|
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"], {"api_type": "cloud"}
|
|
)
|
|
await _async_rexel_oauth_external_step(
|
|
hass, hass_client_no_auth, aioclient_mock, result["flow_id"]
|
|
)
|
|
|
|
with patch(
|
|
"homeassistant.components.overkiz.config_flow.OverkizClient.discover_gateways",
|
|
side_effect=ClientError,
|
|
):
|
|
result = await hass.config_entries.flow.async_configure(result["flow_id"])
|
|
|
|
assert result["type"] is FlowResultType.ABORT
|
|
assert result["reason"] == "cannot_connect"
|
|
|
|
|
|
@pytest.mark.usefixtures("current_request_with_host", "setup_rexel_credentials")
|
|
async def test_rexel_reauth_wrong_account(
|
|
hass: HomeAssistant,
|
|
hass_client_no_auth: ClientSessionGenerator,
|
|
aioclient_mock: AiohttpClientMocker,
|
|
mock_rexel_config_entry: MockConfigEntry,
|
|
) -> None:
|
|
"""Reauth with a different Rexel gateway aborts."""
|
|
mock_rexel_config_entry.add_to_hass(hass)
|
|
|
|
# Reauth carries the stored hub, so the flow skips the server picker and
|
|
# goes to the local/cloud choice before re-running the OAuth2 flow.
|
|
result = await mock_rexel_config_entry.start_reauth_flow(hass)
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "local_or_cloud"
|
|
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"], {"api_type": "cloud"}
|
|
)
|
|
assert result["type"] is FlowResultType.EXTERNAL_STEP
|
|
|
|
await _async_rexel_oauth_external_step(
|
|
hass, hass_client_no_auth, aioclient_mock, result["flow_id"]
|
|
)
|
|
|
|
with patch(
|
|
"homeassistant.components.overkiz.config_flow.OverkizClient.discover_gateways",
|
|
return_value=[GatewayCandidate(gateway_id=TEST_GATEWAY_ID2, label="Other")],
|
|
):
|
|
result = await hass.config_entries.flow.async_configure(result["flow_id"])
|
|
|
|
assert result["type"] is FlowResultType.ABORT
|
|
assert result["reason"] == "reauth_wrong_account"
|
|
|
|
|
|
@pytest.mark.usefixtures("current_request_with_host", "setup_rexel_credentials")
|
|
async def test_rexel_reauth_success(
|
|
hass: HomeAssistant,
|
|
hass_client_no_auth: ClientSessionGenerator,
|
|
aioclient_mock: AiohttpClientMocker,
|
|
mock_rexel_config_entry: MockConfigEntry,
|
|
) -> None:
|
|
"""Reauth with the same Rexel gateway updates the entry and reloads."""
|
|
mock_rexel_config_entry.add_to_hass(hass)
|
|
|
|
result = await mock_rexel_config_entry.start_reauth_flow(hass)
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "local_or_cloud"
|
|
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"], {"api_type": "cloud"}
|
|
)
|
|
assert result["type"] is FlowResultType.EXTERNAL_STEP
|
|
|
|
await _async_rexel_oauth_external_step(
|
|
hass, hass_client_no_auth, aioclient_mock, result["flow_id"]
|
|
)
|
|
|
|
with patch(
|
|
"homeassistant.components.overkiz.config_flow.OverkizClient.discover_gateways",
|
|
return_value=[GatewayCandidate(gateway_id=TEST_GATEWAY_ID, label="My Home")],
|
|
):
|
|
result = await hass.config_entries.flow.async_configure(result["flow_id"])
|
|
|
|
assert result["type"] is FlowResultType.ABORT
|
|
assert result["reason"] == "reauth_successful"
|
|
assert mock_rexel_config_entry.data["token"]["access_token"] == "mock-access-token"
|
|
|
|
|
|
@pytest.mark.usefixtures("current_request_with_host", "setup_rexel_credentials")
|
|
async def test_rexel_reconfigure_wrong_account(
|
|
hass: HomeAssistant,
|
|
hass_client_no_auth: ClientSessionGenerator,
|
|
aioclient_mock: AiohttpClientMocker,
|
|
mock_rexel_config_entry: MockConfigEntry,
|
|
) -> None:
|
|
"""Reconfigure with a different Rexel gateway aborts."""
|
|
mock_rexel_config_entry.add_to_hass(hass)
|
|
|
|
# Reconfigure carries the stored hub, so the flow skips the server picker
|
|
# and goes to the local/cloud choice before re-running the OAuth2 flow.
|
|
result = await mock_rexel_config_entry.start_reconfigure_flow(hass)
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "local_or_cloud"
|
|
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"], {"api_type": "cloud"}
|
|
)
|
|
assert result["type"] is FlowResultType.EXTERNAL_STEP
|
|
|
|
await _async_rexel_oauth_external_step(
|
|
hass, hass_client_no_auth, aioclient_mock, result["flow_id"]
|
|
)
|
|
|
|
with patch(
|
|
"homeassistant.components.overkiz.config_flow.OverkizClient.discover_gateways",
|
|
return_value=[GatewayCandidate(gateway_id=TEST_GATEWAY_ID2, label="Other")],
|
|
):
|
|
result = await hass.config_entries.flow.async_configure(result["flow_id"])
|
|
|
|
assert result["type"] is FlowResultType.ABORT
|
|
assert result["reason"] == "reconfigure_wrong_account"
|
|
|
|
|
|
@pytest.mark.usefixtures("current_request_with_host", "setup_rexel_credentials")
|
|
async def test_rexel_reconfigure_success(
|
|
hass: HomeAssistant,
|
|
hass_client_no_auth: ClientSessionGenerator,
|
|
aioclient_mock: AiohttpClientMocker,
|
|
mock_rexel_config_entry: MockConfigEntry,
|
|
) -> None:
|
|
"""Reconfigure with the same Rexel gateway updates the entry and reloads."""
|
|
mock_rexel_config_entry.add_to_hass(hass)
|
|
|
|
result = await mock_rexel_config_entry.start_reconfigure_flow(hass)
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "local_or_cloud"
|
|
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"], {"api_type": "cloud"}
|
|
)
|
|
assert result["type"] is FlowResultType.EXTERNAL_STEP
|
|
|
|
await _async_rexel_oauth_external_step(
|
|
hass, hass_client_no_auth, aioclient_mock, result["flow_id"]
|
|
)
|
|
|
|
with patch(
|
|
"homeassistant.components.overkiz.config_flow.OverkizClient.discover_gateways",
|
|
return_value=[GatewayCandidate(gateway_id=TEST_GATEWAY_ID, label="My Home")],
|
|
):
|
|
result = await hass.config_entries.flow.async_configure(result["flow_id"])
|
|
|
|
assert result["type"] is FlowResultType.ABORT
|
|
assert result["reason"] == "reconfigure_successful"
|
|
assert mock_rexel_config_entry.data["token"]["access_token"] == "mock-access-token"
|