747dd55225
- deps 新增 list_active_pm_study_ids、is_active_project_pm 与 require_admin_or_any_project_pm 依赖,用于把 PM 项目范围带进鉴权与 监控。get_cra_site_scope 内部延后导入 site CRUD,避免循环依赖。 - system_permissions API 改用 PM/ADMIN 双角色入口,permissions/monitoring 系统级权限新增 PM 配额,并细化访问日志、告警、监控指标的可见范围。 - members API 调整:项目 PM 仅可管理低于 PM 的项目角色,禁止互相 改写或授予 PM。 - api_permissions API 增加 GET /api-permissions/me,返回当前用户在该 项目的有效权限矩阵;保存权限矩阵时校验 PM 行为不被篡改。 - core/api_permissions:新增立项配置接口键、PM 默认拥有的监控/权限 系统级条目,并在权限元信息中标注 PM 共享角色。 - core/project_permissions:role_has_api_permission 命中默认角色矩阵; replace_api_endpoint_permissions 改为部分更新且永不持久化 ADMIN/PM。 - studies setup-config 各端点改用接口级权限装饰器,与新的 setup_config 权限键对齐。permission_monitor 新增 get_metrics 摘要供 PM 视图调用。 - 测试:新增 test_admin_pm_permissions 覆盖 PM 系统级权限、监控范围和 成员管理边界;conftest 兼容 SA_UUID 列;权限相关用例同步移除已失效 的 module_permission 链路。 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
198 lines
5.0 KiB
Python
198 lines
5.0 KiB
Python
"""监控测试:权限系统内存监控职责验证。"""
|
|
|
|
import time
|
|
|
|
import pytest
|
|
|
|
from app.core.permission_monitor import (
|
|
CacheMetrics,
|
|
PermissionMonitor,
|
|
get_permission_monitor,
|
|
set_permission_monitor,
|
|
)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_cache_metrics():
|
|
"""测试缓存指标"""
|
|
monitor = PermissionMonitor()
|
|
|
|
monitor.record_cache_hit()
|
|
monitor.record_cache_hit()
|
|
monitor.record_cache_miss()
|
|
monitor.record_cache_hit()
|
|
|
|
metrics = monitor.metrics.cache_metrics
|
|
assert metrics.total_accesses == 4
|
|
assert metrics.cache_hits == 3
|
|
assert metrics.cache_misses == 1
|
|
assert metrics.hit_rate == pytest.approx(75.0, 0.1)
|
|
assert metrics.miss_rate == pytest.approx(25.0, 0.1)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_cache_invalidation_tracking():
|
|
"""测试缓存失效追踪"""
|
|
monitor = PermissionMonitor()
|
|
|
|
monitor.record_cache_invalidation()
|
|
monitor.record_cache_invalidation()
|
|
monitor.record_cache_invalidation()
|
|
|
|
assert monitor.metrics.cache_metrics.cache_invalidations == 3
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_slow_check_alert_generation():
|
|
"""测试慢权限检查告警生成"""
|
|
monitor = PermissionMonitor()
|
|
|
|
monitor.record_slow_check_alert(100)
|
|
|
|
alerts = monitor.get_alerts()
|
|
assert len(alerts) == 1
|
|
assert alerts[0]["level"] == "warning"
|
|
assert alerts[0]["type"] == "slow_permission_check"
|
|
assert alerts[0]["data"]["elapsed_ms"] == 100
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_slow_check_alert_ignores_fast_checks():
|
|
"""未超过阈值的权限检查不应生成告警"""
|
|
monitor = PermissionMonitor()
|
|
|
|
monitor.record_slow_check_alert(10)
|
|
|
|
assert monitor.get_alerts() == []
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_error_alert_generation():
|
|
"""测试错误告警生成"""
|
|
monitor = PermissionMonitor()
|
|
|
|
monitor.record_error_alert(ValueError("test error"))
|
|
|
|
alerts = monitor.get_alerts()
|
|
assert len(alerts) == 1
|
|
assert alerts[0]["level"] == "error"
|
|
assert alerts[0]["type"] == "permission_check_error"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_alert_filtering():
|
|
"""测试告警过滤"""
|
|
monitor = PermissionMonitor()
|
|
|
|
monitor.record_slow_check_alert(100)
|
|
monitor.record_error_alert(ValueError("test error"))
|
|
|
|
warning_alerts = monitor.get_alerts(level="warning")
|
|
assert len(warning_alerts) == 1
|
|
assert all(alert["level"] == "warning" for alert in warning_alerts)
|
|
|
|
error_alerts = monitor.get_alerts(level="error")
|
|
assert len(error_alerts) == 1
|
|
assert all(alert["level"] == "error" for alert in error_alerts)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_alert_limit():
|
|
"""测试告警数量限制"""
|
|
monitor = PermissionMonitor()
|
|
|
|
for _ in range(50):
|
|
monitor.record_slow_check_alert(100)
|
|
|
|
assert len(monitor.get_alerts(limit=10)) == 10
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_metrics_reset():
|
|
"""测试指标重置"""
|
|
monitor = PermissionMonitor()
|
|
|
|
monitor.record_cache_hit()
|
|
assert monitor.metrics.cache_metrics.total_accesses == 1
|
|
|
|
monitor.reset_metrics()
|
|
|
|
assert monitor.metrics.cache_metrics.total_accesses == 0
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_alerts_clear():
|
|
"""测试告警清除"""
|
|
monitor = PermissionMonitor()
|
|
|
|
monitor.record_slow_check_alert(100)
|
|
assert len(monitor.get_alerts()) == 1
|
|
|
|
monitor.clear_alerts()
|
|
|
|
assert monitor.get_alerts() == []
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_metrics_to_dict():
|
|
"""测试指标转换为字典"""
|
|
monitor = PermissionMonitor()
|
|
|
|
monitor.record_cache_hit()
|
|
monitor.record_cache_miss()
|
|
|
|
metrics_dict = monitor.get_metrics()
|
|
|
|
assert "cache_metrics" in metrics_dict
|
|
assert "uptime_seconds" in metrics_dict
|
|
assert metrics_dict["cache_metrics"]["total_accesses"] == 2
|
|
assert metrics_dict["cache_metrics"]["cache_hits"] == 1
|
|
assert metrics_dict["cache_metrics"]["cache_misses"] == 1
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_global_monitor_instance():
|
|
"""测试全局监控器实例"""
|
|
monitor1 = get_permission_monitor()
|
|
monitor2 = get_permission_monitor()
|
|
|
|
assert monitor1 is monitor2
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_set_global_monitor():
|
|
"""测试设置全局监控器实例"""
|
|
new_monitor = PermissionMonitor()
|
|
set_permission_monitor(new_monitor)
|
|
|
|
assert get_permission_monitor() is new_monitor
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_alert_timestamp():
|
|
"""测试告警时间戳"""
|
|
monitor = PermissionMonitor()
|
|
|
|
before_time = time.time()
|
|
monitor.record_slow_check_alert(100)
|
|
after_time = time.time()
|
|
|
|
alerts = monitor.get_alerts()
|
|
assert before_time <= alerts[0]["timestamp"] <= after_time
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_cache_metrics_dataclass():
|
|
"""测试缓存指标数据类"""
|
|
metrics = CacheMetrics()
|
|
|
|
assert metrics.total_accesses == 0
|
|
assert metrics.hit_rate == 0.0
|
|
|
|
metrics.total_accesses = 100
|
|
metrics.cache_hits = 80
|
|
metrics.cache_misses = 20
|
|
|
|
assert metrics.hit_rate == pytest.approx(80.0, 0.1)
|
|
assert metrics.miss_rate == pytest.approx(20.0, 0.1)
|