1
0

test_mdns_discovery.py 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990
  1. """Unit tests for mDNS table discovery (modules/core/mdns_discovery)."""
  2. from modules.core.mdns_discovery import TableDiscovery, service_info_to_table
  3. class FakeServiceInfo:
  4. """Stub of zeroconf ServiceInfo exposing only what service_info_to_table reads."""
  5. def __init__(self, properties=None, addresses=None, port=8080):
  6. self.properties = properties or {}
  7. self._addresses = addresses if addresses is not None else ["192.168.68.130"]
  8. self.port = port
  9. def parsed_addresses(self):
  10. return self._addresses
  11. class TestServiceInfoToTable:
  12. def test_basic_conversion(self):
  13. info = FakeServiceInfo(
  14. properties={b"id": b"abc-123", b"name": b"Living Room", b"version": b"1.2.3"},
  15. addresses=["192.168.68.130"],
  16. port=8080,
  17. )
  18. table = service_info_to_table(info)
  19. assert table == {
  20. "id": "abc-123",
  21. "name": "Living Room",
  22. "url": "http://192.168.68.130:8080",
  23. "host": "192.168.68.130",
  24. "port": 8080,
  25. "version": "1.2.3",
  26. }
  27. def test_port_80_omitted_from_url(self):
  28. info = FakeServiceInfo(properties={b"id": b"abc"}, port=80)
  29. table = service_info_to_table(info)
  30. assert table["url"] == "http://192.168.68.130"
  31. def test_missing_id_returns_none(self):
  32. info = FakeServiceInfo(properties={b"name": b"No Id"})
  33. assert service_info_to_table(info) is None
  34. def test_ipv6_only_returns_none(self):
  35. info = FakeServiceInfo(properties={b"id": b"abc"}, addresses=["fe80::1"])
  36. assert service_info_to_table(info) is None
  37. def test_ipv4_preferred_over_ipv6(self):
  38. info = FakeServiceInfo(
  39. properties={b"id": b"abc"},
  40. addresses=["fe80::1", "192.168.68.130"],
  41. )
  42. assert service_info_to_table(info)["host"] == "192.168.68.130"
  43. def test_empty_name_defaults(self):
  44. info = FakeServiceInfo(properties={b"id": b"abc", b"name": b""})
  45. assert service_info_to_table(info)["name"] == "Dune Weaver"
  46. def test_string_properties_accepted(self):
  47. # zeroconf usually yields bytes, but tolerate str keys/values
  48. info = FakeServiceInfo(properties={"id": "abc", "name": "Str Table"})
  49. table = service_info_to_table(info)
  50. assert table["id"] == "abc"
  51. assert table["name"] == "Str Table"
  52. class TestTableDiscovery:
  53. def test_starts_empty_and_not_running(self):
  54. discovery = TableDiscovery()
  55. assert discovery.get_tables() == []
  56. assert not discovery.is_running
  57. def test_removed_service_is_evicted(self):
  58. discovery = TableDiscovery()
  59. discovery._discovered["svc-name"] = {"id": "abc", "name": "T", "url": "http://x"}
  60. try:
  61. from zeroconf import ServiceStateChange
  62. except ImportError:
  63. return # zeroconf not installed; eviction path can't run anyway
  64. discovery._on_service_state_change(
  65. None, "_dune-weaver._tcp.local.", "svc-name", ServiceStateChange.Removed
  66. )
  67. assert discovery.get_tables() == []
  68. def test_instance_label_sanitizes(self):
  69. assert TableDiscovery._instance_label("My.Table!") == "MyTable"
  70. assert TableDiscovery._instance_label("") == "Dune Weaver"
  71. assert len(TableDiscovery._instance_label("x" * 100)) == 40