refactor(tests): format
This commit is contained in:
@@ -1,21 +1,27 @@
|
|||||||
import pytest
|
|
||||||
import os
|
import os
|
||||||
import time
|
|
||||||
import random
|
import random
|
||||||
from unittest.mock import MagicMock, patch
|
import time
|
||||||
from hypothesis import given, strategies as st, settings, HealthCheck
|
|
||||||
from meshchatx.meshchat import ReticulumMeshChat
|
|
||||||
import RNS
|
|
||||||
import LXMF
|
|
||||||
from contextlib import ExitStack
|
from contextlib import ExitStack
|
||||||
from meshchatx.src.backend.telemetry_utils import Telemeter
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
|
import LXMF
|
||||||
|
import pytest
|
||||||
|
import RNS
|
||||||
|
from hypothesis import HealthCheck, given, settings
|
||||||
|
from hypothesis import strategies as st
|
||||||
|
|
||||||
|
from meshchatx.meshchat import ReticulumMeshChat
|
||||||
from meshchatx.src.backend.interface_config_parser import InterfaceConfigParser
|
from meshchatx.src.backend.interface_config_parser import InterfaceConfigParser
|
||||||
from meshchatx.src.backend.lxmf_message_fields import LxmfAudioField, LxmfImageField, LxmfFileAttachment
|
from meshchatx.src.backend.lxmf_message_fields import (
|
||||||
|
LxmfAudioField,
|
||||||
|
LxmfFileAttachment,
|
||||||
|
LxmfImageField,
|
||||||
|
)
|
||||||
|
from meshchatx.src.backend.telemetry_utils import Telemeter
|
||||||
|
|
||||||
|
|
||||||
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
|
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
|
||||||
@given(
|
@given(data=st.binary(min_size=0, max_size=1000))
|
||||||
data=st.binary(min_size=0, max_size=1000)
|
|
||||||
)
|
|
||||||
def test_telemetry_unpack_fuzzing(data):
|
def test_telemetry_unpack_fuzzing(data):
|
||||||
"""Fuzz the telemetry unpacking logic with random binary data."""
|
"""Fuzz the telemetry unpacking logic with random binary data."""
|
||||||
try:
|
try:
|
||||||
@@ -25,10 +31,9 @@ def test_telemetry_unpack_fuzzing(data):
|
|||||||
# We expect some failures for invalid packed data, but no crashes
|
# We expect some failures for invalid packed data, but no crashes
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
|
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
|
||||||
@given(
|
@given(config_text=st.text(min_size=0, max_size=5000))
|
||||||
config_text=st.text(min_size=0, max_size=5000)
|
|
||||||
)
|
|
||||||
def test_interface_config_parsing_fuzzing(config_text):
|
def test_interface_config_parsing_fuzzing(config_text):
|
||||||
"""Fuzz the interface configuration parser with random text."""
|
"""Fuzz the interface configuration parser with random text."""
|
||||||
try:
|
try:
|
||||||
@@ -36,10 +41,9 @@ def test_interface_config_parsing_fuzzing(config_text):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
pytest.fail(f"InterfaceConfigParser crashed with input: {e}")
|
pytest.fail(f"InterfaceConfigParser crashed with input: {e}")
|
||||||
|
|
||||||
|
|
||||||
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
|
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
|
||||||
@given(
|
@given(identity_bytes=st.binary(min_size=0, max_size=2048))
|
||||||
identity_bytes=st.binary(min_size=0, max_size=2048)
|
|
||||||
)
|
|
||||||
def test_identity_parsing_fuzzing(identity_bytes):
|
def test_identity_parsing_fuzzing(identity_bytes):
|
||||||
"""Fuzz RNS.Identity loading with random bytes."""
|
"""Fuzz RNS.Identity loading with random bytes."""
|
||||||
try:
|
try:
|
||||||
@@ -49,32 +53,37 @@ def test_identity_parsing_fuzzing(identity_bytes):
|
|||||||
# but it should not cause an unhandled crash of the process.
|
# but it should not cause an unhandled crash of the process.
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
|
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
|
||||||
@given(
|
@given(path_data=st.one_of(st.none(), st.text(min_size=0, max_size=1000)))
|
||||||
path_data=st.one_of(st.none(), st.text(min_size=0, max_size=1000))
|
|
||||||
)
|
|
||||||
def test_nomadnet_string_conversion_fuzzing(path_data):
|
def test_nomadnet_string_conversion_fuzzing(path_data):
|
||||||
"""Fuzz the nomadnet string to map conversion."""
|
"""Fuzz the nomadnet string to map conversion."""
|
||||||
try:
|
try:
|
||||||
ReticulumMeshChat.convert_nomadnet_string_data_to_map(path_data)
|
ReticulumMeshChat.convert_nomadnet_string_data_to_map(path_data)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
pytest.fail(f"convert_nomadnet_string_data_to_map crashed with data {path_data}: {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)
|
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
|
||||||
@given(
|
@given(
|
||||||
field_data=st.one_of(st.none(), st.dictionaries(keys=st.text(), values=st.text()), st.text())
|
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):
|
def test_nomadnet_field_conversion_fuzzing(field_data):
|
||||||
"""Fuzz the nomadnet field data to map conversion."""
|
"""Fuzz the nomadnet field data to map conversion."""
|
||||||
try:
|
try:
|
||||||
ReticulumMeshChat.convert_nomadnet_field_data_to_map(field_data)
|
ReticulumMeshChat.convert_nomadnet_field_data_to_map(field_data)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
pytest.fail(f"convert_nomadnet_field_data_to_map crashed with data {field_data}: {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)
|
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
|
||||||
@given(
|
@given(app_data_base64=st.one_of(st.none(), st.text(min_size=0, max_size=1000)))
|
||||||
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):
|
def test_display_name_parsing_fuzzing(app_data_base64):
|
||||||
"""Fuzz the display name parsing methods."""
|
"""Fuzz the display name parsing methods."""
|
||||||
try:
|
try:
|
||||||
@@ -83,10 +92,12 @@ def test_display_name_parsing_fuzzing(app_data_base64):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
pytest.fail(f"Display name parsing crashed with data {app_data_base64}: {e}")
|
pytest.fail(f"Display name parsing crashed with data {app_data_base64}: {e}")
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def temp_dir(tmp_path):
|
def temp_dir(tmp_path):
|
||||||
return str(tmp_path)
|
return str(tmp_path)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def mock_app(temp_dir):
|
def mock_app(temp_dir):
|
||||||
with ExitStack() as stack:
|
with ExitStack() as stack:
|
||||||
@@ -110,9 +121,17 @@ def mock_app(temp_dir):
|
|||||||
stack.enter_context(patch("RNS.Reticulum"))
|
stack.enter_context(patch("RNS.Reticulum"))
|
||||||
stack.enter_context(patch("RNS.Transport"))
|
stack.enter_context(patch("RNS.Transport"))
|
||||||
stack.enter_context(patch("threading.Thread"))
|
stack.enter_context(patch("threading.Thread"))
|
||||||
stack.enter_context(patch.object(ReticulumMeshChat, "announce_loop", return_value=None))
|
stack.enter_context(
|
||||||
stack.enter_context(patch.object(ReticulumMeshChat, "announce_sync_propagation_nodes", return_value=None))
|
patch.object(ReticulumMeshChat, "announce_loop", return_value=None),
|
||||||
stack.enter_context(patch.object(ReticulumMeshChat, "crawler_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 = MagicMock()
|
||||||
mock_id.hash = b"test_hash_32_bytes_long_01234567"
|
mock_id.hash = b"test_hash_32_bytes_long_01234567"
|
||||||
@@ -141,7 +160,9 @@ def mock_app(temp_dir):
|
|||||||
app.config.auto_send_failed_messages_to_propagation_node.get.return_value = True
|
app.config.auto_send_failed_messages_to_propagation_node.get.return_value = True
|
||||||
app.config.show_suggested_community_interfaces.get.return_value = True
|
app.config.show_suggested_community_interfaces.get.return_value = True
|
||||||
app.config.lxmf_local_propagation_node_enabled.get.return_value = False
|
app.config.lxmf_local_propagation_node_enabled.get.return_value = False
|
||||||
app.config.lxmf_preferred_propagation_node_destination_hash.get.return_value = None
|
app.config.lxmf_preferred_propagation_node_destination_hash.get.return_value = (
|
||||||
|
None
|
||||||
|
)
|
||||||
app.config.lxmf_preferred_propagation_node_auto_sync_interval_seconds.get.return_value = 3600
|
app.config.lxmf_preferred_propagation_node_auto_sync_interval_seconds.get.return_value = 3600
|
||||||
app.config.lxmf_preferred_propagation_node_last_synced_at.get.return_value = 0
|
app.config.lxmf_preferred_propagation_node_last_synced_at.get.return_value = 0
|
||||||
app.config.lxmf_user_icon_name.get.return_value = "user"
|
app.config.lxmf_user_icon_name.get.return_value = "user"
|
||||||
@@ -153,10 +174,16 @@ def mock_app(temp_dir):
|
|||||||
app.config.lxmf_auto_sync_propagation_nodes_min_hops.get.return_value = 1
|
app.config.lxmf_auto_sync_propagation_nodes_min_hops.get.return_value = 1
|
||||||
app.config.lxmf_auto_sync_propagation_nodes_max_hops.get.return_value = 5
|
app.config.lxmf_auto_sync_propagation_nodes_max_hops.get.return_value = 5
|
||||||
app.config.lxmf_auto_sync_propagation_nodes_max_count.get.return_value = 10
|
app.config.lxmf_auto_sync_propagation_nodes_max_count.get.return_value = 10
|
||||||
app.config.lxmf_auto_sync_propagation_nodes_max_age_seconds.get.return_value = 86400
|
app.config.lxmf_auto_sync_propagation_nodes_max_age_seconds.get.return_value = (
|
||||||
app.config.lxmf_auto_sync_propagation_nodes_max_size_bytes.get.return_value = 1000000
|
86400
|
||||||
|
)
|
||||||
|
app.config.lxmf_auto_sync_propagation_nodes_max_size_bytes.get.return_value = (
|
||||||
|
1000000
|
||||||
|
)
|
||||||
app.config.lxmf_auto_sync_propagation_nodes_max_total_size_bytes.get.return_value = 10000000
|
app.config.lxmf_auto_sync_propagation_nodes_max_total_size_bytes.get.return_value = 10000000
|
||||||
app.config.lxmf_auto_sync_propagation_nodes_max_total_count.get.return_value = 100
|
app.config.lxmf_auto_sync_propagation_nodes_max_total_count.get.return_value = (
|
||||||
|
100
|
||||||
|
)
|
||||||
app.config.lxmf_auto_sync_propagation_nodes_max_total_age_seconds.get.return_value = 864000
|
app.config.lxmf_auto_sync_propagation_nodes_max_total_age_seconds.get.return_value = 864000
|
||||||
app.config.lxmf_auto_sync_propagation_nodes_max_total_size_bytes_per_node.get.return_value = 1000000
|
app.config.lxmf_auto_sync_propagation_nodes_max_total_size_bytes_per_node.get.return_value = 1000000
|
||||||
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_count_per_node.get.return_value = 100
|
||||||
@@ -169,10 +196,13 @@ def mock_app(temp_dir):
|
|||||||
app.handle_forwarding = MagicMock()
|
app.handle_forwarding = MagicMock()
|
||||||
app.convert_db_announce_to_dict = MagicMock(return_value={})
|
app.convert_db_announce_to_dict = MagicMock(return_value={})
|
||||||
app.get_config_dict = MagicMock(return_value={"test_config": "test_value"})
|
app.get_config_dict = MagicMock(return_value={"test_config": "test_value"})
|
||||||
app.resend_failed_messages_for_destination = MagicMock(side_effect=lambda dest: None)
|
app.resend_failed_messages_for_destination = MagicMock(
|
||||||
|
side_effect=lambda dest: None,
|
||||||
|
)
|
||||||
|
|
||||||
yield app
|
yield app
|
||||||
|
|
||||||
|
|
||||||
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
|
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
|
||||||
@given(
|
@given(
|
||||||
num_announces=st.integers(min_value=10, max_value=100),
|
num_announces=st.integers(min_value=10, max_value=100),
|
||||||
@@ -189,7 +219,7 @@ def test_announce_overload(mock_app, num_announces):
|
|||||||
mock_app.database.announces.get_announce_by_hash.return_value = {
|
mock_app.database.announces.get_announce_by_hash.return_value = {
|
||||||
"aspect": "lxmf.delivery",
|
"aspect": "lxmf.delivery",
|
||||||
"destination_hash": "some_hash",
|
"destination_hash": "some_hash",
|
||||||
"display_name": "Test Peer"
|
"display_name": "Test Peer",
|
||||||
}
|
}
|
||||||
|
|
||||||
for i in range(num_announces):
|
for i in range(num_announces):
|
||||||
@@ -199,16 +229,13 @@ def test_announce_overload(mock_app, num_announces):
|
|||||||
announce_packet_hash = os.urandom(16)
|
announce_packet_hash = os.urandom(16)
|
||||||
|
|
||||||
mock_app.on_lxmf_announce_received(
|
mock_app.on_lxmf_announce_received(
|
||||||
aspect,
|
aspect, destination_hash, announced_identity, app_data, announce_packet_hash,
|
||||||
destination_hash,
|
|
||||||
announced_identity,
|
|
||||||
app_data,
|
|
||||||
announce_packet_hash
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Verify that the database was called for each announce
|
# Verify that the database was called for each announce
|
||||||
assert mock_app.announce_manager.upsert_announce.call_count == num_announces
|
assert mock_app.announce_manager.upsert_announce.call_count == num_announces
|
||||||
|
|
||||||
|
|
||||||
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
|
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
|
||||||
@given(
|
@given(
|
||||||
num_messages=st.integers(min_value=10, max_value=100),
|
num_messages=st.integers(min_value=10, max_value=100),
|
||||||
@@ -229,6 +256,7 @@ def test_message_spamming(mock_app, num_messages):
|
|||||||
|
|
||||||
assert mock_app.db_upsert_lxmf_message.call_count == num_messages
|
assert mock_app.db_upsert_lxmf_message.call_count == num_messages
|
||||||
|
|
||||||
|
|
||||||
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
|
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
|
||||||
@given(
|
@given(
|
||||||
num_messages=st.integers(min_value=10, max_value=50),
|
num_messages=st.integers(min_value=10, max_value=50),
|
||||||
@@ -250,18 +278,34 @@ def test_message_spamming_large_payloads(mock_app, num_messages, payload_size):
|
|||||||
|
|
||||||
assert mock_app.db_upsert_lxmf_message.call_count == num_messages
|
assert mock_app.db_upsert_lxmf_message.call_count == num_messages
|
||||||
|
|
||||||
|
|
||||||
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
|
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
|
||||||
@given(
|
@given(
|
||||||
msg=st.dictionaries(
|
msg=st.dictionaries(
|
||||||
keys=st.text(),
|
keys=st.text(),
|
||||||
values=st.one_of(st.text(), st.integers(), st.booleans(), st.dictionaries(keys=st.text(), values=st.text())),
|
values=st.one_of(
|
||||||
min_size=1, max_size=10
|
st.text(),
|
||||||
).flatmap(lambda d: st.sampled_from([
|
st.integers(),
|
||||||
"ping", "config.set", "nomadnet.download.cancel",
|
st.booleans(),
|
||||||
"nomadnet.page.archives.get", "lxmf.forwarding.rule.add",
|
st.dictionaries(keys=st.text(), values=st.text()),
|
||||||
"lxmf.forwarding.rule.delete", "lxm.ingest_uri",
|
),
|
||||||
"lxm.generate_paper_uri", "keyboard_shortcuts.get"
|
min_size=1,
|
||||||
]).map(lambda t: {**d, "type": t}))
|
max_size=10,
|
||||||
|
).flatmap(
|
||||||
|
lambda d: st.sampled_from(
|
||||||
|
[
|
||||||
|
"ping",
|
||||||
|
"config.set",
|
||||||
|
"nomadnet.download.cancel",
|
||||||
|
"nomadnet.page.archives.get",
|
||||||
|
"lxmf.forwarding.rule.add",
|
||||||
|
"lxmf.forwarding.rule.delete",
|
||||||
|
"lxm.ingest_uri",
|
||||||
|
"lxm.generate_paper_uri",
|
||||||
|
"keyboard_shortcuts.get",
|
||||||
|
],
|
||||||
|
).map(lambda t: {**d, "type": t}),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_websocket_api_hypothesis(mock_app, msg):
|
async def test_websocket_api_hypothesis(mock_app, msg):
|
||||||
@@ -274,6 +318,7 @@ async def test_websocket_api_hypothesis(mock_app, msg):
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_websocket_api_fuzzing(mock_app):
|
async def test_websocket_api_fuzzing(mock_app):
|
||||||
"""Fuzz the websocket API with various message types and payloads."""
|
"""Fuzz the websocket API with various message types and payloads."""
|
||||||
@@ -287,11 +332,19 @@ async def test_websocket_api_fuzzing(mock_app):
|
|||||||
{"type": "config.set", "config": {"invalid_key": "invalid_value"}},
|
{"type": "config.set", "config": {"invalid_key": "invalid_value"}},
|
||||||
{"type": "config.set", "config": "not_a_dict"},
|
{"type": "config.set", "config": "not_a_dict"},
|
||||||
{"type": "nomadnet.download.cancel", "download_id": "non_existent_id"},
|
{"type": "nomadnet.download.cancel", "download_id": "non_existent_id"},
|
||||||
{"type": "nomadnet.page.archives.get", "destination_hash": "invalid_hash", "page_path": "/invalid"},
|
{
|
||||||
|
"type": "nomadnet.page.archives.get",
|
||||||
|
"destination_hash": "invalid_hash",
|
||||||
|
"page_path": "/invalid",
|
||||||
|
},
|
||||||
{"type": "lxmf.forwarding.rule.add", "rule": {}},
|
{"type": "lxmf.forwarding.rule.add", "rule": {}},
|
||||||
{"type": "lxmf.forwarding.rule.delete", "id": -1},
|
{"type": "lxmf.forwarding.rule.delete", "id": -1},
|
||||||
{"type": "lxm.ingest_uri", "uri": "invalid_uri"},
|
{"type": "lxm.ingest_uri", "uri": "invalid_uri"},
|
||||||
{"type": "lxm.generate_paper_uri", "destination_hash": "00" * 16, "content": "test"},
|
{
|
||||||
|
"type": "lxm.generate_paper_uri",
|
||||||
|
"destination_hash": "00" * 16,
|
||||||
|
"content": "test",
|
||||||
|
},
|
||||||
{"type": "non_existent_type", "data": "random_data"},
|
{"type": "non_existent_type", "data": "random_data"},
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -301,6 +354,7 @@ async def test_websocket_api_fuzzing(mock_app):
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_config_fuzzing(mock_app):
|
async def test_config_fuzzing(mock_app):
|
||||||
"""Fuzz the config update logic with various values."""
|
"""Fuzz the config update logic with various values."""
|
||||||
@@ -319,31 +373,23 @@ async def test_config_fuzzing(mock_app):
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def test_malformed_announce_data(mock_app):
|
def test_malformed_announce_data(mock_app):
|
||||||
"""Test handling of malformed or unexpected data in announces."""
|
"""Test handling of malformed or unexpected data in announces."""
|
||||||
aspect = "lxmf.delivery"
|
aspect = "lxmf.delivery"
|
||||||
destination_hash = b"too_short" # Malformed hash
|
destination_hash = b"too_short" # Malformed hash
|
||||||
|
|
||||||
# Test with None identity
|
# Test with None identity
|
||||||
mock_app.on_lxmf_announce_received(
|
mock_app.on_lxmf_announce_received(aspect, destination_hash, None, None, b"")
|
||||||
aspect,
|
|
||||||
destination_hash,
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
b""
|
|
||||||
)
|
|
||||||
|
|
||||||
# Test with identity having None hash
|
# Test with identity having None hash
|
||||||
announced_identity = MagicMock()
|
announced_identity = MagicMock()
|
||||||
announced_identity.hash = None
|
announced_identity.hash = None
|
||||||
mock_app.on_lxmf_announce_received(
|
mock_app.on_lxmf_announce_received(
|
||||||
aspect,
|
aspect, destination_hash, announced_identity, None, b"",
|
||||||
destination_hash,
|
|
||||||
announced_identity,
|
|
||||||
None,
|
|
||||||
b""
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_malformed_message_data(mock_app):
|
def test_malformed_message_data(mock_app):
|
||||||
"""Test handling of malformed LXMF messages."""
|
"""Test handling of malformed LXMF messages."""
|
||||||
mock_message = MagicMock()
|
mock_message = MagicMock()
|
||||||
@@ -353,10 +399,11 @@ def test_malformed_message_data(mock_app):
|
|||||||
# This should be caught by the try-except in on_lxmf_delivery
|
# This should be caught by the try-except in on_lxmf_delivery
|
||||||
mock_app.on_lxmf_delivery(mock_message)
|
mock_app.on_lxmf_delivery(mock_message)
|
||||||
|
|
||||||
|
|
||||||
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
|
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
|
||||||
@given(
|
@given(
|
||||||
weird_string=st.text(min_size=0, max_size=1000),
|
weird_string=st.text(min_size=0, max_size=1000),
|
||||||
large_binary=st.binary(min_size=0, max_size=10000)
|
large_binary=st.binary(min_size=0, max_size=10000),
|
||||||
)
|
)
|
||||||
def test_database_dao_fuzzing(mock_app, weird_string, large_binary):
|
def test_database_dao_fuzzing(mock_app, weird_string, large_binary):
|
||||||
"""Fuzz the database DAOs with weird strings and large binary data."""
|
"""Fuzz the database DAOs with weird strings and large binary data."""
|
||||||
@@ -369,7 +416,7 @@ def test_database_dao_fuzzing(mock_app, weird_string, large_binary):
|
|||||||
"app_data": large_binary,
|
"app_data": large_binary,
|
||||||
"rssi": random.randint(-120, 0),
|
"rssi": random.randint(-120, 0),
|
||||||
"snr": random.uniform(-20, 20),
|
"snr": random.uniform(-20, 20),
|
||||||
"quality": random.uniform(0, 100)
|
"quality": random.uniform(0, 100),
|
||||||
}
|
}
|
||||||
try:
|
try:
|
||||||
mock_app.database.announces.upsert_announce(announce_data)
|
mock_app.database.announces.upsert_announce(announce_data)
|
||||||
@@ -387,31 +434,34 @@ def test_database_dao_fuzzing(mock_app, weird_string, large_binary):
|
|||||||
"fields": {"weird": weird_string},
|
"fields": {"weird": weird_string},
|
||||||
"timestamp": time.time(),
|
"timestamp": time.time(),
|
||||||
"is_incoming": random.choice([0, 1]),
|
"is_incoming": random.choice([0, 1]),
|
||||||
"is_spam": random.choice([0, 1])
|
"is_spam": random.choice([0, 1]),
|
||||||
}
|
}
|
||||||
try:
|
try:
|
||||||
mock_app.database.messages.upsert_lxmf_message(message_data)
|
mock_app.database.messages.upsert_lxmf_message(message_data)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
|
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
|
||||||
@given(
|
@given(
|
||||||
audio_bytes=st.binary(min_size=0, max_size=5000),
|
audio_bytes=st.binary(min_size=0, max_size=5000),
|
||||||
image_bytes=st.binary(min_size=0, max_size=10000)
|
image_bytes=st.binary(min_size=0, max_size=10000),
|
||||||
)
|
)
|
||||||
def test_lxmf_field_fuzzing(audio_bytes, image_bytes):
|
def test_lxmf_field_fuzzing(audio_bytes, image_bytes):
|
||||||
"""Fuzz the LXMF field helper classes."""
|
"""Fuzz the LXMF field helper classes."""
|
||||||
try:
|
try:
|
||||||
LxmfAudioField(audio_mode=random.randint(0, 10), audio_bytes=audio_bytes)
|
LxmfAudioField(audio_mode=random.randint(0, 10), audio_bytes=audio_bytes)
|
||||||
LxmfImageField(image_type=random.choice(["png", "jpg", "webp", "invalid"]), image_bytes=image_bytes)
|
LxmfImageField(
|
||||||
|
image_type=random.choice(["png", "jpg", "webp", "invalid"]),
|
||||||
|
image_bytes=image_bytes,
|
||||||
|
)
|
||||||
LxmfFileAttachment(file_name="test.txt", file_bytes=audio_bytes)
|
LxmfFileAttachment(file_name="test.txt", file_bytes=audio_bytes)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
pytest.fail(f"LXMF field classes crashed: {e}")
|
pytest.fail(f"LXMF field classes crashed: {e}")
|
||||||
|
|
||||||
|
|
||||||
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
|
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
|
||||||
@given(
|
@given(command_bytes=st.binary(min_size=1, max_size=100))
|
||||||
command_bytes=st.binary(min_size=1, max_size=100)
|
|
||||||
)
|
|
||||||
def test_sideband_command_fuzzing(mock_app, command_bytes):
|
def test_sideband_command_fuzzing(mock_app, command_bytes):
|
||||||
"""Fuzz the sideband command parsing in LXMF delivery."""
|
"""Fuzz the sideband command parsing in LXMF delivery."""
|
||||||
mock_message = MagicMock()
|
mock_message = MagicMock()
|
||||||
@@ -425,11 +475,12 @@ def test_sideband_command_fuzzing(mock_app, command_bytes):
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
|
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
|
||||||
@given(
|
@given(
|
||||||
destination_hash=st.text(min_size=0, max_size=100),
|
destination_hash=st.text(min_size=0, max_size=100),
|
||||||
page_path=st.text(min_size=0, max_size=500),
|
page_path=st.text(min_size=0, max_size=500),
|
||||||
content=st.text(min_size=0, max_size=10000)
|
content=st.text(min_size=0, max_size=10000),
|
||||||
)
|
)
|
||||||
def test_archiver_manager_fuzzing(mock_app, destination_hash, page_path, content):
|
def test_archiver_manager_fuzzing(mock_app, destination_hash, page_path, content):
|
||||||
"""Fuzz the archiver manager's page archiving logic."""
|
"""Fuzz the archiver manager's page archiving logic."""
|
||||||
@@ -438,10 +489,9 @@ def test_archiver_manager_fuzzing(mock_app, destination_hash, page_path, content
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
|
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
|
||||||
@given(
|
@given(state=st.integers(min_value=-10, max_value=30))
|
||||||
state=st.integers(min_value=-10, max_value=30)
|
|
||||||
)
|
|
||||||
def test_lxmf_state_conversion_fuzzing(mock_app, state):
|
def test_lxmf_state_conversion_fuzzing(mock_app, state):
|
||||||
"""Fuzz LXMF state string conversion."""
|
"""Fuzz LXMF state string conversion."""
|
||||||
mock_message = MagicMock()
|
mock_message = MagicMock()
|
||||||
@@ -451,10 +501,9 @@ def test_lxmf_state_conversion_fuzzing(mock_app, state):
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
|
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
|
||||||
@given(
|
@given(method=st.integers(min_value=-10, max_value=30))
|
||||||
method=st.integers(min_value=-10, max_value=30)
|
|
||||||
)
|
|
||||||
def test_lxmf_method_conversion_fuzzing(mock_app, method):
|
def test_lxmf_method_conversion_fuzzing(mock_app, method):
|
||||||
"""Fuzz LXMF method string conversion."""
|
"""Fuzz LXMF method string conversion."""
|
||||||
mock_message = MagicMock()
|
mock_message = MagicMock()
|
||||||
@@ -464,6 +513,7 @@ def test_lxmf_method_conversion_fuzzing(mock_app, method):
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def test_telephone_announce_fuzzing(mock_app):
|
def test_telephone_announce_fuzzing(mock_app):
|
||||||
"""Fuzz telephone announce reception."""
|
"""Fuzz telephone announce reception."""
|
||||||
aspect = "telephone.call"
|
aspect = "telephone.call"
|
||||||
@@ -475,11 +525,7 @@ def test_telephone_announce_fuzzing(mock_app):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
mock_app.on_telephone_announce_received(
|
mock_app.on_telephone_announce_received(
|
||||||
aspect,
|
aspect, destination_hash, announced_identity, app_data, announce_packet_hash,
|
||||||
destination_hash,
|
|
||||||
announced_identity,
|
|
||||||
app_data,
|
|
||||||
announce_packet_hash
|
|
||||||
)
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -38,7 +38,9 @@ def mock_app(temp_dir):
|
|||||||
patch("RNS.Transport"),
|
patch("RNS.Transport"),
|
||||||
patch("threading.Thread"),
|
patch("threading.Thread"),
|
||||||
patch.object(
|
patch.object(
|
||||||
ReticulumMeshChat, "announce_loop", new=MagicMock(return_value=None),
|
ReticulumMeshChat,
|
||||||
|
"announce_loop",
|
||||||
|
new=MagicMock(return_value=None),
|
||||||
),
|
),
|
||||||
patch.object(
|
patch.object(
|
||||||
ReticulumMeshChat,
|
ReticulumMeshChat,
|
||||||
@@ -46,7 +48,9 @@ def mock_app(temp_dir):
|
|||||||
new=MagicMock(return_value=None),
|
new=MagicMock(return_value=None),
|
||||||
),
|
),
|
||||||
patch.object(
|
patch.object(
|
||||||
ReticulumMeshChat, "crawler_loop", new=MagicMock(return_value=None),
|
ReticulumMeshChat,
|
||||||
|
"crawler_loop",
|
||||||
|
new=MagicMock(return_value=None),
|
||||||
),
|
),
|
||||||
):
|
):
|
||||||
mock_id = MagicMock()
|
mock_id = MagicMock()
|
||||||
|
|||||||
@@ -16,7 +16,9 @@ def mock_rns():
|
|||||||
patch("RNS.Identity") as mock_identity,
|
patch("RNS.Identity") as mock_identity,
|
||||||
patch("threading.Thread"),
|
patch("threading.Thread"),
|
||||||
patch.object(
|
patch.object(
|
||||||
ReticulumMeshChat, "announce_loop", new=MagicMock(return_value=None),
|
ReticulumMeshChat,
|
||||||
|
"announce_loop",
|
||||||
|
new=MagicMock(return_value=None),
|
||||||
),
|
),
|
||||||
patch.object(
|
patch.object(
|
||||||
ReticulumMeshChat,
|
ReticulumMeshChat,
|
||||||
@@ -24,7 +26,9 @@ def mock_rns():
|
|||||||
new=MagicMock(return_value=None),
|
new=MagicMock(return_value=None),
|
||||||
),
|
),
|
||||||
patch.object(
|
patch.object(
|
||||||
ReticulumMeshChat, "crawler_loop", new=MagicMock(return_value=None),
|
ReticulumMeshChat,
|
||||||
|
"crawler_loop",
|
||||||
|
new=MagicMock(return_value=None),
|
||||||
),
|
),
|
||||||
):
|
):
|
||||||
# Setup mock identity
|
# Setup mock identity
|
||||||
@@ -215,7 +219,9 @@ async def test_reload_reticulum_failure_recovery(mock_rns, temp_dir):
|
|||||||
# We need to make something else fail to reach the except block
|
# We need to make something else fail to reach the except block
|
||||||
# or just mock a method inside the try block to raise.
|
# or just mock a method inside the try block to raise.
|
||||||
with patch.object(
|
with patch.object(
|
||||||
app, "teardown_identity", side_effect=Exception("Reload failed"),
|
app,
|
||||||
|
"teardown_identity",
|
||||||
|
side_effect=Exception("Reload failed"),
|
||||||
):
|
):
|
||||||
result = await app.reload_reticulum()
|
result = await app.reload_reticulum()
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
import pytest
|
|
||||||
import os
|
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
|
from contextlib import ExitStack
|
||||||
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
|
import LXMF
|
||||||
|
import pytest
|
||||||
|
from hypothesis import HealthCheck, given, settings
|
||||||
|
from hypothesis import strategies as st
|
||||||
|
|
||||||
|
from meshchatx.meshchat import ReticulumMeshChat
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def mock_app():
|
def mock_app():
|
||||||
@@ -28,9 +32,17 @@ def mock_app():
|
|||||||
stack.enter_context(patch("RNS.Reticulum"))
|
stack.enter_context(patch("RNS.Reticulum"))
|
||||||
stack.enter_context(patch("RNS.Transport"))
|
stack.enter_context(patch("RNS.Transport"))
|
||||||
stack.enter_context(patch("threading.Thread"))
|
stack.enter_context(patch("threading.Thread"))
|
||||||
stack.enter_context(patch.object(ReticulumMeshChat, "announce_loop", return_value=None))
|
stack.enter_context(
|
||||||
stack.enter_context(patch.object(ReticulumMeshChat, "announce_sync_propagation_nodes", return_value=None))
|
patch.object(ReticulumMeshChat, "announce_loop", return_value=None),
|
||||||
stack.enter_context(patch.object(ReticulumMeshChat, "crawler_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 = MagicMock()
|
||||||
mock_id.hash = b"test_hash_32_bytes_long_01234567"
|
mock_id.hash = b"test_hash_32_bytes_long_01234567"
|
||||||
@@ -69,14 +81,36 @@ def mock_app():
|
|||||||
|
|
||||||
yield app
|
yield app
|
||||||
|
|
||||||
|
|
||||||
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
|
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
|
||||||
@given(
|
@given(
|
||||||
field_data=st.one_of(
|
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.lists(
|
||||||
st.dictionaries(keys=st.text(), values=st.one_of(st.text(), st.binary(), st.integers(), st.floats(), st.booleans(), st.none())),
|
st.one_of(
|
||||||
|
st.text(),
|
||||||
st.binary(),
|
st.binary(),
|
||||||
st.text()
|
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):
|
def test_lxmf_icon_appearance_fuzzing(mock_app, field_data):
|
||||||
"""Fuzz LXMF.FIELD_ICON_APPEARANCE parsing in on_lxmf_delivery."""
|
"""Fuzz LXMF.FIELD_ICON_APPEARANCE parsing in on_lxmf_delivery."""
|
||||||
@@ -90,22 +124,37 @@ def test_lxmf_icon_appearance_fuzzing(mock_app, field_data):
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
|
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
|
||||||
@given(
|
@given(
|
||||||
attachments_data=st.lists(
|
attachments_data=st.lists(
|
||||||
st.one_of(
|
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.lists(
|
||||||
|
st.one_of(
|
||||||
st.text(),
|
st.text(),
|
||||||
st.binary(),
|
st.binary(),
|
||||||
st.none()
|
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,
|
||||||
),
|
),
|
||||||
min_size=0, max_size=10
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
def test_lxmf_attachments_fuzzing(mock_app, attachments_data):
|
def test_lxmf_attachments_fuzzing(mock_app, attachments_data):
|
||||||
"""Fuzz LXMF.FIELD_FILE_ATTACHMENTS parsing."""
|
"""Fuzz LXMF.FIELD_FILE_ATTACHMENTS parsing."""
|
||||||
mock_message = MagicMock()
|
mock_message = MagicMock()
|
||||||
mock_message.get_fields.return_value = {LXMF.FIELD_FILE_ATTACHMENTS: attachments_data}
|
mock_message.get_fields.return_value = {
|
||||||
|
LXMF.FIELD_FILE_ATTACHMENTS: attachments_data,
|
||||||
|
}
|
||||||
mock_message.source_hash = os.urandom(16)
|
mock_message.source_hash = os.urandom(16)
|
||||||
mock_message.hash = os.urandom(16)
|
mock_message.hash = os.urandom(16)
|
||||||
|
|
||||||
@@ -114,13 +163,25 @@ def test_lxmf_attachments_fuzzing(mock_app, attachments_data):
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
|
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
|
||||||
@given(
|
@given(
|
||||||
image_data=st.one_of(
|
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.lists(
|
||||||
|
st.one_of(
|
||||||
|
st.text(),
|
||||||
st.binary(),
|
st.binary(),
|
||||||
st.none()
|
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):
|
def test_lxmf_image_field_fuzzing(mock_app, image_data):
|
||||||
"""Fuzz LXMF.FIELD_IMAGE parsing."""
|
"""Fuzz LXMF.FIELD_IMAGE parsing."""
|
||||||
@@ -134,13 +195,25 @@ def test_lxmf_image_field_fuzzing(mock_app, image_data):
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
|
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
|
||||||
@given(
|
@given(
|
||||||
audio_data=st.one_of(
|
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.lists(
|
||||||
|
st.one_of(
|
||||||
|
st.text(),
|
||||||
st.binary(),
|
st.binary(),
|
||||||
st.none()
|
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):
|
def test_lxmf_audio_field_fuzzing(mock_app, audio_data):
|
||||||
"""Fuzz LXMF.FIELD_AUDIO parsing."""
|
"""Fuzz LXMF.FIELD_AUDIO parsing."""
|
||||||
@@ -154,16 +227,17 @@ def test_lxmf_audio_field_fuzzing(mock_app, audio_data):
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
|
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
|
||||||
@given(
|
@given(
|
||||||
filename=st.text(min_size=0, max_size=1000),
|
filename=st.text(min_size=0, max_size=1000),
|
||||||
file_bytes=st.binary(min_size=0, max_size=10000)
|
file_bytes=st.binary(min_size=0, max_size=10000),
|
||||||
)
|
)
|
||||||
def test_attachment_filename_security(mock_app, filename, file_bytes):
|
def test_attachment_filename_security(mock_app, filename, file_bytes):
|
||||||
"""Test for potential directory traversal or malicious filenames in attachments."""
|
"""Test for potential directory traversal or malicious filenames in attachments."""
|
||||||
mock_message = MagicMock()
|
mock_message = MagicMock()
|
||||||
mock_message.get_fields.return_value = {
|
mock_message.get_fields.return_value = {
|
||||||
LXMF.FIELD_FILE_ATTACHMENTS: [[filename, file_bytes]]
|
LXMF.FIELD_FILE_ATTACHMENTS: [[filename, file_bytes]],
|
||||||
}
|
}
|
||||||
mock_message.source_hash = os.urandom(16)
|
mock_message.source_hash = os.urandom(16)
|
||||||
mock_message.hash = os.urandom(16)
|
mock_message.hash = os.urandom(16)
|
||||||
@@ -174,10 +248,9 @@ def test_attachment_filename_security(mock_app, filename, file_bytes):
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
|
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
|
||||||
@given(
|
@given(caller_id_bytes=st.binary(min_size=0, max_size=1000))
|
||||||
caller_id_bytes=st.binary(min_size=0, max_size=1000)
|
|
||||||
)
|
|
||||||
def test_telephone_callback_fuzzing(mock_app, caller_id_bytes):
|
def test_telephone_callback_fuzzing(mock_app, caller_id_bytes):
|
||||||
"""Fuzz telephone manager callbacks with malformed identity bytes."""
|
"""Fuzz telephone manager callbacks with malformed identity bytes."""
|
||||||
try:
|
try:
|
||||||
@@ -190,6 +263,7 @@ def test_telephone_callback_fuzzing(mock_app, caller_id_bytes):
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
|
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
|
||||||
@given(
|
@given(
|
||||||
data=st.dictionaries(
|
data=st.dictionaries(
|
||||||
@@ -200,9 +274,9 @@ def test_telephone_callback_fuzzing(mock_app, caller_id_bytes):
|
|||||||
st.integers(),
|
st.integers(),
|
||||||
st.floats(),
|
st.floats(),
|
||||||
st.lists(st.text()),
|
st.lists(st.text()),
|
||||||
st.dictionaries(keys=st.text(), values=st.text())
|
st.dictionaries(keys=st.text(), values=st.text()),
|
||||||
)
|
),
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
def test_message_dao_upsert_fuzzing(mock_app, data):
|
def test_message_dao_upsert_fuzzing(mock_app, data):
|
||||||
"""Fuzz MessageDAO.upsert_lxmf_message with varied dictionary data."""
|
"""Fuzz MessageDAO.upsert_lxmf_message with varied dictionary data."""
|
||||||
@@ -211,10 +285,11 @@ def test_message_dao_upsert_fuzzing(mock_app, data):
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
|
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
|
||||||
@given(
|
@given(
|
||||||
title_bytes=st.binary(min_size=0, max_size=1000),
|
title_bytes=st.binary(min_size=0, max_size=1000),
|
||||||
content_bytes=st.binary(min_size=0, max_size=5000)
|
content_bytes=st.binary(min_size=0, max_size=5000),
|
||||||
)
|
)
|
||||||
def test_lxmf_message_decoding_fuzzing(mock_app, title_bytes, content_bytes):
|
def test_lxmf_message_decoding_fuzzing(mock_app, title_bytes, content_bytes):
|
||||||
"""Fuzz LXMF message title and content decoding."""
|
"""Fuzz LXMF message title and content decoding."""
|
||||||
@@ -239,10 +314,9 @@ def test_lxmf_message_decoding_fuzzing(mock_app, title_bytes, content_bytes):
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
|
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
|
||||||
@given(
|
@given(greeting_text=st.text(min_size=0, max_size=1000))
|
||||||
greeting_text=st.text(min_size=0, max_size=1000)
|
|
||||||
)
|
|
||||||
def test_voicemail_greeting_fuzzing(mock_app, greeting_text):
|
def test_voicemail_greeting_fuzzing(mock_app, greeting_text):
|
||||||
"""Fuzz voicemail greeting generation with varied text."""
|
"""Fuzz voicemail greeting generation with varied text."""
|
||||||
mock_app.voicemail_manager.has_espeak = True
|
mock_app.voicemail_manager.has_espeak = True
|
||||||
@@ -256,10 +330,9 @@ def test_voicemail_greeting_fuzzing(mock_app, greeting_text):
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
|
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
|
||||||
@given(
|
@given(caller_hash=st.binary(min_size=0, max_size=32))
|
||||||
caller_hash=st.binary(min_size=0, max_size=32)
|
|
||||||
)
|
|
||||||
def test_voicemail_incoming_call_fuzzing(mock_app, caller_hash):
|
def test_voicemail_incoming_call_fuzzing(mock_app, caller_hash):
|
||||||
"""Fuzz voicemail incoming call handling."""
|
"""Fuzz voicemail incoming call handling."""
|
||||||
mock_identity = MagicMock()
|
mock_identity = MagicMock()
|
||||||
@@ -270,23 +343,27 @@ def test_voicemail_incoming_call_fuzzing(mock_app, caller_hash):
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
|
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
|
||||||
@given(
|
@given(
|
||||||
source_hash=st.text(min_size=0, max_size=64),
|
source_hash=st.text(min_size=0, max_size=64),
|
||||||
recipient_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)
|
dest_hash=st.text(min_size=0, max_size=64),
|
||||||
)
|
)
|
||||||
def test_forwarding_manager_mapping_fuzzing(mock_app, source_hash, recipient_hash, dest_hash):
|
def test_forwarding_manager_mapping_fuzzing(
|
||||||
|
mock_app, source_hash, recipient_hash, dest_hash,
|
||||||
|
):
|
||||||
"""Fuzz forwarding manager mapping creation."""
|
"""Fuzz forwarding manager mapping creation."""
|
||||||
try:
|
try:
|
||||||
mock_app.forwarding_manager.get_or_create_mapping(source_hash, recipient_hash, dest_hash)
|
mock_app.forwarding_manager.get_or_create_mapping(
|
||||||
|
source_hash, recipient_hash, dest_hash,
|
||||||
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
|
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
|
||||||
@given(
|
@given(uri=st.text(min_size=0, max_size=5000))
|
||||||
uri=st.text(min_size=0, max_size=5000)
|
|
||||||
)
|
|
||||||
def test_lxm_ingest_uri_fuzzing(mock_app, uri):
|
def test_lxm_ingest_uri_fuzzing(mock_app, uri):
|
||||||
"""Fuzz the lxm.ingest_uri WebSocket handler."""
|
"""Fuzz the lxm.ingest_uri WebSocket handler."""
|
||||||
mock_client = MagicMock()
|
mock_client = MagicMock()
|
||||||
@@ -295,33 +372,46 @@ def test_lxm_ingest_uri_fuzzing(mock_app, uri):
|
|||||||
try:
|
try:
|
||||||
# We need to wrap it in a task since it's async
|
# We need to wrap it in a task since it's async
|
||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
loop = asyncio.new_event_loop()
|
loop = asyncio.new_event_loop()
|
||||||
asyncio.set_event_loop(loop)
|
asyncio.set_event_loop(loop)
|
||||||
loop.run_until_complete(mock_app.on_websocket_data_received(mock_client, {"type": "lxm.ingest_uri", "uri": uri}))
|
loop.run_until_complete(
|
||||||
|
mock_app.on_websocket_data_received(
|
||||||
|
mock_client, {"type": "lxm.ingest_uri", "uri": uri},
|
||||||
|
),
|
||||||
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
|
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
|
||||||
@given(
|
@given(
|
||||||
config_data=st.dictionaries(
|
config_data=st.dictionaries(
|
||||||
keys=st.text(),
|
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()))
|
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):
|
def test_update_config_fuzzing(mock_app, config_data):
|
||||||
"""Fuzz the update_config method with randomized dictionary data."""
|
"""Fuzz the update_config method with randomized dictionary data."""
|
||||||
try:
|
try:
|
||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
loop = asyncio.new_event_loop()
|
loop = asyncio.new_event_loop()
|
||||||
asyncio.set_event_loop(loop)
|
asyncio.set_event_loop(loop)
|
||||||
loop.run_until_complete(mock_app.update_config(config_data))
|
loop.run_until_complete(mock_app.update_config(config_data))
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
|
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
|
||||||
@given(
|
@given(large_string=st.text(min_size=1000, max_size=10000))
|
||||||
large_string=st.text(min_size=1000, max_size=10000)
|
|
||||||
)
|
|
||||||
def test_large_payload_dos_resistance(mock_app, large_string):
|
def test_large_payload_dos_resistance(mock_app, large_string):
|
||||||
"""Check resistance to DoS via large strings in various fields."""
|
"""Check resistance to DoS via large strings in various fields."""
|
||||||
mock_message = MagicMock()
|
mock_message = MagicMock()
|
||||||
@@ -336,50 +426,61 @@ def test_large_payload_dos_resistance(mock_app, large_string):
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
|
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
|
||||||
@given(
|
@given(
|
||||||
nested_data=st.recursive(
|
nested_data=st.recursive(
|
||||||
st.one_of(st.text(), st.integers()),
|
st.one_of(st.text(), st.integers()),
|
||||||
lambda children: st.dictionaries(st.text(), children) | st.lists(children),
|
lambda children: st.dictionaries(st.text(), children) | st.lists(children),
|
||||||
max_leaves=100
|
max_leaves=100,
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
def test_websocket_recursion_fuzzing(mock_app, nested_data):
|
def test_websocket_recursion_fuzzing(mock_app, nested_data):
|
||||||
"""Fuzz the WebSocket handler with deeply nested JSON data."""
|
"""Fuzz the WebSocket handler with deeply nested JSON data."""
|
||||||
mock_client = MagicMock()
|
mock_client = MagicMock()
|
||||||
try:
|
try:
|
||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
loop = asyncio.new_event_loop()
|
loop = asyncio.new_event_loop()
|
||||||
asyncio.set_event_loop(loop)
|
asyncio.set_event_loop(loop)
|
||||||
loop.run_until_complete(mock_app.on_websocket_data_received(mock_client, {"type": "ping", "data": nested_data}))
|
loop.run_until_complete(
|
||||||
|
mock_app.on_websocket_data_received(
|
||||||
|
mock_client, {"type": "ping", "data": nested_data},
|
||||||
|
),
|
||||||
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
|
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
|
||||||
@given(
|
@given(dest_hash=st.text(), content=st.text())
|
||||||
dest_hash=st.text(),
|
|
||||||
content=st.text()
|
|
||||||
)
|
|
||||||
def test_lxm_generate_paper_uri_fuzzing(mock_app, dest_hash, content):
|
def test_lxm_generate_paper_uri_fuzzing(mock_app, dest_hash, content):
|
||||||
"""Fuzz paper URI generation with randomized inputs."""
|
"""Fuzz paper URI generation with randomized inputs."""
|
||||||
mock_client = MagicMock()
|
mock_client = MagicMock()
|
||||||
try:
|
try:
|
||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
loop = asyncio.new_event_loop()
|
loop = asyncio.new_event_loop()
|
||||||
asyncio.set_event_loop(loop)
|
asyncio.set_event_loop(loop)
|
||||||
loop.run_until_complete(mock_app.on_websocket_data_received(mock_client, {
|
loop.run_until_complete(
|
||||||
|
mock_app.on_websocket_data_received(
|
||||||
|
mock_client,
|
||||||
|
{
|
||||||
"type": "lxm.generate_paper_uri",
|
"type": "lxm.generate_paper_uri",
|
||||||
"destination_hash": dest_hash,
|
"destination_hash": dest_hash,
|
||||||
"content": content
|
"content": content,
|
||||||
}))
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
|
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
|
||||||
@given(
|
@given(
|
||||||
lon=st.floats(allow_nan=False, allow_infinity=False),
|
lon=st.floats(allow_nan=False, allow_infinity=False),
|
||||||
lat=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)
|
zoom=st.integers(min_value=-100, max_value=100),
|
||||||
)
|
)
|
||||||
def test_map_manager_coord_fuzzing(mock_app, lon, lat, zoom):
|
def test_map_manager_coord_fuzzing(mock_app, lon, lat, zoom):
|
||||||
"""Fuzz coordinate to tile conversion in MapManager."""
|
"""Fuzz coordinate to tile conversion in MapManager."""
|
||||||
@@ -388,11 +489,12 @@ def test_map_manager_coord_fuzzing(mock_app, lon, lat, zoom):
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
|
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
|
||||||
@given(
|
@given(
|
||||||
text=st.text(),
|
text=st.text(),
|
||||||
source_lang=st.text(min_size=0, max_size=10),
|
source_lang=st.text(min_size=0, max_size=10),
|
||||||
target_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):
|
def test_translator_handler_fuzzing(mock_app, text, source_lang, target_lang):
|
||||||
"""Fuzz the TranslatorHandler translate_text method."""
|
"""Fuzz the TranslatorHandler translate_text method."""
|
||||||
@@ -404,28 +506,26 @@ def test_translator_handler_fuzzing(mock_app, text, source_lang, target_lang):
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
|
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
|
||||||
@given(
|
@given(dest_hash=st.text(), icon_name=st.text(), fg_color=st.text(), bg_color=st.text())
|
||||||
dest_hash=st.text(),
|
def test_update_lxmf_user_icon_fuzzing(
|
||||||
icon_name=st.text(),
|
mock_app, dest_hash, icon_name, fg_color, bg_color,
|
||||||
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."""
|
"""Fuzz user icon update logic with malformed strings."""
|
||||||
try:
|
try:
|
||||||
mock_app.update_lxmf_user_icon(dest_hash, icon_name, fg_color, bg_color)
|
mock_app.update_lxmf_user_icon(dest_hash, icon_name, fg_color, bg_color)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
|
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
|
||||||
@given(
|
@given(binary_data=st.binary(min_size=0, max_size=10000))
|
||||||
binary_data=st.binary(min_size=0, max_size=10000)
|
|
||||||
)
|
|
||||||
def test_rns_identity_load_fuzzing(mock_app, binary_data):
|
def test_rns_identity_load_fuzzing(mock_app, binary_data):
|
||||||
"""Fuzz RNS.Identity loading with random binary data."""
|
"""Fuzz RNS.Identity loading with random binary data."""
|
||||||
try:
|
try:
|
||||||
import RNS
|
import RNS
|
||||||
|
|
||||||
try:
|
try:
|
||||||
RNS.Identity.from_bytes(binary_data)
|
RNS.Identity.from_bytes(binary_data)
|
||||||
except Exception:
|
except Exception:
|
||||||
|
|||||||
Reference in New Issue
Block a user