Fix qr code data update in AVM Fritz!Tools (#95470)
* use async_update * improve tests * use async_imagepull/95478/head
parent
79f1c86789
commit
b64be798df
|
@ -393,7 +393,6 @@ omit =
|
|||
homeassistant/components/freebox/switch.py
|
||||
homeassistant/components/fritz/common.py
|
||||
homeassistant/components/fritz/device_tracker.py
|
||||
homeassistant/components/fritz/image.py
|
||||
homeassistant/components/fritz/services.py
|
||||
homeassistant/components/fritz/switch.py
|
||||
homeassistant/components/fritzbox_callmonitor/__init__.py
|
||||
|
|
|
@ -48,6 +48,7 @@ class FritzGuestWifiQRImage(FritzBoxBaseEntity, ImageEntity):
|
|||
_attr_content_type = "image/png"
|
||||
_attr_entity_category = EntityCategory.DIAGNOSTIC
|
||||
_attr_has_entity_name = True
|
||||
_attr_should_poll = True
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
|
@ -63,22 +64,24 @@ class FritzGuestWifiQRImage(FritzBoxBaseEntity, ImageEntity):
|
|||
super().__init__(avm_wrapper, device_friendly_name)
|
||||
ImageEntity.__init__(self, hass)
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Set the update time."""
|
||||
self._attr_image_last_updated = dt_util.utcnow()
|
||||
|
||||
async def async_image(self) -> bytes:
|
||||
"""Return bytes of image."""
|
||||
async def _fetch_image(self) -> bytes:
|
||||
"""Fetch the QR code from the Fritz!Box."""
|
||||
qr_stream: BytesIO = await self.hass.async_add_executor_job(
|
||||
self._avm_wrapper.fritz_guest_wifi.get_wifi_qr_code, "png"
|
||||
)
|
||||
qr_bytes = qr_stream.getvalue()
|
||||
|
||||
_LOGGER.debug("fetched %s bytes", len(qr_bytes))
|
||||
|
||||
if self._current_qr_bytes is None:
|
||||
self._current_qr_bytes = qr_bytes
|
||||
return qr_bytes
|
||||
return qr_bytes
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Fetch and set initial data and state."""
|
||||
self._current_qr_bytes = await self._fetch_image()
|
||||
self._attr_image_last_updated = dt_util.utcnow()
|
||||
|
||||
async def async_update(self) -> None:
|
||||
"""Update the image entity data."""
|
||||
qr_bytes = await self._fetch_image()
|
||||
|
||||
if self._current_qr_bytes != qr_bytes:
|
||||
dt_now = dt_util.utcnow()
|
||||
|
@ -87,4 +90,6 @@ class FritzGuestWifiQRImage(FritzBoxBaseEntity, ImageEntity):
|
|||
self._current_qr_bytes = qr_bytes
|
||||
self.async_write_ha_state()
|
||||
|
||||
return qr_bytes
|
||||
async def async_image(self) -> bytes | None:
|
||||
"""Return bytes of image."""
|
||||
return self._current_qr_bytes
|
||||
|
|
|
@ -43,6 +43,10 @@ class FritzConnectionMock: # pylint: disable=too-few-public-methods
|
|||
else:
|
||||
self.call_action = self._call_action
|
||||
|
||||
def override_services(self, services) -> None:
|
||||
"""Overrire services data."""
|
||||
self._services = services
|
||||
|
||||
def _call_action(self, service: str, action: str, **kwargs):
|
||||
LOGGER.debug(
|
||||
"_call_action service: %s, action: %s, **kwargs: %s",
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
# serializer version: 1
|
||||
# name: test_image[fc_data0]
|
||||
b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x94\x00\x00\x00\x94\x01\x00\x00\x00\x00]G=y\x00\x00\x00\xf5IDATx\xda\xedVQ\x0eC!\x0c"\xbb@\xef\x7fKn\xe0\x00\xfd\xdb\xcf6\xf9|\xc6\xc4\xc6\x0f\xd2\x02\xadb},\xe2\xb9\xfb\xe5\x0e\xc0(\x18\xf2\x84/|\xaeo\xef\x847\xda\x14\x1af\x1c\xde\xe3\x19(X\tKxN\xb2\x87\x17j9\x1d<m\x01)\xbbU\xe1\xcf\xa2\x9eU\xd1\xd7\xcbe.\xcc\xf6\xd05\x7f\x02\x82\x1d\xb8\x1c\xdd\xd7\x1b\xef\t\x90\x13an\xf1b\x13P\xb9\x01\xac\xd4k\xee\x04\xa5.\xd1.\xe8+\x90\x88\x1b\x0e\x0b\xfe\x03\xd3 \xd4Y\xe0\xef\x10\xa7z\xe3\xe9F\x7f(?;\xc6\x80\x95\xfc\xe2\x13\x1ddC\x0fZ\x07\xec6f\xc3/.\x94i\xddi\xf8\x8f\x9b9k<\x8d\xf9\xeci`\xfb\xed\xf1R\x99/g\x9e\xaei\xcc\x830\xb7\xf6\x83\xd4\xf1_\x9e\x0f\xf7.*\xf3\xc0\xf6\x1b\x86\xbf\x12\xde\xac\xed\x16\xb0\xf4\xbe\x9dO\x02\xd0\xe1\x8f\xee^\x0f|v\xf4\x15 \x13\xaf\x8e\xff\x9e\x7f\xe2\x9fwo\x06\xf4\x81v\xeb\xb3\xcc\xc3\x00\x00\x00\x00IEND\xaeB`\x82'
|
||||
# ---
|
||||
# name: test_image_download[fc_data0]
|
||||
b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x94\x00\x00\x00\x94\x01\x00\x00\x00\x00]G=y\x00\x00\x00\xf5IDATx\xda\xedVQ\x0eC!\x0c"\xbb@\xef\x7fKn\xe0\x00\xfd\xdb\xcf6\xf9|\xc6\xc4\xc6\x0f\xd2\x02\xadb},\xe2\xb9\xfb\xe5\x0e\xc0(\x18\xf2\x84/|\xaeo\xef\x847\xda\x14\x1af\x1c\xde\xe3\x19(X\tKxN\xb2\x87\x17j9\x1d<m\x01)\xbbU\xe1\xcf\xa2\x9eU\xd1\xd7\xcbe.\xcc\xf6\xd05\x7f\x02\x82\x1d\xb8\x1c\xdd\xd7\x1b\xef\t\x90\x13an\xf1b\x13P\xb9\x01\xac\xd4k\xee\x04\xa5.\xd1.\xe8+\x90\x88\x1b\x0e\x0b\xfe\x03\xd3 \xd4Y\xe0\xef\x10\xa7z\xe3\xe9F\x7f(?;\xc6\x80\x95\xfc\xe2\x13\x1ddC\x0fZ\x07\xec6f\xc3/.\x94i\xddi\xf8\x8f\x9b9k<\x8d\xf9\xeci`\xfb\xed\xf1R\x99/g\x9e\xaei\xcc\x830\xb7\xf6\x83\xd4\xf1_\x9e\x0f\xf7.*\xf3\xc0\xf6\x1b\x86\xbf\x12\xde\xac\xed\x16\xb0\xf4\xbe\x9dO\x02\xd0\xe1\x8f\xee^\x0f|v\xf4\x15 \x13\xaf\x8e\xff\x9e\x7f\xe2\x9fwo\x06\xf4\x81v\xeb\xb3\xcc\xc3\x00\x00\x00\x00IEND\xaeB`\x82'
|
||||
# ---
|
||||
# name: test_image_entity[fc_data0]
|
||||
b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x94\x00\x00\x00\x94\x01\x00\x00\x00\x00]G=y\x00\x00\x00\xf5IDATx\xda\xedVQ\x0eC!\x0c"\xbb@\xef\x7fKn\xe0\x00\xfd\xdb\xcf6\xf9|\xc6\xc4\xc6\x0f\xd2\x02\xadb},\xe2\xb9\xfb\xe5\x0e\xc0(\x18\xf2\x84/|\xaeo\xef\x847\xda\x14\x1af\x1c\xde\xe3\x19(X\tKxN\xb2\x87\x17j9\x1d<m\x01)\xbbU\xe1\xcf\xa2\x9eU\xd1\xd7\xcbe.\xcc\xf6\xd05\x7f\x02\x82\x1d\xb8\x1c\xdd\xd7\x1b\xef\t\x90\x13an\xf1b\x13P\xb9\x01\xac\xd4k\xee\x04\xa5.\xd1.\xe8+\x90\x88\x1b\x0e\x0b\xfe\x03\xd3 \xd4Y\xe0\xef\x10\xa7z\xe3\xe9F\x7f(?;\xc6\x80\x95\xfc\xe2\x13\x1ddC\x0fZ\x07\xec6f\xc3/.\x94i\xddi\xf8\x8f\x9b9k<\x8d\xf9\xeci`\xfb\xed\xf1R\x99/g\x9e\xaei\xcc\x830\xb7\xf6\x83\xd4\xf1_\x9e\x0f\xf7.*\xf3\xc0\xf6\x1b\x86\xbf\x12\xde\xac\xed\x16\xb0\xf4\xbe\x9dO\x02\xd0\xe1\x8f\xee^\x0f|v\xf4\x15 \x13\xaf\x8e\xff\x9e\x7f\xe2\x9fwo\x06\xf4\x81v\xeb\xb3\xcc\xc3\x00\x00\x00\x00IEND\xaeB`\x82'
|
||||
# ---
|
||||
# name: test_image_update[fc_data0]
|
||||
b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x94\x00\x00\x00\x94\x01\x00\x00\x00\x00]G=y\x00\x00\x00\xf9IDATx\xda\xedV\xc1\r\xc40\x0cB\xb7\x80\xf7\xdf\x92\r\\\xb0\xfb\xeb\xe7\xaa\xf0l\xd4\xaaQ\x1e\xc8\x06L\x8a~,\xe2;{s\x06\xa0\xd8z9\xdb\xe6\x0f\xcf\xf5\xef\x99\xf0J\x0f\x85\x86*o\xcf\xf1\x04\x04\x1ak\xb6\x11<\x97\xa6\xa6\x83x&\xb32x\x86\xa4\xab\xeb\x08\x7f\x16\xf5^\x11}\xbd$\xb0\x80k=t\xcc\x9f\xfdg\xfa\xda\xe5\x1d\xe3\t\x8br_\xdb3\x85D}\x063u\x00\x03\xfd\xb6<\xe2\xeaL\xa2y<\xae\xcf\xe3!\x895\xbfL\xf07\x0eT]n7\xc3_{0\xd4\xefx:\xc0\x1f\xc6}\x9e\xb7\x84\x1e\xfb\x91\x0e\x12\x84\t=z\xd2t\x07\x8e\x1d\xc9\x03\xc7\xa9G\xb7\x12\xf3&0\x176\x19\x98\xc8g\x8b;\x88@\xc6\x7f\x93\xa9\xfbVD\xdf\x193\xde9\x1d\xd1\xc3\x9ev`E\xf2oo\xa3\xe1/\x847\xad\x8a?0t\xffN\xb4p\xf35\xf3\x7f\x80\xad\xafS\xf7\x1bD`D\x8f\xef\x9f\xf0\xe0\xec\x02\xa4\xc0\x83\x92\xcf\xf3\xf9a\x00\x00\x00\x00IEND\xaeB`\x82'
|
||||
# ---
|
|
@ -1,74 +1,156 @@
|
|||
"""Tests for Fritz!Tools image platform."""
|
||||
from datetime import timedelta
|
||||
from http import HTTPStatus
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
from syrupy.assertion import SnapshotAssertion
|
||||
|
||||
from homeassistant.components.fritz.const import DOMAIN
|
||||
from homeassistant.components.image import DOMAIN as IMAGE_DOMAIN
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_registry import async_get as async_get_entity_registry
|
||||
from homeassistant.setup import async_setup_component
|
||||
from homeassistant.util.dt import utcnow
|
||||
|
||||
from .const import MOCK_FB_SERVICES, MOCK_USER_DATA
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
from tests.common import MockConfigEntry, async_fire_time_changed
|
||||
from tests.typing import ClientSessionGenerator
|
||||
|
||||
GUEST_WIFI_ENABLED: dict[str, dict] = {
|
||||
"WLANConfiguration0": {
|
||||
"GetInfo": {
|
||||
"NewEnable": True,
|
||||
"NewSSID": "HomeWifi",
|
||||
}
|
||||
},
|
||||
"WLANConfiguration0": {},
|
||||
"WLANConfiguration1": {
|
||||
"GetInfo": {
|
||||
"NewEnable": True,
|
||||
"NewStatus": "Up",
|
||||
"NewSSID": "GuestWifi",
|
||||
}
|
||||
"NewBeaconType": "11iandWPA3",
|
||||
"NewX_AVM-DE_PossibleBeaconTypes": "None,11i,11iandWPA3",
|
||||
"NewStandard": "ax",
|
||||
"NewBSSID": "1C:ED:6F:12:34:13",
|
||||
},
|
||||
"GetSSID": {
|
||||
"NewSSID": "GuestWifi",
|
||||
},
|
||||
"GetSecurityKeys": {"NewKeyPassphrase": "1234567890"},
|
||||
},
|
||||
}
|
||||
|
||||
GUEST_WIFI_CHANGED: dict[str, dict] = {
|
||||
"WLANConfiguration0": {},
|
||||
"WLANConfiguration1": {
|
||||
"GetInfo": {
|
||||
"NewEnable": True,
|
||||
"NewStatus": "Up",
|
||||
"NewSSID": "GuestWifi",
|
||||
"NewBeaconType": "11iandWPA3",
|
||||
"NewX_AVM-DE_PossibleBeaconTypes": "None,11i,11iandWPA3",
|
||||
"NewStandard": "ax",
|
||||
"NewBSSID": "1C:ED:6F:12:34:13",
|
||||
},
|
||||
"GetSSID": {
|
||||
"NewSSID": "GuestWifi",
|
||||
},
|
||||
"GetSecurityKeys": {"NewKeyPassphrase": "abcdefghij"},
|
||||
},
|
||||
}
|
||||
|
||||
GUEST_WIFI_DISABLED: dict[str, dict] = {
|
||||
"WLANConfiguration0": {
|
||||
"GetInfo": {
|
||||
"NewEnable": True,
|
||||
"NewSSID": "HomeWifi",
|
||||
}
|
||||
},
|
||||
"WLANConfiguration1": {
|
||||
"GetInfo": {
|
||||
"NewEnable": False,
|
||||
"NewSSID": "GuestWifi",
|
||||
}
|
||||
},
|
||||
"WLANConfiguration0": {},
|
||||
"WLANConfiguration1": {"GetInfo": {"NewEnable": False}},
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(("fc_data"), [({**MOCK_FB_SERVICES, **GUEST_WIFI_ENABLED})])
|
||||
async def test_image_entities_initialized(
|
||||
async def test_image_entity(
|
||||
hass: HomeAssistant,
|
||||
hass_client: ClientSessionGenerator,
|
||||
snapshot: SnapshotAssertion,
|
||||
fc_class_mock,
|
||||
fh_class_mock,
|
||||
) -> None:
|
||||
"""Test image entities."""
|
||||
"""Test image entity."""
|
||||
|
||||
entry = MockConfigEntry(domain=DOMAIN, data=MOCK_USER_DATA)
|
||||
entry.add_to_hass(hass)
|
||||
# setup component with image platform only
|
||||
with patch(
|
||||
"homeassistant.components.fritz.PLATFORMS",
|
||||
[Platform.IMAGE],
|
||||
):
|
||||
entry = MockConfigEntry(domain=DOMAIN, data=MOCK_USER_DATA)
|
||||
entry.add_to_hass(hass)
|
||||
assert await async_setup_component(hass, DOMAIN, {})
|
||||
|
||||
assert await async_setup_component(hass, DOMAIN, {})
|
||||
await hass.async_block_till_done()
|
||||
assert entry.state == ConfigEntryState.LOADED
|
||||
|
||||
images = hass.states.async_all(IMAGE_DOMAIN)
|
||||
assert len(images) == 1
|
||||
assert images[0].name == "Mock Title GuestWifi"
|
||||
# test image entity is generated as expected
|
||||
states = hass.states.async_all(IMAGE_DOMAIN)
|
||||
assert len(states) == 1
|
||||
|
||||
state = states[0]
|
||||
assert state.name == "Mock Title GuestWifi"
|
||||
assert state.entity_id == "image.mock_title_guestwifi"
|
||||
|
||||
access_token = state.attributes["access_token"]
|
||||
assert state.attributes == {
|
||||
"access_token": access_token,
|
||||
"entity_picture": f"/api/image_proxy/image.mock_title_guestwifi?token={access_token}",
|
||||
"friendly_name": "Mock Title GuestWifi",
|
||||
}
|
||||
|
||||
entity_registry = async_get_entity_registry(hass)
|
||||
entity_entry = entity_registry.async_get("image.mock_title_guestwifi")
|
||||
|
||||
assert entity_entry.unique_id == "1c_ed_6f_12_34_11_guestwifi_qr_code"
|
||||
|
||||
# test image download
|
||||
client = await hass_client()
|
||||
resp = await client.get("/api/image_proxy/image.mock_title_guestwifi")
|
||||
assert resp.status == HTTPStatus.OK
|
||||
|
||||
body = await resp.read()
|
||||
assert body == snapshot
|
||||
|
||||
|
||||
@pytest.mark.parametrize(("fc_data"), [({**MOCK_FB_SERVICES, **GUEST_WIFI_ENABLED})])
|
||||
async def test_image_update(
|
||||
hass: HomeAssistant,
|
||||
hass_client: ClientSessionGenerator,
|
||||
snapshot: SnapshotAssertion,
|
||||
fc_class_mock,
|
||||
fh_class_mock,
|
||||
) -> None:
|
||||
"""Test image update."""
|
||||
|
||||
# setup component with image platform only
|
||||
with patch(
|
||||
"homeassistant.components.fritz.PLATFORMS",
|
||||
[Platform.IMAGE],
|
||||
):
|
||||
entry = MockConfigEntry(domain=DOMAIN, data=MOCK_USER_DATA)
|
||||
entry.add_to_hass(hass)
|
||||
assert await async_setup_component(hass, DOMAIN, {})
|
||||
|
||||
await hass.async_block_till_done()
|
||||
assert entry.state == ConfigEntryState.LOADED
|
||||
|
||||
client = await hass_client()
|
||||
resp = await client.get("/api/image_proxy/image.mock_title_guestwifi")
|
||||
resp_body = await resp.read()
|
||||
assert resp.status == HTTPStatus.OK
|
||||
|
||||
fc_class_mock().override_services({**MOCK_FB_SERVICES, **GUEST_WIFI_CHANGED})
|
||||
async_fire_time_changed(hass, utcnow() + timedelta(seconds=60))
|
||||
await hass.async_block_till_done()
|
||||
|
||||
resp = await client.get("/api/image_proxy/image.mock_title_guestwifi")
|
||||
resp_body_new = await resp.read()
|
||||
|
||||
assert resp_body != resp_body_new
|
||||
assert resp_body_new == snapshot
|
||||
|
||||
|
||||
@pytest.mark.parametrize(("fc_data"), [({**MOCK_FB_SERVICES, **GUEST_WIFI_DISABLED})])
|
||||
async def test_image_guest_wifi_disabled(
|
||||
|
|
Loading…
Reference in New Issue