refactor(shared): create shared/ package and migrate foundation modules

Move models.py, db_manager.py, db_migrations.py, and security_utils.py
from modules/ to a new shared/ top-level package that can be imported by
both the bot and the web viewer without coupling them.

Update all imports across ~75 files (commands, service plugins, tests,
web viewer, generate_website.py). No logic changes — pure file moves and
import path updates.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
agessaman
2026-05-18 17:19:46 -07:00
parent 0b56e86de9
commit 23da88c37d
79 changed files with 102 additions and 101 deletions
+1 -1
View File
@@ -21,7 +21,7 @@ try:
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from modules.config_validation import strip_optional_quotes
from modules.db_manager import DBManager
from shared.db_manager import DBManager
from modules.plugin_loader import PluginLoader
from modules.utils import resolve_path
except ImportError as e:
+1 -1
View File
@@ -16,7 +16,7 @@ from configparser import ConfigParser
from dataclasses import dataclass
from typing import Any
from ..security_utils import sanitize_input
from shared.security_utils import sanitize_input
MQTT_WEATHER_PREFIX = "custom.mqtt_weather."
+2 -2
View File
@@ -21,9 +21,9 @@ from .config_validation import (
_channel_name_is_public,
strip_optional_quotes,
)
from .models import CHANNEL_REGIONAL_FLOOD_SCOPE_BODY_OVERHEAD, MeshMessage
from shared.models import CHANNEL_REGIONAL_FLOOD_SCOPE_BODY_OVERHEAD, MeshMessage
from .plugin_loader import PluginLoader
from .security_utils import sanitize_name, validate_safe_path
from shared.security_utils import sanitize_name, validate_safe_path
from .utils import check_internet_connectivity_async, decode_escape_sequences, format_keyword_response_with_placeholders
+1 -1
View File
@@ -8,7 +8,7 @@ import asyncio
import time
from typing import Any
from ..models import MeshMessage
from shared.models import MeshMessage
from .base_command import BaseCommand
+1 -1
View File
@@ -11,7 +11,7 @@ from typing import Any, Optional
import requests
from ..models import MeshMessage
from shared.models import MeshMessage
from ..utils import calculate_distance
from .base_command import BaseCommand
+1 -1
View File
@@ -15,7 +15,7 @@ import requests
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from ..models import MeshMessage
from shared.models import MeshMessage
from ..utils import calculate_distance, geocode_city_sync, geocode_zipcode_sync, rate_limited_nominatim_reverse_sync
from .base_command import BaseCommand
@@ -10,7 +10,7 @@ from typing import Any, Optional, Union
import requests
from ...models import MeshMessage
from shared.models import MeshMessage
from ...utils import (
format_temperature_high_low,
geocode_city_sync,
+2 -2
View File
@@ -6,8 +6,8 @@ Allows authorized users to send announcements to channels via DM
import time
from ..models import MeshMessage
from ..security_utils import validate_pubkey_format
from shared.models import MeshMessage
from shared.security_utils import validate_pubkey_format
from .base_command import BaseCommand
+1 -1
View File
@@ -10,7 +10,7 @@ import openmeteo_requests
import requests_cache
from retry_requests import retry
from ..models import MeshMessage
from shared.models import MeshMessage
from ..utils import (
abbreviate_location,
geocode_city_sync,
+1 -1
View File
@@ -9,7 +9,7 @@ from datetime import datetime, timezone
from typing import Optional
from ..clients.noaa_aurora_client import NOAAAuroraClient
from ..models import MeshMessage
from shared.models import MeshMessage
from ..utils import geocode_city_sync, geocode_zipcode_sync, get_config_timezone
from .base_command import BaseCommand
+2 -2
View File
@@ -9,8 +9,8 @@ from abc import ABC, abstractmethod
from datetime import datetime
from typing import Any, Optional
from ..models import CHANNEL_REGIONAL_FLOOD_SCOPE_BODY_OVERHEAD, MeshMessage
from ..security_utils import validate_pubkey_format
from shared.models import CHANNEL_REGIONAL_FLOOD_SCOPE_BODY_OVERHEAD, MeshMessage
from shared.security_utils import validate_pubkey_format
from ..utils import format_elapsed_display, get_config_timezone
+1 -1
View File
@@ -6,7 +6,7 @@ Provides random cat facts as a hidden easter egg command
import random
from ..models import MeshMessage
from shared.models import MeshMessage
from .base_command import BaseCommand
+1 -1
View File
@@ -4,7 +4,7 @@ Channel pause command
DM-only admin: pause or resume bot responses on public channels (in-memory only).
"""
from ..models import MeshMessage
from shared.models import MeshMessage
from .base_command import BaseCommand
+1 -1
View File
@@ -8,7 +8,7 @@ import asyncio
import re
from typing import Optional
from ..models import MeshMessage
from shared.models import MeshMessage
from .base_command import BaseCommand
+1 -1
View File
@@ -6,7 +6,7 @@ Lists available commands in a compact, comma-separated format for LoRa
from typing import Any, Optional
from ..models import MeshMessage
from shared.models import MeshMessage
from .base_command import BaseCommand
+1 -1
View File
@@ -10,7 +10,7 @@ from typing import Any, Optional
import aiohttp
from ..models import MeshMessage
from shared.models import MeshMessage
from .base_command import BaseCommand
logger = logging.getLogger("MeshCoreBot")
+1 -1
View File
@@ -6,7 +6,7 @@ Handles dice rolling for D&D and other tabletop games
import random
from ..models import MeshMessage
from shared.models import MeshMessage
from .base_command import BaseCommand
+2 -2
View File
@@ -7,8 +7,8 @@ Handles RSS and API feed subscription management
import json
from typing import Optional
from ..models import MeshMessage
from ..security_utils import sanitize_input, sanitize_name, validate_external_url
from shared.models import MeshMessage
from shared.security_utils import sanitize_input, sanitize_name, validate_external_url
from .base_command import BaseCommand
+1 -1
View File
@@ -10,7 +10,7 @@ import time
from datetime import datetime, timedelta
from typing import Any, Optional
from ..models import MeshMessage
from shared.models import MeshMessage
from ..utils import decode_escape_sequences
from .base_command import BaseCommand
+1 -1
View File
@@ -7,7 +7,7 @@ Responds to Linux commands with hilarious supervillain mainframe error messages
import random
from typing import Any
from ..models import MeshMessage
from shared.models import MeshMessage
from .base_command import BaseCommand
+1 -1
View File
@@ -9,7 +9,7 @@ import random
import re
from typing import Any
from ..models import MeshMessage
from shared.models import MeshMessage
from ..utils import get_config_timezone
from .base_command import BaseCommand
+1 -1
View File
@@ -7,7 +7,7 @@ Provides help information for commands and general usage
from collections import defaultdict
from typing import Any, Optional
from ..models import MeshMessage
from shared.models import MeshMessage
from .base_command import BaseCommand
+1 -1
View File
@@ -3,7 +3,7 @@
HF Conditions Command - Provides HF band conditions for ham radio
"""
from ..models import MeshMessage
from shared.models import MeshMessage
from ..solar_conditions import hf_band_conditions
from .base_command import BaseCommand
+1 -1
View File
@@ -9,7 +9,7 @@ from typing import Any, Optional
import aiohttp
from ..models import MeshMessage
from shared.models import MeshMessage
from .base_command import BaseCommand
+1 -1
View File
@@ -6,7 +6,7 @@ Handles the 'magic8' keyword response
import random
from typing import Optional
from ..models import MeshMessage
from shared.models import MeshMessage
from .base_command import BaseCommand
magic8_responses = ["It is certain.","It is decidedly so.","Without a doubt.","Yes definitely.","You may rely on it.","As I see it, yes.","Most likely.","Outlook good.","Yes.","Signs point to yes.","Reply hazy, try again.","Ask again later.","Better not tell you now.","Cannot predict now.","Concentrate and ask again.","Don't count on it.","My reply is no.","My sources say no.","Outlook not so good.","Very doubtful."]
+1 -1
View File
@@ -3,7 +3,7 @@
Moon Command - Provides moon phase and position information
"""
from ..models import MeshMessage
from shared.models import MeshMessage
from ..solar_conditions import get_moon
from .base_command import BaseCommand
+1 -1
View File
@@ -13,7 +13,7 @@ from typing import Literal, Optional
CondensePathsMode = Literal["off", "flat", "nested"]
from ..models import MeshMessage
from shared.models import MeshMessage
from ..utils import calculate_packet_hash, parse_path_string
from .base_command import BaseCommand
+2 -2
View File
@@ -10,8 +10,8 @@ import re
import time
from typing import Any, Callable, Optional
from ..models import MeshMessage
from ..security_utils import sanitize_name
from shared.models import MeshMessage
from shared.security_utils import sanitize_name
from ..utils import bytes_per_hop_from_routing_and_nodes, calculate_distance, parse_path_string
from .base_command import BaseCommand
+1 -1
View File
@@ -6,7 +6,7 @@ Handles the 'ping' keyword response
from typing import Optional
from ..models import MeshMessage
from shared.models import MeshMessage
from .base_command import BaseCommand
+1 -1
View File
@@ -14,7 +14,7 @@ from typing import Any, Optional
import aiohttp
from ..models import MeshMessage
from shared.models import MeshMessage
from ..utils import abbreviate_location, calculate_distance, format_location_for_display, geocode_city, geocode_zipcode
from .base_command import BaseCommand
+1 -1
View File
@@ -4,7 +4,7 @@ Reload Command
Allows admin users to reload the bot configuration without restarting
"""
from ..models import MeshMessage
from shared.models import MeshMessage
from .base_command import BaseCommand
+1 -1
View File
@@ -6,7 +6,7 @@ Provides commands to manage repeater contacts and purging operations
import asyncio
from ..models import MeshMessage
from shared.models import MeshMessage
from .base_command import BaseCommand
+1 -1
View File
@@ -7,7 +7,7 @@ Handles random number generation between 1 and X (default 100)
import random
from typing import Optional
from ..models import MeshMessage
from shared.models import MeshMessage
from .base_command import BaseCommand
+1 -1
View File
@@ -3,7 +3,7 @@
Satellite Pass Command - Provides satellite pass information
"""
from ..models import MeshMessage
from shared.models import MeshMessage
from ..solar_conditions import get_next_satellite_pass
from .base_command import BaseCommand
+1 -1
View File
@@ -6,7 +6,7 @@ Lists upcoming scheduled messages and interval advertising settings.
from typing import Any, Optional
from ..models import MeshMessage
from shared.models import MeshMessage
from .base_command import BaseCommand
+1 -1
View File
@@ -3,7 +3,7 @@
Solar Command - Provides solar conditions and HF band information
"""
from ..models import MeshMessage
from shared.models import MeshMessage
from ..solar_conditions import solar_conditions
from .base_command import BaseCommand
+2 -2
View File
@@ -12,8 +12,8 @@ from typing import Optional
import requests
from ..models import MeshMessage
from ..security_utils import sanitize_name
from shared.models import MeshMessage
from shared.security_utils import sanitize_name
from ..utils import (
abbreviate_location,
geocode_city,
+1 -1
View File
@@ -25,7 +25,7 @@ from typing import TYPE_CHECKING, Optional
from ..clients.espn_client import ESPNClient
from ..clients.sports_mappings import LEAGUE_MAPPINGS, SPORT_EMOJIS, TEAM_MAPPINGS
from ..clients.thesportsdb_client import TheSportsDBClient
from ..models import MeshMessage
from shared.models import MeshMessage
from .base_command import BaseCommand
if TYPE_CHECKING:
+1 -1
View File
@@ -7,7 +7,7 @@ Provides comprehensive statistics about bot usage, messages, and activity
import time
from typing import Any, Optional
from ..models import MeshMessage
from shared.models import MeshMessage
from .base_command import BaseCommand
+1 -1
View File
@@ -9,7 +9,7 @@ from __future__ import annotations
from datetime import datetime, timezone
from typing import Any
from ..models import MeshMessage
from shared.models import MeshMessage
from .base_command import BaseCommand
+1 -1
View File
@@ -3,7 +3,7 @@
Sun Command - Provides sunrise/sunset information
"""
from ..models import MeshMessage
from shared.models import MeshMessage
from ..solar_conditions import get_sun
from .base_command import BaseCommand
+1 -1
View File
@@ -9,7 +9,7 @@ import re
from datetime import datetime
from typing import Any, Optional
from ..models import MeshMessage
from shared.models import MeshMessage
from ..response_template import format_piped_template
from ..utils import calculate_distance, extract_path_node_ids_from_message
from .base_command import BaseCommand
+1 -1
View File
@@ -9,7 +9,7 @@ import re
from typing import Optional
from ..graph_trace_helper import update_mesh_graph_from_trace_data
from ..models import MeshMessage
from shared.models import MeshMessage
from ..trace_runner import RunTraceResult, run_trace
from .base_command import BaseCommand
+1 -1
View File
@@ -6,7 +6,7 @@ Returns the currently running bot version string.
from typing import Any
from ..models import MeshMessage
from shared.models import MeshMessage
from ..version_info import resolve_runtime_version
from .base_command import BaseCommand
+1 -1
View File
@@ -4,7 +4,7 @@ Web Viewer Command
Provides commands to manage the web viewer integration
"""
from ..models import MeshMessage
from shared.models import MeshMessage
from .base_command import BaseCommand
+1 -1
View File
@@ -13,7 +13,7 @@ import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
from ..models import MeshMessage
from shared.models import MeshMessage
from ..utils import (
format_temperature_high_low,
geocode_city_sync,
+1 -1
View File
@@ -27,7 +27,7 @@ from meshcore import EventType
from .channel_manager import ChannelManager
from .command_manager import CommandManager
from .db_manager import AsyncDBManager, DBManager
from shared.db_manager import AsyncDBManager, DBManager
from .feed_manager import FeedManager
from .i18n import Translator
from .message_handler import MessageHandler
+1 -1
View File
@@ -22,7 +22,7 @@ import aiohttp
import feedparser
from modules.feed_filter_eval import item_passes_filter_config
from modules.security_utils import sanitize_input, validate_external_url
from shared.security_utils import sanitize_input, validate_external_url
from modules.url_shortener import _coerce_url_string, shorten_url_sync
+1 -1
View File
@@ -11,7 +11,7 @@ import time
from pathlib import Path
from typing import TYPE_CHECKING, Any, Callable
from .security_utils import validate_external_url
from shared.security_utils import validate_external_url
if TYPE_CHECKING:
pass
+2 -2
View File
@@ -14,8 +14,8 @@ from typing import Any, TypedDict
from .enums import AdvertFlags, DeviceRole, PayloadType, PayloadVersion, RouteType
from .graph_trace_helper import update_mesh_graph_from_trace_data
from .models import MeshMessage
from .security_utils import sanitize_input, sanitize_name
from shared.models import MeshMessage
from shared.security_utils import sanitize_input, sanitize_name
from .utils import (
calculate_packet_hash,
decode_path_len_byte,
+1 -1
View File
@@ -12,7 +12,7 @@ from typing import Any, NamedTuple, Optional
from meshcore import EventType
from .security_utils import sanitize_name, validate_pubkey_format
from shared.security_utils import sanitize_name, validate_pubkey_format
from .utils import rate_limited_nominatim_reverse_sync
+1 -1
View File
@@ -25,7 +25,7 @@ from .scheduled_message_cron import (
parse_schedule_key,
parse_scheduled_message_value,
)
from .security_utils import validate_external_url
from shared.security_utils import validate_external_url
from .utils import decode_escape_sequences, format_keyword_response_with_placeholders, get_config_timezone
@@ -36,7 +36,7 @@ except ImportError:
import contextlib
from ..profanity_filter import censor, contains_profanity
from ..security_utils import sanitize_name
from shared.security_utils import sanitize_name
from .base_service import BaseServicePlugin
+2 -2
View File
@@ -42,7 +42,7 @@ from flask import (
)
from flask_socketio import SocketIO, disconnect, emit
from modules.security_utils import (
from shared.security_utils import (
VALID_JOURNAL_MODES,
validate_external_url,
validate_sql_identifier,
@@ -367,7 +367,7 @@ class BotDataViewer:
"""Initialize database connections"""
try:
# Initialize database manager for metadata access
from modules.db_manager import DBManager
from shared.db_manager import DBManager
# Create a minimal bot object for DBManager
class MinimalBot:
def __init__(self, logger, config, db_manager=None):
+3 -2
View File
@@ -51,7 +51,8 @@ meshcore-viewer = "modules.web_viewer.app:main"
[tool.setuptools]
# Include both the main module and the modules package
py-modules = ["meshcore_bot"]
packages = ["modules", "modules.commands", "modules.commands.alternatives",
packages = ["shared", "shared.parsers",
"modules", "modules.commands", "modules.commands.alternatives",
"modules.commands.alternatives.inactive", "modules.service_plugins", "modules.web_viewer"]
[tool.setuptools.package-data]
@@ -130,7 +131,7 @@ module = [
"modules.message_handler",
"modules.utils",
"modules.plugin_loader",
"modules.security_utils",
"shared.security_utils",
"modules.commands.base_command",
]
disallow_untyped_defs = true
View File
View File
+1 -1
View File
@@ -10,7 +10,7 @@ from modules.commands.joke_command import JokeCommand
from modules.commands.ping_command import PingCommand
from modules.commands.sports_command import SportsCommand
from modules.commands.stats_command import StatsCommand
from modules.models import CHANNEL_REGIONAL_FLOOD_SCOPE_BODY_OVERHEAD, MeshMessage
from shared.models import CHANNEL_REGIONAL_FLOOD_SCOPE_BODY_OVERHEAD, MeshMessage
from tests.conftest import mock_message
+2 -2
View File
@@ -13,9 +13,9 @@ from unittest.mock import AsyncMock, MagicMock, Mock
import pytest
from modules.db_manager import DBManager
from shared.db_manager import DBManager
from modules.mesh_graph import MeshGraph
from modules.models import MeshMessage
from shared.models import MeshMessage
from tests.helpers import create_test_edge, populate_test_graph
+1 -1
View File
@@ -16,7 +16,7 @@ import pytest
from modules.command_manager import CommandManager
from modules.message_handler import MessageHandler
from modules.models import MeshMessage
from shared.models import MeshMessage
# ── helpers ──────────────────────────────────────────────────────────────────
+1 -1
View File
@@ -8,7 +8,7 @@ from unittest.mock import AsyncMock, MagicMock, Mock, patch
import pytest
from modules.command_manager import CommandManager, InternetStatusCache
from modules.models import MeshMessage
from shared.models import MeshMessage
from tests.conftest import mock_message
+1 -1
View File
@@ -14,7 +14,7 @@ from modules.command_manager import CommandManager
from modules.commands.base_command import BaseCommand
from modules.commands.hello_command import HelloCommand
from modules.commands.ping_command import PingCommand
from modules.models import MeshMessage
from shared.models import MeshMessage
class MockTestCommand(BaseCommand):
+1 -1
View File
@@ -6,7 +6,7 @@ from unittest.mock import Mock
import pytest
from modules.db_manager import DBManager
from shared.db_manager import DBManager
@pytest.fixture
+1 -1
View File
@@ -5,7 +5,7 @@ import sqlite3
import pytest
from modules.db_migrations import (
from shared.db_migrations import (
MIGRATIONS,
MigrationRunner,
_add_column,
+1 -1
View File
@@ -8,7 +8,7 @@ from unittest.mock import MagicMock, Mock
import pytest
from modules.db_manager import DBManager
from shared.db_manager import DBManager
from modules.feed_manager import FeedManager
+1 -1
View File
@@ -9,7 +9,7 @@ from unittest.mock import AsyncMock, MagicMock, Mock, patch
import pytest
from modules.db_manager import DBManager
from shared.db_manager import DBManager
from modules.feed_manager import FeedManager
+1 -1
View File
@@ -7,7 +7,7 @@ from unittest.mock import AsyncMock, Mock, patch
import pytest
from modules.message_handler import MessageHandler
from modules.models import MeshMessage
from shared.models import MeshMessage
from tests.conftest import mock_message as make_message
+1 -1
View File
@@ -1,7 +1,7 @@
"""Tests for modules/models.py — MeshMessage dataclass."""
from modules.models import MeshMessage
from shared.models import MeshMessage
class TestMeshMessageDefaults:
+1 -1
View File
@@ -167,7 +167,7 @@ class TestCategoryAndFailed:
# Minimal local plugin source (valid BaseCommand subclass)
_LOCAL_PLUGIN_SOURCE = '''
from modules.commands.base_command import BaseCommand
from modules.models import MeshMessage
from shared.models import MeshMessage
class HelloLocalCommand(BaseCommand):
+18 -18
View File
@@ -6,7 +6,7 @@ from unittest.mock import patch
import pytest
from modules.security_utils import (
from shared.security_utils import (
sanitize_input,
validate_api_key_format,
validate_external_url,
@@ -43,7 +43,7 @@ class TestValidatePubkeyFormat:
class TestValidateSafePath:
"""Tests for validate_safe_path()."""
@patch("modules.security_utils._is_nix_environment", return_value=True)
@patch("shared.security_utils._is_nix_environment", return_value=True)
def test_relative_path_resolution(self, mock_nix, tmp_path):
# Patch Nix check so tmp_path (under /private on macOS) doesn't trigger dangerous path
result = validate_safe_path("subdir/file.db", base_dir=str(tmp_path), allow_absolute=False)
@@ -57,7 +57,7 @@ class TestValidateSafePath:
with pytest.raises(ValueError, match="Path traversal"):
validate_safe_path("/etc/passwd", base_dir=str(tmp_path), allow_absolute=False)
@patch("modules.security_utils._is_nix_environment", return_value=True)
@patch("shared.security_utils._is_nix_environment", return_value=True)
def test_absolute_path_when_allowed(self, mock_nix, tmp_path):
target = tmp_path / "data" / "file.db"
target.parent.mkdir(parents=True, exist_ok=True)
@@ -233,26 +233,26 @@ class TestIsNixEnvironment:
def test_nix_env_var_enables_dangerous_path_access(self, tmp_path):
# When NIX_STORE is set, system-path check is skipped
import modules.security_utils as su
import shared.security_utils as su
with patch.object(su, "_is_nix_environment", return_value=True):
# /proc is dangerous on Linux, but Nix mode should allow it via allow_absolute
result = validate_safe_path(str(tmp_path), base_dir=str(tmp_path), allow_absolute=True)
assert result is not None
def test_non_nix_env_detects_nix_store_var(self):
import modules.security_utils as su
import shared.security_utils as su
with patch.dict(os.environ, {"NIX_STORE": "/nix/store"}, clear=False):
assert su._is_nix_environment() is True
def test_non_nix_env_detects_nix_path_var(self):
import modules.security_utils as su
import shared.security_utils as su
env = {k: v for k, v in os.environ.items()
if k not in ("NIX_STORE", "NIX_PATH", "NIX_REMOTE", "IN_NIX_SHELL")}
with patch.dict(os.environ, {**env, "NIX_PATH": "/nix"}, clear=True):
assert su._is_nix_environment() is True
def test_no_nix_vars_returns_false(self):
import modules.security_utils as su
import shared.security_utils as su
env = {k: v for k, v in os.environ.items()
if k not in ("NIX_STORE", "NIX_PATH", "NIX_REMOTE", "IN_NIX_SHELL")}
with patch.dict(os.environ, env, clear=True):
@@ -282,13 +282,13 @@ class TestValidateSafePathExtra:
"""Additional coverage for validate_safe_path() exception paths."""
def test_dangerous_system_path_rejected_on_linux(self, tmp_path):
import modules.security_utils as su
import shared.security_utils as su
with patch.object(su, "_is_nix_environment", return_value=False):
with pytest.raises(ValueError, match="system directory"):
validate_safe_path("/etc/passwd", allow_absolute=True)
def test_unexpected_exception_wrapped_as_value_error(self, tmp_path):
with patch("modules.security_utils.Path.resolve", side_effect=OSError("disk fail")):
with patch("shared.security_utils.Path.resolve", side_effect=OSError("disk fail")):
with pytest.raises(ValueError, match="Invalid or unsafe file path"):
validate_safe_path("some_file.db", base_dir=str(tmp_path))
@@ -297,39 +297,39 @@ class TestSanitizeName:
"""Tests for sanitize_name() — log-safe identifier sanitization."""
def test_newline_stripped(self):
from modules.security_utils import sanitize_name
from shared.security_utils import sanitize_name
assert "\n" not in sanitize_name("Evil\nNode")
def test_carriage_return_stripped(self):
from modules.security_utils import sanitize_name
from shared.security_utils import sanitize_name
assert "\r" not in sanitize_name("Evil\rNode")
def test_tab_stripped(self):
from modules.security_utils import sanitize_name
from shared.security_utils import sanitize_name
assert "\t" not in sanitize_name("Tab\tNode")
def test_null_byte_stripped(self):
from modules.security_utils import sanitize_name
from shared.security_utils import sanitize_name
assert "\x00" not in sanitize_name("Bad\x00Name")
def test_ansi_escape_stripped(self):
from modules.security_utils import sanitize_name
from shared.security_utils import sanitize_name
assert "\x1b" not in sanitize_name("\x1b[31mRed\x1b[0m")
def test_truncated_to_max_length(self):
from modules.security_utils import sanitize_name
from shared.security_utils import sanitize_name
result = sanitize_name("A" * 100, max_length=64)
assert len(result) <= 64
def test_normal_name_unchanged(self):
from modules.security_utils import sanitize_name
from shared.security_utils import sanitize_name
assert sanitize_name("Alice") == "Alice"
def test_non_string_coerced(self):
from modules.security_utils import sanitize_name
from shared.security_utils import sanitize_name
assert sanitize_name(42) == "42"
def test_negative_max_length_raises(self):
from modules.security_utils import sanitize_name
from shared.security_utils import sanitize_name
with pytest.raises(ValueError):
sanitize_name("test", max_length=-1)
+2 -2
View File
@@ -2326,7 +2326,7 @@ class TestBotIntegrationQueue:
bot.bot_root = str(tmp_path)
# Ensure schema exists (packet_stream is migration-owned).
from modules.db_manager import DBManager
from shared.db_manager import DBManager
class MinimalBot:
def __init__(self, logger, config):
@@ -2358,7 +2358,7 @@ class TestBotIntegrationQueue:
bot.config = cfg
bot.bot_root = str(tmp_path)
from modules.db_manager import DBManager
from shared.db_manager import DBManager
class MinimalBot:
def __init__(self, logger, config):
+1 -1
View File
@@ -6,7 +6,7 @@ from unittest.mock import AsyncMock, MagicMock, Mock, patch
import pytest
from modules.command_manager import CommandManager
from modules.models import MeshMessage
from shared.models import MeshMessage
from modules.service_plugins.base_service import BaseServicePlugin
+1 -1
View File
@@ -9,7 +9,7 @@ from unittest.mock import MagicMock, Mock
import pytest
from modules.commands.path_command import PathCommand
from modules.models import MeshMessage
from shared.models import MeshMessage
@pytest.mark.unit
@@ -6,7 +6,7 @@ from unittest.mock import AsyncMock, MagicMock, Mock, patch
import pytest
from modules.commands.path_command import PathCommand
from modules.models import MeshMessage
from shared.models import MeshMessage
@pytest.mark.unit
+1 -1
View File
@@ -7,7 +7,7 @@ from unittest.mock import MagicMock, Mock
import pytest
from modules.commands.test_command import TestCommand as MeshTestCommand
from modules.models import MeshMessage
from shared.models import MeshMessage
from modules.response_template import format_piped_template
from modules.utils import message_path_bytes_per_hop