Files
Aether/tests/api/admin/test_rust_only_admin_surfaces.py
fawney19 ddf18fed9a feat: 扩展 Rust gateway 全功能模块,新增 billing/crypto/wallet crate 及完整数据层
- 新增 aether-billing、aether-crypto、aether-wallet 独立 crate
- aether-data 扩展 repository 层:announcements、auth_modules、billing、
  candidate_selection、gemini_file_mappings、global_models、management_tokens、
  oauth_providers、proxy_nodes、quota、users、wallet 等模块
- aether-gateway 新增 api/auth/billing/control/middleware/scheduler/usage/
  video_tasks/hooks/maintenance/model_fetch/provider_transport 等功能模块
- 重构 executor decision 和 gateway state 为模块目录结构
- 新增 gateway router、frontdoor 路由层及对应测试
- Python 侧 API 路由重构,新增 compat/support 模块
- 前端 Logo 组件更新及 Provider 管理页面调整
2026-03-31 19:19:04 +08:00

260 lines
8.1 KiB
Python

from __future__ import annotations
from io import BytesIO
from types import SimpleNamespace
from unittest.mock import AsyncMock
import pytest
from fastapi import HTTPException
from fastapi.responses import JSONResponse
from starlette.datastructures import UploadFile
class _FakeQuery:
def __init__(self, result: object) -> None:
self._result = result
def filter(self, *_args: object, **_kwargs: object) -> "_FakeQuery":
return self
def first(self) -> object:
return self._result
class _FakeDB:
def __init__(self, result: object) -> None:
self._result = result
def query(self, *_args: object, **_kwargs: object) -> _FakeQuery:
return _FakeQuery(self._result)
@pytest.mark.asyncio
async def test_admin_system_check_update_returns_unavailable_payload(
monkeypatch: pytest.MonkeyPatch,
) -> None:
from src.api.admin import system as mod
monkeypatch.setattr(mod, "_get_current_version", lambda: "1.2.3")
result = mod._build_check_update_unavailable_response()
assert result == {
"current_version": "1.2.3",
"latest_version": None,
"has_update": False,
"release_url": None,
"release_notes": None,
"published_at": None,
"error": "检查更新需要 Rust 管理后端",
}
@pytest.mark.asyncio
async def test_admin_system_aws_regions_uses_local_cache_when_present(
monkeypatch: pytest.MonkeyPatch,
) -> None:
from src.api.admin import system as mod
from src.core.cache_service import CacheService
monkeypatch.setattr(CacheService, "get", AsyncMock(return_value=["us-east-1", "us-west-2"]))
mod._aws_regions_mem_cache = None
result = await mod._get_aws_regions_response()
assert result == {"regions": ["us-east-1", "us-west-2"]}
@pytest.mark.asyncio
async def test_admin_system_aws_regions_raises_without_local_cache(
monkeypatch: pytest.MonkeyPatch,
) -> None:
from src.api.admin import system as mod
from src.core.cache_service import CacheService
monkeypatch.setattr(CacheService, "get", AsyncMock(return_value=None))
mod._aws_regions_mem_cache = None
with pytest.raises(HTTPException) as exc_info:
await mod._get_aws_regions_response()
assert exc_info.value.status_code == 503
assert exc_info.value.detail == "AWS regions requires Rust admin backend"
@pytest.mark.asyncio
async def test_admin_gemini_file_upload_requires_rust_uploader() -> None:
from src.api.admin import gemini_files as mod
upload = UploadFile(filename="example.txt", file=BytesIO(b"hello"), headers=None)
with pytest.raises(HTTPException) as exc_info:
await mod._upload_file_response(file=upload, key_ids="key_1")
assert exc_info.value.status_code == 503
assert exc_info.value.detail == "Admin Gemini file upload requires Rust uploader"
@pytest.mark.asyncio
async def test_admin_external_models_returns_cached_data(
monkeypatch: pytest.MonkeyPatch,
) -> None:
from src.api.admin.models import external as mod
monkeypatch.setattr(
mod,
"_get_cached_data",
AsyncMock(return_value={"openai": {"official": True, "models": []}}),
)
response = await mod._get_external_models_response()
assert isinstance(response, JSONResponse)
assert response.status_code == 200
assert b'"official":true' in response.body
@pytest.mark.asyncio
async def test_admin_external_models_raise_without_cache(
monkeypatch: pytest.MonkeyPatch,
) -> None:
from src.api.admin.models import external as mod
monkeypatch.setattr(mod, "_get_cached_data", AsyncMock(return_value=None))
with pytest.raises(HTTPException) as exc_info:
await mod._get_external_models_response()
assert exc_info.value.status_code == 503
assert exc_info.value.detail == "External models catalog requires Rust admin backend"
@pytest.mark.asyncio
async def test_admin_video_proxy_raises_when_google_proxy_is_required(
monkeypatch: pytest.MonkeyPatch,
) -> None:
from src.api.admin.video_tasks import routes as mod
from src.utils import auth_utils
task = SimpleNamespace(
id="task_1",
user_id="user_1",
video_url="https://generativelanguage.googleapis.com/v1/media/video.mp4",
)
request = SimpleNamespace(cookies={}, headers={"Authorization": "Bearer token"})
db = _FakeDB(task)
monkeypatch.setattr(
auth_utils,
"authenticate_user_from_bearer_token",
AsyncMock(return_value=SimpleNamespace(id="admin_1", role=mod.UserRole.ADMIN)),
)
with pytest.raises(HTTPException) as exc_info:
await mod._proxy_video_stream_response(task_id="task_1", request=request, token=None, db=db)
assert exc_info.value.status_code == 503
assert exc_info.value.detail == "Admin video proxy requires Rust/public download path"
@pytest.mark.asyncio
async def test_admin_usage_replay_requires_rust_maintenance_backend() -> None:
from src.api.admin.usage import routes as mod
adapter = mod.AdminUsageReplayAdapter(usage_id="usage_1")
with pytest.raises(HTTPException) as exc_info:
await adapter.handle(SimpleNamespace())
assert exc_info.value.status_code == 503
assert exc_info.value.detail == "Admin usage replay requires Rust maintenance backend"
@pytest.mark.asyncio
@pytest.mark.parametrize(
("call_factory"),
[
lambda mod: mod.start_oauth("key_1", request=SimpleNamespace(), db=None, _=None),
lambda mod: mod.complete_oauth(
"key_1",
mod.CompleteOAuthRequest(callback_url="http://localhost/?code=x&state=y"),
request=SimpleNamespace(),
db=None,
_=None,
),
lambda mod: mod.refresh_oauth("key_1", request=SimpleNamespace(), db=None, _=None),
lambda mod: mod.start_provider_oauth(
"provider_1", request=SimpleNamespace(), db=None, _=None
),
lambda mod: mod.complete_provider_oauth(
"provider_1",
mod.ProviderCompleteOAuthRequest(
callback_url="http://localhost/?code=x&state=y",
),
request=SimpleNamespace(),
db=None,
_=None,
),
lambda mod: mod.import_refresh_token(
"provider_1",
mod.ImportRefreshTokenRequest(refresh_token="refresh-token"),
request=SimpleNamespace(),
db=None,
_=None,
),
lambda mod: mod.batch_import_oauth(
"provider_1",
mod.BatchImportRequest(credentials="refresh-token"),
request=SimpleNamespace(),
db=None,
_=None,
),
lambda mod: mod.start_batch_import_oauth_task(
"provider_1",
mod.BatchImportRequest(credentials="refresh-token"),
request=SimpleNamespace(),
db=None,
_=None,
),
lambda mod: mod.get_batch_import_oauth_task_status(
"provider_1",
"task_1",
request=SimpleNamespace(),
db=None,
_=None,
),
lambda mod: mod.device_authorize(
"provider_1",
mod.DeviceAuthorizeRequest(),
request=SimpleNamespace(),
db=None,
_=None,
),
lambda mod: mod.device_poll(
"provider_1",
mod.DevicePollRequest(session_id="session_1"),
request=SimpleNamespace(),
db=None,
_=None,
),
],
)
async def test_admin_provider_oauth_routes_require_rust_maintenance_backend(
call_factory,
monkeypatch: pytest.MonkeyPatch,
) -> None:
from src.api.admin import provider_oauth as mod
async def _fake_pipeline_run(*, adapter: object, http_request: object, db: object, mode: object):
_ = http_request, db, mode
context = SimpleNamespace(add_audit_metadata=lambda **_kwargs: None)
return await adapter.handle(context) # type: ignore[attr-defined]
monkeypatch.setattr(mod.pipeline, "run", _fake_pipeline_run)
with pytest.raises(HTTPException) as exc_info:
await call_factory(mod)
assert exc_info.value.status_code == 503
assert exc_info.value.detail == "Admin provider OAuth requires Rust maintenance backend"