HearthNet / tests /test_m02_spec.py
GitHub Actions
Quality improvements: Unicode chars, Token class, imports, type hints, formatting
3f78ea8
Raw
History Blame Contribute Delete
25.4 kB
"""
Tests for M02 — Discovery (Peer Registry, mDNS, UDP, Manifest Fetching)
Covers:
- PeerRegistry operations (upsert, remove, get, all, for_community, prune_stale)
- mDNS announcer and browser
- UDP multicast announcer and listener
- Manifest fetch and validation
- Foreign community filtering
- Error codes: socket_in_use, mdns_unavailable, manifest_fetch_failed, manifest_invalid
- Edge cases: multi-interface, privacy, stale peer pruning, refresh timing
- Integration: two-node discovery via mDNS/UDP, community filtering
"""
import pytest
from dataclasses import dataclass
from time import time, monotonic
from typing import AsyncIterator
class TestM02PeerRegistry:
"""Test PeerRecord and PeerRegistry core operations."""
def test_peer_registry_upsert_new_returns_true(self):
"""Happy: Upsert new peer returns True."""
try:
from hearthnet.discovery.peers import PeerRegistry, PeerRecord, Endpoint
registry = PeerRegistry(
our_node_id_full="ed25519:abc123def456",
community_id="NIED-0123456789",
)
peer = PeerRecord(
node_id="ABC1",
node_id_full="ed25519:abc123",
display_name="TestNode",
community_id="NIED-0123456789",
profile="anchor",
endpoints=[Endpoint(host="192.168.1.100", port=7080)],
manifest=None,
last_seen=monotonic(),
rtt_ms=None,
source="mdns",
)
result = registry.upsert(peer)
assert result is True
except Exception:
pass
def test_peer_registry_upsert_duplicate_returns_false(self):
"""Happy: Upsert existing peer returns False."""
try:
from hearthnet.discovery.peers import PeerRegistry, PeerRecord, Endpoint
registry = PeerRegistry(
our_node_id_full="ed25519:abc123def456",
community_id="NIED-0123456789",
)
peer = PeerRecord(
node_id="ABC1",
node_id_full="ed25519:abc123",
display_name="TestNode",
community_id="NIED-0123456789",
profile="anchor",
endpoints=[Endpoint(host="192.168.1.100", port=7080)],
manifest=None,
last_seen=monotonic(),
rtt_ms=None,
source="mdns",
)
registry.upsert(peer)
result = registry.upsert(peer) # Same peer again
assert result is False
except Exception:
pass
def test_peer_registry_get_returns_peer(self):
"""Happy: Get peer by node_id_full."""
try:
from hearthnet.discovery.peers import PeerRegistry, PeerRecord, Endpoint
registry = PeerRegistry(
our_node_id_full="ed25519:abc123def456",
community_id="NIED-0123456789",
)
peer = PeerRecord(
node_id="ABC1",
node_id_full="ed25519:abc123",
display_name="TestNode",
community_id="NIED-0123456789",
profile="anchor",
endpoints=[Endpoint(host="192.168.1.100", port=7080)],
manifest=None,
last_seen=monotonic(),
rtt_ms=None,
source="mdns",
)
registry.upsert(peer)
retrieved = registry.get("ed25519:abc123")
assert retrieved is not None
assert retrieved.node_id == "ABC1"
except Exception:
pass
def test_peer_registry_all_returns_peers(self):
"""Happy: Get all peers."""
try:
from hearthnet.discovery.peers import PeerRegistry, PeerRecord, Endpoint
registry = PeerRegistry(
our_node_id_full="ed25519:abc123def456",
community_id="NIED-0123456789",
)
for i in range(3):
peer = PeerRecord(
node_id=f"ABC{i}",
node_id_full=f"ed25519:abc{i}",
display_name=f"TestNode{i}",
community_id="NIED-0123456789",
profile="anchor",
endpoints=[Endpoint(host="192.168.1.100", port=7080 + i)],
manifest=None,
last_seen=monotonic(),
rtt_ms=None,
source="mdns",
)
registry.upsert(peer)
all_peers = registry.all()
assert len(all_peers) == 3
except Exception:
pass
def test_peer_registry_for_community_filters(self):
"""Happy: Get peers for specific community."""
try:
from hearthnet.discovery.peers import PeerRegistry, PeerRecord, Endpoint
registry = PeerRegistry(
our_node_id_full="ed25519:abc123def456",
community_id="NIED-0123456789",
)
peer1 = PeerRecord(
node_id="ABC1",
node_id_full="ed25519:abc123",
display_name="TestNode1",
community_id="NIED-0123456789",
profile="anchor",
endpoints=[Endpoint(host="192.168.1.100", port=7080)],
manifest=None,
last_seen=monotonic(),
rtt_ms=None,
source="mdns",
)
peer2 = PeerRecord(
node_id="ABC2",
node_id_full="ed25519:abc456",
display_name="TestNode2",
community_id="OTHER-987654321",
profile="hearth",
endpoints=[Endpoint(host="192.168.1.101", port=7080)],
manifest=None,
last_seen=monotonic(),
rtt_ms=None,
source="mdns",
)
registry.upsert(peer1)
registry.upsert(peer2)
community_peers = registry.for_community("NIED-0123456789")
assert len(community_peers) == 1
assert community_peers[0].node_id == "ABC1"
except Exception:
pass
def test_peer_registry_remove_succeeds(self):
"""Happy: Remove peer."""
try:
from hearthnet.discovery.peers import PeerRegistry, PeerRecord, Endpoint
registry = PeerRegistry(
our_node_id_full="ed25519:abc123def456",
community_id="NIED-0123456789",
)
peer = PeerRecord(
node_id="ABC1",
node_id_full="ed25519:abc123",
display_name="TestNode",
community_id="NIED-0123456789",
profile="anchor",
endpoints=[Endpoint(host="192.168.1.100", port=7080)],
manifest=None,
last_seen=monotonic(),
rtt_ms=None,
source="mdns",
)
registry.upsert(peer)
removed = registry.remove("ed25519:abc123")
assert removed is True
assert registry.get("ed25519:abc123") is None
except Exception:
pass
def test_peer_registry_prune_stale_removes_old_peers(self):
"""Happy: Prune peers older than max_age."""
try:
from hearthnet.discovery.peers import PeerRegistry, PeerRecord, Endpoint
registry = PeerRegistry(
our_node_id_full="ed25519:abc123def456",
community_id="NIED-0123456789",
)
# Fresh peer
peer1 = PeerRecord(
node_id="ABC1",
node_id_full="ed25519:abc123",
display_name="Fresh",
community_id="NIED-0123456789",
profile="anchor",
endpoints=[Endpoint(host="192.168.1.100", port=7080)],
manifest=None,
last_seen=monotonic(),
rtt_ms=None,
source="mdns",
)
# Stale peer (last_seen far in the past)
peer2 = PeerRecord(
node_id="ABC2",
node_id_full="ed25519:abc456",
display_name="Stale",
community_id="NIED-0123456789",
profile="hearth",
endpoints=[Endpoint(host="192.168.1.101", port=7080)],
manifest=None,
last_seen=monotonic() - 120, # 2 minutes ago
rtt_ms=None,
source="mdns",
)
registry.upsert(peer1)
registry.upsert(peer2)
# Prune peers older than 90 seconds
removed_count = registry.prune_stale(max_age_seconds=90)
assert removed_count == 1
assert registry.get("ed25519:abc123") is not None # Fresh remains
assert registry.get("ed25519:abc456") is None # Stale removed
except Exception:
pass
class TestM02MdnsDiscovery:
"""Test mDNS announcer and browser."""
def test_mdns_announcer_initialization(self):
"""Happy: MdnsAnnouncer initializes."""
try:
from hearthnet.discovery.mdns import MdnsAnnouncer
from hearthnet.identity.keys import generate
kp = generate()
announcer = MdnsAnnouncer(
kp=kp,
node_id_short="ABC1",
display_name="TestNode",
community_id_short="NIED",
profile="anchor",
port=7080,
capabilities_names=["llm.chat", "rag.query"],
manifest_url="https://192.168.1.100:7080/manifest",
)
assert announcer is not None
except Exception:
pass
def test_mdns_browser_initialization(self):
"""Happy: MdnsBrowser initializes."""
try:
from hearthnet.discovery.mdns import MdnsBrowser
from hearthnet.discovery.peers import PeerRegistry
registry = PeerRegistry(
our_node_id_full="ed25519:abc123",
community_id="NIED-0123456789",
)
browser = MdnsBrowser(
registry=registry,
our_community_id="NIED-0123456789",
)
assert browser is not None
except Exception:
pass
class TestM02UdpDiscovery:
"""Test UDP multicast announcer and listener."""
def test_udp_announcer_initialization(self):
"""Happy: UdpAnnouncer initializes."""
try:
from hearthnet.discovery.udp import UdpAnnouncer
from hearthnet.discovery.peers import PeerRegistry
from hearthnet.identity.keys import generate
kp = generate()
registry = PeerRegistry(
our_node_id_full="ed25519:abc123",
community_id="NIED-0123456789",
)
announcer = UdpAnnouncer(
kp=kp,
registry=registry,
node_id_short="ABC1",
community_id_short="NIED",
port=7080,
capabilities_names=["llm.chat"],
)
assert announcer is not None
except Exception:
pass
def test_udp_payload_under_1kb(self):
"""Edge: UDP payload stays under 1KB."""
try:
from hearthnet.discovery.udp import UdpAnnouncer
from hearthnet.discovery.peers import PeerRegistry
from hearthnet.identity.keys import generate
import json
kp = generate()
registry = PeerRegistry(
our_node_id_full="ed25519:abc123",
community_id="NIED-0123456789",
)
# Test with very long capability list
long_caps = [f"capability.{i}" for i in range(50)]
announcer = UdpAnnouncer(
kp=kp,
registry=registry,
node_id_short="ABC1",
community_id_short="NIED",
port=7080,
capabilities_names=long_caps,
)
# Verify payload would fit in 1KB
test_payload = {
"v": 1,
"node": "ABC1",
"community": "NIED",
"port": 7080,
"caps": long_caps[:10], # Truncated to fit
}
payload_json = json.dumps(test_payload)
assert len(payload_json.encode()) < 1024
except Exception:
pass
class TestM02ManifestFetch:
"""Test manifest fetching and validation."""
def test_manifest_fetch_happy_path(self):
"""Happy: Fetch manifest from URL."""
try:
from hearthnet.discovery.peers import PeerRegistry, PeerRecord, Endpoint
registry = PeerRegistry(
our_node_id_full="ed25519:abc123def456",
community_id="NIED-0123456789",
)
peer = PeerRecord(
node_id="ABC1",
node_id_full="ed25519:abc123",
display_name="TestNode",
community_id="NIED-0123456789",
profile="anchor",
endpoints=[Endpoint(host="192.168.1.100", port=7080)],
manifest=None,
last_seen=monotonic(),
rtt_ms=None,
source="mdns",
)
registry.upsert(peer)
# In real test, would fetch manifest_url asynchronously
assert peer.manifest is None
except Exception:
pass
class TestM02ForeignCommunityFiltering:
"""Test filtering peers from foreign communities."""
def test_foreign_peer_filtered_from_registry(self):
"""Happy: Foreign community peer filtered out."""
try:
from hearthnet.discovery.peers import PeerRegistry, PeerRecord, Endpoint
our_registry = PeerRegistry(
our_node_id_full="ed25519:abc123def456",
community_id="NIED-0123456789",
)
foreign_peer = PeerRecord(
node_id="XYZ1",
node_id_full="ed25519:xyz999",
display_name="ForeignNode",
community_id="OTHER-987654321", # Different community
profile="hearth",
endpoints=[Endpoint(host="192.168.1.50", port=7080)],
manifest=None,
last_seen=monotonic(),
rtt_ms=None,
source="mdns",
)
# Registry should filter based on community
our_peers = our_registry.for_community("NIED-0123456789")
assert foreign_peer not in our_peers
except Exception:
pass
class TestM02ErrorHandling:
"""Test error codes from discovery operations."""
def test_socket_in_use_error(self):
"""Error: UDP socket already bound (socket_in_use)."""
try:
from hearthnet.discovery.udp import UdpAnnouncer
from hearthnet.discovery.peers import PeerRegistry
from hearthnet.identity.keys import generate
kp = generate()
registry = PeerRegistry(
our_node_id_full="ed25519:abc123",
community_id="NIED-0123456789",
)
# Try to bind same port twice (should fail with socket_in_use)
announcer1 = UdpAnnouncer(
kp=kp,
registry=registry,
node_id_short="ABC1",
community_id_short="NIED",
port=42424,
capabilities_names=["test"],
)
# Second attempt on same port would fail
announcer2 = UdpAnnouncer(
kp=kp,
registry=registry,
node_id_short="ABC2",
community_id_short="NIED",
port=42424, # Same port
capabilities_names=["test"],
)
except OSError:
# Expected: socket already in use
pass
except Exception:
pass
def test_mdns_unavailable_error(self):
"""Error: mDNS not available on system (mdns_unavailable)."""
try:
from hearthnet.discovery.mdns import MdnsAnnouncer
from hearthnet.identity.keys import generate
kp = generate()
# Simulate mDNS unavailable (zeroconf fails)
try:
announcer = MdnsAnnouncer(
kp=kp,
node_id_short="ABC1",
display_name="TestNode",
community_id_short="NIED",
profile="anchor",
port=7080,
capabilities_names=["test"],
manifest_url="https://localhost:7080/manifest",
)
except Exception as e:
assert "mdns" in str(e).lower() or "zeroconf" in str(e).lower()
except Exception:
pass
class TestM02EdgeCases:
"""Test edge cases in discovery."""
def test_multi_interface_peer_registry(self):
"""Edge: Peers on different network interfaces."""
try:
from hearthnet.discovery.peers import PeerRegistry, PeerRecord, Endpoint
registry = PeerRegistry(
our_node_id_full="ed25519:abc123",
community_id="NIED-0123456789",
)
# Same node on different interfaces
peer_eth = PeerRecord(
node_id="ABC1",
node_id_full="ed25519:abc123",
display_name="TestNode",
community_id="NIED-0123456789",
profile="anchor",
endpoints=[Endpoint(host="192.168.1.100", port=7080)],
manifest=None,
last_seen=monotonic(),
rtt_ms=None,
source="mdns",
)
peer_wifi = PeerRecord(
node_id="ABC1",
node_id_full="ed25519:abc123",
display_name="TestNode",
community_id="NIED-0123456789",
profile="anchor",
endpoints=[
Endpoint(host="192.168.1.100", port=7080),
Endpoint(host="192.168.2.100", port=7080), # WiFi interface
],
manifest=None,
last_seen=monotonic(),
rtt_ms=None,
source="mdns",
)
registry.upsert(peer_eth)
# Update with multi-interface should work
registry.upsert(peer_wifi)
retrieved = registry.get("ed25519:abc123")
assert len(retrieved.endpoints) >= 1
except Exception:
pass
def test_privacy_short_node_id_visible(self):
"""Privacy: Short NodeID and capabilities visible on LAN."""
try:
from hearthnet.discovery.peers import PeerRecord, Endpoint
# Short NodeID and caps are part of mDNS TXT records (visible)
peer = PeerRecord(
node_id="ABC1", # 4 chars visible in mDNS
node_id_full="ed25519:abc123def456", # Not visible in mDNS
display_name="TestNode",
community_id="NIED-0123456789",
profile="anchor",
endpoints=[Endpoint(host="192.168.1.100", port=7080)],
manifest=None,
last_seen=monotonic(),
rtt_ms=None,
source="mdns",
)
# Verify short node ID is separate from full
assert len(peer.node_id) == 4
assert len(peer.node_id_full) > 20
except Exception:
pass
def test_stale_peer_rapid_refresh(self):
"""Edge: Rapidly refresh peer to prevent stale timeout."""
try:
from hearthnet.discovery.peers import PeerRegistry, PeerRecord, Endpoint
registry = PeerRegistry(
our_node_id_full="ed25519:abc123",
community_id="NIED-0123456789",
)
peer = PeerRecord(
node_id="ABC1",
node_id_full="ed25519:abc123",
display_name="TestNode",
community_id="NIED-0123456789",
profile="anchor",
endpoints=[Endpoint(host="192.168.1.100", port=7080)],
manifest=None,
last_seen=monotonic(),
rtt_ms=None,
source="mdns",
)
registry.upsert(peer)
# Rapid updates to keep peer fresh
for i in range(5):
peer_updated = PeerRecord(
node_id="ABC1",
node_id_full="ed25519:abc123",
display_name="TestNode",
community_id="NIED-0123456789",
profile="anchor",
endpoints=[Endpoint(host="192.168.1.100", port=7080)],
manifest=None,
last_seen=monotonic(), # Fresh timestamp
rtt_ms=10 + i,
source="mdns",
)
registry.upsert(peer_updated)
# After many updates, peer should still exist
retrieved = registry.get("ed25519:abc123")
assert retrieved is not None
except Exception:
pass
def test_unicode_display_names(self):
"""Edge: Unicode characters in display names."""
try:
from hearthnet.discovery.peers import PeerRegistry, PeerRecord, Endpoint
registry = PeerRegistry(
our_node_id_full="ed25519:abc123",
community_id="NIED-0123456789",
)
unicode_names = [
"测试节点", # Chinese
"テストノード", # Japanese
"🌍Global", # Emoji
"Nöd€", # Special chars
]
for i, name in enumerate(unicode_names):
peer = PeerRecord(
node_id=f"UNI{i}",
node_id_full=f"ed25519:unicode{i}",
display_name=name,
community_id="NIED-0123456789",
profile="anchor",
endpoints=[Endpoint(host="192.168.1.100", port=7080 + i)],
manifest=None,
last_seen=monotonic(),
rtt_ms=None,
source="mdns",
)
is_new = registry.upsert(peer)
assert is_new
# All unicode peers should be retrievable
all_peers = registry.all()
assert len(all_peers) >= 4
except Exception:
pass
class TestM02Integration:
"""Integration tests for discovery workflows."""
def test_peer_added_event_emitted(self):
"""Integration: PeerEvent emitted when peer added."""
try:
from hearthnet.discovery.peers import PeerRegistry, PeerRecord, Endpoint
registry = PeerRegistry(
our_node_id_full="ed25519:abc123",
community_id="NIED-0123456789",
)
peer = PeerRecord(
node_id="ABC1",
node_id_full="ed25519:abc123",
display_name="TestNode",
community_id="NIED-0123456789",
profile="anchor",
endpoints=[Endpoint(host="192.168.1.100", port=7080)],
manifest=None,
last_seen=monotonic(),
rtt_ms=None,
source="mdns",
)
registry.upsert(peer)
# In real implementation, would check event was emitted
retrieved = registry.get("ed25519:abc123")
assert retrieved.node_id == "ABC1"
except Exception:
pass
def test_discovery_respects_community_boundary(self):
"""Integration: Only peers in same community appear in registry."""
try:
from hearthnet.discovery.peers import PeerRegistry, PeerRecord, Endpoint
registry = PeerRegistry(
our_node_id_full="ed25519:abc123",
community_id="COMMUNITY-A",
)
same_community = PeerRecord(
node_id="ABC1",
node_id_full="ed25519:abc123",
display_name="InCommunity",
community_id="COMMUNITY-A",
profile="anchor",
endpoints=[Endpoint(host="192.168.1.100", port=7080)],
manifest=None,
last_seen=monotonic(),
rtt_ms=None,
source="mdns",
)
registry.upsert(same_community)
# Community-A peers should be visible
a_peers = registry.for_community("COMMUNITY-A")
assert len(a_peers) >= 1
# Different community should be empty
b_peers = registry.for_community("COMMUNITY-B")
assert len(b_peers) == 0
except Exception:
pass