Use fixtures for Network component tests (#135220)

This commit is contained in:
Abílio Costa 2025-01-20 14:19:17 +00:00 committed by GitHub
parent 63d294e58e
commit 3e716a1308
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 115 additions and 181 deletions

View File

@ -1 +1,4 @@
"""Tests for the Network Configuration integration.""" """Tests for the Network Configuration integration."""
NO_LOOPBACK_IPADDR = "192.168.1.5"
LOOPBACK_IPADDR = "127.0.0.1"

View File

@ -1,14 +1,51 @@
"""Tests for the Network Configuration integration.""" """Tests for the Network Configuration integration."""
from collections.abc import Generator from collections.abc import Generator
from unittest.mock import _patch from unittest.mock import MagicMock, Mock, _patch, patch
import ifaddr
import pytest 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) @pytest.fixture(autouse=True)
def mock_network(): def mock_network() -> Generator[None]:
"""Override mock of network util's async_get_adapters.""" """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) @pytest.fixture(autouse=True)
@ -19,3 +56,21 @@ def override_mock_get_source_ip(
mock_get_source_ip.stop() mock_get_source_ip.stop()
yield yield
mock_get_source_ip.start() 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])

View File

@ -4,7 +4,6 @@ from ipaddress import IPv4Address
from typing import Any from typing import Any
from unittest.mock import MagicMock, Mock, patch from unittest.mock import MagicMock, Mock, patch
import ifaddr
import pytest import pytest
from homeassistant.components import network from homeassistant.components import network
@ -20,17 +19,10 @@ from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
from homeassistant.setup import async_setup_component from homeassistant.setup import async_setup_component
from . import LOOPBACK_IPADDR, NO_LOOPBACK_IPADDR
from tests.typing import WebSocketGenerator 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): def _mock_cond_socket(sockname):
class CondMockSock(MagicMock): class CondMockSock(MagicMock):
@ -54,40 +46,11 @@ def _mock_socket_exception(exc):
return mock_socket return mock_socket
def _generate_mock_adapters(): @pytest.mark.usefixtures("mock_socket_no_loopback")
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]
async def test_async_detect_interfaces_setting_non_loopback_route( async def test_async_detect_interfaces_setting_non_loopback_route(
hass: HomeAssistant, hass_storage: dict[str, Any] hass: HomeAssistant, hass_storage: dict[str, Any]
) -> None: ) -> None:
"""Test without default interface config and the route returns a non-loopback address.""" """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: {}}) assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}})
await hass.async_block_till_done() await hass.async_block_till_done()
@ -141,20 +104,11 @@ 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( async def test_async_detect_interfaces_setting_loopback_route(
hass: HomeAssistant, hass_storage: dict[str, Any] hass: HomeAssistant, hass_storage: dict[str, Any]
) -> None: ) -> None:
"""Test without default interface config and the route returns a loopback address.""" """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: {}}) assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}})
await hass.async_block_till_done() await hass.async_block_till_done()
@ -207,20 +161,12 @@ 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( async def test_async_detect_interfaces_setting_empty_route(
hass: HomeAssistant, hass_storage: dict[str, Any] hass: HomeAssistant, hass_storage: dict[str, Any]
) -> None: ) -> None:
"""Test without default interface config and the route returns nothing.""" """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: {}}) assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}})
await hass.async_block_till_done() await hass.async_block_till_done()
@ -277,15 +223,9 @@ async def test_async_detect_interfaces_setting_exception(
hass: HomeAssistant, hass_storage: dict[str, Any] hass: HomeAssistant, hass_storage: dict[str, Any]
) -> None: ) -> None:
"""Test without default interface config and the route throws an exception.""" """Test without default interface config and the route throws an exception."""
with ( with patch(
patch(
"homeassistant.components.network.util.socket.socket", "homeassistant.components.network.util.socket.socket",
return_value=_mock_socket_exception(AttributeError), return_value=_mock_socket_exception(AttributeError),
),
patch(
"homeassistant.components.network.util.ifaddr.get_adapters",
return_value=_generate_mock_adapters(),
),
): ):
assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}}) assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}})
await hass.async_block_till_done() 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( async def test_interfaces_configured_from_storage(
hass: HomeAssistant, hass_storage: dict[str, Any] hass: HomeAssistant, hass_storage: dict[str, Any]
) -> None: ) -> None:
@ -348,16 +289,7 @@ async def test_interfaces_configured_from_storage(
"key": STORAGE_KEY, "key": STORAGE_KEY,
"data": {ATTR_CONFIGURED_ADAPTERS: ["eth0", "eth1", "vtun0"]}, "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: {}}) assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}})
await hass.async_block_till_done() await hass.async_block_till_done()
@ -422,15 +354,9 @@ async def test_interfaces_configured_from_storage_websocket_update(
"key": STORAGE_KEY, "key": STORAGE_KEY,
"data": {ATTR_CONFIGURED_ADAPTERS: ["eth0", "eth1", "vtun0"]}, "data": {ATTR_CONFIGURED_ADAPTERS: ["eth0", "eth1", "vtun0"]},
} }
with ( with patch(
patch(
"homeassistant.components.network.util.socket.socket", "homeassistant.components.network.util.socket.socket",
return_value=_mock_socket([_NO_LOOPBACK_IPADDR]), return_value=MagicMock(getsockname=Mock(return_value=[NO_LOOPBACK_IPADDR])),
),
patch(
"homeassistant.components.network.util.ifaddr.get_adapters",
return_value=_generate_mock_adapters(),
),
): ):
assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}}) assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}})
await hass.async_block_till_done() 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( async def test_async_get_source_ip_matching_interface(
hass: HomeAssistant, hass_storage: dict[str, Any] hass: HomeAssistant, hass_storage: dict[str, Any]
) -> None: ) -> None:
@ -556,22 +483,13 @@ async def test_async_get_source_ip_matching_interface(
"data": {ATTR_CONFIGURED_ADAPTERS: ["eth1"]}, "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: {}}) assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}})
await hass.async_block_till_done() 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( async def test_async_get_source_ip_interface_not_match(
hass: HomeAssistant, hass_storage: dict[str, Any] hass: HomeAssistant, hass_storage: dict[str, Any]
) -> None: ) -> None:
@ -582,22 +500,14 @@ async def test_async_get_source_ip_interface_not_match(
"data": {ATTR_CONFIGURED_ADAPTERS: ["vtun0"]}, "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: {}}) assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}})
await hass.async_block_till_done() 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( async def test_async_get_source_ip_cannot_determine_target(
hass: HomeAssistant, hass_storage: dict[str, Any] hass: HomeAssistant, hass_storage: dict[str, Any]
) -> None: ) -> None:
@ -608,22 +518,13 @@ async def test_async_get_source_ip_cannot_determine_target(
"data": {ATTR_CONFIGURED_ADAPTERS: ["eth1"]}, "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: {}}) assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}})
await hass.async_block_till_done() 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( async def test_async_get_ipv4_broadcast_addresses_default(
hass: HomeAssistant, hass_storage: dict[str, Any] hass: HomeAssistant, hass_storage: dict[str, Any]
) -> None: ) -> None:
@ -634,16 +535,6 @@ async def test_async_get_ipv4_broadcast_addresses_default(
"data": {ATTR_CONFIGURED_ADAPTERS: ["eth1"]}, "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: {}}) assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}})
await hass.async_block_till_done() await hass.async_block_till_done()
@ -652,6 +543,7 @@ async def test_async_get_ipv4_broadcast_addresses_default(
} }
@pytest.mark.usefixtures("mock_socket_loopback")
async def test_async_get_ipv4_broadcast_addresses_multiple( async def test_async_get_ipv4_broadcast_addresses_multiple(
hass: HomeAssistant, hass_storage: dict[str, Any] hass: HomeAssistant, hass_storage: dict[str, Any]
) -> None: ) -> None:
@ -662,16 +554,6 @@ async def test_async_get_ipv4_broadcast_addresses_multiple(
"data": {ATTR_CONFIGURED_ADAPTERS: ["eth1", "vtun0"]}, "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: {}}) assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}})
await hass.async_block_till_done() await hass.async_block_till_done()
@ -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( async def test_async_get_source_ip_no_enabled_addresses(
hass: HomeAssistant, hass_storage: dict[str, Any], caplog: pytest.LogCaptureFixture hass: HomeAssistant, hass_storage: dict[str, Any], caplog: pytest.LogCaptureFixture
) -> None: ) -> None:
@ -692,24 +575,23 @@ async def test_async_get_source_ip_no_enabled_addresses(
"data": {ATTR_CONFIGURED_ADAPTERS: ["eth1"]}, "data": {ATTR_CONFIGURED_ADAPTERS: ["eth1"]},
} }
with ( with patch(
patch(
"homeassistant.components.network.util.ifaddr.get_adapters", "homeassistant.components.network.util.ifaddr.get_adapters",
return_value=[], return_value=[],
),
patch(
"homeassistant.components.network.util.socket.socket",
return_value=_mock_socket(["192.168.1.5"]),
),
): ):
assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}}) assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}})
await hass.async_block_till_done() 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 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( async def test_async_get_source_ip_cannot_be_determined_and_no_enabled_addresses(
hass: HomeAssistant, hass_storage: dict[str, Any], caplog: pytest.LogCaptureFixture hass: HomeAssistant, hass_storage: dict[str, Any], caplog: pytest.LogCaptureFixture
) -> None: ) -> None:
@ -720,15 +602,9 @@ async def test_async_get_source_ip_cannot_be_determined_and_no_enabled_addresses
"data": {ATTR_CONFIGURED_ADAPTERS: ["eth1"]}, "data": {ATTR_CONFIGURED_ADAPTERS: ["eth1"]},
} }
with ( with patch(
patch(
"homeassistant.components.network.util.ifaddr.get_adapters", "homeassistant.components.network.util.ifaddr.get_adapters",
return_value=[], return_value=[],
),
patch(
"homeassistant.components.network.util.socket.socket",
return_value=_mock_socket([None]),
),
): ):
assert not await async_setup_component(hass, DOMAIN, {DOMAIN: {}}) assert not await async_setup_component(hass, DOMAIN, {DOMAIN: {}})
await hass.async_block_till_done() await hass.async_block_till_done()
@ -753,7 +629,7 @@ async def test_async_get_source_ip_no_ip_loopback(
), ),
patch( patch(
"homeassistant.components.network.util.socket.socket", "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: {}}) assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}})