mirror of
https://github.com/Quad4-Software/MeshChatX.git
synced 2026-08-18 09:49:09 -04:00
335 lines
12 KiB
Python
335 lines
12 KiB
Python
# SPDX-License-Identifier: 0BSD
|
|
|
|
import os
|
|
import shutil
|
|
import tempfile
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
import pytest
|
|
|
|
|
|
@pytest.fixture
|
|
def storage_dir():
|
|
d = tempfile.mkdtemp()
|
|
yield d
|
|
shutil.rmtree(d, ignore_errors=True)
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_rns():
|
|
with patch("meshchatx.src.backend.page_node.RNS") as mock:
|
|
mock_identity = MagicMock()
|
|
mock_identity.hash = b"\x01" * 16
|
|
mock_identity.get_public_key.return_value = b"\x02" * 64
|
|
|
|
mock_destination = MagicMock()
|
|
mock_destination.hash = b"\x03" * 16
|
|
|
|
mock.Identity.return_value = mock_identity
|
|
mock.Identity.from_file.return_value = mock_identity
|
|
mock.Destination.return_value = mock_destination
|
|
mock.Destination.IN = 1
|
|
mock.Destination.SINGLE = 0
|
|
mock.Destination.ALLOW_ALL = 0xFF
|
|
mock.Transport = MagicMock()
|
|
mock.vendor = MagicMock()
|
|
mock.vendor.platformutils = MagicMock()
|
|
mock.vendor.platformutils.is_windows.return_value = False
|
|
|
|
yield mock, mock_identity, mock_destination
|
|
|
|
|
|
def _make_manager(storage_dir):
|
|
from meshchatx.src.backend.page_node_manager import PageNodeManager
|
|
|
|
return PageNodeManager(storage_dir)
|
|
|
|
|
|
class TestPageNodeManagerCreate:
|
|
def test_create_node(self, storage_dir, mock_rns):
|
|
mgr = _make_manager(storage_dir)
|
|
node = mgr.create_node("My Server")
|
|
assert node.name == "My Server"
|
|
assert node.node_id in mgr.nodes
|
|
assert os.path.isdir(node.base_dir)
|
|
|
|
def test_add_page_without_starting(self, storage_dir, mock_rns):
|
|
mgr = _make_manager(storage_dir)
|
|
node = mgr.create_node("Publish Target")
|
|
saved_name = node.add_page("index", "hello mesh")
|
|
assert saved_name == "index.mu"
|
|
assert node.list_pages() == [{"name": "index.mu", "executable": False}]
|
|
assert node.running is False
|
|
|
|
def test_create_node_with_custom_id(self, storage_dir, mock_rns):
|
|
mgr = _make_manager(storage_dir)
|
|
node = mgr.create_node("Custom", node_id="custom-123")
|
|
assert node.node_id == "custom-123"
|
|
|
|
def test_create_node_saves_config(self, storage_dir, mock_rns):
|
|
mgr = _make_manager(storage_dir)
|
|
node = mgr.create_node("Saved")
|
|
config_path = os.path.join(node.base_dir, "config.json")
|
|
assert os.path.isfile(config_path)
|
|
|
|
|
|
class TestPageNodeManagerDelete:
|
|
def test_delete_node(self, storage_dir, mock_rns):
|
|
mgr = _make_manager(storage_dir)
|
|
node = mgr.create_node("ToDelete")
|
|
node_id = node.node_id
|
|
base_dir = node.base_dir
|
|
assert mgr.delete_node(node_id) is True
|
|
assert node_id not in mgr.nodes
|
|
assert not os.path.exists(base_dir)
|
|
|
|
def test_delete_running_node_stops_first(self, storage_dir, mock_rns):
|
|
mgr = _make_manager(storage_dir)
|
|
node = mgr.create_node("Running")
|
|
mgr.start_node(node.node_id)
|
|
assert node.running is True
|
|
mgr.delete_node(node.node_id)
|
|
assert node.running is False
|
|
|
|
def test_delete_nonexistent_returns_false(self, storage_dir, mock_rns):
|
|
mgr = _make_manager(storage_dir)
|
|
assert mgr.delete_node("nonexistent") is False
|
|
|
|
|
|
class TestPageNodeManagerStartStop:
|
|
def test_start_node(self, storage_dir, mock_rns):
|
|
mgr = _make_manager(storage_dir)
|
|
node = mgr.create_node("StartMe")
|
|
dest_hash = mgr.start_node(node.node_id)
|
|
assert node.running is True
|
|
assert dest_hash is not None
|
|
|
|
def test_start_already_running(self, storage_dir, mock_rns):
|
|
mgr = _make_manager(storage_dir)
|
|
node = mgr.create_node("AlreadyRunning")
|
|
mgr.start_node(node.node_id)
|
|
result = mgr.start_node(node.node_id)
|
|
assert result is not None
|
|
|
|
def test_start_nonexistent_raises(self, storage_dir, mock_rns):
|
|
mgr = _make_manager(storage_dir)
|
|
with pytest.raises(KeyError):
|
|
mgr.start_node("nope")
|
|
|
|
def test_stop_node(self, storage_dir, mock_rns):
|
|
mgr = _make_manager(storage_dir)
|
|
node = mgr.create_node("StopMe")
|
|
mgr.start_node(node.node_id)
|
|
mgr.stop_node(node.node_id)
|
|
assert node.running is False
|
|
|
|
def test_stop_nonexistent_raises(self, storage_dir, mock_rns):
|
|
mgr = _make_manager(storage_dir)
|
|
with pytest.raises(KeyError):
|
|
mgr.stop_node("nope")
|
|
|
|
def test_start_all(self, storage_dir, mock_rns):
|
|
mgr = _make_manager(storage_dir)
|
|
mgr.create_node("A", node_id="a")
|
|
mgr.create_node("B", node_id="b")
|
|
mgr.start_all()
|
|
for node in mgr.nodes.values():
|
|
assert node.running is True
|
|
|
|
def test_stop_all(self, storage_dir, mock_rns):
|
|
mgr = _make_manager(storage_dir)
|
|
mgr.create_node("A", node_id="a")
|
|
mgr.create_node("B", node_id="b")
|
|
mgr.start_all()
|
|
mgr.stop_all()
|
|
for node in mgr.nodes.values():
|
|
assert node.running is False
|
|
|
|
|
|
class TestPageNodeManagerAnnounce:
|
|
def test_announce_node(self, storage_dir, mock_rns):
|
|
mgr = _make_manager(storage_dir)
|
|
node = mgr.create_node("Announcer")
|
|
mgr.start_node(node.node_id)
|
|
_, _, mock_dest = mock_rns
|
|
mgr.announce_node(node.node_id)
|
|
mock_dest.announce.assert_called()
|
|
|
|
def test_announce_nonexistent_raises(self, storage_dir, mock_rns):
|
|
mgr = _make_manager(storage_dir)
|
|
with pytest.raises(KeyError):
|
|
mgr.announce_node("nope")
|
|
|
|
def test_announce_all(self, storage_dir, mock_rns):
|
|
mgr = _make_manager(storage_dir)
|
|
mgr.create_node("A", node_id="a")
|
|
mgr.create_node("B", node_id="b")
|
|
mgr.start_all()
|
|
_, _, mock_dest = mock_rns
|
|
mgr.announce_all()
|
|
assert mock_dest.announce.call_count >= 2
|
|
|
|
|
|
class TestPageNodeManagerAnnounceSettings:
|
|
def test_create_node_with_custom_announce_settings(self, storage_dir, mock_rns):
|
|
mgr = _make_manager(storage_dir)
|
|
node = mgr.create_node(
|
|
"Custom", announce_enabled=False, announce_interval_seconds=120
|
|
)
|
|
assert node.announce_enabled is False
|
|
assert node.announce_interval_seconds == 120
|
|
|
|
def test_create_node_defaults_announce_enabled(self, storage_dir, mock_rns):
|
|
mgr = _make_manager(storage_dir)
|
|
node = mgr.create_node("Defaults")
|
|
assert node.announce_enabled is True
|
|
|
|
def test_create_node_with_executable_pages_enabled(self, storage_dir, mock_rns):
|
|
mgr = _make_manager(storage_dir)
|
|
node = mgr.create_node("Dynamic", executable_pages_enabled=True)
|
|
assert node.executable_pages_enabled is True
|
|
|
|
def test_set_executable_pages_enabled_persists(self, storage_dir, mock_rns):
|
|
from meshchatx.src.backend.page_node import PageNode
|
|
|
|
mgr = _make_manager(storage_dir)
|
|
node = mgr.create_node("Dynamic")
|
|
mgr.set_executable_pages_enabled(node.node_id, True)
|
|
config = PageNode.load_config(node.base_dir)
|
|
assert config["executable_pages_enabled"] is True
|
|
|
|
def test_set_announce_settings_updates_node(self, storage_dir, mock_rns):
|
|
mgr = _make_manager(storage_dir)
|
|
node = mgr.create_node("Settable")
|
|
mgr.set_announce_settings(
|
|
node.node_id, announce_enabled=False, announce_interval_seconds=300
|
|
)
|
|
assert node.announce_enabled is False
|
|
assert node.announce_interval_seconds == 300
|
|
|
|
def test_set_announce_settings_persists_to_disk(self, storage_dir, mock_rns):
|
|
from meshchatx.src.backend.page_node import PageNode
|
|
|
|
mgr = _make_manager(storage_dir)
|
|
node = mgr.create_node("Persisted", node_id="persist-announce")
|
|
mgr.set_announce_settings(node.node_id, announce_enabled=False)
|
|
|
|
config = PageNode.load_config(node.base_dir)
|
|
assert config["announce_enabled"] is False
|
|
|
|
def test_set_announce_settings_nonexistent_raises(self, storage_dir, mock_rns):
|
|
mgr = _make_manager(storage_dir)
|
|
with pytest.raises(KeyError):
|
|
mgr.set_announce_settings("nope", announce_enabled=False)
|
|
|
|
def test_load_nodes_restores_announce_settings(self, storage_dir, mock_rns):
|
|
mgr = _make_manager(storage_dir)
|
|
node = mgr.create_node("Reloaded", node_id="reload-1")
|
|
mgr.set_announce_settings(
|
|
node.node_id, announce_enabled=False, announce_interval_seconds=200
|
|
)
|
|
|
|
mgr2 = _make_manager(storage_dir)
|
|
mgr2.load_nodes()
|
|
reloaded = mgr2.nodes["reload-1"]
|
|
assert reloaded.announce_enabled is False
|
|
assert reloaded.announce_interval_seconds == 200
|
|
|
|
def test_load_nodes_restores_executable_page_names(self, storage_dir, mock_rns):
|
|
mgr = _make_manager(storage_dir)
|
|
node = mgr.create_node("Scripts", node_id="reload-exec")
|
|
node.add_page("dyn.mu", "#!/usr/bin/env python3\nprint(1)\n", executable=True)
|
|
|
|
mgr2 = _make_manager(storage_dir)
|
|
mgr2.load_nodes()
|
|
reloaded = mgr2.nodes["reload-exec"]
|
|
assert reloaded.is_page_executable("dyn.mu") is True
|
|
|
|
def test_on_announce_callback_wired_through_manager(self, storage_dir, mock_rns):
|
|
calls = []
|
|
mgr = _make_manager(storage_dir)
|
|
mgr.on_announce = calls.append
|
|
node = mgr.create_node("Callback")
|
|
mgr.start_node(node.node_id)
|
|
assert calls == [node]
|
|
|
|
def test_on_announce_callback_wired_for_loaded_nodes(self, storage_dir, mock_rns):
|
|
mgr = _make_manager(storage_dir)
|
|
node = mgr.create_node("ToReload", node_id="reload-cb")
|
|
node.save_config()
|
|
|
|
calls = []
|
|
mgr2 = _make_manager(storage_dir)
|
|
mgr2.on_announce = calls.append
|
|
mgr2.load_nodes()
|
|
mgr2.start_node("reload-cb")
|
|
assert calls == [mgr2.nodes["reload-cb"]]
|
|
|
|
|
|
class TestPageNodeManagerRename:
|
|
def test_rename_node(self, storage_dir, mock_rns):
|
|
mgr = _make_manager(storage_dir)
|
|
node = mgr.create_node("OldName")
|
|
mgr.rename_node(node.node_id, "NewName")
|
|
assert node.name == "NewName"
|
|
|
|
def test_rename_nonexistent_raises(self, storage_dir, mock_rns):
|
|
mgr = _make_manager(storage_dir)
|
|
with pytest.raises(KeyError):
|
|
mgr.rename_node("nope", "Ignored")
|
|
|
|
|
|
class TestPageNodeManagerGetAndList:
|
|
def test_get_node(self, storage_dir, mock_rns):
|
|
mgr = _make_manager(storage_dir)
|
|
node = mgr.create_node("FindMe")
|
|
assert mgr.get_node(node.node_id) is node
|
|
|
|
def test_get_node_missing(self, storage_dir, mock_rns):
|
|
mgr = _make_manager(storage_dir)
|
|
assert mgr.get_node("nope") is None
|
|
|
|
def test_list_nodes(self, storage_dir, mock_rns):
|
|
mgr = _make_manager(storage_dir)
|
|
mgr.create_node("A", node_id="a")
|
|
mgr.create_node("B", node_id="b")
|
|
result = mgr.list_nodes()
|
|
assert len(result) == 2
|
|
ids = {n["node_id"] for n in result}
|
|
assert ids == {"a", "b"}
|
|
|
|
|
|
class TestPageNodeManagerLoad:
|
|
def test_load_nodes_from_disk(self, storage_dir, mock_rns):
|
|
mgr = _make_manager(storage_dir)
|
|
node = mgr.create_node("Persisted", node_id="persist-1")
|
|
node.save_config()
|
|
|
|
mgr2 = _make_manager(storage_dir)
|
|
mgr2.load_nodes()
|
|
assert "persist-1" in mgr2.nodes
|
|
assert mgr2.nodes["persist-1"].name == "Persisted"
|
|
|
|
def test_load_nodes_skips_bad_dirs(self, storage_dir, mock_rns):
|
|
mgr = _make_manager(storage_dir)
|
|
bad_dir = os.path.join(mgr.storage_dir, "bad-node")
|
|
os.makedirs(bad_dir, exist_ok=True)
|
|
mgr.load_nodes()
|
|
assert "bad-node" not in mgr.nodes
|
|
|
|
def test_load_nodes_skips_already_loaded(self, storage_dir, mock_rns):
|
|
mgr = _make_manager(storage_dir)
|
|
node = mgr.create_node("Already", node_id="already-1")
|
|
node.save_config()
|
|
mgr.load_nodes()
|
|
assert len(mgr.nodes) == 1
|
|
|
|
|
|
class TestPageNodeManagerTeardown:
|
|
def test_teardown_stops_and_clears(self, storage_dir, mock_rns):
|
|
mgr = _make_manager(storage_dir)
|
|
mgr.create_node("A", node_id="a")
|
|
mgr.create_node("B", node_id="b")
|
|
mgr.start_all()
|
|
mgr.teardown()
|
|
assert len(mgr.nodes) == 0
|