/* * Copyright (c) 2016 Couchbase, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.couchbase.client.core; import com.couchbase.client.core.config.BucketConfig; import com.couchbase.client.core.config.ClusterConfig; import com.couchbase.client.core.config.ConfigurationProvider; import com.couchbase.client.core.endpoint.ResponseStatusConverter; import com.couchbase.client.core.endpoint.kv.KeyValueStatus; import com.couchbase.client.core.env.CoreEnvironment; import com.couchbase.client.core.env.DefaultCoreEnvironment; import com.couchbase.client.core.message.CouchbaseRequest; import com.couchbase.client.core.message.ResponseStatus; import com.couchbase.client.core.message.kv.GetRequest; import com.couchbase.client.core.message.kv.GetResponse; import com.couchbase.client.core.message.kv.InsertRequest; import com.couchbase.client.core.message.kv.InsertResponse; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.util.CharsetUtil; import org.junit.Test; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import rx.subjects.Subject; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.mockito.Matchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; /** * Verifies functionality of the {@link ResponseHandler}. * * @author Michael Nitschinger * @since 1.0.3 */ public class ResponseHandlerTest { private static final CoreEnvironment ENVIRONMENT = DefaultCoreEnvironment.create(); @Test public void shouldSendProposedConfigToProvider() throws Exception { ClusterFacade clusterMock = mock(ClusterFacade.class); ConfigurationProvider providerMock = mock(ConfigurationProvider.class); ResponseHandler handler = new ResponseHandler(ENVIRONMENT, clusterMock, providerMock); ByteBuf config = Unpooled.copiedBuffer("{\"json\": true}", CharsetUtil.UTF_8); ResponseEvent retryEvent = new ResponseEvent(); retryEvent.setMessage(new InsertResponse(ResponseStatus.RETRY, KeyValueStatus.ERR_TEMP_FAIL.code(), 0, "bucket", config, null, mock(InsertRequest.class))); retryEvent.setObservable(mock(Subject.class)); handler.onEvent(retryEvent, 1, true); verify(providerMock, times(1)).proposeBucketConfig("bucket", "{\"json\": true}"); assertEquals(0, config.refCnt()); assertNull(retryEvent.getMessage()); assertNull(retryEvent.getObservable()); } @Test public void shouldIgnoreInvalidConfig() throws Exception { ClusterFacade clusterMock = mock(ClusterFacade.class); ConfigurationProvider providerMock = mock(ConfigurationProvider.class); ResponseHandler handler = new ResponseHandler(ENVIRONMENT, clusterMock, providerMock); ByteBuf config = Unpooled.copiedBuffer("Not my Vbucket", CharsetUtil.UTF_8); ResponseEvent retryEvent = new ResponseEvent(); retryEvent.setMessage(new InsertResponse(ResponseStatus.RETRY, KeyValueStatus.ERR_TEMP_FAIL.code(), 0, "bucket", config, null, mock(InsertRequest.class))); retryEvent.setObservable(mock(Subject.class)); handler.onEvent(retryEvent, 1, true); verify(providerMock, never()).proposeBucketConfig("bucket", "Not my Vbucket"); assertEquals(0, config.refCnt()); assertNull(retryEvent.getMessage()); assertNull(retryEvent.getObservable()); } @Test public void shouldDispatchFirstNMVImmediately() throws Exception { final CountDownLatch latch = new CountDownLatch(1); ClusterFacade clusterMock = mock(ClusterFacade.class); when(clusterMock.send(any(CouchbaseRequest.class))).then(new Answer<Object>() { @Override public Object answer(InvocationOnMock invocationOnMock) throws Throwable { latch.countDown(); return null; } }); ConfigurationProvider providerMock = mock(ConfigurationProvider.class); ClusterConfig clusterConfig = mock(ClusterConfig.class); BucketConfig bucketConfig = mock(BucketConfig.class); when(providerMock.config()).thenReturn(clusterConfig); when(clusterConfig.bucketConfig("bucket")).thenReturn(bucketConfig); when(bucketConfig.hasFastForwardMap()).thenReturn(true); ResponseHandler handler = new ResponseHandler(ENVIRONMENT, clusterMock, providerMock); GetRequest request = new GetRequest("key", "bucket"); GetResponse response = new GetResponse(ResponseStatus.RETRY, (short) 0, 0L ,0, "bucket", Unpooled.EMPTY_BUFFER, request); ResponseEvent retryEvent = new ResponseEvent(); retryEvent.setMessage(response); retryEvent.setObservable(request.observable()); handler.onEvent(retryEvent, 1, true); long start = System.nanoTime(); latch.await(5, TimeUnit.SECONDS); long end = System.nanoTime(); // assert immediate dispatch assertTrue(TimeUnit.NANOSECONDS.toMillis(end - start) < 10); } @Test public void shouldDispatchFirstNMVBWithDelayIfNoFFMap() throws Exception { final CountDownLatch latch = new CountDownLatch(1); ClusterFacade clusterMock = mock(ClusterFacade.class); when(clusterMock.send(any(CouchbaseRequest.class))).then(new Answer<Object>() { @Override public Object answer(InvocationOnMock invocationOnMock) throws Throwable { latch.countDown(); return null; } }); ConfigurationProvider providerMock = mock(ConfigurationProvider.class); ClusterConfig clusterConfig = mock(ClusterConfig.class); BucketConfig bucketConfig = mock(BucketConfig.class); when(providerMock.config()).thenReturn(clusterConfig); when(clusterConfig.bucketConfig("bucket")).thenReturn(bucketConfig); when(bucketConfig.hasFastForwardMap()).thenReturn(false); ResponseHandler handler = new ResponseHandler(ENVIRONMENT, clusterMock, providerMock); GetRequest request = new GetRequest("key", "bucket"); GetResponse response = new GetResponse(ResponseStatus.RETRY, (short) 0, 0L ,0, "bucket", Unpooled.EMPTY_BUFFER, request); ResponseEvent retryEvent = new ResponseEvent(); retryEvent.setMessage(response); retryEvent.setObservable(request.observable()); handler.onEvent(retryEvent, 1, true); long start = System.nanoTime(); latch.await(5, TimeUnit.SECONDS); long end = System.nanoTime(); // assert delayed dispatch assertTrue(TimeUnit.NANOSECONDS.toMillis(end - start) >= 100); } @Test public void shouldDispatchSecondNMVWithDelay() throws Exception { final CountDownLatch latch = new CountDownLatch(1); ClusterFacade clusterMock = mock(ClusterFacade.class); when(clusterMock.send(any(CouchbaseRequest.class))).then(new Answer<Object>() { @Override public Object answer(InvocationOnMock invocationOnMock) throws Throwable { latch.countDown(); return null; } }); ConfigurationProvider providerMock = mock(ConfigurationProvider.class); ClusterConfig clusterConfig = mock(ClusterConfig.class); BucketConfig bucketConfig = mock(BucketConfig.class); when(providerMock.config()).thenReturn(clusterConfig); when(clusterConfig.bucketConfig("bucket")).thenReturn(bucketConfig); when(bucketConfig.hasFastForwardMap()).thenReturn(true); ResponseHandler handler = new ResponseHandler(ENVIRONMENT, clusterMock, providerMock); GetRequest request = new GetRequest("key", "bucket"); request.incrementRetryCount(); // pretend its at least once retried! GetResponse response = new GetResponse(ResponseStatus.RETRY, (short) 0, 0L ,0, "bucket", Unpooled.EMPTY_BUFFER, request); ResponseEvent retryEvent = new ResponseEvent(); retryEvent.setMessage(response); retryEvent.setObservable(request.observable()); handler.onEvent(retryEvent, 1, true); long start = System.nanoTime(); latch.await(5, TimeUnit.SECONDS); long end = System.nanoTime(); // assert delayed dispatch assertTrue(TimeUnit.NANOSECONDS.toMillis(end - start) >= 100); } }