/**
* Copyright (C) 2013-2015 all@code-story.net
*
* 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 net.codestory.http.payload;
import net.codestory.http.Cookie;
import net.codestory.http.Cookies;
import net.codestory.http.Request;
import net.codestory.http.Response;
import net.codestory.http.compilers.CompilerFacade;
import net.codestory.http.io.Resources;
import net.codestory.http.misc.Env;
import net.codestory.http.templating.Site;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Optional;
import java.util.stream.Stream;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Collections.emptyList;
import static java.util.Collections.emptyMap;
import static java.util.Collections.singletonMap;
import static net.codestory.http.constants.Headers.*;
import static net.codestory.http.constants.HttpStatus.*;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.*;
public class PayloadWriterTest {
static Env env = Env.dev();
static Resources resources = new Resources(env);
static Site site = new Site(env, resources);
static CompilerFacade compilerFacade = new CompilerFacade(env, resources);
Request request = mock(Request.class);
Response response = mock(Response.class);
OutputStream outputStream = mock(OutputStream.class);
Cookies cookies = mock(Cookies.class);
PayloadWriter writer = new PayloadWriter(request, response, env, site, resources, compilerFacade);
@Before
public void setupContext() throws IOException {
when(request.cookies()).thenReturn(cookies);
when(response.outputStream()).thenReturn(outputStream);
}
@Test
public void support_string() throws IOException {
writer.write(new Payload(new Payload("Hello")));
verify(response).setStatus(200);
verify(response).setHeader(CONTENT_TYPE, "text/html;charset=UTF-8");
verify(outputStream).write("Hello".getBytes(UTF_8));
}
@Test
public void support_byte_array() throws IOException {
byte[] bytes = "Hello".getBytes(UTF_8);
writer.write(new Payload(bytes));
verify(response).setHeader(CONTENT_TYPE, "application/octet-stream");
verify(outputStream).write(bytes);
}
@Test
public void support_bean_to_json() throws IOException {
writer.write(new Payload(new Person("NAME", 42)));
verify(response).setHeader(CONTENT_TYPE, "application/json;charset=UTF-8");
verify(outputStream).write("{\"name\":\"NAME\",\"age\":42}".getBytes(UTF_8));
}
@Test
public void support_custom_content_type() throws IOException {
writer.write(new Payload("text/plain", "Hello"));
verify(response).setHeader(CONTENT_TYPE, "text/plain");
verify(outputStream).write("Hello".getBytes(UTF_8));
}
@Test
public void support_stream() throws IOException {
writer.write(new Payload("text/plain", new ByteArrayInputStream("Hello".getBytes(UTF_8))));
verify(response).setHeader(CONTENT_TYPE, "text/plain");
verify(response).setHeader(CACHE_CONTROL, "no-cache");
verify(response).setHeader(CONNECTION, "keep-alive");
ArgumentCaptor<byte[]> bytesCaptor = ArgumentCaptor.forClass(byte[].class);
verify(outputStream).write(bytesCaptor.capture(), eq(0), eq(5));
assertThat(bytesCaptor.getValue()).startsWith((byte) 'H', (byte) 'e', (byte) 'l', (byte) 'l', (byte) 'o');
}
@Test
public void should_close_stream_on_interrupted_connection() throws IOException {
Runnable closeHandler = mock(Runnable.class);
doThrow(IOException.class).when(outputStream).write(any(byte[].class), anyInt(), anyInt());
writer.writeEventStream(new Payload(Stream.iterate(1, i -> i + 1)
.peek(i -> {
if (i > 1) throw new RuntimeException("It should never reach that point");
})
.onClose(closeHandler)));
verify(closeHandler).run();
}
@Test
public void support_present_optional() throws IOException {
writer.write(new Payload("text/plain", Optional.of("TEXT")));
verify(response).setHeader(CONTENT_TYPE, "text/plain");
verify(outputStream).write("TEXT".getBytes(UTF_8));
}
@Test
public void support_absent_optional() throws IOException {
Payload payload = new Payload("text/plain", Optional.empty());
writer.write(payload);
verify(response).setHeaders(emptyMap());
verify(response).setCookies(emptyList());
verify(response).setStatus(NOT_FOUND);
verify(response).setContentLength(0);
verifyNoMoreInteractions(response);
}
@Test
public void json_cookie() {
Payload payload = Payload.ok();
payload.withCookie("person", new Person("Bob", 42));
Cookie cookie = payload.cookies().get(0);
assertThat(cookie.name()).isEqualTo("person");
assertThat(cookie.value()).isEqualTo("{\"name\":\"Bob\",\"age\":42}");
}
@Test
public void redirect() throws IOException {
Payload payload = Payload.seeOther("/url");
writer.write(payload);
verify(response).setHeaders(singletonMap("Location", "/url"));
verify(response).setCookies(emptyList());
verify(response).setStatus(SEE_OTHER);
verify(response).setContentLength(0);
verifyNoMoreInteractions(response);
}
@Test
public void forbidden() throws IOException {
Payload payload = Payload.forbidden();
writer.write(payload);
verify(response).setHeaders(emptyMap());
verify(response).setCookies(emptyList());
verify(response).setStatus(FORBIDDEN);
verify(response).setContentLength(0);
verifyNoMoreInteractions(response);
}
@Test
public void permanent_move() throws IOException {
Payload payload = Payload.movedPermanently("/url");
writer.write(payload);
verify(response).setHeaders(singletonMap("Location", "/url"));
verify(response).setCookies(emptyList());
verify(response).setStatus(MOVED_PERMANENTLY);
verify(response).setContentLength(0);
verifyNoMoreInteractions(response);
}
@Test
public void last_modified() throws IOException {
Path path = Paths.get("hello.md");
Payload payload = new Payload(path);
writer.write(payload);
verify(response).setHeader(eq("Last-Modified"), anyString());
}
@Test
public void etag() throws IOException {
Payload payload = new Payload("Hello");
writer.write(payload);
verify(response).setStatus(OK);
verify(response).setHeader("ETag", "8b1a9953c4611296a827abf8c47804d7");
}
@Test
public void not_modified() throws IOException {
when(request.header("If-None-Match")).thenReturn("8b1a9953c4611296a827abf8c47804d7");
Payload payload = new Payload("Hello");
writer.write(payload);
verify(response).setStatus(NOT_MODIFIED);
}
@Test
public void head() throws IOException {
when(request.method()).thenReturn("HEAD");
Payload payload = new Payload("Hello");
writer.write(payload);
verify(response).setStatus(OK);
verify(response, never()).setContentLength(anyInt());
verify(response, never()).outputStream();
}
static class Person {
String name;
int age;
Person(String name, int age) {
this.name = name;
this.age = age;
}
}
}