Added new integration test
This commit is contained in:
parent
d5f78e6db0
commit
40276c6c15
@ -15,6 +15,7 @@
|
||||
*/
|
||||
package org.thingsboard.server.cf;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import org.junit.Test;
|
||||
@ -29,22 +30,33 @@ import org.thingsboard.server.common.data.cf.CalculatedField;
|
||||
import org.thingsboard.server.common.data.cf.CalculatedFieldType;
|
||||
import org.thingsboard.server.common.data.cf.configuration.Argument;
|
||||
import org.thingsboard.server.common.data.cf.configuration.ArgumentType;
|
||||
import org.thingsboard.server.common.data.cf.configuration.CFArgumentDynamicSourceType;
|
||||
import org.thingsboard.server.common.data.cf.configuration.GeofencingCalculatedFieldConfiguration;
|
||||
import org.thingsboard.server.common.data.cf.configuration.GeofencingEvent;
|
||||
import org.thingsboard.server.common.data.cf.configuration.GeofencingZoneGroupConfiguration;
|
||||
import org.thingsboard.server.common.data.cf.configuration.Output;
|
||||
import org.thingsboard.server.common.data.cf.configuration.OutputType;
|
||||
import org.thingsboard.server.common.data.cf.configuration.ReferencedEntityKey;
|
||||
import org.thingsboard.server.common.data.cf.configuration.RelationQueryDynamicSourceConfiguration;
|
||||
import org.thingsboard.server.common.data.cf.configuration.ScriptCalculatedFieldConfiguration;
|
||||
import org.thingsboard.server.common.data.cf.configuration.SimpleCalculatedFieldConfiguration;
|
||||
import org.thingsboard.server.common.data.debug.DebugSettings;
|
||||
import org.thingsboard.server.common.data.id.AssetProfileId;
|
||||
import org.thingsboard.server.common.data.id.EntityId;
|
||||
import org.thingsboard.server.common.data.relation.EntityRelation;
|
||||
import org.thingsboard.server.common.data.relation.EntitySearchDirection;
|
||||
import org.thingsboard.server.controller.CalculatedFieldControllerTest;
|
||||
import org.thingsboard.server.dao.service.DaoSqlTest;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.awaitility.Awaitility.await;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
@DaoSqlTest
|
||||
public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTest {
|
||||
@ -606,6 +618,139 @@ public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTes
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGeofencingCalculatedField_SingleZonePerGroup() throws Exception {
|
||||
// --- Arrange entities ---
|
||||
Device device = createDevice("GF Device", "sn-geo-1");
|
||||
|
||||
// Allowed zone polygon (square)
|
||||
String allowedPolygon = """
|
||||
{"type":"POLYGON","polygonsDefinition":"[[50.472000, 30.504000], [50.472000, 30.506000], [50.474000, 30.506000], [50.474000, 30.504000]]"}
|
||||
""";
|
||||
// Restricted zone polygon (square)
|
||||
String restrictedPolygon = """
|
||||
{"type":"POLYGON","polygonsDefinition":"[[50.475000, 30.510000], [50.475000, 30.512000], [50.477000, 30.512000], [50.477000, 30.510000]]"}
|
||||
""";
|
||||
|
||||
Asset allowedZoneAsset = createAsset("Allowed Zone", null);
|
||||
doPost("/api/plugins/telemetry/ASSET/" + allowedZoneAsset.getUuidId() + "/attributes/" + DataConstants.SERVER_SCOPE,
|
||||
JacksonUtil.toJsonNode("{\"zone\":" + allowedPolygon + "}")).andExpect(status().isOk());;
|
||||
|
||||
Asset restrictedZoneAsset = createAsset("Restricted Zone", null);
|
||||
doPost("/api/plugins/telemetry/ASSET/" + restrictedZoneAsset.getUuidId() + "/attributes/" + DataConstants.SERVER_SCOPE,
|
||||
JacksonUtil.toJsonNode("{\"zone\":" + restrictedPolygon + "}")).andExpect(status().isOk());;
|
||||
|
||||
// Relations from device to zones
|
||||
EntityRelation deviceToAllowedZoneRelation = new EntityRelation();
|
||||
deviceToAllowedZoneRelation.setFrom(device.getId());
|
||||
deviceToAllowedZoneRelation.setTo(allowedZoneAsset.getId());
|
||||
deviceToAllowedZoneRelation.setType("AllowedZone");
|
||||
|
||||
EntityRelation deviceToRestrictedZoneRelation = new EntityRelation();
|
||||
deviceToRestrictedZoneRelation.setFrom(device.getId());
|
||||
deviceToRestrictedZoneRelation.setTo(restrictedZoneAsset.getId());
|
||||
deviceToRestrictedZoneRelation.setType("RestrictedZone");
|
||||
|
||||
doPost("/api/relation", deviceToAllowedZoneRelation).andExpect(status().isOk());
|
||||
doPost("/api/relation", deviceToRestrictedZoneRelation).andExpect(status().isOk());
|
||||
|
||||
// Initial device coordinates (inside Allowed, outside Restricted)
|
||||
doPost("/api/plugins/telemetry/DEVICE/" + device.getUuidId() + "/timeseries/unusedScope",
|
||||
JacksonUtil.toJsonNode("{\"latitude\":50.4730,\"longitude\":30.5050}"));
|
||||
|
||||
// --- Build CF: GEOFENCING ---
|
||||
CalculatedField cf = new CalculatedField();
|
||||
cf.setEntityId(device.getId());
|
||||
cf.setType(CalculatedFieldType.GEOFENCING);
|
||||
cf.setName("Geofencing CF");
|
||||
cf.setDebugSettings(DebugSettings.off());
|
||||
|
||||
GeofencingCalculatedFieldConfiguration cfg = new GeofencingCalculatedFieldConfiguration();
|
||||
|
||||
// Coordinates: TS_LATEST on the device
|
||||
Argument lat = new Argument();
|
||||
lat.setRefEntityKey(new ReferencedEntityKey("latitude", ArgumentType.TS_LATEST, null));
|
||||
Argument lon = new Argument();
|
||||
lon.setRefEntityKey(new ReferencedEntityKey("longitude", ArgumentType.TS_LATEST, null));
|
||||
|
||||
// Zone groups: ATTRIBUTE on specific assets (one zone per group)
|
||||
Argument allowedZones = new Argument();
|
||||
var allowedZonesRefDynamicSourceConfiguration = new RelationQueryDynamicSourceConfiguration();
|
||||
allowedZonesRefDynamicSourceConfiguration.setDirection(EntitySearchDirection.FROM);
|
||||
allowedZonesRefDynamicSourceConfiguration.setRelationType("AllowedZone");
|
||||
allowedZonesRefDynamicSourceConfiguration.setMaxLevel(1);
|
||||
allowedZonesRefDynamicSourceConfiguration.setFetchLastLevelOnly(true);
|
||||
allowedZones.setRefEntityKey(new ReferencedEntityKey("zone", ArgumentType.ATTRIBUTE, AttributeScope.SERVER_SCOPE));
|
||||
allowedZones.setRefDynamicSource(CFArgumentDynamicSourceType.RELATION_QUERY);
|
||||
allowedZones.setRefDynamicSourceConfiguration(allowedZonesRefDynamicSourceConfiguration);
|
||||
|
||||
Argument restrictedZones = new Argument();
|
||||
var restrictedZonesRefDynamicSourceConfiguration = new RelationQueryDynamicSourceConfiguration();
|
||||
restrictedZonesRefDynamicSourceConfiguration.setDirection(EntitySearchDirection.FROM);
|
||||
restrictedZonesRefDynamicSourceConfiguration.setRelationType("RestrictedZone");
|
||||
restrictedZonesRefDynamicSourceConfiguration.setMaxLevel(1);
|
||||
restrictedZonesRefDynamicSourceConfiguration.setFetchLastLevelOnly(true);
|
||||
restrictedZones.setRefEntityKey(new ReferencedEntityKey("zone", ArgumentType.ATTRIBUTE, AttributeScope.SERVER_SCOPE));
|
||||
restrictedZones.setRefDynamicSource(CFArgumentDynamicSourceType.RELATION_QUERY);
|
||||
restrictedZones.setRefDynamicSourceConfiguration(restrictedZonesRefDynamicSourceConfiguration);
|
||||
|
||||
cfg.setArguments(Map.of(
|
||||
GeofencingCalculatedFieldConfiguration.ENTITY_ID_LATITUDE_ARGUMENT_KEY, lat,
|
||||
GeofencingCalculatedFieldConfiguration.ENTITY_ID_LONGITUDE_ARGUMENT_KEY, lon,
|
||||
"allowedZones", allowedZones,
|
||||
"restrictedZones", restrictedZones
|
||||
));
|
||||
|
||||
// Zone group reporting config
|
||||
List<GeofencingEvent> reportEvents = Arrays.stream(GeofencingEvent.values()).toList();
|
||||
|
||||
GeofencingZoneGroupConfiguration allowedCfg = new GeofencingZoneGroupConfiguration("allowedZone", reportEvents);
|
||||
GeofencingZoneGroupConfiguration restrictedCfg = new GeofencingZoneGroupConfiguration("restrictedZone", reportEvents);
|
||||
|
||||
cfg.setGeofencingZoneGroupConfigurations(Map.of(
|
||||
"allowedZones", allowedCfg,
|
||||
"restrictedZones", restrictedCfg
|
||||
));
|
||||
|
||||
// Output to server attributes
|
||||
Output out = new Output();
|
||||
out.setType(OutputType.ATTRIBUTES);
|
||||
out.setScope(AttributeScope.SERVER_SCOPE);
|
||||
cfg.setOutput(out);
|
||||
|
||||
cf.setConfiguration(cfg);
|
||||
|
||||
doPost("/api/calculatedField", cf, CalculatedField.class);
|
||||
|
||||
// --- Assert initial evaluation (ENTERED / OUTSIDE) ---
|
||||
await().alias("initial geofencing evaluation")
|
||||
.atMost(TIMEOUT, TimeUnit.SECONDS)
|
||||
.pollInterval(POLL_INTERVAL, TimeUnit.SECONDS)
|
||||
.untilAsserted(() -> {
|
||||
ArrayNode attrs = getServerAttributes(device.getId(), "allowedZoneEvent", "restrictedZoneEvent");
|
||||
assertThat(attrs).isNotNull().isNotEmpty().hasSize(2);
|
||||
Map<String, String> m = kv(attrs);
|
||||
assertThat(m).containsEntry("allowedZoneEvent", "ENTERED")
|
||||
.containsEntry("restrictedZoneEvent", "OUTSIDE");
|
||||
});
|
||||
|
||||
// --- Move device into Restricted zone (and outside Allowed) ---
|
||||
doPost("/api/plugins/telemetry/DEVICE/" + device.getUuidId() + "/timeseries/unusedScope",
|
||||
JacksonUtil.toJsonNode("{\"latitude\":50.4760,\"longitude\":30.5110}"));
|
||||
|
||||
// --- Assert transition (LEFT / ENTERED) ---
|
||||
await().alias("transition evaluation after movement")
|
||||
.atMost(TIMEOUT, TimeUnit.SECONDS)
|
||||
.pollInterval(POLL_INTERVAL, TimeUnit.SECONDS)
|
||||
.untilAsserted(() -> {
|
||||
ArrayNode attrs = getServerAttributes(device.getId(), "allowedZoneEvent", "restrictedZoneEvent");
|
||||
assertThat(attrs).isNotNull().isNotEmpty().hasSize(2);
|
||||
Map<String, String> m = kv(attrs);
|
||||
assertThat(m).containsEntry("allowedZoneEvent", "LEFT")
|
||||
.containsEntry("restrictedZoneEvent", "ENTERED");
|
||||
});
|
||||
}
|
||||
|
||||
private ObjectNode getLatestTelemetry(EntityId entityId, String... keys) throws Exception {
|
||||
return doGetAsync("/api/plugins/telemetry/" + entityId.getEntityType() + "/" + entityId.getId() + "/values/timeseries?keys=" + String.join(",", keys), ObjectNode.class);
|
||||
}
|
||||
@ -621,4 +766,12 @@ public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTes
|
||||
return doPost("/api/asset", asset, Asset.class);
|
||||
}
|
||||
|
||||
private static Map<String, String> kv(ArrayNode attrs) {
|
||||
Map<String, String> m = new HashMap<>();
|
||||
for (JsonNode n : attrs) {
|
||||
m.put(n.get("key").asText(), n.get("value").asText());
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user