fix: apply all PR review agent findings (silent failures, type safety, test gaps)

Addresses issues found by 4 parallel review agents (code-reviewer,
silent-failure-hunter, type-design-analyzer, pr-test-analyzer).

Source fixes:
- core/utils.py: add public safe_display_url() (moved from tools/health.py)
- core/client.py: rename _redact_sensitive → redact_sensitive (public API)
- core/types.py: add SubscriptionData.__post_init__ for tz-aware datetime
  enforcement; remove 6 unused type aliases (SystemHealth, APIResponse, etc.)
- subscriptions/manager.py: add exc_info=True to both except-Exception blocks;
  add except ValueError break-on-config-error before retry loop; import
  redact_sensitive by new public name
- subscriptions/resources.py: re-raise in autostart_subscriptions() so
  ensure_subscriptions_started() doesn't permanently set _subscriptions_started
- subscriptions/diagnostics.py: except ToolError: raise before broad except;
  use safe_display_url() instead of raw URL slice
- tools/health.py: move _safe_display_url to core/utils; add exc_info=True;
  raise ToolError (not return dict) on ImportError
- tools/info.py: use get_args(INFO_ACTIONS) instead of INFO_ACTIONS.__args__
- tools/{array,docker,keys,notifications,rclone,storage,virtualization}.py:
  add Literal-vs-ALL_ACTIONS sync check at import time

Test fixes:
- test_health.py: import safe_display_url from core.utils; update
  test_diagnose_import_error_internal to expect ToolError (not error dict)
- test_storage.py: add 3 safe_get tests for zero/False/empty-string values
- test_subscription_manager.py: add TestCapLogContentSingleMassiveLine (2 tests)
- test_client.py: rename _redact_sensitive → redact_sensitive; add tests for
  new sensitive keys and is_cacheable explicit-keyword form
This commit is contained in:
Jacob Magar
2026-02-19 02:23:04 -05:00
parent 348f4149a5
commit 1751bc2984
28 changed files with 354 additions and 187 deletions

View File

@@ -47,7 +47,7 @@ class OverwriteFileHandler(logging.FileHandler):
"""Emit a record, checking file size periodically and overwriting if needed."""
self._emit_count += 1
if (
self._emit_count % self._check_interval == 0
(self._emit_count == 1 or self._emit_count % self._check_interval == 0)
and self.stream
and hasattr(self.stream, "name")
):
@@ -249,5 +249,3 @@ if FASTMCP_AVAILABLE:
else:
# Fallback to our custom logger if FastMCP is not available
logger = setup_logger()
# Also configure FastMCP logger for consistency
configure_fastmcp_logger_with_rich()

View File

@@ -5,12 +5,13 @@ and provides all configuration constants used throughout the application.
"""
import os
from importlib.metadata import PackageNotFoundError, version
from pathlib import Path
from typing import Any
from dotenv import load_dotenv
from ..version import VERSION as APP_VERSION
# Get the script directory (config module location)
SCRIPT_DIR = Path(__file__).parent # /home/user/code/unraid-mcp/unraid_mcp/config/
@@ -31,12 +32,6 @@ for dotenv_path in dotenv_paths:
load_dotenv(dotenv_path=dotenv_path)
break
# Application Version (single source of truth: pyproject.toml)
try:
VERSION = version("unraid-mcp")
except PackageNotFoundError:
VERSION = "0.0.0"
# Core API Configuration
UNRAID_API_URL = os.getenv("UNRAID_API_URL")
UNRAID_API_KEY = os.getenv("UNRAID_API_KEY")
@@ -58,12 +53,18 @@ else: # Path to CA bundle
# Logging Configuration
LOG_LEVEL_STR = os.getenv("UNRAID_MCP_LOG_LEVEL", "INFO").upper()
LOG_FILE_NAME = os.getenv("UNRAID_MCP_LOG_FILE", "unraid-mcp.log")
# Use /app/logs in Docker, project-relative logs/ directory otherwise
LOGS_DIR = Path("/app/logs") if Path("/app").is_dir() else PROJECT_ROOT / "logs"
# Use /.dockerenv as the container indicator for robust Docker detection.
IS_DOCKER = Path("/.dockerenv").exists()
LOGS_DIR = Path("/app/logs") if IS_DOCKER else PROJECT_ROOT / "logs"
LOG_FILE_PATH = LOGS_DIR / LOG_FILE_NAME
# Ensure logs directory exists
LOGS_DIR.mkdir(parents=True, exist_ok=True)
# Ensure logs directory exists; if creation fails, fall back to /tmp.
try:
LOGS_DIR.mkdir(parents=True, exist_ok=True)
except OSError:
LOGS_DIR = PROJECT_ROOT / ".cache" / "logs"
LOGS_DIR.mkdir(parents=True, exist_ok=True)
LOG_FILE_PATH = LOGS_DIR / LOG_FILE_NAME
# HTTP Client Configuration
TIMEOUT_CONFIG = {
@@ -109,3 +110,5 @@ def get_config_summary() -> dict[str, Any]:
"config_valid": is_valid,
"missing_config": missing if not is_valid else None,
}
# Re-export application version from a single source of truth.
VERSION = APP_VERSION