test: add comprehensive tests for critical untested files and fix deprecated warnings

- Add tests for migrate_logs_to_steps.py, upload_to_paperless.py, dropbox API,
  upload_to_dropbox.py, upload_to_nextcloud.py, upload_to_onedrive.py,
  upload_with_rclone.py, and config_validator.py
- Fix PydanticDeprecatedSince20: @validator → @field_validator in config.py, url_upload.py
- Fix PydanticDeprecatedSince20: class Config → model_config = SettingsConfigDict
- Fix PydanticDeprecatedSince211: filter Pydantic internals in settings_display.py
- Fix MovedIn20Warning: use sqlalchemy.orm.declarative_base instead of ext.declarative

Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-02-12 17:32:17 +00:00
parent fca5236b47
commit dff32e08e1
12 changed files with 2840 additions and 35 deletions
+3 -2
View File
@@ -12,7 +12,7 @@ from typing import Optional
import requests
from fastapi import APIRouter, HTTPException, Request
from pydantic import BaseModel, HttpUrl, validator
from pydantic import BaseModel, HttpUrl, field_validator
from app.auth import require_login
from app.config import settings
@@ -31,7 +31,8 @@ class URLUploadRequest(BaseModel):
url: HttpUrl
filename: Optional[str] = None
@validator("url")
@field_validator("url")
@classmethod
def validate_url_scheme(cls, v):
"""Ensure only HTTP/HTTPS schemes are allowed"""
parsed = urllib.parse.urlparse(str(v))
+11 -30
View File
@@ -3,11 +3,13 @@
import os
from typing import Any, List, Optional, Union
from pydantic import Field, validator
from pydantic_settings import BaseSettings
from pydantic import Field, field_validator
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
model_config = SettingsConfigDict(env_file=".env")
database_url: str
redis_url: str
openai_api_key: str
@@ -265,7 +267,8 @@ class Settings(BaseSettings):
description="Stricter rate limit for authentication endpoints to prevent brute force attacks.",
)
@validator("notification_urls", pre=True)
@field_validator("notification_urls", mode="before")
@classmethod
def parse_notification_urls(cls, v):
"""Parse notification URLs from string or list"""
if isinstance(v, str):
@@ -276,12 +279,13 @@ class Settings(BaseSettings):
return []
return v
@validator("session_secret")
def validate_session_secret(cls, v, values):
@field_validator("session_secret")
@classmethod
def validate_session_secret(cls, v, info):
"""Validate that session_secret is set and has sufficient length when auth is enabled"""
if values.get("auth_enabled") and not v:
if info.data.get("auth_enabled") and not v:
raise ValueError("SESSION_SECRET must be set when AUTH_ENABLED=True")
if values.get("auth_enabled") and v and len(v) < 32:
if info.data.get("auth_enabled") and v and len(v) < 32:
raise ValueError("SESSION_SECRET must be at least 32 characters long")
return v
@@ -347,28 +351,5 @@ class Settings(BaseSettings):
# Return basic info if file not found
return f"Version: {self.version}\nBuild Date: {self.build_date}\nGit SHA: {self.git_sha}"
class Config:
env_file = ".env"
# Convert string representations of booleans to actual booleans
# and strip quotes from string values
@classmethod
def parse_env_var(cls, field_name: str, raw_val: str) -> Any:
# First, strip quotes from the value if it's a string
if isinstance(raw_val, str):
if (raw_val.startswith('"') and raw_val.endswith('"')) or (
raw_val.startswith("'") and raw_val.endswith("'")
):
raw_val = raw_val[1:-1]
raw_val = raw_val.strip()
# Convert string representations of booleans to actual booleans
if field_name.endswith("_enabled") or field_name == "debug":
if raw_val.lower() in ("false", "0", "no", "n", "f"):
return False
if raw_val.lower() in ("true", "1", "yes", "y", "t"):
return True
return raw_val
settings = Settings()
+1 -1
View File
@@ -5,7 +5,7 @@ import os
from sqlalchemy import create_engine, exc
from sqlalchemy.engine.url import make_url
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import declarative_base
from sqlalchemy.orm import sessionmaker
from app.config import settings
@@ -9,12 +9,15 @@ from app.utils.config_validator.masking import mask_sensitive_value
logger = logging.getLogger(__name__)
# Pydantic model attributes that should not be iterated as user settings
_PYDANTIC_INTERNALS = {"model_computed_fields", "model_config", "model_extra", "model_fields", "model_fields_set"}
def dump_all_settings():
"""Log all settings values for diagnostic purposes"""
logger.info("--- DUMPING ALL SETTINGS FOR DIAGNOSTIC PURPOSES ---")
for key in dir(settings):
if not key.startswith("_") and not callable(getattr(settings, key)):
if not key.startswith("_") and key not in _PYDANTIC_INTERNALS and not callable(getattr(settings, key)):
value = getattr(settings, key)
# Mask sensitive values in logs
if (
@@ -190,8 +193,8 @@ def get_settings_for_display(show_values=False):
key
for key in dir(settings)
if not key.startswith("_")
and key not in _PYDANTIC_INTERNALS
and not callable(getattr(settings, key))
and key not in ["model_computed_fields", "model_config", "model_extra", "model_fields", "model_fields_set"]
]
)