|
18 | 18 | from zeroconf import DNSAddress, RecordUpdate, const |
19 | 19 | from zeroconf._protocol.outgoing import DNSOutgoing |
20 | 20 | 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 |
22 | 23 | from zeroconf._utils.net import IPVersion |
23 | 24 | from zeroconf.asyncio import AsyncZeroconf |
24 | 25 |
|
@@ -877,6 +878,65 @@ async def test_scoped_address_replaces_unscoped_in_live_update(): |
877 | 878 | await aiozc.async_close() |
878 | 879 |
|
879 | 880 |
|
| 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 | + |
880 | 940 | # This test uses asyncio because it needs to access the cache directly |
881 | 941 | # which is not threadsafe |
882 | 942 | @pytest.mark.asyncio |
|
0 commit comments