diff --git a/pom.xml b/pom.xml index b4c0aac..e3a2a46 100644 --- a/pom.xml +++ b/pom.xml @@ -20,12 +20,41 @@ jedis 5.1.5 + + org.mockito + mockito-core + 5.18.0 + test + + + org.projectlombok + lombok + 1.18.38 + + + org.slf4j + slf4j-api + 2.0.17 + + + org.slf4j + slf4j-reload4j + 2.0.17 + compile + org.junit.jupiter junit-jupiter 5.8.1 test + + com.redis + testcontainers-redis + 2.2.2 + test + + \ No newline at end of file diff --git a/src/main/java/org/redis/Main.java b/src/main/java/org/redis/Main.java index 68b3db6..46f4ad5 100644 --- a/src/main/java/org/redis/Main.java +++ b/src/main/java/org/redis/Main.java @@ -2,6 +2,9 @@ public class Main { public static void main(String[] args) { - System.out.println("Hello world!"); + + } + + } \ No newline at end of file diff --git a/src/main/java/org/redis/RedisMap.java b/src/main/java/org/redis/RedisMap.java new file mode 100644 index 0000000..9b82a16 --- /dev/null +++ b/src/main/java/org/redis/RedisMap.java @@ -0,0 +1,370 @@ +package org.redis; + +import lombok.NonNull; +import redis.clients.jedis.Jedis; +import redis.clients.jedis.JedisPool; +import redis.clients.jedis.exceptions.JedisConnectionException; + +import java.util.*; + + +/** + * Map implementation for Redis database. + * This implementation not provides optional matching operations and does not allow the use of null values or null keys. + * This class does not provide any guarantees that the data will be up-to-date + * when the Redis database is changed from multiple sources or multiple RedisMap instances. + * + * @Author Alexei Shvariov + */ +public class RedisMap implements Map, AutoCloseable { + private final JedisPool jedisPool; + private final Jedis jedis; + + /** + * Constructs an RedisMap with the specified connection parameters. + * @param host - Redis database connection host. + * @param port - Redis database connection port. + * @throws NullPointerException - if host is null. + * @throws IllegalArgumentException – if host empty or port equals 0; + * */ + public RedisMap(String host, int port) { + if (host == null) { + throw new NullPointerException("Host cannot be null"); + } + if (host.isEmpty()) { + throw new IllegalArgumentException("Host is empty. Please set redis database connection host."); + } + if (port == 0) { + throw new IllegalArgumentException("The port is not specified. Please set redis database connection port."); + } + try { + this.jedisPool = new JedisPool(host, port); + this.jedis = jedisPool.getResource(); + } catch (JedisConnectionException ex) { + throw new IllegalArgumentException("Invalid host or port value. Connection error: " + ex.getMessage()); + } + } + + /** + * Constructs an RedisMap with the specified connection parameters. + * @param jedisPool - pool of connections for Redis database. + * @throws NullPointerException - if host is null. + */ + public RedisMap(JedisPool jedisPool) { + if (jedisPool == null) { + throw new NullPointerException("Jedis pool cannot be null"); + } + try { + this.jedisPool = jedisPool; + this.jedis = jedisPool.getResource(); + } catch (JedisConnectionException ex) { + throw new IllegalArgumentException("Invalid host or port value. Connection error: " + ex.getMessage()); + } + } + + /** + * Returns the number of key mappings in redis database. + * If the map contains more than Integer.MAX_VALUE elements, returns Integer.MAX_VALUE. + * For getting size in long format use {@link RedisMap#getSize()} + * @return the number of key mappings in redis database + */ + @Override + public int size() { + final long size = getSize(); + if (size > Integer.MAX_VALUE) { + return Integer.MAX_VALUE; + } + return (int) size; + } + + /** + * Returns the number of key mappings in redis database. + * @return the number of key mappings in redis database + */ + public long getSize() { + return jedis.dbSize(); + } + + /** + * Returns true if this map contains no key-value mappings. + * @return true if this map contains no key-value mappings + * */ + @Override + public boolean isEmpty() { + return getSize() == 0L; + } + + /** + * Returns true if this map contains a mapping for the specified key. + * More formally, returns true if and only if this map contains a mapping for a key k such that Objects.equals(key, k). + * (There can be at most one such mapping.) + * @param key – key whose presence in this map is to be tested + * @return true if this map contains a mapping for the specified key + * @throws ClassCastException – if the key's type not String. + * @throws NullPointerException – if the specified key is null. + */ + @Override + public boolean containsKey(Object key) { + if (key == null) { + throw new NullPointerException("Key cannot be null"); + } + if (!(key instanceof String)) { + throw new ClassCastException("Key type must be String"); + } + return jedis.exists((String) key); + } + + /** + * Returns true if Redis maps one or more keys to the specified value. + * More formally, returns true if and only if Redis contains at least one mapping to a value v such that Objects.equals(value, v). + * @param value – value whose presence in this RedisMap is to be tested + * @return true if this RedisMap maps one or more keys to the specified value + * @throws ClassCastException – if the value is a not String type. + * @throws NullPointerException – if the specified value is null. + * */ + @Override + public boolean containsValue(Object value) { + if (value == null) { + throw new NullPointerException("Key cannot be null"); + } + if (!(value instanceof String)) { + throw new ClassCastException("Key type must be String"); + } + for (String key : keySet()) { + String keyValue = jedis.get(key); + if (keyValue.equals(value)) { + return true; + } + } + return false; + } + + /** + * Returns the value to which the specified key is mapped, or null if this map contains no mapping for the key. + * @param key – the key whose associated value is to be returned + * @return the value to which the specified key is mapped, or null if this map contains no mapping for the key + * @throws ClassCastException – if the key's type is not a string. + * @throws NullPointerException – if the specified key is null. + */ + @Override + public String get(Object key) { + if (key == null) { + throw new NullPointerException("Key cannot be null"); + } + if (!(key instanceof String)) { + throw new ClassCastException("Key type must be String"); + } + return jedis.get((String) key); + } + + /** + * Associates the specified value with the specified key in this map + * If the map previously contained a mapping for the key, the old value is replaced by the specified value. + * (A map m is said to contain a mapping for a key k if and only if m.containsKey(k) would return true.) + * @param key – key with which the specified value is to be associated + * @param value – value to be associated with the specified key + * @return the previous value associated with key, or null if there was no mapping for key. + * @throws NullPointerException – if the specified key or value is null and this map does not permit null keys or values + * */ + @Override + public String put(String key, String value) { + if (key == null || value == null) { + throw new NullPointerException("Key or value cannot be null"); + } + String previousValue = get(key); + jedis.set(key, value); + return previousValue; + } + + /** + * Removes the mapping for a key from this map if it is present. + *

Returns the value to which this map previously associated the key, + * or {@code null} if the map contained no mapping for the key. + * + *

The map will not contain a mapping for the specified key once the + * call returns. + * + * @param key - key whose mapping is to be removed from the map + * @return the previous value associated with {@code key}, or + * {@code null} if there was no mapping for {@code key}. + * @throws ClassCastException if the key is of an inappropriate type for + * this map ({@linkplain Collection##optional-restrictions optional}) + * @throws NullPointerException if the specified key is null and this + * map does not permit null keys ({@linkplain Collection##optional-restrictions optional}) + */ + @Override + public String remove(Object key) { + if (key == null) { + throw new NullPointerException("Key value cannot be null"); + } + if (!(key instanceof String)) { + throw new ClassCastException("Key value must be string"); + } + String deletedValue = get(key); + jedis.del((String) key); + return deletedValue; + } + + /** + * Copies all the mappings from the specified map to redis. + * The effect of this call is equivalent to that of calling put(k, v) on this map once for each mapping from key k to value v in redis. + * The behavior of this operation is undefined if the specified map is modified while the operation is in progress. + * If the specified map has a defined encounter order, processing of its mappings generally occurs in that order. + * @param m – mappings to be stored in this map. + * @throws NullPointerException – if the specified map is null, or if this map does not permit null keys or values, + * and the specified map contains null keys or values. + * */ + @Override + public void putAll(@NonNull Map m) { + if (m.containsKey(null) || m.containsValue(null)) { + throw new NullPointerException("Key value cannot be null"); + } + for (String k : m.keySet()) { + jedis.set(k, m.get(k)); + } + } + + /** + * Removes all the mappings from this map. + * The map will be empty after this call returns. + */ + @Override + public void clear() { + jedis.flushDB(); + } + + /** + * KeySet - is a snapshot of a collection of Redis keys. + * Changes to the RedisMap after calling KeySet() are not reflected in the set. + * Operations on a KeySet do not change the RedisMap. + * @return a set view of the keys contained in Redis. + */ + @Override + @NonNull + public Set keySet() { + return jedis.keys("*"); + } + + /** + * Values - is a collection of the Redis mappings values. + * Changes to the RedisMap after calling values() are not reflected in the collection. + * Operations on values do not modify the values on Redis. + * @return - collection of the Redis mappings values. + * */ + @Override + @NonNull + public Collection values() { + List values = new ArrayList<>(); + for (String key : keySet()) { + values.add(get(key)); + } + return values; + } + + /** + * Returns a Set view of the mappings contained in Redis. + * The set is not fully supported by the RedisMap, but when the {@link Entry} method {@link Entry#setValue(String)} is called, + * the value for the entry key in the Entry and in the RedisMap is set. + * If set elements no changes to the Set, but changes to the map, + * when invokes {@link Entry} methods: {@link Entry#getKey()}, {@link Entry#getValue()}, {@link Entry#setValue(String)} + * then throws {@link IllegalStateException}. + * If the map is modified while an iteration over the set is in progress the results of the iteration are undefined. + * It does not support the add or addAll operations. + * @return a set view of the mappings contained in Redis + * */ + @Override + @NonNull + public Set> entrySet() { + Set> entrySet = new HashSet<>(); + for (String key : keySet()) { + Map.Entry entry = new Entry(key, RedisMap.this.get(key)); + entrySet.add(entry); + } + return entrySet; + } + + /** + * A RedisMap entry (key-value pair). + *

+ * Entry is not fully bound to RedisMap. When {@link Entry#setValue(String)} method is called, + * the value for the entry key is set in Entry and in RedisMap. + * If the RedisMap is modified and the key or value of the Entry is not valid for Redis, + * methods {@link Entry#getKey()}, {@link Entry#getValue()}, {@link Entry#setValue(String)} throw {@link IllegalStateException}. + * It is also undefined if the backing map has been modified after the Entry was + * returned by the iterator, except through the {@link Entry#setValue(String)} method. + *

+ * An Entry may also be obtained from a RedisMap's entry-set view by other means, for + * example, using the + * {@link Set#parallelStream parallelStream}, + * {@link Set#stream stream}, + * {@link Set#spliterator spliterator} methods, + * any of the + * {@link Set#toArray toArray} overloads, + * or by copying the entry-set view into another collection. + * + * @see RedisMap#entrySet() + */ + final class Entry implements Map.Entry { + private final String key; + private String value; + + public Entry(String key, String value) { + this.key = key; + this.value = value; + } + + /** + * Returns the key corresponding to this entry. + * @return the key corresponding to this entry + * @throws IllegalStateException if the entry has been removed from the RedisMap. + * */ + @Override + public String getKey() { + if (!RedisMap.this.containsKey(key)) { + throw new IllegalStateException("Entry key not found in Redis"); + } + return key; + } + + /** + * Returns the value corresponding to this entry. + * If the mapping has been deleted from Redis, an IllegalStateException will be thrown. + * If the mapping value has been change in Redis, an IllegalStateException will be thrown. + * @return string value corresponding to this entry. + * @throws IllegalStateException if entry value not equals value in Redis for entry's key. + * */ + @Override + public String getValue() { + String redisValue = RedisMap.this.get(key); + if (redisValue == null || !redisValue.equals(value)) { + throw new IllegalStateException("Entry value not actual value in Redis for key = " + key); + } + return value; + } + + /** + * Replaces the value corresponding to this entry and the mapping's value Redis with the specified value. + * If the mapping has already been removed from Redis, the entry's key will be added with the new value and return null. + * @param value – new value to be stored in this entry and RedisMap. + * @return old value corresponding to the entry + * @throws NullPointerException – if new value is null. + * @throws IllegalStateException if entry value not equals value in Redis for entry's key. + */ + @Override + public String setValue(String value) { + if (value == null) { + throw new NullPointerException("Entry value cannot be null"); + } + String previousValue = getValue(); + RedisMap.this.put(key, value); + this.value = value; + return previousValue; + } + } + + @Override + public void close() { + jedis.close(); + jedisPool.close(); + } +} diff --git a/src/main/resources/log4j.properties b/src/main/resources/log4j.properties new file mode 100644 index 0000000..3b71844 --- /dev/null +++ b/src/main/resources/log4j.properties @@ -0,0 +1,4 @@ +log4j.rootLogger=INFO, stdout +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1} - %m%n diff --git a/src/test/java/RedisMapTest.java b/src/test/java/RedisMapTest.java new file mode 100644 index 0000000..e211155 --- /dev/null +++ b/src/test/java/RedisMapTest.java @@ -0,0 +1,546 @@ +import com.redis.testcontainers.RedisContainer; +import org.junit.jupiter.api.*; +import org.mockito.Mockito; +import org.redis.RedisMap; +import org.testcontainers.utility.DockerImageName; +import redis.clients.jedis.Jedis; +import redis.clients.jedis.JedisPool; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import static org.mockito.Mockito.*; + +/** + * Tests for RedisMap + * @author Alexei Shvariov + */ +public class RedisMapTest { + private static final RedisContainer REDIS = new RedisContainer(DockerImageName.parse("redis:6.2.6")); + private static JedisPool jedisPool; + + private final JedisPool mockJedisPool = Mockito.mock(JedisPool.class); + + @BeforeAll + public static void setUp() { + REDIS.start(); + if (REDIS.isRunning()) { + jedisPool = new JedisPool(REDIS.getHost(), REDIS.getFirstMappedPort()); + } + + + } + + @BeforeEach + public void fillDatabase() { + Jedis jedis = jedisPool.getResource(); + jedis.set("1", "one"); + jedis.set("2", "two"); + jedis.set("3", "three"); + jedis.close(); + } + + @AfterEach + public void clearDatabase() { + Jedis jedis = jedisPool.getResource(); + jedis.flushDB(); + jedis.close(); + } + + + @AfterAll + public static void closeResources() { + jedisPool.close(); + REDIS.stop(); + } + + + + @Test + @DisplayName("Test constructor with port and host params when set valid connection params then return new RedisMap") + public void testConstructor_whenSetValidConnectionParam_thenReturnNewRedisMap() { + try (RedisMap redisMap = new RedisMap(REDIS.getHost(), REDIS.getFirstMappedPort())) { + Assertions.assertNotNull(redisMap); + } + } + + @Test + @DisplayName("Test constructor with port and host params when host is null then throw nullPointerException") + public void testConstructor_whenHostIsNull_thenThrowNullPointerException() { + Assertions.assertThrows(NullPointerException.class, () -> { + try (RedisMap redisMap = new RedisMap(null, REDIS.getFirstMappedPort())) { + Assertions.assertNull(redisMap); + } + } ); + } + + @Test + @DisplayName("Test constructor with port and host params when port = 0 then throw nullPointerException") + public void testConstructor_whenPortEquals0_thenThrowIllegalArgumentException() { + Assertions.assertThrows(IllegalArgumentException.class, () -> { + try (RedisMap redisMap = new RedisMap(REDIS.getHost(), 0)) { + Assertions.assertNull(redisMap); + } + } ); + } + + @Test + @DisplayName("Test constructor with port and host params when set not valid host then throw nullPointerException") + public void testConstructor_whenSetNotValidHost_thenThrowIllegalArgumentException() { + Assertions.assertThrows(IllegalArgumentException.class, () -> { + try (RedisMap redisMap = new RedisMap("notValidHost", REDIS.getFirstMappedPort())) { + Assertions.assertNull(redisMap); + } + } ); + } + + @Test + @DisplayName("Test constructor with port and host params when set wrong port then throw nullPointerException") + public void testConstructor_whenSetWrongPort_thenThrowIllegalArgumentException() { + Assertions.assertThrows(IllegalArgumentException.class, () -> { + try (RedisMap redisMap = new RedisMap(REDIS.getHost(), REDIS.getFirstMappedPort() + 9999)) { + Assertions.assertNull(redisMap); + } + } ); + } + + @Test + @DisplayName("Test size when method called then return valid value") + public void testSize_whenMethodCalled_thenReturnValidValue() { + final int expectedSize = 3; + try (RedisMap redisMap = new RedisMap(REDIS.getHost(), REDIS.getFirstMappedPort())) { + Assertions.assertEquals(expectedSize, redisMap.size()); + } + } + + @Test + @DisplayName("Test getSize when method called then return valid value") + public void testGetSize_whenMethodCalled_thenReturnValidValue() { + final int expectedSize = 3; + try (RedisMap redisMap = new RedisMap(REDIS.getHost(), REDIS.getFirstMappedPort())) { + Assertions.assertEquals(expectedSize, redisMap.getSize()); + } + } + + @Test + @DisplayName("Test isEmpty when Redis is not empty then return false") + public void testIsEmpty_whenRedisNotEmpty_thenReturnFalse() { + try (RedisMap redisMap = new RedisMap(REDIS.getHost(), REDIS.getFirstMappedPort())) { + Assertions.assertFalse(redisMap.isEmpty()); + } + } + + @Test + @DisplayName("Test isEmpty when Redis is empty then return true") + public void testIsEmpty_whenRedisIsEmpty_thenReturnTrue() { + Jedis jedis = jedisPool.getResource(); + jedis.flushDB(); + jedis.close(); + try (RedisMap redisMap = new RedisMap(REDIS.getHost(), REDIS.getFirstMappedPort())) { + Assertions.assertTrue(redisMap.isEmpty()); + } + } + + @Test + @DisplayName("Test containsKey when key is contains in Redis then return true") + public void testContainsKey_whenKeyIsContainsInRedis_thenReturnTrue() { + String keyFromRedis = "2"; + try (RedisMap redisMap = new RedisMap(REDIS.getHost(), REDIS.getFirstMappedPort())) { + Assertions.assertTrue(redisMap.containsKey(keyFromRedis)); + } + } + + @Test + @DisplayName("Test containsKey when key is not contains in Redis then return false") + public void testContainsKey_whenKeyIsNotContainsInRedis_thenReturnFalse() { + try (RedisMap redisMap = new RedisMap(REDIS.getHost(), REDIS.getFirstMappedPort())) { + Assertions.assertFalse(redisMap.containsKey("unknown key")); + } + } + + @Test + @DisplayName("Test containsKey when key is not string type then throw ClassCastException") + public void testContainsKey_whenKeyIsNotStringType_thenThrowClassCastException() { + Assertions.assertThrows(ClassCastException.class, () -> { + try (RedisMap redisMap = new RedisMap(REDIS.getHost(), REDIS.getFirstMappedPort())) { + redisMap.containsKey(55L); + } + } ); + } + + @Test + @DisplayName("Test containsKey when key is null then throw NullPointerException") + public void testContainsKey_whenKeyIsNull_thenThrowNullPointerException() { + Assertions.assertThrows(NullPointerException.class, () -> { + try (RedisMap redisMap = new RedisMap(REDIS.getHost(), REDIS.getFirstMappedPort())) { + redisMap.containsKey(null); + } + } ); + } + + @Test + @DisplayName("Test containsValue when value contains in Redis then return true") + public void testContainsValue_whenValueContainsInRedis_thenReturnTrue() { + String valueFromRedis = "two"; + try (RedisMap redisMap = new RedisMap(REDIS.getHost(), REDIS.getFirstMappedPort())) { + Assertions.assertTrue(redisMap.containsValue(valueFromRedis)); + } + } + + @Test + @DisplayName("Test containsValue when value not contains in Redis then return false") + public void testContainsValue_whenValueNotContainsInRedis_thenReturnFalse() { + try (RedisMap redisMap = new RedisMap(REDIS.getHost(), REDIS.getFirstMappedPort())) { + Assertions.assertFalse(redisMap.containsValue("unknown value")); + } + } + + @Test + @DisplayName("Test containsValue when value is not string type in Redis then throw ClassCastException") + public void testContainsValue_whenValueIsNotStringType_thenThrowClassCastException() { + Assertions.assertThrows(ClassCastException.class, () -> { + try (RedisMap redisMap = new RedisMap(REDIS.getHost(), REDIS.getFirstMappedPort())) { + redisMap.containsValue(55L); + } + } ); + } + + @Test + @DisplayName("Test containsValue when value is null then throw NullPointerException") + public void testContainsValue_whenValueIsNull_thenThrowNullPointerException() { + Assertions.assertThrows(NullPointerException.class, () -> { + try (RedisMap redisMap = new RedisMap(REDIS.getHost(), REDIS.getFirstMappedPort())) { + redisMap.containsValue(null); + } + } ); + } + + @Test + @DisplayName("Test get when key is valid then return valid value") + public void testGet_whenKeyIsValid_thenReturnValidValue() { + String expectedValue = "three"; + try (RedisMap redisMap = new RedisMap(REDIS.getHost(), REDIS.getFirstMappedPort())) { + Assertions.assertEquals(expectedValue, redisMap.get("3")); + } + } + + @Test + @DisplayName("Test get when key is not valid then return null") + public void testGet_whenKeyIsNotValid_thenReturnNull() { + try (RedisMap redisMap = new RedisMap(REDIS.getHost(), REDIS.getFirstMappedPort())) { + Assertions.assertNull(redisMap.get("unknown key")); + } + } + + @Test + @DisplayName("Test get when key is null then throw NullPointerException") + public void testGet_whenKeyIsNull_thenThrowNullPointerException() { + Assertions.assertThrows(NullPointerException.class, () -> { + try (RedisMap redisMap = new RedisMap(REDIS.getHost(), REDIS.getFirstMappedPort())) { + redisMap.get(null); + } + } ); + } + + @Test + @DisplayName("Test get when key is not string then throw ClassCastException") + public void testGet_whenKeyIsNotString_thenThrowClassCastException() { + Assertions.assertThrows(ClassCastException.class, () -> { + try (RedisMap redisMap = new RedisMap(REDIS.getHost(), REDIS.getFirstMappedPort())) { + redisMap.get(777L); + } + } ); + } + + @Test + @DisplayName("Test put when put new key and valid value then return null") + public void testPut_whenPutNewKeyAndValidValue_thenReturnNull() { + final int expectedSize = 4; + try (RedisMap redisMap = new RedisMap(REDIS.getHost(), REDIS.getFirstMappedPort())) { + Assertions.assertNull(redisMap.put("5", "five")); + Assertions.assertEquals(expectedSize, redisMap.getSize()); + } + } + + @Test + @DisplayName("Test put when put available key and new value then return previous value") + public void testPut_whenPutAvailableKeyAndNewValue_thenReturnPreviousValue() { + final int expectedSize = 3; + final String previousValue = "two"; + try (RedisMap redisMap = new RedisMap(REDIS.getHost(), REDIS.getFirstMappedPort())) { + Assertions.assertEquals(previousValue,redisMap.put("2", "два")); + Assertions.assertEquals(expectedSize, redisMap.getSize()); + } + } + + @Test + @DisplayName("Test put when put when key is null then throw NullPointerException") + public void testPut_whenKeyIsNull_thenThrowNullPointerException() { + Assertions.assertThrows(NullPointerException.class, () -> { + try (RedisMap redisMap = new RedisMap(REDIS.getHost(), REDIS.getFirstMappedPort())) { + redisMap.put(null, "value"); + } + } ); + } + + @Test + @DisplayName("Test put when put when value is null then throw NullPointerException") + public void testPut_whenValueIsNull_thenThrowNullPointerException() { + Assertions.assertThrows(NullPointerException.class, () -> { + try (RedisMap redisMap = new RedisMap(REDIS.getHost(), REDIS.getFirstMappedPort())) { + redisMap.put("key", null); + } + } ); + } + + @Test + @DisplayName("Test remove when Redis contains key then mapping success delete") + public void testRemove_whenRedisContainsKey_thenMappingSuccessDelete() { + final int expectedSize = 2; + final String expectedPreviousValue = "two"; + try (RedisMap redisMap = new RedisMap(REDIS.getHost(), REDIS.getFirstMappedPort())) { + Assertions.assertEquals(expectedPreviousValue,redisMap.remove("2")); + Assertions.assertEquals(expectedSize, redisMap.getSize()); + } + } + + @Test + @DisplayName("Test remove when key missing in Redis then nothing will happen") + public void testRemove_whenKeyMissingInRedis_thenNothingWillHappen() { + final int expectedSize = 3; + try (RedisMap redisMap = new RedisMap(REDIS.getHost(), REDIS.getFirstMappedPort())) { + Assertions.assertNull(redisMap.remove("unknown key")); + Assertions.assertEquals(expectedSize, redisMap.getSize()); + } + } + + @Test + @DisplayName("Test remove when key is null then throw NullPointerException") + public void testRemove_whenKeyIsNull_thenThrowNullPointerException() { + Assertions.assertThrows(NullPointerException.class, () -> { + try (RedisMap redisMap = new RedisMap(REDIS.getHost(), REDIS.getFirstMappedPort())) { + redisMap.remove(null); + } + } ); + } + + @Test + @DisplayName("Test remove when key is not string then throw ClassCastException") + public void testRemove_whenKeyIsNotString_thenThrowClassCastException() { + Assertions.assertThrows(ClassCastException.class, () -> { + try (RedisMap redisMap = new RedisMap(REDIS.getHost(), REDIS.getFirstMappedPort())) { + redisMap.remove(23L); + } + } ); + } + + @Test + @DisplayName("Test putAll when collection being added is valid then collection is successfully added") + public void testPutAll_whenCollectionBeingAddedIsValid_thenCollectionIsSuccessfullyAdded() { + Map numbers = new HashMap<>(); + numbers.put("4", "four"); + numbers.put("5", "five"); + numbers.put("6", "six"); + final int expectedSize = 6; + try (RedisMap redisMap = new RedisMap(REDIS.getHost(), REDIS.getFirstMappedPort())) { + redisMap.putAll(numbers); + Assertions.assertEquals(expectedSize, redisMap.getSize()); + } + } + + @Test + @DisplayName("Test putAll when collection is null then throw NullPointerException") + public void testPutAll_whenCollectionIsNull_thenThrowNullPointerException() { + Assertions.assertThrows(NullPointerException.class, () -> { + try (RedisMap redisMap = new RedisMap(REDIS.getHost(), REDIS.getFirstMappedPort())) { + redisMap.putAll(null); + } + } ); + } + + @Test + @DisplayName("Test putAll when collection's key is null then throw NullPointerException") + public void testPutAll_whenCollectionKeyIsNull_thenThrowNullPointerException() { + Map numbers = new HashMap<>(); + numbers.put("4", "four"); + numbers.put(null, "five"); + numbers.put("6", "six"); + Assertions.assertThrows(NullPointerException.class, () -> { + try (RedisMap redisMap = new RedisMap(REDIS.getHost(), REDIS.getFirstMappedPort())) { + redisMap.putAll(numbers); + } + } ); + } + + @Test + @DisplayName("Test putAll when collection's value is null then throw NullPointerException") + public void testPutAll_whenCollectionValueIsNull_thenThrowNullPointerException() { + Map numbers = new HashMap<>(); + numbers.put("4", "four"); + numbers.put("5", null); + numbers.put("6", "six"); + Assertions.assertThrows(NullPointerException.class, () -> { + try (RedisMap redisMap = new RedisMap(REDIS.getHost(), REDIS.getFirstMappedPort())) { + redisMap.putAll(numbers); + } + } ); + } + + @Test + @DisplayName("Test clear when invoke method then Redis successfully cleared") + public void testClear_whenInvokeMethod_thenRedisSuccessfullyCleared() { + try (RedisMap redisMap = new RedisMap(REDIS.getHost(), REDIS.getFirstMappedPort())) { + Assertions.assertTrue(redisMap.getSize() > 0); + redisMap.clear(); + Assertions.assertEquals(0, redisMap.getSize()); + } + } + + @Test + @DisplayName("Test keySet when invoke method then return set of Redis keys") + public void testKeySet_whenInvokeMethod_thenReturnSetOfRedisKeys() { + final int expectedSize = 3; + final String keyFromRedis = "2"; + try (RedisMap redisMap = new RedisMap(REDIS.getHost(), REDIS.getFirstMappedPort())) { + Set keySet = redisMap.keySet(); + Assertions.assertEquals(expectedSize, keySet.size()); + Assertions.assertTrue(keySet.contains(keyFromRedis)); + } + } + + @Test + @DisplayName("Test values when invoke method then return collection of Redis values") + public void testValues_whenInvokeMethod_thenReturnCollectionOfValues() { + String valueFromRedis = "two"; + try (RedisMap redisMap = new RedisMap(REDIS.getHost(), REDIS.getFirstMappedPort())) { + Collection values = redisMap.values(); + Assertions.assertFalse(values.isEmpty()); + Assertions.assertTrue(values.contains(valueFromRedis)); + } + } + + @Test + @DisplayName("Test entrySet when invoke method then return set of entry") + public void testEntrySet_whenInvokeMethod_thenReturnSetOfEntry() { + final int expectedSize = 3; + try (RedisMap redisMap = new RedisMap(REDIS.getHost(), REDIS.getFirstMappedPort())) { + Set> entries = redisMap.entrySet(); + Assertions.assertFalse(entries.isEmpty()); + Assertions.assertEquals(expectedSize, entries.size()); + } + } + + @Test + @DisplayName("Test entry's method getKey when invoke method then return entry's key") + public void testEntryGetKey_whenRedisContainKey_thenReturnEntryKet() { + try (RedisMap redisMap = new RedisMap(REDIS.getHost(), REDIS.getFirstMappedPort())) { + String key = redisMap.entrySet().stream().findAny().orElseThrow().getKey(); + Assertions.assertNotNull(key); + Assertions.assertTrue(redisMap.containsKey(key)); + } + } + + @Test + @DisplayName("Test entry's method getKey when key not found in Redis then throw IllegalStateException") + public void testEntryGetKey_whenKeyNotFoundInRedis_thenThrowIllegalStateException() { + Assertions.assertThrows(IllegalStateException.class, () -> { + try (RedisMap redisMap = new RedisMap(REDIS.getHost(), REDIS.getFirstMappedPort())) { + redisMap.remove("1"); + redisMap.remove("2"); + Map.Entry entry = redisMap.entrySet().stream().findAny().orElseThrow(); + redisMap.remove("3"); + entry.getKey(); + } + }); + } + + @Test + @DisplayName("Test entry's method getValue when Redis contain value then return entry's value") + public void testEntryGetValue_whenRedisContainValue_thenReturnEntryValue() { + try (RedisMap redisMap = new RedisMap(REDIS.getHost(), REDIS.getFirstMappedPort())) { + String value = redisMap.entrySet().stream().findAny().orElseThrow().getValue(); + Assertions.assertNotNull(value); + Assertions.assertTrue(redisMap.containsValue(value)); + } + } + + @Test + @DisplayName("Test entry's method getValue when value not found in redis then throw IllegalStateException") + public void testEntryGetValue_whenValueNotFoundInRedis_thenThrowIllegalStateException() { + Assertions.assertThrows(IllegalStateException.class, () -> { + try (RedisMap redisMap = new RedisMap(REDIS.getHost(), REDIS.getFirstMappedPort())) { + redisMap.remove("1"); + redisMap.remove("2"); + Map.Entry entry = redisMap.entrySet().stream().findAny().orElseThrow(); + redisMap.remove("3"); + entry.getValue(); + } + }); + } + + + @Test + @DisplayName("Test entry's method getValue when value has been changed in Redis then throw IllegalStateException") + public void testEntryGetValue_whenValueHasBeenChangedInRedis_thenThrowIllegalStateException() { + Assertions.assertThrows(IllegalStateException.class, () -> { + try (RedisMap redisMap = new RedisMap(REDIS.getHost(), REDIS.getFirstMappedPort())) { + Map.Entry entry = redisMap.entrySet().stream().findAny().orElseThrow(); + redisMap.put("1", "один"); + redisMap.put("2", "два"); + redisMap.put("3", "три"); + entry.getValue(); + } + }); + } + + @Test + @DisplayName("Test entry's method setValue when Redis contain entry then return previous value") + public void testEntrySetValue_whenRedisContainEntry_thenReturnEntryValue() { + final String newValue = "newValue"; + try (RedisMap redisMap = new RedisMap(REDIS.getHost(), REDIS.getFirstMappedPort())) { + Map.Entry entry = redisMap.entrySet().stream().findAny().orElseThrow(); + Assertions.assertFalse(redisMap.containsValue(newValue)); + Assertions.assertNotNull(entry.setValue(newValue)); + Assertions.assertTrue(redisMap.containsValue(newValue)); + } + } + + @Test + @DisplayName("Test entry's method setValue when value is null then throw NullPointerException") + public void testEntrySetValue_whenValueIsNull_thenThrowNullPointerException() { + Assertions.assertThrows(NullPointerException.class, () -> { + try (RedisMap redisMap = new RedisMap(REDIS.getHost(), REDIS.getFirstMappedPort())) { + Map.Entry entry = redisMap.entrySet().stream().findAny().orElseThrow(); + entry.setValue(null); + } + }); + } + + @Test + @DisplayName("Test entry's method setValue when value has been changed in Redis then throw IllegalStateException") + public void testEntrySetValue_whenValueHasBeenChangedInRedis_thenThrowIllegalStateException() { + Assertions.assertThrows(IllegalStateException.class, () -> { + try (RedisMap redisMap = new RedisMap(REDIS.getHost(), REDIS.getFirstMappedPort())) { + Map.Entry entry = redisMap.entrySet().stream().findAny().orElseThrow(); + redisMap.put("1", "один"); + redisMap.put("2", "два"); + redisMap.put("3", "три"); + entry.setValue("New value"); + } + }); + } + + @Test + @DisplayName("Test close when invoke method then JedisPool is closed") + public void testClose_whenInvokeMethod_thenJedisPoolClosed() { + when(mockJedisPool.getResource()) + .thenReturn(jedisPool.getResource()); + try(RedisMap redisMap = new RedisMap(mockJedisPool)) { + redisMap.getSize(); + } + verify(mockJedisPool,times(1)).close(); + } + +}