/*
* Copyright 2012 Jason Miller
*
* 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 jj.http.server;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.mockito.BDDMockito.*;
import java.io.IOException;
import java.nio.channels.FileChannel;
import jj.Version;
import jj.event.Publisher;
import jj.http.server.HttpServerRequestImpl;
import jj.http.server.HttpServerResponseImpl;
import jj.logging.LoggedEvent;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.DefaultFullHttpRequest;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpHeaderValues;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Answers;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
/**
* @author jason
*
*/
@RunWith(MockitoJUnitRunner.class)
public class HttpServerResponseImplTest {
final String sha1 = "this is not really a sha";
final String mime = "this is not really a mime";
final ByteBuf bytes = Unpooled.wrappedBuffer("this is the bytes".getBytes(UTF_8));
final long size = bytes.readableBytes();
String host = "hostname";
DefaultFullHttpRequest nettyRequest;
HttpServerRequestImpl request;
@Mock(answer = Answers.RETURNS_DEEP_STUBS) ChannelHandlerContext ctx;
@Mock Publisher publisher;
@Mock Version version;
HttpServerResponseImpl response;
@Captor ArgumentCaptor<LoggedEvent> eventCaptor;
@Before
public void before() {
nettyRequest = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/");
nettyRequest.headers().add(HttpHeaderNames.HOST, host);
request = new HttpServerRequestImpl(nettyRequest, ctx);
response = new HttpServerResponseImpl(version, request, ctx, publisher);
assertThat(response.charset(), is(UTF_8));
}
private void testCachedResource(ServableResource resource) throws IOException {
response.sendCachableResource(resource);
assertThat(response.status(), is(HttpResponseStatus.OK));
assertThat(response.header(HttpHeaderNames.CONTENT_TYPE), is(mime));
assertThat(response.header(HttpHeaderNames.ETAG), is(sha1));
assertThat(response.header(HttpHeaderNames.CONTENT_LENGTH), is(String.valueOf(size)));
assertThat(response.header(HttpHeaderNames.CACHE_CONTROL), is(HttpServerResponse.MAX_AGE_ONE_YEAR));
}
private void testUncachedResource(ServableResource resource) throws IOException {
response.sendUncachableResource(resource);
assertThat(response.status(), is(HttpResponseStatus.OK));
assertThat(response.header(HttpHeaderNames.CONTENT_TYPE), is(mime));
assertThat(response.header(HttpHeaderNames.ETAG), is(sha1));
assertThat(response.header(HttpHeaderNames.CONTENT_LENGTH), is(String.valueOf(size)));
assertThat(response.header(HttpHeaderNames.CACHE_CONTROL), is(HttpHeaderValues.NO_CACHE.toString()));
}
private void testCachedNotModifiedResource(ServableResource resource) throws IOException {
response.sendNotModified(resource, true);
assertThat(response.status(), is(HttpResponseStatus.NOT_MODIFIED));
assertThat(response.header(HttpHeaderNames.ETAG), is(sha1));
assertThat(response.header(HttpHeaderNames.CACHE_CONTROL), is(HttpServerResponse.MAX_AGE_ONE_YEAR));
assertThat(response.hasNoBody(), is(true));
}
private void testUncachedNotModifiedResource(ServableResource resource) throws IOException {
response.sendNotModified(resource);
assertThat(response.status(), is(HttpResponseStatus.NOT_MODIFIED));
assertThat(response.header(HttpHeaderNames.ETAG), is(sha1));
assertThat(response.containsHeader(HttpHeaderNames.CACHE_CONTROL), is(false));
assertThat(response.hasNoBody(), is(true));
}
private void verifyInlineResponse() {
verify(ctx, times(2)).write(any());
verify(ctx).writeAndFlush(any());
verifyNoMoreInteractions(ctx);
verifyRequestRespondedIsPublished();
}
private void verifyRequestRespondedIsPublished() {
// since we ALWAYS respond, every test will call this, so
// we capture all publications here
verify(publisher, atLeastOnce()).publish(eventCaptor.capture());
verifyEventIsPublished(RequestResponded.class);
}
private void verifyEventIsPublished(Class<?> eventType) {
boolean found = false;
for (LoggedEvent event : eventCaptor.getAllValues()) {
if (eventType.isInstance(event)) {
found = true;
break;
}
}
assertTrue("could not verify the RequestResponded event was properly fired", found);
}
@Test
public void testNotFound() {
response.sendNotFound();
assertThat(response.status(), is(HttpResponseStatus.NOT_FOUND));
verifyInlineResponse();
}
@Test
public void testError() {
Exception e = new Exception();
response.error(e);
assertThat(response.status(), is(HttpResponseStatus.INTERNAL_SERVER_ERROR));
verifyInlineResponse();
verifyEventIsPublished(RequestErrored.class);
}
LoadedResource givenALoadedResource() throws IOException {
LoadedResource lr = mock(LoadedResource.class);
given(lr.sha1()).willReturn(sha1);
given(lr.contentType()).willReturn(mime);
given(lr.size()).willReturn(size);
given(lr.bytes()).willReturn(bytes);
return lr;
}
@Test
public void testCachedLoadedResource() throws IOException {
testCachedResource(givenALoadedResource());
verifyInlineResponse();
}
@Test
public void testUncachedLoadedResource() throws IOException {
testUncachedResource(givenALoadedResource());
verifyInlineResponse();
}
@Test
public void testCachedNotModifiedLoadedResource() throws IOException {
testCachedNotModifiedResource(givenALoadedResource());
verifyInlineResponse();
}
@Test
public void testUncachedNotModifiedLoadedResource() throws IOException {
testUncachedNotModifiedResource(givenALoadedResource());
verifyInlineResponse();
}
TransferableResource givenATransferableResource() throws IOException {
TransferableResource tr = mock(TransferableResource.class);
given(tr.sha1()).willReturn(sha1);
given(tr.contentType()).willReturn(mime);
given(tr.size()).willReturn(size);
given(tr.fileChannel()).willReturn(mock(FileChannel.class));
return tr;
}
private void verifyTransferredResponse() {
// this verifies the response write, and the file region write
verify(ctx, times(2)).write(any());
// this verifies the LastHttpContent
verify(ctx).writeAndFlush(anyObject());
verifyNoMoreInteractions(ctx);
verifyRequestRespondedIsPublished();
}
@Test
public void testCachedTransferableResource() throws IOException {
testCachedResource(givenATransferableResource());
verifyTransferredResponse();
}
@Test
public void testUncachedTransferableResource() throws IOException {
testUncachedResource(givenATransferableResource());
verifyTransferredResponse();
}
@Test
public void testCachedNotModifiedTransferableResource() throws IOException {
testCachedNotModifiedResource(givenATransferableResource());
verifyInlineResponse();
}
@Test
public void testUncachedNotModifiedTransferableResource() throws IOException {
testUncachedNotModifiedResource(givenATransferableResource());
verifyInlineResponse();
}
}