numerous improvements

This commit is contained in:
2026-01-05 11:47:35 -06:00
parent 5694c1ee67
commit fda9187e95
104 changed files with 4567 additions and 1070 deletions

View File

@@ -1,9 +1,10 @@
import os
import random
import secrets
import shutil
import tempfile
import time
import random
import secrets
from meshchatx.src.backend.database import Database
@@ -68,7 +69,7 @@ def test_db_performance():
convs = db.messages.get_conversations()
end_time = time.time()
print(
f"get_conversations() returned {len(convs)} conversations in {end_time - start_time:.4f} seconds"
f"get_conversations() returned {len(convs)} conversations in {end_time - start_time:.4f} seconds",
)
# Test get_conversation_messages for a random peer
@@ -78,7 +79,7 @@ def test_db_performance():
msgs = db.messages.get_conversation_messages(target_peer, limit=50)
end_time = time.time()
print(
f"get_conversation_messages() returned {len(msgs)} messages in {end_time - start_time:.4f} seconds"
f"get_conversation_messages() returned {len(msgs)} messages in {end_time - start_time:.4f} seconds",
)
# Test unread states for all peers
@@ -87,7 +88,7 @@ def test_db_performance():
_ = db.messages.get_conversations_unread_states(peer_hashes)
end_time = time.time()
print(
f"get_conversations_unread_states() for {len(peer_hashes)} peers took {end_time - start_time:.4f} seconds"
f"get_conversations_unread_states() for {len(peer_hashes)} peers took {end_time - start_time:.4f} seconds",
)
# Test announces performance

View File

@@ -1,9 +1,10 @@
import os
import psutil
import gc
import os
import time
from functools import wraps
import psutil
def get_memory_usage_mb():
"""Returns the current process memory usage in MB."""
@@ -81,5 +82,5 @@ class MemoryTracker:
self.duration_ms = (self.end_time - self.start_time) * 1000
self.mem_delta = self.end_mem - self.start_mem
print(
f"TRACKER [{self.name}]: {self.duration_ms:.2f}ms, {self.mem_delta:.2f}MB"
f"TRACKER [{self.name}]: {self.duration_ms:.2f}ms, {self.mem_delta:.2f}MB",
)

View File

@@ -1,6 +1,7 @@
import pytest
from unittest.mock import patch
import asyncio
from unittest.mock import patch
import pytest
@pytest.fixture(autouse=True)

View File

@@ -1,13 +1,15 @@
import gc
import json
import os
import random
import secrets
import shutil
import tempfile
import time
import random
import secrets
import psutil
import gc
import json
from unittest.mock import MagicMock
import psutil
from meshchatx.src.backend.database import Database
@@ -72,7 +74,9 @@ class MapBenchmarker:
)
self.record_benchmark(
f"Telemetry Insertion ({count} entries)", run_telemetry, count
f"Telemetry Insertion ({count} entries)",
run_telemetry,
count,
)
def benchmark_telemetry_retrieval(self, count=100):
@@ -90,7 +94,9 @@ class MapBenchmarker:
self.db.telemetry.get_telemetry_history(dest_hash, limit=100)
self.record_benchmark(
f"Telemetry History Retrieval ({count} calls)", run_retrieval, count
f"Telemetry History Retrieval ({count} calls)",
run_retrieval,
count,
)
def benchmark_drawing_storage(self, count=500):
@@ -112,7 +118,7 @@ class MapBenchmarker:
}
for i in range(100)
],
}
},
)
def run_drawings():
@@ -125,7 +131,9 @@ class MapBenchmarker:
)
self.record_benchmark(
f"Map Drawing Insertion ({count} layers)", run_drawings, count
f"Map Drawing Insertion ({count} layers)",
run_drawings,
count,
)
def benchmark_drawing_listing(self, count=100):
@@ -154,7 +162,9 @@ class MapBenchmarker:
mm.list_mbtiles()
self.record_benchmark(
f"MBTiles Listing ({count} calls, 5 files)", run_list, count
f"MBTiles Listing ({count} calls, 5 files)",
run_list,
count,
)
@@ -173,7 +183,7 @@ def main():
print("-" * 80)
for r in bench.results:
print(
f"{r['name']:40} | {r['duration_ms']:8.2f} ms | {r['memory_growth_mb']:8.2f} MB"
f"{r['name']:40} | {r['duration_ms']:8.2f} ms | {r['memory_growth_mb']:8.2f} MB",
)
print("=" * 80)

View File

@@ -1,11 +1,13 @@
import gc
import os
import random
import secrets
import shutil
import tempfile
import time
import random
import secrets
import psutil
import gc
from meshchatx.src.backend.database import Database
from meshchatx.src.backend.recovery import CrashRecovery
@@ -112,7 +114,9 @@ class PerformanceBenchmarker:
recovery.run_diagnosis(file=open(os.devnull, "w"))
self.record_benchmark(
"CrashRecovery Diagnosis Overhead (50 runs)", run_recovery_check, 50
"CrashRecovery Diagnosis Overhead (50 runs)",
run_recovery_check,
50,
)
def benchmark_identity_generation(self, count=20):
@@ -123,7 +127,9 @@ class PerformanceBenchmarker:
RNS.Identity(create_keys=True)
self.record_benchmark(
f"RNS Identity Generation ({count} identities)", run_gen, count
f"RNS Identity Generation ({count} identities)",
run_gen,
count,
)
def benchmark_identity_listing(self, count=100):
@@ -142,7 +148,9 @@ class PerformanceBenchmarker:
manager.list_identities(current_identity_hash=hashes[0])
self.record_benchmark(
f"Identity Listing ({count} runs, 10 identities)", run_list, count
f"Identity Listing ({count} runs, 10 identities)",
run_list,
count,
)
@@ -161,7 +169,7 @@ def main():
print("-" * 80)
for r in bench.results:
print(
f"{r['name']:40} | {r['duration_ms']:8.2f} ms | {r['memory_growth_mb']:8.2f} MB"
f"{r['name']:40} | {r['duration_ms']:8.2f} ms | {r['memory_growth_mb']:8.2f} MB",
)
print("=" * 80)

View File

@@ -1,18 +1,19 @@
import os
import sys
import time
import shutil
import tempfile
import random
import secrets
import shutil
import sys
import tempfile
import time
# Ensure we can import meshchatx
sys.path.append(os.getcwd())
import json
from meshchatx.src.backend.database import Database
from meshchatx.src.backend.identity_manager import IdentityManager
from meshchatx.src.backend.database.telephone import TelephoneDAO
from meshchatx.src.backend.identity_manager import IdentityManager
from tests.backend.benchmarking_utils import (
benchmark,
get_memory_usage_mb,
@@ -76,7 +77,7 @@ class BackendBenchmarker:
"delivery_attempts": 1,
"title": f"Extreme Msg {b + i}",
"content": secrets.token_bytes(
1024
1024,
).hex(), # 2KB hex string
"fields": json.dumps({"test": "data" * 10}),
"timestamp": time.time() - (total_messages - (b + i)),
@@ -87,13 +88,15 @@ class BackendBenchmarker:
}
self.db.messages.upsert_lxmf_message(msg)
print(
f" Progress: {b + batch_size}/{total_messages} messages inserted..."
f" Progress: {b + batch_size}/{total_messages} messages inserted...",
)
@benchmark("EXTREME: Search 100k Messages (Wildcard)", iterations=5)
def run_extreme_search():
return self.db.messages.get_conversation_messages(
peer_hashes[0], limit=100, offset=50000
peer_hashes[0],
limit=100,
offset=50000,
)
_, res_flood = run_extreme_flood()
@@ -115,7 +118,7 @@ class BackendBenchmarker:
data = {
"destination_hash": secrets.token_hex(16),
"aspect": random.choice(
["lxmf.delivery", "lxst.telephony", "group.chat"]
["lxmf.delivery", "lxst.telephony", "group.chat"],
),
"identity_hash": secrets.token_hex(16),
"identity_public_key": secrets.token_hex(32),
@@ -130,7 +133,9 @@ class BackendBenchmarker:
@benchmark("EXTREME: Filter 50k Announces (Complex)", iterations=10)
def run_ann_filter():
return self.db.announces.get_filtered_announces(
aspect="lxmf.delivery", limit=100, offset=25000
aspect="lxmf.delivery",
limit=100,
offset=25000,
)
_, res_flood = run_ann_flood()
@@ -164,7 +169,8 @@ class BackendBenchmarker:
@benchmark("Database Initialization", iterations=5)
def run():
tmp_db_path = os.path.join(
self.temp_dir, f"init_test_{random.randint(0, 1000)}.db"
self.temp_dir,
f"init_test_{random.randint(0, 1000)}.db",
)
db = Database(tmp_db_path)
db.initialize()
@@ -210,7 +216,9 @@ class BackendBenchmarker:
@benchmark("Get Messages for Conversation (offset 500)", iterations=20)
def get_messages():
return self.db.messages.get_conversation_messages(
peer_hashes[0], limit=50, offset=500
peer_hashes[0],
limit=50,
offset=500,
)
_, res = upsert_batch()
@@ -295,7 +303,7 @@ class BackendBenchmarker:
print(f"{'-' * 40}-|-{'-' * 10}-|-{'-' * 10}")
for r in self.results:
print(
f"{r.name:40} | {r.duration_ms:8.2f} ms | {r.memory_delta_mb:8.2f} MB"
f"{r.name:40} | {r.duration_ms:8.2f} ms | {r.memory_delta_mb:8.2f} MB",
)
print(f"{'=' * 59}")
print(f"Final Memory Usage: {get_memory_usage_mb():.2f} MB")
@@ -306,7 +314,9 @@ if __name__ == "__main__":
parser = argparse.ArgumentParser(description="MeshChatX Backend Benchmarker")
parser.add_argument(
"--extreme", action="store_true", help="Run extreme stress tests"
"--extreme",
action="store_true",
help="Run extreme stress tests",
)
args = parser.parse_args()

View File

@@ -1,9 +1,11 @@
import os
import tempfile
import pytest
from meshchatx.src.backend.database.announces import AnnounceDAO
from meshchatx.src.backend.database.provider import DatabaseProvider
from meshchatx.src.backend.database.schema import DatabaseSchema
from meshchatx.src.backend.database.announces import AnnounceDAO
@pytest.fixture
@@ -37,7 +39,7 @@ def test_get_filtered_announces_identity_hash(announce_dao):
"rssi": -50,
"snr": 10,
"quality": 1.0,
}
},
)
announce_dao.upsert_announce(
{
@@ -49,7 +51,7 @@ def test_get_filtered_announces_identity_hash(announce_dao):
"rssi": -50,
"snr": 10,
"quality": 1.0,
}
},
)
announce_dao.upsert_announce(
{
@@ -61,7 +63,7 @@ def test_get_filtered_announces_identity_hash(announce_dao):
"rssi": -50,
"snr": 10,
"quality": 1.0,
}
},
)
# Test filtering by identity_hash
@@ -71,7 +73,8 @@ def test_get_filtered_announces_identity_hash(announce_dao):
# Test filtering by identity_hash and aspect
results = announce_dao.get_filtered_announces(
identity_hash="ident1", aspect="lxmf.propagation"
identity_hash="ident1",
aspect="lxmf.propagation",
)
assert len(results) == 1
assert results[0]["destination_hash"] == "dest1"
@@ -89,6 +92,7 @@ def test_get_filtered_announces_robustness(announce_dao):
# Test with multiple filters that yield no results
results = announce_dao.get_filtered_announces(
identity_hash="ident1", aspect="non_existent_aspect"
identity_hash="ident1",
aspect="non_existent_aspect",
)
assert len(results) == 0

