diff --git a/java/client/build.gradle b/java/client/build.gradle index e3010b3bbb..21675d2640 100644 --- a/java/client/build.gradle +++ b/java/client/build.gradle @@ -26,6 +26,7 @@ dependencies { testAnnotationProcessor 'org.projectlombok:lombok:1.18.30' // junit + testImplementation group: 'org.mockito', name: 'mockito-inline', version: '3.12.4' testImplementation('org.junit.jupiter:junit-jupiter:5.6.2') testImplementation group: 'org.mockito', name: 'mockito-junit-jupiter', version: '3.12.4' } diff --git a/java/client/src/main/java/glide/api/RedisClient.java b/java/client/src/main/java/glide/api/RedisClient.java index a8b0f65424..1ca7fda11d 100644 --- a/java/client/src/main/java/glide/api/RedisClient.java +++ b/java/client/src/main/java/glide/api/RedisClient.java @@ -10,10 +10,11 @@ import glide.managers.ConnectionManager; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; -import java.util.concurrent.atomic.AtomicBoolean; -/** Async (non-blocking) client for Redis in Standalone mode. Use {@link #CreateClient()} to - * request a client to Redis. */ +/** + * Async (non-blocking) client for Redis in Standalone mode. Use {@link #CreateClient()} to request + * a client to Redis. + */ public class RedisClient extends BaseClient { public static CompletableFuture CreateClient() { @@ -25,7 +26,6 @@ public static CompletableFuture CreateClient(String host, Integer p RedisClientConfiguration.builder() .address(NodeAddress.builder().host(host).port(port).build()) .build(); - return CreateClient(config); } @@ -40,11 +40,10 @@ public static CompletableFuture CreateClient(RedisClientConfigurati ChannelHandler channelHandler = new ChannelHandler(callbackDispatcher, getSocket()); var connectionManager = new ConnectionManager(channelHandler); var commandManager = new CommandManager(new CompletableFuture<>()); - // TODO: send request with configuration to connection Manager as part of a follow-up PR return CreateClient(config, connectionManager, commandManager); } - private static CompletableFuture CreateClient( + protected static CompletableFuture CreateClient( RedisClientConfiguration config, ConnectionManager connectionManager, CommandManager commandManager) { diff --git a/java/client/src/main/java/glide/api/models/configuration/BaseClientConfiguration.java b/java/client/src/main/java/glide/api/models/configuration/BaseClientConfiguration.java index 7835867f09..b131b0e4cc 100644 --- a/java/client/src/main/java/glide/api/models/configuration/BaseClientConfiguration.java +++ b/java/client/src/main/java/glide/api/models/configuration/BaseClientConfiguration.java @@ -7,7 +7,10 @@ import lombok.Singular; import lombok.experimental.SuperBuilder; -/** Configuration settings class for creating a Redis Client. Shared settings for both a standalone and cluster clients. */ +/** + * Configuration settings class for creating a Redis Client. Shared settings for both a standalone + * and cluster clients. + */ @Getter @SuperBuilder public abstract class BaseClientConfiguration { diff --git a/java/client/src/main/java/glide/connectors/handlers/CallbackDispatcher.java b/java/client/src/main/java/glide/connectors/handlers/CallbackDispatcher.java index de2cf39541..185f778805 100644 --- a/java/client/src/main/java/glide/connectors/handlers/CallbackDispatcher.java +++ b/java/client/src/main/java/glide/connectors/handlers/CallbackDispatcher.java @@ -13,8 +13,8 @@ public class CallbackDispatcher { private final AtomicInteger requestId = new AtomicInteger(0); /** - * Storage of Futures to handle responses. Map key is callback id, which starts from 0. - * Each future is a promise for every submitted by user request. + * Storage of Futures to handle responses. Map key is callback id, which starts from 0. Each + * future is a promise for every submitted by user request. */ private final Map> responses = new ConcurrentHashMap<>(); diff --git a/java/client/src/main/java/glide/connectors/handlers/ChannelHandler.java b/java/client/src/main/java/glide/connectors/handlers/ChannelHandler.java index 7607973da4..3b30cc05a3 100644 --- a/java/client/src/main/java/glide/connectors/handlers/ChannelHandler.java +++ b/java/client/src/main/java/glide/connectors/handlers/ChannelHandler.java @@ -12,8 +12,8 @@ import response.ResponseOuterClass.Response; /** - * Class responsible for handling calls to/from a netty.io {@link Channel}. - * Uses a {@link CallbackDispatcher} to record callbacks of every request sent. + * Class responsible for handling calls to/from a netty.io {@link Channel}. Uses a {@link + * CallbackDispatcher} to record callbacks of every request sent. */ public class ChannelHandler { diff --git a/java/client/src/main/java/glide/connectors/resources/Platform.java b/java/client/src/main/java/glide/connectors/resources/Platform.java index 2445ab1b88..f9e66cca69 100644 --- a/java/client/src/main/java/glide/connectors/resources/Platform.java +++ b/java/client/src/main/java/glide/connectors/resources/Platform.java @@ -45,8 +45,8 @@ private static class Capabilities { new Capabilities(isKQueueAvailable(), isEPollAvailable(), false, false); /** - * Thread pools supplied to Netty to perform all async IO. - * Map key is supposed to be pool name + thread count as a string concat product. + * Thread pools supplied to Netty to perform all async IO. Map key is supposed to be pool + * name + thread count as a string concat product. */ private static final Map groups = new ConcurrentHashMap<>(); @@ -123,8 +123,8 @@ public static Class getClientUdsNettyChannelType( /** * A JVM shutdown hook to be registered. It is responsible for closing connection and freeing - * resources. It is recommended to use a class instead of lambda to ensure that it is called. - * See {@link Runtime#addShutdownHook}. + * resources. It is recommended to use a class instead of lambda to ensure that it is called. See + * {@link Runtime#addShutdownHook}. */ private static class ShutdownHook implements Runnable { @Override diff --git a/java/client/src/main/java/glide/managers/CallbackManager.java b/java/client/src/main/java/glide/managers/CallbackManager.java index 54e3a19078..5ac6af263c 100644 --- a/java/client/src/main/java/glide/managers/CallbackManager.java +++ b/java/client/src/main/java/glide/managers/CallbackManager.java @@ -14,8 +14,8 @@ public class CallbackManager { private final AtomicInteger requestId = new AtomicInteger(0); /** - * Storage of Futures to handle responses. Map key is callback id, which starts from 0. - * Each future is a promise for every submitted by user request. + * Storage of Futures to handle responses. Map key is callback id, which starts from 0. Each + * future is a promise for every submitted by user request. */ private final Map> responses = new ConcurrentHashMap<>(); diff --git a/java/client/src/main/java/glide/managers/ConnectionManager.java b/java/client/src/main/java/glide/managers/ConnectionManager.java index 128a9d4519..2316ecda01 100644 --- a/java/client/src/main/java/glide/managers/ConnectionManager.java +++ b/java/client/src/main/java/glide/managers/ConnectionManager.java @@ -11,7 +11,6 @@ import glide.api.models.configuration.RedisClientConfiguration; import glide.api.models.configuration.RedisClusterClientConfiguration; import glide.connectors.handlers.ChannelHandler; -import glide.ffi.resolvers.RedisValueResolver; import java.util.concurrent.CompletableFuture; import lombok.RequiredArgsConstructor; import response.ResponseOuterClass.Response; diff --git a/java/client/src/test/java/glide/api/RedisClientCreateTest.java b/java/client/src/test/java/glide/api/RedisClientCreateTest.java new file mode 100644 index 0000000000..65e52716b6 --- /dev/null +++ b/java/client/src/test/java/glide/api/RedisClientCreateTest.java @@ -0,0 +1,153 @@ +package glide.api; + +import static glide.api.RedisClient.CreateClient; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.mockito.Mockito.withSettings; + +import glide.api.models.configuration.RedisClientConfiguration; +import glide.ffi.resolvers.SocketListenerResolver; +import glide.managers.CommandManager; +import glide.managers.ConnectionManager; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import lombok.SneakyThrows; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; +import org.mockito.Mockito; + +public class RedisClientCreateTest { + + @Test + @SneakyThrows + public void createClient_withConfig_successfullyReturnsRedisClient() { + try (MockedStatic mockedClient = Mockito.mockStatic(RedisClient.class); + MockedStatic mockedSocketListener = + Mockito.mockStatic(SocketListenerResolver.class)) { + + // setup + CompletableFuture testFuture = new CompletableFuture<>(); + RedisClientConfiguration config = RedisClientConfiguration.builder().build(); + + mockedSocketListener.when(SocketListenerResolver::getSocket).thenReturn("test_socket"); + mockedClient.when(() -> CreateClient(any(), any(), any())).thenReturn(testFuture); + + // method under test + mockedClient.when(() -> CreateClient(config)).thenCallRealMethod(); + + // exercise + CompletableFuture result = CreateClient(config); + + // verify + mockedClient.verify(() -> CreateClient(any(), any(), any())); + assertEquals(testFuture, result); + } + } + + @Test + @SneakyThrows + public void createClient_noArgs_successfullyReturnsRedisClient() { + try (MockedStatic mockedClient = + Mockito.mockStatic(RedisClient.class, withSettings().verboseLogging()); + MockedStatic mockedSocketListener = + Mockito.mockStatic(SocketListenerResolver.class, withSettings().verboseLogging())) { + + // setup + mockedSocketListener.when(SocketListenerResolver::getSocket).thenReturn("test_socket"); + CompletableFuture testFuture = new CompletableFuture<>(); + mockedClient.when(() -> CreateClient(any(), any(), any())).thenReturn(testFuture); + + // method under test + mockedClient.when(RedisClient::CreateClient).thenCallRealMethod(); + mockedClient.when(() -> CreateClient(any())).thenCallRealMethod(); + + // exercise + CompletableFuture result = CreateClient(); + + // verify + assertEquals(testFuture, result); + } + } + + @Test + @SneakyThrows + public void createClient_withHostPort_successfullyReturnsRedisClient() { + try (MockedStatic mockedClient = + Mockito.mockStatic(RedisClient.class, withSettings().verboseLogging()); + MockedStatic mockedSocketListener = + Mockito.mockStatic(SocketListenerResolver.class, withSettings().verboseLogging())) { + + // setup + String host = "testhost"; + int port = 999; + + mockedSocketListener.when(SocketListenerResolver::getSocket).thenReturn("test_socket"); + CompletableFuture testFuture = new CompletableFuture<>(); + mockedClient.when(() -> CreateClient(any())).thenReturn(testFuture); + + // method under test + mockedClient.when(RedisClient::CreateClient).thenCallRealMethod(); + mockedClient.when(() -> CreateClient(host, port)).thenCallRealMethod(); + + // exercise + CompletableFuture result = CreateClient(); + + // verify + assertEquals(testFuture, result); + } + } + + @SneakyThrows + @Test + public void createClient_successfulConnectionReturnsRedisClient() { + + // setup + ConnectionManager connectionManager = mock(ConnectionManager.class); + CommandManager commandManager = mock(CommandManager.class); + RedisClientConfiguration configuration = RedisClientConfiguration.builder().build(); + CompletableFuture connectionFuture = new CompletableFuture<>(); + connectionFuture.complete(null); + when(connectionManager.connectToRedis(eq(configuration))).thenReturn(connectionFuture); + + // exercise + CompletableFuture response = + RedisClient.CreateClient(configuration, connectionManager, commandManager); + RedisClient client = response.get(); + + // verify + assertEquals(connectionManager, client.connectionManager); + assertEquals(commandManager, client.commandManager); + + // teardown + } + + @SneakyThrows + @Test + public void createClient_errorOnConnectionThrowsExecutionException() { + + // setup + ConnectionManager connectionManager = mock(ConnectionManager.class); + CommandManager commandManager = mock(CommandManager.class); + RedisClientConfiguration configuration = RedisClientConfiguration.builder().build(); + CompletableFuture connectionFuture = new CompletableFuture<>(); + RuntimeException exception = new RuntimeException("disconnected"); + connectionFuture.completeExceptionally(exception); + when(connectionManager.connectToRedis(eq(configuration))).thenReturn(connectionFuture); + + // exercise + CompletableFuture response = + RedisClient.CreateClient(configuration, connectionManager, commandManager); + + ExecutionException executionException = + assertThrows(ExecutionException.class, () -> response.get()); + + // verify + assertEquals(exception, executionException.getCause()); + + // teardown + } +} diff --git a/java/client/src/test/java/glide/api/ConnectionManagerTest.java b/java/client/src/test/java/glide/managers/ConnectionManagerTest.java similarity index 99% rename from java/client/src/test/java/glide/api/ConnectionManagerTest.java rename to java/client/src/test/java/glide/managers/ConnectionManagerTest.java index 44b6d545a1..ee11eb1285 100644 --- a/java/client/src/test/java/glide/api/ConnectionManagerTest.java +++ b/java/client/src/test/java/glide/managers/ConnectionManagerTest.java @@ -1,4 +1,4 @@ -package glide.api; +package glide.managers; import static glide.api.models.configuration.NodeAddress.DEFAULT_HOST; import static glide.api.models.configuration.NodeAddress.DEFAULT_PORT; @@ -22,7 +22,6 @@ import glide.api.models.configuration.RedisClusterClientConfiguration; import glide.api.models.configuration.RedisCredentials; import glide.connectors.handlers.ChannelHandler; -import glide.managers.ConnectionManager; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import org.junit.jupiter.api.BeforeEach;