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 管理页面调整
This commit is contained in:
fawney19
2026-03-31 19:19:04 +08:00
parent b5a0070023
commit ddf18fed9a
690 changed files with 235085 additions and 16299 deletions
@@ -0,0 +1,213 @@
use super::*;
#[tokio::test]
async fn gateway_routes_video_get_to_public_proxy_even_with_legacy_headers() {
let execute_hits = Arc::new(Mutex::new(0usize));
let execute_hits_clone = Arc::clone(&execute_hits);
let public_hits = Arc::new(Mutex::new(0usize));
let public_hits_clone = Arc::clone(&public_hits);
let upstream = Router::new()
.route(
"/api/internal/gateway/execute-sync",
any(move |_request: Request| {
let execute_hits_inner = Arc::clone(&execute_hits_clone);
async move {
*execute_hits_inner.lock().expect("mutex should lock") += 1;
let mut response = Response::builder()
.status(StatusCode::OK)
.body(Body::from("{\"status\":\"queued\"}"))
.expect("response should build");
response.headers_mut().insert(
http::header::CONTENT_TYPE,
HeaderValue::from_static("application/json"),
);
response.headers_mut().insert(
HeaderName::from_static(CONTROL_EXECUTED_HEADER),
HeaderValue::from_static("true"),
);
response
}
}),
)
.route(
"/v1/videos/task-123",
any(move |_request: Request| {
let public_hits_inner = Arc::clone(&public_hits_clone);
async move {
*public_hits_inner.lock().expect("mutex should lock") += 1;
(StatusCode::IM_A_TEAPOT, Body::from("public-route-hit"))
}
}),
);
let (upstream_url, upstream_handle) = start_server(upstream).await;
let gateway = build_router_with_control(upstream_url.clone(), Some(upstream_url))
.expect("gateway should build");
let (gateway_url, gateway_handle) = start_server(gateway).await;
let response = reqwest::Client::new()
.get(format!("{gateway_url}/v1/videos/task-123"))
.header(CONTROL_EXECUTE_FALLBACK_HEADER, "true")
.header(LEGACY_INTERNAL_GATEWAY_HEADER, "true")
.send()
.await
.expect("request should succeed");
assert_eq!(response.status(), StatusCode::SERVICE_UNAVAILABLE);
let payload: serde_json::Value = response.json().await.expect("body should parse");
assert_eq!(payload["error"]["type"], "http_error");
assert_eq!(
payload["error"]["message"],
"OpenAI video executor miss did not match a Rust execution path, and Python fallback has been removed"
);
assert_eq!(*execute_hits.lock().expect("mutex should lock"), 0);
assert_eq!(*public_hits.lock().expect("mutex should lock"), 0);
gateway_handle.abort();
upstream_handle.abort();
}
#[tokio::test]
async fn gateway_uses_video_control_sync_without_legacy_header() {
let execute_hits = Arc::new(Mutex::new(0usize));
let execute_hits_clone = Arc::clone(&execute_hits);
let public_hits = Arc::new(Mutex::new(0usize));
let public_hits_clone = Arc::clone(&public_hits);
let public_execution_path = Arc::new(Mutex::new(None::<String>));
let public_execution_path_clone = Arc::clone(&public_execution_path);
let upstream = Router::new()
.route(
"/api/internal/gateway/execute-sync",
any(move |_request: Request| {
let execute_hits_inner = Arc::clone(&execute_hits_clone);
async move {
*execute_hits_inner.lock().expect("mutex should lock") += 1;
let mut response = Response::builder()
.status(StatusCode::OK)
.body(Body::from("{\"unexpected\":true}"))
.expect("response should build");
response.headers_mut().insert(
HeaderName::from_static(CONTROL_EXECUTED_HEADER),
HeaderValue::from_static("true"),
);
response
}
}),
)
.route(
"/v1/videos/task-123",
any(move |request: Request| {
let public_hits_inner = Arc::clone(&public_hits_clone);
let public_execution_path_inner = Arc::clone(&public_execution_path_clone);
async move {
*public_hits_inner.lock().expect("mutex should lock") += 1;
*public_execution_path_inner
.lock()
.expect("mutex should lock") = Some(
request
.headers()
.get(EXECUTION_PATH_HEADER)
.and_then(|value| value.to_str().ok())
.unwrap_or_default()
.to_string(),
);
(StatusCode::IM_A_TEAPOT, Body::from("public-route-hit"))
}
}),
);
let (upstream_url, upstream_handle) = start_server(upstream).await;
let gateway = build_router_with_control(upstream_url.clone(), Some(upstream_url))
.expect("gateway should build");
let (gateway_url, gateway_handle) = start_server(gateway).await;
let response = reqwest::Client::new()
.get(format!("{gateway_url}/v1/videos/task-123"))
.header(CONTROL_EXECUTE_FALLBACK_HEADER, "true")
.send()
.await
.expect("request should succeed");
assert_eq!(response.status(), StatusCode::OK);
assert_eq!(
response.text().await.expect("body should read"),
"{\"unexpected\":true}"
);
assert_eq!(*execute_hits.lock().expect("mutex should lock"), 1);
assert_eq!(*public_hits.lock().expect("mutex should lock"), 0);
assert_eq!(
public_execution_path
.lock()
.expect("mutex should lock")
.clone()
.as_deref(),
None
);
gateway_handle.abort();
upstream_handle.abort();
}
#[tokio::test]
async fn gateway_skips_video_get_control_sync_without_legacy_header() {
let execute_hits = Arc::new(Mutex::new(0usize));
let execute_hits_clone = Arc::clone(&execute_hits);
let public_hits = Arc::new(Mutex::new(0usize));
let public_hits_clone = Arc::clone(&public_hits);
let upstream = Router::new()
.route(
"/api/internal/gateway/execute-sync",
any(move |_request: Request| {
let execute_hits_inner = Arc::clone(&execute_hits_clone);
async move {
*execute_hits_inner.lock().expect("mutex should lock") += 1;
let mut response = Response::builder()
.status(StatusCode::OK)
.body(Body::from("{\"status\":\"queued\"}"))
.expect("response should build");
response.headers_mut().insert(
HeaderName::from_static(CONTROL_EXECUTED_HEADER),
HeaderValue::from_static("true"),
);
response
}
}),
)
.route(
"/v1/videos/task-123",
any(move |_request: Request| {
let public_hits_inner = Arc::clone(&public_hits_clone);
async move {
*public_hits_inner.lock().expect("mutex should lock") += 1;
(StatusCode::IM_A_TEAPOT, Body::from("public-route-hit"))
}
}),
);
let (upstream_url, upstream_handle) = start_server(upstream).await;
let gateway = build_router_with_control(upstream_url.clone(), Some(upstream_url))
.expect("gateway should build");
let (gateway_url, gateway_handle) = start_server(gateway).await;
let response = reqwest::Client::new()
.get(format!("{gateway_url}/v1/videos/task-123"))
.send()
.await
.expect("request should succeed");
assert_eq!(response.status(), StatusCode::SERVICE_UNAVAILABLE);
let payload: serde_json::Value = response.json().await.expect("body should parse");
assert_eq!(payload["error"]["type"], "http_error");
assert_eq!(
payload["error"]["message"],
"OpenAI video executor miss did not match a Rust execution path, and Python fallback has been removed"
);
assert_eq!(*execute_hits.lock().expect("mutex should lock"), 0);
assert_eq!(*public_hits.lock().expect("mutex should lock"), 0);
gateway_handle.abort();
upstream_handle.abort();
}