diff --git a/tests/backend/conftest.py b/tests/backend/conftest.py index 9fe7e1d..fa28b61 100644 --- a/tests/backend/conftest.py +++ b/tests/backend/conftest.py @@ -43,9 +43,6 @@ def global_mocks(): @pytest.fixture(autouse=True) def cleanup_sqlite_connections(): yield - # After each test, try to close any lingering sqlite connections if possible - # This is a bit hard globally without tracking them, but we can at least - # trigger GC which often helps with ResourceWarnings. import gc gc.collect() diff --git a/tests/backend/test_database_snapshots.py b/tests/backend/test_database_snapshots.py index 49f29ca..bdcd0fd 100644 --- a/tests/backend/test_database_snapshots.py +++ b/tests/backend/test_database_snapshots.py @@ -65,7 +65,6 @@ def test_database_snapshot_restoration(temp_dir): def test_database_auto_backup_logic(temp_dir): - # This test verifies the loop logic if possible, or just the backup method db_path = os.path.join(temp_dir, "test.db") db = Database(db_path) db.initialize() diff --git a/tests/backend/test_display_name_fuzzing.py b/tests/backend/test_display_name_fuzzing.py index 5b13ab6..e69cd84 100644 --- a/tests/backend/test_display_name_fuzzing.py +++ b/tests/backend/test_display_name_fuzzing.py @@ -66,7 +66,6 @@ def test_parse_nomadnetwork_node_display_name_property_based(name): def test_parse_lxmf_display_name_invalid_base64(data): # Generate something that is NOT valid base64 (by adding invalid chars) invalid_b64 = base64.b64encode(data).decode("utf-8") + "!!!" - # This should not crash, it should just return the default value or fail gracefully result = parse_lxmf_display_name(invalid_b64) assert isinstance(result, str) diff --git a/tests/backend/test_docs_manager.py b/tests/backend/test_docs_manager.py index 9044cef..b1ba6c7 100644 --- a/tests/backend/test_docs_manager.py +++ b/tests/backend/test_docs_manager.py @@ -52,7 +52,6 @@ def test_docs_manager_storage_dir_fallback(tmp_path): def test_docs_manager_readonly_public_dir_handling(tmp_path): - # This test simulates a read-only public dir without storage_dir public_dir = tmp_path / "readonly_public" public_dir.mkdir() @@ -189,7 +188,6 @@ def test_extract_docs_malformed_zip(docs_manager, temp_dirs): # 1. Zip with no folders at all create_mock_zip(zip_path, ["file_at_root.txt"]) try: - # This might fail with IndexError at namelist()[0].split('/')[0] if no slash docs_manager._extract_docs(zip_path) except (IndexError, Exception): pass # Expected or at least handled by not crashing the whole app diff --git a/tests/backend/test_fuzzing.py b/tests/backend/test_fuzzing.py index 97d7f46..de9799b 100644 --- a/tests/backend/test_fuzzing.py +++ b/tests/backend/test_fuzzing.py @@ -33,7 +33,6 @@ from meshchatx.src.backend.telemetry_utils import Telemeter def test_telemetry_unpack_fuzzing(data): """Fuzz the telemetry unpacking logic with random binary data.""" try: - # This should not raise unhandled exceptions Telemeter.from_packed(data) except Exception: # We expect some failures for invalid packed data, but no crashes @@ -113,7 +112,6 @@ def test_display_name_parsing_fuzzing(app_data_base64): def test_lxmf_fields_parsing_fuzzing(fields_data): """Fuzz the parsing of LXMF message fields.""" try: - # This simulates how meshchat.py processes fields in on_lxmf_delivery for field_id, field_data in fields_data.items(): if field_id == 0x01: # FIELD_COMMANDS try: @@ -514,10 +512,7 @@ def test_malformed_announce_data(mock_app): def test_malformed_message_data(mock_app): """Test handling of malformed LXMF messages.""" mock_message = MagicMock() - # Simulate missing attributes or methods del mock_message.source_hash - - # This should be caught by the try-except in on_lxmf_delivery mock_app.on_lxmf_delivery(mock_message) diff --git a/tests/backend/test_fuzzing_extended.py b/tests/backend/test_fuzzing_extended.py index 1d8850b..b5b58b9 100644 --- a/tests/backend/test_fuzzing_extended.py +++ b/tests/backend/test_fuzzing_extended.py @@ -246,7 +246,6 @@ def test_lxmf_message_construction_fuzzing(mock_app, content, title, fields): ) def test_database_record_fuzzing(mock_app, table_name, data): """Fuzz database record insertion logic (simulated).""" - # This tests the DAO layer's resilience to weird data types if they aren't properly sanitized try: dao = None if table_name == "messages" and hasattr(mock_app.database, "messages"): @@ -350,6 +349,36 @@ def test_markdown_renderer_fuzzing(text): pytest.fail(f"MarkdownRenderer crashed: {e}") +@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None) +@given( + text=st.one_of( + st.text(min_size=0, max_size=5000), + st.sampled_from([ + "", + "[x](javascript:alert(1))", + "[x](data:text/html,)", + "**" * 2000, + "#" * 2000, + "`" * 2000, + "[](" * 500 + ")" * 500, + "\x00\x01\x02\n\t", + "\ufffd" * 100, + ]), + ), +) +def test_markdown_renderer_dangerous_patterns(text): + """Fuzz markdown renderer with known risky patterns (XSS, ReDoS, control chars).""" + from meshchatx.src.backend.markdown_renderer import MarkdownRenderer + + try: + html_out = MarkdownRenderer.render(text) + assert isinstance(html_out, str) + assert "", + "", + "", + "", + ] + for s in cases: + r = MarkdownRenderer.render(s) + self.assertNotIn("", r, msg=s) + self.assertIn("<", r, msg=s) + + def test_xss_event_handlers(self): + cases = [ + '', + '', + 'x', + '', + ] + for s in cases: + r = MarkdownRenderer.render(s) + self.assertNotIn("alert(1))") + self.assertNotIn("data:", r) + self.assertIn('href="#"', r) + + def test_xss_link_href_vbscript(self): + r = MarkdownRenderer.render("[click](vbscript:msgbox(1))") + self.assertNotIn("vbscript:", r) + self.assertIn('href="#"', r) + + def test_safe_links_preserved(self): + r = MarkdownRenderer.render("[link](https://example.com/path)") + self.assertIn('href="https://example.com/path"', r) + r = MarkdownRenderer.render("[link](/relative)") + self.assertIn('href="/relative"', r) + r = MarkdownRenderer.render("[link](#anchor)") + self.assertIn('href="#anchor"', r) + + def test_redos_safe_repeated_markers(self): + t0 = time.perf_counter() + MarkdownRenderer.render("*" * 8000) + MarkdownRenderer.render("#" * 8000) + MarkdownRenderer.render("`" * 8000) + MarkdownRenderer.render("[](" * 2000 + "x" * 2000 + ")" * 2000) + elapsed = time.perf_counter() - t0 + self.assertLess(elapsed, 2.0, "ReDoS or excessive backtracking suspected") + + def test_malformed_unclosed_markdown(self): + cases = [ + "**bold no close", + "```\ncode no close", + "*italic", + "`code", + "___under", + "[unclosed link](url", + "![unclosed img](src", + ] + for s in cases: + r = MarkdownRenderer.render(s) + self.assertIsInstance(r, str) + self.assertNotIn("