|
|
@@ -6,8 +6,10 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
|
|
import cz.senslog.telemetry.TestPropertiesUtils;
|
|
|
import cz.senslog.telemetry.app.PropertyConfig;
|
|
|
import cz.senslog.telemetry.database.domain.Campaign;
|
|
|
+import cz.senslog.telemetry.database.domain.CampaignUnit;
|
|
|
import cz.senslog.telemetry.database.repository.SensLogRepository;
|
|
|
import cz.senslog.telemetry.server.HttpVertxServer;
|
|
|
+import cz.senslog.telemetry.utils.Tuple;
|
|
|
import io.vertx.core.*;
|
|
|
import io.vertx.core.http.HttpClientResponse;
|
|
|
import io.vertx.core.http.HttpHeaders;
|
|
|
@@ -18,6 +20,9 @@ import io.vertx.junit5.VertxExtension;
|
|
|
import io.vertx.junit5.VertxTestContext;
|
|
|
import org.junit.jupiter.api.*;
|
|
|
import org.junit.jupiter.api.extension.ExtendWith;
|
|
|
+import org.junit.jupiter.params.ParameterizedTest;
|
|
|
+import org.junit.jupiter.params.provider.Arguments;
|
|
|
+import org.junit.jupiter.params.provider.MethodSource;
|
|
|
import org.mockito.Mockito;
|
|
|
import org.openapi4j.core.exception.EncodeException;
|
|
|
import org.openapi4j.core.exception.ResolutionException;
|
|
|
@@ -25,7 +30,6 @@ import org.openapi4j.core.validation.ValidationException;
|
|
|
import org.openapi4j.parser.OpenApi3Parser;
|
|
|
import org.openapi4j.parser.model.v3.OpenApi3;
|
|
|
import org.openapi4j.parser.model.v3.Operation;
|
|
|
-import org.openapi4j.parser.model.v3.Schema;
|
|
|
import org.openapi4j.schema.validator.ValidationContext;
|
|
|
import org.openapi4j.schema.validator.ValidationData;
|
|
|
import org.openapi4j.schema.validator.v3.SchemaValidator;
|
|
|
@@ -35,38 +39,61 @@ import uk.org.webcompere.systemstubs.jupiter.SystemStubsExtension;
|
|
|
|
|
|
import java.net.MalformedURLException;
|
|
|
import java.net.URL;
|
|
|
-import java.time.LocalDateTime;
|
|
|
-import java.time.OffsetDateTime;
|
|
|
-import java.time.ZoneOffset;
|
|
|
+import java.time.*;
|
|
|
+import java.util.Collections;
|
|
|
import java.util.List;
|
|
|
import java.util.Map;
|
|
|
import java.util.Properties;
|
|
|
+import java.util.stream.Stream;
|
|
|
|
|
|
import static cz.senslog.telemetry.server.ws.ContentType.JSON;
|
|
|
+import static cz.senslog.telemetry.server.ws.OpenAPIHandlerTest.OpenAPIResponseTyp.SUCCESS;
|
|
|
+import static cz.senslog.telemetry.server.ws.OpenAPIHandlerTest.OpenAPIResponseTyp.ERROR;
|
|
|
+import static java.time.format.DateTimeFormatter.ISO_OFFSET_DATE_TIME;
|
|
|
import static org.assertj.core.api.Assertions.assertThat;
|
|
|
+import static org.mockito.ArgumentMatchers.anyLong;
|
|
|
|
|
|
@ExtendWith({VertxExtension.class, SystemStubsExtension.class})
|
|
|
class OpenAPIHandlerTest {
|
|
|
|
|
|
private static final int PORT = 8080;
|
|
|
private static final String HOST = "localhost";
|
|
|
+
|
|
|
+ private static final Instant BASE_INSTANT_TIMESTAMP = LocalDateTime.of(2023, 1, 1, 0, 0).toInstant(ZoneOffset.UTC);
|
|
|
+
|
|
|
private static OpenApi3 OPEN_API;
|
|
|
|
|
|
+ enum OpenAPIResponseTyp {
|
|
|
+ SUCCESS ("200"), ERROR ("default")
|
|
|
+ ;
|
|
|
+
|
|
|
+ private final String type;
|
|
|
+
|
|
|
+ OpenAPIResponseTyp(String type) {
|
|
|
+ this.type = type;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
@SystemStub
|
|
|
private EnvironmentVariables envVariable;
|
|
|
|
|
|
+ private SensLogRepository repo;
|
|
|
+
|
|
|
private static final ObjectMapper mapper = new ObjectMapper();
|
|
|
|
|
|
- private static void validateSuccess(String bodyResponse, Operation operation, ContentType contentType) throws EncodeException, JsonProcessingException {
|
|
|
- JsonNode responseResult = mapper.readTree(bodyResponse);
|
|
|
- Schema schema = operation.getResponse("200").getContentMediaType(contentType.contentType()).getSchema();
|
|
|
- JsonNode schemaNode = schema.toNode();
|
|
|
+ private static void validateAssert(String bodyResponse, Operation operation, OpenAPIResponseTyp responseTyp, ContentType contentType) throws EncodeException, JsonProcessingException {
|
|
|
+ JsonNode responseResult = toJsonNode(bodyResponse);
|
|
|
+ JsonNode schemaNode = operation.getResponse(responseTyp.type).getContentMediaType(contentType.contentType()).getSchema().toNode();
|
|
|
SchemaValidator schemaValidator = new SchemaValidator(new ValidationContext<>(OPEN_API.getContext()), null, schemaNode);
|
|
|
ValidationData<Void> validation = new ValidationData<>();
|
|
|
schemaValidator.validate(responseResult, validation);
|
|
|
assertThat(validation.isValid()).overridingErrorMessage(validation.results().toString()).isTrue();
|
|
|
}
|
|
|
|
|
|
+ private static JsonNode toJsonNode(String bodyResponse) throws JsonProcessingException {
|
|
|
+ return mapper.readTree(bodyResponse);
|
|
|
+ }
|
|
|
+
|
|
|
@BeforeAll
|
|
|
static void setUp() throws MalformedURLException, ResolutionException, ValidationException {
|
|
|
OPEN_API = new OpenApi3Parser().parse(new URL("file:src/main/resources/openAPISpec.yaml"), false);
|
|
|
@@ -84,14 +111,7 @@ class OpenAPIHandlerTest {
|
|
|
envVariable.set(propEntry.getKey(), propEntry.getValue());
|
|
|
}
|
|
|
|
|
|
- SensLogRepository repo = Mockito.mock(SensLogRepository.class);
|
|
|
-
|
|
|
- OffsetDateTime baseTimestamp = OffsetDateTime.of(LocalDateTime.of(2023, 1, 1, 0, 0), ZoneOffset.UTC);
|
|
|
-
|
|
|
- Mockito.when(repo.allCampaigns()).thenReturn(Future.succeededFuture(List.of(
|
|
|
- Campaign.of(1, "mock(description)", baseTimestamp, baseTimestamp.plusYears(1)),
|
|
|
- Campaign.of(2, "mock(description)", baseTimestamp, baseTimestamp.plusYears(2))
|
|
|
- )));
|
|
|
+ repo = Mockito.mock(SensLogRepository.class);
|
|
|
|
|
|
PropertyConfig config = PropertyConfig.getInstance();
|
|
|
vertx.deployVerticle(new HttpVertxServer(repo), new DeploymentOptions().setConfig(JsonObject.of(
|
|
|
@@ -109,7 +129,7 @@ class OpenAPIHandlerTest {
|
|
|
}
|
|
|
|
|
|
@Test
|
|
|
- @DisplayName("API Test: infoGET")
|
|
|
+ @DisplayName("API Spec Test: infoGET")
|
|
|
void infoGET(Vertx vertx, VertxTestContext testContext) {
|
|
|
Operation operation = OPEN_API.getOperationById("infoGET");
|
|
|
RequestOptions reqOpt = new RequestOptions()
|
|
|
@@ -119,14 +139,38 @@ class OpenAPIHandlerTest {
|
|
|
vertx.createHttpClient().request(reqOpt)
|
|
|
.compose(req -> req.send().compose(HttpClientResponse::body))
|
|
|
.onComplete(testContext.succeeding(b -> testContext.verify(() -> {
|
|
|
- validateSuccess(b.toString(), operation, JSON);
|
|
|
+ validateAssert(b.toString(), operation, SUCCESS, JSON);
|
|
|
testContext.completeNow();
|
|
|
})));
|
|
|
}
|
|
|
|
|
|
- @Test
|
|
|
- @DisplayName("API Test: campaignsGET")
|
|
|
- void campaignsGET(Vertx vertx, VertxTestContext testContext) {
|
|
|
+ private static Stream<Arguments> campaignsGET_dataSource() {
|
|
|
+ OffsetDateTime baseTimestamp = OffsetDateTime.ofInstant(BASE_INSTANT_TIMESTAMP, ZoneOffset.UTC);
|
|
|
+ return Stream.of(
|
|
|
+ Arguments.of(
|
|
|
+ Future.succeededFuture(List.of(
|
|
|
+ Campaign.of(1, "mock(description)", baseTimestamp, baseTimestamp.plusYears(1)),
|
|
|
+ Campaign.of(2, "mock(description)", baseTimestamp, baseTimestamp.plusYears(2)))),
|
|
|
+ SUCCESS, JSON
|
|
|
+ ),
|
|
|
+ Arguments.of(
|
|
|
+ Future.succeededFuture(Collections.emptyList()),
|
|
|
+ SUCCESS, JSON
|
|
|
+ ),
|
|
|
+ Arguments.of(
|
|
|
+ Future.failedFuture("DB Failure"),
|
|
|
+ ERROR, JSON
|
|
|
+ )
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ @ParameterizedTest
|
|
|
+ @MethodSource("campaignsGET_dataSource")
|
|
|
+ @DisplayName("API Spec Test: campaignsGET")
|
|
|
+ void campaignsGET_bySpec(Future<List<Campaign>> dbFuture, OpenAPIResponseTyp responseTyp, ContentType contentType, Vertx vertx, VertxTestContext testContext) {
|
|
|
+
|
|
|
+ Mockito.when(repo.allCampaigns()).thenReturn(dbFuture);
|
|
|
+
|
|
|
Operation operation = OPEN_API.getOperationById("campaignsGET");
|
|
|
RequestOptions reqOpt = new RequestOptions()
|
|
|
.setMethod(HttpMethod.GET).setPort(PORT).setHost(HOST).setURI("/campaigns")
|
|
|
@@ -136,9 +180,195 @@ class OpenAPIHandlerTest {
|
|
|
vertx.createHttpClient().request(reqOpt)
|
|
|
.compose(req -> req.send().compose(HttpClientResponse::body))
|
|
|
.onComplete(testContext.succeeding(b -> testContext.verify(() -> {
|
|
|
- validateSuccess(b.toString(), operation, JSON);
|
|
|
+ validateAssert(b.toString(), operation, responseTyp, contentType);
|
|
|
+ testContext.completeNow();
|
|
|
+ })));
|
|
|
+ }
|
|
|
+
|
|
|
+ private static Stream<Arguments> campaignIdGET_dataSource() {
|
|
|
+ OffsetDateTime baseTimestamp = OffsetDateTime.ofInstant(BASE_INSTANT_TIMESTAMP, ZoneOffset.UTC);
|
|
|
+ return Stream.of(
|
|
|
+ Arguments.of(
|
|
|
+ Future.succeededFuture(Campaign.of(1, "mock(description)", baseTimestamp, baseTimestamp.plusYears(1))),
|
|
|
+ SUCCESS, JSON
|
|
|
+ ),
|
|
|
+ Arguments.of(
|
|
|
+ Future.failedFuture("Not found"),
|
|
|
+ ERROR, JSON
|
|
|
+ )
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ @ParameterizedTest
|
|
|
+ @MethodSource("campaignIdGET_dataSource")
|
|
|
+ @DisplayName("API Spec Test: campaignIdGET")
|
|
|
+ void campaignIdGET_bySpec(Future<Campaign> dbFuture, OpenAPIResponseTyp responseTyp, ContentType contentType, Vertx vertx, VertxTestContext testContext) {
|
|
|
+
|
|
|
+ Mockito.when(repo.findCampaignById(anyLong())).thenReturn(dbFuture);
|
|
|
+
|
|
|
+ Operation operation = OPEN_API.getOperationById("campaignIdGET");
|
|
|
+ RequestOptions reqOpt = new RequestOptions()
|
|
|
+ .setMethod(HttpMethod.GET).setPort(PORT).setHost(HOST).setURI("/campaigns/1")
|
|
|
+ .setHeaders(MultiMap.caseInsensitiveMultiMap()
|
|
|
+ .add(HttpHeaders.ACCEPT, JSON.contentType()));
|
|
|
+
|
|
|
+ vertx.createHttpClient().request(reqOpt)
|
|
|
+ .compose(req -> req.send().compose(HttpClientResponse::body))
|
|
|
+ .onComplete(testContext.succeeding(b -> testContext.verify(() -> {
|
|
|
+ validateAssert(b.toString(), operation, responseTyp, contentType);
|
|
|
testContext.completeNow();
|
|
|
})));
|
|
|
}
|
|
|
|
|
|
+ private static Stream<Arguments> campaignIdGET_Params_dataSource() {
|
|
|
+ OffsetDateTime baseTimestamp = OffsetDateTime.ofInstant(BASE_INSTANT_TIMESTAMP, ZoneOffset.UTC);
|
|
|
+ return Stream.of(
|
|
|
+ Arguments.of(
|
|
|
+ Future.succeededFuture(Campaign.of(1, "mock(description)", baseTimestamp, baseTimestamp.plusYears(1))),
|
|
|
+ "UTC",
|
|
|
+ Tuple.of(
|
|
|
+ OffsetDateTime.ofInstant(BASE_INSTANT_TIMESTAMP, ZoneOffset.UTC).format(ISO_OFFSET_DATE_TIME),
|
|
|
+ OffsetDateTime.ofInstant(BASE_INSTANT_TIMESTAMP, ZoneOffset.UTC).plusYears(1).format(ISO_OFFSET_DATE_TIME)
|
|
|
+ ),
|
|
|
+ false
|
|
|
+
|
|
|
+ ),
|
|
|
+ Arguments.of(
|
|
|
+ Future.succeededFuture(Campaign.of(1, "mock(description)", baseTimestamp, baseTimestamp.plusYears(1))),
|
|
|
+ "%2b2",
|
|
|
+ Tuple.of(
|
|
|
+ OffsetDateTime.ofInstant(BASE_INSTANT_TIMESTAMP, ZoneOffset.ofHours(2)).format(ISO_OFFSET_DATE_TIME),
|
|
|
+ OffsetDateTime.ofInstant(BASE_INSTANT_TIMESTAMP, ZoneOffset.ofHours(2)).plusYears(1).format(ISO_OFFSET_DATE_TIME)
|
|
|
+ ),
|
|
|
+ true
|
|
|
+ )
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ @ParameterizedTest
|
|
|
+ @MethodSource("campaignIdGET_Params_dataSource")
|
|
|
+ @DisplayName("API Params Test: campaignIdGET")
|
|
|
+ void campaignIdGET_byParams(Future<Campaign> dbFuture, String zoneParam, Tuple<String, String> expectedTimezones, boolean navigationLinks, Vertx vertx, VertxTestContext testContext) {
|
|
|
+
|
|
|
+ Mockito.when(repo.findCampaignById(anyLong())).thenReturn(dbFuture);
|
|
|
+
|
|
|
+ RequestOptions reqOpt = new RequestOptions()
|
|
|
+ .setMethod(HttpMethod.GET).setPort(PORT).setHost(HOST).setURI(
|
|
|
+ String.format("/campaigns/1?zone=%s&navigationLinks=%s", zoneParam, navigationLinks))
|
|
|
+ .setHeaders(MultiMap.caseInsensitiveMultiMap()
|
|
|
+ .add(HttpHeaders.ACCEPT, JSON.contentType()));
|
|
|
+
|
|
|
+ vertx.createHttpClient().request(reqOpt)
|
|
|
+ .compose(req -> req.send().compose(HttpClientResponse::body))
|
|
|
+ .onComplete(testContext.succeeding(b -> testContext.verify(() -> {
|
|
|
+ JsonNode resJson = toJsonNode(b.toString());
|
|
|
+ assertThat(resJson.get("fromTime").asText()).isEqualTo(expectedTimezones.item1());
|
|
|
+ assertThat(resJson.get("toTime").asText()).isEqualTo(expectedTimezones.item2());
|
|
|
+
|
|
|
+ assertThat(resJson.has("self@NavigationLink")).isEqualTo(navigationLinks);
|
|
|
+ assertThat(resJson.has("Observations@NavigationLink")).isEqualTo(navigationLinks);
|
|
|
+ assertThat(resJson.has("Locations@NavigationLink")).isEqualTo(navigationLinks);
|
|
|
+ assertThat(resJson.has("Units@NavigationLink")).isEqualTo(navigationLinks);
|
|
|
+ testContext.completeNow();
|
|
|
+ })));
|
|
|
+ }
|
|
|
+
|
|
|
+ private static Stream<Arguments> campaignIdUnitsGET_dataSource() {
|
|
|
+ OffsetDateTime baseTimestamp = OffsetDateTime.ofInstant(BASE_INSTANT_TIMESTAMP, ZoneOffset.UTC);
|
|
|
+ return Stream.of(
|
|
|
+ Arguments.of(
|
|
|
+ Future.succeededFuture(List.of(
|
|
|
+ CampaignUnit.of(1000, 1, "mock(name)", "mock(description)", baseTimestamp, baseTimestamp.plusMonths(1)),
|
|
|
+ CampaignUnit.of(2000, 1, "mock(name)", "mock(description)", baseTimestamp, baseTimestamp.plusMonths(1))
|
|
|
+ )),
|
|
|
+ SUCCESS, JSON
|
|
|
+ ),
|
|
|
+ Arguments.of(
|
|
|
+ Future.succeededFuture(Collections.emptyList()),
|
|
|
+ SUCCESS, JSON
|
|
|
+ ),
|
|
|
+ Arguments.of(
|
|
|
+ Future.failedFuture("Not found"),
|
|
|
+ ERROR, JSON
|
|
|
+ )
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ @ParameterizedTest
|
|
|
+ @MethodSource("campaignIdUnitsGET_dataSource")
|
|
|
+ @DisplayName("API Spec Test: campaignIdUnitsGET")
|
|
|
+ void campaignIdUnitsGET_bySpec(Future<List<CampaignUnit>> dbFuture, OpenAPIResponseTyp responseTyp, ContentType contentType, Vertx vertx, VertxTestContext testContext) {
|
|
|
+
|
|
|
+ Mockito.when(repo.findUnitsByCampaignId(anyLong())).thenReturn(dbFuture);
|
|
|
+
|
|
|
+ Operation operation = OPEN_API.getOperationById("campaignIdUnitsGET");
|
|
|
+ RequestOptions reqOpt = new RequestOptions()
|
|
|
+ .setMethod(HttpMethod.GET).setPort(PORT).setHost(HOST).setURI("/campaigns/1/units")
|
|
|
+ .setHeaders(MultiMap.caseInsensitiveMultiMap()
|
|
|
+ .add(HttpHeaders.ACCEPT, JSON.contentType()));
|
|
|
+
|
|
|
+ vertx.createHttpClient().request(reqOpt)
|
|
|
+ .compose(req -> req.send().compose(HttpClientResponse::body))
|
|
|
+ .onComplete(testContext.succeeding(b -> testContext.verify(() -> {
|
|
|
+ validateAssert(b.toString(), operation, responseTyp, contentType);
|
|
|
+ testContext.completeNow();
|
|
|
+ })));
|
|
|
+ }
|
|
|
+
|
|
|
+ private static Stream<Arguments> campaignIdUnitsGET_Params_dataSource() {
|
|
|
+ OffsetDateTime baseTimestamp = OffsetDateTime.ofInstant(BASE_INSTANT_TIMESTAMP, ZoneOffset.UTC);
|
|
|
+ Future<List<CampaignUnit>> future = Future.succeededFuture(List.of(
|
|
|
+ CampaignUnit.of(1000, 1, "mock(name)", "mock(description)", baseTimestamp, baseTimestamp.plusMonths(1))
|
|
|
+ ));
|
|
|
+
|
|
|
+ return Stream.of(
|
|
|
+ Arguments.of(future,
|
|
|
+ "UTC",
|
|
|
+ Tuple.of(
|
|
|
+ OffsetDateTime.ofInstant(BASE_INSTANT_TIMESTAMP, ZoneOffset.UTC).format(ISO_OFFSET_DATE_TIME),
|
|
|
+ OffsetDateTime.ofInstant(BASE_INSTANT_TIMESTAMP, ZoneOffset.UTC).plusMonths(1).format(ISO_OFFSET_DATE_TIME)
|
|
|
+ ),
|
|
|
+ false
|
|
|
+
|
|
|
+ ),
|
|
|
+ Arguments.of(future,
|
|
|
+ "%2b2", // +2
|
|
|
+ Tuple.of(
|
|
|
+ OffsetDateTime.ofInstant(BASE_INSTANT_TIMESTAMP, ZoneOffset.ofHours(2)).format(ISO_OFFSET_DATE_TIME),
|
|
|
+ OffsetDateTime.ofInstant(BASE_INSTANT_TIMESTAMP, ZoneOffset.ofHours(2)).plusMonths(1).format(ISO_OFFSET_DATE_TIME)
|
|
|
+ ),
|
|
|
+ true
|
|
|
+ )
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ @ParameterizedTest
|
|
|
+ @MethodSource("campaignIdUnitsGET_Params_dataSource")
|
|
|
+ @DisplayName("API Params Test: campaignIdUnitsGET")
|
|
|
+ void campaignIdUnitsGET_byParams(Future<List<CampaignUnit>> dbFuture, String zoneParam, Tuple<String, String> expectedTimezones, boolean navigationLinks, Vertx vertx, VertxTestContext testContext) {
|
|
|
+
|
|
|
+ Mockito.when(repo.findUnitsByCampaignId(anyLong())).thenReturn(dbFuture);
|
|
|
+
|
|
|
+ RequestOptions reqOpt = new RequestOptions()
|
|
|
+ .setMethod(HttpMethod.GET).setPort(PORT).setHost(HOST).setURI(
|
|
|
+ String.format("/campaigns/1/units?zone=%s&navigationLinks=%s", zoneParam, navigationLinks))
|
|
|
+ .setHeaders(MultiMap.caseInsensitiveMultiMap()
|
|
|
+ .add(HttpHeaders.ACCEPT, JSON.contentType()));
|
|
|
+
|
|
|
+ vertx.createHttpClient().request(reqOpt)
|
|
|
+ .compose(req -> req.send().compose(HttpClientResponse::body))
|
|
|
+ .onComplete(testContext.succeeding(b -> testContext.verify(() -> {
|
|
|
+ JsonNode resJson = toJsonNode(b.toString());
|
|
|
+ assertThat(resJson.size()).isEqualTo(1);
|
|
|
+
|
|
|
+ JsonNode jsonObj1 = resJson.get(0);
|
|
|
+
|
|
|
+ assertThat(jsonObj1.get("fromTime").asText()).isEqualTo(expectedTimezones.item1());
|
|
|
+ assertThat(jsonObj1.get("toTime").asText()).isEqualTo(expectedTimezones.item2());
|
|
|
+
|
|
|
+ assertThat(jsonObj1.has("Unit@NavigationLink")).isEqualTo(navigationLinks);
|
|
|
+ assertThat(jsonObj1.has("CampaignUnit@NavigationLink")).isEqualTo(navigationLinks);
|
|
|
+ testContext.completeNow();
|
|
|
+ })));
|
|
|
+ }
|
|
|
}
|