diff --git a/src/postgresclient/installer.rs b/src/postgresclient/installer.rs index 862f596..c310609 100644 --- a/src/postgresclient/installer.rs +++ b/src/postgresclient/installer.rs @@ -168,6 +168,20 @@ pub fn install_postgres( // Build the image name let image = format!("postgres:{}", config.version); + // Pull the PostgreSQL image to ensure we have the latest version + println!("Pulling PostgreSQL image: {}...", image); + let pull_result = Command::new("nerdctl") + .args(&["pull", &image]) + .output() + .map_err(|e| PostgresInstallerError::IoError(e))?; + + if !pull_result.status.success() { + return Err(PostgresInstallerError::NerdctlError(format!( + "Failed to pull PostgreSQL image: {}", + String::from_utf8_lossy(&pull_result.stderr) + ))); + } + // Create the container let mut container = Container::new(&config.container_name).map_err(|e| { PostgresInstallerError::NerdctlError(format!("Failed to create container: {}", e)) diff --git a/src/postgresclient/tests.rs b/src/postgresclient/tests.rs index 5102617..19015d6 100644 --- a/src/postgresclient/tests.rs +++ b/src/postgresclient/tests.rs @@ -1,4 +1,5 @@ use super::*; +use std::collections::HashMap; use std::env; #[cfg(test)] @@ -134,6 +135,234 @@ mod postgres_client_tests { // 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::*;