mirror of
https://github.com/home-assistant/core.git
synced 2025-04-24 17:27:52 +00:00
Add Homeassistant Analytics Insights integration (#107634)
* Add Homeassistant Analytics integration * Add Homeassistant Analytics integration * Add Homeassistant Analytics integration * Fix feedback * Fix test * Update conftest.py * Add some testcases * Make code clear * log exception * Bump python-homeassistant-analytics to 0.2.1 * Bump python-homeassistant-analytics to 0.3.0 * Change domain to homeassistant_analytics_consumer * Add integration name to config flow selector * Update homeassistant/components/homeassistant_analytics_consumer/manifest.json Co-authored-by: Sid <27780930+autinerd@users.noreply.github.com> * Fix hassfest * Apply suggestions from code review Co-authored-by: Robert Resch <robert@resch.dev> * Bump python-homeassistant-analytics to 0.4.0 * Rename to Home Assistant Analytics Insights * Update homeassistant/components/analytics_insights/config_flow.py Co-authored-by: Robert Resch <robert@resch.dev> * Update homeassistant/components/analytics_insights/manifest.json Co-authored-by: Robert Resch <robert@resch.dev> * Rename to Home Assistant Analytics Insights * add test * Fallback to 0 when there is no data found * Allow to select any integration * Fix tests * Fix tests * Update tests/components/analytics_insights/conftest.py Co-authored-by: Robert Resch <robert@resch.dev> * Update tests/components/analytics_insights/test_sensor.py Co-authored-by: Robert Resch <robert@resch.dev> * Fix format * Fix tests --------- Co-authored-by: Sid <27780930+autinerd@users.noreply.github.com> Co-authored-by: Robert Resch <robert@resch.dev>
This commit is contained in:
parent
52ede95c4f
commit
d9f1450ee6
@ -69,6 +69,7 @@ homeassistant.components.ambient_station.*
|
||||
homeassistant.components.amcrest.*
|
||||
homeassistant.components.ampio.*
|
||||
homeassistant.components.analytics.*
|
||||
homeassistant.components.analytics_insights.*
|
||||
homeassistant.components.android_ip_webcam.*
|
||||
homeassistant.components.androidtv.*
|
||||
homeassistant.components.androidtv_remote.*
|
||||
|
@ -76,6 +76,8 @@ build.json @home-assistant/supervisor
|
||||
/homeassistant/components/amcrest/ @flacjacket
|
||||
/homeassistant/components/analytics/ @home-assistant/core @ludeeus
|
||||
/tests/components/analytics/ @home-assistant/core @ludeeus
|
||||
/homeassistant/components/analytics_insights/ @joostlek
|
||||
/tests/components/analytics_insights/ @joostlek
|
||||
/homeassistant/components/android_ip_webcam/ @engrbm87
|
||||
/tests/components/android_ip_webcam/ @engrbm87
|
||||
/homeassistant/components/androidtv/ @JeffLIrion @ollo69
|
||||
|
58
homeassistant/components/analytics_insights/__init__.py
Normal file
58
homeassistant/components/analytics_insights/__init__.py
Normal file
@ -0,0 +1,58 @@
|
||||
"""The Homeassistant Analytics integration."""
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
from python_homeassistant_analytics import HomeassistantAnalyticsClient
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
|
||||
from .const import CONF_TRACKED_INTEGRATIONS, DOMAIN
|
||||
from .coordinator import HomeassistantAnalyticsDataUpdateCoordinator
|
||||
|
||||
PLATFORMS: list[Platform] = [Platform.SENSOR]
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class AnalyticsData:
|
||||
"""Analytics data class."""
|
||||
|
||||
coordinator: HomeassistantAnalyticsDataUpdateCoordinator
|
||||
names: dict[str, str]
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Set up Homeassistant Analytics from a config entry."""
|
||||
client = HomeassistantAnalyticsClient(session=async_get_clientsession(hass))
|
||||
|
||||
integrations = await client.get_integrations()
|
||||
|
||||
names = {}
|
||||
for integration in entry.options[CONF_TRACKED_INTEGRATIONS]:
|
||||
if integration not in integrations:
|
||||
names[integration] = integration
|
||||
continue
|
||||
names[integration] = integrations[integration].title
|
||||
|
||||
coordinator = HomeassistantAnalyticsDataUpdateCoordinator(hass, client)
|
||||
|
||||
await coordinator.async_config_entry_first_refresh()
|
||||
|
||||
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = AnalyticsData(
|
||||
coordinator=coordinator, names=names
|
||||
)
|
||||
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Unload a config entry."""
|
||||
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
|
||||
hass.data[DOMAIN].pop(entry.entry_id)
|
||||
|
||||
return unload_ok
|
74
homeassistant/components/analytics_insights/config_flow.py
Normal file
74
homeassistant/components/analytics_insights/config_flow.py
Normal file
@ -0,0 +1,74 @@
|
||||
"""Config flow for Homeassistant Analytics integration."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from python_homeassistant_analytics import (
|
||||
HomeassistantAnalyticsClient,
|
||||
HomeassistantAnalyticsConnectionError,
|
||||
)
|
||||
from python_homeassistant_analytics.models import IntegrationType
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.config_entries import ConfigFlow
|
||||
from homeassistant.data_entry_flow import FlowResult
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.selector import (
|
||||
SelectOptionDict,
|
||||
SelectSelector,
|
||||
SelectSelectorConfig,
|
||||
)
|
||||
|
||||
from .const import CONF_TRACKED_INTEGRATIONS, DOMAIN, LOGGER
|
||||
|
||||
INTEGRATION_TYPES_WITHOUT_ANALYTICS = (
|
||||
IntegrationType.BRAND,
|
||||
IntegrationType.ENTITY,
|
||||
IntegrationType.VIRTUAL,
|
||||
)
|
||||
|
||||
|
||||
class HomeassistantAnalyticsConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
"""Handle a config flow for Homeassistant Analytics."""
|
||||
|
||||
async def async_step_user(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> FlowResult:
|
||||
"""Handle the initial step."""
|
||||
self._async_abort_entries_match()
|
||||
if user_input:
|
||||
return self.async_create_entry(
|
||||
title="Home Assistant Analytics Insights", data={}, options=user_input
|
||||
)
|
||||
|
||||
client = HomeassistantAnalyticsClient(
|
||||
session=async_get_clientsession(self.hass)
|
||||
)
|
||||
try:
|
||||
integrations = await client.get_integrations()
|
||||
except HomeassistantAnalyticsConnectionError:
|
||||
LOGGER.exception("Error connecting to Home Assistant analytics")
|
||||
return self.async_abort(reason="cannot_connect")
|
||||
|
||||
options = [
|
||||
SelectOptionDict(
|
||||
value=domain,
|
||||
label=integration.title,
|
||||
)
|
||||
for domain, integration in integrations.items()
|
||||
if integration.integration_type not in INTEGRATION_TYPES_WITHOUT_ANALYTICS
|
||||
]
|
||||
return self.async_show_form(
|
||||
step_id="user",
|
||||
data_schema=vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_TRACKED_INTEGRATIONS): SelectSelector(
|
||||
SelectSelectorConfig(
|
||||
options=options,
|
||||
multiple=True,
|
||||
sort=True,
|
||||
)
|
||||
),
|
||||
}
|
||||
),
|
||||
)
|
8
homeassistant/components/analytics_insights/const.py
Normal file
8
homeassistant/components/analytics_insights/const.py
Normal file
@ -0,0 +1,8 @@
|
||||
"""Constants for the Homeassistant Analytics integration."""
|
||||
import logging
|
||||
|
||||
DOMAIN = "analytics_insights"
|
||||
|
||||
CONF_TRACKED_INTEGRATIONS = "tracked_integrations"
|
||||
|
||||
LOGGER = logging.getLogger(__package__)
|
53
homeassistant/components/analytics_insights/coordinator.py
Normal file
53
homeassistant/components/analytics_insights/coordinator.py
Normal file
@ -0,0 +1,53 @@
|
||||
"""DataUpdateCoordinator for the Homeassistant Analytics integration."""
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import timedelta
|
||||
|
||||
from python_homeassistant_analytics import (
|
||||
HomeassistantAnalyticsClient,
|
||||
HomeassistantAnalyticsConnectionError,
|
||||
HomeassistantAnalyticsNotModifiedError,
|
||||
)
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
|
||||
from .const import CONF_TRACKED_INTEGRATIONS, DOMAIN, LOGGER
|
||||
|
||||
|
||||
class HomeassistantAnalyticsDataUpdateCoordinator(
|
||||
DataUpdateCoordinator[dict[str, int]]
|
||||
):
|
||||
"""A Homeassistant Analytics Data Update Coordinator."""
|
||||
|
||||
config_entry: ConfigEntry
|
||||
|
||||
def __init__(
|
||||
self, hass: HomeAssistant, client: HomeassistantAnalyticsClient
|
||||
) -> None:
|
||||
"""Initialize the Homeassistant Analytics data coordinator."""
|
||||
super().__init__(
|
||||
hass,
|
||||
LOGGER,
|
||||
name=DOMAIN,
|
||||
update_interval=timedelta(hours=12),
|
||||
)
|
||||
self._client = client
|
||||
self._tracked_integrations = self.config_entry.options[
|
||||
CONF_TRACKED_INTEGRATIONS
|
||||
]
|
||||
|
||||
async def _async_update_data(self) -> dict[str, int]:
|
||||
try:
|
||||
data = await self._client.get_current_analytics()
|
||||
except HomeassistantAnalyticsConnectionError as err:
|
||||
raise UpdateFailed(
|
||||
"Error communicating with Homeassistant Analytics"
|
||||
) from err
|
||||
except HomeassistantAnalyticsNotModifiedError:
|
||||
return self.data
|
||||
return {
|
||||
integration: data.integrations.get(integration, 0)
|
||||
for integration in self._tracked_integrations
|
||||
}
|
11
homeassistant/components/analytics_insights/manifest.json
Normal file
11
homeassistant/components/analytics_insights/manifest.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"domain": "analytics_insights",
|
||||
"name": "Home Assistant Analytics Insights",
|
||||
"codeowners": ["@joostlek"],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/analytics_insights",
|
||||
"integration_type": "service",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["python_homeassistant_analytics"],
|
||||
"requirements": ["python-homeassistant-analytics==0.5.0"]
|
||||
}
|
62
homeassistant/components/analytics_insights/sensor.py
Normal file
62
homeassistant/components/analytics_insights/sensor.py
Normal file
@ -0,0 +1,62 @@
|
||||
"""Sensor for Home Assistant analytics."""
|
||||
from __future__ import annotations
|
||||
|
||||
from homeassistant.components.sensor import SensorEntity, SensorStateClass
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from . import AnalyticsData
|
||||
from .const import DOMAIN
|
||||
from .coordinator import HomeassistantAnalyticsDataUpdateCoordinator
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: ConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Initialize the entries."""
|
||||
|
||||
analytics_data: AnalyticsData = hass.data[DOMAIN][entry.entry_id]
|
||||
async_add_entities(
|
||||
HomeassistantAnalyticsSensor(
|
||||
analytics_data.coordinator,
|
||||
integration_domain,
|
||||
analytics_data.names[integration_domain],
|
||||
)
|
||||
for integration_domain in analytics_data.coordinator.data
|
||||
)
|
||||
|
||||
|
||||
class HomeassistantAnalyticsSensor(
|
||||
CoordinatorEntity[HomeassistantAnalyticsDataUpdateCoordinator], SensorEntity
|
||||
):
|
||||
"""Home Assistant Analytics Sensor."""
|
||||
|
||||
_attr_has_entity_name = True
|
||||
_attr_state_class = SensorStateClass.TOTAL
|
||||
_attr_native_unit_of_measurement = "active installations"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: HomeassistantAnalyticsDataUpdateCoordinator,
|
||||
integration_domain: str,
|
||||
name: str,
|
||||
) -> None:
|
||||
"""Initialize the sensor."""
|
||||
super().__init__(coordinator)
|
||||
self._attr_name = name
|
||||
self._attr_unique_id = f"core_{integration_domain}_active_installations"
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, DOMAIN)},
|
||||
entry_type=DeviceEntryType.SERVICE,
|
||||
)
|
||||
self._integration_domain = integration_domain
|
||||
|
||||
@property
|
||||
def native_value(self) -> int | None:
|
||||
"""Return the state of the sensor."""
|
||||
return self.coordinator.data.get(self._integration_domain)
|
18
homeassistant/components/analytics_insights/strings.json
Normal file
18
homeassistant/components/analytics_insights/strings.json
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"tracked_integrations": "Integrations"
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||
"unknown": "[%key:common::config_flow::error::unknown%]"
|
||||
},
|
||||
"abort": {
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_service%]"
|
||||
}
|
||||
}
|
||||
}
|
@ -42,6 +42,7 @@ FLOWS = {
|
||||
"amberelectric",
|
||||
"ambiclimate",
|
||||
"ambient_station",
|
||||
"analytics_insights",
|
||||
"android_ip_webcam",
|
||||
"androidtv",
|
||||
"androidtv_remote",
|
||||
|
@ -255,6 +255,12 @@
|
||||
"config_flow": false,
|
||||
"iot_class": "cloud_polling"
|
||||
},
|
||||
"analytics_insights": {
|
||||
"name": "Home Assistant Analytics Insights",
|
||||
"integration_type": "service",
|
||||
"config_flow": true,
|
||||
"iot_class": "cloud_polling"
|
||||
},
|
||||
"android_ip_webcam": {
|
||||
"name": "Android IP Webcam",
|
||||
"integration_type": "hub",
|
||||
|
10
mypy.ini
10
mypy.ini
@ -450,6 +450,16 @@ disallow_untyped_defs = true
|
||||
warn_return_any = true
|
||||
warn_unreachable = true
|
||||
|
||||
[mypy-homeassistant.components.analytics_insights.*]
|
||||
check_untyped_defs = true
|
||||
disallow_incomplete_defs = true
|
||||
disallow_subclassing_any = true
|
||||
disallow_untyped_calls = true
|
||||
disallow_untyped_decorators = true
|
||||
disallow_untyped_defs = true
|
||||
warn_return_any = true
|
||||
warn_unreachable = true
|
||||
|
||||
[mypy-homeassistant.components.android_ip_webcam.*]
|
||||
check_untyped_defs = true
|
||||
disallow_incomplete_defs = true
|
||||
|
@ -2197,6 +2197,9 @@ python-gc100==1.0.3a0
|
||||
# homeassistant.components.gitlab_ci
|
||||
python-gitlab==1.6.0
|
||||
|
||||
# homeassistant.components.analytics_insights
|
||||
python-homeassistant-analytics==0.5.0
|
||||
|
||||
# homeassistant.components.homewizard
|
||||
python-homewizard-energy==4.1.0
|
||||
|
||||
|
@ -1673,6 +1673,9 @@ python-ecobee-api==0.2.17
|
||||
# homeassistant.components.fully_kiosk
|
||||
python-fullykiosk==0.0.12
|
||||
|
||||
# homeassistant.components.analytics_insights
|
||||
python-homeassistant-analytics==0.5.0
|
||||
|
||||
# homeassistant.components.homewizard
|
||||
python-homewizard-energy==4.1.0
|
||||
|
||||
|
11
tests/components/analytics_insights/__init__.py
Normal file
11
tests/components/analytics_insights/__init__.py
Normal file
@ -0,0 +1,11 @@
|
||||
"""Tests for the Homeassistant Analytics integration."""
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
async def setup_integration(hass: HomeAssistant, config_entry: MockConfigEntry) -> None:
|
||||
"""Fixture for setting up the component."""
|
||||
config_entry.add_to_hass(hass)
|
||||
|
||||
await hass.config_entries.async_setup(config_entry.entry_id)
|
54
tests/components/analytics_insights/conftest.py
Normal file
54
tests/components/analytics_insights/conftest.py
Normal file
@ -0,0 +1,54 @@
|
||||
"""Common fixtures for the Homeassistant Analytics tests."""
|
||||
from collections.abc import Generator
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
import pytest
|
||||
from python_homeassistant_analytics import CurrentAnalytics
|
||||
from python_homeassistant_analytics.models import Integration
|
||||
|
||||
from homeassistant.components.analytics_insights import DOMAIN
|
||||
from homeassistant.components.analytics_insights.const import CONF_TRACKED_INTEGRATIONS
|
||||
|
||||
from tests.common import MockConfigEntry, load_fixture, load_json_object_fixture
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_setup_entry() -> Generator[AsyncMock, None, None]:
|
||||
"""Override async_setup_entry."""
|
||||
with patch(
|
||||
"homeassistant.components.analytics_insights.async_setup_entry",
|
||||
return_value=True,
|
||||
) as mock_setup_entry:
|
||||
yield mock_setup_entry
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_analytics_client() -> Generator[AsyncMock, None, None]:
|
||||
"""Mock a Homeassistant Analytics client."""
|
||||
with patch(
|
||||
"homeassistant.components.analytics_insights.HomeassistantAnalyticsClient",
|
||||
autospec=True,
|
||||
) as mock_client, patch(
|
||||
"homeassistant.components.analytics_insights.config_flow.HomeassistantAnalyticsClient",
|
||||
new=mock_client,
|
||||
):
|
||||
client = mock_client.return_value
|
||||
client.get_current_analytics.return_value = CurrentAnalytics.from_json(
|
||||
load_fixture("analytics_insights/current_data.json")
|
||||
)
|
||||
integrations = load_json_object_fixture("analytics_insights/integrations.json")
|
||||
client.get_integrations.return_value = {
|
||||
key: Integration.from_dict(value) for key, value in integrations.items()
|
||||
}
|
||||
yield client
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_config_entry() -> MockConfigEntry:
|
||||
"""Mock a config entry."""
|
||||
return MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
title="Homeassistant Analytics",
|
||||
data={},
|
||||
options={CONF_TRACKED_INTEGRATIONS: ["youtube", "spotify", "myq"]},
|
||||
)
|
2516
tests/components/analytics_insights/fixtures/current_data.json
Normal file
2516
tests/components/analytics_insights/fixtures/current_data.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,9 @@
|
||||
{
|
||||
"youtube": {
|
||||
"title": "YouTube",
|
||||
"description": "Instructions on how to integrate YouTube within Home Assistant.",
|
||||
"quality_scale": "",
|
||||
"iot_class": "Cloud Polling",
|
||||
"integration_type": "service"
|
||||
}
|
||||
}
|
142
tests/components/analytics_insights/snapshots/test_sensor.ambr
Normal file
142
tests/components/analytics_insights/snapshots/test_sensor.ambr
Normal file
@ -0,0 +1,142 @@
|
||||
# serializer version: 1
|
||||
# name: test_all_entities[sensor.homeassistant_analytics_myq-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.TOTAL: 'total'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'sensor',
|
||||
'entity_category': None,
|
||||
'entity_id': 'sensor.homeassistant_analytics_myq',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'myq',
|
||||
'platform': 'analytics_insights',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': 'core_myq_active_installations',
|
||||
'unit_of_measurement': 'active installations',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[sensor.homeassistant_analytics_myq-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Homeassistant Analytics myq',
|
||||
'state_class': <SensorStateClass.TOTAL: 'total'>,
|
||||
'unit_of_measurement': 'active installations',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.homeassistant_analytics_myq',
|
||||
'last_changed': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '0',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[sensor.homeassistant_analytics_spotify-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.TOTAL: 'total'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'sensor',
|
||||
'entity_category': None,
|
||||
'entity_id': 'sensor.homeassistant_analytics_spotify',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'spotify',
|
||||
'platform': 'analytics_insights',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': 'core_spotify_active_installations',
|
||||
'unit_of_measurement': 'active installations',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[sensor.homeassistant_analytics_spotify-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Homeassistant Analytics spotify',
|
||||
'state_class': <SensorStateClass.TOTAL: 'total'>,
|
||||
'unit_of_measurement': 'active installations',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.homeassistant_analytics_spotify',
|
||||
'last_changed': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '24388',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[sensor.homeassistant_analytics_youtube-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.TOTAL: 'total'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'sensor',
|
||||
'entity_category': None,
|
||||
'entity_id': 'sensor.homeassistant_analytics_youtube',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'YouTube',
|
||||
'platform': 'analytics_insights',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': 'core_youtube_active_installations',
|
||||
'unit_of_measurement': 'active installations',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[sensor.homeassistant_analytics_youtube-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Homeassistant Analytics YouTube',
|
||||
'state_class': <SensorStateClass.TOTAL: 'total'>,
|
||||
'unit_of_measurement': 'active installations',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.homeassistant_analytics_youtube',
|
||||
'last_changed': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '339',
|
||||
})
|
||||
# ---
|
70
tests/components/analytics_insights/test_config_flow.py
Normal file
70
tests/components/analytics_insights/test_config_flow.py
Normal file
@ -0,0 +1,70 @@
|
||||
"""Test the Homeassistant Analytics config flow."""
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
from python_homeassistant_analytics import HomeassistantAnalyticsConnectionError
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components.analytics_insights.const import (
|
||||
CONF_TRACKED_INTEGRATIONS,
|
||||
DOMAIN,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.data_entry_flow import FlowResultType
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
async def test_form(
|
||||
hass: HomeAssistant, mock_setup_entry: AsyncMock, mock_analytics_client: AsyncMock
|
||||
) -> None:
|
||||
"""Test we get the form."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
assert result["type"] == FlowResultType.FORM
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{CONF_TRACKED_INTEGRATIONS: ["youtube"]},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result["type"] == FlowResultType.CREATE_ENTRY
|
||||
assert result["title"] == "Home Assistant Analytics Insights"
|
||||
assert result["data"] == {}
|
||||
assert result["options"] == {CONF_TRACKED_INTEGRATIONS: ["youtube"]}
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
|
||||
async def test_form_cannot_connect(
|
||||
hass: HomeAssistant, mock_analytics_client: AsyncMock
|
||||
) -> None:
|
||||
"""Test we handle cannot connect error."""
|
||||
|
||||
mock_analytics_client.get_integrations.side_effect = (
|
||||
HomeassistantAnalyticsConnectionError
|
||||
)
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
assert result["type"] == FlowResultType.ABORT
|
||||
assert result["reason"] == "cannot_connect"
|
||||
|
||||
|
||||
async def test_form_already_configured(
|
||||
hass: HomeAssistant, mock_setup_entry: AsyncMock
|
||||
) -> None:
|
||||
"""Test we handle cannot connect error."""
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
data={},
|
||||
options={CONF_TRACKED_INTEGRATIONS: ["youtube", "spotify"]},
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
assert result["type"] == FlowResultType.ABORT
|
||||
assert result["reason"] == "already_configured"
|
28
tests/components/analytics_insights/test_init.py
Normal file
28
tests/components/analytics_insights/test_init.py
Normal file
@ -0,0 +1,28 @@
|
||||
"""Test the Home Assistant analytics init module."""
|
||||
from __future__ import annotations
|
||||
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
from homeassistant.components.analytics_insights.const import DOMAIN
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
from tests.components.analytics_insights import setup_integration
|
||||
|
||||
|
||||
async def test_load_unload_entry(
|
||||
hass: HomeAssistant,
|
||||
mock_analytics_client: AsyncMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test load and unload entry."""
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
entry = hass.config_entries.async_entries(DOMAIN)[0]
|
||||
|
||||
assert entry.state == ConfigEntryState.LOADED
|
||||
|
||||
await hass.config_entries.async_remove(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert entry.state == ConfigEntryState.NOT_LOADED
|
86
tests/components/analytics_insights/test_sensor.py
Normal file
86
tests/components/analytics_insights/test_sensor.py
Normal file
@ -0,0 +1,86 @@
|
||||
"""Test the Home Assistant analytics sensor module."""
|
||||
from datetime import timedelta
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
from freezegun.api import FrozenDateTimeFactory
|
||||
from python_homeassistant_analytics import (
|
||||
HomeassistantAnalyticsConnectionError,
|
||||
HomeassistantAnalyticsNotModifiedError,
|
||||
)
|
||||
from syrupy import SnapshotAssertion
|
||||
|
||||
from homeassistant.const import STATE_UNAVAILABLE, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
|
||||
from . import setup_integration
|
||||
|
||||
from tests.common import MockConfigEntry, async_fire_time_changed
|
||||
|
||||
|
||||
async def test_all_entities(
|
||||
hass: HomeAssistant,
|
||||
snapshot: SnapshotAssertion,
|
||||
mock_analytics_client: AsyncMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
entity_registry: er.EntityRegistry,
|
||||
) -> None:
|
||||
"""Test all entities."""
|
||||
with patch(
|
||||
"homeassistant.components.analytics_insights.PLATFORMS",
|
||||
[Platform.SENSOR],
|
||||
):
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
entity_entries = er.async_entries_for_config_entry(
|
||||
entity_registry, mock_config_entry.entry_id
|
||||
)
|
||||
|
||||
assert entity_entries
|
||||
for entity_entry in entity_entries:
|
||||
assert hass.states.get(entity_entry.entity_id) == snapshot(
|
||||
name=f"{entity_entry.entity_id}-state"
|
||||
)
|
||||
assert entity_entry == snapshot(name=f"{entity_entry.entity_id}-entry")
|
||||
|
||||
|
||||
async def test_connection_error(
|
||||
hass: HomeAssistant,
|
||||
mock_analytics_client: AsyncMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
) -> None:
|
||||
"""Test connection error."""
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
mock_analytics_client.get_current_analytics.side_effect = (
|
||||
HomeassistantAnalyticsConnectionError()
|
||||
)
|
||||
freezer.tick(delta=timedelta(hours=12))
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert (
|
||||
hass.states.get("sensor.homeassistant_analytics_spotify").state
|
||||
== STATE_UNAVAILABLE
|
||||
)
|
||||
|
||||
|
||||
async def test_data_not_modified(
|
||||
hass: HomeAssistant,
|
||||
mock_analytics_client: AsyncMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
) -> None:
|
||||
"""Test not updating data if its not modified."""
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
assert hass.states.get("sensor.homeassistant_analytics_spotify").state == "24388"
|
||||
mock_analytics_client.get_current_analytics.side_effect = (
|
||||
HomeassistantAnalyticsNotModifiedError
|
||||
)
|
||||
freezer.tick(delta=timedelta(hours=12))
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
mock_analytics_client.get_current_analytics.assert_called()
|
||||
assert hass.states.get("sensor.homeassistant_analytics_spotify").state == "24388"
|
Loading…
x
Reference in New Issue
Block a user