feat(saas): Phase 4 — 配置迁移模块
- 配置项 CRUD (列表/详情/创建/更新/删除) - 配置分析端点 (按类别汇总, SaaS 托管统计) - 13 个默认配置项种子数据 (server/agent/memory/llm) - 配置同步协议 (客户端→SaaS, SaaS 优先策略) - 同步日志记录和查询 - 3 个新集成测试覆盖配置迁移端点
This commit is contained in:
@@ -23,6 +23,7 @@ async fn build_test_app() -> axum::Router {
|
||||
.merge(zclaw_saas::account::routes())
|
||||
.merge(zclaw_saas::model_config::routes())
|
||||
.merge(zclaw_saas::relay::routes())
|
||||
.merge(zclaw_saas::migration::routes())
|
||||
.layer(axum::middleware::from_fn_with_state(
|
||||
state.clone(),
|
||||
zclaw_saas::auth::auth_middleware,
|
||||
@@ -349,3 +350,92 @@ async fn test_relay_tasks_list() {
|
||||
let resp = app.oneshot(req).await.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
}
|
||||
|
||||
// ============ Phase 4: 配置迁移测试 ============
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_config_analysis_empty() {
|
||||
let app = build_test_app().await;
|
||||
let token = register_and_login(&app, "cfguser", "cfguser@example.com").await;
|
||||
|
||||
// 初始分析 (无种子数据 → 空列表)
|
||||
let req = Request::builder()
|
||||
.method("GET")
|
||||
.uri("/api/v1/config/analysis")
|
||||
.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_eq!(body["total_items"], 0);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_config_seed_and_list() {
|
||||
let app = build_test_app().await;
|
||||
let token = register_and_login(&app, "cfgseed", "cfgseed@example.com").await;
|
||||
|
||||
// 种子配置 (普通用户无权限 → 403)
|
||||
let seed_req = Request::builder()
|
||||
.method("POST")
|
||||
.uri("/api/v1/config/seed")
|
||||
.header("Authorization", auth_header(&token))
|
||||
.body(Body::empty())
|
||||
.unwrap();
|
||||
|
||||
let resp = app.clone().oneshot(seed_req).await.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::FORBIDDEN);
|
||||
|
||||
// 列出配置项 (空列表)
|
||||
let list_req = Request::builder()
|
||||
.method("GET")
|
||||
.uri("/api/v1/config/items")
|
||||
.header("Authorization", auth_header(&token))
|
||||
.body(Body::empty())
|
||||
.unwrap();
|
||||
|
||||
let resp = app.clone().oneshot(list_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());
|
||||
assert_eq!(body.as_array().unwrap().len(), 0);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_config_sync() {
|
||||
let app = build_test_app().await;
|
||||
let token = register_and_login(&app, "cfgsync", "cfgsync@example.com").await;
|
||||
|
||||
let sync_req = Request::builder()
|
||||
.method("POST")
|
||||
.uri("/api/v1/config/sync")
|
||||
.header("Content-Type", "application/json")
|
||||
.header("Authorization", auth_header(&token))
|
||||
.body(Body::from(serde_json::to_string(&json!({
|
||||
"client_fingerprint": "test-desktop-v1",
|
||||
"config_keys": ["server.host", "agent.defaults.default_model"],
|
||||
"client_values": {
|
||||
"server.host": "0.0.0.0",
|
||||
"agent.defaults.default_model": "deepseek/deepseek-chat"
|
||||
}
|
||||
})).unwrap()))
|
||||
.unwrap();
|
||||
|
||||
let resp = app.clone().oneshot(sync_req).await.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
|
||||
// 查看同步日志
|
||||
let logs_req = Request::builder()
|
||||
.method("GET")
|
||||
.uri("/api/v1/config/sync-logs")
|
||||
.header("Authorization", auth_header(&token))
|
||||
.body(Body::empty())
|
||||
.unwrap();
|
||||
|
||||
let resp = app.oneshot(logs_req).await.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user