View File

@@ -1,11 +1,13 @@
import asyncio
import json
import shutil
import tempfile
import pytest
import json
from unittest.mock import MagicMock, patch
from meshchatx.meshchat import ReticulumMeshChat
import pytest
import RNS
import asyncio
from meshchatx.meshchat import ReticulumMeshChat
@pytest.fixture

View File

@@ -1,10 +1,12 @@
import shutil
import tempfile
import pytest
from unittest.mock import MagicMock, patch
from meshchatx.meshchat import ReticulumMeshChat
import pytest
import RNS
from meshchatx.meshchat import ReticulumMeshChat
@pytest.fixture
def temp_dir():
@@ -36,43 +38,43 @@ async def test_app_status_endpoints(mock_rns_minimal, temp_dir):
with ExitStack() as stack:
# Patch all dependencies
stack.enter_context(
patch("meshchatx.src.backend.identity_context.MessageHandler")
patch("meshchatx.src.backend.identity_context.MessageHandler"),
)
stack.enter_context(
patch("meshchatx.src.backend.identity_context.AnnounceManager")
patch("meshchatx.src.backend.identity_context.AnnounceManager"),
)
stack.enter_context(
patch("meshchatx.src.backend.identity_context.ArchiverManager")
patch("meshchatx.src.backend.identity_context.ArchiverManager"),
)
stack.enter_context(patch("meshchatx.src.backend.identity_context.MapManager"))
stack.enter_context(patch("meshchatx.src.backend.identity_context.DocsManager"))
stack.enter_context(
patch("meshchatx.src.backend.identity_context.NomadNetworkManager")
patch("meshchatx.src.backend.identity_context.NomadNetworkManager"),
)
stack.enter_context(
patch("meshchatx.src.backend.identity_context.TelephoneManager")
patch("meshchatx.src.backend.identity_context.TelephoneManager"),
)
stack.enter_context(
patch("meshchatx.src.backend.identity_context.VoicemailManager")
patch("meshchatx.src.backend.identity_context.VoicemailManager"),
)
stack.enter_context(
patch("meshchatx.src.backend.identity_context.RingtoneManager")
patch("meshchatx.src.backend.identity_context.RingtoneManager"),
)
stack.enter_context(patch("meshchatx.src.backend.identity_context.RNCPHandler"))
stack.enter_context(
patch("meshchatx.src.backend.identity_context.RNStatusHandler")
patch("meshchatx.src.backend.identity_context.RNStatusHandler"),
)
stack.enter_context(
patch("meshchatx.src.backend.identity_context.RNProbeHandler")
patch("meshchatx.src.backend.identity_context.RNProbeHandler"),
)
stack.enter_context(
patch("meshchatx.src.backend.identity_context.TranslatorHandler")
patch("meshchatx.src.backend.identity_context.TranslatorHandler"),
)
stack.enter_context(
patch("meshchatx.src.backend.identity_context.CommunityInterfacesManager")
patch("meshchatx.src.backend.identity_context.CommunityInterfacesManager"),
)
stack.enter_context(
patch("meshchatx.src.backend.sideband_commands.SidebandCommands")
patch("meshchatx.src.backend.sideband_commands.SidebandCommands"),
)
stack.enter_context(patch("meshchatx.meshchat.Telemeter"))
stack.enter_context(patch("meshchatx.meshchat.CrashRecovery"))

View File

@@ -1,9 +1,9 @@
import unittest
import hashlib
import json
import os
import shutil
import tempfile
import json
import hashlib
import unittest
from pathlib import Path
@@ -55,7 +55,7 @@ class TestBackendIntegrity(unittest.TestCase):
def test_manifest_generation(self):
"""Test that the build script logic produces a valid manifest."""
manifest_path = self.generate_manifest()
with open(manifest_path, "r") as f:
with open(manifest_path) as f:
manifest = json.load(f)
self.assertEqual(len(manifest["files"]), 2)
@@ -66,7 +66,7 @@ class TestBackendIntegrity(unittest.TestCase):
def test_tampering_detection_logic(self):
"""Test that modifying a file changes its hash (logic check)."""
manifest_path = self.generate_manifest()
with open(manifest_path, "r") as f:
with open(manifest_path) as f:
manifest = json.load(f)
old_hash = manifest["files"]["ReticulumMeshChatX"]

View File

@@ -1,11 +1,13 @@
import json
import shutil
import tempfile
from unittest.mock import AsyncMock, MagicMock, patch
import pytest
import json
from unittest.mock import MagicMock, patch, AsyncMock
from meshchatx.meshchat import ReticulumMeshChat
import RNS
from meshchatx.meshchat import ReticulumMeshChat
@pytest.fixture
def temp_dir():
@@ -74,7 +76,7 @@ async def test_banish_identity_with_blackhole(mock_rns_minimal, temp_dir):
# Verify DB call
app_instance.database.misc.add_blocked_destination.assert_called_with(
target_hash
target_hash,
)
# Verify RNS blackhole call
@@ -100,7 +102,7 @@ async def test_banish_identity_with_resolution(mock_rns_minimal, temp_dir):
# Mock identity resolution
app_instance.database.announces.get_announce_by_hash.return_value = {
"identity_hash": ident_hash
"identity_hash": ident_hash,
}
request = MagicMock()
@@ -147,7 +149,7 @@ async def test_banish_identity_disabled_integration(mock_rns_minimal, temp_dir):
# DB call should still happen
app_instance.database.misc.add_blocked_destination.assert_called_with(
target_hash
target_hash,
)
# RNS blackhole call should NOT happen
@@ -189,7 +191,7 @@ async def test_lift_banishment(mock_rns_minimal, temp_dir):
# Verify DB call
app_instance.database.misc.delete_blocked_destination.assert_called_with(
target_hash
target_hash,
)
# Verify RNS unblackhole call
@@ -213,7 +215,7 @@ async def test_get_blackhole_list(mock_rns_minimal, temp_dir):
"source": b"\x02" * 32,
"until": 1234567890,
"reason": "Spam",
}
},
}
request = MagicMock()

View File

@@ -1,5 +1,7 @@
import pytest
from unittest.mock import MagicMock, patch
import pytest
from meshchatx.src.backend.community_interfaces import CommunityInterfacesManager
from meshchatx.src.backend.rnstatus_handler import RNStatusHandler
@@ -42,7 +44,7 @@ async def test_rnstatus_integration_simulated():
"rxb": 0,
"txb": 0,
},
]
],
}
handler = RNStatusHandler(mock_reticulum)

View File

@@ -1,8 +1,10 @@
import os
import pytest
from meshchatx.src.backend.database.contacts import ContactsDAO
from meshchatx.src.backend.database.provider import DatabaseProvider
from meshchatx.src.backend.database.schema import DatabaseSchema
from meshchatx.src.backend.database.contacts import ContactsDAO
@pytest.fixture
@@ -39,7 +41,8 @@ def test_contacts_with_custom_image(db_provider):
# Test updating contact image
contacts_dao.update_contact(
contact["id"], custom_image="data:image/png;base64,updateddata"
contact["id"],
custom_image="data:image/png;base64,updateddata",
)
contact = contacts_dao.get_contact(contact["id"])

View File

@@ -1,10 +1,11 @@
import unittest
import io
import os
import shutil
import tempfile
import sys
import io
import sqlite3
import sys
import tempfile
import unittest
from meshchatx.src.backend.recovery.crash_recovery import CrashRecovery

View File

