RedisClusterClientMigrationIntegrationTest.java
package redis.clients.jedis.builders;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import java.util.HashSet;
import java.util.Set;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import redis.clients.jedis.Connection;
import redis.clients.jedis.ConnectionPoolConfig;
import redis.clients.jedis.DefaultJedisClientConfig;
import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.HostAndPorts;
import redis.clients.jedis.JedisClientConfig;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.RedisClusterClient;
/**
* Integration test that verifies migration compatibility from the legacy JedisCluster constructor
* to the new RedisClusterClient.Builder pattern.
* <p>
* This test demonstrates that the following legacy JedisCluster constructor:
*
* <pre>
* public JedisCluster(Set<HostAndPort> clusterNodes, JedisClientConfig clientConfig,
* int maxAttempts, GenericObjectPoolConfig<Connection> poolConfig)
* </pre>
*
* can be replaced with the RedisClusterClient.Builder pattern while maintaining the same
* functionality.
*/
@Tag("integration")
public class RedisClusterClientMigrationIntegrationTest {
private static final Set<HostAndPort> CLUSTER_NODES = new HashSet<>(
HostAndPorts.getStableClusterServers());
private static final String PASSWORD = "cluster";
private static final int MAX_ATTEMPTS = 3;
private JedisCluster legacyCluster;
private RedisClusterClient newCluster;
@BeforeEach
public void setUp() {
// Clean up any existing data before each test
cleanClusterData();
}
@AfterEach
public void tearDown() {
// Close connections
if (legacyCluster != null) {
legacyCluster.close();
legacyCluster = null;
}
if (newCluster != null) {
newCluster.close();
newCluster = null;
}
// Clean up data after tests
cleanClusterData();
}
/**
* Test that verifies both approaches can handle cluster operations correctly. Tests constructor:
* JedisCluster(Set<HostAndPort>, JedisClientConfig, int,
* GenericObjectPoolConfig<Connection>)
*/
@Test
public void testSpringDataRedisConstructor() {
// Prepare common configuration
JedisClientConfig clientConfig = DefaultJedisClientConfig.builder().password(PASSWORD)
.socketTimeoutMillis(2000).connectionTimeoutMillis(2000).build();
GenericObjectPoolConfig<Connection> poolConfig = new ConnectionPoolConfig();
// Create both cluster clients
legacyCluster = new JedisCluster(CLUSTER_NODES, clientConfig, MAX_ATTEMPTS, poolConfig);
newCluster = RedisClusterClient.builder().nodes(CLUSTER_NODES).clientConfig(clientConfig)
.maxAttempts(MAX_ATTEMPTS).poolConfig(poolConfig).build();
verifyBothClients(legacyCluster, newCluster);
}
/**
* Test migration from constructor with username, password, clientName, and SSL. Tests
* constructor: JedisCluster(Set<HostAndPort>, int, int, int, String, String, String,
* GenericObjectPoolConfig<Connection>, boolean)
*/
@Test
public void testConstructorWithUsernamePasswordClientNameAndSsl() {
int connectionTimeout = 2000;
int socketTimeout = 2000;
String username = "default";
String clientName = "test-client";
boolean ssl = false; // SSL requires special cluster setup
GenericObjectPoolConfig<Connection> poolConfig = new ConnectionPoolConfig();
// Legacy constructor with username
legacyCluster = new JedisCluster(CLUSTER_NODES, connectionTimeout, socketTimeout, MAX_ATTEMPTS,
username, PASSWORD, clientName, poolConfig, ssl);
// New Builder pattern
JedisClientConfig clientConfig = DefaultJedisClientConfig.builder()
.connectionTimeoutMillis(connectionTimeout).socketTimeoutMillis(socketTimeout)
.user(username).password(PASSWORD).clientName(clientName).ssl(ssl).build();
newCluster = RedisClusterClient.builder().nodes(CLUSTER_NODES).clientConfig(clientConfig)
.maxAttempts(MAX_ATTEMPTS).poolConfig(poolConfig).build();
verifyBothClients(legacyCluster, newCluster);
}
/**
* Test migration from constructor with password, clientName, and SSL (no username). Tests
* constructor: JedisCluster(Set<HostAndPort>, int, int, int, String, String,
* GenericObjectPoolConfig<Connection>, boolean)
*/
@Test
public void testConstructorWithPasswordClientNameAndSsl() {
int connectionTimeout = 2000;
int socketTimeout = 2000;
String clientName = "test-client";
boolean ssl = false; // SSL requires special cluster setup
GenericObjectPoolConfig<Connection> poolConfig = new ConnectionPoolConfig();
// Legacy constructor without username
legacyCluster = new JedisCluster(CLUSTER_NODES, connectionTimeout, socketTimeout, MAX_ATTEMPTS,
PASSWORD, clientName, poolConfig, ssl);
// New Builder pattern
JedisClientConfig clientConfig = DefaultJedisClientConfig.builder()
.connectionTimeoutMillis(connectionTimeout).socketTimeoutMillis(socketTimeout)
.password(PASSWORD).clientName(clientName).ssl(ssl).build();
newCluster = RedisClusterClient.builder().nodes(CLUSTER_NODES).clientConfig(clientConfig)
.maxAttempts(MAX_ATTEMPTS).poolConfig(poolConfig).build();
verifyBothClients(legacyCluster, newCluster);
}
/**
* Test migration from simple constructor with just nodes and poolConfig. Tests constructor:
* JedisCluster(Set<HostAndPort>, GenericObjectPoolConfig<Connection>)
*/
@Test
public void testConstructorWithNodesAndPoolConfig() {
GenericObjectPoolConfig<Connection> poolConfig = new ConnectionPoolConfig();
poolConfig.setMaxTotal(8);
poolConfig.setMaxIdle(8);
JedisClientConfig clientConfig = DefaultJedisClientConfig.builder().password(PASSWORD).build();
// Legacy constructor
legacyCluster = new JedisCluster(CLUSTER_NODES, clientConfig, poolConfig);
// New Builder pattern - need to add password via clientConfig
newCluster = RedisClusterClient.builder().nodes(CLUSTER_NODES).clientConfig(clientConfig)
.poolConfig(poolConfig).build();
verifyBothClients(legacyCluster, newCluster);
}
/**
* Test migration from constructor with connection timeout, socket timeout, maxAttempts, password,
* and poolConfig. Tests constructor: JedisCluster(Set<HostAndPort>, int, int, int, String,
* GenericObjectPoolConfig<Connection>)
*/
@Test
public void testConstructorWithTimeoutsPasswordAndPoolConfig() {
int connectionTimeout = 2000;
int socketTimeout = 2000;
GenericObjectPoolConfig<Connection> poolConfig = new ConnectionPoolConfig();
// Legacy constructor
legacyCluster = new JedisCluster(CLUSTER_NODES, connectionTimeout, socketTimeout, MAX_ATTEMPTS,
PASSWORD, poolConfig);
// New Builder pattern
JedisClientConfig clientConfig = DefaultJedisClientConfig.builder()
.connectionTimeoutMillis(connectionTimeout).socketTimeoutMillis(socketTimeout)
.password(PASSWORD).build();
newCluster = RedisClusterClient.builder().nodes(CLUSTER_NODES).clientConfig(clientConfig)
.maxAttempts(MAX_ATTEMPTS).poolConfig(poolConfig).build();
verifyBothClients(legacyCluster, newCluster);
}
/**
* Test migration from constructor with timeout, maxAttempts, and poolConfig. Tests constructor:
* JedisCluster(Set<HostAndPort>, int, int, GenericObjectPoolConfig<Connection>)
*/
@Test
public void testConstructorWithTimeoutMaxAttemptsAndPoolConfig() {
int timeout = 2000;
GenericObjectPoolConfig<Connection> poolConfig = new ConnectionPoolConfig();
JedisClientConfig clientConfig = DefaultJedisClientConfig.builder().timeoutMillis(timeout)
.password(PASSWORD).build();
// Legacy constructor - uses same timeout for connection and socket
legacyCluster = new JedisCluster(CLUSTER_NODES, timeout, timeout, MAX_ATTEMPTS, PASSWORD,
poolConfig);
// New Builder pattern
newCluster = RedisClusterClient.builder().nodes(CLUSTER_NODES).clientConfig(clientConfig)
.maxAttempts(MAX_ATTEMPTS).poolConfig(poolConfig).build();
verifyBothClients(legacyCluster, newCluster);
}
/**
* Test migration from single HostAndPort constructor with poolConfig. Tests constructor:
* JedisCluster(HostAndPort, GenericObjectPoolConfig<Connection>)
*/
@Test
public void testConstructorWithSingleNodeAndPoolConfig() {
int timeout = 2000;
HostAndPort singleNode = HostAndPorts.getStableClusterServers().get(0);
GenericObjectPoolConfig<Connection> poolConfig = new ConnectionPoolConfig();
JedisClientConfig clientConfig = DefaultJedisClientConfig.builder().password(PASSWORD).build();
// Legacy constructor with single node
legacyCluster = new JedisCluster(singleNode, timeout, timeout, MAX_ATTEMPTS, PASSWORD,
poolConfig);
// New Builder pattern - need to add password and wrap single node in a Set
Set<HostAndPort> singleNodeSet = new HashSet<>();
singleNodeSet.add(singleNode);
newCluster = RedisClusterClient.builder().nodes(singleNodeSet).clientConfig(clientConfig)
.poolConfig(poolConfig).build();
verifyBothClients(legacyCluster, newCluster);
}
protected void verifyBothClients(JedisCluster legacyCluster, RedisClusterClient newCluster) {
// Verify both discovered the cluster nodes
assertNotNull(legacyCluster.getClusterNodes(), "Legacy cluster should discover cluster nodes");
assertNotNull(newCluster.getClusterNodes(), "New cluster should discover cluster nodes");
// Both should have discovered the same number of nodes
assertEquals(legacyCluster.getClusterNodes().size(), newCluster.getClusterNodes().size(),
"Both approaches should discover the same number of cluster nodes");
// Test basic string operations
String key1 = "test-string-key";
legacyCluster.set(key1, "value1");
assertEquals("value1", newCluster.get(key1));
// Test increment operations
String key2 = "test-counter-key";
legacyCluster.set(key2, "0");
newCluster.incr(key2);
assertEquals("1", legacyCluster.get(key2));
// Clean up
newCluster.del(key1);
newCluster.del(key2);
}
/**
* Helper method to clean up cluster data before and after tests.
*/
private void cleanClusterData() {
// Connect to each stable cluster node and flush data
for (HostAndPort node : HostAndPorts.getStableClusterServers()) {
try (redis.clients.jedis.Jedis jedis = new redis.clients.jedis.Jedis(node)) {
jedis.auth(PASSWORD);
jedis.flushDB();
} catch (Exception e) {
// Ignore errors during cleanup
}
}
}
}