RedisRestAPIUnitTest.java
package redis.clients.jedis.mcf;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
import java.io.ByteArrayInputStream;
import java.net.HttpURLConnection;
import java.util.Arrays;
import java.util.List;
import java.util.function.Supplier;
import org.junit.jupiter.api.Test;
import redis.clients.jedis.DefaultRedisCredentials;
import redis.clients.jedis.Endpoint;
import redis.clients.jedis.RedisCredentials;
public class RedisRestAPIUnitTest {
static class TestEndpoint implements Endpoint {
@Override
public String getHost() {
return "localhost";
}
@Override
public int getPort() {
return 8443;
}
}
@Test
void getBdbs_parsesArrayOfObjects() throws Exception {
RedisRestAPI api = spy(new RedisRestAPI(new TestEndpoint(), creds(), 1000));
HttpURLConnection conn = mock(HttpURLConnection.class);
doReturn(conn).when(api).createConnection(any(), any(), any());
when(conn.getResponseCode()).thenReturn(200);
String body = "[ {\"uid\":\"1\", \"endpoints\":[]}, {\"uid\":\"2\", \"endpoints\":[]} ]";
when(conn.getInputStream()).thenReturn(new ByteArrayInputStream(body.getBytes()));
List<RedisRestAPI.BdbInfo> result = api.getBdbs();
assertEquals(2, result.size());
assertEquals("1", result.get(0).getUid());
assertEquals("2", result.get(1).getUid());
verify(conn, times(1)).disconnect();
}
@Test
void availability_logsAndReturnsFalseForNon200() throws Exception {
RedisRestAPI api = spy(new RedisRestAPI(new TestEndpoint(), creds(), 1000));
HttpURLConnection conn = mock(HttpURLConnection.class);
doReturn(conn).when(api).createConnection(any(), any(), any());
when(conn.getResponseCode()).thenReturn(503);
String body = "{\"error_code\":\"bdb_unavailable\",\"description\":\"Database is not available\"}";
when(conn.getErrorStream()).thenReturn(new ByteArrayInputStream(body.getBytes()));
assertFalse(api.checkBdbAvailability("2", false));
}
private static Supplier<RedisCredentials> creds() {
return () -> new DefaultRedisCredentials("testUser", "testPwd");
}
@Test
void availability_200_and_503_paths_cover_lagAware_toggle() throws Exception {
RedisRestAPI api = spy(new RedisRestAPI(new TestEndpoint(), creds(), 1000));
HttpURLConnection conn = mock(HttpURLConnection.class);
doReturn(conn).when(api).createConnection(any(), any(), any());
// Healthy path (200)
when(conn.getResponseCode()).thenReturn(200);
assertTrue(api.checkBdbAvailability("123", true));
// Unhealthy path (503) with error body
reset(conn);
doReturn(conn).when(api).createConnection(any(), any(), any());
when(conn.getResponseCode()).thenReturn(503);
when(conn.getErrorStream())
.thenReturn(new ByteArrayInputStream("{\"error_code\":\"bdb_unavailable\"}".getBytes()));
assertFalse(api.checkBdbAvailability("123", false));
}
@Test
void testCheckBdbAvailabilityWithExtendedCheck() throws Exception {
RedisRestAPI api = spy(
new RedisRestAPI(new TestEndpoint(), () -> new DefaultRedisCredentials("user", "pass")));
HttpURLConnection conn = mock(HttpURLConnection.class);
doReturn(conn).when(api).createConnection(any(), any(), any());
when(conn.getResponseCode()).thenReturn(200);
assertTrue(api.checkBdbAvailability("123", true, 100L));
// Verify the correct URL was constructed with extended check parameters
verify(api).createConnection(eq(
"https://localhost:8443/v1/bdbs/123/availability?extend_check=lag&availability_lag_tolerance_ms=100"),
eq("GET"), any());
}
@Test
void testCheckBdbAvailabilityWithExtendedCheckNoTolerance() throws Exception {
RedisRestAPI api = spy(
new RedisRestAPI(new TestEndpoint(), () -> new DefaultRedisCredentials("user", "pass")));
HttpURLConnection conn = mock(HttpURLConnection.class);
doReturn(conn).when(api).createConnection(any(), any(), any());
when(conn.getResponseCode()).thenReturn(200);
assertTrue(api.checkBdbAvailability("123", true, null));
// Verify the correct URL was constructed with extended check but no tolerance parameter
verify(api).createConnection(
eq("https://localhost:8443/v1/bdbs/123/availability?extend_check=lag"), eq("GET"), any());
}
@Test
void testCheckBdbAvailabilityWithStandardCheck() throws Exception {
RedisRestAPI api = spy(
new RedisRestAPI(new TestEndpoint(), () -> new DefaultRedisCredentials("user", "pass")));
HttpURLConnection conn = mock(HttpURLConnection.class);
doReturn(conn).when(api).createConnection(any(), any(), any());
when(conn.getResponseCode()).thenReturn(200);
assertTrue(api.checkBdbAvailability("123", false, null));
// Verify the correct URL was constructed for standard check (no query parameters)
verify(api).createConnection(eq("https://localhost:8443/v1/bdbs/123/availability"), eq("GET"),
any());
}
// ========== Parsing and BDB Matching Tests ==========
@Test
void parseBdbInfoFromResponse_parses_correctly() {
String responseBody = "[\n" + " {\n" + " \"uid\": \"1\",\n"
+ " \"endpoints\": [\n" + " {\n"
+ " \"dns_name\": \"redis-db1.example.com\",\n"
+ " \"addr\": [\"10.0.1.100\"],\n" + " \"port\": 6379,\n"
+ " \"uid\": \"1:1\"\n" + " }\n" + " ]\n" + " },\n"
+ " {\n" + " \"uid\": \"2\",\n" + " \"endpoints\": [\n" + " {\n"
+ " \"dns_name\": \"redis-db2.example.com\",\n"
+ " \"addr\": [\"10.0.1.101\"],\n" + " \"port\": 6380,\n"
+ " \"uid\": \"2:1\"\n" + " }\n" + " ]\n" + " }\n"
+ "]";
List<RedisRestAPI.BdbInfo> result = RedisRestAPI.parseBdbInfoFromResponse(responseBody);
assertEquals(2, result.size());
RedisRestAPI.BdbInfo bdb1 = result.get(0);
assertEquals("1", bdb1.getUid());
assertEquals(1, bdb1.getEndpoints().size());
RedisRestAPI.EndpointInfo endpoint1 = bdb1.getEndpoints().get(0);
assertEquals("redis-db1.example.com", endpoint1.getDnsName());
assertEquals(Arrays.asList("10.0.1.100"), endpoint1.getAddr());
assertEquals(Integer.valueOf(6379), endpoint1.getPort());
assertEquals("1:1", endpoint1.getUid());
}
@Test
void findMatchingBdb_matches_dns_name() {
RedisRestAPI.BdbInfo bdb1 = new RedisRestAPI.BdbInfo("1",
Arrays.asList(new RedisRestAPI.EndpointInfo(Arrays.asList("10.0.1.100"),
"redis-db1.example.com", 6379, "1:1")));
RedisRestAPI.BdbInfo bdb2 = new RedisRestAPI.BdbInfo("2",
Arrays.asList(new RedisRestAPI.EndpointInfo(Arrays.asList("10.0.1.101"),
"redis-db2.example.com", 6380, "2:1")));
List<RedisRestAPI.BdbInfo> bdbs = Arrays.asList(bdb1, bdb2);
RedisRestAPI.BdbInfo result = RedisRestAPI.BdbInfo.findMatchingBdb(bdbs,
"redis-db2.example.com");
assertNotNull(result);
assertEquals("2", result.getUid());
}
@Test
void findMatchingBdb_matches_ip_address() {
RedisRestAPI.BdbInfo bdb1 = new RedisRestAPI.BdbInfo("1",
Arrays.asList(new RedisRestAPI.EndpointInfo(Arrays.asList("10.0.1.100", "192.168.1.100"),
"redis-db1.example.com", 6379, "1:1")));
RedisRestAPI.BdbInfo bdb2 = new RedisRestAPI.BdbInfo("2",
Arrays.asList(new RedisRestAPI.EndpointInfo(Arrays.asList("10.0.1.101"),
"redis-db2.example.com", 6380, "2:1")));
List<RedisRestAPI.BdbInfo> bdbs = Arrays.asList(bdb1, bdb2);
RedisRestAPI.BdbInfo result = RedisRestAPI.BdbInfo.findMatchingBdb(bdbs, "192.168.1.100");
assertNotNull(result);
assertEquals("1", result.getUid());
}
@Test
void findMatchingBdb_returns_null_when_no_match() {
RedisRestAPI.BdbInfo bdb1 = new RedisRestAPI.BdbInfo("1",
Arrays.asList(new RedisRestAPI.EndpointInfo(Arrays.asList("10.0.1.100"),
"redis-db1.example.com", 6379, "1:1")));
List<RedisRestAPI.BdbInfo> bdbs = Arrays.asList(bdb1);
RedisRestAPI.BdbInfo result = RedisRestAPI.BdbInfo.findMatchingBdb(bdbs,
"nonexistent.example.com");
assertNull(result);
}
@Test
void findMatchingBdb_handles_multiple_endpoints_per_bdb() {
RedisRestAPI.BdbInfo bdb1 = new RedisRestAPI.BdbInfo("1",
Arrays.asList(
new RedisRestAPI.EndpointInfo(Arrays.asList("10.0.1.100"),
"redis-db1-primary.example.com", 6379, "1:1"),
new RedisRestAPI.EndpointInfo(Arrays.asList("10.0.1.101"),
"redis-db1-replica.example.com", 6380, "1:2")));
List<RedisRestAPI.BdbInfo> bdbs = Arrays.asList(bdb1);
RedisRestAPI.BdbInfo result = RedisRestAPI.BdbInfo.findMatchingBdb(bdbs,
"redis-db1-replica.example.com");
assertNotNull(result);
assertEquals("1", result.getUid());
}
@Test
void parseBdbInfoFromResponse_handles_missing_fields_gracefully() {
String responseBody = "[\n" + " {\n" + " \"uid\": \"1\"\n" + " },\n" + " {\n"
+ " \"endpoints\": [\n" + " {\n"
+ " \"dns_name\": \"redis-db2.example.com\"\n" + " }\n"
+ " ]\n" + " },\n" + " {\n" + " \"uid\": \"3\",\n"
+ " \"endpoints\": [\n" + " {\n"
+ " \"dns_name\": \"redis-db3.example.com\",\n"
+ " \"addr\": [\"10.0.1.103\"],\n" + " \"port\": 6379\n"
+ " }\n" + " ]\n" + " }\n" + "]";
List<RedisRestAPI.BdbInfo> result = RedisRestAPI.parseBdbInfoFromResponse(responseBody);
assertEquals(2, result.size()); // Only BDBs with uid are included
RedisRestAPI.BdbInfo bdb1 = result.get(0);
assertEquals("1", bdb1.getUid());
assertEquals(0, bdb1.getEndpoints().size()); // No endpoints
RedisRestAPI.BdbInfo bdb3 = result.get(1);
assertEquals("3", bdb3.getUid());
assertEquals(1, bdb3.getEndpoints().size());
RedisRestAPI.EndpointInfo endpoint = bdb3.getEndpoints().get(0);
assertEquals("redis-db3.example.com", endpoint.getDnsName());
assertEquals(Arrays.asList("10.0.1.103"), endpoint.getAddr());
assertEquals(Integer.valueOf(6379), endpoint.getPort());
assertNull(endpoint.getUid()); // Missing uid field
}
@Test
void parseBdbInfoFromResponse_handles_empty_response() {
String responseBody = "[]";
List<RedisRestAPI.BdbInfo> result = RedisRestAPI.parseBdbInfoFromResponse(responseBody);
assertTrue(result.isEmpty());
}
@Test
void findMatchingBdb_prefers_dns_name_over_addr() {
RedisRestAPI.BdbInfo bdb1 = new RedisRestAPI.BdbInfo("1",
Arrays.asList(new RedisRestAPI.EndpointInfo(Arrays.asList("10.0.1.100"),
"target-host.example.com", 6379, "1:1")));
RedisRestAPI.BdbInfo bdb2 = new RedisRestAPI.BdbInfo("2",
Arrays.asList(new RedisRestAPI.EndpointInfo(Arrays.asList("target-host.example.com"),
"other-host.example.com", 6380, "2:1")));
List<RedisRestAPI.BdbInfo> bdbs = Arrays.asList(bdb1, bdb2);
// Should match BDB 1 by dns_name, not BDB 2 by addr
RedisRestAPI.BdbInfo result = RedisRestAPI.BdbInfo.findMatchingBdb(bdbs,
"target-host.example.com");
assertNotNull(result);
assertEquals("1", result.getUid());
}
@Test
void findMatchingBdb_matches_correct_bdb_with_same_host_different_ports() {
// Two BDBs with same DNS name but different ports
RedisRestAPI.BdbInfo bdb1 = new RedisRestAPI.BdbInfo("1",
Arrays.asList(new RedisRestAPI.EndpointInfo(Arrays.asList("10.0.1.100"),
"redis.example.com", 6379, "1:1")));
RedisRestAPI.BdbInfo bdb2 = new RedisRestAPI.BdbInfo("2",
Arrays.asList(new RedisRestAPI.EndpointInfo(Arrays.asList("10.0.1.100"),
"redis.example.com", 6380, "2:1")));
List<RedisRestAPI.BdbInfo> bdbs = Arrays.asList(bdb1, bdb2);
// Should match first BDB found with the DNS name (current implementation matches by host only)
RedisRestAPI.BdbInfo result = RedisRestAPI.BdbInfo.findMatchingBdb(bdbs, "redis.example.com");
assertNotNull(result);
assertEquals("1", result.getUid()); // First match wins
}
@Test
void findMatchingBdb_matches_correct_bdb_with_same_ip_different_ports() {
// Two BDBs with same IP but different ports and DNS names
RedisRestAPI.BdbInfo bdb1 = new RedisRestAPI.BdbInfo("1",
Arrays.asList(new RedisRestAPI.EndpointInfo(Arrays.asList("10.0.1.100"),
"redis1.example.com", 6379, "1:1")));
RedisRestAPI.BdbInfo bdb2 = new RedisRestAPI.BdbInfo("2",
Arrays.asList(new RedisRestAPI.EndpointInfo(Arrays.asList("10.0.1.100"),
"redis2.example.com", 6380, "2:1")));
List<RedisRestAPI.BdbInfo> bdbs = Arrays.asList(bdb1, bdb2);
// Should match first BDB found with the IP address (current implementation matches by host
// only)
RedisRestAPI.BdbInfo result = RedisRestAPI.BdbInfo.findMatchingBdb(bdbs, "10.0.1.100");
assertNotNull(result);
assertEquals("1", result.getUid()); // First match wins
}
}