use redis::{AsyncCommands, Client}; use std::env; #[tokio::test] async fn test_redis_url_parsing() { // Тестируем различные форматы Redis URL let test_urls = vec![ // Простой URL без пароля "redis://localhost:6379", // URL с паролем (должен использовать дефолтное имя пользователя 'redis') "redis://:password@localhost:6379", // URL с пользователем и паролем "redis://user:password@localhost:6379", // URL с длинным hex паролем (как в Dokku) - должен использовать дефолтное имя 'redis' "redis://:dbc5f9f9007c555e209964454c9d6abecae5f1db72e490acd5c94354dc012282@dokku-redis-discoursio-redis:6379", // URL с IP адресом "redis://172.17.0.3:6379", // URL с exposed портом "redis://localhost:12389", // Тест с явным указанием дефолтного пользователя 'redis' "redis://redis:password@localhost:6379", // Тест с пустым пользователем и паролем (должен работать как без аутентификации) "redis://:@localhost:6379", ]; for url in test_urls { // Парсим URL для детального вывода if let Ok(parsed) = url::Url::parse(url) { println!( "Testing Redis URL: {}", url.replace(url.split('@').next().unwrap_or(""), "***") ); println!(" Host: {}", parsed.host_str().unwrap_or("none")); println!(" Port: {}", parsed.port().unwrap_or(0)); println!(" Username: '{}'", parsed.username()); println!( " Password: '{}'", if parsed.password().is_some() { "***" } else { "none" } ); } else { println!("Testing Redis URL: {}", url); } match Client::open(url) { Ok(client) => { println!("✅ URL parsed successfully"); // Попробуем подключиться (с коротким таймаутом) match tokio::time::timeout( std::time::Duration::from_secs(2), client.get_multiplexed_async_connection(), ) .await { Ok(Ok(_)) => println!("✅ Connection successful"), Ok(Err(e)) => println!("❌ Connection failed: {}", e), Err(_) => println!("⏰ Connection timeout"), } } Err(e) => { println!("❌ URL parsing failed: {}", e); } } println!("---"); } } #[tokio::test] async fn test_redis_connection_with_env() { // Тестируем с реальным REDIS_URL из окружения if let Ok(redis_url) = env::var("REDIS_URL") { println!( "Testing with real REDIS_URL: {}", redis_url.replace(redis_url.split('@').next().unwrap_or(""), "***") ); // Парсим реальный URL для детального вывода if let Ok(parsed) = url::Url::parse(&redis_url) { println!(" Host: {}", parsed.host_str().unwrap_or("none")); println!(" Port: {}", parsed.port().unwrap_or(0)); println!(" Username: '{}'", parsed.username()); println!( " Password: '{}'", if parsed.password().is_some() { "***" } else { "none" } ); } match Client::open(redis_url) { Ok(client) => { println!("✅ Real URL parsed successfully"); match tokio::time::timeout( std::time::Duration::from_secs(5), client.get_multiplexed_async_connection(), ) .await { Ok(Ok(mut conn)) => { println!("✅ Real connection successful"); // Попробуем выполнить простую команду match tokio::time::timeout( std::time::Duration::from_secs(2), conn.ping::(), ) .await { Ok(Ok(result)) => println!("✅ PING successful: {}", result), Ok(Err(e)) => println!("❌ PING failed: {}", e), Err(_) => println!("⏰ PING timeout"), } } Ok(Err(e)) => println!("❌ Real connection failed: {}", e), Err(_) => println!("⏰ Real connection timeout"), } } Err(e) => { println!("❌ Real URL parsing failed: {}", e); } } } else { println!("⚠️ REDIS_URL not set, skipping real connection test"); } } #[test] fn test_redis_url_components() { // Тестируем парсинг компонентов URL let test_url = "redis://:dbc5f9f9007c555e209964454c9d6abecae5f1db72e490acd5c94354dc012282@dokku-redis-discoursio-redis:6379"; if let Ok(parsed) = url::Url::parse(test_url) { println!("✅ URL parsed successfully"); println!(" Scheme: {}", parsed.scheme()); println!(" Host: {}", parsed.host_str().unwrap_or("none")); println!(" Port: {}", parsed.port().unwrap_or(0)); println!(" Username: '{}'", parsed.username()); println!( " Password: {}", if parsed.password().is_some() { "***" } else { "none" } ); println!(" Path: {}", parsed.path()); // Проверяем, что пустое имя пользователя означает дефолтное 'redis' if parsed.username().is_empty() && parsed.password().is_some() { println!( " ⚠️ Empty username with password - Redis client should use default 'redis' user" ); } } else { println!("❌ URL parsing failed"); } } #[tokio::test] async fn test_redis_default_username_behavior() { // Тестируем поведение Redis client с пустым именем пользователя println!("Testing Redis default username behavior..."); let test_cases = vec![ ( "redis://:password@localhost:6379", "Empty username with password", ), ( "redis://redis:password@localhost:6379", "Explicit redis username", ), ("redis://:@localhost:6379", "Empty username and password"), ("redis://localhost:6379", "No authentication"), ]; for (url, description) in test_cases { println!("\n--- {} ---", description); println!( "URL: {}", url.replace(url.split('@').next().unwrap_or(""), "***") ); if let Ok(parsed) = url::Url::parse(url) { println!(" Parsed Username: '{}'", parsed.username()); println!( " Parsed Password: {}", if parsed.password().is_some() { "***" } else { "none" } ); // Redis client поведение: // - Если username пустой, но есть password -> использует дефолтное имя 'redis' // - Если username и password пустые -> без аутентификации if parsed.username().is_empty() && parsed.password().is_some() { println!(" Expected Redis behavior: Use default username 'redis'"); } else if parsed.username().is_empty() && parsed.password().is_none() { println!(" Expected Redis behavior: No authentication"); } else { println!(" Expected Redis behavior: Use explicit credentials"); } } } }