Files
Aether/tests/api/test_admin_provider_routes.py
fawney19 6e51a3f45d 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 删除后的残留引用
- 补充相关测试用例
2026-03-12 01:11:35 +08:00

129 lines
4.1 KiB
Python

from __future__ import annotations
from types import SimpleNamespace
from unittest.mock import AsyncMock, MagicMock
import pytest
from src.api.admin.providers.routes import (
AdminDeleteProviderAdapter,
AdminProviderDeleteTaskStatusAdapter,
)
@pytest.mark.asyncio
async def test_delete_provider_adapter_submits_async_task_and_deactivates_provider(
monkeypatch: pytest.MonkeyPatch,
) -> None:
db = MagicMock()
provider = SimpleNamespace(id="provider-1", name="Provider 1", is_active=True)
db.query.return_value.filter.return_value.first.return_value = provider
submit_task_mock = AsyncMock(return_value="task-1")
invalidate_models_mock = AsyncMock()
invalidate_resolve_mock = AsyncMock()
invalidate_provider_cache_mock = AsyncMock()
monkeypatch.setattr(
"src.api.admin.providers.routes.submit_provider_delete",
submit_task_mock,
)
monkeypatch.setattr(
"src.api.admin.providers.routes.invalidate_models_list_cache",
invalidate_models_mock,
)
monkeypatch.setattr(
"src.api.admin.providers.routes.ModelCacheService.invalidate_all_resolve_cache",
invalidate_resolve_mock,
)
monkeypatch.setattr(
"src.api.admin.providers.routes.ProviderCacheService.invalidate_provider_cache",
invalidate_provider_cache_mock,
)
audit_calls: list[dict[str, object]] = []
context = SimpleNamespace(
db=db,
request=SimpleNamespace(state=SimpleNamespace()),
add_audit_metadata=lambda **kwargs: audit_calls.append(kwargs),
)
adapter = AdminDeleteProviderAdapter(provider_id="provider-1")
result = await adapter.handle(context)
assert result == {
"task_id": "task-1",
"status": "pending",
"message": "删除任务已提交,提供商已进入后台删除队列",
}
submit_task_mock.assert_awaited_once_with("provider-1")
assert provider.is_active is False
db.commit.assert_called_once()
invalidate_models_mock.assert_awaited_once()
invalidate_resolve_mock.assert_awaited_once()
invalidate_provider_cache_mock.assert_awaited_once_with("provider-1")
assert audit_calls[0]["action"] == "delete_provider"
assert audit_calls[1]["task_id"] == "task-1"
assert audit_calls[1]["provider_deactivated"] is True
@pytest.mark.asyncio
async def test_delete_provider_adapter_reuses_task_without_extra_commit(
monkeypatch: pytest.MonkeyPatch,
) -> None:
db = MagicMock()
provider = SimpleNamespace(id="provider-1", name="Provider 1", is_active=False)
db.query.return_value.filter.return_value.first.return_value = provider
submit_task_mock = AsyncMock(return_value="task-1")
monkeypatch.setattr(
"src.api.admin.providers.routes.submit_provider_delete",
submit_task_mock,
)
context = SimpleNamespace(
db=db,
request=SimpleNamespace(state=SimpleNamespace()),
add_audit_metadata=lambda **kwargs: None,
)
adapter = AdminDeleteProviderAdapter(provider_id="provider-1")
result = await adapter.handle(context)
assert result["task_id"] == "task-1"
db.commit.assert_not_called()
@pytest.mark.asyncio
async def test_delete_provider_task_status_adapter_returns_task_payload(
monkeypatch: pytest.MonkeyPatch,
) -> None:
task = SimpleNamespace(
task_id="task-1",
provider_id="provider-1",
status="running",
stage="deleting_keys",
total_keys=100,
deleted_keys=25,
total_endpoints=8,
deleted_endpoints=2,
message="deleted key batch 1/2",
)
monkeypatch.setattr(
"src.api.admin.providers.routes.get_provider_delete_task",
AsyncMock(return_value=task),
)
context = SimpleNamespace(db=MagicMock(), request=SimpleNamespace(state=SimpleNamespace()))
adapter = AdminProviderDeleteTaskStatusAdapter(provider_id="provider-1", task_id="task-1")
result = await adapter.handle(context)
assert result.task_id == "task-1"
assert result.status == "running"
assert result.stage == "deleting_keys"
assert result.deleted_keys == 25
assert result.deleted_endpoints == 2