浏览代码

Added JUnit covering the OpenAPI Spec for the endpoints 'campaignIdGET' and 'campaignIdUnitsGET'

Lukas Cerny 1 年之前
父节点
当前提交
6a851d1e8b

+ 1 - 1
src/main/java/cz/senslog/telemetry/server/ws/OpenAPIHandler.java

@@ -141,7 +141,7 @@ public class OpenAPIHandler {
                                 "CampaignUnit@NavigationLink", String.format("%s/campaigns/%d/units/%d",host, u.getCampaignId(), u.getUnitId())
                         ) : JsonObject.of()).mergeIn(JsonObject.of(
                                 "unitId", u.getUnitId(),
-                                "name", u.getUnitId(),
+                                "name", u.getName(),
                                 "description", u.getDescription(),
                                 "fromTime", DATE_TIME_FORMATTER.apply(u.getFromTime(), zone),
                                 "toTime", DATE_TIME_FORMATTER.apply(u.getToTime(), zone)

+ 252 - 22
src/test/java/cz/senslog/telemetry/server/ws/OpenAPIHandlerTest.java

@@ -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();
+                })));
+    }
 }