mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 09:20:22 +08:00
feat: 引入 Rust executor/gateway sidecar 及 Python 侧双后端适配
- 新增 Rust workspace crates: aether-contracts, aether-executor, aether-gateway - aether-executor: 支持 Unix Socket/TCP 双传输模式,处理同步/流式上游请求 - aether-gateway: 作为本地主入口代理,集成 /api/internal/gateway/resolve 认证预解析 - Python 侧新增 ExecutionPlan 契约和 RustExecutorClient,各 handler 支持 executor_backend=rust 时将可序列化请求转发给 Rust executor 执行 - 重构 dev.sh 支持 executor/gateway 进程编排与生命周期管理 - 新增 internal gateway 路由,提供 resolve/passthrough 端点 - handler 层(chat/cli/video/endpoint_checker 等)全面适配 Rust executor 回退逻辑 - pipeline 层支持 trusted auth context 跳过重复认证 - 新增 Rust CI workflow 及对应测试用例
This commit is contained in:
633
tests/api/test_internal_gateway_routes.py
Normal file
633
tests/api/test_internal_gateway_routes.py
Normal file
@@ -0,0 +1,633 @@
|
||||
import base64
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
import pytest
|
||||
from fastapi import FastAPI
|
||||
from fastapi.testclient import TestClient
|
||||
from fastapi.responses import JSONResponse, StreamingResponse
|
||||
|
||||
from src.api.internal.gateway import (
|
||||
CONTROL_ACTION_HEADER,
|
||||
CONTROL_ACTION_PROXY_PUBLIC,
|
||||
CONTROL_EXECUTED_HEADER,
|
||||
GatewayResolveRequest,
|
||||
_is_streaming_sync_payload,
|
||||
_resolve_auth_context,
|
||||
_resolve_gateway_sync_adapter,
|
||||
classify_gateway_route,
|
||||
router,
|
||||
)
|
||||
from src.database import get_db
|
||||
|
||||
|
||||
def test_classify_openai_chat_route_as_ai_public() -> None:
|
||||
decision = classify_gateway_route("POST", "/v1/chat/completions")
|
||||
|
||||
assert decision.route_class == "ai_public"
|
||||
assert decision.route_family == "openai"
|
||||
assert decision.route_kind == "chat"
|
||||
assert decision.auth_endpoint_signature == "openai:chat"
|
||||
assert decision.executor_candidate is True
|
||||
assert decision.action == "proxy_public"
|
||||
|
||||
|
||||
def test_classify_gemini_files_download_route_as_ai_public() -> None:
|
||||
decision = classify_gateway_route("GET", "/v1beta/files/file-123:download")
|
||||
|
||||
assert decision.route_class == "ai_public"
|
||||
assert decision.route_family == "gemini"
|
||||
assert decision.route_kind == "files"
|
||||
assert decision.auth_endpoint_signature == "gemini:chat"
|
||||
assert decision.executor_candidate is True
|
||||
|
||||
|
||||
def test_classify_gemini_files_nested_metadata_route_as_ai_public() -> None:
|
||||
decision = classify_gateway_route("GET", "/v1beta/files/files/abc-123")
|
||||
|
||||
assert decision.route_class == "ai_public"
|
||||
assert decision.route_family == "gemini"
|
||||
assert decision.route_kind == "files"
|
||||
assert decision.executor_candidate is True
|
||||
|
||||
|
||||
def test_classify_gemini_video_operation_route_as_ai_public() -> None:
|
||||
decision = classify_gateway_route("GET", "/v1beta/models/veo-3/operations/op-123")
|
||||
|
||||
assert decision.route_class == "ai_public"
|
||||
assert decision.route_family == "gemini"
|
||||
assert decision.route_kind == "video"
|
||||
assert decision.auth_endpoint_signature == "gemini:video"
|
||||
assert decision.executor_candidate is True
|
||||
|
||||
|
||||
def test_classify_non_ai_route_as_passthrough() -> None:
|
||||
decision = classify_gateway_route("GET", "/api/admin/system/info")
|
||||
|
||||
assert decision.route_class == "passthrough"
|
||||
assert decision.route_family is None
|
||||
assert decision.route_kind is None
|
||||
assert decision.executor_candidate is False
|
||||
|
||||
|
||||
def test_classify_claude_cli_route_from_bearer_header() -> None:
|
||||
decision = classify_gateway_route(
|
||||
"POST",
|
||||
"/v1/messages",
|
||||
{"authorization": "Bearer sk-cli"},
|
||||
)
|
||||
|
||||
assert decision.route_class == "ai_public"
|
||||
assert decision.route_family == "claude"
|
||||
assert decision.route_kind == "cli"
|
||||
assert decision.auth_endpoint_signature == "claude:cli"
|
||||
|
||||
|
||||
def test_classify_gemini_cli_route_from_user_agent() -> None:
|
||||
decision = classify_gateway_route(
|
||||
"POST",
|
||||
"/v1beta/models/gemini-2.5-pro:generateContent",
|
||||
{"user-agent": "GeminiCLI/1.2.3"},
|
||||
)
|
||||
|
||||
assert decision.route_class == "ai_public"
|
||||
assert decision.route_family == "gemini"
|
||||
assert decision.route_kind == "cli"
|
||||
assert decision.auth_endpoint_signature == "gemini:cli"
|
||||
|
||||
|
||||
def test_resolve_sync_adapter_for_openai_chat_route() -> None:
|
||||
decision = classify_gateway_route("POST", "/v1/chat/completions")
|
||||
|
||||
adapter, path_params = _resolve_gateway_sync_adapter(decision, "/v1/chat/completions")
|
||||
|
||||
assert adapter is not None
|
||||
assert adapter.name == "openai.chat"
|
||||
assert path_params == {}
|
||||
|
||||
|
||||
def test_resolve_sync_adapter_for_gemini_route_extracts_model_path_params() -> None:
|
||||
decision = classify_gateway_route("POST", "/v1beta/models/gemini-2.5-pro:generateContent")
|
||||
|
||||
adapter, path_params = _resolve_gateway_sync_adapter(
|
||||
decision,
|
||||
"/v1beta/models/gemini-2.5-pro:generateContent",
|
||||
)
|
||||
|
||||
assert adapter is not None
|
||||
assert adapter.name == "gemini.chat"
|
||||
assert path_params == {"model": "gemini-2.5-pro", "stream": False}
|
||||
|
||||
|
||||
def test_resolve_sync_adapter_rejects_non_sync_files_route() -> None:
|
||||
decision = classify_gateway_route("GET", "/v1beta/files/file-123:download")
|
||||
|
||||
adapter, path_params = _resolve_gateway_sync_adapter(decision, "/v1beta/files/file-123:download")
|
||||
|
||||
assert adapter is None
|
||||
assert path_params == {}
|
||||
|
||||
|
||||
def test_is_streaming_sync_payload_detects_body_and_path_stream_flags() -> None:
|
||||
assert _is_streaming_sync_payload({"stream": True}, {}) is True
|
||||
assert _is_streaming_sync_payload({}, {"stream": True}) is True
|
||||
assert _is_streaming_sync_payload({"stream": False}, {"stream": False}) is False
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_resolve_auth_context_from_openai_bearer_header(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
payload = GatewayResolveRequest(
|
||||
method="POST",
|
||||
path="/v1/chat/completions",
|
||||
headers={"Authorization": "Bearer sk-test"},
|
||||
)
|
||||
decision = classify_gateway_route(payload.method, payload.path, payload.headers)
|
||||
monkeypatch.setattr(
|
||||
"src.api.internal.gateway.AuthService.authenticate_api_key_threadsafe",
|
||||
AsyncMock(
|
||||
return_value=SimpleNamespace(
|
||||
user=SimpleNamespace(id="user-123"),
|
||||
api_key=SimpleNamespace(id="key-123"),
|
||||
balance_remaining=42.5,
|
||||
access_allowed=True,
|
||||
)
|
||||
),
|
||||
)
|
||||
|
||||
auth_context = await _resolve_auth_context(payload, decision)
|
||||
|
||||
assert auth_context == {
|
||||
"user_id": "user-123",
|
||||
"api_key_id": "key-123",
|
||||
"balance_remaining": 42.5,
|
||||
"access_allowed": True,
|
||||
}
|
||||
|
||||
|
||||
def test_execute_sync_route_returns_controlled_response(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
app = FastAPI()
|
||||
app.include_router(router)
|
||||
app.dependency_overrides[get_db] = lambda: object()
|
||||
monkeypatch.setattr("src.api.internal.gateway.ensure_loopback", lambda request: None)
|
||||
|
||||
class FakeAdapter:
|
||||
mode = SimpleNamespace(value="standard")
|
||||
allowed_api_formats = ["openai:chat"]
|
||||
|
||||
def authorize(self, context: object) -> None:
|
||||
self.authorized_context = context
|
||||
|
||||
async def handle(self, context: object) -> JSONResponse:
|
||||
assert getattr(context, "path_params", {}) == {}
|
||||
return JSONResponse(status_code=201, content={"ok": True, "request_id": context.request_id})
|
||||
|
||||
fake_adapter = FakeAdapter()
|
||||
fake_pipeline = SimpleNamespace(_check_user_rate_limit=AsyncMock(return_value=None))
|
||||
|
||||
monkeypatch.setattr(
|
||||
"src.api.internal.gateway._resolve_gateway_sync_adapter",
|
||||
lambda decision, path: (fake_adapter, {}),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"src.api.internal.gateway._load_gateway_auth_models",
|
||||
lambda db, auth_context: (
|
||||
SimpleNamespace(id="user-123"),
|
||||
SimpleNamespace(id="key-123"),
|
||||
),
|
||||
)
|
||||
monkeypatch.setattr("src.api.internal.gateway.get_pipeline", lambda: fake_pipeline)
|
||||
|
||||
client = TestClient(app, base_url="http://127.0.0.1")
|
||||
response = client.post(
|
||||
"/api/internal/gateway/execute-sync",
|
||||
json={
|
||||
"trace_id": "trace-sync-123",
|
||||
"method": "POST",
|
||||
"path": "/v1/chat/completions",
|
||||
"headers": {"user-agent": "pytest"},
|
||||
"body_json": {"model": "gpt-5", "messages": []},
|
||||
"auth_context": {
|
||||
"user_id": "user-123",
|
||||
"api_key_id": "key-123",
|
||||
"balance_remaining": 12.5,
|
||||
"access_allowed": True,
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
assert response.status_code == 201
|
||||
assert response.headers[CONTROL_EXECUTED_HEADER] == "true"
|
||||
assert response.json()["ok"] is True
|
||||
assert response.json()["request_id"] == "trace-sync-123"
|
||||
|
||||
|
||||
def test_execute_sync_route_falls_back_for_stream_payload() -> None:
|
||||
app = FastAPI()
|
||||
app.include_router(router)
|
||||
app.dependency_overrides[get_db] = lambda: object()
|
||||
monkeypatch = pytest.MonkeyPatch()
|
||||
monkeypatch.setattr("src.api.internal.gateway.ensure_loopback", lambda request: None)
|
||||
|
||||
client = TestClient(app, base_url="http://127.0.0.1")
|
||||
response = client.post(
|
||||
"/api/internal/gateway/execute-sync",
|
||||
json={
|
||||
"trace_id": "trace-stream-123",
|
||||
"method": "POST",
|
||||
"path": "/v1/chat/completions",
|
||||
"headers": {"user-agent": "pytest"},
|
||||
"body_json": {"model": "gpt-5", "messages": [], "stream": True},
|
||||
"auth_context": {
|
||||
"user_id": "user-123",
|
||||
"api_key_id": "key-123",
|
||||
"access_allowed": True,
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
assert response.status_code == 409
|
||||
assert response.headers[CONTROL_ACTION_HEADER] == CONTROL_ACTION_PROXY_PUBLIC
|
||||
assert response.json() == {"action": CONTROL_ACTION_PROXY_PUBLIC}
|
||||
monkeypatch.undo()
|
||||
|
||||
|
||||
def test_execute_stream_route_returns_controlled_stream(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
app = FastAPI()
|
||||
app.include_router(router)
|
||||
app.dependency_overrides[get_db] = lambda: object()
|
||||
monkeypatch.setattr("src.api.internal.gateway.ensure_loopback", lambda request: None)
|
||||
|
||||
class FakeAdapter:
|
||||
mode = SimpleNamespace(value="standard")
|
||||
allowed_api_formats = ["openai:chat"]
|
||||
|
||||
def authorize(self, context: object) -> None:
|
||||
self.authorized_context = context
|
||||
|
||||
async def handle(self, context: object) -> StreamingResponse:
|
||||
async def _iter() -> object:
|
||||
yield b"data: one\n\n"
|
||||
yield b"data: [DONE]\n\n"
|
||||
|
||||
return StreamingResponse(_iter(), media_type="text/event-stream")
|
||||
|
||||
fake_pipeline = SimpleNamespace(_check_user_rate_limit=AsyncMock(return_value=None))
|
||||
monkeypatch.setattr(
|
||||
"src.api.internal.gateway._resolve_gateway_sync_adapter",
|
||||
lambda decision, path: (FakeAdapter(), {}),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"src.api.internal.gateway._load_gateway_auth_models",
|
||||
lambda db, auth_context: (
|
||||
SimpleNamespace(id="user-123"),
|
||||
SimpleNamespace(id="key-123"),
|
||||
),
|
||||
)
|
||||
monkeypatch.setattr("src.api.internal.gateway.get_pipeline", lambda: fake_pipeline)
|
||||
|
||||
client = TestClient(app, base_url="http://127.0.0.1")
|
||||
response = client.post(
|
||||
"/api/internal/gateway/execute-stream",
|
||||
json={
|
||||
"trace_id": "trace-stream-123",
|
||||
"method": "POST",
|
||||
"path": "/v1/chat/completions",
|
||||
"headers": {"user-agent": "pytest"},
|
||||
"body_json": {"model": "gpt-5", "messages": [], "stream": True},
|
||||
"auth_context": {
|
||||
"user_id": "user-123",
|
||||
"api_key_id": "key-123",
|
||||
"balance_remaining": 12.5,
|
||||
"access_allowed": True,
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.headers[CONTROL_EXECUTED_HEADER] == "true"
|
||||
assert response.text == "data: one\n\ndata: [DONE]\n\n"
|
||||
|
||||
|
||||
def test_execute_stream_route_falls_back_for_sync_payload(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
app = FastAPI()
|
||||
app.include_router(router)
|
||||
app.dependency_overrides[get_db] = lambda: object()
|
||||
monkeypatch.setattr("src.api.internal.gateway.ensure_loopback", lambda request: None)
|
||||
|
||||
client = TestClient(app, base_url="http://127.0.0.1")
|
||||
response = client.post(
|
||||
"/api/internal/gateway/execute-stream",
|
||||
json={
|
||||
"trace_id": "trace-sync-456",
|
||||
"method": "POST",
|
||||
"path": "/v1/chat/completions",
|
||||
"headers": {"user-agent": "pytest"},
|
||||
"body_json": {"model": "gpt-5", "messages": []},
|
||||
"auth_context": {
|
||||
"user_id": "user-123",
|
||||
"api_key_id": "key-123",
|
||||
"access_allowed": True,
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
assert response.status_code == 409
|
||||
assert response.headers[CONTROL_ACTION_HEADER] == CONTROL_ACTION_PROXY_PUBLIC
|
||||
assert response.json() == {"action": CONTROL_ACTION_PROXY_PUBLIC}
|
||||
|
||||
|
||||
def test_execute_sync_route_handles_gemini_files_list(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
app = FastAPI()
|
||||
app.include_router(router)
|
||||
app.dependency_overrides[get_db] = lambda: object()
|
||||
monkeypatch.setattr("src.api.internal.gateway.ensure_loopback", lambda request: None)
|
||||
monkeypatch.setattr(
|
||||
"src.api.internal.gateway._load_gateway_auth_models",
|
||||
lambda db, auth_context: (
|
||||
SimpleNamespace(id="user-123"),
|
||||
SimpleNamespace(id="key-123"),
|
||||
),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"src.api.internal.gateway.get_pipeline",
|
||||
lambda: SimpleNamespace(_check_user_rate_limit=AsyncMock(return_value=None)),
|
||||
)
|
||||
|
||||
async def _fake_list_files(
|
||||
request: object,
|
||||
pageSize: int | None = None,
|
||||
pageToken: str | None = None,
|
||||
) -> JSONResponse:
|
||||
del request
|
||||
assert pageSize == 25
|
||||
assert pageToken == "page-2"
|
||||
return JSONResponse(status_code=200, content={"files": [{"name": "files/abc"}]})
|
||||
|
||||
monkeypatch.setattr("src.api.public.gemini_files.list_files", _fake_list_files)
|
||||
|
||||
client = TestClient(app, base_url="http://127.0.0.1")
|
||||
response = client.post(
|
||||
"/api/internal/gateway/execute-sync",
|
||||
json={
|
||||
"trace_id": "trace-files-list",
|
||||
"method": "GET",
|
||||
"path": "/v1beta/files",
|
||||
"query_string": "pageSize=25&pageToken=page-2",
|
||||
"headers": {"x-goog-api-key": "client-key"},
|
||||
"auth_context": {
|
||||
"user_id": "user-123",
|
||||
"api_key_id": "key-123",
|
||||
"access_allowed": True,
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.headers[CONTROL_EXECUTED_HEADER] == "true"
|
||||
assert response.json() == {"files": [{"name": "files/abc"}]}
|
||||
|
||||
|
||||
def test_execute_sync_route_handles_gemini_files_upload_raw_body(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
app = FastAPI()
|
||||
app.include_router(router)
|
||||
app.dependency_overrides[get_db] = lambda: object()
|
||||
monkeypatch.setattr("src.api.internal.gateway.ensure_loopback", lambda request: None)
|
||||
monkeypatch.setattr(
|
||||
"src.api.internal.gateway._load_gateway_auth_models",
|
||||
lambda db, auth_context: (
|
||||
SimpleNamespace(id="user-123"),
|
||||
SimpleNamespace(id="key-123"),
|
||||
),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"src.api.internal.gateway.get_pipeline",
|
||||
lambda: SimpleNamespace(_check_user_rate_limit=AsyncMock(return_value=None)),
|
||||
)
|
||||
|
||||
async def _fake_upload_file(request: object) -> JSONResponse:
|
||||
body = await request.body()
|
||||
assert body == b"upload-bytes"
|
||||
assert request.headers["content-type"] == "application/octet-stream"
|
||||
return JSONResponse(status_code=201, content={"uploaded": True})
|
||||
|
||||
monkeypatch.setattr("src.api.public.gemini_files.upload_file", _fake_upload_file)
|
||||
|
||||
client = TestClient(app, base_url="http://127.0.0.1")
|
||||
response = client.post(
|
||||
"/api/internal/gateway/execute-sync",
|
||||
json={
|
||||
"trace_id": "trace-files-upload",
|
||||
"method": "POST",
|
||||
"path": "/upload/v1beta/files",
|
||||
"query_string": "uploadType=resumable",
|
||||
"headers": {
|
||||
"x-goog-api-key": "client-key",
|
||||
"content-type": "application/octet-stream",
|
||||
},
|
||||
"body_base64": base64.b64encode(b"upload-bytes").decode("ascii"),
|
||||
"auth_context": {
|
||||
"user_id": "user-123",
|
||||
"api_key_id": "key-123",
|
||||
"access_allowed": True,
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
assert response.status_code == 201
|
||||
assert response.headers[CONTROL_EXECUTED_HEADER] == "true"
|
||||
assert response.json() == {"uploaded": True}
|
||||
|
||||
|
||||
def test_execute_sync_route_handles_openai_video_remix_with_original_request(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
app = FastAPI()
|
||||
app.include_router(router)
|
||||
app.dependency_overrides[get_db] = lambda: object()
|
||||
monkeypatch.setattr("src.api.internal.gateway.ensure_loopback", lambda request: None)
|
||||
|
||||
class FakeVideoAdapter:
|
||||
mode = SimpleNamespace(value="standard")
|
||||
allowed_api_formats = ["openai:video"]
|
||||
|
||||
def authorize(self, context: object) -> None:
|
||||
self.authorized_context = context
|
||||
|
||||
async def handle(self, context: object) -> JSONResponse:
|
||||
assert context.request.method == "POST"
|
||||
assert context.request.url.path == "/v1/videos/task-123/remix"
|
||||
assert context.path_params == {"task_id": "task-123"}
|
||||
assert await context.ensure_json_body_async() == {"prompt": "remix this"}
|
||||
return JSONResponse(status_code=200, content={"video": True})
|
||||
|
||||
monkeypatch.setattr(
|
||||
"src.api.internal.gateway._resolve_gateway_sync_adapter",
|
||||
lambda decision, path: (FakeVideoAdapter(), {"task_id": "task-123"}),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"src.api.internal.gateway._load_gateway_auth_models",
|
||||
lambda db, auth_context: (
|
||||
SimpleNamespace(id="user-123"),
|
||||
SimpleNamespace(id="key-123"),
|
||||
),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"src.api.internal.gateway.get_pipeline",
|
||||
lambda: SimpleNamespace(_check_user_rate_limit=AsyncMock(return_value=None)),
|
||||
)
|
||||
|
||||
client = TestClient(app, base_url="http://127.0.0.1")
|
||||
response = client.post(
|
||||
"/api/internal/gateway/execute-sync",
|
||||
json={
|
||||
"trace_id": "trace-video-remix",
|
||||
"method": "POST",
|
||||
"path": "/v1/videos/task-123/remix",
|
||||
"headers": {
|
||||
"content-type": "application/json",
|
||||
"user-agent": "pytest",
|
||||
},
|
||||
"body_json": {"prompt": "remix this"},
|
||||
"auth_context": {
|
||||
"user_id": "user-123",
|
||||
"api_key_id": "key-123",
|
||||
"access_allowed": True,
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.headers[CONTROL_EXECUTED_HEADER] == "true"
|
||||
assert response.json() == {"video": True}
|
||||
|
||||
|
||||
def test_plan_stream_route_returns_executor_plan_for_gemini_files_download(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
app = FastAPI()
|
||||
app.include_router(router)
|
||||
app.dependency_overrides[get_db] = lambda: object()
|
||||
monkeypatch.setattr("src.api.internal.gateway.ensure_loopback", lambda request: None)
|
||||
|
||||
fake_plan = {
|
||||
"request_id": "plan-123",
|
||||
"provider_id": "provider-123",
|
||||
"endpoint_id": "endpoint-123",
|
||||
"key_id": "key-123",
|
||||
"method": "GET",
|
||||
"url": "https://example.com/v1beta/files/file-123:download",
|
||||
"headers": {"x-goog-api-key": "upstream-key"},
|
||||
"body": {},
|
||||
"stream": True,
|
||||
"provider_api_format": "gemini:files",
|
||||
"client_api_format": "gemini:files",
|
||||
"model_name": "gemini-files",
|
||||
}
|
||||
|
||||
monkeypatch.setattr(
|
||||
"src.api.internal.gateway._load_gateway_auth_models",
|
||||
lambda db, auth_context: (
|
||||
SimpleNamespace(id="user-123"),
|
||||
SimpleNamespace(id="key-123"),
|
||||
),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"src.api.internal.gateway.get_pipeline",
|
||||
lambda: SimpleNamespace(_check_user_rate_limit=AsyncMock(return_value=None)),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"src.api.internal.gateway._build_gemini_files_download_stream_plan",
|
||||
AsyncMock(return_value=fake_plan),
|
||||
)
|
||||
|
||||
client = TestClient(app, base_url="http://127.0.0.1")
|
||||
response = client.post(
|
||||
"/api/internal/gateway/plan-stream",
|
||||
json={
|
||||
"trace_id": "trace-files-plan",
|
||||
"method": "GET",
|
||||
"path": "/v1beta/files/file-123:download",
|
||||
"query_string": "alt=media",
|
||||
"headers": {"x-goog-api-key": "client-key"},
|
||||
"auth_context": {
|
||||
"user_id": "user-123",
|
||||
"api_key_id": "key-123",
|
||||
"access_allowed": True,
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {
|
||||
"action": "executor_stream",
|
||||
"plan_kind": "gemini_files_download",
|
||||
"plan": fake_plan,
|
||||
}
|
||||
|
||||
|
||||
def test_plan_stream_route_returns_executor_plan_for_openai_video_content(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
app = FastAPI()
|
||||
app.include_router(router)
|
||||
app.dependency_overrides[get_db] = lambda: object()
|
||||
monkeypatch.setattr("src.api.internal.gateway.ensure_loopback", lambda request: None)
|
||||
|
||||
fake_plan = {
|
||||
"request_id": "plan-video-123",
|
||||
"provider_id": "provider-video-123",
|
||||
"endpoint_id": "endpoint-video-123",
|
||||
"key_id": "key-video-123",
|
||||
"method": "GET",
|
||||
"url": "https://api.openai.com/v1/videos/ext-123/content",
|
||||
"headers": {"authorization": "Bearer upstream-key"},
|
||||
"body": {},
|
||||
"stream": True,
|
||||
"provider_api_format": "openai:video",
|
||||
"client_api_format": "openai:video",
|
||||
"model_name": "sora-2",
|
||||
}
|
||||
|
||||
monkeypatch.setattr(
|
||||
"src.api.internal.gateway._load_gateway_auth_models",
|
||||
lambda db, auth_context: (
|
||||
SimpleNamespace(id="user-123"),
|
||||
SimpleNamespace(id="key-123"),
|
||||
),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"src.api.internal.gateway.get_pipeline",
|
||||
lambda: SimpleNamespace(_check_user_rate_limit=AsyncMock(return_value=None)),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"src.api.internal.gateway._build_openai_video_content_stream_plan",
|
||||
AsyncMock(return_value=fake_plan),
|
||||
)
|
||||
|
||||
client = TestClient(app, base_url="http://127.0.0.1")
|
||||
response = client.post(
|
||||
"/api/internal/gateway/plan-stream",
|
||||
json={
|
||||
"trace_id": "trace-video-plan",
|
||||
"method": "GET",
|
||||
"path": "/v1/videos/task-123/content",
|
||||
"query_string": "variant=video",
|
||||
"headers": {"authorization": "Bearer client-key"},
|
||||
"auth_context": {
|
||||
"user_id": "user-123",
|
||||
"api_key_id": "key-123",
|
||||
"access_allowed": True,
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {
|
||||
"action": "executor_stream",
|
||||
"plan_kind": "openai_video_content",
|
||||
"plan": fake_plan,
|
||||
}
|
||||
Reference in New Issue
Block a user