feat(saas): Phase 3 — 模型请求中转服务
- OpenAI 兼容 API 代理 (/api/v1/relay/chat/completions) - 中转任务管理 (创建/查询/状态跟踪) - 可用模型列表端点 (仅 enabled providers+models) - 任务生命周期 (queued → processing → completed/failed) - 用量自动记录 (token 统计 + 错误追踪) - 3 个新集成测试覆盖中转端点
This commit is contained in:
@@ -22,6 +22,7 @@ async fn build_test_app() -> axum::Router {
|
||||
let protected_routes = zclaw_saas::auth::protected_routes()
|
||||
.merge(zclaw_saas::account::routes())
|
||||
.merge(zclaw_saas::model_config::routes())
|
||||
.merge(zclaw_saas::relay::routes())
|
||||
.layer(axum::middleware::from_fn_with_state(
|
||||
state.clone(),
|
||||
zclaw_saas::auth::auth_middleware,
|
||||
@@ -288,3 +289,63 @@ async fn test_api_keys_lifecycle() {
|
||||
// provider 不存在 → 404
|
||||
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
|
||||
}
|
||||
|
||||
// ============ Phase 3: 中转服务测试 ============
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_relay_models_list() {
|
||||
let app = build_test_app().await;
|
||||
let token = register_and_login(&app, "relayuser", "relayuser@example.com").await;
|
||||
|
||||
// 列出可用中转模型 (空列表,因为没有 provider/model 种子数据)
|
||||
let req = Request::builder()
|
||||
.method("GET")
|
||||
.uri("/api/v1/relay/models")
|
||||
.header("Authorization", auth_header(&token))
|
||||
.body(Body::empty())
|
||||
.unwrap();
|
||||
|
||||
let resp = app.clone().oneshot(req).await.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
let body_bytes = axum::body::to_bytes(resp.into_body(), MAX_BODY_SIZE).await.unwrap();
|
||||
let body: serde_json::Value = serde_json::from_slice(&body_bytes).unwrap();
|
||||
assert!(body.is_array());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_relay_chat_no_model() {
|
||||
let app = build_test_app().await;
|
||||
let token = register_and_login(&app, "relayfail", "relayfail@example.com").await;
|
||||
|
||||
// 尝试中转到不存在的模型
|
||||
let req = Request::builder()
|
||||
.method("POST")
|
||||
.uri("/api/v1/relay/chat/completions")
|
||||
.header("Content-Type", "application/json")
|
||||
.header("Authorization", auth_header(&token))
|
||||
.body(Body::from(serde_json::to_string(&json!({
|
||||
"model": "nonexistent-model",
|
||||
"messages": [{"role": "user", "content": "hello"}]
|
||||
})).unwrap()))
|
||||
.unwrap();
|
||||
|
||||
let resp = app.clone().oneshot(req).await.unwrap();
|
||||
// 模型不存在 → 404
|
||||
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_relay_tasks_list() {
|
||||
let app = build_test_app().await;
|
||||
let token = register_and_login(&app, "relaytasks", "relaytasks@example.com").await;
|
||||
|
||||
let req = Request::builder()
|
||||
.method("GET")
|
||||
.uri("/api/v1/relay/tasks")
|
||||
.header("Authorization", auth_header(&token))
|
||||
.body(Body::empty())
|
||||
.unwrap();
|
||||
|
||||
let resp = app.oneshot(req).await.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user