Detect Early Access versions of UniFi Protect and Warn User (#81758)
This commit is contained in:
parent
8252d7f3b5
commit
bfd2eb50b2
13 changed files with 368 additions and 59 deletions
|
@ -5,31 +5,19 @@ import asyncio
|
|||
from datetime import timedelta
|
||||
import logging
|
||||
|
||||
from aiohttp import CookieJar
|
||||
from aiohttp.client_exceptions import ServerDisconnectedError
|
||||
from pyunifiprotect import ProtectApiClient
|
||||
from pyunifiprotect.exceptions import ClientError, NotAuthorized
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
CONF_HOST,
|
||||
CONF_PASSWORD,
|
||||
CONF_PORT,
|
||||
CONF_USERNAME,
|
||||
CONF_VERIFY_SSL,
|
||||
EVENT_HOMEASSISTANT_STOP,
|
||||
)
|
||||
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
|
||||
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 .const import (
|
||||
CONF_ALL_UPDATES,
|
||||
CONF_OVERRIDE_CHOST,
|
||||
CONF_ALLOW_EA,
|
||||
DEFAULT_SCAN_INTERVAL,
|
||||
DEVICES_FOR_SUBSCRIBE,
|
||||
DEVICES_THAT_ADOPT,
|
||||
DOMAIN,
|
||||
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 .migrate import async_migrate_data
|
||||
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
|
||||
|
||||
_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."""
|
||||
|
||||
async_start_discovery(hass)
|
||||
session = async_create_clientsession(hass, cookie_jar=CookieJar(unsafe=True))
|
||||
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,
|
||||
)
|
||||
protect = async_create_api_client(hass, entry)
|
||||
_LOGGER.debug("Connect to UniFi Protect")
|
||||
data_service = ProtectData(hass, protect, SCAN_INTERVAL, entry)
|
||||
|
||||
|
@ -83,42 +63,75 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||
)
|
||||
return False
|
||||
|
||||
await async_migrate_data(hass, entry, protect)
|
||||
if entry.unique_id is None:
|
||||
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
|
||||
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(
|
||||
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, data_service.async_stop)
|
||||
)
|
||||
|
||||
if await data_service.api.bootstrap.get_is_prerelease():
|
||||
protect_version = data_service.api.bootstrap.nvr.version
|
||||
if (
|
||||
not entry.options.get(CONF_ALLOW_EA, False)
|
||||
and await nvr_info.get_is_prerelease()
|
||||
):
|
||||
ir.async_create_issue(
|
||||
hass,
|
||||
DOMAIN,
|
||||
f"ea_warning_{protect_version}",
|
||||
is_fixable=False,
|
||||
is_persistent=False,
|
||||
"ea_warning",
|
||||
is_fixable=True,
|
||||
is_persistent=True,
|
||||
learn_more_url="https://www.home-assistant.io/integrations/unifiprotect#about-unifi-early-access",
|
||||
severity=IssueSeverity.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
|
||||
|
||||
|
||||
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:
|
||||
"""Update options."""
|
||||
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:
|
||||
"""Unload UniFi Protect config entry."""
|
||||
|
||||
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
|
||||
data: ProtectData = hass.data[DOMAIN][entry.entry_id]
|
||||
await data.async_stop()
|
||||
|
|
|
@ -34,6 +34,7 @@ from homeassistant.util.network import is_ip_address
|
|||
|
||||
from .const import (
|
||||
CONF_ALL_UPDATES,
|
||||
CONF_ALLOW_EA,
|
||||
CONF_DISABLE_RTSP,
|
||||
CONF_MAX_MEDIA,
|
||||
CONF_OVERRIDE_CHOST,
|
||||
|
@ -224,6 +225,7 @@ class ProtectFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
CONF_ALL_UPDATES: False,
|
||||
CONF_OVERRIDE_CHOST: False,
|
||||
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
|
||||
),
|
||||
): 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,
|
||||
}
|
||||
),
|
||||
)
|
||||
|
|
|
@ -20,6 +20,7 @@ CONF_DISABLE_RTSP = "disable_rtsp"
|
|||
CONF_ALL_UPDATES = "all_updates"
|
||||
CONF_OVERRIDE_CHOST = "override_connection_host"
|
||||
CONF_MAX_MEDIA = "max_media"
|
||||
CONF_ALLOW_EA = "allow_ea"
|
||||
|
||||
CONFIG_OPTIONS = [
|
||||
CONF_ALL_UPDATES,
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/unifiprotect",
|
||||
"requirements": ["pyunifiprotect==4.4.1", "unifi-discovery==1.1.7"],
|
||||
"dependencies": ["http"],
|
||||
"dependencies": ["http", "repairs"],
|
||||
"codeowners": ["@briis", "@AngellusMortis", "@bdraco"],
|
||||
"quality_scale": "platinum",
|
||||
"iot_class": "local_push",
|
||||
|
|
96
homeassistant/components/unifiprotect/repairs.py
Normal file
96
homeassistant/components/unifiprotect/repairs.py
Normal 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()
|
|
@ -50,7 +50,8 @@
|
|||
"disable_rtsp": "Disable the RTSP stream",
|
||||
"all_updates": "Realtime metrics (WARNING: Greatly increases CPU usage)",
|
||||
"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": {
|
||||
"ea_warning": {
|
||||
"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}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,21 +42,33 @@
|
|||
}
|
||||
},
|
||||
"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": {
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"error": {
|
||||
"invalid_mac_list": "Must be a list of MAC addresses seperated by commas"
|
||||
},
|
||||
"step": {
|
||||
"init": {
|
||||
"data": {
|
||||
"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",
|
||||
"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)",
|
||||
"override_connection_host": "Override Connection Host"
|
||||
},
|
||||
|
|
|
@ -7,6 +7,8 @@ from enum import Enum
|
|||
import socket
|
||||
from typing import Any
|
||||
|
||||
from aiohttp import CookieJar
|
||||
from pyunifiprotect import ProtectApiClient
|
||||
from pyunifiprotect.data import (
|
||||
Bootstrap,
|
||||
Light,
|
||||
|
@ -16,9 +18,23 @@ from pyunifiprotect.data import (
|
|||
)
|
||||
|
||||
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.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:
|
||||
|
@ -106,3 +122,24 @@ def async_dispatch_id(entry: ConfigEntry, dispatch: str) -> str:
|
|||
"""Generate entry specific dispatch ID."""
|
||||
|
||||
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,
|
||||
)
|
||||
|
|
|
@ -128,7 +128,7 @@ def mock_entry(
|
|||
"""Mock ProtectApiClient for testing."""
|
||||
|
||||
with _patch_discovery(no_device=True), patch(
|
||||
"homeassistant.components.unifiprotect.ProtectApiClient"
|
||||
"homeassistant.components.unifiprotect.utils.ProtectApiClient"
|
||||
) as mock_api:
|
||||
ufp_config_entry.add_to_hass(hass)
|
||||
|
||||
|
|
|
@ -246,7 +246,7 @@ async def test_form_options(hass: HomeAssistant, ufp_client: ProtectApiClient) -
|
|||
mock_config.add_to_hass(hass)
|
||||
|
||||
with _patch_discovery(), patch(
|
||||
"homeassistant.components.unifiprotect.ProtectApiClient"
|
||||
"homeassistant.components.unifiprotect.utils.ProtectApiClient"
|
||||
) as mock_api:
|
||||
mock_api.return_value = ufp_client
|
||||
|
||||
|
@ -274,6 +274,7 @@ async def test_form_options(hass: HomeAssistant, ufp_client: ProtectApiClient) -
|
|||
"disable_rtsp": True,
|
||||
"override_connection_host": True,
|
||||
"max_media": 1000,
|
||||
"allow_ea": False,
|
||||
}
|
||||
await hass.config_entries.async_unload(mock_config.entry_id)
|
||||
|
||||
|
|
|
@ -72,7 +72,9 @@ async def test_setup_multiple(
|
|||
nvr.id
|
||||
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(
|
||||
domain=DOMAIN,
|
||||
data={
|
||||
|
@ -194,7 +196,7 @@ async def test_setup_starts_discovery(
|
|||
):
|
||||
"""Test setting up will start discovery."""
|
||||
with _patch_discovery(), patch(
|
||||
"homeassistant.components.unifiprotect.ProtectApiClient"
|
||||
"homeassistant.components.unifiprotect.utils.ProtectApiClient"
|
||||
) as mock_api:
|
||||
ufp_config_entry.add_to_hass(hass)
|
||||
mock_api.return_value = ufp_client
|
||||
|
|
|
@ -222,7 +222,9 @@ async def test_browse_media_root_multiple_consoles(
|
|||
api2.update = AsyncMock(return_value=bootstrap2)
|
||||
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(
|
||||
domain=DOMAIN,
|
||||
data={
|
||||
|
@ -285,7 +287,9 @@ async def test_browse_media_root_multiple_consoles_only_one_media(
|
|||
api2.update = AsyncMock(return_value=bootstrap2)
|
||||
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(
|
||||
domain=DOMAIN,
|
||||
data={
|
||||
|
|
120
tests/components/unifiprotect/test_repairs.py
Normal file
120
tests/components/unifiprotect/test_repairs.py
Normal 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"
|
Loading…
Add table
Add a link
Reference in a new issue