@@ -1,7 +1,8 @@
import unittest
import os
import tempfile
import shutil
import tempfile
import unittest
from meshchatx.src.backend.database.provider import DatabaseProvider
from meshchatx.src.backend.database.schema import DatabaseSchema
@@ -42,7 +43,8 @@ class TestDatabaseRobustness(unittest.TestCase):
)
""")
self.provider.execute(
"INSERT INTO config (key, value) VALUES (?, ?)", ("database_version", "1")
"INSERT INTO config (key, value) VALUES (?, ?)",
("database_version", "1"),
)
# 3. Attempt initialization.
@@ -77,7 +79,7 @@ class TestDatabaseRobustness(unittest.TestCase):
# 3. Version should now be set to LATEST
row = self.provider.fetchone(
"SELECT value FROM config WHERE key = 'database_version'"
"SELECT value FROM config WHERE key = 'database_version'",
)
self.assertEqual(int(row["value"]), self.schema.LATEST_VERSION)

View File

@@ -3,6 +3,7 @@ import shutil
import tempfile
import pytest
from meshchatx.src.backend.database import Database
@@ -20,7 +21,8 @@ def test_database_snapshot_creation(temp_dir):
# Add some data
db.execute_sql(
"INSERT INTO config (key, value) VALUES (?, ?)", ("test_key", "test_value")
"INSERT INTO config (key, value) VALUES (?, ?)",
("test_key", "test_value"),
)
# Create snapshot

View File

@@ -1,8 +1,10 @@
import time
import pytest
import logging
from meshchatx.src.backend.persistent_log_handler import PersistentLogHandler
import time
import pytest
from meshchatx.src.backend.database import Database
from meshchatx.src.backend.persistent_log_handler import PersistentLogHandler
@pytest.fixture

View File

@@ -141,7 +141,7 @@ def create_mock_zip(zip_path, file_list):
)
@given(
root_folder_name=st.text(min_size=1, max_size=50).filter(
lambda x: "/" not in x and x not in [".", ".."]
lambda x: "/" not in x and x not in [".", ".."],
),
docs_file=st.text(min_size=1, max_size=50).filter(lambda x: "/" not in x),
)

View File

@@ -5,6 +5,7 @@ from unittest.mock import MagicMock, patch
import pytest
import RNS
from meshchatx.meshchat import ReticulumMeshChat
@@ -39,7 +40,9 @@ def mock_rns():
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
MockIdentityClass,
"from_bytes",
return_value=mock_id_instance,
),
):
mock_transport.interfaces = []
@@ -73,7 +76,7 @@ def test_emergency_mode_startup_logic(mock_rns, temp_dir):
patch("meshchatx.src.backend.identity_context.DocsManager"),
patch("meshchatx.src.backend.identity_context.NomadNetworkManager"),
patch(
"meshchatx.src.backend.identity_context.TelephoneManager"
"meshchatx.src.backend.identity_context.TelephoneManager",
) as mock_tel_class,
patch("meshchatx.src.backend.identity_context.VoicemailManager"),
patch("meshchatx.src.backend.identity_context.RingtoneManager"),
@@ -83,10 +86,10 @@ def test_emergency_mode_startup_logic(mock_rns, temp_dir):
patch("meshchatx.src.backend.identity_context.TranslatorHandler"),
patch("meshchatx.src.backend.identity_context.CommunityInterfacesManager"),
patch(
"meshchatx.src.backend.identity_context.IntegrityManager"
"meshchatx.src.backend.identity_context.IntegrityManager",
) as mock_integrity_class,
patch(
"meshchatx.src.backend.identity_context.IdentityContext.start_background_threads"
"meshchatx.src.backend.identity_context.IdentityContext.start_background_threads",
),
):
# Initialize app in emergency mode
@@ -139,7 +142,7 @@ def test_emergency_mode_env_var(mock_rns, temp_dir):
patch("meshchatx.src.backend.identity_context.TranslatorHandler"),
patch("meshchatx.src.backend.identity_context.CommunityInterfacesManager"),
patch(
"meshchatx.src.backend.identity_context.IdentityContext.start_background_threads"
"meshchatx.src.backend.identity_context.IdentityContext.start_background_threads",
),
):
# We need to simulate the argparse processing that happens in main()
@@ -170,7 +173,7 @@ def test_normal_mode_startup_logic(mock_rns, temp_dir):
patch("meshchatx.src.backend.identity_context.DocsManager"),
patch("meshchatx.src.backend.identity_context.NomadNetworkManager"),
patch(
"meshchatx.src.backend.identity_context.TelephoneManager"
"meshchatx.src.backend.identity_context.TelephoneManager",
) as mock_tel_class,
patch("meshchatx.src.backend.identity_context.VoicemailManager"),
patch("meshchatx.src.backend.identity_context.RingtoneManager"),
@@ -180,10 +183,10 @@ def test_normal_mode_startup_logic(mock_rns, temp_dir):
patch("meshchatx.src.backend.identity_context.TranslatorHandler"),
patch("meshchatx.src.backend.identity_context.CommunityInterfacesManager"),
patch(
"meshchatx.src.backend.identity_context.IntegrityManager"
"meshchatx.src.backend.identity_context.IntegrityManager",
) as mock_integrity_class,
patch(
"meshchatx.src.backend.identity_context.IdentityContext.start_background_threads"
"meshchatx.src.backend.identity_context.IdentityContext.start_background_threads",
),
):
# Configure mocks BEFORE instantiating app

View File

@@ -12,6 +12,11 @@ from hypothesis import strategies as st
from meshchatx.meshchat import ReticulumMeshChat
from meshchatx.src.backend.interface_config_parser import InterfaceConfigParser
from meshchatx.src.backend.lxmf_message_fields import (
LxmfAudioField,
LxmfFileAttachment,
LxmfImageField,
)
from meshchatx.src.backend.meshchat_utils import (
parse_lxmf_display_name,
parse_nomadnetwork_node_display_name,
@@ -20,11 +25,6 @@ from meshchatx.src.backend.nomadnet_utils import (
convert_nomadnet_field_data_to_map,
convert_nomadnet_string_data_to_map,
)
from meshchatx.src.backend.lxmf_message_fields import (
LxmfAudioField,
LxmfFileAttachment,
LxmfImageField,
)
from meshchatx.src.backend.telemetry_utils import Telemeter
@@ -122,39 +122,39 @@ def mock_app(temp_dir):
# Mock database and other managers to avoid heavy initialization
stack.enter_context(patch("meshchatx.src.backend.identity_context.Database"))
stack.enter_context(
patch("meshchatx.src.backend.identity_context.ConfigManager")
patch("meshchatx.src.backend.identity_context.ConfigManager"),
)
stack.enter_context(
patch("meshchatx.src.backend.identity_context.MessageHandler")
patch("meshchatx.src.backend.identity_context.MessageHandler"),
)
stack.enter_context(
patch("meshchatx.src.backend.identity_context.AnnounceManager")
patch("meshchatx.src.backend.identity_context.AnnounceManager"),
)
stack.enter_context(
patch("meshchatx.src.backend.identity_context.ArchiverManager")
patch("meshchatx.src.backend.identity_context.ArchiverManager"),
)
stack.enter_context(patch("meshchatx.src.backend.identity_context.MapManager"))
stack.enter_context(
patch("meshchatx.src.backend.identity_context.TelephoneManager")
patch("meshchatx.src.backend.identity_context.TelephoneManager"),
)
stack.enter_context(
patch("meshchatx.src.backend.identity_context.VoicemailManager")
patch("meshchatx.src.backend.identity_context.VoicemailManager"),
)
stack.enter_context(
patch("meshchatx.src.backend.identity_context.RingtoneManager")
patch("meshchatx.src.backend.identity_context.RingtoneManager"),
)
stack.enter_context(patch("meshchatx.src.backend.identity_context.RNCPHandler"))
stack.enter_context(
patch("meshchatx.src.backend.identity_context.RNStatusHandler")
patch("meshchatx.src.backend.identity_context.RNStatusHandler"),
)
stack.enter_context(
patch("meshchatx.src.backend.identity_context.RNProbeHandler")
patch("meshchatx.src.backend.identity_context.RNProbeHandler"),
)
stack.enter_context(
patch("meshchatx.src.backend.identity_context.TranslatorHandler")
patch("meshchatx.src.backend.identity_context.TranslatorHandler"),
)
stack.enter_context(
patch("meshchatx.src.backend.identity_context.CommunityInterfacesManager")
patch("meshchatx.src.backend.identity_context.CommunityInterfacesManager"),
)
mock_async_utils = stack.enter_context(patch("meshchatx.meshchat.AsyncUtils"))
stack.enter_context(patch("LXMF.LXMRouter"))
@@ -171,7 +171,9 @@ def mock_app(temp_dir):
stack.enter_context(patch("threading.Thread"))
stack.enter_context(
patch.object(
ReticulumMeshChat, "announce_loop", new=MagicMock(return_value=None)
ReticulumMeshChat,
"announce_loop",
new=MagicMock(return_value=None),
),
)
stack.enter_context(
@@ -183,12 +185,16 @@ def mock_app(temp_dir):
)
stack.enter_context(
patch.object(
ReticulumMeshChat, "crawler_loop", new=MagicMock(return_value=None)
ReticulumMeshChat,
"crawler_loop",
new=MagicMock(return_value=None),
),
)
stack.enter_context(
patch.object(
ReticulumMeshChat, "auto_backup_loop", new=MagicMock(return_value=None)
ReticulumMeshChat,
"auto_backup_loop",
new=MagicMock(return_value=None),
),
)
@@ -196,13 +202,13 @@ def mock_app(temp_dir):
mock_id.get_private_key = MagicMock(return_value=b"test_private_key")
stack.enter_context(
patch.object(MockIdentityClass, "from_file", return_value=mock_id)
patch.object(MockIdentityClass, "from_file", return_value=mock_id),
)
stack.enter_context(
patch.object(MockIdentityClass, "recall", return_value=mock_id)
patch.object(MockIdentityClass, "recall", return_value=mock_id),
)
stack.enter_context(
patch.object(MockIdentityClass, "from_bytes", return_value=mock_id)
patch.object(MockIdentityClass, "from_bytes", return_value=mock_id),
)
# Make run_async a no-op that doesn't trigger coroutine warnings

View File

@@ -29,39 +29,39 @@ def mock_app(temp_dir):
with ExitStack() as stack:
stack.enter_context(patch("meshchatx.src.backend.identity_context.Database"))
stack.enter_context(
patch("meshchatx.src.backend.identity_context.ConfigManager")
patch("meshchatx.src.backend.identity_context.ConfigManager"),
)
stack.enter_context(
patch("meshchatx.src.backend.identity_context.MessageHandler")
patch("meshchatx.src.backend.identity_context.MessageHandler"),
)
stack.enter_context(
patch("meshchatx.src.backend.identity_context.AnnounceManager")
patch("meshchatx.src.backend.identity_context.AnnounceManager"),
)
stack.enter_context(
patch("meshchatx.src.backend.identity_context.ArchiverManager")
patch("meshchatx.src.backend.identity_context.ArchiverManager"),
)
stack.enter_context(patch("meshchatx.src.backend.identity_context.MapManager"))
stack.enter_context(
patch("meshchatx.src.backend.identity_context.TelephoneManager")
patch("meshchatx.src.backend.identity_context.TelephoneManager"),
)
stack.enter_context(
patch("meshchatx.src.backend.identity_context.VoicemailManager")
patch("meshchatx.src.backend.identity_context.VoicemailManager"),
)
stack.enter_context(
patch("meshchatx.src.backend.identity_context.RingtoneManager")
patch("meshchatx.src.backend.identity_context.RingtoneManager"),
)
stack.enter_context(patch("meshchatx.src.backend.identity_context.RNCPHandler"))
stack.enter_context(
patch("meshchatx.src.backend.identity_context.RNStatusHandler")
patch("meshchatx.src.backend.identity_context.RNStatusHandler"),
)
stack.enter_context(
patch("meshchatx.src.backend.identity_context.RNProbeHandler")
patch("meshchatx.src.backend.identity_context.RNProbeHandler"),
)
stack.enter_context(
patch("meshchatx.src.backend.identity_context.TranslatorHandler")
patch("meshchatx.src.backend.identity_context.TranslatorHandler"),
)
stack.enter_context(
patch("meshchatx.src.backend.identity_context.CommunityInterfacesManager")
patch("meshchatx.src.backend.identity_context.CommunityInterfacesManager"),
)
stack.enter_context(patch("meshchatx.meshchat.AsyncUtils"))
stack.enter_context(patch("LXMF.LXMRouter"))
@@ -76,31 +76,37 @@ def mock_app(temp_dir):
stack.enter_context(patch("threading.Thread"))
stack.enter_context(
patch.object(
ReticulumMeshChat, "announce_loop", new=MagicMock(return_value=None)
)
ReticulumMeshChat,
"announce_loop",
new=MagicMock(return_value=None),
),
)
stack.enter_context(
patch.object(
ReticulumMeshChat,
"announce_sync_propagation_nodes",
new=MagicMock(return_value=None),
)
),
)
stack.enter_context(
patch.object(
ReticulumMeshChat, "crawler_loop", new=MagicMock(return_value=None)
)
ReticulumMeshChat,
"crawler_loop",
new=MagicMock(return_value=None),
),
)
stack.enter_context(
patch.object(
ReticulumMeshChat, "auto_backup_loop", new=MagicMock(return_value=None)
)
ReticulumMeshChat,
"auto_backup_loop",
new=MagicMock(return_value=None),
),
)
mock_id = MockIdentityClass()
mock_id.get_private_key = MagicMock(return_value=b"test_private_key")
stack.enter_context(
patch.object(MockIdentityClass, "from_file", return_value=mock_id)
patch.object(MockIdentityClass, "from_file", return_value=mock_id),
)
app = ReticulumMeshChat(
@@ -136,9 +142,10 @@ def mock_app(temp_dir):
data=st.recursive(
st.one_of(st.none(), st.booleans(), st.floats(), st.text(), st.integers()),
lambda children: st.one_of(
st.lists(children), st.dictionaries(st.text(), children)
st.lists(children),
st.dictionaries(st.text(), children),
),
)
),
)
@pytest.mark.asyncio
async def test_websocket_api_recursive_fuzzing(mock_app, data):
@@ -190,7 +197,8 @@ async def test_lxm_uri_parsing_fuzzing(mock_app, uri):
# Also test it through the websocket interface if it exists there
mock_client = MagicMock()
await mock_app.on_websocket_data_received(
mock_client, {"type": "lxm.ingest_uri", "uri": uri}
mock_client,
{"type": "lxm.ingest_uri", "uri": uri},
)
except (KeyError, TypeError, ValueError, AttributeError):
pass
@@ -232,7 +240,8 @@ def test_lxmf_message_construction_fuzzing(mock_app, content, title, fields):
@given(
table_name=st.sampled_from(["messages", "announces", "identities", "config"]),
data=st.dictionaries(
st.text(), st.one_of(st.text(), st.integers(), st.binary(), st.none())
st.text(),
st.one_of(st.text(), st.integers(), st.binary(), st.none()),
),
)
def test_database_record_fuzzing(mock_app, table_name, data):
@@ -266,10 +275,10 @@ def test_database_record_fuzzing(mock_app, table_name, data):
"map_default_lat",
"map_default_lon",
"lxmf_inbound_stamp_cost",
]
],
),
st.one_of(st.text(), st.integers(), st.booleans(), st.none()),
)
),
)
@pytest.mark.asyncio
async def test_config_update_fuzzing(mock_app, config_updates):
@@ -288,7 +297,10 @@ async def test_config_update_fuzzing(mock_app, config_updates):
@given(destination_hash=st.text(), content=st.text(), title=st.text())
@pytest.mark.asyncio
async def test_lxm_generate_paper_uri_fuzzing(
mock_app, destination_hash, content, title
mock_app,
destination_hash,
content,
title,
):
"""Fuzz lxm.generate_paper_uri WebSocket handler."""
mock_client = MagicMock()
@@ -410,7 +422,10 @@ def test_on_lxmf_delivery_fuzzing(mock_app, content, title):
app_data=st.binary(min_size=0, max_size=1000),
)
def test_on_lxmf_announce_received_fuzzing(
mock_app, aspect, destination_hash, app_data
mock_app,
aspect,
destination_hash,
app_data,
):
"""Fuzz the announce received handler."""
try:
@@ -457,7 +472,10 @@ def test_telemeter_roundtrip_fuzzing(battery, uptime, load, temperature):
try:
t = Telemeter(
battery=battery, uptime=uptime, load=load, temperature=temperature
battery=battery,
uptime=uptime,
load=load,
temperature=temperature,
)
packed = t.pack()
unpacked = Telemeter.from_packed(packed)

View File

@@ -1,8 +1,9 @@
import os
import shutil
import tempfile
from unittest.mock import AsyncMock, MagicMock, patch
from contextlib import ExitStack
from unittest.mock import AsyncMock, MagicMock, patch
import pytest
import RNS
@@ -64,17 +65,19 @@ def mock_rns():
# Mock class methods on MockIdentityClass
mock_id_instance = MockIdentityClass()
mock_id_instance.get_private_key = MagicMock(
return_value=b"initial_private_key"
return_value=b"initial_private_key",
)
stack.enter_context(
patch.object(MockIdentityClass, "from_file", return_value=mock_id_instance)
patch.object(MockIdentityClass, "from_file", return_value=mock_id_instance),
)
stack.enter_context(
patch.object(MockIdentityClass, "recall", return_value=mock_id_instance)
patch.object(MockIdentityClass, "recall", return_value=mock_id_instance),
)
stack.enter_context(
patch.object(MockIdentityClass, "from_bytes", return_value=mock_id_instance)
patch.object(
MockIdentityClass, "from_bytes", return_value=mock_id_instance
),
)
# Access specifically the ones we need to configure
@@ -118,7 +121,7 @@ async def test_hotswap_identity_success(mock_rns, temp_dir):
# Mock methods
app.teardown_identity = MagicMock()
app.setup_identity = MagicMock(
side_effect=lambda id: setattr(app, "current_context", mock_context)
side_effect=lambda id: setattr(app, "current_context", mock_context),
)
app.websocket_broadcast = AsyncMock()
@@ -164,7 +167,7 @@ async def test_hotswap_identity_keep_alive(mock_rns, temp_dir):
# Mock methods
app.teardown_identity = MagicMock()
app.setup_identity = MagicMock(
side_effect=lambda id: setattr(app, "current_context", mock_context)
side_effect=lambda id: setattr(app, "current_context", mock_context),
)
app.websocket_broadcast = AsyncMock()

View File

@@ -1,7 +1,8 @@
import unittest
import shutil
import tempfile
import unittest
from pathlib import Path
from meshchatx.src.backend.integrity_manager import IntegrityManager

View File

@@ -0,0 +1,201 @@
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

View File

@@ -1,4 +1,5 @@
import json
from meshchatx.src.backend.meshchat_utils import message_fields_have_attachments
@@ -22,7 +23,7 @@ def test_message_fields_have_attachments():
# File attachments - with files
assert (
message_fields_have_attachments(
json.dumps({"file_attachments": [{"file_name": "test.txt"}]})
json.dumps({"file_attachments": [{"file_name": "test.txt"}]}),
)
is True
)
@@ -36,8 +37,8 @@ def test_message_fields_have_attachments_mixed():
assert (
message_fields_have_attachments(
json.dumps(
{"image": "img", "file_attachments": [{"file_name": "test.txt"}]}
)
{"image": "img", "file_attachments": [{"file_name": "test.txt"}]},
),
)
is True
)
@@ -45,7 +46,7 @@ def test_message_fields_have_attachments_mixed():
# Unrelated fields
assert (
message_fields_have_attachments(
json.dumps({"title": "hello", "content": "world"})
json.dumps({"title": "hello", "content": "world"}),
)
is False
)

View File

@@ -1,10 +1,11 @@
import shutil
import tempfile
from unittest.mock import AsyncMock, MagicMock, patch
from contextlib import ExitStack
from unittest.mock import AsyncMock, MagicMock, patch
import LXMF
import pytest
import RNS
import LXMF
from meshchatx.meshchat import ReticulumMeshChat
@@ -80,17 +81,19 @@ def mock_rns():
# Mock class methods on MockIdentityClass
mock_id_instance = MockIdentityClass()
mock_id_instance.get_private_key = MagicMock(
return_value=b"initial_private_key"
return_value=b"initial_private_key",
)
stack.enter_context(
patch.object(MockIdentityClass, "from_file", return_value=mock_id_instance)
patch.object(MockIdentityClass, "from_file", return_value=mock_id_instance),
)
stack.enter_context(
patch.object(MockIdentityClass, "recall", return_value=mock_id_instance)
patch.object(MockIdentityClass, "recall", return_value=mock_id_instance),
)
stack.enter_context(
patch.object(MockIdentityClass, "from_bytes", return_value=mock_id_instance)
patch.object(
MockIdentityClass, "from_bytes", return_value=mock_id_instance
),
)
# Setup mock LXMessage
@@ -249,7 +252,7 @@ async def test_receive_message_updates_icon(mock_rns, temp_dir):
"new_icon",
b"\xff\xff\xff", # #ffffff
b"\x00\x00\x00", # #000000
]
],
}
# Mock methods

View File

@@ -1,11 +1,13 @@
import json
import shutil
import tempfile
import pytest
import json
from unittest.mock import MagicMock, patch
from meshchatx.meshchat import ReticulumMeshChat
import RNS
import LXMF
import pytest
import RNS
from meshchatx.meshchat import ReticulumMeshChat
# Store original constants
PR_IDLE = LXMF.LXMRouter.PR_IDLE
@@ -58,7 +60,7 @@ def mock_app(temp_dir):
mock_rns_inst.transport_enabled.return_value = False
with patch(
"meshchatx.src.backend.meshchat_utils.LXMRouter"
"meshchatx.src.backend.meshchat_utils.LXMRouter",
) as mock_utils_router:
mock_utils_router.PR_IDLE = PR_IDLE
mock_utils_router.PR_PATH_REQUESTED = PR_PATH_REQUESTED
@@ -76,7 +78,9 @@ def mock_app(temp_dir):
app.current_context.message_router = mock_router
with patch.object(
app, "send_config_to_websocket_clients", return_value=None
app,
"send_config_to_websocket_clients",
return_value=None,
):
yield app
@@ -87,11 +91,11 @@ async def test_lxmf_propagation_config(mock_app):
node_hash_bytes = bytes.fromhex(node_hash_hex)
await mock_app.update_config(
{"lxmf_preferred_propagation_node_destination_hash": node_hash_hex}
{"lxmf_preferred_propagation_node_destination_hash": node_hash_hex},
)
mock_app.current_context.message_router.set_outbound_propagation_node.assert_called_with(
node_hash_bytes
node_hash_bytes,
)
assert (
mock_app.config.lxmf_preferred_propagation_node_destination_hash.get()
@@ -159,7 +163,7 @@ async def test_send_failed_via_prop_node(mock_app):
@pytest.mark.asyncio
async def test_auto_sync_interval_config(mock_app):
await mock_app.update_config(
{"lxmf_preferred_propagation_node_auto_sync_interval_seconds": 3600}
{"lxmf_preferred_propagation_node_auto_sync_interval_seconds": 3600},
)
assert (
mock_app.config.lxmf_preferred_propagation_node_auto_sync_interval_seconds.get()
@@ -198,17 +202,17 @@ async def test_user_provided_node_hash(mock_app):
# Set this node as preferred
await mock_app.update_config(
{"lxmf_preferred_propagation_node_destination_hash": node_hash_hex}
{"lxmf_preferred_propagation_node_destination_hash": node_hash_hex},
)
# Check if the router was updated with the correct bytes
mock_app.current_context.message_router.set_outbound_propagation_node.assert_called_with(
bytes.fromhex(node_hash_hex)
bytes.fromhex(node_hash_hex),
)
# Trigger a sync request
mock_app.current_context.message_router.get_outbound_propagation_node.return_value = bytes.fromhex(
node_hash_hex
node_hash_hex,
)
sync_handler = next(
r.handler
@@ -219,5 +223,5 @@ async def test_user_provided_node_hash(mock_app):
# Verify the router was told to sync for our identity
mock_app.current_context.message_router.request_messages_from_propagation_node.assert_called_with(
mock_app.current_context.identity
mock_app.current_context.identity,
)

View File

@@ -1,11 +1,13 @@
import json
import shutil
import tempfile
import pytest
import json
from unittest.mock import MagicMock, patch
from meshchatx.meshchat import ReticulumMeshChat
import RNS
import LXMF
import pytest
import RNS
from meshchatx.meshchat import ReticulumMeshChat
# Store original constants
PR_IDLE = LXMF.LXMRouter.PR_IDLE
@@ -48,7 +50,7 @@ def mock_app(temp_dir):
mock_rns_inst.transport_enabled.return_value = False
with patch(
"meshchatx.src.backend.meshchat_utils.LXMRouter"
"meshchatx.src.backend.meshchat_utils.LXMRouter",
) as mock_utils_router:
mock_utils_router.PR_IDLE = PR_IDLE
mock_utils_router.PR_COMPLETE = PR_COMPLETE
@@ -62,7 +64,9 @@ def mock_app(temp_dir):
app.current_context.message_router = mock_router
with patch.object(
app, "send_config_to_websocket_clients", return_value=None
app,
"send_config_to_websocket_clients",
return_value=None,
):
yield app
@@ -117,13 +121,13 @@ async def test_specific_node_hash_validation(mock_app):
with patch.object(mock_app, "send_config_to_websocket_clients", return_value=None):
# Set the preferred propagation node
await mock_app.update_config(
{"lxmf_preferred_propagation_node_destination_hash": node_hash_hex}
{"lxmf_preferred_propagation_node_destination_hash": node_hash_hex},
)
# Verify it was set on the router correctly as 16 bytes
expected_bytes = bytes.fromhex(node_hash_hex)
mock_app.current_context.message_router.set_outbound_propagation_node.assert_called_with(
expected_bytes
expected_bytes,
)
# Trigger sync

View File

@@ -3,10 +3,11 @@ import json
from unittest.mock import MagicMock
import LXMF
from meshchatx.src.backend.lxmf_utils import (
convert_db_lxmf_message_to_dict,
convert_lxmf_message_to_dict,
convert_lxmf_state_to_string,
convert_db_lxmf_message_to_dict,
)
@@ -129,9 +130,9 @@ def test_convert_db_lxmf_message_to_dict():
{
"file_name": "f.txt",
"file_bytes": base64.b64encode(b"file").decode(),
}
},
],
}
},
),
"timestamp": 1234567890,
"rssi": -60,

View File

@@ -1,10 +1,11 @@
import os
import shutil
import tempfile
import sqlite3
import tempfile
from unittest.mock import MagicMock, patch
import pytest
from meshchatx.src.backend.map_manager import MapManager
@@ -83,11 +84,12 @@ def test_get_tile(mock_config, temp_dir):
conn = sqlite3.connect(db_path)
conn.execute(
"CREATE TABLE tiles (zoom_level integer, tile_column integer, tile_row integer, tile_data blob)"
"CREATE TABLE tiles (zoom_level integer, tile_column integer, tile_row integer, tile_data blob)",
)
# Zoom 0, Tile 0,0. TMS y for 0/0/0 is (1<<0)-1-0 = 0
conn.execute(
"INSERT INTO tiles VALUES (0, 0, 0, ?)", (sqlite3.Binary(b"tile_data"),)
"INSERT INTO tiles VALUES (0, 0, 0, ?)",
(sqlite3.Binary(b"tile_data"),),
)
conn.commit()
conn.close()

View File

@@ -1,4 +1,5 @@
import unittest
from meshchatx.src.backend.markdown_renderer import MarkdownRenderer
@@ -24,7 +25,7 @@ class TestMarkdownRenderer(unittest.TestCase):
# Check for escaped characters
self.assertTrue(
"print(&#x27;hello&#x27;)" in rendered
or "print(&#039;hello&#039;)" in rendered
or "print(&#039;hello&#039;)" in rendered,
)
def test_lists(self):

View File

@@ -1,8 +1,9 @@
import unittest
import os
import secrets
import shutil
import tempfile
import secrets
import unittest
from meshchatx.src.backend.database import Database
from meshchatx.src.backend.identity_manager import IdentityManager
from tests.backend.benchmarking_utils import MemoryTracker
@@ -54,7 +55,9 @@ class TestMemoryProfiling(unittest.TestCase):
# 10k messages * 512 bytes is ~5MB of raw content.
# SQLite should handle this efficiently.
self.assertLess(
tracker.mem_delta, 20.0, "Excessive memory growth during DB insertion"
tracker.mem_delta,
20.0,
"Excessive memory growth during DB insertion",
)
def test_identity_manager_memory(self):
@@ -70,7 +73,9 @@ class TestMemoryProfiling(unittest.TestCase):
self.assertEqual(len(identities), 50)
self.assertLess(
tracker.mem_delta, 10.0, "Identity management consumed too much memory"
tracker.mem_delta,
10.0,
"Identity management consumed too much memory",
)
def test_large_message_processing(self):
@@ -124,7 +129,9 @@ class TestMemoryProfiling(unittest.TestCase):
self.db.announces.upsert_announce(data)
self.assertLess(
tracker.mem_delta, 15.0, "Announce updates causing memory bloat"
tracker.mem_delta,
15.0,
"Announce updates causing memory bloat",
)

View File

@@ -1,8 +1,8 @@
import os
import shutil
import tempfile
from unittest.mock import MagicMock, patch
from contextlib import ExitStack
from unittest.mock import MagicMock, patch
import pytest
import RNS
@@ -31,36 +31,36 @@ def mock_app(temp_dir):
with ExitStack() as stack:
stack.enter_context(patch("meshchatx.src.backend.identity_context.Database"))
stack.enter_context(
patch("meshchatx.src.backend.identity_context.ConfigManager")
patch("meshchatx.src.backend.identity_context.ConfigManager"),
)
stack.enter_context(
patch("meshchatx.src.backend.identity_context.MessageHandler")
patch("meshchatx.src.backend.identity_context.MessageHandler"),
)
stack.enter_context(
patch("meshchatx.src.backend.identity_context.AnnounceManager")
patch("meshchatx.src.backend.identity_context.AnnounceManager"),
)
stack.enter_context(
patch("meshchatx.src.backend.identity_context.ArchiverManager")
patch("meshchatx.src.backend.identity_context.ArchiverManager"),
)
stack.enter_context(patch("meshchatx.src.backend.identity_context.MapManager"))
stack.enter_context(
patch("meshchatx.src.backend.identity_context.TelephoneManager")
patch("meshchatx.src.backend.identity_context.TelephoneManager"),
)
stack.enter_context(
patch("meshchatx.src.backend.identity_context.VoicemailManager")
patch("meshchatx.src.backend.identity_context.VoicemailManager"),
)
stack.enter_context(
patch("meshchatx.src.backend.identity_context.RingtoneManager")
patch("meshchatx.src.backend.identity_context.RingtoneManager"),
)
stack.enter_context(patch("meshchatx.src.backend.identity_context.RNCPHandler"))
stack.enter_context(
patch("meshchatx.src.backend.identity_context.RNStatusHandler")
patch("meshchatx.src.backend.identity_context.RNStatusHandler"),
)
stack.enter_context(
patch("meshchatx.src.backend.identity_context.RNProbeHandler")
patch("meshchatx.src.backend.identity_context.RNProbeHandler"),
)
stack.enter_context(
patch("meshchatx.src.backend.identity_context.TranslatorHandler")
patch("meshchatx.src.backend.identity_context.TranslatorHandler"),
)
stack.enter_context(patch("LXMF.LXMRouter"))
stack.enter_context(patch("RNS.Identity", MockIdentityClass))
@@ -72,34 +72,34 @@ def mock_app(temp_dir):
ReticulumMeshChat,
"announce_loop",
new=MagicMock(return_value=None),
)
),
)
stack.enter_context(
patch.object(
ReticulumMeshChat,
"announce_sync_propagation_nodes",
new=MagicMock(return_value=None),
)
),
)
stack.enter_context(
patch.object(
ReticulumMeshChat,
"crawler_loop",
new=MagicMock(return_value=None),
)
),
)
mock_id = MockIdentityClass()
mock_id.get_private_key = MagicMock(return_value=b"test_private_key")
stack.enter_context(
patch.object(MockIdentityClass, "from_file", return_value=mock_id)
patch.object(MockIdentityClass, "from_file", return_value=mock_id),
)
stack.enter_context(
patch.object(MockIdentityClass, "recall", return_value=mock_id)
patch.object(MockIdentityClass, "recall", return_value=mock_id),
)
stack.enter_context(
patch.object(MockIdentityClass, "from_bytes", return_value=mock_id)
patch.object(MockIdentityClass, "from_bytes", return_value=mock_id),
)
app = ReticulumMeshChat(

View File

@@ -1,5 +1,6 @@
import unittest
from unittest.mock import MagicMock
from meshchatx.src.backend.message_handler import MessageHandler

View File

@@ -1,5 +1,6 @@
import unittest
from unittest.mock import MagicMock
from meshchatx.src.backend.nomadnet_downloader import NomadnetDownloader

View File

@@ -1,7 +1,7 @@
import os
import time
from unittest.mock import MagicMock, patch
from contextlib import ExitStack
from unittest.mock import MagicMock, patch
import pytest
import RNS
@@ -49,33 +49,33 @@ def mock_app(db, tmp_path):
stack.enter_context(patch("RNS.Transport"))
stack.enter_context(patch("LXMF.LXMRouter"))
stack.enter_context(
patch("meshchatx.src.backend.identity_context.TelephoneManager")
patch("meshchatx.src.backend.identity_context.TelephoneManager"),
)
stack.enter_context(
patch("meshchatx.src.backend.identity_context.VoicemailManager")
patch("meshchatx.src.backend.identity_context.VoicemailManager"),
)
stack.enter_context(
patch("meshchatx.src.backend.identity_context.RingtoneManager")
patch("meshchatx.src.backend.identity_context.RingtoneManager"),
)
stack.enter_context(patch("meshchatx.src.backend.identity_context.RNCPHandler"))
stack.enter_context(
patch("meshchatx.src.backend.identity_context.RNStatusHandler")
patch("meshchatx.src.backend.identity_context.RNStatusHandler"),
)
stack.enter_context(
patch("meshchatx.src.backend.identity_context.RNProbeHandler")
patch("meshchatx.src.backend.identity_context.RNProbeHandler"),
)
stack.enter_context(
patch("meshchatx.src.backend.identity_context.TranslatorHandler")
patch("meshchatx.src.backend.identity_context.TranslatorHandler"),
)
stack.enter_context(
patch("meshchatx.src.backend.identity_context.ArchiverManager")
patch("meshchatx.src.backend.identity_context.ArchiverManager"),
)
stack.enter_context(patch("meshchatx.src.backend.identity_context.MapManager"))
stack.enter_context(
patch("meshchatx.src.backend.identity_context.MessageHandler")
patch("meshchatx.src.backend.identity_context.MessageHandler"),
)
stack.enter_context(
patch("meshchatx.src.backend.identity_context.AnnounceManager")
patch("meshchatx.src.backend.identity_context.AnnounceManager"),
)
stack.enter_context(patch("threading.Thread"))
@@ -83,44 +83,52 @@ def mock_app(db, tmp_path):
mock_id.get_private_key = MagicMock(return_value=b"test_private_key")
stack.enter_context(
patch.object(MockIdentityClass, "from_file", return_value=mock_id)
patch.object(MockIdentityClass, "from_file", return_value=mock_id),
)
stack.enter_context(
patch.object(MockIdentityClass, "recall", return_value=mock_id)
patch.object(MockIdentityClass, "recall", return_value=mock_id),
)
stack.enter_context(
patch.object(MockIdentityClass, "from_bytes", return_value=mock_id)
patch.object(MockIdentityClass, "from_bytes", return_value=mock_id),
)
# Patch background threads and other heavy init
stack.enter_context(
patch.object(
ReticulumMeshChat, "announce_loop", new=MagicMock(return_value=None)
)
ReticulumMeshChat,
"announce_loop",
new=MagicMock(return_value=None),
),
)
stack.enter_context(
patch.object(
ReticulumMeshChat,
"announce_sync_propagation_nodes",
new=MagicMock(return_value=None),
)
),
)
stack.enter_context(
patch.object(
ReticulumMeshChat, "crawler_loop", new=MagicMock(return_value=None)
)
ReticulumMeshChat,
"crawler_loop",
new=MagicMock(return_value=None),
),
)
stack.enter_context(
patch.object(
ReticulumMeshChat, "auto_backup_loop", new=MagicMock(return_value=None)
)
ReticulumMeshChat,
"auto_backup_loop",
new=MagicMock(return_value=None),
),
)
# Prevent JSON serialization issues with MagicMocks
stack.enter_context(
patch.object(
ReticulumMeshChat, "send_config_to_websocket_clients", return_value=None
)
ReticulumMeshChat,
"send_config_to_websocket_clients",
return_value=None,
),
)
app = ReticulumMeshChat(
@@ -262,7 +270,10 @@ async def test_notifications_api(mock_app):
# Let's test a spike of notifications
for i in range(100):
mock_app.database.misc.add_notification(
f"type{i}", f"hash{i}", f"title{i}", f"content{i}"
f"type{i}",
f"hash{i}",
f"title{i}",
f"content{i}",
)
notifications = mock_app.database.misc.get_notifications(limit=50)
@@ -295,7 +306,10 @@ def test_voicemail_notification_fuzzing(mock_app, remote_hash, remote_name, dura
call_was_established=st.booleans(),
)
def test_missed_call_notification_fuzzing(
mock_app, remote_hash, status_code, call_was_established
mock_app,
remote_hash,
status_code,
call_was_established,
):
"""Fuzz missed call notification triggering."""
mock_app.database.misc.provider.execute("DELETE FROM notifications")

View File

@@ -1,12 +1,13 @@
import unittest
import os
import secrets
import shutil
import tempfile
import time
import secrets
import unittest
from unittest.mock import MagicMock
from meshchatx.src.backend.database import Database
from meshchatx.src.backend.announce_manager import AnnounceManager
from meshchatx.src.backend.database import Database
class TestPerformanceBottlenecks(unittest.TestCase):
@@ -60,7 +61,9 @@ class TestPerformanceBottlenecks(unittest.TestCase):
for offset in offsets:
start = time.time()
msgs = self.db.messages.get_conversation_messages(
peer_hash, limit=limit, offset=offset
peer_hash,
limit=limit,
offset=offset,
)
duration = (time.time() - start) * 1000
print(f"Fetch {limit} messages at offset {offset}: {duration:.2f}ms")
@@ -103,7 +106,7 @@ class TestPerformanceBottlenecks(unittest.TestCase):
duration_total = time.time() - start_total
avg_duration = (duration_total / num_announces) * 1000
print(
f"Processed {num_announces} announces in {duration_total:.2f}s (Avg: {avg_duration:.2f}ms/announce)"
f"Processed {num_announces} announces in {duration_total:.2f}s (Avg: {avg_duration:.2f}ms/announce)",
)
self.assertLess(avg_duration, 20, "Announce processing is too slow!")
@@ -129,7 +132,9 @@ class TestPerformanceBottlenecks(unittest.TestCase):
# Benchmark filtered search with pagination
start = time.time()
results = self.announce_manager.get_filtered_announces(
aspect="lxmf.delivery", limit=50, offset=1000
aspect="lxmf.delivery",
limit=50,
offset=1000,
)
duration = (time.time() - start) * 1000
print(f"Filtered announce pagination (offset 1000): {duration:.2f}ms")
@@ -164,7 +169,7 @@ class TestPerformanceBottlenecks(unittest.TestCase):
]
print(
f"\nRunning {num_threads} threads inserting {announces_per_thread} announces each..."
f"\nRunning {num_threads} threads inserting {announces_per_thread} announces each...",
)
start = time.time()
for t in threads:
@@ -174,7 +179,7 @@ class TestPerformanceBottlenecks(unittest.TestCase):
duration = time.time() - start
print(
f"Concurrent insertion took {duration:.2f}s for {num_threads * announces_per_thread} announces"
f"Concurrent insertion took {duration:.2f}s for {num_threads * announces_per_thread} announces",
)
self.assertLess(duration, 10.0, "Concurrent announce insertion is too slow!")

View File

@@ -1,11 +1,13 @@
import json
import shutil
import tempfile
import pytest
import json
from unittest.mock import MagicMock, patch
from meshchatx.meshchat import ReticulumMeshChat
import pytest
import RNS
from meshchatx.meshchat import ReticulumMeshChat
@pytest.fixture
def temp_dir():

View File

@@ -5,6 +5,7 @@ from unittest.mock import MagicMock, patch
import pytest
import RNS
from meshchatx.src.backend.rncp_handler import RNCPHandler
@@ -40,7 +41,9 @@ def mock_rns():
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
MockIdentityClass,
"from_bytes",
return_value=mock_id_instance,
),
):
mock_dest_instance = MagicMock()
@@ -85,7 +88,9 @@ def test_setup_receive_destination(mock_rns, temp_dir):
mock_rns["Reticulum"].identitypath = temp_dir
_ = handler.setup_receive_destination(
allowed_hashes=["abc123def456"], fetch_allowed=True, fetch_jail=temp_dir
allowed_hashes=["abc123def456"],
fetch_allowed=True,
fetch_jail=temp_dir,
)
assert handler.receive_destination is not None

View File

@@ -1,9 +1,11 @@
import pytest
import json
from unittest.mock import MagicMock, patch, AsyncMock
from meshchatx.meshchat import ReticulumMeshChat
from unittest.mock import AsyncMock, MagicMock, patch
import pytest
import RNS
from meshchatx.meshchat import ReticulumMeshChat
@pytest.fixture
def temp_dir(tmp_path):

View File

@@ -45,7 +45,9 @@ def mock_rns():
new=MagicMock(return_value=None),
),
patch.object(
ReticulumMeshChat, "send_config_to_websocket_clients", return_value=None
ReticulumMeshChat,
"send_config_to_websocket_clients",
return_value=None,
),
):
# Setup mock instance
@@ -57,10 +59,14 @@ def mock_rns():
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
MockIdentityClass,
"from_bytes",
return_value=mock_id_instance,
),
patch.object(
MockIdentityClass, "full_hash", return_value=b"full_hash_bytes"
MockIdentityClass,
"full_hash",
return_value=b"full_hash_bytes",
),
):
# Setup mock transport
@@ -263,7 +269,7 @@ async def test_hotswap_identity(mock_rns, temp_dir):
with (
patch("meshchatx.src.backend.identity_context.Database"),
patch(
"meshchatx.src.backend.identity_context.ConfigManager"
"meshchatx.src.backend.identity_context.ConfigManager",
) as mock_config_class,
patch("meshchatx.src.backend.identity_context.MessageHandler"),
patch("meshchatx.src.backend.identity_context.AnnounceManager"),

View File

@@ -1,8 +1,10 @@
import pytest
from unittest.mock import MagicMock, patch
from meshchatx.src.backend.rnstatus_handler import RNStatusHandler
import pytest
import RNS
from meshchatx.src.backend.rnstatus_handler import RNStatusHandler
@pytest.fixture
def mock_reticulum_instance():
@@ -50,7 +52,7 @@ def test_blackhole_status_missing_api(mock_reticulum_instance):
# But we can patch the RNS object inside rnstatus_handler module.
with patch(
"meshchatx.src.backend.rnstatus_handler.RNS.Reticulum"
"meshchatx.src.backend.rnstatus_handler.RNS.Reticulum",
) as mock_rns_class:
del mock_rns_class.publish_blackhole_enabled

View File

@@ -1,6 +1,6 @@
import base64
import os
import time
import base64
from contextlib import ExitStack
from unittest.mock import MagicMock, patch
@@ -27,39 +27,39 @@ def mock_app():
# Mock core dependencies that interact with the system/network
stack.enter_context(patch("meshchatx.src.backend.identity_context.Database"))
stack.enter_context(
patch("meshchatx.src.backend.identity_context.ConfigManager")
patch("meshchatx.src.backend.identity_context.ConfigManager"),
)
stack.enter_context(
patch("meshchatx.src.backend.identity_context.MessageHandler")
patch("meshchatx.src.backend.identity_context.MessageHandler"),
)
stack.enter_context(
patch("meshchatx.src.backend.identity_context.AnnounceManager")
patch("meshchatx.src.backend.identity_context.AnnounceManager"),
)
stack.enter_context(
patch("meshchatx.src.backend.identity_context.ArchiverManager")
patch("meshchatx.src.backend.identity_context.ArchiverManager"),
)
stack.enter_context(patch("meshchatx.src.backend.identity_context.MapManager"))
stack.enter_context(
patch("meshchatx.src.backend.identity_context.TelephoneManager")
patch("meshchatx.src.backend.identity_context.TelephoneManager"),
)
stack.enter_context(
patch("meshchatx.src.backend.identity_context.VoicemailManager")
patch("meshchatx.src.backend.identity_context.VoicemailManager"),
)
stack.enter_context(
patch("meshchatx.src.backend.identity_context.RingtoneManager")
patch("meshchatx.src.backend.identity_context.RingtoneManager"),
)
stack.enter_context(patch("meshchatx.src.backend.identity_context.RNCPHandler"))
stack.enter_context(
patch("meshchatx.src.backend.identity_context.RNStatusHandler")
patch("meshchatx.src.backend.identity_context.RNStatusHandler"),
)
stack.enter_context(
patch("meshchatx.src.backend.identity_context.RNProbeHandler")
patch("meshchatx.src.backend.identity_context.RNProbeHandler"),
)
stack.enter_context(
patch("meshchatx.src.backend.identity_context.TranslatorHandler")
patch("meshchatx.src.backend.identity_context.TranslatorHandler"),
)
stack.enter_context(
patch("meshchatx.src.backend.identity_context.CommunityInterfacesManager")
patch("meshchatx.src.backend.identity_context.CommunityInterfacesManager"),
)
mock_async_utils = stack.enter_context(patch("meshchatx.meshchat.AsyncUtils"))
@@ -72,36 +72,40 @@ def mock_app():
# Stop background loops
stack.enter_context(
patch.object(ReticulumMeshChat, "announce_loop", return_value=None)
patch.object(ReticulumMeshChat, "announce_loop", return_value=None),
)
stack.enter_context(
patch.object(
ReticulumMeshChat, "announce_sync_propagation_nodes", return_value=None
)
ReticulumMeshChat,
"announce_sync_propagation_nodes",
return_value=None,
),
)
stack.enter_context(
patch.object(ReticulumMeshChat, "crawler_loop", return_value=None)
patch.object(ReticulumMeshChat, "crawler_loop", return_value=None),
)
stack.enter_context(
patch.object(ReticulumMeshChat, "auto_backup_loop", return_value=None)
patch.object(ReticulumMeshChat, "auto_backup_loop", return_value=None),
)
stack.enter_context(
patch.object(
ReticulumMeshChat, "send_config_to_websocket_clients", return_value=None
)
ReticulumMeshChat,
"send_config_to_websocket_clients",
return_value=None,
),
)
mock_id = MockIdentityClass()
mock_id.get_private_key = MagicMock(return_value=b"test_private_key")
stack.enter_context(
patch.object(MockIdentityClass, "from_file", return_value=mock_id)
patch.object(MockIdentityClass, "from_file", return_value=mock_id),
)
stack.enter_context(
patch.object(MockIdentityClass, "recall", return_value=mock_id)
patch.object(MockIdentityClass, "recall", return_value=mock_id),
)
stack.enter_context(
patch.object(MockIdentityClass, "from_bytes", return_value=mock_id)
patch.object(MockIdentityClass, "from_bytes", return_value=mock_id),
)
def mock_run_async(coro):
@@ -117,10 +121,10 @@ def mock_app():
return MagicMock()
mock_telephone_manager = stack.enter_context(
patch("meshchatx.src.backend.identity_context.TelephoneManager")
patch("meshchatx.src.backend.identity_context.TelephoneManager"),
)
mock_telephone_manager.return_value.initiate = MagicMock(
side_effect=mock_initiate
side_effect=mock_initiate,
)
app = ReticulumMeshChat(
@@ -1015,7 +1019,11 @@ def test_telemetry_unpack_location_fuzzing(mock_app, packed_location):
bearing=st.one_of(st.floats(allow_nan=True, allow_infinity=True), st.integers()),
accuracy=st.one_of(st.floats(allow_nan=True, allow_infinity=True), st.integers()),
last_update=st.one_of(
st.integers(), st.floats(), st.text(), st.binary(), st.none()
st.integers(),
st.floats(),
st.text(),
st.binary(),
st.none(),
),
)
def test_telemetry_pack_location_fuzzing(
@@ -1052,12 +1060,15 @@ def test_telemetry_pack_location_fuzzing(
st.none(),
),
data=st.one_of(
st.text(), st.binary(), st.dictionaries(keys=st.text(), values=st.text())
st.text(),
st.binary(),
st.dictionaries(keys=st.text(), values=st.text()),
),
received_from=st.one_of(st.text(), st.binary(), st.none()),
physical_link=st.one_of(
st.dictionaries(
keys=st.text(), values=st.one_of(st.integers(), st.floats(), st.text())
keys=st.text(),
values=st.one_of(st.integers(), st.floats(), st.text()),
),
st.text(),
st.binary(),
@@ -1456,7 +1467,9 @@ def test_lxst_audio_frame_handling_fuzzing(mock_app, audio_frame):
caller_identity_hash=st.binary(min_size=0, max_size=100),
)
def test_lxst_call_state_transitions_fuzzing(
mock_app, call_status, caller_identity_hash
mock_app,
call_status,
caller_identity_hash,
):
"""Fuzz LXST call state transitions with invalid states."""
try:
@@ -1490,7 +1503,7 @@ def test_lxst_call_state_transitions_fuzzing(
"2400",
"3200",
"invalid",
]
],
),
)
def test_codec2_decode_fuzzing(mock_app, codec2_data, codec_mode):
@@ -1577,7 +1590,8 @@ def test_lxst_profile_switching_fuzzing(mock_app, profile_id):
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
@given(
destination_hash=st.one_of(
st.binary(min_size=0, max_size=100), st.text(min_size=0, max_size=100)
st.binary(min_size=0, max_size=100),
st.text(min_size=0, max_size=100),
),
timeout=st.one_of(
st.integers(min_value=-100, max_value=1000),
@@ -1612,7 +1626,8 @@ def test_lxst_call_initiation_fuzzing(mock_app, destination_hash, timeout):
loop.run_until_complete(
mock_app.telephone_manager.initiate(
dest_hash_bytes, timeout_seconds=timeout_int
dest_hash_bytes,
timeout_seconds=timeout_int,
),
)
finally:
@@ -1716,16 +1731,21 @@ def test_lxmf_message_unpacking_fuzzing(mock_app, lxmf_message_data):
pipeline_config=st.dictionaries(
keys=st.text(),
values=st.one_of(
st.text(), st.binary(), st.integers(), st.floats(), st.booleans(), st.none()
st.text(),
st.binary(),
st.integers(),
st.floats(),
st.booleans(),
st.none(),
),
),
)
def test_lxst_pipeline_config_fuzzing(mock_app, pipeline_config):
"""Fuzz LXST Pipeline configuration."""
from LXST.Pipeline import Pipeline
from LXST.Codecs import Null
from LXST.Sources import Source
from LXST.Pipeline import Pipeline
from LXST.Sinks import Sink
from LXST.Sources import Source
class DummySource(Source):
pass
@@ -1748,7 +1768,8 @@ def test_lxst_pipeline_config_fuzzing(mock_app, pipeline_config):
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
@given(
sink_data=st.one_of(
st.binary(min_size=0, max_size=10000), st.text(min_size=0, max_size=1000)
st.binary(min_size=0, max_size=10000),
st.text(min_size=0, max_size=1000),
),
)
def test_lxst_sink_handling_fuzzing(mock_app, sink_data):
@@ -1784,7 +1805,8 @@ def test_telemetry_packing_invariants_regression():
}
packed = Telemeter.pack(
time_utc=original_data["time"]["utc"], location=original_data["location"]
time_utc=original_data["time"]["utc"],
location=original_data["location"],
)
unpacked = Telemeter.from_packed(packed)

View File

@@ -34,12 +34,16 @@ def mock_rns():
patch("meshchatx.meshchat.get_file_path", return_value="/tmp/mock_path"),
patch.object(ReticulumMeshChat, "announce_loop", return_value=None),
patch.object(
ReticulumMeshChat, "announce_sync_propagation_nodes", return_value=None
ReticulumMeshChat,
"announce_sync_propagation_nodes",
return_value=None,
),
patch.object(ReticulumMeshChat, "crawler_loop", return_value=None),
patch.object(ReticulumMeshChat, "auto_backup_loop", return_value=None),
patch.object(
ReticulumMeshChat, "send_config_to_websocket_clients", return_value=None
ReticulumMeshChat,
"send_config_to_websocket_clients",
return_value=None,
),
):
# Setup mock instance
@@ -51,7 +55,9 @@ def mock_rns():
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
MockIdentityClass,
"from_bytes",
return_value=mock_id_instance,
),
):
# Setup mock transport
@@ -83,7 +89,7 @@ def test_reticulum_meshchat_init(mock_rns, temp_dir):
with (
patch("meshchatx.src.backend.identity_context.Database") as mock_db_class,
patch(
"meshchatx.src.backend.identity_context.ConfigManager"
"meshchatx.src.backend.identity_context.ConfigManager",
) as mock_config_class,
patch("meshchatx.src.backend.identity_context.MessageHandler"),
patch("meshchatx.src.backend.identity_context.AnnounceManager"),
@@ -152,7 +158,7 @@ def test_reticulum_meshchat_init_with_auth(mock_rns, temp_dir):
with (
patch("meshchatx.src.backend.identity_context.Database"),
patch(
"meshchatx.src.backend.identity_context.ConfigManager"
"meshchatx.src.backend.identity_context.ConfigManager",
) as mock_config_class,
patch("meshchatx.src.backend.identity_context.MessageHandler"),
patch("meshchatx.src.backend.identity_context.AnnounceManager"),

View File

@@ -1,11 +1,12 @@
import shutil
import tempfile
import base64
import secrets
from unittest.mock import MagicMock, patch, mock_open
import shutil
import tempfile
from unittest.mock import MagicMock, mock_open, patch
import pytest
import RNS
from meshchatx.meshchat import ReticulumMeshChat, main
@@ -43,12 +44,16 @@ def mock_rns():
patch("LXMF.LXMRouter"),
patch.object(ReticulumMeshChat, "announce_loop", return_value=None),
patch.object(
ReticulumMeshChat, "announce_sync_propagation_nodes", return_value=None
ReticulumMeshChat,
"announce_sync_propagation_nodes",
return_value=None,
),
patch.object(ReticulumMeshChat, "crawler_loop", return_value=None),
patch.object(ReticulumMeshChat, "auto_backup_loop", return_value=None),
patch.object(
ReticulumMeshChat, "send_config_to_websocket_clients", return_value=None
ReticulumMeshChat,
"send_config_to_websocket_clients",
return_value=None,
),
):
mock_id_instance = MockIdentityClass()
@@ -57,7 +62,9 @@ def mock_rns():
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
MockIdentityClass,
"from_bytes",
return_value=mock_id_instance,
),
):
yield {
@@ -73,7 +80,7 @@ def test_run_https_logic(mock_rns, temp_dir):
with (
patch("meshchatx.src.backend.identity_context.Database"),
patch(
"meshchatx.src.backend.identity_context.ConfigManager"
"meshchatx.src.backend.identity_context.ConfigManager",
) as mock_config_class,
patch("meshchatx.meshchat.generate_ssl_certificate") as mock_gen_cert,
patch("ssl.SSLContext") as mock_ssl_context,
@@ -97,7 +104,7 @@ def test_run_https_logic(mock_rns, temp_dir):
mock_config = mock_config_class.return_value
# provide a real-looking secret key
mock_config.auth_session_secret.get.return_value = base64.urlsafe_b64encode(
secrets.token_bytes(32)
secrets.token_bytes(32),
).decode()
mock_config.display_name.get.return_value = "Test"
mock_config.lxmf_propagation_node_stamp_cost.get.return_value = 0
@@ -137,7 +144,7 @@ def test_database_integrity_recovery(mock_rns, temp_dir):
with (
patch("meshchatx.src.backend.identity_context.Database") as mock_db_class,
patch(
"meshchatx.src.backend.identity_context.ConfigManager"
"meshchatx.src.backend.identity_context.ConfigManager",
) as mock_config_class,
patch("meshchatx.src.backend.identity_context.MessageHandler"),
patch("meshchatx.src.backend.identity_context.AnnounceManager"),
@@ -190,7 +197,7 @@ def test_identity_loading_fallback(mock_rns, temp_dir):
with (
patch("meshchatx.src.backend.identity_context.Database"),
patch(
"meshchatx.src.backend.identity_context.ConfigManager"
"meshchatx.src.backend.identity_context.ConfigManager",
) as mock_config_class,
patch("RNS.Identity") as mock_id_class,
patch("os.path.exists", return_value=False), # Pretend files don't exist
@@ -210,7 +217,7 @@ def test_identity_loading_fallback(mock_rns, temp_dir):
# Mock sys.argv to use default behavior (random generation)
with patch("sys.argv", ["meshchat.py", "--storage-dir", temp_dir]):
with patch(
"meshchatx.meshchat.ReticulumMeshChat"
"meshchatx.meshchat.ReticulumMeshChat",
): # Mock ReticulumMeshChat to avoid full init
with patch("aiohttp.web.run_app"):
main()
@@ -235,25 +242,25 @@ def test_cli_flags_and_envs(mock_rns, temp_dir):
"MESHCHAT_AUTH": "1",
"MESHCHAT_STORAGE_DIR": temp_dir,
}
with patch.dict("os.environ", env):
with patch("sys.argv", ["meshchat.py"]):
main()
with patch.dict("os.environ", env), patch("sys.argv", ["meshchat.py"]):
main()
# Verify ReticulumMeshChat was called with values from ENV
args, kwargs = mock_app_class.call_args
assert kwargs["auto_recover"] is True
assert kwargs["auth_enabled"] is True
# Verify ReticulumMeshChat was called with values from ENV
args, kwargs = mock_app_class.call_args
assert kwargs["auto_recover"] is True
assert kwargs["auth_enabled"] is True
# Verify run was called with host/port from ENV
mock_app_instance = mock_app_class.return_value
run_args, run_kwargs = mock_app_instance.run.call_args
assert run_args[0] == "1.2.3.4"
assert run_args[1] == 9000
# Verify run was called with host/port from ENV
mock_app_instance = mock_app_class.return_value
run_args, run_kwargs = mock_app_instance.run.call_args
assert run_args[0] == "1.2.3.4"
assert run_args[1] == 9000
# Test CLI Flags (override Envs)
mock_app_class.reset_mock()
with patch.dict("os.environ", env):
with patch(
with (
patch.dict("os.environ", env),
patch(
"sys.argv",
[
"meshchat.py",
@@ -265,11 +272,12 @@ def test_cli_flags_and_envs(mock_rns, temp_dir):
"--storage-dir",
temp_dir,
],
):
main()
),
):
main()
mock_app_instance = mock_app_class.return_value
run_args, run_kwargs = mock_app_instance.run.call_args
assert run_args[0] == "5.6.7.8"
assert run_args[1] == 7000
assert run_kwargs["enable_https"] is False
mock_app_instance = mock_app_class.return_value
run_args, run_kwargs = mock_app_instance.run.call_args
assert run_args[0] == "5.6.7.8"
assert run_args[1] == 7000
assert run_kwargs["enable_https"] is False

View File

@@ -38,7 +38,9 @@ def temp_storage(tmp_path):
def test_telephone_manager_init(mock_identity, mock_config, temp_storage):
tm = TelephoneManager(
mock_identity, config_manager=mock_config, storage_dir=temp_storage
mock_identity,
config_manager=mock_config,
storage_dir=temp_storage,
)
assert tm.identity == mock_identity
assert tm.config_manager == mock_config
@@ -48,7 +50,11 @@ def test_telephone_manager_init(mock_identity, mock_config, temp_storage):
@patch("meshchatx.src.backend.telephone_manager.Telephone")
def test_call_recording_lifecycle(
mock_telephone_class, mock_identity, mock_config, mock_db, temp_storage
mock_telephone_class,
mock_identity,
mock_config,
mock_db,
temp_storage,
):
# Setup mocks
mock_telephone = mock_telephone_class.return_value
@@ -63,7 +69,10 @@ def test_call_recording_lifecycle(
mock_telephone.transmit_mixer = MagicMock()
tm = TelephoneManager(
mock_identity, config_manager=mock_config, storage_dir=temp_storage, db=mock_db
mock_identity,
config_manager=mock_config,
storage_dir=temp_storage,
db=mock_db,
)
tm.get_name_for_identity_hash = MagicMock(return_value="Remote User")
tm.init_telephone()
@@ -90,7 +99,10 @@ def test_call_recording_lifecycle(
def test_call_recording_disabled(mock_identity, mock_config, mock_db, temp_storage):
mock_config.call_recording_enabled.get.return_value = False
tm = TelephoneManager(
mock_identity, config_manager=mock_config, storage_dir=temp_storage, db=mock_db
mock_identity,
config_manager=mock_config,
storage_dir=temp_storage,
db=mock_db,
)
# Mock telephone and active call
@@ -105,13 +117,15 @@ def test_call_recording_disabled(mock_identity, mock_config, mock_db, temp_stora
def test_audio_profile_persistence(mock_identity, mock_config, temp_storage):
with patch(
"meshchatx.src.backend.telephone_manager.Telephone"
"meshchatx.src.backend.telephone_manager.Telephone",
) as mock_telephone_class:
mock_telephone = mock_telephone_class.return_value
mock_config.telephone_audio_profile_id.get.return_value = 4
tm = TelephoneManager(
mock_identity, config_manager=mock_config, storage_dir=temp_storage
mock_identity,
config_manager=mock_config,
storage_dir=temp_storage,
)
tm.init_telephone()
@@ -121,7 +135,11 @@ def test_audio_profile_persistence(mock_identity, mock_config, temp_storage):
@patch("meshchatx.src.backend.telephone_manager.Telephone")
def test_call_recording_saves_after_disconnect(
mock_telephone_class, mock_identity, mock_config, mock_db, temp_storage
mock_telephone_class,
mock_identity,
mock_config,
mock_db,
temp_storage,
):
# Setup mocks
mock_telephone = mock_telephone_class.return_value
@@ -136,7 +154,10 @@ def test_call_recording_saves_after_disconnect(
mock_telephone.transmit_mixer = MagicMock()
tm = TelephoneManager(
mock_identity, config_manager=mock_config, storage_dir=temp_storage, db=mock_db
mock_identity,
config_manager=mock_config,
storage_dir=temp_storage,
db=mock_db,
)
tm.init_telephone()
@@ -162,11 +183,16 @@ def test_call_recording_saves_after_disconnect(
@patch("meshchatx.src.backend.telephone_manager.Telephone")
def test_manual_mute_overrides(
mock_telephone_class, mock_identity, mock_config, temp_storage
mock_telephone_class,
mock_identity,
mock_config,
temp_storage,
):
mock_telephone = mock_telephone_class.return_value
tm = TelephoneManager(
mock_identity, config_manager=mock_config, storage_dir=temp_storage
mock_identity,
config_manager=mock_config,
storage_dir=temp_storage,
)
tm.init_telephone()

View File

@@ -1,5 +1,6 @@
import unittest
from unittest.mock import MagicMock, patch
from meshchatx.src.backend.translator_handler import TranslatorHandler

View File

@@ -4,6 +4,7 @@ import tempfile
from unittest.mock import MagicMock, patch
import pytest
from meshchatx.src.backend.voicemail_manager import VoicemailManager

View File

@@ -1,12 +1,13 @@
import socket
import unittest
from unittest.mock import MagicMock, patch
import socket
from meshchatx.src.backend.interfaces.WebsocketServerInterface import (
WebsocketServerInterface,
)
from meshchatx.src.backend.interfaces.WebsocketClientInterface import (
WebsocketClientInterface,
)
from meshchatx.src.backend.interfaces.WebsocketServerInterface import (
WebsocketServerInterface,
)
class TestWebsocketInterfaces(unittest.TestCase):
@@ -43,7 +44,7 @@ class TestWebsocketInterfaces(unittest.TestCase):
# We don't want it to actually try connecting in this basic test
with patch(
"meshchatx.src.backend.interfaces.WebsocketClientInterface.threading.Thread"
"meshchatx.src.backend.interfaces.WebsocketClientInterface.threading.Thread",
):
client = WebsocketClientInterface(self.owner, config)
self.assertEqual(client.name, "test_ws_client")