/*
* The MIT License (MIT)
* Copyright © 2013 Englishtown <opensource@englishtown.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the “Software”), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.englishtown.vertx.jersey.impl;
import com.englishtown.vertx.jersey.inject.VertxPostResponseProcessor;
import com.englishtown.vertx.jersey.inject.VertxResponseProcessor;
import io.netty.handler.codec.http.DefaultHttpHeaders;
import io.vertx.core.Handler;
import io.vertx.core.MultiMap;
import io.vertx.core.Vertx;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.http.HttpServerRequest;
import io.vertx.core.http.HttpServerResponse;
import io.vertx.core.http.impl.HeadersAdaptor;
import io.vertx.core.logging.Logger;
import org.glassfish.jersey.server.ContainerResponse;
import org.glassfish.jersey.server.spi.ContainerResponseWriter;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MultivaluedHashMap;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import static org.junit.Assert.*;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyLong;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.*;
/**
* {@link VertxResponseWriter} unit tests
*/
@SuppressWarnings("unchecked")
@RunWith(MockitoJUnitRunner.class)
public class VertxResponseWriterTest {
VertxResponseWriter writer;
List<VertxResponseProcessor> responseProcessors = new ArrayList<>();
List<VertxPostResponseProcessor> postResponseProcessors = new ArrayList<>();
long timerId = 10;
@Mock
Vertx vertx;
@Mock
Logger logger;
@Mock
HttpServerRequest request;
@Mock
HttpServerResponse response;
@Before
public void setUp() {
when(request.response()).thenReturn(response);
when(vertx.setTimer(anyLong(), any(Handler.class))).thenReturn(timerId);
writer = new VertxResponseWriter(request, vertx, responseProcessors, postResponseProcessors);
}
@Test
public void testWriteResponseStatusAndHeaders() throws Exception {
ContainerResponse cr = mock(ContainerResponse.class);
MultivaluedMap<String, String> headers = new MultivaluedHashMap<>();
when(cr.getStatusInfo()).thenReturn(mock(Response.StatusType.class));
when(cr.getStringHeaders()).thenReturn(headers);
VertxResponseProcessor processor1 = mock(VertxResponseProcessor.class);
VertxResponseProcessor processor2 = mock(VertxResponseProcessor.class);
responseProcessors.add(processor1);
responseProcessors.add(processor2);
headers.add("x-test", "custom header");
OutputStream outputStream = writer.writeResponseStatusAndHeaders(15, cr);
assertNotNull(outputStream);
verify(response, times(1)).setStatusCode(anyInt());
verify(response, times(1)).setStatusMessage(anyString());
verify(response, times(1)).putHeader(anyString(), anyListOf(String.class));
verify(processor1).process(eq(response), eq(cr));
verify(processor2).process(eq(response), eq(cr));
writer.writeResponseStatusAndHeaders(-1, cr);
verify(response, times(2)).putHeader(anyString(), anyListOf(String.class));
}
@Test
public void testWriteResponse_Throw() throws Exception {
ContainerResponse cr = mock(ContainerResponse.class);
MultivaluedMap<String, String> headers = new MultivaluedHashMap<>();
when(cr.getStatusInfo()).thenReturn(mock(Response.StatusType.class));
when(cr.getStringHeaders()).thenReturn(headers);
VertxResponseProcessor processor1 = mock(VertxResponseProcessor.class);
VertxResponseProcessor processor2 = mock(VertxResponseProcessor.class);
responseProcessors.add(processor1);
responseProcessors.add(processor2);
headers.add("x-test", "custom header");
OutputStream outputStream = writer.writeResponseStatusAndHeaders(15, cr);
assertNotNull(outputStream);
verify(response, times(1)).setStatusCode(anyInt());
verify(response, times(1)).setStatusMessage(anyString());
verify(response, times(1)).putHeader(anyString(), anyListOf(String.class));
verify(processor1).process(eq(response), eq(cr));
verify(processor2).process(eq(response), eq(cr));
writer.writeResponseStatusAndHeaders(-1, cr);
verify(response, times(2)).putHeader(anyString(), anyListOf(String.class));
}
@Test
public void testWrite() throws Exception {
ContainerResponse cr = mock(ContainerResponse.class);
MultivaluedMap<String, String> headers = new MultivaluedHashMap<>();
when(cr.getStatusInfo()).thenReturn(mock(Response.StatusType.class));
when(cr.getStringHeaders()).thenReturn(headers);
DefaultHttpHeaders httpHeaders = new DefaultHttpHeaders();
MultiMap vertxHeaders = new HeadersAdaptor(httpHeaders);
vertxHeaders.add(HttpHeaders.CONTENT_LENGTH, "12");
when(response.headers()).thenReturn(vertxHeaders);
OutputStream outputStream = writer.writeResponseStatusAndHeaders(12, cr);
outputStream.write("callback".getBytes());
outputStream.write(40);
outputStream.flush();
verify(response, times(1)).write(any(Buffer.class));
byte[] buffer = new byte[1024];
buffer[0] = "{".getBytes()[0];
buffer[1] = "}".getBytes()[0];
outputStream.write(buffer, 0, 2);
outputStream.write(41);
outputStream.flush();
verify(response, times(2)).write(any(Buffer.class));
outputStream.write(";".getBytes());
outputStream.close();
verify(response, times(3)).write(any(Buffer.class));
try {
outputStream.write("fail".getBytes());
fail();
} catch (RuntimeException e) {
// Expected
}
}
@Test
public void testWrite_Chunked() throws Exception {
ContainerResponse cr = mock(ContainerResponse.class);
MultivaluedMap<String, String> headers = new MultivaluedHashMap<>();
when(cr.getStatusInfo()).thenReturn(mock(Response.StatusType.class));
when(cr.getStringHeaders()).thenReturn(headers);
when(cr.isChunked()).thenReturn(true);
OutputStream outputStream = writer.writeResponseStatusAndHeaders(-1, cr);
verify(response, times(1)).setChunked(eq(true));
outputStream.write("Chunked data".getBytes());
verify(response, times(1)).write(any(Buffer.class));
outputStream.flush();
verify(response, times(1)).write(any(Buffer.class));
outputStream.write("Chunked data".getBytes());
verify(response, times(2)).write(any(Buffer.class));
outputStream.write("Chunked data".getBytes());
verify(response, times(3)).write(any(Buffer.class));
outputStream.write("Final chunked data".getBytes());
outputStream.close();
verify(response, times(4)).write(any(Buffer.class));
try {
outputStream.write("fail".getBytes());
fail();
} catch (RuntimeException e) {
// Expected
}
}
@Test
public void testSuspend() throws Exception {
boolean result;
ContainerResponseWriter.TimeoutHandler timeoutHandler = mock(ContainerResponseWriter.TimeoutHandler.class);
result = writer.suspend(10, TimeUnit.SECONDS, timeoutHandler);
assertTrue(result);
verify(vertx, times(1)).setTimer(anyLong(), any(Handler.class));
result = writer.suspend(0, TimeUnit.SECONDS, timeoutHandler);
assertTrue(result);
verify(vertx, times(1)).setTimer(anyLong(), any(Handler.class));
result = writer.suspend(10, TimeUnit.SECONDS, timeoutHandler);
assertTrue(result);
verify(vertx, times(2)).setTimer(anyLong(), any(Handler.class));
}
@Test
public void testSetSuspendTimeout() throws Exception {
ContainerResponseWriter.TimeoutHandler timeoutHandler = mock(ContainerResponseWriter.TimeoutHandler.class);
try {
writer.setSuspendTimeout(10, TimeUnit.SECONDS);
fail();
} catch (IllegalStateException e) {
// Expected
}
writer.suspend(0, TimeUnit.SECONDS, timeoutHandler);
writer.setSuspendTimeout(5, TimeUnit.MINUTES);
verify(vertx, times(1)).setTimer(anyLong(), any(Handler.class));
}
@Test
public void testCommit() throws Exception {
VertxPostResponseProcessor processor1 = mock(VertxPostResponseProcessor.class);
VertxPostResponseProcessor processor2 = mock(VertxPostResponseProcessor.class);
postResponseProcessors.add(processor1);
postResponseProcessors.add(processor2);
writer.commit();
verify(response).end();
verify(processor1).process(eq(response), any(ContainerResponse.class));
verify(processor2).process(eq(response), any(ContainerResponse.class));
}
@Test
public void testFailure() throws Exception {
Throwable error = mock(Throwable.class);
writer.failure(error);
verify(response, times(1)).setStatusCode(eq(500));
verify(response, times(1)).setStatusMessage(eq("Internal Server Error"));
verify(response, times(1)).end();
}
@Test
public void testEnableResponseBuffering() throws Exception {
boolean result;
result = writer.enableResponseBuffering();
assertFalse(result);
}
}