mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-03 01:40:21 +08:00
feat(pool): 号池额度主动探测、封禁自动清除、调度硬优先级与前端重构
- 新增 PoolQuotaProbeScheduler,按 probing_interval_minutes 主动探测静默 Key 额度 - pool_advanced 增加 probing_enabled / auto_remove_banned_keys 配置项 - error_handler 和 quota_service 支持封禁 Key 自动删除及缓存清理 - multi_score 策略从加权混合重构为硬优先级排序,引入 mutex_group 互斥组 - 指纹注入从 handler 层下移至 ClaudeCode envelope 层 - OAuth 批量导入支持 concurrency 并发参数 - 前端号池管理拆分高级设置/账号批量/代理设置为独立组件 - 号池总览接口精简,仅返回已启用调度的 Provider
This commit is contained in:
88
tests/services/test_pool_quota_probe_scheduler.py
Normal file
88
tests/services/test_pool_quota_probe_scheduler.py
Normal file
@@ -0,0 +1,88 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from types import SimpleNamespace
|
||||
|
||||
from src.services.provider_keys.pool_quota_probe_scheduler import _select_probe_key_ids
|
||||
|
||||
|
||||
def _key(
|
||||
key_id: str,
|
||||
*,
|
||||
last_used_at: datetime | None = None,
|
||||
upstream_metadata: dict | None = None,
|
||||
) -> SimpleNamespace:
|
||||
return SimpleNamespace(
|
||||
id=key_id,
|
||||
last_used_at=last_used_at,
|
||||
upstream_metadata=upstream_metadata or {},
|
||||
)
|
||||
|
||||
|
||||
def test_select_probe_key_ids_selects_silent_keys_only() -> None:
|
||||
now = datetime(2026, 3, 5, 12, 0, 0, tzinfo=timezone.utc)
|
||||
now_ts = int(now.timestamp())
|
||||
|
||||
keys = [
|
||||
_key("k1"), # never used, should be probed
|
||||
_key("k2", last_used_at=now - timedelta(minutes=2)), # recently used, skip
|
||||
_key(
|
||||
"k3",
|
||||
upstream_metadata={"codex": {"updated_at": now_ts - (20 * 60)}},
|
||||
), # long-time no refresh, should be probed
|
||||
]
|
||||
|
||||
selected = _select_probe_key_ids(
|
||||
keys=keys, # type: ignore[arg-type]
|
||||
provider_type="codex",
|
||||
now_ts=now_ts,
|
||||
interval_seconds=10 * 60,
|
||||
last_probe_timestamps={},
|
||||
limit=0,
|
||||
)
|
||||
assert selected == ["k1", "k3"]
|
||||
|
||||
|
||||
def test_select_probe_key_ids_resets_probe_window_after_key_usage() -> None:
|
||||
now = datetime(2026, 3, 5, 12, 0, 0, tzinfo=timezone.utc)
|
||||
now_ts = int(now.timestamp())
|
||||
|
||||
keys = [
|
||||
_key(
|
||||
"k1",
|
||||
last_used_at=now - timedelta(seconds=30),
|
||||
upstream_metadata={"codex": {"updated_at": now_ts - (40 * 60)}},
|
||||
)
|
||||
]
|
||||
|
||||
# 上一次主动探测非常早,但 key 刚刚被真实流量使用,应跳过本次探测
|
||||
selected = _select_probe_key_ids(
|
||||
keys=keys, # type: ignore[arg-type]
|
||||
provider_type="codex",
|
||||
now_ts=now_ts,
|
||||
interval_seconds=10 * 60,
|
||||
last_probe_timestamps={"k1": now_ts - (25 * 60)},
|
||||
limit=0,
|
||||
)
|
||||
assert selected == []
|
||||
|
||||
|
||||
def test_select_probe_key_ids_applies_limit_by_oldest_anchor_first() -> None:
|
||||
now = datetime(2026, 3, 5, 12, 0, 0, tzinfo=timezone.utc)
|
||||
now_ts = int(now.timestamp())
|
||||
|
||||
keys = [
|
||||
_key("k1", last_used_at=now - timedelta(minutes=60)),
|
||||
_key("k2", last_used_at=now - timedelta(minutes=50)),
|
||||
_key("k3", last_used_at=now - timedelta(minutes=40)),
|
||||
]
|
||||
|
||||
selected = _select_probe_key_ids(
|
||||
keys=keys, # type: ignore[arg-type]
|
||||
provider_type="codex",
|
||||
now_ts=now_ts,
|
||||
interval_seconds=10 * 60,
|
||||
last_probe_timestamps={},
|
||||
limit=2,
|
||||
)
|
||||
assert selected == ["k1", "k2"]
|
||||
Reference in New Issue
Block a user