From f3fab5c1f5b9a6ae489b0dec9d9fd9338cf29e59 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Tue, 3 Jan 2023 02:28:21 +0100 Subject: [PATCH 1/3] Handle not available add-on in hassio add-on manager (#84943) * Handle not available add-on in hassio add-on manager * Fix zwave_js tests * Fix sky connect tests * Fix matter tests * Fix yellow tests * Update hardware tests --- .../components/hassio/addon_manager.py | 11 +++++ tests/components/hassio/test_addon_manager.py | 44 ++++++++++++++++++- .../homeassistant_hardware/conftest.py | 2 + .../homeassistant_sky_connect/conftest.py | 2 + .../homeassistant_yellow/conftest.py | 2 + tests/components/matter/conftest.py | 9 ++++ tests/components/matter/test_init.py | 2 +- tests/components/zwave_js/conftest.py | 11 +++++ tests/components/zwave_js/test_config_flow.py | 1 + 9 files changed, 81 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/hassio/addon_manager.py b/homeassistant/components/hassio/addon_manager.py index f240937c7f5..46eca080b1b 100644 --- a/homeassistant/components/hassio/addon_manager.py +++ b/homeassistant/components/hassio/addon_manager.py @@ -70,6 +70,7 @@ def api_error( class AddonInfo: """Represent the current add-on info state.""" + available: bool hostname: str | None options: dict[str, Any] state: AddonState @@ -144,6 +145,7 @@ class AddonManager: self._logger.debug("Add-on store info: %s", addon_store_info) if not addon_store_info["installed"]: return AddonInfo( + available=addon_store_info["available"], hostname=None, options={}, state=AddonState.NOT_INSTALLED, @@ -154,6 +156,7 @@ class AddonManager: addon_info = await async_get_addon_info(self._hass, self.addon_slug) addon_state = self.async_get_addon_state(addon_info) return AddonInfo( + available=addon_info["available"], hostname=addon_info["hostname"], options=addon_info["options"], state=addon_state, @@ -184,6 +187,11 @@ class AddonManager: @api_error("Failed to install the {addon_name} add-on") async def async_install_addon(self) -> None: """Install the managed add-on.""" + addon_info = await self.async_get_addon_info() + + if not addon_info.available: + raise AddonError(f"{self.addon_name} add-on is not available anymore") + await async_install_addon(self._hass, self.addon_slug) @api_error("Failed to uninstall the {addon_name} add-on") @@ -196,6 +204,9 @@ class AddonManager: """Update the managed add-on if needed.""" addon_info = await self.async_get_addon_info() + if not addon_info.available: + raise AddonError(f"{self.addon_name} add-on is not available anymore") + if addon_info.state is AddonState.NOT_INSTALLED: raise AddonError(f"{self.addon_name} add-on is not installed") diff --git a/tests/components/hassio/test_addon_manager.py b/tests/components/hassio/test_addon_manager.py index 7135f1ea646..5ee7856b9f7 100644 --- a/tests/components/hassio/test_addon_manager.py +++ b/tests/components/hassio/test_addon_manager.py @@ -32,6 +32,7 @@ def addon_not_installed_fixture( addon_store_info: AsyncMock, addon_info: AsyncMock ) -> AsyncMock: """Mock add-on not installed.""" + addon_store_info.return_value["available"] = True return addon_info @@ -41,10 +42,12 @@ def mock_addon_installed( ) -> AsyncMock: """Mock add-on already installed but not running.""" addon_store_info.return_value = { + "available": True, "installed": "1.0.0", "state": "stopped", "version": "1.0.0", } + addon_info.return_value["available"] = True addon_info.return_value["hostname"] = "core-test-addon" addon_info.return_value["state"] = "stopped" addon_info.return_value["version"] = "1.0.0" @@ -67,6 +70,7 @@ def addon_store_info_fixture() -> Generator[AsyncMock, None, None]: "homeassistant.components.hassio.addon_manager.async_get_addon_store_info" ) as addon_store_info: addon_store_info.return_value = { + "available": False, "installed": None, "state": None, "version": "1.0.0", @@ -81,6 +85,7 @@ def addon_info_fixture() -> Generator[AsyncMock, None, None]: "homeassistant.components.hassio.addon_manager.async_get_addon_info", ) as addon_info: addon_info.return_value = { + "available": False, "hostname": None, "options": {}, "state": None, @@ -180,6 +185,26 @@ async def test_not_installed_raises_exception( assert str(err.value) == "Test add-on is not installed" +async def test_not_available_raises_exception( + addon_manager: AddonManager, + addon_store_info: AsyncMock, + addon_info: AsyncMock, +) -> None: + """Test addon not available raises exception.""" + addon_store_info.return_value["available"] = False + addon_info.return_value["available"] = False + + with pytest.raises(AddonError) as err: + await addon_manager.async_install_addon() + + assert str(err.value) == "Test add-on is not available anymore" + + with pytest.raises(AddonError) as err: + await addon_manager.async_update_addon() + + assert str(err.value) == "Test add-on is not available anymore" + + async def test_get_addon_discovery_info( addon_manager: AddonManager, get_addon_discovery_info: AsyncMock ) -> None: @@ -222,6 +247,7 @@ async def test_get_addon_info_not_installed( ) -> None: """Test get addon info when addon is not installed..""" assert await addon_manager.async_get_addon_info() == AddonInfo( + available=True, hostname=None, options={}, state=AddonState.NOT_INSTALLED, @@ -243,6 +269,7 @@ async def test_get_addon_info( """Test get addon info when addon is installed.""" addon_installed.return_value["state"] = addon_info_state assert await addon_manager.async_get_addon_info() == AddonInfo( + available=True, hostname="core-test-addon", options={}, state=addon_state, @@ -308,18 +335,29 @@ async def test_set_addon_options_error( async def test_install_addon( - addon_manager: AddonManager, install_addon: AsyncMock + addon_manager: AddonManager, + install_addon: AsyncMock, + addon_store_info: AsyncMock, + addon_info: AsyncMock, ) -> None: """Test install addon.""" + addon_store_info.return_value["available"] = True + addon_info.return_value["available"] = True + await addon_manager.async_install_addon() assert install_addon.call_count == 1 async def test_install_addon_error( - addon_manager: AddonManager, install_addon: AsyncMock + addon_manager: AddonManager, + install_addon: AsyncMock, + addon_store_info: AsyncMock, + addon_info: AsyncMock, ) -> None: """Test install addon raises error.""" + addon_store_info.return_value["available"] = True + addon_info.return_value["available"] = True install_addon.side_effect = HassioAPIError("Boom") with pytest.raises(AddonError) as err: @@ -341,6 +379,7 @@ async def test_schedule_install_addon( assert addon_manager.task_in_progress() is True assert await addon_manager.async_get_addon_info() == AddonInfo( + available=True, hostname="core-test-addon", options={}, state=AddonState.INSTALLING, @@ -676,6 +715,7 @@ async def test_schedule_update_addon( assert addon_manager.task_in_progress() is True assert await addon_manager.async_get_addon_info() == AddonInfo( + available=True, hostname="core-test-addon", options={}, state=AddonState.UPDATING, diff --git a/tests/components/homeassistant_hardware/conftest.py b/tests/components/homeassistant_hardware/conftest.py index fd0ce2e761b..4add48781a9 100644 --- a/tests/components/homeassistant_hardware/conftest.py +++ b/tests/components/homeassistant_hardware/conftest.py @@ -67,6 +67,7 @@ def addon_store_info_fixture(): "homeassistant.components.hassio.addon_manager.async_get_addon_store_info" ) as addon_store_info: addon_store_info.return_value = { + "available": True, "installed": None, "state": None, "version": "1.0.0", @@ -81,6 +82,7 @@ def addon_info_fixture(): "homeassistant.components.hassio.addon_manager.async_get_addon_info", ) as addon_info: addon_info.return_value = { + "available": True, "hostname": None, "options": {}, "state": None, diff --git a/tests/components/homeassistant_sky_connect/conftest.py b/tests/components/homeassistant_sky_connect/conftest.py index 2d333c62b2d..f7f0bb8d128 100644 --- a/tests/components/homeassistant_sky_connect/conftest.py +++ b/tests/components/homeassistant_sky_connect/conftest.py @@ -69,6 +69,7 @@ def addon_store_info_fixture(): "homeassistant.components.hassio.addon_manager.async_get_addon_store_info" ) as addon_store_info: addon_store_info.return_value = { + "available": True, "installed": None, "state": None, "version": "1.0.0", @@ -83,6 +84,7 @@ def addon_info_fixture(): "homeassistant.components.hassio.addon_manager.async_get_addon_info", ) as addon_info: addon_info.return_value = { + "available": True, "hostname": None, "options": {}, "state": None, diff --git a/tests/components/homeassistant_yellow/conftest.py b/tests/components/homeassistant_yellow/conftest.py index 62595c11fe1..bc48c6b01fd 100644 --- a/tests/components/homeassistant_yellow/conftest.py +++ b/tests/components/homeassistant_yellow/conftest.py @@ -67,6 +67,7 @@ def addon_store_info_fixture(): "homeassistant.components.hassio.addon_manager.async_get_addon_store_info" ) as addon_store_info: addon_store_info.return_value = { + "available": True, "installed": None, "state": None, "version": "1.0.0", @@ -81,6 +82,7 @@ def addon_info_fixture(): "homeassistant.components.hassio.addon_manager.async_get_addon_info", ) as addon_info: addon_info.return_value = { + "available": True, "hostname": None, "options": {}, "state": None, diff --git a/tests/components/matter/conftest.py b/tests/components/matter/conftest.py index 03c8bc35687..b541e7c439e 100644 --- a/tests/components/matter/conftest.py +++ b/tests/components/matter/conftest.py @@ -77,6 +77,7 @@ def addon_store_info_fixture() -> Generator[AsyncMock, None, None]: "homeassistant.components.hassio.addon_manager.async_get_addon_store_info" ) as addon_store_info: addon_store_info.return_value = { + "available": False, "installed": None, "state": None, "version": "1.0.0", @@ -91,6 +92,7 @@ def addon_info_fixture() -> Generator[AsyncMock, None, None]: "homeassistant.components.hassio.addon_manager.async_get_addon_info", ) as addon_info: addon_info.return_value = { + "available": False, "hostname": None, "options": {}, "state": None, @@ -105,6 +107,7 @@ def addon_not_installed_fixture( addon_store_info: AsyncMock, addon_info: AsyncMock ) -> AsyncMock: """Mock add-on not installed.""" + addon_store_info.return_value["available"] = True return addon_info @@ -114,10 +117,12 @@ def addon_installed_fixture( ) -> AsyncMock: """Mock add-on already installed but not running.""" addon_store_info.return_value = { + "available": True, "installed": "1.0.0", "state": "stopped", "version": "1.0.0", } + addon_info.return_value["available"] = True addon_info.return_value["hostname"] = "core-matter-server" addon_info.return_value["state"] = "stopped" addon_info.return_value["version"] = "1.0.0" @@ -130,10 +135,12 @@ def addon_running_fixture( ) -> AsyncMock: """Mock add-on already running.""" addon_store_info.return_value = { + "available": True, "installed": "1.0.0", "state": "started", "version": "1.0.0", } + addon_info.return_value["available"] = True addon_info.return_value["hostname"] = "core-matter-server" addon_info.return_value["state"] = "started" addon_info.return_value["version"] = "1.0.0" @@ -149,10 +156,12 @@ def install_addon_fixture( async def install_addon_side_effect(hass: HomeAssistant, slug: str) -> None: """Mock install add-on.""" addon_store_info.return_value = { + "available": True, "installed": "1.0.0", "state": "stopped", "version": "1.0.0", } + addon_info.return_value["available"] = True addon_info.return_value["state"] = "stopped" addon_info.return_value["version"] = "1.0.0" diff --git a/tests/components/matter/test_init.py b/tests/components/matter/test_init.py index f34e428ecc0..c40d35ff74a 100644 --- a/tests/components/matter/test_init.py +++ b/tests/components/matter/test_init.py @@ -116,7 +116,7 @@ async def test_install_addon( await hass.async_block_till_done() assert entry.state is ConfigEntryState.SETUP_RETRY - assert addon_store_info.call_count == 2 + assert addon_store_info.call_count == 3 assert install_addon.call_count == 1 assert install_addon.call_args == call(hass, "core_matter_server") assert start_addon.call_count == 1 diff --git a/tests/components/zwave_js/conftest.py b/tests/components/zwave_js/conftest.py index ba97cfe4c36..0b0503a3e29 100644 --- a/tests/components/zwave_js/conftest.py +++ b/tests/components/zwave_js/conftest.py @@ -30,6 +30,7 @@ def mock_addon_info(addon_info_side_effect): side_effect=addon_info_side_effect, ) as addon_info: addon_info.return_value = { + "available": False, "hostname": None, "options": {}, "state": None, @@ -53,6 +54,7 @@ def mock_addon_store_info(addon_store_info_side_effect): side_effect=addon_store_info_side_effect, ) as addon_store_info: addon_store_info.return_value = { + "available": False, "installed": None, "state": None, "version": "1.0.0", @@ -64,10 +66,12 @@ def mock_addon_store_info(addon_store_info_side_effect): def mock_addon_running(addon_store_info, addon_info): """Mock add-on already running.""" addon_store_info.return_value = { + "available": True, "installed": "1.0.0", "state": "started", "version": "1.0.0", } + addon_info.return_value["available"] = True addon_info.return_value["state"] = "started" addon_info.return_value["version"] = "1.0.0" return addon_info @@ -77,10 +81,12 @@ def mock_addon_running(addon_store_info, addon_info): def mock_addon_installed(addon_store_info, addon_info): """Mock add-on already installed but not running.""" addon_store_info.return_value = { + "available": True, "installed": "1.0.0", "state": "stopped", "version": "1.0.0", } + addon_info.return_value["available"] = True addon_info.return_value["state"] = "stopped" addon_info.return_value["version"] = "1.0.0" return addon_info @@ -89,6 +95,7 @@ def mock_addon_installed(addon_store_info, addon_info): @pytest.fixture(name="addon_not_installed") def mock_addon_not_installed(addon_store_info, addon_info): """Mock add-on not installed.""" + addon_store_info.return_value["available"] = True return addon_info @@ -126,10 +133,12 @@ def install_addon_side_effect_fixture(addon_store_info, addon_info): async def install_addon(hass, slug): """Mock install add-on.""" addon_store_info.return_value = { + "available": True, "installed": "1.0.0", "state": "stopped", "version": "1.0.0", } + addon_info.return_value["available"] = True addon_info.return_value["state"] = "stopped" addon_info.return_value["version"] = "1.0.0" @@ -162,10 +171,12 @@ def start_addon_side_effect_fixture(addon_store_info, addon_info): async def start_addon(hass, slug): """Mock start add-on.""" addon_store_info.return_value = { + "available": True, "installed": "1.0.0", "state": "started", "version": "1.0.0", } + addon_info.return_value["available"] = True 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 eacf4b61cc8..2bff9c2cccb 100644 --- a/tests/components/zwave_js/test_config_flow.py +++ b/tests/components/zwave_js/test_config_flow.py @@ -536,6 +536,7 @@ async def test_abort_hassio_discovery_for_other_addon( async def test_usb_discovery( hass, supervisor, + addon_not_installed, install_addon, addon_options, get_addon_discovery_info, From 3ba59fbebe4830839da312bead2583f9de1f9caa Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 2 Jan 2023 20:30:09 -0500 Subject: [PATCH 2/3] Bumped version to 2022.12.9 --- homeassistant/const.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 16015beec72..a2d2a23bf6e 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -8,7 +8,7 @@ from .backports.enum import StrEnum APPLICATION_NAME: Final = "HomeAssistant" MAJOR_VERSION: Final = 2022 MINOR_VERSION: Final = 12 -PATCH_VERSION: Final = "8" +PATCH_VERSION: Final = "9" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 9, 0) diff --git a/pyproject.toml b/pyproject.toml index 8cb74b24793..05fda7fa5a0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "homeassistant" -version = "2022.12.8" +version = "2022.12.9" license = {text = "Apache-2.0"} description = "Open-source home automation platform running on Python 3." readme = "README.rst" From 6ebf2ec9ec814fceb22449331d3d392123b1fd2e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 31 Dec 2022 21:38:34 -1000 Subject: [PATCH 3/3] Fix failing HomeKit Controller diagnostics tests (#84936) --- .../homekit_controller/test_diagnostics.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/tests/components/homekit_controller/test_diagnostics.py b/tests/components/homekit_controller/test_diagnostics.py index eecb7ff51f8..218347c4ad6 100644 --- a/tests/components/homekit_controller/test_diagnostics.py +++ b/tests/components/homekit_controller/test_diagnostics.py @@ -1,4 +1,6 @@ """Test homekit_controller diagnostics.""" +from unittest.mock import ANY + from aiohttp import ClientSession from homeassistant.components.homekit_controller.const import KNOWN_DEVICES @@ -247,8 +249,8 @@ async def test_config_entry(hass: HomeAssistant, hass_client: ClientSession, utc "friendly_name": "Koogeek-LS1-20833F Identify" }, "entity_id": "button.koogeek_ls1_20833f_identify", - "last_changed": "2023-01-01T00:00:00+00:00", - "last_updated": "2023-01-01T00:00:00+00:00", + "last_changed": ANY, + "last_updated": ANY, "state": "unknown", }, "unit_of_measurement": None, @@ -269,8 +271,8 @@ async def test_config_entry(hass: HomeAssistant, hass_client: ClientSession, utc "supported_features": 0, }, "entity_id": "light.koogeek_ls1_20833f_light_strip", - "last_changed": "2023-01-01T00:00:00+00:00", - "last_updated": "2023-01-01T00:00:00+00:00", + "last_changed": ANY, + "last_updated": ANY, "state": "off", }, "unit_of_measurement": None, @@ -518,8 +520,8 @@ async def test_device(hass: HomeAssistant, hass_client: ClientSession, utcnow): "friendly_name": "Koogeek-LS1-20833F " "Identify" }, "entity_id": "button.koogeek_ls1_20833f_identify", - "last_changed": "2023-01-01T00:00:00+00:00", - "last_updated": "2023-01-01T00:00:00+00:00", + "last_changed": ANY, + "last_updated": ANY, "state": "unknown", }, "unit_of_measurement": None, @@ -540,8 +542,8 @@ async def test_device(hass: HomeAssistant, hass_client: ClientSession, utcnow): "supported_features": 0, }, "entity_id": "light.koogeek_ls1_20833f_light_strip", - "last_changed": "2023-01-01T00:00:00+00:00", - "last_updated": "2023-01-01T00:00:00+00:00", + "last_changed": ANY, + "last_updated": ANY, "state": "off", }, "unit_of_measurement": None,