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

@@ -0,0 +1,56 @@
from __future__ import annotations
import httpx
from src.api.admin import provider_oauth as module
def test_extract_oauth_refresh_error_reason_for_reused_refresh_token() -> None:
response = httpx.Response(
400,
json={
"error": {
"message": (
"Your refresh token has already been used to generate a new access token. "
"Please try signing in again."
),
"type": "invalid_request_error",
"param": None,
"code": "refresh_token_reused",
}
},
request=httpx.Request("POST", "https://example.com/oauth/token"),
)
assert (
module._extract_oauth_refresh_error_reason(response)
== "refresh_token 已被使用并轮换,请重新登录授权"
)
def test_extract_oauth_refresh_error_reason_prefers_nested_message() -> None:
response = httpx.Response(
401,
json={
"error": {
"message": "refresh token expired",
"type": "invalid_request_error",
}
},
request=httpx.Request("POST", "https://example.com/oauth/token"),
)
assert (
module._extract_oauth_refresh_error_reason(response)
== "refresh_token 无效、已过期或已撤销,请重新登录授权"
)
def test_merge_refresh_failure_reason_keeps_account_block_and_appends_refresh_failure() -> None:
current_reason = "[ACCOUNT_BLOCK] 工作区已停用 (deactivated_workspace)"
refresh_reason = "[REFRESH_FAILED] Token 续期失败 (400): refresh_token_reused"
assert module._merge_refresh_failure_reason(current_reason, refresh_reason) == (
"[ACCOUNT_BLOCK] 工作区已停用 (deactivated_workspace)\n"
"[REFRESH_FAILED] Token 续期失败 (400): refresh_token_reused"
)