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
This commit is contained in:
Martin Hjelmare 2023-01-03 02:28:21 +01:00 committed by GitHub
parent 240e1fd8f3
commit 94b80db968
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 81 additions and 3 deletions

View file

@ -70,6 +70,7 @@ def api_error(
class AddonInfo: class AddonInfo:
"""Represent the current add-on info state.""" """Represent the current add-on info state."""
available: bool
hostname: str | None hostname: str | None
options: dict[str, Any] options: dict[str, Any]
state: AddonState state: AddonState
@ -144,6 +145,7 @@ class AddonManager:
self._logger.debug("Add-on store info: %s", addon_store_info) self._logger.debug("Add-on store info: %s", addon_store_info)
if not addon_store_info["installed"]: if not addon_store_info["installed"]:
return AddonInfo( return AddonInfo(
available=addon_store_info["available"],
hostname=None, hostname=None,
options={}, options={},
state=AddonState.NOT_INSTALLED, state=AddonState.NOT_INSTALLED,
@ -154,6 +156,7 @@ class AddonManager:
addon_info = await async_get_addon_info(self._hass, self.addon_slug) addon_info = await async_get_addon_info(self._hass, self.addon_slug)
addon_state = self.async_get_addon_state(addon_info) addon_state = self.async_get_addon_state(addon_info)
return AddonInfo( return AddonInfo(
available=addon_info["available"],
hostname=addon_info["hostname"], hostname=addon_info["hostname"],
options=addon_info["options"], options=addon_info["options"],
state=addon_state, state=addon_state,
@ -184,6 +187,11 @@ class AddonManager:
@api_error("Failed to install the {addon_name} add-on") @api_error("Failed to install the {addon_name} add-on")
async def async_install_addon(self) -> None: async def async_install_addon(self) -> None:
"""Install the managed add-on.""" """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) await async_install_addon(self._hass, self.addon_slug)
@api_error("Failed to uninstall the {addon_name} add-on") @api_error("Failed to uninstall the {addon_name} add-on")
@ -196,6 +204,9 @@ class AddonManager:
"""Update the managed add-on if needed.""" """Update the managed add-on if needed."""
addon_info = await self.async_get_addon_info() 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: if addon_info.state is AddonState.NOT_INSTALLED:
raise AddonError(f"{self.addon_name} add-on is not installed") raise AddonError(f"{self.addon_name} add-on is not installed")

View file

@ -32,6 +32,7 @@ def addon_not_installed_fixture(
addon_store_info: AsyncMock, addon_info: AsyncMock addon_store_info: AsyncMock, addon_info: AsyncMock
) -> AsyncMock: ) -> AsyncMock:
"""Mock add-on not installed.""" """Mock add-on not installed."""
addon_store_info.return_value["available"] = True
return addon_info return addon_info
@ -41,10 +42,12 @@ def mock_addon_installed(
) -> AsyncMock: ) -> AsyncMock:
"""Mock add-on already installed but not running.""" """Mock add-on already installed but not running."""
addon_store_info.return_value = { addon_store_info.return_value = {
"available": True,
"installed": "1.0.0", "installed": "1.0.0",
"state": "stopped", "state": "stopped",
"version": "1.0.0", "version": "1.0.0",
} }
addon_info.return_value["available"] = True
addon_info.return_value["hostname"] = "core-test-addon" addon_info.return_value["hostname"] = "core-test-addon"
addon_info.return_value["state"] = "stopped" addon_info.return_value["state"] = "stopped"
addon_info.return_value["version"] = "1.0.0" 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" "homeassistant.components.hassio.addon_manager.async_get_addon_store_info"
) as addon_store_info: ) as addon_store_info:
addon_store_info.return_value = { addon_store_info.return_value = {
"available": False,
"installed": None, "installed": None,
"state": None, "state": None,
"version": "1.0.0", "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", "homeassistant.components.hassio.addon_manager.async_get_addon_info",
) as addon_info: ) as addon_info:
addon_info.return_value = { addon_info.return_value = {
"available": False,
"hostname": None, "hostname": None,
"options": {}, "options": {},
"state": None, "state": None,
@ -180,6 +185,26 @@ async def test_not_installed_raises_exception(
assert str(err.value) == "Test add-on is not installed" 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( async def test_get_addon_discovery_info(
addon_manager: AddonManager, get_addon_discovery_info: AsyncMock addon_manager: AddonManager, get_addon_discovery_info: AsyncMock
) -> None: ) -> None:
@ -222,6 +247,7 @@ async def test_get_addon_info_not_installed(
) -> None: ) -> None:
"""Test get addon info when addon is not installed..""" """Test get addon info when addon is not installed.."""
assert await addon_manager.async_get_addon_info() == AddonInfo( assert await addon_manager.async_get_addon_info() == AddonInfo(
available=True,
hostname=None, hostname=None,
options={}, options={},
state=AddonState.NOT_INSTALLED, state=AddonState.NOT_INSTALLED,
@ -243,6 +269,7 @@ async def test_get_addon_info(
"""Test get addon info when addon is installed.""" """Test get addon info when addon is installed."""
addon_installed.return_value["state"] = addon_info_state addon_installed.return_value["state"] = addon_info_state
assert await addon_manager.async_get_addon_info() == AddonInfo( assert await addon_manager.async_get_addon_info() == AddonInfo(
available=True,
hostname="core-test-addon", hostname="core-test-addon",
options={}, options={},
state=addon_state, state=addon_state,
@ -308,18 +335,29 @@ async def test_set_addon_options_error(
async def test_install_addon( 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: ) -> None:
"""Test install addon.""" """Test install addon."""
addon_store_info.return_value["available"] = True
addon_info.return_value["available"] = True
await addon_manager.async_install_addon() await addon_manager.async_install_addon()
assert install_addon.call_count == 1 assert install_addon.call_count == 1
async def test_install_addon_error( 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: ) -> None:
"""Test install addon raises error.""" """Test install addon raises error."""
addon_store_info.return_value["available"] = True
addon_info.return_value["available"] = True
install_addon.side_effect = HassioAPIError("Boom") install_addon.side_effect = HassioAPIError("Boom")
with pytest.raises(AddonError) as err: 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 addon_manager.task_in_progress() is True
assert await addon_manager.async_get_addon_info() == AddonInfo( assert await addon_manager.async_get_addon_info() == AddonInfo(
available=True,
hostname="core-test-addon", hostname="core-test-addon",
options={}, options={},
state=AddonState.INSTALLING, state=AddonState.INSTALLING,
@ -676,6 +715,7 @@ async def test_schedule_update_addon(
assert addon_manager.task_in_progress() is True assert addon_manager.task_in_progress() is True
assert await addon_manager.async_get_addon_info() == AddonInfo( assert await addon_manager.async_get_addon_info() == AddonInfo(
available=True,
hostname="core-test-addon", hostname="core-test-addon",
options={}, options={},
state=AddonState.UPDATING, state=AddonState.UPDATING,

View file

@ -67,6 +67,7 @@ def addon_store_info_fixture():
"homeassistant.components.hassio.addon_manager.async_get_addon_store_info" "homeassistant.components.hassio.addon_manager.async_get_addon_store_info"
) as addon_store_info: ) as addon_store_info:
addon_store_info.return_value = { addon_store_info.return_value = {
"available": True,
"installed": None, "installed": None,
"state": None, "state": None,
"version": "1.0.0", "version": "1.0.0",
@ -81,6 +82,7 @@ def addon_info_fixture():
"homeassistant.components.hassio.addon_manager.async_get_addon_info", "homeassistant.components.hassio.addon_manager.async_get_addon_info",
) as addon_info: ) as addon_info:
addon_info.return_value = { addon_info.return_value = {
"available": True,
"hostname": None, "hostname": None,
"options": {}, "options": {},
"state": None, "state": None,

View file

@ -69,6 +69,7 @@ def addon_store_info_fixture():
"homeassistant.components.hassio.addon_manager.async_get_addon_store_info" "homeassistant.components.hassio.addon_manager.async_get_addon_store_info"
) as addon_store_info: ) as addon_store_info:
addon_store_info.return_value = { addon_store_info.return_value = {
"available": True,
"installed": None, "installed": None,
"state": None, "state": None,
"version": "1.0.0", "version": "1.0.0",
@ -83,6 +84,7 @@ def addon_info_fixture():
"homeassistant.components.hassio.addon_manager.async_get_addon_info", "homeassistant.components.hassio.addon_manager.async_get_addon_info",
) as addon_info: ) as addon_info:
addon_info.return_value = { addon_info.return_value = {
"available": True,
"hostname": None, "hostname": None,
"options": {}, "options": {},
"state": None, "state": None,

View file

@ -67,6 +67,7 @@ def addon_store_info_fixture():
"homeassistant.components.hassio.addon_manager.async_get_addon_store_info" "homeassistant.components.hassio.addon_manager.async_get_addon_store_info"
) as addon_store_info: ) as addon_store_info:
addon_store_info.return_value = { addon_store_info.return_value = {
"available": True,
"installed": None, "installed": None,
"state": None, "state": None,
"version": "1.0.0", "version": "1.0.0",
@ -81,6 +82,7 @@ def addon_info_fixture():
"homeassistant.components.hassio.addon_manager.async_get_addon_info", "homeassistant.components.hassio.addon_manager.async_get_addon_info",
) as addon_info: ) as addon_info:
addon_info.return_value = { addon_info.return_value = {
"available": True,
"hostname": None, "hostname": None,
"options": {}, "options": {},
"state": None, "state": None,

View file

@ -80,6 +80,7 @@ def addon_store_info_fixture() -> Generator[AsyncMock, None, None]:
"homeassistant.components.hassio.addon_manager.async_get_addon_store_info" "homeassistant.components.hassio.addon_manager.async_get_addon_store_info"
) as addon_store_info: ) as addon_store_info:
addon_store_info.return_value = { addon_store_info.return_value = {
"available": False,
"installed": None, "installed": None,
"state": None, "state": None,
"version": "1.0.0", "version": "1.0.0",
@ -94,6 +95,7 @@ def addon_info_fixture() -> Generator[AsyncMock, None, None]:
"homeassistant.components.hassio.addon_manager.async_get_addon_info", "homeassistant.components.hassio.addon_manager.async_get_addon_info",
) as addon_info: ) as addon_info:
addon_info.return_value = { addon_info.return_value = {
"available": False,
"hostname": None, "hostname": None,
"options": {}, "options": {},
"state": None, "state": None,
@ -108,6 +110,7 @@ def addon_not_installed_fixture(
addon_store_info: AsyncMock, addon_info: AsyncMock addon_store_info: AsyncMock, addon_info: AsyncMock
) -> AsyncMock: ) -> AsyncMock:
"""Mock add-on not installed.""" """Mock add-on not installed."""
addon_store_info.return_value["available"] = True
return addon_info return addon_info
@ -117,10 +120,12 @@ def addon_installed_fixture(
) -> AsyncMock: ) -> AsyncMock:
"""Mock add-on already installed but not running.""" """Mock add-on already installed but not running."""
addon_store_info.return_value = { addon_store_info.return_value = {
"available": True,
"installed": "1.0.0", "installed": "1.0.0",
"state": "stopped", "state": "stopped",
"version": "1.0.0", "version": "1.0.0",
} }
addon_info.return_value["available"] = True
addon_info.return_value["hostname"] = "core-matter-server" addon_info.return_value["hostname"] = "core-matter-server"
addon_info.return_value["state"] = "stopped" addon_info.return_value["state"] = "stopped"
addon_info.return_value["version"] = "1.0.0" addon_info.return_value["version"] = "1.0.0"
@ -133,10 +138,12 @@ def addon_running_fixture(
) -> AsyncMock: ) -> AsyncMock:
"""Mock add-on already running.""" """Mock add-on already running."""
addon_store_info.return_value = { addon_store_info.return_value = {
"available": True,
"installed": "1.0.0", "installed": "1.0.0",
"state": "started", "state": "started",
"version": "1.0.0", "version": "1.0.0",
} }
addon_info.return_value["available"] = True
addon_info.return_value["hostname"] = "core-matter-server" addon_info.return_value["hostname"] = "core-matter-server"
addon_info.return_value["state"] = "started" addon_info.return_value["state"] = "started"
addon_info.return_value["version"] = "1.0.0" addon_info.return_value["version"] = "1.0.0"
@ -152,10 +159,12 @@ def install_addon_fixture(
async def install_addon_side_effect(hass: HomeAssistant, slug: str) -> None: async def install_addon_side_effect(hass: HomeAssistant, slug: str) -> None:
"""Mock install add-on.""" """Mock install add-on."""
addon_store_info.return_value = { addon_store_info.return_value = {
"available": True,
"installed": "1.0.0", "installed": "1.0.0",
"state": "stopped", "state": "stopped",
"version": "1.0.0", "version": "1.0.0",
} }
addon_info.return_value["available"] = True
addon_info.return_value["state"] = "stopped" addon_info.return_value["state"] = "stopped"
addon_info.return_value["version"] = "1.0.0" addon_info.return_value["version"] = "1.0.0"

View file

@ -307,7 +307,7 @@ async def test_install_addon(
await hass.async_block_till_done() await hass.async_block_till_done()
assert entry.state is ConfigEntryState.SETUP_RETRY 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_count == 1
assert install_addon.call_args == call(hass, "core_matter_server") assert install_addon.call_args == call(hass, "core_matter_server")
assert start_addon.call_count == 1 assert start_addon.call_count == 1

View file

@ -30,6 +30,7 @@ def mock_addon_info(addon_info_side_effect):
side_effect=addon_info_side_effect, side_effect=addon_info_side_effect,
) as addon_info: ) as addon_info:
addon_info.return_value = { addon_info.return_value = {
"available": False,
"hostname": None, "hostname": None,
"options": {}, "options": {},
"state": None, "state": None,
@ -53,6 +54,7 @@ def mock_addon_store_info(addon_store_info_side_effect):
side_effect=addon_store_info_side_effect, side_effect=addon_store_info_side_effect,
) as addon_store_info: ) as addon_store_info:
addon_store_info.return_value = { addon_store_info.return_value = {
"available": False,
"installed": None, "installed": None,
"state": None, "state": None,
"version": "1.0.0", "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): def mock_addon_running(addon_store_info, addon_info):
"""Mock add-on already running.""" """Mock add-on already running."""
addon_store_info.return_value = { addon_store_info.return_value = {
"available": True,
"installed": "1.0.0", "installed": "1.0.0",
"state": "started", "state": "started",
"version": "1.0.0", "version": "1.0.0",
} }
addon_info.return_value["available"] = True
addon_info.return_value["state"] = "started" addon_info.return_value["state"] = "started"
addon_info.return_value["version"] = "1.0.0" addon_info.return_value["version"] = "1.0.0"
return addon_info 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): 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 = { addon_store_info.return_value = {
"available": True,
"installed": "1.0.0", "installed": "1.0.0",
"state": "stopped", "state": "stopped",
"version": "1.0.0", "version": "1.0.0",
} }
addon_info.return_value["available"] = True
addon_info.return_value["state"] = "stopped" addon_info.return_value["state"] = "stopped"
addon_info.return_value["version"] = "1.0.0" addon_info.return_value["version"] = "1.0.0"
return addon_info return addon_info
@ -89,6 +95,7 @@ def mock_addon_installed(addon_store_info, addon_info):
@pytest.fixture(name="addon_not_installed") @pytest.fixture(name="addon_not_installed")
def mock_addon_not_installed(addon_store_info, addon_info): def mock_addon_not_installed(addon_store_info, addon_info):
"""Mock add-on not installed.""" """Mock add-on not installed."""
addon_store_info.return_value["available"] = True
return addon_info return addon_info
@ -126,10 +133,12 @@ def install_addon_side_effect_fixture(addon_store_info, addon_info):
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 = { addon_store_info.return_value = {
"available": True,
"installed": "1.0.0", "installed": "1.0.0",
"state": "stopped", "state": "stopped",
"version": "1.0.0", "version": "1.0.0",
} }
addon_info.return_value["available"] = True
addon_info.return_value["state"] = "stopped" addon_info.return_value["state"] = "stopped"
addon_info.return_value["version"] = "1.0.0" 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): async def start_addon(hass, slug):
"""Mock start add-on.""" """Mock start add-on."""
addon_store_info.return_value = { addon_store_info.return_value = {
"available": True,
"installed": "1.0.0", "installed": "1.0.0",
"state": "started", "state": "started",
"version": "1.0.0", "version": "1.0.0",
} }
addon_info.return_value["available"] = True
addon_info.return_value["state"] = "started" addon_info.return_value["state"] = "started"
return start_addon return start_addon

View file

@ -536,6 +536,7 @@ async def test_abort_hassio_discovery_for_other_addon(
async def test_usb_discovery( async def test_usb_discovery(
hass, hass,
supervisor, supervisor,
addon_not_installed,
install_addon, install_addon,
addon_options, addon_options,
get_addon_discovery_info, get_addon_discovery_info,