#![cfg(feature = "template-redis")]
use docker_wrapper::{DockerCommand, ExecCommand, RedisSentinelTemplate, Template};
use std::time::Duration;
use tokio::time::timeout;
const TEST_TIMEOUT: Duration = Duration::from_secs(180);
#[tokio::test]
async fn test_redis_sentinel_basic_api() {
let sentinel_name = format!("test-sentinel-basic-{}", uuid::Uuid::new_v4());
let _sentinel = RedisSentinelTemplate::new(&sentinel_name)
.master_name("testmaster")
.num_replicas(2)
.num_sentinels(3)
.quorum(2)
.master_port(7380)
.replica_port_base(7381)
.sentinel_port_base(27379);
assert_eq!(sentinel_name, sentinel_name); }
#[tokio::test]
async fn test_redis_sentinel_with_password_api() {
let sentinel_name = format!("test-sentinel-auth-{}", uuid::Uuid::new_v4());
let test_password = "sentinel-test-password";
let _sentinel = RedisSentinelTemplate::new(&sentinel_name)
.master_name("authmaster")
.num_replicas(1)
.num_sentinels(3)
.quorum(2)
.password(test_password)
.master_port(7390)
.replica_port_base(7391)
.sentinel_port_base(27389);
assert!(!test_password.is_empty(), "Password should be configured");
}
#[tokio::test]
async fn test_redis_sentinel_with_persistence_api() {
let sentinel_name = format!("test-sentinel-persist-{}", uuid::Uuid::new_v4());
let _sentinel = RedisSentinelTemplate::new(&sentinel_name)
.master_name("persistmaster")
.num_replicas(2)
.num_sentinels(3)
.quorum(2)
.with_persistence()
.master_port(7400)
.replica_port_base(7401)
.sentinel_port_base(27400);
assert!(
!sentinel_name.is_empty(),
"Sentinel name should be configured"
);
}
#[tokio::test]
async fn test_redis_sentinel_custom_config_api() {
let sentinel_name = format!("test-sentinel-config-{}", uuid::Uuid::new_v4());
let _sentinel = RedisSentinelTemplate::new(&sentinel_name)
.master_name("custommaster")
.num_replicas(1)
.num_sentinels(3)
.quorum(2)
.down_after_milliseconds(10000)
.failover_timeout(60000)
.parallel_syncs(2)
.master_port(7410)
.replica_port_base(7411)
.sentinel_port_base(27410);
assert!(
!sentinel_name.is_empty(),
"Custom configuration should work"
);
}
#[tokio::test]
async fn test_redis_sentinel_custom_image_platform_api() {
let sentinel_name = format!("test-sentinel-custom-{}", uuid::Uuid::new_v4());
let _sentinel = RedisSentinelTemplate::new(&sentinel_name)
.master_name("customimage")
.num_replicas(1)
.num_sentinels(3)
.quorum(2)
.custom_redis_image("redis", "7-alpine")
.platform("linux/amd64")
.master_port(7420)
.replica_port_base(7421)
.sentinel_port_base(27420);
assert!(
!sentinel_name.is_empty(),
"Custom image configuration should work"
);
}
#[tokio::test]
async fn test_redis_sentinel_minimal_config_api() {
let sentinel_name = format!("test-sentinel-minimal-{}", uuid::Uuid::new_v4());
let _sentinel = RedisSentinelTemplate::new(&sentinel_name)
.master_port(7430)
.replica_port_base(7431)
.sentinel_port_base(27430);
assert!(
!sentinel_name.is_empty(),
"Minimal configuration should work"
);
}
#[tokio::test]
async fn test_redis_sentinel_network_api() {
let sentinel_name = format!("test-sentinel-network-{}", uuid::Uuid::new_v4());
let network_name = format!("{}-network", sentinel_name);
let _sentinel = RedisSentinelTemplate::new(&sentinel_name)
.master_name("netmaster")
.num_replicas(1)
.num_sentinels(3)
.quorum(2)
.network(&network_name)
.master_port(7440)
.replica_port_base(7441)
.sentinel_port_base(27440);
assert!(
!network_name.is_empty(),
"Network configuration should work"
);
}
#[tokio::test]
async fn test_redis_sentinel_builder_pattern_api() {
let sentinel_name = format!("test-sentinel-builder-{}", uuid::Uuid::new_v4());
let _sentinel = RedisSentinelTemplate::new(&sentinel_name)
.master_name("buildermaster")
.num_replicas(2)
.num_sentinels(3)
.quorum(2)
.password("builder-password")
.down_after_milliseconds(5000)
.failover_timeout(30000)
.parallel_syncs(1)
.with_persistence()
.custom_redis_image("redis", "latest")
.platform("linux/amd64")
.master_port(7450)
.replica_port_base(7451)
.sentinel_port_base(27450);
assert!(!sentinel_name.is_empty(), "Builder pattern should work");
}
#[tokio::test]
async fn test_redis_sentinel_error_handling_api() {
let sentinel_name = format!("test-sentinel-error-{}", uuid::Uuid::new_v4());
let _sentinel = RedisSentinelTemplate::new(&sentinel_name)
.master_name("errormaster")
.num_sentinels(2)
.quorum(5) .master_port(7460)
.replica_port_base(7461)
.sentinel_port_base(27460);
assert!(
!sentinel_name.is_empty(),
"Error handling should be graceful"
);
}
#[tokio::test]
async fn test_redis_sentinel_container_identification_api() {
let sentinel_name = format!("test-sentinel-id-{}", uuid::Uuid::new_v4());
let _sentinel = RedisSentinelTemplate::new(&sentinel_name)
.master_name("idmaster")
.num_replicas(1)
.num_sentinels(3)
.quorum(2)
.master_port(7470)
.replica_port_base(7471)
.sentinel_port_base(27470);
assert!(
sentinel_name.contains("test-sentinel-id"),
"Container naming should include base name"
);
}
#[tokio::test]
#[ignore] async fn test_redis_sentinel_container_smoke_test() {
let sentinel_name = format!("test-sentinel-smoke-{}", uuid::Uuid::new_v4());
let sentinel = RedisSentinelTemplate::new(&sentinel_name)
.master_name("smokemaster")
.num_replicas(1)
.num_sentinels(1) .quorum(1)
.master_port(9200)
.replica_port_base(9201)
.sentinel_port_base(29200);
match timeout(TEST_TIMEOUT, sentinel.start()).await {
Ok(Ok(connection_info)) => {
assert!(
!connection_info.containers.is_empty(),
"Should have created containers"
);
let _ = timeout(Duration::from_secs(30), connection_info.stop()).await;
}
Ok(Err(_)) | Err(_) => {
println!(
"Sentinel smoke test skipped - Docker/Redis not available in test environment"
);
}
}
}
#[tokio::test]
#[ignore] async fn test_redis_sentinel_announce_master_addr() {
let sentinel_name = format!("test-sentinel-announce-{}", uuid::Uuid::new_v4());
let master_name = "announcemaster";
let announce_ip = "127.0.0.1";
let master_port: u16 = 9210;
let sentinel = RedisSentinelTemplate::new(&sentinel_name)
.master_name(master_name)
.num_replicas(1)
.num_sentinels(1)
.quorum(1)
.announce_ip(announce_ip)
.master_port(master_port)
.replica_port_base(9211)
.sentinel_port_base(29210);
let connection_info = match timeout(TEST_TIMEOUT, sentinel.start()).await {
Ok(Ok(info)) => info,
Ok(Err(e)) => {
println!("Sentinel announce test skipped - failed to start: {e}");
return;
}
Err(_) => {
println!("Sentinel announce test skipped - start timed out");
return;
}
};
assert_eq!(connection_info.master_host, announce_ip);
assert_eq!(connection_info.master_port, master_port);
tokio::time::sleep(Duration::from_secs(2)).await;
let sentinel_container = format!("{sentinel_name}-sentinel-1");
let exec = ExecCommand::new(
&sentinel_container,
vec![
"redis-cli".to_string(),
"-p".to_string(),
"26379".to_string(),
"sentinel".to_string(),
"get-master-addr-by-name".to_string(),
master_name.to_string(),
],
)
.execute()
.await;
let result = match exec {
Ok(output) => output.stdout,
Err(e) => {
let _ = timeout(Duration::from_secs(30), connection_info.stop()).await;
panic!("Failed to query sentinel get-master-addr-by-name: {e}");
}
};
let lines: Vec<&str> = result.lines().map(str::trim).collect();
let reported_ip = lines.first().copied().unwrap_or_default();
let reported_port = lines.get(1).copied().unwrap_or_default();
let matches_announced = reported_ip == announce_ip && reported_port == master_port.to_string();
let _ = timeout(Duration::from_secs(30), connection_info.stop()).await;
assert!(
matches_announced,
"Expected sentinel to report announced master {announce_ip}:{master_port}, got {reported_ip}:{reported_port}"
);
}
#[tokio::test]
#[ignore] async fn test_redis_sentinel_template_trait_lifecycle() {
let sentinel_name = format!("test-sentinel-trait-{}", uuid::Uuid::new_v4());
let sentinel = RedisSentinelTemplate::new(&sentinel_name)
.master_name("traitmaster")
.num_replicas(1)
.num_sentinels(1)
.quorum(1)
.announce_ip("127.0.0.1")
.master_port(9220)
.replica_port_base(9221)
.sentinel_port_base(29220);
match timeout(TEST_TIMEOUT, Template::start_and_wait(&sentinel)).await {
Ok(Ok(summary)) => {
assert!(
summary.contains(&sentinel_name),
"summary should mention the deployment name"
);
assert!(
Template::is_running(&sentinel).await.unwrap_or(false),
"master container should be running"
);
}
Ok(Err(e)) => {
println!("Sentinel trait lifecycle test skipped - failed to start: {e}");
return;
}
Err(_) => {
println!("Sentinel trait lifecycle test skipped - start timed out");
return;
}
}
let _ = timeout(Duration::from_secs(30), Template::stop(&sentinel)).await;
let _ = timeout(Duration::from_secs(30), Template::remove(&sentinel)).await;
}