diff --git a/tests/components/network/__init__.py b/tests/components/network/__init__.py index f3ccacbd064..bbac7ca2f7c 100644 --- a/tests/components/network/__init__.py +++ b/tests/components/network/__init__.py @@ -1 +1,4 @@ """Tests for the Network Configuration integration.""" + +NO_LOOPBACK_IPADDR = "192.168.1.5" +LOOPBACK_IPADDR = "127.0.0.1" diff --git a/tests/components/network/conftest.py b/tests/components/network/conftest.py index d5fbb95a814..db2f268e968 100644 --- a/tests/components/network/conftest.py +++ b/tests/components/network/conftest.py @@ -1,14 +1,51 @@ """Tests for the Network Configuration integration.""" from collections.abc import Generator -from unittest.mock import _patch +from unittest.mock import MagicMock, Mock, _patch, patch +import ifaddr import pytest +from . import LOOPBACK_IPADDR, NO_LOOPBACK_IPADDR + + +def _generate_mock_adapters(): + mock_lo0 = Mock(spec=ifaddr.Adapter) + mock_lo0.nice_name = "lo0" + mock_lo0.ips = [ifaddr.IP(LOOPBACK_IPADDR, 8, "lo0")] + mock_lo0.index = 0 + mock_eth0 = Mock(spec=ifaddr.Adapter) + mock_eth0.nice_name = "eth0" + mock_eth0.ips = [ifaddr.IP(("2001:db8::", 1, 1), 8, "eth0")] + mock_eth0.index = 1 + mock_eth1 = Mock(spec=ifaddr.Adapter) + mock_eth1.nice_name = "eth1" + mock_eth1.ips = [ifaddr.IP(NO_LOOPBACK_IPADDR, 23, "eth1")] + mock_eth1.index = 2 + mock_vtun0 = Mock(spec=ifaddr.Adapter) + mock_vtun0.nice_name = "vtun0" + mock_vtun0.ips = [ifaddr.IP("169.254.3.2", 16, "vtun0")] + mock_vtun0.index = 3 + return [mock_eth0, mock_lo0, mock_eth1, mock_vtun0] + + +def _mock_socket(sockname: list[str]) -> Generator[None]: + """Mock the network socket.""" + with patch( + "homeassistant.components.network.util.socket.socket", + return_value=MagicMock(getsockname=Mock(return_value=sockname)), + ): + yield + @pytest.fixture(autouse=True) -def mock_network(): +def mock_network() -> Generator[None]: """Override mock of network util's async_get_adapters.""" + with patch( + "homeassistant.components.network.util.ifaddr.get_adapters", + return_value=_generate_mock_adapters(), + ): + yield @pytest.fixture(autouse=True) @@ -19,3 +56,21 @@ def override_mock_get_source_ip( mock_get_source_ip.stop() yield mock_get_source_ip.start() + + +@pytest.fixture +def mock_socket(request: pytest.FixtureRequest) -> Generator[None]: + """Mock the network socket.""" + yield from _mock_socket(request.param) + + +@pytest.fixture +def mock_socket_loopback() -> Generator[None]: + """Mock the network socket with loopback address.""" + yield from _mock_socket([LOOPBACK_IPADDR]) + + +@pytest.fixture +def mock_socket_no_loopback() -> Generator[None]: + """Mock the network socket with loopback address.""" + yield from _mock_socket([NO_LOOPBACK_IPADDR]) diff --git a/tests/components/network/test_init.py b/tests/components/network/test_init.py index dca31106dba..a2352e6af9e 100644 --- a/tests/components/network/test_init.py +++ b/tests/components/network/test_init.py @@ -4,7 +4,6 @@ from ipaddress import IPv4Address from typing import Any from unittest.mock import MagicMock, Mock, patch -import ifaddr import pytest from homeassistant.components import network @@ -20,17 +19,10 @@ from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError from homeassistant.setup import async_setup_component +from . import LOOPBACK_IPADDR, NO_LOOPBACK_IPADDR + from tests.typing import WebSocketGenerator -_NO_LOOPBACK_IPADDR = "192.168.1.5" -_LOOPBACK_IPADDR = "127.0.0.1" - - -def _mock_socket(sockname): - mock_socket = MagicMock() - mock_socket.getsockname = Mock(return_value=sockname) - return mock_socket - def _mock_cond_socket(sockname): class CondMockSock(MagicMock): @@ -54,42 +46,13 @@ def _mock_socket_exception(exc): return mock_socket -def _generate_mock_adapters(): - mock_lo0 = Mock(spec=ifaddr.Adapter) - mock_lo0.nice_name = "lo0" - mock_lo0.ips = [ifaddr.IP("127.0.0.1", 8, "lo0")] - mock_lo0.index = 0 - mock_eth0 = Mock(spec=ifaddr.Adapter) - mock_eth0.nice_name = "eth0" - mock_eth0.ips = [ifaddr.IP(("2001:db8::", 1, 1), 8, "eth0")] - mock_eth0.index = 1 - mock_eth1 = Mock(spec=ifaddr.Adapter) - mock_eth1.nice_name = "eth1" - mock_eth1.ips = [ifaddr.IP("192.168.1.5", 23, "eth1")] - mock_eth1.index = 2 - mock_vtun0 = Mock(spec=ifaddr.Adapter) - mock_vtun0.nice_name = "vtun0" - mock_vtun0.ips = [ifaddr.IP("169.254.3.2", 16, "vtun0")] - mock_vtun0.index = 3 - return [mock_eth0, mock_lo0, mock_eth1, mock_vtun0] - - +@pytest.mark.usefixtures("mock_socket_no_loopback") async def test_async_detect_interfaces_setting_non_loopback_route( hass: HomeAssistant, hass_storage: dict[str, Any] ) -> None: """Test without default interface config and the route returns a non-loopback address.""" - with ( - patch( - "homeassistant.components.network.util.socket.socket", - return_value=_mock_socket([_NO_LOOPBACK_IPADDR]), - ), - patch( - "homeassistant.components.network.util.ifaddr.get_adapters", - return_value=_generate_mock_adapters(), - ), - ): - assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}}) - await hass.async_block_till_done() + assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}}) + await hass.async_block_till_done() network_obj = hass.data[DOMAIN] assert network_obj.configured_adapters == [] @@ -141,22 +104,13 @@ async def test_async_detect_interfaces_setting_non_loopback_route( ] +@pytest.mark.usefixtures("mock_socket_loopback") async def test_async_detect_interfaces_setting_loopback_route( hass: HomeAssistant, hass_storage: dict[str, Any] ) -> None: """Test without default interface config and the route returns a loopback address.""" - with ( - patch( - "homeassistant.components.network.util.socket.socket", - return_value=_mock_socket([_LOOPBACK_IPADDR]), - ), - patch( - "homeassistant.components.network.util.ifaddr.get_adapters", - return_value=_generate_mock_adapters(), - ), - ): - assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}}) - await hass.async_block_till_done() + assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}}) + await hass.async_block_till_done() network_obj = hass.data[DOMAIN] assert network_obj.configured_adapters == [] @@ -207,22 +161,14 @@ async def test_async_detect_interfaces_setting_loopback_route( ] +@pytest.mark.parametrize("mock_socket", [[]], indirect=True) +@pytest.mark.usefixtures("mock_socket") async def test_async_detect_interfaces_setting_empty_route( hass: HomeAssistant, hass_storage: dict[str, Any] ) -> None: """Test without default interface config and the route returns nothing.""" - with ( - patch( - "homeassistant.components.network.util.socket.socket", - return_value=_mock_socket([]), - ), - patch( - "homeassistant.components.network.util.ifaddr.get_adapters", - return_value=_generate_mock_adapters(), - ), - ): - assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}}) - await hass.async_block_till_done() + assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}}) + await hass.async_block_till_done() network_obj = hass.data[DOMAIN] assert network_obj.configured_adapters == [] @@ -277,15 +223,9 @@ async def test_async_detect_interfaces_setting_exception( hass: HomeAssistant, hass_storage: dict[str, Any] ) -> None: """Test without default interface config and the route throws an exception.""" - with ( - patch( - "homeassistant.components.network.util.socket.socket", - return_value=_mock_socket_exception(AttributeError), - ), - patch( - "homeassistant.components.network.util.ifaddr.get_adapters", - return_value=_generate_mock_adapters(), - ), + with patch( + "homeassistant.components.network.util.socket.socket", + return_value=_mock_socket_exception(AttributeError), ): assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}}) await hass.async_block_till_done() @@ -339,6 +279,7 @@ async def test_async_detect_interfaces_setting_exception( ] +@pytest.mark.usefixtures("mock_socket_no_loopback") async def test_interfaces_configured_from_storage( hass: HomeAssistant, hass_storage: dict[str, Any] ) -> None: @@ -348,18 +289,9 @@ async def test_interfaces_configured_from_storage( "key": STORAGE_KEY, "data": {ATTR_CONFIGURED_ADAPTERS: ["eth0", "eth1", "vtun0"]}, } - with ( - patch( - "homeassistant.components.network.util.socket.socket", - return_value=_mock_socket([_NO_LOOPBACK_IPADDR]), - ), - patch( - "homeassistant.components.network.util.ifaddr.get_adapters", - return_value=_generate_mock_adapters(), - ), - ): - assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}}) - await hass.async_block_till_done() + + assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}}) + await hass.async_block_till_done() network_obj = hass.data[DOMAIN] assert network_obj.configured_adapters == ["eth0", "eth1", "vtun0"] @@ -422,15 +354,9 @@ async def test_interfaces_configured_from_storage_websocket_update( "key": STORAGE_KEY, "data": {ATTR_CONFIGURED_ADAPTERS: ["eth0", "eth1", "vtun0"]}, } - with ( - patch( - "homeassistant.components.network.util.socket.socket", - return_value=_mock_socket([_NO_LOOPBACK_IPADDR]), - ), - patch( - "homeassistant.components.network.util.ifaddr.get_adapters", - return_value=_generate_mock_adapters(), - ), + with patch( + "homeassistant.components.network.util.socket.socket", + return_value=MagicMock(getsockname=Mock(return_value=[NO_LOOPBACK_IPADDR])), ): assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}}) await hass.async_block_till_done() @@ -546,6 +472,7 @@ async def test_interfaces_configured_from_storage_websocket_update( ] +@pytest.mark.usefixtures("mock_socket_no_loopback") async def test_async_get_source_ip_matching_interface( hass: HomeAssistant, hass_storage: dict[str, Any] ) -> None: @@ -556,22 +483,13 @@ async def test_async_get_source_ip_matching_interface( "data": {ATTR_CONFIGURED_ADAPTERS: ["eth1"]}, } - with ( - patch( - "homeassistant.components.network.util.ifaddr.get_adapters", - return_value=_generate_mock_adapters(), - ), - patch( - "homeassistant.components.network.util.socket.socket", - return_value=_mock_socket(["192.168.1.5"]), - ), - ): - assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}}) - await hass.async_block_till_done() + assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}}) + await hass.async_block_till_done() - assert await network.async_get_source_ip(hass, MDNS_TARGET_IP) == "192.168.1.5" + assert await network.async_get_source_ip(hass, MDNS_TARGET_IP) == NO_LOOPBACK_IPADDR +@pytest.mark.usefixtures("mock_socket_no_loopback") async def test_async_get_source_ip_interface_not_match( hass: HomeAssistant, hass_storage: dict[str, Any] ) -> None: @@ -582,22 +500,14 @@ async def test_async_get_source_ip_interface_not_match( "data": {ATTR_CONFIGURED_ADAPTERS: ["vtun0"]}, } - with ( - patch( - "homeassistant.components.network.util.ifaddr.get_adapters", - return_value=_generate_mock_adapters(), - ), - patch( - "homeassistant.components.network.util.socket.socket", - return_value=_mock_socket(["192.168.1.5"]), - ), - ): - assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}}) - await hass.async_block_till_done() + assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}}) + await hass.async_block_till_done() - assert await network.async_get_source_ip(hass, MDNS_TARGET_IP) == "169.254.3.2" + assert await network.async_get_source_ip(hass, MDNS_TARGET_IP) == "169.254.3.2" +@pytest.mark.parametrize("mock_socket", [[None]], indirect=True) +@pytest.mark.usefixtures("mock_socket") async def test_async_get_source_ip_cannot_determine_target( hass: HomeAssistant, hass_storage: dict[str, Any] ) -> None: @@ -608,22 +518,13 @@ async def test_async_get_source_ip_cannot_determine_target( "data": {ATTR_CONFIGURED_ADAPTERS: ["eth1"]}, } - with ( - patch( - "homeassistant.components.network.util.ifaddr.get_adapters", - return_value=_generate_mock_adapters(), - ), - patch( - "homeassistant.components.network.util.socket.socket", - return_value=_mock_socket([None]), - ), - ): - assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}}) - await hass.async_block_till_done() + assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}}) + await hass.async_block_till_done() - assert await network.async_get_source_ip(hass, MDNS_TARGET_IP) == "192.168.1.5" + assert await network.async_get_source_ip(hass, MDNS_TARGET_IP) == NO_LOOPBACK_IPADDR +@pytest.mark.usefixtures("mock_socket_no_loopback") async def test_async_get_ipv4_broadcast_addresses_default( hass: HomeAssistant, hass_storage: dict[str, Any] ) -> None: @@ -634,24 +535,15 @@ async def test_async_get_ipv4_broadcast_addresses_default( "data": {ATTR_CONFIGURED_ADAPTERS: ["eth1"]}, } - with ( - patch( - "homeassistant.components.network.util.socket.socket", - return_value=_mock_socket(["192.168.1.5"]), - ), - patch( - "homeassistant.components.network.util.ifaddr.get_adapters", - return_value=_generate_mock_adapters(), - ), - ): - assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}}) - await hass.async_block_till_done() + assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}}) + await hass.async_block_till_done() assert await network.async_get_ipv4_broadcast_addresses(hass) == { IPv4Address("255.255.255.255") } +@pytest.mark.usefixtures("mock_socket_loopback") async def test_async_get_ipv4_broadcast_addresses_multiple( hass: HomeAssistant, hass_storage: dict[str, Any] ) -> None: @@ -662,18 +554,8 @@ async def test_async_get_ipv4_broadcast_addresses_multiple( "data": {ATTR_CONFIGURED_ADAPTERS: ["eth1", "vtun0"]}, } - with ( - patch( - "homeassistant.components.network.util.socket.socket", - return_value=_mock_socket([_LOOPBACK_IPADDR]), - ), - patch( - "homeassistant.components.network.util.ifaddr.get_adapters", - return_value=_generate_mock_adapters(), - ), - ): - assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}}) - await hass.async_block_till_done() + assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}}) + await hass.async_block_till_done() assert await network.async_get_ipv4_broadcast_addresses(hass) == { IPv4Address("255.255.255.255"), @@ -682,6 +564,7 @@ async def test_async_get_ipv4_broadcast_addresses_multiple( } +@pytest.mark.usefixtures("mock_socket_no_loopback") async def test_async_get_source_ip_no_enabled_addresses( hass: HomeAssistant, hass_storage: dict[str, Any], caplog: pytest.LogCaptureFixture ) -> None: @@ -692,24 +575,23 @@ async def test_async_get_source_ip_no_enabled_addresses( "data": {ATTR_CONFIGURED_ADAPTERS: ["eth1"]}, } - with ( - patch( - "homeassistant.components.network.util.ifaddr.get_adapters", - return_value=[], - ), - patch( - "homeassistant.components.network.util.socket.socket", - return_value=_mock_socket(["192.168.1.5"]), - ), + with patch( + "homeassistant.components.network.util.ifaddr.get_adapters", + return_value=[], ): assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}}) await hass.async_block_till_done() - assert await network.async_get_source_ip(hass, MDNS_TARGET_IP) == "192.168.1.5" + assert ( + await network.async_get_source_ip(hass, MDNS_TARGET_IP) + == NO_LOOPBACK_IPADDR + ) assert "source address detection may be inaccurate" in caplog.text +@pytest.mark.parametrize("mock_socket", [[None]], indirect=True) +@pytest.mark.usefixtures("mock_socket") async def test_async_get_source_ip_cannot_be_determined_and_no_enabled_addresses( hass: HomeAssistant, hass_storage: dict[str, Any], caplog: pytest.LogCaptureFixture ) -> None: @@ -720,15 +602,9 @@ async def test_async_get_source_ip_cannot_be_determined_and_no_enabled_addresses "data": {ATTR_CONFIGURED_ADAPTERS: ["eth1"]}, } - with ( - patch( - "homeassistant.components.network.util.ifaddr.get_adapters", - return_value=[], - ), - patch( - "homeassistant.components.network.util.socket.socket", - return_value=_mock_socket([None]), - ), + with patch( + "homeassistant.components.network.util.ifaddr.get_adapters", + return_value=[], ): assert not await async_setup_component(hass, DOMAIN, {DOMAIN: {}}) await hass.async_block_till_done() @@ -753,7 +629,7 @@ async def test_async_get_source_ip_no_ip_loopback( ), patch( "homeassistant.components.network.util.socket.socket", - return_value=_mock_cond_socket(_LOOPBACK_IPADDR), + return_value=_mock_cond_socket(LOOPBACK_IPADDR), ), ): assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}})