[core] Ignore empty CLI address cache entries (#19924)

This commit is contained in:
Panda
2026-09-30 12:41:52 +02:00
committed by GitHub
parent e8c5cd56a8
commit 6c43247395
3 changed files with 65 additions and 2 deletions
+9 -2
View File
@@ -148,6 +148,13 @@ class AddressCache:
continue
hostname, ips = arg.split("=", 1)
# Normalize hostname for consistent lookups
normalized = normalize_hostname(hostname)
cache[normalized] = [ip.strip() for ip in ips.split(",")]
normalized = normalize_hostname(hostname.strip())
addresses = [ip for value in ips.split(",") if (ip := value.strip())]
if not normalized or not addresses:
_LOGGER.warning(
"Invalid cache entry: %s (hostname and at least one address are required)",
arg,
)
continue
cache[normalized] = addresses
return cache
+36
View File
@@ -257,6 +257,42 @@ def test_from_cli_args_invalid_format(caplog: LogCaptureFixture) -> None:
assert "Invalid cache format: also_invalid" in caplog.text
@pytest.mark.parametrize(
"args,expected",
[
(["device.local="], {}),
(["device.local= , , "], {}),
(["=192.168.1.10"], {}),
(["...=192.168.1.10"], {}),
(
[" device.local. = , 192.168.1.10, , fe80::1, "],
{"device.local": ["192.168.1.10", "fe80::1"]},
),
],
)
def test_from_cli_args_discards_empty_addresses_and_hostnames(
args: list[str], expected: dict[str, list[str]], caplog: LogCaptureFixture
) -> None:
"""Only usable cache entries may prevent normal hostname resolution."""
cache = AddressCache.from_cli_args(args, args)
assert cache.mdns_cache == expected
assert cache.dns_cache == expected
assert cache.has_cache() == bool(expected)
if not expected:
assert cache.get_addresses("device.local") is None
assert "Invalid cache entry" in caplog.text
def test_invalid_cache_entry_does_not_replace_valid_entry() -> None:
"""A later empty entry must not remove an earlier usable address."""
args = ["device.local=192.168.1.10", "device.local=", "server.com=10.0.0.1"]
cache = AddressCache.from_cli_args(args, args)
assert cache.get_mdns_addresses("DEVICE.LOCAL.") == ["192.168.1.10"]
assert cache.get_dns_addresses("server.com") == ["10.0.0.1"]
def test_from_cli_args_ipv6() -> None:
"""Test handling of IPv6 addresses."""
mdns_args: list[str] = ["device.local=fe80::1,2001:db8::1"]
+20
View File
@@ -1129,6 +1129,26 @@ def test_resolve_ip_address_cache_miss() -> None:
assert result[0][4][0] == "192.168.1.100"
@pytest.mark.parametrize("hostname", ["test.local", "example.com"])
def test_resolve_ip_address_empty_cache_entry_falls_back(hostname: str) -> None:
"""An empty CLI cache entry must use normal DNS or mDNS resolution."""
cache = AddressCache.from_cli_args([f"{hostname}="], [f"{hostname}="])
mock_addr_info = AddrInfo(
family=socket.AF_INET,
type=socket.SOCK_STREAM,
proto=socket.IPPROTO_TCP,
sockaddr=IPv4Sockaddr(address="192.168.1.100", port=6053),
)
with patch("esphome.resolver.AsyncResolver") as MockResolver:
MockResolver.return_value.resolve.return_value = [mock_addr_info]
result = helpers.resolve_ip_address(hostname, 6053, address_cache=cache)
MockResolver.assert_called_once_with([hostname], 6053)
assert result[0][4][0] == "192.168.1.100"
def test_resolve_ip_address_mixed_cached_uncached() -> None:
"""Test resolution with mix of cached and uncached hosts."""
cache = AddressCache(mdns_cache={"cached.local": ["192.168.1.50"]})