numerous improvements
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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",
|
||||
)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import pytest
|
||||
from unittest.mock import patch
|
||||
import asyncio
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"))
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"])
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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),
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import unittest
|
||||
import shutil
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
from meshchatx.src.backend.integrity_manager import IntegrityManager
|
||||
|
||||
|
||||
|
||||
201
tests/backend/test_interface_discovery.py
Normal file
201
tests/backend/test_interface_discovery.py
Normal 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
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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('hello')" in rendered
|
||||
or "print('hello')" in rendered
|
||||
or "print('hello')" in rendered,
|
||||
)
|
||||
|
||||
def test_lists(self):
|
||||
|
||||
@@ -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",
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import unittest
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from meshchatx.src.backend.message_handler import MessageHandler
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import unittest
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from meshchatx.src.backend.nomadnet_downloader import NomadnetDownloader
|
||||
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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!")
|
||||
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import unittest
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from meshchatx.src.backend.translator_handler import TranslatorHandler
|
||||
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import tempfile
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from meshchatx.src.backend.voicemail_manager import VoicemailManager
|
||||
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user