Detect Early Access versions of UniFi Protect and Warn User (#81758)

This commit is contained in:
Christopher Bailey 2022-11-13 10:34:29 -05:00 committed by GitHub
parent 8252d7f3b5
commit bfd2eb50b2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 368 additions and 59 deletions

View file

@ -5,31 +5,19 @@ import asyncio
from datetime import timedelta from datetime import timedelta
import logging import logging
from aiohttp import CookieJar
from aiohttp.client_exceptions import ServerDisconnectedError from aiohttp.client_exceptions import ServerDisconnectedError
from pyunifiprotect import ProtectApiClient
from pyunifiprotect.exceptions import ClientError, NotAuthorized from pyunifiprotect.exceptions import ClientError, NotAuthorized
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ( from homeassistant.const import EVENT_HOMEASSISTANT_STOP
CONF_HOST,
CONF_PASSWORD,
CONF_PORT,
CONF_USERNAME,
CONF_VERIFY_SSL,
EVENT_HOMEASSISTANT_STOP,
)
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from homeassistant.helpers import device_registry as dr, issue_registry as ir from homeassistant.helpers import device_registry as dr, issue_registry as ir
from homeassistant.helpers.aiohttp_client import async_create_clientsession
from homeassistant.helpers.issue_registry import IssueSeverity from homeassistant.helpers.issue_registry import IssueSeverity
from .const import ( from .const import (
CONF_ALL_UPDATES, CONF_ALLOW_EA,
CONF_OVERRIDE_CHOST,
DEFAULT_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL,
DEVICES_FOR_SUBSCRIBE,
DEVICES_THAT_ADOPT, DEVICES_THAT_ADOPT,
DOMAIN, DOMAIN,
MIN_REQUIRED_PROTECT_V, MIN_REQUIRED_PROTECT_V,
@ -40,7 +28,11 @@ from .data import ProtectData, async_ufp_instance_for_config_entry_ids
from .discovery import async_start_discovery from .discovery import async_start_discovery
from .migrate import async_migrate_data from .migrate import async_migrate_data
from .services import async_cleanup_services, async_setup_services from .services import async_cleanup_services, async_setup_services
from .utils import _async_unifi_mac_from_hass, async_get_devices from .utils import (
_async_unifi_mac_from_hass,
async_create_api_client,
async_get_devices,
)
from .views import ThumbnailProxyView, VideoProxyView from .views import ThumbnailProxyView, VideoProxyView
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -52,19 +44,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up the UniFi Protect config entries.""" """Set up the UniFi Protect config entries."""
async_start_discovery(hass) async_start_discovery(hass)
session = async_create_clientsession(hass, cookie_jar=CookieJar(unsafe=True)) protect = async_create_api_client(hass, entry)
protect = ProtectApiClient(
host=entry.data[CONF_HOST],
port=entry.data[CONF_PORT],
username=entry.data[CONF_USERNAME],
password=entry.data[CONF_PASSWORD],
verify_ssl=entry.data[CONF_VERIFY_SSL],
session=session,
subscribed_models=DEVICES_FOR_SUBSCRIBE,
override_connection_host=entry.options.get(CONF_OVERRIDE_CHOST, False),
ignore_stats=not entry.options.get(CONF_ALL_UPDATES, False),
ignore_unadopted=False,
)
_LOGGER.debug("Connect to UniFi Protect") _LOGGER.debug("Connect to UniFi Protect")
data_service = ProtectData(hass, protect, SCAN_INTERVAL, entry) data_service = ProtectData(hass, protect, SCAN_INTERVAL, entry)
@ -83,42 +63,75 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
) )
return False return False
await async_migrate_data(hass, entry, protect)
if entry.unique_id is None: if entry.unique_id is None:
hass.config_entries.async_update_entry(entry, unique_id=nvr_info.mac) hass.config_entries.async_update_entry(entry, unique_id=nvr_info.mac)
await data_service.async_setup()
if not data_service.last_update_success:
raise ConfigEntryNotReady
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = data_service hass.data.setdefault(DOMAIN, {})[entry.entry_id] = data_service
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
async_setup_services(hass)
hass.http.register_view(ThumbnailProxyView(hass))
hass.http.register_view(VideoProxyView(hass))
entry.async_on_unload(entry.add_update_listener(_async_options_updated)) entry.async_on_unload(entry.add_update_listener(_async_options_updated))
entry.async_on_unload( entry.async_on_unload(
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, data_service.async_stop) hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, data_service.async_stop)
) )
if await data_service.api.bootstrap.get_is_prerelease(): if (
protect_version = data_service.api.bootstrap.nvr.version not entry.options.get(CONF_ALLOW_EA, False)
and await nvr_info.get_is_prerelease()
):
ir.async_create_issue( ir.async_create_issue(
hass, hass,
DOMAIN, DOMAIN,
f"ea_warning_{protect_version}", "ea_warning",
is_fixable=False, is_fixable=True,
is_persistent=False, is_persistent=True,
learn_more_url="https://www.home-assistant.io/integrations/unifiprotect#about-unifi-early-access", learn_more_url="https://www.home-assistant.io/integrations/unifiprotect#about-unifi-early-access",
severity=IssueSeverity.WARNING, severity=IssueSeverity.WARNING,
translation_key="ea_warning", translation_key="ea_warning",
translation_placeholders={"version": str(protect_version)}, translation_placeholders={"version": str(nvr_info.version)},
data={"entry_id": entry.entry_id},
) )
try:
await _async_setup_entry(hass, entry, data_service)
except Exception as err:
if await nvr_info.get_is_prerelease():
# If they are running a pre-release, its quite common for setup
# to fail so we want to create a repair issue for them so its
# obvious what the problem is.
ir.async_create_issue(
hass,
DOMAIN,
f"ea_setup_failed_{nvr_info.version}",
is_fixable=False,
is_persistent=False,
learn_more_url="https://www.home-assistant.io/integrations/unifiprotect#about-unifi-early-access",
severity=IssueSeverity.ERROR,
translation_key="ea_setup_failed",
translation_placeholders={
"error": str(err),
"version": str(nvr_info.version),
},
)
ir.async_delete_issue(hass, DOMAIN, "ea_warning")
_LOGGER.exception("Error setting up UniFi Protect integration: %s", err)
raise
return True return True
async def _async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, data_service: ProtectData
) -> None:
await async_migrate_data(hass, entry, data_service.api)
await data_service.async_setup()
if not data_service.last_update_success:
raise ConfigEntryNotReady
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
async_setup_services(hass)
hass.http.register_view(ThumbnailProxyView(hass))
hass.http.register_view(VideoProxyView(hass))
async def _async_options_updated(hass: HomeAssistant, entry: ConfigEntry) -> None: async def _async_options_updated(hass: HomeAssistant, entry: ConfigEntry) -> None:
"""Update options.""" """Update options."""
await hass.config_entries.async_reload(entry.entry_id) await hass.config_entries.async_reload(entry.entry_id)
@ -126,6 +139,7 @@ async def _async_options_updated(hass: HomeAssistant, entry: ConfigEntry) -> Non
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload UniFi Protect config entry.""" """Unload UniFi Protect config entry."""
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS): if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
data: ProtectData = hass.data[DOMAIN][entry.entry_id] data: ProtectData = hass.data[DOMAIN][entry.entry_id]
await data.async_stop() await data.async_stop()

View file

@ -34,6 +34,7 @@ from homeassistant.util.network import is_ip_address
from .const import ( from .const import (
CONF_ALL_UPDATES, CONF_ALL_UPDATES,
CONF_ALLOW_EA,
CONF_DISABLE_RTSP, CONF_DISABLE_RTSP,
CONF_MAX_MEDIA, CONF_MAX_MEDIA,
CONF_OVERRIDE_CHOST, CONF_OVERRIDE_CHOST,
@ -224,6 +225,7 @@ class ProtectFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
CONF_ALL_UPDATES: False, CONF_ALL_UPDATES: False,
CONF_OVERRIDE_CHOST: False, CONF_OVERRIDE_CHOST: False,
CONF_MAX_MEDIA: DEFAULT_MAX_MEDIA, CONF_MAX_MEDIA: DEFAULT_MAX_MEDIA,
CONF_ALLOW_EA: False,
}, },
) )
@ -392,6 +394,10 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
CONF_MAX_MEDIA, DEFAULT_MAX_MEDIA CONF_MAX_MEDIA, DEFAULT_MAX_MEDIA
), ),
): vol.All(vol.Coerce(int), vol.Range(min=100, max=10000)), ): vol.All(vol.Coerce(int), vol.Range(min=100, max=10000)),
vol.Optional(
CONF_ALLOW_EA,
default=self.config_entry.options.get(CONF_ALLOW_EA, False),
): bool,
} }
), ),
) )

