2447 lines
82 KiB
Python
2447 lines
82 KiB
Python
"""Test the tplink config flow."""
|
|
|
|
import logging
|
|
from unittest.mock import ANY, AsyncMock, patch
|
|
|
|
from kasa import Module, TimeoutError
|
|
import pytest
|
|
|
|
from homeassistant import config_entries
|
|
from homeassistant.components import dhcp, stream
|
|
from homeassistant.components.tplink import (
|
|
DOMAIN,
|
|
AuthenticationError,
|
|
Credentials,
|
|
Device,
|
|
DeviceConfig,
|
|
KasaException,
|
|
)
|
|
from homeassistant.components.tplink.config_flow import TPLinkConfigFlow
|
|
from homeassistant.components.tplink.const import (
|
|
CONF_CAMERA_CREDENTIALS,
|
|
CONF_CONNECTION_PARAMETERS,
|
|
CONF_CREDENTIALS_HASH,
|
|
CONF_DEVICE_CONFIG,
|
|
CONF_LIVE_VIEW,
|
|
)
|
|
from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntryState
|
|
from homeassistant.const import (
|
|
CONF_ALIAS,
|
|
CONF_DEVICE,
|
|
CONF_HOST,
|
|
CONF_MAC,
|
|
CONF_PASSWORD,
|
|
CONF_PORT,
|
|
CONF_USERNAME,
|
|
)
|
|
from homeassistant.core import HomeAssistant
|
|
from homeassistant.data_entry_flow import FlowResultType
|
|
|
|
from . import (
|
|
AES_KEYS,
|
|
ALIAS,
|
|
ALIAS_CAMERA,
|
|
CONN_PARAMS_AES,
|
|
CONN_PARAMS_KLAP,
|
|
CONN_PARAMS_LEGACY,
|
|
CREATE_ENTRY_DATA_AES,
|
|
CREATE_ENTRY_DATA_AES_CAMERA,
|
|
CREATE_ENTRY_DATA_KLAP,
|
|
CREATE_ENTRY_DATA_LEGACY,
|
|
CREDENTIALS_HASH_AES,
|
|
CREDENTIALS_HASH_KLAP,
|
|
DEFAULT_ENTRY_TITLE,
|
|
DEFAULT_ENTRY_TITLE_CAMERA,
|
|
DEVICE_CONFIG_AES,
|
|
DEVICE_CONFIG_AES_CAMERA,
|
|
DEVICE_CONFIG_DICT_KLAP,
|
|
DEVICE_CONFIG_KLAP,
|
|
DEVICE_CONFIG_LEGACY,
|
|
DHCP_FORMATTED_MAC_ADDRESS,
|
|
IP_ADDRESS,
|
|
IP_ADDRESS2,
|
|
IP_ADDRESS3,
|
|
MAC_ADDRESS,
|
|
MAC_ADDRESS2,
|
|
MAC_ADDRESS3,
|
|
MODEL_CAMERA,
|
|
MODULE,
|
|
SMALLEST_VALID_JPEG_BYTES,
|
|
_mocked_device,
|
|
_patch_connect,
|
|
_patch_discovery,
|
|
_patch_single_discovery,
|
|
)
|
|
from .conftest import override_side_effect
|
|
|
|
from tests.common import MockConfigEntry
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("device_config", "expected_entry_data", "credentials_hash"),
|
|
[
|
|
pytest.param(
|
|
DEVICE_CONFIG_KLAP, CREATE_ENTRY_DATA_KLAP, CREDENTIALS_HASH_KLAP, id="KLAP"
|
|
),
|
|
pytest.param(
|
|
DEVICE_CONFIG_AES, CREATE_ENTRY_DATA_AES, CREDENTIALS_HASH_AES, id="AES"
|
|
),
|
|
pytest.param(DEVICE_CONFIG_LEGACY, CREATE_ENTRY_DATA_LEGACY, None, id="Legacy"),
|
|
],
|
|
)
|
|
async def test_discovery(
|
|
hass: HomeAssistant, device_config, expected_entry_data, credentials_hash
|
|
) -> None:
|
|
"""Test setting up discovery."""
|
|
ip_address = device_config.host
|
|
device = _mocked_device(
|
|
device_config=device_config,
|
|
credentials_hash=credentials_hash,
|
|
ip_address=ip_address,
|
|
mac=MAC_ADDRESS,
|
|
)
|
|
with (
|
|
_patch_discovery(device, ip_address=ip_address),
|
|
_patch_single_discovery(device),
|
|
_patch_connect(device),
|
|
):
|
|
result = await hass.config_entries.flow.async_init(
|
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
|
)
|
|
await hass.async_block_till_done()
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "user"
|
|
assert not result["errors"]
|
|
|
|
result2 = await hass.config_entries.flow.async_configure(result["flow_id"], {})
|
|
await hass.async_block_till_done()
|
|
assert result2["type"] is FlowResultType.FORM
|
|
assert result2["step_id"] == "pick_device"
|
|
assert not result2["errors"]
|
|
|
|
# test we can try again
|
|
result = await hass.config_entries.flow.async_init(
|
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
|
)
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "user"
|
|
assert not result["errors"]
|
|
|
|
result2 = await hass.config_entries.flow.async_configure(result["flow_id"], {})
|
|
await hass.async_block_till_done()
|
|
assert result2["type"] is FlowResultType.FORM
|
|
assert result2["step_id"] == "pick_device"
|
|
assert not result2["errors"]
|
|
|
|
with (
|
|
_patch_discovery(device, ip_address=ip_address),
|
|
_patch_single_discovery(device),
|
|
_patch_connect(device),
|
|
patch(f"{MODULE}.async_setup", return_value=True) as mock_setup,
|
|
patch(f"{MODULE}.async_setup_entry", return_value=True) as mock_setup_entry,
|
|
):
|
|
result3 = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
{CONF_DEVICE: MAC_ADDRESS},
|
|
)
|
|
await hass.async_block_till_done(wait_background_tasks=True)
|
|
|
|
assert result3["type"] is FlowResultType.CREATE_ENTRY
|
|
assert result3["title"] == DEFAULT_ENTRY_TITLE
|
|
assert result3["data"] == expected_entry_data
|
|
mock_setup.assert_called_once()
|
|
mock_setup_entry.assert_called_once()
|
|
|
|
# ignore configured devices
|
|
result = await hass.config_entries.flow.async_init(
|
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
|
)
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "user"
|
|
assert not result["errors"]
|
|
|
|
with _patch_discovery(), _patch_single_discovery(), _patch_connect():
|
|
result2 = await hass.config_entries.flow.async_configure(result["flow_id"], {})
|
|
await hass.async_block_till_done()
|
|
|
|
assert result2["type"] is FlowResultType.ABORT
|
|
assert result2["reason"] == "no_devices_found"
|
|
|
|
|
|
async def test_discovery_camera(
|
|
hass: HomeAssistant, mock_discovery: AsyncMock, mock_connect: AsyncMock, mock_init
|
|
) -> None:
|
|
"""Test authenticated discovery for camera with stream."""
|
|
mock_device = _mocked_device(
|
|
alias=ALIAS_CAMERA,
|
|
ip_address=IP_ADDRESS3,
|
|
mac=MAC_ADDRESS3,
|
|
model=MODEL_CAMERA,
|
|
device_config=DEVICE_CONFIG_AES_CAMERA,
|
|
credentials_hash=CREDENTIALS_HASH_AES,
|
|
modules=[Module.Camera],
|
|
)
|
|
|
|
with override_side_effect(mock_connect["connect"], lambda *_, **__: mock_device):
|
|
result = await hass.config_entries.flow.async_init(
|
|
DOMAIN,
|
|
context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY},
|
|
data={
|
|
CONF_HOST: IP_ADDRESS3,
|
|
CONF_MAC: MAC_ADDRESS3,
|
|
CONF_ALIAS: ALIAS,
|
|
CONF_DEVICE: mock_device,
|
|
},
|
|
)
|
|
await hass.async_block_till_done()
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "discovery_confirm"
|
|
assert not result["errors"]
|
|
|
|
with override_side_effect(mock_connect["connect"], lambda *_, **__: mock_device):
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
user_input={},
|
|
)
|
|
await hass.async_block_till_done()
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "camera_auth_confirm"
|
|
assert not result["errors"]
|
|
|
|
with patch(
|
|
"homeassistant.components.stream.async_check_stream_client_error",
|
|
return_value=None,
|
|
):
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
user_input={
|
|
CONF_LIVE_VIEW: True,
|
|
CONF_USERNAME: "camuser",
|
|
CONF_PASSWORD: "campass",
|
|
},
|
|
)
|
|
await hass.async_block_till_done()
|
|
|
|
assert result["type"] is FlowResultType.CREATE_ENTRY
|
|
assert result["title"] == DEFAULT_ENTRY_TITLE_CAMERA
|
|
assert result["data"] == CREATE_ENTRY_DATA_AES_CAMERA
|
|
assert result["context"]["unique_id"] == MAC_ADDRESS3
|
|
|
|
|
|
async def test_discovery_pick_device_camera(
|
|
hass: HomeAssistant, mock_discovery: AsyncMock, mock_connect: AsyncMock, mock_init
|
|
) -> None:
|
|
"""Test authenticated discovery for camera with stream."""
|
|
mock_device = _mocked_device(
|
|
alias=ALIAS_CAMERA,
|
|
ip_address=IP_ADDRESS3,
|
|
mac=MAC_ADDRESS3,
|
|
model=MODEL_CAMERA,
|
|
device_config=DEVICE_CONFIG_AES_CAMERA,
|
|
credentials_hash=CREDENTIALS_HASH_AES,
|
|
modules=[Module.Camera],
|
|
)
|
|
|
|
result = await hass.config_entries.flow.async_init(
|
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
|
)
|
|
await hass.async_block_till_done()
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "user"
|
|
assert not result["errors"]
|
|
|
|
with override_side_effect(
|
|
mock_discovery["discover"], lambda *_, **__: {IP_ADDRESS3: mock_device}
|
|
):
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
user_input={},
|
|
)
|
|
await hass.async_block_till_done()
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "pick_device"
|
|
assert not result["errors"]
|
|
|
|
with override_side_effect(mock_connect["connect"], lambda *_, **__: mock_device):
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
user_input={CONF_DEVICE: MAC_ADDRESS3},
|
|
)
|
|
await hass.async_block_till_done()
|
|
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "camera_auth_confirm"
|
|
assert not result["errors"]
|
|
|
|
with patch(
|
|
"homeassistant.components.stream.async_check_stream_client_error",
|
|
return_value=None,
|
|
):
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
user_input={
|
|
CONF_LIVE_VIEW: True,
|
|
CONF_USERNAME: "camuser",
|
|
CONF_PASSWORD: "campass",
|
|
},
|
|
)
|
|
await hass.async_block_till_done()
|
|
|
|
assert result["type"] is FlowResultType.CREATE_ENTRY
|
|
assert result["title"] == DEFAULT_ENTRY_TITLE_CAMERA
|
|
assert result["data"] == CREATE_ENTRY_DATA_AES_CAMERA
|
|
assert result["context"]["unique_id"] == MAC_ADDRESS3
|
|
|
|
|
|
async def test_discovery_auth(
|
|
hass: HomeAssistant, mock_discovery: AsyncMock, mock_connect: AsyncMock, mock_init
|
|
) -> None:
|
|
"""Test authenticated discovery."""
|
|
mock_device = _mocked_device(
|
|
alias=ALIAS,
|
|
ip_address=IP_ADDRESS,
|
|
mac=MAC_ADDRESS,
|
|
device_config=DEVICE_CONFIG_KLAP,
|
|
credentials_hash=CREDENTIALS_HASH_KLAP,
|
|
)
|
|
|
|
with override_side_effect(mock_connect["connect"], AuthenticationError):
|
|
result = await hass.config_entries.flow.async_init(
|
|
DOMAIN,
|
|
context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY},
|
|
data={
|
|
CONF_HOST: IP_ADDRESS,
|
|
CONF_MAC: MAC_ADDRESS,
|
|
CONF_ALIAS: ALIAS,
|
|
CONF_DEVICE: mock_device,
|
|
},
|
|
)
|
|
await hass.async_block_till_done()
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "discovery_auth_confirm"
|
|
assert not result["errors"]
|
|
|
|
with override_side_effect(mock_connect["connect"], lambda *_, **__: mock_device):
|
|
result2 = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
user_input={
|
|
CONF_USERNAME: "fake_username",
|
|
CONF_PASSWORD: "fake_password",
|
|
},
|
|
)
|
|
|
|
assert result2["type"] is FlowResultType.CREATE_ENTRY
|
|
assert result2["title"] == DEFAULT_ENTRY_TITLE
|
|
assert result2["data"] == CREATE_ENTRY_DATA_KLAP
|
|
assert result2["context"]["unique_id"] == MAC_ADDRESS
|
|
|
|
|
|
async def test_discovery_auth_camera(
|
|
hass: HomeAssistant, mock_discovery: AsyncMock, mock_connect: AsyncMock, mock_init
|
|
) -> None:
|
|
"""Test authenticated discovery for camera with stream."""
|
|
mock_device = _mocked_device(
|
|
alias=ALIAS_CAMERA,
|
|
ip_address=IP_ADDRESS3,
|
|
mac=MAC_ADDRESS3,
|
|
model=MODEL_CAMERA,
|
|
device_config=DEVICE_CONFIG_AES_CAMERA,
|
|
credentials_hash=CREDENTIALS_HASH_AES,
|
|
modules=[Module.Camera],
|
|
)
|
|
|
|
with override_side_effect(mock_connect["connect"], AuthenticationError):
|
|
result = await hass.config_entries.flow.async_init(
|
|
DOMAIN,
|
|
context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY},
|
|
data={
|
|
CONF_HOST: IP_ADDRESS3,
|
|
CONF_MAC: MAC_ADDRESS3,
|
|
CONF_ALIAS: ALIAS,
|
|
CONF_DEVICE: mock_device,
|
|
},
|
|
)
|
|
await hass.async_block_till_done()
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "discovery_auth_confirm"
|
|
assert not result["errors"]
|
|
|
|
with override_side_effect(mock_connect["connect"], lambda *_, **__: mock_device):
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
user_input={
|
|
CONF_USERNAME: "fake_username",
|
|
CONF_PASSWORD: "fake_password",
|
|
},
|
|
)
|
|
await hass.async_block_till_done()
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "camera_auth_confirm"
|
|
assert not result["errors"]
|
|
|
|
with patch(
|
|
"homeassistant.components.stream.async_check_stream_client_error",
|
|
return_value=None,
|
|
):
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
user_input={
|
|
CONF_LIVE_VIEW: True,
|
|
CONF_USERNAME: "camuser",
|
|
CONF_PASSWORD: "campass",
|
|
},
|
|
)
|
|
await hass.async_block_till_done()
|
|
|
|
assert result["type"] is FlowResultType.CREATE_ENTRY
|
|
assert result["title"] == DEFAULT_ENTRY_TITLE_CAMERA
|
|
assert result["data"] == CREATE_ENTRY_DATA_AES_CAMERA
|
|
assert result["context"]["unique_id"] == MAC_ADDRESS3
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("error_type", "errors_msg", "error_placement"),
|
|
[
|
|
(AuthenticationError("auth_error_details"), "invalid_auth", CONF_PASSWORD),
|
|
(KasaException("smart_device_error_details"), "cannot_connect", "base"),
|
|
],
|
|
ids=["invalid-auth", "unknown-error"],
|
|
)
|
|
async def test_discovery_auth_errors(
|
|
hass: HomeAssistant,
|
|
mock_connect: AsyncMock,
|
|
mock_init,
|
|
error_type,
|
|
errors_msg,
|
|
error_placement,
|
|
) -> None:
|
|
"""Test handling of discovery authentication errors.
|
|
|
|
Tests for errors received during credential
|
|
entry during discovery_auth_confirm.
|
|
"""
|
|
mock_device = mock_connect["mock_devices"][IP_ADDRESS]
|
|
|
|
with override_side_effect(mock_connect["connect"], AuthenticationError):
|
|
result = await hass.config_entries.flow.async_init(
|
|
DOMAIN,
|
|
context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY},
|
|
data={
|
|
CONF_HOST: IP_ADDRESS,
|
|
CONF_MAC: MAC_ADDRESS,
|
|
CONF_ALIAS: ALIAS,
|
|
CONF_DEVICE: mock_device,
|
|
},
|
|
)
|
|
await hass.async_block_till_done()
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "discovery_auth_confirm"
|
|
assert not result["errors"]
|
|
|
|
with override_side_effect(mock_connect["connect"], error_type):
|
|
result2 = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
user_input={
|
|
CONF_USERNAME: "fake_username",
|
|
CONF_PASSWORD: "fake_password",
|
|
},
|
|
)
|
|
|
|
assert result2["type"] is FlowResultType.FORM
|
|
assert result2["errors"] == {error_placement: errors_msg}
|
|
assert result2["description_placeholders"]["error"] == str(error_type)
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
result3 = await hass.config_entries.flow.async_configure(
|
|
result2["flow_id"],
|
|
{
|
|
CONF_USERNAME: "fake_username",
|
|
CONF_PASSWORD: "fake_password",
|
|
},
|
|
)
|
|
assert result3["type"] is FlowResultType.CREATE_ENTRY
|
|
assert result3["data"] == CREATE_ENTRY_DATA_KLAP
|
|
assert result3["context"]["unique_id"] == MAC_ADDRESS
|
|
|
|
|
|
async def test_discovery_new_credentials(
|
|
hass: HomeAssistant,
|
|
mock_connect: AsyncMock,
|
|
mock_init,
|
|
) -> None:
|
|
"""Test setting up discovery with new credentials."""
|
|
mock_device = mock_connect["mock_devices"][IP_ADDRESS]
|
|
|
|
with override_side_effect(mock_connect["connect"], AuthenticationError):
|
|
result = await hass.config_entries.flow.async_init(
|
|
DOMAIN,
|
|
context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY},
|
|
data={
|
|
CONF_HOST: IP_ADDRESS,
|
|
CONF_MAC: MAC_ADDRESS,
|
|
CONF_ALIAS: ALIAS,
|
|
CONF_DEVICE: mock_device,
|
|
},
|
|
)
|
|
await hass.async_block_till_done()
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "discovery_auth_confirm"
|
|
assert not result["errors"]
|
|
|
|
assert mock_connect["connect"].call_count == 1
|
|
|
|
with patch(
|
|
"homeassistant.components.tplink.config_flow.get_credentials",
|
|
return_value=Credentials("fake_user", "fake_pass"),
|
|
):
|
|
result2 = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
)
|
|
|
|
assert mock_connect["connect"].call_count == 2
|
|
assert result2["type"] is FlowResultType.FORM
|
|
assert result2["step_id"] == "discovery_confirm"
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
result3 = await hass.config_entries.flow.async_configure(
|
|
result2["flow_id"],
|
|
{},
|
|
)
|
|
assert result3["type"] is FlowResultType.CREATE_ENTRY
|
|
assert result3["data"] == CREATE_ENTRY_DATA_KLAP
|
|
assert result3["context"]["unique_id"] == MAC_ADDRESS
|
|
|
|
|
|
async def test_discovery_new_credentials_invalid(
|
|
hass: HomeAssistant,
|
|
mock_connect: AsyncMock,
|
|
mock_init,
|
|
) -> None:
|
|
"""Test setting up discovery with new invalid credentials."""
|
|
mock_device = mock_connect["mock_devices"][IP_ADDRESS]
|
|
|
|
with (
|
|
patch("homeassistant.components.tplink.Discover.discover", return_value={}),
|
|
patch(
|
|
"homeassistant.components.tplink.config_flow.get_credentials",
|
|
return_value=None,
|
|
),
|
|
override_side_effect(mock_connect["connect"], AuthenticationError),
|
|
):
|
|
result = await hass.config_entries.flow.async_init(
|
|
DOMAIN,
|
|
context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY},
|
|
data={
|
|
CONF_HOST: IP_ADDRESS,
|
|
CONF_MAC: MAC_ADDRESS,
|
|
CONF_ALIAS: ALIAS,
|
|
CONF_DEVICE: mock_device,
|
|
},
|
|
)
|
|
await hass.async_block_till_done()
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "discovery_auth_confirm"
|
|
assert not result["errors"]
|
|
|
|
assert mock_connect["connect"].call_count == 1
|
|
|
|
with (
|
|
patch(
|
|
"homeassistant.components.tplink.config_flow.get_credentials",
|
|
return_value=Credentials("fake_user", "fake_pass"),
|
|
),
|
|
override_side_effect(mock_connect["connect"], AuthenticationError),
|
|
):
|
|
result2 = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
)
|
|
|
|
assert mock_connect["connect"].call_count == 2
|
|
assert result2["type"] is FlowResultType.FORM
|
|
assert result2["step_id"] == "discovery_auth_confirm"
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
result3 = await hass.config_entries.flow.async_configure(
|
|
result2["flow_id"],
|
|
{
|
|
CONF_USERNAME: "fake_username",
|
|
CONF_PASSWORD: "fake_password",
|
|
},
|
|
)
|
|
assert result3["type"] is FlowResultType.CREATE_ENTRY
|
|
assert result3["data"] == CREATE_ENTRY_DATA_KLAP
|
|
assert result3["context"]["unique_id"] == MAC_ADDRESS
|
|
|
|
|
|
async def test_discovery_with_existing_device_present(hass: HomeAssistant) -> None:
|
|
"""Test setting up discovery."""
|
|
config_entry = MockConfigEntry(
|
|
domain=DOMAIN, data={CONF_HOST: IP_ADDRESS2}, unique_id="dd:dd:dd:dd:dd:dd"
|
|
)
|
|
config_entry.add_to_hass(hass)
|
|
|
|
with (
|
|
_patch_discovery(),
|
|
_patch_single_discovery(no_device=True),
|
|
_patch_connect(no_device=True),
|
|
):
|
|
await hass.config_entries.async_setup(config_entry.entry_id)
|
|
await hass.async_block_till_done()
|
|
|
|
result = await hass.config_entries.flow.async_init(
|
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
|
)
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "user"
|
|
assert not result["errors"]
|
|
|
|
with _patch_discovery(), _patch_single_discovery(), _patch_connect():
|
|
result2 = await hass.config_entries.flow.async_configure(result["flow_id"], {})
|
|
await hass.async_block_till_done()
|
|
|
|
assert result2["type"] is FlowResultType.FORM
|
|
assert result2["step_id"] == "pick_device"
|
|
assert not result2["errors"]
|
|
|
|
# Now abort and make sure we can start over
|
|
|
|
result = await hass.config_entries.flow.async_init(
|
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
|
)
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "user"
|
|
assert not result["errors"]
|
|
|
|
with _patch_discovery(), _patch_single_discovery(), _patch_connect():
|
|
result2 = await hass.config_entries.flow.async_configure(result["flow_id"], {})
|
|
await hass.async_block_till_done()
|
|
|
|
assert result2["type"] is FlowResultType.FORM
|
|
assert result2["step_id"] == "pick_device"
|
|
assert not result2["errors"]
|
|
|
|
with (
|
|
_patch_discovery(),
|
|
_patch_single_discovery(),
|
|
_patch_connect(),
|
|
patch(f"{MODULE}.async_setup_entry", return_value=True) as mock_setup_entry,
|
|
):
|
|
result3 = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"], {CONF_DEVICE: MAC_ADDRESS}
|
|
)
|
|
assert result3["type"] is FlowResultType.CREATE_ENTRY
|
|
assert result3["title"] == DEFAULT_ENTRY_TITLE
|
|
assert result3["data"] == CREATE_ENTRY_DATA_LEGACY
|
|
assert result3["context"]["unique_id"] == MAC_ADDRESS
|
|
await hass.async_block_till_done()
|
|
|
|
mock_setup_entry.assert_called_once()
|
|
|
|
# ignore configured devices
|
|
result = await hass.config_entries.flow.async_init(
|
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
|
)
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "user"
|
|
assert not result["errors"]
|
|
|
|
with _patch_discovery(), _patch_single_discovery(), _patch_connect():
|
|
result2 = await hass.config_entries.flow.async_configure(result["flow_id"], {})
|
|
await hass.async_block_till_done()
|
|
|
|
assert result2["type"] is FlowResultType.ABORT
|
|
assert result2["reason"] == "no_devices_found"
|
|
|
|
|
|
async def test_discovery_no_device(hass: HomeAssistant) -> None:
|
|
"""Test discovery without device."""
|
|
result = await hass.config_entries.flow.async_init(
|
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
|
)
|
|
|
|
with _patch_discovery(no_device=True), _patch_single_discovery(), _patch_connect():
|
|
result2 = await hass.config_entries.flow.async_configure(result["flow_id"], {})
|
|
await hass.async_block_till_done()
|
|
|
|
assert result2["type"] is FlowResultType.ABORT
|
|
assert result2["reason"] == "no_devices_found"
|
|
|
|
|
|
async def test_manual(hass: HomeAssistant) -> None:
|
|
"""Test manually setup."""
|
|
result = await hass.config_entries.flow.async_init(
|
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
|
)
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "user"
|
|
assert not result["errors"]
|
|
|
|
# Cannot connect (timeout)
|
|
with (
|
|
_patch_discovery(no_device=True),
|
|
_patch_single_discovery(no_device=True),
|
|
_patch_connect(no_device=True),
|
|
):
|
|
result2 = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"], {CONF_HOST: IP_ADDRESS}
|
|
)
|
|
await hass.async_block_till_done()
|
|
|
|
assert result2["type"] is FlowResultType.FORM
|
|
assert result2["step_id"] == "user"
|
|
assert result2["errors"] == {"base": "cannot_connect"}
|
|
|
|
# Success
|
|
with (
|
|
_patch_discovery(),
|
|
_patch_single_discovery(),
|
|
_patch_connect(),
|
|
patch(f"{MODULE}.async_setup", return_value=True),
|
|
patch(f"{MODULE}.async_setup_entry", return_value=True),
|
|
):
|
|
result4 = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"], {CONF_HOST: IP_ADDRESS}
|
|
)
|
|
await hass.async_block_till_done()
|
|
assert result4["type"] is FlowResultType.CREATE_ENTRY
|
|
assert result4["title"] == DEFAULT_ENTRY_TITLE
|
|
assert result4["data"] == CREATE_ENTRY_DATA_LEGACY
|
|
assert result4["context"]["unique_id"] == MAC_ADDRESS
|
|
|
|
# Duplicate
|
|
result = await hass.config_entries.flow.async_init(
|
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
|
)
|
|
with (
|
|
_patch_discovery(no_device=True),
|
|
_patch_single_discovery(no_device=True),
|
|
_patch_connect(no_device=True),
|
|
):
|
|
result2 = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"], {CONF_HOST: IP_ADDRESS}
|
|
)
|
|
await hass.async_block_till_done()
|
|
|
|
assert result2["type"] is FlowResultType.ABORT
|
|
assert result2["reason"] == "already_configured"
|
|
|
|
|
|
async def test_manual_camera(
|
|
hass: HomeAssistant,
|
|
mock_discovery: AsyncMock,
|
|
mock_connect: AsyncMock,
|
|
) -> None:
|
|
"""Test manual camera."""
|
|
result = await hass.config_entries.flow.async_init(
|
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
|
)
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "user"
|
|
assert not result["errors"]
|
|
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"], {CONF_HOST: IP_ADDRESS3}
|
|
)
|
|
await hass.async_block_till_done()
|
|
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "camera_auth_confirm"
|
|
|
|
# Test no username or pass
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
user_input={
|
|
CONF_LIVE_VIEW: True,
|
|
CONF_USERNAME: "camuser",
|
|
},
|
|
)
|
|
await hass.async_block_till_done()
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "camera_auth_confirm"
|
|
assert result["errors"] == {"base": "camera_creds"}
|
|
|
|
# Test unknown error
|
|
with (
|
|
patch(
|
|
"homeassistant.components.stream.async_check_stream_client_error",
|
|
side_effect=stream.StreamOpenClientError(
|
|
"Stream was not found", error_code=stream.StreamClientError.NotFound
|
|
),
|
|
),
|
|
):
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
user_input={
|
|
CONF_LIVE_VIEW: True,
|
|
CONF_USERNAME: "camuser",
|
|
CONF_PASSWORD: "campass",
|
|
},
|
|
)
|
|
await hass.async_block_till_done()
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "camera_auth_confirm"
|
|
assert result["errors"] == {"base": "cannot_connect_camera"}
|
|
assert "error" in result["description_placeholders"]
|
|
|
|
# Test unknown error
|
|
with (
|
|
patch(
|
|
"homeassistant.components.stream.async_check_stream_client_error",
|
|
side_effect=stream.StreamOpenClientError(
|
|
"Request is unauthorized",
|
|
error_code=stream.StreamClientError.Unauthorized,
|
|
),
|
|
),
|
|
):
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
user_input={
|
|
CONF_LIVE_VIEW: True,
|
|
CONF_USERNAME: "camuser",
|
|
CONF_PASSWORD: "campass",
|
|
},
|
|
)
|
|
await hass.async_block_till_done()
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "camera_auth_confirm"
|
|
assert result["errors"] == {"base": "invalid_camera_auth"}
|
|
|
|
with patch(
|
|
"homeassistant.components.stream.async_check_stream_client_error",
|
|
return_value=None,
|
|
):
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
user_input={
|
|
CONF_LIVE_VIEW: True,
|
|
CONF_USERNAME: "camuser",
|
|
CONF_PASSWORD: "campass",
|
|
},
|
|
)
|
|
await hass.async_block_till_done()
|
|
|
|
assert result["type"] is FlowResultType.CREATE_ENTRY
|
|
assert result["data"][CONF_CAMERA_CREDENTIALS] == {
|
|
CONF_USERNAME: "camuser",
|
|
CONF_PASSWORD: "campass",
|
|
}
|
|
assert result["data"][CONF_LIVE_VIEW] is True
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"exception",
|
|
[
|
|
pytest.param(
|
|
stream.StreamOpenClientError(
|
|
"Stream was not found", error_code=stream.StreamClientError.NotFound
|
|
),
|
|
id="open_client_error",
|
|
),
|
|
pytest.param(Exception(), id="other_error"),
|
|
],
|
|
)
|
|
async def test_manual_camera_no_hls(
|
|
hass: HomeAssistant,
|
|
mock_discovery: AsyncMock,
|
|
mock_connect: AsyncMock,
|
|
exception: Exception,
|
|
) -> None:
|
|
"""Test manual camera when hls stream fails but mpeg stream works."""
|
|
result = await hass.config_entries.flow.async_init(
|
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
|
)
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "user"
|
|
assert not result["errors"]
|
|
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"], {CONF_HOST: IP_ADDRESS3}
|
|
)
|
|
await hass.async_block_till_done()
|
|
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "camera_auth_confirm"
|
|
|
|
# Test stream error
|
|
with (
|
|
patch(
|
|
"homeassistant.components.stream.async_check_stream_client_error",
|
|
side_effect=exception,
|
|
),
|
|
patch("homeassistant.components.ffmpeg.async_get_image", return_value=None),
|
|
):
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
user_input={
|
|
CONF_LIVE_VIEW: True,
|
|
CONF_USERNAME: "camuser",
|
|
CONF_PASSWORD: "campass",
|
|
},
|
|
)
|
|
await hass.async_block_till_done()
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "camera_auth_confirm"
|
|
assert result["errors"] == {"base": "cannot_connect_camera"}
|
|
assert "error" in result["description_placeholders"]
|
|
|
|
# async_get_image will succeed
|
|
with (
|
|
patch(
|
|
"homeassistant.components.stream.async_check_stream_client_error",
|
|
side_effect=exception,
|
|
),
|
|
patch(
|
|
"homeassistant.components.ffmpeg.async_get_image",
|
|
return_value=SMALLEST_VALID_JPEG_BYTES,
|
|
),
|
|
):
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
user_input={
|
|
CONF_LIVE_VIEW: True,
|
|
CONF_USERNAME: "camuser",
|
|
CONF_PASSWORD: "campass",
|
|
},
|
|
)
|
|
await hass.async_block_till_done()
|
|
|
|
assert result["type"] is FlowResultType.CREATE_ENTRY
|
|
assert result["data"][CONF_CAMERA_CREDENTIALS] == {
|
|
CONF_USERNAME: "camuser",
|
|
CONF_PASSWORD: "campass",
|
|
}
|
|
assert result["data"][CONF_LIVE_VIEW] is True
|
|
|
|
|
|
async def test_manual_camera_no_live_view(
|
|
hass: HomeAssistant,
|
|
mock_discovery: AsyncMock,
|
|
mock_connect: AsyncMock,
|
|
) -> None:
|
|
"""Test manual camera."""
|
|
result = await hass.config_entries.flow.async_init(
|
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
|
)
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "user"
|
|
assert not result["errors"]
|
|
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"], {CONF_HOST: IP_ADDRESS3}
|
|
)
|
|
await hass.async_block_till_done()
|
|
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "camera_auth_confirm"
|
|
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
user_input={
|
|
CONF_LIVE_VIEW: False,
|
|
CONF_USERNAME: "camuser",
|
|
CONF_PASSWORD: "campass",
|
|
},
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.CREATE_ENTRY
|
|
assert CONF_CAMERA_CREDENTIALS not in result["data"]
|
|
assert result["data"][CONF_LIVE_VIEW] is False
|
|
|
|
|
|
async def test_manual_no_capabilities(hass: HomeAssistant) -> None:
|
|
"""Test manually setup without successful get_capabilities."""
|
|
result = await hass.config_entries.flow.async_init(
|
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
|
)
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "user"
|
|
assert not result["errors"]
|
|
|
|
with (
|
|
_patch_discovery(no_device=True),
|
|
_patch_single_discovery(),
|
|
_patch_connect(),
|
|
patch(f"{MODULE}.async_setup", return_value=True),
|
|
patch(f"{MODULE}.async_setup_entry", return_value=True),
|
|
):
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"], {CONF_HOST: IP_ADDRESS}
|
|
)
|
|
await hass.async_block_till_done()
|
|
|
|
assert result["type"] is FlowResultType.CREATE_ENTRY
|
|
assert result["data"] == CREATE_ENTRY_DATA_LEGACY
|
|
assert result["context"]["unique_id"] == MAC_ADDRESS
|
|
|
|
|
|
async def test_manual_auth(
|
|
hass: HomeAssistant,
|
|
mock_discovery: AsyncMock,
|
|
mock_connect: AsyncMock,
|
|
mock_init,
|
|
) -> None:
|
|
"""Test manually setup."""
|
|
result = await hass.config_entries.flow.async_init(
|
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
|
)
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "user"
|
|
assert not result["errors"]
|
|
|
|
mock_discovery["mock_devices"][IP_ADDRESS].update.side_effect = AuthenticationError
|
|
|
|
result2 = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"], user_input={CONF_HOST: IP_ADDRESS}
|
|
)
|
|
await hass.async_block_till_done()
|
|
|
|
assert result2["type"] is FlowResultType.FORM
|
|
assert result2["step_id"] == "user_auth_confirm"
|
|
assert not result2["errors"]
|
|
|
|
mock_discovery["mock_devices"][IP_ADDRESS].update.reset_mock(side_effect=True)
|
|
|
|
result3 = await hass.config_entries.flow.async_configure(
|
|
result2["flow_id"],
|
|
user_input={
|
|
CONF_USERNAME: "fake_username",
|
|
CONF_PASSWORD: "fake_password",
|
|
},
|
|
)
|
|
await hass.async_block_till_done()
|
|
assert result3["type"] is FlowResultType.CREATE_ENTRY
|
|
assert result3["title"] == DEFAULT_ENTRY_TITLE
|
|
assert result3["data"] == CREATE_ENTRY_DATA_KLAP
|
|
assert result3["context"]["unique_id"] == MAC_ADDRESS
|
|
|
|
|
|
async def test_manual_auth_camera(
|
|
hass: HomeAssistant,
|
|
mock_discovery: AsyncMock,
|
|
mock_connect: AsyncMock,
|
|
) -> None:
|
|
"""Test manual camera."""
|
|
result = await hass.config_entries.flow.async_init(
|
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
|
)
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "user"
|
|
assert not result["errors"]
|
|
|
|
with override_side_effect(
|
|
mock_discovery["mock_devices"][IP_ADDRESS3].update, AuthenticationError
|
|
):
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"], {CONF_HOST: IP_ADDRESS3}
|
|
)
|
|
await hass.async_block_till_done()
|
|
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "user_auth_confirm"
|
|
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
user_input={
|
|
CONF_USERNAME: "foobar",
|
|
CONF_PASSWORD: "foobar",
|
|
},
|
|
)
|
|
await hass.async_block_till_done()
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "camera_auth_confirm"
|
|
|
|
with patch(
|
|
"homeassistant.components.stream.async_check_stream_client_error",
|
|
return_value=None,
|
|
):
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
user_input={
|
|
CONF_LIVE_VIEW: True,
|
|
CONF_USERNAME: "camuser",
|
|
CONF_PASSWORD: "campass",
|
|
},
|
|
)
|
|
await hass.async_block_till_done()
|
|
|
|
assert result["type"] is FlowResultType.CREATE_ENTRY
|
|
assert result["data"][CONF_CAMERA_CREDENTIALS] == {
|
|
CONF_USERNAME: "camuser",
|
|
CONF_PASSWORD: "campass",
|
|
}
|
|
assert result["data"][CONF_LIVE_VIEW] is True
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("error_type", "errors_msg", "error_placement"),
|
|
[
|
|
(AuthenticationError("auth_error_details"), "invalid_auth", CONF_PASSWORD),
|
|
(KasaException("smart_device_error_details"), "cannot_connect", "base"),
|
|
],
|
|
ids=["invalid-auth", "unknown-error"],
|
|
)
|
|
async def test_manual_auth_errors(
|
|
hass: HomeAssistant,
|
|
mock_discovery: AsyncMock,
|
|
mock_connect: AsyncMock,
|
|
mock_init,
|
|
error_type,
|
|
errors_msg,
|
|
error_placement,
|
|
) -> None:
|
|
"""Test manually setup auth errors."""
|
|
result = await hass.config_entries.flow.async_init(
|
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
|
)
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "user"
|
|
assert not result["errors"]
|
|
|
|
mock_discovery["mock_devices"][IP_ADDRESS].update.side_effect = AuthenticationError
|
|
|
|
with override_side_effect(mock_connect["connect"], error_type):
|
|
result2 = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"], user_input={CONF_HOST: IP_ADDRESS}
|
|
)
|
|
assert result2["type"] is FlowResultType.FORM
|
|
assert result2["step_id"] == "user_auth_confirm"
|
|
assert not result2["errors"]
|
|
|
|
await hass.async_block_till_done()
|
|
with override_side_effect(mock_connect["connect"], error_type):
|
|
result3 = await hass.config_entries.flow.async_configure(
|
|
result2["flow_id"],
|
|
user_input={
|
|
CONF_USERNAME: "fake_username",
|
|
CONF_PASSWORD: "fake_password",
|
|
},
|
|
)
|
|
await hass.async_block_till_done()
|
|
assert result3["type"] is FlowResultType.FORM
|
|
assert result3["step_id"] == "user_auth_confirm"
|
|
assert result3["errors"] == {error_placement: errors_msg}
|
|
assert result3["description_placeholders"]["error"] == str(error_type)
|
|
|
|
result4 = await hass.config_entries.flow.async_configure(
|
|
result3["flow_id"],
|
|
{
|
|
CONF_USERNAME: "fake_username",
|
|
CONF_PASSWORD: "fake_password",
|
|
},
|
|
)
|
|
assert result4["type"] is FlowResultType.CREATE_ENTRY
|
|
assert result4["data"] == CREATE_ENTRY_DATA_KLAP
|
|
assert result4["context"]["unique_id"] == MAC_ADDRESS
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("host_str", "host", "port"),
|
|
[
|
|
(f"{IP_ADDRESS}:1234", IP_ADDRESS, 1234),
|
|
("[2001:db8:0::1]:4321", "2001:db8:0::1", 4321),
|
|
],
|
|
)
|
|
async def test_manual_port_override(
|
|
hass: HomeAssistant,
|
|
mock_connect: AsyncMock,
|
|
mock_discovery: AsyncMock,
|
|
host_str,
|
|
host,
|
|
port,
|
|
) -> None:
|
|
"""Test manually setup."""
|
|
config = DeviceConfig(
|
|
host,
|
|
credentials=None,
|
|
port_override=port,
|
|
uses_http=True,
|
|
connection_type=CONN_PARAMS_KLAP,
|
|
)
|
|
mock_device = _mocked_device(
|
|
alias=ALIAS,
|
|
ip_address=host,
|
|
mac=MAC_ADDRESS,
|
|
device_config=config,
|
|
credentials_hash=CREDENTIALS_HASH_KLAP,
|
|
)
|
|
|
|
with override_side_effect(
|
|
mock_discovery["try_connect_all"], lambda *_, **__: mock_device
|
|
):
|
|
result = await hass.config_entries.flow.async_init(
|
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
|
)
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "user"
|
|
assert not result["errors"]
|
|
|
|
# side_effects to cause auth confirm as the port override usually only
|
|
# works with direct connections.
|
|
mock_discovery["discover_single"].side_effect = TimeoutError
|
|
mock_connect["connect"].side_effect = AuthenticationError
|
|
|
|
with override_side_effect(
|
|
mock_discovery["try_connect_all"], lambda *_, **__: mock_device
|
|
):
|
|
result2 = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"], {CONF_HOST: host_str}
|
|
)
|
|
await hass.async_block_till_done()
|
|
|
|
assert result2["type"] is FlowResultType.FORM
|
|
assert result2["step_id"] == "user_auth_confirm"
|
|
assert not result2["errors"]
|
|
|
|
creds = Credentials("fake_username", "fake_password")
|
|
with override_side_effect(
|
|
mock_discovery["try_connect_all"], lambda *_, **__: mock_device
|
|
):
|
|
result3 = await hass.config_entries.flow.async_configure(
|
|
result2["flow_id"],
|
|
user_input={
|
|
CONF_USERNAME: "fake_username",
|
|
CONF_PASSWORD: "fake_password",
|
|
},
|
|
)
|
|
await hass.async_block_till_done()
|
|
mock_discovery["try_connect_all"].assert_called_once_with(
|
|
host, credentials=creds, port=port, http_client=ANY
|
|
)
|
|
assert result3["type"] is FlowResultType.CREATE_ENTRY
|
|
assert result3["title"] == DEFAULT_ENTRY_TITLE
|
|
assert result3["data"] == {
|
|
**CREATE_ENTRY_DATA_KLAP,
|
|
CONF_PORT: port,
|
|
CONF_HOST: host,
|
|
}
|
|
assert result3["context"]["unique_id"] == MAC_ADDRESS
|
|
|
|
|
|
async def test_manual_port_override_invalid(
|
|
hass: HomeAssistant, mock_connect: AsyncMock, mock_discovery: AsyncMock
|
|
) -> None:
|
|
"""Test manually setup."""
|
|
result = await hass.config_entries.flow.async_init(
|
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
|
)
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "user"
|
|
assert not result["errors"]
|
|
|
|
result2 = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"], {CONF_HOST: f"{IP_ADDRESS}:foo"}
|
|
)
|
|
await hass.async_block_till_done()
|
|
|
|
mock_discovery["discover_single"].assert_called_once_with(
|
|
IP_ADDRESS, credentials=None, port=None
|
|
)
|
|
|
|
assert result2["type"] is FlowResultType.CREATE_ENTRY
|
|
assert result2["title"] == DEFAULT_ENTRY_TITLE
|
|
assert result2["data"] == CREATE_ENTRY_DATA_KLAP
|
|
assert result2["context"]["unique_id"] == MAC_ADDRESS
|
|
|
|
|
|
async def test_discovered_by_discovery_and_dhcp(hass: HomeAssistant) -> None:
|
|
"""Test we get the form with discovery and abort for dhcp source when we get both."""
|
|
|
|
with _patch_discovery(), _patch_single_discovery(), _patch_connect():
|
|
result = await hass.config_entries.flow.async_init(
|
|
DOMAIN,
|
|
context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY},
|
|
data={
|
|
CONF_HOST: IP_ADDRESS,
|
|
CONF_MAC: MAC_ADDRESS,
|
|
CONF_ALIAS: ALIAS,
|
|
CONF_DEVICE: _mocked_device(device_config=DEVICE_CONFIG_LEGACY),
|
|
},
|
|
)
|
|
await hass.async_block_till_done()
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["errors"] is None
|
|
|
|
real_is_matching = TPLinkConfigFlow.is_matching
|
|
return_values = []
|
|
|
|
def is_matching(self, other_flow) -> bool:
|
|
return_values.append(real_is_matching(self, other_flow))
|
|
return return_values[-1]
|
|
|
|
with (
|
|
_patch_discovery(),
|
|
_patch_single_discovery(),
|
|
_patch_connect(),
|
|
patch.object(TPLinkConfigFlow, "is_matching", wraps=is_matching, autospec=True),
|
|
):
|
|
result2 = await hass.config_entries.flow.async_init(
|
|
DOMAIN,
|
|
context={"source": config_entries.SOURCE_DHCP},
|
|
data=dhcp.DhcpServiceInfo(
|
|
ip=IP_ADDRESS, macaddress=DHCP_FORMATTED_MAC_ADDRESS, hostname=ALIAS
|
|
),
|
|
)
|
|
await hass.async_block_till_done()
|
|
assert result2["type"] is FlowResultType.ABORT
|
|
assert result2["reason"] == "already_in_progress"
|
|
# Ensure the is_matching method returned True
|
|
assert return_values == [True]
|
|
|
|
with _patch_discovery(), _patch_single_discovery(), _patch_connect():
|
|
result3 = await hass.config_entries.flow.async_init(
|
|
DOMAIN,
|
|
context={"source": config_entries.SOURCE_DHCP},
|
|
data=dhcp.DhcpServiceInfo(
|
|
ip=IP_ADDRESS, macaddress="000000000000", hostname="mock_hostname"
|
|
),
|
|
)
|
|
await hass.async_block_till_done()
|
|
assert result3["type"] is FlowResultType.ABORT
|
|
assert result3["reason"] == "already_in_progress"
|
|
|
|
with (
|
|
_patch_discovery(no_device=True),
|
|
_patch_single_discovery(no_device=True),
|
|
_patch_connect(no_device=True),
|
|
):
|
|
result3 = await hass.config_entries.flow.async_init(
|
|
DOMAIN,
|
|
context={"source": config_entries.SOURCE_DHCP},
|
|
data=dhcp.DhcpServiceInfo(
|
|
ip="1.2.3.5", macaddress="000000000001", hostname="mock_hostname"
|
|
),
|
|
)
|
|
await hass.async_block_till_done()
|
|
assert result3["type"] is FlowResultType.ABORT
|
|
assert result3["reason"] == "cannot_connect"
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("source", "data"),
|
|
[
|
|
(
|
|
config_entries.SOURCE_DHCP,
|
|
dhcp.DhcpServiceInfo(
|
|
ip=IP_ADDRESS, macaddress=DHCP_FORMATTED_MAC_ADDRESS, hostname=ALIAS
|
|
),
|
|
),
|
|
(
|
|
config_entries.SOURCE_INTEGRATION_DISCOVERY,
|
|
{
|
|
CONF_HOST: IP_ADDRESS,
|
|
CONF_MAC: MAC_ADDRESS,
|
|
CONF_ALIAS: ALIAS,
|
|
CONF_DEVICE: _mocked_device(device_config=DEVICE_CONFIG_LEGACY),
|
|
},
|
|
),
|
|
],
|
|
)
|
|
async def test_discovered_by_dhcp_or_discovery(
|
|
hass: HomeAssistant, source, data
|
|
) -> None:
|
|
"""Test we can setup when discovered from dhcp or discovery."""
|
|
|
|
with _patch_discovery(), _patch_single_discovery(), _patch_connect():
|
|
result = await hass.config_entries.flow.async_init(
|
|
DOMAIN, context={"source": source}, data=data
|
|
)
|
|
await hass.async_block_till_done()
|
|
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["errors"] is None
|
|
|
|
with (
|
|
_patch_discovery(),
|
|
_patch_single_discovery(),
|
|
_patch_connect(),
|
|
patch(f"{MODULE}.async_setup", return_value=True) as mock_async_setup,
|
|
patch(
|
|
f"{MODULE}.async_setup_entry", return_value=True
|
|
) as mock_async_setup_entry,
|
|
):
|
|
result2 = await hass.config_entries.flow.async_configure(result["flow_id"], {})
|
|
await hass.async_block_till_done()
|
|
|
|
assert result2["type"] is FlowResultType.CREATE_ENTRY
|
|
assert result2["data"] == CREATE_ENTRY_DATA_LEGACY
|
|
assert result2["context"]["unique_id"] == MAC_ADDRESS
|
|
|
|
assert mock_async_setup.called
|
|
assert mock_async_setup_entry.called
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("source", "data"),
|
|
[
|
|
(
|
|
config_entries.SOURCE_DHCP,
|
|
dhcp.DhcpServiceInfo(
|
|
ip=IP_ADDRESS, macaddress=DHCP_FORMATTED_MAC_ADDRESS, hostname=ALIAS
|
|
),
|
|
),
|
|
(
|
|
config_entries.SOURCE_INTEGRATION_DISCOVERY,
|
|
{
|
|
CONF_HOST: IP_ADDRESS,
|
|
CONF_MAC: MAC_ADDRESS,
|
|
CONF_ALIAS: ALIAS,
|
|
CONF_DEVICE: _mocked_device(device_config=DEVICE_CONFIG_LEGACY),
|
|
},
|
|
),
|
|
],
|
|
)
|
|
async def test_discovered_by_dhcp_or_discovery_failed_to_get_device(
|
|
hass: HomeAssistant, source, data
|
|
) -> None:
|
|
"""Test we abort if we cannot get the unique id when discovered from dhcp."""
|
|
|
|
with (
|
|
_patch_discovery(no_device=True),
|
|
_patch_single_discovery(no_device=True),
|
|
_patch_connect(no_device=True),
|
|
):
|
|
result = await hass.config_entries.flow.async_init(
|
|
DOMAIN, context={"source": source}, data=data
|
|
)
|
|
await hass.async_block_till_done()
|
|
assert result["type"] is FlowResultType.ABORT
|
|
assert result["reason"] == "cannot_connect"
|
|
|
|
|
|
async def test_integration_discovery_with_ip_change(
|
|
hass: HomeAssistant,
|
|
mock_config_entry: MockConfigEntry,
|
|
mock_discovery: AsyncMock,
|
|
mock_connect: AsyncMock,
|
|
) -> None:
|
|
"""Test reauth flow."""
|
|
mock_config_entry.add_to_hass(hass)
|
|
with (
|
|
patch("homeassistant.components.tplink.Discover.discover", return_value={}),
|
|
override_side_effect(mock_connect["connect"], KasaException()),
|
|
):
|
|
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
|
await hass.async_block_till_done()
|
|
|
|
assert mock_config_entry.state is ConfigEntryState.SETUP_RETRY
|
|
|
|
flows = hass.config_entries.flow.async_progress()
|
|
assert len(flows) == 0
|
|
assert (
|
|
mock_config_entry.data[CONF_CONNECTION_PARAMETERS]
|
|
== CONN_PARAMS_LEGACY.to_dict()
|
|
)
|
|
assert mock_config_entry.data[CONF_HOST] == IP_ADDRESS
|
|
|
|
mocked_device = _mocked_device(device_config=DEVICE_CONFIG_KLAP)
|
|
with override_side_effect(mock_connect["connect"], lambda *_, **__: mocked_device):
|
|
discovery_result = await hass.config_entries.flow.async_init(
|
|
DOMAIN,
|
|
context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY},
|
|
data={
|
|
CONF_HOST: IP_ADDRESS2,
|
|
CONF_MAC: MAC_ADDRESS,
|
|
CONF_ALIAS: ALIAS,
|
|
CONF_DEVICE: mocked_device,
|
|
},
|
|
)
|
|
await hass.async_block_till_done()
|
|
assert discovery_result["type"] is FlowResultType.ABORT
|
|
assert discovery_result["reason"] == "already_configured"
|
|
assert (
|
|
mock_config_entry.data[CONF_CONNECTION_PARAMETERS] == CONN_PARAMS_KLAP.to_dict()
|
|
)
|
|
assert mock_config_entry.data[CONF_HOST] == IP_ADDRESS2
|
|
|
|
config = DeviceConfig.from_dict(DEVICE_CONFIG_DICT_KLAP)
|
|
|
|
# Do a reload here and check that the
|
|
# new config is picked up in setup_entry
|
|
mock_connect["connect"].reset_mock(side_effect=True)
|
|
bulb = _mocked_device(
|
|
device_config=config,
|
|
mac=mock_config_entry.unique_id,
|
|
)
|
|
|
|
with (
|
|
patch(
|
|
"homeassistant.components.tplink.async_create_clientsession",
|
|
return_value="Foo",
|
|
),
|
|
override_side_effect(mock_connect["connect"], lambda *_, **__: bulb),
|
|
):
|
|
await hass.config_entries.async_reload(mock_config_entry.entry_id)
|
|
await hass.async_block_till_done()
|
|
assert mock_config_entry.state is ConfigEntryState.LOADED
|
|
# Check that init set the new host correctly before calling connect
|
|
assert config.host == IP_ADDRESS
|
|
config.host = IP_ADDRESS2
|
|
config.uses_http = False # Not passed in to new config class
|
|
config.http_client = "Foo"
|
|
mock_connect["connect"].assert_awaited_once_with(config=config)
|
|
|
|
|
|
async def test_integration_discovery_with_connection_change(
|
|
hass: HomeAssistant,
|
|
mock_config_entry: MockConfigEntry,
|
|
mock_discovery: AsyncMock,
|
|
mock_connect: AsyncMock,
|
|
) -> None:
|
|
"""Test that config entry is updated with new device config.
|
|
|
|
And that connection_hash is removed as it will be invalid.
|
|
"""
|
|
mock_config_entry = MockConfigEntry(
|
|
title="TPLink",
|
|
domain=DOMAIN,
|
|
data=CREATE_ENTRY_DATA_AES,
|
|
unique_id=MAC_ADDRESS2,
|
|
)
|
|
mock_config_entry.add_to_hass(hass)
|
|
with (
|
|
patch("homeassistant.components.tplink.Discover.discover", return_value={}),
|
|
override_side_effect(mock_connect["connect"], KasaException()),
|
|
):
|
|
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
|
await hass.async_block_till_done(wait_background_tasks=True)
|
|
|
|
assert mock_config_entry.state is ConfigEntryState.SETUP_RETRY
|
|
assert (
|
|
len(
|
|
hass.config_entries.flow.async_progress_by_handler(
|
|
DOMAIN, match_context={"source": SOURCE_REAUTH}
|
|
)
|
|
)
|
|
== 0
|
|
)
|
|
assert mock_config_entry.data[CONF_HOST] == IP_ADDRESS2
|
|
assert (
|
|
mock_config_entry.data[CONF_CONNECTION_PARAMETERS] == CONN_PARAMS_AES.to_dict()
|
|
)
|
|
assert mock_config_entry.data[CONF_CREDENTIALS_HASH] == CREDENTIALS_HASH_AES
|
|
|
|
mock_connect["connect"].reset_mock()
|
|
NEW_DEVICE_CONFIG = {
|
|
**DEVICE_CONFIG_DICT_KLAP,
|
|
"connection_type": CONN_PARAMS_KLAP.to_dict(),
|
|
CONF_HOST: IP_ADDRESS2,
|
|
}
|
|
config = DeviceConfig.from_dict(NEW_DEVICE_CONFIG)
|
|
# Reset the connect mock so when the config flow reloads the entry it succeeds
|
|
|
|
bulb = _mocked_device(
|
|
device_config=config,
|
|
mac=mock_config_entry.unique_id,
|
|
)
|
|
|
|
with (
|
|
patch(
|
|
"homeassistant.components.tplink.async_create_clientsession",
|
|
return_value="Foo",
|
|
),
|
|
override_side_effect(mock_connect["connect"], lambda *_, **__: bulb),
|
|
):
|
|
discovery_result = await hass.config_entries.flow.async_init(
|
|
DOMAIN,
|
|
context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY},
|
|
data={
|
|
CONF_HOST: IP_ADDRESS2,
|
|
CONF_MAC: MAC_ADDRESS2,
|
|
CONF_ALIAS: ALIAS,
|
|
CONF_DEVICE: bulb,
|
|
},
|
|
)
|
|
await hass.async_block_till_done(wait_background_tasks=True)
|
|
assert discovery_result["type"] is FlowResultType.ABORT
|
|
assert discovery_result["reason"] == "already_configured"
|
|
assert (
|
|
mock_config_entry.data[CONF_CONNECTION_PARAMETERS] == CONN_PARAMS_KLAP.to_dict()
|
|
)
|
|
assert mock_config_entry.data[CONF_HOST] == IP_ADDRESS2
|
|
assert CREDENTIALS_HASH_AES not in mock_config_entry.data
|
|
|
|
assert mock_config_entry.state is ConfigEntryState.LOADED
|
|
|
|
config.host = IP_ADDRESS2
|
|
config.uses_http = False # Not passed in to new config class
|
|
config.http_client = "Foo"
|
|
config.aes_keys = AES_KEYS
|
|
mock_connect["connect"].assert_awaited_once_with(config=config)
|
|
|
|
|
|
async def test_dhcp_discovery_with_ip_change(
|
|
hass: HomeAssistant,
|
|
mock_config_entry: MockConfigEntry,
|
|
mock_discovery: AsyncMock,
|
|
mock_connect: AsyncMock,
|
|
) -> None:
|
|
"""Test dhcp discovery with an IP change."""
|
|
mock_config_entry.add_to_hass(hass)
|
|
with (
|
|
patch("homeassistant.components.tplink.Discover.discover", return_value={}),
|
|
override_side_effect(mock_connect["connect"], KasaException()),
|
|
):
|
|
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
|
await hass.async_block_till_done()
|
|
assert mock_config_entry.state is ConfigEntryState.SETUP_RETRY
|
|
|
|
flows = hass.config_entries.flow.async_progress()
|
|
assert len(flows) == 0
|
|
assert mock_config_entry.data[CONF_HOST] == IP_ADDRESS
|
|
|
|
discovery_result = await hass.config_entries.flow.async_init(
|
|
DOMAIN,
|
|
context={"source": config_entries.SOURCE_DHCP},
|
|
data=dhcp.DhcpServiceInfo(
|
|
ip=IP_ADDRESS2, macaddress=DHCP_FORMATTED_MAC_ADDRESS, hostname=ALIAS
|
|
),
|
|
)
|
|
assert discovery_result["type"] is FlowResultType.ABORT
|
|
assert discovery_result["reason"] == "already_configured"
|
|
assert mock_config_entry.data[CONF_HOST] == IP_ADDRESS2
|
|
|
|
|
|
async def test_dhcp_discovery_discover_fail(
|
|
hass: HomeAssistant,
|
|
mock_config_entry: MockConfigEntry,
|
|
mock_discovery: AsyncMock,
|
|
mock_connect: AsyncMock,
|
|
) -> None:
|
|
"""Test dhcp discovery source cannot discover_single."""
|
|
|
|
flows = hass.config_entries.flow.async_progress()
|
|
assert len(flows) == 0
|
|
assert mock_config_entry.data[CONF_HOST] == IP_ADDRESS
|
|
|
|
with override_side_effect(mock_discovery["discover_single"], TimeoutError):
|
|
discovery_result = await hass.config_entries.flow.async_init(
|
|
DOMAIN,
|
|
context={"source": config_entries.SOURCE_DHCP},
|
|
data=dhcp.DhcpServiceInfo(
|
|
ip=IP_ADDRESS2, macaddress=DHCP_FORMATTED_MAC_ADDRESS, hostname=ALIAS
|
|
),
|
|
)
|
|
assert discovery_result["type"] is FlowResultType.ABORT
|
|
assert discovery_result["reason"] == "cannot_connect"
|
|
|
|
|
|
async def test_reauth(
|
|
hass: HomeAssistant,
|
|
mock_added_config_entry: MockConfigEntry,
|
|
mock_discovery: AsyncMock,
|
|
mock_connect: AsyncMock,
|
|
) -> None:
|
|
"""Test reauth flow."""
|
|
mock_added_config_entry.async_start_reauth(hass)
|
|
await hass.async_block_till_done()
|
|
|
|
assert mock_added_config_entry.state is ConfigEntryState.LOADED
|
|
flows = hass.config_entries.flow.async_progress()
|
|
assert len(flows) == 1
|
|
[result] = flows
|
|
assert result["step_id"] == "reauth_confirm"
|
|
|
|
result2 = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
user_input={
|
|
CONF_USERNAME: "fake_username",
|
|
CONF_PASSWORD: "fake_password",
|
|
},
|
|
)
|
|
credentials = Credentials("fake_username", "fake_password")
|
|
mock_discovery["discover_single"].assert_called_once_with(
|
|
IP_ADDRESS, credentials=credentials, port=None
|
|
)
|
|
mock_discovery["mock_devices"][IP_ADDRESS].update.assert_called_once_with()
|
|
assert result2["type"] is FlowResultType.ABORT
|
|
assert result2["reason"] == "reauth_successful"
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
|
|
async def test_reauth_camera(
|
|
hass: HomeAssistant,
|
|
mock_camera_config_entry: MockConfigEntry,
|
|
mock_discovery: AsyncMock,
|
|
mock_connect: AsyncMock,
|
|
) -> None:
|
|
"""Test async_get_image."""
|
|
mock_device = mock_connect["mock_devices"][IP_ADDRESS3]
|
|
mock_camera_config_entry.add_to_hass(hass)
|
|
mock_camera_config_entry.async_start_reauth(
|
|
hass,
|
|
config_entries.ConfigFlowContext(
|
|
reauth_source=CONF_CAMERA_CREDENTIALS, # type: ignore[typeddict-unknown-key]
|
|
),
|
|
{"device": mock_device},
|
|
)
|
|
await hass.async_block_till_done()
|
|
flows = hass.config_entries.flow.async_progress()
|
|
assert len(flows) == 1
|
|
[result] = flows
|
|
|
|
assert result["step_id"] == "camera_auth_confirm"
|
|
|
|
with patch(
|
|
"homeassistant.components.stream.async_check_stream_client_error",
|
|
return_value=None,
|
|
):
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
user_input={
|
|
CONF_LIVE_VIEW: True,
|
|
CONF_USERNAME: "camuser2",
|
|
CONF_PASSWORD: "campass2",
|
|
},
|
|
)
|
|
assert result["type"] is FlowResultType.ABORT
|
|
assert result["reason"] == "reauth_successful"
|
|
assert dict(mock_camera_config_entry.data) == {
|
|
**CREATE_ENTRY_DATA_AES_CAMERA,
|
|
CONF_CAMERA_CREDENTIALS: {CONF_USERNAME: "camuser2", CONF_PASSWORD: "campass2"},
|
|
}
|
|
|
|
|
|
async def test_reauth_try_connect_all(
|
|
hass: HomeAssistant,
|
|
mock_added_config_entry: MockConfigEntry,
|
|
mock_discovery: AsyncMock,
|
|
mock_connect: AsyncMock,
|
|
) -> None:
|
|
"""Test reauth flow."""
|
|
mock_added_config_entry.async_start_reauth(hass)
|
|
await hass.async_block_till_done()
|
|
|
|
assert mock_added_config_entry.state is ConfigEntryState.LOADED
|
|
flows = hass.config_entries.flow.async_progress()
|
|
assert len(flows) == 1
|
|
[result] = flows
|
|
assert result["step_id"] == "reauth_confirm"
|
|
|
|
with override_side_effect(mock_discovery["discover_single"], TimeoutError):
|
|
result2 = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
user_input={
|
|
CONF_USERNAME: "fake_username",
|
|
CONF_PASSWORD: "fake_password",
|
|
},
|
|
)
|
|
credentials = Credentials("fake_username", "fake_password")
|
|
mock_discovery["discover_single"].assert_called_once_with(
|
|
IP_ADDRESS, credentials=credentials, port=None
|
|
)
|
|
mock_discovery["try_connect_all"].assert_called_once()
|
|
assert result2["type"] is FlowResultType.ABORT
|
|
assert result2["reason"] == "reauth_successful"
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
|
|
async def test_reauth_try_connect_all_fail(
|
|
hass: HomeAssistant,
|
|
mock_added_config_entry: MockConfigEntry,
|
|
mock_discovery: AsyncMock,
|
|
mock_connect: AsyncMock,
|
|
) -> None:
|
|
"""Test reauth flow."""
|
|
mock_added_config_entry.async_start_reauth(hass)
|
|
await hass.async_block_till_done()
|
|
|
|
assert mock_added_config_entry.state is ConfigEntryState.LOADED
|
|
flows = hass.config_entries.flow.async_progress()
|
|
assert len(flows) == 1
|
|
[result] = flows
|
|
assert result["step_id"] == "reauth_confirm"
|
|
|
|
with (
|
|
override_side_effect(mock_discovery["discover_single"], TimeoutError),
|
|
override_side_effect(mock_discovery["try_connect_all"], lambda *_, **__: None),
|
|
):
|
|
result2 = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
user_input={
|
|
CONF_USERNAME: "fake_username",
|
|
CONF_PASSWORD: "fake_password",
|
|
},
|
|
)
|
|
credentials = Credentials("fake_username", "fake_password")
|
|
mock_discovery["discover_single"].assert_called_once_with(
|
|
IP_ADDRESS, credentials=credentials, port=None
|
|
)
|
|
mock_discovery["try_connect_all"].assert_called_once()
|
|
assert result2["errors"] == {"base": "cannot_connect"}
|
|
|
|
|
|
async def test_reauth_update_with_encryption_change(
|
|
hass: HomeAssistant,
|
|
mock_discovery: AsyncMock,
|
|
mock_connect: AsyncMock,
|
|
caplog: pytest.LogCaptureFixture,
|
|
) -> None:
|
|
"""Test reauth flow."""
|
|
|
|
mock_config_entry = MockConfigEntry(
|
|
title="TPLink",
|
|
domain=DOMAIN,
|
|
data={**CREATE_ENTRY_DATA_AES},
|
|
unique_id=MAC_ADDRESS2,
|
|
)
|
|
mock_config_entry.add_to_hass(hass)
|
|
assert (
|
|
mock_config_entry.data[CONF_CONNECTION_PARAMETERS] == CONN_PARAMS_AES.to_dict()
|
|
)
|
|
assert mock_config_entry.data[CONF_CREDENTIALS_HASH] == CREDENTIALS_HASH_AES
|
|
|
|
with (
|
|
patch("homeassistant.components.tplink.Discover.discover", return_value={}),
|
|
override_side_effect(mock_connect["connect"], AuthenticationError()),
|
|
):
|
|
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
|
await hass.async_block_till_done()
|
|
assert mock_config_entry.state is ConfigEntryState.SETUP_ERROR
|
|
|
|
caplog.set_level(logging.DEBUG)
|
|
flows = hass.config_entries.flow.async_progress()
|
|
assert len(flows) == 1
|
|
[result] = flows
|
|
assert result["step_id"] == "reauth_confirm"
|
|
assert (
|
|
mock_config_entry.data[CONF_CONNECTION_PARAMETERS] == CONN_PARAMS_AES.to_dict()
|
|
)
|
|
assert CONF_CREDENTIALS_HASH not in mock_config_entry.data
|
|
|
|
new_config = DeviceConfig(
|
|
IP_ADDRESS2,
|
|
credentials=None,
|
|
connection_type=Device.ConnectionParameters(
|
|
Device.Family.SmartTapoPlug, Device.EncryptionType.Klap
|
|
),
|
|
uses_http=True,
|
|
)
|
|
mock_device = _mocked_device(
|
|
alias="my_device",
|
|
ip_address=IP_ADDRESS2,
|
|
mac=MAC_ADDRESS2,
|
|
device_config=new_config,
|
|
credentials_hash=CREDENTIALS_HASH_KLAP,
|
|
)
|
|
|
|
with (
|
|
override_side_effect(
|
|
mock_discovery["discover_single"], lambda *_, **__: mock_device
|
|
),
|
|
override_side_effect(mock_connect["connect"], lambda *_, **__: mock_device),
|
|
):
|
|
result2 = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
user_input={
|
|
CONF_USERNAME: "fake_username",
|
|
CONF_PASSWORD: "fake_password",
|
|
},
|
|
)
|
|
await hass.async_block_till_done(wait_background_tasks=True)
|
|
assert "Connection type changed for 127.0.0.2" in caplog.text
|
|
credentials = Credentials("fake_username", "fake_password")
|
|
mock_discovery["discover_single"].assert_called_once_with(
|
|
IP_ADDRESS2, credentials=credentials, port=None
|
|
)
|
|
mock_device.update.assert_called_once_with()
|
|
assert result2["type"] is FlowResultType.ABORT
|
|
assert result2["reason"] == "reauth_successful"
|
|
assert mock_config_entry.state is ConfigEntryState.LOADED
|
|
assert (
|
|
mock_config_entry.data[CONF_CONNECTION_PARAMETERS] == CONN_PARAMS_KLAP.to_dict()
|
|
)
|
|
assert mock_config_entry.data[CONF_HOST] == IP_ADDRESS2
|
|
assert mock_config_entry.data[CONF_CREDENTIALS_HASH] == CREDENTIALS_HASH_KLAP
|
|
|
|
|
|
async def test_reauth_update_from_discovery(
|
|
hass: HomeAssistant,
|
|
mock_config_entry: MockConfigEntry,
|
|
mock_discovery: AsyncMock,
|
|
mock_connect: AsyncMock,
|
|
) -> None:
|
|
"""Test reauth flow."""
|
|
mock_config_entry.add_to_hass(hass)
|
|
with (
|
|
patch("homeassistant.components.tplink.Discover.discover", return_value={}),
|
|
override_side_effect(mock_connect["connect"], AuthenticationError()),
|
|
):
|
|
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
|
await hass.async_block_till_done()
|
|
|
|
assert mock_config_entry.state is ConfigEntryState.SETUP_ERROR
|
|
|
|
flows = hass.config_entries.flow.async_progress()
|
|
assert len(flows) == 1
|
|
[result] = flows
|
|
assert result["step_id"] == "reauth_confirm"
|
|
assert (
|
|
mock_config_entry.data[CONF_CONNECTION_PARAMETERS]
|
|
== CONN_PARAMS_LEGACY.to_dict()
|
|
)
|
|
|
|
device = _mocked_device(
|
|
device_config=DEVICE_CONFIG_KLAP,
|
|
mac=mock_config_entry.unique_id,
|
|
)
|
|
with override_side_effect(mock_connect["connect"], lambda *_, **__: device):
|
|
discovery_result = await hass.config_entries.flow.async_init(
|
|
DOMAIN,
|
|
context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY},
|
|
data={
|
|
CONF_HOST: IP_ADDRESS,
|
|
CONF_MAC: MAC_ADDRESS,
|
|
CONF_ALIAS: ALIAS,
|
|
CONF_DEVICE: device,
|
|
},
|
|
)
|
|
await hass.async_block_till_done()
|
|
assert discovery_result["type"] is FlowResultType.ABORT
|
|
assert discovery_result["reason"] == "already_configured"
|
|
assert (
|
|
mock_config_entry.data[CONF_CONNECTION_PARAMETERS] == CONN_PARAMS_KLAP.to_dict()
|
|
)
|
|
|
|
|
|
async def test_reauth_update_from_discovery_with_ip_change(
|
|
hass: HomeAssistant,
|
|
mock_config_entry: MockConfigEntry,
|
|
mock_discovery: AsyncMock,
|
|
mock_connect: AsyncMock,
|
|
) -> None:
|
|
"""Test reauth flow."""
|
|
mock_config_entry.add_to_hass(hass)
|
|
with (
|
|
patch("homeassistant.components.tplink.Discover.discover", return_value={}),
|
|
override_side_effect(mock_connect["connect"], AuthenticationError()),
|
|
):
|
|
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
|
await hass.async_block_till_done()
|
|
assert mock_config_entry.state is ConfigEntryState.SETUP_ERROR
|
|
|
|
flows = hass.config_entries.flow.async_progress()
|
|
assert len(flows) == 1
|
|
[result] = flows
|
|
assert result["step_id"] == "reauth_confirm"
|
|
assert (
|
|
mock_config_entry.data[CONF_CONNECTION_PARAMETERS]
|
|
== CONN_PARAMS_LEGACY.to_dict()
|
|
)
|
|
|
|
device = _mocked_device(
|
|
device_config=DEVICE_CONFIG_KLAP,
|
|
mac=mock_config_entry.unique_id,
|
|
)
|
|
with override_side_effect(mock_connect["connect"], lambda *_, **__: device):
|
|
discovery_result = await hass.config_entries.flow.async_init(
|
|
DOMAIN,
|
|
context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY},
|
|
data={
|
|
CONF_HOST: IP_ADDRESS2,
|
|
CONF_MAC: MAC_ADDRESS,
|
|
CONF_ALIAS: ALIAS,
|
|
CONF_DEVICE: device,
|
|
},
|
|
)
|
|
await hass.async_block_till_done()
|
|
assert discovery_result["type"] is FlowResultType.ABORT
|
|
assert discovery_result["reason"] == "already_configured"
|
|
assert (
|
|
mock_config_entry.data[CONF_CONNECTION_PARAMETERS] == CONN_PARAMS_KLAP.to_dict()
|
|
)
|
|
assert mock_config_entry.data[CONF_HOST] == IP_ADDRESS2
|
|
|
|
|
|
async def test_reauth_no_update_if_config_and_ip_the_same(
|
|
hass: HomeAssistant,
|
|
mock_config_entry: MockConfigEntry,
|
|
mock_discovery: AsyncMock,
|
|
mock_connect: AsyncMock,
|
|
) -> None:
|
|
"""Test reauth discovery does not update when the host and config are the same."""
|
|
mock_config_entry.add_to_hass(hass)
|
|
|
|
hass.config_entries.async_update_entry(
|
|
mock_config_entry,
|
|
data={
|
|
**mock_config_entry.data,
|
|
CONF_DEVICE_CONFIG: DEVICE_CONFIG_DICT_KLAP,
|
|
},
|
|
)
|
|
with override_side_effect(mock_connect["connect"], AuthenticationError()):
|
|
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
|
await hass.async_block_till_done()
|
|
assert mock_config_entry.state is ConfigEntryState.SETUP_ERROR
|
|
|
|
flows = hass.config_entries.flow.async_progress()
|
|
assert len(flows) == 1
|
|
[result] = flows
|
|
assert result["step_id"] == "reauth_confirm"
|
|
assert (
|
|
mock_config_entry.data[CONF_CONNECTION_PARAMETERS] == CONN_PARAMS_KLAP.to_dict()
|
|
)
|
|
|
|
device = _mocked_device(
|
|
device_config=DEVICE_CONFIG_KLAP,
|
|
mac=mock_config_entry.unique_id,
|
|
)
|
|
with override_side_effect(mock_connect["connect"], lambda *_, **__: device):
|
|
discovery_result = await hass.config_entries.flow.async_init(
|
|
DOMAIN,
|
|
context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY},
|
|
data={
|
|
CONF_HOST: IP_ADDRESS,
|
|
CONF_MAC: MAC_ADDRESS,
|
|
CONF_ALIAS: ALIAS,
|
|
CONF_DEVICE: device,
|
|
},
|
|
)
|
|
await hass.async_block_till_done()
|
|
assert discovery_result["type"] is FlowResultType.ABORT
|
|
assert discovery_result["reason"] == "already_configured"
|
|
assert (
|
|
mock_config_entry.data[CONF_CONNECTION_PARAMETERS] == CONN_PARAMS_KLAP.to_dict()
|
|
)
|
|
assert mock_config_entry.data[CONF_HOST] == IP_ADDRESS
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("error_type", "errors_msg", "error_placement"),
|
|
[
|
|
(AuthenticationError("auth_error_details"), "invalid_auth", CONF_PASSWORD),
|
|
(KasaException("smart_device_error_details"), "cannot_connect", "base"),
|
|
],
|
|
ids=["invalid-auth", "unknown-error"],
|
|
)
|
|
async def test_reauth_errors(
|
|
hass: HomeAssistant,
|
|
mock_added_config_entry: MockConfigEntry,
|
|
mock_discovery: AsyncMock,
|
|
mock_connect: AsyncMock,
|
|
error_type,
|
|
errors_msg,
|
|
error_placement,
|
|
) -> None:
|
|
"""Test reauth errors."""
|
|
mock_added_config_entry.async_start_reauth(hass)
|
|
await hass.async_block_till_done()
|
|
|
|
assert mock_added_config_entry.state is ConfigEntryState.LOADED
|
|
flows = hass.config_entries.flow.async_progress()
|
|
assert len(flows) == 1
|
|
[result] = flows
|
|
assert result["step_id"] == "reauth_confirm"
|
|
|
|
mock_device = mock_discovery["mock_devices"][IP_ADDRESS]
|
|
with override_side_effect(mock_device.update, error_type):
|
|
result2 = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
user_input={
|
|
CONF_USERNAME: "fake_username",
|
|
CONF_PASSWORD: "fake_password",
|
|
},
|
|
)
|
|
credentials = Credentials("fake_username", "fake_password")
|
|
|
|
mock_discovery["discover_single"].assert_called_once_with(
|
|
IP_ADDRESS, credentials=credentials, port=None
|
|
)
|
|
mock_device.update.assert_called_once_with()
|
|
assert result2["type"] is FlowResultType.FORM
|
|
assert result2["errors"] == {error_placement: errors_msg}
|
|
assert result2["description_placeholders"]["error"] == str(error_type)
|
|
|
|
mock_discovery["discover_single"].reset_mock()
|
|
mock_device.update.reset_mock(side_effect=True)
|
|
result3 = await hass.config_entries.flow.async_configure(
|
|
result2["flow_id"],
|
|
user_input={
|
|
CONF_USERNAME: "fake_username",
|
|
CONF_PASSWORD: "fake_password",
|
|
},
|
|
)
|
|
|
|
mock_discovery["discover_single"].assert_called_once_with(
|
|
IP_ADDRESS, credentials=credentials, port=None
|
|
)
|
|
mock_device.update.assert_called_once_with()
|
|
|
|
assert result3["type"] is FlowResultType.ABORT
|
|
assert result3["reason"] == "reauth_successful"
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("error_type", "expected_flow"),
|
|
[
|
|
(AuthenticationError, FlowResultType.FORM),
|
|
(KasaException, FlowResultType.ABORT),
|
|
],
|
|
ids=["invalid-auth", "unknown-error"],
|
|
)
|
|
async def test_pick_device_errors(
|
|
hass: HomeAssistant,
|
|
mock_discovery: AsyncMock,
|
|
mock_connect: AsyncMock,
|
|
error_type,
|
|
expected_flow,
|
|
) -> None:
|
|
"""Test errors on pick_device."""
|
|
result = await hass.config_entries.flow.async_init(
|
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
|
)
|
|
await hass.async_block_till_done()
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "user"
|
|
assert not result["errors"]
|
|
|
|
result2 = await hass.config_entries.flow.async_configure(result["flow_id"], {})
|
|
await hass.async_block_till_done()
|
|
assert result2["type"] is FlowResultType.FORM
|
|
assert result2["step_id"] == "pick_device"
|
|
assert not result2["errors"]
|
|
|
|
with override_side_effect(mock_connect["connect"], error_type):
|
|
result3 = await hass.config_entries.flow.async_configure(
|
|
result2["flow_id"],
|
|
{CONF_DEVICE: MAC_ADDRESS},
|
|
)
|
|
await hass.async_block_till_done()
|
|
assert result3["type"] == expected_flow
|
|
|
|
if expected_flow != FlowResultType.ABORT:
|
|
result4 = await hass.config_entries.flow.async_configure(
|
|
result3["flow_id"],
|
|
user_input={
|
|
CONF_USERNAME: "fake_username",
|
|
CONF_PASSWORD: "fake_password",
|
|
},
|
|
)
|
|
assert result4["type"] is FlowResultType.CREATE_ENTRY
|
|
assert result4["context"]["unique_id"] == MAC_ADDRESS
|
|
|
|
|
|
async def test_discovery_timeout_try_connect_all(
|
|
hass: HomeAssistant,
|
|
mock_discovery: AsyncMock,
|
|
mock_connect: AsyncMock,
|
|
mock_init,
|
|
) -> None:
|
|
"""Test discovery tries legacy connect on timeout."""
|
|
result = await hass.config_entries.flow.async_init(
|
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
|
)
|
|
mock_discovery["discover_single"].side_effect = TimeoutError
|
|
await hass.async_block_till_done()
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "user"
|
|
assert not result["errors"]
|
|
assert mock_connect["connect"].call_count == 0
|
|
|
|
result2 = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"], {CONF_HOST: IP_ADDRESS}
|
|
)
|
|
await hass.async_block_till_done()
|
|
assert result2["type"] is FlowResultType.CREATE_ENTRY
|
|
assert result2["context"]["unique_id"] == MAC_ADDRESS
|
|
assert mock_connect["connect"].call_count == 1
|
|
|
|
|
|
async def test_discovery_timeout_try_connect_all_needs_creds(
|
|
hass: HomeAssistant,
|
|
mock_discovery: AsyncMock,
|
|
mock_connect: AsyncMock,
|
|
mock_init,
|
|
) -> None:
|
|
"""Test discovery tries legacy connect on timeout."""
|
|
result = await hass.config_entries.flow.async_init(
|
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
|
)
|
|
mock_discovery["discover_single"].side_effect = TimeoutError
|
|
await hass.async_block_till_done()
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "user"
|
|
assert not result["errors"]
|
|
assert mock_connect["connect"].call_count == 0
|
|
|
|
with override_side_effect(mock_connect["connect"], KasaException):
|
|
result2 = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"], {CONF_HOST: IP_ADDRESS}
|
|
)
|
|
await hass.async_block_till_done()
|
|
assert result2["step_id"] == "user_auth_confirm"
|
|
assert result2["type"] is FlowResultType.FORM
|
|
|
|
result3 = await hass.config_entries.flow.async_configure(
|
|
result2["flow_id"],
|
|
user_input={
|
|
CONF_USERNAME: "fake_username",
|
|
CONF_PASSWORD: "fake_password",
|
|
},
|
|
)
|
|
await hass.async_block_till_done()
|
|
assert result3["type"] is FlowResultType.CREATE_ENTRY
|
|
assert result3["context"]["unique_id"] == MAC_ADDRESS
|
|
assert mock_connect["connect"].call_count == 1
|
|
|
|
|
|
async def test_discovery_timeout_try_connect_all_fail(
|
|
hass: HomeAssistant,
|
|
mock_discovery: AsyncMock,
|
|
mock_connect: AsyncMock,
|
|
mock_init,
|
|
) -> None:
|
|
"""Test discovery tries legacy connect on timeout."""
|
|
result = await hass.config_entries.flow.async_init(
|
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
|
)
|
|
mock_discovery["discover_single"].side_effect = TimeoutError
|
|
await hass.async_block_till_done()
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "user"
|
|
assert not result["errors"]
|
|
assert mock_connect["connect"].call_count == 0
|
|
|
|
with override_side_effect(mock_connect["connect"], KasaException):
|
|
result2 = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"], {CONF_HOST: IP_ADDRESS}
|
|
)
|
|
await hass.async_block_till_done()
|
|
assert result2["step_id"] == "user_auth_confirm"
|
|
assert result2["type"] is FlowResultType.FORM
|
|
|
|
with override_side_effect(mock_discovery["try_connect_all"], lambda *_, **__: None):
|
|
result3 = await hass.config_entries.flow.async_configure(
|
|
result2["flow_id"],
|
|
user_input={
|
|
CONF_USERNAME: "fake_username",
|
|
CONF_PASSWORD: "fake_password",
|
|
},
|
|
)
|
|
await hass.async_block_till_done()
|
|
assert result3["errors"] == {"base": "cannot_connect"}
|
|
assert mock_connect["connect"].call_count == 1
|
|
|
|
|
|
async def test_reauth_update_other_flows(
|
|
hass: HomeAssistant,
|
|
mock_discovery: AsyncMock,
|
|
mock_connect: AsyncMock,
|
|
) -> None:
|
|
"""Test reauth updates other reauth flows."""
|
|
mock_config_entry = MockConfigEntry(
|
|
title="TPLink",
|
|
domain=DOMAIN,
|
|
data={**CREATE_ENTRY_DATA_KLAP},
|
|
unique_id=MAC_ADDRESS,
|
|
)
|
|
mock_config_entry2 = MockConfigEntry(
|
|
title="TPLink",
|
|
domain=DOMAIN,
|
|
data={**CREATE_ENTRY_DATA_AES},
|
|
unique_id=MAC_ADDRESS2,
|
|
)
|
|
mock_config_entry.add_to_hass(hass)
|
|
mock_config_entry2.add_to_hass(hass)
|
|
with (
|
|
patch("homeassistant.components.tplink.Discover.discover", return_value={}),
|
|
override_side_effect(mock_connect["connect"], AuthenticationError()),
|
|
):
|
|
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
|
await hass.async_block_till_done()
|
|
|
|
assert mock_config_entry2.state is ConfigEntryState.SETUP_ERROR
|
|
assert mock_config_entry.state is ConfigEntryState.SETUP_ERROR
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
flows = hass.config_entries.flow.async_progress()
|
|
assert len(flows) == 2
|
|
flows_by_entry_id = {flow["context"]["entry_id"]: flow for flow in flows}
|
|
result = flows_by_entry_id[mock_config_entry.entry_id]
|
|
assert result["step_id"] == "reauth_confirm"
|
|
assert (
|
|
mock_config_entry.data[CONF_CONNECTION_PARAMETERS] == CONN_PARAMS_KLAP.to_dict()
|
|
)
|
|
result2 = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
user_input={
|
|
CONF_USERNAME: "fake_username",
|
|
CONF_PASSWORD: "fake_password",
|
|
},
|
|
)
|
|
credentials = Credentials("fake_username", "fake_password")
|
|
mock_discovery["discover_single"].assert_called_once_with(
|
|
IP_ADDRESS, credentials=credentials, port=None
|
|
)
|
|
mock_discovery["mock_devices"][IP_ADDRESS].update.assert_called_once_with()
|
|
assert result2["type"] is FlowResultType.ABORT
|
|
assert result2["reason"] == "reauth_successful"
|
|
|
|
await hass.async_block_till_done()
|
|
flows = hass.config_entries.flow.async_progress()
|
|
assert len(flows) == 0
|
|
|
|
|
|
async def test_reconfigure(
|
|
hass: HomeAssistant,
|
|
mock_added_config_entry: MockConfigEntry,
|
|
mock_discovery: AsyncMock,
|
|
mock_connect: AsyncMock,
|
|
) -> None:
|
|
"""Test reconfigure flow."""
|
|
result = await mock_added_config_entry.start_reconfigure_flow(hass)
|
|
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "reconfigure"
|
|
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
user_input={
|
|
CONF_HOST: IP_ADDRESS,
|
|
},
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.ABORT
|
|
assert result["reason"] == "reconfigure_successful"
|
|
|
|
|
|
async def test_reconfigure_auth_discovered(
|
|
hass: HomeAssistant,
|
|
mock_added_config_entry: MockConfigEntry,
|
|
mock_discovery: AsyncMock,
|
|
mock_connect: AsyncMock,
|
|
) -> None:
|
|
"""Test reconfigure auth flow for device that's discovered."""
|
|
result = await mock_added_config_entry.start_reconfigure_flow(hass)
|
|
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "reconfigure"
|
|
|
|
# Simulate a bad host
|
|
with (
|
|
override_side_effect(
|
|
mock_discovery["mock_devices"][IP_ADDRESS].update, KasaException
|
|
),
|
|
):
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
user_input={
|
|
CONF_HOST: "WRONG_IP",
|
|
},
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "reconfigure"
|
|
assert result["errors"] == {"base": "cannot_connect"}
|
|
assert "error" in result["description_placeholders"]
|
|
|
|
with (
|
|
override_side_effect(
|
|
mock_discovery["mock_devices"][IP_ADDRESS].update, AuthenticationError
|
|
),
|
|
):
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
user_input={
|
|
CONF_HOST: IP_ADDRESS,
|
|
},
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "user_auth_confirm"
|
|
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
user_input={
|
|
CONF_USERNAME: "user",
|
|
CONF_PASSWORD: "pass",
|
|
},
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.ABORT
|
|
assert result["reason"] == "reconfigure_successful"
|
|
|
|
|
|
async def test_reconfigure_auth_try_connect_all(
|
|
hass: HomeAssistant,
|
|
mock_added_config_entry: MockConfigEntry,
|
|
mock_discovery: AsyncMock,
|
|
mock_connect: AsyncMock,
|
|
) -> None:
|
|
"""Test reconfigure auth flow for device that's not discovered."""
|
|
result = await mock_added_config_entry.start_reconfigure_flow(hass)
|
|
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "reconfigure"
|
|
|
|
with (
|
|
override_side_effect(mock_discovery["discover_single"], TimeoutError),
|
|
override_side_effect(mock_connect["connect"], KasaException),
|
|
):
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
user_input={
|
|
CONF_HOST: IP_ADDRESS,
|
|
},
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "user_auth_confirm"
|
|
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
user_input={
|
|
CONF_USERNAME: "user",
|
|
CONF_PASSWORD: "pass",
|
|
},
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.ABORT
|
|
assert result["reason"] == "reconfigure_successful"
|
|
|
|
|
|
async def test_reconfigure_camera(
|
|
hass: HomeAssistant,
|
|
mock_camera_config_entry: MockConfigEntry,
|
|
mock_discovery: AsyncMock,
|
|
mock_connect: AsyncMock,
|
|
) -> None:
|
|
"""Test reconfigure flow."""
|
|
mock_camera_config_entry.add_to_hass(hass)
|
|
result = await mock_camera_config_entry.start_reconfigure_flow(hass)
|
|
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "reconfigure"
|
|
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
user_input={
|
|
CONF_HOST: IP_ADDRESS3,
|
|
},
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "camera_auth_confirm"
|
|
|
|
with patch(
|
|
"homeassistant.components.stream.async_check_stream_client_error",
|
|
return_value=None,
|
|
):
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
user_input={
|
|
CONF_LIVE_VIEW: True,
|
|
CONF_USERNAME: "camuser",
|
|
CONF_PASSWORD: "campass",
|
|
},
|
|
)
|
|
assert result["type"] is FlowResultType.ABORT
|
|
assert result["reason"] == "reconfigure_successful"
|