Guide users to migrate from Ubiquiti Cloud Accounts to local for UniFi Protect (#111018)
Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
parent
fb04df5392
commit
7eb6614818
9 changed files with 260 additions and 40 deletions
|
@ -61,7 +61,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||
data_service = ProtectData(hass, protect, SCAN_INTERVAL, entry)
|
||||
|
||||
try:
|
||||
nvr_info = await protect.get_nvr()
|
||||
bootstrap = await protect.get_bootstrap()
|
||||
nvr_info = bootstrap.nvr
|
||||
except NotAuthorized as err:
|
||||
retry_key = f"{entry.entry_id}_auth"
|
||||
retries = hass.data.setdefault(DOMAIN, {}).get(retry_key, 0)
|
||||
|
@ -73,6 +74,20 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||
except (TimeoutError, ClientError, ServerDisconnectedError) as err:
|
||||
raise ConfigEntryNotReady from err
|
||||
|
||||
auth_user = bootstrap.users.get(bootstrap.auth_user_id)
|
||||
if auth_user and auth_user.cloud_account:
|
||||
ir.async_create_issue(
|
||||
hass,
|
||||
DOMAIN,
|
||||
"cloud_user",
|
||||
is_fixable=True,
|
||||
is_persistent=False,
|
||||
learn_more_url="https://www.home-assistant.io/integrations/unifiprotect/#local-user",
|
||||
severity=IssueSeverity.ERROR,
|
||||
translation_key="cloud_user",
|
||||
data={"entry_id": entry.entry_id},
|
||||
)
|
||||
|
||||
if nvr_info.version < MIN_REQUIRED_PROTECT_V:
|
||||
_LOGGER.error(
|
||||
OUTDATED_LOG_MESSAGE,
|
||||
|
|
|
@ -256,7 +256,8 @@ class ProtectFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
errors = {}
|
||||
nvr_data = None
|
||||
try:
|
||||
nvr_data = await protect.get_nvr()
|
||||
bootstrap = await protect.get_bootstrap()
|
||||
nvr_data = bootstrap.nvr
|
||||
except NotAuthorized as ex:
|
||||
_LOGGER.debug(ex)
|
||||
errors[CONF_PASSWORD] = "invalid_auth"
|
||||
|
@ -272,6 +273,10 @@ class ProtectFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
)
|
||||
errors["base"] = "protect_version"
|
||||
|
||||
auth_user = bootstrap.users.get(bootstrap.auth_user_id)
|
||||
if auth_user and auth_user.cloud_account:
|
||||
errors["base"] = "cloud_user"
|
||||
|
||||
return nvr_data, errors
|
||||
|
||||
async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult:
|
||||
|
|
|
@ -20,7 +20,7 @@ from .utils import async_create_api_client
|
|||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class EAConfirm(RepairsFlow):
|
||||
class ProtectRepair(RepairsFlow):
|
||||
"""Handler for an issue fixing flow."""
|
||||
|
||||
_api: ProtectApiClient
|
||||
|
@ -34,14 +34,20 @@ class EAConfirm(RepairsFlow):
|
|||
super().__init__()
|
||||
|
||||
@callback
|
||||
def _async_get_placeholders(self) -> dict[str, str] | None:
|
||||
def _async_get_placeholders(self) -> dict[str, str]:
|
||||
issue_registry = async_get_issue_registry(self.hass)
|
||||
description_placeholders = None
|
||||
description_placeholders = {}
|
||||
if issue := issue_registry.async_get_issue(self.handler, self.issue_id):
|
||||
description_placeholders = issue.translation_placeholders
|
||||
description_placeholders = issue.translation_placeholders or {}
|
||||
if issue.learn_more_url:
|
||||
description_placeholders["learn_more"] = issue.learn_more_url
|
||||
|
||||
return description_placeholders
|
||||
|
||||
|
||||
class EAConfirm(ProtectRepair):
|
||||
"""Handler for an issue fixing flow."""
|
||||
|
||||
async def async_step_init(
|
||||
self, user_input: dict[str, str] | None = None
|
||||
) -> data_entry_flow.FlowResult:
|
||||
|
@ -85,6 +91,33 @@ class EAConfirm(RepairsFlow):
|
|||
)
|
||||
|
||||
|
||||
class CloudAccount(ProtectRepair):
|
||||
"""Handler for an issue fixing flow."""
|
||||
|
||||
async def async_step_init(
|
||||
self, user_input: dict[str, str] | None = None
|
||||
) -> data_entry_flow.FlowResult:
|
||||
"""Handle the first step of a fix flow."""
|
||||
|
||||
return await self.async_step_confirm()
|
||||
|
||||
async def async_step_confirm(
|
||||
self, user_input: dict[str, str] | None = None
|
||||
) -> data_entry_flow.FlowResult:
|
||||
"""Handle the first step of a fix flow."""
|
||||
|
||||
if user_input is None:
|
||||
placeholders = self._async_get_placeholders()
|
||||
return self.async_show_form(
|
||||
step_id="confirm",
|
||||
data_schema=vol.Schema({}),
|
||||
description_placeholders=placeholders,
|
||||
)
|
||||
|
||||
self._entry.async_start_reauth(self.hass)
|
||||
return self.async_create_entry(data={})
|
||||
|
||||
|
||||
async def async_create_fix_flow(
|
||||
hass: HomeAssistant,
|
||||
issue_id: str,
|
||||
|
@ -96,4 +129,9 @@ async def async_create_fix_flow(
|
|||
if (entry := hass.config_entries.async_get_entry(entry_id)) is not None:
|
||||
api = async_create_api_client(hass, entry)
|
||||
return EAConfirm(api, entry)
|
||||
elif data is not None and issue_id == "cloud_user":
|
||||
entry_id = cast(str, data["entry_id"])
|
||||
if (entry := hass.config_entries.async_get_entry(entry_id)) is not None:
|
||||
api = async_create_api_client(hass, entry)
|
||||
return CloudAccount(api, entry)
|
||||
return ConfirmRepairFlow()
|
||||
|
|
|
@ -37,7 +37,8 @@
|
|||
"error": {
|
||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
|
||||
"protect_version": "Minimum required version is v1.20.0. Please upgrade UniFi Protect and then retry."
|
||||
"protect_version": "Minimum required version is v1.20.0. Please upgrade UniFi Protect and then retry.",
|
||||
"cloud_user": "Ubiquiti Cloud users are not Supported. Please use a Local only user."
|
||||
},
|
||||
"abort": {
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
|
||||
|
@ -78,6 +79,17 @@
|
|||
"ea_setup_failed": {
|
||||
"title": "Setup error using Early Access version",
|
||||
"description": "You are using v{version} of UniFi Protect which is an Early Access version. An unrecoverable error occurred while trying to load the integration. Please [downgrade to a stable version](https://www.home-assistant.io/integrations/unifiprotect#downgrading-unifi-protect) of UniFi Protect to continue using the integration.\n\nError: {error}"
|
||||
},
|
||||
"cloud_user": {
|
||||
"title": "Ubiquiti Cloud Users are not Supported",
|
||||
"fix_flow": {
|
||||
"step": {
|
||||
"confirm": {
|
||||
"title": "Ubiquiti Cloud Users are not Supported",
|
||||
"description": "Starting on July 22nd, 2024, Ubiquiti will require all cloud users to enroll in multi-factor authentication (MFA), which is incompatible with Home Assistant.\n\nIt would be best to migrate to using a [local user]({learn_more}) as soon as possible to keep the integration working.\n\nConfirming this repair will trigger a re-authentication flow to enter the needed authentication credentials."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"entity": {
|
||||
|
|
|
@ -19,6 +19,7 @@ from pyunifiprotect.data import (
|
|||
Bootstrap,
|
||||
Camera,
|
||||
Chime,
|
||||
CloudAccount,
|
||||
Doorlock,
|
||||
Light,
|
||||
Liveview,
|
||||
|
@ -119,6 +120,7 @@ def mock_ufp_client(bootstrap: Bootstrap):
|
|||
client.base_url = "https://127.0.0.1"
|
||||
client.connection_host = IPv4Address("127.0.0.1")
|
||||
client.get_nvr = AsyncMock(return_value=nvr)
|
||||
client.get_bootstrap = AsyncMock(return_value=bootstrap)
|
||||
client.update = AsyncMock(return_value=bootstrap)
|
||||
client.async_disconnect_ws = AsyncMock()
|
||||
return client
|
||||
|
@ -345,3 +347,19 @@ def chime():
|
|||
def fixed_now_fixture():
|
||||
"""Return datetime object that will be consistent throughout test."""
|
||||
return dt_util.utcnow()
|
||||
|
||||
|
||||
@pytest.fixture(name="cloud_account")
|
||||
def cloud_account() -> CloudAccount:
|
||||
"""Return UI Cloud Account."""
|
||||
|
||||
return CloudAccount(
|
||||
id="42",
|
||||
first_name="Test",
|
||||
last_name="User",
|
||||
email="test@example.com",
|
||||
user_id="42",
|
||||
name="Test User",
|
||||
location=None,
|
||||
profile_img=None,
|
||||
)
|
||||
|
|
|
@ -7,7 +7,7 @@ from unittest.mock import patch
|
|||
|
||||
import pytest
|
||||
from pyunifiprotect import NotAuthorized, NvrError, ProtectApiClient
|
||||
from pyunifiprotect.data import NVR
|
||||
from pyunifiprotect.data import NVR, Bootstrap, CloudAccount
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components import dhcp, ssdp
|
||||
|
@ -57,7 +57,7 @@ UNIFI_DISCOVERY_DICT = asdict(UNIFI_DISCOVERY)
|
|||
UNIFI_DISCOVERY_DICT_PARTIAL = asdict(UNIFI_DISCOVERY_PARTIAL)
|
||||
|
||||
|
||||
async def test_form(hass: HomeAssistant, nvr: NVR) -> None:
|
||||
async def test_form(hass: HomeAssistant, bootstrap: Bootstrap, nvr: NVR) -> None:
|
||||
"""Test we get the form."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
|
@ -65,9 +65,10 @@ async def test_form(hass: HomeAssistant, nvr: NVR) -> None:
|
|||
assert result["type"] == FlowResultType.FORM
|
||||
assert not result["errors"]
|
||||
|
||||
bootstrap.nvr = nvr
|
||||
with patch(
|
||||
"homeassistant.components.unifiprotect.config_flow.ProtectApiClient.get_nvr",
|
||||
return_value=nvr,
|
||||
"homeassistant.components.unifiprotect.config_flow.ProtectApiClient.get_bootstrap",
|
||||
return_value=bootstrap,
|
||||
), patch(
|
||||
"homeassistant.components.unifiprotect.async_setup_entry",
|
||||
return_value=True,
|
||||
|
@ -99,15 +100,18 @@ async def test_form(hass: HomeAssistant, nvr: NVR) -> None:
|
|||
assert len(mock_setup.mock_calls) == 1
|
||||
|
||||
|
||||
async def test_form_version_too_old(hass: HomeAssistant, old_nvr: NVR) -> None:
|
||||
async def test_form_version_too_old(
|
||||
hass: HomeAssistant, bootstrap: Bootstrap, old_nvr: NVR
|
||||
) -> None:
|
||||
"""Test we handle the version being too old."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
|
||||
bootstrap.nvr = old_nvr
|
||||
with patch(
|
||||
"homeassistant.components.unifiprotect.config_flow.ProtectApiClient.get_nvr",
|
||||
return_value=old_nvr,
|
||||
"homeassistant.components.unifiprotect.config_flow.ProtectApiClient.get_bootstrap",
|
||||
return_value=bootstrap,
|
||||
):
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
|
@ -129,7 +133,7 @@ async def test_form_invalid_auth(hass: HomeAssistant) -> None:
|
|||
)
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.unifiprotect.config_flow.ProtectApiClient.get_nvr",
|
||||
"homeassistant.components.unifiprotect.config_flow.ProtectApiClient.get_bootstrap",
|
||||
side_effect=NotAuthorized,
|
||||
):
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
|
@ -145,6 +149,34 @@ async def test_form_invalid_auth(hass: HomeAssistant) -> None:
|
|||
assert result2["errors"] == {"password": "invalid_auth"}
|
||||
|
||||
|
||||
async def test_form_cloud_user(
|
||||
hass: HomeAssistant, bootstrap: Bootstrap, cloud_account: CloudAccount
|
||||
) -> None:
|
||||
"""Test we handle cloud users."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
|
||||
user = bootstrap.users[bootstrap.auth_user_id]
|
||||
user.cloud_account = cloud_account
|
||||
bootstrap.users[bootstrap.auth_user_id] = user
|
||||
with patch(
|
||||
"homeassistant.components.unifiprotect.config_flow.ProtectApiClient.get_bootstrap",
|
||||
return_value=bootstrap,
|
||||
):
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{
|
||||
"host": "1.1.1.1",
|
||||
"username": "test-username",
|
||||
"password": "test-password",
|
||||
},
|
||||
)
|
||||
|
||||
assert result2["type"] == FlowResultType.FORM
|
||||
assert result2["errors"] == {"base": "cloud_user"}
|
||||
|
||||
|
||||
async def test_form_cannot_connect(hass: HomeAssistant) -> None:
|
||||
"""Test we handle cannot connect error."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
|
@ -152,7 +184,7 @@ async def test_form_cannot_connect(hass: HomeAssistant) -> None:
|
|||
)
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.unifiprotect.config_flow.ProtectApiClient.get_nvr",
|
||||
"homeassistant.components.unifiprotect.config_flow.ProtectApiClient.get_bootstrap",
|
||||
side_effect=NvrError,
|
||||
):
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
|
@ -168,7 +200,9 @@ async def test_form_cannot_connect(hass: HomeAssistant) -> None:
|
|||
assert result2["errors"] == {"base": "cannot_connect"}
|
||||
|
||||
|
||||
async def test_form_reauth_auth(hass: HomeAssistant, nvr: NVR) -> None:
|
||||
async def test_form_reauth_auth(
|
||||
hass: HomeAssistant, bootstrap: Bootstrap, nvr: NVR
|
||||
) -> None:
|
||||
"""Test we handle reauth auth."""
|
||||
mock_config = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
|
@ -200,7 +234,7 @@ async def test_form_reauth_auth(hass: HomeAssistant, nvr: NVR) -> None:
|
|||
}
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.unifiprotect.config_flow.ProtectApiClient.get_nvr",
|
||||
"homeassistant.components.unifiprotect.config_flow.ProtectApiClient.get_bootstrap",
|
||||
side_effect=NotAuthorized,
|
||||
):
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
|
@ -215,9 +249,10 @@ async def test_form_reauth_auth(hass: HomeAssistant, nvr: NVR) -> None:
|
|||
assert result2["errors"] == {"password": "invalid_auth"}
|
||||
assert result2["step_id"] == "reauth_confirm"
|
||||
|
||||
bootstrap.nvr = nvr
|
||||
with patch(
|
||||
"homeassistant.components.unifiprotect.config_flow.ProtectApiClient.get_nvr",
|
||||
return_value=nvr,
|
||||
"homeassistant.components.unifiprotect.config_flow.ProtectApiClient.get_bootstrap",
|
||||
return_value=bootstrap,
|
||||
), patch(
|
||||
"homeassistant.components.unifiprotect.async_setup",
|
||||
return_value=True,
|
||||
|
@ -313,7 +348,7 @@ async def test_discovered_by_ssdp_or_dhcp(
|
|||
|
||||
|
||||
async def test_discovered_by_unifi_discovery_direct_connect(
|
||||
hass: HomeAssistant, nvr: NVR
|
||||
hass: HomeAssistant, bootstrap: Bootstrap, nvr: NVR
|
||||
) -> None:
|
||||
"""Test a discovery from unifi-discovery."""
|
||||
|
||||
|
@ -335,9 +370,10 @@ async def test_discovered_by_unifi_discovery_direct_connect(
|
|||
|
||||
assert not result["errors"]
|
||||
|
||||
bootstrap.nvr = nvr
|
||||
with patch(
|
||||
"homeassistant.components.unifiprotect.config_flow.ProtectApiClient.get_nvr",
|
||||
return_value=nvr,
|
||||
"homeassistant.components.unifiprotect.config_flow.ProtectApiClient.get_bootstrap",
|
||||
return_value=bootstrap,
|
||||
), patch(
|
||||
"homeassistant.components.unifiprotect.async_setup_entry",
|
||||
return_value=True,
|
||||
|
@ -501,7 +537,9 @@ async def test_discovered_host_not_updated_if_existing_is_a_hostname(
|
|||
assert mock_config.data[CONF_HOST] == "a.hostname"
|
||||
|
||||
|
||||
async def test_discovered_by_unifi_discovery(hass: HomeAssistant, nvr: NVR) -> None:
|
||||
async def test_discovered_by_unifi_discovery(
|
||||
hass: HomeAssistant, bootstrap: Bootstrap, nvr: NVR
|
||||
) -> None:
|
||||
"""Test a discovery from unifi-discovery."""
|
||||
|
||||
with _patch_discovery():
|
||||
|
@ -522,9 +560,10 @@ async def test_discovered_by_unifi_discovery(hass: HomeAssistant, nvr: NVR) -> N
|
|||
|
||||
assert not result["errors"]
|
||||
|
||||
bootstrap.nvr = nvr
|
||||
with patch(
|
||||
"homeassistant.components.unifiprotect.config_flow.ProtectApiClient.get_nvr",
|
||||
side_effect=[NotAuthorized, nvr],
|
||||
"homeassistant.components.unifiprotect.config_flow.ProtectApiClient.get_bootstrap",
|
||||
side_effect=[NotAuthorized, bootstrap],
|
||||
), patch(
|
||||
"homeassistant.components.unifiprotect.async_setup_entry",
|
||||
return_value=True,
|
||||
|
@ -556,7 +595,7 @@ async def test_discovered_by_unifi_discovery(hass: HomeAssistant, nvr: NVR) -> N
|
|||
|
||||
|
||||
async def test_discovered_by_unifi_discovery_partial(
|
||||
hass: HomeAssistant, nvr: NVR
|
||||
hass: HomeAssistant, bootstrap: Bootstrap, nvr: NVR
|
||||
) -> None:
|
||||
"""Test a discovery from unifi-discovery partial."""
|
||||
|
||||
|
@ -578,9 +617,10 @@ async def test_discovered_by_unifi_discovery_partial(
|
|||
|
||||
assert not result["errors"]
|
||||
|
||||
bootstrap.nvr = nvr
|
||||
with patch(
|
||||
"homeassistant.components.unifiprotect.config_flow.ProtectApiClient.get_nvr",
|
||||
return_value=nvr,
|
||||
"homeassistant.components.unifiprotect.config_flow.ProtectApiClient.get_bootstrap",
|
||||
return_value=bootstrap,
|
||||
), patch(
|
||||
"homeassistant.components.unifiprotect.async_setup_entry",
|
||||
return_value=True,
|
||||
|
@ -710,7 +750,7 @@ async def test_discovered_by_unifi_discovery_direct_connect_on_different_interfa
|
|||
|
||||
|
||||
async def test_discovered_by_unifi_discovery_direct_connect_on_different_interface_resolver_fails(
|
||||
hass: HomeAssistant, nvr: NVR
|
||||
hass: HomeAssistant, bootstrap: Bootstrap, nvr: NVR
|
||||
) -> None:
|
||||
"""Test we can still configure if the resolver fails."""
|
||||
mock_config = MockConfigEntry(
|
||||
|
@ -751,9 +791,10 @@ async def test_discovered_by_unifi_discovery_direct_connect_on_different_interfa
|
|||
|
||||
assert not result["errors"]
|
||||
|
||||
bootstrap.nvr = nvr
|
||||
with patch(
|
||||
"homeassistant.components.unifiprotect.config_flow.ProtectApiClient.get_nvr",
|
||||
return_value=nvr,
|
||||
"homeassistant.components.unifiprotect.config_flow.ProtectApiClient.get_bootstrap",
|
||||
return_value=bootstrap,
|
||||
), patch(
|
||||
"homeassistant.components.unifiprotect.async_setup_entry",
|
||||
return_value=True,
|
||||
|
|
|
@ -6,7 +6,7 @@ from unittest.mock import AsyncMock, patch
|
|||
|
||||
import aiohttp
|
||||
from pyunifiprotect import NotAuthorized, NvrError, ProtectApiClient
|
||||
from pyunifiprotect.data import NVR, Bootstrap, Light
|
||||
from pyunifiprotect.data import NVR, Bootstrap, CloudAccount, Light
|
||||
|
||||
from homeassistant.components.unifiprotect.const import (
|
||||
AUTH_RETRIES,
|
||||
|
@ -132,7 +132,9 @@ async def test_setup_too_old(
|
|||
) -> None:
|
||||
"""Test setup of unifiprotect entry with too old of version of UniFi Protect."""
|
||||
|
||||
ufp.api.get_nvr.return_value = old_nvr
|
||||
old_bootstrap = ufp.api.bootstrap.copy()
|
||||
old_bootstrap.nvr = old_nvr
|
||||
ufp.api.get_bootstrap.return_value = old_bootstrap
|
||||
|
||||
await hass.config_entries.async_setup(ufp.entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
@ -140,6 +142,37 @@ async def test_setup_too_old(
|
|||
assert not ufp.api.update.called
|
||||
|
||||
|
||||
async def test_setup_cloud_account(
|
||||
hass: HomeAssistant,
|
||||
ufp: MockUFPFixture,
|
||||
cloud_account: CloudAccount,
|
||||
hass_ws_client: WebSocketGenerator,
|
||||
) -> None:
|
||||
"""Test setup of unifiprotect entry with cloud account."""
|
||||
|
||||
bootstrap = ufp.api.bootstrap
|
||||
user = bootstrap.users[bootstrap.auth_user_id]
|
||||
user.cloud_account = cloud_account
|
||||
bootstrap.users[bootstrap.auth_user_id] = user
|
||||
ufp.api.get_bootstrap.return_value = bootstrap
|
||||
ws_client = await hass_ws_client(hass)
|
||||
|
||||
await hass.config_entries.async_setup(ufp.entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
assert ufp.entry.state == ConfigEntryState.LOADED
|
||||
|
||||
await ws_client.send_json({"id": 1, "type": "repairs/list_issues"})
|
||||
msg = await ws_client.receive_json()
|
||||
|
||||
assert msg["success"]
|
||||
assert len(msg["result"]["issues"]) > 0
|
||||
issue = None
|
||||
for i in msg["result"]["issues"]:
|
||||
if i["issue_id"] == "cloud_user":
|
||||
issue = i
|
||||
assert issue is not None
|
||||
|
||||
|
||||
async def test_setup_failed_update(hass: HomeAssistant, ufp: MockUFPFixture) -> None:
|
||||
"""Test setup of unifiprotect entry with failed update."""
|
||||
|
||||
|
@ -178,7 +211,7 @@ async def test_setup_failed_update_reauth(
|
|||
async def test_setup_failed_error(hass: HomeAssistant, ufp: MockUFPFixture) -> None:
|
||||
"""Test setup of unifiprotect entry with generic error."""
|
||||
|
||||
ufp.api.get_nvr = AsyncMock(side_effect=NvrError)
|
||||
ufp.api.get_bootstrap = AsyncMock(side_effect=NvrError)
|
||||
|
||||
await hass.config_entries.async_setup(ufp.entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
@ -189,7 +222,7 @@ async def test_setup_failed_error(hass: HomeAssistant, ufp: MockUFPFixture) -> N
|
|||
async def test_setup_failed_auth(hass: HomeAssistant, ufp: MockUFPFixture) -> None:
|
||||
"""Test setup of unifiprotect entry with unauthorized error after multiple retries."""
|
||||
|
||||
ufp.api.get_nvr = AsyncMock(side_effect=NotAuthorized)
|
||||
ufp.api.get_bootstrap = AsyncMock(side_effect=NotAuthorized)
|
||||
|
||||
await hass.config_entries.async_setup(ufp.entry.entry_id)
|
||||
assert ufp.entry.state is ConfigEntryState.SETUP_RETRY
|
||||
|
|
|
@ -216,6 +216,7 @@ async def test_browse_media_root_multiple_consoles(
|
|||
api2.api_path = "/api"
|
||||
api2.base_url = "https://127.0.0.2"
|
||||
api2.connection_host = IPv4Address("127.0.0.2")
|
||||
api2.get_bootstrap = AsyncMock(return_value=bootstrap2)
|
||||
api2.get_nvr = AsyncMock(return_value=bootstrap2.nvr)
|
||||
api2.update = AsyncMock(return_value=bootstrap2)
|
||||
api2.async_disconnect_ws = AsyncMock()
|
||||
|
|
|
@ -5,7 +5,7 @@ from copy import copy
|
|||
from http import HTTPStatus
|
||||
from unittest.mock import Mock
|
||||
|
||||
from pyunifiprotect.data import Version
|
||||
from pyunifiprotect.data import CloudAccount, Version
|
||||
|
||||
from homeassistant.components.repairs.issue_handler import (
|
||||
async_process_repairs_platforms,
|
||||
|
@ -15,6 +15,7 @@ from homeassistant.components.repairs.websocket_api import (
|
|||
RepairsFlowResourceView,
|
||||
)
|
||||
from homeassistant.components.unifiprotect.const import DOMAIN
|
||||
from homeassistant.config_entries import SOURCE_REAUTH
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from .utils import MockUFPFixture, init_entry
|
||||
|
@ -54,7 +55,10 @@ async def test_ea_warning_ignore(
|
|||
data = await resp.json()
|
||||
|
||||
flow_id = data["flow_id"]
|
||||
assert data["description_placeholders"] == {"version": str(version)}
|
||||
assert data["description_placeholders"] == {
|
||||
"learn_more": "https://www.home-assistant.io/integrations/unifiprotect#about-unifi-early-access",
|
||||
"version": str(version),
|
||||
}
|
||||
assert data["step_id"] == "start"
|
||||
|
||||
url = RepairsFlowResourceView.url.format(flow_id=flow_id)
|
||||
|
@ -63,7 +67,10 @@ async def test_ea_warning_ignore(
|
|||
data = await resp.json()
|
||||
|
||||
flow_id = data["flow_id"]
|
||||
assert data["description_placeholders"] == {"version": str(version)}
|
||||
assert data["description_placeholders"] == {
|
||||
"learn_more": "https://www.home-assistant.io/integrations/unifiprotect#about-unifi-early-access",
|
||||
"version": str(version),
|
||||
}
|
||||
assert data["step_id"] == "confirm"
|
||||
|
||||
url = RepairsFlowResourceView.url.format(flow_id=flow_id)
|
||||
|
@ -106,7 +113,10 @@ async def test_ea_warning_fix(
|
|||
data = await resp.json()
|
||||
|
||||
flow_id = data["flow_id"]
|
||||
assert data["description_placeholders"] == {"version": str(version)}
|
||||
assert data["description_placeholders"] == {
|
||||
"learn_more": "https://www.home-assistant.io/integrations/unifiprotect#about-unifi-early-access",
|
||||
"version": str(version),
|
||||
}
|
||||
assert data["step_id"] == "start"
|
||||
|
||||
new_nvr = copy(ufp.api.bootstrap.nvr)
|
||||
|
@ -125,3 +135,50 @@ async def test_ea_warning_fix(
|
|||
data = await resp.json()
|
||||
|
||||
assert data["type"] == "create_entry"
|
||||
|
||||
|
||||
async def test_cloud_user_fix(
|
||||
hass: HomeAssistant,
|
||||
ufp: MockUFPFixture,
|
||||
cloud_account: CloudAccount,
|
||||
hass_client: ClientSessionGenerator,
|
||||
hass_ws_client: WebSocketGenerator,
|
||||
) -> None:
|
||||
"""Test EA warning is created if using prerelease version of Protect."""
|
||||
|
||||
ufp.api.bootstrap.nvr.version = Version("2.2.6")
|
||||
user = ufp.api.bootstrap.users[ufp.api.bootstrap.auth_user_id]
|
||||
user.cloud_account = cloud_account
|
||||
ufp.api.bootstrap.users[ufp.api.bootstrap.auth_user_id] = user
|
||||
await init_entry(hass, ufp, [])
|
||||
await async_process_repairs_platforms(hass)
|
||||
ws_client = await hass_ws_client(hass)
|
||||
client = await hass_client()
|
||||
|
||||
await ws_client.send_json({"id": 1, "type": "repairs/list_issues"})
|
||||
msg = await ws_client.receive_json()
|
||||
|
||||
assert msg["success"]
|
||||
assert len(msg["result"]["issues"]) > 0
|
||||
issue = None
|
||||
for i in msg["result"]["issues"]:
|
||||
if i["issue_id"] == "cloud_user":
|
||||
issue = i
|
||||
assert issue is not None
|
||||
|
||||
url = RepairsFlowIndexView.url
|
||||
resp = await client.post(url, json={"handler": DOMAIN, "issue_id": "cloud_user"})
|
||||
assert resp.status == HTTPStatus.OK
|
||||
data = await resp.json()
|
||||
|
||||
flow_id = data["flow_id"]
|
||||
assert data["step_id"] == "confirm"
|
||||
|
||||
url = RepairsFlowResourceView.url.format(flow_id=flow_id)
|
||||
resp = await client.post(url)
|
||||
assert resp.status == HTTPStatus.OK
|
||||
data = await resp.json()
|
||||
|
||||
assert data["type"] == "create_entry"
|
||||
await hass.async_block_till_done()
|
||||
assert any(ufp.entry.async_get_active_flows(hass, {SOURCE_REAUTH}))
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue