mirror of
https://git.quad4.io/RNS-Things/MeshChatX.git
synced 2026-10-23 15:03:19 +00:00
fix(tests): clean up test formatting and improve readability in various test files
This commit is contained in:
11 files changed
+1720
-73
No files matched your search
@@ -85,7 +85,7 @@ def main() -> int:
|
||||
}
|
||||
args.output.parent.mkdir(parents=True, exist_ok=True)
|
||||
args.output.write_text(
|
||||
json.dumps(doc, indent=2, ensure_ascii=False) + "\n", encoding="utf-8"
|
||||
json.dumps(doc, indent=2, ensure_ascii=False) + "\n", encoding="utf-8",
|
||||
)
|
||||
print(f"Wrote {len(out_list)} interfaces to {args.output}")
|
||||
return 0
|
||||
|
||||
@@ -203,33 +203,44 @@ class TestCustomDisplayNameLifecycle:
|
||||
class TestContactsEdgeCases:
|
||||
def test_add_contact_upsert_preserves_addresses(self, contacts_dao, provider):
|
||||
contacts_dao.add_contact(
|
||||
"Alice", "ih1", lxmf_address="lxmf1", lxst_address="lxst1",
|
||||
"Alice",
|
||||
"ih1",
|
||||
lxmf_address="lxmf1",
|
||||
lxst_address="lxst1",
|
||||
)
|
||||
contacts_dao.add_contact(
|
||||
"Alice Updated", "ih1", lxmf_address=None, lxst_address=None,
|
||||
"Alice Updated",
|
||||
"ih1",
|
||||
lxmf_address=None,
|
||||
lxst_address=None,
|
||||
)
|
||||
row = provider.fetchone(
|
||||
"SELECT * FROM contacts WHERE remote_identity_hash = ?", ("ih1",),
|
||||
"SELECT * FROM contacts WHERE remote_identity_hash = ?",
|
||||
("ih1",),
|
||||
)
|
||||
assert row["name"] == "Alice Updated"
|
||||
assert row["lxmf_address"] == "lxmf1"
|
||||
assert row["lxst_address"] == "lxst1"
|
||||
|
||||
def test_add_contact_upsert_replaces_name_unconditionally(
|
||||
self, contacts_dao, provider,
|
||||
self,
|
||||
contacts_dao,
|
||||
provider,
|
||||
):
|
||||
"""Verifies add_contact always overwrites name on conflict."""
|
||||
contacts_dao.add_contact("Real Name", "ih2")
|
||||
contacts_dao.add_contact("Overwritten", "ih2")
|
||||
row = provider.fetchone(
|
||||
"SELECT * FROM contacts WHERE remote_identity_hash = ?", ("ih2",),
|
||||
"SELECT * FROM contacts WHERE remote_identity_hash = ?",
|
||||
("ih2",),
|
||||
)
|
||||
assert row["name"] == "Overwritten"
|
||||
|
||||
def test_update_contact_partial(self, contacts_dao, provider):
|
||||
contacts_dao.add_contact("Alice", "ih3", lxmf_address="lx3")
|
||||
row = provider.fetchone(
|
||||
"SELECT id FROM contacts WHERE remote_identity_hash = ?", ("ih3",),
|
||||
"SELECT id FROM contacts WHERE remote_identity_hash = ?",
|
||||
("ih3",),
|
||||
)
|
||||
cid = row["id"]
|
||||
contacts_dao.update_contact(cid, name="Alice Renamed")
|
||||
@@ -240,7 +251,8 @@ class TestContactsEdgeCases:
|
||||
def test_update_contact_no_fields_is_noop(self, contacts_dao, provider):
|
||||
contacts_dao.add_contact("NoOp", "ih4")
|
||||
row = provider.fetchone(
|
||||
"SELECT id FROM contacts WHERE remote_identity_hash = ?", ("ih4",),
|
||||
"SELECT id FROM contacts WHERE remote_identity_hash = ?",
|
||||
("ih4",),
|
||||
)
|
||||
contacts_dao.update_contact(row["id"])
|
||||
updated = provider.fetchone("SELECT * FROM contacts WHERE id = ?", (row["id"],))
|
||||
@@ -248,10 +260,13 @@ class TestContactsEdgeCases:
|
||||
|
||||
def test_update_contact_clear_image(self, contacts_dao, provider):
|
||||
contacts_dao.add_contact(
|
||||
"WithImage", "ih5", custom_image="data:image/png;base64,abc",
|
||||
"WithImage",
|
||||
"ih5",
|
||||
custom_image="data:image/png;base64,abc",
|
||||
)
|
||||
row = provider.fetchone(
|
||||
"SELECT id FROM contacts WHERE remote_identity_hash = ?", ("ih5",),
|
||||
"SELECT id FROM contacts WHERE remote_identity_hash = ?",
|
||||
("ih5",),
|
||||
)
|
||||
assert row is not None
|
||||
cid = row["id"]
|
||||
@@ -277,7 +292,8 @@ class TestContactsEdgeCases:
|
||||
def test_unicode_contact_name(self, contacts_dao, provider):
|
||||
contacts_dao.add_contact("\u00c9milie \u00d6sterreich", "ih8")
|
||||
row = provider.fetchone(
|
||||
"SELECT * FROM contacts WHERE remote_identity_hash = ?", ("ih8",),
|
||||
"SELECT * FROM contacts WHERE remote_identity_hash = ?",
|
||||
("ih8",),
|
||||
)
|
||||
assert row["name"] == "\u00c9milie \u00d6sterreich"
|
||||
|
||||
@@ -341,9 +357,7 @@ class TestParseLxmfDisplayNameFallback:
|
||||
|
||||
|
||||
class TestNameResolutionPriority:
|
||||
"""Simulates the priority chain:
|
||||
custom_display_name > announce app_data > contact name > 'Anonymous Peer'.
|
||||
"""
|
||||
"""Display name resolution: custom > announce > contact > anonymous."""
|
||||
|
||||
def test_custom_name_wins_over_announce(self, announce_dao):
|
||||
dest = "f" * 32
|
||||
@@ -376,7 +390,10 @@ class TestNameResolutionPriority:
|
||||
assert display == "Anonymous Peer"
|
||||
|
||||
def test_contact_name_used_when_no_announce_no_custom(
|
||||
self, announce_dao, contacts_dao, provider,
|
||||
self,
|
||||
announce_dao,
|
||||
contacts_dao,
|
||||
provider,
|
||||
):
|
||||
dest = "f" * 32
|
||||
announce_dao.upsert_announce(_base_announce(dest=dest, app_data=None))
|
||||
@@ -401,11 +418,11 @@ class TestNameResolutionPriority:
|
||||
assert display == "AnnounceName"
|
||||
|
||||
def test_wiping_announce_with_contact_still_resolves(
|
||||
self, announce_dao, contacts_dao,
|
||||
self,
|
||||
announce_dao,
|
||||
contacts_dao,
|
||||
):
|
||||
"""After the COALESCE fix, this should not happen, but if app_data
|
||||
was already NULL, the contact name should still be available.
|
||||
"""
|
||||
"""When ``app_data`` is NULL, fall back to the contact name."""
|
||||
dest = "f" * 32
|
||||
contacts_dao.add_contact("ContactFallback", dest, lxmf_address=dest)
|
||||
announce_dao.upsert_announce(_base_announce(dest=dest, app_data=None))
|
||||
@@ -444,11 +461,15 @@ class TestAnnounceTrimSafety:
|
||||
|
||||
class TestContactCustomNameSync:
|
||||
def test_renaming_contact_and_custom_name_independently(
|
||||
self, contacts_dao, announce_dao, provider,
|
||||
self,
|
||||
contacts_dao,
|
||||
announce_dao,
|
||||
provider,
|
||||
):
|
||||
contacts_dao.add_contact("Alice", "ih20", lxmf_address="lx20")
|
||||
row = provider.fetchone(
|
||||
"SELECT id FROM contacts WHERE remote_identity_hash = ?", ("ih20",),
|
||||
"SELECT id FROM contacts WHERE remote_identity_hash = ?",
|
||||
("ih20",),
|
||||
)
|
||||
cid = row["id"]
|
||||
|
||||
@@ -463,7 +484,8 @@ class TestContactCustomNameSync:
|
||||
"""Simulates what the UI should do: update both contact and custom name."""
|
||||
contacts_dao.add_contact("Bob", "ih21", lxmf_address="lx21")
|
||||
row = provider.fetchone(
|
||||
"SELECT id FROM contacts WHERE remote_identity_hash = ?", ("ih21",),
|
||||
"SELECT id FROM contacts WHERE remote_identity_hash = ?",
|
||||
("ih21",),
|
||||
)
|
||||
cid = row["id"]
|
||||
|
||||
|
||||
@@ -8,11 +8,8 @@ from meshchatx.src.backend.meshchat_utils import parse_lxmf_display_name
|
||||
from meshchatx.src.backend.telemetry_utils import Telemeter
|
||||
|
||||
|
||||
def test_parse_lxmf_display_name_bug_fix():
|
||||
"""Test that parse_lxmf_display_name handles both bytes and strings
|
||||
in the msgpack list, fixing the 'str' object has no attribute 'decode' bug.
|
||||
"""
|
||||
# 1. Test with bytes (normal case)
|
||||
def test_parse_lxmf_display_name_bytes_and_strings_in_msgpack_list():
|
||||
"""``parse_lxmf_display_name`` accepts msgpack list elements as bytes or str."""
|
||||
display_name_bytes = b"Test User"
|
||||
app_data_list = [display_name_bytes, None, None]
|
||||
app_data_bytes = msgpack.packb(app_data_list)
|
||||
@@ -20,17 +17,12 @@ def test_parse_lxmf_display_name_bug_fix():
|
||||
|
||||
assert parse_lxmf_display_name(app_data_base64) == "Test User"
|
||||
|
||||
# 2. Test with string (the bug case where msgpack already decoded it)
|
||||
# We simulate this by mocking msgpack.unpackb to return strings
|
||||
display_name_str = "Test User Str"
|
||||
app_data_list_str = [display_name_str, None, None]
|
||||
|
||||
with patch("RNS.vendor.umsgpack.unpackb", return_value=app_data_list_str):
|
||||
# The input app_data_base64 doesn't really matter much here since we mock unpackb,
|
||||
# but it must be valid base64 for the initial decode.
|
||||
assert parse_lxmf_display_name(app_data_base64) == "Test User Str"
|
||||
|
||||
# 3. Test with bytes directly passed (as in meshchat.py updated call)
|
||||
assert parse_lxmf_display_name(app_data_bytes) == "Test User"
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
"""Tests that HTTPS/WSS is used so local traffic cannot be sniffed by other apps.
|
||||
Server must speak TLS only on the API/WS port; plain HTTP must not be accepted.
|
||||
"""
|
||||
"""HTTPS/WSS: local API traffic must be TLS-only (no plain HTTP on the same port)."""
|
||||
|
||||
import os
|
||||
import socket
|
||||
@@ -74,9 +72,9 @@ def ssl_context_and_cert(temp_storage):
|
||||
async def test_https_serves_over_tls_only_plain_http_gets_no_http_response(
|
||||
ssl_context_and_cert,
|
||||
):
|
||||
"""Server started with ssl_context must not respond to plain HTTP.
|
||||
A local sniffer connecting with raw TCP and sending HTTP would get
|
||||
TLS handshake bytes or connection closure, not plaintext HTTP response.
|
||||
"""TLS-only server must not emit a plaintext HTTP response to raw HTTP bytes.
|
||||
|
||||
Raw TCP clients should see handshake noise or close, not ``HTTP/`` headers.
|
||||
"""
|
||||
ssl_context, _, _ = ssl_context_and_cert
|
||||
app = web.Application()
|
||||
@@ -124,9 +122,7 @@ async def test_https_serves_over_tls_only_plain_http_gets_no_http_response(
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_wss_over_same_tls_port(ssl_context_and_cert):
|
||||
"""WebSocket upgrade over the same TLS port uses WSS (encrypted).
|
||||
Verifies that a WS endpoint is reachable only via TLS.
|
||||
"""
|
||||
"""WebSocket on the TLS port must use WSS (encrypted transport)."""
|
||||
ssl_context, _, _ = ssl_context_and_cert
|
||||
app = web.Application()
|
||||
|
||||
@@ -147,10 +143,13 @@ async def test_wss_over_same_tls_port(ssl_context_and_cert):
|
||||
client_ctx.check_hostname = False
|
||||
client_ctx.verify_mode = ssl.CERT_NONE
|
||||
|
||||
async with aiohttp.ClientSession() as session, session.ws_connect(
|
||||
f"wss://127.0.0.1:{port}/ws",
|
||||
ssl=client_ctx,
|
||||
) as ws:
|
||||
async with (
|
||||
aiohttp.ClientSession() as session,
|
||||
session.ws_connect(
|
||||
f"wss://127.0.0.1:{port}/ws",
|
||||
ssl=client_ctx,
|
||||
) as ws,
|
||||
):
|
||||
msg = await ws.receive()
|
||||
assert msg.type in (
|
||||
aiohttp.WSMsgType.CLOSE,
|
||||
|
||||
@@ -357,9 +357,7 @@ def test_validate_identifier_rejects_pure_metacharacter_strings(name):
|
||||
)
|
||||
@settings(deadline=None, suppress_health_check=[HealthCheck.too_slow])
|
||||
def test_attach_path_escaping_never_breaks_sql(path_segment):
|
||||
"""The quote-doubling escaping produces a string that SQLite can parse
|
||||
without breaking out of the literal, regardless of the path content.
|
||||
"""
|
||||
"""Quote-doubled ATTACH paths stay inside the SQL string literal."""
|
||||
safe = path_segment.replace("'", "''")
|
||||
sql = f"ATTACH DATABASE '{safe}' AS test_alias"
|
||||
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,6 +1,4 @@
|
||||
"""Path traversal regression tests and property-based fuzzing for mesh PageNode
|
||||
page/file APIs and normalize_page_filename.
|
||||
"""
|
||||
"""Path traversal and fuzz tests for PageNode and ``normalize_page_filename``."""
|
||||
|
||||
import os
|
||||
import shutil
|
||||
@@ -177,7 +175,9 @@ class TestPageRespondersTraversal:
|
||||
assert result == b"ok"
|
||||
|
||||
def test_file_responder_ignores_path_prefix_in_request_path(
|
||||
self, node_dir, mock_rns,
|
||||
self,
|
||||
node_dir,
|
||||
mock_rns,
|
||||
):
|
||||
node = _make_node(node_dir, mock_rns)
|
||||
node.setup()
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
"""Regression tests for conversation/bell unread semantics: no false positives from
|
||||
outgoing-latest threads or inconsistent read cursors.
|
||||
"""
|
||||
"""Unread semantics for conversations and the bell (no false positives)."""
|
||||
|
||||
from datetime import UTC, datetime
|
||||
|
||||
|
||||
@@ -213,9 +213,7 @@ def test_notification_spike_fuzzing(db, num_notifs):
|
||||
|
||||
|
||||
class TestNotificationReliability:
|
||||
"""Comprehensive tests to verify notifications are accurate, reliable,
|
||||
and never produce false positives.
|
||||
"""
|
||||
"""Notification counts and read state stay consistent (no false positives)."""
|
||||
|
||||
def test_unread_count_matches_actual_unread(self, db):
|
||||
"""Unread count must exactly match unviewed notifications."""
|
||||
|
||||
@@ -642,14 +642,8 @@ class TestPerformanceHotPaths(unittest.TestCase):
|
||||
self.assertEqual(len(write_errors), 0, f"Write errors: {write_errors[:5]}")
|
||||
self.assertEqual(len(read_errors), 0, f"Read errors: {read_errors[:5]}")
|
||||
|
||||
# ===================================================================
|
||||
# LIKE SEARCH SCALING — how search degrades with data size
|
||||
# ===================================================================
|
||||
|
||||
def test_like_search_scaling(self):
|
||||
"""Measure how LIKE search scales across different table sizes.
|
||||
This catches missing FTS indexes or query plan regressions.
|
||||
"""
|
||||
"""Exercise LIKE search at scale (catches missing indexes / bad plans)."""
|
||||
print("\n[Scaling] LIKE search across data sizes:")
|
||||
|
||||
# Message search on the existing 10k dataset
|
||||
@@ -699,13 +693,16 @@ class TestPerformanceHotPaths(unittest.TestCase):
|
||||
durations = []
|
||||
for _ in range(5):
|
||||
_, ms = timed_call(
|
||||
self.db.messages.mark_all_notifications_as_viewed, hashes,
|
||||
self.db.messages.mark_all_notifications_as_viewed,
|
||||
hashes,
|
||||
)
|
||||
durations.append(ms)
|
||||
|
||||
stats = latency_report("mark_viewed_200", durations)
|
||||
self.assertLess(
|
||||
stats["p95"], 50, "mark_all_notifications_as_viewed(200) p95 > 50ms",
|
||||
stats["p95"],
|
||||
50,
|
||||
"mark_all_notifications_as_viewed(200) p95 > 50ms",
|
||||
)
|
||||
|
||||
def test_move_conversations_to_folder_batch(self):
|
||||
@@ -719,13 +716,17 @@ class TestPerformanceHotPaths(unittest.TestCase):
|
||||
durations = []
|
||||
for _ in range(5):
|
||||
_, ms = timed_call(
|
||||
self.db.messages.move_conversations_to_folder, hashes, folder_id,
|
||||
self.db.messages.move_conversations_to_folder,
|
||||
hashes,
|
||||
folder_id,
|
||||
)
|
||||
durations.append(ms)
|
||||
|
||||
stats = latency_report("move_folder_200", durations)
|
||||
self.assertLess(
|
||||
stats["p95"], 50, "move_conversations_to_folder(200) p95 > 50ms",
|
||||
stats["p95"],
|
||||
50,
|
||||
"move_conversations_to_folder(200) p95 > 50ms",
|
||||
)
|
||||
|
||||
# ===================================================================
|
||||
|
||||
@@ -68,11 +68,10 @@ async def test_websocket_broadcast_soak_iterations(mock_app):
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_websocket_broadcast_iterates_snapshot_not_live_list(mock_app):
|
||||
"""Mutating ``websocket_clients`` during iteration (e.g. another handler removes
|
||||
a connection) must not skip entries; iterate a snapshot, not the live list.
|
||||
"""Broadcast must iterate a snapshot of websocket clients, not the live list.
|
||||
|
||||
Without ``list(...)``, removing a later client while iterating can skip that client
|
||||
entirely (classic for-loop over mutating list).
|
||||
If another coroutine mutates ``websocket_clients`` during iteration, using
|
||||
``list(...)`` avoids skipping entries (classic mutating-list pitfall).
|
||||
"""
|
||||
mock_app.websocket_clients.clear()
|
||||
clients = [MagicWs() for _ in range(5)]
|
||||
|
||||
Reference in new issue
Block a user