View file

@ -20,6 +20,7 @@ CONF_DISABLE_RTSP = "disable_rtsp"
CONF_ALL_UPDATES = "all_updates" CONF_ALL_UPDATES = "all_updates"
CONF_OVERRIDE_CHOST = "override_connection_host" CONF_OVERRIDE_CHOST = "override_connection_host"
CONF_MAX_MEDIA = "max_media" CONF_MAX_MEDIA = "max_media"
CONF_ALLOW_EA = "allow_ea"
CONFIG_OPTIONS = [ CONFIG_OPTIONS = [
CONF_ALL_UPDATES, CONF_ALL_UPDATES,

View file

@ -5,7 +5,7 @@
"config_flow": true, "config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/unifiprotect", "documentation": "https://www.home-assistant.io/integrations/unifiprotect",
"requirements": ["pyunifiprotect==4.4.1", "unifi-discovery==1.1.7"], "requirements": ["pyunifiprotect==4.4.1", "unifi-discovery==1.1.7"],
"dependencies": ["http"], "dependencies": ["http", "repairs"],
"codeowners": ["@briis", "@AngellusMortis", "@bdraco"], "codeowners": ["@briis", "@AngellusMortis", "@bdraco"],
"quality_scale": "platinum", "quality_scale": "platinum",
"iot_class": "local_push", "iot_class": "local_push",

View file

@ -0,0 +1,96 @@
"""unifiprotect.repairs."""
from __future__ import annotations
from typing import cast
from pyunifiprotect import ProtectApiClient
import voluptuous as vol
from homeassistant import data_entry_flow
from homeassistant.components.repairs import ConfirmRepairFlow, RepairsFlow
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.issue_registry import async_get as async_get_issue_registry
from .const import CONF_ALLOW_EA
from .utils import async_create_api_client
class EAConfirm(RepairsFlow):
"""Handler for an issue fixing flow."""
_api: ProtectApiClient
_entry: ConfigEntry
def __init__(self, api: ProtectApiClient, entry: ConfigEntry) -> None:
"""Create flow."""
self._api = api
self._entry = entry
super().__init__()
@callback
def _async_get_placeholders(self) -> dict[str, str] | None:
issue_registry = async_get_issue_registry(self.hass)
description_placeholders = None
if issue := issue_registry.async_get_issue(self.handler, self.issue_id):
description_placeholders = issue.translation_placeholders
return description_placeholders
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_start()
async def async_step_start(
self, user_input: dict[str, str] | None = None
) -> data_entry_flow.FlowResult:
"""Handle the confirm step of a fix flow."""
if user_input is None:
placeholders = self._async_get_placeholders()
return self.async_show_form(
step_id="start",
data_schema=vol.Schema({}),
description_placeholders=placeholders,
)
nvr = await self._api.get_nvr()
if await nvr.get_is_prerelease():
return await self.async_step_confirm()
await self.hass.config_entries.async_reload(self._entry.entry_id)
return self.async_create_entry(title="", data={})
async def async_step_confirm(
self, user_input: dict[str, str] | None = None
) -> data_entry_flow.FlowResult:
"""Handle the confirm step of a fix flow."""
if user_input is not None:
options = dict(self._entry.options)
options[CONF_ALLOW_EA] = True
self.hass.config_entries.async_update_entry(self._entry, options=options)
return self.async_create_entry(title="", data={})
placeholders = self._async_get_placeholders()
return self.async_show_form(
step_id="confirm",
data_schema=vol.Schema({}),
description_placeholders=placeholders,
)
async def async_create_fix_flow(
hass: HomeAssistant,
issue_id: str,
data: dict[str, str | int | float | None] | None,
) -> RepairsFlow:
"""Create flow."""
if data is not None and issue_id == "ea_warning":
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 EAConfirm(api, entry)
return ConfirmRepairFlow()

View file

@ -50,7 +50,8 @@
"disable_rtsp": "Disable the RTSP stream", "disable_rtsp": "Disable the RTSP stream",
"all_updates": "Realtime metrics (WARNING: Greatly increases CPU usage)", "all_updates": "Realtime metrics (WARNING: Greatly increases CPU usage)",
"override_connection_host": "Override Connection Host", "override_connection_host": "Override Connection Host",
"max_media": "Max number of event to load for Media Browser (increases RAM usage)" "max_media": "Max number of event to load for Media Browser (increases RAM usage)",
"allow_ea": "Allow Early Access versions of Protect (WARNING: Will mark your integration as unsupported)"
} }
} }
} }
@ -58,7 +59,22 @@
"issues": { "issues": {
"ea_warning": { "ea_warning": {
"title": "UniFi Protect v{version} is an Early Access version", "title": "UniFi Protect v{version} is an Early Access version",
"description": "You are using v{version} of UniFi Protect which is an Early Access version. Early Access versions are not supported by Home Assistant and may cause your UniFi Protect integration to break or not work as expected." "fix_flow": {
"step": {
"start": {
"title": "v{version} is an Early Access version",
"description": "You are using v{version} of UniFi Protect which is an Early Access version. [Early Access versions are not supported by Home Assistant](https://www.home-assistant.io/integrations/unifiprotect#about-unifi-early-access) and it is recommended to go back to a stable release as soon as possible.\n\nBy submitting this form you have either [downgraded UniFi Protect](https://www.home-assistant.io/integrations/unifiprotect#downgrading-unifi-protect) or you agree to run an unsupported version of UniFi Protect."
},
"confirm": {
"title": "v{version} is an Early Access version",
"description": "Are you sure you want to run unsupported versions of UniFi Protect? This may cause your Home Assistant integration to break."
}
}
}
},
"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}"
} }
} }
} }

