feat: Provider 异步删除、可配置密码策略、Hub 超时优化及多项改进

- 新增 Provider 异步删除任务系统,后台分阶段删除子资源并清理残留引用
- 新增可配置密码策略等级(weak/medium/strong),支持系统设置面板调整
- aether-hub 升级至 0.1.4,idle timeout 支持禁用(设为 0),worker 默认超时调整为 120s
- OAuth 手动续期增加 Redis 分布式锁,防止并发刷新冲突
- ProxyNode 心跳检测改为 asyncio.to_thread,避免阻塞事件循环
- 删除 ModelMultiSelect 和 useInvalidModels,MultiSelect 组件通用化
- 明确 allowed_providers/allowed_api_formats 的 NULL 与空数组语义
- 前端 StandaloneKeyFormDialog、UserFormDialog 等多处 UI 优化
- 新增 Alembic 迁移脚本清理 Provider 删除后的残留引用
- 补充相关测试用例
This commit is contained in:
fawney19
2026-03-12 01:11:35 +08:00
parent 0d770d1c4d
commit 6e51a3f45d
55 changed files with 3219 additions and 862 deletions

View File

@@ -9,6 +9,7 @@ import pytest
from fastapi import FastAPI
from fastapi.testclient import TestClient
from src.api.admin.users.routes import AdminCreateUserAdapter
from src.api.admin.users.routes import router as admin_users_router
from src.database import get_db
@@ -91,3 +92,53 @@ def test_list_users_uses_wallet_batch_lookup(monkeypatch: pytest.MonkeyPatch) ->
assert response.json()[1]["unlimited"] is False
batch_getter.assert_called_once()
assert batch_getter.call_args.args[1] == ["user-1", "user-2"]
@pytest.mark.asyncio
async def test_create_user_adapter_preserves_empty_restriction_lists(
monkeypatch: pytest.MonkeyPatch,
) -> None:
db = MagicMock()
captured: dict[str, Any] = {}
def _create_user(**kwargs: Any) -> SimpleNamespace:
captured.update(kwargs)
return SimpleNamespace(
id="user-3",
email="u3@example.com",
username="user3",
role=SimpleNamespace(value="user"),
is_active=True,
allowed_providers=[],
allowed_api_formats=[],
allowed_models=[],
)
monkeypatch.setattr("src.api.admin.users.routes.UserService.create_user", _create_user)
monkeypatch.setattr(
"src.api.admin.users.routes._serialize_user",
lambda _db, user: {"id": user.id},
)
context = SimpleNamespace(
db=db,
request=SimpleNamespace(state=SimpleNamespace()),
ensure_json_body=lambda: {
"username": "user3",
"password": "Abcd12",
"email": "u3@example.com",
"role": "user",
"initial_gift_usd": 10,
"allowed_providers": [],
"allowed_api_formats": [],
"allowed_models": [],
},
add_audit_metadata=lambda **_: None,
)
result = await AdminCreateUserAdapter().handle(context)
assert result == {"id": "user-3"}
assert captured["allowed_providers"] == []
assert captured["allowed_api_formats"] == []
assert captured["allowed_models"] == []