diff --git a/homeassistant/components/analytics/analytics.py b/homeassistant/components/analytics/analytics.py index e6e8678cc109..daadab65228f 100644 --- a/homeassistant/components/analytics/analytics.py +++ b/homeassistant/components/analytics/analytics.py @@ -18,6 +18,7 @@ from homeassistant.setup import async_get_loaded_integrations from .const import ( ANALYTICS_ENDPOINT_URL, + ANALYTICS_ENDPOINT_URL_DEV, ATTR_ADDON_COUNT, ATTR_ADDONS, ATTR_AUTO_UPDATE, @@ -78,6 +79,14 @@ class Analytics: """Return the uuid for the analytics integration.""" return self._data[ATTR_UUID] + @property + def endpoint(self) -> str: + """Return the endpoint that will receive the payload.""" + if HA_VERSION.endswith("0.dev0"): + # dev installations will contact the dev analytics environment + return ANALYTICS_ENDPOINT_URL_DEV + return ANALYTICS_ENDPOINT_URL + @property def supervisor(self) -> bool: """Return bool if a supervisor is present.""" @@ -219,7 +228,7 @@ class Analytics: try: with async_timeout.timeout(30): - response = await self.session.post(ANALYTICS_ENDPOINT_URL, json=payload) + response = await self.session.post(self.endpoint, json=payload) if response.status == 200: LOGGER.info( ( @@ -230,7 +239,9 @@ class Analytics: ) else: LOGGER.warning( - "Sending analytics failed with statuscode %s", response.status + "Sending analytics failed with statuscode %s from %s", + response.status, + self.endpoint, ) except asyncio.TimeoutError: LOGGER.error("Timeout sending analytics to %s", ANALYTICS_ENDPOINT_URL) diff --git a/homeassistant/components/analytics/const.py b/homeassistant/components/analytics/const.py index a6fe91b5a44b..e7046898e9b1 100644 --- a/homeassistant/components/analytics/const.py +++ b/homeassistant/components/analytics/const.py @@ -5,6 +5,7 @@ import logging import voluptuous as vol ANALYTICS_ENDPOINT_URL = "https://analytics-api.home-assistant.io/v1" +ANALYTICS_ENDPOINT_URL_DEV = "https://analytics-api-dev.home-assistant.io/v1" DOMAIN = "analytics" INTERVAL = timedelta(days=1) STORAGE_KEY = "core.analytics" diff --git a/tests/components/analytics/test_analytics.py b/tests/components/analytics/test_analytics.py index e1716df9cdb9..1ac8c0fa8f07 100644 --- a/tests/components/analytics/test_analytics.py +++ b/tests/components/analytics/test_analytics.py @@ -7,6 +7,7 @@ import pytest from homeassistant.components.analytics.analytics import Analytics from homeassistant.components.analytics.const import ( ANALYTICS_ENDPOINT_URL, + ANALYTICS_ENDPOINT_URL_DEV, ATTR_BASE, ATTR_DIAGNOSTICS, ATTR_PREFERENCES, @@ -14,16 +15,18 @@ from homeassistant.components.analytics.const import ( ATTR_USAGE, ) from homeassistant.components.api import ATTR_UUID -from homeassistant.const import ATTR_DOMAIN, __version__ as HA_VERSION +from homeassistant.const import ATTR_DOMAIN from homeassistant.loader import IntegrationNotFound from homeassistant.setup import async_setup_component MOCK_UUID = "abcdefg" +MOCK_VERSION = "1970.1.0" +MOCK_VERSION_DEV = "1970.1.0.dev0" +MOCK_VERSION_NIGHTLY = "1970.1.0.dev19700101" async def test_no_send(hass, caplog, aioclient_mock): """Test send when no prefrences are defined.""" - aioclient_mock.post(ANALYTICS_ENDPOINT_URL, status=200) analytics = Analytics(hass) with patch( "homeassistant.components.hassio.is_hassio", @@ -77,8 +80,13 @@ 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] - await analytics.send_analytics() - assert "Sending analytics failed with statuscode 400" in caplog.text + + with patch("homeassistant.components.analytics.analytics.HA_VERSION", MOCK_VERSION): + await analytics.send_analytics() + assert ( + f"Sending analytics failed with statuscode 400 from {ANALYTICS_ENDPOINT_URL}" + in caplog.text + ) async def test_failed_to_send_raises(hass, caplog, aioclient_mock): @@ -87,7 +95,9 @@ 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] - await analytics.send_analytics() + + with patch("homeassistant.components.analytics.analytics.HA_VERSION", MOCK_VERSION): + await analytics.send_analytics() assert "Error sending analytics" in caplog.text @@ -99,12 +109,14 @@ async def test_send_base(hass, caplog, aioclient_mock): await analytics.save_preferences({ATTR_BASE: True}) assert analytics.preferences[ATTR_BASE] - with patch("uuid.UUID.hex", new_callable=PropertyMock) as hex: + with patch("uuid.UUID.hex", new_callable=PropertyMock) as hex, patch( + "homeassistant.components.analytics.analytics.HA_VERSION", MOCK_VERSION + ): hex.return_value = MOCK_UUID await analytics.send_analytics() assert f"'uuid': '{MOCK_UUID}'" in caplog.text - assert f"'version': '{HA_VERSION}'" in caplog.text + assert f"'version': '{MOCK_VERSION}'" in caplog.text assert "'installation_type':" in caplog.text assert "'integration_count':" not in caplog.text assert "'integrations':" not in caplog.text @@ -132,14 +144,16 @@ async def test_send_base_with_supervisor(hass, caplog, aioclient_mock): side_effect=Mock(return_value=True), ), patch( "uuid.UUID.hex", new_callable=PropertyMock - ) as hex: + ) as hex, patch( + "homeassistant.components.analytics.analytics.HA_VERSION", MOCK_VERSION + ): hex.return_value = MOCK_UUID await analytics.load() await analytics.send_analytics() assert f"'uuid': '{MOCK_UUID}'" in caplog.text - assert f"'version': '{HA_VERSION}'" in caplog.text + assert f"'version': '{MOCK_VERSION}'" in caplog.text assert "'supervisor': {'healthy': True, 'supported': True}}" in caplog.text assert "'installation_type':" in caplog.text assert "'integration_count':" not in caplog.text @@ -156,7 +170,8 @@ async def test_send_usage(hass, caplog, aioclient_mock): assert analytics.preferences[ATTR_USAGE] hass.config.components = ["default_config"] - await analytics.send_analytics() + with patch("homeassistant.components.analytics.analytics.HA_VERSION", MOCK_VERSION): + await analytics.send_analytics() assert "'integrations': ['default_config']" in caplog.text assert "'integration_count':" not in caplog.text @@ -200,6 +215,8 @@ 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.components.analytics.analytics.HA_VERSION", MOCK_VERSION ): await analytics.send_analytics() assert ( @@ -218,7 +235,8 @@ async def test_send_statistics(hass, caplog, aioclient_mock): assert analytics.preferences[ATTR_STATISTICS] hass.config.components = ["default_config"] - await analytics.send_analytics() + with patch("homeassistant.components.analytics.analytics.HA_VERSION", MOCK_VERSION): + await analytics.send_analytics() assert ( "'state_count': 0, 'automation_count': 0, 'integration_count': 1, 'user_count': 0" in caplog.text @@ -238,7 +256,7 @@ 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.components.analytics.analytics.HA_VERSION", MOCK_VERSION): await analytics.send_analytics() post_call = aioclient_mock.mock_calls[0] @@ -260,7 +278,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.components.analytics.analytics.HA_VERSION", MOCK_VERSION): await analytics.send_analytics() @@ -300,6 +318,8 @@ 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.components.analytics.analytics.HA_VERSION", MOCK_VERSION ): await analytics.send_analytics() assert "'addon_count': 1" in caplog.text @@ -314,7 +334,9 @@ async def test_reusing_uuid(hass, aioclient_mock): await analytics.save_preferences({ATTR_BASE: True}) - with patch("uuid.UUID.hex", new_callable=PropertyMock) as hex: + with patch("uuid.UUID.hex", new_callable=PropertyMock) as hex, patch( + "homeassistant.components.analytics.analytics.HA_VERSION", MOCK_VERSION + ): # This is not actually called but that in itself prove the test hex.return_value = MOCK_UUID await analytics.send_analytics() @@ -329,7 +351,59 @@ async def test_custom_integrations(hass, aioclient_mock): assert await async_setup_component(hass, "test_package", {"test_package": {}}) await analytics.save_preferences({ATTR_BASE: True, ATTR_USAGE: True}) - await analytics.send_analytics() + with patch("homeassistant.components.analytics.analytics.HA_VERSION", MOCK_VERSION): + await analytics.send_analytics() payload = aioclient_mock.mock_calls[0][2] assert payload["custom_integrations"][0][ATTR_DOMAIN] == "test_package" + + +async def test_dev_url(hass, aioclient_mock): + """Test sending payload to dev url.""" + aioclient_mock.post(ANALYTICS_ENDPOINT_URL_DEV, status=200) + analytics = Analytics(hass) + await analytics.save_preferences({ATTR_BASE: True}) + + with patch( + "homeassistant.components.analytics.analytics.HA_VERSION", MOCK_VERSION_DEV + ): + await analytics.send_analytics() + + payload = aioclient_mock.mock_calls[0] + assert str(payload[1]) == ANALYTICS_ENDPOINT_URL_DEV + + +async def test_dev_url_error(hass, aioclient_mock, caplog): + """Test sending payload to dev url that returns error.""" + aioclient_mock.post(ANALYTICS_ENDPOINT_URL_DEV, status=400) + analytics = Analytics(hass) + await analytics.save_preferences({ATTR_BASE: True}) + + with patch( + "homeassistant.components.analytics.analytics.HA_VERSION", MOCK_VERSION_DEV + ): + + await analytics.send_analytics() + + payload = aioclient_mock.mock_calls[0] + assert str(payload[1]) == ANALYTICS_ENDPOINT_URL_DEV + assert ( + f"Sending analytics failed with statuscode 400 from {ANALYTICS_ENDPOINT_URL_DEV}" + in caplog.text + ) + + +async def test_nightly_endpoint(hass, aioclient_mock): + """Test sending payload to production url when running nightly.""" + aioclient_mock.post(ANALYTICS_ENDPOINT_URL, status=200) + analytics = Analytics(hass) + await analytics.save_preferences({ATTR_BASE: True}) + + with patch( + "homeassistant.components.analytics.analytics.HA_VERSION", MOCK_VERSION_NIGHTLY + ): + + await analytics.send_analytics() + + payload = aioclient_mock.mock_calls[0] + assert str(payload[1]) == ANALYTICS_ENDPOINT_URL diff --git a/tests/components/analytics/test_init.py b/tests/components/analytics/test_init.py index af1059269264..e48d662594d3 100644 --- a/tests/components/analytics/test_init.py +++ b/tests/components/analytics/test_init.py @@ -1,7 +1,11 @@ """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 +MOCK_VERSION = "1970.1.0" + async def test_setup(hass): """Test setup of the integration.""" @@ -24,10 +28,11 @@ async def test_websocket(hass, hass_ws_client, aioclient_mock): assert response["success"] - await ws_client.send_json( - {"id": 2, "type": "analytics/preferences", "preferences": {"base": True}} - ) - response = await ws_client.receive_json() + with patch("homeassistant.components.analytics.analytics.HA_VERSION", MOCK_VERSION): + await ws_client.send_json( + {"id": 2, "type": "analytics/preferences", "preferences": {"base": True}} + ) + response = await ws_client.receive_json() assert len(aioclient_mock.mock_calls) == 1 assert response["result"]["preferences"]["base"]