From cac4a59a9e1332196842d81ff00b3e52b99c628a Mon Sep 17 00:00:00 2001
From: Matthew Swift <matthew.swift@forgerock.com>
Date: Wed, 11 Dec 2013 10:53:56 +0000
Subject: [PATCH] Unit tests for OPENDJ-1247: Client side timeouts do not cancel bind or startTLS requests properly

---
 opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldap/LDAPConnectionFactoryTestCase.java |  182 ++++++++++++++++++++++++++++++++++++++++++++-
 1 files changed, 176 insertions(+), 6 deletions(-)

diff --git a/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldap/LDAPConnectionFactoryTestCase.java b/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldap/LDAPConnectionFactoryTestCase.java
index 01c2914..26d8d6d 100644
--- a/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldap/LDAPConnectionFactoryTestCase.java
+++ b/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldap/LDAPConnectionFactoryTestCase.java
@@ -25,26 +25,186 @@
  */
 package org.forgerock.opendj.ldap;
 
+import static com.forgerock.opendj.util.StaticUtils.closeSilently;
 import static org.fest.assertions.Assertions.assertThat;
 import static org.forgerock.opendj.ldap.TestCaseUtils.findFreeSocketAddress;
-import static org.mockito.Mockito.mock;
+import static org.forgerock.opendj.ldap.requests.Requests.newSimpleBindRequest;
+import static org.mockito.Mockito.*;
 
 import java.io.IOException;
 import java.util.concurrent.Semaphore;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicReference;
 
+import org.forgerock.opendj.ldap.requests.AbandonRequest;
+import org.forgerock.opendj.ldap.requests.BindRequest;
+import org.forgerock.opendj.ldap.requests.SearchRequest;
+import org.forgerock.opendj.ldap.requests.UnbindRequest;
+import org.forgerock.opendj.ldap.responses.BindResult;
+import org.forgerock.opendj.ldap.responses.SearchResultEntry;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+import org.mockito.stubbing.Stubber;
 import org.testng.annotations.Test;
 
 /**
  * Tests the {@link LDAPConnectionFactory} class.
  */
