Update: 将子项目从 submodule 转为完整内容
- 移除 GovAI, nomifun-tauri, 算力盒子 的 submodule 引用 - 添加所有子项目的完整源代码 - 保留原始 .git 为 .git.bak 备份
This commit is contained in:
@@ -0,0 +1,340 @@
|
||||
//! Black-box integration tests for `IRemoteAgentRepository`.
|
||||
//!
|
||||
//! Tests exercise the repository trait interface without knowledge of
|
||||
//! the underlying SQLite implementation details.
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use nomifun_db::{
|
||||
CreateRemoteAgentParams, DbError, IRemoteAgentRepository, SqliteRemoteAgentRepository, UpdateRemoteAgentParams,
|
||||
init_database_memory,
|
||||
};
|
||||
|
||||
async fn repo() -> (Arc<dyn IRemoteAgentRepository>, nomifun_db::Database) {
|
||||
let db = init_database_memory().await.unwrap();
|
||||
let r = Arc::new(SqliteRemoteAgentRepository::new(db.pool().clone()));
|
||||
(r as Arc<dyn IRemoteAgentRepository>, db)
|
||||
}
|
||||
|
||||
fn bearer_params() -> CreateRemoteAgentParams<'static> {
|
||||
CreateRemoteAgentParams {
|
||||
name: "Remote Server",
|
||||
protocol: "acp",
|
||||
url: "wss://remote.example.com",
|
||||
auth_type: "bearer",
|
||||
auth_token: Some("encrypted_bearer_token"),
|
||||
allow_insecure: false,
|
||||
avatar: None,
|
||||
description: Some("Production agent"),
|
||||
device_id: None,
|
||||
device_public_key: None,
|
||||
device_private_key: None,
|
||||
device_token: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn openclaw_params() -> CreateRemoteAgentParams<'static> {
|
||||
CreateRemoteAgentParams {
|
||||
name: "OpenClaw Agent",
|
||||
protocol: "openClaw",
|
||||
url: "wss://openclaw.example.com",
|
||||
auth_type: "none",
|
||||
auth_token: None,
|
||||
allow_insecure: false,
|
||||
avatar: Some("https://example.com/avatar.png"),
|
||||
description: None,
|
||||
device_id: Some("dev-abc-123"),
|
||||
device_public_key: Some("enc_ed25519_pub"),
|
||||
device_private_key: Some("enc_ed25519_priv"),
|
||||
device_token: Some("enc_device_tok"),
|
||||
}
|
||||
}
|
||||
|
||||
// -- 1.1 Create Remote Agent --
|
||||
|
||||
#[tokio::test]
|
||||
async fn create_bearer_agent_returns_complete_object() {
|
||||
let (r, _db) = repo().await;
|
||||
let agent = r.create(bearer_params()).await.unwrap();
|
||||
|
||||
assert!(agent.id > 0);
|
||||
assert_eq!(agent.name, "Remote Server");
|
||||
assert_eq!(agent.protocol, "acp");
|
||||
assert_eq!(agent.url, "wss://remote.example.com");
|
||||
assert_eq!(agent.auth_type, "bearer");
|
||||
assert_eq!(agent.auth_token.as_deref(), Some("encrypted_bearer_token"));
|
||||
assert!(!agent.allow_insecure);
|
||||
assert_eq!(agent.status, "unknown");
|
||||
assert!(agent.last_connected_at.is_none());
|
||||
assert!(agent.created_at > 0);
|
||||
assert!(agent.updated_at > 0);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn create_openclaw_agent_includes_device_fields() {
|
||||
let (r, _db) = repo().await;
|
||||
let agent = r.create(openclaw_params()).await.unwrap();
|
||||
|
||||
assert_eq!(agent.protocol, "openClaw");
|
||||
assert_eq!(agent.device_id.as_deref(), Some("dev-abc-123"));
|
||||
assert_eq!(agent.device_public_key.as_deref(), Some("enc_ed25519_pub"));
|
||||
assert_eq!(agent.device_private_key.as_deref(), Some("enc_ed25519_priv"));
|
||||
assert_eq!(agent.device_token.as_deref(), Some("enc_device_tok"));
|
||||
}
|
||||
|
||||
// -- 1.2 List Remote Agents --
|
||||
|
||||
#[tokio::test]
|
||||
async fn list_empty_returns_empty_vec() {
|
||||
let (r, _db) = repo().await;
|
||||
let agents = r.list().await.unwrap();
|
||||
assert!(agents.is_empty());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn list_returns_all_agents_ordered() {
|
||||
let (r, _db) = repo().await;
|
||||
let a1 = r.create(bearer_params()).await.unwrap();
|
||||
let a2 = r.create(openclaw_params()).await.unwrap();
|
||||
|
||||
let all = r.list().await.unwrap();
|
||||
assert_eq!(all.len(), 2);
|
||||
assert_eq!(all[0].id, a1.id);
|
||||
assert_eq!(all[1].id, a2.id);
|
||||
}
|
||||
|
||||
// -- 1.3 Get Single Remote Agent --
|
||||
|
||||
#[tokio::test]
|
||||
async fn find_by_id_returns_full_record() {
|
||||
let (r, _db) = repo().await;
|
||||
let created = r.create(bearer_params()).await.unwrap();
|
||||
|
||||
let found = r.find_by_id(created.id).await.unwrap().unwrap();
|
||||
assert_eq!(found.id, created.id);
|
||||
assert_eq!(found.name, "Remote Server");
|
||||
assert_eq!(found.auth_token.as_deref(), Some("encrypted_bearer_token"));
|
||||
assert_eq!(found.description.as_deref(), Some("Production agent"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn find_by_id_nonexistent_returns_none() {
|
||||
let (r, _db) = repo().await;
|
||||
let result = r.find_by_id(999_999).await.unwrap();
|
||||
assert!(result.is_none());
|
||||
}
|
||||
|
||||
// -- 1.4 Update Remote Agent --
|
||||
|
||||
#[tokio::test]
|
||||
async fn update_name_only_preserves_other_fields() {
|
||||
let (r, _db) = repo().await;
|
||||
let created = r.create(bearer_params()).await.unwrap();
|
||||
|
||||
let updated = r
|
||||
.update(
|
||||
created.id,
|
||||
UpdateRemoteAgentParams {
|
||||
name: Some("New Name"),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(updated.name, "New Name");
|
||||
assert_eq!(updated.protocol, created.protocol);
|
||||
assert_eq!(updated.url, created.url);
|
||||
assert_eq!(updated.auth_type, created.auth_type);
|
||||
assert_eq!(updated.auth_token, created.auth_token);
|
||||
assert_eq!(updated.allow_insecure, created.allow_insecure);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn update_multiple_fields() {
|
||||
let (r, _db) = repo().await;
|
||||
let created = r.create(bearer_params()).await.unwrap();
|
||||
|
||||
let updated = r
|
||||
.update(
|
||||
created.id,
|
||||
UpdateRemoteAgentParams {
|
||||
name: Some("Updated"),
|
||||
url: Some("wss://new-url.example.com"),
|
||||
auth_token: Some(Some("new_encrypted_token")),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(updated.name, "Updated");
|
||||
assert_eq!(updated.url, "wss://new-url.example.com");
|
||||
assert_eq!(updated.auth_token.as_deref(), Some("new_encrypted_token"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn update_nonexistent_returns_not_found() {
|
||||
let (r, _db) = repo().await;
|
||||
let err = r
|
||||
.update(999_999, UpdateRemoteAgentParams::default())
|
||||
.await
|
||||
.unwrap_err();
|
||||
assert!(matches!(err, DbError::NotFound(_)));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn update_can_clear_optional_fields() {
|
||||
let (r, _db) = repo().await;
|
||||
let created = r.create(bearer_params()).await.unwrap();
|
||||
assert!(created.description.is_some());
|
||||
assert!(created.auth_token.is_some());
|
||||
|
||||
let updated = r
|
||||
.update(
|
||||
created.id,
|
||||
UpdateRemoteAgentParams {
|
||||
description: Some(None),
|
||||
auth_token: Some(None),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert!(updated.description.is_none());
|
||||
assert!(updated.auth_token.is_none());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn update_persists_to_database() {
|
||||
let (r, _db) = repo().await;
|
||||
let created = r.create(bearer_params()).await.unwrap();
|
||||
|
||||
r.update(
|
||||
created.id,
|
||||
UpdateRemoteAgentParams {
|
||||
name: Some("Persisted Name"),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let found = r.find_by_id(created.id).await.unwrap().unwrap();
|
||||
assert_eq!(found.name, "Persisted Name");
|
||||
}
|
||||
|
||||
// -- 1.5 Delete Remote Agent --
|
||||
|
||||
#[tokio::test]
|
||||
async fn delete_existing_removes_record() {
|
||||
let (r, _db) = repo().await;
|
||||
let created = r.create(bearer_params()).await.unwrap();
|
||||
|
||||
r.delete(created.id).await.unwrap();
|
||||
|
||||
let found = r.find_by_id(created.id).await.unwrap();
|
||||
assert!(found.is_none());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn delete_nonexistent_returns_not_found() {
|
||||
let (r, _db) = repo().await;
|
||||
let err = r.delete(999_999).await.unwrap_err();
|
||||
assert!(matches!(err, DbError::NotFound(_)));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn delete_one_does_not_affect_others() {
|
||||
let (r, _db) = repo().await;
|
||||
let a1 = r.create(bearer_params()).await.unwrap();
|
||||
let a2 = r.create(openclaw_params()).await.unwrap();
|
||||
|
||||
r.delete(a1.id).await.unwrap();
|
||||
|
||||
let remaining = r.list().await.unwrap();
|
||||
assert_eq!(remaining.len(), 1);
|
||||
assert_eq!(remaining[0].id, a2.id);
|
||||
}
|
||||
|
||||
// -- Status updates --
|
||||
|
||||
#[tokio::test]
|
||||
async fn update_status_to_connected_with_timestamp() {
|
||||
let (r, _db) = repo().await;
|
||||
let created = r.create(bearer_params()).await.unwrap();
|
||||
|
||||
let ts = nomifun_common::now_ms();
|
||||
r.update_status(created.id, "connected", Some(ts)).await.unwrap();
|
||||
|
||||
let found = r.find_by_id(created.id).await.unwrap().unwrap();
|
||||
assert_eq!(found.status, "connected");
|
||||
assert_eq!(found.last_connected_at, Some(ts));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn update_status_to_error_without_timestamp() {
|
||||
let (r, _db) = repo().await;
|
||||
let created = r.create(bearer_params()).await.unwrap();
|
||||
|
||||
r.update_status(created.id, "error", None).await.unwrap();
|
||||
|
||||
let found = r.find_by_id(created.id).await.unwrap().unwrap();
|
||||
assert_eq!(found.status, "error");
|
||||
assert!(found.last_connected_at.is_none());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn update_status_nonexistent_returns_not_found() {
|
||||
let (r, _db) = repo().await;
|
||||
let err = r
|
||||
.update_status(999_999, "connected", None)
|
||||
.await
|
||||
.unwrap_err();
|
||||
assert!(matches!(err, DbError::NotFound(_)));
|
||||
}
|
||||
|
||||
// -- Full CRUD lifecycle --
|
||||
|
||||
#[tokio::test]
|
||||
async fn full_crud_lifecycle() {
|
||||
let (r, _db) = repo().await;
|
||||
|
||||
// Create
|
||||
let created = r.create(bearer_params()).await.unwrap();
|
||||
assert_eq!(created.name, "Remote Server");
|
||||
|
||||
// Read
|
||||
let found = r.find_by_id(created.id).await.unwrap().unwrap();
|
||||
assert_eq!(found.id, created.id);
|
||||
|
||||
// Update
|
||||
let updated = r
|
||||
.update(
|
||||
created.id,
|
||||
UpdateRemoteAgentParams {
|
||||
name: Some("Renamed Server"),
|
||||
description: Some(Some("Updated desc")),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(updated.name, "Renamed Server");
|
||||
assert_eq!(updated.description.as_deref(), Some("Updated desc"));
|
||||
|
||||
// Update status
|
||||
r.update_status(created.id, "connected", Some(nomifun_common::now_ms()))
|
||||
.await
|
||||
.unwrap();
|
||||
let after_status = r.find_by_id(created.id).await.unwrap().unwrap();
|
||||
assert_eq!(after_status.status, "connected");
|
||||
|
||||
// Delete
|
||||
r.delete(created.id).await.unwrap();
|
||||
assert!(r.find_by_id(created.id).await.unwrap().is_none());
|
||||
|
||||
// List should be empty
|
||||
assert!(r.list().await.unwrap().is_empty());
|
||||
}
|
||||
Reference in New Issue
Block a user