feat: auto committed

This commit is contained in:
SpecialX
2026-07-10 18:57:39 +08:00
parent c09d6fb7d2
commit 9ea81f1bd7
96 changed files with 13000 additions and 392 deletions

View File

@@ -0,0 +1,148 @@
"""用量记录 + 配额管理 + Kafka 生产者测试."""
import pytest
from src.ai.errors import AIQuotaExceededError
from src.ai.usage.kafka_producer import KafkaProducer, UsageEvent
from src.ai.usage.quota_enforcer import QuotaEnforcer, QuotaStatus
from src.ai.usage.usage_recorder import UsageRecord, UsageRecorder
class TestUsageRecorder:
"""UsageRecorder 测试Redis=None 降级模式)."""
async def test_record_no_redis_skipped(self) -> None:
"""Redis=None 时跳过记录."""
recorder = UsageRecorder(redis=None)
record = UsageRecord(
user_id="u-1",
school_id="s-1",
provider="openai",
model="gpt-4o",
operation="chat",
total_tokens=100,
)
await recorder.record(record) # 不抛异常即通过
async def test_get_user_usage_no_redis(self) -> None:
"""Redis=None 时返回 0."""
recorder = UsageRecorder(redis=None)
usage = await recorder.get_user_usage("u-1")
assert usage == 0
async def test_get_school_usage_no_redis(self) -> None:
"""Redis=None 时返回 0."""
recorder = UsageRecorder(redis=None)
usage = await recorder.get_school_usage("s-1")
assert usage == 0
class TestQuotaEnforcer:
"""QuotaEnforcer 测试."""
async def test_within_budget_allowed(self) -> None:
"""预算内允许."""
recorder = UsageRecorder(redis=None)
enforcer = QuotaEnforcer(
usage_recorder=recorder,
user_monthly_budget=100_000,
school_monthly_budget=1_000_000,
)
statuses = await enforcer.check("u-1", "s-1")
assert len(statuses) == 2
assert all(s.allowed for s in statuses)
async def test_exceeds_budget_raises(self) -> None:
"""超预算抛 AIQuotaExceededError."""
recorder = UsageRecorder(redis=None)
enforcer = QuotaEnforcer(
usage_recorder=recorder,
user_monthly_budget=0, # budget=0 → used(0) < 0 is False
)
# used=0, budget=0 → allowed = 0 < 0 = False
with pytest.raises(AIQuotaExceededError):
await enforcer.check("u-1")
async def test_no_user_id_no_check(self) -> None:
"""无 user_id 返回空列表."""
recorder = UsageRecorder(redis=None)
enforcer = QuotaEnforcer(usage_recorder=recorder)
statuses = await enforcer.check("")
assert statuses == []
async def test_only_user(self) -> None:
"""仅检查 user."""
recorder = UsageRecorder(redis=None)
enforcer = QuotaEnforcer(usage_recorder=recorder)
statuses = await enforcer.check("u-1")
assert len(statuses) == 1
assert statuses[0].scope == "user"
def test_quota_status_usage_percentage(self) -> None:
"""usage_percentage 计算."""
status = QuotaStatus(allowed=True, scope="user", used=50, budget=200, remaining=150)
assert status.usage_percentage == 25.0
def test_quota_status_zero_budget(self) -> None:
"""budget=0 时 usage_percentage=0."""
status = QuotaStatus(allowed=True, scope="user", used=10, budget=0, remaining=0)
assert status.usage_percentage == 0.0
class TestUsageEvent:
"""UsageEvent dataclass 测试."""
def test_to_dict_fills_defaults(self) -> None:
"""to_dict 填充 event_id + occurred_at."""
event = UsageEvent(user_id="u-1", operation="chat")
data = event.to_dict()
assert data["event_id"] # 自动生成
assert data["occurred_at"] > 0 # 自动填充
assert data["user_id"] == "u-1"
def test_from_usage_record(self) -> None:
"""from_usage_record 工厂方法."""
event = UsageEvent.from_usage_record(
user_id="u-1",
school_id="s-1",
request_id="r-1",
provider="openai",
model="gpt-4o",
operation="chat",
prompt_tokens=10,
completion_tokens=20,
total_tokens=30,
latency_ms=500,
)
assert event.event_id
assert event.aggregate_id == "r-1"
assert event.event_type == "AIUsageRecorded"
assert event.total_tokens == 30
class TestKafkaProducer:
"""KafkaProducer 测试(降级模式,不连真实 Kafka."""
async def test_start_without_kafka_degrades(self) -> None:
"""Kafka 不可用时降级."""
producer = KafkaProducer(bootstrap_servers="localhost:9999")
await producer.start()
assert producer.is_started is False
async def test_publish_not_started_skipped(self) -> None:
"""未启动时 publish 跳过."""
producer = KafkaProducer()
event = UsageEvent(user_id="u-1")
await producer.publish(event) # 不抛异常
async def test_stop_when_not_started(self) -> None:
"""未启动时 stop 不抛异常."""
producer = KafkaProducer()
await producer.stop()
async def test_stop_after_failed_start(self) -> None:
"""启动失败后 stop 不抛异常."""
producer = KafkaProducer(bootstrap_servers="localhost:9999")
await producer.start()
await producer.stop()
assert producer.is_started is False