package jj.document.servable;
import static jj.AnswerWithSelf.ANSWER_WITH_SELF;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.*;
import static org.mockito.BDDMockito.*;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;
import jj.document.CurrentDocumentRequestProcessor;
import jj.document.DocumentScriptEnvironment;
import jj.document.servable.DocumentFilter;
import jj.document.servable.DocumentRequestProcessor;
import jj.execution.MockTaskRunner;
import jj.http.server.HttpServerRequest;
import jj.http.server.HttpServerResponse;
import jj.http.server.uri.URIMatch;
import jj.script.PendingKey;
import jj.script.DependsOnScriptEnvironmentInitialization;
import jj.script.ScriptTask;
import io.netty.channel.Channel;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpHeaderValues;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.mozilla.javascript.Callable;
@RunWith(MockitoJUnitRunner.class)
public class DocumentRequestProcessorTest {
Document document;
String baseName;
MockTaskRunner taskRunner;
@Mock DependsOnScriptEnvironmentInitialization initializer;
@Mock DocumentScriptEnvironment documentScriptEnvironment;
@Mock Callable callable;
CurrentDocumentRequestProcessor currentDocument;
@Mock Channel channel;
@Mock HttpServerRequest httpRequest;
HttpServerResponse httpResponse;
PendingKey pendingKey;
int filterCalls;
class TestDocumentFilter implements DocumentFilter {
private boolean io;
private int sequence;
TestDocumentFilter(boolean io, int sequence) {
this.io = io;
this.sequence = sequence;
}
@Override
public boolean needsIO(DocumentRequestProcessor documentRequestProcessor) {
return io;
}
@Override
public void filter(DocumentRequestProcessor documentRequestProcessor) {
assertThat(++filterCalls, is(sequence));
}
}
private static final String HTML = "<html><head><title>what</title></head><body></body></html>";
byte[] bytes = HTML.getBytes(UTF_8);
@Before
public void before() {
filterCalls = 0;
document = Jsoup.parse(HTML);
document.outputSettings().prettyPrint(false);
baseName = "baseName";
taskRunner = new MockTaskRunner();
given(documentScriptEnvironment.name()).willReturn(baseName);
given(documentScriptEnvironment.document()).willReturn(document);
given(documentScriptEnvironment.charset()).willReturn(UTF_8);
given(documentScriptEnvironment.contentType()).willReturn("text/html; charset=UTF-8");
given(httpRequest.uriMatch()).willReturn(new URIMatch("/"));
// auto-stubbing the builder pattern
httpResponse = mock(HttpServerResponse.class, ANSWER_WITH_SELF);
pendingKey = new PendingKey();
currentDocument = new CurrentDocumentRequestProcessor();
}
private DocumentRequestProcessor toTest(Set<DocumentFilter> filters) {
return new DocumentRequestProcessor(
taskRunner,
initializer,
currentDocument,
documentScriptEnvironment,
httpRequest,
httpResponse,
filters
);
}
@Test
public void testRespondsDirectlyWhenNoServerScript() throws Exception {
//given
DocumentRequestProcessor toTest = toTest(Collections.<DocumentFilter>emptySet());
toTest.process();
assertThat(taskRunner.tasks.size(), is(1));
assertThat(taskRunner.tasks.get(0), is(instanceOf(ScriptTask.class)));
taskRunner.runUntilIdle();
verify(httpResponse).header(HttpHeaderNames.CONTENT_LENGTH, bytes.length);
verify(httpResponse).header(HttpHeaderNames.CACHE_CONTROL, HttpHeaderValues.NO_STORE);
verify(httpResponse).header(HttpHeaderNames.CONTENT_TYPE, "text/html; charset=UTF-8");
verify(httpResponse).content(bytes);
}
@SuppressWarnings("unchecked")
@Test
public void testWaitsForInitialization() throws Exception {
DocumentRequestProcessor toTest = toTest(Collections.<DocumentFilter>emptySet());
given(documentScriptEnvironment.hasServerScript()).willReturn(true);
toTest.process();
taskRunner.runUntilIdle();
verify(initializer).executeOnInitialization(eq(documentScriptEnvironment), any(ScriptTask.class));
}
@Test
public void testInvokesReadyFunctionWithInitializedScript() throws Exception {
// given
DocumentRequestProcessor toTest = toTest(Collections.<DocumentFilter>emptySet());
given(documentScriptEnvironment.hasServerScript()).willReturn(true);
given(documentScriptEnvironment.initialized()).willReturn(true);
given(documentScriptEnvironment.getFunction(DocumentScriptEnvironment.READY_FUNCTION_KEY)).willReturn(callable);
toTest.process();
taskRunner.runUntilIdle();
verify(documentScriptEnvironment).execute(callable);
verify(httpResponse).header(HttpHeaderNames.CONTENT_LENGTH, bytes.length);
verify(httpResponse).header(HttpHeaderNames.CACHE_CONTROL, HttpHeaderValues.NO_STORE);
verify(httpResponse).header(HttpHeaderNames.CONTENT_TYPE, "text/html; charset=UTF-8");
verify(httpResponse).content(bytes);
verifyZeroInteractions(initializer);
}
@Test
public void testIOFilterBehavior() throws Exception {
// given
Set<DocumentFilter> filters = new LinkedHashSet<>();
filters.add(new TestDocumentFilter(true, 4));
filters.add(new TestDocumentFilter(true, 5));
filters.add(new TestDocumentFilter(false, 1));
filters.add(new TestDocumentFilter(false, 2));
filters.add(new TestDocumentFilter(true, 6));
filters.add(new TestDocumentFilter(false, 3));
DocumentRequestProcessor toTest = toTest(filters);
// when
toTest.respond();
taskRunner.runUntilIdle();
// then
assertThat(filterCalls, is(6));
// TODO can make this smarter later
}
@Test
public void testWritesDocumentCorrectly() throws Exception {
// given
DocumentRequestProcessor toTest = toTest(Collections.<DocumentFilter>emptySet());
// when
toTest.respond();
taskRunner.runUntilIdle();
// then
verify(httpResponse).header(HttpHeaderNames.CONTENT_LENGTH, bytes.length);
verify(httpResponse).header(HttpHeaderNames.CACHE_CONTROL, HttpHeaderValues.NO_STORE);
verify(httpResponse).header(HttpHeaderNames.CONTENT_TYPE, "text/html; charset=UTF-8");
verify(httpResponse).content(bytes);
}
}