View file

@ -42,21 +42,33 @@
} }
}, },
"issues": { "issues": {
"ea_setup_failed": {
"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}",
"title": "Setup error using Early Access version"
},
"ea_warning": { "ea_warning": {
"description": "You are using v{version} of UniFi Protect which is an Early Access version. Early Access versions are not supported by Home Assistant and may cause your UniFi Protect integration to break or not work as expected.", "fix_flow": {
"step": {
"confirm": {
"description": "Are you sure you want to run unsupported versions of UniFi Protect? This may cause your Home Assistant integration to break.",
"title": "v{version} is an Early Access version"
},
"start": {
"description": "You are using v{version} of UniFi Protect which is an Early Access version. [Early Access versions are not supported by Home Assistant](https://www.home-assistant.io/integrations/unifiprotect#about-unifi-early-access) and it is recommended to go back to a stable release as soon as possible.\n\nBy submitting this form you have either [downgraded UniFi Protect](https://www.home-assistant.io/integrations/unifiprotect#downgrading-unifi-protect) or you agree to run an unsupported version of UniFi Protect.",
"title": "v{version} is an Early Access version"
}
}
},
"title": "UniFi Protect v{version} is an Early Access version" "title": "UniFi Protect v{version} is an Early Access version"
} }
}, },
"options": { "options": {
"error": {
"invalid_mac_list": "Must be a list of MAC addresses seperated by commas"
},
"step": { "step": {
"init": { "init": {
"data": { "data": {
"all_updates": "Realtime metrics (WARNING: Greatly increases CPU usage)", "all_updates": "Realtime metrics (WARNING: Greatly increases CPU usage)",
"allow_ea": "Allow Early Access versions of Protect (WARNING: Will mark your integration as unsupported)",
"disable_rtsp": "Disable the RTSP stream", "disable_rtsp": "Disable the RTSP stream",
"ignored_devices": "Comma separated list of MAC addresses of devices to ignore",
"max_media": "Max number of event to load for Media Browser (increases RAM usage)", "max_media": "Max number of event to load for Media Browser (increases RAM usage)",
"override_connection_host": "Override Connection Host" "override_connection_host": "Override Connection Host"
}, },

View file

@ -7,6 +7,8 @@ from enum import Enum
import socket import socket
from typing import Any from typing import Any
from aiohttp import CookieJar
from pyunifiprotect import ProtectApiClient
from pyunifiprotect.data import ( from pyunifiprotect.data import (
Bootstrap, Bootstrap,
Light, Light,
@ -16,9 +18,23 @@ from pyunifiprotect.data import (
) )
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
CONF_HOST,
CONF_PASSWORD,
CONF_PORT,
CONF_USERNAME,
CONF_VERIFY_SSL,
)
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.aiohttp_client import async_create_clientsession
from .const import DOMAIN, ModelType from .const import (
CONF_ALL_UPDATES,
CONF_OVERRIDE_CHOST,
DEVICES_FOR_SUBSCRIBE,
DOMAIN,
ModelType,
)
def get_nested_attr(obj: Any, attr: str) -> Any: def get_nested_attr(obj: Any, attr: str) -> Any:
@ -106,3 +122,24 @@ def async_dispatch_id(entry: ConfigEntry, dispatch: str) -> str:
"""Generate entry specific dispatch ID.""" """Generate entry specific dispatch ID."""
return f"{DOMAIN}.{entry.entry_id}.{dispatch}" return f"{DOMAIN}.{entry.entry_id}.{dispatch}"
@callback
def async_create_api_client(
hass: HomeAssistant, entry: ConfigEntry
) -> ProtectApiClient:
"""Create ProtectApiClient from config entry."""
session = async_create_clientsession(hass, cookie_jar=CookieJar(unsafe=True))
return ProtectApiClient(
host=entry.data[CONF_HOST],
port=entry.data[CONF_PORT],
username=entry.data[CONF_USERNAME],
password=entry.data[CONF_PASSWORD],
verify_ssl=entry.data[CONF_VERIFY_SSL],
session=session,
subscribed_models=DEVICES_FOR_SUBSCRIBE,
override_connection_host=entry.options.get(CONF_OVERRIDE_CHOST, False),
ignore_stats=not entry.options.get(CONF_ALL_UPDATES, False),
ignore_unadopted=False,
)

View file

@ -128,7 +128,7 @@ def mock_entry(
"""Mock ProtectApiClient for testing.""" """Mock ProtectApiClient for testing."""
with _patch_discovery(no_device=True), patch( with _patch_discovery(no_device=True), patch(
"homeassistant.components.unifiprotect.ProtectApiClient" "homeassistant.components.unifiprotect.utils.ProtectApiClient"
) as mock_api: ) as mock_api:
ufp_config_entry.add_to_hass(hass) ufp_config_entry.add_to_hass(hass)

View file

@ -246,7 +246,7 @@ async def test_form_options(hass: HomeAssistant, ufp_client: ProtectApiClient) -
mock_config.add_to_hass(hass) mock_config.add_to_hass(hass)
with _patch_discovery(), patch( with _patch_discovery(), patch(
"homeassistant.components.unifiprotect.ProtectApiClient" "homeassistant.components.unifiprotect.utils.ProtectApiClient"
) as mock_api: ) as mock_api:
mock_api.return_value = ufp_client mock_api.return_value = ufp_client
@ -274,6 +274,7 @@ async def test_form_options(hass: HomeAssistant, ufp_client: ProtectApiClient) -
"disable_rtsp": True, "disable_rtsp": True,
"override_connection_host": True, "override_connection_host": True,
"max_media": 1000, "max_media": 1000,
"allow_ea": False,
} }
await hass.config_entries.async_unload(mock_config.entry_id) await hass.config_entries.async_unload(mock_config.entry_id)

View file

@ -72,7 +72,9 @@ async def test_setup_multiple(
nvr.id nvr.id
ufp.api.get_nvr = AsyncMock(return_value=nvr) ufp.api.get_nvr = AsyncMock(return_value=nvr)
with patch("homeassistant.components.unifiprotect.ProtectApiClient") as mock_api: with patch(
"homeassistant.components.unifiprotect.utils.ProtectApiClient"
) as mock_api:
mock_config = MockConfigEntry( mock_config = MockConfigEntry(
domain=DOMAIN, domain=DOMAIN,
data={ data={
@ -194,7 +196,7 @@ async def test_setup_starts_discovery(
): ):
"""Test setting up will start discovery.""" """Test setting up will start discovery."""
with _patch_discovery(), patch( with _patch_discovery(), patch(
"homeassistant.components.unifiprotect.ProtectApiClient" "homeassistant.components.unifiprotect.utils.ProtectApiClient"
) as mock_api: ) as mock_api:
ufp_config_entry.add_to_hass(hass) ufp_config_entry.add_to_hass(hass)
mock_api.return_value = ufp_client mock_api.return_value = ufp_client

View file

@ -222,7 +222,9 @@ async def test_browse_media_root_multiple_consoles(
api2.update = AsyncMock(return_value=bootstrap2) api2.update = AsyncMock(return_value=bootstrap2)
api2.async_disconnect_ws = AsyncMock() api2.async_disconnect_ws = AsyncMock()
with patch("homeassistant.components.unifiprotect.ProtectApiClient") as mock_api: with patch(
"homeassistant.components.unifiprotect.utils.ProtectApiClient"
) as mock_api:
mock_config = MockConfigEntry( mock_config = MockConfigEntry(
domain=DOMAIN, domain=DOMAIN,
data={ data={
@ -285,7 +287,9 @@ async def test_browse_media_root_multiple_consoles_only_one_media(
api2.update = AsyncMock(return_value=bootstrap2) api2.update = AsyncMock(return_value=bootstrap2)
api2.async_disconnect_ws = AsyncMock() api2.async_disconnect_ws = AsyncMock()
with patch("homeassistant.components.unifiprotect.ProtectApiClient") as mock_api: with patch(
"homeassistant.components.unifiprotect.utils.ProtectApiClient"
) as mock_api:
mock_config = MockConfigEntry( mock_config = MockConfigEntry(
domain=DOMAIN, domain=DOMAIN,
data={ data={

View file

@ -0,0 +1,120 @@
"""Test repairs for unifiprotect."""
from __future__ import annotations
from copy import copy
from http import HTTPStatus
from unittest.mock import Mock
from pyunifiprotect.data import Version
from homeassistant.components.repairs.issue_handler import (
async_process_repairs_platforms,
)
from homeassistant.components.repairs.websocket_api import (
RepairsFlowIndexView,
RepairsFlowResourceView,
)
from homeassistant.components.unifiprotect.const import DOMAIN
from homeassistant.core import HomeAssistant
from .utils import MockUFPFixture, init_entry
async def test_ea_warning_ignore(
hass: HomeAssistant,
ufp: MockUFPFixture,
hass_client,
hass_ws_client,
):
"""Test EA warning is created if using prerelease version of Protect."""
version = ufp.api.bootstrap.nvr.version
assert version.is_prerelease
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"]) == 1
issue = msg["result"]["issues"][0]
assert issue["issue_id"] == "ea_warning"
url = RepairsFlowIndexView.url
resp = await client.post(url, json={"handler": DOMAIN, "issue_id": "ea_warning"})
assert resp.status == HTTPStatus.OK
data = await resp.json()
flow_id = data["flow_id"]
assert data["description_placeholders"] == {"version": str(version)}
assert data["step_id"] == "start"
url = RepairsFlowResourceView.url.format(flow_id=flow_id)
resp = await client.post(url)
assert resp.status == HTTPStatus.OK
data = await resp.json()
flow_id = data["flow_id"]
assert data["description_placeholders"] == {"version": str(version)}
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"
async def test_ea_warning_fix(
hass: HomeAssistant,
ufp: MockUFPFixture,
hass_client,
hass_ws_client,
):
"""Test EA warning is created if using prerelease version of Protect."""
version = ufp.api.bootstrap.nvr.version
assert version.is_prerelease
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"]) == 1
issue = msg["result"]["issues"][0]
assert issue["issue_id"] == "ea_warning"
url = RepairsFlowIndexView.url
resp = await client.post(url, json={"handler": DOMAIN, "issue_id": "ea_warning"})
assert resp.status == HTTPStatus.OK
data = await resp.json()
flow_id = data["flow_id"]
assert data["description_placeholders"] == {"version": str(version)}
assert data["step_id"] == "start"
new_nvr = copy(ufp.api.bootstrap.nvr)
new_nvr.version = Version("2.2.6")
mock_msg = Mock()
mock_msg.changed_data = {"version": "2.2.6"}
mock_msg.new_obj = new_nvr
ufp.api.bootstrap.nvr = new_nvr
ufp.ws_msg(mock_msg)
await hass.async_block_till_done()
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"