ClusterReplyAggregatorTest.java
package redis.clients.jedis.executors.aggregators;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import redis.clients.jedis.CommandFlagsRegistry;
import redis.clients.jedis.exceptions.UnsupportedAggregationException;
import redis.clients.jedis.util.ByteArrayMapMatcher;
import redis.clients.jedis.util.JedisByteHashMap;
import redis.clients.jedis.util.JedisByteMap;
import redis.clients.jedis.util.JedisByteMapMatcher;
import redis.clients.jedis.util.KeyValue;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Stream;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.arrayContainingInAnyOrder;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.sameInstance;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class ClusterReplyAggregatorTest {
// ==================== aggregateAllSucceeded Tests ====================
// Per Redis ALL_SUCCEEDED spec: returns successfully only if there are no error replies.
// Error handling is done separately by the caller (MultiNodeResultAggregator.addError()),
// so aggregateAllSucceeded simply returns the first reply when aggregating successful responses.
@Nested
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class AggregateAllSucceededTests {
/**
* Provides test cases for ALL_SUCCEEDED aggregator. Each Object[] contains {firstValue,
* secondValue, thirdValue}. Includes Long, Integer, Double, String, Boolean.
*/
Stream<Object[]> valuesProvider() {
return Stream.of(new Object[] { 42L, 42L, 100L }, // Long
new Object[] { 123, 123, 456 }, // Integer
new Object[] { 3.14159, 3.14159, 2.71828 }, // Double
new Object[] { "OK", "OK", "DIFFERENT" }, // String
new Object[] { true, true, false }, // Boolean
new Object[] { false, false, true }, // Boolean
new Object[] { new byte[] { 1, 2 }, new byte[] { 1, 2 }, new byte[] { 3, 4 } } // byte[]
);
}
@ParameterizedTest
@MethodSource("valuesProvider")
void testAggregateAllSucceeded_returnsFirstValue(Object first, Object second, Object third) {
@SuppressWarnings("unchecked")
ClusterReplyAggregator<Object> aggregator = new ClusterReplyAggregator<>(
CommandFlagsRegistry.ResponsePolicy.ALL_SUCCEEDED);
// first addition
aggregator.add(first);
assertThat("First value should be result", aggregator.getResult(), equalTo(first));
// add same value again
aggregator.add(second);
assertThat("Result should remain first value", aggregator.getResult(), equalTo(first));
// add a different value
aggregator.add(third);
assertThat("Result should still remain first value", aggregator.getResult(), equalTo(first));
}
}
// ==================== aggregateDefault - List<String> Tests ====================
@Nested
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class AggregateDefaultTests {
// ==================== aggregateDefault - Unsupported Types Throw Exception
// ====================
@Test
public void testAggregateDefault_nonListTypes_throwsUnsupportedAggregationException() {
String first = "existing";
ClusterReplyAggregator<String> aggregator = new ClusterReplyAggregator<>(
CommandFlagsRegistry.ResponsePolicy.DEFAULT);
UnsupportedAggregationException exception = assertThrows(
UnsupportedAggregationException.class, () -> aggregator.add(first));
assertTrue(exception.getMessage().contains("DEFAULT policy requires"),
"Exception message should describe the policy requirement");
assertTrue(exception.getMessage().contains("String"),
"Exception message should mention the unsupported type");
}
@Nested
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class AggregateDefaultListTests {
/**
* Provides test cases: {firstList, secondList, expectedResult}.
*/
Stream<Object[]> listProvider() {
return Stream.of(
// aggregate non empty lists
new Object[] { Arrays.asList("key1", "key2"), Arrays.asList("key3", "key4"),
Arrays.asList("key1", "key2", "key3", "key4") },
// aggregate null and non empty list
new Object[] { null, Arrays.asList("key1", "key2"), Arrays.asList("key1", "key2") },
// aggregate empty and non empty list
new Object[] { Collections.emptyList(), Arrays.asList("key1", "key2"),
Arrays.asList("key1", "key2") },
// aggregate empty and non empty list
new Object[] { new ArrayList<>(), Arrays.asList("key1", "key2"),
Arrays.asList("key1", "key2") },
// aggregate non empty and empty list
new Object[] { Arrays.asList("key1", "key2"), new ArrayList<>(),
Arrays.asList("key1", "key2") },
// aggregate two empty lists
new Object[] { new ArrayList<>(), new ArrayList<>(), new ArrayList<>() }, // both empty ���
// empty
// aggregate two null lists
new Object[] { null, null, null });
}
@ParameterizedTest
@MethodSource("listProvider")
void testAggregateDefault_lists(List<String> first, List<String> second,
List<String> expected) {
ClusterReplyAggregator<List<String>> aggregator = new ClusterReplyAggregator<>(
CommandFlagsRegistry.ResponsePolicy.DEFAULT);
aggregator.add(first);
aggregator.add(second);
List<String> result = aggregator.getResult();
assertThat("Aggregated list should match expected", result, equalTo(expected));
}
// ==================== aggregateDefault - List<byte[]> Tests ====================
@Test
public void testAggregateDefault_twoByteArrayLists_concatenatesThem() {
List<byte[]> first = new ArrayList<>(
Arrays.asList(new byte[] { 1, 2 }, new byte[] { 3, 4 }));
List<byte[]> second = new ArrayList<>(
Arrays.asList(new byte[] { 5, 6 }, new byte[] { 7, 8 }));
ClusterReplyAggregator<List<byte[]>> aggregator = new ClusterReplyAggregator<>(
CommandFlagsRegistry.ResponsePolicy.DEFAULT);
aggregator.add(first);
aggregator.add(second);
List<byte[]> result = aggregator.getResult();
assertEquals(4, result.size(), "Should contain all byte arrays from both lists");
assertThat(result, contains(new byte[] { 1, 2 }, new byte[] { 3, 4 }, new byte[] { 5, 6 },
new byte[] { 7, 8 }));
}
// ==================== aggregateDefault - Different List Implementations ====================
@Test
public void testAggregateDefault_linkedListAndArrayList() {
List<String> first = new LinkedList<>(Arrays.asList("a", "b"));
List<String> second = new ArrayList<>(Arrays.asList("c", "d"));
ClusterReplyAggregator<List<String>> aggregator = new ClusterReplyAggregator<>(
CommandFlagsRegistry.ResponsePolicy.DEFAULT);
aggregator.add(first);
aggregator.add(second);
List<String> result = aggregator.getResult();
assertEquals(4, result.size(), "Should concatenate different list implementations");
assertEquals(Arrays.asList("a", "b", "c", "d"), result);
}
// ==================== aggregateDefault - Mutates Existing ArrayList In Place
// ====================
@Test
public void testAggregateDefault_singleReplyDoesNotCreateNewList() {
List<String> first = null;
List<String> second = new ArrayList<>(Arrays.asList("c", "d"));
ClusterReplyAggregator<List<String>> aggregator = new ClusterReplyAggregator<>(
CommandFlagsRegistry.ResponsePolicy.DEFAULT);
aggregator.add(first);
aggregator.add(second);
List<String> result = aggregator.getResult();
// If single non null reply, Result should be the same instance
assertSame(second, result, "Result should be the same instance as first non null reply");
assertThat(result, contains("c", "d"));
assertThat(result, sameInstance(second));
}
}
// ==================== aggregateDefault - Map Tests ====================
@Nested
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class AggregateDefaultMapTests {
/**
* Provides test cases: {firstMap, secondMap, expectedResult}.
*/
Stream<Object[]> mapProvider() {
Map<String, Integer> firstMap = new HashMap<>();
firstMap.put("key1", 1);
firstMap.put("key2", 2);
Map<String, Integer> secondMap = new HashMap<>();
secondMap.put("key3", 3);
secondMap.put("key4", 4);
Map<String, Integer> expectedFirstOnly = new HashMap<>();
expectedFirstOnly.put("key1", 1);
expectedFirstOnly.put("key2", 2);
Map<String, Integer> expectedMergedMap = new HashMap<>();
expectedMergedMap.put("key1", 1);
expectedMergedMap.put("key2", 2);
expectedMergedMap.put("key3", 3);
expectedMergedMap.put("key4", 4);
Map<String, Integer> overlappingMap = new HashMap<>();
overlappingMap.put("key1", 1);
overlappingMap.put("key3", 3);
Map<String, Integer> expectedOverlappingMap = new HashMap<>();
expectedOverlappingMap.put("key1", 1);
expectedOverlappingMap.put("key2", 2);
expectedOverlappingMap.put("key3", 3);
return Stream.of(
// empty + non-empty ��� non-empty
new Object[] { new HashMap<String, Integer>(), firstMap, expectedFirstOnly },
// non-empty + empty ��� non-empty
new Object[] { firstMap, new HashMap<String, Integer>(), expectedFirstOnly },
// empty + empty ��� empty
new Object[] { new HashMap<String, Integer>(), new HashMap<String, Integer>(),
new HashMap<String, Integer>() },
// null + null ��� null
new Object[] { null, null, null },
// null + empty ��� empty
new Object[] { null, new HashMap<String, Integer>(), new HashMap<String, Integer>() },
// unmodifiableMap + non-empty ��� non-empty
new Object[] { Collections.emptyMap(), firstMap, expectedFirstOnly },
// non-empty + unmodifiableMap ��� non-empty
new Object[] { firstMap, Collections.emptyMap(), expectedFirstOnly },
// maps with different keys
new Object[] { firstMap, secondMap, expectedMergedMap },
// maps with overlapping keys, second map takes precedence
new Object[] { firstMap, overlappingMap, expectedOverlappingMap }
);
}
@ParameterizedTest
@MethodSource("mapProvider")
void testAggregateDefault_maps(Map<String, Integer> first, Map<String, Integer> second,
Map<String, Integer> expected) {
ClusterReplyAggregator<Map<String, Integer>> aggregator = new ClusterReplyAggregator<>(
CommandFlagsRegistry.ResponsePolicy.DEFAULT);
aggregator.add(first);
aggregator.add(second);
Map<String, Integer> result = aggregator.getResult();
assertThat("Aggregated map should match expected", result, equalTo(expected));
}
@Test
public void testAggregateDefault_differentMapImplementations_mergesThem() {
Map<String, String> first = new LinkedHashMap<>();
first.put("a", "1");
first.put("b", "2");
Map<String, String> second = new HashMap<>();
second.put("c", "3");
second.put("d", "4");
ClusterReplyAggregator<Map<String, String>> aggregator = new ClusterReplyAggregator<>(
CommandFlagsRegistry.ResponsePolicy.DEFAULT);
aggregator.add(first);
aggregator.add(second);
Map<String, String> result = aggregator.getResult();
assertEquals(4, result.size(), "Should merge different map implementations");
assertEquals("1", result.get("a"));
assertEquals("2", result.get("b"));
assertEquals("3", result.get("c"));
assertEquals("4", result.get("d"));
assertTrue(result instanceof HashMap, "Result should be a HashMap");
}
}
// ==================== aggregateDefault - Set Tests ====================
@Nested
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class AggregateDefaultSetTests {
/**
* Provides test cases: {firstSet, secondSet, expectedResult}.
*/
Stream<Object[]> setProvider() {
Set<String> nonEmptySet = new HashSet<>(Arrays.asList("a", "b"));
Set<String> expectedSet = new HashSet<>(Arrays.asList("a", "b"));
return Stream.of(
// empty + non-empty ��� non-empty
new Object[] { new HashSet<String>(), nonEmptySet, expectedSet },
// non-empty + empty ��� non-empty
new Object[] { nonEmptySet, new HashSet<String>(), expectedSet },
// empty + empty ��� empty
new Object[] { new HashSet<String>(), new HashSet<String>(), new HashSet<String>() },
// unmodifiableSet + non-empty ��� non-empty
new Object[] { Collections.emptySet(), nonEmptySet, expectedSet },
// non-empty + unmodifiableSet ��� non-empty
new Object[] { nonEmptySet, Collections.emptySet(), expectedSet },
// sets with overlapping elements, merges without duplicates
new Object[] { new HashSet<String>(Arrays.asList("a", "b", "c")),
new HashSet<String>(Arrays.asList("b", "c", "d")),
new HashSet<String>(Arrays.asList("a", "b", "c", "d")) },
// sets with different elements, merges all elements
new Object[] { new HashSet<String>(Arrays.asList("a", "b")),
new HashSet<String>(Arrays.asList("c", "d")),
new HashSet<String>(Arrays.asList("a", "b", "c", "d")) },
// different set implementations, merges all elements
new Object[] { new LinkedHashSet<String>(Arrays.asList("a", "b")),
new HashSet<String>(Arrays.asList("c", "d")),
new HashSet<String>(Arrays.asList("a", "b", "c", "d")) });
}
@ParameterizedTest
@MethodSource("setProvider")
void testAggregateDefault_sets(Set<String> first, Set<String> second, Set<String> expected) {
ClusterReplyAggregator<Set<String>> aggregator = new ClusterReplyAggregator<>(
CommandFlagsRegistry.ResponsePolicy.DEFAULT);
aggregator.add(first);
aggregator.add(second);
Set<String> result = aggregator.getResult();
assertThat("Aggregated set should match expected", result, instanceOf(HashSet.class));
assertThat("Aggregated set should match expected", result,
containsInAnyOrder(expected.toArray(new String[0])));
}
/**
* Provides test cases: {firstSet, secondSet, expectedResult}.
*/
Stream<Object[]> setByteArrayProvider() {
Set<byte[]> nonEmptySet1 = new HashSet<>(Arrays.asList("a".getBytes(), "b".getBytes()));
Set<byte[]> nonEmptySet2 = new HashSet<>(Arrays.asList("c".getBytes(), "d".getBytes()));
Set<byte[]> expectedSet = new HashSet<>(
Arrays.asList("a".getBytes(), "b".getBytes(), "c".getBytes(), "d".getBytes()));
return Stream.of(
// set of byte arrays
new Object[] { nonEmptySet1, nonEmptySet2, expectedSet },
// overlapping elements
new Object[] { new HashSet<>(Arrays.asList("a".getBytes(), "b".getBytes())),
new HashSet<>(Arrays.asList("b".getBytes(), "c".getBytes())),
new HashSet<>(Arrays.asList("a".getBytes(), "b".getBytes(), "c".getBytes())) },
// empty + non-empty ��� non-empty
new Object[] { new HashSet<byte[]>(), nonEmptySet1, nonEmptySet1 },
// non-empty + empty ��� non-empty
new Object[] { nonEmptySet1, new HashSet<byte[]>(), nonEmptySet1 });
}
@ParameterizedTest
@MethodSource("setByteArrayProvider")
void testAggregateDefault_sets_byteArrays(Set<byte[]> first, Set<byte[]> second,
Set<byte[]> expected) {
ClusterReplyAggregator<Set<byte[]>> aggregator = new ClusterReplyAggregator<>(
CommandFlagsRegistry.ResponsePolicy.DEFAULT);
aggregator.add(first);
aggregator.add(second);
Set<byte[]> result = aggregator.getResult();
assertThat("Aggregated set should match expected", result, instanceOf(HashSet.class));
assertThat(result.toArray(new byte[0][]),
arrayContainingInAnyOrder(expected.toArray(new byte[0][])));
}
@Test
public void testAggregateDefault_singleSet_returnsSameInstance() {
Set<String> first = null;
Set<String> second = new HashSet<>(Arrays.asList("c", "d"));
ClusterReplyAggregator<Set<String>> aggregator = new ClusterReplyAggregator<>(
CommandFlagsRegistry.ResponsePolicy.DEFAULT);
aggregator.add(first);
aggregator.add(second);
Set<String> result = aggregator.getResult();
// ClusterReplyAggregator mutates the first set in place
assertThat(result, sameInstance(second));
assertThat(result, contains("c", "d"));
}
}
// ==================== aggregateDefault - JedisByteHashMap Tests ====================
@Nested
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class AggregateDefaultJedisByteHashMapTests {
/**
* Provides test cases: {firstMap, secondMap, expectedResult}.
*/
Stream<Object[]> jedisByteHashMapProvider() {
JedisByteHashMap first = new JedisByteHashMap();
first.put(new byte[] { 'k', '1' }, new byte[] { 'v', '1' });
first.put(new byte[] { 'k', '2' }, new byte[] { 'v', '2' });
JedisByteHashMap second = new JedisByteHashMap();
second.put(new byte[] { 'k', '3' }, new byte[] { 'v', '3' });
second.put(new byte[] { 'k', '4' }, new byte[] { 'v', '4' });
JedisByteHashMap expectedFirstOnly = new JedisByteHashMap();
expectedFirstOnly.put(new byte[] { 'k', '1' }, new byte[] { 'v', '1' });
expectedFirstOnly.put(new byte[] { 'k', '2' }, new byte[] { 'v', '2' });
JedisByteHashMap expectedFirstSecondMerged = new JedisByteHashMap();
expectedFirstSecondMerged.put(new byte[] { 'k', '1' }, new byte[] { 'v', '1' });
expectedFirstSecondMerged.put(new byte[] { 'k', '2' }, new byte[] { 'v', '2' });
expectedFirstSecondMerged.put(new byte[] { 'k', '3' }, new byte[] { 'v', '3' });
expectedFirstSecondMerged.put(new byte[] { 'k', '4' }, new byte[] { 'v', '4' });
JedisByteHashMap overlapFirstKeys = new JedisByteHashMap();
overlapFirstKeys.put(new byte[] { 'k', '1' }, new byte[] { 'v', 'A' });
overlapFirstKeys.put(new byte[] { 'k', '2' }, new byte[] { 'v', '2' });
overlapFirstKeys.put(new byte[] { 'k', '3' }, new byte[] { 'v', '3' });
JedisByteHashMap overlapKeysMerged = new JedisByteHashMap();
overlapKeysMerged.put(new byte[] { 'k', '1' }, new byte[] { 'v', 'A' });
overlapKeysMerged.put(new byte[] { 'k', '2' }, new byte[] { 'v', '2' });
overlapKeysMerged.put(new byte[] { 'k', '3' }, new byte[] { 'v', '3' });
return Stream.of(
// empty + non-empty ��� non-empty
new Object[] { new JedisByteHashMap(), first, expectedFirstOnly },
// non-empty + empty ��� non-empty
new Object[] { first, new JedisByteHashMap(), expectedFirstOnly },
// empty + empty ��� empty
new Object[] { new JedisByteHashMap(), new JedisByteHashMap(), new JedisByteHashMap() },
// null + null ��� null
new Object[] { null, null, null },
// null + empty ��� empty
new Object[] { null, new JedisByteHashMap(), new JedisByteHashMap() },
// maps with no overlapping keys
new Object[] { first, second, expectedFirstSecondMerged },
// maps with overlapping keys, second map takes precedence
new Object[] { first, overlapFirstKeys, overlapKeysMerged });
}
@ParameterizedTest
@MethodSource("jedisByteHashMapProvider")
void testAggregateDefault_jedisByteHashMap(JedisByteHashMap first, JedisByteHashMap second,
JedisByteHashMap expected) {
ClusterReplyAggregator<Map<byte[], byte[]>> aggregator = new ClusterReplyAggregator<>(
CommandFlagsRegistry.ResponsePolicy.DEFAULT);
aggregator.add(first);
aggregator.add(second);
Map<byte[], byte[]> result = aggregator.getResult();
if (expected == null) {
assertNull(result);
} else {
assertThat(result, instanceOf(JedisByteHashMap.class));
assertThat(result, ByteArrayMapMatcher.contentEquals(expected));
}
}
@Test
public void testAggregateDefault_twoJedisByteHashMapsWithOverlappingKeys_secondMapTakesPrecedence() {
JedisByteHashMap first = new JedisByteHashMap();
first.put(new byte[] { 's', 'h', 'a', 'r', 'e', 'd' },
new byte[] { 'f', 'i', 'r', 's', 't' });
first.put(new byte[] { 'u', 'n', 'i', 'q', '1' }, new byte[] { 'v', 'a', 'l', '1' });
JedisByteHashMap second = new JedisByteHashMap();
second.put(new byte[] { 's', 'h', 'a', 'r', 'e', 'd' },
new byte[] { 's', 'e', 'c', 'o', 'n', 'd' });
second.put(new byte[] { 'u', 'n', 'i', 'q', '2' }, new byte[] { 'v', 'a', 'l', '2' });
ClusterReplyAggregator<JedisByteHashMap> aggregator = new ClusterReplyAggregator<>(
CommandFlagsRegistry.ResponsePolicy.DEFAULT);
aggregator.add(first);
aggregator.add(second);
JedisByteHashMap result = aggregator.getResult();
assertEquals(3, result.size(), "Should contain merged entries");
assertArrayEquals(new byte[] { 's', 'e', 'c', 'o', 'n', 'd' },
result.get(new byte[] { 's', 'h', 'a', 'r', 'e', 'd' }),
"Second map's value should overwrite first");
assertArrayEquals(new byte[] { 'v', 'a', 'l', '1' },
result.get(new byte[] { 'u', 'n', 'i', 'q', '1' }));
assertArrayEquals(new byte[] { 'v', 'a', 'l', '2' },
result.get(new byte[] { 'u', 'n', 'i', 'q', '2' }));
}
}
// ==================== aggregateDefault - JedisByteMap Tests ====================
@Nested
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class AggregateDefaultJedisByteMapTests {
/**
* Provides test cases: {firstMap, secondMap, expectedResult}.
*/
Stream<Object[]> jedisByteMapProvider() {
JedisByteMap<String> first = new JedisByteMap<>();
first.put(new byte[] { 'k', '1' }, "v1");
first.put(new byte[] { 'k', '2' }, "v2");
JedisByteMap<String> second = new JedisByteMap<>();
second.put(new byte[] { 'k', '3' }, "v3");
second.put(new byte[] { 'k', '4' }, "v4");
JedisByteMap<String> expectedFirstOnly = new JedisByteMap<>();
expectedFirstOnly.put(new byte[] { 'k', '1' }, "v1");
expectedFirstOnly.put(new byte[] { 'k', '2' }, "v2");
JedisByteMap<String> expectedFirstSecondMerged = new JedisByteMap<>();
expectedFirstSecondMerged.put(new byte[] { 'k', '1' }, "v1");
expectedFirstSecondMerged.put(new byte[] { 'k', '2' }, "v2");
expectedFirstSecondMerged.put(new byte[] { 'k', '3' }, "v3");
expectedFirstSecondMerged.put(new byte[] { 'k', '4' }, "v4");
JedisByteMap<String> overlapFirstKeys = new JedisByteMap<>();
overlapFirstKeys.put(new byte[] { 'k', '1' }, "vA");
overlapFirstKeys.put(new byte[] { 'k', '2' }, "v2");
overlapFirstKeys.put(new byte[] { 'k', '3' }, "v3");
JedisByteMap<String> overlapKeysMerged = new JedisByteMap<>();
overlapKeysMerged.put(new byte[] { 'k', '1' }, "vA");
overlapKeysMerged.put(new byte[] { 'k', '2' }, "v2");
overlapKeysMerged.put(new byte[] { 'k', '3' }, "v3");
return Stream.of(
// empty + non-empty ��� non-empty
new Object[] { new JedisByteMap<>(), first, expectedFirstOnly },
// non-empty + empty ��� non-empty
new Object[] { first, new JedisByteMap<>(), expectedFirstOnly },
// empty + empty ��� empty
new Object[] { new JedisByteMap<>(), new JedisByteMap<>(), new JedisByteMap<>() },
// null + null ��� null
new Object[] { null, null, null },
// null + empty ��� empty
new Object[] { null, new JedisByteMap<>(), new JedisByteMap<>() },
// maps with no overlapping keys
new Object[] { first, second, expectedFirstSecondMerged },
// maps with overlapping keys, second map takes precedence
new Object[] { first, overlapFirstKeys, overlapKeysMerged });
}
@ParameterizedTest
@MethodSource("jedisByteMapProvider")
void testAggregateDefault_jedisByteHashMap(Map<byte[], String> first,
Map<byte[], String> second, Map<byte[], String> expected) {
ClusterReplyAggregator<Map<byte[], String>> aggregator = new ClusterReplyAggregator<>(
CommandFlagsRegistry.ResponsePolicy.DEFAULT);
aggregator.add(first);
aggregator.add(second);
Map<byte[], String> result = aggregator.getResult();
if (expected == null) {
assertNull(result);
} else {
assertThat(result, instanceOf(JedisByteMap.class));
assertThat(result, JedisByteMapMatcher.contentEquals(expected));
}
}
}
}
// ==================== aggregateMin - KeyValue Tests ====================
@Nested
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class AggregateMinTests {
@Test
public void testAggregateMin_keyValueLongLong_returnsMinOfEachComponent() {
KeyValue<Long, Long> first = KeyValue.of(10L, 20L);
KeyValue<Long, Long> second = KeyValue.of(5L, 25L);
ClusterReplyAggregator<KeyValue<Long, Long>> aggregator = new ClusterReplyAggregator<>(
CommandFlagsRegistry.ResponsePolicy.AGG_MIN);
aggregator.add(first);
aggregator.add(second);
KeyValue<Long, Long> result = aggregator.getResult();
assertEquals(5L, result.getKey(), "Should return minimum key");
assertEquals(20L, result.getValue(), "Should return minimum value");
}
@Test
public void testAggregateMin_keyValueLongLong_firstSmaller() {
KeyValue<Long, Long> first = KeyValue.of(1L, 2L);
KeyValue<Long, Long> second = KeyValue.of(10L, 20L);
ClusterReplyAggregator<KeyValue<Long, Long>> aggregator = new ClusterReplyAggregator<>(
CommandFlagsRegistry.ResponsePolicy.AGG_MIN);
aggregator.add(first);
aggregator.add(second);
KeyValue<Long, Long> result = aggregator.getResult();
assertEquals(1L, result.getKey(), "Should return minimum key from first");
assertEquals(2L, result.getValue(), "Should return minimum value from first");
}
@Test
public void testAggregateMin_keyValueLongLong_secondSmaller() {
KeyValue<Long, Long> first = KeyValue.of(10L, 20L);
KeyValue<Long, Long> second = KeyValue.of(1L, 2L);
ClusterReplyAggregator<KeyValue<Long, Long>> aggregator = new ClusterReplyAggregator<>(
CommandFlagsRegistry.ResponsePolicy.AGG_MIN);
aggregator.add(first);
aggregator.add(second);
KeyValue<Long, Long> result = aggregator.getResult();
assertEquals(1L, result.getKey(), "Should return minimum key from second");
assertEquals(2L, result.getValue(), "Should return minimum value from second");
}
@Test
public void testAggregateMin_keyValueLongLong_equalValues() {
KeyValue<Long, Long> first = KeyValue.of(5L, 5L);
KeyValue<Long, Long> second = KeyValue.of(5L, 5L);
ClusterReplyAggregator<KeyValue<Long, Long>> aggregator = new ClusterReplyAggregator<>(
CommandFlagsRegistry.ResponsePolicy.AGG_MIN);
aggregator.add(first);
aggregator.add(second);
KeyValue<Long, Long> result = aggregator.getResult();
assertEquals(5L, result.getKey(), "Should return equal key");
assertEquals(5L, result.getValue(), "Should return equal value");
}
@Test
public void testAggregateMin_keyValueStringString_returnsMinOfEachComponent() {
KeyValue<String, String> first = KeyValue.of("b", "y");
KeyValue<String, String> second = KeyValue.of("a", "z");
ClusterReplyAggregator<KeyValue<String, String>> aggregator = new ClusterReplyAggregator<>(
CommandFlagsRegistry.ResponsePolicy.AGG_MIN);
aggregator.add(first);
aggregator.add(second);
KeyValue<String, String> result = aggregator.getResult();
assertEquals("a", result.getKey(), "Should return minimum key");
assertEquals("y", result.getValue(), "Should return minimum value");
}
}
// ==================== aggregateMax - KeyValue Tests ====================
@Nested
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class AggregateMaxTests {
@Test
public void testAggregateMax_keyValueLongLong_returnsMaxOfEachComponent() {
KeyValue<Long, Long> first = KeyValue.of(10L, 20L);
KeyValue<Long, Long> second = KeyValue.of(5L, 25L);
ClusterReplyAggregator<KeyValue<Long, Long>> aggregator = new ClusterReplyAggregator<>(
CommandFlagsRegistry.ResponsePolicy.AGG_MAX);
aggregator.add(first);
aggregator.add(second);
KeyValue<Long, Long> result = aggregator.getResult();
assertEquals(10L, result.getKey(), "Should return maximum key");
assertEquals(25L, result.getValue(), "Should return maximum value");
}
@Test
public void testAggregateMax_keyValueLongLong_firstLarger() {
KeyValue<Long, Long> first = KeyValue.of(10L, 20L);
KeyValue<Long, Long> second = KeyValue.of(1L, 2L);
ClusterReplyAggregator<KeyValue<Long, Long>> aggregator = new ClusterReplyAggregator<>(
CommandFlagsRegistry.ResponsePolicy.AGG_MAX);
aggregator.add(first);
aggregator.add(second);
KeyValue<Long, Long> result = aggregator.getResult();
assertEquals(10L, result.getKey(), "Should return maximum key from first");
assertEquals(20L, result.getValue(), "Should return maximum value from first");
}
@Test
public void testAggregateMax_keyValueLongLong_secondLarger() {
KeyValue<Long, Long> first = KeyValue.of(1L, 2L);
KeyValue<Long, Long> second = KeyValue.of(10L, 20L);
ClusterReplyAggregator<KeyValue<Long, Long>> aggregator = new ClusterReplyAggregator<>(
CommandFlagsRegistry.ResponsePolicy.AGG_MAX);
aggregator.add(first);
aggregator.add(second);
KeyValue<Long, Long> result = aggregator.getResult();
assertEquals(10L, result.getKey(), "Should return maximum key from second");
assertEquals(20L, result.getValue(), "Should return maximum value from second");
}
@Test
public void testAggregateMax_keyValueLongLong_equalValues() {
KeyValue<Long, Long> first = KeyValue.of(5L, 5L);
KeyValue<Long, Long> second = KeyValue.of(5L, 5L);
ClusterReplyAggregator<KeyValue<Long, Long>> aggregator = new ClusterReplyAggregator<>(
CommandFlagsRegistry.ResponsePolicy.AGG_MAX);
aggregator.add(first);
aggregator.add(second);
KeyValue<Long, Long> result = aggregator.getResult();
assertEquals(5L, result.getKey(), "Should return equal key");
assertEquals(5L, result.getValue(), "Should return equal value");
}
@Test
public void testAggregateMax_keyValueStringString_returnsMaxOfEachComponent() {
KeyValue<String, String> first = KeyValue.of("b", "y");
KeyValue<String, String> second = KeyValue.of("a", "z");
ClusterReplyAggregator<KeyValue<String, String>> aggregator = new ClusterReplyAggregator<>(
CommandFlagsRegistry.ResponsePolicy.AGG_MAX);
aggregator.add(first);
aggregator.add(second);
KeyValue<String, String> result = aggregator.getResult();
assertEquals("b", result.getKey(), "Should return maximum key");
assertEquals("z", result.getValue(), "Should return maximum value");
}
}
}