feat: 更新项目进度,完成 Phase 4 和 Phase 5,添加监控与健康检查功能

This commit is contained in:
锦麟 王
2026-02-05 10:32:52 +08:00
parent df76882178
commit 23bf2cfaef
11 changed files with 1588 additions and 41 deletions

220
tests/test_cache.py Normal file
View File

@@ -0,0 +1,220 @@
"""缓存模块测试"""
from __future__ import annotations
import time
import pytest
from minenasai.core.cache import (
MemoryCache,
RateLimiter,
get_rate_limiter,
get_response_cache,
make_cache_key,
)
class TestMemoryCache:
"""MemoryCache 测试"""
def test_set_and_get(self):
"""测试设置和获取"""
cache: MemoryCache[str] = MemoryCache()
cache.set("key1", "value1")
assert cache.get("key1") == "value1"
def test_get_nonexistent(self):
"""测试获取不存在的 key"""
cache: MemoryCache[str] = MemoryCache()
assert cache.get("nonexistent") is None
def test_ttl_expiration(self):
"""测试 TTL 过期"""
cache: MemoryCache[str] = MemoryCache(default_ttl=0.1)
cache.set("key1", "value1")
assert cache.get("key1") == "value1"
time.sleep(0.15)
assert cache.get("key1") is None
def test_custom_ttl(self):
"""测试自定义 TTL"""
cache: MemoryCache[str] = MemoryCache(default_ttl=10.0)
cache.set("key1", "value1", ttl=0.1)
time.sleep(0.15)
assert cache.get("key1") is None
def test_delete(self):
"""测试删除"""
cache: MemoryCache[str] = MemoryCache()
cache.set("key1", "value1")
assert cache.delete("key1") is True
assert cache.get("key1") is None
assert cache.delete("key1") is False
def test_clear(self):
"""测试清空"""
cache: MemoryCache[str] = MemoryCache()
cache.set("key1", "value1")
cache.set("key2", "value2")
count = cache.clear()
assert count == 2
assert cache.get("key1") is None
assert cache.get("key2") is None
def test_exists(self):
"""测试存在检查"""
cache: MemoryCache[str] = MemoryCache()
cache.set("key1", "value1")
assert cache.exists("key1") is True
assert cache.exists("key2") is False
def test_max_size_eviction(self):
"""测试最大容量淘汰"""
cache: MemoryCache[int] = MemoryCache(max_size=5)
for i in range(10):
cache.set(f"key{i}", i)
# 应该只保留部分
assert len(cache._cache) <= 5
def test_hit_tracking(self):
"""测试命中跟踪"""
cache: MemoryCache[str] = MemoryCache()
cache.set("key1", "value1")
cache.get("key1")
cache.get("key1")
cache.get("nonexistent")
stats = cache.get_stats()
assert stats["hits"] == 2
assert stats["misses"] == 1
def test_get_stats(self):
"""测试获取统计"""
cache: MemoryCache[str] = MemoryCache(max_size=100, default_ttl=60.0)
cache.set("key1", "value1")
cache.get("key1")
stats = cache.get_stats()
assert stats["size"] == 1
assert stats["max_size"] == 100
assert stats["default_ttl"] == 60.0
assert "hit_rate" in stats
class TestRateLimiter:
"""RateLimiter 测试"""
def test_acquire_within_limit(self):
"""测试在限制内获取"""
limiter = RateLimiter(rate=10.0, burst=5)
# 可以获取 burst 数量的令牌
for _ in range(5):
assert limiter.acquire() is True
def test_acquire_exceeds_limit(self):
"""测试超出限制"""
limiter = RateLimiter(rate=10.0, burst=2)
assert limiter.acquire() is True
assert limiter.acquire() is True
assert limiter.acquire() is False
def test_token_refill(self):
"""测试令牌补充"""
limiter = RateLimiter(rate=100.0, burst=2)
# 消耗所有令牌
limiter.acquire()
limiter.acquire()
assert limiter.acquire() is False
# 等待补充
time.sleep(0.05)
assert limiter.acquire() is True
def test_available_tokens(self):
"""测试可用令牌数"""
limiter = RateLimiter(rate=10.0, burst=5)
assert limiter.available_tokens == pytest.approx(5.0, abs=0.1)
limiter.acquire(2)
assert limiter.available_tokens == pytest.approx(3.0, abs=0.1)
@pytest.mark.asyncio
async def test_wait(self):
"""测试等待获取"""
limiter = RateLimiter(rate=100.0, burst=1)
limiter.acquire()
start = time.time()
await limiter.wait()
elapsed = time.time() - start
# 应该等待了一小段时间
assert elapsed > 0
class TestCacheKey:
"""make_cache_key 测试"""
def test_same_args_same_key(self):
"""测试相同参数生成相同 key"""
key1 = make_cache_key("a", "b", c=1)
key2 = make_cache_key("a", "b", c=1)
assert key1 == key2
def test_different_args_different_key(self):
"""测试不同参数生成不同 key"""
key1 = make_cache_key("a", "b")
key2 = make_cache_key("a", "c")
assert key1 != key2
def test_kwargs_order_independent(self):
"""测试 kwargs 顺序无关"""
key1 = make_cache_key(a=1, b=2)
key2 = make_cache_key(b=2, a=1)
assert key1 == key2
class TestGlobalInstances:
"""全局实例测试"""
def test_get_response_cache(self):
"""测试获取响应缓存"""
cache = get_response_cache()
assert isinstance(cache, MemoryCache)
def test_get_rate_limiter(self):
"""测试获取速率限制器"""
limiter = get_rate_limiter("test", rate=10.0, burst=20)
assert isinstance(limiter, RateLimiter)
def test_get_rate_limiter_reuse(self):
"""测试速率限制器复用"""
limiter1 = get_rate_limiter("shared")
limiter2 = get_rate_limiter("shared")
assert limiter1 is limiter2

