"""Test network helper.""" from unittest.mock import Mock, patch import pytest from homeassistant.components import cloud from homeassistant.config import async_process_ha_core_config from homeassistant.core import HomeAssistant from homeassistant.helpers.network import ( NoURLAvailableError, _get_cloud_url, _get_external_url, _get_internal_url, _get_request_host, get_url, is_internal_request, ) from tests.common import mock_component async def test_get_url_internal(hass: HomeAssistant): """Test getting an instance URL when the user has set an internal URL.""" assert hass.config.internal_url is None with pytest.raises(NoURLAvailableError): _get_internal_url(hass, require_current_request=True) # Test with internal URL: http://example.local:8123 await async_process_ha_core_config( hass, {"internal_url": "http://example.local:8123"}, ) assert hass.config.internal_url == "http://example.local:8123" assert _get_internal_url(hass) == "http://example.local:8123" assert _get_internal_url(hass, allow_ip=False) == "http://example.local:8123" with pytest.raises(NoURLAvailableError): _get_internal_url(hass, require_standard_port=True) with pytest.raises(NoURLAvailableError): _get_internal_url(hass, require_ssl=True) with pytest.raises(NoURLAvailableError): _get_internal_url(hass, require_current_request=True) with patch( "homeassistant.helpers.network._get_request_host", return_value="example.local" ): assert ( _get_internal_url(hass, require_current_request=True) == "http://example.local:8123" ) with pytest.raises(NoURLAvailableError): _get_internal_url( hass, require_current_request=True, require_standard_port=True ) with pytest.raises(NoURLAvailableError): _get_internal_url(hass, require_current_request=True, require_ssl=True) with patch( "homeassistant.helpers.network._get_request_host", return_value="no_match.example.local", ), pytest.raises(NoURLAvailableError): _get_internal_url(hass, require_current_request=True) # Test with internal URL: https://example.local:8123 await async_process_ha_core_config( hass, {"internal_url": "https://example.local:8123"}, ) assert hass.config.internal_url == "https://example.local:8123" assert _get_internal_url(hass) == "https://example.local:8123" assert _get_internal_url(hass, allow_ip=False) == "https://example.local:8123" assert _get_internal_url(hass, require_ssl=True) == "https://example.local:8123" with pytest.raises(NoURLAvailableError): _get_internal_url(hass, require_standard_port=True) # Test with internal URL: http://example.local:80/ await async_process_ha_core_config( hass, {"internal_url": "http://example.local:80/"}, ) assert hass.config.internal_url == "http://example.local:80/" assert _get_internal_url(hass) == "http://example.local" assert _get_internal_url(hass, allow_ip=False) == "http://example.local" assert _get_internal_url(hass, require_standard_port=True) == "http://example.local" with pytest.raises(NoURLAvailableError): _get_internal_url(hass, require_ssl=True) # Test with internal URL: https://example.local:443 await async_process_ha_core_config( hass, {"internal_url": "https://example.local:443"}, ) assert hass.config.internal_url == "https://example.local:443" assert _get_internal_url(hass) == "https://example.local" assert _get_internal_url(hass, allow_ip=False) == "https://example.local" assert ( _get_internal_url(hass, require_standard_port=True) == "https://example.local" ) assert _get_internal_url(hass, require_ssl=True) == "https://example.local" # Test with internal URL: https://192.168.0.1 await async_process_ha_core_config( hass, {"internal_url": "https://192.168.0.1"}, ) assert hass.config.internal_url == "https://192.168.0.1" assert _get_internal_url(hass) == "https://192.168.0.1" assert _get_internal_url(hass, require_standard_port=True) == "https://192.168.0.1" assert _get_internal_url(hass, require_ssl=True) == "https://192.168.0.1" with pytest.raises(NoURLAvailableError): _get_internal_url(hass, allow_ip=False) # Test with internal URL: http://192.168.0.1:8123 await async_process_ha_core_config( hass, {"internal_url": "http://192.168.0.1:8123"}, ) assert hass.config.internal_url == "http://192.168.0.1:8123" assert _get_internal_url(hass) == "http://192.168.0.1:8123" with pytest.raises(NoURLAvailableError): _get_internal_url(hass, require_standard_port=True) with pytest.raises(NoURLAvailableError): _get_internal_url(hass, require_ssl=True) with pytest.raises(NoURLAvailableError): _get_internal_url(hass, allow_ip=False) with patch( "homeassistant.helpers.network._get_request_host", return_value="192.168.0.1" ): assert ( _get_internal_url(hass, require_current_request=True) == "http://192.168.0.1:8123" ) with pytest.raises(NoURLAvailableError): _get_internal_url(hass, require_current_request=True, allow_ip=False) with pytest.raises(NoURLAvailableError): _get_internal_url( hass, require_current_request=True, require_standard_port=True ) with pytest.raises(NoURLAvailableError): _get_internal_url(hass, require_current_request=True, require_ssl=True) async def test_get_url_internal_fallback(hass: HomeAssistant): """Test getting an instance URL when the user has not set an internal URL.""" assert hass.config.internal_url is None hass.config.api = Mock(use_ssl=False, port=8123, local_ip="192.168.123.123") assert _get_internal_url(hass) == "http://192.168.123.123:8123" with pytest.raises(NoURLAvailableError): _get_internal_url(hass, allow_ip=False) with pytest.raises(NoURLAvailableError): _get_internal_url(hass, require_standard_port=True) with pytest.raises(NoURLAvailableError): _get_internal_url(hass, require_ssl=True) hass.config.api = Mock(use_ssl=False, port=80, local_ip="192.168.123.123") assert _get_internal_url(hass) == "http://192.168.123.123" assert ( _get_internal_url(hass, require_standard_port=True) == "http://192.168.123.123" ) with pytest.raises(NoURLAvailableError): _get_internal_url(hass, allow_ip=False) with pytest.raises(NoURLAvailableError): _get_internal_url(hass, require_ssl=True) hass.config.api = Mock(use_ssl=True, port=443) with pytest.raises(NoURLAvailableError): _get_internal_url(hass) with pytest.raises(NoURLAvailableError): _get_internal_url(hass, require_standard_port=True) with pytest.raises(NoURLAvailableError): _get_internal_url(hass, allow_ip=False) with pytest.raises(NoURLAvailableError): _get_internal_url(hass, require_ssl=True) # Do no accept any local loopback address as fallback hass.config.api = Mock(use_ssl=False, port=80, local_ip="127.0.0.1") with pytest.raises(NoURLAvailableError): _get_internal_url(hass) with pytest.raises(NoURLAvailableError): _get_internal_url(hass, require_standard_port=True) with pytest.raises(NoURLAvailableError): _get_internal_url(hass, allow_ip=False) with pytest.raises(NoURLAvailableError): _get_internal_url(hass, require_ssl=True) async def test_get_url_external(hass: HomeAssistant): """Test getting an instance URL when the user has set an external URL.""" assert hass.config.external_url is None with pytest.raises(NoURLAvailableError): _get_external_url(hass, require_current_request=True) # Test with external URL: http://example.com:8123 await async_process_ha_core_config( hass, {"external_url": "http://example.com:8123"}, ) assert hass.config.external_url == "http://example.com:8123" assert _get_external_url(hass) == "http://example.com:8123" assert _get_external_url(hass, allow_cloud=False) == "http://example.com:8123" assert _get_external_url(hass, allow_ip=False) == "http://example.com:8123" assert _get_external_url(hass, prefer_cloud=True) == "http://example.com:8123" with pytest.raises(NoURLAvailableError): _get_external_url(hass, require_standard_port=True) with pytest.raises(NoURLAvailableError): _get_external_url(hass, require_ssl=True) with pytest.raises(NoURLAvailableError): _get_external_url(hass, require_current_request=True) with patch( "homeassistant.helpers.network._get_request_host", return_value="example.com" ): assert ( _get_external_url(hass, require_current_request=True) == "http://example.com:8123" ) with pytest.raises(NoURLAvailableError): _get_external_url( hass, require_current_request=True, require_standard_port=True ) with pytest.raises(NoURLAvailableError): _get_external_url(hass, require_current_request=True, require_ssl=True) with patch( "homeassistant.helpers.network._get_request_host", return_value="no_match.example.com", ), pytest.raises(NoURLAvailableError): _get_external_url(hass, require_current_request=True) # Test with external URL: http://example.com:80/ await async_process_ha_core_config( hass, {"external_url": "http://example.com:80/"}, ) assert hass.config.external_url == "http://example.com:80/" assert _get_external_url(hass) == "http://example.com" assert _get_external_url(hass, allow_cloud=False) == "http://example.com" assert _get_external_url(hass, allow_ip=False) == "http://example.com" assert _get_external_url(hass, prefer_cloud=True) == "http://example.com" assert _get_external_url(hass, require_standard_port=True) == "http://example.com" with pytest.raises(NoURLAvailableError): _get_external_url(hass, require_ssl=True) # Test with external url: https://example.com:443/ await async_process_ha_core_config( hass, {"external_url": "https://example.com:443/"}, ) assert hass.config.external_url == "https://example.com:443/" assert _get_external_url(hass) == "https://example.com" assert _get_external_url(hass, allow_cloud=False) == "https://example.com" assert _get_external_url(hass, allow_ip=False) == "https://example.com" assert _get_external_url(hass, prefer_cloud=True) == "https://example.com" assert _get_external_url(hass, require_ssl=False) == "https://example.com" assert _get_external_url(hass, require_standard_port=True) == "https://example.com" # Test with external URL: https://example.com:80 await async_process_ha_core_config( hass, {"external_url": "https://example.com:80"}, ) assert hass.config.external_url == "https://example.com:80" assert _get_external_url(hass) == "https://example.com:80" assert _get_external_url(hass, allow_cloud=False) == "https://example.com:80" assert _get_external_url(hass, allow_ip=False) == "https://example.com:80" assert _get_external_url(hass, prefer_cloud=True) == "https://example.com:80" assert _get_external_url(hass, require_ssl=True) == "https://example.com:80" with pytest.raises(NoURLAvailableError): _get_external_url(hass, require_standard_port=True) # Test with external URL: https://192.168.0.1 await async_process_ha_core_config( hass, {"external_url": "https://192.168.0.1"}, ) assert hass.config.external_url == "https://192.168.0.1" assert _get_external_url(hass) == "https://192.168.0.1" assert _get_external_url(hass, allow_cloud=False) == "https://192.168.0.1" assert _get_external_url(hass, prefer_cloud=True) == "https://192.168.0.1" assert _get_external_url(hass, require_standard_port=True) == "https://192.168.0.1" with pytest.raises(NoURLAvailableError): _get_external_url(hass, allow_ip=False) with pytest.raises(NoURLAvailableError): _get_external_url(hass, require_ssl=True) with patch( "homeassistant.helpers.network._get_request_host", return_value="192.168.0.1" ): assert ( _get_external_url(hass, require_current_request=True) == "https://192.168.0.1" ) with pytest.raises(NoURLAvailableError): _get_external_url(hass, require_current_request=True, allow_ip=False) with pytest.raises(NoURLAvailableError): _get_external_url(hass, require_current_request=True, require_ssl=True) async def test_get_cloud_url(hass: HomeAssistant): """Test getting an instance URL when the user has set an external URL.""" assert hass.config.external_url is None hass.config.components.add("cloud") with patch.object( hass.components.cloud, "async_remote_ui_url", return_value="https://example.nabu.casa", ): assert _get_cloud_url(hass) == "https://example.nabu.casa" with pytest.raises(NoURLAvailableError): _get_cloud_url(hass, require_current_request=True) with patch( "homeassistant.helpers.network._get_request_host", return_value="example.nabu.casa", ): assert ( _get_cloud_url(hass, require_current_request=True) == "https://example.nabu.casa" ) with patch( "homeassistant.helpers.network._get_request_host", return_value="no_match.nabu.casa", ), pytest.raises(NoURLAvailableError): _get_cloud_url(hass, require_current_request=True) with patch.object( hass.components.cloud, "async_remote_ui_url", side_effect=cloud.CloudNotAvailable, ), pytest.raises(NoURLAvailableError): _get_cloud_url(hass) async def test_get_external_url_cloud_fallback(hass: HomeAssistant): """Test getting an external instance URL with cloud fallback.""" assert hass.config.external_url is None # Test with external URL: http://1.1.1.1:8123 await async_process_ha_core_config( hass, {"external_url": "http://1.1.1.1:8123"}, ) assert hass.config.external_url == "http://1.1.1.1:8123" assert _get_external_url(hass, prefer_cloud=True) == "http://1.1.1.1:8123" # Add Cloud to the previous test hass.config.components.add("cloud") with patch.object( hass.components.cloud, "async_remote_ui_url", return_value="https://example.nabu.casa", ): assert _get_external_url(hass, allow_cloud=False) == "http://1.1.1.1:8123" assert _get_external_url(hass, allow_ip=False) == "https://example.nabu.casa" assert _get_external_url(hass, prefer_cloud=False) == "http://1.1.1.1:8123" assert _get_external_url(hass, prefer_cloud=True) == "https://example.nabu.casa" assert _get_external_url(hass, require_ssl=True) == "https://example.nabu.casa" assert ( _get_external_url(hass, require_standard_port=True) == "https://example.nabu.casa" ) # Test with external URL: https://example.com await async_process_ha_core_config( hass, {"external_url": "https://example.com"}, ) assert hass.config.external_url == "https://example.com" assert _get_external_url(hass, prefer_cloud=True) == "https://example.com" # Add Cloud to the previous test hass.config.components.add("cloud") with patch.object( hass.components.cloud, "async_remote_ui_url", return_value="https://example.nabu.casa", ): assert _get_external_url(hass, allow_cloud=False) == "https://example.com" assert _get_external_url(hass, allow_ip=False) == "https://example.com" assert _get_external_url(hass, prefer_cloud=False) == "https://example.com" assert _get_external_url(hass, prefer_cloud=True) == "https://example.nabu.casa" assert _get_external_url(hass, require_ssl=True) == "https://example.com" assert ( _get_external_url(hass, require_standard_port=True) == "https://example.com" ) assert ( _get_external_url(hass, prefer_cloud=True, allow_cloud=False) == "https://example.com" ) async def test_get_url(hass: HomeAssistant): """Test getting an instance URL.""" assert hass.config.external_url is None assert hass.config.internal_url is None with pytest.raises(NoURLAvailableError): get_url(hass) hass.config.api = Mock(use_ssl=False, port=8123, local_ip="192.168.123.123") assert get_url(hass) == "http://192.168.123.123:8123" assert get_url(hass, prefer_external=True) == "http://192.168.123.123:8123" with pytest.raises(NoURLAvailableError): get_url(hass, allow_internal=False) # Test only external hass.config.api = None await async_process_ha_core_config( hass, {"external_url": "https://example.com"}, ) assert hass.config.external_url == "https://example.com" assert hass.config.internal_url is None assert get_url(hass) == "https://example.com" # Test preference or allowance await async_process_ha_core_config( hass, {"internal_url": "http://example.local", "external_url": "https://example.com"}, ) assert hass.config.external_url == "https://example.com" assert hass.config.internal_url == "http://example.local" assert get_url(hass) == "http://example.local" assert get_url(hass, prefer_external=True) == "https://example.com" assert get_url(hass, allow_internal=False) == "https://example.com" assert ( get_url(hass, prefer_external=True, allow_external=False) == "http://example.local" ) with pytest.raises(NoURLAvailableError): get_url(hass, allow_external=False, require_ssl=True) with pytest.raises(NoURLAvailableError): get_url(hass, allow_external=False, allow_internal=False) with pytest.raises(NoURLAvailableError): get_url(hass, require_current_request=True) with patch( "homeassistant.helpers.network._get_request_host", return_value="example.com" ), patch("homeassistant.components.http.current_request"): assert get_url(hass, require_current_request=True) == "https://example.com" assert ( get_url(hass, require_current_request=True, require_ssl=True) == "https://example.com" ) with pytest.raises(NoURLAvailableError): get_url(hass, require_current_request=True, allow_external=False) with patch( "homeassistant.helpers.network._get_request_host", return_value="example.local" ), patch("homeassistant.components.http.current_request"): assert get_url(hass, require_current_request=True) == "http://example.local" with pytest.raises(NoURLAvailableError): get_url(hass, require_current_request=True, allow_internal=False) with pytest.raises(NoURLAvailableError): get_url(hass, require_current_request=True, require_ssl=True) with patch( "homeassistant.helpers.network._get_request_host", return_value="no_match.example.com", ), pytest.raises(NoURLAvailableError): _get_internal_url(hass, require_current_request=True) async def test_get_request_host(hass: HomeAssistant): """Test getting the host of the current web request from the request context.""" with pytest.raises(NoURLAvailableError): _get_request_host() with patch("homeassistant.components.http.current_request") as mock_request_context: mock_request = Mock() mock_request.url = "http://example.com:8123/test/request" mock_request_context.get = Mock(return_value=mock_request) assert _get_request_host() == "example.com" async def test_get_current_request_url_with_known_host( hass: HomeAssistant, current_request ): """Test getting current request URL with known hosts addresses.""" hass.config.api = Mock(use_ssl=False, port=8123, local_ip="127.0.0.1") assert hass.config.internal_url is None with pytest.raises(NoURLAvailableError): get_url(hass, require_current_request=True) # Ensure we accept localhost with patch( "homeassistant.helpers.network._get_request_host", return_value="localhost" ): assert get_url(hass, require_current_request=True) == "http://localhost:8123" with pytest.raises(NoURLAvailableError): get_url(hass, require_current_request=True, require_ssl=True) with pytest.raises(NoURLAvailableError): get_url(hass, require_current_request=True, require_standard_port=True) # Ensure we accept local loopback ip (e.g., 127.0.0.1) with patch( "homeassistant.helpers.network._get_request_host", return_value="127.0.0.8" ): assert get_url(hass, require_current_request=True) == "http://127.0.0.8:8123" with pytest.raises(NoURLAvailableError): get_url(hass, require_current_request=True, allow_ip=False) # Ensure hostname from Supervisor is accepted transparently mock_component(hass, "hassio") hass.components.hassio.is_hassio = Mock(return_value=True) hass.components.hassio.get_host_info = Mock( return_value={"hostname": "homeassistant"} ) with patch( "homeassistant.helpers.network._get_request_host", return_value="homeassistant.local", ): assert ( get_url(hass, require_current_request=True) == "http://homeassistant.local:8123" ) with patch( "homeassistant.helpers.network._get_request_host", return_value="homeassistant", ): assert ( get_url(hass, require_current_request=True) == "http://homeassistant:8123" ) with patch( "homeassistant.helpers.network._get_request_host", return_value="unknown.local" ), pytest.raises(NoURLAvailableError): get_url(hass, require_current_request=True) async def test_is_internal_request(hass: HomeAssistant): """Test if accessing an instance on its internal URL.""" # Test with internal URL: http://example.local:8123 await async_process_ha_core_config( hass, {"internal_url": "http://example.local:8123"}, ) assert hass.config.internal_url == "http://example.local:8123" assert not is_internal_request(hass) with patch( "homeassistant.helpers.network._get_request_host", return_value="example.local" ): assert is_internal_request(hass) with patch( "homeassistant.helpers.network._get_request_host", return_value="no_match.example.local", ): assert not is_internal_request(hass) # Test with internal URL: http://192.168.0.1:8123 await async_process_ha_core_config( hass, {"internal_url": "http://192.168.0.1:8123"}, ) assert hass.config.internal_url == "http://192.168.0.1:8123" assert not is_internal_request(hass) with patch( "homeassistant.helpers.network._get_request_host", return_value="192.168.0.1" ): assert is_internal_request(hass)