安全加固 + 数据管线修复 + 质量改进(第2批)
安全加固: - /api/v1/predict 限流改用 get_client_ip 防 X-Forwarded-For 伪造 - 新增 TRUST_PROXY_HEADERS/REQUIRE_ADMIN_AUTH 配置,默认 fail-closed - 生产环境管理接口未配置鉴权时拒绝(503),不再放行 数据管线修复: - injuries IntegrityError 改用 begin_nested(SAVEPOINT)隔离批次 - injuries 空名单 vs 未配置语义区分(has_data 精确标记) - bzzoiro 统计字段映射(shots/possession/cards) + 入库条件放宽 - available_at 语义收紧(回测防泄漏) - team_names NFKD 去变音双重查找 + 补齐变体键 预测系统改进: - multi-agent 全专家失败时 status=degraded 跳过终裁 - agent_weights 独立持久化到 predictions 表 新增迁移: - 0014_predictions_agent_weights.py 新增测试(8个文件): - test_ip_spoofing.py: 限流防伪造 - test_require_admin_fail_closed.py: 生产 fail-closed - test_injuries_integrity_rollback.py: SAVEPOINT 隔离 - test_injuries_slice_semantic.py: 空名单 vs 未配置 - test_available_at.py: 回测防泄漏 - test_bzzoirot_stats.py: 统计字段映射 - test_team_names_normalize.py: NFKD 变体 - test_multi_agent_degraded.py: 全失败 degraded - test_agent_weights_persist.py: 权重持久化
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
"""回归测试: 生产环境管理接口鉴权 fail-closed。
|
||||
|
||||
验证:
|
||||
1. REQUIRE_ADMIN_AUTH=True + 未配置 → 拒绝(503)
|
||||
2. APP_ENV=production + 未配置 → 拒绝(503)
|
||||
3. development + 未配置 → 放行(fail-open + warning)
|
||||
4. 已配置密码 → 正常验证路径不受影响
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
import pytest
|
||||
from fastapi import HTTPException
|
||||
|
||||
from src.api.deps import require_admin
|
||||
from src.core.config import Settings
|
||||
|
||||
|
||||
class TestRequireAdminFailClosed:
|
||||
"""生产环境 fail-closed 逻辑。"""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_require_admin_auth_true_rejects_when_unconfigured(self):
|
||||
"""REQUIRE_ADMIN_AUTH=True + 未配置 → 503 拒绝。"""
|
||||
mock_request = AsyncMock()
|
||||
mock_request.cookies = {}
|
||||
mock_request.headers = {}
|
||||
|
||||
with patch("src.api.deps.settings", Settings(REQUIRE_ADMIN_AUTH=True, APP_ENV="development")), \
|
||||
patch("src.api.deps.auth_configured", AsyncMock(return_value=False)):
|
||||
|
||||
with pytest.raises(HTTPException) as exc_info:
|
||||
await require_admin(mock_request)
|
||||
|
||||
assert exc_info.value.status_code == 503
|
||||
assert "未配置" in exc_info.value.detail or "鉴权" in exc_info.value.detail
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_production_env_rejects_when_unconfigured(self):
|
||||
"""APP_ENV=production + 未配置 → 503 拒绝。"""
|
||||
mock_request = AsyncMock()
|
||||
mock_request.cookies = {}
|
||||
mock_request.headers = {}
|
||||
|
||||
with patch("src.api.deps.settings", Settings(REQUIRE_ADMIN_AUTH=False, APP_ENV="production")), \
|
||||
patch("src.api.deps.auth_configured", AsyncMock(return_value=False)):
|
||||
|
||||
with pytest.raises(HTTPException) as exc_info:
|
||||
await require_admin(mock_request)
|
||||
|
||||
assert exc_info.value.status_code == 503
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_development_env_allows_when_unconfigured(self):
|
||||
"""development + 未配置 → fail-open 放行。"""
|
||||
mock_request = AsyncMock()
|
||||
mock_request.cookies = {}
|
||||
mock_request.headers = {}
|
||||
|
||||
with patch("src.api.deps.settings", Settings(REQUIRE_ADMIN_AUTH=False, APP_ENV="development")), \
|
||||
patch("src.api.deps.auth_configured", AsyncMock(return_value=False)):
|
||||
|
||||
# 不应抛异常
|
||||
await require_admin(mock_request)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_configured_password_works_normally(self):
|
||||
"""已配置密码 → 正常验证路径(401 未登录,而非 503)。"""
|
||||
mock_request = AsyncMock()
|
||||
mock_request.cookies = {} # 无 cookie
|
||||
mock_request.headers = {} # 无 API Key
|
||||
|
||||
with patch("src.api.deps.settings", Settings(REQUIRE_ADMIN_AUTH=True, APP_ENV="production")), \
|
||||
patch("src.api.deps.auth_configured", AsyncMock(return_value=True)), \
|
||||
patch("src.api.deps.get_session_secret", AsyncMock(return_value=b"secret")):
|
||||
|
||||
with pytest.raises(HTTPException) as exc_info:
|
||||
await require_admin(mock_request)
|
||||
|
||||
# 已配置 → 401(未登录),不是 503(未配置)
|
||||
assert exc_info.value.status_code == 401
|
||||
Reference in New Issue
Block a user