Skip to content

Commit 60708f9

Browse files
committed
test: cover scope_id ipv6 dedupe partial branches
Add a cache LIFO case where a scoped AAAA is encountered before its unscoped twin so the unscoped variant fails the _has_more_scope_info check and the scoped entry stays in place. Add a direct call against _has_more_scope_info with an IPv4 address so its non-IPv6 short-circuit return is exercised.
1 parent 7a8102f commit 60708f9

1 file changed

Lines changed: 61 additions & 1 deletion

File tree

tests/services/test_info.py

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@
1818
from zeroconf import DNSAddress, RecordUpdate, const
1919
from zeroconf._protocol.outgoing import DNSOutgoing
2020
from zeroconf._services import info
21-
from zeroconf._services.info import ServiceInfo
21+
from zeroconf._services.info import ServiceInfo, _has_more_scope_info
22+
from zeroconf._utils.ipaddress import ZeroconfIPv4Address
2223
from zeroconf._utils.net import IPVersion
2324
from zeroconf.asyncio import AsyncZeroconf
2425

@@ -877,6 +878,65 @@ async def test_scoped_address_replaces_unscoped_in_live_update():
877878
await aiozc.async_close()
878879

879880

881+
def test_scoped_address_kept_when_unscoped_arrives_after_in_cache():
882+
"""Scoped AAAA seen first in iteration keeps its scope when an unscoped duplicate follows."""
883+
type_ = "_http._tcp.local."
884+
registration_name = f"scoped-after.{type_}"
885+
zeroconf = r.Zeroconf(interfaces=["127.0.0.1"])
886+
host = "scoped-after.local."
887+
packed = socket.inet_pton(socket.AF_INET6, "fe80::52e:c2f2:bc5f:e9c6")
888+
889+
zeroconf.cache.async_add_records(
890+
[
891+
r.DNSPointer(
892+
type_,
893+
const._TYPE_PTR,
894+
const._CLASS_IN | const._CLASS_UNIQUE,
895+
120,
896+
registration_name,
897+
),
898+
r.DNSService(
899+
registration_name,
900+
const._TYPE_SRV,
901+
const._CLASS_IN | const._CLASS_UNIQUE,
902+
120,
903+
0,
904+
0,
905+
80,
906+
host,
907+
),
908+
r.DNSAddress(
909+
host,
910+
const._TYPE_AAAA,
911+
const._CLASS_IN | const._CLASS_UNIQUE,
912+
120,
913+
packed,
914+
scope_id=5,
915+
),
916+
r.DNSAddress(
917+
host,
918+
const._TYPE_AAAA,
919+
const._CLASS_IN | const._CLASS_UNIQUE,
920+
120,
921+
packed,
922+
scope_id=None,
923+
),
924+
]
925+
)
926+
927+
info = ServiceInfo(type_, registration_name)
928+
info.load_from_cache(zeroconf)
929+
assert info.parsed_scoped_addresses() == ["fe80::52e:c2f2:bc5f:e9c6%5"]
930+
assert info.ip_addresses_by_version(r.IPVersion.V6Only) == [ip_address("fe80::52e:c2f2:bc5f:e9c6%5")]
931+
zeroconf.close()
932+
933+
934+
def test_has_more_scope_info_returns_false_for_ipv4():
935+
"""The scope_id helper short-circuits for IPv4 since A records carry no scope."""
936+
ip4 = ZeroconfIPv4Address("192.0.2.1")
937+
assert _has_more_scope_info(ip4, ip4) is False
938+
939+
880940
# This test uses asyncio because it needs to access the cache directly
881941
# which is not threadsafe
882942
@pytest.mark.asyncio

0 commit comments

Comments
 (0)