diff --git a/esphome/core/__init__.py b/esphome/core/__init__.py index 0d4ddf56d4..476ff1c618 100644 --- a/esphome/core/__init__.py +++ b/esphome/core/__init__.py @@ -6,6 +6,9 @@ import os import re from typing import TYPE_CHECKING +if TYPE_CHECKING: + from esphome.address_cache import AddressCache + from esphome.const import ( CONF_COMMENT, CONF_ESPHOME, @@ -584,7 +587,7 @@ class EsphomeCore: # The current component being processed during validation self.current_component: str | None = None # Address cache for DNS and mDNS lookups from command line arguments - self.address_cache: object | None = None + self.address_cache: AddressCache | None = None def reset(self): from esphome.pins import PIN_SCHEMA_REGISTRY diff --git a/esphome/dashboard/web_server.py b/esphome/dashboard/web_server.py index 767144fd19..ff92fea958 100644 --- a/esphome/dashboard/web_server.py +++ b/esphome/dashboard/web_server.py @@ -364,14 +364,16 @@ class EsphomePortCommandWebSocket(EsphomeCommandWebSocket): addresses.extend(sort_ip_addresses(cached)) dns_cache_entries[entry.name] = set(cached) - # Build cache arguments to pass to CLI + # Build cache arguments to pass to CLI (normalize hostnames) for hostname, addrs in dns_cache_entries.items(): + normalized = hostname.rstrip(".").lower() cache_args.extend( - ["--dns-lookup-cache", f"{hostname}={','.join(sorted(addrs))}"] + ["--dns-lookup-cache", f"{normalized}={','.join(sorted(addrs))}"] ) for hostname, addrs in mdns_cache_entries.items(): + normalized = hostname.rstrip(".").lower() cache_args.extend( - ["--mdns-lookup-cache", f"{hostname}={','.join(sorted(addrs))}"] + ["--mdns-lookup-cache", f"{normalized}={','.join(sorted(addrs))}"] ) if not addresses: diff --git a/tests/unit_tests/test_main.py b/tests/unit_tests/test_main.py index ce19f18a1f..a00d8ce43a 100644 --- a/tests/unit_tests/test_main.py +++ b/tests/unit_tests/test_main.py @@ -581,3 +581,47 @@ def test_address_cache_get_methods() -> None: assert cache.has_cache() is True empty_cache = AddressCache() assert empty_cache.has_cache() is False + + +def test_address_cache_hostname_normalization() -> None: + """Test that hostnames are normalized for cache lookups.""" + from esphome.address_cache import normalize_hostname + + # Test normalize_hostname function + assert normalize_hostname("test.local") == "test.local" + assert normalize_hostname("test.local.") == "test.local" + assert normalize_hostname("TEST.LOCAL") == "test.local" + assert normalize_hostname("TeSt.LoCaL.") == "test.local" + assert normalize_hostname("example.com.") == "example.com" + + # Test cache with normalized lookups + cache = AddressCache( + mdns_cache={"test.local": ["192.168.1.1"]}, + dns_cache={"example.com": ["10.0.0.1"]}, + ) + + # Should find with different case and trailing dots + assert cache.get_mdns_addresses("test.local") == ["192.168.1.1"] + assert cache.get_mdns_addresses("TEST.LOCAL") == ["192.168.1.1"] + assert cache.get_mdns_addresses("test.local.") == ["192.168.1.1"] + assert cache.get_mdns_addresses("TEST.LOCAL.") == ["192.168.1.1"] + + assert cache.get_dns_addresses("example.com") == ["10.0.0.1"] + assert cache.get_dns_addresses("EXAMPLE.COM") == ["10.0.0.1"] + assert cache.get_dns_addresses("example.com.") == ["10.0.0.1"] + assert cache.get_dns_addresses("EXAMPLE.COM.") == ["10.0.0.1"] + + # Test from_cli_args also normalizes + cache = AddressCache.from_cli_args( + ["TEST.LOCAL.=192.168.1.1"], ["EXAMPLE.COM.=10.0.0.1"] + ) + + # Should store as normalized + assert "test.local" in cache.mdns_cache + assert "example.com" in cache.dns_cache + + # Should find with any variation + assert cache.get_addresses("test.local") == ["192.168.1.1"] + assert cache.get_addresses("TEST.LOCAL.") == ["192.168.1.1"] + assert cache.get_addresses("example.com") == ["10.0.0.1"] + assert cache.get_addresses("EXAMPLE.COM.") == ["10.0.0.1"]