228
tests/test_monitoring.py Normal file
View File

@@ -0,0 +1,228 @@
"""监控模块测试"""
from __future__ import annotations
import pytest
from minenasai.core.monitoring import (
ComponentHealth,
HealthChecker,
HealthStatus,
SystemMetrics,
get_health_checker,
get_metrics,
)
class TestHealthStatus:
"""HealthStatus 测试"""
def test_health_status_values(self):
"""测试健康状态值"""
assert HealthStatus.HEALTHY.value == "healthy"
assert HealthStatus.DEGRADED.value == "degraded"
assert HealthStatus.UNHEALTHY.value == "unhealthy"
class TestSystemMetrics:
"""SystemMetrics 测试"""
def test_initial_metrics(self):
"""测试初始指标"""
metrics = SystemMetrics()
assert metrics.total_requests == 0
assert metrics.successful_requests == 0
assert metrics.failed_requests == 0
assert metrics.active_connections == 0
def test_record_request(self):
"""测试记录请求"""
metrics = SystemMetrics()
metrics.record_request(100.0, success=True)
metrics.record_request(200.0, success=True)
metrics.record_request(50.0, success=False)
assert metrics.total_requests == 3
assert metrics.successful_requests == 2
assert metrics.failed_requests == 1
assert metrics.min_response_time_ms == 50.0
assert metrics.max_response_time_ms == 200.0
def test_avg_response_time(self):
"""测试平均响应时间"""
metrics = SystemMetrics()
metrics.record_request(100.0, success=True)
metrics.record_request(200.0, success=True)
assert metrics.avg_response_time_ms == 150.0
def test_success_rate(self):
"""测试成功率"""
metrics = SystemMetrics()
metrics.record_request(100.0, success=True)
metrics.record_request(100.0, success=True)
metrics.record_request(100.0, success=False)
assert metrics.success_rate == pytest.approx(2 / 3)
def test_record_error(self):
"""测试记录错误"""
metrics = SystemMetrics()
metrics.record_error("ValueError")
metrics.record_error("ValueError")
metrics.record_error("TypeError")
assert metrics.errors_by_type["ValueError"] == 2
assert metrics.errors_by_type["TypeError"] == 1
def test_to_dict(self):
"""测试转换为字典"""
metrics = SystemMetrics()
metrics.record_request(100.0, success=True)
result = metrics.to_dict()
assert "requests" in result
assert "response_time_ms" in result
assert "connections" in result
assert "uptime_seconds" in result
class TestComponentHealth:
"""ComponentHealth 测试"""
def test_component_health(self):
"""测试组件健康状态"""
health = ComponentHealth(
name="test",
status=HealthStatus.HEALTHY,
message="OK",
latency_ms=10.0,
)
assert health.name == "test"
assert health.status == HealthStatus.HEALTHY
assert health.message == "OK"
assert health.latency_ms == 10.0
class TestHealthChecker:
"""HealthChecker 测试"""
def setup_method(self):
"""初始化"""
self.checker = HealthChecker()
@pytest.mark.asyncio
async def test_register_and_check(self):
"""测试注册和检查"""
async def check_ok() -> ComponentHealth:
return ComponentHealth(
name="test",
status=HealthStatus.HEALTHY,
message="OK",
)
self.checker.register("test", check_ok)
result = await self.checker.check_component("test")
assert result.status == HealthStatus.HEALTHY
assert result.latency_ms > 0
@pytest.mark.asyncio
async def test_check_unknown_component(self):
"""测试检查未知组件"""
result = await self.checker.check_component("unknown")
assert result.status == HealthStatus.UNHEALTHY
assert "未知组件" in result.message
@pytest.mark.asyncio
async def test_check_all(self):
"""测试检查所有组件"""
async def check_a() -> ComponentHealth:
return ComponentHealth(name="a", status=HealthStatus.HEALTHY)
async def check_b() -> ComponentHealth:
return ComponentHealth(name="b", status=HealthStatus.HEALTHY)
self.checker.register("a", check_a)
self.checker.register("b", check_b)
results = await self.checker.check_all()
assert len(results) == 2
assert "a" in results
assert "b" in results
@pytest.mark.asyncio
async def test_overall_status_healthy(self):
"""测试总体状态 - 健康"""
async def check_ok() -> ComponentHealth:
return ComponentHealth(name="test", status=HealthStatus.HEALTHY)
self.checker.register("test", check_ok)
await self.checker.check_all()
assert self.checker.get_overall_status() == HealthStatus.HEALTHY
@pytest.mark.asyncio
async def test_overall_status_degraded(self):
"""测试总体状态 - 降级"""
async def check_degraded() -> ComponentHealth:
return ComponentHealth(name="test", status=HealthStatus.DEGRADED)
self.checker.register("test", check_degraded)
await self.checker.check_all()
assert self.checker.get_overall_status() == HealthStatus.DEGRADED
@pytest.mark.asyncio
async def test_overall_status_unhealthy(self):
"""测试总体状态 - 不健康"""
async def check_unhealthy() -> ComponentHealth:
return ComponentHealth(name="test", status=HealthStatus.UNHEALTHY)
self.checker.register("test", check_unhealthy)
await self.checker.check_all()
assert self.checker.get_overall_status() == HealthStatus.UNHEALTHY
@pytest.mark.asyncio
async def test_check_timeout(self):
"""测试检查超时"""
import asyncio
async def slow_check() -> ComponentHealth:
await asyncio.sleep(10) # 超过5秒超时
return ComponentHealth(name="slow", status=HealthStatus.HEALTHY)
self.checker.register("slow", slow_check)
result = await self.checker.check_component("slow")
assert result.status == HealthStatus.UNHEALTHY
assert "超时" in result.message
class TestGlobalInstances:
"""全局实例测试"""
def test_get_metrics(self):
"""测试获取全局指标"""
metrics = get_metrics()
assert isinstance(metrics, SystemMetrics)
def test_get_health_checker(self):
"""测试获取健康检查器"""
checker = get_health_checker()
assert isinstance(checker, HealthChecker)