From 24a1d5956223760076e611e148ba74c88cf84fc5 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 17 Jul 2026 18:14:07 +0200 Subject: [PATCH] Refresh add-on update entities after store reload through Supervisor API proxy (#176648) Co-authored-by: Claude --- .../components/hassio/coordinator.py | 5 ++ .../components/hassio/websocket_api.py | 14 ++++++ tests/components/hassio/test_websocket_api.py | 50 +++++++++++++++++++ 3 files changed, 69 insertions(+) diff --git a/homeassistant/components/hassio/coordinator.py b/homeassistant/components/hassio/coordinator.py index 191d3b6e2338..0dd9e2da0187 100644 --- a/homeassistant/components/hassio/coordinator.py +++ b/homeassistant/components/hassio/coordinator.py @@ -1411,6 +1411,11 @@ class HassioAddOnDataUpdateCoordinator(DataUpdateCoordinator[HassioAddonData]): log_failures, raise_on_auth_failed, scheduled, raise_on_entry_error ) + async def async_refresh_after_store_reload(self) -> None: + """Refresh addon data when the store was already reloaded externally.""" + async with self._debounced_refresh.async_lock(): + await super()._async_refresh(log_failures=True) + async def force_addon_info_data_refresh(self, addon_slug: str) -> None: """Force refresh of addon info data for a specific addon.""" try: diff --git a/homeassistant/components/hassio/websocket_api.py b/homeassistant/components/hassio/websocket_api.py index ed3034437e1f..dea7dbfbd45a 100644 --- a/homeassistant/components/hassio/websocket_api.py +++ b/homeassistant/components/hassio/websocket_api.py @@ -20,6 +20,7 @@ from homeassistant.helpers.dispatcher import ( from .config import HassioUpdateParametersDict from .const import ( + ADDONS_COORDINATOR, ATTR_DATA, ATTR_ENDPOINT, ATTR_METHOD, @@ -59,6 +60,10 @@ WS_NO_ADMIN_ENDPOINTS = re.compile( r")$" ) +# Endpoint that reloads the add-on store. Afterwards the add-on update +# entities must be refreshed so they don't report stale update information. +STORE_RELOAD_ENDPOINT = "/store/reload" + _LOGGER: logging.Logger = logging.getLogger(__package__) @@ -159,6 +164,15 @@ async def websocket_supervisor_api( # sensitive information and the frontend does not require it for ingress. if not connection.user.is_admin and WS_ADDONS_INFO_ENDPOINT.match(command): data.pop("options", None) + # Await so the frontend only sees the reload finish once the add-on + # update entities reflect the reloaded store. + if ( + command == STORE_RELOAD_ENDPOINT + and msg[ATTR_METHOD] == "post" + and (coordinator := hass.data.get(ADDONS_COORDINATOR)) + ): + await coordinator.async_refresh_after_store_reload() + connection.send_result(msg[WS_ID], data) diff --git a/tests/components/hassio/test_websocket_api.py b/tests/components/hassio/test_websocket_api.py index df4700467bce..3b666f9e430f 100644 --- a/tests/components/hassio/test_websocket_api.py +++ b/tests/components/hassio/test_websocket_api.py @@ -375,6 +375,56 @@ async def test_websocket_non_admin_user( assert msg["error"]["message"] == "Unauthorized" +async def test_websocket_store_reload_refreshes_update_entities( + hass: HomeAssistant, + hass_ws_client: WebSocketGenerator, + aioclient_mock: AiohttpClientMocker, + supervisor_client: AsyncMock, + addons_list: AsyncMock, +) -> None: + """Test add-on update entities refresh after a store reload via the API proxy.""" + addons_list.return_value = [ + replace( + addons_list.return_value[0], + update_available=False, + version_latest="2.0.0", + ) + ] + config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN) + config_entry.add_to_hass(hass) + + with patch.dict(os.environ, MOCK_ENVIRON): + assert await async_setup_component(hass, DOMAIN, {"hassio": {}}) + await hass.async_block_till_done() + + assert hass.states.get("update.test_update").state == "off" + + addons_list.return_value = [ + replace( + addons_list.return_value[0], + update_available=True, + version_latest="2.0.1", + ) + ] + aioclient_mock.post( + "http://127.0.0.1/store/reload", json={"result": "ok", "data": {}} + ) + + websocket_client = await hass_ws_client(hass) + await websocket_client.send_json_auto_id( + { + WS_TYPE: WS_TYPE_API, + ATTR_ENDPOINT: "/store/reload", + ATTR_METHOD: "post", + } + ) + msg = await websocket_client.receive_json() + assert msg["success"] + + assert hass.states.get("update.test_update").state == "on" + supervisor_client.store.reload.assert_not_called() + + async def test_update_addon( hass: HomeAssistant, hass_ws_client: WebSocketGenerator,