Skip to content

Commit dcc8bbb

Browse files
committed
feat: add unit and integration tests for mDNS.
1 parent b258ff3 commit dcc8bbb

File tree

7 files changed

+520
-0
lines changed

7 files changed

+520
-0
lines changed

tests/discovery/__init__.py

Whitespace-only changes.

tests/discovery/mdns/__init__.py

Whitespace-only changes.
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
"""
2+
Unit tests for mDNS broadcaster component.
3+
"""
4+
import socket
5+
import pytest
6+
from zeroconf import ServiceInfo, Zeroconf
7+
8+
from libp2p.discovery.mdns.broadcaster import PeerBroadcaster
9+
from libp2p.peer.id import ID
10+
11+
12+
class TestPeerBroadcaster:
13+
"""Basic unit tests for PeerBroadcaster."""
14+
15+
def test_broadcaster_initialization(self):
16+
"""Test that broadcaster initializes correctly."""
17+
zeroconf = Zeroconf()
18+
service_type = "_p2p._udp.local."
19+
service_name = "test-peer._p2p._udp.local."
20+
peer_id = "QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN" # String, not ID object
21+
port = 8000
22+
23+
broadcaster = PeerBroadcaster(
24+
zeroconf=zeroconf,
25+
service_type=service_type,
26+
service_name=service_name,
27+
peer_id=peer_id,
28+
port=port
29+
)
30+
31+
assert broadcaster.zeroconf == zeroconf
32+
assert broadcaster.service_type == service_type
33+
assert broadcaster.service_name == service_name
34+
assert broadcaster.peer_id == peer_id
35+
assert broadcaster.port == port
36+
37+
# Clean up
38+
zeroconf.close()
39+
40+
def test_broadcaster_service_creation(self):
41+
"""Test that broadcaster creates valid service info."""
42+
zeroconf = Zeroconf()
43+
service_type = "_p2p._udp.local."
44+
service_name = "test-peer2._p2p._udp.local."
45+
peer_id_obj = ID.from_base58("QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN")
46+
peer_id = str(peer_id_obj) # Convert to string
47+
port = 8000
48+
49+
broadcaster = PeerBroadcaster(
50+
zeroconf=zeroconf,
51+
service_type=service_type,
52+
service_name=service_name,
53+
peer_id=peer_id,
54+
port=port
55+
)
56+
57+
# Verify service was created and registered
58+
service_info = broadcaster.service_info
59+
assert service_info is not None
60+
assert service_info.type == service_type
61+
assert service_info.name == service_name
62+
assert service_info.port == port
63+
assert b"id" in service_info.properties
64+
assert service_info.properties[b"id"] == peer_id.encode()
65+
66+
# Clean up
67+
zeroconf.close()
68+
69+
def test_broadcaster_start_stop(self):
70+
"""Test that broadcaster can start and stop correctly."""
71+
zeroconf = Zeroconf()
72+
service_type = "_p2p._udp.local."
73+
service_name = "test-start-stop._p2p._udp.local."
74+
peer_id_obj = ID.from_base58("QmYyQSo1c1Ym7orWxLYvCrM2EmxFTANf8wXmmE7DWjhx5N")
75+
peer_id = str(peer_id_obj) # Convert to string
76+
port = 8001
77+
78+
broadcaster = PeerBroadcaster(
79+
zeroconf=zeroconf,
80+
service_type=service_type,
81+
service_name=service_name,
82+
peer_id=peer_id,
83+
port=port
84+
)
85+
86+
# Service should be registered
87+
assert broadcaster.service_info is not None
88+
89+
# Clean up
90+
zeroconf.close()
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
"""
2+
Basic integration tests for mDNS components.
3+
"""
4+
import socket
5+
import pytest
6+
from zeroconf import ServiceInfo, Zeroconf
7+
8+
from libp2p.discovery.mdns.broadcaster import PeerBroadcaster
9+
from libp2p.discovery.mdns.listener import PeerListener
10+
from libp2p.peer.id import ID
11+
from libp2p.peer.peerstore import PeerStore
12+
13+
14+
class TestMDNSIntegration:
15+
"""Basic integration tests for mDNS components."""
16+
17+
def test_broadcaster_listener_basic_integration(self):
18+
"""Test basic broadcaster and listener integration with actual service discovery."""
19+
import time
20+
21+
# Create two separate Zeroconf instances
22+
zeroconf1 = Zeroconf()
23+
zeroconf2 = Zeroconf()
24+
25+
try:
26+
# Set up broadcaster
27+
broadcaster_peer_id_obj = ID.from_base58("QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN")
28+
broadcaster_peer_id = str(broadcaster_peer_id_obj) # Convert to string
29+
broadcaster = PeerBroadcaster(
30+
zeroconf=zeroconf1,
31+
service_type="_p2p._udp.local.",
32+
service_name="broadcaster-peer._p2p._udp.local.",
33+
peer_id=broadcaster_peer_id,
34+
port=8000
35+
)
36+
37+
# Set up listener
38+
peerstore = PeerStore()
39+
listener = PeerListener(
40+
peerstore=peerstore,
41+
zeroconf=zeroconf2,
42+
service_type="_p2p._udp.local.",
43+
service_name="listener-peer._p2p._udp.local.",
44+
)
45+
46+
# Verify initial state
47+
assert broadcaster.service_info is not None
48+
assert listener.discovered_services == {}
49+
assert len(peerstore.peer_ids()) == 0
50+
51+
# Broadcaster registers its service
52+
broadcaster.register()
53+
54+
# Simulate discovery - listener discovers the broadcaster's service
55+
listener.add_service(
56+
zeroconf1, # Use broadcaster's zeroconf to find the service
57+
"_p2p._udp.local.",
58+
"broadcaster-peer._p2p._udp.local."
59+
)
60+
61+
# Verify that the listener discovered the broadcaster
62+
assert len(listener.discovered_services) > 0
63+
assert "broadcaster-peer._p2p._udp.local." in listener.discovered_services
64+
65+
# Verify the discovered peer ID matches what was broadcast
66+
discovered_peer_id = listener.discovered_services["broadcaster-peer._p2p._udp.local."]
67+
assert str(discovered_peer_id) == broadcaster_peer_id
68+
69+
# Verify the peer was added to the peerstore
70+
assert len(peerstore.peer_ids()) > 0
71+
assert discovered_peer_id in peerstore.peer_ids()
72+
73+
# Verify the addresses were correctly stored
74+
addrs = peerstore.addrs(discovered_peer_id)
75+
assert len(addrs) > 0
76+
# Should be TCP since we always use TCP protocol
77+
assert "/tcp/8000" in str(addrs[0])
78+
79+
print(f"✅ Integration test successful!")
80+
print(f" Broadcaster peer ID: {broadcaster_peer_id}")
81+
print(f" Discovered peer ID: {discovered_peer_id}")
82+
print(f" Discovered addresses: {[str(addr) for addr in addrs]}")
83+
84+
# Clean up
85+
broadcaster.unregister()
86+
87+
finally:
88+
zeroconf1.close()
89+
zeroconf2.close()
90+
91+
def test_service_info_extraction(self):
92+
"""Test service info extraction functionality."""
93+
peerstore = PeerStore()
94+
zeroconf = Zeroconf()
95+
96+
try:
97+
listener = PeerListener(
98+
peerstore=peerstore,
99+
zeroconf=zeroconf,
100+
service_type="_p2p._udp.local.",
101+
service_name="test-listener._p2p._udp.local.",
102+
)
103+
104+
# Create a test service info
105+
test_peer_id = ID.from_base58("QmYyQSo1c1Ym7orWxLYvCrM2EmxFTANf8wXmmE7DWjhx5N")
106+
hostname = socket.gethostname()
107+
108+
service_info = ServiceInfo(
109+
type_="_p2p._udp.local.",
110+
name="test-service._p2p._udp.local.",
111+
port=8001,
112+
properties={b"id": str(test_peer_id).encode()},
113+
server=f"{hostname}.local.",
114+
addresses=[socket.inet_aton("192.168.1.100")],
115+
)
116+
117+
# Test extraction
118+
peer_info = listener._extract_peer_info(service_info)
119+
120+
assert peer_info is not None
121+
assert peer_info.peer_id == test_peer_id
122+
assert len(peer_info.addrs) == 1
123+
assert "/tcp/8001" in str(peer_info.addrs[0])
124+
125+
# Clean up
126+
127+
finally:
128+
zeroconf.close()

