f7a720204a
- 移除 GovAI, nomifun-tauri, 算力盒子 的 submodule 引用 - 添加所有子项目的完整源代码 - 保留原始 .git 为 .git.bak 备份
352 lines
10 KiB
Rust
352 lines
10 KiB
Rust
//! Integration tests for McpConfigService with real SQLite.
|
|
//!
|
|
//! Tests from test-plan §1 (CRUD) at the service layer.
|
|
|
|
use std::collections::HashMap;
|
|
use std::sync::Arc;
|
|
|
|
use nomifun_api_types::{
|
|
BatchImportMcpServersRequest, CreateMcpServerRequest, ImportMcpServerRequest, McpTransport, UpdateMcpServerRequest,
|
|
};
|
|
use nomifun_db::SqliteMcpServerRepository;
|
|
use nomifun_mcp::{McpConfigService, McpError};
|
|
|
|
async fn make_service() -> McpConfigService {
|
|
let db = nomifun_db::init_database_memory().await.unwrap();
|
|
let repo = Arc::new(SqliteMcpServerRepository::new(db.pool().clone()));
|
|
McpConfigService::new(repo)
|
|
}
|
|
|
|
fn stdio_req(name: &str) -> CreateMcpServerRequest {
|
|
CreateMcpServerRequest {
|
|
name: name.to_owned(),
|
|
description: Some("test".to_owned()),
|
|
transport: McpTransport::Stdio {
|
|
command: "npx".into(),
|
|
args: vec!["-y".into(), "@test/server".into()],
|
|
env: HashMap::new(),
|
|
},
|
|
original_json: None,
|
|
builtin: false,
|
|
}
|
|
}
|
|
|
|
fn http_req(name: &str) -> CreateMcpServerRequest {
|
|
CreateMcpServerRequest {
|
|
name: name.to_owned(),
|
|
description: None,
|
|
transport: McpTransport::Http {
|
|
url: "https://example.com/mcp".into(),
|
|
headers: HashMap::from([("Auth".into(), "Bearer tok".into())]),
|
|
},
|
|
original_json: None,
|
|
builtin: false,
|
|
}
|
|
}
|
|
|
|
fn stdio_import_req(name: &str) -> ImportMcpServerRequest {
|
|
ImportMcpServerRequest {
|
|
name: name.to_owned(),
|
|
description: Some("test".to_owned()),
|
|
transport: McpTransport::Stdio {
|
|
command: "npx".into(),
|
|
args: vec!["-y".into(), "@test/server".into()],
|
|
env: HashMap::new(),
|
|
},
|
|
original_json: None,
|
|
builtin: false,
|
|
enabled: None,
|
|
}
|
|
}
|
|
|
|
fn http_import_req(name: &str) -> ImportMcpServerRequest {
|
|
ImportMcpServerRequest {
|
|
name: name.to_owned(),
|
|
description: None,
|
|
transport: McpTransport::Http {
|
|
url: "https://example.com/mcp".into(),
|
|
headers: HashMap::from([("Auth".into(), "Bearer tok".into())]),
|
|
},
|
|
original_json: None,
|
|
builtin: false,
|
|
enabled: None,
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Create
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[tokio::test]
|
|
async fn create_and_get_stdio_server() {
|
|
let svc = make_service().await;
|
|
let resp = svc.add_server(stdio_req("test-stdio")).await.unwrap();
|
|
|
|
// Host-local INTEGER primary key, surfaced as a number on the DTO.
|
|
assert!(resp.id > 0);
|
|
assert_eq!(resp.name, "test-stdio");
|
|
assert!(!resp.enabled);
|
|
assert_eq!(resp.description.as_deref(), Some("test"));
|
|
|
|
let found = svc.get_server(&resp.id.to_string()).await.unwrap();
|
|
assert_eq!(found.id, resp.id);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn create_http_with_headers() {
|
|
let svc = make_service().await;
|
|
let resp = svc.add_server(http_req("test-http")).await.unwrap();
|
|
|
|
match resp.transport {
|
|
McpTransport::Http { ref url, ref headers } => {
|
|
assert_eq!(url, "https://example.com/mcp");
|
|
assert_eq!(headers.get("Auth").unwrap(), "Bearer tok");
|
|
}
|
|
_ => panic!("expected Http"),
|
|
}
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn create_same_name_upserts() {
|
|
let svc = make_service().await;
|
|
let first = svc.add_server(stdio_req("dup")).await.unwrap();
|
|
let second = svc.add_server(http_req("dup")).await.unwrap();
|
|
|
|
assert_eq!(first.id, second.id);
|
|
match second.transport {
|
|
McpTransport::Http { .. } => {}
|
|
_ => panic!("expected Http after upsert"),
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Read
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[tokio::test]
|
|
async fn list_empty() {
|
|
let svc = make_service().await;
|
|
assert!(svc.list_servers().await.unwrap().is_empty());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn list_returns_all() {
|
|
let svc = make_service().await;
|
|
svc.add_server(stdio_req("a")).await.unwrap();
|
|
svc.add_server(http_req("b")).await.unwrap();
|
|
assert_eq!(svc.list_servers().await.unwrap().len(), 2);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn get_not_found() {
|
|
let svc = make_service().await;
|
|
let err = svc.get_server("nonexistent").await.unwrap_err();
|
|
assert!(matches!(err, McpError::NotFound(_)));
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Update
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[tokio::test]
|
|
async fn edit_name_is_rejected() {
|
|
let svc = make_service().await;
|
|
let created = svc.add_server(stdio_req("old")).await.unwrap();
|
|
|
|
let err = svc
|
|
.edit_server(
|
|
&created.id.to_string(),
|
|
UpdateMcpServerRequest {
|
|
name: Some("new".into()),
|
|
description: None,
|
|
transport: None,
|
|
original_json: None,
|
|
builtin: None,
|
|
},
|
|
)
|
|
.await
|
|
.unwrap_err();
|
|
assert!(matches!(err, McpError::InvalidEdit(_)));
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn edit_transport() {
|
|
let svc = make_service().await;
|
|
let created = svc.add_server(stdio_req("test")).await.unwrap();
|
|
|
|
let updated = svc
|
|
.edit_server(
|
|
&created.id.to_string(),
|
|
UpdateMcpServerRequest {
|
|
name: None,
|
|
description: None,
|
|
transport: Some(McpTransport::Sse {
|
|
url: "https://new.url".into(),
|
|
headers: HashMap::new(),
|
|
}),
|
|
original_json: None,
|
|
builtin: None,
|
|
},
|
|
)
|
|
.await
|
|
.unwrap();
|
|
match updated.transport {
|
|
McpTransport::Sse { ref url, .. } => assert_eq!(url, "https://new.url"),
|
|
_ => panic!("expected Sse"),
|
|
}
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn edit_clears_description() {
|
|
let svc = make_service().await;
|
|
let created = svc.add_server(stdio_req("test")).await.unwrap();
|
|
assert!(created.description.is_some());
|
|
|
|
let updated = svc
|
|
.edit_server(
|
|
&created.id.to_string(),
|
|
UpdateMcpServerRequest {
|
|
name: None,
|
|
description: Some(None),
|
|
transport: None,
|
|
original_json: None,
|
|
builtin: None,
|
|
},
|
|
)
|
|
.await
|
|
.unwrap();
|
|
assert!(updated.description.is_none());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn edit_not_found() {
|
|
let svc = make_service().await;
|
|
let err = svc
|
|
.edit_server(
|
|
"nonexistent",
|
|
UpdateMcpServerRequest {
|
|
name: Some("x".into()),
|
|
description: None,
|
|
transport: None,
|
|
original_json: None,
|
|
builtin: None,
|
|
},
|
|
)
|
|
.await
|
|
.unwrap_err();
|
|
assert!(matches!(err, McpError::NotFound(_)));
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn edit_name_conflict() {
|
|
let svc = make_service().await;
|
|
svc.add_server(stdio_req("a")).await.unwrap();
|
|
let b = svc.add_server(stdio_req("b")).await.unwrap();
|
|
|
|
let err = svc
|
|
.edit_server(
|
|
&b.id.to_string(),
|
|
UpdateMcpServerRequest {
|
|
name: Some("a".into()),
|
|
description: None,
|
|
transport: None,
|
|
original_json: None,
|
|
builtin: None,
|
|
},
|
|
)
|
|
.await
|
|
.unwrap_err();
|
|
assert!(matches!(err, McpError::InvalidEdit(_)));
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Delete
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[tokio::test]
|
|
async fn delete_removes_server() {
|
|
let svc = make_service().await;
|
|
let created = svc.add_server(stdio_req("del")).await.unwrap();
|
|
let was_enabled = svc.delete_server(&created.id.to_string()).await.unwrap();
|
|
assert!(!was_enabled);
|
|
|
|
let err = svc.get_server(&created.id.to_string()).await.unwrap_err();
|
|
assert!(matches!(err, McpError::NotFound(_)));
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn delete_enabled_returns_true() {
|
|
let svc = make_service().await;
|
|
let created = svc.add_server(stdio_req("del-en")).await.unwrap();
|
|
svc.toggle_server(&created.id.to_string()).await.unwrap();
|
|
|
|
let was_enabled = svc.delete_server(&created.id.to_string()).await.unwrap();
|
|
assert!(was_enabled);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn delete_not_found() {
|
|
let svc = make_service().await;
|
|
let err = svc.delete_server("nonexistent").await.unwrap_err();
|
|
assert!(matches!(err, McpError::NotFound(_)));
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Toggle
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[tokio::test]
|
|
async fn toggle_enables_then_disables() {
|
|
let svc = make_service().await;
|
|
let created = svc.add_server(stdio_req("tog")).await.unwrap();
|
|
assert!(!created.enabled);
|
|
|
|
let toggled = svc.toggle_server(&created.id.to_string()).await.unwrap();
|
|
assert!(toggled.enabled);
|
|
|
|
let toggled_back = svc.toggle_server(&created.id.to_string()).await.unwrap();
|
|
assert!(!toggled_back.enabled);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Batch import
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[tokio::test]
|
|
async fn batch_import_creates_and_upserts() {
|
|
let svc = make_service().await;
|
|
svc.add_server(stdio_req("existing")).await.unwrap();
|
|
|
|
let req = BatchImportMcpServersRequest {
|
|
servers: vec![
|
|
http_import_req("existing"), // upsert
|
|
stdio_import_req("new"), // create
|
|
],
|
|
};
|
|
let results = svc.batch_import(req).await.unwrap();
|
|
assert_eq!(results.len(), 2);
|
|
|
|
let all = svc.list_servers().await.unwrap();
|
|
assert_eq!(all.len(), 2);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn batch_import_preserves_enabled_in_database() {
|
|
let svc = make_service().await;
|
|
let mut req = stdio_import_req("enabled-db-mcp");
|
|
req.enabled = Some(true);
|
|
|
|
let result = svc
|
|
.batch_import(BatchImportMcpServersRequest { servers: vec![req] })
|
|
.await
|
|
.unwrap();
|
|
|
|
assert_eq!(result.len(), 1);
|
|
assert_eq!(result[0].name, "enabled-db-mcp");
|
|
assert!(result[0].enabled);
|
|
|
|
let listed = svc.list_servers().await.unwrap();
|
|
assert_eq!(listed.len(), 1);
|
|
assert!(listed[0].enabled);
|
|
}
|