diff --git a/homeassistant/components/analytics/__init__.py b/homeassistant/components/analytics/__init__.py index 3a06c56add5..c1187af7f17 100644 --- a/homeassistant/components/analytics/__init__.py +++ b/homeassistant/components/analytics/__init__.py @@ -7,7 +7,7 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers.event import async_call_later, async_track_time_interval 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, _): @@ -44,10 +44,9 @@ async def websocket_analytics( ) -> None: """Return analytics preferences.""" analytics: Analytics = hass.data[DOMAIN] - huuid = await hass.helpers.instance_id.async_get() connection.send_result( msg["id"], - {ATTR_PREFERENCES: analytics.preferences, ATTR_HUUID: huuid}, + {ATTR_PREFERENCES: analytics.preferences}, ) diff --git a/homeassistant/components/analytics/analytics.py b/homeassistant/components/analytics/analytics.py index ef7c2fbde6e..d7764a052c8 100644 --- a/homeassistant/components/analytics/analytics.py +++ b/homeassistant/components/analytics/analytics.py @@ -1,5 +1,6 @@ """Analytics helper class for the analytics integration.""" import asyncio +import uuid import aiohttp import async_timeout @@ -24,7 +25,6 @@ from .const import ( ATTR_BASE, ATTR_DIAGNOSTICS, ATTR_HEALTHY, - ATTR_HUUID, ATTR_INTEGRATION_COUNT, ATTR_INTEGRATIONS, ATTR_ONBOARDED, @@ -37,6 +37,7 @@ from .const import ( ATTR_SUPPORTED, ATTR_USAGE, ATTR_USER_COUNT, + ATTR_UUID, ATTR_VERSION, LOGGER, PREFERENCE_SCHEMA, @@ -52,7 +53,7 @@ class Analytics: """Initialize the Analytics class.""" self.hass: HomeAssistant = 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) @property @@ -71,6 +72,11 @@ class Analytics: """Return bool if the user has made a choice.""" return self._data[ATTR_ONBOARDED] + @property + def uuid(self) -> bool: + """Return the uuid for the analytics integration.""" + return self._data[ATTR_UUID] + @property def supervisor(self) -> bool: """Return bool if a supervisor is present.""" @@ -81,6 +87,7 @@ class Analytics: stored = await self._store.async_load() if stored: self._data = stored + if self.supervisor: supervisor_info = hassio.get_supervisor_info(self.hass) if not self.onboarded: @@ -99,6 +106,7 @@ class Analytics: preferences = PREFERENCE_SCHEMA(preferences) self._data[ATTR_PREFERENCES].update(preferences) self._data[ATTR_ONBOARDED] = True + await self._store.async_save(self._data) if self.supervisor: @@ -114,7 +122,9 @@ class Analytics: LOGGER.debug("Nothing to submit") 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: supervisor_info = hassio.get_supervisor_info(self.hass) @@ -123,7 +133,7 @@ class Analytics: integrations = [] addons = [] payload: dict = { - ATTR_HUUID: huuid, + ATTR_UUID: self.uuid, ATTR_VERSION: HA_VERSION, ATTR_INSTALLATION_TYPE: system_info[ATTR_INSTALLATION_TYPE], } diff --git a/homeassistant/components/analytics/const.py b/homeassistant/components/analytics/const.py index ba56ba265a7..998dac9cf80 100644 --- a/homeassistant/components/analytics/const.py +++ b/homeassistant/components/analytics/const.py @@ -20,7 +20,6 @@ ATTR_AUTOMATION_COUNT = "automation_count" ATTR_BASE = "base" ATTR_DIAGNOSTICS = "diagnostics" ATTR_HEALTHY = "healthy" -ATTR_HUUID = "huuid" ATTR_INSTALLATION_TYPE = "installation_type" ATTR_INTEGRATION_COUNT = "integration_count" ATTR_INTEGRATIONS = "integrations" @@ -34,6 +33,7 @@ ATTR_SUPERVISOR = "supervisor" ATTR_SUPPORTED = "supported" ATTR_USAGE = "usage" ATTR_USER_COUNT = "user_count" +ATTR_UUID = "uuid" ATTR_VERSION = "version" diff --git a/tests/components/analytics/test_analytics.py b/tests/components/analytics/test_analytics.py index 1a636d16598..f7f55c510c1 100644 --- a/tests/components/analytics/test_analytics.py +++ b/tests/components/analytics/test_analytics.py @@ -1,5 +1,5 @@ """The tests for the analytics .""" -from unittest.mock import AsyncMock, Mock, patch +from unittest.mock import AsyncMock, Mock, PropertyMock, patch import aiohttp import pytest @@ -13,10 +13,11 @@ from homeassistant.components.analytics.const import ( ATTR_STATISTICS, ATTR_USAGE, ) +from homeassistant.components.api import ATTR_UUID from homeassistant.const import __version__ as HA_VERSION from homeassistant.loader import IntegrationNotFound -MOCK_HUUID = "abcdefg" +MOCK_UUID = "abcdefg" async def test_no_send(hass, caplog, aioclient_mock): @@ -26,8 +27,7 @@ async def test_no_send(hass, caplog, aioclient_mock): with patch( "homeassistant.components.hassio.is_hassio", 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] await analytics.send_analytics() @@ -76,9 +76,7 @@ async def test_failed_to_send(hass, caplog, aioclient_mock): analytics = Analytics(hass) await analytics.save_preferences({ATTR_BASE: True}) 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 @@ -88,9 +86,7 @@ async def test_failed_to_send_raises(hass, caplog, aioclient_mock): analytics = Analytics(hass) await analytics.save_preferences({ATTR_BASE: True}) 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 @@ -98,12 +94,15 @@ async def test_send_base(hass, caplog, aioclient_mock): """Test send base prefrences are defined.""" aioclient_mock.post(ANALYTICS_ENDPOINT_URL, status=200) analytics = Analytics(hass) + await analytics.save_preferences({ATTR_BASE: True}) 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() - 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 "'installation_type':" 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", side_effect=Mock(return_value=True), ), 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() - 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 "'supervisor': {'healthy': True, 'supported': True}}" 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) analytics = Analytics(hass) await analytics.save_preferences({ATTR_BASE: True, ATTR_USAGE: True}) + assert analytics.preferences[ATTR_BASE] assert analytics.preferences[ATTR_USAGE] 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 "'integration_count':" not in caplog.text @@ -195,8 +199,6 @@ async def test_send_usage_with_supervisor(hass, caplog, aioclient_mock): ), patch( "homeassistant.components.hassio.is_hassio", side_effect=Mock(return_value=True), - ), patch( - "homeassistant.helpers.instance_id.async_get", return_value=MOCK_HUUID ): await analytics.send_analytics() assert ( @@ -215,8 +217,7 @@ async def test_send_statistics(hass, caplog, aioclient_mock): assert analytics.preferences[ATTR_STATISTICS] 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 ( "'state_count': 0, 'automation_count': 0, 'integration_count': 1, 'user_count': 0" in caplog.text @@ -236,11 +237,11 @@ async def test_send_statistics_one_integration_fails(hass, caplog, aioclient_moc with patch( "homeassistant.components.analytics.analytics.async_get_integration", side_effect=IntegrationNotFound("any"), - ), patch("homeassistant.helpers.instance_id.async_get", return_value=MOCK_HUUID): + ): await analytics.send_analytics() 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 @@ -258,7 +259,7 @@ async def test_send_statistics_async_get_integration_unknown_exception( with pytest.raises(ValueError), patch( "homeassistant.components.analytics.analytics.async_get_integration", side_effect=ValueError, - ), patch("homeassistant.helpers.instance_id.async_get", return_value=MOCK_HUUID): + ): await analytics.send_analytics() @@ -298,9 +299,23 @@ async def test_send_statistics_with_supervisor(hass, caplog, aioclient_mock): ), patch( "homeassistant.components.hassio.is_hassio", side_effect=Mock(return_value=True), - ), patch( - "homeassistant.helpers.instance_id.async_get", return_value=MOCK_HUUID ): await analytics.send_analytics() assert "'addon_count': 1" 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" diff --git a/tests/components/analytics/test_init.py b/tests/components/analytics/test_init.py index 4f8c95bc6b4..af105926926 100644 --- a/tests/components/analytics/test_init.py +++ b/tests/components/analytics/test_init.py @@ -1,6 +1,4 @@ """The tests for the analytics .""" -from unittest.mock import patch - from homeassistant.components.analytics.const import ANALYTICS_ENDPOINT_URL, DOMAIN 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) 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["result"]["huuid"] == "abcdef" await ws_client.send_json( {"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"] 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"]["huuid"] == "abcdef"