#![cfg(feature = "template-redis-cluster")]
use docker_wrapper::{NodeRole, RedisClusterConnection, RedisClusterTemplate, Template};
use std::time::Duration;
use tokio::time::timeout;
const TEST_TIMEOUT: Duration = Duration::from_secs(180);
#[tokio::test]
async fn test_redis_cluster_basic_api() {
let cluster_name = format!("test-cluster-basic-{}", uuid::Uuid::new_v4());
let cluster = RedisClusterTemplate::new(&cluster_name)
.num_masters(3)
.port_base(8100);
let conn = RedisClusterConnection::from_template(&cluster);
let nodes_string = conn.nodes_string();
assert!(nodes_string.contains("8100"), "Should contain base port");
assert!(nodes_string.contains("8101"), "Should contain second port");
assert!(nodes_string.contains("8102"), "Should contain third port");
let cluster_url = conn.cluster_url();
assert!(
cluster_url.starts_with("redis-cluster://"),
"Should generate valid cluster URL, got: {}",
cluster_url
);
}
#[tokio::test]
async fn test_redis_cluster_with_replicas_api() {
let cluster_name = format!("test-cluster-replicas-{}", uuid::Uuid::new_v4());
let cluster = RedisClusterTemplate::new(&cluster_name)
.num_masters(3)
.num_replicas(1) .port_base(8200);
let conn = RedisClusterConnection::from_template(&cluster);
let nodes_string = conn.nodes_string();
let node_count = nodes_string.matches("8").count(); assert!(node_count >= 6, "Should have at least 6 nodes in cluster");
}
#[tokio::test]
async fn test_redis_cluster_with_password_api() {
let cluster_name = format!("test-cluster-auth-{}", uuid::Uuid::new_v4());
let test_password = "secure-cluster-password";
let cluster = RedisClusterTemplate::new(&cluster_name)
.num_masters(3)
.port_base(8300)
.password(test_password);
let conn = RedisClusterConnection::from_template(&cluster);
let cluster_url = conn.cluster_url();
assert!(
cluster_url.contains(test_password),
"Cluster URL should include password"
);
}
#[tokio::test]
async fn test_redis_cluster_with_persistence_api() {
let cluster_name = format!("test-cluster-persist-{}", uuid::Uuid::new_v4());
let data_volume = format!("{}-data", cluster_name);
let cluster = RedisClusterTemplate::new(&cluster_name)
.num_masters(3)
.port_base(8400)
.with_persistence(&data_volume);
let conn = RedisClusterConnection::from_template(&cluster);
let nodes_string = conn.nodes_string();
assert!(
nodes_string.contains("8400"),
"Should contain configured port"
);
}
#[tokio::test]
async fn test_redis_stack_cluster_api() {
let cluster_name = format!("test-stack-cluster-{}", uuid::Uuid::new_v4());
let cluster = RedisClusterTemplate::new(&cluster_name)
.num_masters(3)
.port_base(8500)
.with_redis_stack();
let conn = RedisClusterConnection::from_template(&cluster);
let nodes_string = conn.nodes_string();
assert!(
nodes_string.contains("8500"),
"Should contain configured port"
);
}
#[tokio::test]
async fn test_redis_cluster_custom_image_platform_api() {
let cluster_name = format!("test-cluster-custom-{}", uuid::Uuid::new_v4());
let cluster = RedisClusterTemplate::new(&cluster_name)
.num_masters(3)
.port_base(8600)
.custom_redis_image("redis", "7-alpine")
.platform("linux/amd64");
let conn = RedisClusterConnection::from_template(&cluster);
let nodes_string = conn.nodes_string();
assert!(
nodes_string.contains("8600"),
"Should contain configured port"
);
}
#[tokio::test]
async fn test_redis_cluster_error_handling_api() {
let cluster_name = format!("test-cluster-error-{}", uuid::Uuid::new_v4());
let cluster = RedisClusterTemplate::new(&cluster_name)
.num_masters(1000) .port_base(8900);
let conn = RedisClusterConnection::from_template(&cluster);
let cluster_url = conn.cluster_url();
assert!(cluster_url.contains("8900"), "Should contain base port");
}
#[tokio::test]
async fn test_redis_cluster_builder_pattern_api() {
let cluster_name = format!("test-cluster-builder-{}", uuid::Uuid::new_v4());
let cluster = RedisClusterTemplate::new(&cluster_name)
.num_masters(3)
.num_replicas(1)
.port_base(8800)
.password("builder-password")
.cluster_announce_ip("127.0.0.1")
.cluster_node_timeout(15000)
.memory_limit("128m")
.auto_remove();
let conn = RedisClusterConnection::from_template(&cluster);
let cluster_url = conn.cluster_url();
assert!(
cluster_url.contains("builder-password"),
"Should include configured password"
);
assert!(
cluster_url.contains("8800"),
"Should include configured port"
);
}
#[tokio::test]
async fn test_redis_cluster_node_accessors_api() {
let cluster_name = format!("test-cluster-nodes-{}", uuid::Uuid::new_v4());
let cluster = RedisClusterTemplate::new(&cluster_name)
.num_masters(3)
.num_replicas(1) .port_base(8700);
let names = cluster.node_names();
assert_eq!(names.len(), 6, "3 masters + 3 replicas = 6 nodes");
assert_eq!(names[0], format!("{}-node-0", cluster_name));
assert_eq!(names[5], format!("{}-node-5", cluster_name));
let master = cluster.node(0).expect("node 0 exists");
assert_eq!(master.container_name, format!("{}-node-0", cluster_name));
assert_eq!(master.host_port, 8700);
assert_eq!(master.role, NodeRole::Master);
let replica = cluster.node(3).expect("node 3 exists");
assert_eq!(replica.host_port, 8703);
assert_eq!(replica.role, NodeRole::Replica);
assert!(cluster.node(6).is_none());
for (i, name) in names.iter().enumerate() {
assert_eq!(&cluster.node(i).unwrap().container_name, name);
}
}
#[tokio::test]
#[ignore] async fn test_redis_cluster_container_smoke_test() {
let cluster_name = format!("test-cluster-smoke-{}", uuid::Uuid::new_v4());
let cluster = RedisClusterTemplate::new(&cluster_name)
.num_masters(3)
.port_base(9100)
.auto_remove();
match timeout(TEST_TIMEOUT, cluster.start()).await {
Ok(Ok(result)) => {
assert!(!result.is_empty(), "Cluster result should not be empty");
let node0 = cluster.node(0).expect("node 0 exists");
assert_eq!(node0.role, NodeRole::Master);
if let Ok(Ok(role)) =
timeout(Duration::from_secs(30), cluster.node_role(node0.index)).await
{
assert_eq!(role, NodeRole::Master, "node 0 should report as master");
}
let _ = timeout(Duration::from_secs(30), cluster.stop()).await;
let _ = timeout(Duration::from_secs(30), cluster.remove()).await;
}
Ok(Err(_)) | Err(_) => {
println!("Cluster smoke test skipped - Docker/Redis not available in test environment");
}
}
}
#[tokio::test]
async fn test_redis_cluster_host_network_wiring_api() {
let cluster_name = format!("test-cluster-host-{}", uuid::Uuid::new_v4());
let cluster = RedisClusterTemplate::new(&cluster_name)
.num_masters(3)
.port_base(9300)
.host_network();
let conn = RedisClusterConnection::from_template(&cluster);
assert_eq!(
conn.nodes(),
&[
"localhost:9300".to_string(),
"localhost:9301".to_string(),
"localhost:9302".to_string(),
]
);
assert_eq!(cluster.node(0).expect("node 0").host_port, 9300);
assert_eq!(cluster.node(2).expect("node 2").host_port, 9302);
let via_mode = RedisClusterTemplate::new(&cluster_name)
.num_masters(3)
.port_base(9300)
.network_mode("host");
assert_eq!(
RedisClusterConnection::from_template(&via_mode).nodes(),
conn.nodes()
);
}
#[tokio::test]
#[ignore] async fn test_redis_cluster_host_network_smoke_test() {
if !cfg!(target_os = "linux") {
println!("Host networking smoke test skipped - Linux-only Docker feature");
return;
}
let cluster_name = format!("test-cluster-host-smoke-{}", uuid::Uuid::new_v4());
let cluster = RedisClusterTemplate::new(&cluster_name)
.num_masters(3)
.port_base(9400)
.host_network()
.auto_remove();
match timeout(TEST_TIMEOUT, cluster.start()).await {
Ok(Ok(result)) => {
assert!(!result.is_empty(), "Cluster result should not be empty");
let ready = timeout(
Duration::from_secs(60),
cluster.wait_until_ready(Duration::from_secs(60)),
)
.await;
assert!(
matches!(ready, Ok(Ok(()))),
"host-mode cluster should become ready"
);
let node0 = cluster.node(0).expect("node 0 exists");
if let Ok(Ok(role)) =
timeout(Duration::from_secs(30), cluster.node_role(node0.index)).await
{
assert_eq!(role, NodeRole::Master, "node 0 should report as master");
}
let _ = timeout(Duration::from_secs(30), cluster.stop()).await;
let _ = timeout(Duration::from_secs(30), cluster.remove()).await;
}
Ok(Err(_)) | Err(_) => {
let _ = timeout(Duration::from_secs(30), cluster.stop()).await;
let _ = timeout(Duration::from_secs(30), cluster.remove()).await;
println!("Host networking smoke test skipped - Docker not available or host mode unsupported");
}
}
}