Browse Source

Fixed bugs in SQLs, added JUnits for endpoints

Lukas Cerny 1 năm trước cách đây
mục cha
commit
9b5aede74b

+ 68 - 40
src/main/java/cz/senslog/telemetry/database/repository/MapLogRepository.java

@@ -814,7 +814,12 @@ public class MapLogRepository implements SensLogRepository {
                         "JOIN maplog.unit_to_campaign utc on utc.campaign_id = uc.campaign_id and utc.unit_id = e.unit_id " +
                         "JOIN maplog.unit_to_campaign utc on utc.campaign_id = uc.campaign_id and utc.unit_id = e.unit_id " +
                         "JOIN maplog.system_user AS su ON su.id = uc.user_id " +
                         "JOIN maplog.system_user AS su ON su.id = uc.user_id " +
                         "WHERE e.entity_id = $1 AND e.unit_id = $2 AND e.action_id = $3 " +
                         "WHERE e.entity_id = $1 AND e.unit_id = $2 AND e.action_id = $3 " +
-                            "AND utc.from_time <= e.from_time AND utc.to_time >= coalesce(e.to_time, utc.to_time) " +
+                            "AND utc.from_time <= e.from_time AND " +
+                        "      (CASE WHEN utc.to_time IS NULL AND e.to_time IS NULL THEN 1 ELSE " +
+                        "          (CASE WHEN utc.to_time IS NULL THEN " +
+                        "              (CASE WHEN coalesce(utc.to_time, now()) >= e.to_time THEN 1 ELSE 0 END) " +
+                        "            END) " +
+                        "        END) = 1 " +
                             "AND su.identity = $4 ORDER BY e.id")
                             "AND su.identity = $4 ORDER BY e.id")
                 .execute(Tuple.of(entityId, unitId, actionId, userIdentity))
                 .execute(Tuple.of(entityId, unitId, actionId, userIdentity))
                 .map(rs -> StreamSupport.stream(rs.spliterator(), false)
                 .map(rs -> StreamSupport.stream(rs.spliterator(), false)
@@ -1443,16 +1448,17 @@ public class MapLogRepository implements SensLogRepository {
         String whereTimestampClause;
         String whereTimestampClause;
         Tuple tupleParams;
         Tuple tupleParams;
         if (from != null && to != null) {
         if (from != null && to != null) {
-            whereTimestampClause = "tel.time_stamp >= (CASE WHEN $5 < utc.from_time THEN utc.from_time ELSE $5 END) AND tel.time_stamp <= (CASE WHEN $6 > utc.to_time THEN utc.to_time ELSE $6 END)";
+            whereTimestampClause = "tel.time_stamp >= (CASE WHEN $5 < utc.from_time THEN utc.from_time ELSE $5 END) " +
+                    "AND tel.time_stamp <= (CASE WHEN utc.to_time IS NULL THEN now() ELSE (CASE WHEN $6 > utc.to_time THEN utc.to_time ELSE $6 END) END)";
             tupleParams = Tuple.of(campaignId, unitId, offset, limit, from, to);
             tupleParams = Tuple.of(campaignId, unitId, offset, limit, from, to);
         } else if (from != null) {
         } else if (from != null) {
-            whereTimestampClause = "tel.time_stamp >= (CASE WHEN $5 < utc.from_time THEN utc.from_time ELSE $5 END) AND tel.time_stamp <= utc.to_time";
+            whereTimestampClause = "tel.time_stamp >= (CASE WHEN $5 < utc.from_time THEN utc.from_time ELSE $5 END) AND tel.time_stamp <= (CASE WHEN utc.to_time IS NULL THEN now() ELSE utc.to_time END)";
             tupleParams = Tuple.of(campaignId, unitId, offset, limit, from);
             tupleParams = Tuple.of(campaignId, unitId, offset, limit, from);
         } else if (to != null) {
         } else if (to != null) {
-            whereTimestampClause = "tel.time_stamp >= utc.from_time AND tel.time_stamp <= (CASE WHEN $5 > utc.to_time THEN utc.to_time ELSE $5 END)";
+            whereTimestampClause = "tel.time_stamp >= utc.from_time AND tel.time_stamp <= (CASE WHEN utc.to_time IS NULL THEN now() ELSE (CASE WHEN $5 > utc.to_time THEN utc.to_time ELSE $5 END) END)";
             tupleParams = Tuple.of(campaignId, unitId, offset, limit, to);
             tupleParams = Tuple.of(campaignId, unitId, offset, limit, to);
         } else {
         } else {
-            whereTimestampClause = "tel.time_stamp >= utc.from_time AND tel.time_stamp <= utc.to_time";
+            whereTimestampClause = "tel.time_stamp >= utc.from_time AND tel.time_stamp <= (CASE WHEN utc.to_time IS NULL THEN now() ELSE utc.to_time END)";
             tupleParams = Tuple.of(campaignId, unitId, offset, limit);
             tupleParams = Tuple.of(campaignId, unitId, offset, limit);
         }
         }
 
 
@@ -1521,16 +1527,17 @@ public class MapLogRepository implements SensLogRepository {
         String whereTimestampClause;
         String whereTimestampClause;
         Tuple tupleParams;
         Tuple tupleParams;
         if (from != null && to != null) {
         if (from != null && to != null) {
-            whereTimestampClause = "tel.time_stamp >= (CASE WHEN $6 < utc.from_time THEN utc.from_time ELSE $6 END) AND tel.time_stamp <= (CASE WHEN $7 > utc.to_time THEN utc.to_time ELSE $7 END)";
+            whereTimestampClause = "tel.time_stamp >= (CASE WHEN $6 < utc.from_time THEN utc.from_time ELSE $6 END) " +
+                    "AND tel.time_stamp <= (CASE WHEN utc.to_time IS NULL THEN now() ELSE (CASE WHEN $7 > utc.to_time THEN utc.to_time ELSE $7 END) END)";
             tupleParams = Tuple.of(campaignId, unitId, offset, limit, userIdentity, from, to);
             tupleParams = Tuple.of(campaignId, unitId, offset, limit, userIdentity, from, to);
         } else if (from != null) {
         } else if (from != null) {
-            whereTimestampClause = "tel.time_stamp >= (CASE WHEN $6 < utc.from_time THEN utc.from_time ELSE $6 END) AND tel.time_stamp <= utc.to_time";
+            whereTimestampClause = "tel.time_stamp >= (CASE WHEN $6 < utc.from_time THEN utc.from_time ELSE $6 END) AND tel.time_stamp <= (CASE WHEN utc.to_time IS NULL THEN now() ELSE utc.to_time END)";
             tupleParams = Tuple.of(campaignId, unitId, offset, limit, userIdentity, from);
             tupleParams = Tuple.of(campaignId, unitId, offset, limit, userIdentity, from);
         } else if (to != null) {
         } else if (to != null) {
-            whereTimestampClause = "tel.time_stamp >= utc.from_time AND tel.time_stamp <= (CASE WHEN $6 > utc.to_time THEN utc.to_time ELSE $6 END)";
+            whereTimestampClause = "tel.time_stamp >= utc.from_time AND tel.time_stamp <= (CASE WHEN utc.to_time IS NULL THEN now() ELSE (CASE WHEN $6 > utc.to_time THEN utc.to_time ELSE $6 END) END)";
             tupleParams = Tuple.of(campaignId, unitId, offset, limit, userIdentity, to);
             tupleParams = Tuple.of(campaignId, unitId, offset, limit, userIdentity, to);
         } else {
         } else {
-            whereTimestampClause = "tel.time_stamp >= utc.from_time AND tel.time_stamp <= utc.to_time";
+            whereTimestampClause = "tel.time_stamp >= utc.from_time AND tel.time_stamp <= (CASE WHEN utc.to_time IS NULL THEN now() ELSE utc.to_time END)";
             tupleParams = Tuple.of(campaignId, unitId, offset, limit, userIdentity);
             tupleParams = Tuple.of(campaignId, unitId, offset, limit, userIdentity);
         }
         }
 
 
@@ -1763,16 +1770,17 @@ public class MapLogRepository implements SensLogRepository {
         String whereTimestampClause;
         String whereTimestampClause;
         Tuple tupleParams;
         Tuple tupleParams;
         if (from != null && to != null) {
         if (from != null && to != null) {
-            whereTimestampClause = "tel.time_stamp >= (CASE WHEN $6 < utc.from_time THEN utc.from_time ELSE $6 END) AND tel.time_stamp <= (CASE WHEN $7 > utc.to_time THEN utc.to_time ELSE $7 END)";
+            whereTimestampClause = "tel.time_stamp >= (CASE WHEN $6 < utc.from_time THEN utc.from_time ELSE $6 END) " +
+                    "AND tel.time_stamp <= (CASE WHEN utc.to_time IS NULL THEN now() ELSE (CASE WHEN $7 > utc.to_time THEN utc.to_time ELSE $7 END) END)";
             tupleParams = Tuple.of(campaignId, unitId, sensorId, offset, limit, from, to);
             tupleParams = Tuple.of(campaignId, unitId, sensorId, offset, limit, from, to);
         } else if (from != null) {
         } else if (from != null) {
-            whereTimestampClause = "tel.time_stamp >= (CASE WHEN $6 < utc.from_time THEN utc.from_time ELSE $6 END) AND tel.time_stamp <= utc.to_time";
+            whereTimestampClause = "tel.time_stamp >= (CASE WHEN $6 < utc.from_time THEN utc.from_time ELSE $6 END) AND tel.time_stamp <= (CASE WHEN utc.to_time IS NULL THEN now() ELSE utc.to_time END)";
             tupleParams = Tuple.of(campaignId, unitId, sensorId, offset, limit, from);
             tupleParams = Tuple.of(campaignId, unitId, sensorId, offset, limit, from);
         } else if (to != null) {
         } else if (to != null) {
-            whereTimestampClause = "tel.time_stamp >= utc.from_time AND tel.time_stamp <= (CASE WHEN $6 > utc.to_time THEN utc.to_time ELSE $6 END)";
+            whereTimestampClause = "tel.time_stamp >= utc.from_time AND tel.time_stamp <= (CASE WHEN utc.to_time IS NULL THEN now() ELSE (CASE WHEN $6 > utc.to_time THEN utc.to_time ELSE $6 END) END)";
             tupleParams = Tuple.of(campaignId, unitId, sensorId, offset, limit, to);
             tupleParams = Tuple.of(campaignId, unitId, sensorId, offset, limit, to);
         } else {
         } else {
-            whereTimestampClause = "tel.time_stamp >= utc.from_time AND tel.time_stamp <= utc.to_time";
+            whereTimestampClause = "tel.time_stamp >= utc.from_time AND tel.time_stamp <= (CASE WHEN utc.to_time IS NULL THEN now() ELSE utc.to_time END)";
             tupleParams = Tuple.of(campaignId, unitId, sensorId, offset, limit);
             tupleParams = Tuple.of(campaignId, unitId, sensorId, offset, limit);
         }
         }
 
 
@@ -1834,16 +1842,17 @@ public class MapLogRepository implements SensLogRepository {
         String whereTimestampClause;
         String whereTimestampClause;
         Tuple tupleParams;
         Tuple tupleParams;
         if (from != null && to != null) {
         if (from != null && to != null) {
-            whereTimestampClause = "tel.time_stamp >= (CASE WHEN $7 < utc.from_time THEN utc.from_time ELSE $7 END) AND tel.time_stamp <= (CASE WHEN $8 > utc.to_time THEN utc.to_time ELSE $8 END)";
+            whereTimestampClause = "tel.time_stamp >= (CASE WHEN $7 < utc.from_time THEN utc.from_time ELSE $7 END) " +
+                    "AND tel.time_stamp <= (CASE WHEN utc.to_time IS NULL THEN now() ELSE (CASE WHEN $8 > utc.to_time THEN utc.to_time ELSE $8 END) END)";
             tupleParams = Tuple.of(campaignId, unitId, sensorId, offset, limit, userIdentity, from, to);
             tupleParams = Tuple.of(campaignId, unitId, sensorId, offset, limit, userIdentity, from, to);
         } else if (from != null) {
         } else if (from != null) {
-            whereTimestampClause = "tel.time_stamp >= (CASE WHEN $7 < utc.from_time THEN utc.from_time ELSE $7 END) AND tel.time_stamp <= utc.to_time";
+            whereTimestampClause = "tel.time_stamp >= (CASE WHEN $7 < utc.from_time THEN utc.from_time ELSE $7 END) AND tel.time_stamp <= (CASE WHEN utc.to_time IS NULL THEN now() ELSE utc.to_time END)";
             tupleParams = Tuple.of(campaignId, unitId, sensorId, offset, limit, userIdentity, from);
             tupleParams = Tuple.of(campaignId, unitId, sensorId, offset, limit, userIdentity, from);
         } else if (to != null) {
         } else if (to != null) {
-            whereTimestampClause = "tel.time_stamp >= utc.from_time AND tel.time_stamp <= (CASE WHEN $7 > utc.to_time THEN utc.to_time ELSE $7 END)";
+            whereTimestampClause = "tel.time_stamp >= utc.from_time AND tel.time_stamp <= (CASE WHEN utc.to_time IS NULL THEN now() ELSE (CASE WHEN $7 > utc.to_time THEN utc.to_time ELSE $7 END) END)";
             tupleParams = Tuple.of(campaignId, unitId, sensorId, offset, limit, userIdentity, to);
             tupleParams = Tuple.of(campaignId, unitId, sensorId, offset, limit, userIdentity, to);
         } else {
         } else {
-            whereTimestampClause = "tel.time_stamp >= utc.from_time AND tel.time_stamp <= utc.to_time";
+            whereTimestampClause = "tel.time_stamp >= utc.from_time AND tel.time_stamp <= (CASE WHEN utc.to_time IS NULL THEN now() ELSE utc.to_time END)";
             tupleParams = Tuple.of(campaignId, unitId, sensorId, offset, limit, userIdentity);
             tupleParams = Tuple.of(campaignId, unitId, sensorId, offset, limit, userIdentity);
         }
         }
 
 
@@ -1909,16 +1918,17 @@ public class MapLogRepository implements SensLogRepository {
         String whereTimestampClause;
         String whereTimestampClause;
         Tuple tupleParams;
         Tuple tupleParams;
         if (from != null && to != null) {
         if (from != null && to != null) {
-            whereTimestampClause = "tel.time_stamp >= (CASE WHEN $5 < utc.from_time THEN utc.from_time ELSE $5 END) AND tel.time_stamp <= (CASE WHEN $6 > utc.to_time THEN utc.to_time ELSE $6 END)";
+            whereTimestampClause = "tel.time_stamp >= (CASE WHEN $5 < utc.from_time THEN utc.from_time ELSE $5 END) " +
+                    "AND tel.time_stamp <= (CASE WHEN utc.to_time IS NULL THEN now() ELSE (CASE WHEN $6 > utc.to_time THEN utc.to_time ELSE $6 END) END)";
             tupleParams = Tuple.of(campaignId, unitId, offset, limit, from, to);
             tupleParams = Tuple.of(campaignId, unitId, offset, limit, from, to);
         } else if (from != null) {
         } else if (from != null) {
-            whereTimestampClause = "tel.time_stamp >= (CASE WHEN $5 < utc.from_time THEN utc.from_time ELSE $5 END) AND tel.time_stamp <= utc.to_time";
+            whereTimestampClause = "tel.time_stamp >= (CASE WHEN $5 < utc.from_time THEN utc.from_time ELSE $5 END) AND tel.time_stamp <= (CASE WHEN utc.to_time IS NULL THEN now() ELSE utc.to_time END)";
             tupleParams = Tuple.of(campaignId, unitId, offset, limit, from);
             tupleParams = Tuple.of(campaignId, unitId, offset, limit, from);
         } else if (to != null) {
         } else if (to != null) {
-            whereTimestampClause = "tel.time_stamp >= utc.from_time AND tel.time_stamp <= (CASE WHEN $5 > utc.to_time THEN utc.to_time ELSE $5 END)";
+            whereTimestampClause = "tel.time_stamp >= utc.from_time AND tel.time_stamp <= (CASE WHEN utc.to_time IS NULL THEN now() ELSE (CASE WHEN $5 > utc.to_time THEN utc.to_time ELSE $5 END) END)";
             tupleParams = Tuple.of(campaignId, unitId, offset, limit, to);
             tupleParams = Tuple.of(campaignId, unitId, offset, limit, to);
         } else {
         } else {
-            whereTimestampClause = "tel.time_stamp >= utc.from_time AND tel.time_stamp <= utc.to_time";
+            whereTimestampClause = "tel.time_stamp >= utc.from_time AND tel.time_stamp <= (CASE WHEN utc.to_time IS NULL THEN now() ELSE utc.to_time END)";
             tupleParams = Tuple.of(campaignId, unitId, offset, limit);
             tupleParams = Tuple.of(campaignId, unitId, offset, limit);
         }
         }
 
 
@@ -1978,16 +1988,17 @@ public class MapLogRepository implements SensLogRepository {
         String whereTimestampClause;
         String whereTimestampClause;
         Tuple tupleParams;
         Tuple tupleParams;
         if (from != null && to != null) {
         if (from != null && to != null) {
-            whereTimestampClause = "tel.time_stamp >= (CASE WHEN $6 < utc.from_time THEN utc.from_time ELSE $6 END) AND tel.time_stamp <= (CASE WHEN $7 > utc.to_time THEN utc.to_time ELSE $7 END)";
+            whereTimestampClause = "tel.time_stamp >= (CASE WHEN $6 < utc.from_time THEN utc.from_time ELSE $6 END) " +
+                    "AND tel.time_stamp <= (CASE WHEN utc.to_time IS NULL THEN now() ELSE (CASE WHEN $7 > utc.to_time THEN utc.to_time ELSE $7 END) END)";
             tupleParams = Tuple.of(campaignId, unitId, offset, limit, userIdentity, from, to);
             tupleParams = Tuple.of(campaignId, unitId, offset, limit, userIdentity, from, to);
         } else if (from != null) {
         } else if (from != null) {
-            whereTimestampClause = "tel.time_stamp >= (CASE WHEN $6 < utc.from_time THEN utc.from_time ELSE $6 END) AND tel.time_stamp <= utc.to_time";
+            whereTimestampClause = "tel.time_stamp >= (CASE WHEN $6 < utc.from_time THEN utc.from_time ELSE $6 END) AND tel.time_stamp <= (CASE WHEN utc.to_time IS NULL THEN now() ELSE utc.to_time END)";
             tupleParams = Tuple.of(campaignId, unitId, offset, limit, userIdentity, from);
             tupleParams = Tuple.of(campaignId, unitId, offset, limit, userIdentity, from);
         } else if (to != null) {
         } else if (to != null) {
-            whereTimestampClause = "tel.time_stamp >= utc.from_time AND tel.time_stamp <= (CASE WHEN $6 > utc.to_time THEN utc.to_time ELSE $6 END)";
+            whereTimestampClause = "tel.time_stamp >= utc.from_time AND tel.time_stamp <= (CASE WHEN utc.to_time IS NULL THEN now() ELSE (CASE WHEN $6 > utc.to_time THEN utc.to_time ELSE $6 END) END)";
             tupleParams = Tuple.of(campaignId, unitId, offset, limit, userIdentity, to);
             tupleParams = Tuple.of(campaignId, unitId, offset, limit, userIdentity, to);
         } else {
         } else {
-            whereTimestampClause = "tel.time_stamp >= utc.from_time AND tel.time_stamp <= utc.to_time";
+            whereTimestampClause = "tel.time_stamp >= utc.from_time AND tel.time_stamp <= (CASE WHEN utc.to_time IS NULL THEN now() ELSE utc.to_time END)";
             tupleParams = Tuple.of(campaignId, unitId, offset, limit, userIdentity);
             tupleParams = Tuple.of(campaignId, unitId, offset, limit, userIdentity);
         }
         }
 
 
@@ -2336,7 +2347,8 @@ public class MapLogRepository implements SensLogRepository {
         return client.preparedQuery("SELECT a.id, utc.campaign_id, a.unit_id, a.message, a.time_stamp, a.status FROM maplog.alert AS a " +
         return client.preparedQuery("SELECT a.id, utc.campaign_id, a.unit_id, a.message, a.time_stamp, a.status FROM maplog.alert AS a " +
                         "JOIN maplog.unit u on u.unit_id = a.unit_id " +
                         "JOIN maplog.unit u on u.unit_id = a.unit_id " +
                         "JOIN maplog.unit_to_campaign utc on u.unit_id = utc.unit_id " +
                         "JOIN maplog.unit_to_campaign utc on u.unit_id = utc.unit_id " +
-                        "WHERE utc.from_time <= a.time_stamp AND utc.to_time >= a.time_stamp AND a.id = $1")
+                        "WHERE utc.from_time <= a.time_stamp AND (CASE WHEN utc.to_time IS NULL THEN now() ELSE utc.to_time END) >= a.time_stamp " +
+                        "AND a.id = $1")
                 .execute(Tuple.of(alertId))
                 .execute(Tuple.of(alertId))
                 .map(RowSet::iterator)
                 .map(RowSet::iterator)
                 .map(it -> it.hasNext() ? ROW_TO_ALERT.apply(it.next()) : null)
                 .map(it -> it.hasNext() ? ROW_TO_ALERT.apply(it.next()) : null)
@@ -2352,8 +2364,8 @@ public class MapLogRepository implements SensLogRepository {
                         "JOIN maplog.event e on e.unit_id = utc.unit_id " +
                         "JOIN maplog.event e on e.unit_id = utc.unit_id " +
                         "JOIN maplog.user_to_campaign_config uc ON uc.campaign_id = utc.campaign_id and uc.entity_id = e.entity_id " +
                         "JOIN maplog.user_to_campaign_config uc ON uc.campaign_id = utc.campaign_id and uc.entity_id = e.entity_id " +
                         "JOIN maplog.system_user AS su ON su.id = uc.user_id " +
                         "JOIN maplog.system_user AS su ON su.id = uc.user_id " +
-                        "WHERE utc.from_time <= a.time_stamp AND utc.to_time >= a.time_stamp AND a.id = $1 AND su.identity = $2 " +
-                        "GROUP BY a.id, utc.campaign_id")
+                        "WHERE utc.from_time <= a.time_stamp AND (CASE WHEN utc.to_time IS NULL THEN now() ELSE utc.to_time END) >= a.time_stamp " +
+                        "AND a.id = $1 AND su.identity = $2 GROUP BY a.id, utc.campaign_id")
                 .execute(Tuple.of(alertId, userIdentity))
                 .execute(Tuple.of(alertId, userIdentity))
                 .map(RowSet::iterator)
                 .map(RowSet::iterator)
                 .map(it -> it.hasNext() ? ROW_TO_ALERT.apply(it.next()) : null)
                 .map(it -> it.hasNext() ? ROW_TO_ALERT.apply(it.next()) : null)
@@ -2499,13 +2511,16 @@ public class MapLogRepository implements SensLogRepository {
         String whereTimestampClause;
         String whereTimestampClause;
         Tuple tupleParams;
         Tuple tupleParams;
         if (from != null && to != null) {
         if (from != null && to != null) {
-            whereTimestampClause = "WHERE utc.campaign_id = $1 AND a.status = ANY($2) AND a.time_stamp >= (CASE WHEN utc.from_time > $3 THEN utc.from_time ELSE $3 END) AND a.time_stamp <= (CASE WHEN utc.to_time < $4 THEN utc.to_time ELSE $4 END)";
+            whereTimestampClause = "WHERE utc.campaign_id = $1 AND a.status = ANY($2) AND a.time_stamp >= (CASE WHEN utc.from_time > $3 THEN utc.from_time ELSE $3 END) " +
+                    "AND a.time_stamp <= (CASE WHEN utc.to_time IS NULL THEN now() ELSE (CASE WHEN utc.to_time < $4 THEN utc.to_time ELSE $4 END) END)";
             tupleParams = Tuple.of(campaignId, statuses, from, to);
             tupleParams = Tuple.of(campaignId, statuses, from, to);
         } else if (from != null) {
         } else if (from != null) {
-            whereTimestampClause = "WHERE utc.campaign_id = $1 AND a.status = ANY($2) AND a.time_stamp >= (CASE WHEN utc.from_time > $3 THEN utc.from_time ELSE $3 END) AND a.time_stamp <= utc.to_time";
+            whereTimestampClause = "WHERE utc.campaign_id = $1 AND a.status = ANY($2) AND a.time_stamp >= (CASE WHEN utc.from_time > $3 THEN utc.from_time ELSE $3 END) " +
+                    "AND a.time_stamp <= (CASE WHEN utc.to_time IS NULL THEN now() ELSE utc.to_time END)";
             tupleParams = Tuple.of(campaignId, statuses, from);
             tupleParams = Tuple.of(campaignId, statuses, from);
         } else if (to != null) {
         } else if (to != null) {
-            whereTimestampClause = "WHERE utc.campaign_id = $1 AND a.status = ANY($2) AND a.time_stamp >= utc.from_time AND a.time_stamp <= (CASE WHEN utc.to_time < $3 THEN utc.to_time ELSE $3 END)";
+            whereTimestampClause = "WHERE utc.campaign_id = $1 AND a.status = ANY($2) AND a.time_stamp >= utc.from_time " +
+                    "AND a.time_stamp <= (CASE WHEN utc.to_time IS NULL THEN now() ELSE (CASE WHEN utc.to_time < $3 THEN utc.to_time ELSE $3 END) END)";
             tupleParams = Tuple.of(campaignId, statuses, to);
             tupleParams = Tuple.of(campaignId, statuses, to);
         } else {
         } else {
             whereTimestampClause = "WHERE utc.campaign_id = $1 AND a.status = ANY($2)";
             whereTimestampClause = "WHERE utc.campaign_id = $1 AND a.status = ANY($2)";
@@ -2541,13 +2556,17 @@ public class MapLogRepository implements SensLogRepository {
         String whereTimestampClause;
         String whereTimestampClause;
         Tuple tupleParams;
         Tuple tupleParams;
         if (from != null && to != null) {
         if (from != null && to != null) {
-            whereTimestampClause = "utc.campaign_id = $1 AND su.identity = $2 AND a.status = ANY($3) AND a.time_stamp >= (CASE WHEN utc.from_time > $4 THEN utc.from_time ELSE $4 END) AND a.time_stamp <= (CASE WHEN utc.to_time < $5 THEN utc.to_time ELSE $5 END)";
+            whereTimestampClause = "utc.campaign_id = $1 AND su.identity = $2 AND a.status = ANY($3) " +
+                    "AND a.time_stamp >= (CASE WHEN utc.from_time > $4 THEN utc.from_time ELSE $4 END) " +
+                    "AND a.time_stamp <= (CASE WHEN utc.to_time IS NULL THEN now() ELSE (CASE WHEN utc.to_time < $5 THEN utc.to_time ELSE $5 END) END)";
             tupleParams = Tuple.of(campaignId, userIdentity, statuses, from, to);
             tupleParams = Tuple.of(campaignId, userIdentity, statuses, from, to);
         } else if (from != null) {
         } else if (from != null) {
-            whereTimestampClause = "utc.campaign_id = $1 AND su.identity = $2 AND a.status = ANY($3) AND a.time_stamp >= (CASE WHEN utc.from_time > $4 THEN utc.from_time ELSE $4 END) AND a.time_stamp <= utc.to_time";
+            whereTimestampClause = "utc.campaign_id = $1 AND su.identity = $2 AND a.status = ANY($3) " +
+                    "AND a.time_stamp >= (CASE WHEN utc.from_time > $4 THEN utc.from_time ELSE $4 END) AND a.time_stamp <= (CASE WHEN utc.to_time IS NULL THEN now() ELSE utc.to_time END)";
             tupleParams = Tuple.of(campaignId, userIdentity, statuses, from);
             tupleParams = Tuple.of(campaignId, userIdentity, statuses, from);
         } else if (to != null) {
         } else if (to != null) {
-            whereTimestampClause = "utc.campaign_id = $1 AND su.identity = $2 AND a.status = ANY($3) AND a.time_stamp >= utc.from_time AND a.time_stamp <= (CASE WHEN utc.to_time < $4 THEN utc.to_time ELSE $4 END)";
+            whereTimestampClause = "utc.campaign_id = $1 AND su.identity = $2 AND a.status = ANY($3) AND a.time_stamp >= utc.from_time " +
+                    "AND a.time_stamp <= (CASE WHEN utc.to_time IS NULL THEN now() ELSE (CASE WHEN utc.to_time < $4 THEN utc.to_time ELSE $4 END) END)";
             tupleParams = Tuple.of(campaignId, userIdentity, statuses, to);
             tupleParams = Tuple.of(campaignId, userIdentity, statuses, to);
         } else {
         } else {
             whereTimestampClause = "utc.campaign_id = $1 AND su.identity = $2 AND a.status = ANY($3)";
             whereTimestampClause = "utc.campaign_id = $1 AND su.identity = $2 AND a.status = ANY($3)";
@@ -2587,13 +2606,17 @@ public class MapLogRepository implements SensLogRepository {
         String whereTimestampClause;
         String whereTimestampClause;
         Tuple tupleParams;
         Tuple tupleParams;
         if (from != null && to != null) {
         if (from != null && to != null) {
-            whereTimestampClause = "WHERE utc.campaign_id = $1 AND utc.unit_id = $2 AND a.status = ANY($3) AND a.time_stamp >= (CASE WHEN utc.from_time > $4 THEN utc.from_time ELSE $4 END) AND a.time_stamp <= (CASE WHEN utc.to_time < $5 THEN utc.to_time ELSE $5 END)";
+            whereTimestampClause = "WHERE utc.campaign_id = $1 AND utc.unit_id = $2 AND a.status = ANY($3) " +
+                    "AND a.time_stamp >= (CASE WHEN utc.from_time > $4 THEN utc.from_time ELSE $4 END) " +
+                    "AND a.time_stamp <= (CASE WHEN utc.to_time IS NULL THEN now() ELSE (CASE WHEN utc.to_time < $5 THEN utc.to_time ELSE $5 END) END)";
             tupleParams = Tuple.of(campaignId, unitId, statuses, from, to);
             tupleParams = Tuple.of(campaignId, unitId, statuses, from, to);
         } else if (from != null) {
         } else if (from != null) {
-            whereTimestampClause = "WHERE utc.campaign_id = $1 AND utc.unit_id = $2 AND a.status = ANY($3) AND a.time_stamp >= (CASE WHEN utc.from_time > $4 THEN utc.from_time ELSE $4 END) AND a.time_stamp <= utc.to_time";
+            whereTimestampClause = "WHERE utc.campaign_id = $1 AND utc.unit_id = $2 AND a.status = ANY($3) " +
+                    "AND a.time_stamp >= (CASE WHEN utc.from_time > $4 THEN utc.from_time ELSE $4 END) AND a.time_stamp <= (CASE WHEN utc.to_time IS NULL THEN now() ELSE utc.to_time END)";
             tupleParams = Tuple.of(campaignId, unitId, statuses, from);
             tupleParams = Tuple.of(campaignId, unitId, statuses, from);
         } else if (to != null) {
         } else if (to != null) {
-            whereTimestampClause = "WHERE utc.campaign_id = $1 AND utc.unit_id = $2 AND a.status = ANY($3) AND a.time_stamp >= utc.from_time AND a.time_stamp <= (CASE WHEN utc.to_time < $4 THEN utc.to_time ELSE $4 END)";
+            whereTimestampClause = "WHERE utc.campaign_id = $1 AND utc.unit_id = $2 AND a.status = ANY($3) AND a.time_stamp >= utc.from_time " +
+                    "AND a.time_stamp <= (CASE WHEN utc.to_time IS NULL THEN now() ELSE (CASE WHEN utc.to_time < $4 THEN utc.to_time ELSE $4 END) END)";
             tupleParams = Tuple.of(campaignId, unitId, statuses, to);
             tupleParams = Tuple.of(campaignId, unitId, statuses, to);
         } else {
         } else {
             whereTimestampClause = "WHERE utc.campaign_id = $1 AND utc.unit_id = $2 AND a.status = ANY($3)";
             whereTimestampClause = "WHERE utc.campaign_id = $1 AND utc.unit_id = $2 AND a.status = ANY($3)";
@@ -2628,13 +2651,18 @@ public class MapLogRepository implements SensLogRepository {
         String whereTimestampClause;
         String whereTimestampClause;
         Tuple tupleParams;
         Tuple tupleParams;
         if (from != null && to != null) {
         if (from != null && to != null) {
-            whereTimestampClause = "utc.campaign_id = $1 AND utc.unit_id = $2 AND su.identity = $3 AND a.status = ANY($4) AND a.time_stamp >= (CASE WHEN utc.from_time > $5 THEN utc.from_time ELSE $5 END) AND a.time_stamp <= (CASE WHEN utc.to_time < $6 THEN utc.to_time ELSE $6 END)";
+            whereTimestampClause = "utc.campaign_id = $1 AND utc.unit_id = $2 AND su.identity = $3 AND a.status = ANY($4) " +
+                    "AND a.time_stamp >= (CASE WHEN utc.from_time > $5 THEN utc.from_time ELSE $5 END) " +
+                    "AND a.time_stamp <= (CASE WHEN utc.to_time IS NULL THEN now() ELSE (CASE WHEN utc.to_time < $6 THEN utc.to_time ELSE $6 END) END)";
             tupleParams = Tuple.of(campaignId, unitId, userIdentity, statuses, from, to);
             tupleParams = Tuple.of(campaignId, unitId, userIdentity, statuses, from, to);
         } else if (from != null) {
         } else if (from != null) {
-            whereTimestampClause = "utc.campaign_id = $1 AND utc.unit_id = $2 AND su.identity = $3 AND a.status = ANY($4) AND a.time_stamp >= (CASE WHEN utc.from_time > $5 THEN utc.from_time ELSE $5 END) AND a.time_stamp <= utc.to_time";
+            whereTimestampClause = "utc.campaign_id = $1 AND utc.unit_id = $2 AND su.identity = $3 AND a.status = ANY($4) " +
+                    "AND a.time_stamp >= (CASE WHEN utc.from_time > $5 THEN utc.from_time ELSE $5 END) " +
+                    "AND a.time_stamp <= (CASE WHEN utc.to_time IS NULL THEN now() ELSE utc.to_time END)";
             tupleParams = Tuple.of(campaignId, unitId, userIdentity, statuses, from);
             tupleParams = Tuple.of(campaignId, unitId, userIdentity, statuses, from);
         } else if (to != null) {
         } else if (to != null) {
-            whereTimestampClause = "utc.campaign_id = $1 AND utc.unit_id = $2 AND su.identity = $3 AND a.status = ANY($4) AND a.time_stamp >= utc.from_time AND a.time_stamp <= (CASE WHEN utc.to_time < $5 THEN utc.to_time ELSE $5 END)";
+            whereTimestampClause = "utc.campaign_id = $1 AND utc.unit_id = $2 AND su.identity = $3 AND a.status = ANY($4) " +
+                    "AND a.time_stamp >= utc.from_time AND AND a.time_stamp <= (CASE WHEN utc.to_time IS NULL THEN now() ELSE (CASE WHEN utc.to_time < $5 THEN utc.to_time ELSE $5 END) END)";
             tupleParams = Tuple.of(campaignId, unitId, userIdentity, statuses, to);
             tupleParams = Tuple.of(campaignId, unitId, userIdentity, statuses, to);
         } else {
         } else {
             whereTimestampClause = "utc.campaign_id = $1 AND utc.unit_id = $2 AND su.identity = $3 AND a.status = ANY($4)";
             whereTimestampClause = "utc.campaign_id = $1 AND utc.unit_id = $2 AND su.identity = $3 AND a.status = ANY($4)";

+ 6 - 2
src/main/java/cz/senslog/telemetry/server/ws/WSParameters.java

@@ -96,7 +96,9 @@ public class WSParameters {
             }
             }
 
 
             OffsetDateTime from = OffsetDateTime.parse(strParam);
             OffsetDateTime from = OffsetDateTime.parse(strParam);
-            backup.put("from", from.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME));
+            if (backup != null) {
+                backup.put("from", from.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME));
+            }
             return from;
             return from;
         }
         }
 
 
@@ -107,7 +109,9 @@ public class WSParameters {
             }
             }
 
 
             OffsetDateTime to = OffsetDateTime.parse(strParam);
             OffsetDateTime to = OffsetDateTime.parse(strParam);
-            backup.put("to", to.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME));
+            if (backup != null) {
+                backup.put("to", to.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME));
+            }
             return to;
             return to;
         }
         }
 
 

+ 5 - 1
src/main/java/cz/senslog/telemetry/utils/CascadeCondition.java

@@ -36,8 +36,11 @@ public interface CascadeCondition {
         private O result;
         private O result;
         private Supplier<O> defaultConsumer;
         private Supplier<O> defaultConsumer;
 
 
+        private boolean isResulted;
+
         private CascadeConditionImpl(I input) {
         private CascadeConditionImpl(I input) {
             this.input = input;
             this.input = input;
+            this.isResulted = false;
         }
         }
 
 
         @Override
         @Override
@@ -45,6 +48,7 @@ public interface CascadeCondition {
             this.defaultConsumer = consumer;
             this.defaultConsumer = consumer;
             if (input == null || condition.apply(input)) {
             if (input == null || condition.apply(input)) {
                 this.result = consumer.get();
                 this.result = consumer.get();
+                this.isResulted = true;
             }
             }
             return this;
             return this;
         }
         }
@@ -60,7 +64,7 @@ public interface CascadeCondition {
 
 
         @Override
         @Override
         public CascadeConditionBuilder<I, O> ifThen(Function<I, Boolean> condition, Function<I, O> consumer) {
         public CascadeConditionBuilder<I, O> ifThen(Function<I, Boolean> condition, Function<I, O> consumer) {
-            if (result == null && condition.apply(input)) {
+            if (!isResulted && condition.apply(input)) {
                 this.result = consumer.apply(input);
                 this.result = consumer.apply(input);
             }
             }
             return this;
             return this;

+ 608 - 7
src/test/java/cz/senslog/telemetry/server/ws/OpenAPIHandlerTest.java

@@ -5,6 +5,7 @@ import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import cz.senslog.telemetry.TestPropertiesUtils;
 import cz.senslog.telemetry.TestPropertiesUtils;
 import cz.senslog.telemetry.app.PropertyConfig;
 import cz.senslog.telemetry.app.PropertyConfig;
+import cz.senslog.telemetry.database.PagingRetrieve;
 import cz.senslog.telemetry.database.domain.*;
 import cz.senslog.telemetry.database.domain.*;
 import cz.senslog.telemetry.database.repository.SensLogRepository;
 import cz.senslog.telemetry.database.repository.SensLogRepository;
 import cz.senslog.telemetry.server.HttpVertxServer;
 import cz.senslog.telemetry.server.HttpVertxServer;
@@ -39,9 +40,7 @@ import uk.org.webcompere.systemstubs.jupiter.SystemStubsExtension;
 import java.net.MalformedURLException;
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.net.URL;
 import java.time.*;
 import java.time.*;
-import java.util.List;
-import java.util.Map;
-import java.util.Properties;
+import java.util.*;
 import java.util.stream.Stream;
 import java.util.stream.Stream;
 
 
 import static cz.senslog.telemetry.server.ws.ContentType.JSON;
 import static cz.senslog.telemetry.server.ws.ContentType.JSON;
@@ -50,6 +49,7 @@ import static cz.senslog.telemetry.server.ws.OpenAPIHandlerTest.OpenAPIResponseT
 import static java.time.format.DateTimeFormatter.ISO_OFFSET_DATE_TIME;
 import static java.time.format.DateTimeFormatter.ISO_OFFSET_DATE_TIME;
 import static java.time.format.DateTimeFormatter.ISO_ZONED_DATE_TIME;
 import static java.time.format.DateTimeFormatter.ISO_ZONED_DATE_TIME;
 import static java.util.Collections.emptyList;
 import static java.util.Collections.emptyList;
+import static java.util.stream.Collectors.toSet;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.ArgumentMatchers.*;
 import static org.mockito.ArgumentMatchers.*;
 
 
@@ -63,6 +63,9 @@ class OpenAPIHandlerTest {
 
 
     private static OpenApi3 OPEN_API;
     private static OpenApi3 OPEN_API;
 
 
+    private static Set<String> VISITED_TEST_NODES;
+
+
     enum OpenAPIResponseTyp {
     enum OpenAPIResponseTyp {
         SUCCESS ("200"), ERROR ("default")
         SUCCESS ("200"), ERROR ("default")
         ;
         ;
@@ -97,10 +100,7 @@ class OpenAPIHandlerTest {
     @BeforeAll
     @BeforeAll
     static void setUp() throws MalformedURLException, ResolutionException, ValidationException {
     static void setUp() throws MalformedURLException, ResolutionException, ValidationException {
         OPEN_API = new OpenApi3Parser().parse(new URL("file:src/main/resources/openAPISpec.yaml"), false);
         OPEN_API = new OpenApi3Parser().parse(new URL("file:src/main/resources/openAPISpec.yaml"), false);
-    }
-
-    @AfterAll
-    static void tearDown() {
+        VISITED_TEST_NODES = new HashSet<>(OPEN_API.getPaths().size());
     }
     }
 
 
     @BeforeEach
     @BeforeEach
@@ -128,10 +128,20 @@ class OpenAPIHandlerTest {
                 .hasSize(1);
                 .hasSize(1);
     }
     }
 
 
+    @AfterAll
+    static void tearDown() {
+        if (OPEN_API.getPaths().size() != VISITED_TEST_NODES.size()) {
+            assertThat(VISITED_TEST_NODES).containsAll(OPEN_API.getPaths().values()
+                    .stream().map(p -> p.getGet().getOperationId()).collect(toSet()));
+        }
+    }
+
     @Test
     @Test
     @DisplayName("API Spec Test: infoGET")
     @DisplayName("API Spec Test: infoGET")
     void infoGET(Vertx vertx, VertxTestContext testContext) {
     void infoGET(Vertx vertx, VertxTestContext testContext) {
         Operation operation = OPEN_API.getOperationById("infoGET");
         Operation operation = OPEN_API.getOperationById("infoGET");
+        VISITED_TEST_NODES.add(operation.getOperationId());
+
         RequestOptions reqOpt = new RequestOptions()
         RequestOptions reqOpt = new RequestOptions()
                 .setMethod(HttpMethod.GET).setPort(PORT).setHost(HOST).setURI("/info")
                 .setMethod(HttpMethod.GET).setPort(PORT).setHost(HOST).setURI("/info")
                 .setHeaders(MultiMap.caseInsensitiveMultiMap().add(HttpHeaders.ACCEPT, JSON.contentType()));
                 .setHeaders(MultiMap.caseInsensitiveMultiMap().add(HttpHeaders.ACCEPT, JSON.contentType()));
@@ -172,6 +182,8 @@ class OpenAPIHandlerTest {
         Mockito.when(repo.allCampaigns()).thenReturn(dbFuture);
         Mockito.when(repo.allCampaigns()).thenReturn(dbFuture);
 
 
         Operation operation = OPEN_API.getOperationById("campaignsGET");
         Operation operation = OPEN_API.getOperationById("campaignsGET");
+        VISITED_TEST_NODES.add(operation.getOperationId());
+
         RequestOptions reqOpt = new RequestOptions()
         RequestOptions reqOpt = new RequestOptions()
                 .setMethod(HttpMethod.GET).setPort(PORT).setHost(HOST).setURI("/campaigns")
                 .setMethod(HttpMethod.GET).setPort(PORT).setHost(HOST).setURI("/campaigns")
                 .setHeaders(MultiMap.caseInsensitiveMultiMap()
                 .setHeaders(MultiMap.caseInsensitiveMultiMap()
@@ -207,6 +219,8 @@ class OpenAPIHandlerTest {
         Mockito.when(repo.findCampaignById(anyLong())).thenReturn(dbFuture);
         Mockito.when(repo.findCampaignById(anyLong())).thenReturn(dbFuture);
 
 
         Operation operation = OPEN_API.getOperationById("campaignIdGET");
         Operation operation = OPEN_API.getOperationById("campaignIdGET");
+        VISITED_TEST_NODES.add(operation.getOperationId());
+
         RequestOptions reqOpt = new RequestOptions()
         RequestOptions reqOpt = new RequestOptions()
                 .setMethod(HttpMethod.GET).setPort(PORT).setHost(HOST).setURI("/campaigns/1")
                 .setMethod(HttpMethod.GET).setPort(PORT).setHost(HOST).setURI("/campaigns/1")
                 .setHeaders(MultiMap.caseInsensitiveMultiMap()
                 .setHeaders(MultiMap.caseInsensitiveMultiMap()
@@ -302,6 +316,8 @@ class OpenAPIHandlerTest {
         Mockito.when(repo.findUnitsByCampaignId(anyLong())).thenReturn(dbFuture);
         Mockito.when(repo.findUnitsByCampaignId(anyLong())).thenReturn(dbFuture);
 
 
         Operation operation = OPEN_API.getOperationById("campaignIdUnitsGET");
         Operation operation = OPEN_API.getOperationById("campaignIdUnitsGET");
+        VISITED_TEST_NODES.add(operation.getOperationId());
+
         RequestOptions reqOpt = new RequestOptions()
         RequestOptions reqOpt = new RequestOptions()
                 .setMethod(HttpMethod.GET).setPort(PORT).setHost(HOST).setURI("/campaigns/1/units")
                 .setMethod(HttpMethod.GET).setPort(PORT).setHost(HOST).setURI("/campaigns/1/units")
                 .setHeaders(MultiMap.caseInsensitiveMultiMap()
                 .setHeaders(MultiMap.caseInsensitiveMultiMap()
@@ -394,6 +410,8 @@ class OpenAPIHandlerTest {
         Mockito.when(repo.findUnitByIdAndCampaignId(anyLong(), anyLong())).thenReturn(dbFuture);
         Mockito.when(repo.findUnitByIdAndCampaignId(anyLong(), anyLong())).thenReturn(dbFuture);
 
 
         Operation operation = OPEN_API.getOperationById("campaignIdUnitIdGET");
         Operation operation = OPEN_API.getOperationById("campaignIdUnitIdGET");
+        VISITED_TEST_NODES.add(operation.getOperationId());
+
         RequestOptions reqOpt = new RequestOptions()
         RequestOptions reqOpt = new RequestOptions()
                 .setMethod(HttpMethod.GET).setPort(PORT).setHost(HOST).setURI("/campaigns/1/units/1000")
                 .setMethod(HttpMethod.GET).setPort(PORT).setHost(HOST).setURI("/campaigns/1/units/1000")
                 .setHeaders(MultiMap.caseInsensitiveMultiMap()
                 .setHeaders(MultiMap.caseInsensitiveMultiMap()
@@ -491,6 +509,8 @@ class OpenAPIHandlerTest {
         Mockito.when(repo.findSensorsByCampaignIdAndUnitId(anyLong(), anyLong())).thenReturn(dbFuture);
         Mockito.when(repo.findSensorsByCampaignIdAndUnitId(anyLong(), anyLong())).thenReturn(dbFuture);
 
 
         Operation operation = OPEN_API.getOperationById("campaignIdUnitIdSensorsGET");
         Operation operation = OPEN_API.getOperationById("campaignIdUnitIdSensorsGET");
+        VISITED_TEST_NODES.add(operation.getOperationId());
+
         RequestOptions reqOpt = new RequestOptions()
         RequestOptions reqOpt = new RequestOptions()
                 .setMethod(HttpMethod.GET).setPort(PORT).setHost(HOST).setURI("/campaigns/1/units/1000/sensors")
                 .setMethod(HttpMethod.GET).setPort(PORT).setHost(HOST).setURI("/campaigns/1/units/1000/sensors")
                 .setHeaders(MultiMap.caseInsensitiveMultiMap()
                 .setHeaders(MultiMap.caseInsensitiveMultiMap()
@@ -557,6 +577,8 @@ class OpenAPIHandlerTest {
         Mockito.when(repo.findSensorByCampaignIdAndUnitId(anyLong(), anyLong(), anyLong())).thenReturn(dbFuture);
         Mockito.when(repo.findSensorByCampaignIdAndUnitId(anyLong(), anyLong(), anyLong())).thenReturn(dbFuture);
 
 
         Operation operation = OPEN_API.getOperationById("campaignIdUnitIdSensorIdGET");
         Operation operation = OPEN_API.getOperationById("campaignIdUnitIdSensorIdGET");
+        VISITED_TEST_NODES.add(operation.getOperationId());
+
         RequestOptions reqOpt = new RequestOptions()
         RequestOptions reqOpt = new RequestOptions()
                 .setMethod(HttpMethod.GET).setPort(PORT).setHost(HOST).setURI("/campaigns/1/units/1000/sensors/105")
                 .setMethod(HttpMethod.GET).setPort(PORT).setHost(HOST).setURI("/campaigns/1/units/1000/sensors/105")
                 .setHeaders(MultiMap.caseInsensitiveMultiMap()
                 .setHeaders(MultiMap.caseInsensitiveMultiMap()
@@ -631,6 +653,8 @@ class OpenAPIHandlerTest {
         Mockito.when(repo.allUnits()).thenReturn(dbFuture);
         Mockito.when(repo.allUnits()).thenReturn(dbFuture);
 
 
         Operation operation = OPEN_API.getOperationById("unitsGET");
         Operation operation = OPEN_API.getOperationById("unitsGET");
+        VISITED_TEST_NODES.add(operation.getOperationId());
+
         RequestOptions reqOpt = new RequestOptions()
         RequestOptions reqOpt = new RequestOptions()
                 .setMethod(HttpMethod.GET).setPort(PORT).setHost(HOST).setURI("/units")
                 .setMethod(HttpMethod.GET).setPort(PORT).setHost(HOST).setURI("/units")
                 .setHeaders(MultiMap.caseInsensitiveMultiMap()
                 .setHeaders(MultiMap.caseInsensitiveMultiMap()
@@ -663,6 +687,8 @@ class OpenAPIHandlerTest {
         Mockito.when(repo.findUnitById(anyLong())).thenReturn(dbFuture);
         Mockito.when(repo.findUnitById(anyLong())).thenReturn(dbFuture);
 
 
         Operation operation = OPEN_API.getOperationById("unitIdGET");
         Operation operation = OPEN_API.getOperationById("unitIdGET");
+        VISITED_TEST_NODES.add(operation.getOperationId());
+
         RequestOptions reqOpt = new RequestOptions()
         RequestOptions reqOpt = new RequestOptions()
                 .setMethod(HttpMethod.GET).setPort(PORT).setHost(HOST).setURI("/units/1000")
                 .setMethod(HttpMethod.GET).setPort(PORT).setHost(HOST).setURI("/units/1000")
                 .setHeaders(MultiMap.caseInsensitiveMultiMap()
                 .setHeaders(MultiMap.caseInsensitiveMultiMap()
@@ -736,6 +762,8 @@ class OpenAPIHandlerTest {
         Mockito.when(repo.findSensorsByUnitId(anyLong())).thenReturn(dbFuture);
         Mockito.when(repo.findSensorsByUnitId(anyLong())).thenReturn(dbFuture);
 
 
         Operation operation = OPEN_API.getOperationById("unitIdSensorsGET");
         Operation operation = OPEN_API.getOperationById("unitIdSensorsGET");
+        VISITED_TEST_NODES.add(operation.getOperationId());
+
         RequestOptions reqOpt = new RequestOptions()
         RequestOptions reqOpt = new RequestOptions()
                 .setMethod(HttpMethod.GET).setPort(PORT).setHost(HOST).setURI("/units/1000/sensors")
                 .setMethod(HttpMethod.GET).setPort(PORT).setHost(HOST).setURI("/units/1000/sensors")
                 .setHeaders(MultiMap.caseInsensitiveMultiMap()
                 .setHeaders(MultiMap.caseInsensitiveMultiMap()
@@ -809,6 +837,8 @@ class OpenAPIHandlerTest {
         Mockito.when(repo.findCampaignsByUnitId(anyLong())).thenReturn(dbFuture);
         Mockito.when(repo.findCampaignsByUnitId(anyLong())).thenReturn(dbFuture);
 
 
         Operation operation = OPEN_API.getOperationById("unitIdCampaignsGET");
         Operation operation = OPEN_API.getOperationById("unitIdCampaignsGET");
+        VISITED_TEST_NODES.add(operation.getOperationId());
+
         RequestOptions reqOpt = new RequestOptions()
         RequestOptions reqOpt = new RequestOptions()
                 .setMethod(HttpMethod.GET).setPort(PORT).setHost(HOST).setURI("/units/1000/campaigns")
                 .setMethod(HttpMethod.GET).setPort(PORT).setHost(HOST).setURI("/units/1000/campaigns")
                 .setHeaders(MultiMap.caseInsensitiveMultiMap()
                 .setHeaders(MultiMap.caseInsensitiveMultiMap()
@@ -899,6 +929,8 @@ class OpenAPIHandlerTest {
         Mockito.when(repo.findEntitiesByUnitId(anyLong())).thenReturn(dbFuture);
         Mockito.when(repo.findEntitiesByUnitId(anyLong())).thenReturn(dbFuture);
 
 
         Operation operation = OPEN_API.getOperationById("unitIdEntitiesGET");
         Operation operation = OPEN_API.getOperationById("unitIdEntitiesGET");
+        VISITED_TEST_NODES.add(operation.getOperationId());
+
         RequestOptions reqOpt = new RequestOptions()
         RequestOptions reqOpt = new RequestOptions()
                 .setMethod(HttpMethod.GET).setPort(PORT).setHost(HOST).setURI("/units/1000/entities")
                 .setMethod(HttpMethod.GET).setPort(PORT).setHost(HOST).setURI("/units/1000/entities")
                 .setHeaders(MultiMap.caseInsensitiveMultiMap()
                 .setHeaders(MultiMap.caseInsensitiveMultiMap()
@@ -971,6 +1003,8 @@ class OpenAPIHandlerTest {
         Mockito.when(repo.allSensors()).thenReturn(dbFuture);
         Mockito.when(repo.allSensors()).thenReturn(dbFuture);
 
 
         Operation operation = OPEN_API.getOperationById("sensorsGET");
         Operation operation = OPEN_API.getOperationById("sensorsGET");
+        VISITED_TEST_NODES.add(operation.getOperationId());
+
         RequestOptions reqOpt = new RequestOptions()
         RequestOptions reqOpt = new RequestOptions()
                 .setMethod(HttpMethod.GET).setPort(PORT).setHost(HOST).setURI("/sensors")
                 .setMethod(HttpMethod.GET).setPort(PORT).setHost(HOST).setURI("/sensors")
                 .setHeaders(MultiMap.caseInsensitiveMultiMap()
                 .setHeaders(MultiMap.caseInsensitiveMultiMap()
@@ -1038,6 +1072,8 @@ class OpenAPIHandlerTest {
         Mockito.when(repo.findSensorById(anyLong())).thenReturn(dbFuture);
         Mockito.when(repo.findSensorById(anyLong())).thenReturn(dbFuture);
 
 
         Operation operation = OPEN_API.getOperationById("sensorIdGET");
         Operation operation = OPEN_API.getOperationById("sensorIdGET");
+        VISITED_TEST_NODES.add(operation.getOperationId());
+
         RequestOptions reqOpt = new RequestOptions()
         RequestOptions reqOpt = new RequestOptions()
                 .setMethod(HttpMethod.GET).setPort(PORT).setHost(HOST).setURI("/sensors/105")
                 .setMethod(HttpMethod.GET).setPort(PORT).setHost(HOST).setURI("/sensors/105")
                 .setHeaders(MultiMap.caseInsensitiveMultiMap()
                 .setHeaders(MultiMap.caseInsensitiveMultiMap()
@@ -1110,6 +1146,8 @@ class OpenAPIHandlerTest {
         Mockito.when(repo.findUnitsBySensorId(anyLong())).thenReturn(dbFuture);
         Mockito.when(repo.findUnitsBySensorId(anyLong())).thenReturn(dbFuture);
 
 
         Operation operation = OPEN_API.getOperationById("sensorIdUnitsGET");
         Operation operation = OPEN_API.getOperationById("sensorIdUnitsGET");
+        VISITED_TEST_NODES.add(operation.getOperationId());
+
         RequestOptions reqOpt = new RequestOptions()
         RequestOptions reqOpt = new RequestOptions()
                 .setMethod(HttpMethod.GET).setPort(PORT).setHost(HOST).setURI("/sensors/105/units")
                 .setMethod(HttpMethod.GET).setPort(PORT).setHost(HOST).setURI("/sensors/105/units")
                 .setHeaders(MultiMap.caseInsensitiveMultiMap()
                 .setHeaders(MultiMap.caseInsensitiveMultiMap()
@@ -1182,6 +1220,8 @@ class OpenAPIHandlerTest {
         Mockito.when(repo.allPhenomenons()).thenReturn(dbFuture);
         Mockito.when(repo.allPhenomenons()).thenReturn(dbFuture);
 
 
         Operation operation = OPEN_API.getOperationById("phenomenonsGET");
         Operation operation = OPEN_API.getOperationById("phenomenonsGET");
+        VISITED_TEST_NODES.add(operation.getOperationId());
+
         RequestOptions reqOpt = new RequestOptions()
         RequestOptions reqOpt = new RequestOptions()
                 .setMethod(HttpMethod.GET).setPort(PORT).setHost(HOST).setURI("/phenomenons")
                 .setMethod(HttpMethod.GET).setPort(PORT).setHost(HOST).setURI("/phenomenons")
                 .setHeaders(MultiMap.caseInsensitiveMultiMap()
                 .setHeaders(MultiMap.caseInsensitiveMultiMap()
@@ -1248,6 +1288,8 @@ class OpenAPIHandlerTest {
         Mockito.when(repo.findPhenomenonById(anyLong())).thenReturn(dbFuture);
         Mockito.when(repo.findPhenomenonById(anyLong())).thenReturn(dbFuture);
 
 
         Operation operation = OPEN_API.getOperationById("phenomenonIdGET");
         Operation operation = OPEN_API.getOperationById("phenomenonIdGET");
+        VISITED_TEST_NODES.add(operation.getOperationId());
+
         RequestOptions reqOpt = new RequestOptions()
         RequestOptions reqOpt = new RequestOptions()
                 .setMethod(HttpMethod.GET).setPort(PORT).setHost(HOST).setURI("/phenomenons/1")
                 .setMethod(HttpMethod.GET).setPort(PORT).setHost(HOST).setURI("/phenomenons/1")
                 .setHeaders(MultiMap.caseInsensitiveMultiMap()
                 .setHeaders(MultiMap.caseInsensitiveMultiMap()
@@ -1320,6 +1362,8 @@ class OpenAPIHandlerTest {
         Mockito.when(repo.findSensorsByPhenomenonId(anyLong())).thenReturn(dbFuture);
         Mockito.when(repo.findSensorsByPhenomenonId(anyLong())).thenReturn(dbFuture);
 
 
         Operation operation = OPEN_API.getOperationById("phenomenonIdSensorsGET");
         Operation operation = OPEN_API.getOperationById("phenomenonIdSensorsGET");
+        VISITED_TEST_NODES.add(operation.getOperationId());
+
         RequestOptions reqOpt = new RequestOptions()
         RequestOptions reqOpt = new RequestOptions()
                 .setMethod(HttpMethod.GET).setPort(PORT).setHost(HOST).setURI("/phenomenons/1/sensors")
                 .setMethod(HttpMethod.GET).setPort(PORT).setHost(HOST).setURI("/phenomenons/1/sensors")
                 .setHeaders(MultiMap.caseInsensitiveMultiMap()
                 .setHeaders(MultiMap.caseInsensitiveMultiMap()
@@ -1393,6 +1437,8 @@ class OpenAPIHandlerTest {
         Mockito.when(repo.allEntities()).thenReturn(dbFuture);
         Mockito.when(repo.allEntities()).thenReturn(dbFuture);
 
 
         Operation operation = OPEN_API.getOperationById("entitiesGET");
         Operation operation = OPEN_API.getOperationById("entitiesGET");
+        VISITED_TEST_NODES.add(operation.getOperationId());
+
         RequestOptions reqOpt = new RequestOptions()
         RequestOptions reqOpt = new RequestOptions()
                 .setMethod(HttpMethod.GET).setPort(PORT).setHost(HOST).setURI("/entities")
                 .setMethod(HttpMethod.GET).setPort(PORT).setHost(HOST).setURI("/entities")
                 .setHeaders(MultiMap.caseInsensitiveMultiMap()
                 .setHeaders(MultiMap.caseInsensitiveMultiMap()
@@ -1459,6 +1505,8 @@ class OpenAPIHandlerTest {
         Mockito.when(repo.findEntityById(anyInt())).thenReturn(dbFuture);
         Mockito.when(repo.findEntityById(anyInt())).thenReturn(dbFuture);
 
 
         Operation operation = OPEN_API.getOperationById("entityIdGET");
         Operation operation = OPEN_API.getOperationById("entityIdGET");
+        VISITED_TEST_NODES.add(operation.getOperationId());
+
         RequestOptions reqOpt = new RequestOptions()
         RequestOptions reqOpt = new RequestOptions()
                 .setMethod(HttpMethod.GET).setPort(PORT).setHost(HOST).setURI("/entities/1")
                 .setMethod(HttpMethod.GET).setPort(PORT).setHost(HOST).setURI("/entities/1")
                 .setHeaders(MultiMap.caseInsensitiveMultiMap()
                 .setHeaders(MultiMap.caseInsensitiveMultiMap()
@@ -1532,6 +1580,8 @@ class OpenAPIHandlerTest {
         Mockito.when(repo.findUnitsByEntityId(anyInt(), any(), any())).thenReturn(dbFuture);
         Mockito.when(repo.findUnitsByEntityId(anyInt(), any(), any())).thenReturn(dbFuture);
 
 
         Operation operation = OPEN_API.getOperationById("entityIdUnitsGET");
         Operation operation = OPEN_API.getOperationById("entityIdUnitsGET");
+        VISITED_TEST_NODES.add(operation.getOperationId());
+
         RequestOptions reqOpt = new RequestOptions()
         RequestOptions reqOpt = new RequestOptions()
                 .setMethod(HttpMethod.GET).setPort(PORT).setHost(HOST).setURI("/entities/1/units")
                 .setMethod(HttpMethod.GET).setPort(PORT).setHost(HOST).setURI("/entities/1/units")
                 .setHeaders(MultiMap.caseInsensitiveMultiMap()
                 .setHeaders(MultiMap.caseInsensitiveMultiMap()
@@ -1606,6 +1656,8 @@ class OpenAPIHandlerTest {
         Mockito.when(repo.findUnitByIdAndEntityId(anyLong(), anyInt())).thenReturn(dbFuture);
         Mockito.when(repo.findUnitByIdAndEntityId(anyLong(), anyInt())).thenReturn(dbFuture);
 
 
         Operation operation = OPEN_API.getOperationById("entityIdUnitIdGET");
         Operation operation = OPEN_API.getOperationById("entityIdUnitIdGET");
+        VISITED_TEST_NODES.add(operation.getOperationId());
+
         RequestOptions reqOpt = new RequestOptions()
         RequestOptions reqOpt = new RequestOptions()
                 .setMethod(HttpMethod.GET).setPort(PORT).setHost(HOST).setURI("/entities/1/units/1000")
                 .setMethod(HttpMethod.GET).setPort(PORT).setHost(HOST).setURI("/entities/1/units/1000")
                 .setHeaders(MultiMap.caseInsensitiveMultiMap()
                 .setHeaders(MultiMap.caseInsensitiveMultiMap()
@@ -1679,6 +1731,8 @@ class OpenAPIHandlerTest {
         Mockito.when(repo.findActionsByEntityIdAndUnitId(anyInt(), anyLong())).thenReturn(dbFuture);
         Mockito.when(repo.findActionsByEntityIdAndUnitId(anyInt(), anyLong())).thenReturn(dbFuture);
 
 
         Operation operation = OPEN_API.getOperationById("entityIdUnitIdActionsGET");
         Operation operation = OPEN_API.getOperationById("entityIdUnitIdActionsGET");
+        VISITED_TEST_NODES.add(operation.getOperationId());
+
         RequestOptions reqOpt = new RequestOptions()
         RequestOptions reqOpt = new RequestOptions()
                 .setMethod(HttpMethod.GET).setPort(PORT).setHost(HOST).setURI("/entities/1/units/1000/actions")
                 .setMethod(HttpMethod.GET).setPort(PORT).setHost(HOST).setURI("/entities/1/units/1000/actions")
                 .setHeaders(MultiMap.caseInsensitiveMultiMap()
                 .setHeaders(MultiMap.caseInsensitiveMultiMap()
@@ -1752,6 +1806,8 @@ class OpenAPIHandlerTest {
         Mockito.when(repo.findActionsByEntityId(anyInt(), any(), any())).thenReturn(dbFuture);
         Mockito.when(repo.findActionsByEntityId(anyInt(), any(), any())).thenReturn(dbFuture);
 
 
         Operation operation = OPEN_API.getOperationById("entityIdActionsGET");
         Operation operation = OPEN_API.getOperationById("entityIdActionsGET");
+        VISITED_TEST_NODES.add(operation.getOperationId());
+
         RequestOptions reqOpt = new RequestOptions()
         RequestOptions reqOpt = new RequestOptions()
                 .setMethod(HttpMethod.GET).setPort(PORT).setHost(HOST).setURI("/entities/1/actions")
                 .setMethod(HttpMethod.GET).setPort(PORT).setHost(HOST).setURI("/entities/1/actions")
                 .setHeaders(MultiMap.caseInsensitiveMultiMap()
                 .setHeaders(MultiMap.caseInsensitiveMultiMap()
@@ -1826,6 +1882,8 @@ class OpenAPIHandlerTest {
         Mockito.when(repo.findActionByIdAndEntityId(anyInt(), anyInt())).thenReturn(dbFuture);
         Mockito.when(repo.findActionByIdAndEntityId(anyInt(), anyInt())).thenReturn(dbFuture);
 
 
         Operation operation = OPEN_API.getOperationById("entityIdActionIdGET");
         Operation operation = OPEN_API.getOperationById("entityIdActionIdGET");
+        VISITED_TEST_NODES.add(operation.getOperationId());
+
         RequestOptions reqOpt = new RequestOptions()
         RequestOptions reqOpt = new RequestOptions()
                 .setMethod(HttpMethod.GET).setPort(PORT).setHost(HOST).setURI("/entities/1/actions/1")
                 .setMethod(HttpMethod.GET).setPort(PORT).setHost(HOST).setURI("/entities/1/actions/1")
                 .setHeaders(MultiMap.caseInsensitiveMultiMap()
                 .setHeaders(MultiMap.caseInsensitiveMultiMap()
@@ -1899,6 +1957,8 @@ class OpenAPIHandlerTest {
         Mockito.when(repo.findUnitsByEntityIdAndActionId(anyInt(), anyInt())).thenReturn(dbFuture);
         Mockito.when(repo.findUnitsByEntityIdAndActionId(anyInt(), anyInt())).thenReturn(dbFuture);
 
 
         Operation operation = OPEN_API.getOperationById("entityIdActionIdUnitsGET");
         Operation operation = OPEN_API.getOperationById("entityIdActionIdUnitsGET");
+        VISITED_TEST_NODES.add(operation.getOperationId());
+
         RequestOptions reqOpt = new RequestOptions()
         RequestOptions reqOpt = new RequestOptions()
                 .setMethod(HttpMethod.GET).setPort(PORT).setHost(HOST).setURI("/entities/1/actions/1/units")
                 .setMethod(HttpMethod.GET).setPort(PORT).setHost(HOST).setURI("/entities/1/actions/1/units")
                 .setHeaders(MultiMap.caseInsensitiveMultiMap()
                 .setHeaders(MultiMap.caseInsensitiveMultiMap()
@@ -1965,6 +2025,8 @@ class OpenAPIHandlerTest {
         Mockito.when(repo.findUnitByIdAndEntityIdAndActionId(anyLong(), anyInt(), anyInt())).thenReturn(dbFuture);
         Mockito.when(repo.findUnitByIdAndEntityIdAndActionId(anyLong(), anyInt(), anyInt())).thenReturn(dbFuture);
 
 
         Operation operation = OPEN_API.getOperationById("entityIdActionIdUnitIdGET");
         Operation operation = OPEN_API.getOperationById("entityIdActionIdUnitIdGET");
+        VISITED_TEST_NODES.add(operation.getOperationId());
+
         RequestOptions reqOpt = new RequestOptions()
         RequestOptions reqOpt = new RequestOptions()
                 .setMethod(HttpMethod.GET).setPort(PORT).setHost(HOST).setURI("/entities/1/actions/1/units/1000")
                 .setMethod(HttpMethod.GET).setPort(PORT).setHost(HOST).setURI("/entities/1/actions/1/units/1000")
                 .setHeaders(MultiMap.caseInsensitiveMultiMap()
                 .setHeaders(MultiMap.caseInsensitiveMultiMap()
@@ -2032,6 +2094,8 @@ class OpenAPIHandlerTest {
         Mockito.when(repo.findActionByIdAndEntityIdAndUnitId(anyInt(), anyInt(), anyLong())).thenReturn(dbFuture);
         Mockito.when(repo.findActionByIdAndEntityIdAndUnitId(anyInt(), anyInt(), anyLong())).thenReturn(dbFuture);
 
 
         Operation operation = OPEN_API.getOperationById("entityIdUnitIdActionIdGET");
         Operation operation = OPEN_API.getOperationById("entityIdUnitIdActionIdGET");
+        VISITED_TEST_NODES.add(operation.getOperationId());
+
         RequestOptions reqOpt = new RequestOptions()
         RequestOptions reqOpt = new RequestOptions()
                 .setMethod(HttpMethod.GET).setPort(PORT).setHost(HOST).setURI("/entities/1/units/1000/actions/1")
                 .setMethod(HttpMethod.GET).setPort(PORT).setHost(HOST).setURI("/entities/1/units/1000/actions/1")
                 .setHeaders(MultiMap.caseInsensitiveMultiMap()
                 .setHeaders(MultiMap.caseInsensitiveMultiMap()
@@ -2108,6 +2172,8 @@ class OpenAPIHandlerTest {
         Mockito.when(repo.findEventsByEntityIdAndUnitIdAndActionId(anyInt(), anyLong(), anyInt())).thenReturn(dbFuture);
         Mockito.when(repo.findEventsByEntityIdAndUnitIdAndActionId(anyInt(), anyLong(), anyInt())).thenReturn(dbFuture);
 
 
         Operation operation = OPEN_API.getOperationById("entityIdUnitIdActionIdEventsGET");
         Operation operation = OPEN_API.getOperationById("entityIdUnitIdActionIdEventsGET");
+        VISITED_TEST_NODES.add(operation.getOperationId());
+
         RequestOptions reqOpt = new RequestOptions()
         RequestOptions reqOpt = new RequestOptions()
                 .setMethod(HttpMethod.GET).setPort(PORT).setHost(HOST).setURI("/entities/1/units/1000/actions/1/events")
                 .setMethod(HttpMethod.GET).setPort(PORT).setHost(HOST).setURI("/entities/1/units/1000/actions/1/events")
                 .setHeaders(MultiMap.caseInsensitiveMultiMap()
                 .setHeaders(MultiMap.caseInsensitiveMultiMap()
@@ -2199,6 +2265,8 @@ class OpenAPIHandlerTest {
         Mockito.when(repo.findEventById(anyLong())).thenReturn(dbFuture);
         Mockito.when(repo.findEventById(anyLong())).thenReturn(dbFuture);
 
 
         Operation operation = OPEN_API.getOperationById("eventIdGET");
         Operation operation = OPEN_API.getOperationById("eventIdGET");
+        VISITED_TEST_NODES.add(operation.getOperationId());
+
         RequestOptions reqOpt = new RequestOptions()
         RequestOptions reqOpt = new RequestOptions()
                 .setMethod(HttpMethod.GET).setPort(PORT).setHost(HOST).setURI("/events/1")
                 .setMethod(HttpMethod.GET).setPort(PORT).setHost(HOST).setURI("/events/1")
                 .setHeaders(MultiMap.caseInsensitiveMultiMap()
                 .setHeaders(MultiMap.caseInsensitiveMultiMap()
@@ -2269,4 +2337,537 @@ class OpenAPIHandlerTest {
                     testContext.completeNow();
                     testContext.completeNow();
                 })));
                 })));
     }
     }
+
+    private static Stream<Arguments> campaignIdUnitsAlertsGET_dataSource() {
+        OffsetDateTime baseTimestamp = OffsetDateTime.ofInstant(BASE_INSTANT_TIMESTAMP, ZoneOffset.UTC);
+        return Stream.of(
+                Arguments.of(
+                        Future.succeededFuture(List.of(
+                                CampaignUnitAlert.of(1, 1, 10, "mock(message)", baseTimestamp, AlertStatus.CREATED),
+                                CampaignUnitAlert.of(1, 1, 10, "mock(message)", baseTimestamp.plusHours(1), AlertStatus.CREATED)
+                        )),
+                        SUCCESS, JSON
+                ),
+                Arguments.of(
+                        Future.succeededFuture(emptyList()), SUCCESS, JSON
+                ),
+                Arguments.of(
+                        Future.failedFuture("Not found"), ERROR, JSON
+                )
+        );
+    }
+
+    @ParameterizedTest
+    @MethodSource("campaignIdUnitsAlertsGET_dataSource")
+    @DisplayName("API Spec Test: campaignIdUnitsAlertsGET")
+    void campaignIdUnitsAlertsGET_bySpec(Future<List<CampaignUnitAlert>> dbFuture, OpenAPIResponseTyp responseTyp, ContentType contentType, Vertx vertx, VertxTestContext testContext) {
+
+        Mockito.when(repo.findAlertsByCampaignId(anyLong(), any(), any(), any(), any())).thenReturn(dbFuture);
+
+        Operation operation = OPEN_API.getOperationById("campaignIdUnitsAlertsGET");
+        VISITED_TEST_NODES.add(operation.getOperationId());
+
+        RequestOptions reqOpt = new RequestOptions()
+                .setMethod(HttpMethod.GET).setPort(PORT).setHost(HOST).setURI("/campaigns/1/units/alerts")
+                .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> alertIdGET_dataSource() {
+        OffsetDateTime baseTimestamp = OffsetDateTime.ofInstant(BASE_INSTANT_TIMESTAMP, ZoneOffset.UTC);
+        return Stream.of(
+                Arguments.of(
+                        Future.succeededFuture(
+                                CampaignUnitAlert.of(1, 1, 10, "mock(message)", baseTimestamp, AlertStatus.CREATED)
+                        ), SUCCESS, JSON
+                ),
+                Arguments.of(
+                        Future.failedFuture("Not found"), ERROR, JSON
+                )
+        );
+    }
+
+    @ParameterizedTest
+    @MethodSource("alertIdGET_dataSource")
+    @DisplayName("API Spec Test: alertIdGET")
+    void alertIdGET_bySpec(Future<CampaignUnitAlert> dbFuture, OpenAPIResponseTyp responseTyp, ContentType contentType, Vertx vertx, VertxTestContext testContext) {
+
+        Mockito.when(repo.findAlertById(anyLong())).thenReturn(dbFuture);
+
+        Operation operation = OPEN_API.getOperationById("alertIdGET");
+        VISITED_TEST_NODES.add(operation.getOperationId());
+
+        RequestOptions reqOpt = new RequestOptions()
+                .setMethod(HttpMethod.GET).setPort(PORT).setHost(HOST).setURI("/alerts/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> campaignIdUnitIdObservationsGET_dataSource() {
+        OffsetDateTime baseTimestamp = OffsetDateTime.ofInstant(BASE_INSTANT_TIMESTAMP, ZoneOffset.UTC);
+        Location location = Location.of(10.5f, 10.f, 450f, 1);
+        return Stream.of(
+                Arguments.of(
+                        Future.succeededFuture(new PagingRetrieve<>(false, 2, List.of(
+                                UnitTelemetry.of(1, 10, baseTimestamp, location, 0, JsonObject.of()),
+                                UnitTelemetry.of(1, 10, baseTimestamp.plusMinutes(1), location, 0, JsonObject.of())
+                        ))), SUCCESS, JSON
+                ),
+                Arguments.of(
+                        Future.succeededFuture(new PagingRetrieve<>(false, 0,emptyList())), SUCCESS, JSON
+                ),
+                Arguments.of(
+                        Future.failedFuture("Not found"), ERROR, JSON
+                )
+        );
+    }
+
+    @ParameterizedTest
+    @MethodSource("campaignIdUnitIdObservationsGET_dataSource")
+    @DisplayName("API Spec Test: campaignIdUnitIdObservationsGET")
+    void campaignIdUnitIdObservationsGET_bySpec(Future<PagingRetrieve<List<UnitTelemetry>>> dbFuture, OpenAPIResponseTyp responseTyp, ContentType contentType, Vertx vertx, VertxTestContext testContext) {
+
+        Mockito.when(repo.findObservationsByCampaignIdAndUnitIdWithPaging(anyLong(), anyLong(), any(), any(), anyInt(), anyInt(), any())).thenReturn(dbFuture);
+
+        Operation operation = OPEN_API.getOperationById("campaignIdUnitIdObservationsGET");
+        VISITED_TEST_NODES.add(operation.getOperationId());
+
+        RequestOptions reqOpt = new RequestOptions()
+                .setMethod(HttpMethod.GET).setPort(PORT).setHost(HOST).setURI("/campaigns/1/units/10/observations")
+                .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> campaignIdUnitIdSensorIdObservationsGET_dataSource() {
+        OffsetDateTime baseTimestamp = OffsetDateTime.ofInstant(BASE_INSTANT_TIMESTAMP, ZoneOffset.UTC);
+        Location location = Location.of(10.5f, 10.f, 450f, 1);
+        return Stream.of(
+                Arguments.of(
+                        Future.succeededFuture(new PagingRetrieve<>(false, 2, List.of(
+                                SensorTelemetry.of(1, 10.0, baseTimestamp, location, 0),
+                                SensorTelemetry.of(1, 10.0, baseTimestamp.plusMinutes(1), location, 0)
+                        ))), SUCCESS, JSON
+                ),
+                Arguments.of(
+                        Future.succeededFuture(new PagingRetrieve<>(false, 0,emptyList())), SUCCESS, JSON
+                ),
+                Arguments.of(
+                        Future.failedFuture("Not found"), ERROR, JSON
+                )
+        );
+    }
+
+    @ParameterizedTest
+    @MethodSource("campaignIdUnitIdSensorIdObservationsGET_dataSource")
+    @DisplayName("API Spec Test: campaignIdUnitIdSensorIdObservationsGET")
+    void campaignIdUnitIdSensorIdObservationsGET_bySpec(Future<PagingRetrieve<List<SensorTelemetry>>> dbFuture, OpenAPIResponseTyp responseTyp, ContentType contentType, Vertx vertx, VertxTestContext testContext) {
+
+        Mockito.when(repo.findObservationsByCampaignIdAndUnitIdAndSensorIdWithPaging(anyLong(), anyLong(), anyLong(), any(), any(), anyInt(), anyInt(), any())).thenReturn(dbFuture);
+
+        Operation operation = OPEN_API.getOperationById("campaignIdUnitIdSensorIdObservationsGET");
+        VISITED_TEST_NODES.add(operation.getOperationId());
+
+        RequestOptions reqOpt = new RequestOptions()
+                .setMethod(HttpMethod.GET).setPort(PORT).setHost(HOST).setURI("/campaigns/1/units/10/sensors/100/observations")
+                .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> campaignIdUnitsObservationsLocationsGET_dataSource() {
+        OffsetDateTime baseTimestamp = OffsetDateTime.ofInstant(BASE_INSTANT_TIMESTAMP, ZoneOffset.UTC);
+        Location location = Location.of(10.5f, 10.f, 450f, 1);
+        return Stream.of(
+                Arguments.of(
+                        Future.succeededFuture(List.of(
+                                UnitLocation.of(1, baseTimestamp, location),
+                                UnitLocation.of(1, baseTimestamp.plusHours(1), location)
+                        )), SUCCESS, JSON
+                ),
+                Arguments.of(
+                        Future.succeededFuture(emptyList()), SUCCESS, JSON
+                ),
+                Arguments.of(
+                        Future.failedFuture("Not found"), ERROR, JSON
+                )
+        );
+    }
+
+    @ParameterizedTest
+    @MethodSource("campaignIdUnitsObservationsLocationsGET_dataSource")
+    @DisplayName("API Spec Test: campaignIdUnitsObservationsLocationsGET")
+    void campaignIdUnitsObservationsLocationsGET_bySpec(Future<List<UnitLocation>> dbFuture, OpenAPIResponseTyp responseTyp, ContentType contentType, Vertx vertx, VertxTestContext testContext) {
+
+        Mockito.when(repo.findUnitsLocationsByCampaignId(anyLong(), anyInt(), any(), any(), any(), any())).thenReturn(dbFuture);
+
+        Operation operation = OPEN_API.getOperationById("campaignIdUnitsObservationsLocationsGET");
+        VISITED_TEST_NODES.add(operation.getOperationId());
+
+        RequestOptions reqOpt = new RequestOptions()
+                .setMethod(HttpMethod.GET).setPort(PORT).setHost(HOST).setURI("/campaigns/1/units/observations/locations?limitPerUnit=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> eventIdObservationsGET_dataSource() {
+        OffsetDateTime baseTimestamp = OffsetDateTime.ofInstant(BASE_INSTANT_TIMESTAMP, ZoneOffset.UTC);
+        Location location = Location.of(10.5f, 10.f, 450f, 1);
+        return Stream.of(
+                Arguments.of(
+                        Future.succeededFuture(new PagingRetrieve<>(false, 2, List.of(
+                                UnitTelemetry.of(1, 10, baseTimestamp, location, 0, JsonObject.of()),
+                                UnitTelemetry.of(1, 10, baseTimestamp.plusHours(1), location, 0, JsonObject.of())
+                        ))), SUCCESS, JSON
+                ),
+                Arguments.of(
+                        Future.succeededFuture(new PagingRetrieve<>(false, 0, emptyList())), SUCCESS, JSON
+                ),
+                Arguments.of(
+                        Future.failedFuture("Not found"), ERROR, JSON
+                )
+        );
+    }
+
+    @ParameterizedTest
+    @MethodSource("eventIdObservationsGET_dataSource")
+    @DisplayName("API Spec Test: eventIdObservationsGET")
+    void eventIdObservationsGET_bySpec(Future<PagingRetrieve<List<UnitTelemetry>>> dbFuture, OpenAPIResponseTyp responseTyp, ContentType contentType, Vertx vertx, VertxTestContext testContext) {
+
+        Mockito.when(repo.findObservationsByEventIdWithPaging(anyLong(), any(), any(), anyInt(), anyInt(), any())).thenReturn(dbFuture);
+
+        Operation operation = OPEN_API.getOperationById("eventIdObservationsGET");
+        VISITED_TEST_NODES.add(operation.getOperationId());
+
+        RequestOptions reqOpt = new RequestOptions()
+                .setMethod(HttpMethod.GET).setPort(PORT).setHost(HOST).setURI("/events/1/observations")
+                .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> campaignIdUnitsObservationsGET_dataSource() {
+        OffsetDateTime baseTimestamp = OffsetDateTime.ofInstant(BASE_INSTANT_TIMESTAMP, ZoneOffset.UTC);
+        Location location = Location.of(10.5f, 10.f, 450f, 1);
+        return Stream.of(
+                Arguments.of(
+                        Future.succeededFuture(new PagingRetrieve<>(false, 2, List.of(
+                                UnitTelemetry.of(1, 10, baseTimestamp, location, 0, JsonObject.of()),
+                                UnitTelemetry.of(1, 10, baseTimestamp.plusHours(1), location, 0, JsonObject.of())
+                        ))), SUCCESS, JSON
+                ),
+                Arguments.of(
+                        Future.succeededFuture(new PagingRetrieve<>(false, 0, emptyList())), SUCCESS, JSON
+                ),
+                Arguments.of(
+                        Future.failedFuture("Not found"), ERROR, JSON
+                )
+        );
+    }
+
+    @ParameterizedTest
+    @MethodSource("campaignIdUnitsObservationsGET_dataSource")
+    @DisplayName("API Spec Test: campaignIdUnitsObservationsGET")
+    void campaignIdUnitsObservationsGET_bySpec(Future<PagingRetrieve<List<UnitTelemetry>>> dbFuture, OpenAPIResponseTyp responseTyp, ContentType contentType, Vertx vertx, VertxTestContext testContext) {
+
+        Mockito.when(repo.findObservationsByCampaignIdWithPaging(anyLong(), any(), any(), anyInt(), anyInt(), any())).thenReturn(dbFuture);
+
+        Operation operation = OPEN_API.getOperationById("campaignIdUnitsObservationsGET");
+        VISITED_TEST_NODES.add(operation.getOperationId());
+
+        RequestOptions reqOpt = new RequestOptions()
+                .setMethod(HttpMethod.GET).setPort(PORT).setHost(HOST).setURI("/campaigns/1/units/observations")
+                .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> eventIdAlertsGET_dataSource() {
+        OffsetDateTime baseTimestamp = OffsetDateTime.ofInstant(BASE_INSTANT_TIMESTAMP, ZoneOffset.UTC);
+        return Stream.of(
+                Arguments.of(
+                        Future.succeededFuture(List.of(
+                                Alert.of(1, "mock(message)", baseTimestamp, AlertStatus.CREATED),
+                                Alert.of(2, "mock(message)", baseTimestamp.plusHours(1), AlertStatus.CREATED)
+                        )), SUCCESS, JSON
+                ),
+                Arguments.of(
+                        Future.succeededFuture(emptyList()), SUCCESS, JSON
+                ),
+                Arguments.of(
+                        Future.failedFuture("Not found"), ERROR, JSON
+                )
+        );
+    }
+
+    @ParameterizedTest
+    @MethodSource("eventIdAlertsGET_dataSource")
+    @DisplayName("API Spec Test: eventIdAlertsGET")
+    void eventIdAlertsGET_bySpec(Future<List<Alert>> dbFuture, OpenAPIResponseTyp responseTyp, ContentType contentType, Vertx vertx, VertxTestContext testContext) {
+
+        Mockito.when(repo.findAlertsByEventId(anyLong(), any(), any())).thenReturn(dbFuture);
+
+        Operation operation = OPEN_API.getOperationById("eventIdAlertsGET");
+        VISITED_TEST_NODES.add(operation.getOperationId());
+
+        RequestOptions reqOpt = new RequestOptions()
+                .setMethod(HttpMethod.GET).setPort(PORT).setHost(HOST).setURI("/events/1/alerts")
+                .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> eventIdLocationsGET_dataSource() {
+        OffsetDateTime baseTimestamp = OffsetDateTime.ofInstant(BASE_INSTANT_TIMESTAMP, ZoneOffset.UTC);
+        Location location = Location.of(10.5f, 10.f, 450f, 1);
+        return Stream.of(
+                Arguments.of(
+                        Future.succeededFuture(new PagingRetrieve<>(false, 2, List.of(
+                                UnitLocation.of(10, baseTimestamp, location),
+                                UnitLocation.of(10, baseTimestamp.plusHours(1), location)
+                        ))), SUCCESS, JSON
+                ),
+                Arguments.of(
+                        Future.succeededFuture(new PagingRetrieve<>(false, 0, emptyList())), SUCCESS, JSON
+                ),
+                Arguments.of(
+                        Future.failedFuture("Not found"), ERROR, JSON
+                )
+        );
+    }
+
+    @ParameterizedTest
+    @MethodSource("eventIdLocationsGET_dataSource")
+    @DisplayName("API Spec Test: eventIdLocationsGET")
+    void eventIdLocationsGET_bySpec(Future<PagingRetrieve<List<UnitLocation>>> dbFuture, OpenAPIResponseTyp responseTyp, ContentType contentType, Vertx vertx, VertxTestContext testContext) {
+
+        Mockito.when(repo.findLocationsByEventIdWithPaging(anyLong(), any(), any(), anyInt(), anyInt(), any())).thenReturn(dbFuture);
+
+        Operation operation = OPEN_API.getOperationById("eventIdLocationsGET");
+        VISITED_TEST_NODES.add(operation.getOperationId());
+
+        RequestOptions reqOpt = new RequestOptions()
+                .setMethod(HttpMethod.GET).setPort(PORT).setHost(HOST).setURI("/events/1/observations/locations")
+                .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> entityIdActionIdUnitIdAlertIdGET_dataSource() {
+        OffsetDateTime baseTimestamp = OffsetDateTime.ofInstant(BASE_INSTANT_TIMESTAMP, ZoneOffset.UTC);
+        return Stream.of(
+                Arguments.of(
+                        Future.succeededFuture(
+                                EventAlert.of(1, 1, 10, "mock(message)", baseTimestamp, AlertStatus.CREATED)
+                        ), SUCCESS, JSON
+                ),
+                Arguments.of(
+                        Future.failedFuture("Not found"), ERROR, JSON
+                )
+        );
+    }
+
+    @ParameterizedTest
+    @MethodSource("entityIdActionIdUnitIdAlertIdGET_dataSource")
+    @DisplayName("API Spec Test: entityIdActionIdUnitIdAlertIdGET")
+    void entityIdActionIdUnitIdAlertIdGET_bySpec(Future<EventAlert> dbFuture, OpenAPIResponseTyp responseTyp, ContentType contentType, Vertx vertx, VertxTestContext testContext) {
+
+        Mockito.when(repo.findAlertByIdAndEventId(anyLong(), anyLong())).thenReturn(dbFuture);
+
+        Operation operation = OPEN_API.getOperationById("entityIdActionIdUnitIdAlertIdGET");
+        VISITED_TEST_NODES.add(operation.getOperationId());
+
+        RequestOptions reqOpt = new RequestOptions()
+                .setMethod(HttpMethod.GET).setPort(PORT).setHost(HOST).setURI("/events/1/alerts/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> campaignIdUnitIdAlertsGET_dataSource() {
+        OffsetDateTime baseTimestamp = OffsetDateTime.ofInstant(BASE_INSTANT_TIMESTAMP, ZoneOffset.UTC);
+        return Stream.of(
+                Arguments.of(
+                        Future.succeededFuture(List.of(
+                                UnitAlert.of(1, 10, "mock(message)", baseTimestamp, AlertStatus.CREATED),
+                                UnitAlert.of(2, 10, "mock(message)", baseTimestamp.plusHours(1), AlertStatus.CREATED)
+                        )), SUCCESS, JSON
+                ),
+                Arguments.of(
+                        Future.succeededFuture(emptyList()), SUCCESS, JSON
+                ),
+                Arguments.of(
+                        Future.failedFuture("Not found"), ERROR, JSON
+                )
+        );
+    }
+
+    @ParameterizedTest
+    @MethodSource("campaignIdUnitIdAlertsGET_dataSource")
+    @DisplayName("API Spec Test: campaignIdUnitIdAlertsGET")
+    void campaignIdUnitIdAlertsGET_bySpec(Future<List<UnitAlert>> dbFuture, OpenAPIResponseTyp responseTyp, ContentType contentType, Vertx vertx, VertxTestContext testContext) {
+
+        Mockito.when(repo.findAlertsByCampaignIdAndUnitId(anyLong(), anyLong(), any(), any(), any(), any())).thenReturn(dbFuture);
+
+        Operation operation = OPEN_API.getOperationById("campaignIdUnitIdAlertsGET");
+        VISITED_TEST_NODES.add(operation.getOperationId());
+
+        RequestOptions reqOpt = new RequestOptions()
+                .setMethod(HttpMethod.GET).setPort(PORT).setHost(HOST).setURI("/campaigns/1/units/10/alerts")
+                .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> entityIdActionIdUnitIdAlertsGET_dataSource() {
+        OffsetDateTime baseTimestamp = OffsetDateTime.ofInstant(BASE_INSTANT_TIMESTAMP, ZoneOffset.UTC);
+        return Stream.of(
+                Arguments.of(
+                        Future.succeededFuture(List.of(
+                                Alert.of(1, "mock(message)", baseTimestamp, AlertStatus.CREATED),
+                                Alert.of(2, "mock(message)", baseTimestamp.plusHours(1), AlertStatus.CREATED)
+                        )), SUCCESS, JSON
+                ),
+                Arguments.of(
+                        Future.succeededFuture(emptyList()), SUCCESS, JSON
+                ),
+                Arguments.of(
+                        Future.failedFuture("Not found"), ERROR, JSON
+                )
+        );
+    }
+
+    @ParameterizedTest
+    @MethodSource("entityIdActionIdUnitIdAlertsGET_dataSource")
+    @DisplayName("API Spec Test: entityIdActionIdUnitIdAlertsGET")
+    void entityIdActionIdUnitIdAlertsGET_bySpec(Future<List<Alert>> dbFuture, OpenAPIResponseTyp responseTyp, ContentType contentType, Vertx vertx, VertxTestContext testContext) {
+
+        Mockito.when(repo.findAlertsByEntityIdAndActionIdAndUnitId(anyLong(), anyLong(), anyLong(), any(), any())).thenReturn(dbFuture);
+
+        Operation operation = OPEN_API.getOperationById("entityIdActionIdUnitIdAlertsGET");
+        VISITED_TEST_NODES.add(operation.getOperationId());
+
+        RequestOptions reqOpt = new RequestOptions()
+                .setMethod(HttpMethod.GET).setPort(PORT).setHost(HOST).setURI("/entities/1/actions/1/units/10/alerts")
+                .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> campaignIdUnitIdLocationsGET_dataSource() {
+        OffsetDateTime baseTimestamp = OffsetDateTime.ofInstant(BASE_INSTANT_TIMESTAMP, ZoneOffset.UTC);
+        Location location = Location.of(10.5f, 10.f, 450f, 1);
+        return Stream.of(
+                Arguments.of(
+                        Future.succeededFuture(new PagingRetrieve<>(false, 2, List.of(
+                                UnitLocation.of(10, baseTimestamp, location),
+                                UnitLocation.of(10, baseTimestamp.plusMinutes(1), location)
+                        ))), SUCCESS, JSON
+                ),
+                Arguments.of(
+                        Future.succeededFuture(new PagingRetrieve<>(false, 0, emptyList())), SUCCESS, JSON
+                ),
+                Arguments.of(
+                        Future.failedFuture("Not found"), ERROR, JSON
+                )
+        );
+    }
+
+    @ParameterizedTest
+    @MethodSource("campaignIdUnitIdLocationsGET_dataSource")
+    @DisplayName("API Spec Test: campaignIdUnitIdLocationsGET")
+    void campaignIdUnitIdLocationsGET_bySpec(Future<PagingRetrieve<List<UnitLocation>>> dbFuture, OpenAPIResponseTyp responseTyp, ContentType contentType, Vertx vertx, VertxTestContext testContext) {
+
+        Mockito.when(repo.findLocationsByCampaignIdAndUnitIdWithPaging(anyLong(), anyLong(), any(), any(), anyInt(), anyInt(), any())).thenReturn(dbFuture);
+
+        Operation operation = OPEN_API.getOperationById("campaignIdUnitIdLocationsGET");
+        VISITED_TEST_NODES.add(operation.getOperationId());
+
+        RequestOptions reqOpt = new RequestOptions()
+                .setMethod(HttpMethod.GET).setPort(PORT).setHost(HOST).setURI("/campaigns/1/units/10/observations/locations")
+                .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();
+                })));
+    }
 }
 }