156 lines
5.4 KiB
Python
156 lines
5.4 KiB
Python
import os
|
|
import shutil
|
|
import tempfile
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
import pytest
|
|
import RNS
|
|
from meshchatx.src.backend.rncp_handler import RNCPHandler
|
|
|
|
|
|
@pytest.fixture
|
|
def temp_dir():
|
|
dir_path = tempfile.mkdtemp()
|
|
yield dir_path
|
|
shutil.rmtree(dir_path)
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_rns():
|
|
# Save real Identity class to use as base for our mock class
|
|
real_identity_class = RNS.Identity
|
|
|
|
class MockIdentityClass(real_identity_class):
|
|
def __init__(self, *args, **kwargs):
|
|
self.hash = b"test_hash_32_bytes_long_01234567"
|
|
self.hexhash = self.hash.hex()
|
|
|
|
with (
|
|
patch("RNS.Reticulum") as mock_reticulum,
|
|
patch("RNS.Transport") as mock_transport,
|
|
patch("RNS.Identity", MockIdentityClass),
|
|
patch("RNS.Destination") as mock_destination,
|
|
patch("RNS.Resource") as mock_resource,
|
|
patch("RNS.Link") as mock_link_class,
|
|
):
|
|
mock_id_instance = MockIdentityClass()
|
|
mock_id_instance.get_private_key = MagicMock(return_value=b"test_private_key")
|
|
|
|
with (
|
|
patch.object(MockIdentityClass, "from_file", return_value=mock_id_instance),
|
|
patch.object(MockIdentityClass, "recall", return_value=mock_id_instance),
|
|
patch.object(
|
|
MockIdentityClass, "from_bytes", return_value=mock_id_instance
|
|
),
|
|
):
|
|
mock_dest_instance = MagicMock()
|
|
mock_destination.return_value = mock_dest_instance
|
|
|
|
mock_link_instance = MagicMock()
|
|
mock_link_class.return_value = mock_link_instance
|
|
mock_link_instance.status = RNS.Link.ACTIVE
|
|
|
|
mock_resource_instance = MagicMock()
|
|
mock_resource_instance.status = 2 # COMPLETE
|
|
mock_resource_instance.hash = b"res_hash"
|
|
mock_resource.return_value = mock_resource_instance
|
|
mock_resource.COMPLETE = 2
|
|
mock_resource.FAILED = 3
|
|
|
|
mock_transport.active_links = []
|
|
mock_transport.has_path.return_value = True
|
|
|
|
yield {
|
|
"Reticulum": mock_reticulum,
|
|
"Transport": mock_transport,
|
|
"Identity": MockIdentityClass,
|
|
"Destination": mock_destination,
|
|
"Resource": mock_resource,
|
|
"Link": mock_link_class,
|
|
"link_instance": mock_link_instance,
|
|
"id_instance": mock_id_instance,
|
|
"dest_instance": mock_dest_instance,
|
|
}
|
|
|
|
|
|
def test_rncp_handler_init(mock_rns, temp_dir):
|
|
handler = RNCPHandler(mock_rns["Reticulum"], mock_rns["id_instance"], temp_dir)
|
|
assert handler.reticulum == mock_rns["Reticulum"]
|
|
assert handler.identity == mock_rns["id_instance"]
|
|
assert handler.storage_dir == temp_dir
|
|
|
|
|
|
def test_setup_receive_destination(mock_rns, temp_dir):
|
|
handler = RNCPHandler(mock_rns["Reticulum"], mock_rns["id_instance"], temp_dir)
|
|
|
|
mock_rns["Reticulum"].identitypath = temp_dir
|
|
dest_hash = handler.setup_receive_destination(
|
|
allowed_hashes=["abc123def456"], fetch_allowed=True, fetch_jail=temp_dir
|
|
)
|
|
|
|
assert handler.receive_destination is not None
|
|
mock_rns["Destination"].assert_called()
|
|
assert handler.allowed_identity_hashes == [bytes.fromhex("abc123def456")]
|
|
assert handler.fetch_jail == temp_dir
|
|
|
|
|
|
def test_receive_resource_callback(mock_rns, temp_dir):
|
|
handler = RNCPHandler(mock_rns["Reticulum"], mock_rns["id_instance"], temp_dir)
|
|
handler.allowed_identity_hashes = [b"allowed_hash"]
|
|
|
|
mock_resource = MagicMock()
|
|
mock_link = MagicMock()
|
|
mock_remote_id = MagicMock()
|
|
mock_remote_id.hash = b"allowed_hash"
|
|
mock_link.get_remote_identity.return_value = mock_remote_id
|
|
mock_resource.link = mock_link
|
|
|
|
# Allowed
|
|
assert handler._receive_resource_callback(mock_resource) is True
|
|
|
|
# Not allowed
|
|
mock_remote_id.hash = b"other_hash"
|
|
assert handler._receive_resource_callback(mock_resource) is False
|
|
|
|
|
|
def test_receive_resource_concluded_success(mock_rns, temp_dir):
|
|
handler = RNCPHandler(mock_rns["Reticulum"], mock_rns["id_instance"], temp_dir)
|
|
|
|
mock_resource = MagicMock()
|
|
mock_resource.status = RNS.Resource.COMPLETE
|
|
mock_resource.hash = b"resource_hash"
|
|
mock_resource.metadata = {"name": b"test_file.txt"}
|
|
|
|
# Create dummy source file
|
|
source_file = os.path.join(temp_dir, "temp_resource_data")
|
|
with open(source_file, "w") as f:
|
|
f.write("test data")
|
|
mock_resource.data.name = source_file
|
|
|
|
handler.active_transfers["7265736f757263655f68617368"] = {"status": "receiving"}
|
|
|
|
handler._receive_resource_concluded(mock_resource)
|
|
|
|
# Check if file was moved to rncp_received
|
|
received_dir = os.path.join(temp_dir, "rncp_received")
|
|
assert os.path.exists(os.path.join(received_dir, "test_file.txt"))
|
|
assert (
|
|
handler.active_transfers["7265736f757263655f68617368"]["status"] == "completed"
|
|
)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_send_file_success(mock_rns, temp_dir):
|
|
handler = RNCPHandler(mock_rns["Reticulum"], mock_rns["id_instance"], temp_dir)
|
|
|
|
test_file = os.path.join(temp_dir, "send_me.txt")
|
|
with open(test_file, "w") as f:
|
|
f.write("payload")
|
|
|
|
# Mocking the async behavior
|
|
result = await handler.send_file(b"dest_hash", test_file, timeout=10)
|
|
|
|
assert result["status"] == "completed"
|
|
mock_rns["Link"].assert_called()
|
|
mock_rns["Resource"].assert_called()
|