Files
MyAiDesk/nomifun-tauri/crates/backend/nomifun-app/tests/remote_agent_e2e.rs
T
freedak f7a720204a Update: 将子项目从 submodule 转为完整内容
- 移除 GovAI, nomifun-tauri, 算力盒子 的 submodule 引用
- 添加所有子项目的完整源代码
- 保留原始 .git 为 .git.bak 备份
2026-07-04 19:20:46 +08:00

375 lines
14 KiB
Rust

//! 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());
}