From e5e3ffa1e191aa1069b2be6d88300ec5d589dd33 Mon Sep 17 00:00:00 2001 From: gielk Date: Thu, 30 Apr 2026 16:41:46 +0200 Subject: [PATCH] Use local brand icons for update entities --- custom_components/hacs/brands.py | 59 ++++++++++++++++ custom_components/hacs/const.py | 2 + custom_components/hacs/frontend.py | 2 + custom_components/hacs/update.py | 4 +- .../test_discard_invalid_repo_data.json | 2 +- .../test_download_repository.json | 2 +- .../test_remove_repository_post.json | 2 +- .../test_remove_repository_pre.json | 2 +- .../test_update_repository_entity.json | 2 +- .../test_update_repository_websocket.json | 2 +- .../test_discard_invalid_repo_data.json | 2 +- .../test_download_repository.json | 4 +- .../test_remove_repository_post.json | 2 +- .../test_remove_repository_pre.json | 2 +- .../test_switch/entity_states.json | 4 +- .../test_update_entity_state.json | 4 +- .../test_update_repository_entity.json | 4 +- .../test_update_repository_websocket.json | 2 +- .../test_discard_invalid_repo_data.json | 2 +- .../test_download_repository.json | 2 +- .../test_remove_repository_post.json | 2 +- .../test_remove_repository_pre.json | 2 +- .../test_update_repository_entity.json | 2 +- .../test_update_repository_websocket.json | 2 +- .../test_discard_invalid_repo_data.json | 2 +- .../test_download_repository.json | 2 +- .../test_remove_repository_post.json | 2 +- .../test_remove_repository_pre.json | 2 +- .../test_update_repository_entity.json | 2 +- .../test_update_repository_websocket.json | 2 +- .../test_discard_invalid_repo_data.json | 2 +- .../test_download_repository.json | 2 +- .../test_remove_repository_post.json | 2 +- .../test_remove_repository_pre.json | 2 +- .../test_update_repository_entity.json | 2 +- .../test_update_repository_websocket.json | 2 +- .../test_discard_invalid_repo_data.json | 2 +- .../theme-basic/test_download_repository.json | 2 +- .../test_remove_repository_post.json | 2 +- .../test_remove_repository_pre.json | 2 +- .../test_update_repository_entity.json | 2 +- .../test_update_repository_websocket.json | 2 +- tests/snapshots/test_integration_setup.json | 2 +- tests/test_brands.py | 70 +++++++++++++++++++ 44 files changed, 178 insertions(+), 45 deletions(-) create mode 100644 custom_components/hacs/brands.py create mode 100644 tests/test_brands.py diff --git a/custom_components/hacs/brands.py b/custom_components/hacs/brands.py new file mode 100644 index 00000000000..0217eafedac --- /dev/null +++ b/custom_components/hacs/brands.py @@ -0,0 +1,59 @@ +"""Brand icon endpoint for HACS update entities.""" + +from __future__ import annotations + +from http import HTTPStatus +from pathlib import Path +import re + +from aiohttp import web +from homeassistant.components.http import HomeAssistantView +from homeassistant.core import HomeAssistant + +from .base import HacsBase +from .const import BRAND_ICON_CDN_URL, BRAND_ICON_URL +from .enums import HacsCategory + +DOMAIN_RE = re.compile(r"^[a-z0-9_]+$") + + +def _read_brand_icon(path: Path) -> bytes | None: + """Read a brand icon from disk.""" + if not path.is_file(): + return None + return path.read_bytes() + + +class HacsBrandIconView(HomeAssistantView): + """Serve installed integration brand icons with a CDN fallback.""" + + name = "api:hacs:brand_icon" + url = BRAND_ICON_URL + requires_auth = False + + def __init__(self, hass: HomeAssistant, hacs: HacsBase) -> None: + """Initialize the view.""" + self.hass = hass + self.hacs = hacs + + async def get(self, request: web.Request, domain: str) -> web.Response: + """Return the local brand icon for an installed HACS integration.""" + if not DOMAIN_RE.fullmatch(domain): + return web.Response(status=HTTPStatus.NOT_FOUND) + + if (icon_path := self._brand_icon_path(domain)) is not None: + data = await self.hass.async_add_executor_job(_read_brand_icon, icon_path) + if data is not None: + return web.Response(body=data, content_type="image/png") + + raise web.HTTPFound(BRAND_ICON_CDN_URL.format(domain=domain)) + + def _brand_icon_path(self, domain: str) -> Path | None: + """Return the local brand icon path for an installed integration.""" + for repository in self.hacs.repositories.list_downloaded: + if repository.data.category != HacsCategory.INTEGRATION: + continue + if repository.data.domain != domain or repository.content.path.local is None: + continue + return Path(repository.content.path.local) / "brand" / "icon.png" + return None diff --git a/custom_components/hacs/const.py b/custom_components/hacs/const.py index 371c7eaede3..f7c22d55415 100644 --- a/custom_components/hacs/const.py +++ b/custom_components/hacs/const.py @@ -10,6 +10,8 @@ MINIMUM_HA_VERSION = "0.0.0" URL_BASE = "/hacsfiles" +BRAND_ICON_URL = "/api/hacs/brands/{domain}/icon.png" +BRAND_ICON_CDN_URL = "https://brands.home-assistant.io/_/{domain}/icon.png" TV = TypeVar("TV") diff --git a/custom_components/hacs/frontend.py b/custom_components/hacs/frontend.py index 7038f3c1510..0d5d54c6b61 100644 --- a/custom_components/hacs/frontend.py +++ b/custom_components/hacs/frontend.py @@ -10,6 +10,7 @@ async_register_built_in_panel, ) +from .brands import HacsBrandIconView from .const import DOMAIN, URL_BASE from .hacs_frontend import VERSION as FE_VERSION, locate_dir from .utils.workarounds import async_register_static_path @@ -22,6 +23,7 @@ async def async_register_frontend(hass: HomeAssistant, hacs: HacsBase) -> None: """Register the frontend.""" + hass.http.register_view(HacsBrandIconView(hass, hacs)) # Register frontend if hacs.configuration.dev and (frontend_path := os.getenv("HACS_FRONTEND_DIR")): diff --git a/custom_components/hacs/update.py b/custom_components/hacs/update.py index ec4014302db..f80d8715336 100644 --- a/custom_components/hacs/update.py +++ b/custom_components/hacs/update.py @@ -11,7 +11,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from .base import HacsBase -from .const import DOMAIN +from .const import BRAND_ICON_URL, DOMAIN from .entity import HacsRepositoryEntity from .enums import HacsCategory, HacsDispatchEvent from .exceptions import HacsException @@ -76,7 +76,7 @@ def entity_picture(self) -> str | None: ): return None - return f"https://brands.home-assistant.io/_/{self.repository.data.domain}/icon.png" + return BRAND_ICON_URL.format(domain=self.repository.data.domain) async def async_install(self, version: str | None, backup: bool, **kwargs: Any) -> None: """Install an update.""" diff --git a/tests/snapshots/hacs-test-org/appdaemon-basic/test_discard_invalid_repo_data.json b/tests/snapshots/hacs-test-org/appdaemon-basic/test_discard_invalid_repo_data.json index b3b68343939..4d81261607d 100644 --- a/tests/snapshots/hacs-test-org/appdaemon-basic/test_discard_invalid_repo_data.json +++ b/tests/snapshots/hacs-test-org/appdaemon-basic/test_discard_invalid_repo_data.json @@ -61,7 +61,7 @@ "area_id": null, "attributes": { "auto_update": false, - "entity_picture": "https://brands.home-assistant.io/_/hacs/icon.png", + "entity_picture": "/api/hacs/brands/hacs/icon.png", "friendly_name": "HACS update", "in_progress": false, "installed_version": "0.0.0", diff --git a/tests/snapshots/hacs-test-org/appdaemon-basic/test_download_repository.json b/tests/snapshots/hacs-test-org/appdaemon-basic/test_download_repository.json index 0022afb8944..bb9fec43f81 100644 --- a/tests/snapshots/hacs-test-org/appdaemon-basic/test_download_repository.json +++ b/tests/snapshots/hacs-test-org/appdaemon-basic/test_download_repository.json @@ -120,7 +120,7 @@ "area_id": null, "attributes": { "auto_update": false, - "entity_picture": "https://brands.home-assistant.io/_/hacs/icon.png", + "entity_picture": "/api/hacs/brands/hacs/icon.png", "friendly_name": "HACS update", "in_progress": false, "installed_version": "0.0.0", diff --git a/tests/snapshots/hacs-test-org/appdaemon-basic/test_remove_repository_post.json b/tests/snapshots/hacs-test-org/appdaemon-basic/test_remove_repository_post.json index 1a7a2d72f3a..fb5a418faab 100644 --- a/tests/snapshots/hacs-test-org/appdaemon-basic/test_remove_repository_post.json +++ b/tests/snapshots/hacs-test-org/appdaemon-basic/test_remove_repository_post.json @@ -67,7 +67,7 @@ "area_id": null, "attributes": { "auto_update": false, - "entity_picture": "https://brands.home-assistant.io/_/hacs/icon.png", + "entity_picture": "/api/hacs/brands/hacs/icon.png", "friendly_name": "HACS update", "in_progress": false, "installed_version": "0.0.0", diff --git a/tests/snapshots/hacs-test-org/appdaemon-basic/test_remove_repository_pre.json b/tests/snapshots/hacs-test-org/appdaemon-basic/test_remove_repository_pre.json index bb92445ac99..7940271f491 100644 --- a/tests/snapshots/hacs-test-org/appdaemon-basic/test_remove_repository_pre.json +++ b/tests/snapshots/hacs-test-org/appdaemon-basic/test_remove_repository_pre.json @@ -78,7 +78,7 @@ "area_id": null, "attributes": { "auto_update": false, - "entity_picture": "https://brands.home-assistant.io/_/hacs/icon.png", + "entity_picture": "/api/hacs/brands/hacs/icon.png", "friendly_name": "HACS update", "in_progress": false, "installed_version": "0.0.0", diff --git a/tests/snapshots/hacs-test-org/appdaemon-basic/test_update_repository_entity.json b/tests/snapshots/hacs-test-org/appdaemon-basic/test_update_repository_entity.json index c5c7197a73d..a7ba217b53f 100644 --- a/tests/snapshots/hacs-test-org/appdaemon-basic/test_update_repository_entity.json +++ b/tests/snapshots/hacs-test-org/appdaemon-basic/test_update_repository_entity.json @@ -134,7 +134,7 @@ "area_id": null, "attributes": { "auto_update": false, - "entity_picture": "https://brands.home-assistant.io/_/hacs/icon.png", + "entity_picture": "/api/hacs/brands/hacs/icon.png", "friendly_name": "HACS update", "in_progress": false, "installed_version": "0.0.0", diff --git a/tests/snapshots/hacs-test-org/appdaemon-basic/test_update_repository_websocket.json b/tests/snapshots/hacs-test-org/appdaemon-basic/test_update_repository_websocket.json index 4cb3d707c37..25964d87043 100644 --- a/tests/snapshots/hacs-test-org/appdaemon-basic/test_update_repository_websocket.json +++ b/tests/snapshots/hacs-test-org/appdaemon-basic/test_update_repository_websocket.json @@ -93,7 +93,7 @@ "area_id": null, "attributes": { "auto_update": false, - "entity_picture": "https://brands.home-assistant.io/_/hacs/icon.png", + "entity_picture": "/api/hacs/brands/hacs/icon.png", "friendly_name": "HACS update", "in_progress": false, "installed_version": "0.0.0", diff --git a/tests/snapshots/hacs-test-org/integration-basic/test_discard_invalid_repo_data.json b/tests/snapshots/hacs-test-org/integration-basic/test_discard_invalid_repo_data.json index a94edadc001..05a8a5580eb 100644 --- a/tests/snapshots/hacs-test-org/integration-basic/test_discard_invalid_repo_data.json +++ b/tests/snapshots/hacs-test-org/integration-basic/test_discard_invalid_repo_data.json @@ -57,7 +57,7 @@ "area_id": null, "attributes": { "auto_update": false, - "entity_picture": "https://brands.home-assistant.io/_/hacs/icon.png", + "entity_picture": "/api/hacs/brands/hacs/icon.png", "friendly_name": "HACS update", "in_progress": false, "installed_version": "0.0.0", diff --git a/tests/snapshots/hacs-test-org/integration-basic/test_download_repository.json b/tests/snapshots/hacs-test-org/integration-basic/test_download_repository.json index 93c55339181..9dbfd11c4af 100644 --- a/tests/snapshots/hacs-test-org/integration-basic/test_download_repository.json +++ b/tests/snapshots/hacs-test-org/integration-basic/test_download_repository.json @@ -98,7 +98,7 @@ "area_id": null, "attributes": { "auto_update": false, - "entity_picture": "https://brands.home-assistant.io/_/example/icon.png", + "entity_picture": "/api/hacs/brands/example/icon.png", "friendly_name": "Proxy manifest update", "in_progress": false, "installed_version": "1.0.0", @@ -126,7 +126,7 @@ "area_id": null, "attributes": { "auto_update": false, - "entity_picture": "https://brands.home-assistant.io/_/hacs/icon.png", + "entity_picture": "/api/hacs/brands/hacs/icon.png", "friendly_name": "HACS update", "in_progress": false, "installed_version": "0.0.0", diff --git a/tests/snapshots/hacs-test-org/integration-basic/test_remove_repository_post.json b/tests/snapshots/hacs-test-org/integration-basic/test_remove_repository_post.json index 1a7a2d72f3a..fb5a418faab 100644 --- a/tests/snapshots/hacs-test-org/integration-basic/test_remove_repository_post.json +++ b/tests/snapshots/hacs-test-org/integration-basic/test_remove_repository_post.json @@ -67,7 +67,7 @@ "area_id": null, "attributes": { "auto_update": false, - "entity_picture": "https://brands.home-assistant.io/_/hacs/icon.png", + "entity_picture": "/api/hacs/brands/hacs/icon.png", "friendly_name": "HACS update", "in_progress": false, "installed_version": "0.0.0", diff --git a/tests/snapshots/hacs-test-org/integration-basic/test_remove_repository_pre.json b/tests/snapshots/hacs-test-org/integration-basic/test_remove_repository_pre.json index 4b141876b0d..95a5bb43c7d 100644 --- a/tests/snapshots/hacs-test-org/integration-basic/test_remove_repository_pre.json +++ b/tests/snapshots/hacs-test-org/integration-basic/test_remove_repository_pre.json @@ -81,7 +81,7 @@ "area_id": null, "attributes": { "auto_update": false, - "entity_picture": "https://brands.home-assistant.io/_/hacs/icon.png", + "entity_picture": "/api/hacs/brands/hacs/icon.png", "friendly_name": "HACS update", "in_progress": false, "installed_version": "0.0.0", diff --git a/tests/snapshots/hacs-test-org/integration-basic/test_switch/entity_states.json b/tests/snapshots/hacs-test-org/integration-basic/test_switch/entity_states.json index 5a6694b2dfd..07bb95d09e2 100644 --- a/tests/snapshots/hacs-test-org/integration-basic/test_switch/entity_states.json +++ b/tests/snapshots/hacs-test-org/integration-basic/test_switch/entity_states.json @@ -27,7 +27,7 @@ "initial": { "attributes": { "auto_update": false, - "entity_picture": "https://brands.home-assistant.io/_/example/icon.png", + "entity_picture": "/api/hacs/brands/example/icon.png", "friendly_name": "Basic integration update", "in_progress": false, "installed_version": "1.0.0", @@ -48,7 +48,7 @@ "updated": { "attributes": { "auto_update": false, - "entity_picture": "https://brands.home-assistant.io/_/example/icon.png", + "entity_picture": "/api/hacs/brands/example/icon.png", "friendly_name": "Basic integration update", "in_progress": false, "installed_version": "1.0.0", diff --git a/tests/snapshots/hacs-test-org/integration-basic/test_update_entity_state.json b/tests/snapshots/hacs-test-org/integration-basic/test_update_entity_state.json index 796672102d0..e766bbb3946 100644 --- a/tests/snapshots/hacs-test-org/integration-basic/test_update_entity_state.json +++ b/tests/snapshots/hacs-test-org/integration-basic/test_update_entity_state.json @@ -2,7 +2,7 @@ "initial_state": { "attributes": { "auto_update": false, - "entity_picture": "https://brands.home-assistant.io/_/example/icon.png", + "entity_picture": "/api/hacs/brands/example/icon.png", "friendly_name": "Basic integration update", "in_progress": false, "installed_version": "1.0.0", @@ -23,7 +23,7 @@ "updated_state": { "attributes": { "auto_update": false, - "entity_picture": "https://brands.home-assistant.io/_/example/icon.png", + "entity_picture": "/api/hacs/brands/example/icon.png", "friendly_name": "Basic integration update", "in_progress": false, "installed_version": "1.0.0", diff --git a/tests/snapshots/hacs-test-org/integration-basic/test_update_repository_entity.json b/tests/snapshots/hacs-test-org/integration-basic/test_update_repository_entity.json index 0e4b5a0be62..f54f0c8feb5 100644 --- a/tests/snapshots/hacs-test-org/integration-basic/test_update_repository_entity.json +++ b/tests/snapshots/hacs-test-org/integration-basic/test_update_repository_entity.json @@ -113,7 +113,7 @@ "area_id": null, "attributes": { "auto_update": false, - "entity_picture": "https://brands.home-assistant.io/_/example/icon.png", + "entity_picture": "/api/hacs/brands/example/icon.png", "friendly_name": "Proxy manifest update", "in_progress": false, "installed_version": "2.0.0", @@ -141,7 +141,7 @@ "area_id": null, "attributes": { "auto_update": false, - "entity_picture": "https://brands.home-assistant.io/_/hacs/icon.png", + "entity_picture": "/api/hacs/brands/hacs/icon.png", "friendly_name": "HACS update", "in_progress": false, "installed_version": "0.0.0", diff --git a/tests/snapshots/hacs-test-org/integration-basic/test_update_repository_websocket.json b/tests/snapshots/hacs-test-org/integration-basic/test_update_repository_websocket.json index 0c77bc565eb..5e4716da1be 100644 --- a/tests/snapshots/hacs-test-org/integration-basic/test_update_repository_websocket.json +++ b/tests/snapshots/hacs-test-org/integration-basic/test_update_repository_websocket.json @@ -99,7 +99,7 @@ "area_id": null, "attributes": { "auto_update": false, - "entity_picture": "https://brands.home-assistant.io/_/hacs/icon.png", + "entity_picture": "/api/hacs/brands/hacs/icon.png", "friendly_name": "HACS update", "in_progress": false, "installed_version": "0.0.0", diff --git a/tests/snapshots/hacs-test-org/plugin-basic/test_discard_invalid_repo_data.json b/tests/snapshots/hacs-test-org/plugin-basic/test_discard_invalid_repo_data.json index 1d72347f357..f39bbd0e11f 100644 --- a/tests/snapshots/hacs-test-org/plugin-basic/test_discard_invalid_repo_data.json +++ b/tests/snapshots/hacs-test-org/plugin-basic/test_discard_invalid_repo_data.json @@ -55,7 +55,7 @@ "area_id": null, "attributes": { "auto_update": false, - "entity_picture": "https://brands.home-assistant.io/_/hacs/icon.png", + "entity_picture": "/api/hacs/brands/hacs/icon.png", "friendly_name": "HACS update", "in_progress": false, "installed_version": "0.0.0", diff --git a/tests/snapshots/hacs-test-org/plugin-basic/test_download_repository.json b/tests/snapshots/hacs-test-org/plugin-basic/test_download_repository.json index 30d4793938f..2745d970103 100644 --- a/tests/snapshots/hacs-test-org/plugin-basic/test_download_repository.json +++ b/tests/snapshots/hacs-test-org/plugin-basic/test_download_repository.json @@ -126,7 +126,7 @@ "area_id": null, "attributes": { "auto_update": false, - "entity_picture": "https://brands.home-assistant.io/_/hacs/icon.png", + "entity_picture": "/api/hacs/brands/hacs/icon.png", "friendly_name": "HACS update", "in_progress": false, "installed_version": "0.0.0", diff --git a/tests/snapshots/hacs-test-org/plugin-basic/test_remove_repository_post.json b/tests/snapshots/hacs-test-org/plugin-basic/test_remove_repository_post.json index 1a7a2d72f3a..fb5a418faab 100644 --- a/tests/snapshots/hacs-test-org/plugin-basic/test_remove_repository_post.json +++ b/tests/snapshots/hacs-test-org/plugin-basic/test_remove_repository_post.json @@ -67,7 +67,7 @@ "area_id": null, "attributes": { "auto_update": false, - "entity_picture": "https://brands.home-assistant.io/_/hacs/icon.png", + "entity_picture": "/api/hacs/brands/hacs/icon.png", "friendly_name": "HACS update", "in_progress": false, "installed_version": "0.0.0", diff --git a/tests/snapshots/hacs-test-org/plugin-basic/test_remove_repository_pre.json b/tests/snapshots/hacs-test-org/plugin-basic/test_remove_repository_pre.json index 85578cd2a05..9f530095514 100644 --- a/tests/snapshots/hacs-test-org/plugin-basic/test_remove_repository_pre.json +++ b/tests/snapshots/hacs-test-org/plugin-basic/test_remove_repository_pre.json @@ -83,7 +83,7 @@ "area_id": null, "attributes": { "auto_update": false, - "entity_picture": "https://brands.home-assistant.io/_/hacs/icon.png", + "entity_picture": "/api/hacs/brands/hacs/icon.png", "friendly_name": "HACS update", "in_progress": false, "installed_version": "0.0.0", diff --git a/tests/snapshots/hacs-test-org/plugin-basic/test_update_repository_entity.json b/tests/snapshots/hacs-test-org/plugin-basic/test_update_repository_entity.json index 38fece4f08f..14d46db24e2 100644 --- a/tests/snapshots/hacs-test-org/plugin-basic/test_update_repository_entity.json +++ b/tests/snapshots/hacs-test-org/plugin-basic/test_update_repository_entity.json @@ -140,7 +140,7 @@ "area_id": null, "attributes": { "auto_update": false, - "entity_picture": "https://brands.home-assistant.io/_/hacs/icon.png", + "entity_picture": "/api/hacs/brands/hacs/icon.png", "friendly_name": "HACS update", "in_progress": false, "installed_version": "0.0.0", diff --git a/tests/snapshots/hacs-test-org/plugin-basic/test_update_repository_websocket.json b/tests/snapshots/hacs-test-org/plugin-basic/test_update_repository_websocket.json index 3b2917a17b8..6da32271b20 100644 --- a/tests/snapshots/hacs-test-org/plugin-basic/test_update_repository_websocket.json +++ b/tests/snapshots/hacs-test-org/plugin-basic/test_update_repository_websocket.json @@ -99,7 +99,7 @@ "area_id": null, "attributes": { "auto_update": false, - "entity_picture": "https://brands.home-assistant.io/_/hacs/icon.png", + "entity_picture": "/api/hacs/brands/hacs/icon.png", "friendly_name": "HACS update", "in_progress": false, "installed_version": "0.0.0", diff --git a/tests/snapshots/hacs-test-org/python_script-basic/test_discard_invalid_repo_data.json b/tests/snapshots/hacs-test-org/python_script-basic/test_discard_invalid_repo_data.json index 91286d16ffa..ff938e59a3f 100644 --- a/tests/snapshots/hacs-test-org/python_script-basic/test_discard_invalid_repo_data.json +++ b/tests/snapshots/hacs-test-org/python_script-basic/test_discard_invalid_repo_data.json @@ -55,7 +55,7 @@ "area_id": null, "attributes": { "auto_update": false, - "entity_picture": "https://brands.home-assistant.io/_/hacs/icon.png", + "entity_picture": "/api/hacs/brands/hacs/icon.png", "friendly_name": "HACS update", "in_progress": false, "installed_version": "0.0.0", diff --git a/tests/snapshots/hacs-test-org/python_script-basic/test_download_repository.json b/tests/snapshots/hacs-test-org/python_script-basic/test_download_repository.json index 430dd71ab2c..a13a3012597 100644 --- a/tests/snapshots/hacs-test-org/python_script-basic/test_download_repository.json +++ b/tests/snapshots/hacs-test-org/python_script-basic/test_download_repository.json @@ -120,7 +120,7 @@ "area_id": null, "attributes": { "auto_update": false, - "entity_picture": "https://brands.home-assistant.io/_/hacs/icon.png", + "entity_picture": "/api/hacs/brands/hacs/icon.png", "friendly_name": "HACS update", "in_progress": false, "installed_version": "0.0.0", diff --git a/tests/snapshots/hacs-test-org/python_script-basic/test_remove_repository_post.json b/tests/snapshots/hacs-test-org/python_script-basic/test_remove_repository_post.json index 1a7a2d72f3a..fb5a418faab 100644 --- a/tests/snapshots/hacs-test-org/python_script-basic/test_remove_repository_post.json +++ b/tests/snapshots/hacs-test-org/python_script-basic/test_remove_repository_post.json @@ -67,7 +67,7 @@ "area_id": null, "attributes": { "auto_update": false, - "entity_picture": "https://brands.home-assistant.io/_/hacs/icon.png", + "entity_picture": "/api/hacs/brands/hacs/icon.png", "friendly_name": "HACS update", "in_progress": false, "installed_version": "0.0.0", diff --git a/tests/snapshots/hacs-test-org/python_script-basic/test_remove_repository_pre.json b/tests/snapshots/hacs-test-org/python_script-basic/test_remove_repository_pre.json index e133cf5f724..b9b5965a466 100644 --- a/tests/snapshots/hacs-test-org/python_script-basic/test_remove_repository_pre.json +++ b/tests/snapshots/hacs-test-org/python_script-basic/test_remove_repository_pre.json @@ -77,7 +77,7 @@ "area_id": null, "attributes": { "auto_update": false, - "entity_picture": "https://brands.home-assistant.io/_/hacs/icon.png", + "entity_picture": "/api/hacs/brands/hacs/icon.png", "friendly_name": "HACS update", "in_progress": false, "installed_version": "0.0.0", diff --git a/tests/snapshots/hacs-test-org/python_script-basic/test_update_repository_entity.json b/tests/snapshots/hacs-test-org/python_script-basic/test_update_repository_entity.json index 480b865bd99..4b613298778 100644 --- a/tests/snapshots/hacs-test-org/python_script-basic/test_update_repository_entity.json +++ b/tests/snapshots/hacs-test-org/python_script-basic/test_update_repository_entity.json @@ -134,7 +134,7 @@ "area_id": null, "attributes": { "auto_update": false, - "entity_picture": "https://brands.home-assistant.io/_/hacs/icon.png", + "entity_picture": "/api/hacs/brands/hacs/icon.png", "friendly_name": "HACS update", "in_progress": false, "installed_version": "0.0.0", diff --git a/tests/snapshots/hacs-test-org/python_script-basic/test_update_repository_websocket.json b/tests/snapshots/hacs-test-org/python_script-basic/test_update_repository_websocket.json index 089d87e787f..cf0072a717c 100644 --- a/tests/snapshots/hacs-test-org/python_script-basic/test_update_repository_websocket.json +++ b/tests/snapshots/hacs-test-org/python_script-basic/test_update_repository_websocket.json @@ -93,7 +93,7 @@ "area_id": null, "attributes": { "auto_update": false, - "entity_picture": "https://brands.home-assistant.io/_/hacs/icon.png", + "entity_picture": "/api/hacs/brands/hacs/icon.png", "friendly_name": "HACS update", "in_progress": false, "installed_version": "0.0.0", diff --git a/tests/snapshots/hacs-test-org/template-basic/test_discard_invalid_repo_data.json b/tests/snapshots/hacs-test-org/template-basic/test_discard_invalid_repo_data.json index 99d53594a99..2aaf6753fbb 100644 --- a/tests/snapshots/hacs-test-org/template-basic/test_discard_invalid_repo_data.json +++ b/tests/snapshots/hacs-test-org/template-basic/test_discard_invalid_repo_data.json @@ -55,7 +55,7 @@ "area_id": null, "attributes": { "auto_update": false, - "entity_picture": "https://brands.home-assistant.io/_/hacs/icon.png", + "entity_picture": "/api/hacs/brands/hacs/icon.png", "friendly_name": "HACS update", "in_progress": false, "installed_version": "0.0.0", diff --git a/tests/snapshots/hacs-test-org/template-basic/test_download_repository.json b/tests/snapshots/hacs-test-org/template-basic/test_download_repository.json index 5e91d48e9ea..171be26a3ee 100644 --- a/tests/snapshots/hacs-test-org/template-basic/test_download_repository.json +++ b/tests/snapshots/hacs-test-org/template-basic/test_download_repository.json @@ -122,7 +122,7 @@ "area_id": null, "attributes": { "auto_update": false, - "entity_picture": "https://brands.home-assistant.io/_/hacs/icon.png", + "entity_picture": "/api/hacs/brands/hacs/icon.png", "friendly_name": "HACS update", "in_progress": false, "installed_version": "0.0.0", diff --git a/tests/snapshots/hacs-test-org/template-basic/test_remove_repository_post.json b/tests/snapshots/hacs-test-org/template-basic/test_remove_repository_post.json index 1a7a2d72f3a..fb5a418faab 100644 --- a/tests/snapshots/hacs-test-org/template-basic/test_remove_repository_post.json +++ b/tests/snapshots/hacs-test-org/template-basic/test_remove_repository_post.json @@ -67,7 +67,7 @@ "area_id": null, "attributes": { "auto_update": false, - "entity_picture": "https://brands.home-assistant.io/_/hacs/icon.png", + "entity_picture": "/api/hacs/brands/hacs/icon.png", "friendly_name": "HACS update", "in_progress": false, "installed_version": "0.0.0", diff --git a/tests/snapshots/hacs-test-org/template-basic/test_remove_repository_pre.json b/tests/snapshots/hacs-test-org/template-basic/test_remove_repository_pre.json index 578193046bb..097011b389c 100644 --- a/tests/snapshots/hacs-test-org/template-basic/test_remove_repository_pre.json +++ b/tests/snapshots/hacs-test-org/template-basic/test_remove_repository_pre.json @@ -77,7 +77,7 @@ "area_id": null, "attributes": { "auto_update": false, - "entity_picture": "https://brands.home-assistant.io/_/hacs/icon.png", + "entity_picture": "/api/hacs/brands/hacs/icon.png", "friendly_name": "HACS update", "in_progress": false, "installed_version": "0.0.0", diff --git a/tests/snapshots/hacs-test-org/template-basic/test_update_repository_entity.json b/tests/snapshots/hacs-test-org/template-basic/test_update_repository_entity.json index e40c08d12bc..a4c7d46a3db 100644 --- a/tests/snapshots/hacs-test-org/template-basic/test_update_repository_entity.json +++ b/tests/snapshots/hacs-test-org/template-basic/test_update_repository_entity.json @@ -136,7 +136,7 @@ "area_id": null, "attributes": { "auto_update": false, - "entity_picture": "https://brands.home-assistant.io/_/hacs/icon.png", + "entity_picture": "/api/hacs/brands/hacs/icon.png", "friendly_name": "HACS update", "in_progress": false, "installed_version": "0.0.0", diff --git a/tests/snapshots/hacs-test-org/template-basic/test_update_repository_websocket.json b/tests/snapshots/hacs-test-org/template-basic/test_update_repository_websocket.json index 7f19f7c1569..28bbf622c9c 100644 --- a/tests/snapshots/hacs-test-org/template-basic/test_update_repository_websocket.json +++ b/tests/snapshots/hacs-test-org/template-basic/test_update_repository_websocket.json @@ -95,7 +95,7 @@ "area_id": null, "attributes": { "auto_update": false, - "entity_picture": "https://brands.home-assistant.io/_/hacs/icon.png", + "entity_picture": "/api/hacs/brands/hacs/icon.png", "friendly_name": "HACS update", "in_progress": false, "installed_version": "0.0.0", diff --git a/tests/snapshots/hacs-test-org/theme-basic/test_discard_invalid_repo_data.json b/tests/snapshots/hacs-test-org/theme-basic/test_discard_invalid_repo_data.json index ad436550c5f..8025126384e 100644 --- a/tests/snapshots/hacs-test-org/theme-basic/test_discard_invalid_repo_data.json +++ b/tests/snapshots/hacs-test-org/theme-basic/test_discard_invalid_repo_data.json @@ -55,7 +55,7 @@ "area_id": null, "attributes": { "auto_update": false, - "entity_picture": "https://brands.home-assistant.io/_/hacs/icon.png", + "entity_picture": "/api/hacs/brands/hacs/icon.png", "friendly_name": "HACS update", "in_progress": false, "installed_version": "0.0.0", diff --git a/tests/snapshots/hacs-test-org/theme-basic/test_download_repository.json b/tests/snapshots/hacs-test-org/theme-basic/test_download_repository.json index acc2d0da5e1..b82a11fd083 100644 --- a/tests/snapshots/hacs-test-org/theme-basic/test_download_repository.json +++ b/tests/snapshots/hacs-test-org/theme-basic/test_download_repository.json @@ -121,7 +121,7 @@ "area_id": null, "attributes": { "auto_update": false, - "entity_picture": "https://brands.home-assistant.io/_/hacs/icon.png", + "entity_picture": "/api/hacs/brands/hacs/icon.png", "friendly_name": "HACS update", "in_progress": false, "installed_version": "0.0.0", diff --git a/tests/snapshots/hacs-test-org/theme-basic/test_remove_repository_post.json b/tests/snapshots/hacs-test-org/theme-basic/test_remove_repository_post.json index 1a7a2d72f3a..fb5a418faab 100644 --- a/tests/snapshots/hacs-test-org/theme-basic/test_remove_repository_post.json +++ b/tests/snapshots/hacs-test-org/theme-basic/test_remove_repository_post.json @@ -67,7 +67,7 @@ "area_id": null, "attributes": { "auto_update": false, - "entity_picture": "https://brands.home-assistant.io/_/hacs/icon.png", + "entity_picture": "/api/hacs/brands/hacs/icon.png", "friendly_name": "HACS update", "in_progress": false, "installed_version": "0.0.0", diff --git a/tests/snapshots/hacs-test-org/theme-basic/test_remove_repository_pre.json b/tests/snapshots/hacs-test-org/theme-basic/test_remove_repository_pre.json index b1ade9ce808..745a12d7558 100644 --- a/tests/snapshots/hacs-test-org/theme-basic/test_remove_repository_pre.json +++ b/tests/snapshots/hacs-test-org/theme-basic/test_remove_repository_pre.json @@ -77,7 +77,7 @@ "area_id": null, "attributes": { "auto_update": false, - "entity_picture": "https://brands.home-assistant.io/_/hacs/icon.png", + "entity_picture": "/api/hacs/brands/hacs/icon.png", "friendly_name": "HACS update", "in_progress": false, "installed_version": "0.0.0", diff --git a/tests/snapshots/hacs-test-org/theme-basic/test_update_repository_entity.json b/tests/snapshots/hacs-test-org/theme-basic/test_update_repository_entity.json index 642e4076932..000107de6f2 100644 --- a/tests/snapshots/hacs-test-org/theme-basic/test_update_repository_entity.json +++ b/tests/snapshots/hacs-test-org/theme-basic/test_update_repository_entity.json @@ -135,7 +135,7 @@ "area_id": null, "attributes": { "auto_update": false, - "entity_picture": "https://brands.home-assistant.io/_/hacs/icon.png", + "entity_picture": "/api/hacs/brands/hacs/icon.png", "friendly_name": "HACS update", "in_progress": false, "installed_version": "0.0.0", diff --git a/tests/snapshots/hacs-test-org/theme-basic/test_update_repository_websocket.json b/tests/snapshots/hacs-test-org/theme-basic/test_update_repository_websocket.json index 05a8358ce09..f0833b20bd7 100644 --- a/tests/snapshots/hacs-test-org/theme-basic/test_update_repository_websocket.json +++ b/tests/snapshots/hacs-test-org/theme-basic/test_update_repository_websocket.json @@ -94,7 +94,7 @@ "area_id": null, "attributes": { "auto_update": false, - "entity_picture": "https://brands.home-assistant.io/_/hacs/icon.png", + "entity_picture": "/api/hacs/brands/hacs/icon.png", "friendly_name": "HACS update", "in_progress": false, "installed_version": "0.0.0", diff --git a/tests/snapshots/test_integration_setup.json b/tests/snapshots/test_integration_setup.json index efa68d74de3..9f7a3c870b6 100644 --- a/tests/snapshots/test_integration_setup.json +++ b/tests/snapshots/test_integration_setup.json @@ -61,7 +61,7 @@ "area_id": null, "attributes": { "auto_update": false, - "entity_picture": "https://brands.home-assistant.io/_/hacs/icon.png", + "entity_picture": "/api/hacs/brands/hacs/icon.png", "friendly_name": "HACS update", "in_progress": false, "installed_version": "0.0.0", diff --git a/tests/test_brands.py b/tests/test_brands.py new file mode 100644 index 00000000000..41022621ed5 --- /dev/null +++ b/tests/test_brands.py @@ -0,0 +1,70 @@ +"""Test HACS brand icon view.""" + +from types import SimpleNamespace + +from aiohttp import web +import pytest + +from custom_components.hacs.brands import HacsBrandIconView +from custom_components.hacs.const import BRAND_ICON_CDN_URL +from custom_components.hacs.enums import HacsCategory + + +class FakeHass: + """Minimal Home Assistant test double.""" + + async def async_add_executor_job(self, target, *args): + """Run executor jobs inline for the unit test.""" + return target(*args) + + +def _repository(*, category=HacsCategory.INTEGRATION, domain="example", local_path=None): + """Build a minimal HACS repository test double.""" + return SimpleNamespace( + data=SimpleNamespace(category=category, domain=domain), + content=SimpleNamespace(path=SimpleNamespace(local=local_path)), + ) + + +async def test_brand_icon_view_serves_installed_integration_icon(tmp_path): + """Test local brand icons are served for installed integrations.""" + brand_dir = tmp_path / "brand" + brand_dir.mkdir() + (brand_dir / "icon.png").write_bytes(b"icon") + + hacs = SimpleNamespace( + repositories=SimpleNamespace( + list_downloaded=[_repository(domain="example", local_path=str(tmp_path))] + ) + ) + view = HacsBrandIconView(FakeHass(), hacs) + + response = await view.get(None, "example") + + assert response.body == b"icon" + assert response.content_type == "image/png" + + +async def test_brand_icon_view_falls_back_to_cdn_for_missing_icon(tmp_path): + """Test missing local icons redirect to the existing CDN fallback.""" + hacs = SimpleNamespace( + repositories=SimpleNamespace( + list_downloaded=[_repository(domain="example", local_path=str(tmp_path))] + ) + ) + view = HacsBrandIconView(FakeHass(), hacs) + + with pytest.raises(web.HTTPFound) as err: + await view.get(None, "example") + + assert err.value.location == BRAND_ICON_CDN_URL.format(domain="example") + + +async def test_brand_icon_view_rejects_invalid_domain(): + """Test invalid domains are not redirected.""" + hacs = SimpleNamespace(repositories=SimpleNamespace(list_downloaded=[])) + view = HacsBrandIconView(FakeHass(), hacs) + + response = await view.get(None, "../example") + + assert response.status == 404