diff --git a/homeassistant/components/hassio/__init__.py b/homeassistant/components/hassio/__init__.py index 46592cbc20c..8535a0c3cc6 100644 --- a/homeassistant/components/hassio/__init__.py +++ b/homeassistant/components/hassio/__init__.py @@ -223,12 +223,24 @@ HARDWARE_INTEGRATIONS = { async def async_get_addon_info(hass: HomeAssistant, slug: str) -> dict: """Return add-on info. + The add-on must be installed. The caller of the function should handle HassioAPIError. """ hassio = hass.data[DOMAIN] 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 async def async_update_diagnostics(hass: HomeAssistant, diagnostics: bool) -> dict: """Update Supervisor diagnostics toggle. diff --git a/homeassistant/components/zwave_js/addon.py b/homeassistant/components/zwave_js/addon.py index 7552ee117cc..610fc850e90 100644 --- a/homeassistant/components/zwave_js/addon.py +++ b/homeassistant/components/zwave_js/addon.py @@ -14,6 +14,7 @@ from homeassistant.components.hassio import ( async_create_backup, async_get_addon_discovery_info, async_get_addon_info, + async_get_addon_store_info, async_install_addon, async_restart_addon, async_set_addon_options, @@ -136,7 +137,17 @@ class AddonManager: @api_error("Failed to get the Z-Wave JS add-on info") async def async_get_addon_info(self) -> AddonInfo: """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) return AddonInfo( options=addon_info["options"], @@ -148,10 +159,8 @@ class AddonManager: @callback def async_get_addon_state(self, addon_info: dict[str, Any]) -> AddonState: """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": addon_state = AddonState.RUNNING 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.""" 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") if not addon_info.update_available: @@ -301,6 +310,9 @@ class AddonManager: """Configure and start Z-Wave JS add-on.""" 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 = { CONF_ADDON_DEVICE: usb_path, CONF_ADDON_S0_LEGACY_KEY: s0_legacy_key, diff --git a/tests/components/hassio/test_init.py b/tests/components/hassio/test_init.py index 60fec517aa9..41b679e448a 100644 --- a/tests/components/hassio/test_init.py +++ b/tests/components/hassio/test_init.py @@ -8,7 +8,12 @@ import pytest from homeassistant.auth.const import GROUP_ID_ADMIN from homeassistant.components import frontend 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.sensor import DOMAIN as SENSOR_DOMAIN 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 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 diff --git a/tests/components/zwave_js/conftest.py b/tests/components/zwave_js/conftest.py index 7cf7ebd7ea2..1524aca719e 100644 --- a/tests/components/zwave_js/conftest.py +++ b/tests/components/zwave_js/conftest.py @@ -38,18 +38,56 @@ def mock_addon_info(addon_info_side_effect): 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") -def mock_addon_running(addon_info): +def mock_addon_running(addon_store_info, addon_info): """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["version"] = "1.0.0" return addon_info @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.""" + 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["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 @@ -81,13 +119,18 @@ def mock_set_addon_options(set_addon_options_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.""" async def install_addon(hass, slug): """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["version"] = "1.0" + addon_info.return_value["version"] = "1.0.0" return install_addon @@ -112,11 +155,16 @@ def mock_update_addon(): @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.""" async def start_addon(hass, slug): """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" return start_addon diff --git a/tests/components/zwave_js/test_config_flow.py b/tests/components/zwave_js/test_config_flow.py index f107a5fd8e2..a8a2c6c7191 100644 --- a/tests/components/zwave_js/test_config_flow.py +++ b/tests/components/zwave_js/test_config_flow.py @@ -422,7 +422,7 @@ async def test_abort_discovery_with_existing_entry( 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.""" 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( hass, supervisor, - addon_installed, + addon_not_installed, install_addon, addon_options, set_addon_options, start_addon, ): """Test discovery with add-on not installed.""" - addon_installed.return_value["version"] = None - result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_HASSIO}, @@ -1443,7 +1441,7 @@ async def test_addon_installed_already_configured( async def test_addon_not_installed( hass, supervisor, - addon_installed, + addon_not_installed, install_addon, addon_options, set_addon_options, @@ -1451,8 +1449,6 @@ async def test_addon_not_installed( get_addon_discovery_info, ): """Test add-on not installed.""" - addon_installed.return_value["version"] = None - result = await hass.config_entries.flow.async_init( 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 -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.""" - addon_installed.return_value["version"] = None install_addon.side_effect = HassioAPIError() result = await hass.config_entries.flow.async_init( @@ -2292,7 +2289,7 @@ async def test_options_addon_not_installed( hass, client, supervisor, - addon_installed, + addon_not_installed, install_addon, integration, addon_options, @@ -2306,7 +2303,6 @@ async def test_options_addon_not_installed( disconnect_calls, ): """Test options flow and add-on not installed on Supervisor.""" - addon_installed.return_value["version"] = None addon_options.update(old_addon_options) entry = integration entry.unique_id = "1234" diff --git a/tests/components/zwave_js/test_init.py b/tests/components/zwave_js/test_init.py index a2962261ac3..202088bb481 100644 --- a/tests/components/zwave_js/test_init.py +++ b/tests/components/zwave_js/test_init.py @@ -432,10 +432,14 @@ async def test_start_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.""" - addon_installed.return_value["version"] = None device = "/test" s0_legacy_key = "s0_legacy" 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, " "update_addon_side_effect, create_backup_side_effect", [ - ("1.0", True, 1, 1, None, None), - ("1.0", False, 0, 0, None, None), - ("1.0", True, 1, 1, HassioAPIError("Boom"), None), - ("1.0", True, 0, 1, None, HassioAPIError("Boom")), + ("1.0.0", True, 1, 1, None, None), + ("1.0.0", False, 0, 0, None, None), + ("1.0.0", True, 1, 1, HassioAPIError("Boom"), None), + ("1.0.0", True, 0, 1, None, HassioAPIError("Boom")), ], ) async def test_update_addon( @@ -720,7 +724,7 @@ async def test_remove_entry( assert create_backup.call_count == 1 assert create_backup.call_args == call( 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, ) 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_args == call( 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, ) 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_args == call( 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, ) assert uninstall_addon.call_count == 1