Generate a seperate UUID for the analytics integration (#48742)

pull/48753/head
Joakim Sørensen 2021-04-07 02:34:49 +02:00 committed by GitHub
parent 82cc5148d7
commit 89f2f458d2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 60 additions and 42 deletions

View File

@ -7,7 +7,7 @@ from homeassistant.core import HomeAssistant
from homeassistant.helpers.event import async_call_later, async_track_time_interval from homeassistant.helpers.event import async_call_later, async_track_time_interval
from .analytics import Analytics from .analytics import Analytics
from .const import ATTR_HUUID, ATTR_PREFERENCES, DOMAIN, INTERVAL, PREFERENCE_SCHEMA from .const import ATTR_PREFERENCES, DOMAIN, INTERVAL, PREFERENCE_SCHEMA
async def async_setup(hass: HomeAssistant, _): async def async_setup(hass: HomeAssistant, _):
@ -44,10 +44,9 @@ async def websocket_analytics(
) -> None: ) -> None:
"""Return analytics preferences.""" """Return analytics preferences."""
analytics: Analytics = hass.data[DOMAIN] analytics: Analytics = hass.data[DOMAIN]
huuid = await hass.helpers.instance_id.async_get()
connection.send_result( connection.send_result(
msg["id"], msg["id"],
{ATTR_PREFERENCES: analytics.preferences, ATTR_HUUID: huuid}, {ATTR_PREFERENCES: analytics.preferences},
) )

View File

@ -1,5 +1,6 @@
"""Analytics helper class for the analytics integration.""" """Analytics helper class for the analytics integration."""
import asyncio import asyncio
import uuid
import aiohttp import aiohttp
import async_timeout import async_timeout
@ -24,7 +25,6 @@ from .const import (
ATTR_BASE, ATTR_BASE,
ATTR_DIAGNOSTICS, ATTR_DIAGNOSTICS,
ATTR_HEALTHY, ATTR_HEALTHY,
ATTR_HUUID,
ATTR_INTEGRATION_COUNT, ATTR_INTEGRATION_COUNT,
ATTR_INTEGRATIONS, ATTR_INTEGRATIONS,
ATTR_ONBOARDED, ATTR_ONBOARDED,
@ -37,6 +37,7 @@ from .const import (
ATTR_SUPPORTED, ATTR_SUPPORTED,
ATTR_USAGE, ATTR_USAGE,
ATTR_USER_COUNT, ATTR_USER_COUNT,
ATTR_UUID,
ATTR_VERSION, ATTR_VERSION,
LOGGER, LOGGER,
PREFERENCE_SCHEMA, PREFERENCE_SCHEMA,
@ -52,7 +53,7 @@ class Analytics:
"""Initialize the Analytics class.""" """Initialize the Analytics class."""
self.hass: HomeAssistant = hass self.hass: HomeAssistant = hass
self.session = async_get_clientsession(hass) self.session = async_get_clientsession(hass)
self._data = {ATTR_PREFERENCES: {}, ATTR_ONBOARDED: False} self._data = {ATTR_PREFERENCES: {}, ATTR_ONBOARDED: False, ATTR_UUID: None}
self._store: Store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY) self._store: Store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY)
@property @property
@ -71,6 +72,11 @@ class Analytics:
"""Return bool if the user has made a choice.""" """Return bool if the user has made a choice."""
return self._data[ATTR_ONBOARDED] return self._data[ATTR_ONBOARDED]
@property
def uuid(self) -> bool:
"""Return the uuid for the analytics integration."""
return self._data[ATTR_UUID]
@property @property
def supervisor(self) -> bool: def supervisor(self) -> bool:
"""Return bool if a supervisor is present.""" """Return bool if a supervisor is present."""
@ -81,6 +87,7 @@ class Analytics:
stored = await self._store.async_load() stored = await self._store.async_load()
if stored: if stored:
self._data = stored self._data = stored
if self.supervisor: if self.supervisor:
supervisor_info = hassio.get_supervisor_info(self.hass) supervisor_info = hassio.get_supervisor_info(self.hass)
if not self.onboarded: if not self.onboarded:
@ -99,6 +106,7 @@ class Analytics:
preferences = PREFERENCE_SCHEMA(preferences) preferences = PREFERENCE_SCHEMA(preferences)
self._data[ATTR_PREFERENCES].update(preferences) self._data[ATTR_PREFERENCES].update(preferences)
self._data[ATTR_ONBOARDED] = True self._data[ATTR_ONBOARDED] = True
await self._store.async_save(self._data) await self._store.async_save(self._data)
if self.supervisor: if self.supervisor:
@ -114,7 +122,9 @@ class Analytics:
LOGGER.debug("Nothing to submit") LOGGER.debug("Nothing to submit")
return return
huuid = await self.hass.helpers.instance_id.async_get() if self._data.get(ATTR_UUID) is None:
self._data[ATTR_UUID] = uuid.uuid4().hex
await self._store.async_save(self._data)
if self.supervisor: if self.supervisor:
supervisor_info = hassio.get_supervisor_info(self.hass) supervisor_info = hassio.get_supervisor_info(self.hass)
@ -123,7 +133,7 @@ class Analytics:
integrations = [] integrations = []
addons = [] addons = []
payload: dict = { payload: dict = {
ATTR_HUUID: huuid, ATTR_UUID: self.uuid,
ATTR_VERSION: HA_VERSION, ATTR_VERSION: HA_VERSION,
ATTR_INSTALLATION_TYPE: system_info[ATTR_INSTALLATION_TYPE], ATTR_INSTALLATION_TYPE: system_info[ATTR_INSTALLATION_TYPE],
} }

