Add zwave_js add-on manager (#47251)
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
This commit is contained in:
parent
e443597b46
commit
d3721bcf26
10 changed files with 797 additions and 155 deletions
|
@ -183,6 +183,18 @@ async def async_uninstall_addon(hass: HomeAssistantType, slug: str) -> dict:
|
||||||
return await hassio.send_command(command, timeout=60)
|
return await hassio.send_command(command, timeout=60)
|
||||||
|
|
||||||
|
|
||||||
|
@bind_hass
|
||||||
|
@api_data
|
||||||
|
async def async_update_addon(hass: HomeAssistantType, slug: str) -> dict:
|
||||||
|
"""Update add-on.
|
||||||
|
|
||||||
|
The caller of the function should handle HassioAPIError.
|
||||||
|
"""
|
||||||
|
hassio = hass.data[DOMAIN]
|
||||||
|
command = f"/addons/{slug}/update"
|
||||||
|
return await hassio.send_command(command, timeout=None)
|
||||||
|
|
||||||
|
|
||||||
@bind_hass
|
@bind_hass
|
||||||
@api_data
|
@api_data
|
||||||
async def async_start_addon(hass: HomeAssistantType, slug: str) -> dict:
|
async def async_start_addon(hass: HomeAssistantType, slug: str) -> dict:
|
||||||
|
@ -232,6 +244,21 @@ async def async_get_addon_discovery_info(
|
||||||
return next((addon for addon in discovered_addons if addon["addon"] == slug), None)
|
return next((addon for addon in discovered_addons if addon["addon"] == slug), None)
|
||||||
|
|
||||||
|
|
||||||
|
@bind_hass
|
||||||
|
@api_data
|
||||||
|
async def async_create_snapshot(
|
||||||
|
hass: HomeAssistantType, payload: dict, partial: bool = False
|
||||||
|
) -> dict:
|
||||||
|
"""Create a full or partial snapshot.
|
||||||
|
|
||||||
|
The caller of the function should handle HassioAPIError.
|
||||||
|
"""
|
||||||
|
hassio = hass.data[DOMAIN]
|
||||||
|
snapshot_type = "partial" if partial else "full"
|
||||||
|
command = f"/snapshots/new/{snapshot_type}"
|
||||||
|
return await hassio.send_command(command, payload=payload, timeout=None)
|
||||||
|
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
@bind_hass
|
@bind_hass
|
||||||
def get_info(hass):
|
def get_info(hass):
|
||||||
|
|
|
@ -1,16 +1,14 @@
|
||||||
"""The Z-Wave JS integration."""
|
"""The Z-Wave JS integration."""
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
|
||||||
from typing import Callable, List
|
from typing import Callable, List
|
||||||
|
|
||||||
from async_timeout import timeout
|
from async_timeout import timeout
|
||||||
from zwave_js_server.client import Client as ZwaveClient
|
from zwave_js_server.client import Client as ZwaveClient
|
||||||
from zwave_js_server.exceptions import BaseZwaveJSServerError
|
from zwave_js_server.exceptions import BaseZwaveJSServerError, InvalidServerVersion
|
||||||
from zwave_js_server.model.node import Node as ZwaveNode
|
from zwave_js_server.model.node import Node as ZwaveNode
|
||||||
from zwave_js_server.model.notification import Notification
|
from zwave_js_server.model.notification import Notification
|
||||||
from zwave_js_server.model.value import ValueNotification
|
from zwave_js_server.model.value import ValueNotification
|
||||||
|
|
||||||
from homeassistant.components.hassio.handler import HassioAPIError
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import ATTR_DOMAIN, CONF_URL, EVENT_HOMEASSISTANT_STOP
|
from homeassistant.const import ATTR_DOMAIN, CONF_URL, EVENT_HOMEASSISTANT_STOP
|
||||||
from homeassistant.core import Event, HomeAssistant, callback
|
from homeassistant.core import Event, HomeAssistant, callback
|
||||||
|
@ -19,9 +17,9 @@ from homeassistant.helpers import device_registry, entity_registry
|
||||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
||||||
|
|
||||||
|
from .addon import AddonError, AddonManager, get_addon_manager
|
||||||
from .api import async_register_api
|
from .api import async_register_api
|
||||||
from .const import (
|
from .const import (
|
||||||
ADDON_SLUG,
|
|
||||||
ATTR_COMMAND_CLASS,
|
ATTR_COMMAND_CLASS,
|
||||||
ATTR_COMMAND_CLASS_NAME,
|
ATTR_COMMAND_CLASS_NAME,
|
||||||
ATTR_DEVICE_ID,
|
ATTR_DEVICE_ID,
|
||||||
|
@ -38,10 +36,14 @@ from .const import (
|
||||||
ATTR_VALUE,
|
ATTR_VALUE,
|
||||||
ATTR_VALUE_RAW,
|
ATTR_VALUE_RAW,
|
||||||
CONF_INTEGRATION_CREATED_ADDON,
|
CONF_INTEGRATION_CREATED_ADDON,
|
||||||
|
CONF_NETWORK_KEY,
|
||||||
|
CONF_USB_PATH,
|
||||||
|
CONF_USE_ADDON,
|
||||||
DATA_CLIENT,
|
DATA_CLIENT,
|
||||||
DATA_UNSUBSCRIBE,
|
DATA_UNSUBSCRIBE,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
EVENT_DEVICE_ADDED_TO_REGISTRY,
|
EVENT_DEVICE_ADDED_TO_REGISTRY,
|
||||||
|
LOGGER,
|
||||||
PLATFORMS,
|
PLATFORMS,
|
||||||
ZWAVE_JS_EVENT,
|
ZWAVE_JS_EVENT,
|
||||||
)
|
)
|
||||||
|
@ -49,10 +51,11 @@ from .discovery import async_discover_values
|
||||||
from .helpers import get_device_id, get_old_value_id, get_unique_id
|
from .helpers import get_device_id, get_old_value_id, get_unique_id
|
||||||
from .services import ZWaveServices
|
from .services import ZWaveServices
|
||||||
|
|
||||||
LOGGER = logging.getLogger(__package__)
|
|
||||||
CONNECT_TIMEOUT = 10
|
CONNECT_TIMEOUT = 10
|
||||||
DATA_CLIENT_LISTEN_TASK = "client_listen_task"
|
DATA_CLIENT_LISTEN_TASK = "client_listen_task"
|
||||||
DATA_START_PLATFORM_TASK = "start_platform_task"
|
DATA_START_PLATFORM_TASK = "start_platform_task"
|
||||||
|
DATA_CONNECT_FAILED_LOGGED = "connect_failed_logged"
|
||||||
|
DATA_INVALID_SERVER_VERSION_LOGGED = "invalid_server_version_logged"
|
||||||
|
|
||||||
|
|
||||||
async def async_setup(hass: HomeAssistant, config: dict) -> bool:
|
async def async_setup(hass: HomeAssistant, config: dict) -> bool:
|
||||||
|
@ -87,6 +90,10 @@ def register_node_in_dev_reg(
|
||||||
|
|
||||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
"""Set up Z-Wave JS from a config entry."""
|
"""Set up Z-Wave JS from a config entry."""
|
||||||
|
use_addon = entry.data.get(CONF_USE_ADDON)
|
||||||
|
if use_addon:
|
||||||
|
await async_ensure_addon_running(hass, entry)
|
||||||
|
|
||||||
client = ZwaveClient(entry.data[CONF_URL], async_get_clientsession(hass))
|
client = ZwaveClient(entry.data[CONF_URL], async_get_clientsession(hass))
|
||||||
dev_reg = await device_registry.async_get_registry(hass)
|
dev_reg = await device_registry.async_get_registry(hass)
|
||||||
ent_reg = entity_registry.async_get(hass)
|
ent_reg = entity_registry.async_get(hass)
|
||||||
|
@ -257,21 +264,31 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
entry_hass_data: dict = hass.data[DOMAIN].setdefault(entry.entry_id, {})
|
||||||
# connect and throw error if connection failed
|
# connect and throw error if connection failed
|
||||||
try:
|
try:
|
||||||
async with timeout(CONNECT_TIMEOUT):
|
async with timeout(CONNECT_TIMEOUT):
|
||||||
await client.connect()
|
await client.connect()
|
||||||
|
except InvalidServerVersion as err:
|
||||||
|
if not entry_hass_data.get(DATA_INVALID_SERVER_VERSION_LOGGED):
|
||||||
|
LOGGER.error("Invalid server version: %s", err)
|
||||||
|
entry_hass_data[DATA_INVALID_SERVER_VERSION_LOGGED] = True
|
||||||
|
if use_addon:
|
||||||
|
async_ensure_addon_updated(hass)
|
||||||
|
raise ConfigEntryNotReady from err
|
||||||
except (asyncio.TimeoutError, BaseZwaveJSServerError) as err:
|
except (asyncio.TimeoutError, BaseZwaveJSServerError) as err:
|
||||||
LOGGER.error("Failed to connect: %s", err)
|
if not entry_hass_data.get(DATA_CONNECT_FAILED_LOGGED):
|
||||||
|
LOGGER.error("Failed to connect: %s", err)
|
||||||
|
entry_hass_data[DATA_CONNECT_FAILED_LOGGED] = True
|
||||||
raise ConfigEntryNotReady from err
|
raise ConfigEntryNotReady from err
|
||||||
else:
|
else:
|
||||||
LOGGER.info("Connected to Zwave JS Server")
|
LOGGER.info("Connected to Zwave JS Server")
|
||||||
|
entry_hass_data[DATA_CONNECT_FAILED_LOGGED] = False
|
||||||
|
entry_hass_data[DATA_INVALID_SERVER_VERSION_LOGGED] = False
|
||||||
|
|
||||||
unsubscribe_callbacks: List[Callable] = []
|
unsubscribe_callbacks: List[Callable] = []
|
||||||
hass.data[DOMAIN][entry.entry_id] = {
|
entry_hass_data[DATA_CLIENT] = client
|
||||||
DATA_CLIENT: client,
|
entry_hass_data[DATA_UNSUBSCRIBE] = unsubscribe_callbacks
|
||||||
DATA_UNSUBSCRIBE: unsubscribe_callbacks,
|
|
||||||
}
|
|
||||||
|
|
||||||
services = ZWaveServices(hass, ent_reg)
|
services = ZWaveServices(hass, ent_reg)
|
||||||
services.async_register()
|
services.async_register()
|
||||||
|
@ -298,7 +315,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
listen_task = asyncio.create_task(
|
listen_task = asyncio.create_task(
|
||||||
client_listen(hass, entry, client, driver_ready)
|
client_listen(hass, entry, client, driver_ready)
|
||||||
)
|
)
|
||||||
hass.data[DOMAIN][entry.entry_id][DATA_CLIENT_LISTEN_TASK] = listen_task
|
entry_hass_data[DATA_CLIENT_LISTEN_TASK] = listen_task
|
||||||
unsubscribe_callbacks.append(
|
unsubscribe_callbacks.append(
|
||||||
hass.bus.async_listen(EVENT_HOMEASSISTANT_STOP, handle_ha_shutdown)
|
hass.bus.async_listen(EVENT_HOMEASSISTANT_STOP, handle_ha_shutdown)
|
||||||
)
|
)
|
||||||
|
@ -340,7 +357,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
)
|
)
|
||||||
|
|
||||||
platform_task = hass.async_create_task(start_platforms())
|
platform_task = hass.async_create_task(start_platforms())
|
||||||
hass.data[DOMAIN][entry.entry_id][DATA_START_PLATFORM_TASK] = platform_task
|
entry_hass_data[DATA_START_PLATFORM_TASK] = platform_task
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -416,6 +433,15 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
platform_task=info[DATA_START_PLATFORM_TASK],
|
platform_task=info[DATA_START_PLATFORM_TASK],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if entry.data.get(CONF_USE_ADDON) and entry.disabled_by:
|
||||||
|
addon_manager: AddonManager = get_addon_manager(hass)
|
||||||
|
LOGGER.debug("Stopping Z-Wave JS add-on")
|
||||||
|
try:
|
||||||
|
await addon_manager.async_stop_addon()
|
||||||
|
except AddonError as err:
|
||||||
|
LOGGER.error("Failed to stop the Z-Wave JS add-on: %s", err)
|
||||||
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
@ -424,12 +450,51 @@ async def async_remove_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:
|
||||||
if not entry.data.get(CONF_INTEGRATION_CREATED_ADDON):
|
if not entry.data.get(CONF_INTEGRATION_CREATED_ADDON):
|
||||||
return
|
return
|
||||||
|
|
||||||
|
addon_manager: AddonManager = get_addon_manager(hass)
|
||||||
try:
|
try:
|
||||||
await hass.components.hassio.async_stop_addon(ADDON_SLUG)
|
await addon_manager.async_stop_addon()
|
||||||
except HassioAPIError as err:
|
except AddonError as err:
|
||||||
LOGGER.error("Failed to stop the Z-Wave JS add-on: %s", err)
|
LOGGER.error(err)
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
await hass.components.hassio.async_uninstall_addon(ADDON_SLUG)
|
await addon_manager.async_create_snapshot()
|
||||||
except HassioAPIError as err:
|
except AddonError as err:
|
||||||
LOGGER.error("Failed to uninstall the Z-Wave JS add-on: %s", err)
|
LOGGER.error(err)
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
await addon_manager.async_uninstall_addon()
|
||||||
|
except AddonError as err:
|
||||||
|
LOGGER.error(err)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_ensure_addon_running(hass: HomeAssistant, entry: ConfigEntry) -> None:
|
||||||
|
"""Ensure that Z-Wave JS add-on is installed and running."""
|
||||||
|
addon_manager: AddonManager = get_addon_manager(hass)
|
||||||
|
if addon_manager.task_in_progress():
|
||||||
|
raise ConfigEntryNotReady
|
||||||
|
try:
|
||||||
|
addon_is_installed = await addon_manager.async_is_addon_installed()
|
||||||
|
addon_is_running = await addon_manager.async_is_addon_running()
|
||||||
|
except AddonError as err:
|
||||||
|
LOGGER.error("Failed to get the Z-Wave JS add-on info")
|
||||||
|
raise ConfigEntryNotReady from err
|
||||||
|
|
||||||
|
usb_path: str = entry.data[CONF_USB_PATH]
|
||||||
|
network_key: str = entry.data[CONF_NETWORK_KEY]
|
||||||
|
|
||||||
|
if not addon_is_installed:
|
||||||
|
addon_manager.async_schedule_install_addon(usb_path, network_key)
|
||||||
|
raise ConfigEntryNotReady
|
||||||
|
|
||||||
|
if not addon_is_running:
|
||||||
|
addon_manager.async_schedule_setup_addon(usb_path, network_key)
|
||||||
|
raise ConfigEntryNotReady
|
||||||
|
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def async_ensure_addon_updated(hass: HomeAssistant) -> None:
|
||||||
|
"""Ensure that Z-Wave JS add-on is updated and running."""
|
||||||
|
addon_manager: AddonManager = get_addon_manager(hass)
|
||||||
|
if addon_manager.task_in_progress():
|
||||||
|
raise ConfigEntryNotReady
|
||||||
|
addon_manager.async_schedule_update_addon()
|
||||||
|
|
246
homeassistant/components/zwave_js/addon.py
Normal file
246
homeassistant/components/zwave_js/addon.py
Normal file
|
@ -0,0 +1,246 @@
|
||||||
|
"""Provide add-on management."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
from functools import partial
|
||||||
|
from typing import Any, Callable, Optional, TypeVar, cast
|
||||||
|
|
||||||
|
from homeassistant.components.hassio import (
|
||||||
|
async_create_snapshot,
|
||||||
|
async_get_addon_discovery_info,
|
||||||
|
async_get_addon_info,
|
||||||
|
async_install_addon,
|
||||||
|
async_set_addon_options,
|
||||||
|
async_start_addon,
|
||||||
|
async_stop_addon,
|
||||||
|
async_uninstall_addon,
|
||||||
|
async_update_addon,
|
||||||
|
)
|
||||||
|
from homeassistant.components.hassio.handler import HassioAPIError
|
||||||
|
from homeassistant.core import HomeAssistant, callback
|
||||||
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
|
from homeassistant.helpers.singleton import singleton
|
||||||
|
|
||||||
|
from .const import ADDON_SLUG, CONF_ADDON_DEVICE, CONF_ADDON_NETWORK_KEY, DOMAIN, LOGGER
|
||||||
|
|
||||||
|
F = TypeVar("F", bound=Callable[..., Any]) # pylint: disable=invalid-name
|
||||||
|
|
||||||
|
DATA_ADDON_MANAGER = f"{DOMAIN}_addon_manager"
|
||||||
|
|
||||||
|
|
||||||
|
@singleton(DATA_ADDON_MANAGER)
|
||||||
|
@callback
|
||||||
|
def get_addon_manager(hass: HomeAssistant) -> AddonManager:
|
||||||
|
"""Get the add-on manager."""
|
||||||
|
return AddonManager(hass)
|
||||||
|
|
||||||
|
|
||||||
|
def api_error(error_message: str) -> Callable[[F], F]:
|
||||||
|
"""Handle HassioAPIError and raise a specific AddonError."""
|
||||||
|
|
||||||
|
def handle_hassio_api_error(func: F) -> F:
|
||||||
|
"""Handle a HassioAPIError."""
|
||||||
|
|
||||||
|
async def wrapper(*args, **kwargs): # type: ignore
|
||||||
|
"""Wrap an add-on manager method."""
|
||||||
|
try:
|
||||||
|
return_value = await func(*args, **kwargs)
|
||||||
|
except HassioAPIError as err:
|
||||||
|
raise AddonError(error_message) from err
|
||||||
|
|
||||||
|
return return_value
|
||||||
|
|
||||||
|
return cast(F, wrapper)
|
||||||
|
|
||||||
|
return handle_hassio_api_error
|
||||||
|
|
||||||
|
|
||||||
|
class AddonManager:
|
||||||
|
"""Manage the add-on.
|
||||||
|
|
||||||
|
Methods may raise AddonError.
|
||||||
|
Only one instance of this class may exist
|
||||||
|
to keep track of running add-on tasks.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, hass: HomeAssistant) -> None:
|
||||||
|
"""Set up the add-on manager."""
|
||||||
|
self._hass = hass
|
||||||
|
self._install_task: Optional[asyncio.Task] = None
|
||||||
|
self._update_task: Optional[asyncio.Task] = None
|
||||||
|
self._setup_task: Optional[asyncio.Task] = None
|
||||||
|
|
||||||
|
def task_in_progress(self) -> bool:
|
||||||
|
"""Return True if any of the add-on tasks are in progress."""
|
||||||
|
return any(
|
||||||
|
task and not task.done()
|
||||||
|
for task in (
|
||||||
|
self._install_task,
|
||||||
|
self._setup_task,
|
||||||
|
self._update_task,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
@api_error("Failed to get Z-Wave JS add-on discovery info")
|
||||||
|
async def async_get_addon_discovery_info(self) -> dict:
|
||||||
|
"""Return add-on discovery info."""
|
||||||
|
discovery_info = await async_get_addon_discovery_info(self._hass, ADDON_SLUG)
|
||||||
|
|
||||||
|
if not discovery_info:
|
||||||
|
raise AddonError("Failed to get Z-Wave JS add-on discovery info")
|
||||||
|
|
||||||
|
discovery_info_config: dict = discovery_info["config"]
|
||||||
|
return discovery_info_config
|
||||||
|
|
||||||
|
@api_error("Failed to get the Z-Wave JS add-on info")
|
||||||
|
async def async_get_addon_info(self) -> dict:
|
||||||
|
"""Return and cache Z-Wave JS add-on info."""
|
||||||
|
addon_info: dict = await async_get_addon_info(self._hass, ADDON_SLUG)
|
||||||
|
return addon_info
|
||||||
|
|
||||||
|
async def async_is_addon_running(self) -> bool:
|
||||||
|
"""Return True if Z-Wave JS add-on is running."""
|
||||||
|
addon_info = await self.async_get_addon_info()
|
||||||
|
return bool(addon_info["state"] == "started")
|
||||||
|
|
||||||
|
async def async_is_addon_installed(self) -> bool:
|
||||||
|
"""Return True if Z-Wave JS add-on is installed."""
|
||||||
|
addon_info = await self.async_get_addon_info()
|
||||||
|
return addon_info["version"] is not None
|
||||||
|
|
||||||
|
async def async_get_addon_options(self) -> dict:
|
||||||
|
"""Get Z-Wave JS add-on options."""
|
||||||
|
addon_info = await self.async_get_addon_info()
|
||||||
|
return cast(dict, addon_info["options"])
|
||||||
|
|
||||||
|
@api_error("Failed to set the Z-Wave JS add-on options")
|
||||||
|
async def async_set_addon_options(self, config: dict) -> None:
|
||||||
|
"""Set Z-Wave JS add-on options."""
|
||||||
|
options = {"options": config}
|
||||||
|
await async_set_addon_options(self._hass, ADDON_SLUG, options)
|
||||||
|
|
||||||
|
@api_error("Failed to install the Z-Wave JS add-on")
|
||||||
|
async def async_install_addon(self) -> None:
|
||||||
|
"""Install the Z-Wave JS add-on."""
|
||||||
|
await async_install_addon(self._hass, ADDON_SLUG)
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def async_schedule_install_addon(
|
||||||
|
self, usb_path: str, network_key: str
|
||||||
|
) -> asyncio.Task:
|
||||||
|
"""Schedule a task that installs and sets up the Z-Wave JS add-on.
|
||||||
|
|
||||||
|
Only schedule a new install task if the there's no running task.
|
||||||
|
"""
|
||||||
|
if not self._install_task or self._install_task.done():
|
||||||
|
LOGGER.info("Z-Wave JS add-on is not installed. Installing add-on")
|
||||||
|
self._install_task = self._async_schedule_addon_operation(
|
||||||
|
self.async_install_addon,
|
||||||
|
partial(self.async_setup_addon, usb_path, network_key),
|
||||||
|
)
|
||||||
|
return self._install_task
|
||||||
|
|
||||||
|
@api_error("Failed to uninstall the Z-Wave JS add-on")
|
||||||
|
async def async_uninstall_addon(self) -> None:
|
||||||
|
"""Uninstall the Z-Wave JS add-on."""
|
||||||
|
await async_uninstall_addon(self._hass, ADDON_SLUG)
|
||||||
|
|
||||||
|
@api_error("Failed to update the Z-Wave JS add-on")
|
||||||
|
async def async_update_addon(self) -> None:
|
||||||
|
"""Update the Z-Wave JS add-on if needed."""
|
||||||
|
addon_info = await self.async_get_addon_info()
|
||||||
|
addon_version = addon_info["version"]
|
||||||
|
update_available = addon_info["update_available"]
|
||||||
|
|
||||||
|
if addon_version is None:
|
||||||
|
raise AddonError("Z-Wave JS add-on is not installed")
|
||||||
|
|
||||||
|
if not update_available:
|
||||||
|
return
|
||||||
|
|
||||||
|
await async_update_addon(self._hass, ADDON_SLUG)
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def async_schedule_update_addon(self) -> asyncio.Task:
|
||||||
|
"""Schedule a task that updates and sets up the Z-Wave JS add-on.
|
||||||
|
|
||||||
|
Only schedule a new update task if the there's no running task.
|
||||||
|
"""
|
||||||
|
if not self._update_task or self._update_task.done():
|
||||||
|
LOGGER.info("Trying to update the Z-Wave JS add-on")
|
||||||
|
self._update_task = self._async_schedule_addon_operation(
|
||||||
|
self.async_create_snapshot, self.async_update_addon
|
||||||
|
)
|
||||||
|
return self._update_task
|
||||||
|
|
||||||
|
@api_error("Failed to start the Z-Wave JS add-on")
|
||||||
|
async def async_start_addon(self) -> None:
|
||||||
|
"""Start the Z-Wave JS add-on."""
|
||||||
|
await async_start_addon(self._hass, ADDON_SLUG)
|
||||||
|
|
||||||
|
@api_error("Failed to stop the Z-Wave JS add-on")
|
||||||
|
async def async_stop_addon(self) -> None:
|
||||||
|
"""Stop the Z-Wave JS add-on."""
|
||||||
|
await async_stop_addon(self._hass, ADDON_SLUG)
|
||||||
|
|
||||||
|
async def async_setup_addon(self, usb_path: str, network_key: str) -> None:
|
||||||
|
"""Configure and start Z-Wave JS add-on."""
|
||||||
|
addon_options = await self.async_get_addon_options()
|
||||||
|
|
||||||
|
new_addon_options = {
|
||||||
|
CONF_ADDON_DEVICE: usb_path,
|
||||||
|
CONF_ADDON_NETWORK_KEY: network_key,
|
||||||
|
}
|
||||||
|
|
||||||
|
if new_addon_options != addon_options:
|
||||||
|
await self.async_set_addon_options(new_addon_options)
|
||||||
|
|
||||||
|
await self.async_start_addon()
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def async_schedule_setup_addon(
|
||||||
|
self, usb_path: str, network_key: str
|
||||||
|
) -> asyncio.Task:
|
||||||
|
"""Schedule a task that configures and starts the Z-Wave JS add-on.
|
||||||
|
|
||||||
|
Only schedule a new setup task if the there's no running task.
|
||||||
|
"""
|
||||||
|
if not self._setup_task or self._setup_task.done():
|
||||||
|
LOGGER.info("Z-Wave JS add-on is not running. Starting add-on")
|
||||||
|
self._setup_task = self._async_schedule_addon_operation(
|
||||||
|
partial(self.async_setup_addon, usb_path, network_key)
|
||||||
|
)
|
||||||
|
return self._setup_task
|
||||||
|
|
||||||
|
@api_error("Failed to create a snapshot of the Z-Wave JS add-on.")
|
||||||
|
async def async_create_snapshot(self) -> None:
|
||||||
|
"""Create a partial snapshot of the Z-Wave JS add-on."""
|
||||||
|
addon_info = await self.async_get_addon_info()
|
||||||
|
addon_version = addon_info["version"]
|
||||||
|
name = f"addon_{ADDON_SLUG}_{addon_version}"
|
||||||
|
|
||||||
|
LOGGER.debug("Creating snapshot: %s", name)
|
||||||
|
await async_create_snapshot(
|
||||||
|
self._hass,
|
||||||
|
{"name": name, "addons": [ADDON_SLUG]},
|
||||||
|
partial=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def _async_schedule_addon_operation(self, *funcs: Callable) -> asyncio.Task:
|
||||||
|
"""Schedule an add-on task."""
|
||||||
|
|
||||||
|
async def addon_operation() -> None:
|
||||||
|
"""Do the add-on operation and catch AddonError."""
|
||||||
|
for func in funcs:
|
||||||
|
try:
|
||||||
|
await func()
|
||||||
|
except AddonError as err:
|
||||||
|
LOGGER.error(err)
|
||||||
|
break
|
||||||
|
|
||||||
|
return self._hass.async_create_task(addon_operation())
|
||||||
|
|
||||||
|
|
||||||
|
class AddonError(HomeAssistantError):
|
||||||
|
"""Represent an error with Z-Wave JS add-on."""
|
|
@ -9,33 +9,25 @@ import voluptuous as vol
|
||||||
from zwave_js_server.version import VersionInfo, get_server_version
|
from zwave_js_server.version import VersionInfo, get_server_version
|
||||||
|
|
||||||
from homeassistant import config_entries, exceptions
|
from homeassistant import config_entries, exceptions
|
||||||
from homeassistant.components.hassio import (
|
from homeassistant.components.hassio import is_hassio
|
||||||
async_get_addon_discovery_info,
|
|
||||||
async_get_addon_info,
|
|
||||||
async_install_addon,
|
|
||||||
async_set_addon_options,
|
|
||||||
async_start_addon,
|
|
||||||
is_hassio,
|
|
||||||
)
|
|
||||||
from homeassistant.components.hassio.handler import HassioAPIError
|
|
||||||
from homeassistant.const import CONF_URL
|
from homeassistant.const import CONF_URL
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.data_entry_flow import AbortFlow
|
from homeassistant.data_entry_flow import AbortFlow
|
||||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
|
|
||||||
|
from .addon import AddonError, AddonManager, get_addon_manager
|
||||||
from .const import ( # pylint:disable=unused-import
|
from .const import ( # pylint:disable=unused-import
|
||||||
ADDON_SLUG,
|
CONF_ADDON_DEVICE,
|
||||||
|
CONF_ADDON_NETWORK_KEY,
|
||||||
CONF_INTEGRATION_CREATED_ADDON,
|
CONF_INTEGRATION_CREATED_ADDON,
|
||||||
|
CONF_NETWORK_KEY,
|
||||||
|
CONF_USB_PATH,
|
||||||
CONF_USE_ADDON,
|
CONF_USE_ADDON,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
)
|
)
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
CONF_ADDON_DEVICE = "device"
|
|
||||||
CONF_ADDON_NETWORK_KEY = "network_key"
|
|
||||||
CONF_NETWORK_KEY = "network_key"
|
|
||||||
CONF_USB_PATH = "usb_path"
|
|
||||||
DEFAULT_URL = "ws://localhost:3000"
|
DEFAULT_URL = "ws://localhost:3000"
|
||||||
TITLE = "Z-Wave JS"
|
TITLE = "Z-Wave JS"
|
||||||
|
|
||||||
|
@ -180,6 +172,11 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
self, user_input: Optional[Dict[str, Any]] = None
|
self, user_input: Optional[Dict[str, Any]] = None
|
||||||
) -> Dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
"""Handle logic when on Supervisor host."""
|
"""Handle logic when on Supervisor host."""
|
||||||
|
# Only one entry with Supervisor add-on support is allowed.
|
||||||
|
for entry in self.hass.config_entries.async_entries(DOMAIN):
|
||||||
|
if entry.data.get(CONF_USE_ADDON):
|
||||||
|
return await self.async_step_manual()
|
||||||
|
|
||||||
if user_input is None:
|
if user_input is None:
|
||||||
return self.async_show_form(
|
return self.async_show_form(
|
||||||
step_id="on_supervisor", data_schema=ON_SUPERVISOR_SCHEMA
|
step_id="on_supervisor", data_schema=ON_SUPERVISOR_SCHEMA
|
||||||
|
@ -212,7 +209,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await self.install_task
|
await self.install_task
|
||||||
except HassioAPIError as err:
|
except AddonError as err:
|
||||||
_LOGGER.error("Failed to install Z-Wave JS add-on: %s", err)
|
_LOGGER.error("Failed to install Z-Wave JS add-on: %s", err)
|
||||||
return self.async_show_progress_done(next_step_id="install_failed")
|
return self.async_show_progress_done(next_step_id="install_failed")
|
||||||
|
|
||||||
|
@ -275,7 +272,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await self.start_task
|
await self.start_task
|
||||||
except (CannotConnect, HassioAPIError) as err:
|
except (CannotConnect, AddonError) as err:
|
||||||
_LOGGER.error("Failed to start Z-Wave JS add-on: %s", err)
|
_LOGGER.error("Failed to start Z-Wave JS add-on: %s", err)
|
||||||
return self.async_show_progress_done(next_step_id="start_failed")
|
return self.async_show_progress_done(next_step_id="start_failed")
|
||||||
|
|
||||||
|
@ -290,8 +287,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
async def _async_start_addon(self) -> None:
|
async def _async_start_addon(self) -> None:
|
||||||
"""Start the Z-Wave JS add-on."""
|
"""Start the Z-Wave JS add-on."""
|
||||||
assert self.hass
|
assert self.hass
|
||||||
|
addon_manager: AddonManager = get_addon_manager(self.hass)
|
||||||
try:
|
try:
|
||||||
await async_start_addon(self.hass, ADDON_SLUG)
|
await addon_manager.async_start_addon()
|
||||||
# Sleep some seconds to let the add-on start properly before connecting.
|
# Sleep some seconds to let the add-on start properly before connecting.
|
||||||
for _ in range(ADDON_SETUP_TIMEOUT_ROUNDS):
|
for _ in range(ADDON_SETUP_TIMEOUT_ROUNDS):
|
||||||
await asyncio.sleep(ADDON_SETUP_TIMEOUT)
|
await asyncio.sleep(ADDON_SETUP_TIMEOUT)
|
||||||
|
@ -345,9 +343,10 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
|
|
||||||
async def _async_get_addon_info(self) -> dict:
|
async def _async_get_addon_info(self) -> dict:
|
||||||
"""Return and cache Z-Wave JS add-on info."""
|
"""Return and cache Z-Wave JS add-on info."""
|
||||||
|
addon_manager: AddonManager = get_addon_manager(self.hass)
|
||||||
try:
|
try:
|
||||||
addon_info: dict = await async_get_addon_info(self.hass, ADDON_SLUG)
|
addon_info: dict = await addon_manager.async_get_addon_info()
|
||||||
except HassioAPIError as err:
|
except AddonError as err:
|
||||||
_LOGGER.error("Failed to get Z-Wave JS add-on info: %s", err)
|
_LOGGER.error("Failed to get Z-Wave JS add-on info: %s", err)
|
||||||
raise AbortFlow("addon_info_failed") from err
|
raise AbortFlow("addon_info_failed") from err
|
||||||
|
|
||||||
|
@ -371,16 +370,18 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
async def _async_set_addon_config(self, config: dict) -> None:
|
async def _async_set_addon_config(self, config: dict) -> None:
|
||||||
"""Set Z-Wave JS add-on config."""
|
"""Set Z-Wave JS add-on config."""
|
||||||
options = {"options": config}
|
options = {"options": config}
|
||||||
|
addon_manager: AddonManager = get_addon_manager(self.hass)
|
||||||
try:
|
try:
|
||||||
await async_set_addon_options(self.hass, ADDON_SLUG, options)
|
await addon_manager.async_set_addon_options(options)
|
||||||
except HassioAPIError as err:
|
except AddonError as err:
|
||||||
_LOGGER.error("Failed to set Z-Wave JS add-on config: %s", err)
|
_LOGGER.error("Failed to set Z-Wave JS add-on config: %s", err)
|
||||||
raise AbortFlow("addon_set_config_failed") from err
|
raise AbortFlow("addon_set_config_failed") from err
|
||||||
|
|
||||||
async def _async_install_addon(self) -> None:
|
async def _async_install_addon(self) -> None:
|
||||||
"""Install the Z-Wave JS add-on."""
|
"""Install the Z-Wave JS add-on."""
|
||||||
|
addon_manager: AddonManager = get_addon_manager(self.hass)
|
||||||
try:
|
try:
|
||||||
await async_install_addon(self.hass, ADDON_SLUG)
|
await addon_manager.async_install_addon()
|
||||||
finally:
|
finally:
|
||||||
# Continue the flow after show progress when the task is done.
|
# Continue the flow after show progress when the task is done.
|
||||||
self.hass.async_create_task(
|
self.hass.async_create_task(
|
||||||
|
@ -389,17 +390,13 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
|
|
||||||
async def _async_get_addon_discovery_info(self) -> dict:
|
async def _async_get_addon_discovery_info(self) -> dict:
|
||||||
"""Return add-on discovery info."""
|
"""Return add-on discovery info."""
|
||||||
|
addon_manager: AddonManager = get_addon_manager(self.hass)
|
||||||
try:
|
try:
|
||||||
discovery_info = await async_get_addon_discovery_info(self.hass, ADDON_SLUG)
|
discovery_info_config = await addon_manager.async_get_addon_discovery_info()
|
||||||
except HassioAPIError as err:
|
except AddonError as err:
|
||||||
_LOGGER.error("Failed to get Z-Wave JS add-on discovery info: %s", err)
|
_LOGGER.error("Failed to get Z-Wave JS add-on discovery info: %s", err)
|
||||||
raise AbortFlow("addon_get_discovery_info_failed") from err
|
raise AbortFlow("addon_get_discovery_info_failed") from err
|
||||||
|
|
||||||
if not discovery_info:
|
|
||||||
_LOGGER.error("Failed to get Z-Wave JS add-on discovery info")
|
|
||||||
raise AbortFlow("addon_missing_discovery_info")
|
|
||||||
|
|
||||||
discovery_info_config: dict = discovery_info["config"]
|
|
||||||
return discovery_info_config
|
return discovery_info_config
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,11 @@
|
||||||
"""Constants for the Z-Wave JS integration."""
|
"""Constants for the Z-Wave JS integration."""
|
||||||
|
import logging
|
||||||
|
|
||||||
|
CONF_ADDON_DEVICE = "device"
|
||||||
|
CONF_ADDON_NETWORK_KEY = "network_key"
|
||||||
CONF_INTEGRATION_CREATED_ADDON = "integration_created_addon"
|
CONF_INTEGRATION_CREATED_ADDON = "integration_created_addon"
|
||||||
|
CONF_NETWORK_KEY = "network_key"
|
||||||
|
CONF_USB_PATH = "usb_path"
|
||||||
CONF_USE_ADDON = "use_addon"
|
CONF_USE_ADDON = "use_addon"
|
||||||
DOMAIN = "zwave_js"
|
DOMAIN = "zwave_js"
|
||||||
PLATFORMS = [
|
PLATFORMS = [
|
||||||
|
@ -19,6 +25,8 @@ DATA_UNSUBSCRIBE = "unsubs"
|
||||||
|
|
||||||
EVENT_DEVICE_ADDED_TO_REGISTRY = f"{DOMAIN}_device_added_to_registry"
|
EVENT_DEVICE_ADDED_TO_REGISTRY = f"{DOMAIN}_device_added_to_registry"
|
||||||
|
|
||||||
|
LOGGER = logging.getLogger(__package__)
|
||||||
|
|
||||||
# constants for events
|
# constants for events
|
||||||
ZWAVE_JS_EVENT = f"{DOMAIN}_event"
|
ZWAVE_JS_EVENT = f"{DOMAIN}_event"
|
||||||
ATTR_NODE_ID = "node_id"
|
ATTR_NODE_ID = "node_id"
|
||||||
|
|
|
@ -41,7 +41,6 @@
|
||||||
"addon_set_config_failed": "Failed to set Z-Wave JS configuration.",
|
"addon_set_config_failed": "Failed to set Z-Wave JS configuration.",
|
||||||
"addon_start_failed": "Failed to start the Z-Wave JS add-on.",
|
"addon_start_failed": "Failed to start the Z-Wave JS add-on.",
|
||||||
"addon_get_discovery_info_failed": "Failed to get Z-Wave JS add-on discovery info.",
|
"addon_get_discovery_info_failed": "Failed to get Z-Wave JS add-on discovery info.",
|
||||||
"addon_missing_discovery_info": "Missing Z-Wave JS add-on discovery info.",
|
|
||||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
|
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
|
||||||
},
|
},
|
||||||
"progress": {
|
"progress": {
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
"addon_get_discovery_info_failed": "Failed to get Z-Wave JS add-on discovery info.",
|
"addon_get_discovery_info_failed": "Failed to get Z-Wave JS add-on discovery info.",
|
||||||
"addon_info_failed": "Failed to get Z-Wave JS add-on info.",
|
"addon_info_failed": "Failed to get Z-Wave JS add-on info.",
|
||||||
"addon_install_failed": "Failed to install the Z-Wave JS add-on.",
|
"addon_install_failed": "Failed to install the Z-Wave JS add-on.",
|
||||||
"addon_missing_discovery_info": "Missing Z-Wave JS add-on discovery info.",
|
|
||||||
"addon_set_config_failed": "Failed to set Z-Wave JS configuration.",
|
"addon_set_config_failed": "Failed to set Z-Wave JS configuration.",
|
||||||
"addon_start_failed": "Failed to start the Z-Wave JS add-on.",
|
"addon_start_failed": "Failed to start the Z-Wave JS add-on.",
|
||||||
"already_configured": "Device is already configured",
|
"already_configured": "Device is already configured",
|
||||||
|
@ -49,11 +48,6 @@
|
||||||
},
|
},
|
||||||
"start_addon": {
|
"start_addon": {
|
||||||
"title": "The Z-Wave JS add-on is starting."
|
"title": "The Z-Wave JS add-on is starting."
|
||||||
},
|
|
||||||
"user": {
|
|
||||||
"data": {
|
|
||||||
"url": "URL"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -14,6 +14,124 @@ from homeassistant.helpers.device_registry import async_get as async_get_device_
|
||||||
|
|
||||||
from tests.common import MockConfigEntry, load_fixture
|
from tests.common import MockConfigEntry, load_fixture
|
||||||
|
|
||||||
|
# Add-on fixtures
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(name="addon_info_side_effect")
|
||||||
|
def addon_info_side_effect_fixture():
|
||||||
|
"""Return the add-on info side effect."""
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(name="addon_info")
|
||||||
|
def mock_addon_info(addon_info_side_effect):
|
||||||
|
"""Mock Supervisor add-on info."""
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.zwave_js.addon.async_get_addon_info",
|
||||||
|
side_effect=addon_info_side_effect,
|
||||||
|
) as addon_info:
|
||||||
|
addon_info.return_value = {}
|
||||||
|
yield addon_info
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(name="addon_running")
|
||||||
|
def mock_addon_running(addon_info):
|
||||||
|
"""Mock add-on already running."""
|
||||||
|
addon_info.return_value["state"] = "started"
|
||||||
|
return addon_info
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(name="addon_installed")
|
||||||
|
def mock_addon_installed(addon_info):
|
||||||
|
"""Mock add-on already installed but not running."""
|
||||||
|
addon_info.return_value["state"] = "stopped"
|
||||||
|
addon_info.return_value["version"] = "1.0"
|
||||||
|
return addon_info
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(name="addon_options")
|
||||||
|
def mock_addon_options(addon_info):
|
||||||
|
"""Mock add-on options."""
|
||||||
|
addon_info.return_value["options"] = {}
|
||||||
|
return addon_info.return_value["options"]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(name="set_addon_options_side_effect")
|
||||||
|
def set_addon_options_side_effect_fixture():
|
||||||
|
"""Return the set add-on options side effect."""
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(name="set_addon_options")
|
||||||
|
def mock_set_addon_options(set_addon_options_side_effect):
|
||||||
|
"""Mock set add-on options."""
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.zwave_js.addon.async_set_addon_options",
|
||||||
|
side_effect=set_addon_options_side_effect,
|
||||||
|
) as set_options:
|
||||||
|
yield set_options
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(name="install_addon")
|
||||||
|
def mock_install_addon():
|
||||||
|
"""Mock install add-on."""
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.zwave_js.addon.async_install_addon"
|
||||||
|
) as install_addon:
|
||||||
|
yield install_addon
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(name="update_addon")
|
||||||
|
def mock_update_addon():
|
||||||
|
"""Mock update add-on."""
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.zwave_js.addon.async_update_addon"
|
||||||
|
) as update_addon:
|
||||||
|
yield update_addon
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(name="start_addon_side_effect")
|
||||||
|
def start_addon_side_effect_fixture():
|
||||||
|
"""Return the set add-on options side effect."""
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(name="start_addon")
|
||||||
|
def mock_start_addon(start_addon_side_effect):
|
||||||
|
"""Mock start add-on."""
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.zwave_js.addon.async_start_addon",
|
||||||
|
side_effect=start_addon_side_effect,
|
||||||
|
) as start_addon:
|
||||||
|
yield start_addon
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(name="stop_addon")
|
||||||
|
def stop_addon_fixture():
|
||||||
|
"""Mock stop add-on."""
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.zwave_js.addon.async_stop_addon"
|
||||||
|
) as stop_addon:
|
||||||
|
yield stop_addon
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(name="uninstall_addon")
|
||||||
|
def uninstall_addon_fixture():
|
||||||
|
"""Mock uninstall add-on."""
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.zwave_js.addon.async_uninstall_addon"
|
||||||
|
) as uninstall_addon:
|
||||||
|
yield uninstall_addon
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(name="create_shapshot")
|
||||||
|
def create_snapshot_fixture():
|
||||||
|
"""Mock create snapshot."""
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.zwave_js.addon.async_create_snapshot"
|
||||||
|
) as create_shapshot:
|
||||||
|
yield create_shapshot
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(name="device_registry")
|
@pytest.fixture(name="device_registry")
|
||||||
async def device_registry_fixture(hass):
|
async def device_registry_fixture(hass):
|
||||||
|
|
|
@ -44,93 +44,13 @@ def discovery_info_side_effect_fixture():
|
||||||
def mock_get_addon_discovery_info(discovery_info, discovery_info_side_effect):
|
def mock_get_addon_discovery_info(discovery_info, discovery_info_side_effect):
|
||||||
"""Mock get add-on discovery info."""
|
"""Mock get add-on discovery info."""
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.zwave_js.config_flow.async_get_addon_discovery_info",
|
"homeassistant.components.zwave_js.addon.async_get_addon_discovery_info",
|
||||||
side_effect=discovery_info_side_effect,
|
side_effect=discovery_info_side_effect,
|
||||||
return_value=discovery_info,
|
return_value=discovery_info,
|
||||||
) as get_addon_discovery_info:
|
) as get_addon_discovery_info:
|
||||||
yield get_addon_discovery_info
|
yield get_addon_discovery_info
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(name="addon_info_side_effect")
|
|
||||||
def addon_info_side_effect_fixture():
|
|
||||||
"""Return the add-on info side effect."""
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(name="addon_info")
|
|
||||||
def mock_addon_info(addon_info_side_effect):
|
|
||||||
"""Mock Supervisor add-on info."""
|
|
||||||
with patch(
|
|
||||||
"homeassistant.components.zwave_js.config_flow.async_get_addon_info",
|
|
||||||
side_effect=addon_info_side_effect,
|
|
||||||
) as addon_info:
|
|
||||||
addon_info.return_value = {}
|
|
||||||
yield addon_info
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(name="addon_running")
|
|
||||||
def mock_addon_running(addon_info):
|
|
||||||
"""Mock add-on already running."""
|
|
||||||
addon_info.return_value["state"] = "started"
|
|
||||||
return addon_info
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(name="addon_installed")
|
|
||||||
def mock_addon_installed(addon_info):
|
|
||||||
"""Mock add-on already installed but not running."""
|
|
||||||
addon_info.return_value["state"] = "stopped"
|
|
||||||
addon_info.return_value["version"] = "1.0"
|
|
||||||
return addon_info
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(name="addon_options")
|
|
||||||
def mock_addon_options(addon_info):
|
|
||||||
"""Mock add-on options."""
|
|
||||||
addon_info.return_value["options"] = {}
|
|
||||||
return addon_info.return_value["options"]
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(name="set_addon_options_side_effect")
|
|
||||||
def set_addon_options_side_effect_fixture():
|
|
||||||
"""Return the set add-on options side effect."""
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(name="set_addon_options")
|
|
||||||
def mock_set_addon_options(set_addon_options_side_effect):
|
|
||||||
"""Mock set add-on options."""
|
|
||||||
with patch(
|
|
||||||
"homeassistant.components.zwave_js.config_flow.async_set_addon_options",
|
|
||||||
side_effect=set_addon_options_side_effect,
|
|
||||||
) as set_options:
|
|
||||||
yield set_options
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(name="install_addon")
|
|
||||||
def mock_install_addon():
|
|
||||||
"""Mock install add-on."""
|
|
||||||
with patch(
|
|
||||||
"homeassistant.components.zwave_js.config_flow.async_install_addon"
|
|
||||||
) as install_addon:
|
|
||||||
yield install_addon
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(name="start_addon_side_effect")
|
|
||||||
def start_addon_side_effect_fixture():
|
|
||||||
"""Return the set add-on options side effect."""
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(name="start_addon")
|
|
||||||
def mock_start_addon(start_addon_side_effect):
|
|
||||||
"""Mock start add-on."""
|
|
||||||
with patch(
|
|
||||||
"homeassistant.components.zwave_js.config_flow.async_start_addon",
|
|
||||||
side_effect=start_addon_side_effect,
|
|
||||||
) as start_addon:
|
|
||||||
yield start_addon
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(name="server_version_side_effect")
|
@pytest.fixture(name="server_version_side_effect")
|
||||||
def server_version_side_effect_fixture():
|
def server_version_side_effect_fixture():
|
||||||
"""Return the server version side effect."""
|
"""Return the server version side effect."""
|
||||||
|
@ -587,6 +507,49 @@ async def test_not_addon(hass, supervisor):
|
||||||
assert len(mock_setup_entry.mock_calls) == 1
|
assert len(mock_setup_entry.mock_calls) == 1
|
||||||
|
|
||||||
|
|
||||||
|
async def test_addon_already_configured(hass, supervisor):
|
||||||
|
"""Test add-on already configured leads to manual step."""
|
||||||
|
entry = MockConfigEntry(
|
||||||
|
domain=DOMAIN, data={"use_addon": True}, title=TITLE, unique_id=5678
|
||||||
|
)
|
||||||
|
entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
await setup.async_setup_component(hass, "persistent_notification", {})
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == "form"
|
||||||
|
assert result["step_id"] == "manual"
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.zwave_js.async_setup", return_value=True
|
||||||
|
) as mock_setup, patch(
|
||||||
|
"homeassistant.components.zwave_js.async_setup_entry",
|
||||||
|
return_value=True,
|
||||||
|
) as mock_setup_entry:
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
{
|
||||||
|
"url": "ws://localhost:3000",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert result["type"] == "create_entry"
|
||||||
|
assert result["title"] == TITLE
|
||||||
|
assert result["data"] == {
|
||||||
|
"url": "ws://localhost:3000",
|
||||||
|
"usb_path": None,
|
||||||
|
"network_key": None,
|
||||||
|
"use_addon": False,
|
||||||
|
"integration_created_addon": False,
|
||||||
|
}
|
||||||
|
assert len(mock_setup.mock_calls) == 1
|
||||||
|
assert len(mock_setup_entry.mock_calls) == 2
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("discovery_info", [{"config": ADDON_DISCOVERY_INFO}])
|
@pytest.mark.parametrize("discovery_info", [{"config": ADDON_DISCOVERY_INFO}])
|
||||||
async def test_addon_running(
|
async def test_addon_running(
|
||||||
hass,
|
hass,
|
||||||
|
@ -654,7 +617,7 @@ async def test_addon_running(
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
"addon_missing_discovery_info",
|
"addon_get_discovery_info_failed",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
{"config": ADDON_DISCOVERY_INFO},
|
{"config": ADDON_DISCOVERY_INFO},
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
"""Test the Z-Wave JS init module."""
|
"""Test the Z-Wave JS init module."""
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from unittest.mock import patch
|
from unittest.mock import call, patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from zwave_js_server.exceptions import BaseZwaveJSServerError
|
from zwave_js_server.exceptions import BaseZwaveJSServerError, InvalidServerVersion
|
||||||
from zwave_js_server.model.node import Node
|
from zwave_js_server.model.node import Node
|
||||||
|
|
||||||
from homeassistant.components.hassio.handler import HassioAPIError
|
from homeassistant.components.hassio.handler import HassioAPIError
|
||||||
|
@ -11,6 +11,7 @@ from homeassistant.components.zwave_js.const import DOMAIN
|
||||||
from homeassistant.components.zwave_js.helpers import get_device_id
|
from homeassistant.components.zwave_js.helpers import get_device_id
|
||||||
from homeassistant.config_entries import (
|
from homeassistant.config_entries import (
|
||||||
CONN_CLASS_LOCAL_PUSH,
|
CONN_CLASS_LOCAL_PUSH,
|
||||||
|
DISABLED_USER,
|
||||||
ENTRY_STATE_LOADED,
|
ENTRY_STATE_LOADED,
|
||||||
ENTRY_STATE_NOT_LOADED,
|
ENTRY_STATE_NOT_LOADED,
|
||||||
ENTRY_STATE_SETUP_RETRY,
|
ENTRY_STATE_SETUP_RETRY,
|
||||||
|
@ -34,22 +35,6 @@ def connect_timeout_fixture():
|
||||||
yield timeout
|
yield timeout
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(name="stop_addon")
|
|
||||||
def stop_addon_fixture():
|
|
||||||
"""Mock stop add-on."""
|
|
||||||
with patch("homeassistant.components.hassio.async_stop_addon") as stop_addon:
|
|
||||||
yield stop_addon
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(name="uninstall_addon")
|
|
||||||
def uninstall_addon_fixture():
|
|
||||||
"""Mock uninstall add-on."""
|
|
||||||
with patch(
|
|
||||||
"homeassistant.components.hassio.async_uninstall_addon"
|
|
||||||
) as uninstall_addon:
|
|
||||||
yield uninstall_addon
|
|
||||||
|
|
||||||
|
|
||||||
async def test_entry_setup_unload(hass, client, integration):
|
async def test_entry_setup_unload(hass, client, integration):
|
||||||
"""Test the integration set up and unload."""
|
"""Test the integration set up and unload."""
|
||||||
entry = integration
|
entry = integration
|
||||||
|
@ -367,7 +352,203 @@ async def test_existing_node_not_ready(hass, client, multisensor_6, device_regis
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def test_remove_entry(hass, stop_addon, uninstall_addon, caplog):
|
async def test_start_addon(
|
||||||
|
hass, addon_installed, install_addon, addon_options, set_addon_options, start_addon
|
||||||
|
):
|
||||||
|
"""Test start the Z-Wave JS add-on during entry setup."""
|
||||||
|
device = "/test"
|
||||||
|
network_key = "abc123"
|
||||||
|
addon_options = {
|
||||||
|
"device": device,
|
||||||
|
"network_key": network_key,
|
||||||
|
}
|
||||||
|
entry = MockConfigEntry(
|
||||||
|
domain=DOMAIN,
|
||||||
|
title="Z-Wave JS",
|
||||||
|
connection_class=CONN_CLASS_LOCAL_PUSH,
|
||||||
|
data={"use_addon": True, "usb_path": device, "network_key": network_key},
|
||||||
|
)
|
||||||
|
entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
await hass.config_entries.async_setup(entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert entry.state == ENTRY_STATE_SETUP_RETRY
|
||||||
|
assert install_addon.call_count == 0
|
||||||
|
assert set_addon_options.call_count == 1
|
||||||
|
assert set_addon_options.call_args == call(
|
||||||
|
hass, "core_zwave_js", {"options": addon_options}
|
||||||
|
)
|
||||||
|
assert start_addon.call_count == 1
|
||||||
|
assert start_addon.call_args == call(hass, "core_zwave_js")
|
||||||
|
|
||||||
|
|
||||||
|
async def test_install_addon(
|
||||||
|
hass, addon_installed, install_addon, addon_options, set_addon_options, start_addon
|
||||||
|
):
|
||||||
|
"""Test install and start the Z-Wave JS add-on during entry setup."""
|
||||||
|
addon_installed.return_value["version"] = None
|
||||||
|
device = "/test"
|
||||||
|
network_key = "abc123"
|
||||||
|
addon_options = {
|
||||||
|
"device": device,
|
||||||
|
"network_key": network_key,
|
||||||
|
}
|
||||||
|
entry = MockConfigEntry(
|
||||||
|
domain=DOMAIN,
|
||||||
|
title="Z-Wave JS",
|
||||||
|
connection_class=CONN_CLASS_LOCAL_PUSH,
|
||||||
|
data={"use_addon": True, "usb_path": device, "network_key": network_key},
|
||||||
|
)
|
||||||
|
entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
await hass.config_entries.async_setup(entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert entry.state == ENTRY_STATE_SETUP_RETRY
|
||||||
|
assert install_addon.call_count == 1
|
||||||
|
assert install_addon.call_args == call(hass, "core_zwave_js")
|
||||||
|
assert set_addon_options.call_count == 1
|
||||||
|
assert set_addon_options.call_args == call(
|
||||||
|
hass, "core_zwave_js", {"options": addon_options}
|
||||||
|
)
|
||||||
|
assert start_addon.call_count == 1
|
||||||
|
assert start_addon.call_args == call(hass, "core_zwave_js")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("addon_info_side_effect", [HassioAPIError("Boom")])
|
||||||
|
async def test_addon_info_failure(
|
||||||
|
hass,
|
||||||
|
addon_installed,
|
||||||
|
install_addon,
|
||||||
|
addon_options,
|
||||||
|
set_addon_options,
|
||||||
|
start_addon,
|
||||||
|
):
|
||||||
|
"""Test failure to get add-on info for Z-Wave JS add-on during entry setup."""
|
||||||
|
device = "/test"
|
||||||
|
network_key = "abc123"
|
||||||
|
entry = MockConfigEntry(
|
||||||
|
domain=DOMAIN,
|
||||||
|
title="Z-Wave JS",
|
||||||
|
connection_class=CONN_CLASS_LOCAL_PUSH,
|
||||||
|
data={"use_addon": True, "usb_path": device, "network_key": network_key},
|
||||||
|
)
|
||||||
|
entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
await hass.config_entries.async_setup(entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert entry.state == ENTRY_STATE_SETUP_RETRY
|
||||||
|
assert install_addon.call_count == 0
|
||||||
|
assert start_addon.call_count == 0
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"addon_version, update_available, update_calls, update_addon_side_effect",
|
||||||
|
[
|
||||||
|
("1.0", True, 1, None),
|
||||||
|
("1.0", False, 0, None),
|
||||||
|
("1.0", True, 1, HassioAPIError("Boom")),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_update_addon(
|
||||||
|
hass,
|
||||||
|
client,
|
||||||
|
addon_info,
|
||||||
|
addon_installed,
|
||||||
|
addon_running,
|
||||||
|
create_shapshot,
|
||||||
|
update_addon,
|
||||||
|
addon_options,
|
||||||
|
addon_version,
|
||||||
|
update_available,
|
||||||
|
update_calls,
|
||||||
|
update_addon_side_effect,
|
||||||
|
):
|
||||||
|
"""Test update the Z-Wave JS add-on during entry setup."""
|
||||||
|
addon_info.return_value["version"] = addon_version
|
||||||
|
addon_info.return_value["update_available"] = update_available
|
||||||
|
update_addon.side_effect = update_addon_side_effect
|
||||||
|
client.connect.side_effect = InvalidServerVersion("Invalid version")
|
||||||
|
device = "/test"
|
||||||
|
network_key = "abc123"
|
||||||
|
entry = MockConfigEntry(
|
||||||
|
domain=DOMAIN,
|
||||||
|
title="Z-Wave JS",
|
||||||
|
connection_class=CONN_CLASS_LOCAL_PUSH,
|
||||||
|
data={
|
||||||
|
"url": "ws://host1:3001",
|
||||||
|
"use_addon": True,
|
||||||
|
"usb_path": device,
|
||||||
|
"network_key": network_key,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
await hass.config_entries.async_setup(entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert entry.state == ENTRY_STATE_SETUP_RETRY
|
||||||
|
assert create_shapshot.call_count == 1
|
||||||
|
assert create_shapshot.call_args == call(
|
||||||
|
hass,
|
||||||
|
{"name": f"addon_core_zwave_js_{addon_version}", "addons": ["core_zwave_js"]},
|
||||||
|
partial=True,
|
||||||
|
)
|
||||||
|
assert update_addon.call_count == update_calls
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"stop_addon_side_effect, entry_state",
|
||||||
|
[
|
||||||
|
(None, ENTRY_STATE_NOT_LOADED),
|
||||||
|
(HassioAPIError("Boom"), ENTRY_STATE_LOADED),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_stop_addon(
|
||||||
|
hass,
|
||||||
|
client,
|
||||||
|
addon_installed,
|
||||||
|
addon_running,
|
||||||
|
addon_options,
|
||||||
|
stop_addon,
|
||||||
|
stop_addon_side_effect,
|
||||||
|
entry_state,
|
||||||
|
):
|
||||||
|
"""Test stop the Z-Wave JS add-on on entry unload if entry is disabled."""
|
||||||
|
stop_addon.side_effect = stop_addon_side_effect
|
||||||
|
device = "/test"
|
||||||
|
network_key = "abc123"
|
||||||
|
entry = MockConfigEntry(
|
||||||
|
domain=DOMAIN,
|
||||||
|
title="Z-Wave JS",
|
||||||
|
connection_class=CONN_CLASS_LOCAL_PUSH,
|
||||||
|
data={
|
||||||
|
"url": "ws://host1:3001",
|
||||||
|
"use_addon": True,
|
||||||
|
"usb_path": device,
|
||||||
|
"network_key": network_key,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
await hass.config_entries.async_setup(entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert entry.state == ENTRY_STATE_LOADED
|
||||||
|
|
||||||
|
await hass.config_entries.async_set_disabled_by(entry.entry_id, DISABLED_USER)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert entry.state == entry_state
|
||||||
|
assert stop_addon.call_count == 1
|
||||||
|
assert stop_addon.call_args == call(hass, "core_zwave_js")
|
||||||
|
|
||||||
|
|
||||||
|
async def test_remove_entry(
|
||||||
|
hass, addon_installed, stop_addon, create_shapshot, uninstall_addon, caplog
|
||||||
|
):
|
||||||
"""Test remove the config entry."""
|
"""Test remove the config entry."""
|
||||||
# test successful remove without created add-on
|
# test successful remove without created add-on
|
||||||
entry = MockConfigEntry(
|
entry = MockConfigEntry(
|
||||||
|
@ -398,10 +579,19 @@ async def test_remove_entry(hass, stop_addon, uninstall_addon, caplog):
|
||||||
await hass.config_entries.async_remove(entry.entry_id)
|
await hass.config_entries.async_remove(entry.entry_id)
|
||||||
|
|
||||||
assert stop_addon.call_count == 1
|
assert stop_addon.call_count == 1
|
||||||
|
assert stop_addon.call_args == call(hass, "core_zwave_js")
|
||||||
|
assert create_shapshot.call_count == 1
|
||||||
|
assert create_shapshot.call_args == call(
|
||||||
|
hass,
|
||||||
|
{"name": "addon_core_zwave_js_1.0", "addons": ["core_zwave_js"]},
|
||||||
|
partial=True,
|
||||||
|
)
|
||||||
assert uninstall_addon.call_count == 1
|
assert uninstall_addon.call_count == 1
|
||||||
|
assert uninstall_addon.call_args == call(hass, "core_zwave_js")
|
||||||
assert entry.state == ENTRY_STATE_NOT_LOADED
|
assert entry.state == ENTRY_STATE_NOT_LOADED
|
||||||
assert len(hass.config_entries.async_entries(DOMAIN)) == 0
|
assert len(hass.config_entries.async_entries(DOMAIN)) == 0
|
||||||
stop_addon.reset_mock()
|
stop_addon.reset_mock()
|
||||||
|
create_shapshot.reset_mock()
|
||||||
uninstall_addon.reset_mock()
|
uninstall_addon.reset_mock()
|
||||||
|
|
||||||
# test add-on stop failure
|
# test add-on stop failure
|
||||||
|
@ -412,12 +602,39 @@ async def test_remove_entry(hass, stop_addon, uninstall_addon, caplog):
|
||||||
await hass.config_entries.async_remove(entry.entry_id)
|
await hass.config_entries.async_remove(entry.entry_id)
|
||||||
|
|
||||||
assert stop_addon.call_count == 1
|
assert stop_addon.call_count == 1
|
||||||
|
assert stop_addon.call_args == call(hass, "core_zwave_js")
|
||||||
|
assert create_shapshot.call_count == 0
|
||||||
assert uninstall_addon.call_count == 0
|
assert uninstall_addon.call_count == 0
|
||||||
assert entry.state == ENTRY_STATE_NOT_LOADED
|
assert entry.state == ENTRY_STATE_NOT_LOADED
|
||||||
assert len(hass.config_entries.async_entries(DOMAIN)) == 0
|
assert len(hass.config_entries.async_entries(DOMAIN)) == 0
|
||||||
assert "Failed to stop the Z-Wave JS add-on" in caplog.text
|
assert "Failed to stop the Z-Wave JS add-on" in caplog.text
|
||||||
stop_addon.side_effect = None
|
stop_addon.side_effect = None
|
||||||
stop_addon.reset_mock()
|
stop_addon.reset_mock()
|
||||||
|
create_shapshot.reset_mock()
|
||||||
|
uninstall_addon.reset_mock()
|
||||||
|
|
||||||
|
# test create snapshot failure
|
||||||
|
entry.add_to_hass(hass)
|
||||||
|
assert len(hass.config_entries.async_entries(DOMAIN)) == 1
|
||||||
|
create_shapshot.side_effect = HassioAPIError()
|
||||||
|
|
||||||
|
await hass.config_entries.async_remove(entry.entry_id)
|
||||||
|
|
||||||
|
assert stop_addon.call_count == 1
|
||||||
|
assert stop_addon.call_args == call(hass, "core_zwave_js")
|
||||||
|
assert create_shapshot.call_count == 1
|
||||||
|
assert create_shapshot.call_args == call(
|
||||||
|
hass,
|
||||||
|
{"name": "addon_core_zwave_js_1.0", "addons": ["core_zwave_js"]},
|
||||||
|
partial=True,
|
||||||
|
)
|
||||||
|
assert uninstall_addon.call_count == 0
|
||||||
|
assert entry.state == ENTRY_STATE_NOT_LOADED
|
||||||
|
assert len(hass.config_entries.async_entries(DOMAIN)) == 0
|
||||||
|
assert "Failed to create a snapshot of the Z-Wave JS add-on" in caplog.text
|
||||||
|
create_shapshot.side_effect = None
|
||||||
|
stop_addon.reset_mock()
|
||||||
|
create_shapshot.reset_mock()
|
||||||
uninstall_addon.reset_mock()
|
uninstall_addon.reset_mock()
|
||||||
|
|
||||||
# test add-on uninstall failure
|
# test add-on uninstall failure
|
||||||
|
@ -428,7 +645,15 @@ async def test_remove_entry(hass, stop_addon, uninstall_addon, caplog):
|
||||||
await hass.config_entries.async_remove(entry.entry_id)
|
await hass.config_entries.async_remove(entry.entry_id)
|
||||||
|
|
||||||
assert stop_addon.call_count == 1
|
assert stop_addon.call_count == 1
|
||||||
|
assert stop_addon.call_args == call(hass, "core_zwave_js")
|
||||||
|
assert create_shapshot.call_count == 1
|
||||||
|
assert create_shapshot.call_args == call(
|
||||||
|
hass,
|
||||||
|
{"name": "addon_core_zwave_js_1.0", "addons": ["core_zwave_js"]},
|
||||||
|
partial=True,
|
||||||
|
)
|
||||||
assert uninstall_addon.call_count == 1
|
assert uninstall_addon.call_count == 1
|
||||||
|
assert uninstall_addon.call_args == call(hass, "core_zwave_js")
|
||||||
assert entry.state == ENTRY_STATE_NOT_LOADED
|
assert entry.state == ENTRY_STATE_NOT_LOADED
|
||||||
assert len(hass.config_entries.async_entries(DOMAIN)) == 0
|
assert len(hass.config_entries.async_entries(DOMAIN)) == 0
|
||||||
assert "Failed to uninstall the Z-Wave JS add-on" in caplog.text
|
assert "Failed to uninstall the Z-Wave JS add-on" in caplog.text
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue