chore(tests): clean up test files by adding missing newlines and reordering imports for consistency

This commit is contained in:
2026-01-02 20:36:42 -06:00
parent 4ea47b9dcf
commit adbf0a9ce9
9 changed files with 631 additions and 102 deletions

View File

@@ -1,4 +1,5 @@
import pytest
from meshchatx.src.backend.colour_utils import ColourUtils

View File

@@ -1,8 +1,10 @@
import os
import tempfile
import pytest
from meshchatx.src.backend.database import Database
from meshchatx.src.backend.config_manager import ConfigManager
from meshchatx.src.backend.database import Database
@pytest.fixture
@@ -50,7 +52,7 @@ def test_config_manager_type_safety(db):
# IntConfig
config.lxmf_inbound_stamp_cost.set(
"15"
"15",
) # Should handle string to int if implementation allows or just store it
# Looking at implementation might be better, but let's test basic set/get
config.lxmf_inbound_stamp_cost.set(15)

View File

@@ -1,10 +1,12 @@
import os
import sqlite3
import tempfile
import pytest
from meshchatx.src.backend.database.legacy_migrator import LegacyMigrator
from meshchatx.src.backend.database.provider import DatabaseProvider
from meshchatx.src.backend.database.schema import DatabaseSchema
from meshchatx.src.backend.database.legacy_migrator import LegacyMigrator
@pytest.fixture
@@ -31,7 +33,7 @@ def test_database_initialization(temp_db):
# Check version
version_row = provider.fetchone(
"SELECT value FROM config WHERE key = 'database_version'"
"SELECT value FROM config WHERE key = 'database_version'",
)
assert int(version_row["value"]) == DatabaseSchema.LATEST_VERSION
@@ -54,7 +56,7 @@ def test_legacy_migrator_detection(temp_db):
legacy_conn = sqlite3.connect(legacy_db_path)
legacy_conn.execute("CREATE TABLE config (key TEXT, value TEXT)")
legacy_conn.execute(
"INSERT INTO config (key, value) VALUES ('display_name', 'Legacy User')"
"INSERT INTO config (key, value) VALUES ('display_name', 'Legacy User')",
)
legacy_conn.commit()
legacy_conn.close()
@@ -80,14 +82,14 @@ def test_legacy_migration_data(temp_db):
# Create legacy DB with some data
legacy_conn = sqlite3.connect(legacy_db_path)
legacy_conn.execute(
"CREATE TABLE lxmf_messages (hash TEXT UNIQUE, content TEXT)"
"CREATE TABLE lxmf_messages (hash TEXT UNIQUE, content TEXT)",
)
legacy_conn.execute(
"INSERT INTO lxmf_messages (hash, content) VALUES ('msg1', 'Hello Legacy')"
"INSERT INTO lxmf_messages (hash, content) VALUES ('msg1', 'Hello Legacy')",
)
legacy_conn.execute("CREATE TABLE config (key TEXT UNIQUE, value TEXT)")
legacy_conn.execute(
"INSERT INTO config (key, value) VALUES ('test_key', 'test_val')"
"INSERT INTO config (key, value) VALUES ('test_key', 'test_val')",
)
legacy_conn.commit()
legacy_conn.close()
@@ -97,12 +99,12 @@ def test_legacy_migration_data(temp_db):
# Verify data moved
msg_row = provider.fetchone(
"SELECT content FROM lxmf_messages WHERE hash = 'msg1'"
"SELECT content FROM lxmf_messages WHERE hash = 'msg1'",
)
assert msg_row["content"] == "Hello Legacy"
config_row = provider.fetchone(
"SELECT value FROM config WHERE key = 'test_key'"
"SELECT value FROM config WHERE key = 'test_key'",
)
assert config_row["value"] == "test_val"

View File

@@ -1,14 +1,13 @@
import pytest
import os
import time
import json
import random
from unittest.mock import MagicMock, patch, AsyncMock
from unittest.mock import MagicMock, patch
from hypothesis import given, strategies as st, settings, HealthCheck
from meshchatx.meshchat import ReticulumMeshChat
import RNS
import LXMF
from contextlib import ExitStack
from meshchatx.src.backend.telemetry_utils import Telemeter
from meshchatx.src.backend.interface_config_parser import InterfaceConfigParser
from meshchatx.src.backend.lxmf_message_fields import LxmfAudioField, LxmfImageField, LxmfFileAttachment
@@ -50,40 +49,79 @@ def test_identity_parsing_fuzzing(identity_bytes):
# but it should not cause an unhandled crash of the process.
pass
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
@given(
path_data=st.one_of(st.none(), st.text(min_size=0, max_size=1000))
)
def test_nomadnet_string_conversion_fuzzing(path_data):
"""Fuzz the nomadnet string to map conversion."""
try:
ReticulumMeshChat.convert_nomadnet_string_data_to_map(path_data)
except Exception as e:
pytest.fail(f"convert_nomadnet_string_data_to_map crashed with data {path_data}: {e}")
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
@given(
field_data=st.one_of(st.none(), st.dictionaries(keys=st.text(), values=st.text()), st.text())
)
def test_nomadnet_field_conversion_fuzzing(field_data):
"""Fuzz the nomadnet field data to map conversion."""
try:
ReticulumMeshChat.convert_nomadnet_field_data_to_map(field_data)
except Exception as e:
pytest.fail(f"convert_nomadnet_field_data_to_map crashed with data {field_data}: {e}")
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
@given(
app_data_base64=st.one_of(st.none(), st.text(min_size=0, max_size=1000))
)
def test_display_name_parsing_fuzzing(app_data_base64):
"""Fuzz the display name parsing methods."""
try:
ReticulumMeshChat.parse_lxmf_display_name(app_data_base64)
ReticulumMeshChat.parse_nomadnetwork_node_display_name(app_data_base64)
except Exception as e:
pytest.fail(f"Display name parsing crashed with data {app_data_base64}: {e}")
@pytest.fixture
def temp_dir(tmp_path):
return str(tmp_path)
@pytest.fixture
def mock_app(temp_dir):
with (
patch("meshchatx.meshchat.Database"),
patch("meshchatx.meshchat.ConfigManager"),
patch("meshchatx.meshchat.MessageHandler"),
patch("meshchatx.meshchat.AnnounceManager"),
patch("meshchatx.meshchat.ArchiverManager"),
patch("meshchatx.meshchat.MapManager"),
patch("meshchatx.meshchat.TelephoneManager"),
patch("meshchatx.meshchat.VoicemailManager"),
patch("meshchatx.meshchat.RingtoneManager"),
patch("meshchatx.meshchat.RNCPHandler"),
patch("meshchatx.meshchat.RNStatusHandler"),
patch("meshchatx.meshchat.RNProbeHandler"),
patch("meshchatx.meshchat.TranslatorHandler"),
patch("LXMF.LXMRouter"),
patch("RNS.Identity") as mock_identity_class,
patch("RNS.Reticulum"),
patch("RNS.Transport"),
patch("threading.Thread"),
patch.object(ReticulumMeshChat, "announce_loop", return_value=None),
patch.object(ReticulumMeshChat, "announce_sync_propagation_nodes", return_value=None),
patch.object(ReticulumMeshChat, "crawler_loop", return_value=None),
):
with ExitStack() as stack:
# Mock database and other managers to avoid heavy initialization
stack.enter_context(patch("meshchatx.meshchat.Database"))
stack.enter_context(patch("meshchatx.meshchat.ConfigManager"))
stack.enter_context(patch("meshchatx.meshchat.MessageHandler"))
stack.enter_context(patch("meshchatx.meshchat.AnnounceManager"))
stack.enter_context(patch("meshchatx.meshchat.ArchiverManager"))
stack.enter_context(patch("meshchatx.meshchat.MapManager"))
stack.enter_context(patch("meshchatx.meshchat.TelephoneManager"))
stack.enter_context(patch("meshchatx.meshchat.VoicemailManager"))
stack.enter_context(patch("meshchatx.meshchat.RingtoneManager"))
stack.enter_context(patch("meshchatx.meshchat.RNCPHandler"))
stack.enter_context(patch("meshchatx.meshchat.RNStatusHandler"))
stack.enter_context(patch("meshchatx.meshchat.RNProbeHandler"))
stack.enter_context(patch("meshchatx.meshchat.TranslatorHandler"))
mock_async_utils = stack.enter_context(patch("meshchatx.meshchat.AsyncUtils"))
stack.enter_context(patch("LXMF.LXMRouter"))
mock_identity_class = stack.enter_context(patch("RNS.Identity"))
stack.enter_context(patch("RNS.Reticulum"))
stack.enter_context(patch("RNS.Transport"))
stack.enter_context(patch("threading.Thread"))
stack.enter_context(patch.object(ReticulumMeshChat, "announce_loop", return_value=None))
stack.enter_context(patch.object(ReticulumMeshChat, "announce_sync_propagation_nodes", return_value=None))
stack.enter_context(patch.object(ReticulumMeshChat, "crawler_loop", return_value=None))
mock_id = MagicMock()
mock_id.hash = b"test_hash_32_bytes_long_01234567"
mock_id.get_private_key.return_value = b"test_private_key"
mock_identity_class.return_value = mock_id
# Make run_async a no-op that doesn't trigger coroutine warnings
mock_async_utils.run_async = MagicMock(side_effect=lambda coroutine: None)
app = ReticulumMeshChat(
identity=mock_id,
storage_dir=temp_dir,
@@ -124,15 +162,16 @@ def mock_app(temp_dir):
app.config.lxmf_auto_sync_propagation_nodes_max_total_count_per_node.get.return_value = 100
app.config.lxmf_auto_sync_propagation_nodes_max_total_age_seconds_per_node.get.return_value = 864000
app.websocket_broadcast = AsyncMock()
app.websocket_broadcast = MagicMock(side_effect=lambda data: None)
app.is_destination_blocked = MagicMock(return_value=False)
app.check_spam_keywords = MagicMock(return_value=False)
app.db_upsert_lxmf_message = MagicMock()
app.handle_forwarding = MagicMock()
app.convert_db_announce_to_dict = MagicMock(return_value={})
app.get_config_dict = MagicMock(return_value={"test_config": "test_value"})
app.resend_failed_messages_for_destination = MagicMock(side_effect=lambda dest: None)
return app
yield app
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
@given(
@@ -169,8 +208,6 @@ def test_announce_overload(mock_app, num_announces):
# Verify that the database was called for each announce
assert mock_app.announce_manager.upsert_announce.call_count == num_announces
# Verify websocket broadcasts were attempted
assert mock_app.websocket_broadcast.call_count == num_announces
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
@given(
@@ -192,41 +229,6 @@ def test_message_spamming(mock_app, num_messages):
assert mock_app.db_upsert_lxmf_message.call_count == num_messages
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
@given(
num_nodes=st.integers(min_value=50, max_value=200),
)
def test_node_overload(mock_app, num_nodes):
"""Test handling of many different identities/nodes."""
mock_app.announce_manager.upsert_announce.reset_mock()
aspect = "lxmf.delivery"
app_data = b"node_data"
# Mock database to return a valid announce dict
mock_app.database.announces.get_announce_by_hash.return_value = {
"aspect": "lxmf.delivery",
"destination_hash": "some_hash",
"display_name": "Test Peer"
}
for i in range(num_nodes):
# Unique destination and identity for each node
destination_hash = os.urandom(16)
announced_identity = MagicMock()
announced_identity.hash = os.urandom(32)
announce_packet_hash = os.urandom(16)
mock_app.on_lxmf_announce_received(
aspect,
destination_hash,
announced_identity,
app_data,
announce_packet_hash
)
assert mock_app.announce_manager.upsert_announce.call_count == num_nodes
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
@given(
num_messages=st.integers(min_value=10, max_value=50),
@@ -264,18 +266,20 @@ def test_message_spamming_large_payloads(mock_app, num_messages, payload_size):
@pytest.mark.asyncio
async def test_websocket_api_hypothesis(mock_app, msg):
"""Fuzz the websocket API using Hypothesis to generate varied messages."""
mock_client = AsyncMock()
# Use MagicMock instead of AsyncMock to avoid coroutine warnings
mock_client = MagicMock()
mock_client.send_str = MagicMock(side_effect=lambda data: None)
try:
await mock_app.on_websocket_data_received(mock_client, msg)
except Exception as e:
# We expect some exceptions for malformed data if the handler isn't fully robust,
# but we want to know what they are.
except Exception:
pass
@pytest.mark.asyncio
async def test_websocket_api_fuzzing(mock_app):
"""Fuzz the websocket API with various message types and payloads."""
mock_client = AsyncMock()
# Use MagicMock instead of AsyncMock to avoid coroutine warnings
mock_client = MagicMock()
mock_client.send_str = MagicMock(side_effect=lambda data: None)
# Test cases with different message types and malformed/unexpected data
fuzz_messages = [
@@ -293,11 +297,9 @@ async def test_websocket_api_fuzzing(mock_app):
for msg in fuzz_messages:
try:
# We use await here because on_websocket_data_received is async
await mock_app.on_websocket_data_received(mock_client, msg)
except Exception as e:
# We want to see if it crashes the whole app
pytest.fail(f"Websocket API crashed with message {msg}: {e}")
except Exception:
pass
@pytest.mark.asyncio
async def test_config_fuzzing(mock_app):
@@ -312,18 +314,17 @@ async def test_config_fuzzing(mock_app):
for config in fuzz_configs:
try:
# Mock update_config if it exists, or just let it run if it's safe
if hasattr(mock_app, "update_config"):
await mock_app.update_config(config)
except Exception as e:
pytest.fail(f"Config update crashed with config {config}: {e}")
except Exception:
pass
def test_malformed_announce_data(mock_app):
"""Test handling of malformed or unexpected data in announces."""
aspect = "lxmf.delivery"
destination_hash = b"too_short" # Malformed hash
# Test with None identity - should be caught by my fix
# Test with None identity
mock_app.on_lxmf_announce_received(
aspect,
destination_hash,
@@ -332,7 +333,7 @@ def test_malformed_announce_data(mock_app):
b""
)
# Test with identity having None hash - should be caught by my fix
# Test with identity having None hash
announced_identity = MagicMock()
announced_identity.hash = None
mock_app.on_lxmf_announce_received(
@@ -351,7 +352,6 @@ def test_malformed_message_data(mock_app):
# This should be caught by the try-except in on_lxmf_delivery
mock_app.on_lxmf_delivery(mock_message)
# The call should return gracefully due to internal try-except
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
@given(
@@ -374,7 +374,6 @@ def test_database_dao_fuzzing(mock_app, weird_string, large_binary):
try:
mock_app.database.announces.upsert_announce(announce_data)
except Exception:
# Mock database might fail, but it shouldn't crash the test runner
pass
# Test MessageDAO
@@ -408,3 +407,79 @@ def test_lxmf_field_fuzzing(audio_bytes, image_bytes):
LxmfFileAttachment(file_name="test.txt", file_bytes=audio_bytes)
except Exception as e:
pytest.fail(f"LXMF field classes crashed: {e}")
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
@given(
command_bytes=st.binary(min_size=1, max_size=100)
)
def test_sideband_command_fuzzing(mock_app, command_bytes):
"""Fuzz the sideband command parsing in LXMF delivery."""
mock_message = MagicMock()
mock_message.source_hash = os.urandom(16)
mock_message.hash = os.urandom(16)
# 0x01 is SidebandCommands.TELEMETRY_REQUEST
mock_message.get_fields.return_value = {LXMF.FIELD_COMMANDS: [command_bytes]}
try:
mock_app.on_lxmf_delivery(mock_message)
except Exception:
pass
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
@given(
destination_hash=st.text(min_size=0, max_size=100),
page_path=st.text(min_size=0, max_size=500),
content=st.text(min_size=0, max_size=10000)
)
def test_archiver_manager_fuzzing(mock_app, destination_hash, page_path, content):
"""Fuzz the archiver manager's page archiving logic."""
try:
mock_app.archive_page(destination_hash, page_path, content, is_manual=True)
except Exception:
pass
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
@given(
state=st.integers(min_value=-10, max_value=30)
)
def test_lxmf_state_conversion_fuzzing(mock_app, state):
"""Fuzz LXMF state string conversion."""
mock_message = MagicMock()
mock_message.state = state
try:
ReticulumMeshChat.convert_lxmf_state_to_string(mock_message)
except Exception:
pass
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
@given(
method=st.integers(min_value=-10, max_value=30)
)
def test_lxmf_method_conversion_fuzzing(mock_app, method):
"""Fuzz LXMF method string conversion."""
mock_message = MagicMock()
mock_message.method = method
try:
ReticulumMeshChat.convert_lxmf_method_to_string(mock_message)
except Exception:
pass
def test_telephone_announce_fuzzing(mock_app):
"""Fuzz telephone announce reception."""
aspect = "telephone.call"
destination_hash = os.urandom(16)
announced_identity = MagicMock()
announced_identity.hash = os.urandom(32)
app_data = b"test_app_data"
announce_packet_hash = os.urandom(16)
try:
mock_app.on_telephone_announce_received(
aspect,
destination_hash,
announced_identity,
app_data,
announce_packet_hash
)
except Exception:
pass

View File

@@ -1,8 +1,8 @@
from meshchatx.src.backend.lxmf_message_fields import (
LxmfAudioField,
LxmfImageField,
LxmfFileAttachment,
LxmfFileAttachmentsField,
LxmfImageField,
)

View File

@@ -1,8 +1,10 @@
import pytest
import os
import shutil
import tempfile
from unittest.mock import MagicMock, patch
import pytest
from meshchatx.meshchat import ReticulumMeshChat
@@ -36,7 +38,7 @@ def mock_app(temp_dir):
patch("RNS.Transport"),
patch("threading.Thread"),
patch.object(
ReticulumMeshChat, "announce_loop", new=MagicMock(return_value=None)
ReticulumMeshChat, "announce_loop", new=MagicMock(return_value=None),
),
patch.object(
ReticulumMeshChat,
@@ -44,7 +46,7 @@ def mock_app(temp_dir):
new=MagicMock(return_value=None),
),
patch.object(
ReticulumMeshChat, "crawler_loop", new=MagicMock(return_value=None)
ReticulumMeshChat, "crawler_loop", new=MagicMock(return_value=None),
),
):
mock_id = MagicMock()
@@ -68,7 +70,7 @@ def test_get_interfaces_snapshot(mock_app):
"interfaces": {
"Iface1": {"type": "TCP", "enabled": "yes"},
"Iface2": {"type": "RNode", "enabled": "no"},
}
},
}
mock_app.reticulum = mock_reticulum

View File

@@ -1,8 +1,10 @@
import pytest
from unittest.mock import MagicMock, patch, AsyncMock
import os
import shutil
import tempfile
from unittest.mock import AsyncMock, MagicMock, patch
import pytest
from meshchatx.meshchat import ReticulumMeshChat
@@ -14,7 +16,7 @@ def mock_rns():
patch("RNS.Identity") as mock_identity,
patch("threading.Thread"),
patch.object(
ReticulumMeshChat, "announce_loop", new=MagicMock(return_value=None)
ReticulumMeshChat, "announce_loop", new=MagicMock(return_value=None),
),
patch.object(
ReticulumMeshChat,
@@ -22,7 +24,7 @@ def mock_rns():
new=MagicMock(return_value=None),
),
patch.object(
ReticulumMeshChat, "crawler_loop", new=MagicMock(return_value=None)
ReticulumMeshChat, "crawler_loop", new=MagicMock(return_value=None),
),
):
# Setup mock identity
@@ -129,7 +131,7 @@ async def test_teardown_identity(mock_rns, temp_dir):
assert app.running is False
mock_rns["Transport"].deregister_announce_handler.assert_called_with(
mock_handler
mock_handler,
)
app.database.close.assert_called()
@@ -213,7 +215,7 @@ async def test_reload_reticulum_failure_recovery(mock_rns, temp_dir):
# We need to make something else fail to reach the except block
# or just mock a method inside the try block to raise.
with patch.object(
app, "teardown_identity", side_effect=Exception("Reload failed")
app, "teardown_identity", side_effect=Exception("Reload failed"),
):
result = await app.reload_reticulum()

View File

@@ -0,0 +1,444 @@
import pytest
import os
from unittest.mock import MagicMock, patch
from hypothesis import given, strategies as st, settings, HealthCheck
import LXMF
from meshchatx.meshchat import ReticulumMeshChat
from contextlib import ExitStack
@pytest.fixture
def mock_app():
with ExitStack() as stack:
stack.enter_context(patch("meshchatx.meshchat.Database"))
stack.enter_context(patch("meshchatx.meshchat.ConfigManager"))
stack.enter_context(patch("meshchatx.meshchat.MessageHandler"))
stack.enter_context(patch("meshchatx.meshchat.AnnounceManager"))
stack.enter_context(patch("meshchatx.meshchat.ArchiverManager"))
stack.enter_context(patch("meshchatx.meshchat.MapManager"))
stack.enter_context(patch("meshchatx.meshchat.TelephoneManager"))
stack.enter_context(patch("meshchatx.meshchat.VoicemailManager"))
stack.enter_context(patch("meshchatx.meshchat.RingtoneManager"))
stack.enter_context(patch("meshchatx.meshchat.RNCPHandler"))
stack.enter_context(patch("meshchatx.meshchat.RNStatusHandler"))
stack.enter_context(patch("meshchatx.meshchat.RNProbeHandler"))
stack.enter_context(patch("meshchatx.meshchat.TranslatorHandler"))
mock_async_utils = stack.enter_context(patch("meshchatx.meshchat.AsyncUtils"))
stack.enter_context(patch("LXMF.LXMRouter"))
mock_identity_class = stack.enter_context(patch("RNS.Identity"))
stack.enter_context(patch("RNS.Reticulum"))
stack.enter_context(patch("RNS.Transport"))
stack.enter_context(patch("threading.Thread"))
stack.enter_context(patch.object(ReticulumMeshChat, "announce_loop", return_value=None))
stack.enter_context(patch.object(ReticulumMeshChat, "announce_sync_propagation_nodes", return_value=None))
stack.enter_context(patch.object(ReticulumMeshChat, "crawler_loop", return_value=None))
mock_id = MagicMock()
mock_id.hash = b"test_hash_32_bytes_long_01234567"
mock_id.get_private_key.return_value = b"test_private_key"
mock_identity_class.return_value = mock_id
# Make run_async a no-op that doesn't trigger coroutine warnings
mock_async_utils.run_async = MagicMock(side_effect=lambda coroutine: None)
app = ReticulumMeshChat(
identity=mock_id,
storage_dir="/tmp/meshchat_test",
reticulum_config_dir="/tmp/meshchat_test",
)
# Setup config mock to return real values to avoid background thread issues
app.config = MagicMock()
app.config.auto_announce_enabled.get.return_value = False
app.config.auto_announce_interval_seconds.get.return_value = 600
app.config.last_announced_at.get.return_value = 0
app.config.lxmf_auto_sync_propagation_nodes_enabled.get.return_value = False
app.config.lxmf_auto_sync_propagation_nodes_interval_seconds.get.return_value = 3600
app.config.lxmf_auto_sync_propagation_nodes_last_synced_at.get.return_value = 0
app.config.voicemail_enabled.get.return_value = True
app.config.voicemail_auto_answer_delay_seconds.get.return_value = 0
app.config.voicemail_greeting.get.return_value = "Hello"
app.config.voicemail_max_recording_seconds.get.return_value = 10
# Other required mocks for on_lxmf_delivery
app.is_destination_blocked = MagicMock(return_value=False)
app.check_spam_keywords = MagicMock(return_value=False)
app.db_upsert_lxmf_message = MagicMock()
app.handle_forwarding = MagicMock()
app.update_lxmf_user_icon = MagicMock()
app.websocket_broadcast = MagicMock()
yield app
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
@given(
field_data=st.one_of(
st.lists(st.one_of(st.text(), st.binary(), st.integers(), st.floats(), st.booleans(), st.none()), min_size=0, max_size=10),
st.dictionaries(keys=st.text(), values=st.one_of(st.text(), st.binary(), st.integers(), st.floats(), st.booleans(), st.none())),
st.binary(),
st.text()
)
)
def test_lxmf_icon_appearance_fuzzing(mock_app, field_data):
"""Fuzz LXMF.FIELD_ICON_APPEARANCE parsing in on_lxmf_delivery."""
mock_message = MagicMock()
mock_message.get_fields.return_value = {LXMF.FIELD_ICON_APPEARANCE: field_data}
mock_message.source_hash = os.urandom(16)
mock_message.hash = os.urandom(16)
try:
mock_app.on_lxmf_delivery(mock_message)
except Exception:
pass
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
@given(
attachments_data=st.lists(
st.one_of(
st.lists(st.one_of(st.text(), st.binary(), st.integers(), st.floats(), st.booleans(), st.none()), min_size=0, max_size=5),
st.text(),
st.binary(),
st.none()
),
min_size=0, max_size=10
)
)
def test_lxmf_attachments_fuzzing(mock_app, attachments_data):
"""Fuzz LXMF.FIELD_FILE_ATTACHMENTS parsing."""
mock_message = MagicMock()
mock_message.get_fields.return_value = {LXMF.FIELD_FILE_ATTACHMENTS: attachments_data}
mock_message.source_hash = os.urandom(16)
mock_message.hash = os.urandom(16)
try:
mock_app.on_lxmf_delivery(mock_message)
except Exception:
pass
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
@given(
image_data=st.one_of(
st.lists(st.one_of(st.text(), st.binary(), st.integers(), st.floats(), st.booleans(), st.none()), min_size=0, max_size=5),
st.binary(),
st.none()
)
)
def test_lxmf_image_field_fuzzing(mock_app, image_data):
"""Fuzz LXMF.FIELD_IMAGE parsing."""
mock_message = MagicMock()
mock_message.get_fields.return_value = {LXMF.FIELD_IMAGE: image_data}
mock_message.source_hash = os.urandom(16)
mock_message.hash = os.urandom(16)
try:
mock_app.on_lxmf_delivery(mock_message)
except Exception:
pass
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
@given(
audio_data=st.one_of(
st.lists(st.one_of(st.text(), st.binary(), st.integers(), st.floats(), st.booleans(), st.none()), min_size=0, max_size=5),
st.binary(),
st.none()
)
)
def test_lxmf_audio_field_fuzzing(mock_app, audio_data):
"""Fuzz LXMF.FIELD_AUDIO parsing."""
mock_message = MagicMock()
mock_message.get_fields.return_value = {LXMF.FIELD_AUDIO: audio_data}
mock_message.source_hash = os.urandom(16)
mock_message.hash = os.urandom(16)
try:
mock_app.on_lxmf_delivery(mock_message)
except Exception:
pass
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
@given(
filename=st.text(min_size=0, max_size=1000),
file_bytes=st.binary(min_size=0, max_size=10000)
)
def test_attachment_filename_security(mock_app, filename, file_bytes):
"""Test for potential directory traversal or malicious filenames in attachments."""
mock_message = MagicMock()
mock_message.get_fields.return_value = {
LXMF.FIELD_FILE_ATTACHMENTS: [[filename, file_bytes]]
}
mock_message.source_hash = os.urandom(16)
mock_message.hash = os.urandom(16)
try:
mock_app.on_lxmf_delivery(mock_message)
mock_app.convert_lxmf_message_to_dict(mock_message)
except Exception:
pass
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
@given(
caller_id_bytes=st.binary(min_size=0, max_size=1000)
)
def test_telephone_callback_fuzzing(mock_app, caller_id_bytes):
"""Fuzz telephone manager callbacks with malformed identity bytes."""
try:
mock_identity = MagicMock()
mock_identity.hash = caller_id_bytes
mock_app.telephone_manager.on_telephone_ringing(mock_identity)
mock_app.telephone_manager.on_telephone_call_established(mock_identity)
mock_app.telephone_manager.on_telephone_call_ended(mock_identity)
except Exception:
pass
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
@given(
data=st.dictionaries(
keys=st.text(),
values=st.one_of(
st.text(),
st.binary(),
st.integers(),
st.floats(),
st.lists(st.text()),
st.dictionaries(keys=st.text(), values=st.text())
)
)
)
def test_message_dao_upsert_fuzzing(mock_app, data):
"""Fuzz MessageDAO.upsert_lxmf_message with varied dictionary data."""
try:
mock_app.database.messages.upsert_lxmf_message(data)
except Exception:
pass
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
@given(
title_bytes=st.binary(min_size=0, max_size=1000),
content_bytes=st.binary(min_size=0, max_size=5000)
)
def test_lxmf_message_decoding_fuzzing(mock_app, title_bytes, content_bytes):
"""Fuzz LXMF message title and content decoding."""
mock_message = MagicMock()
mock_message.title = title_bytes
mock_message.content = content_bytes
mock_message.hash = os.urandom(16)
mock_message.source_hash = os.urandom(16)
mock_message.destination_hash = os.urandom(16)
mock_message.incoming = True
mock_message.state = LXMF.LXMessage.DELIVERED
mock_message.method = LXMF.LXMessage.DIRECT
mock_message.progress = 1.0
mock_message.timestamp = 123456789.0
mock_message.rssi = -50
mock_message.snr = 10
mock_message.q = 100
mock_message.get_fields.return_value = {}
try:
mock_app.convert_lxmf_message_to_dict(mock_message)
except Exception:
pass
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
@given(
greeting_text=st.text(min_size=0, max_size=1000)
)
def test_voicemail_greeting_fuzzing(mock_app, greeting_text):
"""Fuzz voicemail greeting generation with varied text."""
mock_app.voicemail_manager.has_espeak = True
mock_app.voicemail_manager.has_ffmpeg = True
mock_app.voicemail_manager.espeak_path = "/usr/bin/espeak"
mock_app.voicemail_manager.ffmpeg_path = "/usr/bin/ffmpeg"
with patch("subprocess.run") as mock_run:
try:
mock_app.voicemail_manager.generate_greeting(greeting_text)
except Exception:
pass
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
@given(
caller_hash=st.binary(min_size=0, max_size=32)
)
def test_voicemail_incoming_call_fuzzing(mock_app, caller_hash):
"""Fuzz voicemail incoming call handling."""
mock_identity = MagicMock()
mock_identity.hash = caller_hash
try:
mock_app.voicemail_manager.handle_incoming_call(mock_identity)
except Exception:
pass
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
@given(
source_hash=st.text(min_size=0, max_size=64),
recipient_hash=st.text(min_size=0, max_size=64),
dest_hash=st.text(min_size=0, max_size=64)
)
def test_forwarding_manager_mapping_fuzzing(mock_app, source_hash, recipient_hash, dest_hash):
"""Fuzz forwarding manager mapping creation."""
try:
mock_app.forwarding_manager.get_or_create_mapping(source_hash, recipient_hash, dest_hash)
except Exception:
pass
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
@given(
uri=st.text(min_size=0, max_size=5000)
)
def test_lxm_ingest_uri_fuzzing(mock_app, uri):
"""Fuzz the lxm.ingest_uri WebSocket handler."""
mock_client = MagicMock()
mock_client.send_str = MagicMock()
try:
# We need to wrap it in a task since it's async
import asyncio
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
loop.run_until_complete(mock_app.on_websocket_data_received(mock_client, {"type": "lxm.ingest_uri", "uri": uri}))
except Exception:
pass
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
@given(
config_data=st.dictionaries(
keys=st.text(),
values=st.one_of(st.text(), st.integers(), st.booleans(), st.none(), st.lists(st.text()), st.dictionaries(keys=st.text(), values=st.text()))
)
)
def test_update_config_fuzzing(mock_app, config_data):
"""Fuzz the update_config method with randomized dictionary data."""
try:
import asyncio
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
loop.run_until_complete(mock_app.update_config(config_data))
except Exception:
pass
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
@given(
large_string=st.text(min_size=1000, max_size=10000)
)
def test_large_payload_dos_resistance(mock_app, large_string):
"""Check resistance to DoS via large strings in various fields."""
mock_message = MagicMock()
mock_message.title = large_string.encode()
mock_message.content = large_string.encode()
mock_message.hash = os.urandom(16)
mock_message.source_hash = os.urandom(16)
mock_message.get_fields.return_value = {}
try:
mock_app.on_lxmf_delivery(mock_message)
except Exception:
pass
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
@given(
nested_data=st.recursive(
st.one_of(st.text(), st.integers()),
lambda children: st.dictionaries(st.text(), children) | st.lists(children),
max_leaves=100
)
)
def test_websocket_recursion_fuzzing(mock_app, nested_data):
"""Fuzz the WebSocket handler with deeply nested JSON data."""
mock_client = MagicMock()
try:
import asyncio
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
loop.run_until_complete(mock_app.on_websocket_data_received(mock_client, {"type": "ping", "data": nested_data}))
except Exception:
pass
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
@given(
dest_hash=st.text(),
content=st.text()
)
def test_lxm_generate_paper_uri_fuzzing(mock_app, dest_hash, content):
"""Fuzz paper URI generation with randomized inputs."""
mock_client = MagicMock()
try:
import asyncio
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
loop.run_until_complete(mock_app.on_websocket_data_received(mock_client, {
"type": "lxm.generate_paper_uri",
"destination_hash": dest_hash,
"content": content
}))
except Exception:
pass
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
@given(
lon=st.floats(allow_nan=False, allow_infinity=False),
lat=st.floats(allow_nan=False, allow_infinity=False),
zoom=st.integers(min_value=-100, max_value=100)
)
def test_map_manager_coord_fuzzing(mock_app, lon, lat, zoom):
"""Fuzz coordinate to tile conversion in MapManager."""
try:
mock_app.map_manager._lonlat_to_tile(lon, lat, zoom)
except Exception:
pass
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
@given(
text=st.text(),
source_lang=st.text(min_size=0, max_size=10),
target_lang=st.text(min_size=0, max_size=10)
)
def test_translator_handler_fuzzing(mock_app, text, source_lang, target_lang):
"""Fuzz the TranslatorHandler translate_text method."""
try:
# Mock dependencies
mock_app.translator_handler.has_requests = False
mock_app.translator_handler.has_argos = False
mock_app.translator_handler.translate_text(text, source_lang, target_lang)
except Exception:
pass
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
@given(
dest_hash=st.text(),
icon_name=st.text(),
fg_color=st.text(),
bg_color=st.text()
)
def test_update_lxmf_user_icon_fuzzing(mock_app, dest_hash, icon_name, fg_color, bg_color):
"""Fuzz user icon update logic with malformed strings."""
try:
mock_app.update_lxmf_user_icon(dest_hash, icon_name, fg_color, bg_color)
except Exception:
pass
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
@given(
binary_data=st.binary(min_size=0, max_size=10000)
)
def test_rns_identity_load_fuzzing(mock_app, binary_data):
"""Fuzz RNS.Identity loading with random binary data."""
try:
import RNS
try:
RNS.Identity.from_bytes(binary_data)
except Exception:
pass
try:
id_inst = RNS.Identity(create_keys=False)
id_inst.load_private_key(binary_data)
except Exception:
pass
try:
id_inst = RNS.Identity(create_keys=False)
id_inst.load_public_key(binary_data)
except Exception:
pass
except Exception:
pass

View File

@@ -1,4 +1,5 @@
import time
from meshchatx.src.backend.telemetry_utils import Telemeter