Add zwave_js add-on manager (#47251)
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>pull/47292/head
parent
e443597b46
commit
d3721bcf26
|
@ -183,6 +183,18 @@ async def async_uninstall_addon(hass: HomeAssistantType, slug: str) -> dict:
|
|||
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
|
||||
@api_data
|
||||
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)
|
||||
|
||||
|
||||
@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
|
||||
@bind_hass
|
||||
def get_info(hass):
|
||||
|
|
|
@ -1,16 +1,14 @@
|
|||
"""The Z-Wave JS integration."""
|
||||
import asyncio
|
||||
import logging
|
||||
from typing import Callable, List
|
||||
|
||||
from async_timeout import timeout
|
||||
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.notification import Notification
|
||||
from zwave_js_server.model.value import ValueNotification
|
||||
|
||||
from homeassistant.components.hassio.handler import HassioAPIError
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import ATTR_DOMAIN, CONF_URL, EVENT_HOMEASSISTANT_STOP
|
||||
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.dispatcher import async_dispatcher_send
|
||||
|
||||
from .addon import AddonError, AddonManager, get_addon_manager
|
||||
from .api import async_register_api
|
||||
from .const import (
|
||||
ADDON_SLUG,
|
||||
ATTR_COMMAND_CLASS,
|
||||
ATTR_COMMAND_CLASS_NAME,
|
||||
ATTR_DEVICE_ID,
|
||||
|
@ -38,10 +36,14 @@ from .const import (
|
|||
ATTR_VALUE,
|
||||
ATTR_VALUE_RAW,
|
||||
CONF_INTEGRATION_CREATED_ADDON,
|
||||
CONF_NETWORK_KEY,
|
||||
CONF_USB_PATH,
|
||||
CONF_USE_ADDON,
|
||||
DATA_CLIENT,
|
||||
DATA_UNSUBSCRIBE,
|
||||
DOMAIN,
|
||||
EVENT_DEVICE_ADDED_TO_REGISTRY,
|
||||
LOGGER,
|
||||
PLATFORMS,
|
||||
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 .services import ZWaveServices
|
||||
|
||||
LOGGER = logging.getLogger(__package__)
|
||||
CONNECT_TIMEOUT = 10
|
||||
DATA_CLIENT_LISTEN_TASK = "client_listen_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:
|
||||
|
@ -87,6 +90,10 @@ def register_node_in_dev_reg(
|
|||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""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))
|
||||
dev_reg = await device_registry.async_get_registry(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
|
||||
try:
|
||||
async with timeout(CONNECT_TIMEOUT):
|
||||
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:
|
||||
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
|
||||
else:
|
||||
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] = []
|
||||
hass.data[DOMAIN][entry.entry_id] = {
|
||||
DATA_CLIENT: client,
|
||||
DATA_UNSUBSCRIBE: unsubscribe_callbacks,
|
||||
}
|
||||
entry_hass_data[DATA_CLIENT] = client
|
||||
entry_hass_data[DATA_UNSUBSCRIBE] = unsubscribe_callbacks
|
||||
|
||||
services = ZWaveServices(hass, ent_reg)
|
||||
services.async_register()
|
||||
|
@ -298,7 +315,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||
listen_task = asyncio.create_task(
|
||||
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(
|
||||
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())
|
||||
hass.data[DOMAIN][entry.entry_id][DATA_START_PLATFORM_TASK] = platform_task
|
||||
entry_hass_data[DATA_START_PLATFORM_TASK] = platform_task
|
||||
|
||||
return True
|
||||
|
||||
|
@ -416,6 +433,15 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||
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
|
||||
|
||||
|
||||
|
@ -424,12 +450,51 @@ async def async_remove_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:
|
|||
if not entry.data.get(CONF_INTEGRATION_CREATED_ADDON):
|
||||
return
|
||||
|
||||
addon_manager: AddonManager = get_addon_manager(hass)
|
||||
try:
|
||||
await hass.components.hassio.async_stop_addon(ADDON_SLUG)
|
||||
except HassioAPIError as err:
|
||||
LOGGER.error("Failed to stop the Z-Wave JS add-on: %s", err)
|
||||
await addon_manager.async_stop_addon()
|
||||
except AddonError as err:
|
||||
LOGGER.error(err)
|
||||
return
|
||||
try:
|
||||
await hass.components.hassio.async_uninstall_addon(ADDON_SLUG)
|
||||
except HassioAPIError as err:
|
||||
LOGGER.error("Failed to uninstall the Z-Wave JS add-on: %s", err)
|
||||
await addon_manager.async_create_snapshot()
|
||||
except AddonError as 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()
|
||||
|
|
|
@ -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 homeassistant import config_entries, exceptions
|
||||
from homeassistant.components.hassio import (
|
||||
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.components.hassio import is_hassio
|
||||
from homeassistant.const import CONF_URL
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.data_entry_flow import AbortFlow
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
|
||||
from .addon import AddonError, AddonManager, get_addon_manager
|
||||
from .const import ( # pylint:disable=unused-import
|
||||
ADDON_SLUG,
|
||||
CONF_ADDON_DEVICE,
|
||||
CONF_ADDON_NETWORK_KEY,
|
||||
CONF_INTEGRATION_CREATED_ADDON,
|
||||
CONF_NETWORK_KEY,
|
||||
CONF_USB_PATH,
|
||||
CONF_USE_ADDON,
|
||||
DOMAIN,
|
||||
)
|
||||
|
||||
_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"
|
||||
TITLE = "Z-Wave JS"
|
||||
|
||||
|
@ -180,6 +172,11 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
self, user_input: Optional[Dict[str, Any]] = None
|
||||
) -> Dict[str, Any]:
|
||||
"""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:
|
||||
return self.async_show_form(
|
||||
step_id="on_supervisor", data_schema=ON_SUPERVISOR_SCHEMA
|
||||
|
@ -212,7 +209,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
|
||||
try:
|
||||
await self.install_task
|
||||
except HassioAPIError as err:
|
||||
except AddonError as err:
|
||||
_LOGGER.error("Failed to install Z-Wave JS add-on: %s", err)
|
||||
return self.async_show_progress_done(next_step_id="install_failed")
|
||||
|
||||
|
@ -275,7 +272,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
|
||||
try:
|
||||
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)
|
||||
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:
|
||||
"""Start the Z-Wave JS add-on."""
|
||||
assert self.hass
|
||||
addon_manager: AddonManager = get_addon_manager(self.hass)
|
||||
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.
|
||||
for _ in range(ADDON_SETUP_TIMEOUT_ROUNDS):
|
||||
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:
|
||||
"""Return and cache Z-Wave JS add-on info."""
|
||||
addon_manager: AddonManager = get_addon_manager(self.hass)
|
||||
try:
|
||||
addon_info: dict = await async_get_addon_info(self.hass, ADDON_SLUG)
|
||||
except HassioAPIError as err:
|
||||
addon_info: dict = await addon_manager.async_get_addon_info()
|
||||
except AddonError as err:
|
||||
_LOGGER.error("Failed to get Z-Wave JS add-on info: %s", 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:
|
||||
"""Set Z-Wave JS add-on config."""
|
||||
options = {"options": config}
|
||||
addon_manager: AddonManager = get_addon_manager(self.hass)
|
||||
try:
|
||||
await async_set_addon_options(self.hass, ADDON_SLUG, options)
|
||||
except HassioAPIError as err:
|
||||
await addon_manager.async_set_addon_options(options)
|
||||
except AddonError as err:
|
||||
_LOGGER.error("Failed to set Z-Wave JS add-on config: %s", err)
|
||||
raise AbortFlow("addon_set_config_failed") from err
|
||||
|
||||
async def _async_install_addon(self) -> None:
|
||||
"""Install the Z-Wave JS add-on."""
|
||||
addon_manager: AddonManager = get_addon_manager(self.hass)
|
||||
try:
|
||||
await async_install_addon(self.hass, ADDON_SLUG)
|
||||
await addon_manager.async_install_addon()
|
||||
finally:
|
||||
# Continue the flow after show progress when the task is done.
|
||||
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:
|
||||
"""Return add-on discovery info."""
|
||||
addon_manager: AddonManager = get_addon_manager(self.hass)
|
||||
try:
|
||||
discovery_info = await async_get_addon_discovery_info(self.hass, ADDON_SLUG)
|
||||
except HassioAPIError as err:
|
||||
discovery_info_config = await addon_manager.async_get_addon_discovery_info()
|
||||
except AddonError as err:
|
||||
_LOGGER.error("Failed to get Z-Wave JS add-on discovery info: %s", 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
|
||||
|
||||
|
||||
|
|
|
@ -1,5 +1,11 @@
|
|||
"""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_NETWORK_KEY = "network_key"
|
||||
CONF_USB_PATH = "usb_path"
|
||||
CONF_USE_ADDON = "use_addon"
|
||||
DOMAIN = "zwave_js"
|
||||
PLATFORMS = [
|
||||
|
@ -19,6 +25,8 @@ DATA_UNSUBSCRIBE = "unsubs"
|
|||
|
||||
EVENT_DEVICE_ADDED_TO_REGISTRY = f"{DOMAIN}_device_added_to_registry"
|
||||
|
||||
LOGGER = logging.getLogger(__package__)
|
||||
|
||||
# constants for events
|
||||
ZWAVE_JS_EVENT = f"{DOMAIN}_event"
|
||||
ATTR_NODE_ID = "node_id"
|
||||
|
|
|
@ -41,7 +41,6 @@
|
|||
"addon_set_config_failed": "Failed to set Z-Wave JS configuration.",
|
||||
"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_missing_discovery_info": "Missing Z-Wave JS add-on discovery info.",
|
||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
|
||||
},
|
||||
"progress": {
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
"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_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_start_failed": "Failed to start the Z-Wave JS add-on.",
|
||||
"already_configured": "Device is already configured",
|
||||
|
@ -49,11 +48,6 @@
|
|||
},
|
||||
"start_addon": {
|
||||
"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
|
||||
|
||||
# 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")
|
||||
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):
|
||||
"""Mock get add-on discovery info."""
|
||||
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,
|
||||
return_value=discovery_info,
|
||||
) as 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")
|
||||
def server_version_side_effect_fixture():
|
||||
"""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
|
||||
|
||||
|
||||
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}])
|
||||
async def test_addon_running(
|
||||
hass,
|
||||
|
@ -654,7 +617,7 @@ async def test_addon_running(
|
|||
None,
|
||||
None,
|
||||
None,
|
||||
"addon_missing_discovery_info",
|
||||
"addon_get_discovery_info_failed",
|
||||
),
|
||||
(
|
||||
{"config": ADDON_DISCOVERY_INFO},
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
"""Test the Z-Wave JS init module."""
|
||||
from copy import deepcopy
|
||||
from unittest.mock import patch
|
||||
from unittest.mock import call, patch
|
||||
|
||||
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 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.config_entries import (
|
||||
CONN_CLASS_LOCAL_PUSH,
|
||||
DISABLED_USER,
|
||||
ENTRY_STATE_LOADED,
|
||||
ENTRY_STATE_NOT_LOADED,
|
||||
ENTRY_STATE_SETUP_RETRY,
|
||||
|
@ -34,22 +35,6 @@ def connect_timeout_fixture():
|
|||
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):
|
||||
"""Test the integration set up and unload."""
|
||||
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 successful remove without created add-on
|
||||
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)
|
||||
|
||||
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_args == call(hass, "core_zwave_js")
|
||||
assert entry.state == ENTRY_STATE_NOT_LOADED
|
||||
assert len(hass.config_entries.async_entries(DOMAIN)) == 0
|
||||
stop_addon.reset_mock()
|
||||
create_shapshot.reset_mock()
|
||||
uninstall_addon.reset_mock()
|
||||
|
||||
# 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)
|
||||
|
||||
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 entry.state == ENTRY_STATE_NOT_LOADED
|
||||
assert len(hass.config_entries.async_entries(DOMAIN)) == 0
|
||||
assert "Failed to stop the Z-Wave JS add-on" in caplog.text
|
||||
stop_addon.side_effect = None
|
||||
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()
|
||||
|
||||
# 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)
|
||||
|
||||
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_args == call(hass, "core_zwave_js")
|
||||
assert entry.state == ENTRY_STATE_NOT_LOADED
|
||||
assert len(hass.config_entries.async_entries(DOMAIN)) == 0
|
||||
assert "Failed to uninstall the Z-Wave JS add-on" in caplog.text
|
||||
|
|
Loading…
Reference in New Issue