Add vehicle-route assignment endpoint and split distance config handling

This commit is contained in:
sladro 2026-03-25 13:26:36 +08:00
parent 9342f8a161
commit 20af9b8288
5 changed files with 553 additions and 11 deletions

View File

@ -3,6 +3,8 @@ package com.qaup.collision.controller;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.qaup.collision.pathconflict.model.entity.ObjectRouteAssignment;
import com.qaup.collision.pathconflict.model.entity.TransportRoute;
import com.qaup.collision.service.PlatformRuntimeStateService;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
@ -11,8 +13,10 @@ import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.locationtech.jts.geom.LineString;
import java.util.ArrayList;
import java.util.Locale;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@ -85,8 +89,84 @@ public class PlatformIntegrationController {
@PostMapping(path = "/config/collision/diverging_release_distance", consumes = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Map<String, Object>> updateCollisionDivergingReleaseDistance(@RequestBody String requestBody) {
return updateNumericConfig(requestBody, "collision", "collision.diverging_release_distance",
platformRuntimeStateService::updateCollisionDivergingReleaseDistance);
return updateCollisionDistanceConfig(requestBody);
}
@PostMapping(path = "/api/vehicle-route/assignment", consumes = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Map<String, Object>> submitVehicleRoute(@RequestBody String requestBody) {
try {
JsonNode root = objectMapper.readTree(requestBody);
if (!root.isObject()) {
return invalidFieldType("Request body must be a JSON object");
}
String objectId = getTextValue(root, "vehicleID");
if (objectId == null) {
objectId = getTextValue(root, "objectName");
}
if (objectId == null || objectId.isBlank()) {
return error(HttpStatus.BAD_REQUEST, "Missing field: vehicleID or objectName");
}
String objectTypeValue = getTextValue(root, "vehicleType");
if (objectTypeValue == null) {
objectTypeValue = getTextValue(root, "objectType");
}
ObjectRouteAssignment.ObjectType objectType = parseObjectType(objectTypeValue);
if (objectType == null) {
return error(HttpStatus.BAD_REQUEST, "Invalid or missing field: vehicleType/objectType");
}
JsonNode routeNode = root.get("route");
List<double[]> points = parseRoutePoints(routeNode);
if (points.isEmpty()) {
points = parsePointArray(root.get("points"));
}
if (points.isEmpty()) {
return error(HttpStatus.BAD_REQUEST, "Missing field: route.points (at least 2 points)");
}
LineString routeGeometry = platformRuntimeStateService.buildLineStringFromCoordinates(points);
if (routeGeometry == null) {
return error(HttpStatus.BAD_REQUEST, "Invalid route points");
}
Double maxSpeedKph = parseOptionalDouble(root.get("maxSpeedKph"), "maxSpeedKph");
Double typicalSpeedKph = parseOptionalDouble(root.get("typicalSpeedKph"), "typicalSpeedKph");
Boolean isBidirectional = parseOptionalBoolean(root.get("isBidirectional"), "isBidirectional");
TransportRoute.RouteStatus status = parseRouteStatus(root.get("status"));
String routeName = getTextValue(root, "routeName");
String description = getTextValue(root, "description");
var result = platformRuntimeStateService.submitVehicleRoute(
objectId,
objectType,
routeGeometry,
routeName,
maxSpeedKph,
typicalSpeedKph,
isBidirectional,
status,
description
);
Map<String, Object> payload = new LinkedHashMap<>();
payload.put("status", "success");
payload.put("objectId", objectId);
payload.put("objectType", objectType.name());
payload.put("routeId", result.routeId());
payload.put("routeName", result.routeName());
payload.put("routeType", result.routeType());
payload.put("assignmentId", result.assignmentId());
payload.put("pointsCount", points.size());
return ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON).body(payload);
} catch (JsonProcessingException e) {
return invalidJson(e);
} catch (IllegalArgumentException e) {
return error(HttpStatus.BAD_REQUEST, e.getMessage());
} catch (Exception e) {
return internalError(e);
}
}
private void validateVehicleRegistryItem(
@ -179,6 +259,189 @@ public class PlatformIntegrationController {
}
}
private ResponseEntity<Map<String, Object>> updateCollisionDistanceConfig(String requestBody) {
try {
JsonNode root = objectMapper.readTree(requestBody);
if (!root.isObject()) {
return invalidFieldType("Request body must be a JSON object");
}
JsonNode valueNode = root.get("value");
JsonNode vehicleNode = root.get("vehicleDistance");
JsonNode aircraftNode = root.get("aircraftDistance");
if ((vehicleNode == null || vehicleNode.isNull())
&& (aircraftNode == null || aircraftNode.isNull())
&& (valueNode == null || valueNode.isNull())) {
return error(HttpStatus.BAD_REQUEST, "Missing field: value");
}
if (vehicleNode != null && !vehicleNode.isNull() && !vehicleNode.isNumber()) {
return invalidFieldType("Field 'vehicleDistance' must be a number");
}
if (aircraftNode != null && !aircraftNode.isNull() && !aircraftNode.isNumber()) {
return invalidFieldType("Field 'aircraftDistance' must be a number");
}
if (valueNode != null && !valueNode.isNull() && !valueNode.isNumber()) {
return invalidFieldType("Field 'value' must be a number");
}
Double newVehicleDistance = null;
Double newAircraftDistance = null;
if (vehicleNode != null && !vehicleNode.isNull()) {
if (!vehicleNode.isNumber()) {
return invalidFieldType("Field 'vehicleDistance' must be a number");
}
newVehicleDistance = vehicleNode.asDouble();
if (!Double.isFinite(newVehicleDistance) || newVehicleDistance <= 0) {
return error(HttpStatus.BAD_REQUEST, "Invalid value: must be a finite number greater than 0");
}
}
if (aircraftNode != null && !aircraftNode.isNull()) {
if (!aircraftNode.isNumber()) {
return invalidFieldType("Field 'aircraftDistance' must be a number");
}
newAircraftDistance = aircraftNode.asDouble();
if (!Double.isFinite(newAircraftDistance) || newAircraftDistance <= 0) {
return error(HttpStatus.BAD_REQUEST, "Invalid value: must be a finite number greater than 0");
}
}
if (valueNode != null && !valueNode.isNull() && vehicleNode == null && aircraftNode == null) {
double newValue = valueNode.asDouble();
if (!Double.isFinite(newValue) || newValue <= 0) {
return error(HttpStatus.BAD_REQUEST, "Invalid value: must be a finite number greater than 0");
}
double oldValue = platformRuntimeStateService.updateCollisionDivergingReleaseDistance(newValue);
Map<String, Object> payload = new LinkedHashMap<>();
payload.put("status", "success");
payload.put("area", "collision");
payload.put("field", "collision.diverging_release_distance");
payload.put("old", oldValue);
payload.put("new", newValue);
return ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON).body(payload);
}
double oldVehicleDistance = platformRuntimeStateService.getCollisionDivergingReleaseDistanceForVehicle();
double oldAircraftDistance = platformRuntimeStateService.getCollisionDivergingReleaseDistanceForAircraft();
if (newVehicleDistance != null) {
oldVehicleDistance = platformRuntimeStateService.updateCollisionDivergingReleaseDistanceForVehicle(newVehicleDistance);
}
if (newAircraftDistance != null) {
oldAircraftDistance = platformRuntimeStateService.updateCollisionDivergingReleaseDistanceForAircraft(newAircraftDistance);
}
Map<String, Object> payload = new LinkedHashMap<>();
payload.put("status", "success");
payload.put("area", "collision");
payload.put("field", "collision.diverging_release_distance");
payload.put("oldVehicleDistance", oldVehicleDistance);
payload.put("newVehicleDistance", newVehicleDistance != null ? newVehicleDistance : platformRuntimeStateService.getCollisionDivergingReleaseDistanceForVehicle());
payload.put("oldAircraftDistance", oldAircraftDistance);
payload.put("newAircraftDistance", newAircraftDistance != null ? newAircraftDistance : platformRuntimeStateService.getCollisionDivergingReleaseDistanceForAircraft());
payload.put("updatedVehicle", newVehicleDistance != null);
payload.put("updatedAircraft", newAircraftDistance != null);
return ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON).body(payload);
} catch (JsonProcessingException e) {
return invalidJson(e);
} catch (Exception e) {
return internalError(e);
}
}
private static ObjectRouteAssignment.ObjectType parseObjectType(String objectTypeValue) {
if (objectTypeValue == null || objectTypeValue.isBlank()) {
return null;
}
String normalized = objectTypeValue.trim().toUpperCase(Locale.ROOT);
return switch (normalized) {
case "UNMANNED_VEHICLE", "UNMANNED", "UV", "WUREN" -> ObjectRouteAssignment.ObjectType.UNMANNED_VEHICLE;
case "SPECIAL_VEHICLE", "SPECIAL", "TEQIN", "SPECIALVEHICLE" -> ObjectRouteAssignment.ObjectType.SPECIAL_VEHICLE;
default -> null;
};
}
private List<double[]> parseRoutePoints(JsonNode routeNode) {
if (routeNode == null || !routeNode.isObject()) {
return List.of();
}
JsonNode pointsNode = routeNode.get("points");
return parsePointArray(pointsNode);
}
private List<double[]> parsePointArray(JsonNode pointsNode) {
if (pointsNode == null || !pointsNode.isArray() || pointsNode.size() < 2) {
return List.of();
}
List<double[]> points = new ArrayList<>();
for (JsonNode pointNode : pointsNode) {
if (!pointNode.isObject()) {
return List.of();
}
Double lon = parseOptionalDouble(pointNode.get("lon"), "lon");
if (lon == null) {
lon = parseOptionalDouble(pointNode.get("longitude"), "longitude");
}
Double lat = parseOptionalDouble(pointNode.get("lat"), "lat");
if (lat == null) {
lat = parseOptionalDouble(pointNode.get("latitude"), "latitude");
}
if (lon == null || lat == null) {
return List.of();
}
points.add(new double[]{lon, lat});
}
return points;
}
private static TransportRoute.RouteStatus parseRouteStatus(JsonNode statusNode) {
if (statusNode == null || statusNode.isNull() || !statusNode.isTextual()) {
return null;
}
try {
return TransportRoute.RouteStatus.valueOf(statusNode.asText().trim().toUpperCase(Locale.ROOT));
} catch (IllegalArgumentException ignored) {
throw new IllegalArgumentException("Field 'status' must be ACTIVE, INACTIVE or MAINTENANCE");
}
}
private static Double parseOptionalDouble(JsonNode node, String fieldName) {
if (node == null || node.isNull()) {
return null;
}
if (!node.isNumber()) {
throw new IllegalArgumentException("Field '" + fieldName + "' must be a number");
}
double value = node.asDouble();
if (!Double.isFinite(value)) {
throw new IllegalArgumentException("Field '" + fieldName + "' must be finite");
}
return value;
}
private static Boolean parseOptionalBoolean(JsonNode node, String fieldName) {
if (node == null || node.isNull()) {
return null;
}
if (!node.isBoolean()) {
throw new IllegalArgumentException("Field '" + fieldName + "' must be boolean");
}
return node.asBoolean();
}
private static String getTextValue(JsonNode root, String fieldName) {
JsonNode node = root.get(fieldName);
if (node == null || node.isNull() || !node.isTextual()) {
return null;
}
String value = node.asText();
return value == null ? null : value.trim();
}
private ResponseEntity<Map<String, Object>> invalidJson(JsonProcessingException e) {
Map<String, Object> payload = new LinkedHashMap<>();
payload.put("status", "error");

View File

@ -52,6 +52,9 @@ import org.locationtech.jts.geom.PrecisionModel;
@Service
public class DataCollectorService {
private static final boolean VEHICLE_MANAGER_ROUTE_INGEST_ENABLED = false;
private static final java.util.concurrent.atomic.AtomicBoolean ROUTE_INGEST_DISABLED_LOGGED = new java.util.concurrent.atomic.AtomicBoolean(false);
// 机场数据源相关配置
@Value("${data.collector.airport-api.endpoints.vehicle}")
private String airportVehicleEndpoint;
@ -337,7 +340,13 @@ public class DataCollectorService {
case "VehicleSuspendReport" -> handleVehicleSuspendReport(dataNode);
case "VehicleTailerNum" -> handleVehicleTailerNum(dataNode);
case "VehiclePositionInfo" -> handleVehiclePositionInfo(dataNode);
case "NaviShortPathReport" -> handleVehiclePathInfo(dataNode);
case "NaviShortPathReport" -> {
if (VEHICLE_MANAGER_ROUTE_INGEST_ENABLED) {
handleVehiclePathInfo(dataNode);
} else {
logWarnRouteIngestionDisabled("NaviShortPathReport");
}
}
case "GetFmsMessage" -> handleVehicleFmsMessage(dataNode);
default -> log.debug("未识别的车辆管理消息类型: {}", messageName);
}
@ -738,8 +747,9 @@ public class DataCollectorService {
.currentHeading(null) // 不在采集阶段计算方向
.altitude(0.0); // 默认高度
// 提取任务上下文信息
if (statusData.getMissionContext() != null) {
// Task context is disabled for route ingestion.
// Keep position and other realtime data updates, skip route/waypoint injection.
if (VEHICLE_MANAGER_ROUTE_INGEST_ENABLED && statusData.getMissionContext() != null) {
MissionContextDTO missionContext = statusData.getMissionContext();
if (missionContext.getCurrentMission() != null) {
@ -818,6 +828,10 @@ public class DataCollectorService {
// 缓存通用状态数据供后续DataProcessingService推送 vehicle_status_update如前端需要
cacheUniversalVehicleStatus(vehicleId, statusData);
if (!VEHICLE_MANAGER_ROUTE_INGEST_ENABLED && statusData != null && statusData.getMissionContext() != null
&& statusData.getMissionContext().getWaypoints() != null) {
logWarnRouteIngestionDisabled("vehicle-manager:http:missionContext");
}
successCount++;
log.debug("处理无人车完整状态数据并更新缓存: (车辆ID: {}, 位置: {}, {}, 任务ID: {}, 里程: {}米, 电量: {}%)",
@ -939,19 +953,27 @@ public class DataCollectorService {
getUniversalStatusCache().put(cacheKey, cacheEntry);
// 如果存在missionContext数据更新到activeMovingObjectsCache中的无人车对象
if (statusData.getMissionContext() != null) {
if (VEHICLE_MANAGER_ROUTE_INGEST_ENABLED && statusData.getMissionContext() != null) {
log.info("========== 开始更新任务上下文 ==========");
log.info("vehicleId: {}", vehicleId);
log.info("missionContext: {}", statusData.getMissionContext());
log.info("waypoints: {}", statusData.getMissionContext().getWaypoints());
updateUnmannedVehicleMissionContext(vehicleId, statusData.getMissionContext());
log.info("========== 任务上下文更新完成 ==========");
} else if (statusData.getMissionContext() != null && statusData.getMissionContext().getWaypoints() != null) {
logWarnRouteIngestionDisabled("vehicle-manager:universal_status");
}
log.debug("缓存通用车辆状态数据: vehicleId={}, cacheKey={}, 包含任务上下文: {}",
vehicleId, cacheKey, statusData.getMissionContext() != null);
}
private void logWarnRouteIngestionDisabled(String source) {
if (ROUTE_INGEST_DISABLED_LOGGED.compareAndSet(false, true)) {
log.warn("已硬编码关闭车辆管理来源路线/任务上下文入库:{}。前端手工提交路线将保留其来源优先但车辆管理下行NaviShortPathReport 与 missionContext路径点暂不注入。", source);
}
}
/**
* 更新无人车的任务上下文信息

View File

@ -1,8 +1,18 @@
package com.qaup.collision.service;
import com.qaup.collision.pathconflict.model.entity.ObjectRouteAssignment;
import com.qaup.collision.pathconflict.model.entity.TransportRoute;
import com.qaup.collision.pathconflict.repository.ObjectRouteAssignmentRepository;
import com.qaup.collision.pathconflict.repository.TransportRouteRepository;
import com.qaup.collision.common.model.MovingObject;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.LineString;
import org.locationtech.jts.geom.PrecisionModel;
import org.locationtech.jts.geom.GeometryFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.Collections;
@ -22,7 +32,17 @@ public class PlatformRuntimeStateService {
private volatile double runwayWarningZoneRadiusAircraft;
private volatile double runwayAlertZoneRadiusAircraft;
private volatile double collisionDivergingReleaseDistance;
private volatile double collisionDivergingReleaseDistanceForVehicle;
private volatile double collisionDivergingReleaseDistanceForAircraft;
private final GeometryFactory geometryFactory = new GeometryFactory(new PrecisionModel(), 4326);
private static final String DEFAULT_ROUTE_SOURCE = "VehicleRouteIngestion";
private static final String DEFAULT_ROUTE_DESCRIPTION = "Manual route uploaded via platform integration API";
@Autowired
private TransportRouteRepository transportRouteRepository;
@Autowired
private ObjectRouteAssignmentRepository objectRouteAssignmentRepository;
public PlatformRuntimeStateService(
@Value("${qaup.runtime-config.runway.warning-zone-radius.aircraft:200.0}") double runwayWarningZoneRadiusAircraft,
@ -31,7 +51,8 @@ public class PlatformRuntimeStateService {
this.runwayWarningZoneRadiusAircraft = runwayWarningZoneRadiusAircraft;
this.runwayAlertZoneRadiusAircraft = runwayAlertZoneRadiusAircraft;
this.collisionDivergingReleaseDistance = collisionDivergingReleaseDistance;
this.collisionDivergingReleaseDistanceForVehicle = collisionDivergingReleaseDistance;
this.collisionDivergingReleaseDistanceForAircraft = collisionDivergingReleaseDistance;
}
public VehicleRegistryUpdateResult updateVehicleRegistry(List<VehicleRegistryEntry> entries) {
@ -130,15 +151,140 @@ public class PlatformRuntimeStateService {
}
public double getCollisionDivergingReleaseDistance() {
return collisionDivergingReleaseDistance;
return collisionDivergingReleaseDistanceForVehicle;
}
public double updateCollisionDivergingReleaseDistance(double newValue) {
double oldValue = collisionDivergingReleaseDistance;
collisionDivergingReleaseDistance = newValue;
double oldValue = collisionDivergingReleaseDistanceForVehicle;
collisionDivergingReleaseDistanceForVehicle = newValue;
collisionDivergingReleaseDistanceForAircraft = newValue;
return oldValue;
}
public double getCollisionDivergingReleaseDistanceForVehicle() {
return collisionDivergingReleaseDistanceForVehicle;
}
public double getCollisionDivergingReleaseDistanceForAircraft() {
return collisionDivergingReleaseDistanceForAircraft;
}
public double updateCollisionDivergingReleaseDistanceForVehicle(double newValue) {
double oldValue = collisionDivergingReleaseDistanceForVehicle;
collisionDivergingReleaseDistanceForVehicle = newValue;
return oldValue;
}
public double updateCollisionDivergingReleaseDistanceForAircraft(double newValue) {
double oldValue = collisionDivergingReleaseDistanceForAircraft;
collisionDivergingReleaseDistanceForAircraft = newValue;
return oldValue;
}
@Transactional
public VehicleRouteSubmitResult submitVehicleRoute(
String objectId,
ObjectRouteAssignment.ObjectType objectType,
LineString routeGeometry,
String routeName,
Double maxSpeedKph,
Double typicalSpeedKph,
Boolean isBidirectional,
TransportRoute.RouteStatus status,
String description) {
if (objectType == null) {
throw new IllegalArgumentException("objectType is required");
}
if (routeGeometry == null) {
throw new IllegalArgumentException("routeGeometry is required");
}
TransportRoute.RouteType routeType = mapToRouteType(objectType);
if (routeType == null) {
throw new IllegalArgumentException("Unsupported objectType: " + objectType);
}
String finalRouteName = normalizeRouteName(objectId, routeType, routeName);
TransportRoute route = transportRouteRepository.findByRouteNameAndRouteType(finalRouteName, routeType)
.map(existing -> {
existing.setRouteGeometry(routeGeometry);
existing.setDescription(description != null && !description.isBlank() ? description : DEFAULT_ROUTE_DESCRIPTION);
if (maxSpeedKph != null) {
existing.setMaxSpeedKph(maxSpeedKph);
}
if (typicalSpeedKph != null) {
existing.setTypicalSpeedKph(typicalSpeedKph);
}
existing.setUpdatedBy(DEFAULT_ROUTE_SOURCE);
existing.setStatus(status != null ? status : TransportRoute.RouteStatus.ACTIVE);
existing.setIsBidirectional(isBidirectional != null ? isBidirectional : Boolean.FALSE);
existing.setUpdatedAt(java.time.LocalDateTime.now());
return existing;
})
.orElseGet(() -> TransportRoute.builder()
.routeName(finalRouteName)
.routeType(routeType)
.description(description != null && !description.isBlank() ? description : DEFAULT_ROUTE_DESCRIPTION)
.routeGeometry(routeGeometry)
.maxSpeedKph(maxSpeedKph)
.typicalSpeedKph(typicalSpeedKph)
.status(status != null ? status : TransportRoute.RouteStatus.ACTIVE)
.isBidirectional(isBidirectional != null ? isBidirectional : Boolean.FALSE)
.createdBy(DEFAULT_ROUTE_SOURCE)
.updatedBy(DEFAULT_ROUTE_SOURCE)
.createdAt(java.time.LocalDateTime.now())
.updatedAt(java.time.LocalDateTime.now())
.build());
TransportRoute savedRoute = transportRouteRepository.save(route);
ObjectRouteAssignment assignment = ObjectRouteAssignment.builder()
.objectType(objectType)
.objectName(objectId)
.assignedRouteId(savedRoute.getId())
.assignedAt(java.time.LocalDateTime.now())
.build();
ObjectRouteAssignment savedAssignment = objectRouteAssignmentRepository.save(assignment);
return new VehicleRouteSubmitResult(savedRoute.getId(), savedRoute.getRouteName(), savedAssignment.getId(), objectId, objectType.name(), routeType.name());
}
public LineString buildLineStringFromCoordinates(List<double[]> coordinates) {
if (coordinates == null || coordinates.size() < 2) {
return null;
}
List<Coordinate> coordinateList = new ArrayList<>(coordinates.size());
for (double[] point : coordinates) {
if (point == null || point.length < 2) {
return null;
}
if (Double.isNaN(point[0]) || Double.isNaN(point[1]) || Double.isInfinite(point[0]) || Double.isInfinite(point[1])) {
return null;
}
coordinateList.add(new Coordinate(point[0], point[1]));
}
return geometryFactory.createLineString(coordinateList.toArray(Coordinate[]::new));
}
private static String normalizeRouteName(String objectId, TransportRoute.RouteType routeType, String routeName) {
if (routeName != null && !routeName.isBlank()) {
return routeName;
}
String safeObjectId = objectId == null || objectId.isBlank() ? "UNKNOWN" : objectId;
return "MANUAL_" + routeType.name() + "_" + safeObjectId;
}
private static TransportRoute.RouteType mapToRouteType(ObjectRouteAssignment.ObjectType objectType) {
return switch (objectType) {
case UNMANNED_VEHICLE -> TransportRoute.RouteType.UNMANNED_VEHICLE;
case SPECIAL_VEHICLE -> TransportRoute.RouteType.SPECIAL_VEHICLE;
default -> null;
};
}
public record VehicleRegistryEntry(String vehicleID, VehicleRegistryType vehicleType) {
}
@ -150,6 +296,15 @@ public class PlatformRuntimeStateService {
List<String> controllableVehicleIDs) {
}
public record VehicleRouteSubmitResult(
Long routeId,
String routeName,
Long assignmentId,
String objectId,
String objectType,
String routeType) {
}
public enum VehicleRegistryType {
WUREN,
TEQIN,

View File

@ -107,6 +107,34 @@ class PlatformIntegrationControllerTest {
.andExpect(jsonPath("$.message").value("Invalid field type"));
}
@Test
void shouldRejectVehicleDistanceUpdateWhenAircraftDistanceInvalidAndPreservePreviousValue() throws Exception {
mockMvc.perform(post("/config/collision/diverging_release_distance")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"vehicleDistance\":500,\"aircraftDistance\":\"oops\"}"))
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.message").value("Invalid field type"));
mockMvc.perform(post("/config/collision/diverging_release_distance")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"vehicleDistance\":500}"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.oldVehicleDistance").value(40.0))
.andExpect(jsonPath("$.newVehicleDistance").value(500.0));
}
@Test
void shouldUpdateBothCollisionDistancesAtomically() throws Exception {
mockMvc.perform(post("/config/collision/diverging_release_distance")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"vehicleDistance\":500,\"aircraftDistance\":600}"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.newVehicleDistance").value(500.0))
.andExpect(jsonPath("$.newAircraftDistance").value(600.0))
.andExpect(jsonPath("$.updatedVehicle").value(true))
.andExpect(jsonPath("$.updatedAircraft").value(true));
}
@Test
void shouldRejectInvalidJson() throws Exception {
mockMvc.perform(post("/config/runway/alert_zone_radius/aircraft")

View File

@ -0,0 +1,74 @@
package com.qaup.collision.datacollector.service;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.qaup.collision.datacollector.dao.DataCollectorDao;
import com.qaup.collision.datacollector.filter.VehicleLocationFilter;
import com.qaup.collision.datacollector.sdk.AdxpFlightServiceHttpClient;
import com.qaup.collision.dataprocessing.service.DataProcessingService;
import com.qaup.collision.common.service.VehicleLocationService;
import com.qaup.collision.datacollector.config.VehicleManagerProperties;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.test.util.ReflectionTestUtils;
import java.lang.reflect.Method;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
@ExtendWith(MockitoExtension.class)
class DataCollectorServiceVehicleManagerRouteIngestionTest {
@Mock
private DataCollectorDao dataCollectorDao;
@Mock
private VehicleLocationService vehicleLocationService;
@Mock
private DataProcessingService dataProcessingService;
@Mock
private VehicleLocationFilter vehicleLocationFilter;
@Mock
private TrafficLightDataCollector trafficLightDataCollector;
@Mock
private AdxpFlightServiceHttpClient adxpFlightServiceClient;
@Mock
private com.qaup.collision.datacollector.websocket.AdxpFlightServiceWebSocketClient adxpFlightServiceWebSocketClient;
@Mock
private com.qaup.collision.datacollector.websocket.VehicleManagerWebSocketClient vehicleManagerWebSocketClient;
@Mock
private VehicleManagerProperties vehicleManagerProperties;
@Mock
private VehicleManagerCacheService vehicleManagerCacheService;
@Mock
private VehicleStatusAggregationService vehicleStatusAggregationService;
@InjectMocks
private DataCollectorService dataCollectorService;
@Test
void shouldNotPersistNaviShortPathReportWhenRouteIngestionDisabled() throws Exception {
ReflectionTestUtils.setField(dataCollectorService, "vehicleManagerCacheService", vehicleManagerCacheService);
ReflectionTestUtils.setField(dataCollectorService, "collectorDisabled", false);
Method handler = DataCollectorService.class.getDeclaredMethod("handleVehicleManagerMessage", JsonNode.class);
handler.setAccessible(true);
JsonNode messageNode = new ObjectMapper().readTree("""
{
"messageName": "NaviShortPathReport",
"data": {
"vehicleId": "V001",
"path": []
}
}
""");
handler.invoke(dataCollectorService, messageNode);
verify(vehicleManagerCacheService, never()).updateVehiclePath(eq("V001"), any());
}
}