/*
* 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.endpoint.view;
import com.couchbase.client.core.RequestCancelledException;
import com.couchbase.client.core.ResponseEvent;
import com.couchbase.client.core.endpoint.AbstractEndpoint;
import com.couchbase.client.core.env.CoreEnvironment;
import com.couchbase.client.core.message.CouchbaseMessage;
import com.couchbase.client.core.message.CouchbaseRequest;
import com.couchbase.client.core.message.CouchbaseResponse;
import com.couchbase.client.core.message.ResponseStatus;
import com.couchbase.client.core.message.view.GetDesignDocumentRequest;
import com.couchbase.client.core.message.view.GetDesignDocumentResponse;
import com.couchbase.client.core.message.view.ViewQueryRequest;
import com.couchbase.client.core.message.view.ViewQueryResponse;
import com.couchbase.client.core.message.view.ViewRequest;
import com.couchbase.client.core.retry.FailFastRetryStrategy;
import com.couchbase.client.core.util.Resources;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.lmax.disruptor.EventFactory;
import com.lmax.disruptor.EventHandler;
import com.lmax.disruptor.RingBuffer;
import com.lmax.disruptor.dsl.Disruptor;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.embedded.EmbeddedChannel;
import io.netty.handler.codec.http.DefaultFullHttpRequest;
import io.netty.handler.codec.http.DefaultHttpContent;
import io.netty.handler.codec.http.DefaultHttpResponse;
import io.netty.handler.codec.http.DefaultLastHttpContent;
import io.netty.handler.codec.http.HttpContent;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.util.CharsetUtil;
import io.netty.util.ReferenceCountUtil;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import rx.Observable;
import rx.functions.Action1;
import rx.observers.TestSubscriber;
import rx.schedulers.Schedulers;
import rx.subjects.AsyncSubject;
import rx.subjects.Subject;
import java.io.IOException;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
* Verifies the correct functionality of the {@link ViewHandler}.
*
* @author Michael Nitschinger
* @since 1.0
*/
public class ViewHandlerTest {
private ObjectMapper mapper = new ObjectMapper();
private Queue<ViewRequest> queue;
private EmbeddedChannel channel;
private Disruptor<ResponseEvent> responseBuffer;
private RingBuffer<ResponseEvent> responseRingBuffer;
private List<CouchbaseMessage> firedEvents;
private CountDownLatch latch;
private ViewHandler handler;
private AbstractEndpoint endpoint;
@Before
@SuppressWarnings("unchecked")
public void setup() {
responseBuffer = new Disruptor<ResponseEvent>(new EventFactory<ResponseEvent>() {
@Override
public ResponseEvent newInstance() {
return new ResponseEvent();
}
}, 1024, Executors.newCachedThreadPool());
firedEvents = Collections.synchronizedList(new ArrayList<CouchbaseMessage>());
latch = new CountDownLatch(1);
responseBuffer.handleEventsWith(new EventHandler<ResponseEvent>() {
@Override
public void onEvent(ResponseEvent event, long sequence, boolean endOfBatch) throws Exception {
firedEvents.add(event.getMessage());
latch.countDown();
}
});
responseRingBuffer = responseBuffer.start();
CoreEnvironment environment = mock(CoreEnvironment.class);
when(environment.scheduler()).thenReturn(Schedulers.computation());
when(environment.maxRequestLifetime()).thenReturn(10000L); // 10 seconds
when(environment.autoreleaseAfter()).thenReturn(2000L);
when(environment.retryStrategy()).thenReturn(FailFastRetryStrategy.INSTANCE);
endpoint = mock(AbstractEndpoint.class);
when(endpoint.environment()).thenReturn(environment);
when(environment.userAgent()).thenReturn("Couchbase Client Mock");
queue = new ArrayDeque<ViewRequest>();
handler = new ViewHandler(endpoint, responseRingBuffer, queue, false, false);
channel = new EmbeddedChannel(handler);
}
@After
public void clear() {
//triggers the release of the responseContent common buffer
channel.close().awaitUninterruptibly();
responseBuffer.shutdown();
}
@Test
public void shouldEncodeGetDesignDocumentRequest() {
GetDesignDocumentRequest request = new GetDesignDocumentRequest("name", true, "bucket", "password");
channel.writeOutbound(request);
HttpRequest outbound = (HttpRequest) channel.readOutbound();
assertEquals(HttpMethod.GET, outbound.getMethod());
assertEquals(HttpVersion.HTTP_1_1, outbound.getProtocolVersion());
assertEquals("/bucket/_design/dev_name", outbound.getUri());
assertTrue(outbound.headers().contains(HttpHeaders.Names.AUTHORIZATION));
assertEquals("Couchbase Client Mock", outbound.headers().get(HttpHeaders.Names.USER_AGENT));
assertTrue(outbound.headers().contains(HttpHeaders.Names.HOST));
ReferenceCountUtil.releaseLater(outbound); //for consistency, but it uses Unpooled
}
@Test
public void shouldDecodeSuccessfulGetDesignDocumentResponse() throws Exception {
String response = Resources.read("designdoc_success.json", this.getClass());
HttpResponse responseHeader = new DefaultHttpResponse(HttpVersion.HTTP_1_1, new HttpResponseStatus(200, "OK"));
HttpContent responseChunk = new DefaultLastHttpContent(Unpooled.copiedBuffer(response, CharsetUtil.UTF_8));
GetDesignDocumentRequest requestMock = mock(GetDesignDocumentRequest.class);
when(requestMock.name()).thenReturn("name");
when(requestMock.development()).thenReturn(true);
queue.add(requestMock);
channel.writeInbound(responseHeader, responseChunk);
latch.await(1, TimeUnit.SECONDS);
assertEquals(1, firedEvents.size());
GetDesignDocumentResponse inbound = (GetDesignDocumentResponse) firedEvents.get(0);
assertTrue(inbound.status().isSuccess());
assertEquals("name", inbound.name());
assertEquals(true, inbound.development());
assertEquals(response, inbound.content().toString(CharsetUtil.UTF_8));
ReferenceCountUtil.releaseLater(inbound);
}
@Test
public void shouldEncodeViewQueryRequest() {
ViewQueryRequest request = new ViewQueryRequest("design", "view", true, "query", null, "bucket", "password");
channel.writeOutbound(request);
HttpRequest outbound = (HttpRequest) channel.readOutbound();
assertEquals(HttpMethod.GET, outbound.getMethod());
assertEquals(HttpVersion.HTTP_1_1, outbound.getProtocolVersion());
assertEquals("/bucket/_design/dev_design/_view/view?query", outbound.getUri());
assertTrue(outbound.headers().contains(HttpHeaders.Names.AUTHORIZATION));
assertEquals("Couchbase Client Mock", outbound.headers().get(HttpHeaders.Names.USER_AGENT));
ReferenceCountUtil.releaseLater(outbound); //for consistency, but it uses Unpooled
}
@Test
public void shouldDecodeWithDebugViewQueryResponse() throws Exception {
String response = Resources.read("query_debug.json", this.getClass());
HttpResponse responseHeader = new DefaultHttpResponse(HttpVersion.HTTP_1_1, new HttpResponseStatus(200, "OK"));
HttpContent responseChunk = new DefaultLastHttpContent(Unpooled.copiedBuffer(response, CharsetUtil.UTF_8));
ViewQueryRequest requestMock = mock(ViewQueryRequest.class);
queue.add(requestMock);
channel.writeInbound(responseHeader, responseChunk);
latch.await(1, TimeUnit.SECONDS);
assertEquals(1, firedEvents.size());
ViewQueryResponse inbound = (ViewQueryResponse) firedEvents.get(0);
assertTrue(inbound.status().isSuccess());
assertEquals(5, countAndRelease(inbound.rows()));
inbound.info().toBlocking().forEach(new Action1<ByteBuf>() {
@Override
public void call(ByteBuf byteBuf) {
try {
Map found = mapper.readValue(byteBuf.toString(CharsetUtil.UTF_8), Map.class);
assertEquals(2, found.size());
assertTrue(found.containsKey("debug_info"));
assertTrue(found.containsKey("total_rows"));
} catch (IOException e) {
e.printStackTrace();
assertFalse(true);
}
ReferenceCountUtil.releaseLater(byteBuf);
}
});
}
@Test
public void shouldDecodeEmptyViewQueryResponse() throws Exception {
String response = Resources.read("query_empty.json", this.getClass());
HttpResponse responseHeader = new DefaultHttpResponse(HttpVersion.HTTP_1_1, new HttpResponseStatus(200, "OK"));
HttpContent responseChunk = new DefaultLastHttpContent(Unpooled.copiedBuffer(response, CharsetUtil.UTF_8));
ViewQueryRequest requestMock = mock(ViewQueryRequest.class);
queue.add(requestMock);
channel.writeInbound(responseHeader, responseChunk);
latch.await(1, TimeUnit.SECONDS);
assertEquals(1, firedEvents.size());
ViewQueryResponse inbound = (ViewQueryResponse) firedEvents.get(0);
assertTrue(inbound.status().isSuccess());
assertTrue(inbound.rows().toList().toBlocking().single().isEmpty());
final AtomicInteger called = new AtomicInteger();
inbound.info().toBlocking().forEach(new Action1<ByteBuf>() {
@Override
public void call(ByteBuf byteBuf) {
called.incrementAndGet();
assertEquals("{\"total_rows\":7303}", byteBuf.toString(CharsetUtil.UTF_8));
ReferenceCountUtil.releaseLater(byteBuf);
}
});
assertEquals(1, called.get());
}
@Test
public void shouldDecodeOneViewQueryResponse() throws Exception {
String response = Resources.read("query_one.json", this.getClass());
HttpResponse responseHeader = new DefaultHttpResponse(HttpVersion.HTTP_1_1, new HttpResponseStatus(200, "OK"));
HttpContent responseChunk = new DefaultLastHttpContent(Unpooled.copiedBuffer(response, CharsetUtil.UTF_8));
ViewQueryRequest requestMock = mock(ViewQueryRequest.class);
queue.add(requestMock);
channel.writeInbound(responseHeader, responseChunk);
latch.await(1, TimeUnit.SECONDS);
assertEquals(1, firedEvents.size());
ViewQueryResponse inbound = (ViewQueryResponse) firedEvents.get(0);
assertTrue(inbound.status().isSuccess());
assertEquals(200, inbound.responseCode());
assertEquals("OK", inbound.responsePhrase());
final AtomicInteger calledRow = new AtomicInteger();
inbound.rows().toBlocking().forEach(new Action1<ByteBuf>() {
@Override
public void call(ByteBuf byteBuf) {
calledRow.incrementAndGet();
try {
Map found = mapper.readValue(byteBuf.toString(CharsetUtil.UTF_8), Map.class);
assertEquals(3, found.size());
assertTrue(found.containsKey("id"));
assertTrue(found.containsKey("key"));
assertTrue(found.containsKey("value"));
} catch (IOException e) {
e.printStackTrace();
assertFalse(true);
}
ReferenceCountUtil.releaseLater(byteBuf);
}
});
assertEquals(1, calledRow.get());
final AtomicInteger called = new AtomicInteger();
inbound.info().toBlocking().forEach(new Action1<ByteBuf>() {
@Override
public void call(ByteBuf byteBuf) {
called.incrementAndGet();
assertEquals("{\"total_rows\":7303}", byteBuf.toString(CharsetUtil.UTF_8));
ReferenceCountUtil.releaseLater(byteBuf);
}
});
assertEquals(1, called.get());
}
@Test
public void shouldDecodeManyViewQueryResponse() throws Exception {
String response = Resources.read("query_many.json", this.getClass());
HttpResponse responseHeader = new DefaultHttpResponse(HttpVersion.HTTP_1_1, new HttpResponseStatus(200, "OK"));
HttpContent responseChunk1 = new DefaultHttpContent(Unpooled.copiedBuffer(response.substring(0, 500), CharsetUtil.UTF_8));
HttpContent responseChunk2 = new DefaultHttpContent(Unpooled.copiedBuffer(response.substring(500, 1234), CharsetUtil.UTF_8));
HttpContent responseChunk3 = new DefaultLastHttpContent(Unpooled.copiedBuffer(response.substring(1234), CharsetUtil.UTF_8));
ViewQueryRequest requestMock = mock(ViewQueryRequest.class);
queue.add(requestMock);
channel.writeInbound(responseHeader, responseChunk1, responseChunk2, responseChunk3);
latch.await(1, TimeUnit.SECONDS);
assertEquals(1, firedEvents.size());
ViewQueryResponse inbound = (ViewQueryResponse) firedEvents.get(0);
assertTrue(inbound.status().isSuccess());
final AtomicInteger calledRow = new AtomicInteger();
inbound.rows().toBlocking().forEach(new Action1<ByteBuf>() {
@Override
public void call(ByteBuf byteBuf) {
calledRow.incrementAndGet();
try {
Map found = mapper.readValue(byteBuf.toString(CharsetUtil.UTF_8), Map.class);
assertEquals(3, found.size());
assertTrue(found.containsKey("id"));
assertTrue(found.containsKey("key"));
assertTrue(found.containsKey("value"));
} catch (IOException e) {
e.printStackTrace();
assertFalse(true);
}
ReferenceCountUtil.releaseLater(byteBuf);
}
});
assertEquals(500, calledRow.get());
final AtomicInteger called = new AtomicInteger();
inbound.info().toBlocking().forEach(new Action1<ByteBuf>() {
@Override
public void call(ByteBuf byteBuf) {
called.incrementAndGet();
assertEquals("{\"total_rows\":7303}", byteBuf.toString(CharsetUtil.UTF_8));
ReferenceCountUtil.releaseLater(byteBuf);
}
});
assertEquals(1, called.get());
}
@Test
public void shouldFireKeepAlive() throws Exception {
final AtomicInteger keepAliveEventCounter = new AtomicInteger();
final AtomicReference<ChannelHandlerContext> ctxRef = new AtomicReference();
ViewHandler testHandler = new ViewHandler(endpoint, responseRingBuffer, queue, false, false) {
@Override
public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
super.channelRegistered(ctx);
ctxRef.compareAndSet(null, ctx);
}
@Override
protected void onKeepAliveFired(ChannelHandlerContext ctx, CouchbaseRequest keepAliveRequest) {
assertEquals(1, keepAliveEventCounter.incrementAndGet());
}
@Override
protected void onKeepAliveResponse(ChannelHandlerContext ctx, CouchbaseResponse keepAliveResponse) {
assertEquals(2, keepAliveEventCounter.incrementAndGet());
}
};
EmbeddedChannel channel = new EmbeddedChannel(testHandler);
//test idle event triggers a view keepAlive request and hook is called
testHandler.userEventTriggered(ctxRef.get(), IdleStateEvent.FIRST_READER_IDLE_STATE_EVENT);
assertEquals(1, keepAliveEventCounter.get());
assertTrue(queue.peek() instanceof ViewHandler.KeepAliveRequest);
ViewHandler.KeepAliveRequest keepAliveRequest = (ViewHandler.KeepAliveRequest) queue.peek();
//test responding to the request with http response is interpreted into a KeepAliveResponse and hook is called
HttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.NOT_FOUND);
channel.writeInbound(response);
ViewHandler.KeepAliveResponse keepAliveResponse = keepAliveRequest.observable()
.cast(ViewHandler.KeepAliveResponse.class)
.timeout(1, TimeUnit.SECONDS).toBlocking().single();
assertEquals(2, keepAliveEventCounter.get());
assertEquals(ResponseStatus.NOT_EXISTS, keepAliveResponse.status());
//different channel, needs to be closed to release the internal responseContent
channel.close().awaitUninterruptibly();
}
@Test
public void shouldEncodeLongViewQueryRequestWithPOST() {
String keys = Resources.read("key_many.json", this.getClass());
String query = "stale=false&endKey=test";
ViewQueryRequest request = new ViewQueryRequest("design", "view", true, query, keys, "bucket", "password");
channel.writeOutbound(request);
DefaultFullHttpRequest outbound = (DefaultFullHttpRequest) channel.readOutbound();
assertEquals(HttpMethod.POST, outbound.getMethod());
assertFalse(outbound.getUri().contains("keys="));
assertTrue(outbound.getUri().endsWith("?stale=false&endKey=test"));
String content = outbound.content().toString(CharsetUtil.UTF_8);
assertTrue(content.startsWith("{\"keys\":["));
assertTrue(content.endsWith("]}"));
ReferenceCountUtil.releaseLater(outbound);
}
@Test
public void shouldUrlEncodeShortKeys() {
String urlEncodedKeys = "%5B%221%22%2C%222%22%2C%223%22%5D";
String keys = "[\"1\",\"2\",\"3\"]";
String query = "stale=false&endKey=test";
ViewQueryRequest request = new ViewQueryRequest("design", "view", true, query, keys, "bucket", "password");
channel.writeOutbound(request);
DefaultFullHttpRequest outbound = (DefaultFullHttpRequest) channel.readOutbound();
String failMsg = outbound.getUri();
assertEquals(HttpMethod.GET, outbound.getMethod());
assertTrue(failMsg, outbound.getUri().contains("keys="));
assertTrue(failMsg, outbound.getUri().endsWith("?stale=false&endKey=test&keys=" + urlEncodedKeys));
String content = outbound.content().toString(CharsetUtil.UTF_8);
assertTrue(content.isEmpty());
ReferenceCountUtil.releaseLater(outbound); //NO-OP since content is empty but still...
}
@Test
public void shouldProduceValidUrlIfShortKeysAndNoOtherQueryParam() {
String urlEncodedKeys = "%5B%221%22%2C%222%22%2C%223%22%5D";
String keys = "[\"1\",\"2\",\"3\"]";
String query = "";
ViewQueryRequest request = new ViewQueryRequest("design", "view", true, query, keys, "bucket", "password");
channel.writeOutbound(request);
DefaultFullHttpRequest outbound = (DefaultFullHttpRequest) channel.readOutbound();
String failMsg = outbound.getUri();
assertEquals(HttpMethod.GET, outbound.getMethod());
assertTrue(failMsg, outbound.getUri().endsWith("?keys=" + urlEncodedKeys));
String content = outbound.content().toString(CharsetUtil.UTF_8);
assertTrue(content.isEmpty());
ReferenceCountUtil.releaseLater(outbound); //NO-OP since content is empty but still...
}
@Test
public void shouldDoNothingOnNullKeys() {
String keys = null;
String query = "stale=false&endKey=test";
ViewQueryRequest request = new ViewQueryRequest("design", "view", true, query, keys, "bucket", "password");
channel.writeOutbound(request);
DefaultFullHttpRequest outbound = (DefaultFullHttpRequest) channel.readOutbound();
assertEquals(HttpMethod.GET, outbound.getMethod());
assertFalse(outbound.getUri().contains("keys="));
assertTrue(outbound.getUri().endsWith("?stale=false&endKey=test"));
String content = outbound.content().toString(CharsetUtil.UTF_8);
assertTrue(content.isEmpty());
ReferenceCountUtil.releaseLater(outbound); //NO-OP since content is empty but still...
}
@Test
public void shouldDoNothingOnEmptyKeys() {
String keys = "";
String query = "stale=false&endKey=test";
ViewQueryRequest request = new ViewQueryRequest("design", "view", true, query, keys, "bucket", "password");
channel.writeOutbound(request);
DefaultFullHttpRequest outbound = (DefaultFullHttpRequest) channel.readOutbound();
assertEquals(HttpMethod.GET, outbound.getMethod());
assertFalse(outbound.getUri().contains("keys="));
assertTrue(outbound.getUri().endsWith("?stale=false&endKey=test"));
String content = outbound.content().toString(CharsetUtil.UTF_8);
assertTrue(content.isEmpty());
ReferenceCountUtil.releaseLater(outbound); //NO-OP since content is empty but still...
}
@Test
public void shouldNotBreakLinesOnLongAuth() {
String longPassword = "thisIsAveryLongPasswordWhichShouldNotContainLineBreaksAfterEncodingOtherwise"
+ "itWillBreakTheRequestResponseFlowWithTheServer";
GetDesignDocumentRequest request = new GetDesignDocumentRequest("name", true, "bucket", longPassword);
channel.writeOutbound(request);
HttpRequest outbound = (HttpRequest) channel.readOutbound();
assertEquals(HttpMethod.GET, outbound.getMethod());
assertEquals(HttpVersion.HTTP_1_1, outbound.getProtocolVersion());
assertEquals("/bucket/_design/dev_name", outbound.getUri());
assertTrue(outbound.headers().contains(HttpHeaders.Names.AUTHORIZATION));
assertNotNull(outbound.headers().get(HttpHeaders.Names.AUTHORIZATION));
assertEquals("Couchbase Client Mock", outbound.headers().get(HttpHeaders.Names.USER_AGENT));
ReferenceCountUtil.releaseLater(outbound); //for consistency, but it uses Unpooled
}
@Test
@SuppressWarnings("unchecked")
public void shouldParseErrorWithEmptyRows() throws Exception {
String response = Resources.read("error_empty_rows.json", this.getClass());
HttpResponse responseHeader = new DefaultHttpResponse(HttpVersion.HTTP_1_1, new HttpResponseStatus(200, "OK"));
HttpContent responseChunk1 = new DefaultLastHttpContent(Unpooled.copiedBuffer(response, CharsetUtil.UTF_8));
ViewQueryRequest requestMock = mock(ViewQueryRequest.class);
queue.add(requestMock);
channel.writeInbound(responseHeader, responseChunk1);
latch.await(1, TimeUnit.SECONDS);
assertEquals(1, firedEvents.size());
ViewQueryResponse inbound = (ViewQueryResponse) firedEvents.get(0);
assertTrue(inbound.status().isSuccess());
assertEquals(0, countAndRelease(inbound.rows()));
String error = inbound.error().toBlocking().single();
Map<String, Object> parsed = mapper.readValue(error, Map.class);
assertEquals(1, parsed.size());
assertNotNull(parsed.get("errors"));
}
@Test
@SuppressWarnings("unchecked")
public void shouldParseErrorAfterRows() throws Exception {
String response = Resources.read("error_rows.json", this.getClass());
HttpResponse responseHeader = new DefaultHttpResponse(HttpVersion.HTTP_1_1, new HttpResponseStatus(200, "OK"));
HttpContent responseChunk1 = new DefaultLastHttpContent(Unpooled.copiedBuffer(response, CharsetUtil.UTF_8));
ViewQueryRequest requestMock = mock(ViewQueryRequest.class);
queue.add(requestMock);
channel.writeInbound(responseHeader, responseChunk1);
latch.await(1, TimeUnit.SECONDS);
assertEquals(1, firedEvents.size());
ViewQueryResponse inbound = (ViewQueryResponse) firedEvents.get(0);
assertTrue(inbound.status().isSuccess());
assertEquals(10, countAndRelease(inbound.rows()));
String error = inbound.error().toBlocking().single();
Map<String, Object> parsed = mapper.readValue(error, Map.class);
assertEquals(1, parsed.size());
assertNotNull(parsed.get("errors"));
}
@Test
public void shouldParseErrorWithDesignNotFound() throws Exception {
String response = Resources.read("designdoc_notfound.json", this.getClass());
HttpResponse responseHeader = new DefaultHttpResponse(HttpVersion.HTTP_1_1, new HttpResponseStatus(404, "Object Not Found"));
HttpContent responseChunk1 = new DefaultLastHttpContent(Unpooled.copiedBuffer(response, CharsetUtil.UTF_8));
ViewQueryRequest requestMock = mock(ViewQueryRequest.class);
queue.add(requestMock);
channel.writeInbound(responseHeader, responseChunk1);
latch.await(1, TimeUnit.SECONDS);
assertEquals(1, firedEvents.size());
ViewQueryResponse inbound = (ViewQueryResponse) firedEvents.get(0);
assertFalse(inbound.status().isSuccess());
assertEquals(ResponseStatus.NOT_EXISTS, inbound.status());
assertEquals(0, countAndRelease(inbound.rows()));
String error = inbound.error().toBlocking().single();
assertEquals("{\"errors\":[{\"error\":\"not_found\",\"reason\":\"Design document _design/designdoc not found\"}]}", error);
}
@Test
public void shouldParseRowWithBracketInIdKeysAndValue() throws Exception {
String response = Resources.read("query_brackets.json", this.getClass());
HttpResponse responseHeader = new DefaultHttpResponse(HttpVersion.HTTP_1_1, new HttpResponseStatus(200, "OK"));
HttpContent responseChunk = new DefaultLastHttpContent(Unpooled.copiedBuffer(response, CharsetUtil.UTF_8));
ViewQueryRequest requestMock = mock(ViewQueryRequest.class);
queue.add(requestMock);
channel.writeInbound(responseHeader, responseChunk);
latch.await(1, TimeUnit.SECONDS);
assertEquals(1, firedEvents.size());
ViewQueryResponse inbound = (ViewQueryResponse) firedEvents.get(0);
assertTrue(inbound.status().isSuccess());
assertEquals(200, inbound.responseCode());
assertEquals("OK", inbound.responsePhrase());
ByteBuf singleRow = inbound.rows().toBlocking().single(); //single will blow up if not exactly one
String singleRowData = singleRow.toString(CharsetUtil.UTF_8);
ReferenceCountUtil.releaseLater(singleRow);
Map found = null;
try {
found = mapper.readValue(singleRowData, Map.class);
} catch (IOException e) {
e.printStackTrace();
fail("Failed parsing JSON on data " + singleRowData);
}
assertEquals(3, found.size());
assertTrue(found.containsKey("id"));
assertTrue(found.containsKey("key"));
assertTrue(found.containsKey("value"));
assertEquals("IdClosing}BracketId", found.get("id"));
assertEquals(Arrays.asList("KeyClosing}BracketKey", "KeySquareClosing]SquareBracketKey"), found.get("key"));
assertEquals("ValueClosing}BracketValue", found.get("value"));
final AtomicInteger called = new AtomicInteger();
inbound.info().toBlocking().forEach(new Action1<ByteBuf>() {
@Override
public void call(ByteBuf byteBuf) {
called.incrementAndGet();
assertEquals("{\"total_rows\":1}", byteBuf.toString(CharsetUtil.UTF_8));
ReferenceCountUtil.releaseLater(byteBuf);
}
});
assertEquals(1, called.get());
}
private int countAndRelease(Observable<ByteBuf> bufObservable) {
return bufObservable
.doOnNext(new Action1<ByteBuf>() {
@Override
public void call(ByteBuf byteBuf) {
byteBuf.release();
}
}).count()
.toBlocking()
.singleOrDefault(0);
}
@Test
public void shouldHavePipeliningDisabled() {
Subject<CouchbaseResponse,CouchbaseResponse> obs1 = AsyncSubject.create();
ViewQueryRequest requestMock1 = mock(ViewQueryRequest.class);
when(requestMock1.query()).thenReturn("{...}");
when(requestMock1.bucket()).thenReturn("foo");
when(requestMock1.username()).thenReturn("foo");
when(requestMock1.password()).thenReturn("");
when(requestMock1.observable()).thenReturn(obs1);
Subject<CouchbaseResponse,CouchbaseResponse> obs2 = AsyncSubject.create();
ViewQueryRequest requestMock2 = mock(ViewQueryRequest.class);
when(requestMock2.query()).thenReturn("{...}");
when(requestMock2.bucket()).thenReturn("foo");
when(requestMock2.username()).thenReturn("foo");
when(requestMock2.password()).thenReturn("");
when(requestMock2.observable()).thenReturn(obs2);
TestSubscriber<CouchbaseResponse> t1 = TestSubscriber.create();
TestSubscriber<CouchbaseResponse> t2 = TestSubscriber.create();
obs1.subscribe(t1);
obs2.subscribe(t2);
channel.writeOutbound(requestMock1, requestMock2);
t1.assertNotCompleted();
t2.assertError(RequestCancelledException.class);
}
}