diff --git a/homeassistant/components/unifiprotect/__init__.py b/homeassistant/components/unifiprotect/__init__.py index 4d060161fae..94bd3722cfa 100644 --- a/homeassistant/components/unifiprotect/__init__.py +++ b/homeassistant/components/unifiprotect/__init__.py @@ -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, diff --git a/homeassistant/components/unifiprotect/config_flow.py b/homeassistant/components/unifiprotect/config_flow.py index ec756118eb5..29718c8ef35 100644 --- a/homeassistant/components/unifiprotect/config_flow.py +++ b/homeassistant/components/unifiprotect/config_flow.py @@ -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: diff --git a/homeassistant/components/unifiprotect/repairs.py b/homeassistant/components/unifiprotect/repairs.py index 49473744d06..ddc0a257c14 100644 --- a/homeassistant/components/unifiprotect/repairs.py +++ b/homeassistant/components/unifiprotect/repairs.py @@ -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() diff --git a/homeassistant/components/unifiprotect/strings.json b/homeassistant/components/unifiprotect/strings.json index a345a504c42..eccf5829332 100644 --- a/homeassistant/components/unifiprotect/strings.json +++ b/homeassistant/components/unifiprotect/strings.json @@ -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": { diff --git a/tests/components/unifiprotect/conftest.py b/tests/components/unifiprotect/conftest.py index fcfac60fa71..37e7dceecf5 100644 --- a/tests/components/unifiprotect/conftest.py +++ b/tests/components/unifiprotect/conftest.py @@ -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, + ) diff --git a/tests/components/unifiprotect/test_config_flow.py b/tests/components/unifiprotect/test_config_flow.py index 7d907a6ba99..a9ff98fc681 100644 --- a/tests/components/unifiprotect/test_config_flow.py +++ b/tests/components/unifiprotect/test_config_flow.py @@ -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, diff --git a/tests/components/unifiprotect/test_init.py b/tests/components/unifiprotect/test_init.py index d1dd4379515..35477f1e56d 100644 --- a/tests/components/unifiprotect/test_init.py +++ b/tests/components/unifiprotect/test_init.py @@ -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 diff --git a/tests/components/unifiprotect/test_media_source.py b/tests/components/unifiprotect/test_media_source.py index 27b06bea2d2..90d09ac36d6 100644 --- a/tests/components/unifiprotect/test_media_source.py +++ b/tests/components/unifiprotect/test_media_source.py @@ -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() diff --git a/tests/components/unifiprotect/test_repairs.py b/tests/components/unifiprotect/test_repairs.py index f68ebd9c8c6..12701604306 100644 --- a/tests/components/unifiprotect/test_repairs.py +++ b/tests/components/unifiprotect/test_repairs.py @@ -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}))