mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-11 03:39:49 +08:00
refactor: 移除独立 hub/proxy/executor/gateway crate,统一为 gateway tunnel 架构
- 删除 aether-hub、aether-proxy 独立项目及其 Dockerfile/配置 - 删除 crates/aether-executor 和 crates/aether-gateway 全部模块 - 新增 apps/ 目录作为应用入口 - 将 hub 概念重构为 gateway tunnel transport - 将 executor 重构为 execution runtime - 新增 tunnel.rs 合约定义和 testkit tunnel/execution_runtime 模块 - 更新 Python 服务层和测试适配新架构命名
This commit is contained in:
@@ -0,0 +1,202 @@
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn gateway_handles_public_openai_models_without_proxying_python_upstream() {
|
||||
let python_upstream_hits = Arc::new(Mutex::new(0usize));
|
||||
let python_upstream_hits_clone = Arc::clone(&python_upstream_hits);
|
||||
let python_upstream = Router::new().route(
|
||||
"/{*path}",
|
||||
any(move |_request: Request| {
|
||||
let python_upstream_hits_inner = Arc::clone(&python_upstream_hits_clone);
|
||||
async move {
|
||||
*python_upstream_hits_inner
|
||||
.lock()
|
||||
.expect("mutex should lock") += 1;
|
||||
(StatusCode::OK, Body::from("proxied"))
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
let auth_repository = Arc::new(InMemoryAuthApiKeySnapshotRepository::seed(vec![(
|
||||
Some(hash_api_key("sk-openai-models")),
|
||||
unrestricted_models_snapshot("key-1", "user-1"),
|
||||
)]));
|
||||
let candidate_repository =
|
||||
Arc::new(InMemoryMinimalCandidateSelectionReadRepository::seed(vec![
|
||||
sample_models_candidate_row("provider-openai", "openai", "openai:chat", "gpt-5", 10),
|
||||
sample_models_candidate_row("provider-openai", "openai", "openai:chat", "gpt-4.1", 10),
|
||||
]));
|
||||
|
||||
let (python_upstream_url, python_upstream_handle) = start_server(python_upstream).await;
|
||||
let gateway = build_router_with_state(
|
||||
AppState::new(python_upstream_url)
|
||||
.expect("gateway should build")
|
||||
.with_data_state_for_tests(
|
||||
crate::gateway::gateway_data::GatewayDataState::with_minimal_candidate_selection_and_auth_for_tests(
|
||||
candidate_repository,
|
||||
auth_repository,
|
||||
),
|
||||
),
|
||||
);
|
||||
let (gateway_url, gateway_handle) = start_server(gateway).await;
|
||||
|
||||
let response = reqwest::Client::new()
|
||||
.get(format!("{gateway_url}/v1/models"))
|
||||
.header("authorization", "Bearer sk-openai-models")
|
||||
.send()
|
||||
.await
|
||||
.expect("request should succeed");
|
||||
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
let payload: serde_json::Value = response.json().await.expect("json body should parse");
|
||||
assert_eq!(payload["object"], "list");
|
||||
assert_eq!(payload["data"][0]["id"], "gpt-4.1");
|
||||
assert_eq!(payload["data"][1]["id"], "gpt-5");
|
||||
assert_eq!(payload["data"][0]["owned_by"], "openai");
|
||||
assert_eq!(*python_upstream_hits.lock().expect("mutex should lock"), 0);
|
||||
|
||||
gateway_handle.abort();
|
||||
python_upstream_handle.abort();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn gateway_handles_public_claude_models_without_proxying_python_upstream() {
|
||||
let python_upstream_hits = Arc::new(Mutex::new(0usize));
|
||||
let python_upstream_hits_clone = Arc::clone(&python_upstream_hits);
|
||||
let python_upstream = Router::new().route(
|
||||
"/{*path}",
|
||||
any(move |_request: Request| {
|
||||
let python_upstream_hits_inner = Arc::clone(&python_upstream_hits_clone);
|
||||
async move {
|
||||
*python_upstream_hits_inner
|
||||
.lock()
|
||||
.expect("mutex should lock") += 1;
|
||||
(StatusCode::OK, Json(json!({"proxied": true}))).into_response()
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
let auth_repository = Arc::new(InMemoryAuthApiKeySnapshotRepository::seed(vec![(
|
||||
Some(hash_api_key("sk-claude-models")),
|
||||
unrestricted_models_snapshot("key-claude", "user-claude"),
|
||||
)]));
|
||||
let candidate_repository =
|
||||
Arc::new(InMemoryMinimalCandidateSelectionReadRepository::seed(vec![
|
||||
sample_models_candidate_row(
|
||||
"provider-claude",
|
||||
"claude",
|
||||
"claude:chat",
|
||||
"claude-3-7-sonnet",
|
||||
10,
|
||||
),
|
||||
sample_models_candidate_row(
|
||||
"provider-claude",
|
||||
"claude",
|
||||
"claude:chat",
|
||||
"claude-3-5-haiku",
|
||||
10,
|
||||
),
|
||||
]));
|
||||
|
||||
let (python_upstream_url, python_upstream_handle) = start_server(python_upstream).await;
|
||||
let gateway = build_router_with_state(
|
||||
AppState::new(python_upstream_url)
|
||||
.expect("gateway should build")
|
||||
.with_data_state_for_tests(
|
||||
crate::gateway::gateway_data::GatewayDataState::with_minimal_candidate_selection_and_auth_for_tests(
|
||||
candidate_repository,
|
||||
auth_repository,
|
||||
),
|
||||
),
|
||||
);
|
||||
let (gateway_url, gateway_handle) = start_server(gateway).await;
|
||||
|
||||
let response = reqwest::Client::new()
|
||||
.get(format!("{gateway_url}/v1/models?limit=1"))
|
||||
.header("x-api-key", "sk-claude-models")
|
||||
.header("anthropic-version", "2023-06-01")
|
||||
.send()
|
||||
.await
|
||||
.expect("request should succeed");
|
||||
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
let payload: serde_json::Value = response.json().await.expect("json body should parse");
|
||||
assert_eq!(payload["data"][0]["id"], "claude-3-5-haiku");
|
||||
assert_eq!(payload["first_id"], "claude-3-5-haiku");
|
||||
assert_eq!(payload["last_id"], "claude-3-5-haiku");
|
||||
assert_eq!(payload["has_more"], true);
|
||||
assert_eq!(*python_upstream_hits.lock().expect("mutex should lock"), 0);
|
||||
|
||||
gateway_handle.abort();
|
||||
python_upstream_handle.abort();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn gateway_handles_public_gemini_models_without_proxying_python_upstream() {
|
||||
let python_upstream_hits = Arc::new(Mutex::new(0usize));
|
||||
let python_upstream_hits_clone = Arc::clone(&python_upstream_hits);
|
||||
let python_upstream = Router::new().route(
|
||||
"/{*path}",
|
||||
any(move |_request: Request| {
|
||||
let python_upstream_hits_inner = Arc::clone(&python_upstream_hits_clone);
|
||||
async move {
|
||||
*python_upstream_hits_inner
|
||||
.lock()
|
||||
.expect("mutex should lock") += 1;
|
||||
(StatusCode::OK, Json(json!({"proxied": true}))).into_response()
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
let auth_repository = Arc::new(InMemoryAuthApiKeySnapshotRepository::seed(vec![(
|
||||
Some(hash_api_key("sk-gemini-models")),
|
||||
unrestricted_models_snapshot("key-gemini", "user-gemini"),
|
||||
)]));
|
||||
let candidate_repository =
|
||||
Arc::new(InMemoryMinimalCandidateSelectionReadRepository::seed(vec![
|
||||
sample_models_candidate_row(
|
||||
"provider-gemini",
|
||||
"gemini",
|
||||
"gemini:chat",
|
||||
"gemini-2.5-flash",
|
||||
10,
|
||||
),
|
||||
sample_models_candidate_row(
|
||||
"provider-gemini",
|
||||
"gemini",
|
||||
"gemini:chat",
|
||||
"gemini-2.5-pro",
|
||||
10,
|
||||
),
|
||||
]));
|
||||
|
||||
let (python_upstream_url, python_upstream_handle) = start_server(python_upstream).await;
|
||||
let gateway = build_router_with_state(
|
||||
AppState::new(python_upstream_url)
|
||||
.expect("gateway should build")
|
||||
.with_data_state_for_tests(
|
||||
crate::gateway::gateway_data::GatewayDataState::with_minimal_candidate_selection_and_auth_for_tests(
|
||||
candidate_repository,
|
||||
auth_repository,
|
||||
),
|
||||
),
|
||||
);
|
||||
let (gateway_url, gateway_handle) = start_server(gateway).await;
|
||||
|
||||
let response = reqwest::Client::new()
|
||||
.get(format!(
|
||||
"{gateway_url}/v1beta/models?pageSize=1&key=sk-gemini-models"
|
||||
))
|
||||
.send()
|
||||
.await
|
||||
.expect("request should succeed");
|
||||
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
let payload: serde_json::Value = response.json().await.expect("json body should parse");
|
||||
assert_eq!(payload["models"][0]["name"], "models/gemini-2.5-flash");
|
||||
assert_eq!(payload["nextPageToken"], "1");
|
||||
assert_eq!(*python_upstream_hits.lock().expect("mutex should lock"), 0);
|
||||
|
||||
gateway_handle.abort();
|
||||
python_upstream_handle.abort();
|
||||
}
|
||||
Reference in New Issue
Block a user