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:
Martin Hjelmare 2022-08-03 22:33:05 +02:00 committed by GitHub
parent dd862595a3
commit 842cc060f8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 122 additions and 32 deletions

View file

@ -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.

View file

@ -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,

View file

@ -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

View file

@ -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

View file

@ -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"

View file

@ -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