feat: 引入 status_snapshot 统一 provider key 状态管理

- 新增 StatusSnapshot 模型,聚合 OAuth / 账号 / 配额三维状态
- 新增 StatusSnapshotStore 负责快照的持久化与查询
- 重构 response_builder / endpoint_models,基于 snapshot 输出状态字段
- 前端抽取 providerKeyStatus / oauthRefreshFeedback 工具函数,
  统一 PoolManagement、ProviderDetailDrawer、BatchDialog 的状态展示
- errorParser 增加已知 OAuth 错误的友好提示
- refresher 适配 snapshot 写入,account_state 扩展状态分类
- 新增 alembic 迁移及存量数据回填脚本
- 补充前后端单元测试
This commit is contained in:
fawney19
2026-03-20 19:16:52 +08:00
parent 25d38ae632
commit 46737d32f8
39 changed files with 2326 additions and 470 deletions

View File

@@ -3,6 +3,7 @@
from __future__ import annotations
from src.services.provider.pool.account_state import (
build_provider_key_status_snapshot,
resolve_pool_account_state,
should_auto_remove_account_state,
)
@@ -192,3 +193,68 @@ def test_auto_remove_state_excludes_token_expired_and_verification() -> None:
assert should_auto_remove_account_state(expired) is False
assert should_auto_remove_account_state(verification) is False
assert should_auto_remove_account_state(disabled) is True
def test_build_provider_key_status_snapshot_separates_account_block_from_oauth_state() -> None:
snapshot = build_provider_key_status_snapshot(
auth_type="oauth",
oauth_expires_at=2_000_000_000,
oauth_invalid_at=1_900_000_000,
oauth_invalid_reason="[ACCOUNT_BLOCK] 工作区已停用 (deactivated_workspace)",
provider_type="codex",
upstream_metadata=None,
now_ts=1_800_000_000,
)
assert snapshot.account.blocked is True
assert snapshot.account.code == "workspace_deactivated"
assert snapshot.account.label == "工作区停用"
assert snapshot.oauth.code == "valid"
assert snapshot.oauth.requires_reauth is False
def test_build_provider_key_status_snapshot_keeps_refresh_failure_visible_beside_account_block() -> (
None
):
snapshot = build_provider_key_status_snapshot(
auth_type="oauth",
oauth_expires_at=2_000_000_000,
oauth_invalid_at=1_900_000_000,
oauth_invalid_reason=(
"[ACCOUNT_BLOCK] 工作区已停用 (deactivated_workspace)\n"
"[REFRESH_FAILED] Token 续期失败 (400): refresh_token_reused"
),
provider_type="codex",
upstream_metadata=None,
now_ts=1_800_000_000,
)
assert snapshot.account.blocked is True
assert snapshot.account.code == "workspace_deactivated"
assert snapshot.oauth.code == "invalid"
assert snapshot.oauth.label == "已失效"
assert snapshot.oauth.reason == "Token 续期失败 (400): refresh_token_reused"
assert snapshot.oauth.requires_reauth is True
def test_build_provider_key_status_snapshot_marks_quota_exhausted() -> None:
snapshot = build_provider_key_status_snapshot(
auth_type="oauth",
oauth_expires_at=2_000_000_000,
oauth_invalid_at=None,
oauth_invalid_reason=None,
provider_type="codex",
upstream_metadata={
"codex": {
"primary_used_percent": 100.0,
"secondary_used_percent": 20.0,
"updated_at": 1_800_000_000,
"plan_type": "team",
}
},
now_ts=1_800_000_000,
)
assert snapshot.quota.code == "exhausted"
assert snapshot.quota.exhausted is True
assert snapshot.quota.reason == "Codex 周限额剩余 0%"