Ctrl K
ComunidadGuías…

tests/test_mdns.py

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
import sys
import unittest
from unittest.mock import MagicMock, patch

from peach.mdns import (
    DnsSdPublisher, MdnsPublisher, answers_on_network, create_mdns_publisher, lan_ipv4,
)


class MdnsPublisherTests(unittest.TestCase):
    def test_registers_and_unregisters_peach_local_on_all_interfaces(self):
        with patch("peach.mdns.Zeroconf") as zeroconf_type, \
                patch("peach.mdns.answers_on_network", return_value=True):
            publisher = MdnsPublisher(
                "Peach.local", 80, address_resolver=lambda: "192.0.2.10"
            )
            publisher.start()
            info = zeroconf_type.return_value.register_service.call_args.args[0]
            self.assertEqual(info.server, "peach.local.")
            self.assertEqual(info.port, 80)
            self.assertEqual(publisher.status, "peach.local")
            self.assertEqual(publisher.backend, "zeroconf-all-interfaces")
            publisher.stop()
        zeroconf_type.assert_called_once_with()
        zeroconf_type.return_value.register_service.assert_called_once_with(
            info, allow_name_change=True,
        )
        zeroconf_type.return_value.unregister_service.assert_called_once_with(info)
        zeroconf_type.return_value.close.assert_called_once()

    def test_address_change_republishes_so_dhcp_moves_are_followed(self):
        """换 Wi-Fi、DHCP 换地址都只改地址、不通知进程。

        不复查的话 `peach.local` 会一直指向旧地址;zeroconf 的套接字也绑在旧接口上,
        所以是整体重建而不是只换地址字段。
        """
        addresses = iter(["192.0.2.10", "192.0.2.10", "198.51.100.7"])
        current = ["192.0.2.10"]

        def resolver():
            current[0] = next(addresses, current[0])
            return current[0]

        with patch("peach.mdns.Zeroconf") as zeroconf_type, \
                patch("peach.mdns.answers_on_network", return_value=True):
            publisher = MdnsPublisher(
                "peach", 80, address_resolver=resolver, refresh_seconds=0,
            )
            publisher.start()
            self.assertEqual(publisher.address, "192.0.2.10")
            self.assertEqual(publisher.refresh(), "unchanged")
            self.assertEqual(publisher.refresh(), "republished")
            self.assertEqual(publisher.address, "198.51.100.7")
            publisher.stop()
        self.assertEqual(zeroconf_type.call_count, 2)

    def test_losing_the_network_withdraws_the_record_instead_of_keeping_a_stale_one(self):
        """留着一条指向旧地址的记录比没有记录更糟:客户端会一直连一个不存在的地方。"""
        states = iter(["192.0.2.10"])

        def resolver():
            try:
                return next(states)
            except StopIteration:
                raise RuntimeError("no publishable LAN IPv4 address") from None

        with patch("peach.mdns.Zeroconf"), \
                patch("peach.mdns.answers_on_network", return_value=True):
            publisher = MdnsPublisher(
                "peach", 80, address_resolver=resolver, refresh_seconds=0,
            )
            publisher.start()
            self.assertEqual(publisher.status, "peach.local")
            self.assertEqual(publisher.refresh(), "unavailable")
            self.assertEqual(publisher.status, "unavailable")
            self.assertIsNone(publisher.address)

    def test_a_pinned_address_never_republishes(self):
        """生产上显式钉住地址时复查必须是空转,不能自己换成别的网卡。"""
        with patch("peach.mdns.Zeroconf") as zeroconf_type, \
                patch("peach.mdns.answers_on_network", return_value=True):
            publisher = MdnsPublisher(
                "peach", 80, address_resolver=lambda: "192.0.2.10", refresh_seconds=0,
            )
            publisher.start()
            self.assertEqual(publisher.refresh(), "unchanged")
            self.assertEqual(publisher.refresh(), "unchanged")
            publisher.stop()
        self.assertEqual(zeroconf_type.call_count, 1)

    def test_secure_publication_uses_https_service(self):
        with patch("peach.mdns.Zeroconf") as zeroconf_type, \
                patch("peach.mdns.answers_on_network", return_value=True):
            publisher = MdnsPublisher(
                "peach", 443, secure=True, address_resolver=lambda: "192.0.2.10"
            )
            publisher.start()
            info = zeroconf_type.return_value.register_service.call_args.args[0]
            self.assertEqual(info.type, "_https._tcp.local.")
            self.assertEqual(info.port, 443)
            publisher.stop()

    def test_rejects_invalid_hostname(self):
        with self.assertRaises(ValueError):
            MdnsPublisher("bad name", 80)

    def test_factory_picks_the_backend_that_works_on_this_platform(self):
        """macOS 走系统 mDNSResponder:自带 zeroconf 在 launchd 起的进程里会被
        「本地网络」隐私门静默拒绝,注册看着成功、一个多播包都发不出去。"""
        publisher = create_mdns_publisher("peach", 80)
        expected = DnsSdPublisher if sys.platform == "darwin" else MdnsPublisher
        self.assertIsInstance(publisher, expected)

    def test_explicit_address_avoids_tunnel_route(self):
        with patch("peach.mdns.Zeroconf") as zeroconf_type, \
                patch("peach.mdns.answers_on_network", return_value=True):
            publisher = MdnsPublisher(
                "peach", 80, address_resolver=lambda: "198.51.100.162", refresh_seconds=0,
            )
            publisher.start()
            self.assertEqual(publisher.address, "198.51.100.162")
            publisher.stop()
        zeroconf_type.assert_called_once_with()

    def test_tunnel_route_falls_back_to_real_host_interface(self):
        fake_socket = MagicMock()
        fake_socket.getsockname.return_value = ("198.18.0.1", 50000)
        with patch("peach.mdns.socket.socket", return_value=fake_socket), patch(
            "peach.mdns.socket.gethostname", return_value="host",
        ), patch(
            # 前两个是虚拟网卡那种网关端点(末段 1 或 2),`lan_ipv4` 按端点评分把它们
            # 排在真实接口后面,所以第三个胜出。
            "peach.mdns.socket.gethostbyname_ex",
            return_value=("host", [], ["192.0.2.2", "203.0.113.1", "198.51.100.162"]),
        ):
            self.assertEqual(lan_ipv4(), "198.51.100.162")


class ReachabilityTests(unittest.TestCase):
    """可达性不能由服务进程自证。

    macOS 的「本地网络」隐私门按进程判:launchd 起的作业自己发的多播会被静默丢弃,
    所以它去探自己必然收不到回应,会把好的记录判成不可达。运行时因此只报「注册还在
    生效」,真正的可达性验证交给 `scripts/check_mdns.py`——那个从终端跑,有权限。
    """

    def test_runtime_status_does_not_depend_on_a_self_probe(self):
        with patch("peach.mdns.Zeroconf"), \
                patch("peach.mdns.answers_on_network", return_value=False) as probe:
            publisher = MdnsPublisher(
                "peach", 80, address_resolver=lambda: "192.0.2.10", refresh_seconds=0)
            publisher.start()
            self.assertEqual(publisher.status, "peach.local")
            probe.assert_not_called()
            publisher.stop()

    def test_probe_never_raises_on_a_dead_socket(self):
        with patch("peach.mdns.socket.socket", side_effect=OSError("no multicast")):
            self.assertFalse(answers_on_network("peach.local", timeout=0.1))


if __name__ == "__main__":
    unittest.main()