feat: 更新项目进度,完成 Phase 4 和 Phase 5,添加监控与健康检查功能
This commit is contained in:
220
tests/test_cache.py
Normal file
220
tests/test_cache.py
Normal 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
228
tests/test_monitoring.py
Normal 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)
|
||||
Reference in New Issue
Block a user