|
|
@@ -1,33 +1,322 @@
|
|
|
package io.connector.module.afarcloud;
|
|
|
|
|
|
+import io.connector.core.*;
|
|
|
+import io.connector.core.config.DefaultConfig;
|
|
|
+import io.connector.core.http.RequestUriComponent;
|
|
|
+import io.connector.core.http.RequestUriParser;
|
|
|
import io.connector.test.api.TestType;
|
|
|
-import io.vertx.core.Vertx;
|
|
|
+import io.vertx.core.*;
|
|
|
+import io.vertx.core.http.HttpClientOptions;
|
|
|
+import io.vertx.core.http.HttpClientResponse;
|
|
|
+import io.vertx.core.json.JsonArray;
|
|
|
+import io.vertx.core.json.JsonObject;
|
|
|
import io.vertx.junit5.VertxExtension;
|
|
|
import io.vertx.junit5.VertxTestContext;
|
|
|
-import org.junit.jupiter.api.AfterEach;
|
|
|
-import org.junit.jupiter.api.BeforeEach;
|
|
|
-import org.junit.jupiter.api.Tag;
|
|
|
-import org.junit.jupiter.api.Test;
|
|
|
+import io.vertx.core.http.HttpClient;
|
|
|
+import org.junit.jupiter.api.*;
|
|
|
import org.junit.jupiter.api.extension.ExtendWith;
|
|
|
|
|
|
+import java.util.*;
|
|
|
+import java.util.function.Function;
|
|
|
+
|
|
|
+import static io.connector.core.AddressPath.Creator.create;
|
|
|
+import static io.connector.module.afarcloud.OGCSensorThingsAssertions.*;
|
|
|
+import static io.vertx.core.http.HttpMethod.GET;
|
|
|
+import static java.util.Collections.*;
|
|
|
import static org.junit.jupiter.api.Assertions.*;
|
|
|
|
|
|
@Tag(TestType.INTEGRATION)
|
|
|
@ExtendWith(VertxExtension.class)
|
|
|
class OGCSensorThingsOnlineIntegrationTest {
|
|
|
|
|
|
+ private static class HttpHostInfo {
|
|
|
+ private final HttpClientOptions options;
|
|
|
+ private final String pathPrefix;
|
|
|
+
|
|
|
+ private static HttpHostInfo create(int port, String host, String pathPrefix, boolean ssl) {
|
|
|
+ return new HttpHostInfo(port, host, pathPrefix, ssl);
|
|
|
+ }
|
|
|
+
|
|
|
+ private HttpHostInfo(int port, String host, String pathPrefix, boolean ssl) {
|
|
|
+ this.options = new HttpClientOptions().setSsl(ssl).setDefaultPort(port).setDefaultHost(host);
|
|
|
+ this.pathPrefix = pathPrefix;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private static final String API_PREFIX = "/test";
|
|
|
+ private static final int DEFAULT_PORT = 9999;
|
|
|
+ private static final String OGC_JSON_KEY = "ogc";
|
|
|
+
|
|
|
+ private static final String MODULE_ID = "AFarCloud";
|
|
|
+ private static final String GATEWAY_ID = "OGCSensorThings";
|
|
|
+
|
|
|
+ private static final String DEFAULT_OGC_THINK_ID = "urn:afc:AS07:environmentalObservations:IMA:air_sensor:10002222";
|
|
|
+ private static final String DEFAULT_OGC_DATASTREAM_ID = "urn:afc:AS07:environmentalObservations:LESP:air_sensor:1305167561991327&air_temperature";
|
|
|
+ private static final String DEFAULT_AFC_SENSOR_ID = "10002222";
|
|
|
+ private static final String DEFAULT_AFC_OBSERVED_PROPERTY = "air_temperature";
|
|
|
+
|
|
|
+ private static final Map<String, String> DEFAULT_PROPERTIES = new HashMap<String, String>(){{
|
|
|
+ put("multiSensorId", DEFAULT_AFC_SENSOR_ID);
|
|
|
+ }};
|
|
|
+
|
|
|
+ private static final HttpHostInfo OGC_HOST = HttpHostInfo.create(DEFAULT_PORT, "localhost", create(API_PREFIX, MODULE_ID, GATEWAY_ID), false);
|
|
|
+
|
|
|
+ private static final HttpHostInfo AFC_RETRIEVAL_HOST = HttpHostInfo.create(9219, "torcos.etsist.upm.es", "", false);
|
|
|
+ private static final HttpHostInfo AFC_TELEMETRY_HOST = HttpHostInfo.create(9207, "torcos.etsist.upm.es", "", false);
|
|
|
+ private static final HttpHostInfo AFC_INFO_HOST = HttpHostInfo.create(443, "storage07-afarcloud.qa.pdmfc.com", "/storage/rest", true);
|
|
|
+
|
|
|
+
|
|
|
+ private static class TestConfig {
|
|
|
+ private final RequestConfig[] afcRequestConfigs;
|
|
|
+ private final Function<Map<String, String>, Map<String, String>> handler;
|
|
|
+
|
|
|
+ public TestConfig(RequestConfig[] afcRequestConfigs, Function<Map<String, String>, Map<String, String>> handler) {
|
|
|
+ this.afcRequestConfigs = afcRequestConfigs;
|
|
|
+ this.handler = handler;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private static class RequestConfig {
|
|
|
+ private final String key, path;
|
|
|
+ private final HttpHostInfo hostInfo;
|
|
|
+
|
|
|
+ private RequestConfig(String key, HttpHostInfo hostInfo, String path) {
|
|
|
+ this.key = key;
|
|
|
+ this.hostInfo = hostInfo;
|
|
|
+ this.path = create(hostInfo.pathPrefix, path);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private static final Map<String, TestConfig> TEST_CONFIGS;
|
|
|
+
|
|
|
+ private static final Stack<Map.Entry<String, String>> testGraphWalk;
|
|
|
+ private static final Set<String> testVisitedNodes;
|
|
|
+
|
|
|
+
|
|
|
+ static {
|
|
|
+ TEST_CONFIGS = new HashMap<>();
|
|
|
+
|
|
|
+ // configuration for /Things => []
|
|
|
+ TEST_CONFIGS.put("Things", new TestConfig(new RequestConfig[]{
|
|
|
+ new RequestConfig("afcSensors", AFC_INFO_HOST, "registry/getAllSensors"),
|
|
|
+ }, results -> {
|
|
|
+ String afcSensors = results.get("afcSensors");
|
|
|
+ String ogcThings = results.get(OGC_JSON_KEY);
|
|
|
+ assertThings(afcSensors, ogcThings, DEFAULT_PROPERTIES);
|
|
|
+ return findNextNavigationLinks(ogcThings, DEFAULT_OGC_THINK_ID);
|
|
|
+ }));
|
|
|
+
|
|
|
+ // configuration for /Locations => []
|
|
|
+ TEST_CONFIGS.put("Locations", new TestConfig(new RequestConfig [] {
|
|
|
+ new RequestConfig("afcSensor", AFC_INFO_HOST, create("registry/getSensor", DEFAULT_AFC_SENSOR_ID)),
|
|
|
+ new RequestConfig("afcData", AFC_RETRIEVAL_HOST, "getObservationsBySensor/latest"),
|
|
|
+ }, results -> {
|
|
|
+ String afcSensors = results.get("afcSensor");
|
|
|
+ String afcData = results.get("afcData");
|
|
|
+ String ogcLocations = results.get(OGC_JSON_KEY);
|
|
|
+ assertLocations(afcSensors, afcData, ogcLocations, DEFAULT_PROPERTIES);
|
|
|
+ return findNextNavigationLinks(ogcLocations, 0);
|
|
|
+ }));
|
|
|
+
|
|
|
+ // configuration for /HistoricalLocations => {}
|
|
|
+ TEST_CONFIGS.put("HistoricalLocations", new TestConfig(new RequestConfig[]{
|
|
|
+ new RequestConfig("afcSensor", AFC_INFO_HOST, create("registry/getSensor", DEFAULT_AFC_SENSOR_ID)),
|
|
|
+ new RequestConfig("afcData", AFC_RETRIEVAL_HOST, "getObservationsBySensor/latest"),
|
|
|
+ }, results -> {
|
|
|
+ String afcSensors = results.get("afcSensor");
|
|
|
+ String afcData = results.get("afcData");
|
|
|
+ String ogcHistoricalLocations = results.get(OGC_JSON_KEY);
|
|
|
+ assertHistoricalLocation(afcSensors, afcData, ogcHistoricalLocations, DEFAULT_PROPERTIES);
|
|
|
+ return emptyMap();
|
|
|
+ }));
|
|
|
+
|
|
|
+ // configuration for /Datastreams => []
|
|
|
+ TEST_CONFIGS.put("Datastreams", new TestConfig(new RequestConfig[]{
|
|
|
+ new RequestConfig("afcSensor", AFC_INFO_HOST, create("registry/getSensor", DEFAULT_AFC_SENSOR_ID)),
|
|
|
+ new RequestConfig("afcData", AFC_RETRIEVAL_HOST, "getObservationsBySensor/latest"),
|
|
|
+ }, results -> {
|
|
|
+ String afcSensors = results.get("afcSensor");
|
|
|
+ String afcData = results.get("afcData");
|
|
|
+ String ogcDatastreams = results.get(OGC_JSON_KEY);
|
|
|
+ assertDataStream(afcSensors, afcData, ogcDatastreams, DEFAULT_PROPERTIES);
|
|
|
+ return findNextNavigationLinks(ogcDatastreams, DEFAULT_OGC_DATASTREAM_ID);
|
|
|
+ }));
|
|
|
+
|
|
|
+ // configuration for /Sensor => {}
|
|
|
+ TEST_CONFIGS.put("Sensor", new TestConfig(new RequestConfig[]{
|
|
|
+ new RequestConfig("afcSensor", AFC_INFO_HOST, create("registry/getSensor", DEFAULT_AFC_SENSOR_ID))
|
|
|
+ }, results -> {
|
|
|
+ String afcSensors = results.get("afcSensor");
|
|
|
+ String ogcSensor = results.get(OGC_JSON_KEY);
|
|
|
+ assertSensor(afcSensors, ogcSensor, DEFAULT_PROPERTIES);
|
|
|
+ return emptyMap();
|
|
|
+ }));
|
|
|
+
|
|
|
+ // configuration for /ObservedProperty => {}
|
|
|
+ TEST_CONFIGS.put("ObservedProperty", new TestConfig(new RequestConfig[]{
|
|
|
+ new RequestConfig("afcSensor", AFC_INFO_HOST, create("registry/getSensor", DEFAULT_AFC_SENSOR_ID)),
|
|
|
+ new RequestConfig("afcData", AFC_RETRIEVAL_HOST, "getObservationsBySensor/latest"),
|
|
|
+ }, results -> {
|
|
|
+ String afcSensors = results.get("afcSensor");
|
|
|
+ String afcData = results.get("afcData");
|
|
|
+ String ogcObservedProperty = results.get(OGC_JSON_KEY);
|
|
|
+ assertObservedProperty(afcSensors, afcData, ogcObservedProperty, DEFAULT_PROPERTIES);
|
|
|
+ return emptyMap();
|
|
|
+ }));
|
|
|
+
|
|
|
+ // configuration for /Observations => []
|
|
|
+ TEST_CONFIGS.put("Observations", new TestConfig(new RequestConfig[]{
|
|
|
+ new RequestConfig("afcData", AFC_RETRIEVAL_HOST, "getObservationsBySensor/latest"),
|
|
|
+ }, results -> {
|
|
|
+ String afcData = results.get("afcData");
|
|
|
+ String ogcObservations = results.get(OGC_JSON_KEY);
|
|
|
+ assertObservation(afcData, ogcObservations, DEFAULT_PROPERTIES);
|
|
|
+ return emptyMap();
|
|
|
+ }));
|
|
|
+
|
|
|
+ testVisitedNodes = new HashSet<>();
|
|
|
+ testGraphWalk = new Stack<>();
|
|
|
+ testGraphWalk.push(entryOf("Things", "Things"));
|
|
|
+ }
|
|
|
+
|
|
|
+ private static <K, V> Map.Entry<K, V> entryOf(K key, V value) {
|
|
|
+ return new AbstractMap.SimpleEntry<>(key, value);
|
|
|
+ }
|
|
|
+
|
|
|
+ private static <K, V> Map<K, V> toMap(List<Map.Entry<K, V>> entries) {
|
|
|
+ Map<K, V> map = new HashMap<>(entries.size());
|
|
|
+ for (Map.Entry<K, V> entry : entries) {
|
|
|
+ map.put(entry.getKey(), entry.getValue());
|
|
|
+ }
|
|
|
+ return map;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static Map<String, String> findNextNavigationLinks(String ogcJson, String id) {
|
|
|
+ JsonArray jsonArray = new JsonArray(ogcJson);
|
|
|
+ for (int i = 0; i < jsonArray.size(); i++) {
|
|
|
+ JsonObject jsonObject = jsonArray.getJsonObject(i);
|
|
|
+ if (jsonObject.getString("@iot.id", "").equals(id)) {
|
|
|
+ return findNextNavigationLinks(jsonObject);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return emptyMap();
|
|
|
+ }
|
|
|
+
|
|
|
+ private static Map<String, String> findNextNavigationLinks(String ogcJson, int pos) {
|
|
|
+ JsonArray jsonArray = new JsonArray(ogcJson);
|
|
|
+ if (jsonArray.size() < pos) return emptyMap();
|
|
|
+ return findNextNavigationLinks(jsonArray.getJsonObject(pos));
|
|
|
+ }
|
|
|
+
|
|
|
+ private static Map<String, String> findNextNavigationLinks(String ogcJson) {
|
|
|
+ return findNextNavigationLinks(new JsonObject(ogcJson));
|
|
|
+ }
|
|
|
+
|
|
|
+ private static Map<String, String> findNextNavigationLinks(JsonObject ogcJson) {
|
|
|
+ Map<String, String> links = new HashMap<>();
|
|
|
+ for (String fieldName : ogcJson.fieldNames()) {
|
|
|
+ if (fieldName.endsWith("@iot.navigationLink")) {
|
|
|
+ String type = fieldName.split("@")[0];
|
|
|
+ links.put(type, ogcJson.getString(fieldName));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return links;
|
|
|
+ }
|
|
|
+
|
|
|
@BeforeEach
|
|
|
- void setUp() {
|
|
|
+ @DisplayName("Deploy the module:" + MODULE_ID)
|
|
|
+ void prepare(Vertx vertx, VertxTestContext testContext) {
|
|
|
+ DefaultConfig serviceConfig = new DefaultConfig(MODULE_ID, AFCModuleProvider.class);
|
|
|
+ serviceConfig.setProperty("retrievalApi", new HashMap<String, String>(){{
|
|
|
+ put("domain", "http://torcos.etsist.upm.es:9219");
|
|
|
+ }});
|
|
|
+ serviceConfig.setProperty("telemetryApi", new HashMap<String, String>(){{
|
|
|
+ put("domain", "https://torcos.etsist.upm.es:9207");
|
|
|
+ }});
|
|
|
+ serviceConfig.setProperty("infoApi", new HashMap<String, String>(){{
|
|
|
+ put("domain", "https://storage07-afarcloud.qa.pdmfc.com/storage/rest/");
|
|
|
+ }});
|
|
|
+ ModuleDescriptor descriptor = new ModuleDescriptor(serviceConfig.getId(), serviceConfig, null);
|
|
|
+ AFCModuleProvider provider = new AFCModuleProvider();
|
|
|
+
|
|
|
+ JsonObject serverConfig = new JsonObject();
|
|
|
+ serverConfig.put("http.server.port", OGC_HOST.options.getDefaultPort());
|
|
|
+ serverConfig.put("http.server.prefix", API_PREFIX);
|
|
|
|
|
|
+ DeploymentOptions options = new DeploymentOptions().setConfig(serverConfig);
|
|
|
+ List<AbstractModule> modules = singletonList(provider.createModule(descriptor));
|
|
|
+ vertx.deployVerticle(ModuleDeployer.deploy(modules), options, testContext.succeedingThenComplete());
|
|
|
}
|
|
|
|
|
|
@AfterEach
|
|
|
- void tearDown() {
|
|
|
-
|
|
|
+ @DisplayName("Check that the verticle is still there")
|
|
|
+ void lastChecks(Vertx vertx) {
|
|
|
+ assertFalse(vertx.deploymentIDs().isEmpty());
|
|
|
}
|
|
|
|
|
|
@Test
|
|
|
- void run(Vertx vertx, VertxTestContext testContext) {
|
|
|
+ @DisplayName("Endpoint: /info")
|
|
|
+ void test_info(Vertx vertx, VertxTestContext testContext) {
|
|
|
+ HttpClient client = vertx.createHttpClient(OGC_HOST.options);
|
|
|
+ client.request(GET, create(API_PREFIX, "info"))
|
|
|
+ .compose(req -> req.send().compose(HttpClientResponse::body).onFailure(testContext::failNow))
|
|
|
+ .onComplete(testContext.succeeding(
|
|
|
+ buffer -> testContext.verify(() -> {
|
|
|
+ assertInfo(buffer.toString(), new HashMap<String, String>(){{
|
|
|
+ put("module_id", MODULE_ID);
|
|
|
+ put("gateway_id", GATEWAY_ID);
|
|
|
+ }});
|
|
|
+ testContext.completeNow();
|
|
|
+ })
|
|
|
+ ))
|
|
|
+ .onFailure(testContext::failNow);
|
|
|
+ }
|
|
|
+
|
|
|
+ @RepeatedTest(7)
|
|
|
+ @DisplayName("Testing endpoints.")
|
|
|
+ void test_endpointsWalkThrough(Vertx vertx, VertxTestContext testContext) {
|
|
|
+ if (testGraphWalk.isEmpty()) {
|
|
|
+ testContext.completeNow(); return;
|
|
|
+ }
|
|
|
+
|
|
|
+ Map.Entry<String, String> endpoint = testGraphWalk.pop();
|
|
|
+ TestConfig testConfig = TEST_CONFIGS.get(endpoint.getKey());
|
|
|
+ assertNotNull(testConfig, "No test configuration for the node '" + endpoint + "'.");
|
|
|
+
|
|
|
+ List<Future> futures = new ArrayList<>(testConfig.afcRequestConfigs.length);
|
|
|
+ for (RequestConfig requestConfig : testConfig.afcRequestConfigs) {
|
|
|
+ Promise<Map.Entry<String, String>> promise = Promise.promise();
|
|
|
+ futures.add(promise.future());
|
|
|
+ vertx.createHttpClient(requestConfig.hostInfo.options).request(GET, requestConfig.path)
|
|
|
+ .compose(req -> req.send().compose(HttpClientResponse::body).onFailure(testContext::failNow))
|
|
|
+ .onComplete(testContext.succeeding(buffer -> promise.complete(entryOf(requestConfig.key, buffer.toString()))))
|
|
|
+ .onFailure(testContext::failNow);
|
|
|
+ }
|
|
|
+
|
|
|
+ Promise<Map.Entry<String, String>> promise = Promise.promise();
|
|
|
+ futures.add(promise.future());
|
|
|
+ vertx.createHttpClient(OGC_HOST.options).request(GET, create(OGC_HOST.pathPrefix, endpoint.getValue()))
|
|
|
+ .compose(req -> req.send().compose(HttpClientResponse::body).onFailure(testContext::failNow))
|
|
|
+ .onComplete(testContext.succeeding(buffer -> promise.complete(entryOf(OGC_JSON_KEY, buffer.toString()))))
|
|
|
+ .onFailure(testContext::failNow);
|
|
|
|
|
|
+ CompositeFuture.all(futures).onComplete(reply -> testContext.verify(() -> {
|
|
|
+ Map<String, String> results = toMap(reply.result().list());
|
|
|
+ // testing number of results from afc + ogc (1)
|
|
|
+ assertEquals(testConfig.afcRequestConfigs.length + 1, results.size(), "Number of results from endpoints does not match.");
|
|
|
+ Map<String, String> nextLinks = testConfig.handler.apply(results);
|
|
|
+ testVisitedNodes.add(endpoint.getKey());
|
|
|
+ for (Map.Entry<String, String> nextLink : nextLinks.entrySet()) {
|
|
|
+ String nextLinkName = nextLink.getKey();
|
|
|
+ if (TEST_CONFIGS.containsKey(nextLinkName) && !testVisitedNodes.contains(nextLinkName)) {
|
|
|
+ RequestUriComponent uriComponent = RequestUriParser.parse(nextLink.getValue(), MODULE_ID, GATEWAY_ID);
|
|
|
+ if (uriComponent != null) {
|
|
|
+ testGraphWalk.push(entryOf(nextLinkName, uriComponent.getAddress()));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ testContext.completeNow();
|
|
|
+ })
|
|
|
+ );
|
|
|
}
|
|
|
}
|