diff --git a/ilp-cw-api/[POST] queryAvailableDrones/Complex copy copy.bru b/ilp-cw-api/[POST] queryAvailableDrones/Complex copy copy.bru
new file mode 100644
index 0000000..ee46799
--- /dev/null
+++ b/ilp-cw-api/[POST] queryAvailableDrones/Complex copy copy.bru
@@ -0,0 +1,39 @@
+meta {
+ name: Complex copy copy
+ type: http
+ seq: 5
+}
+
+post {
+ url: {{API_BASE}}/calcDeliveryPath
+ body: json
+ auth: inherit
+}
+
+body:json {
+ [
+ {
+ "id": 123,
+ "date": "2025-12-22",
+ "time": "14:30",
+ "requirements": {
+ "capacity": 0.75,
+ "heating": true,
+ "maxCost": 13.5
+ },
+ "delivery": {
+ "lng": -3.17,
+ "lat": 55.9
+ }
+ }
+ ]
+}
+
+assert {
+ res.status: eq 200
+}
+
+settings {
+ encodeUrl: true
+ timeout: 0
+}
diff --git a/ilp-cw-api/[POST] queryAvailableDrones/Complex copy.bru b/ilp-cw-api/[POST] queryAvailableDrones/Complex copy.bru
new file mode 100644
index 0000000..77eba58
--- /dev/null
+++ b/ilp-cw-api/[POST] queryAvailableDrones/Complex copy.bru
@@ -0,0 +1,39 @@
+meta {
+ name: Complex copy
+ type: http
+ seq: 4
+}
+
+post {
+ url: {{API_BASE}}/calcDeliveryPathAsGeoJson
+ body: json
+ auth: inherit
+}
+
+body:json {
+ [
+ {
+ "id": 123,
+ "date": "2025-12-22",
+ "time": "14:30",
+ "requirements": {
+ "capacity": 0.75,
+ "heating": true,
+ "maxCost": 13.5
+ },
+ "delivery": {
+ "lng": -3.17,
+ "lat": 55.9
+ }
+ }
+ ]
+}
+
+assert {
+ res.status: eq 200
+}
+
+settings {
+ encodeUrl: true
+ timeout: 0
+}
diff --git a/src/main/java/io/github/js0ny/ilp_coursework/controller/ApiController.java b/src/main/java/io/github/js0ny/ilp_coursework/controller/ApiController.java
index 1fba40b..dbd2147 100644
--- a/src/main/java/io/github/js0ny/ilp_coursework/controller/ApiController.java
+++ b/src/main/java/io/github/js0ny/ilp_coursework/controller/ApiController.java
@@ -7,6 +7,7 @@ import io.github.js0ny.ilp_coursework.data.request.DistanceRequest;
import io.github.js0ny.ilp_coursework.data.request.MovementRequest;
import io.github.js0ny.ilp_coursework.data.request.RegionCheckRequest;
import io.github.js0ny.ilp_coursework.service.GpsCalculationService;
+
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
@@ -15,10 +16,10 @@ import org.springframework.web.bind.annotation.RestController;
/**
* Main REST Controller for the ILP Coursework 1 application.
- *
- * This class handles incoming HTTP requests for the API under {@code /api/v1} path (defined in CW1)
- * This is responsible for mapping requests to the appropriate service method and returning the results as responses.
- * The business logic is delegated to {@link GpsCalculationService}
+ *
+ *
This class handles incoming HTTP requests for the API under {@code /api/v1} path (defined in
+ * CW1) This is responsible for mapping requests to the appropriate service method and returning the
+ * results as responses. The business logic is delegated to {@link GpsCalculationService}
*/
@RestController
@RequestMapping("/api/v1")
@@ -27,9 +28,11 @@ public class ApiController {
private final GpsCalculationService gpsService;
/**
- * Constructor of the {@code ApiController} with the business logic dependency {@code GpsCalculationService}
+ * Constructor of the {@code ApiController} with the business logic dependency {@code
+ * GpsCalculationService}
*
- * @param gpsService The service component that contains all business logic, injected by Spring's DI.
+ * @param gpsService The service component that contains all business logic, injected by
+ * Spring's DI.
*/
public ApiController(GpsCalculationService gpsService) {
this.gpsService = gpsService;
@@ -62,7 +65,8 @@ public class ApiController {
* Handles POST requests to check if the two coordinates are close to each other
*
* @param request A {@link DistanceRequest} containing the two coordinates
- * @return {@code true} if the distance is less than the predefined threshold, {@code false} otherwise
+ * @return {@code true} if the distance is less than the predefined threshold, {@code false}
+ * otherwise
*/
@PostMapping("/isCloseTo")
public boolean getIsCloseTo(@RequestBody DistanceRequest request) {
@@ -74,7 +78,8 @@ public class ApiController {
/**
* Handles POST requests to get the next position after an angle of movement
*
- * @param request A {@link MovementRequest} containing the start coordinate and angle of the movement.
+ * @param request A {@link MovementRequest} containing the start coordinate and angle of the
+ * movement.
* @return A {@link LngLat} representing the destination
*/
@PostMapping("/nextPosition")
diff --git a/src/main/java/io/github/js0ny/ilp_coursework/controller/DroneController.java b/src/main/java/io/github/js0ny/ilp_coursework/controller/DroneController.java
index 541a92d..712fa8d 100644
--- a/src/main/java/io/github/js0ny/ilp_coursework/controller/DroneController.java
+++ b/src/main/java/io/github/js0ny/ilp_coursework/controller/DroneController.java
@@ -7,17 +7,17 @@ import io.github.js0ny.ilp_coursework.data.response.DeliveryPathResponse;
import io.github.js0ny.ilp_coursework.service.DroneAttrComparatorService;
import io.github.js0ny.ilp_coursework.service.DroneInfoService;
import io.github.js0ny.ilp_coursework.service.PathFinderService;
-import java.util.List;
+
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
-import org.springframework.web.client.RestTemplate;
+
+import java.util.List;
/**
* Main Rest Controller for the ILP Coursework 2 application.
- *
- * This class handles incoming HTTP requests for the API under {@code /api/v1}
- * path (defined in CW2)
- * The business logic is delegated to {@link DroneInfoService}
+ *
+ *
This class handles incoming HTTP requests for the API under {@code /api/v1} path (defined in
+ * CW2) The business logic is delegated to {@link DroneInfoService}
*/
@RestController
@RequestMapping("/api/v1")
@@ -28,37 +28,33 @@ public class DroneController {
private final PathFinderService pathFinderService;
/**
- * Constructor of the {@code DroneController} with the business logic dependency
- * {@code DroneInfoService}
- *
- * We handle the {@code baseUrl} here. Use a predefined URL if the environment
- * variable {@code ILP_ENDPOINT}
- * is not given.
+ * Constructor of the {@code DroneController} with the business logic dependency {@code
+ * DroneInfoService}
+ *
+ *
We handle the {@code baseUrl} here. Use a predefined URL if the environment variable
+ * {@code ILP_ENDPOINT} is not given.
*
* @param droneService The service component that contains all business logic
*/
public DroneController(
- DroneInfoService droneService,
- DroneAttrComparatorService droneAttrComparatorService,
- PathFinderService pathFinderService
- ) {
+ DroneInfoService droneService,
+ DroneAttrComparatorService droneAttrComparatorService,
+ PathFinderService pathFinderService) {
this.droneInfoService = droneService;
this.droneAttrComparatorService = droneAttrComparatorService;
this.pathFinderService = pathFinderService;
}
/**
- * Handles GET requests to retrieve an array of drones (identified by id) that
- * has the capability of cooling
+ * Handles GET requests to retrieve an array of drones (identified by id) that has the
+ * capability of cooling
*
- * @param state The path variable that indicates the return should have or not
- * have the capability
+ * @param state The path variable that indicates the return should have or not have the
+ * capability
* @return An array of drone id with cooling capability.
*/
@GetMapping("/dronesWithCooling/{state}")
- public List getDronesWithCoolingCapability(
- @PathVariable boolean state
- ) {
+ public List getDronesWithCoolingCapability(@PathVariable boolean state) {
return droneInfoService.dronesWithCooling(state);
}
@@ -66,8 +62,8 @@ public class DroneController {
* Handles GET requests to retrieve the drone detail identified by id
*
* @param id The id of the drone to be queried.
- * @return 200 with {@link Drone}-style json if success, 404 if {@code id}
- * not found, 400 otherwise
+ * @return 200 with {@link Drone}-style json if success, 404 if {@code id} not found, 400
+ * otherwise
*/
@GetMapping("/droneDetails/{id}")
public ResponseEntity getDroneDetail(@PathVariable String id) {
@@ -80,51 +76,36 @@ public class DroneController {
}
/**
- * Handles GET requests to retrieve an array of drone ids that
- * {@code capability.attrName = attrVal}
+ * Handles GET requests to retrieve an array of drone ids that {@code capability.attrName =
+ * attrVal}
*
* @param attrName The name of the attribute to be queried
- * @param attrVal The value of the attribute to be queried
+ * @param attrVal The value of the attribute to be queried
* @return An array of drone id that matches the attribute name and value
*/
@GetMapping("/queryAsPath/{attrName}/{attrVal}")
public List getIdByAttrMap(
- @PathVariable String attrName,
- @PathVariable String attrVal
- ) {
- return droneAttrComparatorService.dronesWithAttribute(
- attrName,
- attrVal
- );
+ @PathVariable String attrName, @PathVariable String attrVal) {
+ return droneAttrComparatorService.dronesWithAttribute(attrName, attrVal);
}
@PostMapping("/query")
- public List getIdByAttrMapPost(
- @RequestBody AttrQueryRequest[] attrComparators
- ) {
- return droneAttrComparatorService.dronesSatisfyingAttributes(
- attrComparators
- );
+ public List getIdByAttrMapPost(@RequestBody AttrQueryRequest[] attrComparators) {
+ return droneAttrComparatorService.dronesSatisfyingAttributes(attrComparators);
}
@PostMapping("/queryAvailableDrones")
- public List queryAvailableDrones(
- @RequestBody MedDispatchRecRequest[] records
- ) {
+ public List queryAvailableDrones(@RequestBody MedDispatchRecRequest[] records) {
return droneInfoService.dronesMatchesRequirements(records);
}
@PostMapping("/calcDeliveryPath")
- public DeliveryPathResponse calculateDeliveryPath(
- @RequestBody MedDispatchRecRequest[] record
- ) {
+ public DeliveryPathResponse calculateDeliveryPath(@RequestBody MedDispatchRecRequest[] record) {
return pathFinderService.calculateDeliveryPath(record);
}
@PostMapping("/calcDeliveryPathAsGeoJson")
- public String calculateDeliveryPathAsGeoJson(
- @RequestBody MedDispatchRecRequest[] record
- ) {
+ public String calculateDeliveryPathAsGeoJson(@RequestBody MedDispatchRecRequest[] record) {
return pathFinderService.calculateDeliveryPathAsGeoJson(record);
}
}
diff --git a/src/main/java/io/github/js0ny/ilp_coursework/controller/GeoJsonDataController.java b/src/main/java/io/github/js0ny/ilp_coursework/controller/GeoJsonDataController.java
index a372ccb..4002fd5 100644
--- a/src/main/java/io/github/js0ny/ilp_coursework/controller/GeoJsonDataController.java
+++ b/src/main/java/io/github/js0ny/ilp_coursework/controller/GeoJsonDataController.java
@@ -1,14 +1,13 @@
package io.github.js0ny.ilp_coursework.controller;
import com.fasterxml.jackson.core.JsonProcessingException;
+
import io.github.js0ny.ilp_coursework.service.DroneInfoService;
-import org.springframework.stereotype.Controller;
+
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
-import java.util.List;
-
@RestController
@RequestMapping("/api/v1")
public class GeoJsonDataController {
diff --git a/src/main/java/io/github/js0ny/ilp_coursework/data/common/AltitudeRange.java b/src/main/java/io/github/js0ny/ilp_coursework/data/common/AltitudeRange.java
index 519a371..b4ca9b2 100644
--- a/src/main/java/io/github/js0ny/ilp_coursework/data/common/AltitudeRange.java
+++ b/src/main/java/io/github/js0ny/ilp_coursework/data/common/AltitudeRange.java
@@ -6,9 +6,7 @@ import io.github.js0ny.ilp_coursework.data.external.RestrictedArea;
* Represents a range of altitude values (that is a fly-zone in {@link RestrictedArea}).
*
* @param lower The lower bound of the altitude range.
- * @param upper The upper bound of the altitude range. If {@code upper = -1}, then the region
- * is not a fly zone.
- *
+ * @param upper The upper bound of the altitude range. If {@code upper = -1}, then the region is not
+ * a fly zone.
*/
-public record AltitudeRange(double lower, double upper) {
-}
+public record AltitudeRange(double lower, double upper) {}
diff --git a/src/main/java/io/github/js0ny/ilp_coursework/data/common/Angle.java b/src/main/java/io/github/js0ny/ilp_coursework/data/common/Angle.java
index bcdd0c2..56c87bd 100644
--- a/src/main/java/io/github/js0ny/ilp_coursework/data/common/Angle.java
+++ b/src/main/java/io/github/js0ny/ilp_coursework/data/common/Angle.java
@@ -11,8 +11,7 @@ public record Angle(double degrees) {
public Angle {
if (degrees < 0 || degrees >= 360) {
- throw new IllegalArgumentException(
- "Angle must be in range [0, 360). Got: " + degrees);
+ throw new IllegalArgumentException("Angle must be in range [0, 360). Got: " + degrees);
}
// Should be a multiple of 22.5 (one of the 16 major directions)
@@ -22,18 +21,16 @@ public record Angle(double degrees) {
// 1.0e-15
// So we need to check if the remainder is small enough, or close enough to STEP
// (handling negative errors)
- if (Math.abs(remainder) > EPSILON &&
- Math.abs(remainder - STEP) > EPSILON) {
+ if (Math.abs(remainder) > EPSILON && Math.abs(remainder - STEP) > EPSILON) {
throw new IllegalArgumentException(
- "Angle must be a multiple of 22.5 (one of the 16 major directions). Got: " +
- degrees);
+ "Angle must be a multiple of 22.5 (one of the 16 major directions). Got: "
+ + degrees);
}
}
public static Angle fromIndex(int index) {
if (index < 0 || index > 15) {
- throw new IllegalArgumentException(
- "Direction index must be between 0 and 15");
+ throw new IllegalArgumentException("Direction index must be between 0 and 15");
}
return new Angle(index * STEP);
}
@@ -64,5 +61,4 @@ public record Angle(double degrees) {
public double toRadians() {
return Math.toRadians(degrees);
}
-
}
diff --git a/src/main/java/io/github/js0ny/ilp_coursework/data/common/DroneAvailability.java b/src/main/java/io/github/js0ny/ilp_coursework/data/common/DroneAvailability.java
index 231fdfd..d4c1c5b 100644
--- a/src/main/java/io/github/js0ny/ilp_coursework/data/common/DroneAvailability.java
+++ b/src/main/java/io/github/js0ny/ilp_coursework/data/common/DroneAvailability.java
@@ -3,9 +3,7 @@ package io.github.js0ny.ilp_coursework.data.common;
import java.time.DayOfWeek;
import java.time.LocalTime;
-public record DroneAvailability(
- String id,
- TimeWindow[] availability) {
+public record DroneAvailability(String id, TimeWindow[] availability) {
public boolean checkAvailability(DayOfWeek day, LocalTime time) {
diff --git a/src/main/java/io/github/js0ny/ilp_coursework/data/common/DroneCapability.java b/src/main/java/io/github/js0ny/ilp_coursework/data/common/DroneCapability.java
index 66689b1..0d9d016 100644
--- a/src/main/java/io/github/js0ny/ilp_coursework/data/common/DroneCapability.java
+++ b/src/main/java/io/github/js0ny/ilp_coursework/data/common/DroneCapability.java
@@ -7,6 +7,4 @@ public record DroneCapability(
int maxMoves,
float costPerMove,
float costInitial,
- float costFinal) {
-
-}
+ float costFinal) {}
diff --git a/src/main/java/io/github/js0ny/ilp_coursework/data/common/LngLat.java b/src/main/java/io/github/js0ny/ilp_coursework/data/common/LngLat.java
index 99ac2ea..9744299 100644
--- a/src/main/java/io/github/js0ny/ilp_coursework/data/common/LngLat.java
+++ b/src/main/java/io/github/js0ny/ilp_coursework/data/common/LngLat.java
@@ -1,8 +1,8 @@
package io.github.js0ny.ilp_coursework.data.common;
/**
- * Represents the data transfer object for a point or coordinate
- * that defines by a longitude and latitude
+ * Represents the data transfer object for a point or coordinate that defines by a longitude and
+ * latitude
*
* @param lng longitude of the coordinate/point
* @param lat latitude of the coordinate/point
@@ -13,14 +13,12 @@ public record LngLat(double lng, double lat) {
public LngLat {
if (lat < -90 || lat > 90) {
throw new IllegalArgumentException(
- "Latitude must be between -90 and +90 degrees. Got: " + lat
- );
+ "Latitude must be between -90 and +90 degrees. Got: " + lat);
}
if (lng < -180 || lng > 180) {
throw new IllegalArgumentException(
- "Longitude must be between -180 and +180 degrees. Got: " + lng
- );
+ "Longitude must be between -180 and +180 degrees. Got: " + lng);
}
}
@@ -32,9 +30,6 @@ public record LngLat(double lng, double lat) {
if (other == null) {
return false;
}
- return (
- Math.abs(lng - other.lng()) < EPSILON &&
- Math.abs(lat - other.lat()) < EPSILON
- );
+ return (Math.abs(lng - other.lng()) < EPSILON && Math.abs(lat - other.lat()) < EPSILON);
}
}
diff --git a/src/main/java/io/github/js0ny/ilp_coursework/data/common/LngLatAlt.java b/src/main/java/io/github/js0ny/ilp_coursework/data/common/LngLatAlt.java
index 6d4544c..a3a06de 100644
--- a/src/main/java/io/github/js0ny/ilp_coursework/data/common/LngLatAlt.java
+++ b/src/main/java/io/github/js0ny/ilp_coursework/data/common/LngLatAlt.java
@@ -1,12 +1,11 @@
package io.github.js0ny.ilp_coursework.data.common;
/**
- * Represents the data transfer object for a point or coordinate
- * that defines by a longitude and latitude
+ * Represents the data transfer object for a point or coordinate that defines by a longitude and
+ * latitude
*
* @param lng longitude of the coordinate/point
* @param lat latitude of the coordinate/point
* @param alt altitude of the coordinate/point
*/
-public record LngLatAlt(double lng, double lat, double alt) {
-}
+public record LngLatAlt(double lng, double lat, double alt) {}
diff --git a/src/main/java/io/github/js0ny/ilp_coursework/data/common/Region.java b/src/main/java/io/github/js0ny/ilp_coursework/data/common/Region.java
index ff002a4..e9f95a2 100644
--- a/src/main/java/io/github/js0ny/ilp_coursework/data/common/Region.java
+++ b/src/main/java/io/github/js0ny/ilp_coursework/data/common/Region.java
@@ -1,47 +1,42 @@
package io.github.js0ny.ilp_coursework.data.common;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
import io.github.js0ny.ilp_coursework.data.request.RegionCheckRequest;
import java.util.List;
-import java.util.Objects;
-
-import com.fasterxml.jackson.databind.ObjectMapper;
-
import java.util.Map;
+import java.util.Objects;
/**
* Represents the data transfer object for a region definition
- *
- * This record encapsulates the data for calculating if a coordinate is inside
- * the region
- *
- * A built-in method {@code isClosedTo} is defined to check this DTO is valid or
- * not in the mean of closing polygon
*
- * @param name The human-readable name for the region
+ *
This record encapsulates the data for calculating if a coordinate is inside the region
+ *
+ *
A built-in method {@code isClosedTo} is defined to check this DTO is valid or not in the mean
+ * of closing polygon
+ *
+ * @param name The human-readable name for the region
* @param vertices list of coordinates that forms a polygon as a region.
- *
- * In order to define a valid region, the last element of the
- * list should be the same as the first, or
- * known as closed
+ *
In order to define a valid region, the last element of the list should be the same as the
+ * first, or known as closed
* @see RegionCheckRequest
- * @see io.github.js0ny.ilp_coursework.service.GpsCalculationService#checkIsInRegion(LngLat,
- * Region)
+ * @see io.github.js0ny.ilp_coursework.service.GpsCalculationService#checkIsInRegion(LngLat, Region)
*/
public record Region(String name, List vertices) {
/**
* Magic number 4: For a polygon, 3 edges is required.
- *
- * In this dto, edges + 1 vertices is required.
+ *
+ *
In this dto, edges + 1 vertices is required.
*/
private static final int MINIMUM_VERTICES = 4;
/**
- * Method to check if the region has a valid polygon by checking if the
- * {@code vertices} forms a closed polygon
+ * Method to check if the region has a valid polygon by checking if the {@code vertices} forms a
+ * closed polygon
*
- * @return {@code true} if the {@code vertices} are able to form a polygon and
- * form a closed polygon
+ * @return {@code true} if the {@code vertices} are able to form a polygon and form a closed
+ * polygon
*/
public boolean isClosed() {
if (vertices == null || vertices.size() < MINIMUM_VERTICES) {
@@ -56,13 +51,10 @@ public record Region(String name, List vertices) {
try {
ObjectMapper mapper = new ObjectMapper();
- List> ring = vertices.stream()
- .map(v -> List.of(v.lng(), v.lat()))
- .toList();
+ List> ring =
+ vertices.stream().map(v -> List.of(v.lng(), v.lat())).toList();
- return Map.of(
- "type", "Polygon",
- "coordinates", List.of(ring));
+ return Map.of("type", "Polygon", "coordinates", List.of(ring));
} catch (Exception e) {
throw new RuntimeException("Failed to generate GeoJSON", e);
diff --git a/src/main/java/io/github/js0ny/ilp_coursework/data/common/TimeWindow.java b/src/main/java/io/github/js0ny/ilp_coursework/data/common/TimeWindow.java
index 04daeae..25bf152 100644
--- a/src/main/java/io/github/js0ny/ilp_coursework/data/common/TimeWindow.java
+++ b/src/main/java/io/github/js0ny/ilp_coursework/data/common/TimeWindow.java
@@ -3,8 +3,4 @@ package io.github.js0ny.ilp_coursework.data.common;
import java.time.DayOfWeek;
import java.time.LocalTime;
-public record TimeWindow(
- DayOfWeek dayOfWeek,
- LocalTime from,
- LocalTime until) {
-}
+public record TimeWindow(DayOfWeek dayOfWeek, LocalTime from, LocalTime until) {}
diff --git a/src/main/java/io/github/js0ny/ilp_coursework/data/external/Drone.java b/src/main/java/io/github/js0ny/ilp_coursework/data/external/Drone.java
index 29a6c2d..0965663 100644
--- a/src/main/java/io/github/js0ny/ilp_coursework/data/external/Drone.java
+++ b/src/main/java/io/github/js0ny/ilp_coursework/data/external/Drone.java
@@ -2,13 +2,8 @@ package io.github.js0ny.ilp_coursework.data.external;
import io.github.js0ny.ilp_coursework.data.common.DroneCapability;
-/**
- * Represents the data transfer object for a drone, gained from the endpoints
- */
-public record Drone(
- String name,
- String id,
- DroneCapability capability) {
+/** Represents the data transfer object for a drone, gained from the endpoints */
+public record Drone(String name, String id, DroneCapability capability) {
public int parseId() {
try {
diff --git a/src/main/java/io/github/js0ny/ilp_coursework/data/external/RestrictedArea.java b/src/main/java/io/github/js0ny/ilp_coursework/data/external/RestrictedArea.java
index c9e9e10..069c0c8 100644
--- a/src/main/java/io/github/js0ny/ilp_coursework/data/external/RestrictedArea.java
+++ b/src/main/java/io/github/js0ny/ilp_coursework/data/external/RestrictedArea.java
@@ -4,15 +4,11 @@ import io.github.js0ny.ilp_coursework.data.common.AltitudeRange;
import io.github.js0ny.ilp_coursework.data.common.LngLat;
import io.github.js0ny.ilp_coursework.data.common.LngLatAlt;
import io.github.js0ny.ilp_coursework.data.common.Region;
+
import java.util.ArrayList;
import java.util.List;
-public record RestrictedArea(
- String name,
- int id,
- AltitudeRange limits,
- LngLatAlt[] vertices
-) {
+public record RestrictedArea(String name, int id, AltitudeRange limits, LngLatAlt[] vertices) {
public Region toRegion() {
List vertices2D = new ArrayList<>();
for (var vertex : vertices) {
diff --git a/src/main/java/io/github/js0ny/ilp_coursework/data/external/ServicePointDrones.java b/src/main/java/io/github/js0ny/ilp_coursework/data/external/ServicePointDrones.java
index 8f5cbd8..d98c7f5 100644
--- a/src/main/java/io/github/js0ny/ilp_coursework/data/external/ServicePointDrones.java
+++ b/src/main/java/io/github/js0ny/ilp_coursework/data/external/ServicePointDrones.java
@@ -1,11 +1,10 @@
package io.github.js0ny.ilp_coursework.data.external;
import io.github.js0ny.ilp_coursework.data.common.DroneAvailability;
+
import org.springframework.lang.Nullable;
-public record ServicePointDrones(
- int servicePointId,
- DroneAvailability[] drones) {
+public record ServicePointDrones(int servicePointId, DroneAvailability[] drones) {
@Nullable
public DroneAvailability locateDroneById(String droneId) {
diff --git a/src/main/java/io/github/js0ny/ilp_coursework/data/request/AttrQueryRequest.java b/src/main/java/io/github/js0ny/ilp_coursework/data/request/AttrQueryRequest.java
index f87a351..f3420b6 100644
--- a/src/main/java/io/github/js0ny/ilp_coursework/data/request/AttrQueryRequest.java
+++ b/src/main/java/io/github/js0ny/ilp_coursework/data/request/AttrQueryRequest.java
@@ -3,5 +3,4 @@ package io.github.js0ny.ilp_coursework.data.request;
// TODO: Convert operator to Enum
// import io.github.js0ny.ilp_coursework.util.AttrOperator;
-public record AttrQueryRequest(String attribute, String operator, String value) {
-}
+public record AttrQueryRequest(String attribute, String operator, String value) {}
diff --git a/src/main/java/io/github/js0ny/ilp_coursework/data/request/DistanceRequest.java b/src/main/java/io/github/js0ny/ilp_coursework/data/request/DistanceRequest.java
index 34ac900..045ecf3 100644
--- a/src/main/java/io/github/js0ny/ilp_coursework/data/request/DistanceRequest.java
+++ b/src/main/java/io/github/js0ny/ilp_coursework/data/request/DistanceRequest.java
@@ -4,12 +4,11 @@ import io.github.js0ny.ilp_coursework.data.common.LngLat;
/**
* Represents the data transfer object for a distance operation request.
- *
- * This record encapsulates the data for several endpoints that involves two {@code LngLatDto}
+ *
+ *
This record encapsulates the data for several endpoints that involves two {@code LngLatDto}
* and serves as the data contract for those API operation
*
* @param position1 Nested object of {@link LngLat}
* @param position2 Nested object of {@link LngLat}
*/
-public record DistanceRequest(LngLat position1, LngLat position2) {
-}
+public record DistanceRequest(LngLat position1, LngLat position2) {}
diff --git a/src/main/java/io/github/js0ny/ilp_coursework/data/request/MedDispatchRecRequest.java b/src/main/java/io/github/js0ny/ilp_coursework/data/request/MedDispatchRecRequest.java
index 57ee11f..307f213 100644
--- a/src/main/java/io/github/js0ny/ilp_coursework/data/request/MedDispatchRecRequest.java
+++ b/src/main/java/io/github/js0ny/ilp_coursework/data/request/MedDispatchRecRequest.java
@@ -1,6 +1,7 @@
package io.github.js0ny.ilp_coursework.data.request;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+
import io.github.js0ny.ilp_coursework.data.common.LngLat;
import java.time.LocalDate;
@@ -8,17 +9,7 @@ import java.time.LocalTime;
@JsonIgnoreProperties(ignoreUnknown = true)
public record MedDispatchRecRequest(
- int id,
- LocalDate date,
- LocalTime time,
- MedRequirement requirements,
- LngLat delivery) {
+ int id, LocalDate date, LocalTime time, MedRequirement requirements, LngLat delivery) {
@JsonIgnoreProperties(ignoreUnknown = true)
- public record MedRequirement(
- float capacity,
- boolean cooling,
- boolean heating,
- float maxCost
- ) {
- }
+ public record MedRequirement(float capacity, boolean cooling, boolean heating, float maxCost) {}
}
diff --git a/src/main/java/io/github/js0ny/ilp_coursework/data/request/MovementRequest.java b/src/main/java/io/github/js0ny/ilp_coursework/data/request/MovementRequest.java
index 2eedade..fd364ac 100644
--- a/src/main/java/io/github/js0ny/ilp_coursework/data/request/MovementRequest.java
+++ b/src/main/java/io/github/js0ny/ilp_coursework/data/request/MovementRequest.java
@@ -4,14 +4,13 @@ import io.github.js0ny.ilp_coursework.data.common.LngLat;
/**
* Represents the data transfer object for a movement action request.
- *
- * This record encapsulates the data for endpoint /api/v1/nextPosition and serves as the data contract for
- * this API operation
+ *
+ *
This record encapsulates the data for endpoint /api/v1/nextPosition and serves as the data
+ * contract for this API operation
*
* @param start The starting coordinate of the movement
- * @param angle The angle to movement in degree. This corresponds to compass directions.
- * For example: 0 for East, 90 for North
+ * @param angle The angle to movement in degree. This corresponds to compass directions. For
+ * example: 0 for East, 90 for North
* @see io.github.js0ny.ilp_coursework.controller.ApiController#getNextPosition(MovementRequest)
*/
-public record MovementRequest(LngLat start, double angle) {
-}
+public record MovementRequest(LngLat start, double angle) {}
diff --git a/src/main/java/io/github/js0ny/ilp_coursework/data/request/RegionCheckRequest.java b/src/main/java/io/github/js0ny/ilp_coursework/data/request/RegionCheckRequest.java
index 959253c..382edcd 100644
--- a/src/main/java/io/github/js0ny/ilp_coursework/data/request/RegionCheckRequest.java
+++ b/src/main/java/io/github/js0ny/ilp_coursework/data/request/RegionCheckRequest.java
@@ -5,15 +5,14 @@ import io.github.js0ny.ilp_coursework.data.common.Region;
/**
* Represents the data transfer object for a region check request.
- *
- * This record encapsulates the data for endpoint /api/v1/isInRegion and serves as the data contract for
- * this API operation
+ *
+ *
This record encapsulates the data for endpoint /api/v1/isInRegion and serves as the data
+ * contract for this API operation
+ *
*
*
* @param position The coordinate to be checked
- * @param region The region for the check.
- * This is a nested object represented by {@link Region}
+ * @param region The region for the check. This is a nested object represented by {@link Region}
* @see io.github.js0ny.ilp_coursework.controller.ApiController#getIsInRegion(RegionCheckRequest)
*/
-public record RegionCheckRequest(LngLat position, Region region) {
-}
+public record RegionCheckRequest(LngLat position, Region region) {}
diff --git a/src/main/java/io/github/js0ny/ilp_coursework/data/response/DeliveryPathResponse.java b/src/main/java/io/github/js0ny/ilp_coursework/data/response/DeliveryPathResponse.java
index e029594..78d0998 100644
--- a/src/main/java/io/github/js0ny/ilp_coursework/data/response/DeliveryPathResponse.java
+++ b/src/main/java/io/github/js0ny/ilp_coursework/data/response/DeliveryPathResponse.java
@@ -1,13 +1,10 @@
package io.github.js0ny.ilp_coursework.data.response;
import io.github.js0ny.ilp_coursework.data.common.LngLat;
+
import java.util.List;
-public record DeliveryPathResponse(
- float totalCost,
- int totalMoves,
- DronePath[] dronePaths
-) {
+public record DeliveryPathResponse(float totalCost, int totalMoves, DronePath[] dronePaths) {
public record DronePath(int droneId, List deliveries) {
public record Delivery(int deliveryId, List flightPath) {}
}
diff --git a/src/main/java/io/github/js0ny/ilp_coursework/exception/GlobalExceptionHandler.java b/src/main/java/io/github/js0ny/ilp_coursework/exception/GlobalExceptionHandler.java
index 1134f1e..258e64c 100644
--- a/src/main/java/io/github/js0ny/ilp_coursework/exception/GlobalExceptionHandler.java
+++ b/src/main/java/io/github/js0ny/ilp_coursework/exception/GlobalExceptionHandler.java
@@ -1,8 +1,5 @@
package io.github.js0ny.ilp_coursework.exception;
-import java.util.Map;
-import java.util.Optional;
-
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
@@ -11,15 +8,17 @@ import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
-/**
- * Class that handles exception or failed request. Map all error requests to 400.
- */
+import java.util.Map;
+import java.util.Optional;
+
+/** Class that handles exception or failed request. Map all error requests to 400. */
@RestControllerAdvice
public class GlobalExceptionHandler {
/// Use a logger to save logs instead of passing them to user
private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
- private final Map badRequestMap = Map.of("status", "400", "error", "Bad Request");
+ private final Map badRequestMap =
+ Map.of("status", "400", "error", "Bad Request");
@ExceptionHandler(HttpMessageNotReadableException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
@@ -31,8 +30,8 @@ public class GlobalExceptionHandler {
@ExceptionHandler(IllegalArgumentException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public Map handleIllegalArgument(IllegalArgumentException ex) {
- String errorMessage = Optional.ofNullable(ex.getMessage())
- .orElse("Invalid argument provided.");
+ String errorMessage =
+ Optional.ofNullable(ex.getMessage()).orElse("Invalid argument provided.");
log.warn("Illegal argument in request: {}", errorMessage);
return badRequestMap;
}
diff --git a/src/main/java/io/github/js0ny/ilp_coursework/service/DroneAttrComparatorService.java b/src/main/java/io/github/js0ny/ilp_coursework/service/DroneAttrComparatorService.java
index 94f0061..e74c67f 100644
--- a/src/main/java/io/github/js0ny/ilp_coursework/service/DroneAttrComparatorService.java
+++ b/src/main/java/io/github/js0ny/ilp_coursework/service/DroneAttrComparatorService.java
@@ -4,9 +4,14 @@ import static io.github.js0ny.ilp_coursework.util.AttrComparator.isValueMatched;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
+
import io.github.js0ny.ilp_coursework.data.external.Drone;
import io.github.js0ny.ilp_coursework.data.request.AttrQueryRequest;
import io.github.js0ny.ilp_coursework.util.AttrOperator;
+
+import org.springframework.stereotype.Service;
+import org.springframework.web.client.RestTemplate;
+
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
@@ -14,8 +19,6 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
-import org.springframework.stereotype.Service;
-import org.springframework.web.client.RestTemplate;
@Service
public class DroneAttrComparatorService {
@@ -24,14 +27,11 @@ public class DroneAttrComparatorService {
private final String dronesEndpoint = "drones";
private final RestTemplate restTemplate = new RestTemplate();
- /**
- * Constructor, handles the base url here.
- */
+ /** Constructor, handles the base url here. */
public DroneAttrComparatorService() {
String baseUrl = System.getenv("ILP_ENDPOINT");
if (baseUrl == null || baseUrl.isBlank()) {
- this.baseUrl =
- "https://ilp-rest-2025-bvh6e9hschfagrgy.ukwest-01.azurewebsites.net/";
+ this.baseUrl = "https://ilp-rest-2025-bvh6e9hschfagrgy.ukwest-01.azurewebsites.net/";
} else {
// Defensive: Add '/' to the end of the URL
if (!baseUrl.endsWith("/")) {
@@ -43,11 +43,11 @@ public class DroneAttrComparatorService {
/**
* Return an array of ids of drones with a given attribute name and value.
- *
- * Associated service method with {@code /queryAsPath/{attrName}/{attrVal}}
+ *
+ *
Associated service method with {@code /queryAsPath/{attrName}/{attrVal}}
*
* @param attrName the attribute name to filter on
- * @param attrVal the attribute value to filter on
+ * @param attrVal the attribute value to filter on
* @return array of drone ids matching the attribute name and value
* @see #dronesWithAttributeCompared(String, String, AttrOperator)
* @see io.github.js0ny.ilp_coursework.controller.DroneController#getIdByAttrMap
@@ -58,26 +58,19 @@ public class DroneAttrComparatorService {
}
/**
- * Return an array of ids of drones which matches all given complex comparing
- * rules
+ * Return an array of ids of drones which matches all given complex comparing rules
*
* @param attrComparators The filter rule with Name, Value and Operator
* @return array of drone ids that matches all rules
*/
- public List dronesSatisfyingAttributes(
- AttrQueryRequest[] attrComparators
- ) {
+ public List dronesSatisfyingAttributes(AttrQueryRequest[] attrComparators) {
Set matchingDroneIds = null;
for (var comparator : attrComparators) {
String attribute = comparator.attribute();
String operator = comparator.operator();
String value = comparator.value();
AttrOperator op = AttrOperator.fromString(operator);
- List ids = dronesWithAttributeCompared(
- attribute,
- value,
- op
- );
+ List ids = dronesWithAttributeCompared(attribute, value, op);
if (matchingDroneIds == null) {
matchingDroneIds = new HashSet<>(ids);
} else {
@@ -92,25 +85,20 @@ public class DroneAttrComparatorService {
/**
* Helper that wraps the dynamic querying with different comparison operators
- *
- * This method act as a concatenation of
- * {@link io.github.js0ny.ilp_coursework.util.AttrComparator#isValueMatched(JsonNode, String,
+ *
+ *
This method act as a concatenation of {@link
+ * io.github.js0ny.ilp_coursework.util.AttrComparator#isValueMatched(JsonNode, String,
* AttrOperator)}
*
* @param attrName the attribute name to filter on
- * @param attrVal the attribute value to filter on
- * @param op the comparison operator
- * @return array of drone ids matching the attribute name and value (filtered by
- * {@code op})
- * @see io.github.js0ny.ilp_coursework.util.AttrComparator#isValueMatched(JsonNode,
- * String,
- * AttrOperator)
+ * @param attrVal the attribute value to filter on
+ * @param op the comparison operator
+ * @return array of drone ids matching the attribute name and value (filtered by {@code op})
+ * @see io.github.js0ny.ilp_coursework.util.AttrComparator#isValueMatched(JsonNode, String,
+ * AttrOperator)
*/
private List dronesWithAttributeCompared(
- String attrName,
- String attrVal,
- AttrOperator op
- ) {
+ String attrName, String attrVal, AttrOperator op) {
URI droneUrl = URI.create(baseUrl).resolve(dronesEndpoint);
// This is required to make sure the response is valid
Drone[] drones = restTemplate.getForObject(droneUrl, Drone[].class);
@@ -124,17 +112,18 @@ public class DroneAttrComparatorService {
ObjectMapper mapper = new ObjectMapper();
return Arrays.stream(drones)
- .filter(drone -> {
- JsonNode node = mapper.valueToTree(drone);
- JsonNode attrNode = node.findValue(attrName);
- if (attrNode != null) {
- // Manually handle different types of JsonNode
- return isValueMatched(attrNode, attrVal, op);
- } else {
- return false;
- }
- })
- .map(Drone::id)
- .collect(Collectors.toList());
+ .filter(
+ drone -> {
+ JsonNode node = mapper.valueToTree(drone);
+ JsonNode attrNode = node.findValue(attrName);
+ if (attrNode != null) {
+ // Manually handle different types of JsonNode
+ return isValueMatched(attrNode, attrVal, op);
+ } else {
+ return false;
+ }
+ })
+ .map(Drone::id)
+ .collect(Collectors.toList());
}
}
diff --git a/src/main/java/io/github/js0ny/ilp_coursework/service/DroneInfoService.java b/src/main/java/io/github/js0ny/ilp_coursework/service/DroneInfoService.java
index 29f101a..ca34ecc 100644
--- a/src/main/java/io/github/js0ny/ilp_coursework/service/DroneInfoService.java
+++ b/src/main/java/io/github/js0ny/ilp_coursework/service/DroneInfoService.java
@@ -2,6 +2,7 @@ package io.github.js0ny.ilp_coursework.service;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
+
import io.github.js0ny.ilp_coursework.data.common.LngLat;
import io.github.js0ny.ilp_coursework.data.common.Region;
import io.github.js0ny.ilp_coursework.data.external.Drone;
@@ -9,30 +10,29 @@ import io.github.js0ny.ilp_coursework.data.external.RestrictedArea;
import io.github.js0ny.ilp_coursework.data.external.ServicePoint;
import io.github.js0ny.ilp_coursework.data.external.ServicePointDrones;
import io.github.js0ny.ilp_coursework.data.request.MedDispatchRecRequest;
+
+import org.springframework.lang.Nullable;
+import org.springframework.stereotype.Service;
+import org.springframework.web.client.RestTemplate;
+
import java.net.URI;
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.LocalTime;
import java.util.*;
import java.util.stream.Collectors;
-import org.springframework.lang.Nullable;
-import org.springframework.stereotype.Service;
-import org.springframework.web.client.RestTemplate;
@Service
public class DroneInfoService {
private final String baseUrl;
- private final String dronesForServicePointsEndpoint =
- "drones-for-service-points";
+ private final String dronesForServicePointsEndpoint = "drones-for-service-points";
public static final String servicePointsEndpoint = "service-points";
public static final String restrictedAreasEndpoint = "restricted-areas";
private final RestTemplate restTemplate;
- /**
- * Constructor, handles the base url here.
- */
+ /** Constructor, handles the base url here. */
public DroneInfoService() {
this(new RestTemplate());
}
@@ -41,8 +41,7 @@ public class DroneInfoService {
this.restTemplate = restTemplate;
String baseUrl = System.getenv("ILP_ENDPOINT");
if (baseUrl == null || baseUrl.isBlank()) {
- this.baseUrl =
- "https://ilp-rest-2025-bvh6e9hschfagrgy.ukwest-01.azurewebsites.net/";
+ this.baseUrl = "https://ilp-rest-2025-bvh6e9hschfagrgy.ukwest-01.azurewebsites.net/";
} else {
// Defensive: Add '/' to the end of the URL
if (!baseUrl.endsWith("/")) {
@@ -54,13 +53,14 @@ public class DroneInfoService {
/**
* Return an array of ids of drones with/without cooling capability
- *
- * Associated service method with {@code /dronesWithCooling/{state}}
+ *
+ *
Associated service method with {@code /dronesWithCooling/{state}}
*
* @param state determines the capability filtering
- * @return if {@code state} is true, return ids of drones with cooling
- * capability, else without cooling
- * @see io.github.js0ny.ilp_coursework.controller.DroneController#getDronesWithCoolingCapability(boolean)
+ * @return if {@code state} is true, return ids of drones with cooling capability, else without
+ * cooling
+ * @see
+ * io.github.js0ny.ilp_coursework.controller.DroneController#getDronesWithCoolingCapability(boolean)
*/
public List dronesWithCooling(boolean state) {
// URI droneUrl = URI.create(baseUrl).resolve(dronesEndpoint);
@@ -71,25 +71,22 @@ public class DroneInfoService {
return new ArrayList<>();
}
- return drones
- .stream()
- .filter(drone -> drone.capability().cooling() == state)
- .map(Drone::id)
- .collect(Collectors.toList());
+ return drones.stream()
+ .filter(drone -> drone.capability().cooling() == state)
+ .map(Drone::id)
+ .collect(Collectors.toList());
}
/**
* Return a {@link Drone}-style json data structure with the given {@code id}
- *
- * Associated service method with {@code /droneDetails/{id}}
+ *
+ *
Associated service method with {@code /droneDetails/{id}}
*
* @param id The id of the drone
* @return drone json body of given id
- * @throws NullPointerException when cannot fetch available drones from
- * remote
- * @throws IllegalArgumentException when drone with given {@code id} cannot be
- * found
- * this should lead to a 404
+ * @throws NullPointerException when cannot fetch available drones from remote
+ * @throws IllegalArgumentException when drone with given {@code id} cannot be found this should
+ * lead to a 404
* @see io.github.js0ny.ilp_coursework.controller.DroneController#getDroneDetail(String)
*/
public Drone droneDetail(String id) {
@@ -102,16 +99,14 @@ public class DroneInfoService {
}
// This will result in 404
- throw new IllegalArgumentException(
- "drone with that ID cannot be found"
- );
+ throw new IllegalArgumentException("drone with that ID cannot be found");
}
/**
- * Return an array of ids of drones that match all the requirements in the
- * medical dispatch records
- *
- * Associated service method with
+ * Return an array of ids of drones that match all the requirements in the medical dispatch
+ * records
+ *
+ *
Associated service method with
*
* @param rec array of medical dispatch records
* @return List of drone ids that match all the requirements
@@ -121,51 +116,43 @@ public class DroneInfoService {
List drones = fetchAllDrones();
if (rec == null || rec.length == 0) {
- return drones
- .stream()
- .filter(Objects::nonNull)
- .map(Drone::id)
- .collect(Collectors.toList());
+ return drones.stream()
+ .filter(Objects::nonNull)
+ .map(Drone::id)
+ .collect(Collectors.toList());
}
/*
* Traverse and filter drones, pass every record's requirement to helper
*/
- return drones
- .stream()
- .filter(d -> d != null && d.capability() != null)
- .filter(d ->
- Arrays.stream(rec)
- .filter(r -> r != null && r.requirements() != null)
- .allMatch(r -> droneMatchesRequirement(d, r))
- )
- .map(Drone::id)
- .collect(Collectors.toList());
+ return drones.stream()
+ .filter(d -> d != null && d.capability() != null)
+ .filter(
+ d ->
+ Arrays.stream(rec)
+ .filter(r -> r != null && r.requirements() != null)
+ .allMatch(r -> droneMatchesRequirement(d, r)))
+ .map(Drone::id)
+ .collect(Collectors.toList());
}
/**
* Helper to check if a drone meets the requirement of a medical dispatch.
*
- * @param drone the drone to be checked
+ * @param drone the drone to be checked
* @param record the medical dispatch record containing the requirement
* @return true if the drone meets the requirement, false otherwise
- * @throws IllegalArgumentException when record requirements or drone capability
- * is invalid (capacity and id cannot be null
- * in {@code MedDispathRecDto})
+ * @throws IllegalArgumentException when record requirements or drone capability is invalid
+ * (capacity and id cannot be null in {@code MedDispathRecDto})
*/
- public boolean droneMatchesRequirement(
- Drone drone,
- MedDispatchRecRequest record
- ) {
+ public boolean droneMatchesRequirement(Drone drone, MedDispatchRecRequest record) {
var requirements = record.requirements();
if (requirements == null) {
throw new IllegalArgumentException("requirements cannot be null");
}
var capability = drone.capability();
if (capability == null) {
- throw new IllegalArgumentException(
- "drone capability cannot be null"
- );
+ throw new IllegalArgumentException("drone capability cannot be null");
}
float requiredCapacity = requirements.capacity();
@@ -187,11 +174,7 @@ public class DroneInfoService {
// Conditions: All requirements matched + availability matched, use helper
// For minimal privilege, only pass drone id to check availability
- return (
- matchesCooling &&
- matchesHeating &&
- checkAvailability(drone.id(), record)
- ); // &&
+ return (matchesCooling && matchesHeating && checkAvailability(drone.id(), record)); // &&
// checkCost(drone, record) // checkCost is more expensive than
// checkAvailability
}
@@ -200,21 +183,13 @@ public class DroneInfoService {
* Helper to check if a drone is available at the required date and time
*
* @param droneId the id of the drone to be checked
- * @param record the medical dispatch record containing the required date and
- * time
+ * @param record the medical dispatch record containing the required date and time
* @return true if the drone is available, false otherwise
*/
- private boolean checkAvailability(
- String droneId,
- MedDispatchRecRequest record
- ) {
- URI droneUrl = URI.create(baseUrl).resolve(
- dronesForServicePointsEndpoint
- );
- ServicePointDrones[] servicePoints = restTemplate.getForObject(
- droneUrl,
- ServicePointDrones[].class
- );
+ private boolean checkAvailability(String droneId, MedDispatchRecRequest record) {
+ URI droneUrl = URI.create(baseUrl).resolve(dronesForServicePointsEndpoint);
+ ServicePointDrones[] servicePoints =
+ restTemplate.getForObject(droneUrl, ServicePointDrones[].class);
LocalDate requiredDate = record.date();
DayOfWeek requiredDay = requiredDate.getDayOfWeek();
@@ -232,13 +207,9 @@ public class DroneInfoService {
}
private LngLat queryServicePointLocationByDroneId(String droneId) {
- URI droneUrl = URI.create(baseUrl).resolve(
- dronesForServicePointsEndpoint
- );
- ServicePointDrones[] servicePoints = restTemplate.getForObject(
- droneUrl,
- ServicePointDrones[].class
- );
+ URI droneUrl = URI.create(baseUrl).resolve(dronesForServicePointsEndpoint);
+ ServicePointDrones[] servicePoints =
+ restTemplate.getForObject(droneUrl, ServicePointDrones[].class);
assert servicePoints != null;
for (var sp : servicePoints) {
@@ -253,14 +224,10 @@ public class DroneInfoService {
@Nullable
private LngLat queryServicePointLocation(int id) {
- URI servicePointUrl = URI.create(baseUrl).resolve(
- servicePointsEndpoint
- );
+ URI servicePointUrl = URI.create(baseUrl).resolve(servicePointsEndpoint);
- ServicePoint[] servicePoints = restTemplate.getForObject(
- servicePointUrl,
- ServicePoint[].class
- );
+ ServicePoint[] servicePoints =
+ restTemplate.getForObject(servicePointUrl, ServicePoint[].class);
assert servicePoints != null;
for (var sp : servicePoints) {
@@ -282,49 +249,32 @@ public class DroneInfoService {
}
public List fetchRestrictedAreas() {
- URI restrictedUrl = URI.create(baseUrl).resolve(
- restrictedAreasEndpoint
- );
- RestrictedArea[] restrictedAreas = restTemplate.getForObject(
- restrictedUrl,
- RestrictedArea[].class
- );
+ URI restrictedUrl = URI.create(baseUrl).resolve(restrictedAreasEndpoint);
+ RestrictedArea[] restrictedAreas =
+ restTemplate.getForObject(restrictedUrl, RestrictedArea[].class);
assert restrictedAreas != null;
return Arrays.asList(restrictedAreas);
}
- public List fetchRestrictedAreasInGeoJson()
- throws JsonProcessingException {
+ public List fetchRestrictedAreasInGeoJson() throws JsonProcessingException {
var mapper = new ObjectMapper();
var ras = fetchRestrictedAreas();
- var geoJson = ras
- .stream()
- .map(RestrictedArea::toRegion)
- .map(Region::toGeoJson)
- .toList();
+ var geoJson = ras.stream().map(RestrictedArea::toRegion).map(Region::toGeoJson).toList();
return Collections.singletonList(mapper.writeValueAsString(geoJson));
}
public List fetchServicePoints() {
- URI servicePointUrl = URI.create(baseUrl).resolve(
- servicePointsEndpoint
- );
- ServicePoint[] servicePoints = restTemplate.getForObject(
- servicePointUrl,
- ServicePoint[].class
- );
+ URI servicePointUrl = URI.create(baseUrl).resolve(servicePointsEndpoint);
+ ServicePoint[] servicePoints =
+ restTemplate.getForObject(servicePointUrl, ServicePoint[].class);
assert servicePoints != null;
return Arrays.asList(servicePoints);
}
public List fetchDronesForServicePoints() {
- URI servicePointDronesUrl = URI.create(baseUrl).resolve(
- dronesForServicePointsEndpoint
- );
- ServicePointDrones[] servicePointDrones = restTemplate.getForObject(
- servicePointDronesUrl,
- ServicePointDrones[].class
- );
+ URI servicePointDronesUrl = URI.create(baseUrl).resolve(dronesForServicePointsEndpoint);
+ ServicePointDrones[] servicePointDrones =
+ restTemplate.getForObject(servicePointDronesUrl, ServicePointDrones[].class);
assert servicePointDrones != null;
return Arrays.asList(servicePointDrones);
}
diff --git a/src/main/java/io/github/js0ny/ilp_coursework/service/GpsCalculationService.java b/src/main/java/io/github/js0ny/ilp_coursework/service/GpsCalculationService.java
index 2e3abd1..e1f9998 100644
--- a/src/main/java/io/github/js0ny/ilp_coursework/service/GpsCalculationService.java
+++ b/src/main/java/io/github/js0ny/ilp_coursework/service/GpsCalculationService.java
@@ -6,9 +6,11 @@ import io.github.js0ny.ilp_coursework.data.common.Region;
import io.github.js0ny.ilp_coursework.data.request.DistanceRequest;
import io.github.js0ny.ilp_coursework.data.request.MovementRequest;
import io.github.js0ny.ilp_coursework.data.request.RegionCheckRequest;
-import java.util.List;
+
import org.springframework.stereotype.Service;
+import java.util.List;
+
/**
* Class that handles calculations about Coordinates
*
@@ -24,6 +26,7 @@ public class GpsCalculationService {
* @see #nextPosition(LngLat, double)
*/
private static final double STEP = 0.00015;
+
/**
* Given threshold to judge if two points are close to each other
*
@@ -32,14 +35,12 @@ public class GpsCalculationService {
private static final double CLOSE_THRESHOLD = 0.00015;
/**
- * Calculate the Euclidean distance between {@code position1} and
- * {@code position2}, which are coordinates
- * defined as {@link LngLat}
+ * Calculate the Euclidean distance between {@code position1} and {@code position2}, which are
+ * coordinates defined as {@link LngLat}
*
* @param position1 The coordinate of the first position
* @param position2 The coordinate of the second position
- * @return The Euclidean distance between {@code position1} and
- * {@code position2}
+ * @return The Euclidean distance between {@code position1} and {@code position2}
* @see io.github.js0ny.ilp_coursework.controller.ApiController#getDistance(DistanceRequest)
*/
public double calculateDistance(LngLat position1, LngLat position2) {
@@ -55,17 +56,14 @@ public class GpsCalculationService {
}
/**
- * Check if {@code position1} and
- * {@code position2} are close to each other, the threshold is < 0.00015
+ * Check if {@code position1} and {@code position2} are close to each other, the threshold is <
+ * 0.00015
*
- *
- * Note that = 0.00015 will be counted as not close to and will return {@code
- * false}
+ *
Note that = 0.00015 will be counted as not close to and will return {@code false}
*
* @param position1 The coordinate of the first position
* @param position2 The coordinate of the second position
- * @return {@code true} if {@code position1} and
- * {@code position2} are close to each other
+ * @return {@code true} if {@code position1} and {@code position2} are close to each other
* @see #CLOSE_THRESHOLD
* @see io.github.js0ny.ilp_coursework.controller.ApiController#getIsCloseTo(DistanceRequest)
*/
@@ -75,9 +73,8 @@ public class GpsCalculationService {
}
/**
- * Returns the next position moved from {@code start} in the direction with
- * {@code angle}, with step size
- * 0.00015
+ * Returns the next position moved from {@code start} in the direction with {@code angle}, with
+ * step size 0.00015
*
* @param start The coordinate of the original start point.
* @param angle The direction to be moved in angle.
@@ -93,19 +90,18 @@ public class GpsCalculationService {
}
/**
- * Used to check if the given {@code position}
- * is inside the {@code region}, on edge and vertex is considered as inside.
+ * Used to check if the given {@code position} is inside the {@code region}, on edge and vertex
+ * is considered as inside.
*
* @param position The coordinate of the position.
- * @param region A {@link Region} that contains name and a list of
- * {@code LngLatDto}
+ * @param region A {@link Region} that contains name and a list of {@code LngLatDto}
* @return {@code true} if {@code position} is inside the {@code region}.
* @throws IllegalArgumentException If {@code region} is not closed
- * @see io.github.js0ny.ilp_coursework.controller.ApiController#getIsInRegion(RegionCheckRequest)
+ * @see
+ * io.github.js0ny.ilp_coursework.controller.ApiController#getIsInRegion(RegionCheckRequest)
* @see Region#isClosed()
*/
- public boolean checkIsInRegion(LngLat position, Region region)
- throws IllegalArgumentException {
+ public boolean checkIsInRegion(LngLat position, Region region) throws IllegalArgumentException {
if (!region.isClosed()) {
// call method from RegionDto to check if not closed
throw new IllegalArgumentException("Region is not closed.");
@@ -114,14 +110,12 @@ public class GpsCalculationService {
}
/**
- * Helper function to {@code checkIsInRegion}, use of ray-casting algorithm
- * to check if inside the polygon
+ * Helper function to {@code checkIsInRegion}, use of ray-casting algorithm to check if inside
+ * the polygon
*
- * @param point The point to check
- * @param polygon The region that forms a polygon to check if {@code point}
- * sits inside.
- * @return If the {@code point} sits inside the {@code polygon} then
- * return {@code true}
+ * @param point The point to check
+ * @param polygon The region that forms a polygon to check if {@code point} sits inside.
+ * @return If the {@code point} sits inside the {@code polygon} then return {@code true}
* @see #isPointOnEdge(LngLat, LngLat, LngLat)
* @see #checkIsInRegion(LngLat, Region)
*/
@@ -154,9 +148,7 @@ public class GpsCalculationService {
}
double xIntersection =
- a.lng() +
- ((point.lat() - a.lat()) * (b.lng() - a.lng())) /
- (b.lat() - a.lat());
+ a.lng() + ((point.lat() - a.lat()) * (b.lng() - a.lng())) / (b.lat() - a.lat());
if (xIntersection > point.lng()) {
++intersections;
@@ -170,8 +162,7 @@ public class GpsCalculationService {
/**
* Helper function from {@code rayCasting} that used to simply calculation
- * Used to check if point {@code p} is on the edge formed by
- * {@code a} and {@code b}
+ * Used to check if point {@code p} is on the edge formed by {@code a} and {@code b}
*
* @param p point to be checked on the edge
* @param a point that forms the edge
@@ -182,18 +173,16 @@ public class GpsCalculationService {
private boolean isPointOnEdge(LngLat p, LngLat a, LngLat b) {
// Cross product: (p - a) × (b - a)
double crossProduct =
- (p.lng() - a.lng()) * (b.lat() - a.lat()) -
- (p.lat() - a.lat()) * (b.lng() - a.lng());
+ (p.lng() - a.lng()) * (b.lat() - a.lat())
+ - (p.lat() - a.lat()) * (b.lng() - a.lng());
if (Math.abs(crossProduct) > 1e-9) {
return false;
}
boolean isWithinLng =
- p.lng() >= Math.min(a.lng(), b.lng()) &&
- p.lng() <= Math.max(a.lng(), b.lng());
+ p.lng() >= Math.min(a.lng(), b.lng()) && p.lng() <= Math.max(a.lng(), b.lng());
boolean isWithinLat =
- p.lat() >= Math.min(a.lat(), b.lat()) &&
- p.lat() <= Math.max(a.lat(), b.lat());
+ p.lat() >= Math.min(a.lat(), b.lat()) && p.lat() <= Math.max(a.lat(), b.lat());
return isWithinLng && isWithinLat;
}
diff --git a/src/main/java/io/github/js0ny/ilp_coursework/service/PathFinderService.java b/src/main/java/io/github/js0ny/ilp_coursework/service/PathFinderService.java
index 0d26328..6e0a4cf 100644
--- a/src/main/java/io/github/js0ny/ilp_coursework/service/PathFinderService.java
+++ b/src/main/java/io/github/js0ny/ilp_coursework/service/PathFinderService.java
@@ -19,6 +19,9 @@ import io.github.js0ny.ilp_coursework.data.request.MedDispatchRecRequest;
import io.github.js0ny.ilp_coursework.data.response.DeliveryPathResponse;
import io.github.js0ny.ilp_coursework.data.response.DeliveryPathResponse.DronePath;
import io.github.js0ny.ilp_coursework.data.response.DeliveryPathResponse.DronePath.Delivery;
+
+import org.springframework.stereotype.Service;
+
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
@@ -27,7 +30,6 @@ import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
-import org.springframework.stereotype.Service;
/**
* Class that handles calculations about deliverypath
@@ -40,9 +42,8 @@ import org.springframework.stereotype.Service;
public class PathFinderService {
/**
- * Hard stop on how many pathfinding iterations we attempt for a single
- * segment before bailing, useful for preventing infinite loops caused by
- * precision quirks or unexpected map data.
+ * Hard stop on how many pathfinding iterations we attempt for a single segment before bailing,
+ * useful for preventing infinite loops caused by precision quirks or unexpected map data.
*
* @see #computePath(LngLat, LngLat)
*/
@@ -59,17 +60,14 @@ public class PathFinderService {
private final List restrictedRegions;
/**
- * Constructor for PathFinderService. The dependencies are injected by
- * Spring and the constructor pre-computes reference maps used throughout the
- * request lifecycle.
+ * Constructor for PathFinderService. The dependencies are injected by Spring and the
+ * constructor pre-computes reference maps used throughout the request lifecycle.
*
* @param gpsCalculationService Service handling geometric operations.
- * @param droneInfoService Service that exposes drone metadata and
- * capability information.
+ * @param droneInfoService Service that exposes drone metadata and capability information.
*/
public PathFinderService(
- GpsCalculationService gpsCalculationService,
- DroneInfoService droneInfoService) {
+ GpsCalculationService gpsCalculationService, DroneInfoService droneInfoService) {
this.gpsCalculationService = gpsCalculationService;
this.droneInfoService = droneInfoService;
this.objectMapper = new ObjectMapper();
@@ -79,11 +77,11 @@ public class PathFinderService {
this.drones = droneInfoService.fetchAllDrones();
List servicePoints = droneInfoService.fetchServicePoints();
- List servicePointAssignments = droneInfoService.fetchDronesForServicePoints();
+ List servicePointAssignments =
+ droneInfoService.fetchDronesForServicePoints();
List restrictedAreas = droneInfoService.fetchRestrictedAreas();
- this.droneById = this.drones.stream().collect(
- Collectors.toMap(Drone::id, drone -> drone));
+ this.droneById = this.drones.stream().collect(Collectors.toMap(Drone::id, drone -> drone));
this.droneServicePointMap = new HashMap<>();
for (ServicePointDrones assignment : servicePointAssignments) {
@@ -94,40 +92,33 @@ public class PathFinderService {
if (availability == null || availability.id() == null) {
continue;
}
- droneServicePointMap.put(
- availability.id(),
- assignment.servicePointId());
+ droneServicePointMap.put(availability.id(), assignment.servicePointId());
}
}
- this.servicePointLocations = servicePoints
- .stream()
- .collect(
- Collectors.toMap(ServicePoint::id, sp -> new LngLat(sp.location())));
+ this.servicePointLocations =
+ servicePoints.stream()
+ .collect(
+ Collectors.toMap(
+ ServicePoint::id, sp -> new LngLat(sp.location())));
- this.restrictedRegions = restrictedAreas
- .stream()
- .map(RestrictedArea::toRegion)
- .toList();
+ this.restrictedRegions = restrictedAreas.stream().map(RestrictedArea::toRegion).toList();
}
/**
- * Produce a delivery plan for the provided dispatch records. Deliveries are
- * grouped per compatible drone and per trip to satisfy each drone move
- * limit.
+ * Produce a delivery plan for the provided dispatch records. Deliveries are grouped per
+ * compatible drone and per trip to satisfy each drone move limit.
*
* @param records Dispatch records to be fulfilled.
* @return Aggregated path response with cost and move totals.
* @see #calculateDeliveryPathAsGeoJson(MedDispatchRecRequest[])
*/
- public DeliveryPathResponse calculateDeliveryPath(
- MedDispatchRecRequest[] records) {
+ public DeliveryPathResponse calculateDeliveryPath(MedDispatchRecRequest[] records) {
if (records == null || records.length == 0) {
return new DeliveryPathResponse(0f, 0, new DronePath[0]);
}
- Map> assigned = assignDeliveries(
- records);
+ Map> assigned = assignDeliveries(records);
List paths = new ArrayList<>();
float totalCost = 0f;
@@ -148,25 +139,20 @@ public class PathFinderService {
continue;
}
- List sortedDeliveries = entry
- .getValue()
- .stream()
- .sorted(
- Comparator.comparingDouble(rec -> gpsCalculationService.calculateDistance(
- servicePointLocation,
- rec.delivery())))
- .toList();
+ List sortedDeliveries =
+ entry.getValue().stream()
+ .sorted(
+ Comparator.comparingDouble(
+ rec ->
+ gpsCalculationService.calculateDistance(
+ servicePointLocation, rec.delivery())))
+ .toList();
- List> trips = splitTrips(
- sortedDeliveries,
- drone,
- servicePointLocation);
+ List> trips =
+ splitTrips(sortedDeliveries, drone, servicePointLocation);
for (List trip : trips) {
- TripResult result = buildTrip(
- drone,
- servicePointLocation,
- trip);
+ TripResult result = buildTrip(drone, servicePointLocation, trip);
if (result != null) {
totalCost += result.cost();
totalMoves += result.moves();
@@ -175,23 +161,18 @@ public class PathFinderService {
}
}
- return new DeliveryPathResponse(
- totalCost,
- totalMoves,
- paths.toArray(new DronePath[0]));
+ return new DeliveryPathResponse(totalCost, totalMoves, paths.toArray(new DronePath[0]));
}
/**
- * Convenience wrapper around {@link #calculateDeliveryPath} that serializes
- * the result into a GeoJSON FeatureCollection suitable for mapping
- * visualization.
+ * Convenience wrapper around {@link #calculateDeliveryPath} that serializes the result into a
+ * GeoJSON FeatureCollection suitable for mapping visualization.
*
* @param records Dispatch records to be fulfilled.
* @return GeoJSON payload representing every delivery flight path.
* @throws IllegalStateException When the payload cannot be serialized.
*/
- public String calculateDeliveryPathAsGeoJson(
- MedDispatchRecRequest[] records) {
+ public String calculateDeliveryPathAsGeoJson(MedDispatchRecRequest[] records) {
DeliveryPathResponse response = calculateDeliveryPath(records);
Map featureCollection = new LinkedHashMap<>();
featureCollection.put("type", "FeatureCollection");
@@ -232,16 +213,13 @@ public class PathFinderService {
try {
return objectMapper.writeValueAsString(featureCollection);
} catch (JsonProcessingException e) {
- throw new IllegalStateException(
- "Failed to generate GeoJSON payload",
- e);
+ throw new IllegalStateException("Failed to generate GeoJSON payload", e);
}
}
/**
- * Group dispatch records by their assigned drone, ensuring every record is
- * routed through {@link #findBestDrone(MedDispatchRecRequest)} exactly once
- * and discarding invalid entries.
+ * Group dispatch records by their assigned drone, ensuring every record is routed through
+ * {@link #findBestDrone(MedDispatchRecRequest)} exactly once and discarding invalid entries.
*
* @param records Dispatch records to be grouped.
* @return Map keyed by drone ID with the deliveries it should service.
@@ -254,16 +232,14 @@ public class PathFinderService {
continue;
}
String droneId = findBestDrone(record);
- assignments
- .computeIfAbsent(droneId, id -> new ArrayList<>())
- .add(record);
+ assignments.computeIfAbsent(droneId, id -> new ArrayList<>()).add(record);
}
return assignments;
}
/**
- * Choose the best drone for the provided record. Currently that equates to
- * picking the closest compatible drone to the delivery location.
+ * Choose the best drone for the provided record. Currently that equates to picking the closest
+ * compatible drone to the delivery location.
*
* @param record Dispatch record that needs fulfillment.
* @return Identifier of the drone that should fly the mission.
@@ -282,15 +258,14 @@ public class PathFinderService {
if (servicePointId == null) {
continue;
}
- LngLat servicePointLocation = servicePointLocations.get(
- servicePointId);
+ LngLat servicePointLocation = servicePointLocations.get(servicePointId);
if (servicePointLocation == null) {
continue;
}
- double distance = gpsCalculationService.calculateDistance(
- servicePointLocation,
- record.delivery());
+ double distance =
+ gpsCalculationService.calculateDistance(
+ servicePointLocation, record.delivery());
if (distance < bestScore) {
bestScore = distance;
@@ -298,28 +273,23 @@ public class PathFinderService {
}
}
if (bestDrone == null) {
- throw new IllegalStateException(
- "No available drone for delivery " + record.id());
+ throw new IllegalStateException("No available drone for delivery " + record.id());
}
return bestDrone;
}
/**
- * Break a sequence of deliveries into several trips that each respect the
- * drone move limit. The deliveries should already be ordered by proximity
- * for sensible grouping.
+ * Break a sequence of deliveries into several trips that each respect the drone move limit. The
+ * deliveries should already be ordered by proximity for sensible grouping.
*
- * @param deliveries Deliveries assigned to a drone.
- * @param drone Drone that will service the deliveries.
+ * @param deliveries Deliveries assigned to a drone.
+ * @param drone Drone that will service the deliveries.
* @param servicePoint Starting and ending point of every trip.
* @return Partitioned trips with at least one delivery each.
- * @throws IllegalStateException If a single delivery exceeds the drone's
- * move limit.
+ * @throws IllegalStateException If a single delivery exceeds the drone's move limit.
*/
private List> splitTrips(
- List deliveries,
- Drone drone,
- LngLat servicePoint) {
+ List deliveries, Drone drone, LngLat servicePoint) {
List> trips = new ArrayList<>();
List currentTrip = new ArrayList<>();
for (MedDispatchRecRequest delivery : deliveries) {
@@ -329,11 +299,11 @@ public class PathFinderService {
currentTrip.remove(currentTrip.size() - 1);
if (currentTrip.isEmpty()) {
throw new IllegalStateException(
- "Delivery " +
- delivery.id() +
- " exceeds drone " +
- drone.id() +
- " move limit");
+ "Delivery "
+ + delivery.id()
+ + " exceeds drone "
+ + drone.id()
+ + " move limit");
}
trips.add(new ArrayList<>(currentTrip));
currentTrip.clear();
@@ -352,20 +322,18 @@ public class PathFinderService {
}
/**
- * Build a single trip for the provided drone, including the entire flight
- * path to every delivery and back home. The resulting structure contains the
- * {@link DronePath} representation as well as cost and moves consumed.
+ * Build a single trip for the provided drone, including the entire flight path to every
+ * delivery and back home. The resulting structure contains the {@link DronePath} representation
+ * as well as cost and moves consumed.
*
- * @param drone Drone executing the trip.
+ * @param drone Drone executing the trip.
* @param servicePoint Starting/ending location of the trip.
- * @param deliveries Deliveries to include in the trip in execution order.
+ * @param deliveries Deliveries to include in the trip in execution order.
* @return Trip information or {@code null} if no deliveries are provided.
* @see DeliveryPathResponse.DronePath
*/
private TripResult buildTrip(
- Drone drone,
- LngLat servicePoint,
- List deliveries) {
+ Drone drone, LngLat servicePoint, List deliveries) {
if (deliveries == null || deliveries.isEmpty()) {
return null;
}
@@ -390,9 +358,7 @@ public class PathFinderService {
moves += toDelivery.moves();
if (i == deliveries.size() - 1) {
- PathSegment backHome = computePath(
- delivery.delivery(),
- servicePoint);
+ PathSegment backHome = computePath(delivery.delivery(), servicePoint);
backHome.appendSkippingStart(flightPath);
moves += backHome.moves();
current = servicePoint;
@@ -402,9 +368,10 @@ public class PathFinderService {
flightPlans.add(new Delivery(delivery.id(), flightPath));
}
- float cost = drone.capability().costInitial() +
- drone.capability().costFinal() +
- (float) (drone.capability().costPerMove() * moves);
+ float cost =
+ drone.capability().costInitial()
+ + drone.capability().costFinal()
+ + (float) (drone.capability().costPerMove() * moves);
DronePath path = new DronePath(drone.parseId(), flightPlans);
@@ -412,16 +379,14 @@ public class PathFinderService {
}
/**
- * Estimate the number of moves a prospective trip would need by replaying
- * the path calculation without mutating any persistent state.
+ * Estimate the number of moves a prospective trip would need by replaying the path calculation
+ * without mutating any persistent state.
*
* @param servicePoint Trip origin.
- * @param deliveries Deliveries that would compose the trip.
+ * @param deliveries Deliveries that would compose the trip.
* @return Total moves required to fly the proposed itinerary.
*/
- private int estimateTripMoves(
- LngLat servicePoint,
- List deliveries) {
+ private int estimateTripMoves(LngLat servicePoint, List deliveries) {
if (deliveries.isEmpty()) {
return 0;
}
@@ -437,10 +402,10 @@ public class PathFinderService {
}
/**
- * Build a path between {@code start} and {@code target} by repeatedly moving
- * in snapped increments while avoiding restricted zones.
+ * Build a path between {@code start} and {@code target} by repeatedly moving in snapped
+ * increments while avoiding restricted zones.
*
- * @param start Start coordinate.
+ * @param start Start coordinate.
* @param target Destination coordinate.
* @return Sequence of visited coordinates and move count.
* @see #nextPosition(LngLat, LngLat)
@@ -453,8 +418,8 @@ public class PathFinderService {
positions.add(start);
LngLat current = start;
int iterations = 0;
- while (!gpsCalculationService.isCloseTo(current, target) &&
- iterations < MAX_SEGMENT_ITERATIONS) {
+ while (!gpsCalculationService.isCloseTo(current, target)
+ && iterations < MAX_SEGMENT_ITERATIONS) {
LngLat next = nextPosition(current, target);
if (next.isSamePoint(current)) {
break;
@@ -470,20 +435,18 @@ public class PathFinderService {
}
/**
- * Determine the next position on the path from {@code current} toward
- * {@code target}, preferring the snapped angle closest to the desired
- * heading that does not infiltrate a restricted region.
+ * Determine the next position on the path from {@code current} toward {@code target},
+ * preferring the snapped angle closest to the desired heading that does not infiltrate a
+ * restricted region.
*
* @param current Current coordinate.
- * @param target Destination coordinate.
- * @return Next admissible coordinate or the original point if none can be
- * found.
+ * @param target Destination coordinate.
+ * @return Next admissible coordinate or the original point if none can be found.
*/
private LngLat nextPosition(LngLat current, LngLat target) {
- double desiredAngle = Math.toDegrees(
- Math.atan2(
- target.lat() - current.lat(),
- target.lng() - current.lng()));
+ double desiredAngle =
+ Math.toDegrees(
+ Math.atan2(target.lat() - current.lat(), target.lng() - current.lng()));
List candidateAngles = buildAngleCandidates(desiredAngle);
for (Angle angle : candidateAngles) {
LngLat next = gpsCalculationService.nextPosition(current, angle);
@@ -495,12 +458,11 @@ public class PathFinderService {
}
/**
- * Build a sequence of candidate angles centered on the desired heading,
- * expanding symmetrically clockwise and counter-clockwise to explore
- * alternative headings if the primary path is blocked.
+ * Build a sequence of candidate angles centered on the desired heading, expanding symmetrically
+ * clockwise and counter-clockwise to explore alternative headings if the primary path is
+ * blocked.
*
- * @param desiredAngle Bearing in degrees between current and target
- * positions.
+ * @param desiredAngle Bearing in degrees between current and target positions.
* @return Ordered list of candidate snapped angles.
* @see Angle#snap(double)
*/
@@ -532,17 +494,16 @@ public class PathFinderService {
}
/**
- * Representation of a computed path segment wrapping the visited positions
- * and the number of moves taken to traverse them.
+ * Representation of a computed path segment wrapping the visited positions and the number of
+ * moves taken to traverse them.
*
* @param positions Ordered coordinates that describe the path.
- * @param moves Number of moves consumed by the path.
+ * @param moves Number of moves consumed by the path.
*/
private record PathSegment(List positions, int moves) {
/**
- * Append the positions from this segment to {@code target}, skipping the
- * first coordinate as it is already represented by the last coordinate
- * in the consumer path.
+ * Append the positions from this segment to {@code target}, skipping the first coordinate
+ * as it is already represented by the last coordinate in the consumer path.
*
* @param target Mutable list to append to.
*/
@@ -554,9 +515,8 @@ public class PathFinderService {
}
/**
- * Bundle containing the calculated {@link DronePath}, total moves and
- * financial cost for a single trip.
+ * Bundle containing the calculated {@link DronePath}, total moves and financial cost for a
+ * single trip.
*/
- private record TripResult(DronePath path, int moves, float cost) {
- }
+ private record TripResult(DronePath path, int moves, float cost) {}
}
diff --git a/src/main/java/io/github/js0ny/ilp_coursework/util/AttrComparator.java b/src/main/java/io/github/js0ny/ilp_coursework/util/AttrComparator.java
index a88f6b7..3039dc1 100644
--- a/src/main/java/io/github/js0ny/ilp_coursework/util/AttrComparator.java
+++ b/src/main/java/io/github/js0ny/ilp_coursework/util/AttrComparator.java
@@ -1,22 +1,21 @@
package io.github.js0ny.ilp_coursework.util;
-import java.math.BigDecimal;
-
import com.fasterxml.jackson.databind.JsonNode;
+import java.math.BigDecimal;
+
/**
* Comparator for attribute values in {@code JsonNode}.
*
- * This is a helper for dynamic querying.
+ * This is a helper for dynamic querying.
*/
public class AttrComparator {
/**
- * Helper for dynamic querying, to compare the json value with given value in
- * {@code String}.
+ * Helper for dynamic querying, to compare the json value with given value in {@code String}.
*
- * @param node The {@code JsonNode} to be compared
+ * @param node The {@code JsonNode} to be compared
* @param attrVal The Value passed, in {@code String}
- * @param op The comparison operator
+ * @param op The comparison operator
* @return {@code true} if given values are equal, otherwise false.
*/
public static boolean isValueMatched(JsonNode node, String attrVal, AttrOperator op) {
@@ -46,9 +45,9 @@ public class AttrComparator {
return switch (op) {
case EQ -> nodeVal.equals(attrVal);
default -> !nodeVal.equals(attrVal);
- // case NE -> !nodeVal.equals(attrVal);
- // case GT -> !nodeVal.equals(attrVal);// > 0;
- // case LT -> !nodeVal.equals(attrVal);// < 0;
+ // case NE -> !nodeVal.equals(attrVal);
+ // case GT -> !nodeVal.equals(attrVal);// > 0;
+ // case LT -> !nodeVal.equals(attrVal);// < 0;
};
}
@@ -56,9 +55,9 @@ public class AttrComparator {
return switch (op) {
case EQ -> nodeVal == attrVal;
default -> nodeVal != attrVal;
- // case NE -> nodeVal != attrVal;
- // case GT -> !nodeVal && attrVal; // false < true
- // case LT -> nodeVal && !attrVal; // true > false
+ // case NE -> nodeVal != attrVal;
+ // case GT -> !nodeVal && attrVal; // false < true
+ // case LT -> nodeVal && !attrVal; // true > false
};
}
}
diff --git a/src/test/java/io/github/js0ny/ilp_coursework/ActuatorHealthTest.java b/src/test/java/io/github/js0ny/ilp_coursework/ActuatorHealthTest.java
index a00efc2..b455ae2 100644
--- a/src/test/java/io/github/js0ny/ilp_coursework/ActuatorHealthTest.java
+++ b/src/test/java/io/github/js0ny/ilp_coursework/ActuatorHealthTest.java
@@ -1,5 +1,10 @@
package io.github.js0ny.ilp_coursework;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
@@ -7,23 +12,18 @@ import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMock
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MockMvc;
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
-import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
-import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
-import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
-
@SpringBootTest
@AutoConfigureMockMvc
public class ActuatorHealthTest {
- @Autowired
- private MockMvc mockMvc;
+ @Autowired private MockMvc mockMvc;
@Test
@DisplayName("GET /actuator/health -> 200 OK")
void getActuator_shouldReturn200AndON() throws Exception {
String endpoint = "/actuator/health";
- String expected = """
+ String expected =
+ """
{
"status": "UP"
}
diff --git a/src/test/java/io/github/js0ny/ilp_coursework/IlpCourseworkApplicationTests.java b/src/test/java/io/github/js0ny/ilp_coursework/IlpCourseworkApplicationTests.java
index 25e3b6b..6f3a15d 100644
--- a/src/test/java/io/github/js0ny/ilp_coursework/IlpCourseworkApplicationTests.java
+++ b/src/test/java/io/github/js0ny/ilp_coursework/IlpCourseworkApplicationTests.java
@@ -6,8 +6,6 @@ import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class IlpCourseworkApplicationTests {
- @Test
- void contextLoads() {
- }
-
+ @Test
+ void contextLoads() {}
}
diff --git a/src/test/java/io/github/js0ny/ilp_coursework/controller/ApiControllerTest.java b/src/test/java/io/github/js0ny/ilp_coursework/controller/ApiControllerTest.java
index abd310f..fc0c886 100644
--- a/src/test/java/io/github/js0ny/ilp_coursework/controller/ApiControllerTest.java
+++ b/src/test/java/io/github/js0ny/ilp_coursework/controller/ApiControllerTest.java
@@ -8,6 +8,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import com.fasterxml.jackson.databind.ObjectMapper;
+
import io.github.js0ny.ilp_coursework.data.common.Angle;
import io.github.js0ny.ilp_coursework.data.common.LngLat;
import io.github.js0ny.ilp_coursework.data.common.Region;
@@ -15,7 +16,7 @@ import io.github.js0ny.ilp_coursework.data.request.DistanceRequest;
import io.github.js0ny.ilp_coursework.data.request.MovementRequest;
import io.github.js0ny.ilp_coursework.data.request.RegionCheckRequest;
import io.github.js0ny.ilp_coursework.service.GpsCalculationService;
-import java.util.List;
+
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
@@ -26,17 +27,16 @@ import org.springframework.test.context.bean.override.mockito.MockitoBean;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
+import java.util.List;
+
@WebMvcTest(ApiController.class)
public class ApiControllerTest {
- @Autowired
- private MockMvc mockMvc;
+ @Autowired private MockMvc mockMvc;
- @Autowired
- private ObjectMapper objectMapper;
+ @Autowired private ObjectMapper objectMapper;
- @MockitoBean
- private GpsCalculationService service;
+ @MockitoBean private GpsCalculationService service;
@Nested
@DisplayName("GET /uid")
@@ -59,21 +59,19 @@ public class ApiControllerTest {
@Test
@DisplayName("POST /distanceTo -> 200 OK")
- void getDistance_shouldReturn200AndDistance_whenCorrectInput()
- throws Exception {
+ void getDistance_shouldReturn200AndDistance_whenCorrectInput() throws Exception {
double expected = 5.0;
String endpoint = "/api/v1/distanceTo";
LngLat p1 = new LngLat(0, 4.0);
LngLat p2 = new LngLat(3.0, 0);
var req = new DistanceRequest(p1, p2);
- when(
- service.calculateDistance(any(LngLat.class), any(LngLat.class))
- ).thenReturn(expected);
- var mock = mockMvc.perform(
- post(endpoint)
- .contentType(MediaType.APPLICATION_JSON)
- .content(objectMapper.writeValueAsString(req))
- );
+ when(service.calculateDistance(any(LngLat.class), any(LngLat.class)))
+ .thenReturn(expected);
+ var mock =
+ mockMvc.perform(
+ post(endpoint)
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(objectMapper.writeValueAsString(req)));
mock.andExpect(status().isOk());
mock.andExpect(content().string(String.valueOf(expected)));
@@ -83,43 +81,33 @@ public class ApiControllerTest {
@DisplayName("POST /distanceTo -> 400 Bad Request: Missing Field")
void getDistance_shouldReturn400_whenMissingField() throws Exception {
String endpoint = "/api/v1/distanceTo";
- String req = """
- {
- "position1": {
- "lng": 3.0,
- "lat": 4.0
+ String req =
+ """
+ {
+ "position1": {
+ "lng": 3.0,
+ "lat": 4.0
+ }
}
- }
- """;
- when(
- service.calculateDistance(any(LngLat.class), isNull())
- ).thenThrow(new NullPointerException());
- mockMvc
- .perform(
- post(endpoint)
- .contentType(MediaType.APPLICATION_JSON)
- .content(req)
- )
- .andExpect(status().isBadRequest());
+ """;
+ when(service.calculateDistance(any(LngLat.class), isNull()))
+ .thenThrow(new NullPointerException());
+ mockMvc.perform(post(endpoint).contentType(MediaType.APPLICATION_JSON).content(req))
+ .andExpect(status().isBadRequest());
}
@Test
@DisplayName("POST /distanceTo -> 400 Bad Request: Semantic errors")
void getDistance_shouldReturn400_whenInvalidInput() throws Exception {
String endpoint = "/api/v1/distanceTo";
- String req = """
- { "position1": { "lng": -300.192473, "lat": 550.946233 }, "position2": { "lng": -3202.192473, "lat": 5533.942617 } }
- """;
- when(
- service.calculateDistance(any(LngLat.class), isNull())
- ).thenThrow(new NullPointerException());
- mockMvc
- .perform(
- post(endpoint)
- .contentType(MediaType.APPLICATION_JSON)
- .content(req)
- )
- .andExpect(status().isBadRequest());
+ String req =
+ """
+ { "position1": { "lng": -300.192473, "lat": 550.946233 }, "position2": { "lng": -3202.192473, "lat": 5533.942617 } }
+ """;
+ when(service.calculateDistance(any(LngLat.class), isNull()))
+ .thenThrow(new NullPointerException());
+ mockMvc.perform(post(endpoint).contentType(MediaType.APPLICATION_JSON).content(req))
+ .andExpect(status().isBadRequest());
}
}
@@ -129,21 +117,18 @@ public class ApiControllerTest {
@Test
@DisplayName("POST /isCloseTo -> 200 OK")
- void getIsCloseTo_shouldReturn200AndBoolean_whenCorrectInput()
- throws Exception {
+ void getIsCloseTo_shouldReturn200AndBoolean_whenCorrectInput() throws Exception {
boolean expected = false;
String endpoint = "/api/v1/isCloseTo";
LngLat p1 = new LngLat(0, 4.0);
LngLat p2 = new LngLat(3.0, 0);
var req = new DistanceRequest(p1, p2);
- when(
- service.isCloseTo(any(LngLat.class), any(LngLat.class))
- ).thenReturn(expected);
- var mock = mockMvc.perform(
- post(endpoint)
- .contentType(MediaType.APPLICATION_JSON)
- .content(objectMapper.writeValueAsString(req))
- );
+ when(service.isCloseTo(any(LngLat.class), any(LngLat.class))).thenReturn(expected);
+ var mock =
+ mockMvc.perform(
+ post(endpoint)
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(objectMapper.writeValueAsString(req)));
mock.andExpect(status().isOk());
mock.andExpect(content().string(String.valueOf(expected)));
@@ -151,39 +136,32 @@ public class ApiControllerTest {
@Test
@DisplayName("POST /isCloseTo -> 400 Bad Request: Malformed JSON ")
- void getIsCloseTo_shouldReturn400_whenJsonIsMalformed()
- throws Exception {
+ void getIsCloseTo_shouldReturn400_whenJsonIsMalformed() throws Exception {
// json without a bracket
- String malformedJson = """
- {
- "position1": { "lng": 0.0, "lat": 3.0 }
- """;
- mockMvc
- .perform(
- post("/api/v1/isCloseTo")
- .contentType(MediaType.APPLICATION_JSON)
- .content(malformedJson)
- )
- .andExpect(status().isBadRequest());
+ String malformedJson =
+ """
+ {
+ "position1": { "lng": 0.0, "lat": 3.0 }
+ """;
+ mockMvc.perform(
+ post("/api/v1/isCloseTo")
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(malformedJson))
+ .andExpect(status().isBadRequest());
}
@Test
@DisplayName("POST /isCloseTo -> 400 Bad Request: Semantic errors")
void getIsCloseTo_shouldReturn400_whenInvalidInput() throws Exception {
String endpoint = "/api/v1/isCloseTo";
- String req = """
- { "position1": { "lng": -3004.192473, "lat": 550.946233 }, "position2": { "lng": -390.192473, "lat": 551.942617 } }
+ String req =
+ """
+ { "position1": { "lng": -3004.192473, "lat": 550.946233 }, "position2": { "lng": -390.192473, "lat": 551.942617 } }
""";
- when(
- service.calculateDistance(any(LngLat.class), isNull())
- ).thenThrow(new NullPointerException());
- mockMvc
- .perform(
- post(endpoint)
- .contentType(MediaType.APPLICATION_JSON)
- .content(req)
- )
- .andExpect(status().isBadRequest());
+ when(service.calculateDistance(any(LngLat.class), isNull()))
+ .thenThrow(new NullPointerException());
+ mockMvc.perform(post(endpoint).contentType(MediaType.APPLICATION_JSON).content(req))
+ .andExpect(status().isBadRequest());
}
}
@@ -195,73 +173,59 @@ public class ApiControllerTest {
@Test
@DisplayName("POST /nextPosition -> 200 OK")
- void getNextPosition_shouldReturn200AndCoordinate_whenCorrectInput()
- throws Exception {
+ void getNextPosition_shouldReturn200AndCoordinate_whenCorrectInput() throws Exception {
LngLat expected = new LngLat(0.00015, 0.0);
LngLat p = new LngLat(0, 0);
var req = new MovementRequest(p, 0);
- when(
- service.nextPosition(any(LngLat.class), any(Angle.class))
- ).thenReturn(expected);
- var mock = mockMvc.perform(
- post(endpoint)
- .contentType(MediaType.APPLICATION_JSON)
- .content(objectMapper.writeValueAsString(req))
- );
+ when(service.nextPosition(any(LngLat.class), any(Angle.class))).thenReturn(expected);
+ var mock =
+ mockMvc.perform(
+ post(endpoint)
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(objectMapper.writeValueAsString(req)));
mock.andExpect(status().isOk());
- mock.andExpect(
- content().json(objectMapper.writeValueAsString(expected))
- );
+ mock.andExpect(content().json(objectMapper.writeValueAsString(expected)));
}
@Test
@DisplayName("POST /nextPosition -> 400 Bad Request: Missing Field")
- void getNextPosition_shouldReturn400_whenKeyNameError()
- throws Exception {
+ void getNextPosition_shouldReturn400_whenKeyNameError() throws Exception {
// "position" should be "start"
- String malformedJson = """
- {
- "position": { "lng": 0.0, "lat": 3.0 },
- "angle": 180
- }
- """;
- when(service.nextPosition(isNull(), any(Angle.class))).thenThrow(
- new NullPointerException()
- );
- mockMvc
- .perform(
- post("/api/v1/nextPosition")
- .contentType(MediaType.APPLICATION_JSON)
- .content(malformedJson)
- )
- .andExpect(MockMvcResultMatchers.status().isBadRequest());
+ String malformedJson =
+ """
+ {
+ "position": { "lng": 0.0, "lat": 3.0 },
+ "angle": 180
+ }
+ """;
+ when(service.nextPosition(isNull(), any(Angle.class)))
+ .thenThrow(new NullPointerException());
+ mockMvc.perform(
+ post("/api/v1/nextPosition")
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(malformedJson))
+ .andExpect(MockMvcResultMatchers.status().isBadRequest());
}
@Test
@DisplayName("POST /nextPosition -> 400 Bad Request: Semantic errors")
- void getNextPosition_shouldReturn400_whenInvalidInput()
- throws Exception {
+ void getNextPosition_shouldReturn400_whenInvalidInput() throws Exception {
String endpoint = "/api/v1/nextPosition";
- String req = """
- {
- "start": {
- "lng": -3.192473,
- "lat": 55.946233
- },
- "angle": 900
- }
- """;
- when(
- service.calculateDistance(any(LngLat.class), isNull())
- ).thenThrow(new NullPointerException());
- mockMvc
- .perform(
- post(endpoint)
- .contentType(MediaType.APPLICATION_JSON)
- .content(req)
- )
- .andExpect(status().isBadRequest());
+ String req =
+ """
+ {
+ "start": {
+ "lng": -3.192473,
+ "lat": 55.946233
+ },
+ "angle": 900
+ }
+ """;
+ when(service.calculateDistance(any(LngLat.class), isNull()))
+ .thenThrow(new NullPointerException());
+ mockMvc.perform(post(endpoint).contentType(MediaType.APPLICATION_JSON).content(req))
+ .andExpect(status().isBadRequest());
}
}
@@ -271,30 +235,27 @@ public class ApiControllerTest {
@Test
@DisplayName("POST /isInRegion -> 200 OK")
- void getIsInRegion_shouldReturn200AndBoolean_whenCorrectInput()
- throws Exception {
+ void getIsInRegion_shouldReturn200AndBoolean_whenCorrectInput() throws Exception {
boolean expected = false;
String endpoint = "/api/v1/isInRegion";
var position = new LngLat(1.234, 1.222);
- var region = new Region(
- "central",
- List.of(
- new LngLat(-3.192473, 55.946233),
- new LngLat(-3.192473, 55.942617),
- new LngLat(-3.184319, 55.942617),
- new LngLat(-3.184319, 55.946233),
- new LngLat(-3.192473, 55.946233)
- )
- );
+ var region =
+ new Region(
+ "central",
+ List.of(
+ new LngLat(-3.192473, 55.946233),
+ new LngLat(-3.192473, 55.942617),
+ new LngLat(-3.184319, 55.942617),
+ new LngLat(-3.184319, 55.946233),
+ new LngLat(-3.192473, 55.946233)));
var req = new RegionCheckRequest(position, region);
- when(
- service.checkIsInRegion(any(LngLat.class), any(Region.class))
- ).thenReturn(expected);
- var mock = mockMvc.perform(
- post(endpoint)
- .contentType(MediaType.APPLICATION_JSON)
- .content(objectMapper.writeValueAsString(req))
- );
+ when(service.checkIsInRegion(any(LngLat.class), any(Region.class)))
+ .thenReturn(expected);
+ var mock =
+ mockMvc.perform(
+ post(endpoint)
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(objectMapper.writeValueAsString(req)));
mock.andExpect(status().isOk());
mock.andExpect(content().string(String.valueOf(expected)));
@@ -302,53 +263,44 @@ public class ApiControllerTest {
@Test
@DisplayName(
- "POST /isInRegion -> 400 Bad Request: Passing a list of empty vertices to isInRegion"
- )
- void getIsInRegion_shouldReturn400_whenPassingIllegalArguments()
- throws Exception {
+ "POST /isInRegion -> 400 Bad Request: Passing a list of empty vertices to"
+ + " isInRegion")
+ void getIsInRegion_shouldReturn400_whenPassingIllegalArguments() throws Exception {
var position = new LngLat(1, 1);
var region = new Region("illegal", List.of());
var request = new RegionCheckRequest(position, region);
- when(
- service.checkIsInRegion(any(LngLat.class), any(Region.class))
- ).thenThrow(new IllegalArgumentException("Region is not closed."));
- mockMvc
- .perform(
- post("/api/v1/isInRegion")
- .contentType(MediaType.APPLICATION_JSON)
- .content(objectMapper.writeValueAsString(request))
- )
- .andExpect(status().isBadRequest());
+ when(service.checkIsInRegion(any(LngLat.class), any(Region.class)))
+ .thenThrow(new IllegalArgumentException("Region is not closed."));
+ mockMvc.perform(
+ post("/api/v1/isInRegion")
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(objectMapper.writeValueAsString(request)))
+ .andExpect(status().isBadRequest());
}
@Test
@DisplayName(
- "POST /isInRegion -> 400 Bad Request: Passing a list of not-closing vertices to isInRegion"
- )
- void getIsInRegion_shouldReturn400_whenPassingNotClosingVertices()
- throws Exception {
+ "POST /isInRegion -> 400 Bad Request: Passing a list of not-closing vertices to"
+ + " isInRegion")
+ void getIsInRegion_shouldReturn400_whenPassingNotClosingVertices() throws Exception {
var position = new LngLat(1, 1);
- var region = new Region(
- "illegal",
- List.of(
- new LngLat(1, 2),
- new LngLat(3, 4),
- new LngLat(5, 6),
- new LngLat(7, 8),
- new LngLat(9, 10)
- )
- );
+ var region =
+ new Region(
+ "illegal",
+ List.of(
+ new LngLat(1, 2),
+ new LngLat(3, 4),
+ new LngLat(5, 6),
+ new LngLat(7, 8),
+ new LngLat(9, 10)));
var request = new RegionCheckRequest(position, region);
- when(
- service.checkIsInRegion(any(LngLat.class), any(Region.class))
- ).thenThrow(new IllegalArgumentException("Region is not closed."));
- mockMvc
- .perform(
- post("/api/v1/isInRegion")
- .contentType(MediaType.APPLICATION_JSON)
- .content(objectMapper.writeValueAsString(request))
- )
- .andExpect(status().isBadRequest());
+ when(service.checkIsInRegion(any(LngLat.class), any(Region.class)))
+ .thenThrow(new IllegalArgumentException("Region is not closed."));
+ mockMvc.perform(
+ post("/api/v1/isInRegion")
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(objectMapper.writeValueAsString(request)))
+ .andExpect(status().isBadRequest());
}
}
}
diff --git a/src/test/java/io/github/js0ny/ilp_coursework/controller/DroneControllerTest.java b/src/test/java/io/github/js0ny/ilp_coursework/controller/DroneControllerTest.java
index a9ae4f8..a377da7 100644
--- a/src/test/java/io/github/js0ny/ilp_coursework/controller/DroneControllerTest.java
+++ b/src/test/java/io/github/js0ny/ilp_coursework/controller/DroneControllerTest.java
@@ -9,6 +9,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
+
import io.github.js0ny.ilp_coursework.data.common.DroneCapability;
import io.github.js0ny.ilp_coursework.data.common.LngLat;
import io.github.js0ny.ilp_coursework.data.external.Drone;
@@ -17,10 +18,7 @@ import io.github.js0ny.ilp_coursework.data.request.MedDispatchRecRequest;
import io.github.js0ny.ilp_coursework.service.DroneAttrComparatorService;
import io.github.js0ny.ilp_coursework.service.DroneInfoService;
import io.github.js0ny.ilp_coursework.service.PathFinderService;
-import java.time.LocalDate;
-import java.time.LocalTime;
-import java.util.List;
-import java.util.Map;
+
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
@@ -31,22 +29,23 @@ import org.springframework.http.MediaType;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import org.springframework.test.web.servlet.MockMvc;
+import java.time.LocalDate;
+import java.time.LocalTime;
+import java.util.List;
+import java.util.Map;
+
@WebMvcTest(DroneController.class)
public class DroneControllerTest {
- @Autowired
- private MockMvc mockMvc;
+ @Autowired private MockMvc mockMvc;
private ObjectMapper objectMapper;
- @MockitoBean
- private DroneInfoService droneInfoService;
+ @MockitoBean private DroneInfoService droneInfoService;
- @MockitoBean
- private DroneAttrComparatorService droneAttrComparatorService;
+ @MockitoBean private DroneAttrComparatorService droneAttrComparatorService;
- @MockitoBean
- private PathFinderService pathFinderService;
+ @MockitoBean private PathFinderService pathFinderService;
@BeforeEach
void setUp() {
@@ -66,14 +65,11 @@ public class DroneControllerTest {
throws Exception {
String endpoint = API_ENDPOINT_BASE + "true";
List expected = List.of("1", "5", "8", "9");
- when(
- droneInfoService.dronesWithCooling(anyBoolean())).thenReturn(expected);
- var mock = mockMvc.perform(
- get(endpoint).contentType(MediaType.APPLICATION_JSON));
+ when(droneInfoService.dronesWithCooling(anyBoolean())).thenReturn(expected);
+ var mock = mockMvc.perform(get(endpoint).contentType(MediaType.APPLICATION_JSON));
mock.andExpect(status().isOk());
- mock.andExpect(
- content().json(objectMapper.writeValueAsString(expected)));
+ mock.andExpect(content().json(objectMapper.writeValueAsString(expected)));
}
@Test
@@ -82,23 +78,18 @@ public class DroneControllerTest {
throws Exception {
String endpoint = API_ENDPOINT_BASE + "false";
List expected = List.of("2", "3", "4", "6", "7", "10");
- when(
- droneInfoService.dronesWithCooling(anyBoolean())).thenReturn(expected);
- var mock = mockMvc.perform(
- get(endpoint).contentType(MediaType.APPLICATION_JSON));
+ when(droneInfoService.dronesWithCooling(anyBoolean())).thenReturn(expected);
+ var mock = mockMvc.perform(get(endpoint).contentType(MediaType.APPLICATION_JSON));
mock.andExpect(status().isOk());
- mock.andExpect(
- content().json(objectMapper.writeValueAsString(expected)));
+ mock.andExpect(content().json(objectMapper.writeValueAsString(expected)));
}
@Test
@DisplayName("-> 400 Bad Request")
- void getDronesWithCooling_shouldReturn400_whenStateIsInvalid()
- throws Exception {
+ void getDronesWithCooling_shouldReturn400_whenStateIsInvalid() throws Exception {
String endpoint = API_ENDPOINT_BASE + "invalid";
- mockMvc.perform(
- get(endpoint).contentType(MediaType.APPLICATION_JSON))
+ mockMvc.perform(get(endpoint).contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isBadRequest());
}
}
@@ -111,33 +102,29 @@ public class DroneControllerTest {
@Test
@DisplayName("-> 200 OK")
- void getDroneDetails_shouldReturn200AndJson_whenCorrectInput()
- throws Exception {
- Drone expected = new Drone("Drone 1", "1",
- new DroneCapability(true, true, 4.0f, 2000, 0.01f, 4.3f, 6.5f));
+ void getDroneDetails_shouldReturn200AndJson_whenCorrectInput() throws Exception {
+ Drone expected =
+ new Drone(
+ "Drone 1",
+ "1",
+ new DroneCapability(true, true, 4.0f, 2000, 0.01f, 4.3f, 6.5f));
String endpoint = API_ENDPOINT_BASE + "1";
- when(
- droneInfoService.droneDetail(anyString())).thenReturn(expected);
- var mock = mockMvc.perform(
- get(endpoint).contentType(MediaType.APPLICATION_JSON));
+ when(droneInfoService.droneDetail(anyString())).thenReturn(expected);
+ var mock = mockMvc.perform(get(endpoint).contentType(MediaType.APPLICATION_JSON));
mock.andExpect(status().isOk());
- mock.andExpect(
- content().json(objectMapper.writeValueAsString(expected)));
+ mock.andExpect(content().json(objectMapper.writeValueAsString(expected)));
}
@Test
@DisplayName("-> 404 Not Found")
- void getDroneDetails_shouldReturn404_whenDroneNotFound()
- throws Exception {
+ void getDroneDetails_shouldReturn404_whenDroneNotFound() throws Exception {
String endpoint = API_ENDPOINT_BASE + "invalidDroneId";
- when(
- droneInfoService.droneDetail(anyString())).thenThrow(new IllegalArgumentException());
- mockMvc.perform(
- get(endpoint).contentType(MediaType.APPLICATION_JSON))
+ when(droneInfoService.droneDetail(anyString()))
+ .thenThrow(new IllegalArgumentException());
+ mockMvc.perform(get(endpoint).contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isNotFound());
}
-
}
@Nested
@@ -148,64 +135,64 @@ public class DroneControllerTest {
@Test
@DisplayName("capacity = 8 -> 200 OK")
- void getQueryAsPath_shouldReturn200AndArrayOfString_whenCapacityIs8()
- throws Exception {
+ void getQueryAsPath_shouldReturn200AndArrayOfString_whenCapacityIs8() throws Exception {
String attrName = "capacity";
String attrVal = "8";
List expected = List.of("2", "4", "7", "9");
when(droneAttrComparatorService.dronesWithAttribute(attrName, attrVal))
.thenReturn(expected);
- mockMvc.perform(get(API_ENDPOINT_BASE + attrName + "/" + attrVal)
- .contentType(MediaType.APPLICATION_JSON))
+ mockMvc.perform(
+ get(API_ENDPOINT_BASE + attrName + "/" + attrVal)
+ .contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().json(objectMapper.writeValueAsString(expected)));
}
@Test
@DisplayName("heating = true -> 200 OK")
- void getQueryAsPath_shouldReturn200AndArrayOfString_whenHeatingIsTrue()
- throws Exception {
+ void getQueryAsPath_shouldReturn200AndArrayOfString_whenHeatingIsTrue() throws Exception {
String attrName = "heating";
String attrVal = "true";
List expected = List.of("1", "2", "4", "5", "6", "7", "9");
when(droneAttrComparatorService.dronesWithAttribute(attrName, attrVal))
.thenReturn(expected);
- mockMvc.perform(get(API_ENDPOINT_BASE + attrName + "/" + attrVal)
- .contentType(MediaType.APPLICATION_JSON))
+ mockMvc.perform(
+ get(API_ENDPOINT_BASE + attrName + "/" + attrVal)
+ .contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().json(objectMapper.writeValueAsString(expected)));
}
@Test
@DisplayName("cooling = false -> 200 OK")
- void getQueryAsPath_shouldReturn200AndArrayOfString_whenCoolingIsFalse()
- throws Exception {
+ void getQueryAsPath_shouldReturn200AndArrayOfString_whenCoolingIsFalse() throws Exception {
String attrName = "cooling";
String attrVal = "false";
List expected = List.of("2", "3", "4", "6", "7", "10");
when(droneAttrComparatorService.dronesWithAttribute(attrName, attrVal))
.thenReturn(expected);
- mockMvc.perform(get(API_ENDPOINT_BASE + attrName + "/" + attrVal)
- .contentType(MediaType.APPLICATION_JSON))
+ mockMvc.perform(
+ get(API_ENDPOINT_BASE + attrName + "/" + attrVal)
+ .contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().json(objectMapper.writeValueAsString(expected)));
}
@Test
@DisplayName("maxMoves = 1000 -> 200 OK")
- void getQueryAsPath_shouldReturn200AndArrayOfString_whenMaxMovesIs1000()
- throws Exception {
+ void getQueryAsPath_shouldReturn200AndArrayOfString_whenMaxMovesIs1000() throws Exception {
String attrName = "maxMoves";
String attrVal = "1000";
List expected = List.of("2", "4", "7", "9");
when(droneAttrComparatorService.dronesWithAttribute(attrName, attrVal))
.thenReturn(expected);
- mockMvc.perform(get(API_ENDPOINT_BASE + attrName + "/" + attrVal)
- .contentType(MediaType.APPLICATION_JSON))
+ mockMvc.perform(
+ get(API_ENDPOINT_BASE + attrName + "/" + attrVal)
+ .contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().json(objectMapper.writeValueAsString(expected)));
}
@@ -220,8 +207,9 @@ public class DroneControllerTest {
when(droneAttrComparatorService.dronesWithAttribute(attrName, attrVal))
.thenReturn(expected);
- mockMvc.perform(get(API_ENDPOINT_BASE + attrName + "/" + attrVal)
- .contentType(MediaType.APPLICATION_JSON))
+ mockMvc.perform(
+ get(API_ENDPOINT_BASE + attrName + "/" + attrVal)
+ .contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().json(objectMapper.writeValueAsString(expected)));
}
@@ -243,31 +231,34 @@ public class DroneControllerTest {
AttrQueryRequest[] requestBody = {req1, req2, req3};
List expected = List.of("8");
- when(droneAttrComparatorService.dronesSatisfyingAttributes(any(AttrQueryRequest[].class)))
+ when(droneAttrComparatorService.dronesSatisfyingAttributes(
+ any(AttrQueryRequest[].class)))
.thenReturn(expected);
- mockMvc.perform(post(API_ENDPOINT)
- .contentType(MediaType.APPLICATION_JSON)
- .content(objectMapper.writeValueAsString(requestBody)))
+ mockMvc.perform(
+ post(API_ENDPOINT)
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(objectMapper.writeValueAsString(requestBody)))
.andExpect(status().isOk())
.andExpect(content().json(objectMapper.writeValueAsString(expected)));
}
@Test
@DisplayName("GT LT -> 200 OK")
- void postQuery_shouldReturn200AndArrayOfString_whenGtLtConditions()
- throws Exception {
+ void postQuery_shouldReturn200AndArrayOfString_whenGtLtConditions() throws Exception {
AttrQueryRequest req1 = new AttrQueryRequest("capacity", ">", "8");
AttrQueryRequest req2 = new AttrQueryRequest("maxMoves", "<", "2000");
AttrQueryRequest[] requestBody = {req1, req2};
List expected = List.of("5", "10");
- when(droneAttrComparatorService.dronesSatisfyingAttributes(any(AttrQueryRequest[].class)))
+ when(droneAttrComparatorService.dronesSatisfyingAttributes(
+ any(AttrQueryRequest[].class)))
.thenReturn(expected);
- mockMvc.perform(post(API_ENDPOINT)
- .contentType(MediaType.APPLICATION_JSON)
- .content(objectMapper.writeValueAsString(requestBody)))
+ mockMvc.perform(
+ post(API_ENDPOINT)
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(objectMapper.writeValueAsString(requestBody)))
.andExpect(status().isOk())
.andExpect(content().json(objectMapper.writeValueAsString(expected)));
}
@@ -281,32 +272,35 @@ public class DroneControllerTest {
AttrQueryRequest[] requestBody = {req1, req2};
List expected = List.of();
- when(droneAttrComparatorService.dronesSatisfyingAttributes(any(AttrQueryRequest[].class)))
+ when(droneAttrComparatorService.dronesSatisfyingAttributes(
+ any(AttrQueryRequest[].class)))
.thenReturn(expected);
- mockMvc.perform(post(API_ENDPOINT)
- .contentType(MediaType.APPLICATION_JSON)
- .content(objectMapper.writeValueAsString(requestBody)))
+ mockMvc.perform(
+ post(API_ENDPOINT)
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(objectMapper.writeValueAsString(requestBody)))
.andExpect(status().isOk())
.andExpect(content().json(objectMapper.writeValueAsString(expected)));
}
@Test
@DisplayName("GT LT EQ -> 200 OK")
- void postQuery_shouldReturn200AndArrayOfString_whenGtLtEqConditions()
- throws Exception {
+ void postQuery_shouldReturn200AndArrayOfString_whenGtLtEqConditions() throws Exception {
AttrQueryRequest req1 = new AttrQueryRequest("capacity", ">", "8");
AttrQueryRequest req2 = new AttrQueryRequest("maxMoves", "<", "2000");
AttrQueryRequest req3 = new AttrQueryRequest("cooling", "=", "true");
AttrQueryRequest[] requestBody = {req1, req2, req3};
List expected = List.of("5");
- when(droneAttrComparatorService.dronesSatisfyingAttributes(any(AttrQueryRequest[].class)))
+ when(droneAttrComparatorService.dronesSatisfyingAttributes(
+ any(AttrQueryRequest[].class)))
.thenReturn(expected);
- mockMvc.perform(post(API_ENDPOINT)
- .contentType(MediaType.APPLICATION_JSON)
- .content(objectMapper.writeValueAsString(requestBody)))
+ mockMvc.perform(
+ post(API_ENDPOINT)
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(objectMapper.writeValueAsString(requestBody)))
.andExpect(status().isOk())
.andExpect(content().json(objectMapper.writeValueAsString(expected)));
}
@@ -324,16 +318,23 @@ public class DroneControllerTest {
throws Exception {
var reqs = new MedDispatchRecRequest.MedRequirement(0.75f, false, true, 13.5f);
var delivery = new LngLat(-3.00, 55.121);
- var record = new MedDispatchRecRequest(123, LocalDate.parse("2025-12-22"), LocalTime.parse("14:30"), reqs, delivery);
+ var record =
+ new MedDispatchRecRequest(
+ 123,
+ LocalDate.parse("2025-12-22"),
+ LocalTime.parse("14:30"),
+ reqs,
+ delivery);
MedDispatchRecRequest[] requestBody = {record};
List expected = List.of("1", "2", "6", "7", "9");
when(droneInfoService.dronesMatchesRequirements(any(MedDispatchRecRequest[].class)))
.thenReturn(expected);
- mockMvc.perform(post(API_ENDPOINT)
- .contentType(MediaType.APPLICATION_JSON)
- .content(objectMapper.writeValueAsString(requestBody)))
+ mockMvc.perform(
+ post(API_ENDPOINT)
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(objectMapper.writeValueAsString(requestBody)))
.andExpect(status().isOk())
.andExpect(content().json(objectMapper.writeValueAsString(expected)));
}
@@ -342,24 +343,30 @@ public class DroneControllerTest {
@DisplayName("Treat Null as False (Cooling) -> 200 OK")
void postQueryAvailableDrones_shouldReturn200AndArrayOfString_whenCoolingIsNull()
throws Exception {
- var requestMap = Map.of(
- "id", 123,
- "date", "2025-12-22",
- "time", "14:30",
- "requirements", Map.of(
- "capacity", 0.75,
- "heating", true,
- "maxCost", 13.5
- )
- );
+ var requestMap =
+ Map.of(
+ "id",
+ 123,
+ "date",
+ "2025-12-22",
+ "time",
+ "14:30",
+ "requirements",
+ Map.of(
+ "capacity", 0.75,
+ "heating", true,
+ "maxCost", 13.5));
List expected = List.of("1", "2", "6", "7", "9");
when(droneInfoService.dronesMatchesRequirements(any(MedDispatchRecRequest[].class)))
.thenReturn(expected);
- mockMvc.perform(post(API_ENDPOINT)
- .contentType(MediaType.APPLICATION_JSON)
- .content(objectMapper.writeValueAsString(new Object[]{requestMap})))
+ mockMvc.perform(
+ post(API_ENDPOINT)
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(
+ objectMapper.writeValueAsString(
+ new Object[] {requestMap})))
.andExpect(status().isOk())
.andExpect(content().json(objectMapper.writeValueAsString(expected)));
}
@@ -370,11 +377,23 @@ public class DroneControllerTest {
throws Exception {
var reqs1 = new MedDispatchRecRequest.MedRequirement(0.75f, false, true, 13.5f);
var delivery1 = new LngLat(-3.00, 55.121);
- var record1 = new MedDispatchRecRequest(123, LocalDate.parse("2025-12-22"), LocalTime.parse("14:30"), reqs1, delivery1);
+ var record1 =
+ new MedDispatchRecRequest(
+ 123,
+ LocalDate.parse("2025-12-22"),
+ LocalTime.parse("14:30"),
+ reqs1,
+ delivery1);
var reqs2 = new MedDispatchRecRequest.MedRequirement(0.75f, false, true, 13.5f);
var delivery2 = new LngLat(-3.00, 55.121);
- var record2 = new MedDispatchRecRequest(456, LocalDate.parse("2025-12-25"), LocalTime.parse("11:30"), reqs2, delivery2);
+ var record2 =
+ new MedDispatchRecRequest(
+ 456,
+ LocalDate.parse("2025-12-25"),
+ LocalTime.parse("11:30"),
+ reqs2,
+ delivery2);
MedDispatchRecRequest[] requestBody = {record1, record2};
List expected = List.of("2", "7", "9");
@@ -382,11 +401,12 @@ public class DroneControllerTest {
when(droneInfoService.dronesMatchesRequirements(any(MedDispatchRecRequest[].class)))
.thenReturn(expected);
- mockMvc.perform(post(API_ENDPOINT)
- .contentType(MediaType.APPLICATION_JSON)
- .content(objectMapper.writeValueAsString(requestBody)))
+ mockMvc.perform(
+ post(API_ENDPOINT)
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(objectMapper.writeValueAsString(requestBody)))
.andExpect(status().isOk())
.andExpect(content().json(objectMapper.writeValueAsString(expected)));
}
}
-}
\ No newline at end of file
+}
diff --git a/src/test/java/io/github/js0ny/ilp_coursework/service/DroneInfoServiceTest.java b/src/test/java/io/github/js0ny/ilp_coursework/service/DroneInfoServiceTest.java
index 16a39cd..6595e66 100644
--- a/src/test/java/io/github/js0ny/ilp_coursework/service/DroneInfoServiceTest.java
+++ b/src/test/java/io/github/js0ny/ilp_coursework/service/DroneInfoServiceTest.java
@@ -11,11 +11,7 @@ import io.github.js0ny.ilp_coursework.data.common.TimeWindow;
import io.github.js0ny.ilp_coursework.data.external.Drone;
import io.github.js0ny.ilp_coursework.data.external.ServicePointDrones;
import io.github.js0ny.ilp_coursework.data.request.MedDispatchRecRequest;
-import java.net.URI;
-import java.time.DayOfWeek;
-import java.time.LocalDate;
-import java.time.LocalTime;
-import java.util.List;
+
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
@@ -26,11 +22,16 @@ import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.web.client.RestTemplate;
+import java.net.URI;
+import java.time.DayOfWeek;
+import java.time.LocalDate;
+import java.time.LocalTime;
+import java.util.List;
+
@ExtendWith(MockitoExtension.class)
public class DroneInfoServiceTest {
- @Mock
- private RestTemplate restTemplate;
+ @Mock private RestTemplate restTemplate;
private DroneInfoService droneInfoService;
@@ -44,9 +45,9 @@ public class DroneInfoServiceTest {
private Drone[] getMockDrones() {
return new Drone[] {
- new Drone("Drone 1", "1", new DroneCapability(true, true, 10, 1000, 1, 1, 1)),
- new Drone("Drone 2", "2", new DroneCapability(false, true, 20, 2000, 2, 2, 2)),
- new Drone("Drone 3", "3", new DroneCapability(false, false, 30, 3000, 3, 3, 3))
+ new Drone("Drone 1", "1", new DroneCapability(true, true, 10, 1000, 1, 1, 1)),
+ new Drone("Drone 2", "2", new DroneCapability(false, true, 20, 2000, 2, 2, 2)),
+ new Drone("Drone 3", "3", new DroneCapability(false, false, 30, 3000, 3, 3, 3))
};
}
@@ -134,30 +135,46 @@ public class DroneInfoServiceTest {
@DisplayName("dronesMatchesRequirements(MedDispatchRecRequest[]) tests")
class DronesMatchesRequirementsTests {
private ServicePointDrones[] getMockServicePointDrones() {
- TimeWindow[] timeWindows = { new TimeWindow(DayOfWeek.MONDAY, LocalTime.of(9, 0), LocalTime.of(17, 0)) };
+ TimeWindow[] timeWindows = {
+ new TimeWindow(DayOfWeek.MONDAY, LocalTime.of(9, 0), LocalTime.of(17, 0))
+ };
DroneAvailability drone1Avail = new DroneAvailability("1", timeWindows);
- ServicePointDrones spd = new ServicePointDrones(1, new DroneAvailability[] { drone1Avail });
- return new ServicePointDrones[] { spd };
+ ServicePointDrones spd =
+ new ServicePointDrones(1, new DroneAvailability[] {drone1Avail});
+ return new ServicePointDrones[] {spd};
}
@Test
@DisplayName("Should return drones matching a single requirement")
void dronesMatchesRequirements_shouldReturnMatchingDrones_forSingleRequirement() {
// Arrange
- var drones = new Drone[] {
- new Drone("Drone 1", "1", new DroneCapability(true, true, 10, 1000, 1, 1, 1)),
- new Drone("Drone 2", "2", new DroneCapability(false, true, 5, 2000, 2, 2, 2))
- };
- when(restTemplate.getForObject(URI.create(baseUrl + "drones"), Drone[].class)).thenReturn(drones);
- when(restTemplate.getForObject(URI.create(baseUrl + "drones-for-service-points"),
- ServicePointDrones[].class)).thenReturn(getMockServicePointDrones());
+ var drones =
+ new Drone[] {
+ new Drone(
+ "Drone 1", "1", new DroneCapability(true, true, 10, 1000, 1, 1, 1)),
+ new Drone(
+ "Drone 2", "2", new DroneCapability(false, true, 5, 2000, 2, 2, 2))
+ };
+ when(restTemplate.getForObject(URI.create(baseUrl + "drones"), Drone[].class))
+ .thenReturn(drones);
+ when(restTemplate.getForObject(
+ URI.create(baseUrl + "drones-for-service-points"),
+ ServicePointDrones[].class))
+ .thenReturn(getMockServicePointDrones());
var requirement = new MedDispatchRecRequest.MedRequirement(8, true, false, 100);
- var record = new MedDispatchRecRequest(1, LocalDate.now().with(DayOfWeek.MONDAY), LocalTime.of(10, 0),
- requirement, new LngLat(0, 0));
+ var record =
+ new MedDispatchRecRequest(
+ 1,
+ LocalDate.now().with(DayOfWeek.MONDAY),
+ LocalTime.of(10, 0),
+ requirement,
+ new LngLat(0, 0));
// Act
- List result = droneInfoService.dronesMatchesRequirements(new MedDispatchRecRequest[] { record });
+ List result =
+ droneInfoService.dronesMatchesRequirements(
+ new MedDispatchRecRequest[] {record});
// Assert
assertThat(result).containsExactly("1");
@@ -167,19 +184,30 @@ public class DroneInfoServiceTest {
@DisplayName("Should return empty list if no drones match")
void dronesMatchesRequirements_shouldReturnEmptyList_whenNoDronesMatch() {
// Arrange
- var drones = new Drone[] {
- new Drone("Drone 1", "1", new DroneCapability(true, true, 5, 1000, 1, 1, 1)),
- new Drone("Drone 2", "2", new DroneCapability(false, true, 5, 2000, 2, 2, 2))
- };
- when(restTemplate.getForObject(URI.create(baseUrl + "drones"), Drone[].class)).thenReturn(drones);
+ var drones =
+ new Drone[] {
+ new Drone(
+ "Drone 1", "1", new DroneCapability(true, true, 5, 1000, 1, 1, 1)),
+ new Drone(
+ "Drone 2", "2", new DroneCapability(false, true, 5, 2000, 2, 2, 2))
+ };
+ when(restTemplate.getForObject(URI.create(baseUrl + "drones"), Drone[].class))
+ .thenReturn(drones);
// No need to mock drones-for-service-points as it won't be called
var requirement = new MedDispatchRecRequest.MedRequirement(10, true, false, 100);
- var record = new MedDispatchRecRequest(1, LocalDate.now().with(DayOfWeek.MONDAY), LocalTime.of(10, 0),
- requirement, new LngLat(0, 0));
+ var record =
+ new MedDispatchRecRequest(
+ 1,
+ LocalDate.now().with(DayOfWeek.MONDAY),
+ LocalTime.of(10, 0),
+ requirement,
+ new LngLat(0, 0));
// Act
- List result = droneInfoService.dronesMatchesRequirements(new MedDispatchRecRequest[] { record });
+ List result =
+ droneInfoService.dronesMatchesRequirements(
+ new MedDispatchRecRequest[] {record});
// Assert
assertThat(result).isEmpty();
@@ -194,7 +222,8 @@ public class DroneInfoServiceTest {
// Act
List resultNull = droneInfoService.dronesMatchesRequirements(null);
- List resultEmpty = droneInfoService.dronesMatchesRequirements(new MedDispatchRecRequest[0]);
+ List resultEmpty =
+ droneInfoService.dronesMatchesRequirements(new MedDispatchRecRequest[0]);
// Assert
assertThat(resultNull).containsExactly("1", "2", "3");
diff --git a/src/test/java/io/github/js0ny/ilp_coursework/service/GpsCalculationServiceTest.java b/src/test/java/io/github/js0ny/ilp_coursework/service/GpsCalculationServiceTest.java
index fe9ce8c..2bbe52f 100644
--- a/src/test/java/io/github/js0ny/ilp_coursework/service/GpsCalculationServiceTest.java
+++ b/src/test/java/io/github/js0ny/ilp_coursework/service/GpsCalculationServiceTest.java
@@ -7,12 +7,14 @@ import static org.assertj.core.api.AssertionsForClassTypes.within;
import io.github.js0ny.ilp_coursework.data.common.Angle;
import io.github.js0ny.ilp_coursework.data.common.LngLat;
import io.github.js0ny.ilp_coursework.data.common.Region;
-import java.util.List;
+
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
+import java.util.List;
+
public class GpsCalculationServiceTest {
private static final double STEP = 0.00015;
@@ -114,9 +116,7 @@ public class GpsCalculationServiceTest {
}
@Test
- @DisplayName(
- "True: Two points are close to each other and near threshold"
- )
+ @DisplayName("True: Two points are close to each other and near threshold")
void isCloseTo_shouldReturnTrue_whenCloseAndSmallerThanThreshold() {
var p1 = new LngLat(0.0, 0.0);
var p2 = new LngLat(0.0, 0.00014);
@@ -160,20 +160,12 @@ public class GpsCalculationServiceTest {
var actual = service.nextPosition(start, angle);
- assertThat(actual.lng()).isCloseTo(
- expected.lng(),
- within(PRECISION)
- );
- assertThat(actual.lat()).isCloseTo(
- expected.lat(),
- within(PRECISION)
- );
+ assertThat(actual.lng()).isCloseTo(expected.lng(), within(PRECISION));
+ assertThat(actual.lat()).isCloseTo(expected.lat(), within(PRECISION));
}
@Test
- @DisplayName(
- "Cardinal Direction: nextPosition in North direction (90 degrees)"
- )
+ @DisplayName("Cardinal Direction: nextPosition in North direction (90 degrees)")
void nextPosition_shouldMoveNorth_forAngle90() {
var start = new LngLat(0.0, 0.0);
Angle angle = new Angle(90);
@@ -182,20 +174,12 @@ public class GpsCalculationServiceTest {
var actual = service.nextPosition(start, angle);
- assertThat(actual.lng()).isCloseTo(
- expected.lng(),
- within(PRECISION)
- );
- assertThat(actual.lat()).isCloseTo(
- expected.lat(),
- within(PRECISION)
- );
+ assertThat(actual.lng()).isCloseTo(expected.lng(), within(PRECISION));
+ assertThat(actual.lat()).isCloseTo(expected.lat(), within(PRECISION));
}
@Test
- @DisplayName(
- "Cardinal Direction: nextPosition in West direction (180 degrees)"
- )
+ @DisplayName("Cardinal Direction: nextPosition in West direction (180 degrees)")
void nextPosition_shouldMoveWest_forAngle180() {
var start = new LngLat(0.0, 0.0);
Angle angle = new Angle(180);
@@ -205,20 +189,12 @@ public class GpsCalculationServiceTest {
var actual = service.nextPosition(start, angle);
- assertThat(actual.lng()).isCloseTo(
- expected.lng(),
- within(PRECISION)
- );
- assertThat(actual.lat()).isCloseTo(
- expected.lat(),
- within(PRECISION)
- );
+ assertThat(actual.lng()).isCloseTo(expected.lng(), within(PRECISION));
+ assertThat(actual.lat()).isCloseTo(expected.lat(), within(PRECISION));
}
@Test
- @DisplayName(
- "Cardinal Direction: nextPosition in South direction (270 degrees)"
- )
+ @DisplayName("Cardinal Direction: nextPosition in South direction (270 degrees)")
void nextPosition_shouldMoveSouth_forAngle270() {
var start = new LngLat(0.0, 0.0);
Angle angle = new Angle(270);
@@ -228,20 +204,12 @@ public class GpsCalculationServiceTest {
var actual = service.nextPosition(start, angle);
- assertThat(actual.lng()).isCloseTo(
- expected.lng(),
- within(PRECISION)
- );
- assertThat(actual.lat()).isCloseTo(
- expected.lat(),
- within(PRECISION)
- );
+ assertThat(actual.lng()).isCloseTo(expected.lng(), within(PRECISION));
+ assertThat(actual.lat()).isCloseTo(expected.lat(), within(PRECISION));
}
@Test
- @DisplayName(
- "Intercardinal Direction: nextPosition in Northeast direction (45 degrees)"
- )
+ @DisplayName("Intercardinal Direction: nextPosition in Northeast direction (45 degrees)")
void nextPosition_shouldMoveNortheast_forAngle45() {
var start = new LngLat(0.0, 0.0);
Angle angle = new Angle(45);
@@ -252,47 +220,37 @@ public class GpsCalculationServiceTest {
var actual = service.nextPosition(start, angle);
- assertThat(actual.lng()).isCloseTo(
- expected.lng(),
- within(PRECISION)
- );
- assertThat(actual.lat()).isCloseTo(
- expected.lat(),
- within(PRECISION)
- );
+ assertThat(actual.lng()).isCloseTo(expected.lng(), within(PRECISION));
+ assertThat(actual.lat()).isCloseTo(expected.lat(), within(PRECISION));
}
@Nested
- @DisplayName(
- "Test for checkIsInRegion(LngLatDto, RegionDto) -> boolean"
- )
+ @DisplayName("Test for checkIsInRegion(LngLatDto, RegionDto) -> boolean")
class CheckIsInRegionTests {
- public static final Region RECTANGLE_REGION = new Region(
- "rectangle",
- List.of(
- new LngLat(0.0, 0.0),
- new LngLat(2.0, 0.0),
- new LngLat(2.0, 2.0),
- new LngLat(0.0, 2.0),
- new LngLat(0.0, 0.0)
- )
- );
+ public static final Region RECTANGLE_REGION =
+ new Region(
+ "rectangle",
+ List.of(
+ new LngLat(0.0, 0.0),
+ new LngLat(2.0, 0.0),
+ new LngLat(2.0, 2.0),
+ new LngLat(0.0, 2.0),
+ new LngLat(0.0, 0.0)));
@Test
@DisplayName("General Case: Given Example for Testing")
void isInRegion_shouldReturnFalse_givenPolygonCentral() {
var position = new LngLat(1.234, 1.222);
- var region = new Region(
- "central",
- List.of(
- new LngLat(-3.192473, 55.946233),
- new LngLat(-3.192473, 55.942617),
- new LngLat(-3.184319, 55.942617),
- new LngLat(-3.184319, 55.946233),
- new LngLat(-3.192473, 55.946233)
- )
- );
+ var region =
+ new Region(
+ "central",
+ List.of(
+ new LngLat(-3.192473, 55.946233),
+ new LngLat(-3.192473, 55.942617),
+ new LngLat(-3.184319, 55.942617),
+ new LngLat(-3.184319, 55.946233),
+ new LngLat(-3.192473, 55.946233)));
boolean expected = false;
boolean actual = service.checkIsInRegion(position, region);
assertThat(actual).isEqualTo(expected);
@@ -303,10 +261,7 @@ public class GpsCalculationServiceTest {
void isInRegion_shouldReturnTrue_forSimpleRectangle() {
var position = new LngLat(1.0, 1.0);
boolean expected = true;
- boolean actual = service.checkIsInRegion(
- position,
- RECTANGLE_REGION
- );
+ boolean actual = service.checkIsInRegion(position, RECTANGLE_REGION);
assertThat(actual).isEqualTo(expected);
}
@@ -315,10 +270,7 @@ public class GpsCalculationServiceTest {
void isInRegion_shouldReturnFalse_forSimpleRectangle() {
var position = new LngLat(3.0, 1.0);
boolean expected = false;
- boolean actual = service.checkIsInRegion(
- position,
- RECTANGLE_REGION
- );
+ boolean actual = service.checkIsInRegion(position, RECTANGLE_REGION);
assertThat(actual).isEqualTo(expected);
}
@@ -326,18 +278,17 @@ public class GpsCalculationServiceTest {
@DisplayName("General Case: Simple Hexagon")
void isInRegion_shouldReturnTrue_forSimpleHexagon() {
var position = new LngLat(2.0, 2.0);
- var region = new Region(
- "hexagon",
- List.of(
- new LngLat(1.0, 0.0),
- new LngLat(4.0, 0.0),
- new LngLat(5.0, 2.0),
- new LngLat(4.0, 4.0),
- new LngLat(1.0, 4.0),
- new LngLat(0.0, 2.0),
- new LngLat(1.0, 0.0)
- )
- );
+ var region =
+ new Region(
+ "hexagon",
+ List.of(
+ new LngLat(1.0, 0.0),
+ new LngLat(4.0, 0.0),
+ new LngLat(5.0, 2.0),
+ new LngLat(4.0, 4.0),
+ new LngLat(1.0, 4.0),
+ new LngLat(0.0, 2.0),
+ new LngLat(1.0, 0.0)));
boolean expected = true;
boolean actual = service.checkIsInRegion(position, region);
assertThat(actual).isEqualTo(expected);
@@ -347,15 +298,14 @@ public class GpsCalculationServiceTest {
@DisplayName("Edge Case: Small Triangle")
void isInRegion_shouldReturnTrue_forSmallTriangle() {
var position = new LngLat(0.00001, 0.00001);
- var region = new Region(
- "triangle",
- List.of(
- new LngLat(0.0, 0.0),
- new LngLat(0.0001, 0.0),
- new LngLat(0.00005, 0.0001),
- new LngLat(0.0, 0.0)
- )
- );
+ var region =
+ new Region(
+ "triangle",
+ List.of(
+ new LngLat(0.0, 0.0),
+ new LngLat(0.0001, 0.0),
+ new LngLat(0.00005, 0.0001),
+ new LngLat(0.0, 0.0)));
boolean expected = true;
boolean actual = service.checkIsInRegion(position, region);
assertThat(actual).isEqualTo(expected);
@@ -366,10 +316,7 @@ public class GpsCalculationServiceTest {
void isInRegion_shouldReturnTrue_whenPointOnLowerEdge() {
var position = new LngLat(0.0, 1.0);
boolean expected = true;
- boolean actual = service.checkIsInRegion(
- position,
- RECTANGLE_REGION
- );
+ boolean actual = service.checkIsInRegion(position, RECTANGLE_REGION);
assertThat(actual).isEqualTo(expected);
}
@@ -378,10 +325,7 @@ public class GpsCalculationServiceTest {
void isInRegion_shouldReturnTrue_whenPointOnUpperEdge() {
var position = new LngLat(2.0, 1.0);
boolean expected = true;
- boolean actual = service.checkIsInRegion(
- position,
- RECTANGLE_REGION
- );
+ boolean actual = service.checkIsInRegion(position, RECTANGLE_REGION);
assertThat(actual).isEqualTo(expected);
}
@@ -390,10 +334,7 @@ public class GpsCalculationServiceTest {
void isInRegion_shouldReturnTrue_whenPointOnLeftEdge() {
var position = new LngLat(0.0, 1.0);
boolean expected = true;
- boolean actual = service.checkIsInRegion(
- position,
- RECTANGLE_REGION
- );
+ boolean actual = service.checkIsInRegion(position, RECTANGLE_REGION);
assertThat(actual).isEqualTo(expected);
}
@@ -402,10 +343,7 @@ public class GpsCalculationServiceTest {
void isInRegion_shouldReturnTrue_whenPointOnLowerVertex() {
var position = new LngLat(0.0, 0.0);
boolean expected = true;
- boolean actual = service.checkIsInRegion(
- position,
- RECTANGLE_REGION
- );
+ boolean actual = service.checkIsInRegion(position, RECTANGLE_REGION);
assertThat(actual).isEqualTo(expected);
}
@@ -414,10 +352,7 @@ public class GpsCalculationServiceTest {
void isInRegion_shouldReturnTrue_whenPointOnUpperVertex() {
var position = new LngLat(2.0, 2.0);
boolean expected = true;
- boolean actual = service.checkIsInRegion(
- position,
- RECTANGLE_REGION
- );
+ boolean actual = service.checkIsInRegion(position, RECTANGLE_REGION);
assertThat(actual).isEqualTo(expected);
}
@@ -425,40 +360,40 @@ public class GpsCalculationServiceTest {
@DisplayName("Edge Case: Region not forming polygon")
void isInRegion_shouldThrowExceptions_whenRegionNotFormingPolygon() {
var position = new LngLat(2.0, 2.0);
- var region = new Region(
- "line",
- List.of(
- new LngLat(0.0, 0.0),
- new LngLat(0.0001, 0.0),
- new LngLat(0.0, 0.0)
- )
- );
- assertThatThrownBy(() -> {
- service.checkIsInRegion(position, region);
- })
- .isInstanceOf(IllegalArgumentException.class)
- .hasMessage("Region is not closed.");
+ var region =
+ new Region(
+ "line",
+ List.of(
+ new LngLat(0.0, 0.0),
+ new LngLat(0.0001, 0.0),
+ new LngLat(0.0, 0.0)));
+ assertThatThrownBy(
+ () -> {
+ service.checkIsInRegion(position, region);
+ })
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("Region is not closed.");
}
@Test
@DisplayName("Edge Case: Region is not closed")
void isInRegion_shouldThrowExceptions_whenRegionNotClose() {
var position = new LngLat(2.0, 2.0);
- var region = new Region(
- "rectangle",
- List.of(
- new LngLat(0.0, 0.0),
- new LngLat(2.0, 0.0),
- new LngLat(2.0, 2.0),
- new LngLat(0.0, 2.0),
- new LngLat(0.0, -1.0)
- )
- );
- assertThatThrownBy(() -> {
- service.checkIsInRegion(position, region);
- })
- .isInstanceOf(IllegalArgumentException.class)
- .hasMessage("Region is not closed.");
+ var region =
+ new Region(
+ "rectangle",
+ List.of(
+ new LngLat(0.0, 0.0),
+ new LngLat(2.0, 0.0),
+ new LngLat(2.0, 2.0),
+ new LngLat(0.0, 2.0),
+ new LngLat(0.0, -1.0)));
+ assertThatThrownBy(
+ () -> {
+ service.checkIsInRegion(position, region);
+ })
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("Region is not closed.");
}
@Test
@@ -466,11 +401,12 @@ public class GpsCalculationServiceTest {
void isInRegion_shouldThrowExceptions_whenListIsEmpty() {
var position = new LngLat(2.0, 2.0);
var region = new Region("rectangle", List.of());
- assertThatThrownBy(() -> {
- service.checkIsInRegion(position, region);
- })
- .isInstanceOf(IllegalArgumentException.class)
- .hasMessage("Region is not closed.");
+ assertThatThrownBy(
+ () -> {
+ service.checkIsInRegion(position, region);
+ })
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("Region is not closed.");
}
}
}
diff --git a/src/test/java/io/github/js0ny/ilp_coursework/service/PathFinderServiceTest.java b/src/test/java/io/github/js0ny/ilp_coursework/service/PathFinderServiceTest.java
index 623ff5d..3bf6268 100644
--- a/src/test/java/io/github/js0ny/ilp_coursework/service/PathFinderServiceTest.java
+++ b/src/test/java/io/github/js0ny/ilp_coursework/service/PathFinderServiceTest.java
@@ -6,6 +6,7 @@ import static org.mockito.Mockito.when;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
+
import io.github.js0ny.ilp_coursework.data.common.DroneAvailability;
import io.github.js0ny.ilp_coursework.data.common.DroneCapability;
import io.github.js0ny.ilp_coursework.data.common.LngLat;
@@ -18,17 +19,19 @@ import io.github.js0ny.ilp_coursework.data.external.ServicePointDrones;
import io.github.js0ny.ilp_coursework.data.request.MedDispatchRecRequest;
import io.github.js0ny.ilp_coursework.data.request.MedDispatchRecRequest.MedRequirement;
import io.github.js0ny.ilp_coursework.data.response.DeliveryPathResponse;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
import java.io.IOException;
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.LocalTime;
import java.util.Collections;
import java.util.List;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.extension.ExtendWith;
-import org.mockito.Mock;
-import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
class PathFinderServiceTest {
@@ -37,8 +40,7 @@ class PathFinderServiceTest {
private static final LngLat SERVICE_POINT_COORD = new LngLat(0.0, 0.0);
private final ObjectMapper mapper = new ObjectMapper();
- @Mock
- private DroneInfoService droneInfoService;
+ @Mock private DroneInfoService droneInfoService;
private PathFinderService pathFinderService;
@@ -46,66 +48,38 @@ class PathFinderServiceTest {
void setUpPathFinder() {
GpsCalculationService gpsCalculationService = new GpsCalculationService();
- DroneCapability capability = new DroneCapability(
- false,
- true,
- 5.0f,
- 10,
- 0.1f,
- 0.5f,
- 0.5f
- );
+ DroneCapability capability = new DroneCapability(false, true, 5.0f, 10, 0.1f, 0.5f, 0.5f);
Drone drone = new Drone("Test Drone", DRONE_ID, capability);
- ServicePoint servicePoint = new ServicePoint(
- "Test Point",
- 1,
- new LngLatAlt(
- SERVICE_POINT_COORD.lng(),
- SERVICE_POINT_COORD.lat(),
- 50.0
- )
- );
- DroneAvailability availability = new DroneAvailability(
- DRONE_ID,
- new TimeWindow[] {
- new TimeWindow(
- DayOfWeek.MONDAY,
- LocalTime.MIDNIGHT,
- LocalTime.MAX
- ),
- }
- );
- ServicePointDrones servicePointDrones = new ServicePointDrones(
- servicePoint.id(),
- new DroneAvailability[] { availability }
- );
+ ServicePoint servicePoint =
+ new ServicePoint(
+ "Test Point",
+ 1,
+ new LngLatAlt(SERVICE_POINT_COORD.lng(), SERVICE_POINT_COORD.lat(), 50.0));
+ DroneAvailability availability =
+ new DroneAvailability(
+ DRONE_ID,
+ new TimeWindow[] {
+ new TimeWindow(DayOfWeek.MONDAY, LocalTime.MIDNIGHT, LocalTime.MAX),
+ });
+ ServicePointDrones servicePointDrones =
+ new ServicePointDrones(servicePoint.id(), new DroneAvailability[] {availability});
when(droneInfoService.fetchAllDrones()).thenReturn(List.of(drone));
- when(droneInfoService.fetchServicePoints()).thenReturn(
- List.of(servicePoint)
- );
- when(droneInfoService.fetchDronesForServicePoints()).thenReturn(
- List.of(servicePointDrones)
- );
- when(droneInfoService.fetchRestrictedAreas()).thenReturn(
- Collections.emptyList()
- );
- when(droneInfoService.droneMatchesRequirement(any(), any())).thenReturn(
- true
- );
- pathFinderService = new PathFinderService(
- gpsCalculationService,
- droneInfoService
- );
+ when(droneInfoService.fetchServicePoints()).thenReturn(List.of(servicePoint));
+ when(droneInfoService.fetchDronesForServicePoints())
+ .thenReturn(List.of(servicePointDrones));
+ when(droneInfoService.fetchRestrictedAreas())
+ .thenReturn(Collections.emptyList());
+ when(droneInfoService.droneMatchesRequirement(any(), any())).thenReturn(true);
+ pathFinderService = new PathFinderService(gpsCalculationService, droneInfoService);
}
@Test
void calculateDeliveryPath_shouldStayWithinSingleTripBudget() {
MedDispatchRecRequest request = createSampleRequest();
- DeliveryPathResponse response = pathFinderService.calculateDeliveryPath(
- new MedDispatchRecRequest[] { request }
- );
+ DeliveryPathResponse response =
+ pathFinderService.calculateDeliveryPath(new MedDispatchRecRequest[] {request});
assertThat(response.totalMoves()).isGreaterThan(0);
assertThat(response.totalMoves()).isLessThanOrEqualTo(10);
@@ -115,24 +89,18 @@ class PathFinderServiceTest {
assertThat(dronePath.deliveries()).hasSize(1);
var recordedPath = dronePath.deliveries().get(0).flightPath();
assertThat(recordedPath.get(0)).isEqualTo(SERVICE_POINT_COORD);
- assertThat(
- samePoint(
- recordedPath.get(recordedPath.size() - 1),
- SERVICE_POINT_COORD
- )
- )
- .isTrue();
+ assertThat(samePoint(recordedPath.get(recordedPath.size() - 1), SERVICE_POINT_COORD))
+ .isTrue();
assertThat(hasHoverAt(recordedPath, request.delivery())).isTrue();
}
@Test
- void calculateDeliveryPathAsGeoJson_shouldReturnFeatureCollection()
- throws IOException {
+ void calculateDeliveryPathAsGeoJson_shouldReturnFeatureCollection() throws IOException {
MedDispatchRecRequest request = createSampleRequest();
- String geoJson = pathFinderService.calculateDeliveryPathAsGeoJson(
- new MedDispatchRecRequest[] { request }
- );
+ String geoJson =
+ pathFinderService.calculateDeliveryPathAsGeoJson(
+ new MedDispatchRecRequest[] {request});
JsonNode root = mapper.readTree(geoJson);
assertThat(root.get("type").asText()).isEqualTo("FeatureCollection");
@@ -151,20 +119,16 @@ class PathFinderServiceTest {
double startLng = coordinates.get(0).get(0).asDouble();
double startLat = coordinates.get(0).get(1).asDouble();
- assertThat(
- samePoint(new LngLat(startLng, startLat), SERVICE_POINT_COORD)
- )
- .isTrue();
+ assertThat(samePoint(new LngLat(startLng, startLat), SERVICE_POINT_COORD)).isTrue();
}
private MedDispatchRecRequest createSampleRequest() {
return new MedDispatchRecRequest(
- 101,
- LocalDate.of(2025, 1, 6),
- LocalTime.of(12, 0),
- new MedRequirement(0.5f, false, true, 50.0f),
- new LngLat(SERVICE_POINT_COORD.lng() + 0.0003, SERVICE_POINT_COORD.lat())
- );
+ 101,
+ LocalDate.of(2025, 1, 6),
+ LocalTime.of(12, 0),
+ new MedRequirement(0.5f, false, true, 50.0f),
+ new LngLat(SERVICE_POINT_COORD.lng() + 0.0003, SERVICE_POINT_COORD.lat()));
}
private boolean hasHoverAt(List path, LngLat target) {
@@ -178,9 +142,6 @@ class PathFinderServiceTest {
private boolean samePoint(LngLat a, LngLat b) {
double threshold = 1e-9;
- return (
- Math.abs(a.lng() - b.lng()) < threshold &&
- Math.abs(a.lat() - b.lat()) < threshold
- );
+ return (Math.abs(a.lng() - b.lng()) < threshold && Math.abs(a.lat() - b.lat()) < threshold);
}
}