1
0
mirror of https://github.com/esphome/esphome.git synced 2025-09-06 05:12:21 +01:00

fix, cover

This commit is contained in:
J. Nick Koston
2025-09-04 20:16:30 -05:00
parent a282920d7c
commit 2d37518c00
3 changed files with 55 additions and 33 deletions

View File

@@ -34,7 +34,7 @@ class AsyncResolver:
self.result = await hr.async_resolve_host(
hosts, port, timeout=RESOLVE_TIMEOUT
)
except Exception as e:
except (ResolveAPIError, ResolveTimeoutAPIError, OSError) as e:
self.exception = e
finally:
self.event.set()

View File

@@ -285,7 +285,7 @@ def test_sort_ip_addresses(text: list[str], expected: list[str]) -> None:
# DNS resolution tests
def test_is_ip_address_ipv4():
def test_is_ip_address_ipv4() -> None:
"""Test is_ip_address with IPv4 addresses."""
assert helpers.is_ip_address("192.168.1.1") is True
assert helpers.is_ip_address("127.0.0.1") is True
@@ -293,7 +293,7 @@ def test_is_ip_address_ipv4():
assert helpers.is_ip_address("0.0.0.0") is True
def test_is_ip_address_ipv6():
def test_is_ip_address_ipv6() -> None:
"""Test is_ip_address with IPv6 addresses."""
assert helpers.is_ip_address("::1") is True
assert helpers.is_ip_address("2001:db8::1") is True
@@ -301,7 +301,7 @@ def test_is_ip_address_ipv6():
assert helpers.is_ip_address("::") is True
def test_is_ip_address_invalid():
def test_is_ip_address_invalid() -> None:
"""Test is_ip_address with non-IP strings."""
assert helpers.is_ip_address("hostname") is False
assert helpers.is_ip_address("hostname.local") is False
@@ -310,26 +310,38 @@ def test_is_ip_address_invalid():
assert helpers.is_ip_address("") is False
def test_resolve_ip_address_single_ipv4():
def test_resolve_ip_address_single_ipv4() -> None:
"""Test resolving a single IPv4 address (fast path)."""
result = helpers.resolve_ip_address("192.168.1.100", 6053)
assert len(result) == 1
assert result[0][0] == socket.AF_INET # family
assert result[0][1] == socket.SOCK_STREAM # type
assert result[0][2] == socket.IPPROTO_TCP # proto
assert result[0][1] in (
0,
socket.SOCK_STREAM,
) # type (0 on Windows with AI_NUMERICHOST)
assert result[0][2] in (
0,
socket.IPPROTO_TCP,
) # proto (0 on Windows with AI_NUMERICHOST)
assert result[0][3] == "" # canonname
assert result[0][4] == ("192.168.1.100", 6053) # sockaddr
def test_resolve_ip_address_single_ipv6():
def test_resolve_ip_address_single_ipv6() -> None:
"""Test resolving a single IPv6 address (fast path)."""
result = helpers.resolve_ip_address("::1", 6053)
assert len(result) == 1
assert result[0][0] == socket.AF_INET6 # family
assert result[0][1] == socket.SOCK_STREAM # type
assert result[0][2] == socket.IPPROTO_TCP # proto
assert result[0][1] in (
0,
socket.SOCK_STREAM,
) # type (0 on Windows with AI_NUMERICHOST)
assert result[0][2] in (
0,
socket.IPPROTO_TCP,
) # proto (0 on Windows with AI_NUMERICHOST)
assert result[0][3] == "" # canonname
# IPv6 sockaddr has 4 elements
assert len(result[0][4]) == 4
@@ -337,7 +349,7 @@ def test_resolve_ip_address_single_ipv6():
assert result[0][4][1] == 6053 # port
def test_resolve_ip_address_list_of_ips():
def test_resolve_ip_address_list_of_ips() -> None:
"""Test resolving a list of IP addresses (fast path)."""
ips = ["192.168.1.100", "10.0.0.1", "::1"]
result = helpers.resolve_ip_address(ips, 6053)
@@ -348,12 +360,18 @@ def test_resolve_ip_address_list_of_ips():
# Check that results are properly formatted
for addr_info in result:
assert addr_info[0] in (socket.AF_INET, socket.AF_INET6)
assert addr_info[1] == socket.SOCK_STREAM
assert addr_info[2] == socket.IPPROTO_TCP
assert addr_info[1] in (
0,
socket.SOCK_STREAM,
) # 0 on Windows with AI_NUMERICHOST
assert addr_info[2] in (
0,
socket.IPPROTO_TCP,
) # 0 on Windows with AI_NUMERICHOST
assert addr_info[3] == ""
def test_resolve_ip_address_hostname():
def test_resolve_ip_address_hostname() -> None:
"""Test resolving a hostname (async resolver path)."""
mock_addr_info = AddrInfo(
family=socket.AF_INET,
@@ -374,7 +392,7 @@ def test_resolve_ip_address_hostname():
mock_resolver.run.assert_called_once_with(["test.local"], 6053)
def test_resolve_ip_address_mixed_list():
def test_resolve_ip_address_mixed_list() -> None:
"""Test resolving a mix of IPs and hostnames."""
mock_addr_info = AddrInfo(
family=socket.AF_INET,
@@ -395,7 +413,7 @@ def test_resolve_ip_address_mixed_list():
mock_resolver.run.assert_called_once_with(["192.168.1.100", "test.local"], 6053)
def test_resolve_ip_address_url():
def test_resolve_ip_address_url() -> None:
"""Test extracting hostname from URL."""
mock_addr_info = AddrInfo(
family=socket.AF_INET,
@@ -414,7 +432,7 @@ def test_resolve_ip_address_url():
mock_resolver.run.assert_called_once_with(["test.local"], 6053)
def test_resolve_ip_address_ipv6_conversion():
def test_resolve_ip_address_ipv6_conversion() -> None:
"""Test proper IPv6 address info conversion."""
mock_addr_info = AddrInfo(
family=socket.AF_INET6,
@@ -434,7 +452,7 @@ def test_resolve_ip_address_ipv6_conversion():
assert result[0][4] == ("2001:db8::1", 6053, 1, 2)
def test_resolve_ip_address_error_handling():
def test_resolve_ip_address_error_handling() -> None:
"""Test error handling from AsyncResolver."""
with patch("esphome.resolver.AsyncResolver") as MockResolver:
mock_resolver = MockResolver.return_value
@@ -444,7 +462,7 @@ def test_resolve_ip_address_error_handling():
helpers.resolve_ip_address("test.local", 6053)
def test_addr_preference_ipv4():
def test_addr_preference_ipv4() -> None:
"""Test address preference for IPv4."""
addr_info = (
socket.AF_INET,
@@ -456,7 +474,7 @@ def test_addr_preference_ipv4():
assert helpers.addr_preference_(addr_info) == 2
def test_addr_preference_ipv6():
def test_addr_preference_ipv6() -> None:
"""Test address preference for regular IPv6."""
addr_info = (
socket.AF_INET6,
@@ -468,7 +486,7 @@ def test_addr_preference_ipv6():
assert helpers.addr_preference_(addr_info) == 1
def test_addr_preference_ipv6_link_local_no_scope():
def test_addr_preference_ipv6_link_local_no_scope() -> None:
"""Test address preference for link-local IPv6 without scope."""
addr_info = (
socket.AF_INET6,
@@ -480,7 +498,7 @@ def test_addr_preference_ipv6_link_local_no_scope():
assert helpers.addr_preference_(addr_info) == 3
def test_addr_preference_ipv6_link_local_with_scope():
def test_addr_preference_ipv6_link_local_with_scope() -> None:
"""Test address preference for link-local IPv6 with scope."""
addr_info = (
socket.AF_INET6,
@@ -492,7 +510,7 @@ def test_addr_preference_ipv6_link_local_with_scope():
assert helpers.addr_preference_(addr_info) == 1 # Has scope, so it's usable
def test_resolve_ip_address_sorting():
def test_resolve_ip_address_sorting() -> None:
"""Test that results are sorted by preference."""
# Create multiple address infos with different preferences
mock_addr_infos = [

View File

@@ -15,7 +15,7 @@ from esphome.resolver import RESOLVE_TIMEOUT, AsyncResolver
@pytest.fixture
def mock_addr_info_ipv4():
def mock_addr_info_ipv4() -> AddrInfo:
"""Create a mock IPv4 AddrInfo."""
return AddrInfo(
family=socket.AF_INET,
@@ -26,7 +26,7 @@ def mock_addr_info_ipv4():
@pytest.fixture
def mock_addr_info_ipv6():
def mock_addr_info_ipv6() -> AddrInfo:
"""Create a mock IPv6 AddrInfo."""
return AddrInfo(
family=socket.AF_INET6,
@@ -36,7 +36,7 @@ def mock_addr_info_ipv6():
)
def test_async_resolver_successful_resolution(mock_addr_info_ipv4):
def test_async_resolver_successful_resolution(mock_addr_info_ipv4: AddrInfo) -> None:
"""Test successful DNS resolution."""
with patch(
"esphome.resolver.hr.async_resolve_host",
@@ -51,7 +51,9 @@ def test_async_resolver_successful_resolution(mock_addr_info_ipv4):
)
def test_async_resolver_multiple_hosts(mock_addr_info_ipv4, mock_addr_info_ipv6):
def test_async_resolver_multiple_hosts(
mock_addr_info_ipv4: AddrInfo, mock_addr_info_ipv6: AddrInfo
) -> None:
"""Test resolving multiple hosts."""
mock_results = [mock_addr_info_ipv4, mock_addr_info_ipv6]
@@ -68,7 +70,7 @@ def test_async_resolver_multiple_hosts(mock_addr_info_ipv4, mock_addr_info_ipv6)
)
def test_async_resolver_resolve_api_error():
def test_async_resolver_resolve_api_error() -> None:
"""Test handling of ResolveAPIError."""
error_msg = "Failed to resolve"
with patch(
@@ -82,7 +84,7 @@ def test_async_resolver_resolve_api_error():
resolver.run(["test.local"], 6053)
def test_async_resolver_timeout_error():
def test_async_resolver_timeout_error() -> None:
"""Test handling of ResolveTimeoutAPIError."""
error_msg = "Resolution timed out"
with patch(
@@ -96,7 +98,7 @@ def test_async_resolver_timeout_error():
resolver.run(["test.local"], 6053)
def test_async_resolver_generic_exception():
def test_async_resolver_generic_exception() -> None:
"""Test handling of generic exceptions."""
error = RuntimeError("Unexpected error")
with patch(
@@ -108,7 +110,7 @@ def test_async_resolver_generic_exception():
resolver.run(["test.local"], 6053)
def test_async_resolver_thread_timeout():
def test_async_resolver_thread_timeout() -> None:
"""Test timeout when thread doesn't complete in time."""
async def slow_resolve(hosts, port, timeout):
@@ -125,7 +127,7 @@ def test_async_resolver_thread_timeout():
resolver.run(["test.local"], 6053)
def test_async_resolver_ip_addresses(mock_addr_info_ipv4):
def test_async_resolver_ip_addresses(mock_addr_info_ipv4: AddrInfo) -> None:
"""Test resolving IP addresses."""
with patch(
"esphome.resolver.hr.async_resolve_host",
@@ -140,7 +142,9 @@ def test_async_resolver_ip_addresses(mock_addr_info_ipv4):
)
def test_async_resolver_mixed_addresses(mock_addr_info_ipv4, mock_addr_info_ipv6):
def test_async_resolver_mixed_addresses(
mock_addr_info_ipv4: AddrInfo, mock_addr_info_ipv6: AddrInfo
) -> None:
"""Test resolving mix of hostnames and IP addresses."""
mock_results = [mock_addr_info_ipv4, mock_addr_info_ipv6]