ソースを参照

Implemented online integration tests

Lukas Cerny 4 年 前
コミット
768122e831

+ 6 - 7
build.gradle

@@ -88,17 +88,20 @@ project("connector-app") {
 project(":connector-core") {
 
     dependencies {
-        compile group: 'io.vertx', name: 'vertx-core', version: '3.9.1'
-        compile group: 'io.vertx', name: 'vertx-web', version: '3.9.1'
+//        compile group: 'io.vertx', name: 'vertx-core', version: '4.0.3'
+        compile group: 'io.vertx', name: 'vertx-web', version: '4.0.3'
         compile group: 'org.apache.httpcomponents', name: 'httpclient', version: '4.5.9'
         compile group: 'org.yaml', name: 'snakeyaml', version: '1.24'
+
+        compile group: 'io.vertx', name: 'vertx-junit5', version: '4.0.3'
+        compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.12.3'
     }
 }
 
 project(":connector-model") {
 
     dependencies {
-        compile group: 'io.vertx', name: 'vertx-core', version: '3.9.1'
+        compile group: 'io.vertx', name: 'vertx-core', version: '4.0.3'
     }
 }
 
@@ -108,10 +111,6 @@ configure(moduleNames) {
     dependencies {
         compile project(":connector-core")
         compile project(":connector-model")
-
-        compile group: 'io.vertx', name: 'vertx-core', version: '3.9.1'
-
-        testCompile group: 'io.vertx', name: 'vertx-junit5', version: '4.0.3'
     }
 }
 

+ 2 - 1
connector-core/src/main/java/io/connector/core/AddressPath.java

@@ -23,9 +23,10 @@ public final class AddressPath {
 
         private final static char DELIMITER = '/';
 
-        public static String create(String ...parts) {
+        public static String create(String... parts) {
             StringBuilder builder = new StringBuilder();
             for (String part : parts) {
+                if (part.isEmpty()) continue;
                 builder.append(part.charAt(0) == DELIMITER ? part : DELIMITER + part);
             }
             return builder.toString();

+ 9 - 1
connector-core/src/main/java/io/connector/core/http/RequestUriComponent.java

@@ -3,6 +3,8 @@
 
 package io.connector.core.http;
 
+import java.util.Map;
+
 import static io.connector.core.AddressPath.Creator.create;
 
 /**
@@ -18,13 +20,15 @@ public class RequestUriComponent {
     private final String module;
     private final String gateway;
     private final String address;
+    private final Map<String, String> params;
 
-    RequestUriComponent(String domain, String prefix, String module, String gateway, String address) {
+    RequestUriComponent(String domain, String prefix, String module, String gateway, String address, Map<String, String> params) {
         this.domain = domain;
         this.prefix = prefix;
         this.module = module;
         this.gateway = gateway;
         this.address = address;
+        this.params = params;
     }
 
     public String getDomain() {
@@ -62,4 +66,8 @@ public class RequestUriComponent {
     public String getGatewayPath() {
         return create(prefix, module, gateway);
     }
+
+    public Map<String, String> getParams() {
+        return params;
+    }
 }

+ 60 - 3
connector-core/src/main/java/io/connector/core/http/RequestUriParser.java

@@ -5,6 +5,10 @@ package io.connector.core.http;
 
 import io.vertx.core.http.HttpServerRequest;
 
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
 import static io.connector.core.AddressPath.Creator.create;
 
 /**
@@ -15,11 +19,64 @@ import static io.connector.core.AddressPath.Creator.create;
  */
 public final class RequestUriParser {
 
+    public static RequestUriComponent parse(String absolureURI, String moduleId, String gatewayId) {
+        if (absolureURI.isEmpty()) return null;
+        Map<String, String> params;
+        if (absolureURI.contains("?")) {
+            String [] cmp = absolureURI.split("\\?");
+            params = parseParams(cmp[1]);
+            absolureURI = cmp[0];
+        } else {
+            params = Collections.emptyMap();
+        }
+
+        String [] protocolCmp = absolureURI.split("://");
+        String [] pathCmp = protocolCmp[1].split("/");
+        String domain = String.format("%s://%s", protocolCmp[0], pathCmp[0]);
+        StringBuilder apiPrefixBuilder = new StringBuilder();
+        int pathIndex;
+        for (pathIndex = 1; pathIndex < pathCmp.length; pathIndex++) {
+            String currentPath = pathCmp[pathIndex];
+            if (currentPath.equals(moduleId)) break;
+            apiPrefixBuilder.append("/").append(currentPath);
+        }
+
+        boolean isGatewayMultiPart = gatewayId.contains("/");
+        String [] gatewayParts = isGatewayMultiPart ? gatewayId.split("/") : new String[] {gatewayId};
+        StringBuilder addressBuilder = new StringBuilder();
+        for (pathIndex += 1; pathIndex < pathCmp.length; pathIndex++) {
+            String currentPath = pathCmp[pathIndex];
+            if (currentPath.equals(gatewayParts[0])) {
+                for (int i = 1; i < gatewayParts.length; i++) {
+                    int pathIndexTmp = pathIndex + i;
+                    if (pathIndexTmp < pathCmp.length && pathCmp[pathIndexTmp].equals(gatewayParts[i])) {
+                        pathIndex++;
+                    }
+                }
+            } else {
+                addressBuilder.append("/").append(pathCmp[pathIndex]);
+            }
+        }
+
+        return new RequestUriComponent(domain, apiPrefixBuilder.toString(), moduleId, gatewayId, addressBuilder.toString(), params);
+    }
+
     public static RequestUriComponent parse(HttpServerRequest req, String moduleId, String gatewayId) {
-        String params = req.uri().substring(req.uri().indexOf("?")+1);
-        String domain = req.absoluteURI().replace(req.path(), "").replaceAll("\\?"+params, "");
+        String paths = req.uri().substring(req.uri().indexOf("?")+1);
+        String domain = req.absoluteURI().replace(req.path(), "").replaceAll("\\?"+paths, "");
         String prefix = req.path().substring(1, req.path().indexOf(moduleId)-1);
         String address = req.path().replace(create(prefix, moduleId, gatewayId), "").substring(1);
-        return new RequestUriComponent(domain, prefix, moduleId, gatewayId, address);
+        return new RequestUriComponent(domain, prefix, moduleId, gatewayId, address, Collections.emptyMap());
+    }
+
+    private static Map<String, String> parseParams(String params) {
+        if (params.isEmpty()) { return Collections.emptyMap(); }
+        String [] paramsCmp = params.split("&");
+        Map<String, String> paramsMap = new HashMap<>(paramsCmp.length);
+        for (String param : paramsCmp) {
+            String [] cmp = param.split("=");
+            paramsMap.put(cmp[0], cmp[1]);
+        }
+        return paramsMap;
     }
 }

+ 54 - 0
connector-core/src/test/java/io/connector/core/http/RequestUriParserTest.java

@@ -0,0 +1,54 @@
+package io.connector.core.http;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class RequestUriParserTest {
+
+    @Test
+    void parse_fullUrl() {
+        final String absoluteURI = "http://localhost:8080/test/moduleId/gatewayId/path1/path2?param1=test&param2=test";
+
+        RequestUriComponent uriComponent = RequestUriParser.parse(absoluteURI, "moduleId", "gatewayId");
+
+        assertNotNull(uriComponent);
+        assertEquals("http://localhost:8080", uriComponent.getDomain());
+        assertEquals("http://localhost:8080/test/moduleId", uriComponent.getModuleUri());
+        assertEquals("http://localhost:8080/test/moduleId/gatewayId", uriComponent.getGatewayUri());
+
+        assertEquals("/test", uriComponent.getPrefix());
+        assertEquals("/test/moduleId", uriComponent.getModulePath());
+        assertEquals("/test/moduleId/gatewayId", uriComponent.getGatewayPath());
+        assertEquals("/path1/path2", uriComponent.getAddress());
+
+        assertEquals(2, uriComponent.getParams().size());
+        assertTrue(uriComponent.getParams().containsKey("param1"));
+        assertTrue(uriComponent.getParams().containsKey("param2"));
+        assertEquals("test", uriComponent.getParams().get("param1"));
+        assertEquals("test", uriComponent.getParams().get("param2"));
+
+        assertEquals("moduleId", uriComponent.getModule());
+        assertEquals("gatewayId", uriComponent.getGateway());
+     }
+
+    @Test
+    void parse_ipAddr() {
+        final String absoluteURI = "http://127.0.0.1/api/test/moduleId/gatewayId/path1/path2";
+
+        RequestUriComponent uriComponent = RequestUriParser.parse(absoluteURI, "moduleId", "gatewayId");
+
+        assertNotNull(uriComponent);
+        assertEquals("http://127.0.0.1", uriComponent.getDomain());
+        assertEquals("http://127.0.0.1/api/test/moduleId", uriComponent.getModuleUri());
+        assertEquals("http://127.0.0.1/api/test/moduleId/gatewayId", uriComponent.getGatewayUri());
+
+        assertEquals("/api/test", uriComponent.getPrefix());
+        assertEquals("/api/test/moduleId", uriComponent.getModulePath());
+        assertEquals("/api/test/moduleId/gatewayId", uriComponent.getGatewayPath());
+        assertEquals("/path1/path2", uriComponent.getAddress());
+
+        assertEquals("moduleId", uriComponent.getModule());
+        assertEquals("gatewayId", uriComponent.getGateway());
+    }
+}

+ 0 - 2
connector-module-afarcloud/src/main/java/io/connector/module/afarcloud/gateway/OGCSensorThingsGateway.java

@@ -3,7 +3,6 @@
 
 package io.connector.module.afarcloud.gateway;
 
-import com.sun.org.apache.xpath.internal.operations.Bool;
 import io.connector.core.AbstractGateway;
 import io.connector.core.http.RequestUriComponent;
 import io.connector.model.afarcloud.*;
@@ -21,7 +20,6 @@ import org.apache.logging.log4j.Logger;
 
 import java.time.Instant;
 import java.util.*;
-import java.util.function.Function;
 import java.util.function.Supplier;
 
 import static io.connector.core.http.HttpContentType.APPLICATION_JSON;

+ 24 - 2
connector-module-afarcloud/src/test/java/io/connector/module/afarcloud/OGCSensorThingsAssertions.java

@@ -6,6 +6,7 @@ import io.vertx.core.json.JsonObject;
 import java.util.Map;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
 
 class OGCSensorThingsAssertions {
 
@@ -56,7 +57,7 @@ class OGCSensorThingsAssertions {
             }
         }
         return new JsonObject();
-    };
+    }
 
     private static JsonObject findMeasurementAFC(String observedProperty, JsonArray measurements) {
         for (int i = 0; i < measurements.size(); i++) {
@@ -66,7 +67,28 @@ class OGCSensorThingsAssertions {
             }
         }
         return new JsonObject();
-    };
+    }
+
+    public static void assertInfo(String json, Map<String, String> properties) {
+        JsonArray deployedModules = new JsonArray(json);
+        JsonObject afcModule = null;
+        for (int i = 0; i < deployedModules.size(); i++) {
+            JsonObject module = deployedModules.getJsonObject(i);
+            if (module.getString("id", "").equals(properties.get("module_id"))) {
+                afcModule = module; break;
+            }
+        }
+        assertNotNull(afcModule, String.format("Module '%s' is not deployed.", properties.get("module_id")));
+        String ogcGateway = null;
+        JsonArray gateways = afcModule.getJsonArray("gateways", new JsonArray());
+        for (int i = 0; i < gateways.size(); i++) {
+            String gateway = gateways.getString(i);
+            if (gateway.equals(properties.get("gateway_id"))) {
+                ogcGateway = gateway; break;
+            }
+        }
+        assertNotNull(ogcGateway, String.format("Gateway '%s' is not awailable.", properties.get("gateway_id")));
+    }
 
     public static void assertThings(String jsonAFC, String jsonOGC, Map<String, String> properties) {
         JsonObject sensorAFC = findSensorAFC(new JsonArray(jsonAFC), properties);

+ 298 - 9
connector-module-afarcloud/src/test/java/io/connector/module/afarcloud/OGCSensorThingsOnlineIntegrationTest.java

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

BIN
doc/OGCSensorThings_v1.1_navigationLinks.png