mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 09:20:22 +08:00
feat(pool,scheduling): 号池调度维度、配额冷却机制与管理后台重构
- 新增 scheduling_dimensions 模块,为每个 Key 计算多维调度状态(手动/冷却/熔断/成本/健康) - 新增 quota_cooldown 模块,统一判定 Key 的有效冷却原因 - Pool 管理后台 API 扩展 Key 详情字段(调度状态/维度/配额/OAuth 信息) - 前端 Pool 管理页面重写,支持调度状态展示、批量清理封禁 Key - Handler 基类增加请求调度元数据采集,stream telemetry 增强 - 请求时间线组件增强,支持 attempted 候选展示 - Kiro OAuth 凭证导入解析改进 - 新增 usage 表 provider_key 索引迁移 - 补充调度维度、配额冷却、候选枚举等单元测试 Closes #197 Co-authored-by: AAEE86 <33052466+AAEE86@users.noreply.github.com>
This commit is contained in:
94
tests/services/test_pool_scheduling_dimensions.py
Normal file
94
tests/services/test_pool_scheduling_dimensions.py
Normal file
@@ -0,0 +1,94 @@
|
||||
"""Tests for pool scheduling dimension evaluation."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from src.services.provider.pool.scheduling_dimensions import (
|
||||
PoolSchedulingDimensionResult,
|
||||
PoolSchedulingSnapshot,
|
||||
evaluate_pool_scheduling_dimensions,
|
||||
list_pool_scheduling_dimensions,
|
||||
summarize_pool_scheduling_dimensions,
|
||||
)
|
||||
|
||||
|
||||
def _snapshot(**overrides: object) -> PoolSchedulingSnapshot:
|
||||
base = {
|
||||
"is_active": True,
|
||||
"cooldown_reason": None,
|
||||
"cooldown_ttl_seconds": None,
|
||||
"circuit_breaker_open": False,
|
||||
"cost_window_usage": 1200,
|
||||
"cost_limit": 10000,
|
||||
"cost_soft_threshold_percent": 80,
|
||||
"health_score": 0.95,
|
||||
}
|
||||
base.update(overrides)
|
||||
return PoolSchedulingSnapshot(**base) # type: ignore[arg-type]
|
||||
|
||||
|
||||
def test_default_dimension_registry_contains_core_dimensions() -> None:
|
||||
names = list_pool_scheduling_dimensions()
|
||||
assert names == ("manual", "cooldown", "circuit", "cost", "health")
|
||||
|
||||
|
||||
def test_summary_available_when_all_dimensions_ok() -> None:
|
||||
dimensions = evaluate_pool_scheduling_dimensions(_snapshot())
|
||||
summary = summarize_pool_scheduling_dimensions(dimensions)
|
||||
|
||||
assert summary.status == "available"
|
||||
assert summary.reason == "available"
|
||||
assert summary.candidate_eligible is True
|
||||
assert summary.blocked_count == 0
|
||||
assert summary.degraded_count == 0
|
||||
assert summary.score == 100.0
|
||||
|
||||
|
||||
def test_summary_blocked_when_manual_disabled() -> None:
|
||||
dimensions = evaluate_pool_scheduling_dimensions(_snapshot(is_active=False))
|
||||
summary = summarize_pool_scheduling_dimensions(dimensions)
|
||||
|
||||
assert summary.status == "blocked"
|
||||
assert summary.reason == "manual_disabled"
|
||||
assert summary.candidate_eligible is False
|
||||
assert summary.blocked_count >= 1
|
||||
assert summary.score < 100.0
|
||||
|
||||
|
||||
def test_summary_degraded_when_cost_reaches_soft_threshold() -> None:
|
||||
dimensions = evaluate_pool_scheduling_dimensions(
|
||||
_snapshot(cost_window_usage=8200, cost_limit=10000, cost_soft_threshold_percent=80)
|
||||
)
|
||||
summary = summarize_pool_scheduling_dimensions(dimensions)
|
||||
|
||||
assert summary.status == "degraded"
|
||||
assert summary.reason == "cost_soft"
|
||||
assert summary.candidate_eligible is True
|
||||
assert summary.blocked_count == 0
|
||||
assert summary.degraded_count >= 1
|
||||
|
||||
|
||||
def test_summary_blocked_on_cooldown_even_if_other_dimensions_ok() -> None:
|
||||
dimensions = evaluate_pool_scheduling_dimensions(
|
||||
_snapshot(cooldown_reason="rate_limited_429", cooldown_ttl_seconds=120)
|
||||
)
|
||||
summary = summarize_pool_scheduling_dimensions(dimensions)
|
||||
|
||||
assert summary.status == "blocked"
|
||||
assert summary.reason == "cooldown"
|
||||
assert summary.candidate_eligible is False
|
||||
assert summary.blocked_count >= 1
|
||||
|
||||
|
||||
def test_empty_summary_defaults_to_available() -> None:
|
||||
summary = summarize_pool_scheduling_dimensions([])
|
||||
assert summary.status == "available"
|
||||
assert summary.reason == "available"
|
||||
assert summary.score == 100.0
|
||||
|
||||
|
||||
def test_dimension_result_keeps_degraded_health_details() -> None:
|
||||
dimensions = evaluate_pool_scheduling_dimensions(_snapshot(health_score=0.65))
|
||||
health = next((item for item in dimensions if item.code == "health_degraded"), None)
|
||||
assert isinstance(health, PoolSchedulingDimensionResult)
|
||||
assert health.status == "degraded"
|
||||
assert health.detail == "0.65"
|
||||
Reference in New Issue
Block a user