blob: b9b91af9ea806a36d0ccfc7bebda73e5fb5ebdd8 [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.cassandra.sidecar.routes;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.vertx.core.Vertx;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.http.HttpServer;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.client.HttpResponse;
import io.vertx.ext.web.client.WebClient;
import io.vertx.ext.web.client.WebClientOptions;
import io.vertx.junit5.VertxExtension;
import io.vertx.junit5.VertxTestContext;
import static org.assertj.core.api.Assertions.assertThat;
@ExtendWith(VertxExtension.class)
class VertxRoutingTest
{
private Vertx vertx;
private HttpServer server;
private WebClient client;
@BeforeEach
void setup(VertxTestContext context)
{
vertx = Vertx.vertx();
server = vertx.createHttpServer();
client = WebClient.create(vertx, new WebClientOptions());
context.completeNow();
}
@AfterEach
void teardown(VertxTestContext context) throws Exception
{
if (vertx == null)
{
return;
}
if (client != null)
{
client.close();
}
vertx.close(result -> context.completeNow());
assertThat(context.awaitCompletion(10, TimeUnit.SECONDS)).isTrue();
}
@Test
void testRoutesWithSameOrder(VertxTestContext context) throws Exception
{
/*
* For routes declared with same order, they should continue serving requests
*/
CountDownLatch serverReady = new CountDownLatch(1);
Router router = Router.router(vertx);
router.get("/endpoint1").order(1).handler(ctx -> {
ctx.response().end("Endpoint 1 OK");
});
router.get("/endpoint2").order(1).handler(ctx -> {
ctx.response().end("Endpoint 2 OK");
});
server.requestHandler(router).listen(0, result -> serverReady.countDown());
assertThat(serverReady.await(10, TimeUnit.SECONDS))
.isTrue()
.describedAs("Server should be up");
asyncVerifyRequest("/endpoint1", context, response -> {
assertThat(response.statusCode()).isEqualTo(HttpResponseStatus.OK.code());
assertThat(response.bodyAsString()).isEqualTo("Endpoint 1 OK");
});
asyncVerifyRequest("/endpoint2", context, response -> {
assertThat(response.statusCode()).isEqualTo(HttpResponseStatus.OK.code());
assertThat(response.bodyAsString()).isEqualTo("Endpoint 2 OK");
});
}
@Test
void testHandlersWithSameRouteSameOrder(VertxTestContext context) throws Exception
{
/*
* For handlers added to the routes (but same path) with the same order, it is evaluated as the adding order.
*/
CountDownLatch serverReady = new CountDownLatch(1);
Router router = Router.router(vertx);
router.get("/endpoint").order(1).handler(ctx -> {
ctx.response()
.setChunked(true) // required to be `true` for adding data from multiple handlers
.write("handler 1\n");
ctx.next();
});
router.get("/endpoint").order(1).handler(ctx -> {
ctx.response().end("handler 2");
});
server.requestHandler(router).listen(0, result -> serverReady.countDown());
assertThat(serverReady.await(10, TimeUnit.SECONDS))
.isTrue()
.describedAs("Server should be up");
asyncVerifyRequest("/endpoint", context, response -> {
assertThat(response.statusCode()).isEqualTo(HttpResponseStatus.OK.code());
assertThat(response.bodyAsString()).isEqualTo("handler 1\n" +
"handler 2");
});
}
@Test
void testLeveledRoutesWithSameOrder(VertxTestContext context) throws Exception
{
/*
* For the leveled routes that is declared with the same order,
* the effective evaluation order is the adding order
*/
CountDownLatch serverReady = new CountDownLatch(1);
Router router = Router.router(vertx);
router.route().order(1).handler(ctx -> {
ctx.response()
.setChunked(true) // required to be `true` for adding data from multiple handlers
.write("root\n");
ctx.next();
});
router.get("/endpoint").order(1).handler(ctx -> {
ctx.response().end("endpoint");
});
server.requestHandler(router).listen(0, result -> serverReady.countDown());
assertThat(serverReady.await(10, TimeUnit.SECONDS))
.isTrue()
.describedAs("Server should be up");
asyncVerifyRequest("/endpoint", context, response -> {
assertThat(response.statusCode()).isEqualTo(HttpResponseStatus.OK.code());
assertThat(response.bodyAsString()).isEqualTo("root\n" +
"endpoint");
});
}
@Test
void testLeveledRoutesWithSameOrderReversedDeclaration(VertxTestContext context) throws Exception
{
/*
* Similar to testLeveledRoutesWithSameOrder, but the root route is declared after `/endpoint`.
* Since `/endpoint` ends the response and does not forward the evaluation,
* the root route handler is not called.
*/
AtomicBoolean rootEvaluated = new AtomicBoolean(false);
CountDownLatch serverReady = new CountDownLatch(1);
Router router = Router.router(vertx);
// NOTE: the routes declaration is swapped
router.get("/endpoint").order(1).handler(ctx -> {
ctx.response().end("endpoint");
});
router.route().order(1).handler(ctx -> {
rootEvaluated.set(true);
ctx.response()
.setChunked(true) // required to be `true` for adding data from multiple handlers
.write("root\n");
ctx.next();
});
server.requestHandler(router).listen(0, result -> serverReady.countDown());
assertThat(serverReady.await(10, TimeUnit.SECONDS))
.isTrue()
.describedAs("Server should be up");
asyncVerifyRequest("/endpoint", context, response -> {
assertThat(response.statusCode()).isEqualTo(HttpResponseStatus.OK.code());
assertThat(response.bodyAsString()).isEqualTo("endpoint");
assertThat(rootEvaluated.get()).isFalse().describedAs("Root route handler is not reached");
});
}
private void asyncVerifyRequest(String requestURI,
VertxTestContext context,
Consumer<HttpResponse<Buffer>> responseConsumer)
{
client.get(server.actualPort(), "localhost", requestURI)
.send(context.succeeding(response -> {
context.verify(() -> responseConsumer.accept(response))
.completeNow();
}));
}
}