feat: comprehensive test infrastructure improvements
- Fix unit test imports: add env setup in conftest.py before module imports - Add 24 new auth router tests (RBAC, preferences, password validation) - Add 16 new tests for litellm and chat_summary routers - Apply black formatting and ruff linting across codebase - Add pre-commit hooks configuration (black, ruff, file checks) - Increase CI coverage threshold from 40% to 50% Test Results: - 206 tests passing (91 unit + 115 integration) - Coverage: 58.79% on core modules - auth.py: 57% → 85%, litellm.py: 23% → 87%, chat_summary.py: 41% → 100% - auth_service: 96.51%, config: 100%, rbac: 93.48%
This commit is contained in:
@@ -1,16 +1,18 @@
|
||||
"""Core authentication endpoints: login, token refresh, user info, proxy auth."""
|
||||
from datetime import timedelta
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
from datetime import timedelta
|
||||
|
||||
import httpx
|
||||
from typing import Optional
|
||||
from fastapi import APIRouter, Body, Depends, HTTPException, status, Request, Response
|
||||
import jwt
|
||||
from fastapi import APIRouter, Body, Depends, HTTPException, Request, Response, status
|
||||
from pydantic import BaseModel
|
||||
from slowapi import Limiter
|
||||
from slowapi.util import get_remote_address
|
||||
import jwt
|
||||
import config
|
||||
|
||||
import auth_service as auth
|
||||
import logging
|
||||
import config
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
router = APIRouter()
|
||||
@@ -40,7 +42,7 @@ def _set_auth_response_tokens(response: Response, access_token: str, refresh_tok
|
||||
auth.set_auth_cookies(response, access_token, refresh_token)
|
||||
|
||||
|
||||
def _extract_refresh_token(request: Request, req: Optional[RefreshRequest] = None) -> str:
|
||||
def _extract_refresh_token(request: Request, req: RefreshRequest | None = None) -> str:
|
||||
cookie_token = request.cookies.get(auth.REFRESH_COOKIE_NAME, "")
|
||||
if cookie_token:
|
||||
return cookie_token
|
||||
@@ -87,7 +89,7 @@ async def send_failed_login_alert(ip_address: str, username: str, reason: str):
|
||||
res = await client.post(
|
||||
f"https://api.telegram.org/bot{config.TELEGRAM_BOT_TOKEN}/sendMessage",
|
||||
data={"chat_id": config.TELEGRAM_CHAT_ID, "text": msg, "parse_mode": "Markdown"},
|
||||
timeout=10.0
|
||||
timeout=10.0,
|
||||
)
|
||||
logger.info("Telegram alert sent, status: %s", res.status_code)
|
||||
except Exception as e:
|
||||
@@ -147,6 +149,7 @@ async def proxy_auth(request: Request, response: Response):
|
||||
"""Auto-login when Authelia has already authenticated the user via forward-auth.
|
||||
Caddy sets Remote-User and Remote-Groups headers after successful Authelia verification."""
|
||||
from rbac import resolve_role
|
||||
|
||||
if not config.is_trusted_proxy(request.client.host):
|
||||
logger.warning(f"Rejected proxy auth attempt from unauthorized IP: {request.client.host}")
|
||||
raise HTTPException(status_code=403, detail="Unauthorized proxy source")
|
||||
@@ -196,7 +199,7 @@ async def read_users_me(current_user=Depends(auth.get_current_user)):
|
||||
|
||||
@router.post("/refresh")
|
||||
@limiter.limit("10/minute")
|
||||
async def refresh_token(request: Request, response: Response, req: Optional[RefreshRequest] = Body(default=None)):
|
||||
async def refresh_token(request: Request, response: Response, req: RefreshRequest | None = Body(default=None)):
|
||||
refresh_token_value = _extract_refresh_token(request, req)
|
||||
access_token, refresh_token = await _refresh_tokens_from_value(refresh_token_value)
|
||||
_set_auth_response_tokens(response, access_token, refresh_token)
|
||||
@@ -232,6 +235,7 @@ async def rbac_config(current_user=Depends(auth.get_current_user)):
|
||||
if current_user.role != "admin":
|
||||
raise HTTPException(status_code=403, detail="Admin only")
|
||||
from rbac import load_rbac
|
||||
|
||||
return load_rbac()
|
||||
|
||||
|
||||
@@ -240,6 +244,7 @@ async def rbac_set_override(username: str, request: Request, current_user=Depend
|
||||
if current_user.role != "admin":
|
||||
raise HTTPException(status_code=403, detail="Admin only")
|
||||
from rbac import update_rbac
|
||||
|
||||
body = await request.json()
|
||||
pages = body.get("pages")
|
||||
if pages is None:
|
||||
@@ -257,6 +262,7 @@ async def rbac_set_override(username: str, request: Request, current_user=Depend
|
||||
@router.get("/preferences")
|
||||
async def get_preferences(current_user=Depends(auth.get_current_user)):
|
||||
from rbac import get_sidebar_order
|
||||
|
||||
order = get_sidebar_order(current_user.username)
|
||||
return {"sidebar_order": order}
|
||||
|
||||
@@ -264,7 +270,9 @@ async def get_preferences(current_user=Depends(auth.get_current_user)):
|
||||
@router.put("/preferences")
|
||||
async def save_preferences(request: Request, current_user=Depends(auth.get_current_user)):
|
||||
import traceback
|
||||
|
||||
from rbac import save_sidebar_order
|
||||
|
||||
try:
|
||||
body = await request.json()
|
||||
order = body.get("sidebar_order")
|
||||
@@ -298,6 +306,7 @@ async def rbac_update_role(role: str, request: Request, current_user=Depends(aut
|
||||
if current_user.role != "admin":
|
||||
raise HTTPException(status_code=403, detail="Admin only")
|
||||
from rbac import update_rbac
|
||||
|
||||
body = await request.json()
|
||||
pages = body.get("pages")
|
||||
sidebar_links = body.get("sidebar_links")
|
||||
|
||||
Reference in New Issue
Block a user