diff --git a/homeassistant/components/camera/__init__.py b/homeassistant/components/camera/__init__.py index 2430ccebb4f..861b184975b 100644 --- a/homeassistant/components/camera/__init__.py +++ b/homeassistant/components/camera/__init__.py @@ -511,14 +511,14 @@ class Camera(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): self._create_stream_lock: asyncio.Lock | None = None self._rtsp_to_webrtc = False - @property + @cached_property def entity_picture(self) -> str: """Return a link to the camera feed as entity picture.""" if self._attr_entity_picture is not None: return self._attr_entity_picture return ENTITY_IMAGE_URL.format(self.entity_id, self.access_tokens[-1]) - @property + @cached_property def use_stream_for_stills(self) -> bool: """Whether or not to use stream to generate stills.""" return False @@ -745,6 +745,7 @@ class Camera(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): def async_update_token(self) -> None: """Update the used token.""" self.access_tokens.append(hex(_RND.getrandbits(256))[2:]) + self.__dict__.pop("entity_picture", None) async def async_internal_added_to_hass(self) -> None: """Run when entity about to be added to hass.""" diff --git a/tests/components/camera/test_init.py b/tests/components/camera/test_init.py index ccec2b6f50c..dffc7e5aa53 100644 --- a/tests/components/camera/test_init.py +++ b/tests/components/camera/test_init.py @@ -24,10 +24,15 @@ from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import entity_registry as er from homeassistant.setup import async_setup_component +from homeassistant.util import dt as dt_util from .common import EMPTY_8_6_JPEG, WEBRTC_ANSWER, mock_turbo_jpeg -from tests.common import help_test_all, import_and_test_deprecated_constant_enum +from tests.common import ( + async_fire_time_changed, + help_test_all, + import_and_test_deprecated_constant_enum, +) from tests.typing import ClientSessionGenerator, WebSocketGenerator STREAM_SOURCE = "rtsp://127.0.0.1/stream" @@ -1073,3 +1078,23 @@ def test_deprecated_supported_features_ints(caplog: pytest.LogCaptureFixture) -> caplog.clear() assert entity.supported_features_compat is camera.CameraEntityFeature(1) assert "is using deprecated supported features values" not in caplog.text + + +async def test_entity_picture_url_changes_on_token_update( + hass: HomeAssistant, mock_camera +) -> None: + """Test the token is rotated and entity entity picture cache is cleared.""" + await async_setup_component(hass, "camera", {}) + await hass.async_block_till_done() + + camera_state = hass.states.get("camera.demo_camera") + original_picture = camera_state.attributes["entity_picture"] + assert "token=" in original_picture + + async_fire_time_changed(hass, dt_util.utcnow() + camera.TOKEN_CHANGE_INTERVAL) + await hass.async_block_till_done(wait_background_tasks=True) + + camera_state = hass.states.get("camera.demo_camera") + new_entity_picture = camera_state.attributes["entity_picture"] + assert new_entity_picture != original_picture + assert "token=" in new_entity_picture