Fix zwave_js addon info (#76044)
* Add add-on store info command * Use add-on store info command in zwave_js * Fix init tests * Update tests * Fix method for addon store info * Fix response parsing * Fix store addon installed response parsing * Remove addon info log that can contain network keys * Add supervisor store addon info test * Default to version None if add-on not installed Co-authored-by: Mike Degatano <michael.degatano@gmail.com> Co-authored-by: Mike Degatano <michael.degatano@gmail.com>
This commit is contained in:
parent
dd862595a3
commit
842cc060f8
6 changed files with 122 additions and 32 deletions
|
@ -223,12 +223,24 @@ HARDWARE_INTEGRATIONS = {
|
||||||
async def async_get_addon_info(hass: HomeAssistant, slug: str) -> dict:
|
async def async_get_addon_info(hass: HomeAssistant, slug: str) -> dict:
|
||||||
"""Return add-on info.
|
"""Return add-on info.
|
||||||
|
|
||||||
|
The add-on must be installed.
|
||||||
The caller of the function should handle HassioAPIError.
|
The caller of the function should handle HassioAPIError.
|
||||||
"""
|
"""
|
||||||
hassio = hass.data[DOMAIN]
|
hassio = hass.data[DOMAIN]
|
||||||
return await hassio.get_addon_info(slug)
|
return await hassio.get_addon_info(slug)
|
||||||
|
|
||||||
|
|
||||||
|
@api_data
|
||||||
|
async def async_get_addon_store_info(hass: HomeAssistant, slug: str) -> dict:
|
||||||
|
"""Return add-on store info.
|
||||||
|
|
||||||
|
The caller of the function should handle HassioAPIError.
|
||||||
|
"""
|
||||||
|
hassio: HassIO = hass.data[DOMAIN]
|
||||||
|
command = f"/store/addons/{slug}"
|
||||||
|
return await hassio.send_command(command, method="get")
|
||||||
|
|
||||||
|
|
||||||
@bind_hass
|
@bind_hass
|
||||||
async def async_update_diagnostics(hass: HomeAssistant, diagnostics: bool) -> dict:
|
async def async_update_diagnostics(hass: HomeAssistant, diagnostics: bool) -> dict:
|
||||||
"""Update Supervisor diagnostics toggle.
|
"""Update Supervisor diagnostics toggle.
|
||||||
|
|
|
@ -14,6 +14,7 @@ from homeassistant.components.hassio import (
|
||||||
async_create_backup,
|
async_create_backup,
|
||||||
async_get_addon_discovery_info,
|
async_get_addon_discovery_info,
|
||||||
async_get_addon_info,
|
async_get_addon_info,
|
||||||
|
async_get_addon_store_info,
|
||||||
async_install_addon,
|
async_install_addon,
|
||||||
async_restart_addon,
|
async_restart_addon,
|
||||||
async_set_addon_options,
|
async_set_addon_options,
|
||||||
|
@ -136,7 +137,17 @@ class AddonManager:
|
||||||
@api_error("Failed to get the Z-Wave JS add-on info")
|
@api_error("Failed to get the Z-Wave JS add-on info")
|
||||||
async def async_get_addon_info(self) -> AddonInfo:
|
async def async_get_addon_info(self) -> AddonInfo:
|
||||||
"""Return and cache Z-Wave JS add-on info."""
|
"""Return and cache Z-Wave JS add-on info."""
|
||||||
addon_info: dict = await async_get_addon_info(self._hass, ADDON_SLUG)
|
addon_store_info = await async_get_addon_store_info(self._hass, ADDON_SLUG)
|
||||||
|
LOGGER.debug("Add-on store info: %s", addon_store_info)
|
||||||
|
if not addon_store_info["installed"]:
|
||||||
|
return AddonInfo(
|
||||||
|
options={},
|
||||||
|
state=AddonState.NOT_INSTALLED,
|
||||||
|
update_available=False,
|
||||||
|
version=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
addon_info = await async_get_addon_info(self._hass, ADDON_SLUG)
|
||||||
addon_state = self.async_get_addon_state(addon_info)
|
addon_state = self.async_get_addon_state(addon_info)
|
||||||
return AddonInfo(
|
return AddonInfo(
|
||||||
options=addon_info["options"],
|
options=addon_info["options"],
|
||||||
|
@ -148,10 +159,8 @@ class AddonManager:
|
||||||
@callback
|
@callback
|
||||||
def async_get_addon_state(self, addon_info: dict[str, Any]) -> AddonState:
|
def async_get_addon_state(self, addon_info: dict[str, Any]) -> AddonState:
|
||||||
"""Return the current state of the Z-Wave JS add-on."""
|
"""Return the current state of the Z-Wave JS add-on."""
|
||||||
addon_state = AddonState.NOT_INSTALLED
|
addon_state = AddonState.NOT_RUNNING
|
||||||
|
|
||||||
if addon_info["version"] is not None:
|
|
||||||
addon_state = AddonState.NOT_RUNNING
|
|
||||||
if addon_info["state"] == "started":
|
if addon_info["state"] == "started":
|
||||||
addon_state = AddonState.RUNNING
|
addon_state = AddonState.RUNNING
|
||||||
if self._install_task and not self._install_task.done():
|
if self._install_task and not self._install_task.done():
|
||||||
|
@ -226,7 +235,7 @@ class AddonManager:
|
||||||
"""Update the Z-Wave JS add-on if needed."""
|
"""Update the Z-Wave JS add-on if needed."""
|
||||||
addon_info = await self.async_get_addon_info()
|
addon_info = await self.async_get_addon_info()
|
||||||
|
|
||||||
if addon_info.version is None:
|
if addon_info.state is AddonState.NOT_INSTALLED:
|
||||||
raise AddonError("Z-Wave JS add-on is not installed")
|
raise AddonError("Z-Wave JS add-on is not installed")
|
||||||
|
|
||||||
if not addon_info.update_available:
|
if not addon_info.update_available:
|
||||||
|
@ -301,6 +310,9 @@ class AddonManager:
|
||||||
"""Configure and start Z-Wave JS add-on."""
|
"""Configure and start Z-Wave JS add-on."""
|
||||||
addon_info = await self.async_get_addon_info()
|
addon_info = await self.async_get_addon_info()
|
||||||
|
|
||||||
|
if addon_info.state is AddonState.NOT_INSTALLED:
|
||||||
|
raise AddonError("Z-Wave JS add-on is not installed")
|
||||||
|
|
||||||
new_addon_options = {
|
new_addon_options = {
|
||||||
CONF_ADDON_DEVICE: usb_path,
|
CONF_ADDON_DEVICE: usb_path,
|
||||||
CONF_ADDON_S0_LEGACY_KEY: s0_legacy_key,
|
CONF_ADDON_S0_LEGACY_KEY: s0_legacy_key,
|
||||||
|
|
|
@ -8,7 +8,12 @@ import pytest
|
||||||
from homeassistant.auth.const import GROUP_ID_ADMIN
|
from homeassistant.auth.const import GROUP_ID_ADMIN
|
||||||
from homeassistant.components import frontend
|
from homeassistant.components import frontend
|
||||||
from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN
|
from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN
|
||||||
from homeassistant.components.hassio import ADDONS_COORDINATOR, DOMAIN, STORAGE_KEY
|
from homeassistant.components.hassio import (
|
||||||
|
ADDONS_COORDINATOR,
|
||||||
|
DOMAIN,
|
||||||
|
STORAGE_KEY,
|
||||||
|
async_get_addon_store_info,
|
||||||
|
)
|
||||||
from homeassistant.components.hassio.handler import HassioAPIError
|
from homeassistant.components.hassio.handler import HassioAPIError
|
||||||
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
|
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
|
||||||
from homeassistant.helpers.device_registry import async_get
|
from homeassistant.helpers.device_registry import async_get
|
||||||
|
@ -748,3 +753,16 @@ async def test_setup_hardware_integration(hass, aioclient_mock, integration):
|
||||||
|
|
||||||
assert aioclient_mock.call_count == 15
|
assert aioclient_mock.call_count == 15
|
||||||
assert len(mock_setup_entry.mock_calls) == 1
|
assert len(mock_setup_entry.mock_calls) == 1
|
||||||
|
|
||||||
|
|
||||||
|
async def test_get_store_addon_info(hass, hassio_stubs, aioclient_mock):
|
||||||
|
"""Test get store add-on info from Supervisor API."""
|
||||||
|
aioclient_mock.clear_requests()
|
||||||
|
aioclient_mock.get(
|
||||||
|
"http://127.0.0.1/store/addons/test",
|
||||||
|
json={"result": "ok", "data": {"name": "bla"}},
|
||||||
|
)
|
||||||
|
|
||||||
|
data = await async_get_addon_store_info(hass, "test")
|
||||||
|
assert data["name"] == "bla"
|
||||||
|
assert aioclient_mock.call_count == 1
|
||||||
|
|
|
@ -38,18 +38,56 @@ def mock_addon_info(addon_info_side_effect):
|
||||||
yield addon_info
|
yield addon_info
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(name="addon_store_info_side_effect")
|
||||||
|
def addon_store_info_side_effect_fixture():
|
||||||
|
"""Return the add-on store info side effect."""
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(name="addon_store_info")
|
||||||
|
def mock_addon_store_info(addon_store_info_side_effect):
|
||||||
|
"""Mock Supervisor add-on info."""
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.zwave_js.addon.async_get_addon_store_info",
|
||||||
|
side_effect=addon_store_info_side_effect,
|
||||||
|
) as addon_store_info:
|
||||||
|
addon_store_info.return_value = {
|
||||||
|
"installed": None,
|
||||||
|
"state": None,
|
||||||
|
"version": "1.0.0",
|
||||||
|
}
|
||||||
|
yield addon_store_info
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(name="addon_running")
|
@pytest.fixture(name="addon_running")
|
||||||
def mock_addon_running(addon_info):
|
def mock_addon_running(addon_store_info, addon_info):
|
||||||
"""Mock add-on already running."""
|
"""Mock add-on already running."""
|
||||||
|
addon_store_info.return_value = {
|
||||||
|
"installed": "1.0.0",
|
||||||
|
"state": "started",
|
||||||
|
"version": "1.0.0",
|
||||||
|
}
|
||||||
addon_info.return_value["state"] = "started"
|
addon_info.return_value["state"] = "started"
|
||||||
|
addon_info.return_value["version"] = "1.0.0"
|
||||||
return addon_info
|
return addon_info
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(name="addon_installed")
|
@pytest.fixture(name="addon_installed")
|
||||||
def mock_addon_installed(addon_info):
|
def mock_addon_installed(addon_store_info, addon_info):
|
||||||
"""Mock add-on already installed but not running."""
|
"""Mock add-on already installed but not running."""
|
||||||
|
addon_store_info.return_value = {
|
||||||
|
"installed": "1.0.0",
|
||||||
|
"state": "stopped",
|
||||||
|
"version": "1.0.0",
|
||||||
|
}
|
||||||
addon_info.return_value["state"] = "stopped"
|
addon_info.return_value["state"] = "stopped"
|
||||||
addon_info.return_value["version"] = "1.0"
|
addon_info.return_value["version"] = "1.0.0"
|
||||||
|
return addon_info
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(name="addon_not_installed")
|
||||||
|
def mock_addon_not_installed(addon_store_info, addon_info):
|
||||||
|
"""Mock add-on not installed."""
|
||||||
return addon_info
|
return addon_info
|
||||||
|
|
||||||
|
|
||||||
|
@ -81,13 +119,18 @@ def mock_set_addon_options(set_addon_options_side_effect):
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(name="install_addon_side_effect")
|
@pytest.fixture(name="install_addon_side_effect")
|
||||||
def install_addon_side_effect_fixture(addon_info):
|
def install_addon_side_effect_fixture(addon_store_info, addon_info):
|
||||||
"""Return the install add-on side effect."""
|
"""Return the install add-on side effect."""
|
||||||
|
|
||||||
async def install_addon(hass, slug):
|
async def install_addon(hass, slug):
|
||||||
"""Mock install add-on."""
|
"""Mock install add-on."""
|
||||||
|
addon_store_info.return_value = {
|
||||||
|
"installed": "1.0.0",
|
||||||
|
"state": "stopped",
|
||||||
|
"version": "1.0.0",
|
||||||
|
}
|
||||||
addon_info.return_value["state"] = "stopped"
|
addon_info.return_value["state"] = "stopped"
|
||||||
addon_info.return_value["version"] = "1.0"
|
addon_info.return_value["version"] = "1.0.0"
|
||||||
|
|
||||||
return install_addon
|
return install_addon
|
||||||
|
|
||||||
|
@ -112,11 +155,16 @@ def mock_update_addon():
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(name="start_addon_side_effect")
|
@pytest.fixture(name="start_addon_side_effect")
|
||||||
def start_addon_side_effect_fixture(addon_info):
|
def start_addon_side_effect_fixture(addon_store_info, addon_info):
|
||||||
"""Return the start add-on options side effect."""
|
"""Return the start add-on options side effect."""
|
||||||
|
|
||||||
async def start_addon(hass, slug):
|
async def start_addon(hass, slug):
|
||||||
"""Mock start add-on."""
|
"""Mock start add-on."""
|
||||||
|
addon_store_info.return_value = {
|
||||||
|
"installed": "1.0.0",
|
||||||
|
"state": "started",
|
||||||
|
"version": "1.0.0",
|
||||||
|
}
|
||||||
addon_info.return_value["state"] = "started"
|
addon_info.return_value["state"] = "started"
|
||||||
|
|
||||||
return start_addon
|
return start_addon
|
||||||
|
|
|
@ -422,7 +422,7 @@ async def test_abort_discovery_with_existing_entry(
|
||||||
|
|
||||||
|
|
||||||
async def test_abort_hassio_discovery_with_existing_flow(
|
async def test_abort_hassio_discovery_with_existing_flow(
|
||||||
hass, supervisor, addon_options
|
hass, supervisor, addon_installed, addon_options
|
||||||
):
|
):
|
||||||
"""Test hassio discovery flow is aborted when another discovery has happened."""
|
"""Test hassio discovery flow is aborted when another discovery has happened."""
|
||||||
result = await hass.config_entries.flow.async_init(
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
@ -701,15 +701,13 @@ async def test_discovery_addon_not_running(
|
||||||
async def test_discovery_addon_not_installed(
|
async def test_discovery_addon_not_installed(
|
||||||
hass,
|
hass,
|
||||||
supervisor,
|
supervisor,
|
||||||
addon_installed,
|
addon_not_installed,
|
||||||
install_addon,
|
install_addon,
|
||||||
addon_options,
|
addon_options,
|
||||||
set_addon_options,
|
set_addon_options,
|
||||||
start_addon,
|
start_addon,
|
||||||
):
|
):
|
||||||
"""Test discovery with add-on not installed."""
|
"""Test discovery with add-on not installed."""
|
||||||
addon_installed.return_value["version"] = None
|
|
||||||
|
|
||||||
result = await hass.config_entries.flow.async_init(
|
result = await hass.config_entries.flow.async_init(
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
context={"source": config_entries.SOURCE_HASSIO},
|
context={"source": config_entries.SOURCE_HASSIO},
|
||||||
|
@ -1443,7 +1441,7 @@ async def test_addon_installed_already_configured(
|
||||||
async def test_addon_not_installed(
|
async def test_addon_not_installed(
|
||||||
hass,
|
hass,
|
||||||
supervisor,
|
supervisor,
|
||||||
addon_installed,
|
addon_not_installed,
|
||||||
install_addon,
|
install_addon,
|
||||||
addon_options,
|
addon_options,
|
||||||
set_addon_options,
|
set_addon_options,
|
||||||
|
@ -1451,8 +1449,6 @@ async def test_addon_not_installed(
|
||||||
get_addon_discovery_info,
|
get_addon_discovery_info,
|
||||||
):
|
):
|
||||||
"""Test add-on not installed."""
|
"""Test add-on not installed."""
|
||||||
addon_installed.return_value["version"] = None
|
|
||||||
|
|
||||||
result = await hass.config_entries.flow.async_init(
|
result = await hass.config_entries.flow.async_init(
|
||||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
)
|
)
|
||||||
|
@ -1533,9 +1529,10 @@ async def test_addon_not_installed(
|
||||||
assert len(mock_setup_entry.mock_calls) == 1
|
assert len(mock_setup_entry.mock_calls) == 1
|
||||||
|
|
||||||
|
|
||||||
async def test_install_addon_failure(hass, supervisor, addon_installed, install_addon):
|
async def test_install_addon_failure(
|
||||||
|
hass, supervisor, addon_not_installed, install_addon
|
||||||
|
):
|
||||||
"""Test add-on install failure."""
|
"""Test add-on install failure."""
|
||||||
addon_installed.return_value["version"] = None
|
|
||||||
install_addon.side_effect = HassioAPIError()
|
install_addon.side_effect = HassioAPIError()
|
||||||
|
|
||||||
result = await hass.config_entries.flow.async_init(
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
@ -2292,7 +2289,7 @@ async def test_options_addon_not_installed(
|
||||||
hass,
|
hass,
|
||||||
client,
|
client,
|
||||||
supervisor,
|
supervisor,
|
||||||
addon_installed,
|
addon_not_installed,
|
||||||
install_addon,
|
install_addon,
|
||||||
integration,
|
integration,
|
||||||
addon_options,
|
addon_options,
|
||||||
|
@ -2306,7 +2303,6 @@ async def test_options_addon_not_installed(
|
||||||
disconnect_calls,
|
disconnect_calls,
|
||||||
):
|
):
|
||||||
"""Test options flow and add-on not installed on Supervisor."""
|
"""Test options flow and add-on not installed on Supervisor."""
|
||||||
addon_installed.return_value["version"] = None
|
|
||||||
addon_options.update(old_addon_options)
|
addon_options.update(old_addon_options)
|
||||||
entry = integration
|
entry = integration
|
||||||
entry.unique_id = "1234"
|
entry.unique_id = "1234"
|
||||||
|
|
|
@ -432,10 +432,14 @@ async def test_start_addon(
|
||||||
|
|
||||||
|
|
||||||
async def test_install_addon(
|
async def test_install_addon(
|
||||||
hass, addon_installed, install_addon, addon_options, set_addon_options, start_addon
|
hass,
|
||||||
|
addon_not_installed,
|
||||||
|
install_addon,
|
||||||
|
addon_options,
|
||||||
|
set_addon_options,
|
||||||
|
start_addon,
|
||||||
):
|
):
|
||||||
"""Test install and start the Z-Wave JS add-on during entry setup."""
|
"""Test install and start the Z-Wave JS add-on during entry setup."""
|
||||||
addon_installed.return_value["version"] = None
|
|
||||||
device = "/test"
|
device = "/test"
|
||||||
s0_legacy_key = "s0_legacy"
|
s0_legacy_key = "s0_legacy"
|
||||||
s2_access_control_key = "s2_access_control"
|
s2_access_control_key = "s2_access_control"
|
||||||
|
@ -583,10 +587,10 @@ async def test_addon_options_changed(
|
||||||
"addon_version, update_available, update_calls, backup_calls, "
|
"addon_version, update_available, update_calls, backup_calls, "
|
||||||
"update_addon_side_effect, create_backup_side_effect",
|
"update_addon_side_effect, create_backup_side_effect",
|
||||||
[
|
[
|
||||||
("1.0", True, 1, 1, None, None),
|
("1.0.0", True, 1, 1, None, None),
|
||||||
("1.0", False, 0, 0, None, None),
|
("1.0.0", False, 0, 0, None, None),
|
||||||
("1.0", True, 1, 1, HassioAPIError("Boom"), None),
|
("1.0.0", True, 1, 1, HassioAPIError("Boom"), None),
|
||||||
("1.0", True, 0, 1, None, HassioAPIError("Boom")),
|
("1.0.0", True, 0, 1, None, HassioAPIError("Boom")),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
async def test_update_addon(
|
async def test_update_addon(
|
||||||
|
@ -720,7 +724,7 @@ async def test_remove_entry(
|
||||||
assert create_backup.call_count == 1
|
assert create_backup.call_count == 1
|
||||||
assert create_backup.call_args == call(
|
assert create_backup.call_args == call(
|
||||||
hass,
|
hass,
|
||||||
{"name": "addon_core_zwave_js_1.0", "addons": ["core_zwave_js"]},
|
{"name": "addon_core_zwave_js_1.0.0", "addons": ["core_zwave_js"]},
|
||||||
partial=True,
|
partial=True,
|
||||||
)
|
)
|
||||||
assert uninstall_addon.call_count == 1
|
assert uninstall_addon.call_count == 1
|
||||||
|
@ -762,7 +766,7 @@ async def test_remove_entry(
|
||||||
assert create_backup.call_count == 1
|
assert create_backup.call_count == 1
|
||||||
assert create_backup.call_args == call(
|
assert create_backup.call_args == call(
|
||||||
hass,
|
hass,
|
||||||
{"name": "addon_core_zwave_js_1.0", "addons": ["core_zwave_js"]},
|
{"name": "addon_core_zwave_js_1.0.0", "addons": ["core_zwave_js"]},
|
||||||
partial=True,
|
partial=True,
|
||||||
)
|
)
|
||||||
assert uninstall_addon.call_count == 0
|
assert uninstall_addon.call_count == 0
|
||||||
|
@ -786,7 +790,7 @@ async def test_remove_entry(
|
||||||
assert create_backup.call_count == 1
|
assert create_backup.call_count == 1
|
||||||
assert create_backup.call_args == call(
|
assert create_backup.call_args == call(
|
||||||
hass,
|
hass,
|
||||||
{"name": "addon_core_zwave_js_1.0", "addons": ["core_zwave_js"]},
|
{"name": "addon_core_zwave_js_1.0.0", "addons": ["core_zwave_js"]},
|
||||||
partial=True,
|
partial=True,
|
||||||
)
|
)
|
||||||
assert uninstall_addon.call_count == 1
|
assert uninstall_addon.call_count == 1
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue