1
0
mirror of https://github.com/home-assistant/core.git synced 2026-08-06 13:26:29 +01:00
Files
core/tests/components/google_health/test_init.py
T

255 lines
9.5 KiB
Python

"""Tests for Google Health integration lifecycle (init/unloading)."""
import asyncio
from collections.abc import Awaitable, Callable
from datetime import timedelta
from unittest.mock import AsyncMock, patch
from google_health_api.exceptions import (
GoogleHealthApiError,
HealthApiForbiddenException,
HealthAuthException,
)
import pytest
from homeassistant import config_entries
from homeassistant.components.google_health.coordinator import POLLING_INTERVAL
from homeassistant.core import HomeAssistant
from homeassistant.helpers.config_entry_oauth2_flow import (
ImplementationUnavailableError,
)
from homeassistant.util import dt as dt_util
from tests.common import MockConfigEntry, async_fire_time_changed
@pytest.mark.usefixtures("mock_google_health_client")
async def test_setup_and_unload(
hass: HomeAssistant,
config_entry: MockConfigEntry,
integration_setup: Callable[[], Awaitable[bool]],
) -> None:
"""Test standard setup and unloading of the config entry."""
assert await integration_setup()
assert config_entry.state is config_entries.ConfigEntryState.LOADED
assert hass.states.get("sensor.google_health_steps") is not None
assert hass.states.get("sensor.google_health_distance") is not None
assert hass.states.get("sensor.google_health_weight") is not None
assert hass.states.get("sensor.google_health_resting_heart_rate") is not None
assert hass.states.get("sensor.google_health_time_asleep") is not None
assert hass.states.get("sensor.google_health_time_awake") is not None
assert hass.states.get("sensor.google_health_water_intake") is not None
assert hass.states.get("sensor.google_health_calories_consumed") is not None
assert await hass.config_entries.async_unload(config_entry.entry_id)
await hass.async_block_till_done()
assert config_entry.state is config_entries.ConfigEntryState.NOT_LOADED
async def test_setup_api_error(
hass: HomeAssistant,
config_entry: MockConfigEntry,
mock_google_health_client: AsyncMock,
integration_setup: Callable[[], Awaitable[bool]],
) -> None:
"""Test setup error retry handling when API fails."""
mock_google_health_client.steps.today.side_effect = GoogleHealthApiError
assert not await integration_setup()
assert config_entry.state is config_entries.ConfigEntryState.SETUP_RETRY
async def test_setup_auth_error(
hass: HomeAssistant,
config_entry: MockConfigEntry,
mock_google_health_client: AsyncMock,
integration_setup: Callable[[], Awaitable[bool]],
) -> None:
"""Test setup error when API returns auth or forbidden errors."""
mock_google_health_client.steps.today.side_effect = HealthApiForbiddenException
assert not await integration_setup()
assert config_entry.state is config_entries.ConfigEntryState.SETUP_ERROR
flows = hass.config_entries.flow.async_progress()
assert len(flows) == 1
assert flows[0]["step_id"] == "reauth_confirm"
@pytest.mark.usefixtures("mock_google_health_client")
@pytest.mark.parametrize(
"scopes", ["https://www.googleapis.com/auth/health.activity_and_fitness.readonly"]
)
async def test_setup_missing_scopes(
hass: HomeAssistant,
config_entry: MockConfigEntry,
integration_setup: Callable[[], Awaitable[bool]],
) -> None:
"""Test setup fails if token has missing profile scope."""
assert not await integration_setup()
assert config_entry.state is config_entries.ConfigEntryState.SETUP_ERROR
flows = hass.config_entries.flow.async_progress()
assert len(flows) == 1
assert flows[0]["step_id"] == "reauth_confirm"
@pytest.mark.usefixtures("mock_google_health_client")
@pytest.mark.parametrize(
"scopes",
[
[
"https://www.googleapis.com/auth/googlehealth.profile.readonly",
"https://www.googleapis.com/auth/googlehealth.health_metrics_and_measurements.readonly",
]
],
)
async def test_setup_missing_activity_scope(
hass: HomeAssistant,
config_entry: MockConfigEntry,
integration_setup: Callable[[], Awaitable[bool]],
) -> None:
"""Test setup succeeds but steps sensor is not added if activity scope is missing."""
assert await integration_setup()
assert config_entry.state is config_entries.ConfigEntryState.LOADED
assert hass.states.get("sensor.google_health_steps") is None
assert hass.states.get("sensor.google_health_distance") is None
assert hass.states.get("sensor.google_health_active_calories") is None
assert hass.states.get("sensor.google_health_total_calories") is None
assert hass.states.get("sensor.google_health_floors") is None
assert hass.states.get("sensor.google_health_weight") is not None
assert hass.states.get("sensor.google_health_resting_heart_rate") is not None
assert hass.states.get("sensor.google_health_body_fat") is not None
@pytest.mark.usefixtures("mock_google_health_client")
@pytest.mark.parametrize(
"scopes",
[
[
"https://www.googleapis.com/auth/googlehealth.profile.readonly",
"https://www.googleapis.com/auth/googlehealth.activity_and_fitness.readonly",
]
],
)
async def test_setup_missing_measurements_scope(
hass: HomeAssistant,
config_entry: MockConfigEntry,
integration_setup: Callable[[], Awaitable[bool]],
) -> None:
"""Test setup succeeds but weight sensor is not added if measurements scope is missing."""
assert await integration_setup()
assert config_entry.state is config_entries.ConfigEntryState.LOADED
assert hass.states.get("sensor.google_health_weight") is None
assert hass.states.get("sensor.google_health_resting_heart_rate") is None
assert hass.states.get("sensor.google_health_body_fat") is None
assert hass.states.get("sensor.google_health_steps") is not None
assert hass.states.get("sensor.google_health_distance") is not None
assert hass.states.get("sensor.google_health_active_calories") is not None
assert hass.states.get("sensor.google_health_total_calories") is not None
assert hass.states.get("sensor.google_health_floors") is not None
@pytest.mark.usefixtures("mock_google_health_client")
@pytest.mark.parametrize(
"scopes",
[
[
"https://www.googleapis.com/auth/googlehealth.profile.readonly",
"https://www.googleapis.com/auth/googlehealth.activity_and_fitness.readonly",
"https://www.googleapis.com/auth/googlehealth.health_metrics_and_measurements.readonly",
]
],
)
async def test_setup_missing_sleep_scope(
hass: HomeAssistant,
config_entry: MockConfigEntry,
integration_setup: Callable[[], Awaitable[bool]],
) -> None:
"""Test setup succeeds but sleep sensor is not added if sleep scope is missing."""
assert await integration_setup()
assert config_entry.state is config_entries.ConfigEntryState.LOADED
assert hass.states.get("sensor.google_health_time_asleep") is None
assert hass.states.get("sensor.google_health_time_awake") is None
@pytest.mark.usefixtures("mock_google_health_client")
@pytest.mark.parametrize(
"scopes",
[
[
"https://www.googleapis.com/auth/googlehealth.profile.readonly",
"https://www.googleapis.com/auth/googlehealth.activity_and_fitness.readonly",
"https://www.googleapis.com/auth/googlehealth.health_metrics_and_measurements.readonly",
"https://www.googleapis.com/auth/googlehealth.sleep.readonly",
]
],
)
async def test_setup_missing_nutrition_scope(
hass: HomeAssistant,
config_entry: MockConfigEntry,
integration_setup: Callable[[], Awaitable[bool]],
) -> None:
"""Test setup succeeds but nutrition sensors are not added if nutrition scope is missing."""
assert await integration_setup()
assert config_entry.state is config_entries.ConfigEntryState.LOADED
assert hass.states.get("sensor.google_health_water_intake") is None
assert hass.states.get("sensor.google_health_calories_consumed") is None
async def test_setup_oauth_implementation_unavailable(
hass: HomeAssistant,
config_entry: MockConfigEntry,
) -> None:
"""Test that unavailable OAuth implementation raises ConfigEntryNotReady."""
config_entry.add_to_hass(hass)
with patch(
"homeassistant.components.google_health.async_get_config_entry_implementation",
side_effect=ImplementationUnavailableError,
):
await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
assert config_entry.state is config_entries.ConfigEntryState.SETUP_RETRY
@pytest.mark.usefixtures("mock_google_health_client")
async def test_runtime_auth_error(
hass: HomeAssistant,
config_entry: MockConfigEntry,
integration_setup: Callable[[], Awaitable[bool]],
mock_google_health_client: AsyncMock,
) -> None:
"""Test runtime auth failure triggers a reauth flow."""
# Setup the integration
assert await integration_setup()
assert config_entry.state is config_entries.ConfigEntryState.LOADED
# Mock an authorization error on subsequent update refresh
mock_google_health_client.steps.today.side_effect = HealthAuthException(
"Token expired"
)
# Trigger update by advancing time
async_fire_time_changed(
hass,
dt_util.utcnow() + POLLING_INTERVAL + timedelta(seconds=1),
)
await hass.async_block_till_done()
# Yield to let untracked asyncio.gather tasks run
await asyncio.sleep(0)
await hass.async_block_till_done()
# Verify that the flow was initiated
flows = hass.config_entries.flow.async_progress()
assert len(flows) == 1
assert flows[0]["step_id"] == "reauth_confirm"