refactor(codebase): format

This commit is contained in:
Ivan
2026-04-22 18:40:11 -05:00
parent c0312bf1a0
commit 6ff7a652be
86 changed files with 1121 additions and 396 deletions
+3 -1
View File
@@ -20,7 +20,9 @@ changelog_path = ROOT / "CHANGELOG.md"
if changelog_path.exists():
include_files.append((str(changelog_path), "CHANGELOG.md"))
frontend_licenses_path = ROOT / "meshchatx" / "src" / "backend" / "data" / "licenses_frontend.json"
frontend_licenses_path = (
ROOT / "meshchatx" / "src" / "backend" / "data" / "licenses_frontend.json"
)
if frontend_licenses_path.exists():
include_files.append((str(frontend_licenses_path), "licenses_frontend.json"))
+363 -127
View File
File diff suppressed because it is too large Load Diff
+4 -1
View File
@@ -71,7 +71,10 @@ class AsyncUtils:
)
with AsyncUtils._futures_lock:
AsyncUtils._pending_futures.append(future)
if len(AsyncUtils._pending_futures) >= AsyncUtils._FUTURES_SWEEP_THRESHOLD:
if (
len(AsyncUtils._pending_futures)
>= AsyncUtils._FUTURES_SWEEP_THRESHOLD
):
AsyncUtils._pending_futures = [
f for f in AsyncUtils._pending_futures if not f.done()
]
+9 -3
View File
@@ -79,9 +79,13 @@ def _decode_with_wave(data: bytes):
if sample_width == 2:
samples = np.frombuffer(raw, dtype=np.int16).astype(np.float32) / 32768.0
elif sample_width == 1:
samples = (np.frombuffer(raw, dtype=np.uint8).astype(np.float32) - 128.0) / 128.0
samples = (
np.frombuffer(raw, dtype=np.uint8).astype(np.float32) - 128.0
) / 128.0
elif sample_width == 4:
samples = np.frombuffer(raw, dtype=np.int32).astype(np.float32) / 2147483648.0
samples = (
np.frombuffer(raw, dtype=np.int32).astype(np.float32) / 2147483648.0
)
else:
return None
@@ -109,7 +113,9 @@ def _decode_with_miniaudio(data: bytes):
return None
if decoded.num_frames <= 0 or decoded.nchannels <= 0:
return None
samples = np.frombuffer(decoded.samples, dtype=np.int16).astype(np.float32) / 32768.0
samples = (
np.frombuffer(decoded.samples, dtype=np.int16).astype(np.float32) / 32768.0
)
return DecodedAudio(
samples=samples.reshape(-1, decoded.nchannels),
samplerate=decoded.sample_rate,
@@ -134,7 +134,9 @@ class AutoPropagationManager:
if not sorted_candidates:
return
previous_hex = self.config.lxmf_preferred_propagation_node_destination_hash.get()
previous_hex = (
self.config.lxmf_preferred_propagation_node_destination_hash.get()
)
ordered: list[tuple[int, str]] = []
seen_hex: set[str] = set()
if previous_hex and previous_hex in best_by_hex:
+11 -3
View File
@@ -147,10 +147,16 @@ class BotHandler:
# Try running instance first
instance = self.running_bots.get(bot_id, {}).get("instance")
if instance and getattr(instance, "bot", None) and getattr(instance.bot, "local", None):
if (
instance
and getattr(instance, "bot", None)
and getattr(instance.bot, "local", None)
):
with contextlib.suppress(Exception):
lh = instance.bot.local.hash
address_full = lh.hex() if isinstance(lh, (bytes, bytearray)) else None
address_full = (
lh.hex() if isinstance(lh, (bytes, bytearray)) else None
)
if address_full:
address_full = self._normalize_lxmf_hash_hex(address_full)
if address_full:
@@ -164,7 +170,9 @@ class BotHandler:
destination = RNS.Destination(identity, "lxmf", "delivery")
address_full = self._normalize_lxmf_hash_hex(destination.hash)
if address_full:
address_pretty = RNS.prettyhexrep(bytes.fromhex(address_full))
address_pretty = RNS.prettyhexrep(
bytes.fromhex(address_full)
)
if address_full is None:
address_full = self._read_lxmf_address_sidecar(entry.get("storage_dir"))
+3 -1
View File
@@ -60,7 +60,9 @@ def main():
else:
config_path = os.path.join(os.path.abspath(args.storage), "config")
os.makedirs(config_path, exist_ok=True)
reticulum_config_dir = os.path.abspath(os.path.expanduser(args.reticulum_config_dir))
reticulum_config_dir = os.path.abspath(
os.path.expanduser(args.reticulum_config_dir)
)
os.makedirs(reticulum_config_dir, exist_ok=True)
BotCls = TEMPLATE_MAP[args.template]
+11 -5
View File
@@ -162,17 +162,23 @@ class NoteBotTemplate(StoppableBot):
if not ctx.args:
response = "Your Notes:\n"
for i, note in enumerate(notes[-10:], 1):
tags = " ".join(f"#{tag}" for tag in note["tags"]) if note["tags"] else ""
tags = (
" ".join(f"#{tag}" for tag in note["tags"])
if note["tags"]
else ""
)
response += f"{i}. {note['text']} {tags}\n"
if len(notes) > 10:
response += (
f"\nShowing last 10 of {len(notes)} notes. Use /notes all to see all."
)
response += f"\nShowing last 10 of {len(notes)} notes. Use /notes all to see all."
ctx.reply(response)
elif ctx.args[0] == "all":
response = "All Your Notes:\n"
for i, note in enumerate(notes, 1):
tags = " ".join(f"#{tag}" for tag in note["tags"]) if note["tags"] else ""
tags = (
" ".join(f"#{tag}" for tag in note["tags"])
if note["tags"]
else ""
)
response += f"{i}. {note['text']} {tags}\n"
ctx.reply(response)
+6 -4
View File
@@ -66,10 +66,12 @@ class ConfigManager:
"lxmf_preferred_propagation_node_auto_select",
False,
)
self.lxmf_preferred_propagation_node_auto_sync_interval_seconds = self.IntConfig(
self,
"lxmf_preferred_propagation_node_auto_sync_interval_seconds",
0,
self.lxmf_preferred_propagation_node_auto_sync_interval_seconds = (
self.IntConfig(
self,
"lxmf_preferred_propagation_node_auto_sync_interval_seconds",
0,
)
)
self.lxmf_preferred_propagation_node_last_synced_at = self.IntConfig(
self,
+9 -3
View File
@@ -180,7 +180,9 @@ class Database:
_log.warning("DB open health check: no result")
else:
first = integrity_rows[0]
val = next(iter(first.values())) if isinstance(first, dict) else first[0]
val = (
next(iter(first.values())) if isinstance(first, dict) else first[0]
)
if val != "ok":
issues.append(f"Database integrity check failed: {val!s}")
_log.warning("DB open health check: %s", val)
@@ -227,7 +229,9 @@ class Database:
_log.warning("DB close health check: no result")
else:
first = integrity_rows[0]
val = next(iter(first.values())) if isinstance(first, dict) else first[0]
val = (
next(iter(first.values())) if isinstance(first, dict) else first[0]
)
if val != "ok":
issues.append(f"Database integrity check failed: {val!s}")
_log.warning("DB close health check: integrity failed")
@@ -265,7 +269,9 @@ class Database:
page_size = self._get_pragma_value("page_size", 0) or 0
page_count = self._get_pragma_value("page_count", 0) or 0
freelist_pages = self._get_pragma_value("freelist_count", 0) or 0
freelist_bytes = page_size * freelist_pages if page_size > 0 and freelist_pages > 0 else 0
freelist_bytes = (
page_size * freelist_pages if page_size > 0 and freelist_pages > 0 else 0
)
if freelist_bytes > 0:
free_bytes = freelist_bytes
else:
+4 -1
View File
@@ -219,7 +219,10 @@ class UserGifsDAO:
(identity_hash, ch),
)
if self.count_for_identity(identity_hash) >= gif_utils.MAX_GIFS_PER_IDENTITY:
if (
self.count_for_identity(identity_hash)
>= gif_utils.MAX_GIFS_PER_IDENTITY
):
errors.append("gif_limit_reached")
break
@@ -104,9 +104,7 @@ class LegacyMigrator:
try:
# Check if table exists in legacy DB
# We use a f-string here for the alias and table name, which are controlled by us
check_query = (
f"SELECT name FROM {alias}.sqlite_master WHERE type='table' AND name=?"
)
check_query = f"SELECT name FROM {alias}.sqlite_master WHERE type='table' AND name=?"
res = self.provider.fetchone(check_query, (table,))
if res:
+3 -1
View File
@@ -177,7 +177,9 @@ class MiscDAO:
params.append(destination_hash)
if query:
like_term = f"%{query}%"
sql += " AND (destination_hash LIKE ? OR page_path LIKE ? OR content LIKE ?)"
sql += (
" AND (destination_hash LIKE ? OR page_path LIKE ? OR content LIKE ?)"
)
params.extend([like_term, like_term, like_term])
sql += " ORDER BY created_at DESC"
+5 -2
View File
@@ -74,10 +74,13 @@ class DatabaseProvider:
if isinstance(params, dict):
params = {
k: (v.isoformat() if isinstance(v, datetime) else v) for k, v in params.items()
k: (v.isoformat() if isinstance(v, datetime) else v)
for k, v in params.items()
}
else:
params = tuple((p.isoformat() if isinstance(p, datetime) else p) for p in params)
params = tuple(
(p.isoformat() if isinstance(p, datetime) else p) for p in params
)
if params:
cursor.execute(query, params)
+3 -1
View File
@@ -29,7 +29,9 @@ class RingtoneDAO:
display_name = filename
# check if this is the first ringtone, if so make it primary
count = self.provider.fetchone("SELECT COUNT(*) as count FROM ringtones")["count"]
count = self.provider.fetchone("SELECT COUNT(*) as count FROM ringtones")[
"count"
]
is_primary = 1 if count == 0 else 0
cursor = self.provider.execute(
@@ -81,7 +81,10 @@ class UserStickerPacksDAO:
is_strict: bool = True,
) -> dict:
"""Create a new pack. Raises ``ValueError`` on quota or short_name clash."""
if self.count_for_identity(identity_hash) >= sticker_utils.MAX_STICKER_PACKS_PER_IDENTITY:
if (
self.count_for_identity(identity_hash)
>= sticker_utils.MAX_STICKER_PACKS_PER_IDENTITY
):
msg = "pack_limit_reached"
raise ValueError(msg)
sn = sticker_pack_utils.sanitize_pack_short_name(short_name)
@@ -150,7 +153,11 @@ class UserStickerPacksDAO:
if pack_type is not None
else existing["pack_type"]
)
new_cover = existing["cover_sticker_id"] if cover_sticker_id is ... else cover_sticker_id
new_cover = (
existing["cover_sticker_id"]
if cover_sticker_id is ...
else cover_sticker_id
)
cur = self.provider.execute(
"""
UPDATE user_sticker_packs
+8 -2
View File
@@ -187,7 +187,10 @@ class UserStickersDAO:
metadata so the picker can render the sticker correctly without
re-parsing.
"""
if self.count_for_identity(identity_hash) >= sticker_utils.MAX_STICKERS_PER_IDENTITY:
if (
self.count_for_identity(identity_hash)
>= sticker_utils.MAX_STICKERS_PER_IDENTITY
):
msg = "sticker_limit_reached"
raise ValueError(msg)
@@ -345,7 +348,10 @@ class UserStickersDAO:
(identity_hash, ch),
)
if self.count_for_identity(identity_hash) >= sticker_utils.MAX_STICKERS_PER_IDENTITY:
if (
self.count_for_identity(identity_hash)
>= sticker_utils.MAX_STICKERS_PER_IDENTITY
):
errors.append("sticker_limit_reached")
break
+3 -1
View File
@@ -264,7 +264,9 @@ class DocsManager:
def has_meshchatx_docs(self):
return (
any(f.endswith((".md", ".txt")) for f in os.listdir(self.meshchatx_docs_dir))
any(
f.endswith((".md", ".txt")) for f in os.listdir(self.meshchatx_docs_dir)
)
if os.path.exists(self.meshchatx_docs_dir)
else False
)
+4 -1
View File
@@ -144,7 +144,10 @@ class ForwardingManager:
for link in list(RNS.Transport.active_links):
match = False
if hasattr(link, "destination") and link.destination:
if hasattr(link.destination, "identity") and link.destination.identity:
if (
hasattr(link.destination, "identity")
and link.destination.identity
):
if link.destination.identity.hash == ih:
match = True
if match:
+34 -19
View File
@@ -158,7 +158,10 @@ class IdentityContext:
self.config = ConfigManager(self.database)
# Apply overrides from CLI/ENV if provided
if hasattr(self.app, "gitea_base_url_override") and self.app.gitea_base_url_override:
if (
hasattr(self.app, "gitea_base_url_override")
and self.app.gitea_base_url_override
):
self.config.gitea_base_url.set(self.app.gitea_base_url_override)
self.message_handler = MessageHandler(self.database)
@@ -235,7 +238,9 @@ class IdentityContext:
# Restore preferred propagation node on startup
with contextlib.suppress(Exception):
preferred_node = self.config.lxmf_preferred_propagation_node_destination_hash.get()
preferred_node = (
self.config.lxmf_preferred_propagation_node_destination_hash.get()
)
if preferred_node:
self.app.set_active_propagation_node(preferred_node, context=self)
@@ -284,7 +289,9 @@ class IdentityContext:
storage_dir=self.storage_path,
db=self.database,
)
self.telephone_manager.get_name_for_identity_hash = self.app.get_name_for_identity_hash
self.telephone_manager.get_name_for_identity_hash = (
self.app.get_name_for_identity_hash
)
self.telephone_manager.on_initiation_status_callback = lambda status, target: (
self.app.on_telephone_initiation_status(
status,
@@ -312,7 +319,9 @@ class IdentityContext:
telephone_manager=self.telephone_manager,
storage_dir=self.storage_path,
)
self.voicemail_manager.get_name_for_identity_hash = self.app.get_name_for_identity_hash
self.voicemail_manager.get_name_for_identity_hash = (
self.app.get_name_for_identity_hash
)
self.voicemail_manager.on_new_voicemail_callback = lambda vm: (
self.app.on_new_voicemail_received(vm, context=self)
)
@@ -356,7 +365,9 @@ class IdentityContext:
# start background thread for auto syncing propagation nodes
thread = threading.Thread(
target=asyncio.run,
args=(self.app.announce_sync_propagation_nodes(self.session_id, context=self),),
args=(
self.app.announce_sync_propagation_nodes(self.session_id, context=self),
),
)
thread.daemon = True
thread.start()
@@ -419,24 +430,28 @@ class IdentityContext:
),
AnnounceHandler(
"lxmf.propagation",
lambda aspect, dh, ai, ad, aph: self.app.on_lxmf_propagation_announce_received(
aspect,
dh,
ai,
ad,
aph,
context=self,
lambda aspect, dh, ai, ad, aph: (
self.app.on_lxmf_propagation_announce_received(
aspect,
dh,
ai,
ad,
aph,
context=self,
)
),
),
AnnounceHandler(
"nomadnetwork.node",
lambda aspect, dh, ai, ad, aph: self.app.on_nomadnet_node_announce_received(
aspect,
dh,
ai,
ad,
aph,
context=self,
lambda aspect, dh, ai, ad, aph: (
self.app.on_nomadnet_node_announce_received(
aspect,
dh,
ai,
ad,
aph,
context=self,
)
),
),
]
+2 -1
View File
@@ -148,7 +148,8 @@ class IdentityManager:
"lxmf_address": lxmf_address,
"lxst_address": lxst_address,
"is_current": (
current_identity_hash is not None and identity_hash == current_identity_hash
current_identity_hash is not None
and identity_hash == current_identity_hash
),
},
)
+11 -3
View File
@@ -47,7 +47,10 @@ class IntegrityManager:
# to avoid accidentally ignoring important files with similar names.
if "lxmf_router" in path_parts:
# Added more volatile LXMF patterns
if any(part in ["announces", "storage", "identities", "tmp"] for part in path_parts):
if any(
part in ["announces", "storage", "identities", "tmp"]
for part in path_parts
):
return True
# Specifically ignore stamp costs which are frequently updated
@@ -154,7 +157,10 @@ class IntegrityManager:
actual_entropy = self._calculate_entropy(self.database_path)
saved_entropy = manifest_metadata.get(db_rel, {}).get("entropy")
if saved_entropy is not None and abs(actual_entropy - saved_entropy) > 1.0:
if (
saved_entropy is not None
and abs(actual_entropy - saved_entropy) > 1.0
):
issues.append(
f"Database structural anomaly (Entropy Δ: {abs(actual_entropy - saved_entropy):.2f})",
)
@@ -190,7 +196,9 @@ class IntegrityManager:
)
actual_size = full_path.stat().st_size
is_critical = any(c in rel_path for c in ["identity", "config"])
is_critical = any(
c in rel_path for c in ["identity", "config"]
)
if is_critical:
issues.append(
@@ -18,7 +18,9 @@ class WebsocketServerInterface(Interface):
RESTART_DELAY_SECONDS = 5
def __str__(self):
return f"WebsocketServerInterface[{self.name}/{self.listen_ip}:{self.listen_port}]"
return (
f"WebsocketServerInterface[{self.name}/{self.listen_ip}:{self.listen_port}]"
)
def __init__(self, owner, configuration):
super().__init__()
+31 -8
View File
@@ -70,13 +70,17 @@ def is_user_facing_lxmf_payload(fields, content, title) -> bool:
return True
image = fields.get("image")
if isinstance(image, dict) and (image.get("image_size") or image.get("image_bytes")):
if isinstance(image, dict) and (
image.get("image_size") or image.get("image_bytes")
):
return True
if image is None and fields.get(LXMF_IMAGE_FIELD) is not None:
return True
audio = fields.get("audio")
if isinstance(audio, dict) and (audio.get("audio_size") or audio.get("audio_bytes")):
if isinstance(audio, dict) and (
audio.get("audio_size") or audio.get("audio_bytes")
):
return True
if audio is None and fields.get(LXMF_AUDIO_FIELD) is not None:
return True
@@ -110,7 +114,10 @@ def convert_lxmf_message_to_dict(
if field_type == LXMF.FIELD_FILE_ATTACHMENTS and isinstance(value, list):
file_attachments = []
for file_attachment in value:
if not isinstance(file_attachment, (list, tuple)) or len(file_attachment) < 2:
if (
not isinstance(file_attachment, (list, tuple))
or len(file_attachment) < 2
):
continue
file_name = file_attachment[0]
file_data = file_attachment[1]
@@ -133,7 +140,11 @@ def convert_lxmf_message_to_dict(
fields["file_attachments"] = file_attachments
# handle image field
if field_type == LXMF.FIELD_IMAGE and isinstance(value, (list, tuple)) and len(value) >= 2:
if (
field_type == LXMF.FIELD_IMAGE
and isinstance(value, (list, tuple))
and len(value) >= 2
):
image_type = value[0]
image_data = value[1]
if isinstance(image_data, (bytes, bytearray)):
@@ -148,7 +159,11 @@ def convert_lxmf_message_to_dict(
}
# handle audio field
if field_type == LXMF.FIELD_AUDIO and isinstance(value, (list, tuple)) and len(value) >= 2:
if (
field_type == LXMF.FIELD_AUDIO
and isinstance(value, (list, tuple))
and len(value) >= 2
):
audio_mode = value[0]
audio_data = value[1]
if isinstance(audio_data, (bytes, bytearray)):
@@ -196,7 +211,9 @@ def convert_lxmf_message_to_dict(
fields["reply_to"] = value.hex() if isinstance(value, bytes) else value
if field_type == 0x31:
fields["reply_quoted_content"] = (
value.decode("utf-8", errors="replace") if isinstance(value, bytes) else value
value.decode("utf-8", errors="replace")
if isinstance(value, bytes)
else value
)
if field_type == LXMF_APP_EXTENSIONS_FIELD and isinstance(value, dict):
@@ -231,7 +248,11 @@ def convert_lxmf_message_to_dict(
val = message_fields[0x30]
reply_to_hash = val.hex() if isinstance(val, bytes) else val
content = lxmf_message.content.decode("utf-8", errors="replace") if lxmf_message.content else ""
content = (
lxmf_message.content.decode("utf-8", errors="replace")
if lxmf_message.content
else ""
)
# auto-detect reply from content if not present
if not reply_to_hash and content and isinstance(content, str):
@@ -255,7 +276,9 @@ def convert_lxmf_message_to_dict(
"next_delivery_attempt",
None,
), # attribute may not exist yet
"title": lxmf_message.title.decode("utf-8", errors="replace") if lxmf_message.title else "",
"title": lxmf_message.title.decode("utf-8", errors="replace")
if lxmf_message.title
else "",
"content": content,
"fields": fields,
"timestamp": lxmf_message.timestamp,
+9 -3
View File
@@ -315,7 +315,9 @@ class MapManager:
)
return None
tasks = [asyncio.create_task(download_tile(tile)) for tile in tiles_to_download]
tasks = [
asyncio.create_task(download_tile(tile)) for tile in tiles_to_download
]
for coro in asyncio.as_completed(tasks):
if export_id in self._export_cancelled:
@@ -337,7 +339,9 @@ class MapManager:
(current_count / total_tiles) * 100,
)
if len(batch_data) >= batch_size or (current_count == total_tiles and batch_data):
if len(batch_data) >= batch_size or (
current_count == total_tiles and batch_data
):
try:
cursor.executemany(
"INSERT INTO tiles VALUES (?, ?, ?, ?)",
@@ -354,7 +358,9 @@ class MapManager:
n = 2.0**zoom
x = int((lon + 180.0) / 360.0 * n)
y = int(
(1.0 - math.log(math.tan(lat_rad) + (1 / math.cos(lat_rad))) / math.pi) / 2.0 * n,
(1.0 - math.log(math.tan(lat_rad) + (1 / math.cos(lat_rad))) / math.pi)
/ 2.0
* n,
)
return x, y
+3 -3
View File
@@ -136,9 +136,9 @@ def parse_lxmf_display_name(
# Try manual parsing first to avoid LXMF library call.
if len(app_data_bytes) > 0:
if (app_data_bytes[0] >= 0x90 and app_data_bytes[0] <= 0x9F) or app_data_bytes[
0
] == 0xDC:
if (
app_data_bytes[0] >= 0x90 and app_data_bytes[0] <= 0x9F
) or app_data_bytes[0] == 0xDC:
with contextlib.suppress(Exception):
peer_data = msgpack.unpackb(app_data_bytes)
if isinstance(peer_data, list) and len(peer_data) >= 1:
+13 -3
View File
@@ -36,7 +36,11 @@ def get_cached_active_link(destination_hash: bytes):
def sweep_stale_links():
"""Evict all non-ACTIVE links from the global cache."""
with _nomadnet_links_lock:
stale = [k for k, v in nomadnet_cached_links.items() if v.status is not RNS.Link.ACTIVE]
stale = [
k
for k, v in nomadnet_cached_links.items()
if v.status is not RNS.Link.ACTIVE
]
for k in stale:
del nomadnet_cached_links[k]
@@ -181,7 +185,9 @@ class NomadnetDownloader:
timeout_after_seconds = time.time() + link_establishment_timeout
while link.status is not RNS.Link.ACTIVE and time.time() < timeout_after_seconds:
while (
link.status is not RNS.Link.ACTIVE and time.time() < timeout_after_seconds
):
if self.is_cancelled:
return
await asyncio.sleep(_POLL_INTERVAL_S)
@@ -301,7 +307,11 @@ class NomadnetFileDownloader(NomadnetDownloader):
self.on_file_download_success(file_name, file_data)
return
if isinstance(response, list) and len(response) > 1 and isinstance(response[1], dict):
if (
isinstance(response, list)
and len(response) > 1
and isinstance(response[1], dict)
):
file_data: bytes = response[0]
metadata: dict = response[1]
+2 -1
View File
@@ -336,7 +336,8 @@ class PageNode:
return sorted(
f
for f in os.listdir(self.pages_dir)
if os.path.isfile(os.path.join(self.pages_dir, f)) and is_allowed_page_filename(f)
if os.path.isfile(os.path.join(self.pages_dir, f))
and is_allowed_page_filename(f)
)
def get_page_content(self, name):
@@ -64,7 +64,9 @@ class PersistentLogHandler(logging.Handler):
self._error_events.append(now_mono)
# Periodically flush to database if available
if self.database and (time.time() - self.last_flush_time > self.flush_interval):
if self.database and (
time.time() - self.last_flush_time > self.flush_interval
):
self._flush_to_db()
except Exception:
@@ -226,7 +228,9 @@ class PersistentLogHandler(logging.Handler):
if level:
logs = [log for log in logs if log["level"] == level]
if is_anomaly is not None:
logs = [log for log in logs if log["is_anomaly"] == (1 if is_anomaly else 0)]
logs = [
log for log in logs if log["is_anomaly"] == (1 if is_anomaly else 0)
]
# Sort descending
logs.sort(key=lambda x: x["timestamp"], reverse=True)
@@ -383,7 +383,9 @@ class CrashRecovery:
"no_table_config": "no such table: config" in error_msg,
"in_memory_db": diagnosis.get("db_type") == "memory",
"corrupt_in_msg": "corrupt" in error_msg or "malformed" in error_msg,
"async_in_msg": any(x in error_msg for x in ["asyncio", "event loop", "runtimeerror"]),
"async_in_msg": any(
x in error_msg for x in ["asyncio", "event loop", "runtimeerror"]
),
"no_loop_in_msg": "no current event loop" in error_msg
or "no running event loop" in error_msg,
"low_mem": diagnosis.get("low_memory", False),
@@ -392,7 +394,8 @@ class CrashRecovery:
"lxmf_in_msg": "lxmf" in error_msg or "lxmr" in error_msg,
"identity_in_msg": "identity" in error_msg or "private key" in error_msg,
"no_interfaces": diagnosis.get("active_interfaces", 0) == 0,
"old_python": py_version.major < 3 or (py_version.major == 3 and py_version.minor < 10),
"old_python": py_version.major < 3
or (py_version.major == 3 and py_version.minor < 10),
"legacy_kernel": "linux" in platform.system().lower()
and (
(_m := re.search(r"(\d+\.\d+)", platform.release())) is not None
+9 -3
View File
@@ -178,7 +178,9 @@ class RNCPHandler:
if transfer_id in self.active_transfers:
self.active_transfers[transfer_id]["status"] = "completed"
self.active_transfers[transfer_id]["saved_path"] = saved_filename
self.active_transfers[transfer_id]["saved_path"] = (
saved_filename
)
self.active_transfers[transfer_id]["filename"] = filename
self._emit_receive_event(
{
@@ -277,7 +279,9 @@ class RNCPHandler:
RNS.Transport.request_path(destination_hash)
timeout_after = time.time() + timeout
while not RNS.Transport.has_path(destination_hash) and time.time() < timeout_after:
while (
not RNS.Transport.has_path(destination_hash) and time.time() < timeout_after
):
await asyncio.sleep(0.1)
if not RNS.Transport.has_path(destination_hash):
@@ -369,7 +373,9 @@ class RNCPHandler:
RNS.Transport.request_path(destination_hash)
timeout_after = time.time() + timeout
while not RNS.Transport.has_path(destination_hash) and time.time() < timeout_after:
while (
not RNS.Transport.has_path(destination_hash) and time.time() < timeout_after
):
await asyncio.sleep(0.1)
if not RNS.Transport.has_path(destination_hash):
+10 -2
View File
@@ -71,10 +71,18 @@ class RNPathHandler:
total = len(formatted_table)
responsive_count = len(
[e for e in formatted_table if e["state"] == RNS.Transport.STATE_RESPONSIVE],
[
e
for e in formatted_table
if e["state"] == RNS.Transport.STATE_RESPONSIVE
],
)
unresponsive_count = len(
[e for e in formatted_table if e["state"] == RNS.Transport.STATE_UNRESPONSIVE],
[
e
for e in formatted_table
if e["state"] == RNS.Transport.STATE_UNRESPONSIVE
],
)
# Pagination
@@ -51,7 +51,11 @@ class RNPathTraceHandler:
local_hash = "unknown"
if self.identity and hasattr(self.identity, "hash"):
local_hash = self.identity.hash.hex()
elif self.reticulum and hasattr(self.reticulum, "identity") and self.reticulum.identity:
elif (
self.reticulum
and hasattr(self.reticulum, "identity")
and self.reticulum.identity
):
local_hash = self.reticulum.identity.hash.hex()
path.append({"type": "local", "hash": local_hash, "name": "Local Node"})
+11 -4
View File
@@ -34,9 +34,13 @@ class RNProbeHandler:
RNS.Transport.request_path(destination_hash)
timeout_after = time.time() + (
timeout or self.DEFAULT_TIMEOUT + self.reticulum.get_first_hop_timeout(destination_hash)
timeout
or self.DEFAULT_TIMEOUT
+ self.reticulum.get_first_hop_timeout(destination_hash)
)
while not RNS.Transport.has_path(destination_hash) and time.time() < timeout_after:
while (
not RNS.Transport.has_path(destination_hash) and time.time() < timeout_after
):
await asyncio.sleep(0.1)
if not RNS.Transport.has_path(destination_hash):
@@ -79,9 +83,12 @@ class RNProbeHandler:
timeout_after = time.time() + (
timeout
or self.DEFAULT_TIMEOUT + self.reticulum.get_first_hop_timeout(destination_hash)
or self.DEFAULT_TIMEOUT
+ self.reticulum.get_first_hop_timeout(destination_hash)
)
while receipt.status == RNS.PacketReceipt.SENT and time.time() < timeout_after:
while (
receipt.status == RNS.PacketReceipt.SENT and time.time() < timeout_after
):
await asyncio.sleep(0.1)
result: dict = {
+5 -1
View File
@@ -196,7 +196,11 @@ class RNStatusHandler:
name = ifstat.get("name", "")
if name.startswith(
("LocalInterface[", "TCPInterface[Client", "BackboneInterface[Client on")
(
"LocalInterface[",
"TCPInterface[Client",
"BackboneInterface[Client on",
)
):
continue
+6 -2
View File
@@ -124,7 +124,9 @@ def validate_pack_document(data: object) -> dict:
out_stickers.append(
{
"name": item.get("name") if isinstance(item.get("name"), str) else None,
"emoji": item.get("emoji") if isinstance(item.get("emoji"), str) else None,
"emoji": item.get("emoji")
if isinstance(item.get("emoji"), str)
else None,
"image_type": item.get("image_type"),
"image_bytes_b64": b64.strip(),
},
@@ -135,7 +137,9 @@ def validate_pack_document(data: object) -> dict:
"short_name": sanitize_pack_short_name(pack_meta.get("short_name")),
"description": sanitize_pack_description(pack_meta.get("description")),
"pack_type": sanitize_pack_type(pack_meta.get("type")),
"author": pack_meta.get("author") if isinstance(pack_meta.get("author"), str) else None,
"author": pack_meta.get("author")
if isinstance(pack_meta.get("author"), str)
else None,
"is_strict": bool(pack_meta.get("is_strict", True)),
},
"stickers": out_stickers,
+12 -4
View File
@@ -270,7 +270,9 @@ def parse_tgs(data: bytes) -> dict:
}
def _ebml_read_vint(buf: bytes, pos: int, *, mask_marker: bool = True) -> tuple[int, int] | None:
def _ebml_read_vint(
buf: bytes, pos: int, *, mask_marker: bool = True
) -> tuple[int, int] | None:
"""Read an EBML variable-length integer at ``pos``; returns ``(value, next_pos)``."""
if pos >= len(buf):
return None
@@ -362,11 +364,15 @@ def parse_webm(data: bytes) -> dict:
if f_id == 0x83:
track_type = _ebml_read_uint(raw, fb, fe)
elif f_id == 0x86:
track_codec = raw[fb:fe].decode("ascii", errors="replace")
track_codec = raw[fb:fe].decode(
"ascii", errors="replace"
)
elif f_id == 0x23E383:
track_def_dur = _ebml_read_uint(raw, fb, fe)
elif f_id == 0xE0:
for v_id, vb, ve in _ebml_iter_elements(raw, fb, fe):
for v_id, vb, ve in _ebml_iter_elements(
raw, fb, fe
):
if v_id == 0xB0:
t_w = _ebml_read_uint(raw, vb, ve)
elif v_id == 0xBA:
@@ -382,7 +388,9 @@ def parse_webm(data: bytes) -> dict:
elif seg_id == 0x1549A966:
for inf_id, ib, ie in _ebml_iter_elements(raw, sb, se):
if inf_id == 0x2AD7B1:
timecode_scale = _ebml_read_uint(raw, ib, ie) or timecode_scale
timecode_scale = (
_ebml_read_uint(raw, ib, ie) or timecode_scale
)
elif inf_id == 0x4489:
d = _ebml_read_float(raw, ib, ie)
if d is not None:
+5 -2
View File
@@ -60,7 +60,9 @@ class TelephoneManager:
self.storage_dir = storage_dir
self.db = db
self.get_name_for_identity_hash = None
self.recordings_dir = os.path.join(storage_dir, "recordings") if storage_dir else None
self.recordings_dir = (
os.path.join(storage_dir, "recordings") if storage_dir else None
)
if self.recordings_dir:
os.makedirs(self.recordings_dir, exist_ok=True)
@@ -443,7 +445,8 @@ class TelephoneManager:
# to ensure the UI has something to show (either active_call or initiation_status)
for _ in range(40): # Max 4 seconds of defensive waiting
if self.telephone and (
self.telephone.active_call or self.telephone.call_status in [0, 1, 3, 6]
self.telephone.active_call
or self.telephone.call_status in [0, 1, 3, 6]
):
break
await asyncio.sleep(self._status_poll_interval_s)
+6 -4
View File
@@ -261,9 +261,7 @@ class TranslatorHandler:
if detected_lang:
source_lang = detected_lang
else:
msg = (
"Could not auto-detect language. Please select a source language manually."
)
msg = "Could not auto-detect language. Please select a source language manually."
raise ValueError(msg)
else:
msg = (
@@ -363,7 +361,11 @@ class TranslatorHandler:
"source": "argos",
}
except subprocess.CalledProcessError as e:
error_msg = e.stderr.decode() if isinstance(e.stderr, bytes) else (e.stderr or str(e))
error_msg = (
e.stderr.decode()
if isinstance(e.stderr, bytes)
else (e.stderr or str(e))
)
msg = f"Argos Translate CLI error: {error_msg}"
raise RuntimeError(msg) from e
except Exception as e:
+3 -1
View File
@@ -48,7 +48,9 @@ class WebAudioSource(LocalSource):
def push_pcm(self, pcm_bytes: bytes):
try:
samples = np.frombuffer(pcm_bytes, dtype=np.int16).astype(np.float32) / 32768.0
samples = (
np.frombuffer(pcm_bytes, dtype=np.int16).astype(np.float32) / 32768.0
)
if samples.size == 0:
return
samples = samples.reshape(-1, 1)
+15 -5
View File
@@ -93,13 +93,17 @@ def ensure_package_installed(from_code, to_code):
if pkg_to_install:
print_info(f"Downloading package: {pkg_to_install}")
argostranslate.package.install_from_path(pkg_to_install.download())
print_success(f"Successfully installed package: {from_code} -> {to_code}")
print_success(
f"Successfully installed package: {from_code} -> {to_code}"
)
# Refresh installed languages
installed = argostranslate.translate.get_installed_languages()
installed_dict = {lang.code: lang for lang in installed}
else:
print_error(f"Could not find a translation package for {from_code} -> {to_code}")
print_error(
f"Could not find a translation package for {from_code} -> {to_code}"
)
sys.exit(1)
except Exception as e:
print_error(f"Failed to install language package: {e}")
@@ -174,7 +178,9 @@ def translate_dict(data, translate_func, target_name=None):
translated_temp = translate_func(temp_text)
return restore_vars_from_tokens(translated_temp, vars_found)
except Exception as e:
print_warning(f"Failed to translate '{data}': {e}. Falling back to original.")
print_warning(
f"Failed to translate '{data}': {e}. Falling back to original."
)
return data
else:
return data
@@ -184,7 +190,9 @@ def main():
parser = argparse.ArgumentParser(
description="Translate JSON localization files using Argos Translate."
)
parser.add_argument("--from", dest="from_lang", help="Source language code (e.g. 'en')")
parser.add_argument(
"--from", dest="from_lang", help="Source language code (e.g. 'en')"
)
parser.add_argument("--to", dest="to_lang", help="Target language code (e.g. 'zh')")
parser.add_argument("--input", dest="input_file", help="Path to input JSON file")
parser.add_argument("--output", dest="output_file", help="Path to output JSON file")
@@ -232,7 +240,9 @@ def main():
# Get Translator
translate_func = get_translation_func(from_lang, to_lang)
print_info("Starting translation. This may take a moment depending on the file size...")
print_info(
"Starting translation. This may take a moment depending on the file size..."
)
translated_data = translate_dict(source_data, translate_func, target_name)
# Ensure output directory exists
+3 -1
View File
@@ -240,7 +240,9 @@ def main(argv: list[str] | None = None) -> int:
)
if _is_truthy(os.environ.get("MESHCHATX_SKIP_DOCS_FETCH")):
logging.info("MESHCHATX_SKIP_DOCS_FETCH is set; skipping Reticulum manual fetch.")
logging.info(
"MESHCHATX_SKIP_DOCS_FETCH is set; skipping Reticulum manual fetch."
)
return 0
sources: list[str] = []
+6 -2
View File
@@ -14,7 +14,9 @@ def _source_uri() -> str:
server = (
os.environ.get("GITHUB_SERVER_URL") or os.environ.get("GITEA_SERVER_URL") or ""
).rstrip("/")
repo = os.environ.get("GITHUB_REPOSITORY") or os.environ.get("GITEA_REPOSITORY") or ""
repo = (
os.environ.get("GITHUB_REPOSITORY") or os.environ.get("GITEA_REPOSITORY") or ""
)
if not server or not repo:
return ""
if server.startswith(("https://", "http://")):
@@ -29,7 +31,9 @@ def _build_type() -> str:
server = (
os.environ.get("GITHUB_SERVER_URL") or os.environ.get("GITEA_SERVER_URL") or ""
).rstrip("/")
repo = os.environ.get("GITHUB_REPOSITORY") or os.environ.get("GITEA_REPOSITORY") or ""
repo = (
os.environ.get("GITHUB_REPOSITORY") or os.environ.get("GITEA_REPOSITORY") or ""
)
if server and repo:
return f"{server}/{repo}/.gitea/workflows/build.yml"
return "https://slsa.dev/provenance/v1"
+1 -3
View File
@@ -21,9 +21,7 @@ class BenchmarkResult:
self.memory_delta_mb = memory_delta_mb
def __repr__(self):
return (
f"<BenchmarkResult {self.name}: {self.duration_ms:.2f}ms, {self.memory_delta_mb:.2f}MB>"
)
return f"<BenchmarkResult {self.name}: {self.duration_ms:.2f}ms, {self.memory_delta_mb:.2f}MB>"
def benchmark(name=None, iterations=1):
+9 -3
View File
@@ -38,7 +38,9 @@ def _build_wav_pcm16(
wf.setframerate(samplerate)
frames = bytearray()
for i in range(n_samples):
sample = int(0.3 * 32767 * math.sin(2 * math.pi * frequency * (i / samplerate)))
sample = int(
0.3 * 32767 * math.sin(2 * math.pi * frequency * (i / samplerate))
)
for _ in range(channels):
frames.extend(struct.pack("<h", sample))
wf.writeframes(bytes(frames))
@@ -192,7 +194,9 @@ def test_encode_pcm_to_ogg_opus_preserves_duration(duration_seconds):
sr = 48000
n = int(sr * duration_seconds)
t = np.arange(n, dtype=np.float32) / sr
samples = (0.3 * np.sin(2 * math.pi * 440.0 * t)).astype(np.float32).reshape(-1, 1)
samples = (
(0.3 * np.sin(2 * math.pi * 440.0 * t)).astype(np.float32).reshape(-1, 1)
)
audio_codec.encode_pcm_to_ogg_opus(samples, sr, 1, out)
encoded = _ogg_opus_duration_seconds(out)
assert abs(encoded - duration_seconds) < 0.001, (
@@ -215,7 +219,9 @@ def test_encode_pcm_to_ogg_opus_audio_profile_keeps_stereo():
t = np.arange(n, dtype=np.float32) / sr
samples[:, 0] = 0.3 * np.sin(2 * math.pi * 440.0 * t)
samples[:, 1] = 0.3 * np.sin(2 * math.pi * 660.0 * t)
audio_codec.encode_pcm_to_ogg_opus(samples, sr, 2, out, profile=Opus.PROFILE_AUDIO_MAX)
audio_codec.encode_pcm_to_ogg_opus(
samples, sr, 2, out, profile=Opus.PROFILE_AUDIO_MAX
)
with open(out, "rb") as f:
data = f.read()
head = data.find(b"OpusHead")
+15 -5
View File
@@ -56,7 +56,9 @@ async def test_auto_propagation_logic():
patch.object(manager, "_wait_for_path", return_value=True),
patch.object(manager, "_probe_propagation_sync", return_value=True),
):
mock_hops.side_effect = lambda dh: 1 if dh == bytes.fromhex(_VALID_HASH_A) else 3
mock_hops.side_effect = lambda dh: (
1 if dh == bytes.fromhex(_VALID_HASH_A) else 3
)
await manager.check_and_update_propagation_node()
@@ -68,7 +70,9 @@ async def test_auto_propagation_logic():
_VALID_HASH_A,
)
config.lxmf_preferred_propagation_node_destination_hash.get.return_value = _VALID_HASH_B
config.lxmf_preferred_propagation_node_destination_hash.get.return_value = (
_VALID_HASH_B
)
app.set_active_propagation_node.reset_mock()
with (
@@ -77,7 +81,9 @@ async def test_auto_propagation_logic():
patch.object(manager, "_wait_for_path", return_value=True),
patch.object(manager, "_probe_propagation_sync", side_effect=[False, True]),
):
mock_hops.side_effect = lambda dh: 1 if dh == bytes.fromhex(_VALID_HASH_A) else 3
mock_hops.side_effect = lambda dh: (
1 if dh == bytes.fromhex(_VALID_HASH_A) else 3
)
await manager.check_and_update_propagation_node()
@@ -86,7 +92,9 @@ async def test_auto_propagation_logic():
context=context,
)
config.lxmf_preferred_propagation_node_destination_hash.get.return_value = _VALID_HASH_C
config.lxmf_preferred_propagation_node_destination_hash.get.return_value = (
_VALID_HASH_C
)
announce3 = {
"destination_hash": _VALID_HASH_C,
"app_data": _APP_DATA_ENABLED,
@@ -100,7 +108,9 @@ async def test_auto_propagation_logic():
patch.object(manager, "_wait_for_path", return_value=True),
patch.object(manager, "_probe_propagation_sync", return_value=True),
):
mock_hops.side_effect = lambda dh: 1 if dh == bytes.fromhex(_VALID_HASH_A) else 2
mock_hops.side_effect = lambda dh: (
1 if dh == bytes.fromhex(_VALID_HASH_A) else 2
)
await manager.check_and_update_propagation_node()
+6 -2
View File
@@ -103,7 +103,9 @@ async def test_auto_propagation_api(mock_rns_minimal, temp_dir):
response = await patch_handler(mock_request)
data = json.loads(response.body)
assert data["config"]["lxmf_preferred_propagation_node_auto_select"] is False
assert app_instance.config.lxmf_preferred_propagation_node_auto_select.get() is False
assert (
app_instance.config.lxmf_preferred_propagation_node_auto_select.get() is False
)
# Update transfer/sync limits and validate clamping/application
mock_request = MagicMock()
@@ -131,5 +133,7 @@ async def test_auto_propagation_api(mock_rns_minimal, temp_dir):
response = await patch_handler(mock_request)
data = json.loads(response.body)
assert data["config"]["lxmf_delivery_transfer_limit_in_bytes"] == 1_000_000_000
assert app_instance.config.lxmf_delivery_transfer_limit_in_bytes.get() == 1_000_000_000
assert (
app_instance.config.lxmf_delivery_transfer_limit_in_bytes.get() == 1_000_000_000
)
assert app_instance.message_router.delivery_per_transfer_limit == 1_000_000
+9 -3
View File
@@ -111,7 +111,9 @@ def test_get_status_reads_sidecar_lxmf_address(temp_identity_dir):
storage = os.path.join(handler.bots_dir, sid)
os.makedirs(storage, exist_ok=True)
hx = "a" * 32
with open(os.path.join(storage, "meshchatx_lxmf_address.txt"), "w", encoding="utf-8") as f:
with open(
os.path.join(storage, "meshchatx_lxmf_address.txt"), "w", encoding="utf-8"
) as f:
f.write(hx)
handler.bots_state = [{"id": sid, "template_id": "echo", "storage_dir": storage}]
status = handler.get_status()
@@ -176,7 +178,9 @@ def test_request_announce_writes_trigger(mock_alive, temp_identity_dir):
sid = "b1"
storage = os.path.join(handler.bots_dir, sid)
os.makedirs(storage, exist_ok=True)
handler.bots_state = [{"id": sid, "template_id": "echo", "storage_dir": storage, "pid": 99999}]
handler.bots_state = [
{"id": sid, "template_id": "echo", "storage_dir": storage, "pid": 99999}
]
handler.request_announce(sid)
req = os.path.join(storage, "meshchatx_request_announce")
assert os.path.isfile(req)
@@ -189,6 +193,8 @@ def test_request_announce_not_running(temp_identity_dir):
sid = "b1"
storage = os.path.join(handler.bots_dir, sid)
os.makedirs(storage, exist_ok=True)
handler.bots_state = [{"id": sid, "template_id": "echo", "storage_dir": storage, "pid": None}]
handler.bots_state = [
{"id": sid, "template_id": "echo", "storage_dir": storage, "pid": None}
]
with pytest.raises(RuntimeError, match="not running"):
handler.request_announce(sid)
+6 -2
View File
@@ -91,8 +91,12 @@ class TestConcurrencyStress(unittest.TestCase):
def test_database_concurrency(self):
"""Launches multiple reader and writer threads to check for lock contention."""
writers = [threading.Thread(target=self.db_writer_worker, args=(i,)) for i in range(5)]
readers = [threading.Thread(target=self.db_reader_worker, args=(i,)) for i in range(5)]
writers = [
threading.Thread(target=self.db_writer_worker, args=(i,)) for i in range(5)
]
readers = [
threading.Thread(target=self.db_reader_worker, args=(i,)) for i in range(5)
]
for t in writers + readers:
t.start()
@@ -176,7 +176,9 @@ class TestCustomDisplayNameLifecycle:
def test_unicode_display_name(self, announce_dao):
announce_dao.upsert_custom_display_name("dest1", "\u5c71\u7530\u592a\u90ce")
assert announce_dao.get_custom_display_name("dest1") == "\u5c71\u7530\u592a\u90ce"
assert (
announce_dao.get_custom_display_name("dest1") == "\u5c71\u7530\u592a\u90ce"
)
def test_very_long_display_name(self, announce_dao):
long_name = "A" * 10000
+20 -5
View File
@@ -48,7 +48,10 @@ async def test_contacts_export_empty(mock_rns_minimal, temp_dir):
)
handler = None
for route in app.get_routes():
if route.path == "/api/v1/telephone/contacts/export" and route.method == "GET":
if (
route.path == "/api/v1/telephone/contacts/export"
and route.method == "GET"
):
handler = route.handler
break
assert handler is not None
@@ -73,7 +76,10 @@ async def test_contacts_export_with_data(mock_rns_minimal, temp_dir):
handler = None
for route in app.get_routes():
if route.path == "/api/v1/telephone/contacts/export" and route.method == "GET":
if (
route.path == "/api/v1/telephone/contacts/export"
and route.method == "GET"
):
handler = route.handler
break
assert handler is not None
@@ -101,7 +107,10 @@ async def test_contacts_import_valid(mock_rns_minimal, temp_dir):
)
handler = None
for route in app.get_routes():
if route.path == "/api/v1/telephone/contacts/import" and route.method == "POST":
if (
route.path == "/api/v1/telephone/contacts/import"
and route.method == "POST"
):
handler = route.handler
break
assert handler is not None
@@ -138,7 +147,10 @@ async def test_contacts_import_skips_invalid(mock_rns_minimal, temp_dir):
)
handler = None
for route in app.get_routes():
if route.path == "/api/v1/telephone/contacts/import" and route.method == "POST":
if (
route.path == "/api/v1/telephone/contacts/import"
and route.method == "POST"
):
handler = route.handler
break
assert handler is not None
@@ -169,7 +181,10 @@ async def test_contacts_import_rejects_non_array(mock_rns_minimal, temp_dir):
)
handler = None
for route in app.get_routes():
if route.path == "/api/v1/telephone/contacts/import" and route.method == "POST":
if (
route.path == "/api/v1/telephone/contacts/import"
and route.method == "POST"
):
handler = route.handler
break
assert handler is not None
+2 -1
View File
@@ -113,6 +113,7 @@ async def test_config_update_csp(mock_rns_minimal, tmp_path):
assert response.status == 200
assert (
app_instance.config.csp_extra_connect_src.get() == "https://api1.com, https://api2.com"
app_instance.config.csp_extra_connect_src.get()
== "https://api1.com, https://api2.com"
)
assert app_instance.config.csp_extra_img_src.get() == "https://img.com"
+3 -1
View File
@@ -662,7 +662,9 @@ class TestSafeHrefFuzzing:
url = f"{scheme}:something"
result = _safe_href(url)
lower = url.strip().lower()
if any(lower.startswith(p) for p in ("https://", "http://", "/", "#", "mailto:")):
if any(
lower.startswith(p) for p in ("https://", "http://", "/", "#", "mailto:")
):
assert result == url
else:
assert result == "#"
+18 -5
View File
@@ -139,7 +139,9 @@ def test_backup_suspicious_when_messages_gone_skips_cleanup_and_baseline(temp_di
assert "baseline" in result2
assert result2["baseline"]["message_count"] == 1
assert result2["current_stats"]["message_count"] == 0
zip_count_after_suspicious = sum(1 for f in os.listdir(backup_dir) if f.endswith(".zip"))
zip_count_after_suspicious = sum(
1 for f in os.listdir(backup_dir) if f.endswith(".zip")
)
assert zip_count_after_suspicious == 2
assert any("SUSPICIOUS" in f for f in os.listdir(backup_dir) if f.endswith(".zip"))
with open(os.path.join(backup_dir, "backup-baseline.json")) as f:
@@ -302,8 +304,13 @@ def test_is_backup_suspicious_does_not_mistrigger_empty_baseline():
db = Database(":memory:")
db.initialize()
assert db._is_backup_suspicious({"message_count": 0, "total_bytes": 0}, None) is False
assert db._is_backup_suspicious({"message_count": 10, "total_bytes": 1000}, None) is False
assert (
db._is_backup_suspicious({"message_count": 0, "total_bytes": 0}, None) is False
)
assert (
db._is_backup_suspicious({"message_count": 10, "total_bytes": 1000}, None)
is False
)
def test_is_backup_suspicious_does_not_mistrigger_legitimate_empty():
@@ -312,7 +319,10 @@ def test_is_backup_suspicious_does_not_mistrigger_legitimate_empty():
db = Database(":memory:")
db.initialize()
baseline = {"message_count": 0, "total_bytes": 5000}
assert db._is_backup_suspicious({"message_count": 0, "total_bytes": 5000}, baseline) is False
assert (
db._is_backup_suspicious({"message_count": 0, "total_bytes": 5000}, baseline)
is False
)
def test_is_backup_suspicious_does_not_mistrigger_small_db():
@@ -321,4 +331,7 @@ def test_is_backup_suspicious_does_not_mistrigger_small_db():
db = Database(":memory:")
db.initialize()
baseline = {"message_count": 5, "total_bytes": 50_000}
assert db._is_backup_suspicious({"message_count": 5, "total_bytes": 55_000}, baseline) is False
assert (
db._is_backup_suspicious({"message_count": 5, "total_bytes": 55_000}, baseline)
is False
)
+4 -1
View File
@@ -123,7 +123,10 @@ def test_docs_manager_readonly_public_dir_handling(tmp_path):
with patch("os.makedirs", side_effect=OSError("Read-only file system")):
dm = DocsManager(config, str(public_dir))
assert dm.last_error is not None
assert "Read-only file system" in dm.last_error or "Permission denied" in dm.last_error
assert (
"Read-only file system" in dm.last_error
or "Permission denied" in dm.last_error
)
os.chmod(public_dir, 0o755) # noqa: S103
+12 -6
View File
@@ -283,10 +283,10 @@ def mock_app(temp_dir):
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.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_auto_sync_interval_seconds.get.return_value = (
3600
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_last_synced_at.get.return_value = 0
app.config.lxmf_user_icon_name.get.return_value = "user"
app.config.lxmf_user_icon_foreground_colour.get.return_value = "#ffffff"
@@ -297,10 +297,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_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_age_seconds.get.return_value = 86400
app.config.lxmf_auto_sync_propagation_nodes_max_size_bytes.get.return_value = 1000000
app.config.lxmf_auto_sync_propagation_nodes_max_age_seconds.get.return_value = (
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_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_size_bytes_per_node.get.return_value = 1000000
app.config.lxmf_auto_sync_propagation_nodes_max_total_count_per_node.get.return_value = 100
+3 -1
View File
@@ -59,7 +59,9 @@ def mock_rns():
# Apply patches
mocks = {}
for p in patches:
attr_name = p.attribute if hasattr(p, "attribute") else p.target.split(".")[-1]
attr_name = (
p.attribute if hasattr(p, "attribute") else p.target.split(".")[-1]
)
mocks[attr_name] = stack.enter_context(p)
# Mock class methods on MockIdentityClass
+2 -1
View File
@@ -87,7 +87,8 @@ class TestIntegrityManager(unittest.TestCase):
self.assertFalse(is_ok)
self.assertTrue(
any(
"Critical security component" in i or "File signature mismatch" in i for i in issues
"Critical security component" in i or "File signature mismatch" in i
for i in issues
),
)
+16 -5
View File
@@ -100,7 +100,9 @@ async def test_reticulum_discovery_get_and_patch(temp_dir):
assert get_data["discovery"]["discover_interfaces"] == "true"
assert get_data["discovery"]["interface_discovery_sources"] == "abc,def"
assert get_data["discovery"]["interface_discovery_whitelist"] == "tcp-*,10.0.*"
assert get_data["discovery"]["interface_discovery_blacklist"] == "tcp-bad,*:9999"
assert (
get_data["discovery"]["interface_discovery_blacklist"] == "tcp-bad,*:9999"
)
assert get_data["discovery"]["required_discovery_value"] == "16"
assert get_data["discovery"]["autoconnect_discovered_interfaces"] == "2"
assert get_data["discovery"]["network_identity"] == "/tmp/net_id"
@@ -125,7 +127,10 @@ async def test_reticulum_discovery_get_and_patch(temp_dir):
patch_data = json.loads(patch_response.body)
assert patch_data["discovery"]["discover_interfaces"] is False
assert patch_data["discovery"]["interface_discovery_sources"] is None
assert patch_data["discovery"]["interface_discovery_whitelist"] == "peer-*,172.16.*"
assert (
patch_data["discovery"]["interface_discovery_whitelist"]
== "peer-*,172.16.*"
)
assert patch_data["discovery"]["interface_discovery_blacklist"] is None
assert patch_data["discovery"]["required_discovery_value"] == 18
assert patch_data["discovery"]["autoconnect_discovered_interfaces"] == 5
@@ -258,9 +263,15 @@ async def test_discovery_patch_sanitizes_whitelist_blacklist_values(temp_dir):
data = json.loads(response.body)
assert data["discovery"]["interface_discovery_whitelist"] == "peer-1,host:4242"
assert data["discovery"]["interface_discovery_blacklist"] == "bad-node,evilentry"
assert config["reticulum"]["interface_discovery_whitelist"] == "peer-1,host:4242"
assert config["reticulum"]["interface_discovery_blacklist"] == "bad-node,evilentry"
assert (
data["discovery"]["interface_discovery_blacklist"] == "bad-node,evilentry"
)
assert (
config["reticulum"]["interface_discovery_whitelist"] == "peer-1,host:4242"
)
assert (
config["reticulum"]["interface_discovery_blacklist"] == "bad-node,evilentry"
)
assert config.write_called
@@ -61,7 +61,9 @@ async def find_route_handler(app_instance, path, method):
def test_normalize_handles_non_list_input():
assert ReticulumMeshChat.normalize_discovered_ifac_fields({"foo": "bar"}) == {"foo": "bar"}
assert ReticulumMeshChat.normalize_discovered_ifac_fields({"foo": "bar"}) == {
"foo": "bar"
}
assert ReticulumMeshChat.normalize_discovered_ifac_fields(None) is None
+9 -3
View File
@@ -102,8 +102,12 @@ def test_build_licenses_payload_composes_counts_and_meta():
def test_render_third_party_notices_contains_sections_and_rows():
payload = {
"backend": [{"name": "rns", "version": "1.0", "author": "Author A", "license": "MIT"}],
"frontend": [{"name": "vue", "version": "3.0", "author": "Author B", "license": "MIT"}],
"backend": [
{"name": "rns", "version": "1.0", "author": "Author A", "license": "MIT"}
],
"frontend": [
{"name": "vue", "version": "3.0", "author": "Author B", "license": "MIT"}
],
"meta": {"generated_at": "2026-01-01T00:00:00Z", "frontend_source": "pnpm"},
}
rendered = render_third_party_notices(payload)
@@ -131,7 +135,9 @@ def test_write_embedded_license_artifacts_writes_files(tmp_path):
assert frontend_path.exists()
assert notices_path.exists()
assert '"name": "vue"' in frontend_path.read_text(encoding="utf-8")
assert "Reticulum MeshChatX - Third-party notices" in notices_path.read_text(encoding="utf-8")
assert "Reticulum MeshChatX - Third-party notices" in notices_path.read_text(
encoding="utf-8"
)
def test_write_embedded_license_artifacts_preserves_existing_frontend_when_empty(
+3 -1
View File
@@ -18,7 +18,9 @@ def test_message_fields_have_attachments():
assert message_fields_have_attachments(json.dumps({"audio": "base64data"})) is True
# File attachments - empty list
assert message_fields_have_attachments(json.dumps({"file_attachments": []})) is False
assert (
message_fields_have_attachments(json.dumps({"file_attachments": []})) is False
)
# File attachments - with files
assert (
+9 -5
View File
@@ -61,7 +61,9 @@ def mock_rns():
# Apply patches
mocks = {}
for p in patches:
attr_name = p.attribute if hasattr(p, "attribute") else p.target.split(".")[-1]
attr_name = (
p.attribute if hasattr(p, "attribute") else p.target.split(".")[-1]
)
mocks[attr_name] = stack.enter_context(p)
# Access specifically the ones we need to configure
@@ -70,11 +72,13 @@ def mock_rns():
# Setup mock config
mock_config.return_value.display_name.get.return_value = "Test User"
mock_config.return_value.lxmf_user_icon_name.get.return_value = "user"
mock_config.return_value.lxmf_user_icon_foreground_colour.get.return_value = "#ffffff"
mock_config.return_value.lxmf_user_icon_background_colour.get.return_value = "#000000"
mock_config.return_value.auto_send_failed_messages_to_propagation_node.get.return_value = (
False
mock_config.return_value.lxmf_user_icon_foreground_colour.get.return_value = (
"#ffffff"
)
mock_config.return_value.lxmf_user_icon_background_colour.get.return_value = (
"#000000"
)
mock_config.return_value.auto_send_failed_messages_to_propagation_node.get.return_value = False
# Mock class methods on MockIdentityClass
mock_id_instance = MockIdentityClass()
+28 -12
View File
@@ -104,7 +104,10 @@ async def test_lxmf_propagation_config(mock_app):
mock_app.current_context.message_router.set_outbound_propagation_node.assert_called_with(
node_hash_bytes,
)
assert mock_app.config.lxmf_preferred_propagation_node_destination_hash.get() == node_hash_hex
assert (
mock_app.config.lxmf_preferred_propagation_node_destination_hash.get()
== node_hash_hex
)
@pytest.mark.asyncio
@@ -125,7 +128,9 @@ async def test_lxmf_sync_flow(mock_app):
mock_router.propagation_transfer_progress = 0.75
status_handler = next(
r.handler for r in mock_app.get_routes() if r.path == "/api/v1/lxmf/propagation-node/status"
r.handler
for r in mock_app.get_routes()
if r.path == "/api/v1/lxmf/propagation-node/status"
)
response = await status_handler(None)
data = json.loads(response.body)
@@ -139,7 +144,9 @@ async def test_lxmf_sync_requests_path_before_sync(mock_app):
outbound = b"somehash"
mock_router.get_outbound_propagation_node.return_value = outbound
sync_handler = next(
r.handler for r in mock_app.get_routes() if r.path == "/api/v1/lxmf/propagation-node/sync"
r.handler
for r in mock_app.get_routes()
if r.path == "/api/v1/lxmf/propagation-node/sync"
)
with patch("meshchatx.meshchat.RNS.Transport.has_path", return_value=False):
@@ -158,7 +165,9 @@ async def test_lxmf_sync_completes_immediately_for_local_preferred_node(mock_app
mock_router.propagation_destination = SimpleNamespace(hash=local_hash)
mock_router.get_outbound_propagation_node.return_value = local_hash
sync_handler = next(
r.handler for r in mock_app.get_routes() if r.path == "/api/v1/lxmf/propagation-node/sync"
r.handler
for r in mock_app.get_routes()
if r.path == "/api/v1/lxmf/propagation-node/sync"
)
await sync_handler(None)
@@ -203,14 +212,19 @@ async def test_auto_sync_interval_config(mock_app):
await mock_app.update_config(
{"lxmf_preferred_propagation_node_auto_sync_interval_seconds": 3600},
)
assert mock_app.config.lxmf_preferred_propagation_node_auto_sync_interval_seconds.get() == 3600
assert (
mock_app.config.lxmf_preferred_propagation_node_auto_sync_interval_seconds.get()
== 3600
)
@pytest.mark.asyncio
async def test_propagation_node_status_mapping(mock_app):
mock_router = mock_app.current_context.message_router
status_handler = next(
r.handler for r in mock_app.get_routes() if r.path == "/api/v1/lxmf/propagation-node/status"
r.handler
for r in mock_app.get_routes()
if r.path == "/api/v1/lxmf/propagation-node/status"
)
states_to_test = [
@@ -257,7 +271,9 @@ async def test_local_propagation_node_stop_and_restart_routes(mock_app):
}
stop_handler = next(
r.handler for r in mock_app.get_routes() if r.path == "/api/v1/lxmf/propagation-node/stop"
r.handler
for r in mock_app.get_routes()
if r.path == "/api/v1/lxmf/propagation-node/stop"
)
restart_handler = next(
r.handler
@@ -302,13 +318,13 @@ async def test_user_provided_node_hash(mock_app):
)
# Trigger a sync request
mock_app.current_context.message_router.get_outbound_propagation_node.return_value = (
bytes.fromhex(
node_hash_hex,
)
mock_app.current_context.message_router.get_outbound_propagation_node.return_value = bytes.fromhex(
node_hash_hex,
)
sync_handler = next(
r.handler for r in mock_app.get_routes() if r.path == "/api/v1/lxmf/propagation-node/sync"
r.handler
for r in mock_app.get_routes()
if r.path == "/api/v1/lxmf/propagation-node/sync"
)
await sync_handler(None)
@@ -104,7 +104,9 @@ def integration_app(temp_dir):
def _route_handler(app, path, method="GET"):
return next(r.handler for r in app.get_routes() if r.path == path and r.method == method)
return next(
r.handler for r in app.get_routes() if r.path == path and r.method == method
)
@pytest.mark.asyncio
@@ -133,18 +135,24 @@ async def test_remote_propagation_sync_transitions_path_requested_to_complete(
with (
patch("meshchatx.meshchat.RNS.Transport.has_path", side_effect=has_path),
patch("meshchatx.meshchat.RNS.Transport.request_path", side_effect=request_path),
patch(
"meshchatx.meshchat.RNS.Transport.request_path", side_effect=request_path
),
):
first_sync = await sync_handler(None)
assert first_sync.status == 200
first_status = json.loads((await status_handler(None)).body)["propagation_node_status"]
first_status = json.loads((await status_handler(None)).body)[
"propagation_node_status"
]
assert first_status["state"] == "path_requested"
second_sync = await sync_handler(None)
assert second_sync.status == 200
second_status = json.loads((await status_handler(None)).body)["propagation_node_status"]
second_status = json.loads((await status_handler(None)).body)[
"propagation_node_status"
]
assert second_status["state"] == "complete"
assert second_status["progress"] == 100.0
assert fake_router.request_messages_calls >= 2
@@ -169,7 +177,9 @@ async def test_local_preferred_propagation_sync_completes_without_remote_lookup(
response = await sync_handler(None)
assert response.status == 200
status_data = json.loads((await status_handler(None)).body)["propagation_node_status"]
status_data = json.loads((await status_handler(None)).body)[
"propagation_node_status"
]
assert status_data["state"] == "complete"
assert status_data["progress"] == 100.0
assert fake_router.request_messages_calls == 0
+23 -10
View File
@@ -78,7 +78,10 @@ async def test_lxmf_sync_endpoints(mock_app):
# 1. Test status endpoint initially idle
handler = None
for route in mock_app.get_routes():
if route.path == "/api/v1/lxmf/propagation-node/status" and route.method == "GET":
if (
route.path == "/api/v1/lxmf/propagation-node/status"
and route.method == "GET"
):
handler = route.handler
break
@@ -104,7 +107,9 @@ async def test_lxmf_sync_endpoints(mock_app):
mock_app.current_context.message_router.request_messages_from_propagation_node.assert_called_once()
# 3. Test status change to complete
mock_app.current_context.message_router.propagation_transfer_state = LXMF.LXMRouter.PR_COMPLETE
mock_app.current_context.message_router.propagation_transfer_state = (
LXMF.LXMRouter.PR_COMPLETE
)
response = await handler(None)
data = json.loads(response.body)
assert data["propagation_node_status"]["state"] == "complete"
@@ -135,9 +140,7 @@ async def test_specific_node_hash_validation(mock_app):
break
# Ensure it's considered configured
mock_app.current_context.message_router.get_outbound_propagation_node.return_value = (
expected_bytes
)
mock_app.current_context.message_router.get_outbound_propagation_node.return_value = expected_bytes
await sync_handler(None)
mock_app.current_context.message_router.request_messages_from_propagation_node.assert_called_once()
@@ -146,10 +149,14 @@ async def test_specific_node_hash_validation(mock_app):
@pytest.mark.asyncio
async def test_status_includes_sync_storage_and_confirmation_metrics(mock_app):
status_handler = next(
r.handler for r in mock_app.get_routes() if r.path == "/api/v1/lxmf/propagation-node/status"
r.handler
for r in mock_app.get_routes()
if r.path == "/api/v1/lxmf/propagation-node/status"
)
sync_handler = next(
r.handler for r in mock_app.get_routes() if r.path == "/api/v1/lxmf/propagation-node/sync"
r.handler
for r in mock_app.get_routes()
if r.path == "/api/v1/lxmf/propagation-node/sync"
)
mock_app.current_context.message_router.get_outbound_propagation_node.return_value = b"somehash"
@@ -180,7 +187,9 @@ async def test_status_includes_sync_storage_and_confirmation_metrics(mock_app):
@pytest.mark.asyncio
async def test_status_metrics_default_to_zero_before_any_sync(mock_app):
status_handler = next(
r.handler for r in mock_app.get_routes() if r.path == "/api/v1/lxmf/propagation-node/status"
r.handler
for r in mock_app.get_routes()
if r.path == "/api/v1/lxmf/propagation-node/status"
)
response = await status_handler(None)
data = json.loads(response.body)["propagation_node_status"]
@@ -194,10 +203,14 @@ async def test_status_metrics_default_to_zero_before_any_sync(mock_app):
@pytest.mark.asyncio
async def test_status_hidden_metric_is_clamped_to_zero(mock_app):
status_handler = next(
r.handler for r in mock_app.get_routes() if r.path == "/api/v1/lxmf/propagation-node/status"
r.handler
for r in mock_app.get_routes()
if r.path == "/api/v1/lxmf/propagation-node/status"
)
sync_handler = next(
r.handler for r in mock_app.get_routes() if r.path == "/api/v1/lxmf/propagation-node/sync"
r.handler
for r in mock_app.get_routes()
if r.path == "/api/v1/lxmf/propagation-node/sync"
)
mock_app.current_context.message_router.get_outbound_propagation_node.return_value = b"somehash"
+8 -2
View File
@@ -76,9 +76,15 @@ def test_convert_lxmf_message_to_dict_with_attachments():
== base64.b64encode(b"content1").decode()
)
assert result["fields"]["image"]["image_type"] == "png"
assert result["fields"]["image"]["image_bytes"] == base64.b64encode(b"image_data").decode()
assert (
result["fields"]["image"]["image_bytes"]
== base64.b64encode(b"image_data").decode()
)
assert result["fields"]["audio"]["audio_mode"] == "voice"
assert result["fields"]["audio"]["audio_bytes"] == base64.b64encode(b"audio_data").decode()
assert (
result["fields"]["audio"]["audio_bytes"]
== base64.b64encode(b"audio_data").decode()
)
def test_convert_lxmf_state_to_string():
+2 -1
View File
@@ -26,7 +26,8 @@ class TestMarkdownRenderer(unittest.TestCase):
self.assertIn("<code", rendered)
self.assertIn("language-python", rendered)
self.assertTrue(
"print(&#x27;hello&#x27;)" in rendered or "print(&#039;hello&#039;)" in rendered,
"print(&#x27;hello&#x27;)" in rendered
or "print(&#039;hello&#039;)" in rendered,
)
def test_lists(self):
+9 -3
View File
@@ -62,7 +62,9 @@ def test_parse_tgs_gzip_json_fuzz(payload):
merged.setdefault("op", 60.0)
merged.setdefault("w", 100)
merged.setdefault("h", 100)
raw = gzip.compress(json.dumps(merged, default=str).encode("utf-8", errors="surrogateescape"))
raw = gzip.compress(
json.dumps(merged, default=str).encode("utf-8", errors="surrogateescape")
)
if len(raw) > sticker_utils.MAX_ANIMATED_BYTES:
raw = raw[: sticker_utils.MAX_ANIMATED_BYTES]
try:
@@ -116,7 +118,9 @@ def test_extract_metadata_fuzz_never_raises(image_type, raw):
typ=st.one_of(
st.none(),
st.text(max_size=48),
st.sampled_from(["png", "jpeg", "jpg", "webp", "gif", "bmp", "tgs", "webm", "svg", ""]),
st.sampled_from(
["png", "jpeg", "jpg", "webp", "gif", "bmp", "tgs", "webm", "svg", ""]
),
),
strict=st.booleans(),
)
@@ -161,7 +165,9 @@ def test_mime_for_image_type_fuzz_never_raises(t):
description=st.one_of(st.none(), st.text(max_size=400)),
pack_type=st.one_of(st.none(), st.text(max_size=40)),
)
def test_sticker_pack_sanitizers_fuzz_never_raises(title, short_name, description, pack_type):
def test_sticker_pack_sanitizers_fuzz_never_raises(
title, short_name, description, pack_type
):
sticker_pack_utils.sanitize_pack_title(title)
sticker_pack_utils.sanitize_pack_short_name(short_name)
sticker_pack_utils.sanitize_pack_description(description)
+3 -1
View File
@@ -452,7 +452,9 @@ def _build_wav_pcm16(samplerate=48000, duration_seconds=0.5, frequency=440.0):
wf.setframerate(samplerate)
frames = bytearray()
for i in range(n_samples):
sample = int(0.3 * 32767 * math.sin(2 * math.pi * frequency * (i / samplerate)))
sample = int(
0.3 * 32767 * math.sin(2 * math.pi * frequency * (i / samplerate))
)
frames.extend(struct.pack("<h", sample))
wf.writeframes(bytes(frames))
return buf.getvalue()
@@ -84,7 +84,9 @@ def test_app_info_dependency_keys_resolve_in_dev_env(package: str):
assert v != "unknown", f"{package} must resolve when installed"
@pytest.mark.skipif(sys.version_info < (3, 13), reason="audioop-lts only on Python 3.13+")
@pytest.mark.skipif(
sys.version_info < (3, 13), reason="audioop-lts only on Python 3.13+"
)
def test_audioop_lts_resolves_when_applicable():
v = ReticulumMeshChat.get_package_version("audioop-lts")
assert v != "unknown"
@@ -196,7 +196,9 @@ class TestPerformanceBottlenecks(unittest.TestCase):
b"packet",
)
threads = [threading.Thread(target=insert_announces) for _ in range(num_threads)]
threads = [
threading.Thread(target=insert_announces) for _ in range(num_threads)
]
print(
f"\nRunning {num_threads} threads inserting {announces_per_thread} announces each...",
+12 -4
View File
@@ -512,7 +512,9 @@ class TestPerformanceHotPaths(unittest.TestCase):
with lock:
all_durations.extend(thread_durations)
threads = [threading.Thread(target=writer, args=(t,)) for t in range(num_threads)]
threads = [
threading.Thread(target=writer, args=(t,)) for t in range(num_threads)
]
t0 = time.perf_counter()
for t in threads:
t.start()
@@ -556,7 +558,9 @@ class TestPerformanceHotPaths(unittest.TestCase):
with lock:
all_durations.extend(thread_durations)
threads = [threading.Thread(target=writer, args=(t,)) for t in range(num_threads)]
threads = [
threading.Thread(target=writer, args=(t,)) for t in range(num_threads)
]
t0 = time.perf_counter()
for t in threads:
t.start()
@@ -619,8 +623,12 @@ class TestPerformanceHotPaths(unittest.TestCase):
with lock:
read_durations.extend(local_durs)
writers = [threading.Thread(target=writer, args=(t,)) for t in range(num_writers)]
readers = [threading.Thread(target=reader, args=(t,)) for t in range(num_readers)]
writers = [
threading.Thread(target=writer, args=(t,)) for t in range(num_writers)
]
readers = [
threading.Thread(target=reader, args=(t,)) for t in range(num_readers)
]
t0 = time.perf_counter()
for t in writers + readers:
@@ -67,7 +67,10 @@ async def test_propagation_nodes_endpoint_robustness(mock_rns_minimal, temp_dir)
assert "is_local_node" in node
if node.get("is_local_node") and isinstance(node.get("local_node_stats"), dict):
if isinstance(node["local_node_stats"].get("is_running"), bool):
assert node.get("is_propagation_enabled") == node["local_node_stats"]["is_running"]
assert (
node.get("is_propagation_enabled")
== node["local_node_stats"]["is_running"]
)
# Test with invalid limit (should not crash)
request.query = {"limit": "invalid"}
@@ -79,7 +82,10 @@ async def test_propagation_nodes_endpoint_robustness(mock_rns_minimal, temp_dir)
assert "is_local_node" in node
if node.get("is_local_node") and isinstance(node.get("local_node_stats"), dict):
if isinstance(node["local_node_stats"].get("is_running"), bool):
assert node.get("is_propagation_enabled") == node["local_node_stats"]["is_running"]
assert (
node.get("is_propagation_enabled")
== node["local_node_stats"]["is_running"]
)
# Test with missing limit (should not crash)
request.query = {}
@@ -91,4 +97,7 @@ async def test_propagation_nodes_endpoint_robustness(mock_rns_minimal, temp_dir)
assert "is_local_node" in node
if node.get("is_local_node") and isinstance(node.get("local_node_stats"), dict):
if isinstance(node["local_node_stats"].get("is_running"), bool):
assert node.get("is_propagation_enabled") == node["local_node_stats"]["is_running"]
assert (
node.get("is_propagation_enabled")
== node["local_node_stats"]["is_running"]
)
@@ -98,7 +98,9 @@ async def test_download_firmware_returns_zip_for_allowed_url(web_app):
async with TestClient(TestServer(aio_app)) as client:
r = await client.get(
"/api/v1/tools/rnode/download_firmware",
params={"url": "https://github.com/owner/repo/releases/download/v1/firmware.zip"},
params={
"url": "https://github.com/owner/repo/releases/download/v1/firmware.zip"
},
)
assert r.status == 200
assert r.headers.get("Content-Type", "").startswith("application/zip")
@@ -142,7 +144,9 @@ async def test_download_firmware_returns_500_on_exception(web_app):
async with TestClient(TestServer(aio_app)) as client:
r = await client.get(
"/api/v1/tools/rnode/download_firmware",
params={"url": "https://github.com/owner/repo/releases/download/v1/firmware.zip"},
params={
"url": "https://github.com/owner/repo/releases/download/v1/firmware.zip"
},
)
assert r.status == 500
body = await r.json()
+9 -3
View File
@@ -70,7 +70,9 @@ async def test_rnpath_table_endpoint(mock_rns_minimal, temp_dir):
request.query = {}
handler = next(
r.handler for r in app_instance.get_routes() if r.path == "/api/v1/rnpath/table"
r.handler
for r in app_instance.get_routes()
if r.path == "/api/v1/rnpath/table"
)
response = await handler(request)
data = json.loads(response.body)
@@ -96,7 +98,9 @@ async def test_rnpath_request_endpoint(mock_rns_minimal, temp_dir):
request.json = AsyncMock(return_value={"destination_hash": target_hash})
handler = next(
r.handler for r in app_instance.get_routes() if r.path == "/api/v1/rnpath/request"
r.handler
for r in app_instance.get_routes()
if r.path == "/api/v1/rnpath/request"
)
response = await handler(request)
@@ -118,7 +122,9 @@ async def test_rnpath_drop_endpoint(mock_rns_minimal, temp_dir):
request.json = AsyncMock(return_value={"destination_hash": target_hash})
handler = next(
r.handler for r in app_instance.get_routes() if r.path == "/api/v1/rnpath/drop"
r.handler
for r in app_instance.get_routes()
if r.path == "/api/v1/rnpath/drop"
)
response = await handler(request)
+32 -8
View File
@@ -528,7 +528,10 @@ async def test_transport_enable_endpoint_reloads_rns(mock_rns, temp_dir):
handler = None
for route in app.get_routes():
if route.path == "/api/v1/reticulum/enable-transport" and route.method == "POST":
if (
route.path == "/api/v1/reticulum/enable-transport"
and route.method == "POST"
):
handler = route.handler
break
@@ -538,7 +541,10 @@ async def test_transport_enable_endpoint_reloads_rns(mock_rns, temp_dir):
payload = json.loads(response.body)
assert response.status == 200
assert payload["message"] == "Transport mode enabled and RNS restarted successfully."
assert (
payload["message"]
== "Transport mode enabled and RNS restarted successfully."
)
app.reload_reticulum.assert_awaited_once()
app.teardown_identity()
@@ -570,7 +576,10 @@ async def test_transport_disable_endpoint_reloads_rns(mock_rns, temp_dir):
handler = None
for route in app.get_routes():
if route.path == "/api/v1/reticulum/disable-transport" and route.method == "POST":
if (
route.path == "/api/v1/reticulum/disable-transport"
and route.method == "POST"
):
handler = route.handler
break
@@ -580,7 +589,10 @@ async def test_transport_disable_endpoint_reloads_rns(mock_rns, temp_dir):
payload = json.loads(response.body)
assert response.status == 200
assert payload["message"] == "Transport mode disabled and RNS restarted successfully."
assert (
payload["message"]
== "Transport mode disabled and RNS restarted successfully."
)
app.reload_reticulum.assert_awaited_once()
app.teardown_identity()
@@ -612,7 +624,10 @@ async def test_transport_enable_endpoint_reload_failure(mock_rns, temp_dir):
handler = None
for route in app.get_routes():
if route.path == "/api/v1/reticulum/enable-transport" and route.method == "POST":
if (
route.path == "/api/v1/reticulum/enable-transport"
and route.method == "POST"
):
handler = route.handler
break
@@ -622,7 +637,10 @@ async def test_transport_enable_endpoint_reload_failure(mock_rns, temp_dir):
payload = json.loads(response.body)
assert response.status == 500
assert payload["message"] == "Transport mode was enabled in config, but RNS reload failed."
assert (
payload["message"]
== "Transport mode was enabled in config, but RNS reload failed."
)
app.reload_reticulum.assert_awaited_once()
app.teardown_identity()
@@ -654,7 +672,10 @@ async def test_transport_disable_endpoint_reload_failure(mock_rns, temp_dir):
handler = None
for route in app.get_routes():
if route.path == "/api/v1/reticulum/disable-transport" and route.method == "POST":
if (
route.path == "/api/v1/reticulum/disable-transport"
and route.method == "POST"
):
handler = route.handler
break
@@ -664,7 +685,10 @@ async def test_transport_disable_endpoint_reload_failure(mock_rns, temp_dir):
payload = json.loads(response.body)
assert response.status == 500
assert payload["message"] == "Transport mode was disabled in config, but RNS reload failed."
assert (
payload["message"]
== "Transport mode was disabled in config, but RNS reload failed."
)
app.reload_reticulum.assert_awaited_once()
app.teardown_identity()
+22 -8
View File
@@ -647,7 +647,9 @@ def test_lxm_uri_comprehensive_fuzzing(mock_app, uri):
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
try:
uri_str = uri.decode("utf-8", errors="ignore") if isinstance(uri, bytes) else uri
uri_str = (
uri.decode("utf-8", errors="ignore") if isinstance(uri, bytes) else uri
)
loop.run_until_complete(
mock_app.on_websocket_data_received(
mock_client,
@@ -1122,17 +1124,20 @@ def test_map_tile_coordinates_fuzzing(mock_app, z, x, y):
try:
z_int = (
int(z)
if isinstance(z, (int, float)) and (not isinstance(z, float) or math.isfinite(z))
if isinstance(z, (int, float))
and (not isinstance(z, float) or math.isfinite(z))
else 0
)
x_int = (
int(x)
if isinstance(x, (int, float)) and (not isinstance(x, float) or math.isfinite(x))
if isinstance(x, (int, float))
and (not isinstance(x, float) or math.isfinite(x))
else 0
)
y_int = (
int(y)
if isinstance(y, (int, float)) and (not isinstance(y, float) or math.isfinite(y))
if isinstance(y, (int, float))
and (not isinstance(y, float) or math.isfinite(y))
else 0
)
mock_app.map_manager.get_tile(z_int, x_int, y_int)
@@ -1399,7 +1404,9 @@ def test_nomadnet_page_archive_add_fuzzing(
import asyncio
content_str = (
content.decode("utf-8", errors="replace") if isinstance(content, bytes) else content
content.decode("utf-8", errors="replace")
if isinstance(content, bytes)
else content
)
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
@@ -1936,7 +1943,10 @@ def test_lxmf_audio_mode_fuzzing(mock_app, audio_mode, audio_bytes):
)
def test_lxst_profile_switching_fuzzing(mock_app, profile_id):
"""Fuzz LXST audio profile switching."""
if hasattr(mock_app.telephone_manager, "telephone") and mock_app.telephone_manager.telephone:
if (
hasattr(mock_app.telephone_manager, "telephone")
and mock_app.telephone_manager.telephone
):
mock_app.telephone_manager.telephone.switch_profile(profile_id)
@@ -2176,11 +2186,15 @@ def test_lxmf_display_name_parsing_regression():
# None case (fallback to default)
mock_parser.return_value = None
assert parse_lxmf_display_name(valid_b64, default_value="Fallback") == "Fallback"
assert (
parse_lxmf_display_name(valid_b64, default_value="Fallback") == "Fallback"
)
# Exception case
mock_parser.side_effect = Exception("Parsing error")
assert parse_lxmf_display_name(valid_b64, default_value="Fallback") == "Fallback"
assert (
parse_lxmf_display_name(valid_b64, default_value="Fallback") == "Fallback"
)
# None input
assert parse_lxmf_display_name(None, default_value="Fallback") == "Fallback"
+9 -5
View File
@@ -114,14 +114,18 @@ def test_reticulum_meshchat_init(mock_rns, temp_dir):
# Setup config mock values
mock_config_instance.auth_enabled.get.return_value = False
mock_config_instance.lxmf_propagation_node_stamp_cost.get.return_value = 0
mock_config_instance.lxmf_delivery_transfer_limit_in_bytes.get.return_value = 1000000
mock_config_instance.lxmf_delivery_transfer_limit_in_bytes.get.return_value = (
1000000
)
mock_config_instance.lxmf_inbound_stamp_cost.get.return_value = 0
mock_config_instance.display_name.get.return_value = "Test User"
mock_config_instance.lxmf_preferred_propagation_node_destination_hash.get.return_value = (
None
mock_config_instance.lxmf_preferred_propagation_node_destination_hash.get.return_value = None
mock_config_instance.lxmf_local_propagation_node_enabled.get.return_value = (
False
)
mock_config_instance.libretranslate_url.get.return_value = (
"http://localhost:5000"
)
mock_config_instance.lxmf_local_propagation_node_enabled.get.return_value = False
mock_config_instance.libretranslate_url.get.return_value = "http://localhost:5000"
mock_config_instance.translator_enabled.get.return_value = False
app = ReticulumMeshChat(
+4 -1
View File
@@ -19,7 +19,10 @@ def test_sanitize_pack_title_default():
def test_sanitize_pack_short_name():
assert sticker_pack_utils.sanitize_pack_short_name(None) is None
assert sticker_pack_utils.sanitize_pack_short_name("Cats!@#") == "cats"
assert sticker_pack_utils.sanitize_pack_short_name(" Hello_World-1 ") == "hello_world-1"
assert (
sticker_pack_utils.sanitize_pack_short_name(" Hello_World-1 ")
== "hello_world-1"
)
assert sticker_pack_utils.sanitize_pack_short_name("***") is None
assert len(sticker_pack_utils.sanitize_pack_short_name("a" * 200)) == 32
+31 -8
View File
@@ -59,13 +59,24 @@ def test_validate_sticker_payload_magic_type_mismatch():
def test_detect_image_format_from_magic():
assert sticker_utils.detect_image_format_from_magic(b"\x89PNG\r\n\x1a\n") == "png"
assert sticker_utils.detect_image_format_from_magic(b"\xff\xd8\xff\xe0\x00\x10") == "jpeg"
assert sticker_utils.detect_image_format_from_magic(b"GIF89a" + b"\x00" * 4) == "gif"
assert (
sticker_utils.detect_image_format_from_magic(b"\xff\xd8\xff\xe0\x00\x10")
== "jpeg"
)
assert (
sticker_utils.detect_image_format_from_magic(b"GIF89a" + b"\x00" * 4) == "gif"
)
assert sticker_utils.detect_image_format_from_magic(b"BM" + b"\x00" * 20) == "bmp"
webp = b"RIFF\x00\x00\x00\x00WEBP" + b"\x00" * 8
assert sticker_utils.detect_image_format_from_magic(webp) == "webp"
assert sticker_utils.detect_image_format_from_magic(b"\x1a\x45\xdf\xa3" + b"\x00" * 8) == "webm"
assert sticker_utils.detect_image_format_from_magic(b"\x1f\x8b\x08\x00" + b"\x00" * 8) == "tgs"
assert (
sticker_utils.detect_image_format_from_magic(b"\x1a\x45\xdf\xa3" + b"\x00" * 8)
== "webm"
)
assert (
sticker_utils.detect_image_format_from_magic(b"\x1f\x8b\x08\x00" + b"\x00" * 8)
== "tgs"
)
assert sticker_utils.detect_image_format_from_magic(b"") is None
assert sticker_utils.detect_image_format_from_magic(b"short") is None
@@ -188,7 +199,9 @@ def test_validate_export_document_fuzz_never_raises_unexpected(doc):
pass
def _build_tgs(width: int = 512, height: int = 512, fps: float = 30.0, frames: int = 60) -> bytes:
def _build_tgs(
width: int = 512, height: int = 512, fps: float = 30.0, frames: int = 60
) -> bytes:
import gzip
import json
@@ -234,7 +247,9 @@ def test_parse_tgs_invalid_metadata():
import gzip
import json
raw = gzip.compress(json.dumps({"w": 0, "h": 0, "fr": 0, "ip": 0, "op": 0}).encode())
raw = gzip.compress(
json.dumps({"w": 0, "h": 0, "fr": 0, "ip": 0, "op": 0}).encode()
)
with pytest.raises(ValueError, match="invalid_tgs_metadata"):
sticker_utils.parse_tgs(raw)
@@ -290,7 +305,11 @@ def test_validate_strict_webm_too_large():
def test_detect_image_dimensions_png():
raw = b"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR" + struct_pack(512, 512) + b"\x08\x06\x00\x00\x00"
raw = (
b"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR"
+ struct_pack(512, 512)
+ b"\x08\x06\x00\x00\x00"
)
assert sticker_utils.detect_image_dimensions("png", raw) == (512, 512)
@@ -329,7 +348,11 @@ def test_extract_metadata_tgs():
def test_extract_metadata_static_png():
raw = b"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR" + struct_pack(512, 256) + b"\x08\x06\x00\x00\x00"
raw = (
b"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR"
+ struct_pack(512, 256)
+ b"\x08\x06\x00\x00\x00"
)
meta = sticker_utils.extract_metadata("png", raw)
assert meta["width"] == 512
assert meta["height"] == 256
+2 -2
View File
@@ -36,8 +36,8 @@ def mock_app():
# Attach the actual method we want to test if possible,
# but since it's an instance method, we might need to bind it.
app.process_incoming_telemetry = ReticulumMeshChat.process_incoming_telemetry.__get__(
app, ReticulumMeshChat
app.process_incoming_telemetry = (
ReticulumMeshChat.process_incoming_telemetry.__get__(app, ReticulumMeshChat)
)
return app
@@ -73,14 +73,18 @@ class _Query:
@pytest.mark.asyncio
async def test_api_v1_telephone_voicemail_status_json_contract(mock_rns_minimal, temp_dir):
async def test_api_v1_telephone_voicemail_status_json_contract(
mock_rns_minimal, temp_dir
):
with patch("meshchatx.meshchat.generate_ssl_certificate"):
app_instance = ReticulumMeshChat(
identity=mock_rns_minimal,
storage_dir=temp_dir,
reticulum_config_dir=temp_dir,
)
handler = _find_handler(app_instance, "/api/v1/telephone/voicemail/status", "GET")
handler = _find_handler(
app_instance, "/api/v1/telephone/voicemail/status", "GET"
)
assert handler is not None
request = MagicMock()
response = await handler(request)
@@ -106,7 +110,9 @@ async def test_api_v1_telephone_voicemails_json_contract(mock_rns_minimal, temp_
@pytest.mark.asyncio
async def test_api_v1_telephone_ringtones_list_json_contract(mock_rns_minimal, temp_dir):
async def test_api_v1_telephone_ringtones_list_json_contract(
mock_rns_minimal, temp_dir
):
with patch("meshchatx.meshchat.generate_ssl_certificate"):
app_instance = ReticulumMeshChat(
identity=mock_rns_minimal,
@@ -122,14 +128,18 @@ async def test_api_v1_telephone_ringtones_list_json_contract(mock_rns_minimal, t
@pytest.mark.asyncio
async def test_api_v1_telephone_ringtones_status_json_contract(mock_rns_minimal, temp_dir):
async def test_api_v1_telephone_ringtones_status_json_contract(
mock_rns_minimal, temp_dir
):
with patch("meshchatx.meshchat.generate_ssl_certificate"):
app_instance = ReticulumMeshChat(
identity=mock_rns_minimal,
storage_dir=temp_dir,
reticulum_config_dir=temp_dir,
)
handler = _find_handler(app_instance, "/api/v1/telephone/ringtones/status", "GET")
handler = _find_handler(
app_instance, "/api/v1/telephone/ringtones/status", "GET"
)
assert handler is not None
request = MagicMock()
request.query = _Query({})
@@ -156,7 +166,9 @@ async def test_api_v1_telephone_contacts_list_json_contract(mock_rns_minimal, te
@pytest.mark.asyncio
async def test_api_v1_telephone_contacts_check_json_contract(mock_rns_minimal, temp_dir):
async def test_api_v1_telephone_contacts_check_json_contract(
mock_rns_minimal, temp_dir
):
with patch("meshchatx.meshchat.generate_ssl_certificate"):
app_instance = ReticulumMeshChat(
identity=mock_rns_minimal,
+25 -9
View File
@@ -21,7 +21,9 @@ def telephone_manager():
tm._path_retry_interval_s = 0.01
tm._status_poll_interval_s = 0.01
tm._status_events = []
tm.on_initiation_status_callback = lambda status, _target: tm._status_events.append(status)
tm.on_initiation_status_callback = lambda status, _target: tm._status_events.append(
status
)
return tm
@@ -48,7 +50,9 @@ async def test_initiate_retries_path_requests_during_lookup(telephone_manager):
"meshchatx.src.backend.telephone_manager.RNS.Transport.has_path",
side_effect=has_path,
),
patch("meshchatx.src.backend.telephone_manager.RNS.Transport.request_path") as request_path,
patch(
"meshchatx.src.backend.telephone_manager.RNS.Transport.request_path"
) as request_path,
):
await telephone_manager.initiate(destination_hash, timeout_seconds=1)
@@ -79,7 +83,9 @@ async def test_initiate_cancels_quickly_while_finding_path_identity(telephone_ma
side_effect=request_path_and_cancel,
),
):
task = asyncio.create_task(telephone_manager.initiate(destination_hash, timeout_seconds=5))
task = asyncio.create_task(
telephone_manager.initiate(destination_hash, timeout_seconds=5)
)
result = await asyncio.wait_for(task, timeout=0.3)
assert result is None
@@ -105,7 +111,9 @@ async def test_initiate_cancels_quickly_while_dialling(telephone_manager):
return_value=True,
),
):
task = asyncio.create_task(telephone_manager.initiate(destination_hash, timeout_seconds=5))
task = asyncio.create_task(
telephone_manager.initiate(destination_hash, timeout_seconds=5)
)
for _ in range(200):
if telephone_manager.initiation_status in (
"Establishing link...",
@@ -170,7 +178,9 @@ async def test_cancel_after_path_found_before_dialling_stabilizes(telephone_mana
return_value=True,
),
):
task = asyncio.create_task(telephone_manager.initiate(destination_hash, timeout_seconds=2))
task = asyncio.create_task(
telephone_manager.initiate(destination_hash, timeout_seconds=2)
)
for _ in range(200):
if telephone_manager.initiation_status == "Establishing link...":
break
@@ -275,7 +285,9 @@ async def test_call_thread_exception_surfaces_without_hanging(telephone_manager)
"meshchatx.src.backend.telephone_manager.RNS.Transport.has_path",
return_value=True,
),
patch("meshchatx.src.backend.telephone_manager.asyncio.sleep", side_effect=no_wait),
patch(
"meshchatx.src.backend.telephone_manager.asyncio.sleep", side_effect=no_wait
),
):
result = await asyncio.wait_for(
telephone_manager.initiate(destination_hash, timeout_seconds=1),
@@ -307,7 +319,9 @@ async def test_inconsistent_call_status_finishes_within_timeout(telephone_manage
"meshchatx.src.backend.telephone_manager.RNS.Transport.has_path",
return_value=True,
),
patch("meshchatx.src.backend.telephone_manager.asyncio.sleep", side_effect=no_wait),
patch(
"meshchatx.src.backend.telephone_manager.asyncio.sleep", side_effect=no_wait
),
):
result = await asyncio.wait_for(
telephone_manager.initiate(destination_hash, timeout_seconds=0.2),
@@ -360,8 +374,10 @@ async def test_lxst_busy_and_rejected_end_without_stuck_status(telephone_manager
for terminal_state in (0, 1):
telephone_manager._status_events.clear()
telephone_manager.telephone.call_status = 3
telephone_manager.telephone.call.side_effect = lambda _identity, state=terminal_state: (
setattr(telephone_manager.telephone, "call_status", state)
telephone_manager.telephone.call.side_effect = (
lambda _identity, state=terminal_state: setattr(
telephone_manager.telephone, "call_status", state
)
)
with (
@@ -42,12 +42,16 @@ def test_init_telephone(mock_tel_class, tel_manager):
@patch("meshchatx.src.backend.telephone_manager.Telephone")
def test_init_telephone_applies_config_audio_profile(mock_tel_class, mock_identity, tmp_path):
def test_init_telephone_applies_config_audio_profile(
mock_tel_class, mock_identity, tmp_path
):
storage_dir = tmp_path / "tel"
storage_dir.mkdir()
cfg = MagicMock()
cfg.telephone_audio_profile_id.get.return_value = 96
tm = TelephoneManager(mock_identity, config_manager=cfg, storage_dir=str(storage_dir))
tm = TelephoneManager(
mock_identity, config_manager=cfg, storage_dir=str(storage_dir)
)
tm.init_telephone()
mock_tel_class.return_value.switch_profile.assert_called_with(96)
@@ -20,7 +20,9 @@ def _make_app(config_interfaces):
transport_enabled=lambda: True,
)
app._get_interfaces_section = ReticulumMeshChat._get_interfaces_section.__get__(app)
app._detect_failed_autointerfaces = ReticulumMeshChat._detect_failed_autointerfaces.__get__(app)
app._detect_failed_autointerfaces = (
ReticulumMeshChat._detect_failed_autointerfaces.__get__(app)
)
return app
@@ -102,7 +104,9 @@ def test_guidance_message_emitted_for_failed_autointerface():
)
app.config = MagicMock()
app.config.auto_announce_enabled.get.return_value = True
app.build_user_guidance_messages = ReticulumMeshChat.build_user_guidance_messages.__get__(app)
app.build_user_guidance_messages = (
ReticulumMeshChat.build_user_guidance_messages.__get__(app)
)
with patch("meshchatx.meshchat.RNS.Transport") as transport:
transport.interfaces = []
@@ -125,7 +129,9 @@ def test_guidance_message_absent_when_autointerface_running():
)
app.config = MagicMock()
app.config.auto_announce_enabled.get.return_value = True
app.build_user_guidance_messages = ReticulumMeshChat.build_user_guidance_messages.__get__(app)
app.build_user_guidance_messages = (
ReticulumMeshChat.build_user_guidance_messages.__get__(app)
)
with patch("meshchatx.meshchat.RNS.Transport") as transport:
transport.interfaces = [_FakeAutoInterface()]