-@SuppressWarnings("javadoc")
+@SuppressWarnings({ "javadoc", "unchecked" })
 public class LDAPConnectionFactoryTestCase extends SdkTestCase {
     // Test timeout for tests which need to wait for network events.
     private static final long TEST_TIMEOUT = 30L;
 
     /**
+     * Unit test for OPENDJ-1247: a locally timed out bind request will leave a
+     * connection in an invalid state since a bind (or startTLS) is in progress
+     * and no other operations can be performed. Therefore, a timeout should
+     * cause the connection to become invalid and an appropriate connection
+     * event sent. In addition, no abandon request should be sent.
+     * <p>
+     * This test is failing because the connection remains valid.
+     */
+    @Test(enabled = false)
+    public void testClientSideTimeoutForBindRequest() throws Exception {
+        final AtomicReference<LDAPClientContext> context = new AtomicReference<LDAPClientContext>();
+        final Semaphore latch = new Semaphore(0);
+
+        // The server connection should receive a bind, but no abandon request.
+        final ServerConnection<Integer> serverConnection = mock(ServerConnection.class);
+        release(latch).when(serverConnection).handleBind(any(Integer.class), anyInt(),
+                any(BindRequest.class), any(IntermediateResponseHandler.class),
+                any(ResultHandler.class));
+        release(latch).when(serverConnection).handleConnectionClosed(any(Integer.class),
+                any(UnbindRequest.class));
+
+        final LDAPListener server = createServer(latch, context, serverConnection);
+        final ConnectionFactory factory =
+                new LDAPConnectionFactory(server.getSocketAddress(), new LDAPOptions().setTimeout(
+                        1, TimeUnit.MILLISECONDS));
+        Connection connection = null;
+        try {
+            // Connect to the server.
+            connection = factory.getConnection();
+
+            // Wait for the server to accept the connection.
+            assertThat(latch.tryAcquire(TEST_TIMEOUT, TimeUnit.SECONDS)).isTrue();
+
+            /*
+             * A bind request timeout should cause the connection to fail, so
+             * ensure that event listeners are fired.
+             */
+            final ConnectionEventListener listener = mock(ConnectionEventListener.class);
+            connection.addConnectionEventListener(listener);
+
+            final ResultHandler<BindResult> handler = mock(ResultHandler.class);
+            final FutureResult<BindResult> future =
+                    connection.bindAsync(newSimpleBindRequest(), null, handler);
+
+            // Wait for the server to receive the bind request.
+            assertThat(latch.tryAcquire(TEST_TIMEOUT, TimeUnit.SECONDS)).isTrue();
+
+            // Wait for the request to timeout.
+            try {
+                future.get(TEST_TIMEOUT, TimeUnit.SECONDS);
+            } catch (TimeoutResultException e) {
+                assertThat(e.getResult().getResultCode()).isEqualTo(ResultCode.CLIENT_SIDE_TIMEOUT);
+                verify(handler).handleErrorResult(same(e));
+
+                // The connection should no longer be valid.
+                verify(listener).handleConnectionError(eq(false), same(e));
+                assertThat(connection.isValid()).isFalse();
+                connection.close();
+
+                // Wait for the server to receive the close request.
+                assertThat(latch.tryAcquire(TEST_TIMEOUT, TimeUnit.SECONDS)).isTrue();
+
+                /*
+                 * Check that the only interactions were the bind and the close
+                 * and specifically there was no abandon request.
+                 */
+                verify(serverConnection).handleBind(any(Integer.class), eq(3),
+                        any(BindRequest.class), any(IntermediateResponseHandler.class),
+                        any(ResultHandler.class));
+                verify(serverConnection).handleConnectionClosed(any(Integer.class),
+                        any(UnbindRequest.class));
+                verifyNoMoreInteractions(serverConnection);
+            }
+        } finally {
+            closeSilently(connection);
+            factory.close();
+            server.close();
+        }
+    }
+
+    /**
+     * Unit test for OPENDJ-1247: a locally timed out request which is not a
+     * bind or startTLS should result in a client side timeout error, but the
+     * connection should remain valid. In addition, no abandon request should be
+     * sent.
+     * <p>
+     * This test is failing because no abandon request is received.
+     */
+    @Test(enabled = false)
+    public void testClientSideTimeoutForSearchRequest() throws Exception {
+        final AtomicReference<LDAPClientContext> context = new AtomicReference<LDAPClientContext>();
+        final Semaphore latch = new Semaphore(0);
+
+        // The server connection should receive a search and then an abandon.
+        final ServerConnection<Integer> serverConnection = mock(ServerConnection.class);
+        release(latch).when(serverConnection).handleSearch(any(Integer.class),
+                any(SearchRequest.class), any(IntermediateResponseHandler.class),
+                any(SearchResultHandler.class));
+        release(latch).when(serverConnection).handleAbandon(any(Integer.class),
+                any(AbandonRequest.class));
+
+        final LDAPListener server = createServer(latch, context, serverConnection);
+        final ConnectionFactory factory =
+                new LDAPConnectionFactory(server.getSocketAddress(), new LDAPOptions().setTimeout(
+                        1, TimeUnit.MILLISECONDS));
+        Connection connection = null;
+        try {
+            // Connect to the server.
+            connection = factory.getConnection();
+
+            // Wait for the server to accept the connection.
+            assertThat(latch.tryAcquire(TEST_TIMEOUT, TimeUnit.SECONDS)).isTrue();
+
+            /*
+             * A search request timeout should not cause the connection to fail,
+             * so ensure that event listeners are not fired.
+             */
+            final ConnectionEventListener listener = mock(ConnectionEventListener.class);
+            connection.addConnectionEventListener(listener);
+
+            final ResultHandler<SearchResultEntry> handler = mock(ResultHandler.class);
+            final FutureResult<SearchResultEntry> future =
+                    connection.readEntryAsync(DN.valueOf("cn=test"), null, handler);
+
+            // Wait for the server to receive the search request.
+            assertThat(latch.tryAcquire(TEST_TIMEOUT, TimeUnit.SECONDS)).isTrue();
+
+            // Wait for the request to timeout.
+            try {
+                future.get(TEST_TIMEOUT, TimeUnit.SECONDS);
+            } catch (TimeoutResultException e) {
+                assertThat(e.getResult().getResultCode()).isEqualTo(ResultCode.CLIENT_SIDE_TIMEOUT);
+                verify(handler).handleErrorResult(same(e));
+
+                // The connection should still be valid.
+                verifyZeroInteractions(listener);
+                assertThat(connection.isValid()).isTrue();
+
+                // Wait for the server to receive the abandon request.
+                assertThat(latch.tryAcquire(TEST_TIMEOUT, TimeUnit.SECONDS)).isTrue();
+            }
+        } finally {
+            closeSilently(connection);
+            factory.close();
+            server.close();
+        }
+    }
+
+    /**
      * This unit test exposes the bug raised in issue OPENDJ-1156: NPE in
      * ReferenceCountedObject after shutting down directory.
      */
@@ -52,7 +212,7 @@
     public void testResourceManagement() throws Exception {
         final AtomicReference<LDAPClientContext> context = new AtomicReference<LDAPClientContext>();
         final Semaphore latch = new Semaphore(0);
-        final LDAPListener server = createServer(latch, context);
+        final LDAPListener server = createServer(latch, context, mock(ServerConnection.class));
         final ConnectionFactory factory = new LDAPConnectionFactory(server.getSocketAddress());
         try {
             for (int i = 0; i < 100; i++) {
@@ -81,17 +241,27 @@
     }
 
     private LDAPListener createServer(final Semaphore latch,
-            final AtomicReference<LDAPClientContext> context) throws IOException {
+            final AtomicReference<LDAPClientContext> context,
+            final ServerConnection<Integer> serverConnection) throws IOException {
         return new LDAPListener(findFreeSocketAddress(),
                 new ServerConnectionFactory<LDAPClientContext, Integer>() {
-                    @SuppressWarnings("unchecked")
                     @Override
                     public ServerConnection<Integer> handleAccept(
                             final LDAPClientContext clientContext) throws ErrorResultException {
                         context.set(clientContext);
                         latch.release();
-                        return mock(ServerConnection.class);
+                        return serverConnection;
                     }
                 });
     }
+
+    private Stubber release(final Semaphore latch) {
+        return doAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) throws Throwable {
+                latch.release();
+                return null;
+            }
+        });
+    }
 }

--
Gitblit v1.10.0