blob: cf6d5aa95b0651b20cf211a93c8438c39b82e13c [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.servicecomb.foundation.vertx.http;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import javax.servlet.http.Part;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Response.StatusType;
import org.apache.commons.io.FileUtils;
import org.apache.servicecomb.foundation.common.http.HttpStatus;
import org.apache.servicecomb.foundation.common.part.FilePart;
import org.apache.servicecomb.foundation.vertx.stream.PumpFromPart;
import org.hamcrest.MatcherAssert;
import org.hamcrest.Matchers;
import org.junit.Before;
import org.junit.Test;
import org.junit.jupiter.api.Assertions;
import io.vertx.core.AsyncResult;
import io.vertx.core.Context;
import io.vertx.core.Handler;
import io.vertx.core.MultiMap;
import io.vertx.core.Promise;
import io.vertx.core.Vertx;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.http.HttpServerResponse;
import io.vertx.core.impl.SyncContext;
import io.vertx.core.streams.WriteStream;
import mockit.Deencapsulation;
import mockit.Expectations;
import mockit.Mock;
import mockit.MockUp;
import mockit.Mocked;
public class TestVertxServerResponseToHttpServletResponse {
MultiMap headers = MultiMap.caseInsensitiveMultiMap();
HttpStatus httpStatus = new HttpStatus(123, "default");
HttpServerResponse serverResponse;
VertxServerResponseToHttpServletResponse response;
boolean flushWithBody;
boolean runOnContextInvoked;
@Mocked
Vertx vertx;
@Mocked
Context context;
boolean chunked;
@Before
public void setup() {
serverResponse = new MockUp<HttpServerResponse>() {
@Mock
HttpServerResponse setStatusCode(int statusCode) {
Deencapsulation.setField(httpStatus, "statusCode", statusCode);
return serverResponse;
}
@Mock
HttpServerResponse setStatusMessage(String statusMessage) {
Deencapsulation.setField(httpStatus, "reason", statusMessage);
return serverResponse;
}
@Mock
int getStatusCode() {
return httpStatus.getStatusCode();
}
@Mock
String getStatusMessage() {
return httpStatus.getReasonPhrase();
}
@Mock
MultiMap headers() {
return headers;
}
@Mock
HttpServerResponse putHeader(String name, String value) {
headers.set(name, value);
return serverResponse;
}
@Mock
void end() {
flushWithBody = false;
}
@Mock
void end(Buffer chunk) {
flushWithBody = true;
}
@Mock
HttpServerResponse setChunked(boolean chunked) {
TestVertxServerResponseToHttpServletResponse.this.chunked = chunked;
return serverResponse;
}
@Mock
boolean isChunked() {
return chunked;
}
}.getMockInstance();
new Expectations() {
{
Vertx.currentContext();
result = context;
}
};
new MockUp<Context>(context) {
@Mock
void runOnContext(Handler<Void> action) {
runOnContextInvoked = true;
action.handle(null);
}
@Mock
<T> void executeBlocking(Handler<Promise<T>> blockingCodeHandler, boolean ordered,
Handler<AsyncResult<T>> resultHandler) {
SyncContext.syncExecuteBlocking(blockingCodeHandler, resultHandler);
}
@Mock
Vertx owner() {
return vertx;
}
};
response = new VertxServerResponseToHttpServletResponse(serverResponse);
}
@Test
public void construct_invalid() throws IOException {
new Expectations() {
{
Vertx.currentContext();
result = null;
}
};
NullPointerException exception = Assertions.assertThrows(NullPointerException.class, () -> new VertxServerResponseToHttpServletResponse(serverResponse));
Assertions.assertEquals("must run in vertx context.", exception.getMessage());
}
@Test
public void setContentType() {
response.setContentType("json");
Assertions.assertEquals("json", headers.get(HttpHeaders.CONTENT_TYPE));
}
@SuppressWarnings("deprecation")
@Test
public void setStatus() {
response.setStatus(222, "test");
Assertions.assertEquals(222, httpStatus.getStatusCode());
Assertions.assertEquals("test", httpStatus.getReasonPhrase());
}
@Test
public void getStatusType() {
StatusType status = response.getStatusType();
Assertions.assertSame(status, response.getStatusType());
Assertions.assertEquals(123, httpStatus.getStatusCode());
Assertions.assertEquals("default", httpStatus.getReasonPhrase());
}
@Test
public void addHeader() {
response.addHeader("n1", "v1_1");
response.addHeader("n1", "v1_2");
response.addHeader("n2", "v2");
Assertions.assertEquals(2, headers.size());
MatcherAssert.assertThat(headers.getAll("n1"), Matchers.contains("v1_1", "v1_2"));
MatcherAssert.assertThat(headers.getAll("n2"), Matchers.contains("v2"));
}
@Test
public void setHeader() {
response.setHeader("n1", "v1_1");
response.setHeader("n1", "v1_2");
response.setHeader("n2", "v2");
Assertions.assertEquals(2, headers.size());
MatcherAssert.assertThat(headers.getAll("n1"), Matchers.contains("v1_2"));
MatcherAssert.assertThat(headers.getAll("n2"), Matchers.contains("v2"));
}
@Test
public void getStatus() {
Assertions.assertEquals(123, response.getStatus());
}
@Test
public void getContentType() {
headers.set(HttpHeaders.CONTENT_TYPE, "json");
Assertions.assertEquals("json", response.getContentType());
}
@Test
public void getHeader() {
headers.set(HttpHeaders.CONTENT_TYPE, "json");
Assertions.assertEquals("json", response.getHeader(HttpHeaders.CONTENT_TYPE));
}
@Test
public void getHeaders() {
headers.add("h1", "h1_1");
headers.add("h1", "h1_2");
MatcherAssert.assertThat(response.getHeaders("h1"), Matchers.contains("h1_1", "h1_2"));
}
@Test
public void getHeaderNames() {
headers.add("h1", "h1");
headers.add("h2", "h2");
MatcherAssert.assertThat(response.getHeaderNames(), Matchers.contains("h1", "h2"));
}
@Test
public void flushBuffer_sameContext() throws IOException {
response.flushBuffer();
Assertions.assertFalse(runOnContextInvoked);
}
@Test
public void flushBuffer_diffContext() throws IOException {
new Expectations() {
{
Vertx.currentContext();
result = null;
}
};
response.flushBuffer();
Assertions.assertTrue(runOnContextInvoked);
}
@Test
public void internalFlushBufferNoBody() throws IOException {
response.internalFlushBuffer();
Assertions.assertFalse(flushWithBody);
}
@Test
public void internalFlushBufferWithBody() throws IOException {
response.setBodyBuffer(Buffer.buffer());
response.internalFlushBuffer();
Assertions.assertTrue(flushWithBody);
}
@Test
public void prepareSendPartHeader_update(@Mocked Part part) {
new Expectations() {
{
part.getContentType();
result = "type";
part.getSubmittedFileName();
result = "测 试";
}
};
DownloadUtils.prepareDownloadHeader(response, part);
Assertions.assertTrue(serverResponse.isChunked());
Assertions.assertEquals("type", response.getHeader(HttpHeaders.CONTENT_TYPE));
Assertions.assertEquals(
"attachment;filename=%E6%B5%8B%20%20%20%20%20%E8%AF%95;filename*=utf-8''%E6%B5%8B%20%20%20%20%20%E8%AF%95",
response.getHeader(HttpHeaders.CONTENT_DISPOSITION));
}
@Test
public void prepareSendPartHeader_notUpdate(@Mocked Part part) {
headers.add(HttpHeaders.CONTENT_LENGTH, "10");
headers.add(HttpHeaders.CONTENT_TYPE, "type");
headers.add(HttpHeaders.CONTENT_DISPOSITION, "disposition");
DownloadUtils.prepareDownloadHeader(response, part);
Assertions.assertFalse(serverResponse.isChunked());
Assertions.assertEquals("type", response.getHeader(HttpHeaders.CONTENT_TYPE));
Assertions.assertEquals("disposition", response.getHeader(HttpHeaders.CONTENT_DISPOSITION));
}
@Test
public void sendPart_openInputStreamFailed(@Mocked Part part)
throws IOException {
IOException ioException = new IOException("forbid open stream");
new Expectations() {
{
part.getInputStream();
result = ioException;
}
};
CompletableFuture<Void> future = response.sendPart(part);
ExecutionException exception = Assertions.assertThrows(ExecutionException.class, future::get);
Assertions.assertTrue(exception.getCause() instanceof IOException);
}
@Test
public void sendPart_testPartIsNull(@Mocked Part part) throws InterruptedException, ExecutionException {
CompletableFuture<Void> future1 = response.sendPart(null);
Assertions.assertNull(future1.get());
}
@Test
public void sendPart_inputStreamBreak(@Mocked Part part, @Mocked InputStream inputStream)
throws IOException {
IOException ioException = new IOException("forbid read");
new Expectations() {
{
part.getInputStream();
result = inputStream;
inputStream.read((byte[]) any);
result = ioException;
}
};
CompletableFuture<Void> future = response.sendPart(part);
ExecutionException exception = Assertions.assertThrows(ExecutionException.class, future::get);
Assertions.assertTrue(exception.getCause() instanceof IOException);
}
@Test
public void sendPart_ReadStreamPart(@Mocked ReadStreamPart part) {
CompletableFuture<Void> future = new CompletableFuture<>();
new MockUp<PumpFromPart>() {
@Mock
CompletableFuture<Void> toWriteStream(WriteStream<Buffer> writeStream, Handler<Throwable> throwableHandler) {
return future;
}
};
Assertions.assertSame(future, response.sendPart(part));
}
@Test
public void sendPart_succ(@Mocked Part part, @Mocked InputStream inputStream)
throws IOException, InterruptedException, ExecutionException {
new Expectations() {
{
part.getInputStream();
result = inputStream;
inputStream.read((byte[]) any);
result = -1;
}
};
CompletableFuture<Void> future = response.sendPart(part);
Assertions.assertNull(future.get());
}
@Test
public void clearPartResource_deleteFile() throws IOException {
File file = new File("target", UUID.randomUUID() + ".txt");
FileUtils.write(file, "content", StandardCharsets.UTF_8);
FilePart part = new FilePart(null, file).setDeleteAfterFinished(true);
Assertions.assertTrue(file.exists());
DownloadUtils.clearPartResource(part);
Assertions.assertFalse(file.exists());
}
@Test
public void clearPartResource_notDeleteFile() throws IOException {
File file = new File("target", UUID.randomUUID() + ".txt");
FileUtils.write(file, "content", StandardCharsets.UTF_8);
FilePart part = new FilePart(null, file);
Assertions.assertTrue(file.exists());
DownloadUtils.clearPartResource(part);
Assertions.assertTrue(file.exists());
file.delete();
}
}