Update: 将子项目从 submodule 转为完整内容
- 移除 GovAI, nomifun-tauri, 算力盒子 的 submodule 引用 - 添加所有子项目的完整源代码 - 保留原始 .git 为 .git.bak 备份
This commit is contained in:
@@ -0,0 +1,374 @@
|
||||
//! E2E tests for Remote Agent CRUD, connection test, and handshake endpoints.
|
||||
|
||||
mod common;
|
||||
|
||||
use axum::http::StatusCode;
|
||||
use serde_json::json;
|
||||
use tower::ServiceExt;
|
||||
|
||||
use common::{body_json, build_app, delete_with_token, get_with_token, json_with_token, setup_and_login};
|
||||
|
||||
// ── Helpers ───────────────────────────────────────────────────────────
|
||||
|
||||
fn bearer_agent_body() -> serde_json::Value {
|
||||
json!({
|
||||
"name": "Test Remote Server",
|
||||
"protocol": "acp",
|
||||
"url": "wss://remote.example.com",
|
||||
"auth_type": "bearer",
|
||||
"auth_token": "my-secret-token-1234",
|
||||
"description": "Production agent"
|
||||
})
|
||||
}
|
||||
|
||||
fn openclaw_agent_body() -> serde_json::Value {
|
||||
json!({
|
||||
"name": "OpenClaw Agent",
|
||||
"protocol": "openclaw",
|
||||
"url": "wss://openclaw.example.com",
|
||||
"auth_type": "none"
|
||||
})
|
||||
}
|
||||
|
||||
async fn create_agent(app: &mut axum::Router, token: &str, csrf: &str, body: serde_json::Value) -> serde_json::Value {
|
||||
let req = json_with_token("POST", "/api/remote-agents", body, token, csrf);
|
||||
let resp = app.clone().oneshot(req).await.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::CREATED);
|
||||
let json = body_json(resp).await;
|
||||
assert_eq!(json["success"], true);
|
||||
json
|
||||
}
|
||||
|
||||
// ── 1.1 Create Remote Agent ─────────────────────────────────────────
|
||||
|
||||
#[tokio::test]
|
||||
async fn t1_1_create_bearer_agent() {
|
||||
let (mut app, services) = build_app().await;
|
||||
let (token, csrf) = setup_and_login(&mut app, &services, "admin", "StrongP@ss1").await;
|
||||
|
||||
let json = create_agent(&mut app, &token, &csrf, bearer_agent_body()).await;
|
||||
|
||||
let data = &json["data"];
|
||||
assert!(data["id"].as_i64().is_some());
|
||||
assert_eq!(data["name"], "Test Remote Server");
|
||||
assert_eq!(data["protocol"], "acp");
|
||||
assert_eq!(data["url"], "wss://remote.example.com");
|
||||
assert_eq!(data["auth_type"], "bearer");
|
||||
// Auth token should be masked
|
||||
assert_eq!(data["auth_token"], "***1234");
|
||||
assert_eq!(data["status"], "unknown");
|
||||
assert_eq!(data["description"], "Production agent");
|
||||
assert!(data["created_at"].as_i64().is_some());
|
||||
assert!(data["updated_at"].as_i64().is_some());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn t1_2_create_openclaw_agent_generates_device_keys() {
|
||||
let (mut app, services) = build_app().await;
|
||||
let (token, csrf) = setup_and_login(&mut app, &services, "admin", "StrongP@ss1").await;
|
||||
|
||||
let json = create_agent(&mut app, &token, &csrf, openclaw_agent_body()).await;
|
||||
|
||||
let data = &json["data"];
|
||||
assert_eq!(data["protocol"], "openclaw");
|
||||
// Device ID and public key should be generated
|
||||
assert!(data["device_id"].as_str().unwrap().starts_with("dev_"));
|
||||
assert!(data["device_public_key"].as_str().is_some());
|
||||
// Private key should NOT be in the response
|
||||
assert!(data.get("device_private_key").is_none());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn t1_3_create_missing_required_fields() {
|
||||
let (mut app, services) = build_app().await;
|
||||
let (token, csrf) = setup_and_login(&mut app, &services, "admin", "StrongP@ss1").await;
|
||||
|
||||
let body = json!({ "name": "test" });
|
||||
let req = json_with_token("POST", "/api/remote-agents", body, &token, &csrf);
|
||||
let resp = app.oneshot(req).await.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn t1_4_create_unauthenticated() {
|
||||
let (app, _services) = build_app().await;
|
||||
|
||||
let body = bearer_agent_body();
|
||||
let req = axum::http::Request::builder()
|
||||
.method("POST")
|
||||
.uri("/api/remote-agents")
|
||||
.header("content-type", "application/json")
|
||||
.body(axum::body::Body::from(serde_json::to_vec(&body).unwrap()))
|
||||
.unwrap();
|
||||
let resp = app.oneshot(req).await.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::FORBIDDEN);
|
||||
}
|
||||
|
||||
// ── 1.2 List Remote Agents ──────────────────────────────────────────
|
||||
|
||||
#[tokio::test]
|
||||
async fn t2_1_list_returns_agents_without_auth_token() {
|
||||
let (mut app, services) = build_app().await;
|
||||
let (token, csrf) = setup_and_login(&mut app, &services, "admin", "StrongP@ss1").await;
|
||||
|
||||
create_agent(&mut app, &token, &csrf, bearer_agent_body()).await;
|
||||
|
||||
let req = get_with_token("/api/remote-agents", &token);
|
||||
let resp = app.oneshot(req).await.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
|
||||
let json = body_json(resp).await;
|
||||
let data = json["data"].as_array().unwrap();
|
||||
assert_eq!(data.len(), 1);
|
||||
|
||||
// auth_token should NOT appear in list response
|
||||
assert!(data[0].get("auth_token").is_none());
|
||||
assert_eq!(data[0]["name"], "Test Remote Server");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn t2_2_list_empty() {
|
||||
let (mut app, services) = build_app().await;
|
||||
let (token, _csrf) = setup_and_login(&mut app, &services, "admin", "StrongP@ss1").await;
|
||||
|
||||
let req = get_with_token("/api/remote-agents", &token);
|
||||
let resp = app.oneshot(req).await.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
|
||||
let json = body_json(resp).await;
|
||||
let data = json["data"].as_array().unwrap();
|
||||
assert!(data.is_empty());
|
||||
}
|
||||
|
||||
// ── 1.3 Get Single Remote Agent ─────────────────────────────────────
|
||||
|
||||
#[tokio::test]
|
||||
async fn t3_1_get_single_agent_with_masked_token() {
|
||||
let (mut app, services) = build_app().await;
|
||||
let (token, csrf) = setup_and_login(&mut app, &services, "admin", "StrongP@ss1").await;
|
||||
|
||||
let created = create_agent(&mut app, &token, &csrf, bearer_agent_body()).await;
|
||||
let id = created["data"]["id"].as_i64().unwrap();
|
||||
|
||||
let req = get_with_token(&format!("/api/remote-agents/{id}"), &token);
|
||||
let resp = app.oneshot(req).await.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
|
||||
let json = body_json(resp).await;
|
||||
let data = &json["data"];
|
||||
assert_eq!(data["id"], id);
|
||||
assert_eq!(data["auth_token"], "***1234");
|
||||
assert_eq!(data["description"], "Production agent");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn t3_2_get_nonexistent_agent() {
|
||||
let (mut app, services) = build_app().await;
|
||||
let (token, _csrf) = setup_and_login(&mut app, &services, "admin", "StrongP@ss1").await;
|
||||
|
||||
let req = get_with_token("/api/remote-agents/nonexistent-uuid", &token);
|
||||
let resp = app.oneshot(req).await.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
|
||||
}
|
||||
|
||||
// ── 1.4 Update Remote Agent ────────────────────────────────────────
|
||||
|
||||
#[tokio::test]
|
||||
async fn t4_1_update_name_only() {
|
||||
let (mut app, services) = build_app().await;
|
||||
let (token, csrf) = setup_and_login(&mut app, &services, "admin", "StrongP@ss1").await;
|
||||
|
||||
let created = create_agent(&mut app, &token, &csrf, bearer_agent_body()).await;
|
||||
let id = created["data"]["id"].as_i64().unwrap();
|
||||
|
||||
let body = json!({ "name": "Updated Name" });
|
||||
let req = json_with_token("PUT", &format!("/api/remote-agents/{id}"), body, &token, &csrf);
|
||||
let resp = app.oneshot(req).await.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
|
||||
let json = body_json(resp).await;
|
||||
assert_eq!(json["data"]["name"], "Updated Name");
|
||||
// Other fields preserved
|
||||
assert_eq!(json["data"]["protocol"], "acp");
|
||||
assert_eq!(json["data"]["url"], "wss://remote.example.com");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn t4_2_update_multiple_fields() {
|
||||
let (mut app, services) = build_app().await;
|
||||
let (token, csrf) = setup_and_login(&mut app, &services, "admin", "StrongP@ss1").await;
|
||||
|
||||
let created = create_agent(&mut app, &token, &csrf, bearer_agent_body()).await;
|
||||
let id = created["data"]["id"].as_i64().unwrap();
|
||||
|
||||
let body = json!({
|
||||
"name": "Updated",
|
||||
"url": "wss://new-url.example.com",
|
||||
"auth_token": "new-super-secret-token"
|
||||
});
|
||||
let req = json_with_token("PUT", &format!("/api/remote-agents/{id}"), body, &token, &csrf);
|
||||
let resp = app.oneshot(req).await.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
|
||||
let json = body_json(resp).await;
|
||||
assert_eq!(json["data"]["name"], "Updated");
|
||||
assert_eq!(json["data"]["url"], "wss://new-url.example.com");
|
||||
assert_eq!(json["data"]["auth_token"], "***oken");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn t4_3_update_nonexistent_agent() {
|
||||
let (mut app, services) = build_app().await;
|
||||
let (token, csrf) = setup_and_login(&mut app, &services, "admin", "StrongP@ss1").await;
|
||||
|
||||
let body = json!({ "name": "Doesn't Matter" });
|
||||
let req = json_with_token("PUT", "/api/remote-agents/nonexistent-uuid", body, &token, &csrf);
|
||||
let resp = app.oneshot(req).await.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
|
||||
}
|
||||
|
||||
// ── 1.5 Delete Remote Agent ────────────────────────────────────────
|
||||
|
||||
#[tokio::test]
|
||||
async fn t5_1_delete_agent() {
|
||||
let (mut app, services) = build_app().await;
|
||||
let (token, csrf) = setup_and_login(&mut app, &services, "admin", "StrongP@ss1").await;
|
||||
|
||||
let created = create_agent(&mut app, &token, &csrf, bearer_agent_body()).await;
|
||||
let id = created["data"]["id"].as_i64().unwrap();
|
||||
|
||||
let req = delete_with_token(&format!("/api/remote-agents/{id}"), &token, &csrf);
|
||||
let resp = app.clone().oneshot(req).await.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
|
||||
// Verify it's gone
|
||||
let req = get_with_token(&format!("/api/remote-agents/{id}"), &token);
|
||||
let resp = app.oneshot(req).await.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn t5_2_delete_nonexistent_agent() {
|
||||
let (mut app, services) = build_app().await;
|
||||
let (token, csrf) = setup_and_login(&mut app, &services, "admin", "StrongP@ss1").await;
|
||||
|
||||
let req = delete_with_token("/api/remote-agents/nonexistent-uuid", &token, &csrf);
|
||||
let resp = app.oneshot(req).await.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
|
||||
}
|
||||
|
||||
// ── 1.6 Connection Test ─────────────────────────────────────────────
|
||||
|
||||
#[tokio::test]
|
||||
async fn t6_1_test_connection_invalid_protocol() {
|
||||
let (mut app, services) = build_app().await;
|
||||
let (token, csrf) = setup_and_login(&mut app, &services, "admin", "StrongP@ss1").await;
|
||||
|
||||
let body = json!({
|
||||
"url": "http://example.com",
|
||||
"auth_type": "bearer"
|
||||
});
|
||||
let req = json_with_token("POST", "/api/remote-agents/test-connection", body, &token, &csrf);
|
||||
let resp = app.oneshot(req).await.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
|
||||
|
||||
let json = body_json(resp).await;
|
||||
assert_eq!(json["success"], false);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn t6_2_test_connection_unauthenticated() {
|
||||
let (app, _services) = build_app().await;
|
||||
|
||||
let body = json!({
|
||||
"url": "wss://remote.example.com"
|
||||
});
|
||||
let req = axum::http::Request::builder()
|
||||
.method("POST")
|
||||
.uri("/api/remote-agents/test-connection")
|
||||
.header("content-type", "application/json")
|
||||
.body(axum::body::Body::from(serde_json::to_vec(&body).unwrap()))
|
||||
.unwrap();
|
||||
let resp = app.oneshot(req).await.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::FORBIDDEN);
|
||||
}
|
||||
|
||||
// ── 1.7 Handshake ───────────────────────────────────────────────────
|
||||
|
||||
#[tokio::test]
|
||||
async fn t7_1_handshake_non_openclaw_protocol() {
|
||||
let (mut app, services) = build_app().await;
|
||||
let (token, csrf) = setup_and_login(&mut app, &services, "admin", "StrongP@ss1").await;
|
||||
|
||||
let created = create_agent(&mut app, &token, &csrf, bearer_agent_body()).await;
|
||||
let id = created["data"]["id"].as_i64().unwrap();
|
||||
|
||||
let req = json_with_token(
|
||||
"POST",
|
||||
&format!("/api/remote-agents/{id}/handshake"),
|
||||
json!({}),
|
||||
&token,
|
||||
&csrf,
|
||||
);
|
||||
let resp = app.oneshot(req).await.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn t7_2_handshake_nonexistent_agent() {
|
||||
let (mut app, services) = build_app().await;
|
||||
let (token, csrf) = setup_and_login(&mut app, &services, "admin", "StrongP@ss1").await;
|
||||
|
||||
let req = json_with_token(
|
||||
"POST",
|
||||
"/api/remote-agents/nonexistent-uuid/handshake",
|
||||
json!({}),
|
||||
&token,
|
||||
&csrf,
|
||||
);
|
||||
let resp = app.oneshot(req).await.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
|
||||
}
|
||||
|
||||
// ── Full CRUD lifecycle ─────────────────────────────────────────────
|
||||
|
||||
#[tokio::test]
|
||||
async fn t8_full_crud_lifecycle() {
|
||||
let (mut app, services) = build_app().await;
|
||||
let (token, csrf) = setup_and_login(&mut app, &services, "admin", "StrongP@ss1").await;
|
||||
|
||||
// Create
|
||||
let created = create_agent(&mut app, &token, &csrf, bearer_agent_body()).await;
|
||||
let id = created["data"]["id"].as_i64().unwrap();
|
||||
|
||||
// Read list
|
||||
let req = get_with_token("/api/remote-agents", &token);
|
||||
let resp = app.clone().oneshot(req).await.unwrap();
|
||||
let json = body_json(resp).await;
|
||||
assert_eq!(json["data"].as_array().unwrap().len(), 1);
|
||||
|
||||
// Read single
|
||||
let req = get_with_token(&format!("/api/remote-agents/{id}"), &token);
|
||||
let resp = app.clone().oneshot(req).await.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
|
||||
// Update
|
||||
let body = json!({ "name": "Renamed Server", "description": "Updated desc" });
|
||||
let req = json_with_token("PUT", &format!("/api/remote-agents/{id}"), body, &token, &csrf);
|
||||
let resp = app.clone().oneshot(req).await.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
let json = body_json(resp).await;
|
||||
assert_eq!(json["data"]["name"], "Renamed Server");
|
||||
assert_eq!(json["data"]["description"], "Updated desc");
|
||||
|
||||
// Delete
|
||||
let req = delete_with_token(&format!("/api/remote-agents/{id}"), &token, &csrf);
|
||||
let resp = app.clone().oneshot(req).await.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
|
||||
// Verify deleted
|
||||
let req = get_with_token("/api/remote-agents", &token);
|
||||
let resp = app.oneshot(req).await.unwrap();
|
||||
let json = body_json(resp).await;
|
||||
assert!(json["data"].as_array().unwrap().is_empty());
|
||||
}
|
||||
Reference in New Issue
Block a user