use super::*; use std::collections::HashMap; use std::env; #[cfg(test)] mod postgres_client_tests { use super::*; #[test] fn test_env_vars() { // Save original environment variables to restore later let original_host = env::var("POSTGRES_HOST").ok(); let original_port = env::var("POSTGRES_PORT").ok(); let original_user = env::var("POSTGRES_USER").ok(); let original_password = env::var("POSTGRES_PASSWORD").ok(); let original_db = env::var("POSTGRES_DB").ok(); // Set test environment variables env::set_var("POSTGRES_HOST", "test-host"); env::set_var("POSTGRES_PORT", "5433"); env::set_var("POSTGRES_USER", "test-user"); env::set_var("POSTGRES_PASSWORD", "test-password"); env::set_var("POSTGRES_DB", "test-db"); // Test with invalid port env::set_var("POSTGRES_PORT", "invalid"); // Test with unset values env::remove_var("POSTGRES_HOST"); env::remove_var("POSTGRES_PORT"); env::remove_var("POSTGRES_USER"); env::remove_var("POSTGRES_PASSWORD"); env::remove_var("POSTGRES_DB"); // Restore original environment variables if let Some(host) = original_host { env::set_var("POSTGRES_HOST", host); } if let Some(port) = original_port { env::set_var("POSTGRES_PORT", port); } if let Some(user) = original_user { env::set_var("POSTGRES_USER", user); } if let Some(password) = original_password { env::set_var("POSTGRES_PASSWORD", password); } if let Some(db) = original_db { env::set_var("POSTGRES_DB", db); } } #[test] fn test_postgres_config_builder() { // Test the PostgreSQL configuration builder // Test default values let config = PostgresConfigBuilder::new(); assert_eq!(config.host, "localhost"); assert_eq!(config.port, 5432); assert_eq!(config.user, "postgres"); assert_eq!(config.password, None); assert_eq!(config.database, "postgres"); assert_eq!(config.application_name, None); assert_eq!(config.connect_timeout, None); assert_eq!(config.ssl_mode, None); // Test setting values let config = PostgresConfigBuilder::new() .host("pg.example.com") .port(5433) .user("test-user") .password("test-password") .database("test-db") .application_name("test-app") .connect_timeout(30) .ssl_mode("require"); assert_eq!(config.host, "pg.example.com"); assert_eq!(config.port, 5433); assert_eq!(config.user, "test-user"); assert_eq!(config.password, Some("test-password".to_string())); assert_eq!(config.database, "test-db"); assert_eq!(config.application_name, Some("test-app".to_string())); assert_eq!(config.connect_timeout, Some(30)); assert_eq!(config.ssl_mode, Some("require".to_string())); } #[test] fn test_connection_string_building() { // Test building connection strings // Test default connection string let config = PostgresConfigBuilder::new(); let conn_string = config.build_connection_string(); assert!(conn_string.contains("host=localhost")); assert!(conn_string.contains("port=5432")); assert!(conn_string.contains("user=postgres")); assert!(conn_string.contains("dbname=postgres")); assert!(!conn_string.contains("password=")); // Test with all options let config = PostgresConfigBuilder::new() .host("pg.example.com") .port(5433) .user("test-user") .password("test-password") .database("test-db") .application_name("test-app") .connect_timeout(30) .ssl_mode("require"); let conn_string = config.build_connection_string(); assert!(conn_string.contains("host=pg.example.com")); assert!(conn_string.contains("port=5433")); assert!(conn_string.contains("user=test-user")); assert!(conn_string.contains("password=test-password")); assert!(conn_string.contains("dbname=test-db")); assert!(conn_string.contains("application_name=test-app")); assert!(conn_string.contains("connect_timeout=30")); assert!(conn_string.contains("sslmode=require")); } #[test] fn test_reset_mock() { // This is a simplified test that doesn't require an actual PostgreSQL server // Just verify that the reset function doesn't panic if let Err(_) = reset() { // If PostgreSQL is not available, this is expected to fail // So we don't assert anything here } } } // Integration tests that require a real PostgreSQL server // These tests will be skipped if PostgreSQL is not available #[cfg(test)] mod postgres_installer_tests { use super::*; use crate::virt::nerdctl::Container; #[test] fn test_postgres_installer_config() { // Test default configuration let config = PostgresInstallerConfig::default(); assert_eq!(config.container_name, "postgres"); assert_eq!(config.version, "latest"); assert_eq!(config.port, 5432); assert_eq!(config.username, "postgres"); assert_eq!(config.password, "postgres"); assert_eq!(config.data_dir, None); assert_eq!(config.env_vars.len(), 0); assert_eq!(config.persistent, true); // Test builder pattern let config = PostgresInstallerConfig::new() .container_name("my-postgres") .version("15") .port(5433) .username("testuser") .password("testpass") .data_dir("/tmp/pgdata") .env_var("POSTGRES_INITDB_ARGS", "--encoding=UTF8") .persistent(false); assert_eq!(config.container_name, "my-postgres"); assert_eq!(config.version, "15"); assert_eq!(config.port, 5433); assert_eq!(config.username, "testuser"); assert_eq!(config.password, "testpass"); assert_eq!(config.data_dir, Some("/tmp/pgdata".to_string())); assert_eq!(config.env_vars.len(), 1); assert_eq!( config.env_vars.get("POSTGRES_INITDB_ARGS").unwrap(), "--encoding=UTF8" ); assert_eq!(config.persistent, false); } #[test] fn test_postgres_installer_error() { // Test IoError let io_error = std::io::Error::new(std::io::ErrorKind::NotFound, "File not found"); let installer_error = PostgresInstallerError::IoError(io_error); assert!(format!("{}", installer_error).contains("I/O error")); // Test NerdctlError let nerdctl_error = PostgresInstallerError::NerdctlError("Container not found".to_string()); assert!(format!("{}", nerdctl_error).contains("Nerdctl error")); // Test PostgresError let postgres_error = PostgresInstallerError::PostgresError("Database not found".to_string()); assert!(format!("{}", postgres_error).contains("PostgreSQL error")); } #[test] fn test_install_postgres_with_defaults() { // This is a unit test that doesn't actually install PostgreSQL // It just tests the configuration and error handling // Test with default configuration let config = PostgresInstallerConfig::default(); // We expect this to fail because nerdctl is not available let result = install_postgres(config); assert!(result.is_err()); // Check that the error is a NerdctlError or IoError match result { Err(PostgresInstallerError::NerdctlError(_)) => { // This is fine, we expected a NerdctlError } Err(PostgresInstallerError::IoError(_)) => { // This is also fine, we expected an error } _ => panic!("Expected NerdctlError or IoError"), } } #[test] fn test_install_postgres_with_custom_config() { // Test with custom configuration let config = PostgresInstallerConfig::new() .container_name("test-postgres") .version("15") .port(5433) .username("testuser") .password("testpass") .data_dir("/tmp/pgdata") .env_var("POSTGRES_INITDB_ARGS", "--encoding=UTF8") .persistent(true); // We expect this to fail because nerdctl is not available let result = install_postgres(config); assert!(result.is_err()); // Check that the error is a NerdctlError or IoError match result { Err(PostgresInstallerError::NerdctlError(_)) => { // This is fine, we expected a NerdctlError } Err(PostgresInstallerError::IoError(_)) => { // This is also fine, we expected an error } _ => panic!("Expected NerdctlError or IoError"), } } #[test] fn test_create_database() { // Create a mock container // In a real test, we would use mockall to create a mock container // But for this test, we'll just test the error handling // We expect this to fail because the container is not running let result = create_database( &Container { name: "test-postgres".to_string(), container_id: None, image: Some("postgres:15".to_string()), config: HashMap::new(), ports: Vec::new(), volumes: Vec::new(), env_vars: HashMap::new(), network: None, network_aliases: Vec::new(), cpu_limit: None, memory_limit: None, memory_swap_limit: None, cpu_shares: None, restart_policy: None, health_check: None, detach: false, snapshotter: None, }, "testdb", ); assert!(result.is_err()); // Check that the error is a PostgresError match result { Err(PostgresInstallerError::PostgresError(msg)) => { assert!(msg.contains("Container is not running")); } _ => panic!("Expected PostgresError"), } } #[test] fn test_execute_sql() { // Create a mock container // In a real test, we would use mockall to create a mock container // But for this test, we'll just test the error handling // We expect this to fail because the container is not running let result = execute_sql( &Container { name: "test-postgres".to_string(), container_id: None, image: Some("postgres:15".to_string()), config: HashMap::new(), ports: Vec::new(), volumes: Vec::new(), env_vars: HashMap::new(), network: None, network_aliases: Vec::new(), cpu_limit: None, memory_limit: None, memory_swap_limit: None, cpu_shares: None, restart_policy: None, health_check: None, detach: false, snapshotter: None, }, "testdb", "SELECT 1", ); assert!(result.is_err()); // Check that the error is a PostgresError match result { Err(PostgresInstallerError::PostgresError(msg)) => { assert!(msg.contains("Container is not running")); } _ => panic!("Expected PostgresError"), } } #[test] fn test_is_postgres_running() { // Create a mock container // In a real test, we would use mockall to create a mock container // But for this test, we'll just test the error handling // We expect this to return false because the container is not running let result = is_postgres_running(&Container { name: "test-postgres".to_string(), container_id: None, image: Some("postgres:15".to_string()), config: HashMap::new(), ports: Vec::new(), volumes: Vec::new(), env_vars: HashMap::new(), network: None, network_aliases: Vec::new(), cpu_limit: None, memory_limit: None, memory_swap_limit: None, cpu_shares: None, restart_policy: None, health_check: None, detach: false, snapshotter: None, }); assert!(result.is_ok()); assert_eq!(result.unwrap(), false); } } #[cfg(test)] mod postgres_integration_tests { use super::*; use std::time::Duration; // Helper function to check if PostgreSQL is available fn is_postgres_available() -> bool { match get_postgres_client() { Ok(_) => true, Err(_) => false, } } #[test] fn test_postgres_client_integration() { if !is_postgres_available() { println!("Skipping PostgreSQL integration tests - PostgreSQL server not available"); return; } println!("Running PostgreSQL integration tests..."); // Test basic operations test_basic_postgres_operations(); // Test error handling test_error_handling(); } #[test] fn test_connection_pool() { if !is_postgres_available() { println!("Skipping PostgreSQL connection pool tests - PostgreSQL server not available"); return; } run_connection_pool_test(); } fn run_connection_pool_test() { println!("Running PostgreSQL connection pool tests..."); // Test creating a connection pool let config = PostgresConfigBuilder::new() .use_pool(true) .pool_max_size(5) .pool_min_idle(1) .pool_connection_timeout(Duration::from_secs(5)); let pool_result = config.build_pool(); assert!(pool_result.is_ok()); let pool = pool_result.unwrap(); // Test getting a connection from the pool let conn_result = pool.get(); assert!(conn_result.is_ok()); // Test executing a query with the connection let mut conn = conn_result.unwrap(); let query_result = conn.query("SELECT 1", &[]); assert!(query_result.is_ok()); // Test the global pool let global_pool_result = get_postgres_pool(); assert!(global_pool_result.is_ok()); // Test executing queries with the pool let create_table_query = " CREATE TEMPORARY TABLE pool_test ( id SERIAL PRIMARY KEY, name TEXT NOT NULL ) "; let create_result = execute_with_pool(create_table_query, &[]); assert!(create_result.is_ok()); // Test with parameters let insert_result = execute_with_pool( "INSERT INTO pool_test (name) VALUES ($1) RETURNING id", &[&"test_pool"], ); assert!(insert_result.is_ok()); // Test with QueryParams let mut params = QueryParams::new(); params.add_str("test_pool_params"); let insert_params_result = execute_with_pool_params( "INSERT INTO pool_test (name) VALUES ($1) RETURNING id", ¶ms, ); assert!(insert_params_result.is_ok()); // Test query functions let query_result = query_with_pool("SELECT * FROM pool_test", &[]); assert!(query_result.is_ok()); let rows = query_result.unwrap(); assert_eq!(rows.len(), 2); // Test query_one let query_one_result = query_one_with_pool("SELECT * FROM pool_test WHERE name = $1", &[&"test_pool"]); assert!(query_one_result.is_ok()); // Test query_opt let query_opt_result = query_opt_with_pool("SELECT * FROM pool_test WHERE name = $1", &[&"nonexistent"]); assert!(query_opt_result.is_ok()); assert!(query_opt_result.unwrap().is_none()); // Test resetting the pool let reset_result = reset_pool(); assert!(reset_result.is_ok()); // Test getting the pool again after reset let pool_after_reset = get_postgres_pool(); assert!(pool_after_reset.is_ok()); } fn test_basic_postgres_operations() { if !is_postgres_available() { return; } // Create a test table let create_table_query = " CREATE TEMPORARY TABLE test_table ( id SERIAL PRIMARY KEY, name TEXT NOT NULL, value INTEGER ) "; let create_result = execute(create_table_query, &[]); assert!(create_result.is_ok()); // Insert data let insert_query = " INSERT INTO test_table (name, value) VALUES ($1, $2) RETURNING id "; let insert_result = query(insert_query, &[&"test_name", &42]); assert!(insert_result.is_ok()); let rows = insert_result.unwrap(); assert_eq!(rows.len(), 1); let id: i32 = rows[0].get(0); assert!(id > 0); // Query data let select_query = " SELECT id, name, value FROM test_table WHERE id = $1 "; let select_result = query_one(select_query, &[&id]); assert!(select_result.is_ok()); let row = select_result.unwrap(); let name: String = row.get(1); let value: i32 = row.get(2); assert_eq!(name, "test_name"); assert_eq!(value, 42); // Update data let update_query = " UPDATE test_table SET value = $1 WHERE id = $2 "; let update_result = execute(update_query, &[&100, &id]); assert!(update_result.is_ok()); assert_eq!(update_result.unwrap(), 1); // 1 row affected // Verify update let verify_query = " SELECT value FROM test_table WHERE id = $1 "; let verify_result = query_one(verify_query, &[&id]); assert!(verify_result.is_ok()); let row = verify_result.unwrap(); let updated_value: i32 = row.get(0); assert_eq!(updated_value, 100); // Delete data let delete_query = " DELETE FROM test_table WHERE id = $1 "; let delete_result = execute(delete_query, &[&id]); assert!(delete_result.is_ok()); assert_eq!(delete_result.unwrap(), 1); // 1 row affected } #[test] fn test_query_params() { if !is_postgres_available() { println!("Skipping PostgreSQL parameter tests - PostgreSQL server not available"); return; } run_query_params_test(); } #[test] fn test_transactions() { if !is_postgres_available() { println!("Skipping PostgreSQL transaction tests - PostgreSQL server not available"); return; } println!("Running PostgreSQL transaction tests..."); // Test successful transaction let result = transaction(|client| { // Create a temporary table client.execute( "CREATE TEMPORARY TABLE transaction_test (id SERIAL PRIMARY KEY, name TEXT NOT NULL)", &[], )?; // Insert data client.execute( "INSERT INTO transaction_test (name) VALUES ($1)", &[&"test_transaction"], )?; // Query data let rows = client.query( "SELECT * FROM transaction_test WHERE name = $1", &[&"test_transaction"], )?; assert_eq!(rows.len(), 1); let name: String = rows[0].get(1); assert_eq!(name, "test_transaction"); // Return success Ok(true) }); assert!(result.is_ok()); assert_eq!(result.unwrap(), true); // Test failed transaction let result = transaction(|client| { // Create a temporary table client.execute( "CREATE TEMPORARY TABLE transaction_test_fail (id SERIAL PRIMARY KEY, name TEXT NOT NULL)", &[], )?; // Insert data client.execute( "INSERT INTO transaction_test_fail (name) VALUES ($1)", &[&"test_transaction_fail"], )?; // Cause an error with invalid SQL client.execute("THIS IS INVALID SQL", &[])?; // This should not be reached Ok(false) }); assert!(result.is_err()); // Verify that the table was not created (transaction was rolled back) let verify_result = query("SELECT * FROM transaction_test_fail", &[]); assert!(verify_result.is_err()); // Test transaction with pool let result = transaction_with_pool(|client| { // Create a temporary table client.execute( "CREATE TEMPORARY TABLE transaction_pool_test (id SERIAL PRIMARY KEY, name TEXT NOT NULL)", &[], )?; // Insert data client.execute( "INSERT INTO transaction_pool_test (name) VALUES ($1)", &[&"test_transaction_pool"], )?; // Query data let rows = client.query( "SELECT * FROM transaction_pool_test WHERE name = $1", &[&"test_transaction_pool"], )?; assert_eq!(rows.len(), 1); let name: String = rows[0].get(1); assert_eq!(name, "test_transaction_pool"); // Return success Ok(true) }); assert!(result.is_ok()); assert_eq!(result.unwrap(), true); } fn run_query_params_test() { println!("Running PostgreSQL parameter tests..."); // Create a test table let create_table_query = " CREATE TEMPORARY TABLE param_test ( id SERIAL PRIMARY KEY, name TEXT NOT NULL, value INTEGER, active BOOLEAN, score REAL ) "; let create_result = execute(create_table_query, &[]); assert!(create_result.is_ok()); // Test QueryParams builder let mut params = QueryParams::new(); params.add_str("test_name"); params.add_int(42); params.add_bool(true); params.add_float(3.14); // Insert data using QueryParams let insert_query = " INSERT INTO param_test (name, value, active, score) VALUES ($1, $2, $3, $4) RETURNING id "; let insert_result = query_with_params(insert_query, ¶ms); assert!(insert_result.is_ok()); let rows = insert_result.unwrap(); assert_eq!(rows.len(), 1); let id: i32 = rows[0].get(0); assert!(id > 0); // Query data using QueryParams let mut query_params = QueryParams::new(); query_params.add_int(id); let select_query = " SELECT id, name, value, active, score FROM param_test WHERE id = $1 "; let select_result = query_one_with_params(select_query, &query_params); assert!(select_result.is_ok()); let row = select_result.unwrap(); let name: String = row.get(1); let value: i32 = row.get(2); let active: bool = row.get(3); let score: f64 = row.get(4); assert_eq!(name, "test_name"); assert_eq!(value, 42); assert_eq!(active, true); assert_eq!(score, 3.14); // Test optional parameters let mut update_params = QueryParams::new(); update_params.add_int(100); update_params.add_opt::(None); update_params.add_int(id); let update_query = " UPDATE param_test SET value = $1, name = COALESCE($2, name) WHERE id = $3 "; let update_result = execute_with_params(update_query, &update_params); assert!(update_result.is_ok()); assert_eq!(update_result.unwrap(), 1); // 1 row affected // Verify update let verify_result = query_one_with_params(select_query, &query_params); assert!(verify_result.is_ok()); let row = verify_result.unwrap(); let name: String = row.get(1); let value: i32 = row.get(2); assert_eq!(name, "test_name"); // Name should be unchanged assert_eq!(value, 100); // Value should be updated // Test query_opt_with_params let mut nonexistent_params = QueryParams::new(); nonexistent_params.add_int(9999); // ID that doesn't exist let opt_query = " SELECT id, name FROM param_test WHERE id = $1 "; let opt_result = query_opt_with_params(opt_query, &nonexistent_params); assert!(opt_result.is_ok()); assert!(opt_result.unwrap().is_none()); // Clean up let delete_query = " DELETE FROM param_test WHERE id = $1 "; let delete_result = execute_with_params(delete_query, &query_params); assert!(delete_result.is_ok()); assert_eq!(delete_result.unwrap(), 1); // 1 row affected } fn test_error_handling() { if !is_postgres_available() { return; } // Test invalid SQL let invalid_query = "SELECT * FROM nonexistent_table"; let invalid_result = query(invalid_query, &[]); assert!(invalid_result.is_err()); // Test parameter type mismatch let mismatch_query = "SELECT $1::integer"; let mismatch_result = query(mismatch_query, &[&"not_an_integer"]); assert!(mismatch_result.is_err()); // Test query_one with no results let empty_query = "SELECT * FROM pg_tables WHERE tablename = 'nonexistent_table'"; let empty_result = query_one(empty_query, &[]); assert!(empty_result.is_err()); // Test query_opt with no results let opt_query = "SELECT * FROM pg_tables WHERE tablename = 'nonexistent_table'"; let opt_result = query_opt(opt_query, &[]); assert!(opt_result.is_ok()); assert!(opt_result.unwrap().is_none()); } #[test] fn test_notify() { if !is_postgres_available() { println!("Skipping PostgreSQL notification tests - PostgreSQL server not available"); return; } println!("Running PostgreSQL notification tests..."); // Test sending a notification let result = notify("test_channel", "test_payload"); assert!(result.is_ok()); // Test sending a notification with the pool let result = notify_with_pool("test_channel_pool", "test_payload_pool"); assert!(result.is_ok()); } }