diff --git a/desktop/src-tauri/src/commands/connections.rs b/desktop/src-tauri/src/commands/connections.rs index 378ed65..a50b27f 100644 --- a/desktop/src-tauri/src/commands/connections.rs +++ b/desktop/src-tauri/src/commands/connections.rs @@ -2,7 +2,7 @@ use crate::models::{Connection, ConnectionInput}; use crate::store::Store; use std::sync::Mutex; -const VALID_DB_TYPES: [&str; 4] = ["postgresql", "mysql", "sqlite", "redis"]; +const VALID_DB_TYPES: [&str; 5] = ["postgresql", "mysql", "mariadb", "sqlite", "redis"]; fn validate(input: &ConnectionInput) -> Result<(), String> { if input.name.is_empty() || input.name.chars().count() > 100 { @@ -360,3 +360,7 @@ mod tests { assert_eq!(get_recent_connections_inner(&st, 10).unwrap().len(), 0); } } + +#[cfg(test)] +#[path = "connections.test.rs"] +mod connections_tests; diff --git a/desktop/src-tauri/src/commands/connections.test.rs b/desktop/src-tauri/src/commands/connections.test.rs new file mode 100644 index 0000000..839de56 --- /dev/null +++ b/desktop/src-tauri/src/commands/connections.test.rs @@ -0,0 +1,47 @@ +use super::*; + +fn mariadb_input() -> ConnectionInput { + ConnectionInput { + name: "Maria local".into(), + db_type: "mariadb".into(), + host: "127.0.0.1".into(), + port: Some(3306), + username: Some("root".into()), + folder_id: None, + tag_ids: vec![], + password: None, + database: Some("test".into()), + environment: None, + ssh_host: None, + ssh_port: None, + ssh_user: None, + ssh_auth_method: None, + ssh_private_key_path: None, + ssh_password: None, + ssh_passphrase: None, + ssl_mode: None, + ssl_ca_path: None, + ssl_cert_path: None, + ssl_key_path: None, + use_keychain: true, + } +} + +#[test] +fn validate_accepts_mariadb_with_port() { + assert!(validate(&mariadb_input()).is_ok()); +} + +#[test] +fn validate_rejects_mariadb_without_port() { + let mut input = mariadb_input(); + input.port = None; + assert!(validate(&input).is_err(), "mariadb requires a port like mysql"); +} + +#[test] +fn validate_rejects_unknown_db_type() { + let mut input = mariadb_input(); + input.db_type = "oracle".into(); + assert!(validate(&input).is_err()); +} diff --git a/desktop/src-tauri/src/commands/db_viewer.rs b/desktop/src-tauri/src/commands/db_viewer.rs index 59fc72a..9ed0b73 100644 --- a/desktop/src-tauri/src/commands/db_viewer.rs +++ b/desktop/src-tauri/src/commands/db_viewer.rs @@ -1198,7 +1198,7 @@ pub async fn db_connect( } Err(e) => Err(format!("Connection failed: {}", e)), } - } else if config.db_type == "mysql" { + } else if config.db_type == "mysql" || config.db_type == "mariadb" { run_mysql_connect( &connection_id, &config, diff --git a/desktop/src-tauri/src/commands/import_export.rs b/desktop/src-tauri/src/commands/import_export.rs index 89a374c..8db0e18 100644 --- a/desktop/src-tauri/src/commands/import_export.rs +++ b/desktop/src-tauri/src/commands/import_export.rs @@ -3,7 +3,7 @@ use crate::store::Store; use serde::{Deserialize, Serialize}; use std::sync::Mutex; -const VALID_DB_TYPES: [&str; 4] = ["postgresql", "mysql", "sqlite", "redis"]; +const VALID_DB_TYPES: [&str; 5] = ["postgresql", "mysql", "mariadb", "sqlite", "redis"]; #[derive(Debug, Deserialize)] pub(crate) struct ImportRecord { diff --git a/desktop/src-tauri/src/commands/test_connection.rs b/desktop/src-tauri/src/commands/test_connection.rs index 7df38f2..ba56551 100644 --- a/desktop/src-tauri/src/commands/test_connection.rs +++ b/desktop/src-tauri/src/commands/test_connection.rs @@ -107,7 +107,7 @@ fn url_scheme_end(msg: &str, i: usize) -> Option { pub fn validate_test_input(config: &DbConfig) -> Option { let db_type = config.db_type.to_lowercase(); - let valid_types = ["postgresql", "mysql", "sqlite", "redis"]; + let valid_types = ["postgresql", "mysql", "mariadb", "sqlite", "redis"]; if !valid_types.contains(&db_type.as_str()) { return Some(format!( "unsupported database type: {}. Supported types: {}", @@ -220,6 +220,7 @@ pub async fn test_database_connection(config: &DbConfig, ssh: &SshManager) -> Te let result = match config.db_type.to_lowercase().as_str() { "postgresql" => test_pg_connection(config, ssh).await, "mysql" => test_mysql_connection(config, ssh).await, + "mariadb" => test_mysql_connection(config, ssh).await, "sqlite" => test_sqlite_connection(config), "redis" => test_redis_connection(config, ssh).await, other => TestConnectionResult { @@ -724,3 +725,7 @@ mod tests { ); } } + +#[cfg(test)] +#[path = "test_connection.test.rs"] +mod test_connection_tests; diff --git a/desktop/src-tauri/src/commands/test_connection.test.rs b/desktop/src-tauri/src/commands/test_connection.test.rs new file mode 100644 index 0000000..0e1413b --- /dev/null +++ b/desktop/src-tauri/src/commands/test_connection.test.rs @@ -0,0 +1,54 @@ +use super::*; +use crate::commands::ssh::{Ssh2Backend, SshTunnelManager}; +use crate::db::pool::DbConfig; +use std::sync::Arc; + +fn mariadb_config(port: Option) -> DbConfig { + DbConfig { + db_type: "mariadb".into(), + host: "127.0.0.1".into(), + port, + username: Some("root".into()), + password: None, + database: Some("test".into()), + ..Default::default() + } +} + +#[test] +fn validate_test_input_accepts_mariadb_with_port() { + assert_eq!(validate_test_input(&mariadb_config(Some(3306))), None); +} + +#[test] +fn validate_test_input_rejects_mariadb_without_port() { + assert!(validate_test_input(&mariadb_config(None)).is_some()); +} + +/// Live test against a real MariaDB server. Run with: +/// `GRIDLINE_TEST_MARIADB_HOST=… GRIDLINE_TEST_MARIADB_PORT=3306 \ +/// GRIDLINE_TEST_MARIADB_USER=… GRIDLINE_TEST_MARIADB_PASSWORD=… \ +/// GRIDLINE_TEST_MARIADB_DB=… cargo test -- --ignored mariadb_live` +#[test] +#[ignore] +fn mariadb_live_test_connection_reports_mariadb_version() { + let config = DbConfig { + host: std::env::var("GRIDLINE_TEST_MARIADB_HOST").expect("GRIDLINE_TEST_MARIADB_HOST"), + port: std::env::var("GRIDLINE_TEST_MARIADB_PORT") + .ok() + .and_then(|p| p.parse::().ok()), + username: std::env::var("GRIDLINE_TEST_MARIADB_USER").ok(), + password: std::env::var("GRIDLINE_TEST_MARIADB_PASSWORD").ok(), + database: std::env::var("GRIDLINE_TEST_MARIADB_DB").ok(), + ..mariadb_config(None) + }; + let rt = tokio::runtime::Runtime::new().unwrap(); + let ssh = std::sync::Mutex::new(SshTunnelManager::new(Arc::new(Ssh2Backend))); + let result = rt.block_on(async { test_mysql_connection(&config, &ssh).await }); + assert!(result.ok, "connection failed: {:?}", result.error); + let version = result.server_version.unwrap_or_default().to_lowercase(); + assert!( + version.contains("mariadb"), + "expected a MariaDB server, got version: {version:?}" + ); +}