Files
MeshChatX/tests/backend/test_interface_discovery.py
2026-01-05 11:47:35 -06:00

202 lines
6.8 KiB
Python

import json
import shutil
import tempfile
from unittest.mock import MagicMock, patch
import pytest
import RNS
from meshchatx.meshchat import ReticulumMeshChat
class ConfigDict(dict):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.write_called = False
def write(self):
self.write_called = True
return True
@pytest.fixture
def temp_dir():
path = tempfile.mkdtemp()
try:
yield path
finally:
shutil.rmtree(path)
def build_identity():
identity = MagicMock(spec=RNS.Identity)
identity.hash = b"test_hash_32_bytes_long_01234567"
identity.hexhash = identity.hash.hex()
identity.get_private_key.return_value = b"test_private_key"
return identity
async def find_route_handler(app_instance, path, method):
for route in app_instance.get_routes():
if route.path == path and route.method == method:
return route.handler
return None
@pytest.mark.asyncio
async def test_reticulum_discovery_get_and_patch(temp_dir):
config = ConfigDict(
{
"reticulum": {
"discover_interfaces": "true",
"interface_discovery_sources": "abc,def",
"required_discovery_value": "16",
"autoconnect_discovered_interfaces": "2",
"network_identity": "/tmp/net_id",
},
"interfaces": {},
},
)
with (
patch("meshchatx.meshchat.generate_ssl_certificate"),
patch("RNS.Reticulum") as mock_rns,
patch("RNS.Transport"),
patch("LXMF.LXMRouter"),
):
mock_reticulum = mock_rns.return_value
mock_reticulum.config = config
mock_reticulum.configpath = "/tmp/mock_config"
mock_reticulum.is_connected_to_shared_instance = False
mock_reticulum.transport_enabled.return_value = True
app_instance = ReticulumMeshChat(
identity=build_identity(),
storage_dir=temp_dir,
reticulum_config_dir=temp_dir,
)
get_handler = await find_route_handler(
app_instance,
"/api/v1/reticulum/discovery",
"GET",
)
patch_handler = await find_route_handler(
app_instance,
"/api/v1/reticulum/discovery",
"PATCH",
)
assert get_handler and patch_handler
# GET returns current reticulum discovery config
get_response = await get_handler(MagicMock())
get_data = json.loads(get_response.body)
assert get_data["discovery"]["discover_interfaces"] == "true"
assert get_data["discovery"]["interface_discovery_sources"] == "abc,def"
assert get_data["discovery"]["required_discovery_value"] == "16"
assert get_data["discovery"]["autoconnect_discovered_interfaces"] == "2"
assert get_data["discovery"]["network_identity"] == "/tmp/net_id"
# PATCH updates and persists
new_config = {
"discover_interfaces": False,
"interface_discovery_sources": "",
"required_discovery_value": 18,
"autoconnect_discovered_interfaces": 5,
"network_identity": "/tmp/other_id",
}
class PatchRequest:
@staticmethod
async def json():
return new_config
patch_response = await patch_handler(PatchRequest())
patch_data = json.loads(patch_response.body)
assert patch_data["discovery"]["discover_interfaces"] is False
assert patch_data["discovery"]["interface_discovery_sources"] is None
assert patch_data["discovery"]["required_discovery_value"] == 18
assert patch_data["discovery"]["autoconnect_discovered_interfaces"] == 5
assert patch_data["discovery"]["network_identity"] == "/tmp/other_id"
assert config["reticulum"]["discover_interfaces"] is False
assert "interface_discovery_sources" not in config["reticulum"]
assert config["reticulum"]["required_discovery_value"] == 18
assert config["reticulum"]["autoconnect_discovered_interfaces"] == 5
assert config["reticulum"]["network_identity"] == "/tmp/other_id"
assert config.write_called
@pytest.mark.asyncio
async def test_interface_add_includes_discovery_fields(temp_dir):
config = ConfigDict({"reticulum": {}, "interfaces": {}})
with (
patch("meshchatx.meshchat.generate_ssl_certificate"),
patch("RNS.Reticulum") as mock_rns,
patch("RNS.Transport"),
patch("LXMF.LXMRouter"),
):
mock_reticulum = mock_rns.return_value
mock_reticulum.config = config
mock_reticulum.configpath = "/tmp/mock_config"
mock_reticulum.is_connected_to_shared_instance = False
mock_reticulum.transport_enabled.return_value = True
app_instance = ReticulumMeshChat(
identity=build_identity(),
storage_dir=temp_dir,
reticulum_config_dir=temp_dir,
)
add_handler = await find_route_handler(
app_instance,
"/api/v1/reticulum/interfaces/add",
"POST",
)
assert add_handler
payload = {
"allow_overwriting_interface": False,
"name": "TestIface",
"type": "TCPClientInterface",
"target_host": "example.com",
"target_port": "4242",
"discoverable": "yes",
"discovery_name": "Region A",
"announce_interval": 720,
"reachable_on": "/usr/bin/get_ip.sh",
"discovery_stamp_value": 22,
"discovery_encrypt": True,
"publish_ifac": True,
"latitude": 10.1,
"longitude": 20.2,
"height": 30,
"discovery_frequency": 915000000,
"discovery_bandwidth": 125000,
"discovery_modulation": "LoRa",
}
class AddRequest:
@staticmethod
async def json():
return payload
response = await add_handler(AddRequest())
data = json.loads(response.body)
assert "Interface has been added" in data["message"]
saved = config["interfaces"]["TestIface"]
assert saved["discoverable"] == "yes"
assert saved["discovery_name"] == "Region A"
assert saved["announce_interval"] == 720
assert saved["reachable_on"] == "/usr/bin/get_ip.sh"
assert saved["discovery_stamp_value"] == 22
assert saved["discovery_encrypt"] is True
assert saved["publish_ifac"] is True
assert saved["latitude"] == 10.1
assert saved["longitude"] == 20.2
assert saved["height"] == 30
assert saved["discovery_frequency"] == 915000000
assert saved["discovery_bandwidth"] == 125000
assert saved["discovery_modulation"] == "LoRa"
assert config.write_called