mirror of
https://github.com/esphome/esphome.git
synced 2026-10-01 17:00:21 +00:00
[core] Ignore empty CLI address cache entries (#19924)
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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"]})
|
||||
|
||||
Reference in New Issue
Block a user