View File

@ -20,7 +20,6 @@ ATTR_AUTOMATION_COUNT = "automation_count"
ATTR_BASE = "base" ATTR_BASE = "base"
ATTR_DIAGNOSTICS = "diagnostics" ATTR_DIAGNOSTICS = "diagnostics"
ATTR_HEALTHY = "healthy" ATTR_HEALTHY = "healthy"
ATTR_HUUID = "huuid"
ATTR_INSTALLATION_TYPE = "installation_type" ATTR_INSTALLATION_TYPE = "installation_type"
ATTR_INTEGRATION_COUNT = "integration_count" ATTR_INTEGRATION_COUNT = "integration_count"
ATTR_INTEGRATIONS = "integrations" ATTR_INTEGRATIONS = "integrations"
@ -34,6 +33,7 @@ ATTR_SUPERVISOR = "supervisor"
ATTR_SUPPORTED = "supported" ATTR_SUPPORTED = "supported"
ATTR_USAGE = "usage" ATTR_USAGE = "usage"
ATTR_USER_COUNT = "user_count" ATTR_USER_COUNT = "user_count"
ATTR_UUID = "uuid"
ATTR_VERSION = "version" ATTR_VERSION = "version"

View File

@ -1,5 +1,5 @@
"""The tests for the analytics .""" """The tests for the analytics ."""
from unittest.mock import AsyncMock, Mock, patch from unittest.mock import AsyncMock, Mock, PropertyMock, patch
import aiohttp import aiohttp
import pytest import pytest
@ -13,10 +13,11 @@ from homeassistant.components.analytics.const import (
ATTR_STATISTICS, ATTR_STATISTICS,
ATTR_USAGE, ATTR_USAGE,
) )
from homeassistant.components.api import ATTR_UUID
from homeassistant.const import __version__ as HA_VERSION from homeassistant.const import __version__ as HA_VERSION
from homeassistant.loader import IntegrationNotFound from homeassistant.loader import IntegrationNotFound
MOCK_HUUID = "abcdefg" MOCK_UUID = "abcdefg"
async def test_no_send(hass, caplog, aioclient_mock): async def test_no_send(hass, caplog, aioclient_mock):
@ -26,8 +27,7 @@ async def test_no_send(hass, caplog, aioclient_mock):
with patch( with patch(
"homeassistant.components.hassio.is_hassio", "homeassistant.components.hassio.is_hassio",
side_effect=Mock(return_value=False), side_effect=Mock(return_value=False),
), patch("homeassistant.helpers.instance_id.async_get", return_value=MOCK_HUUID): ):
await analytics.load()
assert not analytics.preferences[ATTR_BASE] assert not analytics.preferences[ATTR_BASE]
await analytics.send_analytics() await analytics.send_analytics()
@ -76,8 +76,6 @@ async def test_failed_to_send(hass, caplog, aioclient_mock):
analytics = Analytics(hass) analytics = Analytics(hass)
await analytics.save_preferences({ATTR_BASE: True}) await analytics.save_preferences({ATTR_BASE: True})
assert analytics.preferences[ATTR_BASE] assert analytics.preferences[ATTR_BASE]
with patch("homeassistant.helpers.instance_id.async_get", return_value=MOCK_HUUID):
await analytics.send_analytics() await analytics.send_analytics()
assert "Sending analytics failed with statuscode 400" in caplog.text assert "Sending analytics failed with statuscode 400" in caplog.text
@ -88,8 +86,6 @@ async def test_failed_to_send_raises(hass, caplog, aioclient_mock):
analytics = Analytics(hass) analytics = Analytics(hass)
await analytics.save_preferences({ATTR_BASE: True}) await analytics.save_preferences({ATTR_BASE: True})
assert analytics.preferences[ATTR_BASE] assert analytics.preferences[ATTR_BASE]
with patch("homeassistant.helpers.instance_id.async_get", return_value=MOCK_HUUID):
await analytics.send_analytics() await analytics.send_analytics()
assert "Error sending analytics" in caplog.text assert "Error sending analytics" in caplog.text
@ -98,12 +94,15 @@ async def test_send_base(hass, caplog, aioclient_mock):
"""Test send base prefrences are defined.""" """Test send base prefrences are defined."""
aioclient_mock.post(ANALYTICS_ENDPOINT_URL, status=200) aioclient_mock.post(ANALYTICS_ENDPOINT_URL, status=200)
analytics = Analytics(hass) analytics = Analytics(hass)
await analytics.save_preferences({ATTR_BASE: True}) await analytics.save_preferences({ATTR_BASE: True})
assert analytics.preferences[ATTR_BASE] assert analytics.preferences[ATTR_BASE]
with patch("homeassistant.helpers.instance_id.async_get", return_value=MOCK_HUUID): with patch("uuid.UUID.hex", new_callable=PropertyMock) as hex:
hex.return_value = MOCK_UUID
await analytics.send_analytics() await analytics.send_analytics()
assert f"'huuid': '{MOCK_HUUID}'" in caplog.text
assert f"'uuid': '{MOCK_UUID}'" in caplog.text
assert f"'version': '{HA_VERSION}'" in caplog.text assert f"'version': '{HA_VERSION}'" in caplog.text
assert "'installation_type':" in caplog.text assert "'installation_type':" in caplog.text
assert "'integration_count':" not in caplog.text assert "'integration_count':" not in caplog.text
@ -131,10 +130,14 @@ async def test_send_base_with_supervisor(hass, caplog, aioclient_mock):
"homeassistant.components.hassio.is_hassio", "homeassistant.components.hassio.is_hassio",
side_effect=Mock(return_value=True), side_effect=Mock(return_value=True),
), patch( ), patch(
"homeassistant.helpers.instance_id.async_get", return_value=MOCK_HUUID "uuid.UUID.hex", new_callable=PropertyMock
): ) as hex:
hex.return_value = MOCK_UUID
await analytics.load()
await analytics.send_analytics() await analytics.send_analytics()
assert f"'huuid': '{MOCK_HUUID}'" in caplog.text
assert f"'uuid': '{MOCK_UUID}'" in caplog.text
assert f"'version': '{HA_VERSION}'" in caplog.text assert f"'version': '{HA_VERSION}'" in caplog.text
assert "'supervisor': {'healthy': True, 'supported': True}}" in caplog.text assert "'supervisor': {'healthy': True, 'supported': True}}" in caplog.text
assert "'installation_type':" in caplog.text assert "'installation_type':" in caplog.text
@ -147,12 +150,13 @@ async def test_send_usage(hass, caplog, aioclient_mock):
aioclient_mock.post(ANALYTICS_ENDPOINT_URL, status=200) aioclient_mock.post(ANALYTICS_ENDPOINT_URL, status=200)
analytics = Analytics(hass) analytics = Analytics(hass)
await analytics.save_preferences({ATTR_BASE: True, ATTR_USAGE: True}) await analytics.save_preferences({ATTR_BASE: True, ATTR_USAGE: True})
assert analytics.preferences[ATTR_BASE] assert analytics.preferences[ATTR_BASE]
assert analytics.preferences[ATTR_USAGE] assert analytics.preferences[ATTR_USAGE]
hass.config.components = ["default_config"] hass.config.components = ["default_config"]
with patch("homeassistant.helpers.instance_id.async_get", return_value=MOCK_HUUID):
await analytics.send_analytics() await analytics.send_analytics()
assert "'integrations': ['default_config']" in caplog.text assert "'integrations': ['default_config']" in caplog.text
assert "'integration_count':" not in caplog.text assert "'integration_count':" not in caplog.text
@ -195,8 +199,6 @@ async def test_send_usage_with_supervisor(hass, caplog, aioclient_mock):
), patch( ), patch(
"homeassistant.components.hassio.is_hassio", "homeassistant.components.hassio.is_hassio",
side_effect=Mock(return_value=True), side_effect=Mock(return_value=True),
), patch(
"homeassistant.helpers.instance_id.async_get", return_value=MOCK_HUUID
): ):
await analytics.send_analytics() await analytics.send_analytics()
assert ( assert (
@ -215,7 +217,6 @@ async def test_send_statistics(hass, caplog, aioclient_mock):
assert analytics.preferences[ATTR_STATISTICS] assert analytics.preferences[ATTR_STATISTICS]
hass.config.components = ["default_config"] hass.config.components = ["default_config"]
with patch("homeassistant.helpers.instance_id.async_get", return_value=MOCK_HUUID):
await analytics.send_analytics() await analytics.send_analytics()
assert ( assert (
"'state_count': 0, 'automation_count': 0, 'integration_count': 1, 'user_count': 0" "'state_count': 0, 'automation_count': 0, 'integration_count': 1, 'user_count': 0"
@ -236,11 +237,11 @@ async def test_send_statistics_one_integration_fails(hass, caplog, aioclient_moc
with patch( with patch(
"homeassistant.components.analytics.analytics.async_get_integration", "homeassistant.components.analytics.analytics.async_get_integration",
side_effect=IntegrationNotFound("any"), side_effect=IntegrationNotFound("any"),
), patch("homeassistant.helpers.instance_id.async_get", return_value=MOCK_HUUID): ):
await analytics.send_analytics() await analytics.send_analytics()
post_call = aioclient_mock.mock_calls[0] post_call = aioclient_mock.mock_calls[0]
assert "huuid" in post_call[2] assert "uuid" in post_call[2]
assert post_call[2]["integration_count"] == 0 assert post_call[2]["integration_count"] == 0
@ -258,7 +259,7 @@ async def test_send_statistics_async_get_integration_unknown_exception(
with pytest.raises(ValueError), patch( with pytest.raises(ValueError), patch(
"homeassistant.components.analytics.analytics.async_get_integration", "homeassistant.components.analytics.analytics.async_get_integration",
side_effect=ValueError, side_effect=ValueError,
), patch("homeassistant.helpers.instance_id.async_get", return_value=MOCK_HUUID): ):
await analytics.send_analytics() await analytics.send_analytics()
@ -298,9 +299,23 @@ async def test_send_statistics_with_supervisor(hass, caplog, aioclient_mock):
), patch( ), patch(
"homeassistant.components.hassio.is_hassio", "homeassistant.components.hassio.is_hassio",
side_effect=Mock(return_value=True), side_effect=Mock(return_value=True),
), patch(
"homeassistant.helpers.instance_id.async_get", return_value=MOCK_HUUID
): ):
await analytics.send_analytics() await analytics.send_analytics()
assert "'addon_count': 1" in caplog.text assert "'addon_count': 1" in caplog.text
assert "'integrations':" not in caplog.text assert "'integrations':" not in caplog.text
async def test_reusing_uuid(hass, aioclient_mock):
"""Test reusing the stored UUID."""
aioclient_mock.post(ANALYTICS_ENDPOINT_URL, status=200)
analytics = Analytics(hass)
analytics._data[ATTR_UUID] = "NOT_MOCK_UUID"
await analytics.save_preferences({ATTR_BASE: True})
with patch("uuid.UUID.hex", new_callable=PropertyMock) as hex:
# This is not actually called but that in itself prove the test
hex.return_value = MOCK_UUID
await analytics.send_analytics()
assert analytics.uuid == "NOT_MOCK_UUID"

View File

@ -1,6 +1,4 @@
"""The tests for the analytics .""" """The tests for the analytics ."""
from unittest.mock import patch
from homeassistant.components.analytics.const import ANALYTICS_ENDPOINT_URL, DOMAIN from homeassistant.components.analytics.const import ANALYTICS_ENDPOINT_URL, DOMAIN
from homeassistant.setup import async_setup_component from homeassistant.setup import async_setup_component
@ -22,11 +20,9 @@ async def test_websocket(hass, hass_ws_client, aioclient_mock):
ws_client = await hass_ws_client(hass) ws_client = await hass_ws_client(hass)
await ws_client.send_json({"id": 1, "type": "analytics"}) await ws_client.send_json({"id": 1, "type": "analytics"})
with patch("homeassistant.helpers.instance_id.async_get", return_value="abcdef"):
response = await ws_client.receive_json() response = await ws_client.receive_json()
assert response["success"] assert response["success"]
assert response["result"]["huuid"] == "abcdef"
await ws_client.send_json( await ws_client.send_json(
{"id": 2, "type": "analytics/preferences", "preferences": {"base": True}} {"id": 2, "type": "analytics/preferences", "preferences": {"base": True}}
@ -36,7 +32,5 @@ async def test_websocket(hass, hass_ws_client, aioclient_mock):
assert response["result"]["preferences"]["base"] assert response["result"]["preferences"]["base"]
await ws_client.send_json({"id": 3, "type": "analytics"}) await ws_client.send_json({"id": 3, "type": "analytics"})
with patch("homeassistant.helpers.instance_id.async_get", return_value="abcdef"):
response = await ws_client.receive_json() response = await ws_client.receive_json()
assert response["result"]["preferences"]["base"] assert response["result"]["preferences"]["base"]
assert response["result"]["huuid"] == "abcdef"