mirror of
https://github.com/agessaman/meshcore-bot.git
synced 2026-06-03 14:24:03 +00:00
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:
+1
-1
@@ -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:
|
||||
|
||||
@@ -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."
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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."]
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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 ──────────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -5,7 +5,7 @@ import sqlite3
|
||||
|
||||
import pytest
|
||||
|
||||
from modules.db_migrations import (
|
||||
from shared.db_migrations import (
|
||||
MIGRATIONS,
|
||||
MigrationRunner,
|
||||
_add_column,
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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,7 +1,7 @@
|
||||
"""Tests for modules/models.py — MeshMessage dataclass."""
|
||||
|
||||
|
||||
from modules.models import MeshMessage
|
||||
from shared.models import MeshMessage
|
||||
|
||||
|
||||
class TestMeshMessageDefaults:
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user