mirror of
https://git.quad4.io/RNS-Things/MeshChatX.git
synced 2026-04-27 10:45:44 +00:00
refactor(codebase): format
This commit is contained in:
+3
-1
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -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()
|
||||
]
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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"))
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -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
|
||||
),
|
||||
},
|
||||
)
|
||||
|
||||
@@ -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__()
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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]
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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"})
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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] = []
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 == "#"
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -26,7 +26,8 @@ class TestMarkdownRenderer(unittest.TestCase):
|
||||
self.assertIn("<code", rendered)
|
||||
self.assertIn("language-python", rendered)
|
||||
self.assertTrue(
|
||||
"print('hello')" in rendered or "print('hello')" in rendered,
|
||||
"print('hello')" in rendered
|
||||
or "print('hello')" in rendered,
|
||||
)
|
||||
|
||||
def test_lists(self):
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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...",
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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()]
|
||||
|
||||
Reference in New Issue
Block a user