tests/discovery/mdns/test_listener.py

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
"""
2+
Unit tests for mDNS listener component.
3+
"""
4+
import socket
5+
import pytest
6+
from zeroconf import ServiceInfo, Zeroconf
7+
8+
from libp2p.discovery.mdns.listener import PeerListener
9+
from libp2p.peer.id import ID
10+
from libp2p.peer.peerstore import PeerStore
11+
from libp2p.abc import Multiaddr
12+
13+
14+
class TestPeerListener:
15+
"""Basic unit tests for PeerListener."""
16+
17+
def test_listener_initialization(self):
18+
"""Test that listener initializes correctly."""
19+
peerstore = PeerStore()
20+
zeroconf = Zeroconf()
21+
service_type = "_p2p._udp.local."
22+
service_name = "local-peer._p2p._udp.local."
23+
24+
listener = PeerListener(
25+
peerstore=peerstore,
26+
zeroconf=zeroconf,
27+
service_type=service_type,
28+
service_name=service_name,
29+
)
30+
31+
assert listener.peerstore == peerstore
32+
assert listener.zeroconf == zeroconf
33+
assert listener.service_type == service_type
34+
assert listener.service_name == service_name
35+
assert listener.discovered_services == {}
36+
37+
# Clean up
38+
listener.stop()
39+
zeroconf.close()
40+
41+
def test_listener_extract_peer_info_success(self):
42+
"""Test successful PeerInfo extraction from ServiceInfo."""
43+
peerstore = PeerStore()
44+
zeroconf = Zeroconf()
45+
46+
listener = PeerListener(
47+
peerstore=peerstore,
48+
zeroconf=zeroconf,
49+
service_type="_p2p._udp.local.",
50+
service_name="local._p2p._udp.local.",
51+
)
52+
53+
# Create sample service info
54+
sample_peer_id = ID.from_base58("QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN")
55+
hostname = socket.gethostname()
56+
local_ip = "192.168.1.100"
57+
58+
sample_service_info = ServiceInfo(
59+
type_="_p2p._udp.local.",
60+
name="test-peer._p2p._udp.local.",
61+
port=8000,
62+
properties={b"id": str(sample_peer_id).encode()},
63+
server=f"{hostname}.local.",
64+
addresses=[socket.inet_aton(local_ip)],
65+
)
66+
67+
peer_info = listener._extract_peer_info(sample_service_info)
68+
69+
assert peer_info is not None
70+
assert isinstance(peer_info.peer_id, ID)
71+
assert len(peer_info.addrs) > 0
72+
assert all(isinstance(addr, Multiaddr) for addr in peer_info.addrs)
73+
74+
# Check that protocol is TCP since we always use TCP
75+
assert "/tcp/" in str(peer_info.addrs[0])
76+
77+
# Clean up
78+
listener.stop()
79+
zeroconf.close()
80+
81+
def test_listener_extract_peer_info_invalid_id(self):
82+
"""Test PeerInfo extraction fails with invalid peer ID."""
83+
peerstore = PeerStore()
84+
zeroconf = Zeroconf()
85+
86+
listener = PeerListener(
87+
peerstore=peerstore,
88+
zeroconf=zeroconf,
89+
service_type="_p2p._udp.local.",
90+
service_name="local._p2p._udp.local.",
91+
)
92+
93+
# Create service info with invalid peer ID
94+
hostname = socket.gethostname()
95+
local_ip = "192.168.1.100"
96+
97+
service_info = ServiceInfo(
98+
type_="_p2p._udp.local.",
99+
name="invalid-peer._p2p._udp.local.",
100+
port=8000,
101+
properties={b"id": b"invalid_peer_id_format"},
102+
server=f"{hostname}.local.",
103+
addresses=[socket.inet_aton(local_ip)],
104+
)
105+
106+
peer_info = listener._extract_peer_info(service_info)
107+
assert peer_info is None
108+
109+
# Clean up
110+
listener.stop()
111+
zeroconf.close()

0 commit comments

Comments
 (0)