From b4e4a98f178638ed156d57f229e194aa273fc841 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker <joostlek@outlook.com> Date: Sun, 15 Oct 2023 20:29:20 +0200 Subject: [PATCH] Add diagnostics to Withings (#102066) --- .../components/withings/coordinator.py | 2 + .../components/withings/diagnostics.py | 45 +++++++++++ .../withings/snapshots/test_diagnostics.ambr | 79 ++++++++++++++++++ tests/components/withings/test_diagnostics.py | 80 +++++++++++++++++++ 4 files changed, 206 insertions(+) create mode 100644 homeassistant/components/withings/diagnostics.py create mode 100644 tests/components/withings/snapshots/test_diagnostics.ambr create mode 100644 tests/components/withings/test_diagnostics.py diff --git a/homeassistant/components/withings/coordinator.py b/homeassistant/components/withings/coordinator.py index c5192ba3466..ac320aae3ae 100644 --- a/homeassistant/components/withings/coordinator.py +++ b/homeassistant/components/withings/coordinator.py @@ -34,6 +34,7 @@ class WithingsDataUpdateCoordinator(DataUpdateCoordinator[_T]): config_entry: ConfigEntry _default_update_interval: timedelta | None = UPDATE_INTERVAL _last_valid_update: datetime | None = None + webhooks_connected: bool = False def __init__(self, hass: HomeAssistant, client: WithingsClient) -> None: """Initialize the Withings data coordinator.""" @@ -45,6 +46,7 @@ class WithingsDataUpdateCoordinator(DataUpdateCoordinator[_T]): def webhook_subscription_listener(self, connected: bool) -> None: """Call when webhook status changed.""" + self.webhooks_connected = connected if connected: self.update_interval = None else: diff --git a/homeassistant/components/withings/diagnostics.py b/homeassistant/components/withings/diagnostics.py new file mode 100644 index 00000000000..2424452d0f5 --- /dev/null +++ b/homeassistant/components/withings/diagnostics.py @@ -0,0 +1,45 @@ +"""Diagnostics support for Withings.""" +from __future__ import annotations + +from typing import Any + +from yarl import URL + +from homeassistant.components.webhook import async_generate_url as webhook_generate_url +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_WEBHOOK_ID +from homeassistant.core import HomeAssistant + +from . import ( + CONF_CLOUDHOOK_URL, + WithingsMeasurementDataUpdateCoordinator, + WithingsSleepDataUpdateCoordinator, +) +from .const import DOMAIN, MEASUREMENT_COORDINATOR, SLEEP_COORDINATOR + + +async def async_get_config_entry_diagnostics( + hass: HomeAssistant, entry: ConfigEntry +) -> dict[str, Any]: + """Return diagnostics for a config entry.""" + + webhook_url = webhook_generate_url(hass, entry.data[CONF_WEBHOOK_ID]) + url = URL(webhook_url) + has_valid_external_webhook_url = url.scheme == "https" and url.port == 443 + + has_cloudhooks = CONF_CLOUDHOOK_URL in entry.data + + measurement_coordinator: WithingsMeasurementDataUpdateCoordinator = hass.data[ + DOMAIN + ][entry.entry_id][MEASUREMENT_COORDINATOR] + sleep_coordinator: WithingsSleepDataUpdateCoordinator = hass.data[DOMAIN][ + entry.entry_id + ][SLEEP_COORDINATOR] + + return { + "has_valid_external_webhook_url": has_valid_external_webhook_url, + "has_cloudhooks": has_cloudhooks, + "webhooks_connected": measurement_coordinator.webhooks_connected, + "received_measurements": list(measurement_coordinator.data.keys()), + "received_sleep_data": sleep_coordinator.data is not None, + } diff --git a/tests/components/withings/snapshots/test_diagnostics.ambr b/tests/components/withings/snapshots/test_diagnostics.ambr new file mode 100644 index 00000000000..c65c321a5ef --- /dev/null +++ b/tests/components/withings/snapshots/test_diagnostics.ambr @@ -0,0 +1,79 @@ +# serializer version: 1 +# name: test_diagnostics_cloudhook_instance + dict({ + 'has_cloudhooks': True, + 'has_valid_external_webhook_url': True, + 'received_measurements': list([ + 1, + 8, + 5, + 76, + 88, + 4, + 12, + 71, + 73, + 6, + 9, + 10, + 11, + 54, + 77, + 91, + ]), + 'received_sleep_data': True, + 'webhooks_connected': True, + }) +# --- +# name: test_diagnostics_polling_instance + dict({ + 'has_cloudhooks': False, + 'has_valid_external_webhook_url': False, + 'received_measurements': list([ + 1, + 8, + 5, + 76, + 88, + 4, + 12, + 71, + 73, + 6, + 9, + 10, + 11, + 54, + 77, + 91, + ]), + 'received_sleep_data': True, + 'webhooks_connected': False, + }) +# --- +# name: test_diagnostics_webhook_instance + dict({ + 'has_cloudhooks': False, + 'has_valid_external_webhook_url': True, + 'received_measurements': list([ + 1, + 8, + 5, + 76, + 88, + 4, + 12, + 71, + 73, + 6, + 9, + 10, + 11, + 54, + 77, + 91, + ]), + 'received_sleep_data': True, + 'webhooks_connected': True, + }) +# --- diff --git a/tests/components/withings/test_diagnostics.py b/tests/components/withings/test_diagnostics.py new file mode 100644 index 00000000000..bb5c93e1f09 --- /dev/null +++ b/tests/components/withings/test_diagnostics.py @@ -0,0 +1,80 @@ +"""Tests for the diagnostics data provided by the Withings integration.""" +from unittest.mock import AsyncMock, patch + +from freezegun.api import FrozenDateTimeFactory +from syrupy import SnapshotAssertion + +from homeassistant.core import HomeAssistant + +from tests.common import MockConfigEntry +from tests.components.diagnostics import get_diagnostics_for_config_entry +from tests.components.withings import prepare_webhook_setup, setup_integration +from tests.typing import ClientSessionGenerator + + +async def test_diagnostics_polling_instance( + hass: HomeAssistant, + hass_client: ClientSessionGenerator, + withings: AsyncMock, + polling_config_entry: MockConfigEntry, + snapshot: SnapshotAssertion, +) -> None: + """Test diagnostics.""" + await setup_integration(hass, polling_config_entry, False) + + assert ( + await get_diagnostics_for_config_entry(hass, hass_client, polling_config_entry) + == snapshot + ) + + +async def test_diagnostics_webhook_instance( + hass: HomeAssistant, + hass_client: ClientSessionGenerator, + withings: AsyncMock, + webhook_config_entry: MockConfigEntry, + snapshot: SnapshotAssertion, + freezer: FrozenDateTimeFactory, +) -> None: + """Test diagnostics.""" + await setup_integration(hass, webhook_config_entry) + await prepare_webhook_setup(hass, freezer) + + assert ( + await get_diagnostics_for_config_entry(hass, hass_client, webhook_config_entry) + == snapshot + ) + + +async def test_diagnostics_cloudhook_instance( + hass: HomeAssistant, + hass_client: ClientSessionGenerator, + withings: AsyncMock, + webhook_config_entry: MockConfigEntry, + snapshot: SnapshotAssertion, + freezer: FrozenDateTimeFactory, +) -> None: + """Test diagnostics.""" + with patch( + "homeassistant.components.cloud.async_is_logged_in", return_value=True + ), patch( + "homeassistant.components.cloud.async_is_connected", return_value=True + ), patch( + "homeassistant.components.cloud.async_active_subscription", return_value=True + ), patch( + "homeassistant.components.cloud.async_create_cloudhook", + return_value="https://hooks.nabu.casa/ABCD", + ), patch( + "homeassistant.helpers.config_entry_oauth2_flow.async_get_config_entry_implementation", + ), patch( + "homeassistant.components.cloud.async_delete_cloudhook" + ), patch( + "homeassistant.components.withings.webhook_generate_url" + ): + await setup_integration(hass, webhook_config_entry) + await prepare_webhook_setup(hass, freezer) + + assert ( + await get_diagnostics_for_config_entry(hass, hass_client, webhook_config_entry) + == snapshot + )