diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 411a6d704..4528b4f61 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -24,6 +24,7 @@ log = "0.4" tauri = { version = "2.1.0", features = [ "protocol-asset", 'macos-private-api', + "test" ] } tauri-plugin-log = "2.0.0-rc" tauri-plugin-shell = "2.2.0" diff --git a/src-tauri/src/core/cmd.rs b/src-tauri/src/core/cmd.rs index 56726937f..ef7f3af78 100644 --- a/src-tauri/src/core/cmd.rs +++ b/src-tauri/src/core/cmd.rs @@ -1,11 +1,8 @@ -use rmcp::{ - model::{CallToolRequestParam, CallToolResult, Tool}, - object, -}; +use rmcp::model::{CallToolRequestParam, CallToolResult, Tool}; use serde::{Deserialize, Serialize}; use serde_json::{Map, Value}; use std::{fs, path::PathBuf}; -use tauri::{AppHandle, Manager, State}; +use tauri::{AppHandle, Manager, Runtime, State}; use super::{server, setup, state::AppState}; @@ -26,7 +23,7 @@ impl AppConfiguration { } #[tauri::command] -pub fn get_app_configurations(app_handle: tauri::AppHandle) -> AppConfiguration { +pub fn get_app_configurations(app_handle: tauri::AppHandle) -> AppConfiguration { let mut app_default_configuration = AppConfiguration::default(); if std::env::var("CI").unwrap_or_default() == "e2e" { @@ -95,7 +92,7 @@ pub fn update_app_configuration( } #[tauri::command] -pub fn get_jan_data_folder_path(app_handle: tauri::AppHandle) -> PathBuf { +pub fn get_jan_data_folder_path(app_handle: tauri::AppHandle) -> PathBuf { let app_configurations = get_app_configurations(app_handle); PathBuf::from(app_configurations.data_folder) } @@ -137,7 +134,7 @@ pub fn read_theme(app_handle: tauri::AppHandle, theme_name: String) -> Result PathBuf { +pub fn get_configuration_file_path(app_handle: tauri::AppHandle) -> PathBuf { let app_path = app_handle.path().app_data_dir().unwrap_or_else(|err| { eprintln!( "Failed to get app data directory: {}. Using home directory instead.", @@ -158,7 +155,7 @@ pub fn get_configuration_file_path(app_handle: tauri::AppHandle) -> PathBuf { } #[tauri::command] -pub fn default_data_folder_path(app_handle: tauri::AppHandle) -> String { +pub fn default_data_folder_path(app_handle: tauri::AppHandle) -> String { return app_handle .path() .app_data_dir() diff --git a/src-tauri/src/core/fs.rs b/src-tauri/src/core/fs.rs index dc046689a..35bcceb09 100644 --- a/src-tauri/src/core/fs.rs +++ b/src-tauri/src/core/fs.rs @@ -1,9 +1,10 @@ use crate::core::cmd::get_jan_data_folder_path; use std::fs; use std::path::PathBuf; +use tauri::Runtime; #[tauri::command] -pub fn rm(app_handle: tauri::AppHandle, args: Vec) -> Result<(), String> { +pub fn rm(app_handle: tauri::AppHandle, args: Vec) -> Result<(), String> { if args.is_empty() || args[0].is_empty() { return Err("rm error: Invalid argument".to_string()); } @@ -12,7 +13,7 @@ pub fn rm(app_handle: tauri::AppHandle, args: Vec) -> Result<(), String> fs::remove_dir_all(&path).map_err(|e| e.to_string()) } #[tauri::command] -pub fn mkdir(app_handle: tauri::AppHandle, args: Vec) -> Result<(), String> { +pub fn mkdir(app_handle: tauri::AppHandle, args: Vec) -> Result<(), String> { if args.is_empty() || args[0].is_empty() { return Err("mkdir error: Invalid argument".to_string()); } @@ -22,7 +23,10 @@ pub fn mkdir(app_handle: tauri::AppHandle, args: Vec) -> Result<(), Stri } #[tauri::command] -pub fn join_path(app_handle: tauri::AppHandle, args: Vec) -> Result { +pub fn join_path( + app_handle: tauri::AppHandle, + args: Vec, +) -> Result { if args.is_empty() { return Err("join_path error: Invalid argument".to_string()); } @@ -32,7 +36,10 @@ pub fn join_path(app_handle: tauri::AppHandle, args: Vec) -> Result) -> Result { +pub fn exists_sync( + app_handle: tauri::AppHandle, + args: Vec, +) -> Result { if args.is_empty() || args[0].is_empty() { return Err("exist_sync error: Invalid argument".to_string()); } @@ -42,7 +49,10 @@ pub fn exists_sync(app_handle: tauri::AppHandle, args: Vec) -> Result) -> Result { +pub fn read_file_sync( + app_handle: tauri::AppHandle, + args: Vec, +) -> Result { if args.is_empty() || args[0].is_empty() { return Err("read_file_sync error: Invalid argument".to_string()); } @@ -52,8 +62,8 @@ pub fn read_file_sync(app_handle: tauri::AppHandle, args: Vec) -> Result } #[tauri::command] -pub fn readdir_sync( - app_handle: tauri::AppHandle, +pub fn readdir_sync( + app_handle: tauri::AppHandle, args: Vec, ) -> Result, String> { if args.is_empty() || args[0].is_empty() { @@ -74,7 +84,7 @@ fn normalize_file_path(path: &str) -> String { path.replace("file:/", "").replace("file:\\", "") } -fn resolve_path(app_handle: tauri::AppHandle, path: &str) -> PathBuf { +fn resolve_path(app_handle: tauri::AppHandle, path: &str) -> PathBuf { let path = if path.starts_with("file:/") || path.starts_with("file:\\") { let normalized = normalize_file_path(path); let relative_normalized = normalized.strip_prefix("/").unwrap_or(&normalized); @@ -89,3 +99,93 @@ fn resolve_path(app_handle: tauri::AppHandle, path: &str) -> PathBuf { path.canonicalize().unwrap_or(path) } } + +#[cfg(test)] +mod tests { + use super::*; + use std::fs::{self, File}; + use std::io::Write; + use tauri::test::mock_app; + + #[test] + fn test_rm() { + let app = mock_app(); + let path = "test_rm_dir"; + fs::create_dir_all(get_jan_data_folder_path(app.handle().clone()).join(path)).unwrap(); + let args = vec![format!("file://{}", path).to_string()]; + let result = rm(app.handle().clone(), args); + assert!(result.is_ok()); + assert!(!get_jan_data_folder_path(app.handle().clone()) + .join(path) + .exists()); + } + + #[test] + fn test_mkdir() { + let app = mock_app(); + let path = "test_mkdir_dir"; + let args = vec![format!("file://{}", path).to_string()]; + let result = mkdir(app.handle().clone(), args); + assert!(result.is_ok()); + assert!(get_jan_data_folder_path(app.handle().clone()) + .join(path) + .exists()); + fs::remove_dir_all(get_jan_data_folder_path(app.handle().clone()).join(path)).unwrap(); + } + + #[test] + fn test_join_path() { + let app = mock_app(); + let path = "file://test_dir"; + let args = vec![path.to_string(), "test_file".to_string()]; + let result = join_path(app.handle().clone(), args).unwrap(); + assert_eq!( + result, + get_jan_data_folder_path(app.handle().clone()) + .join("test_dir/test_file") + .to_string_lossy() + .to_string() + ); + } + + #[test] + fn test_exists_sync() { + let app = mock_app(); + let path = "file://test_exists_sync_file"; + let file_path = get_jan_data_folder_path(app.handle().clone()).join(path); + File::create(&file_path).unwrap(); + let args = vec![path.to_string()]; + let result = exists_sync(app.handle().clone(), args).unwrap(); + assert!(result); + fs::remove_file(file_path).unwrap(); + } + + #[test] + fn test_read_file_sync() { + let app = mock_app(); + let path = "file://test_read_file_sync_file"; + let file_path = get_jan_data_folder_path(app.handle().clone()).join(path); + let mut file = File::create(&file_path).unwrap(); + file.write_all(b"test content").unwrap(); + let args = vec![path.to_string()]; + let result = read_file_sync(app.handle().clone(), args).unwrap(); + assert_eq!(result, "test content".to_string()); + fs::remove_file(file_path).unwrap(); + } + + #[test] + fn test_readdir_sync() { + let app = mock_app(); + let path = "file://test_readdir_sync_dir"; + let dir_path = get_jan_data_folder_path(app.handle().clone()).join(path); + fs::create_dir_all(&dir_path).unwrap(); + File::create(dir_path.join("file1.txt")).unwrap(); + File::create(dir_path.join("file2.txt")).unwrap(); + + let args = vec![path.to_string()]; + let result = readdir_sync(app.handle().clone(), args).unwrap(); + assert_eq!(result.len(), 2); + + fs::remove_dir_all(dir_path).unwrap(); + } +} diff --git a/src-tauri/src/core/mcp.rs b/src-tauri/src/core/mcp.rs index 4889e058b..2196ecb01 100644 --- a/src-tauri/src/core/mcp.rs +++ b/src-tauri/src/core/mcp.rs @@ -1,11 +1,6 @@ use std::{collections::HashMap, sync::Arc}; -use rmcp::{ - model::{CallToolRequestParam, GetPromptRequestParam, ReadResourceRequestParam}, - service::RunningService, - transport::TokioChildProcess, - RoleClient, ServiceExt, -}; +use rmcp::{service::RunningService, transport::TokioChildProcess, RoleClient, ServiceExt}; use tokio::{process::Command, sync::Mutex}; pub async fn run_mcp_commands( @@ -71,3 +66,34 @@ pub async fn run_mcp_commands( } Ok(()) } + +#[cfg(test)] +mod tests { + use super::*; + use std::collections::HashMap; + use std::fs::File; + use std::io::Write; + use std::sync::Arc; + use tokio::sync::Mutex; + + #[tokio::test] + async fn test_run_mcp_commands() { + // Create a mock mcp_config.json file + let config_path = "mcp_config.json"; + let mut file = File::create(config_path).expect("Failed to create config file"); + file.write_all(b"{\"mcpServers\":{}}") + .expect("Failed to write to config file"); + + // Call the run_mcp_commands function + let app_path = ".".to_string(); + let servers_state: Arc>>> = + Arc::new(Mutex::new(HashMap::new())); + let result = run_mcp_commands(app_path, servers_state).await; + + // Assert that the function returns Ok(()) + assert!(result.is_ok()); + + // Clean up the mock config file + std::fs::remove_file(config_path).expect("Failed to remove config file"); + } +} diff --git a/src-tauri/src/core/state.rs b/src-tauri/src/core/state.rs index 8f47730ff..93d770bc2 100644 --- a/src-tauri/src/core/state.rs +++ b/src-tauri/src/core/state.rs @@ -1,4 +1,4 @@ -use std::{collections::HashMap, sync::{Arc}}; +use std::{collections::HashMap, sync::Arc}; use rand::{distributions::Alphanumeric, Rng}; use rmcp::{service::RunningService, RoleClient};